UNPKG

@irusland/homebridge-mqttthing

Version:

Homebridge plugin supporting various services over MQTT (with TLS fixes)

180 lines (164 loc) 6.02 kB
/** * Homebridge-MQTTThing JSON Codec (encoder/decoder) * codecs/json.js * * Add configuration giving JSON 'path' for each property used by the accessory. For example: * "jsonCodec": { * "properties": { * "on": "state.power", * "RGB": "state.rgb" * }, * "fixed": { fixed properties object (global/default) }, * "fixedByTopic": { * "topic1": { fixed properties object for topic1 }, * "topic2": { fixed properties object for topic2 } * }, * "retain": true|false * } * * Set retain: true in order to retain the object published for each topic, so that unchanged properties are published. * (Default is retain: false, recreating object from fixed properties on every publish.) */ 'use strict'; function splitJPath( jpath ) { return jpath.split( '.' ); } function setJson( msg, jpath, val ) { let obj = msg; let apath = splitJPath( jpath ); for( let i = 0; i < apath.length - 1; i++ ) { let item = apath[ i ]; if( ! obj.hasOwnProperty( item ) ) { obj[ item ] = {}; } obj = obj[ item ]; } obj[ apath[ apath.length - 1 ] ] = val; } function getJson( msg, jpath ) { let val = msg; for( let pi of splitJPath( jpath ) ) { if( val.hasOwnProperty( pi ) ) { val = val[ pi ]; } else { return; } } return val; } /** * Initialise codec for accessory * @param {object} params Initialisation parameters object * @param {function} params.log Logging function * @param {object} params.config Configuration * @param {function} params.publish Function to publish a message directly to MQTT * @param {function} params.notify Function to send MQTT-Thing a property notification * @return {object} Encode and/or decode functions */ function init( params ) { // extract parameters for convenience let { log, config } = params; let jsonConfig = config.jsonCodec; if( ! jsonConfig ) { log.warn( 'Add jsonCodec object to configuration' ); } let readJPath = function( prop ) { if( jsonConfig && jsonConfig.properties ) { return jsonConfig.properties[ prop ]; } }; let emptyMessage = function( topic ) { if( jsonConfig ) { if( jsonConfig.fixedByTopic && jsonConfig.fixedByTopic[ topic ] ) { return JSON.parse( JSON.stringify( jsonConfig.fixedByTopic[ topic ] ) ); } else if( jsonConfig.fixed ) { return JSON.parse( JSON.stringify( jsonConfig.fixed ) ); } } return {}; }; // pending messages/timers by MQTT topic let pending = {}; // get message object which will be published (automatically) let publishMessage = function( topic, publish ) { let entry = pending[ topic ]; if( entry ) { // existing entry - clear any timer if( entry.tmr ) { clearTimeout( entry.tmr ); } } else { // new entry entry = pending[ topic ] = { msg: emptyMessage( topic ) }; } // publish later entry.tmr = setTimeout( () => { if( jsonConfig && jsonConfig.retain ) { // retain: just clear timer - keep the message entry.tmr = null; } else { // no retain: remove entry pending[ topic ] = null; } publish( JSON.stringify( entry.msg ) ); }, 50 ); return entry.msg; } /** * Encode message before sending. * The output function may be called to deliver an encoded value for the property later. * @param {string} message Message from mqttthing to be published to MQTT * @param {object} info Object giving contextual information * @param {string} info.topic MQTT topic to be published * @param {string} info.property Property associated with publishing operation * @param {function} output Function which may be called to deliver the encoded value asynchronously * @returns {string} Processed message (optionally) */ function encode( message, info, output ) { // eslint-disable-line no-unused-vars let diag = ! jsonConfig || jsonConfig.diag; let jpath = readJPath( info.property ); if( jpath ) { let msg = publishMessage( info.topic, output ); setJson( msg, jpath, message ); } else { diag = true; } if( diag ) { log( `json-codec: encode() called for topic [${info.topic}], property [${info.property}] with message [${message}]` ); } } /** * Decode received message, and optionally return decoded value. * The output function may be called to deliver a decoded value for the property later. * @param {string} message Message received from MQTT * @param {object} info Object giving contextual information * @param {string} info.topic MQTT topic received * @param {string} info.property Property associated with subscription * @param {function} output Function which may be called to deliver the decoded value asynchronously * @returns {string} Processed message (optionally) */ function decode( message, info, output ) { // eslint-disable-line no-unused-vars let diag = ! jsonConfig || jsonConfig.diag; let jpath = readJPath( info.property ); let decoded; if( jpath ) { let msg = JSON.parse( message ); decoded = getJson( msg, jpath ); } else { diag = true; } if( diag ) { log( `json-codec: decode() called for topic [${info.topic}], property [${info.property}] with message [${message}]` ); } return decoded; } // return encode and decode functions return { encode, decode }; } // export initialisation function module.exports = { init };