diff --git a/docs/guide/mqtt.md b/docs/guide/mqtt.md index 83deadda35..d460c2b62a 100644 --- a/docs/guide/mqtt.md +++ b/docs/guide/mqtt.md @@ -1,6 +1,6 @@ # MQTT -You have full access to all [zwavejs APIs](https://zwave-js.github.io/node-zwave-js/#/README) (and more) by simply using MQTT. +You have access to almost all [zwavejs APIs](https://zwave-js.github.io/node-zwave-js/#/README) (and more) via MQTT. ## Zwave Events @@ -34,22 +34,6 @@ Where `args` is an array with the args used to call the api, the topic is: The result will be published on the same topic without `/set` -Example: If I publish the previous json object to the topic - -`zwave/_CLIENTS/ZWAVE_GATEWAY-office/api/getAssociations/set` - -I will get this response (in the same topic without the suffix `/set`): - -```json -{ - "success": true, - "message": "Success zwave api call", - "result": [1] -} -``` - -`result` will contain the value returned from the API. In this example I will get an array with all node IDs that are associated to the group 1 (lifeline) of node 2. - ### APIs This are the available apis: @@ -96,6 +80,62 @@ This are the available apis: - `beginFirmwareUpdate(nodeId, fileName, data)`: Starts a firmware update of a node. The `fileName` is used to check the extension (used to detect the firmware file type) and data is a `Buffer` - `abortFirmwareUpdate(nodeId)`: Aborts a firmware update - `writeValue(valueId, value)`: Write a specific value to a [valueId](https://zwave-js.github.io/node-zwave-js/#/api/valueid?id=valueid) +- `sendCommand(valueId, command, args)`: Send a custom command + +### Api call examples + +#### Get Associations + +Get all the associations of node `23` group `Lifeline` (groupId `1`) + +Topic: `zwave/_CLIENTS/ZWAVE_GATEWAY-office/api/getAssociations/set` + +Payload: + +```js +{ + "args": [ + 23, // nodeid + 1 // lifeline group id + ] +} + +``` + +I will get this response (in the same topic without the suffix `/set`): + +```js +{ + "success": true, + "message": "Success zwave api call", + "result": [1] // the controller id +} +``` + +`result` will contain the value returned from the API. In this example I will get an array with all node IDs that are associated to the group 1 (lifeline) of node 23. + +#### Send Command + +Example calling [startLevelChange](https://github.com/zwave-js/node-zwave-js/blob/c695ee81cb2b1d3cf15e3db1cc14b1e41a911cc0/packages/zwave-js/src/lib/commandclass/MultilevelSwitchCC.ts) command: + +Topic: `zwavejs/_CLIENTS/ZWAVE_GATEWAY-/api/sendCommand/set` + +Payload: + +```js +{ "args": [ + { + "nodeId": 23, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "endpoint": 0, + "property": "targetValue" + }, + "startLevelChange", + [{}] // this are the args, in this case it could be omitted + ] +} +``` ## Set values diff --git a/lib/ZwaveClient.js b/lib/ZwaveClient.js index c4583ca3c2..0b48c29eb8 100644 --- a/lib/ZwaveClient.js +++ b/lib/ZwaveClient.js @@ -74,6 +74,7 @@ const allowedApis = [ 'refreshInfo', 'beginFirmwareUpdate', 'abortFirmwareUpdate', + 'sendCommand', 'writeValue' ] @@ -2278,6 +2279,52 @@ ZwaveClient.prototype.hardReset = async function () { throw Error('Driver is closed') } + +ZwaveClient.prototype.sendCommand = async function (valueId, command, args) { + if (this.driver && !this.closed) { + if (typeof valueId.nodeId !== 'number') { + throw Error('nodeId must be a number') + } + + const error = utils.isValueId(valueId) + + if (typeof error === 'string') { + throw Error(error) + } + + if (args !== undefined && !Array.isArray(args)) { + throw Error('if args is given, it must be an array') + } + + const node = this.getNode(valueId.nodeId) + if (!node) { + throw Error(`Node ${valueId.nodeId} was not found!`) + } + const endpoint = node.getEndpoint(valueId.endpoint || 0) + if (!endpoint) { + throw Error( + `Endpoint ${valueId.endpoint} does not exist on Node ${valueId.nodeId}!` + ) + } + const api = endpoint.commandClasses[valueId.commandClass] + + if (!api || !api.isSupported()) { + throw Error( + `Node ${valueId.nodeId} (Endpoint ${valueId.endpoint}) does not support CC ${valueId.commandClass}` + ) + } else if (!(command in api)) { + throw Error( + `The command ${command} does not exist for CC ${valueId.commandClass}` + ) + } + + const method = api[command].bind(api) + const result = args ? await method(...args) : await method() + return result + } + + throw Error('Driver is closed') +} /** * Calls a specific `client` or `ZwaveClient` method based on `apiName` * ZwaveClients methods used are the ones that overrides default Zwave methods diff --git a/lib/utils.js b/lib/utils.js index ec4dd3bd06..a40dd5360b 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -50,6 +50,35 @@ function joinProps (...props) { return ret } +/** + * Checks if an object is a valueId, returns error otherwise + * + * @param {import('zwave-js').ValueID} v the object + * @returns {boolean|string} Returns true if it's a valid valueId, an error string otherwise + */ +function isValueId (v) { + if (typeof v.commandClass !== 'number' || v.commandClass < 0) { + return 'invalid `commandClass`' + } + if (v.endpoint !== undefined && v.endpoint < 0) { + return 'invalid `endpoint`' + } + if ( + v.property === undefined || + (typeof v.property !== 'string' && typeof v.property !== 'number') + ) { + return 'invalid `property`' + } + if ( + v.propertyKey !== undefined && + typeof v.propertyKey !== 'string' && + typeof v.propertyKey !== 'number' + ) { + return 'invalid `propertyKey`' + } + return true +} + /** * Converts a decimal to an hex number of 4 digits and `0x` as prefix * @@ -150,6 +179,7 @@ module.exports = { getPath, joinPath, joinProps, + isValueId, num2hex, getVersion, sanitizeTopic, diff --git a/types/index.d.ts b/types/index.d.ts index a50cdf845f..85d1b24065 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -382,6 +382,7 @@ export interface ZwaveClient extends EventEmitter { ...args: any ): Promise<{ success: boolean; message: string; result: any; args: any[] }> writeValue(valueId: Z2MValueId, value: number | string): Promise + sendCommand(valueId: Z2MValueId, command: string, args: any[]): Promise } export interface Z2MGateway {