Skip to content

Commit

Permalink
Merge branch 'master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
arachnetech authored Nov 27, 2021
2 parents d4e5a33 + 4ddc880 commit 098d2c7
Show file tree
Hide file tree
Showing 12 changed files with 430 additions and 59 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ test2/.uix-secrets
test2/auth.json
test2/config.json.*
test2/persist/ControllerStorage.*.json
test2/backups/*
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

## Compatibility with previous versions

**From version 1.1.33, MQTT-Thing requires Node.js 14 or later.**

**From version 1.1.x, raw JavaScript values for Boolean properties are passed to MQTT apply functions.** This may change published message formats, e.g. when apply functions are used to build JSON strings.

For full details of changes please see the [Release notes](docs/ReleaseNotes.md).
Expand Down
14 changes: 14 additions & 0 deletions config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,20 @@
"condition": {
"functionBody": "return ['weatherStation'].includes(model.type);"
}
},
"getmaxWind": {
"type": "string",
"description": "Topic used to notify mqttthing of 'wind speed [km/h]' (optional, Eve-only)",
"condition": {
"functionBody": "return ['weatherStation'].includes(model.type);"
}
},
"getDewPoint": {
"type": "string",
"description": "Topic used to notify mqttthing of 'DewPoint' (optional, Eve-only)",
"condition": {
"functionBody": "return ['weatherStation'].includes(model.type);"
}
},
"getActive": {
"type": "string",
Expand Down
2 changes: 1 addition & 1 deletion docs/Codecs.md
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ This section lists the properties available for each accessory type. All accesso

### Weather Station

`currentTemperature`, `statusActive`, `statusFault`, `statusTampered`, `statusLowBattery`, `currentRelativeHumidity`, `airPressure`, `weatherCondition`, `rain1h`, `rain24h`, `uvIndex`, `visibility`, `windDirection`, `windSpeed`
`currentTemperature`, `statusActive`, `statusFault`, `statusTampered`, `statusLowBattery`, `currentRelativeHumidity`, `airPressure`, `weatherCondition`, `rain1h`, `rain24h`, `uvIndex`, `visibility`, `windDirection`, `windSpeed`, `maxwindSpeed`, `Dewpoint`

### Window

Expand Down
4 changes: 2 additions & 2 deletions docs/Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ Avoid the use of "/" in characteristics of the Information Service (e.g. serial

### Confirmation

Some accessories support confirmation for some of their 'set' topics. When enabled by configuring `confirmationPeriodms`, the accessory *must* echo anything sent to appropriate `setX` subject(s) to the corresponding `getX` subject(s). Where homebridge-mqttthing doesn't see a confirmation within the configured configuration period (specified in milliseconds), it will publish the set message again. Messages will be republished up to 3 times by default, but this can be changed by also specifying `retryLimit`.
Some accessories support confirmation for some of their 'set' topics. When enabled by configuring `confirmationPeriodms`, the accessory *must* echo anything sent to appropriate `setX` subject(s) to the corresponding `getX` subject(s). Where homebridge-mqttthing doesn't see a confirmation within the configured confirmation period (specified in milliseconds), it will publish the set message again. Messages will be republished up to 3 times by default, but this can be changed by also specifying `retryLimit`.

Accessories supporting message confirmation list the topics supporting message confirmation below.

Expand Down Expand Up @@ -308,4 +308,4 @@ Any settings which apply to all services may be defined within the custom-type a

Custom accessories are only intended for use with simple services, not with accessories like 'weather station' which already combine multiple services.

Custom accessories cannot be configured through Config UI X.
Custom accessories cannot be configured through Config UI X.
13 changes: 13 additions & 0 deletions docs/ReleaseNotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,19 @@

# Homebridge MQTT-Thing: Release Notes

### Version 1.1.33
+ Revert change in 1.1.32. MQTT-Thing now requires Node.js 14 or later.
+ Engines updated to require Homebridge 1.3.5 and Node.js 14 (thanks, Donavan Becker)
+ Fixed typo in documentation (thanks, Brian White)
+ Fixed duration characteristic validation error message (thanks, Thomas Vandahl)
+ Moved codec loading earlier to allow codecs to manipulate the configuration (thanks, Martin)
+ Added water level characteristic to leak sensor (thanks, Moritz)
+ Added max wind and dewpoint characteristics to weather station (thanks, 2610)
+ Added jsonpath support (thanks, Antonio Yip)

### Version 1.1.32
+ Improve compatibility with older Node.js versions

### Version 1.1.31
+ Improve null handling (multicharacteristic) (thanks, Jakub Samek)
+ Added optimizePublishing option
Expand Down
58 changes: 54 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ var homebridgeLib = require( 'homebridge-lib' );
var fakegatoHistory = require( 'fakegato-history' );
var fs = require( "fs" );
var path = require( "path" );
var jp = require( "jsonpath" );
var mqttlib = require( './libs/mqttlib' );
const EventEmitter = require( 'events' );

Expand Down Expand Up @@ -46,8 +47,28 @@ function makeThing( log, accessoryConfig, api ) {
}

// MQTT Subscribe
function mqttSubscribe( topic, property, handler ) {
mqttlib.subscribe( ctx, topic, property, handler );
function mqttSubscribe( topicWithJsonpath, property, handler ) {
let jsonpathIndex = topicWithJsonpath.indexOf('$');
let topic = jsonpathIndex > 0 ? topicWithJsonpath.substring(0, jsonpathIndex) : topicWithJsonpath
let query = topicWithJsonpath.substring(jsonpathIndex);

mqttlib.subscribe( ctx, topic, property, function( topic, message ) {
log.debug(topic, String(message));

if (jsonpathIndex > 0 && query.length > 0) {
try {
let json = JSON.parse(message);
let values = jp.query(json, query);
log.debug(topic, query, values);
handler(topic, values.shift());
} catch(error) {
log.error(topic, "error:", error, "message:", message);
handler(topic, message);
}
} else {
handler(topic, message);
}
});
}

// MQTT Publish
Expand Down Expand Up @@ -1691,6 +1712,18 @@ function makeThing( log, accessoryConfig, api ) {
floatCharacteristic( service, 'windSpeed', Eve.Characteristics.WindSpeed, null, config.topics.getWindSpeed, 0 );
}

// Characteristic.maxWind (Eve-only)
function characteristic_MaximumWindSpeed( service ) {
service.addOptionalCharacteristic( Eve.Characteristics.MaximumWindSpeed ); // to avoid warnings
floatCharacteristic( service, 'maxWind', Eve.Characteristics.MaximumWindSpeed, null, config.topics.getmaxWind, 0 );
}

// Characteristic.Dewpoint(Eve-only)
function characteristic_DewPoint( service ) {
service.addOptionalCharacteristic( Eve.Characteristics.DewPoint ); // to avoid warnings
floatCharacteristic( service, 'DewPoint', Eve.Characteristics.DewPoint, null, config.topics.getDewPoint, 0 );
}

// Characteristic.ContactSensorState
function characteristic_ContactSensorState( service ) {
booleanCharacteristic( service, 'contactSensorState', Characteristic.ContactSensorState,
Expand Down Expand Up @@ -1995,6 +2028,12 @@ function makeThing( log, accessoryConfig, api ) {
}, undefined, config.resetStateAfterms );
}

// Characteristic.WaterLevel
function characteristic_WaterLevel( service ) {
let options = { minValue: 0, maxValue: 100 }
integerCharacteristic( service, 'waterLevel', Characteristic.WaterLevel, config.topics.setWaterLevel, config.topics.getWaterLevel, options);
}

// Characteristic.TargetPosition
function characteristic_TargetPosition( service ) {
integerCharacteristic( service, 'targetPosition', Characteristic.TargetPosition, config.topics.setTargetPosition, config.topics.getTargetPosition );
Expand Down Expand Up @@ -2450,11 +2489,11 @@ function makeThing( log, accessoryConfig, api ) {
}
if( !topic_setDuration ) {
/* no topic specified, but propery is still created internally */
addCharacteristic( service, property_setDuration, Characteristic.SetDuration, 30, function() {
addCharacteristic( service, property_setDuration, Characteristic.SetDuration, 1200, function() {
log.debug( 'set "' + property_setDuration + '" to ' + state[ property_setDuration ] + 's.' );
} );
} else {
integerCharacteristic( service, property_setDuration, Characteristic.SetDuration, topic_setDuration, topic_getDuration, { initialValue: 30 } );
integerCharacteristic( service, property_setDuration, Characteristic.SetDuration, topic_setDuration, topic_getDuration, { initialValue: 1200 } );
}
// minimum/maximum duration
if( config.minDuration !== undefined || config.maxDuration !== undefined ) {
Expand Down Expand Up @@ -2836,6 +2875,14 @@ function makeThing( log, accessoryConfig, api ) {
if( config.topics.getWindSpeed ) {
characteristic_WindSpeed( weatherSvc );
addWeatherSvc = true;
}
if( config.topics.getmaxWind ) {
characteristic_MaximumWindSpeed( weatherSvc );
addWeatherSvc = true;
}
if( config.topics.getDewPoint ) {
characteristic_DewPoint( weatherSvc );
addWeatherSvc = true;
}
if( addWeatherSvc ) {
services.push( weatherSvc );
Expand Down Expand Up @@ -2968,6 +3015,9 @@ function makeThing( log, accessoryConfig, api ) {
} else if( configType == "leakSensor" ) {
service = new Service.LeakSensor( name, subtype );
characteristic_LeakDetected( service );
if( config.topics.setWaterLevel || config.topics.getWaterLevel ) {
characteristic_WaterLevel( service );
}
addSensorOptionalCharacteristics( service );
} else if( configType == "microphone" ) {
service = new Service.Microphone( name, subtype );
Expand Down
95 changes: 49 additions & 46 deletions libs/mqttlib.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,52 @@ var mqttlib = new function() {
let logmqtt = config.logMqtt;
var clientId = 'mqttthing_' + config.name.replace(/[^\x20-\x7F]/g, "") + '_' + Math.random().toString(16).substr(2, 8);

// Load any codec
if( config.codec ) {
let codecPath = makeCodecPath( config.codec, ctx.homebridgePath );
if( fs.existsSync( codecPath ) ) {
// load codec
log( 'Loading codec from ' + codecPath );
let codecMod = require( codecPath );
if( typeof codecMod.init === "function" ) {

// direct publishing
let directPub = function( topic, message ) {
optimizedPublish( topic, message, ctx );
};

// 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, publish: directPub, notify: notifyByProp } );
if( codec ) {
// encode/decode must be functions
if( typeof codec.encode !== "function" ) {
log.warn( 'No codec encode() function' );
codec.encode = null;
}
if( typeof codec.decode !== "function" ) {
log.warn( 'No codec decode() function' );
codec.decode = null;
}
}
} else {
// no initialisation function
log.error( 'ERROR: No codec initialisation function returned from ' + codecPath );
}
} else {
log.error( 'ERROR: Codec file [' + codecPath + '] does not exist' );
}
}

// start with any configured options object
var options = config.mqttOptions || {};

Expand Down Expand Up @@ -143,52 +189,6 @@ var mqttlib = new function() {
}
});

// Load any codec
if( config.codec ) {
let codecPath = makeCodecPath( config.codec, ctx.homebridgePath );
if( fs.existsSync( codecPath ) ) {
// load codec
log( 'Loading codec from ' + codecPath );
let codecMod = require( codecPath );
if( typeof codecMod.init === "function" ) {

// direct publishing
let directPub = function( topic, message ) {
optimizedPublish( topic, message, ctx );
};

// 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, publish: directPub, notify: notifyByProp } );
if( codec ) {
// encode/decode must be functions
if( typeof codec.encode !== "function" ) {
log.warn( 'No codec encode() function' );
codec.encode = null;
}
if( typeof codec.decode !== "function" ) {
log.warn( 'No codec decode() function' );
codec.decode = null;
}
}
} else {
// no initialisation function
log.error( 'ERROR: No codec initialisation function returned from ' + codecPath );
}
} else {
log.error( 'ERROR: Codec file [' + codecPath + '] does not exist' );
}
}

ctx.mqttClient = mqttClient;
return mqttClient;
};
Expand Down Expand Up @@ -250,6 +250,9 @@ var mqttlib = new function() {
let decoded;
try {
decoded = applyFn( message, getApplyState( ctx, property ) );
if( config.logMqtt ) {
log( 'apply() function decoded message to [' + decoded + ']' );
}
} catch( ex ) {
log( 'Decode function apply( message) { ' + extendedTopic.apply + ' } failed for topic ' + topic + ' with message ' + message + ' - ' + ex );
}
Expand Down
Loading

0 comments on commit 098d2c7

Please sign in to comment.