{"id":28089556,"url":"https://github.com/bblacey/ezlo-hub-kit","last_synced_at":"2025-10-09T17:04:41.817Z","repository":{"id":36980804,"uuid":"329481136","full_name":"bblacey/ezlo-hub-kit","owner":"bblacey","description":"SDK for Ezlo Innovation's Home Automation Hubs/Controllers","archived":false,"fork":false,"pushed_at":"2023-03-03T04:00:59.000Z","size":679,"stargazers_count":5,"open_issues_count":8,"forks_count":2,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-03-05T14:45:39.192Z","etag":null,"topics":["api-wrapper","ezlo","nodejs-api","nodejs-modules","npm-module","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bblacey.png","metadata":{"files":{"readme":"ReadMe.md","changelog":null,"contributing":"Contributing.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2021-01-14T02:01:32.000Z","updated_at":"2023-01-31T18:41:24.000Z","dependencies_parsed_at":"2023-02-14T13:30:52.286Z","dependency_job_id":"f1ad0dac-0b0d-4ef3-b49c-ea33912bf839","html_url":"https://github.com/bblacey/ezlo-hub-kit","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bblacey%2Fezlo-hub-kit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bblacey%2Fezlo-hub-kit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bblacey%2Fezlo-hub-kit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bblacey%2Fezlo-hub-kit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bblacey","download_url":"https://codeload.github.com/bblacey/ezlo-hub-kit/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253948410,"owners_count":21988953,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api-wrapper","ezlo","nodejs-api","nodejs-modules","npm-module","typescript"],"created_at":"2025-05-13T13:00:28.838Z","updated_at":"2025-10-09T17:04:36.768Z","avatar_url":"https://github.com/bblacey.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Ezlo-Hub-Kit\n\n![Continuous Integration](https://github.com/bblacey/ezlo-hub-kit/workflows/Continuous%20Integration/badge.svg)![Publish NPM Package](https://github.com/bblacey/ezlo-hub-kit/workflows/Publish%20NPM%20Package/badge.svg)\n\n## Overview\n\nEzlo-Hub-Kit is a [Node.js Package Manager](https://www.npmjs.com) module that provides a convenient, fully-typed, SDK for Ezlo Innovation's automation hubs. The kit enables applications to discover local hubs, connect to them securely, retrieve properties such as devices and rooms, observe hub events and perform hub actions.\n\n## Motivation\nEzlo Innovation offers a comprehensive [API](https://api.ezlo.com) for their automation hub products running both the Ezlo Linux (e.g Ezlo Plus, Ezlo Secure) and Ezlo RTOS (e.g. Atom, PlugHub) firmware (bravo!).  However, in order to develop an off-hub App using the stock Ezlo API, application developers are required to write a lot of low-level code simply to discover hubs, establish an authenticated connection, craft and send JSON RPC request objects and interpret the responses, keep connections alive, etc.\n\nThe motivation behind Ezlo-Hub-Kit is to enable developers to more rapidly develop off-hub applications with much less code by wrapping the low-level Ezlo APIs into higher level abstractions packaged into a convenient kit published as an npm module.\n\n## Installation\nFollowing broad conventions, the `ezlo-hub-kit` npm module is published as an unscoped public package for convenient access to applications.  This should not create confusion or conflict with any current or future Ezlo APIs and/or SDKs, if any, because Ezlo-developed packages will most likely be published under an Ezlo organization scope.\n```zsh\nnpm install ezlo-hub-kit --save\n```\n\n`ezlo-hub-kit` is a hybrid npm module that supports both commonJS and ESM modules with complete Typescript type definitions.\n\n\u003cspan style=\"color:grey\"\u003e*ESM*\u003c/span\u003e\u003c/p\u003e\n```ts\nimport { EzloHub, EzloCloudResolver } from 'ezlo-hub-kit';\n```\n\n\u003cspan style=\"color:grey\"\u003e*commonJS*\u003c/span\u003e\n```js\nconst { EzloHub, EzloCloudResolver } = require('ezlo-hub-kit');\n```\n\n## Usage\n\n### Primary SDK Entities\n`EzloHub` is a software bridge to a physical Ezlo Innovation hub on the local area network that uses a `CredentialsResolver` to retrieve a hub's `HubCredentials` authentication credentials.\n\n### Hub Authentication Credentials\n`EzloHub` communicates with local hubs over authenticated secure websocket connections. An encrypted user id and token, specific to a given hub, are required to estalish the secure connection. A `CredentialsResolver` provides the authentication credentials for a specific hub represented as a `HubCredentials` object.  Currently there are two `CredentialsResolver`s that cover the normative application credential strategies but can easily be extended to include additional resolvers for application-specific credential strategies.\n\n#### EzloCloudResolver (recommended)\nThe `EzloCloudResolver` retrieves the registered hubs and the associated user-id/authentication-token pairs from the MIOS/Ezlo Cloud.  An `EzloCloudResolver` minimizes Cloud interactions by caching multiple levels of authentication credentials until they expire. The cache exists during the lifecycle of an `EzloCloudResolver`.\n\n```js\nconst { EzloCloudResolver } = require('ezlo-hub-kit');\ncredentialsResolver = new EzloCloudResolver('\u003cMIOS username\u003e', '\u003cMIOS password\u003e');\n```\n\n#### ConfigFileResolver (useful when Cloud access is not available or desirable)\nThe `ConfigFileResolver` provides the hub authentication credentials from a local JSON configuration JSON.  This is particularly useful when Ezlo Cloud access is not available or desirable.  For example, when running unit tests, it might be desirable to reduce test iteration time by using a local credentials file.\n\n```js\nconst { ConfigFileResolver } = require('ezlo-hub-kit');\ncredentialsResolver = new ConfigFileResolver('configFilePath');\n```\n\n### Hub Instantiation\nTwo SDK methods exist to create `EzloHub` instances directly, the `EzloHub.createHub()` factory method and the `EzloHub` designated constructor. Applications should rarely, if ever, need to call the `EzloHub` designated constructor and are encouraged to use the SDK's higher-level methods.  For example, the `CredentialsResolver` employed by the `createHub()` factory method shields applications from the complexity assoicated with accessing a hub's authentication credentials and/or constructing a hub's `wss://` url.\n\n##### Factory method (recommended)\n```js\nconst { EzloHub, EzloCloudResolver } = require('ezlo-hub-kit');\n\nconst credentialsResolver = new EzloCloudResolver('bblacey', '\u003cpassword\u003e');\nconst hub = EzloHub.createHub('90000330', credentialsResolver);\n```\n\n##### Designated contructor\nThe designated constructor call site is `constructor(public url: string, private credentials: HubCredentials)`. An application must provide the hub's `wss://\u003cip address:port\u003e` url and the hub's user credentials represented as a `HubCredentials` object. Use of this method is discouraged in favor of `EzloHub.createHub()` above and is documented here purely for completeness.\n```js\nconst { EzloHub, HubCredentials } = require('ezlo-hub-kit');\n\nconst credentials = new HubCredentials('\u003chub id\u003e', '\u003cuser\u003e', '\u003ctoken\u003e');\nconst hub = new EzloHub('wss://\u003chub ip\u003e:17000', credentials);\n```\n\n### Hub Discovery\nEzlo-Hub-Kit provides a simple method to discover the Ezlo Hubs on the local network segment.  By default, discovery continues for the duration of the application instance but an application may pass an optional duration timeout to limit the discovery interval.\n```js\nconst { discoverEzloHubs } = require('ezlo-hub-kit');\n\ndiscoverEzloHubs(credentialsResolver, (hub) =\u003e {\n\tconsole.log('Discovered Hub: %s', hub.serial);\n}, 10000); // Search for 10 seconds\n```\nIt is useful to note, that the collection of hubs known by a `CredentialsResolver` may be different than the hubs discoverable on the local network segment.  For example, one or more hubs may be offline, yet registered with your Ezlo account or configured in a local credentials file. `CrededentialsResolver` provides a method to retrieve the known hub collection when needed.\n```js\nconst { EzloCloudResolver } = require('ezlo-hub-kit');\n\nhubs = await new EzloCloudResolver(\"bblacey\", \u003cpassword\u003e).hubs();\nconsole.log(\"Hubs Registered with Ezlo Cloud: %s\", hubs)\n```\n\n### Hub Connection\nEzlo-Hub-Kit uses an authenticated connection over secure websockets to communicate with a physical hub and provides a `connect()` method to establish the authenticated secure connection with a hub.\n\n```js\n// Explicitly connect\nconst myHub = EzloHub.createHub('90000330', credentialsResolver)\n                     .then((hub) =\u003e hub.connect();\n```\nIn addition, as a convenience, if the App requests a hub property without first connecting explicitly, Ezlo-Hub-Kit will automatically connect to the hub.\n```js\n// Implicitly connect by requesting a hub property\nconst info = EzloHub.createHub('90000330', credentialsResolver)\n                    .then((hub) =\u003e hub.info()); //Automatically connects\n```\n#### Keep-Alive\nWhen an `EzloHub` establishes a local secure connection with the physical EzloHub, it initiates a best-effort keep-alive strategy to maintain the connection across faults (e.g. hub reboot, stale websocket, etc.).  This ensures that `ezlo-hub-kit` powered applications retain an active connection whenever the hub is operable and accessible on the local network.\n\n### Hub Properties\nUsing an `EzloHub`, applications can retrieve a hub's `info`, `devices`, `items`, `scenes` and `rooms` properties.  The following example retrieves the hub `info` and `devices` for each hub available on the local area network.\n```js\nconst { discoverEzloHubs, EzloCloudResolver } = require('ezlo-hub-kit');\n\ncredentialsResolver = new EzloCloudResolver(\"bblacey\", \"password\");\n\ndiscoverEzloHubs(credentialsResolver, async (hub) =\u003e {\n\t// Get the hub's Info\n\tconst info = await hub.info();\n\tconsole.log('Discovered Hub %s, architecture: %s, firmware: %s', hub.serial, info.architecture, info.firmware);\n\n\t// Get the hub's devices\n\tfor (const device of await hub.devices()) {\n\t\tconsole.log('Device: %s\u003e%s, %s', hub.serial, device.name, device.id);\n\t}\n});\n```\n#### Hub Property objects are opaque\nThe hub properties are intentionally the same opaque objects that the [Ezlo JSON-RPC API](https://api.ezlo.com) returns in the result object because this design choice ensures Ezlo-Hub-Kit resilence to changes that Ezlo will introduce until their API stabilizes.  Once the Ezlo JSON RPC API stabilizes, a future `ezlo-api-kit` revision may wrap the typeless opaque API types in well-typed objects.\n\n#### Consistent Hub Property accessor pattern\nEzlo-Hub-Kit uses a plurality/signularity pattern for property accessors whenever it is semantically sensible.  Scenes, rooms, devices, modes, and items provide two methods.  The plural form returns all the scenes, rooms, etc. whereas the singular form accepts a tag argument that filters the results (currently limted to exact name match).  This enables applications to locate hub properties by tag and then perform an action with the property `._id` that is required to perform an action.\n\n```js\n// Retrieve all scenes on the hub\nconst scenes = await hub.scenes();\n// Retrieve the scene named 'Return' and run it\nconst scene = await hub.scene('Return').then((scene) =\u003e hub.runScene(scene._id));\n// Find all 'switch' items for all devices\nconst switches = await hub.item('switch');\n```\n\n### Hub Actions\n`EzloHub` exposes simple actions to change house modes, run scenes and control devices paired with the hub.  In the case of the later, if the application provides a list of items, then EzloHub will use multicast to broadcast the command.\n```js\n// Set houseMode to 'Away'\nhub.houseMode('Away').then((mode) =\u003e hub.setHouseMode(mode._id));\n\n// Run the scene named 'Return'\nhub.scene('Return').then((scene) =\u003e hub.runScene(scene._id));\n\n// Turn off a light - 5fd39c49129ded1201c7e122 is the switch item for the light device\nhub.setItemValue('5fd39c49129ded1201c7e122', false);\n\n// Dim 2 lights to 50% - the command will be multicast to both lamp items simultaneously\nhub.setItemValue(['5fd39c49129ded1201c7e11f', '5fcd3955129de111fc6e97fe'], 50);\n\n// Dim the Foyer Lamp to 50%\nhub.devices('Foyer Lamp') // Get Foyer Lamp Device\n.then(device =\u003e {\n  hub.items('dimmer', device._id) // Git Foyer Lamp 'dimmer' item\n    .then(item =\u003e hub.setItemValue(item._id, 50)); // Set the dimmer item to 50%\n});\n```\nEzlo-Hub-Kit anticipates common idioms for certain long-running actions such as changing House Mode or running a scene, and returns a promise that resolves when the hub has acknowledges that the action was successful.  This behavior enables apps to conveniently wait to initiate additional actions until the hub completes the current action to avoid \"piling on\" a large number of requests. In the case of House Modes, Ezlo Hubs default to waiting 30 seconds to switch to a mode other than Home.  If an App needs to wait until a House Mode completes before taking some action like running a scene, it is as simple as the following:\n```js\n// Change the house mode to 'Away' waiting for the hub to complete the mode change\nawait hub.houseMode('Away').then(modeId =\u003e hub.setHouseMode(modeId));\n\n// Run the 'Leave' scene, logging when scene execution has finished\nhub.scene('Leave')\n.then(scene =\u003e hub.runScene(scene.id)\n  .then(console.log('Leave scene finished'))\n);\n```\nBehind the scenes, Ezlo-Hub-Kit asynchronously waits for the physical hub to send a `ui_broadcast` message signaling that the mode change or scene execution is complete and, at that point, the SDK resolves the promise returned from `setHouseMode()` so the App can continue issuing actions.  Apps are encouraged to leverage these \"intelligent\" promises because they include logic that encompasses changing the mode only if the hub is in a different mode, timing out if the hub doesn't respond during the required interval, checking hub replies for success/failure, etc.  However apps are free to bypass this convenience and register their own action completion observers and use a \"fire and forget\" action approach (see House Mode example in next section).\n\n### Hub Event Observers\nAppications can register observers for events broadcast by `EzloHub`.  This provides an efficient mechanism to instantly act upon events of interest (e.g. update the UI whan an item changes state).  For example, `ezlo-homebridge` registers item observers for each HomeKit Accessory Characteristic to accurately and efficiently propogate Ezlo Hub device state changes (e.g. dimmer level) to the bridged HomeKit Accessory.\n```js\n// Observe all ui messages for a given hub\nhub.addObserver((msg) =\u003e msg.id === 'ui_broadcast', (msg) =\u003e {\n  console.log('%s %s:ui_broadcast %o\\n', (new Date().toUTCString()), hub.identity, msg);\n});\n```\nAs a convenience, several observation predicates are pre-defined for common hub event broadcasts. For example the snippet above can be re-written to use a pre-defined predicate.\n```js\nhub.addObserver(UIBroadcastPredicate, (msg) =\u003e {\n  console.log('%s %s:ui_broadcast %o\\n', (new Date().toUTCString()), hub.identity, msg);\n});\n```\nReferring back to the House Mode change example above, the following snippet demonstrates how an App client can \"fire and forget\" and then take an additional action upon a mode change.\n```js\n// Register a House Mode Change observer\nhub.addObserver(UIBroadcastHouseModeChangeDonePredicate, (msg) =\u003e {\n  console.log(`The House Mode just changed from ${msg.result.from} to ${msg.result.to}`);\n  hub.removeObserver(modeObserver);\n});\n// The observer above will be called once the hub completes the house mode change\nhub.setHouseMode('1');\n```\nApps can also extend pre-definied observer filter predicates using an expresssion. For example, to limit the aforementioned observer to only fire when the House Mode changes to Home.\n```js\n// Register a House Mode change observer for 'Home' mode\nconst houseModeSwitchedToHome = (msg) =\u003e UIBroadcastHouseModeChangeDonePredicate \u0026\u0026 msg.result?.to === '1';\nhub.addObserver(houseModeSwitchedToHome), (msg) =\u003e {\n  console.log('The House Mode just changed to \"Home\"');\n}\n```\n### Example\n\nTo facility community adoption several, examples are available.  The following in-line example illustrates a common Ezlo-Hub-Kit usage pattern.\n\n#### MQTT Relay\nRelay `ui_broadcast` event messages from all local hubs to an MQTT broker under the topic `/Ezlo/\u003cHub Identifier\u003e/\u003csub_message\u003e/\u003cdevice id\u003e`\n\n```js\nconst miosUser = '\u003cmios portal user id\u003e';\nconst miosPassword = '\u003cmios portal password\u003e';\nconst mqttBrokerUrl = 'mqtt://\u003cip address\u003e'\n\nconst mqtt = require('mqtt');\nconst { EzloCloudResolver, discoverEzloHubs, UIBroadcastPredicate } = require('ezlo-hub-kit');\n\n// Connect to the MQTT broker\nconst client  = mqtt.connect(mqttBrokerUrl)\nclient.on('connect', () =\u003e console.log('connected to mqtt broker'));\n\n// Discover all local Ezlo Hubs\ndiscoverEzloHubs(new EzloCloudResolver(miosUser, miosPassword), async (hub) =\u003e {\n\n    // Report the information about the discovered hub (implicitly connects)\n    const info = await hub.info();\n    console.log('Observing: %s, architecture: %s\\t, model: %s\\t, firmware: %s, uptime: %s',\n                  info.serial, info.architecture, info.model, info.firmware, info.uptime);\n\n    // Register to receive the ui_broadcast messages from this hub and publish to MQTT broker\n    hub.addObserver( UIBroadcastPredicate, (msg) =\u003e {\n        console.log('%s %s:ui_broadcast %o\\n', (new Date().toUTCString()), hub.identity, msg);\n        client.publish(`Ezlo/${hub.identity}/${msg.msg_subclass}/${msg.result.deviceId}`, JSON.stringify(msg));\n    });\n});\n```\n\n##### Sample output\n###### Launch MQTT Relay\n```zsh\n$ node index.js\n\nconnected to mqtt broker\nObserving: 45006642, architecture: mips , model: g150   , firmware: 2.0.5.1213.2, uptime: 3d 3h 45m 20s\nObserving: 90000330, architecture: armv7l       , model: h2.1   , firmware: 2.0.6.1271.3, uptime: 3d 4h 0m 23s\nObserving: 90000369, architecture: armv7l       , model: h2.1   , firmware: 2.0.6.1271.3, uptime: 4d 22h 20m 26s\nObserving: 70060017, architecture: esp32        , model: ATOM32 , firmware: 0.8.528, uptime: 3d 3h 11m 3s\nObserving: 70060095, architecture: esp32        , model: ATOM32 , firmware: 0.8.528, uptime: 0d 5h 10m 43s\nSat, 09 Jan 2021 16:32:49 GMT 70060095:ui_broadcast {\n  id: 'ui_broadcast',\n  msg_subclass: 'hub.item.updated',\n  msg_id: 3025550422,\n  result: {\n    _id: 'E2689956',\n    deviceId: 'ZC0E40D34',\n    deviceName: 'Boardwalk',\n    deviceCategory: 'dimmable_light',\n    deviceSubcategory: 'dimmable_colored',\n    serviceNotification: false,\n    roomName: 'Exterior',\n    userNotification: true,\n    notifications: null,\n    name: 'switch',\n    valueType: 'bool',\n    value: false,\n    syncNotification: false\n  }\n}\n```\n###### mosquitto_sub (message receive by broker on topic /Ezlo/#)\n```zsh\n$ mosquitto_sub -h \u003cbroker\u003e -t 'Ezlo/#' -v\nEzlo/70060095/hub.item.updated/ZA63A835 {\"id\":\"ui_broadcast\",\"msg_subclass\":\"hub.item.updated\",\"msg_id\":3025550429,\"result\":{\"_id\":\"A95EC7DB\",\"deviceId\":\"ZA63A835\",\"deviceName\":\"Corner Garden\",\"deviceCategory\":\"dimmable_light\",\"deviceSubcategory\":\"dimmable_colored\",\"serviceNotification\":false,\"roomName\":\"Exterior\",\"userNotification\":true,\"notifications\":null,\"name\":\"switch\",\"valueType\":\"bool\",\"value\":true,\"syncNotification\":false}}\n```\n\nIf you are interested in a dockerized version of the MQTT Relay, head over to the [Ez-MQTTRelay](https://github.com/bblacey/ez-mqttrelay) GitHub repository.\n\n### Sample Applications\nThere are three EZ (Easy-eZLo) Apps available that illustrate how to use Ezlo-Hub-Kit within an App.  In the spirit of Easy, each EZ-App is packaged as docker image for Intel/AMD and ARM so it is easy to try out.\n\n1. [EZ-HouseMode-Synchronizer](https://github.com/bblacey/ezlo-housemode-synchronizer)\n\nEasy HouseMode-Synchronizer propagates Vera House Mode Changes to Ezlo hubs on the local area network. The EZ-App illustrates how to use Ezlo-Hub-Kit to discover hubs and change House Modes and how to wait for the hub to complete the mode change.  Users who are transitioning from Vera to Ezlo may find this App useful because House Mode changes initiated on a Vera will be propagated to every Ezlo Hub on the LAN.\n\n2. [EZ-HouseMode-SceneRunner](https://github.com/bblacey/ez-housemode-scenerunner)\n\nEasy HouseMode-SceneRunner runs a scene on an Ezlo Hub immediately after it transitions to a new House Mode.  The EZ-App illustrates how to use Ezlo-Hub-Kit to discover hubs, use observers to asynchronously act on House Mode changes and execute scenes.  This EZ-App will appeal to Vera Users who have grown accustomed to employing scenes triggered by House Mode changes.  As of this writing, Ezlo Hub scenes can not use House Mode changes as a trigger. [EZ-HouseMode-SceneRunner](https://github.com/bblacey/ez-housemode-scenerunner) bridges this transtion gap until solutions like [Ezlo's Meshene](https://community.getvera.com/t/until-we-linux/213748/4?u=blacey) and/or [Reactor Multi System](https://community.getvera.com/t/preview-of-multi-system-reactor/216320?u=blacey) become available.\n\n3. [EZ-MQTT-Relay](https://github.com/bblacey/ez-mqttrelay)\n\nEasy MQTT-Relay publishes all [ui_broadcast](https://api.ezlo.com/hub/broadcasts/index.html) messages from Ezlo hubs discovered on the local area network to an MQTT broker.   This EZ-App illustrates how to discover hubs, register observation handlers and publish to MQTT.  This should appeal to Ezlo users who would like to push Ezlo controller/hub data to a time-series database (e.g. InfluxDB) for graphical reporting and analysis (e.g. Grafana).\n\nThe ReadMe for each EZ-App provides the necessary details.\n\nThe EZ-App demonstration apps, are actually designed to operate together to help users navigate their transition from Vera to Ezlo.  An [EZ-Apps](https://github.com/bblacey/ez-apps) repo is also available that includes the simple config files and a docker-compose file that will pull all the latest docker images and start the 3 services.\n\n### Additional Information\nApplication developers are encouraged to review the [Kit Test Suite tests](test) and the [test utilities](test/utils) as well as the in-line documentation.  In addition, Ezlo/Vera Forum users can find additional information in the [Ezlo-Hub-Kit \u0026 EZ-App forum category](https://community.getvera.com/c/apps-amp-remote-access/ezlo-hub-kit-ez-apps/234).\n\n---\n### Contributors Welcome!\n\nEzlo-Hub-Kit is an open source work-in-progress to enable other developers to easily implement an off-app hub without having to reinvent the wheel so to speak.  Other developers are encouraged to leverage Ezlo-Hub-Kit for their own off-hub apps to help flesh out and improve the kit (i.e. fork and submit pull requests).  If you are interested in contributing, please review the [Contributing document](Contributing.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbblacey%2Fezlo-hub-kit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbblacey%2Fezlo-hub-kit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbblacey%2Fezlo-hub-kit/lists"}