public final class SmartHome
- Object
- SmartHome
Entry point for the Codename One smart-home API – reading the accessories in a user’s home, reading and writing what they can do, watching them for change, running scenes, and adding new Matter accessories.
getInstance() never returns null. On a port with no smart-home support
every operation fails fast with HomeError.NOT_SUPPORTED and every graph
accessor returns an empty list, so calling code needs no platform-specific
if.
Quick start
SmartHome home = SmartHome.getInstance();
// refresh() connects, and connecting is what prompts. Until it has run,
// getAvailability() answers NOT_STARTED on iOS -- it cannot know what the
// user decided without asking the platform, and asking is the prompt.
home.refresh().onResult((structures, err) -> {
if (err != null) {
switch (home.getAvailability()) {
case PERMISSION_REQUIRED:
home.requestAuthorization();
break;
case PERMISSION_DENIED:
// Asked and refused. iOS never shows that prompt twice.
home.openHomeSettings();
break;
case NOT_CONFIGURED:
home.openEcosystemApp();
break;
default:
break;
}
return;
}
HomeStructure h = home.getPrimaryStructure();
for (Accessory a : h.getAccessoriesSupporting(Trait.ON_OFF)) {
addRow(a);
}
});
Turning a light on:
AccessoryService svc = lamp.getPrimaryService();
home.write(new TraitWrite(lamp, svc, Trait.ON_OFF, TraitValue.of(true)));
Three things that will surprise you
Android’s default answer is not “available”. With no extra setup an
Android app can commission a Matter accessory and do nothing else: the
graph is empty and no trait can be read or written. That is
HomeAvailability.COMMISSIONING_ONLY, and it exists because reporting
AVAILABLE would make the word mean something entirely different there
than it does on iOS. The full graph needs the Google Home APIs, which need
a Google Cloud project and a Home Developer Console registration only you
can create; getConfigurationProblems() names what is missing.
Nothing wakes your app for an accessory change. HomeKit delivers
changes only while your app is in the foreground, and the Google Home APIs
need a live signed-in client – the home hub, not your app, is what reacts
to a sensor while the phone sleeps. Check
TraitSubscription.isPushDelivery() rather than assuming; where it answers
false, changes arrive when you call drainChanges() and at no other
time.
A missing value is not zero and not an error. An accessory can
legitimately have nothing to report – an unmeasured temperature, a hue on
a light that is currently in white mode. Ask TraitReading.hasValue()
before TraitReading.getValue(); nothing in this API substitutes a zero
for a measurement that was never taken.
Threading
Every method here may be called from the EDT and returns immediately.
Every AsyncResource this class hands back resolves on the EDT, and every
HomeChangeListener and HomeStructureListener delivery arrives on it –
on every platform, including the desktop, simulator and JavaScript ports,
which marshal rather than answering on whichever thread happened to ask. A
callback may touch components directly.
Platform support
- iOS, iPadOS, watchOS, tvOS, macOS – HomeKit. Needs the
com.apple.developer.homekitentitlement and anios.NSHomeKitUsageDescriptionbuild hint; the build fails with an actionable message if the description is missing. Commissioning is iOS-only. - Android – Google Play services Matter commissioning, reported as
HomeAvailability.COMMISSIONING_ONLY: an accessory can be added to the user’s home, and there is no graph to read or control it through. The Google Home APIs that would provide one need a per-developer Cloud project and consent screen that cannot be shipped for you, and are not in this release. - Simulator, desktop, JavaScript – a local, app-private simulated
home, reported as
HomeAvailability.LOCAL_ONLY.
Not claimed in this release
Automations and triggers – scenes only, and isAutomationSupported()
answers false everywhere. Topology writes: creating homes, renaming
rooms, moving accessories. Cameras and video. Security and alarm panels.
Matter events, which is why LockState.JAMMED is unreachable outside
HomeKit. Energy, appliance and diagnostic clusters. And Codename One is not
a Matter controller – everything Matter goes through the OS ecosystem, so
the Apple Home or Google Home app has to be installed and set up.
Methods
Inherited methods
Method details
getInstance
public static synchronized SmartHome getInstance()The smart-home API for this device.
Never null. On a port with no support the returned instance answers
HomeAvailability.NOT_SUPPORTED and every operation fails fast, so
there is nothing to null-check and no platform branch to write.
Returns
nullisSupported
public boolean isSupported()Whether this device has any smart-home support at all.
A coarse question. getAvailability() is the one worth asking,
because “supported” covers a device whose user has never opened the
Home app and one whose every light is ready to switch.
Returns
true when a backend is presentgetAvailability
public HomeAvailability getAvailability()Whether a home graph is usable right now, and when it is not, why.
Check this before anything else, and branch through it rather than through platform detection.
Returns
nullgetBackend
public HomeBackend getBackend()Which platform service is behind this instance.
For explaining a limitation to a user, not for branching on – every
real capability question has its own query. See HomeBackend.
Returns
nullgetConfigurationProblems
public List<String> getConfigurationProblems()Build configuration this backend needs and does not have.
One sentence per problem, each naming the build hint that fixes it –
a missing ios.NSHomeKitUsageDescription, a missing
android.googleHome.projectId. Empty when nothing is missing.
This text is for you, not for your user. Nothing here can be fixed at runtime; it is a description of what the build left out. Log it, show it in a debug screen, and do not put it in front of someone holding a phone.
Returns
areIdsPersistent
public boolean areIdsPersistent()Whether accessory and structure identifiers survive an app restart.
Both shipping backends answer true, so a favourite can be persisted
by id. Ask before doing so anyway – a local or test backend that
regenerates its graph does not, and a favourites list that silently
empties on every launch is a confusing bug to trace.
Returns
true when identifiers are stable across launchesisAutomationSupported
public boolean isAutomationSupported()Whether this release can create or run automations – a scene plus a trigger.
Always false. HomeKit, Google Home and Matter model triggers in
three incompatible ways and Matter has none at all, so there is no
honest common shape to expose. Scenes work everywhere; see
executeScene(Scene).
The method exists rather than the capability being absent silently, so an app can say why the feature it wanted is not offered.
Returns
falsegetCommissioner
public Commissioner getCommissioner()Adding new Matter accessories.
Never null; ask Commissioner.isSupported() before offering an “add
a device” button.
Returns
nullgetAuthorizationStatus
public HomeAuthorizationStatus getAuthorizationStatus()Returns
nullrequestAuthorization
public AsyncResource<HomeAuthorizationStatus> requestAuthorization()Prompts the user for access to their home.
Resolves when the platform’s flow finishes, whatever the user chose – so a resolved result means they were asked, not that they agreed. Read the status it carries.
Returns
openHomeSettings
public boolean openHomeSettings()Opens the system settings page where the user can change this app’s smart-home access.
The only recovery from HomeAuthorizationStatus.DENIED: once the user
has said no, the platform will not ask again from inside the app.
Returns
true when something was openedopenEcosystemApp
public boolean openEcosystemApp()Opens the platform’s ecosystem app – Apple Home, Google Home.
The right answer to HomeAvailability.NOT_CONFIGURED: a user with no
homes has to create one somewhere, and it is not here.
Returns
true when the app was opened; false when it is not installedopenProviderSetup
public boolean openProviderSetup()Opens wherever the user installs or updates the backend’s provider.
The recovery from HomeAvailability.PROVIDER_NOT_INSTALLED and
HomeAvailability.PROVIDER_UPDATE_REQUIRED.
Returns
true when something was openedrefresh
public AsyncResource<List<HomeStructure>> refresh()Loads the home graph from the platform.
Call this once before reading getStructures(), and again whenever a
HomeStructureListener says the topology moved. It connects to the
backend on first use, so it is also what triggers a permission prompt
on a platform that defers one.
Returns
getStructures
public List<HomeStructure> getStructures()The homes, as of the last refresh().
Empty until a refresh has completed – this reads a cached snapshot and
never calls into the platform, so it cannot block and cannot fail. See
Accessory for why the graph is snapshots rather than live handles.
Returns
getPrimaryStructure
public HomeStructure getPrimaryStructure()The user’s default home, as of the last refresh().
The one their ecosystem app opens. An app that only ever wants one home
should use this rather than the first entry of getStructures(),
which is in whatever order the platform gave them.
Returns
null when there are nonefindAccessory
public Accessory findAccessory(String accessoryId)Parameters
accessoryIdString- the identifier to look up, or
null
Returns
null when no home holds itidentify
public AsyncResource<Object> identify(Accessory accessory)Asks an accessory to make itself known – blink, beep, whatever it does.
Best-effort: mandatory in Matter and present on HomeKit, but not
reliably surfaced by the Google Home APIs, where it fails with
HomeError.TRAIT_NOT_SUPPORTED rather than doing nothing quietly.
Parameters
accessoryAccessory- the accessory to identify
Returns
Throws
IllegalArgumentException- when
accessoryisnull
getMaxReadBatchSize
public int getMaxReadBatchSize()The largest number of traits this backend reads in one call, or zero for no limit.
Informational: read(TraitReadRequest) splits a larger request and
recombines the answers, so there is no size a caller has to stay under.
Returns
read
public AsyncResource<List<TraitReading>> read(TraitReadRequest request)Reads trait values.
One TraitReading per requested trait, in the order they were added.
A partial success is the normal case: three values and one
unreachable accessory resolve successfully, with the failure carried on
that one reading. The resource itself fails only when the request never
reached the platform.
Parameters
requestTraitReadRequest- what to read
Returns
Throws
IllegalArgumentException- when
requestisnull
read
public AsyncResource<TraitReading> read(Accessory accessory, AccessoryService service, Trait trait)Reads one trait, for the common case where a request builder would be noise.
Prefer read(TraitReadRequest) when reading more than one: the cost
of a read is the round trip to the platform, so four separate calls are
four of them.
Parameters
accessoryAccessory- the accessory to read
serviceAccessoryService- the service on it
traitTrait- the trait to read
Returns
Throws
IllegalArgumentException- when any argument is
null
getMaxWriteBatchSize
public int getMaxWriteBatchSize()Returns
write
public AsyncResource<List<TraitWriteResult>> write(List<TraitWrite> writes)Writes trait values.
One TraitWriteResult per write, in order. A partial success is the
normal case – “turn off every light” against a home with a dead bulb
mostly worked, and failing the whole operation would have the caller
retry and flicker the house. The resource itself fails only when the
request never reached the platform.
Parameters
writesList<TraitWrite>- what to change
Returns
Throws
IllegalArgumentException- when
writesisnullor holds anull
write
public AsyncResource<TraitWriteResult> write(TraitWrite write)Parameters
writeTraitWrite- what to change
Returns
Throws
IllegalArgumentException- when
writeisnull
subscribe
public TraitSubscription subscribe(SubscriptionRequest request, HomeChangeListener listener)Watches traits for change.
The handle comes back immediately; the platform registration happens
behind it. Hold on to the handle and TraitSubscription.stop() it
– a dropped subscription keeps its listener reachable and keeps the
platform delivering.
Check TraitSubscription.isPushDelivery() on the result. Where it
answers false, which is everywhere except HomeKit in the foreground,
this listener will not fire until you call drainChanges().
Parameters
requestSubscriptionRequest- what to watch
listenerHomeChangeListener- where changes are delivered, on the EDT
Returns
nullThrows
IllegalArgumentException- when either argument is
null, or the request is empty
drainChanges
public AsyncResource<Integer> drainChanges()Collects changes the platform has been holding and delivers them to the subscriptions that asked for them.
The only way changes arrive on every backend except HomeKit in the
foreground; see TraitSubscription.isPushDelivery(). Wire this into
the point where your app comes to the foreground, and into whatever
polling cadence suits what you are showing.
Returns
addStructureListener
public void addStructureListener(HomeStructureListener listener)Watches the home graph for topology changes.
Deliveries arrive on the EDT. This is about accessories appearing, disappearing, being renamed, moving room or changing reachability – not about their values, which is what #subscribe(SubscriptionRequest, HomeChangeListener) is for.
Parameters
listenerHomeStructureListener- the listener to add;
nullis ignored
removeStructureListener
public void removeStructureListener(HomeStructureListener listener)Parameters
listenerHomeStructureListener- the listener to remove;
nulland unknown listeners are ignored
executeScene
public AsyncResource<Scene> executeScene(Scene scene)Parameters
sceneScene- the scene to run
Returns
Throws
IllegalArgumentException- when
sceneisnull
createScene
public AsyncResource<Scene> createScene(HomeStructure structure, String name, List<SceneAction> actions)Creates a scene from a set of accessory states.
Check HomeStructure.isSceneAuthoringSupported() before offering this;
several backends will run a scene and not author one.
Parameters
structureHomeStructure- the home to create it in
nameString- the scene’s name
actionsList<SceneAction>- what it should do;
TraitWrite.toSceneAction()turns the changes a user just made into these
Returns
Throws
IllegalArgumentException- when any argument is
null, when the name is empty, or when there are no actions
deleteScene
public AsyncResource<Scene> deleteScene(Scene scene)Parameters
sceneScene- the scene to delete
Returns
Throws
IllegalArgumentException- when
sceneisnull
deliverStarted
public static void deliverStarted(int requestId, int availabilityOrdinal, String error)refresh()’s first call. Called by the ports from any
thread.Parameters
requestIdint- the request being answered
availabilityOrdinalint- the availability now that the backend is connected
errorString- the encoded failure, or
nullfor success
deliverRefreshed
public static void deliverRefreshed(int requestId, String error)refresh(). Called by the ports from any thread.Parameters
requestIdint- the request being answered
errorString- the encoded failure, or
nullfor success
deliverAuthorization
public static void deliverAuthorization(int requestId, int statusOrdinal, String error)requestAuthorization(). Called by the ports from any
thread.Parameters
requestIdint- the request being answered
statusOrdinalint- the resulting
HomeAuthorizationStatusordinal errorString- the encoded failure, or
nullfor success
deliverReadings
public static void deliverReadings(int requestId, String[] lines, String error)read(TraitReadRequest). Called by the ports from any
thread.Parameters
requestIdint- the request being answered
linesString[]- one encoded reading per requested trait
errorString- the encoded failure, or
nullfor success
deliverWriteResults
public static void deliverWriteResults(int requestId, String[] lines, String error)write(java.util.List). Called by the ports from any thread.Parameters
requestIdint- the request being answered
linesString[]- one outcome per write, as
accessoryId \t serviceId \t traitId \t applied \t errorName \t errorMessage errorString- the encoded failure, or
nullfor success
deliverSceneResult
public static void deliverSceneResult(int requestId, String sceneLine, String structureId, String error)executeScene(Scene), #createScene(HomeStructure,
java.lang.String, java.util.List) and deleteScene(Scene). Called by
the ports from any thread.Parameters
requestIdint- the request being answered
sceneLineString- the affected scene, encoded as in
HomeBridge#getScenes(java.lang.String), ornull structureIdString- the home the scene belongs to
errorString- the encoded failure, or
nullfor success
deliverCommissioningResult
public static void deliverCommissioningResult(int requestId, String accessoryId, String accessoryName, String structureId, int commissionedToThisApp, String error)Commissioner.commission(CommissioningRequest). Called by the
ports from any thread.Parameters
requestIdint- the request being answered
accessoryIdString- the new accessory, or
null accessoryNameString- the name it ended up with, or
null structureIdString- the home it joined, or
null commissionedToThisAppint1when this app can address iterrorString- the encoded failure, or
nullfor success
deliverIdentifyResult
public static void deliverIdentifyResult(int requestId, String error)identify(Accessory). Called by the ports from any thread.Parameters
requestIdint- the request being answered
errorString- the encoded failure, or
nullfor success
deliverDrained
public static void deliverDrained(int requestId, int deliveredCount, String error)drainChanges(). Called by the ports from any thread, after
every [#deliverChanges(java.lang.String, java.lang.String[])] the drain
produced.Parameters
requestIdint- the request being answered
deliveredCountint- how many readings were handed over
errorString- the encoded failure, or
nullfor success
deliverChanges
public static void deliverChanges(String subscriptionId, String[] lines)Parameters
subscriptionIdString- which subscription these belong to
linesString[]- the encoded readings
deliverResyncRequired
public static void deliverResyncRequired(String subscriptionId)Parameters
subscriptionIdString- which subscription lost its stream
notifyStructureChanged
public static void notifyStructureChanged(int changeKindOrdinal, String structureId, String accessoryId)Parameters
changeKindOrdinalint- the
StructureChangeKindordinal structureIdString- the home affected, or
null accessoryIdString- the accessory affected, or
null