Class SmartHome

java.lang.Object
com.codename1.home.SmartHome

public final class SmartHome extends Object

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.

  • Method Details

    • getInstance

      public static 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: 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: 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

      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: 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: 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 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

      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: 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: 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: what to watch

      • listener: 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: the listener to add; null is ignored
    • removeStructureListener

      public void removeStructureListener(HomeStructureListener listener)

      Stops watching the home graph.

      Parameters
      • listener: the listener to remove; null and unknown listeners are ignored
    • executeScene

      public AsyncResource<Scene> executeScene(Scene scene)

      Runs a scene.

      Parameters
      • 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: 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 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: 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: the request being answered

      • availabilityOrdinal: the availability now that the backend is connected

      • error: 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: the request being answered

      • error: 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: the request being answered

      • statusOrdinal: the resulting HomeAuthorizationStatus ordinal

      • error: 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: the request being answered

      • lines: one encoded reading per requested trait

      • error: 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: the request being answered

      • lines: one outcome per write, as accessoryId \t serviceId \t traitId \t applied \t errorName \t errorMessage

      • error: the encoded failure, or null for 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) and deleteScene(Scene). Called by the ports from any thread.

      Parameters
      • requestId: the request being answered

      • sceneLine: the affected scene, encoded as in HomeBridge#getScenes(java.lang.String), or null

      • structureId: the home the scene belongs to

      • error: 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: the request being answered

      • accessoryId: the new accessory, or null

      • accessoryName: the name it ended up with, or null

      • structureId: the home it joined, or null

      • commissionedToThisApp: 1 when this app can address it

      • error: 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: the request being answered

      • error: 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: the request being answered

      • deliveredCount: how many readings were handed over

      • error: 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: which subscription these belong to

      • lines: the encoded readings

    • deliverResyncRequired

      public static void deliverResyncRequired(String subscriptionId)

      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: the StructureChangeKind ordinal

      • structureId: the home affected, or null

      • accessoryId: the accessory affected, or null