Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[zWaveJS] New features suport : Battery, Illuminance, Binary Sensor, Alarm Sensor #2198

Merged
merged 12 commits into from
Feb 10, 2025
120 changes: 120 additions & 0 deletions server/services/zwavejs-ui/lib/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,56 @@ const multilevelSwitchCurtainsStateDefault = {
* Gladys format.
*/
const STATES = {
alarm_sensor: {
state: [{ converter: (val) => (val > 0 ? STATE.ON : STATE.OFF) }],
},
battery: {
level: [{ converter: (val) => val }],
islow: [
{
converter: (val) => {
switch (val) {
case false:
return STATE.OFF;
case true:
return STATE.ON;
default:
return null;
}
},
},
],
},
binary_sensor: {
any: [
{
converter: (val) => {
switch (val) {
case false:
return STATE.OFF;
case true:
return STATE.ON;
default:
return null;
}
},
},
],
general_purpose: [
{
converter: (val) => {
switch (val) {
case false:
return STATE.OFF;
case true:
return STATE.ON;
default:
return null;
}
},
},
],
},
binary_switch: {
currentvalue: [
{
Expand Down Expand Up @@ -72,6 +122,7 @@ const STATES = {
},
multilevel_sensor: {
air_temperature: [{ converter: (val) => val }],
illuminance: [{ converter: (val) => val }],
power: [{ converter: (val) => val }],
},
multilevel_switch: {
Expand Down Expand Up @@ -275,6 +326,58 @@ const ACTIONS = {
* List of supported features.
*/
const EXPOSES = {
alarm_sensor: {
state: {
category: DEVICE_FEATURE_CATEGORIES.MOTION_SENSOR,
type: DEVICE_FEATURE_TYPES.SENSOR.BINARY,
read_only: true,
keep_history: true,
has_feedback: true,
min: 0,
max: 1,
},
},
battery: {
level: {
category: DEVICE_FEATURE_CATEGORIES.BATTERY,
type: DEVICE_FEATURE_TYPES.SENSOR.INTEGER,
unit: DEVICE_FEATURE_UNITS.PERCENT,
read_only: true,
keep_history: true,
has_feedback: true,
min: 0,
max: 100,
},
islow: {
category: DEVICE_FEATURE_CATEGORIES.BATTERY_LOW,
type: DEVICE_FEATURE_TYPES.BATTERY_LOW.BINARY,
read_only: true,
keep_history: true,
has_feedback: true,
min: 0,
max: 1,
},
},
binary_sensor: {
any: {
category: DEVICE_FEATURE_CATEGORIES.MOTION_SENSOR,
type: DEVICE_FEATURE_TYPES.SENSOR.BINARY,
min: 0,
max: 1,
read_only: true,
has_feedback: true,
keep_history: true,
},
general_purpose: {
category: DEVICE_FEATURE_CATEGORIES.MOTION_SENSOR,
type: DEVICE_FEATURE_TYPES.SENSOR.BINARY,
min: 0,
max: 1,
read_only: true,
has_feedback: true,
keep_history: true,
},
},
binary_switch: {
currentvalue: {
category: DEVICE_FEATURE_CATEGORIES.SWITCH,
Expand Down Expand Up @@ -308,6 +411,16 @@ const EXPOSES = {
read_only: true,
has_feedback: false,
},
illuminance: {
category: DEVICE_FEATURE_CATEGORIES.LIGHT_SENSOR,
type: DEVICE_FEATURE_TYPES.SENSOR.DECIMAL,
unit: DEVICE_FEATURE_UNITS.LUX,
min: 0,
max: 100000,
keep_history: true,
read_only: true,
has_feedback: false,
},
power: {
category: DEVICE_FEATURE_CATEGORIES.ENERGY_SENSOR,
type: DEVICE_FEATURE_TYPES.ENERGY_SENSOR.POWER,
Expand Down Expand Up @@ -473,15 +586,22 @@ const EXPOSES = {
};

const COMMANDCLASS = {
ALARM_SENSOR: 156,
BINARY_SENSOR: 48,
BINARY_SWITCH: 37,
MULTILEVEL_SWITCH: 38,
};

const PRODUCTID = {
FIBARO_FGMS001: '271-4097-2048',
};

module.exports = {
ACTIONS,
CONFIGURATION,
EXPOSES,
STATES,
PARAMS,
COMMANDCLASS,
PRODUCTID,
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const { getDeviceFeatureId } = require('../utils/convertToGladysDevice');
const getProperty = require('../utils/getProperty');

/**
* @description This will be called when new Z-Wave node value is updated.
* @description This will be called when a Z-Wave node value is updated.
* @param {object} message - Data sent by ZWave JS UI.
* @returns {Promise} - Promise execution.
* @example zwaveJSUI.onNodeValueUpdated({data: [{node}, {value}]});
Expand Down
80 changes: 80 additions & 0 deletions server/services/zwavejs-ui/lib/zwaveJSUI.refineCategory.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
const { DEVICE_FEATURE_CATEGORIES } = require('../../../utils/constants');
const { COMMANDCLASS } = require('./constants');

/**
* @description Identify a more precise category if we can.
* @param {object} exposed - An exposed feature found.
* @param {object} zwaveNodeValue - The value received from zWave.
* @example refineCategory([{name: '', feature: {category: 'general-sensor', ..}}], {id: 41, ...})
*/
function refineCategory(exposed, zwaveNodeValue) {
const { commandClass } = zwaveNodeValue;

if (
commandClass === COMMANDCLASS.BINARY_SENSOR &&
exposed.feature.category === DEVICE_FEATURE_CATEGORIES.MOTION_SENSOR
) {
const sensorType = zwaveNodeValue.ccSpecific?.sensorType ?? -1;
switch (sensorType) {
case 0x02:
exposed.feature.category = DEVICE_FEATURE_CATEGORIES.SMOKE_SENSOR;
break;
case 0x03:
exposed.feature.category = DEVICE_FEATURE_CATEGORIES.CO_SENSOR;
break;
case 0x04:
exposed.feature.category = DEVICE_FEATURE_CATEGORIES.CO2_SENSOR;
break;
case 0x05:
exposed.feature.category = DEVICE_FEATURE_CATEGORIES.TEMPERATURE_SENSOR;
break;
case 0x06:
exposed.feature.category = DEVICE_FEATURE_CATEGORIES.LEAK_SENSOR;
break;
case 0x07:
exposed.feature.category = DEVICE_FEATURE_CATEGORIES.TEMPERATURE_SENSOR;
break;
case 0x0a:
exposed.feature.category = DEVICE_FEATURE_CATEGORIES.OPENING_SENSOR;
break;
case 0x0b:
exposed.feature.category = DEVICE_FEATURE_CATEGORIES.MOTION_SENSOR;
break;
case 0x0c:
exposed.feature.category = DEVICE_FEATURE_CATEGORIES.MOTION_SENSOR;
break;
default:
break;
}
}

if (
commandClass === COMMANDCLASS.ALARM_SENSOR &&
exposed.feature.category === DEVICE_FEATURE_CATEGORIES.MOTION_SENSOR
) {
const sensorType = zwaveNodeValue.ccSpecific?.sensorType ?? -1;
switch (sensorType) {
case 0x01:
exposed.feature.category = DEVICE_FEATURE_CATEGORIES.SMOKE_SENSOR;
break;
case 0x02:
exposed.feature.category = DEVICE_FEATURE_CATEGORIES.CO_SENSOR;
break;
case 0x03:
exposed.feature.category = DEVICE_FEATURE_CATEGORIES.CO2_SENSOR;
break;
case 0x04:
exposed.feature.category = DEVICE_FEATURE_CATEGORIES.TEMPERATURE_SENSOR;
break;
case 0x05:
exposed.feature.category = DEVICE_FEATURE_CATEGORIES.LEAK_SENSOR;
break;
default:
break;
}
}
}

module.exports = {
refineCategory,
};
35 changes: 32 additions & 3 deletions server/services/zwavejs-ui/utils/convertToGladysDevice.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
const cleanNames = require('./cleanNames');

const { EXPOSES, PARAMS, COMMANDCLASS } = require('../lib/constants');
const { EXPOSES, PARAMS, COMMANDCLASS, PRODUCTID } = require('../lib/constants');
const getProperty = require('./getProperty');
const { refineCategory } = require('../lib/zwaveJSUI.refineCategory');

const getDeviceFeatureId = (nodeId, commandClassName, endpoint, propertyName, propertyKeyName, featureName) => {
const propertyKeyNameClean = cleanNames(propertyKeyName);
Expand All @@ -15,13 +16,15 @@ const getDeviceFeatureId = (nodeId, commandClassName, endpoint, propertyName, pr
* For example: remove a Binary Switch sent by a device on a
* Multilevel Switch (we do manage a virtual one on Gladys).
* @param {Array} features - Detected features on the node.
* @param {object} zwaveJsDevice - The ZwaveJs device.
* @returns {Array} The cleaned up features.
* @example cleanupFeatures(features)
*/
function cleanupFeatures(features) {
function cleanupFeatures(features, zwaveJsDevice) {
let localFeatures = features;
// ------------------------------
// Multilevel Switch special case
// ------------------------------
// Some Multilevel Switch device have an explicit Binary Switch
// exposed some others not (Qubino vs Fibaro for example). As for
// devices that do not expose any Binary Switch value, we manage
Expand All @@ -34,6 +37,29 @@ function cleanupFeatures(features) {
localFeatures = localFeatures.filter((f) => f.command_class !== COMMANDCLASS.BINARY_SWITCH);
}

// ----------------------------------------------
// Fibaro Motion Sensor special case (FGMS-001)
// ----------------------------------------------
// Some Fibaro Motion Sensor have an explicit Binary Sensor General Purpose some others don't.
// We so need to deal with those having the General Purpose and not rely on the "Any" feature.
// On the other hand, those not exposing the General Purpose are correctly handled by the "Any" feature.
// The alarm sensor seems not usefull, so we remove it.
if (zwaveJsDevice.deviceId === PRODUCTID.FIBARO_FGMS001) {
// Remove the Alarm Sensor
localFeatures = localFeatures.filter((f) => f.command_class !== COMMANDCLASS.ALARM_SENSOR);

// Remove the Any Binary Sensor if the General Purpose is present
if (
localFeatures.some(
(f) => f.command_class === COMMANDCLASS.BINARY_SENSOR && cleanNames(f.property_name) === 'general_purpose',
)
) {
localFeatures = localFeatures.filter(
(f) => !(f.command_class === COMMANDCLASS.BINARY_SENSOR && cleanNames(f.property_name) === 'any'),
);
}
}

// Add any other special cleanup necessary... Please, provide an explanation

return localFeatures;
Expand Down Expand Up @@ -75,6 +101,7 @@ const convertToGladysDevice = (serviceId, zwaveJsDevice) => {
if (!exposeFound.feature.category) {
return;
}

const deviceFeatureId = getDeviceFeatureId(
zwaveJsDevice.id,
commandClassName,
Expand All @@ -84,6 +111,8 @@ const convertToGladysDevice = (serviceId, zwaveJsDevice) => {
exposeFound.name,
);

refineCategory(exposeFound, value);

features.push({
...exposeFound.feature,
name: `${value.id}${exposeFound.name !== '' ? `:${exposeFound.name}` : ''}`,
Expand All @@ -110,7 +139,7 @@ const convertToGladysDevice = (serviceId, zwaveJsDevice) => {
selector: `zwavejs-ui:${zwaveJsDevice.id}`,
service_id: serviceId,
should_poll: false,
features: cleanupFeatures(features),
features: cleanupFeatures(features, zwaveJsDevice),
params,
};
};
Expand Down
Loading
Loading