public final class SmartHome

  1. Object
  2. 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.homekit entitlement and an ios.NSHomeKitUsageDescription build 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

public static synchronized SmartHome getInstance()The smart-home API for this device.
public boolean isSupported()Whether this device has any smart-home support at all.
public HomeAvailability getAvailability()Whether a home graph is usable right now, and when it is not, why.
public HomeBackend getBackend()Which platform service is behind this instance.
public List<String> getConfigurationProblems()Build configuration this backend needs and does not have.
public boolean areIdsPersistent()Whether accessory and structure identifiers survive an app restart.
public boolean isAutomationSupported()Whether this release can create or run automations – a scene plus a trigger.
public Commissioner getCommissioner()Adding new Matter accessories.
public HomeAuthorizationStatus getAuthorizationStatus()Whether the user has granted this app access to their home.
public AsyncResource<HomeAuthorizationStatus> requestAuthorization()Prompts the user for access to their home.
public boolean openHomeSettings()Opens the system settings page where the user can change this app’s smart-home access.
public boolean openEcosystemApp()Opens the platform’s ecosystem app – Apple Home, Google Home.
public boolean openProviderSetup()Opens wherever the user installs or updates the backend’s provider.
public AsyncResource<List<HomeStructure>> refresh()Loads the home graph from the platform.
public List<HomeStructure> getStructures()The homes, as of the last refresh().
public HomeStructure getPrimaryStructure()The user’s default home, as of the last refresh().
public Accessory findAccessory(String accessoryId)One accessory by identifier, across every home.
public AsyncResource<Object> identify(Accessory accessory)Asks an accessory to make itself known – blink, beep, whatever it does.
public int getMaxReadBatchSize()The largest number of traits this backend reads in one call, or zero for no limit.
public AsyncResource<List<TraitReading>> read(TraitReadRequest request)Reads trait values.
public AsyncResource<TraitReading> read(Accessory accessory, AccessoryService service, Trait trait)Reads one trait, for the common case where a request builder would be noise.
public int getMaxWriteBatchSize()The largest number of traits this backend writes in one call, or zero for no limit.
public AsyncResource<List<TraitWriteResult>> write(List<TraitWrite> writes)Writes trait values.
public AsyncResource<TraitWriteResult> write(TraitWrite write)Writes one trait value.
public TraitSubscription subscribe(SubscriptionRequest request, HomeChangeListener listener)Watches traits for change.
public AsyncResource<Integer> drainChanges()Collects changes the platform has been holding and delivers them to the subscriptions that asked for them.
public void addStructureListener(HomeStructureListener listener)Watches the home graph for topology changes.
public void removeStructureListener(HomeStructureListener listener)Stops watching the home graph.
public AsyncResource<Scene> executeScene(Scene scene)Runs a scene.
public AsyncResource<Scene> createScene(HomeStructure structure, String name, List<SceneAction> actions)Creates a scene from a set of accessory states.
public AsyncResource<Scene> deleteScene(Scene scene)Deletes a scene.
public static void deliverStarted(int requestId, int availabilityOrdinal, String error)Answers refresh()’s first call.
public static void deliverRefreshed(int requestId, String error)Answers refresh().
public static void deliverAuthorization(int requestId, int statusOrdinal, String error)Answers requestAuthorization().
public static void deliverReadings(int requestId, String[] lines, String error)Answers read(TraitReadRequest).
public static void deliverWriteResults(int requestId, String[] lines, String error)Answers write(java.util.List).
public static void deliverSceneResult(int requestId, String sceneLine, String structureId, String error)Answers executeScene(Scene), #createScene(HomeStructure, java.lang.String, java.util.List) and deleteScene(Scene).
public static void deliverCommissioningResult(int requestId, String accessoryId, String accessoryName, String structureId, int commissionedToThisApp, String error)Answers Commissioner.commission(CommissioningRequest).
public static void deliverIdentifyResult(int requestId, String error)Answers identify(Accessory).
public static void deliverDrained(int requestId, int deliveredCount, String error)Answers drainChanges().
public static void deliverChanges(String subscriptionId, String[] lines)Delivers watched trait changes.
public static void deliverResyncRequired(String subscriptionId)Tells subscribers that changes were missed and their values are stale.
public static void notifyStructureChanged(int changeKindOrdinal, String structureId, String accessoryId)Reports a change to the home graph.

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

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

true when a backend is present

getAvailability

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

the availability, never null

getBackend

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

the backend, never null

getConfigurationProblems

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

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

true when 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; 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

false

getCommissioner

public Commissioner getCommissioner()

Adding new Matter accessories.

Never null; ask Commissioner.isSupported() before offering an “add a device” button.

Returns

the commissioner, never null

getAuthorizationStatus

public HomeAuthorizationStatus getAuthorizationStatus()
Whether the user has granted this app access to their home.

Returns

the status, never null

requestAuthorization

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

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

true when 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

true when the app was opened; false when 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_INSTALLED and HomeAvailability.PROVIDER_UPDATE_REQUIRED.

Returns

true when something was opened

refresh

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

the homes, delivered on the EDT

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

an immutable list, possibly empty

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

the primary home, the first home when none is marked primary, or null when there are none

findAccessory

public Accessory findAccessory(String accessoryId)
One accessory by identifier, across every home.

Parameters

accessoryId String
the identifier to look up, or null

Returns

the accessory, or null when no home holds it

identify

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

accessory Accessory
the accessory to identify

Returns

completion, delivered on the EDT

Throws

IllegalArgumentException
when accessory is null

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

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

request TraitReadRequest
what to read

Returns

the readings, delivered on the EDT

Throws

IllegalArgumentException
when request is null

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

accessory Accessory
the accessory to read
service AccessoryService
the service on it
trait Trait
the trait to read

Returns

the reading, delivered on the EDT

Throws

IllegalArgumentException
when any argument is null

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

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

writes List<TraitWrite>
what to change

Returns

the outcomes, delivered on the EDT

Throws

IllegalArgumentException
when writes is null or holds a null

write

public AsyncResource<TraitWriteResult> write(TraitWrite write)
Writes one trait value.

Parameters

write TraitWrite
what to change

Returns

the outcome, delivered on the EDT

Throws

IllegalArgumentException
when write is null

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

request SubscriptionRequest
what to watch
listener HomeChangeListener
where changes are delivered, on the EDT

Returns

the subscription handle, never null

Throws

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

how many changed readings were delivered, on the EDT

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

listener HomeStructureListener
the listener to add; null is ignored

removeStructureListener

public void removeStructureListener(HomeStructureListener listener)
Stops watching the home graph.

Parameters

listener HomeStructureListener
the listener to remove; null and unknown listeners are ignored

executeScene

public AsyncResource<Scene> executeScene(Scene scene)
Runs a scene.

Parameters

scene Scene
the scene to run

Returns

completion, delivered on the EDT

Throws

IllegalArgumentException
when scene is null

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 HomeStructure
the home to create it in
name String
the scene’s name
actions List<SceneAction>
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 is null, when the name is empty, or when there are no actions

deleteScene

public AsyncResource<Scene> deleteScene(Scene scene)
Deletes a scene.

Parameters

scene Scene
the scene to delete

Returns

completion, delivered on the EDT

Throws

IllegalArgumentException
when scene is null

deliverStarted

public static void deliverStarted(int requestId, int availabilityOrdinal, String error)
Answers refresh()’s first call. Called by the ports from any thread.

Parameters

requestId int
the request being answered
availabilityOrdinal int
the availability now that the backend is connected
error String
the encoded failure, or null for success

deliverRefreshed

public static void deliverRefreshed(int requestId, String error)
Answers refresh(). Called by the ports from any thread.

Parameters

requestId int
the request being answered
error String
the encoded failure, or null for success

deliverAuthorization

public static void deliverAuthorization(int requestId, int statusOrdinal, String error)
Answers requestAuthorization(). Called by the ports from any thread.

Parameters

requestId int
the request being answered
statusOrdinal int
the resulting HomeAuthorizationStatus ordinal
error String
the encoded failure, or null for success

deliverReadings

public static void deliverReadings(int requestId, String[] lines, String error)
Answers read(TraitReadRequest). Called by the ports from any thread.

Parameters

requestId int
the request being answered
lines String[]
one encoded reading per requested trait
error String
the encoded failure, or null for success

deliverWriteResults

public static void deliverWriteResults(int requestId, String[] lines, String error)
Answers write(java.util.List). Called by the ports from any thread.

Parameters

requestId int
the request being answered
lines String[]
one outcome per write, as accessoryId \t serviceId \t traitId \t applied \t errorName \t errorMessage
error String
the encoded failure, or null for success

deliverSceneResult

public static void deliverSceneResult(int requestId, String sceneLine, String structureId, String error)

Parameters

requestId int
the request being answered
sceneLine String
the affected scene, encoded as in HomeBridge#getScenes(java.lang.String), or null
structureId String
the home the scene belongs to
error String
the encoded failure, or null for 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 int
the request being answered
accessoryId String
the new accessory, or null
accessoryName String
the name it ended up with, or null
structureId String
the home it joined, or null
commissionedToThisApp int
1 when this app can address it
error String
the encoded failure, or null for success

deliverIdentifyResult

public static void deliverIdentifyResult(int requestId, String error)
Answers identify(Accessory). Called by the ports from any thread.

Parameters

requestId int
the request being answered
error String
the encoded failure, or null for success

deliverDrained

public static void deliverDrained(int requestId, int deliveredCount, String error)
Answers drainChanges(). Called by the ports from any thread, after every [#deliverChanges(java.lang.String, java.lang.String[])] the drain produced.

Parameters

requestId int
the request being answered
deliveredCount int
how many readings were handed over
error String
the encoded failure, or null for success

deliverChanges

public static void deliverChanges(String subscriptionId, String[] lines)
Delivers watched trait changes. Called by the ports from any thread, unsolicited or in response to a drain.

Parameters

subscriptionId String
which subscription these belong to
lines String[]
the encoded readings

deliverResyncRequired

public static void deliverResyncRequired(String subscriptionId)
Tells subscribers that changes were missed and their values are stale.

Parameters

subscriptionId String
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 int
the StructureChangeKind ordinal
structureId String
the home affected, or null
accessoryId String
the accessory affected, or null