ThinCloud Node SDK
The ThinCloud Node SDK allows developers to write Node applications that utilizes the ThinCloud MQTT API.
Installation
npm install @yonomi/thincloud-v2-node-sdk
Initialization
Usage
To properly authenticate and connect, either pass the configuration into the client's constructor
const { Client } = require('@yonomi/thincloud-v2-node-sdk')
const client = new Client({
host: IOT_HOST,
ca: CA_FILE,
key: KEY,
cert: CERT,
clientId: CLIENT_ID,
deviceId: DEVICE_ID,
isCommissioned: !!DEVICE_ID,
})
await client.connect()
or initialize with an empty constructor and pass the configuration into the connect method.
Note: The configuration passed into the connect method will override the one passed into the client's constructor.
const { Client } = require('@yonomi/thincloud-v2-node-sdk')
const client = new Client()
await client.connect({
host: IOT_HOST,
ca: CA_FILE,
key: KEY,
cert: CERT,
clientId: CLIENT_ID,
deviceId: DEVICE_ID,
isCommissioned: !!DEVICE_ID,
})
Configuration
In order to use the SDK's core functionality, pass a config into the client constructor or to the connect method. The fields that can be passed in are as follows:
- host
- ca
- key
- cert
- clientId
- deviceId
- isCommissioned
- productId
- rpcRequestTimeout
- debugLevel
MQTT Authentication
These fields are all required and are used to connect and authenticate to the MQTT API. Follow the MQTT Authentication docs to generate these.
- host is the URL of the MQTT API endpoint
- ca is the AWS Root CA
- key is the device key
- cert is the device certificate
- clientId is the client ID generated from the public key's fingerprint
Commissioned Device
After a device has been commissioned, if the client disconnects and must be initialized, these fields are required to use the already-commissioned device.
-
deviceId
While this is optional for authentication purposes, it is a required field if persisting the device ID for all requests is required. After commissioning a device, the device ID can be stored locally and passed in here if the client needs to reconnect.
-
isCommissioned
If the device has been commissioned, pass in true when initializing the client.
Optional Fields
-
productId
While this field is optional, in order to commission a device, either pass in a product ID here or in the commission method.
-
rpcRequestTimeout
Timeout in milliseconds for RPC requests (commission, decommission, and getInvitationCode) made from the device to the cloud. By default, this is set to 4500 milliseconds.
-
debugLevel
Level of logs for debugging. Possible values to set this to are: fatal, error, warn, info, debug, and trace. By default, this is set to fatal.
Connecting
To connect to the ThinCloud MQTT API, use the initialized client to call client.connect().
client.isConnected will be true on successful connection.
client.connect([config])
config is an optional configuration argument. If the client configuration is passed in here, it will override the one passed into the client's constructor.
await client.connect()
if(client.isConnected) {
}
Disconnecting
To disconnect from the Thincloud MQTT API, call client.disconnect(). This does not decommission the device.
client.isConnected will be false after successful disconnection.
client.disconnect()
await client.disconnect()
if(!client.isConnected) {
console.log('Client is disconnected')
}
Core Functionality
Once the client is authenticated and connected to the MQTT API, the client can use the core functionality to listen to events and make requests.
Events
Using client.on() and client.off(), attach and detach listeners on events. Use exported Event constants.
const { Event } = require('@yonomi/thincloud-v2-node-sdk')
client.on(Event.OFFLINE, () => {
console.log('Device went offline')
})
client.off(Event.OFFLINE, () => {
console.log('Removed event listener for OFFLINE event')
})
-
Event.CONNECT
function (connack) {}
Emitted on successful (re)connection (i.e. connack rc=0).
-
Event.RECONNECT
function () {}
Emitted when a reconnect starts.
-
Event.CLOSE
function () {}
Emitted after a disconnection.
-
Event.DISCONNECT
function (packet) {}
Emitted after receiving disconnect packet from MQTT broker.
-
Event.OFFLINE
function () {}
Emitted when the client goes offline.
-
Event.ERROR
function (error) {}
Emitted when a message parsing error occurs.
-
Event.END
function (error) {}
Emitted when client.disconnect() is called.
-
Event.COMMAND
function (command) {}
Emitted when a command is sent from the cloud. See Send Command. Use command.success() or command.error() to send back a response.
command.name is the name of the command.
command.payload is the command payload. This is a String and may need to be deserialized.
command.childDeviceId is the child device ID. If this command is for the gateway itself, this will be undefined.
command.success([payload]) send back a success response with an optional payload. The payload can be anything.
command.error([payload]) send back an error response with an optional payload. The payload can be anything.
If this request is sent from the cloud
mutation {
sendCommand(input: {
deviceId: "559307a8-24af-49ff-bfe0-ff4ffcab14ad"
name: "ping"
payload: "ping"
}) {
requestId
}
}
The device can listen on the command event and respond appropriately
client.on(Event.COMMAND, (command) => {
console.log(command.name)
const payload = JSON.parse(command.payload)
console.log(payload)
if(command.name === 'ping') {
command.success({ echo: 'pong' })
} else {
command.error({ error: 'Received unsupported command'})
}
})
-
Event.STATE
function (state) {}
Emitted when state is sent from the cloud. See Send State. To persist state changes to the cloud, send a response using state.confirm()
-
state.params contains contents of the state request
-
state.childDeviceId is the child device ID. This will be undefined is the state event is meant for the gateway itself.
-
state.confirm([payload])
Send a response to persist state changes. If state.confirm() is called with no payload, the SDK will send the contents of state.params by default. Any attributes that are part of the device type definition will not be changed.
If this request is sent from the cloud
mutation {
sendState(input: {
id: "559307a8-24af-49ff-bfe0-ff4ffcab14ad"
type: "Light"
on: true
}) {
requestId
}
}
The device can listen on the state event and respond appropriately.
client.on(Event.STATE, (state) => {
console.log(state.params)
state.confirm()
})
A use case where passing in a payload to state.confirm makes sense would be if this request is sent from the cloud
mutation {
sendState(input: {
id: "559307a8-24af-49ff-bfe0-ff4ffcab14ad"
type: "Light"
rainbowMode: true
}) {
requestId
}
}
If the device is off, then it ignores a state change request to turn on rainbowMode. In that case, the device would respond with
client.on(Event.STATE, (state) => {
console.log(state.params)
state.confirm({ on: false, rainbowMode: false })
})
-
Event.PRODUCT
function (product) {}
Emitted when product updates are sent from the cloud.
product.params contains content of the product update
If this request is sent from the cloud from the Admin GraphQL API
mutation {
updateProduct(
id: "eb7c2710-7c55-4a9e-9c8e-059b2b53ef47",
tenantId: "af65595d-7b80-4051-9bdd-caf19e1a2aec",
updatePayload: {
manufacturer: 'Yonomi',
}
) {
id
manufacturer
}
}
The device can listen on the product event and handle appropriately. A response is not needed.
client.on(Event.PRODUCT, (product) => {
console.log(product.params)
})
-
Event.FIRMWARE_UPDATE
function (firmwareUpdate){}
Emitted when firmware releases are sent from the cloud.
firmwareUpdate.params contains content of the firmware update
The device can listen on the firmwareUpdate event and handle appropriately. A response is not needed.
client.on(Event.FIRMWARE_UPDATE, (firmwareUpdate) => {
const { firmwareVersion, firmwareUrl } = firmwareUpdate.params
console.log(`A release, ${firmwareVersion}, is now available at ${firmwareUrl}`)
})
RPC Requests
All RPC requests are made using the rpcRequestTimeout specified in the client configuration. By default, this is 4500 milliseconds. Each individual RPC request can take an optional timeout argument in milliseconds that will override rpcRequestTimeout for that request.
Commission
Commission a device. To receive commands and state requests, a device must be commissioned. The response contains the device ID.
client.isCommissioned will be set to true and client.deviceId will be set to the device ID returned in the response.
const { deviceId, productId } = await client.commission({ productId: '16e424b0-ca99-40d2-af5d-f22c8664bc33' }, 3000)
client.commission([params], [timeout])
params is optional but is required if productId is not set in the client configuration
params.productId is required.
params.force is optional and when set to true will force commissioning
timeout is optional.
Response
{
deviceId: '559307a8-24af-49ff-bfe0-ff4ffcab14ad',
productId: '16e424b0-ca99-40d2-af5d-f22c8664bc33'
}
deviceId is the device ID. This is used to communicate between device and cloud.
productId is the device's productId. It should be the same as the one used to commission the device.
Decommission
Decommissions a device. The device ID will no longer be valid for commands and state requests.
await client.decommission(3000)
if(!client.deviceId && !client.isCommissioned) {
console.log('Device is decommissioned')
}
client.decommission([timeout])
Response
An empty Object
{}
Get Invitation Code
Get device invitation code, a JWT. This is used for user-device association in the cloud. See Associate to a Device.
const { AccessLevel } = require('@yonomi/thincloud-v2-node-sdk')
const { ADMIN, GUEST, OWNER } = AccessLevel
const { invitationCode } = await client.getInvitationCode({
userId: '248b8e07-211a-4937-829e-80549e315168',
accessLevel: GUEST,
}, 3000)
client.getInvitationCode(params, [timeout])
-
params is required
params.userId is the user ID of the user in the user-device association
params.accessLevel is the access level granted to the user for this device. AccessLevel is exported as a constant from the SDK. AccessLevel.ADMIN, AccessLevel.GUEST, and AccessLevel.OWNER are valid values.
params.expiration is optional, defaults to 3600 ms, and must be between 3600 ms and 18000 ms. It is the number of milliseconds before the generated invitation code expires.
-
timeout is optional
Response
{ invitationCode: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzY290Y2guaW8iLCJleHAiOjEzMDA4MTkzODAsIm5hbWUiOiJDaHJpcyBTZXZpbGxlamEiLCJhZG1pbiI6dHJ1ZX0.03f329983b86f7d9a9f5fef85305880101d5e302afafa20154d094b229f75773' }
Product Information
To get a device's product information, use client.getProductInfo().
const productInfo = await client.getProductInfo({ productId: '16e424b0-ca99-40d2-af5d-f22c8664bc33' }, 3000)
client.getProductInfo([params], [timeout])
params
params.productId is optional if the request is for the gateway device and has been passed into the client config. If this request is being made for a child device or the product ID for gateway has not been passed into the client config, it is required.
timeout is optional.
Response
{
deviceType: 'Lock',
name: 'LockProduct',
manufacturer: 'Yonomi',
brand: 'Yonomi',
model: 'latest',
createdAt: '10-20-2019',
updatedAt: '10-21-2019',
firmwareRelease: [{
productId: '16e424b0-ca99-40d2-af5d-f22c8664bc33',
version: '1.0.1',
status: 'READY',
labels: ['beta'],
md5: '<md5 hash>',
crc: '<crc32 hash>',
}]
customKey: 'customValue',
}
This response returns all the standard product information for the device including the product's firmware release information.
State
Get State
To get device state from the cloud, use client.getState().
client.getState([deviceId], [timeout])
deviceId is optional, but necessary if attempting to get the state of a child device
timeout is optional
To get the gateway device state
await client.getState()
To get a child device's state, pass in the child device id.
const childDeviceId = '3529ea24-dd68-4b8c-9269-325427beb3ec'
await client.getState(childDeviceId)
Set State
To send device state to the cloud, use client.setState().
client.setState(deviceState, [deviceId])
deviceState is required and should be an Object representing the device state. Any attributes that are part of the device type definition will not be changed.
deviceId is optional, but necessary if attempting to set the state of a child device
To set the gateway device state
await client.setState({
on: true,
rainbowMode: true,
})
To set child device state, pass in the child device id.
const childDeviceId = '3529ea24-dd68-4b8c-9269-325427beb3ec'
await client.setState({
on: true,
rainbowMode: true,
}, childDeviceId)
Child Devices
Create And Pair New Child Device
To create and pair a new child device, use client.createAndPair.
await client.createAndPair({
productId: '16e424b0-ca99-40d2-af5d-f22c8664bc33',
})
client.createAndPair(params, [timeout])
params is required
params.productId is required.
timeout is optional
Response
{
deviceId: 'a1736e0f-c16f-4635-8914-a19cb90e3a4e',
productId: '16e424b0-ca99-40d2-af5d-f22c8664bc33'
}
deviceId is the device ID of the child device
productId is the device's productId.
Pair Existing Child Device
To pair a child device that exists in the cloud already, use client.pairChild.
const pairedDevice = await client.pairChild({
deviceId: '7a744e83-c19b-489c-a708-6037bae39a8f'
})
client.pairChild(params, [timeout])
params is required
params.deviceId is required and is the deviceId of the child device to pair
timeout is optional
Response
{
deviceId: '7a744e83-c19b-489c-a708-6037bae39a8f',
productId: '16e424b0-ca99-40d2-af5d-f22c8664bc33'
}
deviceId is the device ID of the child device
productId is the device's product ID.
List Child Devices
To list child devices, use client.listChildren.
const childDevices = await client.listChildren()
client.listChildren([timeout])
Response
The response is an array of Objects with ids representing child device IDs.
[
{ id: '7a744e83-c19b-489c-a708-6037bae39a8f' },
{ id: 'a1736e0f-c16f-4635-8914-a19cb90e3a4e' },
...
]
id is the child device ID
Remove Child Device
Removes a child device. The request includes a deviceId and the response is void.
await client.removeChild({ deviceId: 'a1736e0f-c16f-4635-8914-a19cb90e3a4e' }, 3000)
client.removeChild(params, [timeout])
params is required
params.deviceId is required and should be equal to the deviceId of the child that should be removed
timeout is optional
Response
The response is void
Download Firmware
Once the device has a provided URL to download its firmware, the initialized SDK can be used to download the firmware image. To request the device's firmware URL, use the getFirmwareInfo RPC method.
const firmwareImage = await client.getFirmware({ url: 'https://...' }, 3000)
client.getFirmware(params, [timeout])
params is required
params.url is required and should be the URL where the device firmware is hosted
timeout is optional. By default, it will be 4500 ms.