diff --git a/docs/Codecs.md b/docs/Codecs.md index 9f6070f..00be481 100644 --- a/docs/Codecs.md +++ b/docs/Codecs.md @@ -71,6 +71,8 @@ The `init()` function is passed a single object containing initialisation parame * `params.log` can be used to write to Homebridge's log. * `params.config` is the accessory's configuration (as configured in `config.json`). This gives the codec access to the standard configuration settings, and lets it use its own if required. + * `params.publish` may be used to publish to MQTT directly + * `params.notify` may be used to send MQTT-Thing a property notification The `init()` function must return an object containing `encode()` and `decode()` functions (as described below). This can be just single `encode()` and `decode()` functions for all properties as above. More commonly a properties map containing property-specific functions is used, as follows: @@ -118,6 +120,24 @@ The `decode`() function is called to decode a message received from MQTT before The `decode()` function may either return the decoded message, or it may deliver it asynchronously by passing it as a parameter to the provided `output` function. If it does neither, no notification will be passed on to MQTT-Thing. +### `publish( topic, message )` + +The `publish()` function provided in `init()`'s `params` may be used to publish a message directly to MQTT. + + * `topic` is the MQTT topic to publish + * `message` is the message to publish to MQTT + +The message is published directly to MQTT, ignoring any apply function usually with the topic and not passing through the Codec's `encode()` function. + +### `notify( property, message )` + +The `notify()` function provided in `init()`'s `params` may be used to notify MQTT-Thing of the new value for a property. This will deliver the notification to all internal subscribers to the property. Note that generally a corresponding MQTT 'get' topic must have been configured in order for internal subscribers to exist. + + * `property` is the MQTT-Thing property to update + * `message` is the value to be passed to MQTT-Thing + +The message is passed directly to MQTT-Thing. It does not pass through any apply function or through the Codec's `decode()` function. + ## Empty Codec When writing a codec, you may find it helpful to start with the no-op implementation in [`test/empty-codec.js`](../test/empty-codec.js). diff --git a/docs/ReleaseNotes.md b/docs/ReleaseNotes.md index b047ef7..1d5d5ed 100644 --- a/docs/ReleaseNotes.md +++ b/docs/ReleaseNotes.md @@ -5,6 +5,10 @@ # Homebridge MQTT-Thing: Release Notes +### Version 1.1.12 ++ Extended codecs to support ad hoc property changes and MQTT publishing ++ Codec defaults changed to apply per-function + ### Version 1.1.11 + Fixed publishing of empty messages configured through config-ui-x in startPub (#253) diff --git a/libs/mqttlib.js b/libs/mqttlib.js index e82cdf3..5848296 100644 --- a/libs/mqttlib.js +++ b/libs/mqttlib.js @@ -13,7 +13,8 @@ var mqttlib = new function() { //! Context populated with mqttClient and mqttDispatch. this.init = function( ctx ) { // MQTT message dispatch - let mqttDispatch = ctx.mqttDispatch = {}; // map of topic to function( topic, message ) to handle + let mqttDispatch = ctx.mqttDispatch = {}; // map of topic to [ function( topic, message ) ] to handle + let propDispatch = ctx.propDispatch = {}; // map of proerty to [ rawhandler( topic, message ) ] let { config, log } = ctx; let logmqtt = config.logMqtt; @@ -76,9 +77,9 @@ var mqttlib = new function() { if (logmqtt) { log("Received MQTT: " + topic + " = " + message); } - var handlers = mqttDispatch[topic]; + let handlers = mqttDispatch[topic]; if (handlers) { - for( var i = 0; i < handlers.length; i++ ) { + for( let i = 0; i < handlers.length; i++ ) { handlers[ i ]( topic, message ); } } else { @@ -97,8 +98,27 @@ var mqttlib = new function() { log( 'Loading codec from ' + codecPath ); let codecMod = require( codecPath ); if( typeof codecMod.init === "function" ) { + + // direct publishing + let directPub = function( topic, message ) { + if( config.logMqtt ) { + log( 'Publishing MQTT: ' + topic + ' = ' + message ); + } + mqttClient.publish( topic, message.toString(), config.mqttPubOptions ); + }; + + // notification by property + let notifyByProp = function( property, message ) { + let handlers = propDispatch[ property ]; + if( handlers ) { + for( let i = 0; i < handlers.length; i++ ) { + handlers[ i ]( '_prop-' + property, message ); + } + } + }; + // initialise codec - let codec = ctx.codec = codecMod.init( { log, config } ); + let codec = ctx.codec = codecMod.init( { log, config, publish: directPub, notify: notifyByProp } ); if( codec ) { // encode/decode must be functions if( typeof codec.encode !== "function" ) { @@ -146,7 +166,8 @@ var mqttlib = new function() { // Subscribe this.subscribe = function( ctx, topic, property, handler ) { - let { mqttDispatch, log, mqttClient, codec } = ctx; + let rawHandler = handler; + let { mqttDispatch, log, mqttClient, codec, propDispatch } = ctx; if( ! mqttClient ) { log( 'ERROR: Call mqttlib.init() before mqttlib.subscribe()' ); return; @@ -187,6 +208,22 @@ var mqttlib = new function() { }; } } + + // register property dispatch (codec only) + if( codec ) { + if( propDispatch.hasOwnProperty( property ) ) { + // new handler for existing property + propDispatch[ property ].push( rawHandler ); + } else { + // new property + propDispatch[ property ] = [ rawHandler ]; + if( ctx.config.logMqtt ) { + log( 'Avalable codec notification property: ' + property ); + } + } + } + + // register MQTT dispatch and subscribe if( mqttDispatch.hasOwnProperty( topic ) ) { // new handler for existing topic mqttDispatch[ topic ].push( handler ); diff --git a/package.json b/package.json index 3f443de..677fc52 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "homebridge-mqttthing", - "version": "1.1.11", + "version": "1.1.12", "description": "Homebridge plugin supporting various services over MQTT", "main": "index.js", "scripts": { diff --git a/test/test-codec.js b/test/test-codec.js index 0d0f830..8b71550 100644 --- a/test/test-codec.js +++ b/test/test-codec.js @@ -15,11 +15,20 @@ */ function init( params ) { // extract parameters for convenience - let { log, config } = params; + let { log, config, publish, notify } = params; setTimeout( () => { - log( `Hello from test-codec.js. This is ${config.name}.` ); - }, 10000 ); + let msg = `Hello from test-codec.js. This is ${config.name}.`; + log( msg ); + + // publish a test message + publish( 'hello/mqtt', msg ); + + // update state + notify( 'on', config.onValue || 1 ); + notify( 'brightness', 50 ); + notify( 'HSV', '0,100,100' ); + }, 3000 ); /** * Encode message before sending.