Skip to content

Commit

Permalink
Extended codecs to support ad hoc property changes and MQTT publishing
Browse files Browse the repository at this point in the history
  • Loading branch information
arachnetech committed May 4, 2020
1 parent 7358592 commit 68ff6c8
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 9 deletions.
20 changes: 20 additions & 0 deletions docs/Codecs.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down Expand Up @@ -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).
Expand Down
4 changes: 4 additions & 0 deletions docs/ReleaseNotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
47 changes: 42 additions & 5 deletions libs/mqttlib.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand All @@ -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" ) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 );
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
15 changes: 12 additions & 3 deletions test/test-codec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit 68ff6c8

Please sign in to comment.