Class 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.
-
Method Summary
Modifier and TypeMethodDescriptionvoidaddStructureListener(HomeStructureListener listener) Watches the home graph for topology changes.booleanWhether accessory and structure identifiers survive an app restart.createScene(HomeStructure structure, String name, List<SceneAction> actions) Creates a scene from a set of accessory states.deleteScene(Scene scene) Deletes a scene.static voiddeliverAuthorization(int requestId, int statusOrdinal, String error) AnswersrequestAuthorization().static voiddeliverChanges(String subscriptionId, String[] lines) Delivers watched trait changes.static voiddeliverCommissioningResult(int requestId, String accessoryId, String accessoryName, String structureId, int commissionedToThisApp, String error) static voiddeliverDrained(int requestId, int deliveredCount, String error) AnswersdrainChanges().static voiddeliverIdentifyResult(int requestId, String error) Answersidentify(Accessory).static voiddeliverReadings(int requestId, String[] lines, String error) Answersread(TraitReadRequest).static voiddeliverRefreshed(int requestId, String error) Answersrefresh().static voiddeliverResyncRequired(String subscriptionId) Tells subscribers that changes were missed and their values are stale.static voiddeliverSceneResult(int requestId, String sceneLine, String structureId, String error) static voiddeliverStarted(int requestId, int availabilityOrdinal, String error) Answersrefresh()'s first call.static voiddeliverWriteResults(int requestId, String[] lines, String error) Answerswrite(java.util.List).Collects changes the platform has been holding and delivers them to the subscriptions that asked for them.executeScene(Scene scene) Runs a scene.findAccessory(String accessoryId) One accessory by identifier, across every home.Whether the user has granted this app access to their home.Whether a home graph is usable right now, and when it is not, why.Which platform service is behind this instance.Adding new Matter accessories.Build configuration this backend needs and does not have.static SmartHomeThe smart-home API for this device.intThe largest number of traits this backend reads in one call, or zero for no limit.intThe largest number of traits this backend writes in one call, or zero for no limit.The user's default home, as of the lastrefresh().The homes, as of the lastrefresh().Asks an accessory to make itself known -- blink, beep, whatever it does.booleanWhether this release can create or run automations -- a scene plus a trigger.booleanWhether this device has any smart-home support at all.static voidnotifyStructureChanged(int changeKindOrdinal, String structureId, String accessoryId) Reports a change to the home graph.booleanOpens the platform's ecosystem app -- Apple Home, Google Home.booleanOpens the system settings page where the user can change this app's smart-home access.booleanOpens wherever the user installs or updates the backend's provider.read(Accessory accessory, AccessoryService service, Trait trait) Reads one trait, for the common case where a request builder would be noise.read(TraitReadRequest request) Reads trait values.refresh()Loads the home graph from the platform.voidremoveStructureListener(HomeStructureListener listener) Stops watching the home graph.Prompts the user for access to their home.subscribe(SubscriptionRequest request, HomeChangeListener listener) Watches traits for change.write(TraitWrite write) Writes one trait value.write(List<TraitWrite> writes) Writes trait values.
-
Method Details
-
getInstance
The smart-home API for this device.
Never
null. On a port with no support the returned instance answersHomeAvailability.NOT_SUPPORTEDand every operation fails fast, so there is nothing to null-check and no platform branch to write.Returns
the instance, never
null -
isSupported
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
truewhen a backend is present -
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
the availability, never
null -
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
the backend, never
null -
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 missingandroid.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
an immutable list, possibly empty
-
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
truewhen identifiers are stable across launches -
isAutomationSupported
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; seeexecuteScene(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
false -
getCommissioner
Adding new Matter accessories.
Never
null; askCommissioner.isSupported()before offering an "add a device" button.Returns
the commissioner, never
null -
getAuthorizationStatus
Whether the user has granted this app access to their home.
Returns
the status, never
null -
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
the resulting status, delivered on the EDT
-
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
truewhen something was opened -
openEcosystemApp
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
truewhen the app was opened;falsewhen it is not installed -
openProviderSetup
public boolean openProviderSetup()Opens wherever the user installs or updates the backend's provider.
The recovery from
HomeAvailability.PROVIDER_NOT_INSTALLEDandHomeAvailability.PROVIDER_UPDATE_REQUIRED.Returns
truewhen something was opened -
refresh
Loads the home graph from the platform.
Call this once before reading
getStructures(), and again whenever aHomeStructureListenersays 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
the homes, delivered on the EDT
-
getStructures
-
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
the primary home, the first home when none is marked primary, or
nullwhen there are none -
findAccessory
-
identify
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_SUPPORTEDrather than doing nothing quietly.Parameters
accessory: the accessory to identify
Returns
completion, delivered on the EDT
Throws
IllegalArgumentException: whenaccessoryisnull
-
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
the batch limit, or zero
-
read
Reads trait values.
One
TraitReadingper 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
request: what to read
Returns
the readings, delivered on the EDT
Throws
IllegalArgumentException: whenrequestisnull
-
read
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
-
accessory: the accessory to read -
service: the service on it -
trait: the trait to read
Returns
the reading, delivered on the EDT
Throws
IllegalArgumentException: when any argument isnull
-
-
getMaxWriteBatchSize
public int getMaxWriteBatchSize()The largest number of traits this backend writes in one call, or zero for no limit.
Returns
the batch limit, or zero
-
write
Writes trait values.
One
TraitWriteResultper 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
writes: what to change
Returns
the outcomes, delivered on the EDT
Throws
IllegalArgumentException: whenwritesisnullor holds anull
-
write
Writes one trait value.
Parameters
write: what to change
Returns
the outcome, delivered on the EDT
Throws
IllegalArgumentException: whenwriteisnull
-
subscribe
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 answersfalse, which is everywhere except HomeKit in the foreground, this listener will not fire until you calldrainChanges().Parameters
-
request: what to watch -
listener: where changes are delivered, on the EDT
Returns
the subscription handle, never
nullThrows
IllegalArgumentException: when either argument isnull, or the request is empty
-
-
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
how many changed readings were delivered, on the EDT
-
addStructureListener
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
listener: the listener to add;nullis ignored
-
removeStructureListener
Stops watching the home graph.
Parameters
listener: the listener to remove;nulland unknown listeners are ignored
-
executeScene
Runs a scene.
Parameters
scene: the scene to run
Returns
completion, delivered on the EDT
Throws
IllegalArgumentException: whensceneisnull
-
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
-
structure: the home to create it in -
name: the scene's name -
actions: what it should do;TraitWrite.toSceneAction()turns the changes a user just made into these
Returns
the new scene, delivered on the EDT
Throws
IllegalArgumentException: when any argument isnull, when the name is empty, or when there are no actions
-
-
deleteScene
Deletes a scene.
Parameters
scene: the scene to delete
Returns
completion, delivered on the EDT
Throws
IllegalArgumentException: whensceneisnull
-
deliverStarted
Answers
refresh()'s first call. Called by the ports from any thread.Parameters
-
requestId: the request being answered -
availabilityOrdinal: the availability now that the backend is connected -
error: the encoded failure, ornullfor success
-
-
deliverRefreshed
-
deliverAuthorization
Answers
requestAuthorization(). Called by the ports from any thread.Parameters
-
requestId: the request being answered -
statusOrdinal: the resultingHomeAuthorizationStatusordinal -
error: the encoded failure, ornullfor success
-
-
deliverReadings
Answers
read(TraitReadRequest). Called by the ports from any thread.Parameters
-
requestId: the request being answered -
lines: one encoded reading per requested trait -
error: the encoded failure, ornullfor success
-
-
deliverWriteResults
Answers
write(java.util.List). Called by the ports from any thread.Parameters
-
requestId: the request being answered -
lines: one outcome per write, asaccessoryId \t serviceId \t traitId \t applied \t errorName \t errorMessage -
error: the encoded failure, ornullfor success
-
-
deliverSceneResult
public static void deliverSceneResult(int requestId, String sceneLine, String structureId, String error) Answers
executeScene(Scene), #createScene(HomeStructure, java.lang.String, java.util.List) anddeleteScene(Scene). Called by the ports from any thread.Parameters
-
requestId: the request being answered -
sceneLine: the affected scene, encoded as inHomeBridge#getScenes(java.lang.String), ornull -
structureId: the home the scene belongs to -
error: the encoded failure, ornullfor success
-
-
deliverCommissioningResult
public static void deliverCommissioningResult(int requestId, String accessoryId, String accessoryName, String structureId, int commissionedToThisApp, String error) Answers
Commissioner.commission(CommissioningRequest). Called by the ports from any thread.Parameters
-
requestId: the request being answered -
accessoryId: the new accessory, ornull -
accessoryName: the name it ended up with, ornull -
structureId: the home it joined, ornull -
commissionedToThisApp:1when this app can address it -
error: the encoded failure, ornullfor success
-
-
deliverIdentifyResult
Answers
identify(Accessory). Called by the ports from any thread.Parameters
-
requestId: the request being answered -
error: the encoded failure, ornullfor success
-
-
deliverDrained
Answers
drainChanges(). Called by the ports from any thread, after every [#deliverChanges(java.lang.String, java.lang.String[])] the drain produced.Parameters
-
requestId: the request being answered -
deliveredCount: how many readings were handed over -
error: the encoded failure, ornullfor success
-
-
deliverChanges
-
deliverResyncRequired
Tells subscribers that changes were missed and their values are stale.
Parameters
subscriptionId: which subscription lost its stream
-
notifyStructureChanged
public static void notifyStructureChanged(int changeKindOrdinal, String structureId, String accessoryId) Reports a change to the home graph. Called by the ports from any thread.
Parameters
-
changeKindOrdinal: theStructureChangeKindordinal -
structureId: the home affected, ornull -
accessoryId: the accessory affected, ornull
-
-