diff --git a/front/src/components/boxs/device-in-room/DeviceRow.jsx b/front/src/components/boxs/device-in-room/DeviceRow.jsx
index c490cf2a1e..d4e3f32b1c 100644
--- a/front/src/components/boxs/device-in-room/DeviceRow.jsx
+++ b/front/src/components/boxs/device-in-room/DeviceRow.jsx
@@ -12,6 +12,7 @@ import NumberDeviceFeature from './device-features/NumberDeviceFeature';
import CoverDeviceFeature from './device-features/CoverDeviceFeature';
import ThermostatDeviceFeature from './device-features/ThermostatDeviceFeature';
import AirConditioningModeDeviceFeature from './device-features/AirConditioningModeDeviceFeature';
+import LMHVolumeDeviceFeature from './device-features/LMHVolumeDeviceFeature';
const ROW_TYPE_BY_FEATURE_TYPE = {
[DEVICE_FEATURE_TYPES.LIGHT.BINARY]: BinaryDeviceFeature,
@@ -27,7 +28,10 @@ const ROW_TYPE_BY_FEATURE_TYPE = {
[DEVICE_FEATURE_TYPES.CURTAIN.POSITION]: MultiLevelDeviceFeature,
[DEVICE_FEATURE_TYPES.THERMOSTAT.TARGET_TEMPERATURE]: ThermostatDeviceFeature,
[DEVICE_FEATURE_TYPES.AIR_CONDITIONING.MODE]: AirConditioningModeDeviceFeature,
- [DEVICE_FEATURE_TYPES.AIR_CONDITIONING.TARGET_TEMPERATURE]: ThermostatDeviceFeature
+ [DEVICE_FEATURE_TYPES.AIR_CONDITIONING.TARGET_TEMPERATURE]: ThermostatDeviceFeature,
+ [DEVICE_FEATURE_TYPES.SIREN.LMH_VOLUME]: LMHVolumeDeviceFeature,
+ [DEVICE_FEATURE_TYPES.SIREN.MELODY]: NumberDeviceFeature,
+ [DEVICE_FEATURE_TYPES.DURATION.DECIMAL]: MultiLevelDeviceFeature
};
const DeviceRow = ({ children, ...props }) => {
diff --git a/front/src/components/boxs/device-in-room/SupportedFeatureTypes.jsx b/front/src/components/boxs/device-in-room/SupportedFeatureTypes.jsx
index bd5c5d1705..a4e8516a8b 100644
--- a/front/src/components/boxs/device-in-room/SupportedFeatureTypes.jsx
+++ b/front/src/components/boxs/device-in-room/SupportedFeatureTypes.jsx
@@ -12,7 +12,10 @@ const SUPPORTED_FEATURE_TYPES = [
DEVICE_FEATURE_TYPES.SHUTTER.STATE,
DEVICE_FEATURE_TYPES.THERMOSTAT.TARGET_TEMPERATURE,
DEVICE_FEATURE_TYPES.AIR_CONDITIONING.MODE,
- DEVICE_FEATURE_TYPES.AIR_CONDITIONING.TARGET_TEMPERATURE
+ DEVICE_FEATURE_TYPES.AIR_CONDITIONING.TARGET_TEMPERATURE,
+ DEVICE_FEATURE_TYPES.SIREN.LMH_VOLUME,
+ DEVICE_FEATURE_TYPES.SIREN.MELODY,
+ DEVICE_FEATURE_TYPES.DURATION.DECIMAL
];
export default SUPPORTED_FEATURE_TYPES;
diff --git a/front/src/components/boxs/device-in-room/device-features/LMHVolumeDeviceFeature.jsx b/front/src/components/boxs/device-in-room/device-features/LMHVolumeDeviceFeature.jsx
new file mode 100644
index 0000000000..36f417f62f
--- /dev/null
+++ b/front/src/components/boxs/device-in-room/device-features/LMHVolumeDeviceFeature.jsx
@@ -0,0 +1,69 @@
+import get from 'get-value';
+import { Text } from 'preact-i18n';
+import cx from 'classnames';
+
+import { DeviceFeatureCategoriesIcon } from '../../../../utils/consts';
+import { SIREN_LMH_VOLUME } from '../../../../../../server/utils/constants';
+
+const LMHVolumeDeviceFeature = ({ children, ...props }) => {
+ const { deviceFeature } = props;
+ const { category, type, last_value: lastValue } = deviceFeature;
+
+ function updateValue(value) {
+ props.updateValueWithDebounce(deviceFeature, value);
+ }
+
+ function low() {
+ updateValue(SIREN_LMH_VOLUME.LOW);
+ }
+
+ function medium() {
+ updateValue(SIREN_LMH_VOLUME.MEDIUM);
+ }
+
+ function high() {
+ updateValue(SIREN_LMH_VOLUME.HIGH);
+ }
+
+ return (
+
+
+
+ |
+ {props.rowName} |
+
+
+
+ |
+
+ );
+};
+
+export default LMHVolumeDeviceFeature;
diff --git a/front/src/config/i18n/de.json b/front/src/config/i18n/de.json
index cbc9bbe528..7a8a9ee6c0 100644
--- a/front/src/config/i18n/de.json
+++ b/front/src/config/i18n/de.json
@@ -2598,6 +2598,13 @@
"cooling": "Kühlen",
"heating": "Heizen"
}
+ },
+ "siren": {
+ "volume": {
+ "low": "Niedrig",
+ "medium": "Mittel",
+ "high": "Hoch"
+ }
}
}
},
@@ -2792,7 +2799,9 @@
},
"siren": {
"shortCategoryName": "Sirene",
- "binary": "Sirene"
+ "binary": "Sirene",
+ "volume": "Lautstärke der Sirene",
+ "melody": "Melodie"
},
"cube": {
"shortCategoryName": "Würfel",
diff --git a/front/src/config/i18n/en.json b/front/src/config/i18n/en.json
index 630912132a..08a56e0d65 100644
--- a/front/src/config/i18n/en.json
+++ b/front/src/config/i18n/en.json
@@ -2600,6 +2600,13 @@
"cooling": "Cool",
"heating": "Heat"
}
+ },
+ "siren": {
+ "volume": {
+ "low": "Low",
+ "medium": "Medium",
+ "high": "High"
+ }
}
}
},
@@ -2794,7 +2801,9 @@
},
"siren": {
"shortCategoryName": "Siren",
- "binary": "Siren"
+ "binary": "Siren",
+ "volume": "Siren volume",
+ "melody": "Melody"
},
"cube": {
"shortCategoryName": "Cube",
diff --git a/front/src/config/i18n/fr.json b/front/src/config/i18n/fr.json
index 74e11bcf48..898238441e 100644
--- a/front/src/config/i18n/fr.json
+++ b/front/src/config/i18n/fr.json
@@ -2602,6 +2602,13 @@
"cooling": "Clim.",
"heating": "Chauffage"
}
+ },
+ "siren": {
+ "volume": {
+ "low": "Faible",
+ "medium": "Moyen",
+ "high": "Fort"
+ }
}
}
},
@@ -2796,7 +2803,9 @@
},
"siren": {
"shortCategoryName": "Sirène",
- "binary": "Sirène On/Off"
+ "binary": "Sirène On/Off",
+ "volume": "Volume de la sirène",
+ "melody": "Mélodie"
},
"cube": {
"shortCategoryName": "Cube",
diff --git a/front/src/utils/consts.js b/front/src/utils/consts.js
index c2ff7589ba..62336bea09 100644
--- a/front/src/utils/consts.js
+++ b/front/src/utils/consts.js
@@ -224,7 +224,9 @@ export const DeviceFeatureCategoriesIcon = {
[DEVICE_FEATURE_TYPES.SENSOR.INTEGER]: 'cloud'
},
[DEVICE_FEATURE_CATEGORIES.SIREN]: {
- [DEVICE_FEATURE_TYPES.SIREN.BINARY]: 'bell'
+ [DEVICE_FEATURE_TYPES.SIREN.BINARY]: 'bell',
+ [DEVICE_FEATURE_TYPES.SIREN.LMH_VOLUME]: 'volume-1',
+ [DEVICE_FEATURE_TYPES.SIREN.MELODY]: 'music'
},
[DEVICE_FEATURE_CATEGORIES.TAMPER]: {
[DEVICE_FEATURE_TYPES.SENSOR.BINARY]: 'shield'
diff --git a/server/services/zigbee2mqtt/exposes/enumType.js b/server/services/zigbee2mqtt/exposes/enumType.js
index 46e7280425..335cd216e9 100644
--- a/server/services/zigbee2mqtt/exposes/enumType.js
+++ b/server/services/zigbee2mqtt/exposes/enumType.js
@@ -3,6 +3,7 @@ const {
DEVICE_FEATURE_TYPES,
BUTTON_STATUS,
COVER_STATE,
+ SIREN_LMH_VOLUME,
} = require('../../../utils/constants');
const WRITE_VALUE_MAPPING = {};
@@ -80,9 +81,17 @@ addMapping('state', COVER_STATE.OPEN, 'OPEN');
addMapping('state', COVER_STATE.CLOSE, 'CLOSE');
addMapping('state', COVER_STATE.STOP, 'STOP');
+addMapping('volume', SIREN_LMH_VOLUME.LOW, 'low');
+addMapping('volume', SIREN_LMH_VOLUME.MEDIUM, 'medium');
+addMapping('volume', SIREN_LMH_VOLUME.HIGH, 'high');
+
module.exports = {
type: 'enum',
writeValue: (expose, value) => {
+ if (expose.name === 'melody') {
+ return value;
+ }
+
const relatedValue = (WRITE_VALUE_MAPPING[expose.name] || {})[value];
if (relatedValue && expose.values.includes(relatedValue)) {
@@ -92,6 +101,11 @@ module.exports = {
return undefined;
},
readValue: (expose, value) => {
+ if (expose.name === 'melody') {
+ const intValue = parseInt(value, 10);
+ return intValue;
+ }
+
const subValue = value.replace(/^(\d+_)?/, '');
return (READ_VALUE_MAPPING[expose.name] || {})[subValue];
},
@@ -117,6 +131,18 @@ module.exports = {
},
},
},
+ volume: {
+ feature: {
+ category: DEVICE_FEATURE_CATEGORIES.SIREN,
+ type: DEVICE_FEATURE_TYPES.SIREN.LMH_VOLUME,
+ },
+ },
+ melody: {
+ feature: {
+ category: DEVICE_FEATURE_CATEGORIES.SIREN,
+ type: DEVICE_FEATURE_TYPES.SIREN.MELODY,
+ },
+ },
},
getFeatureIndexes: (values = []) => {
const indexes = values
diff --git a/server/services/zigbee2mqtt/exposes/numericType.js b/server/services/zigbee2mqtt/exposes/numericType.js
index cdc9affaad..05c3f296e6 100644
--- a/server/services/zigbee2mqtt/exposes/numericType.js
+++ b/server/services/zigbee2mqtt/exposes/numericType.js
@@ -33,6 +33,15 @@ module.exports = {
max: 100,
},
},
+ battpercentage: {
+ feature: {
+ category: DEVICE_FEATURE_CATEGORIES.BATTERY,
+ type: DEVICE_FEATURE_TYPES.SENSOR.INTEGER,
+ unit: DEVICE_FEATURE_UNITS.PERCENT,
+ min: 0,
+ max: 100,
+ },
+ },
brightness: {
types: {
light: {
diff --git a/server/test/services/zigbee2mqtt/exposes/melodyEnumType.test.js b/server/test/services/zigbee2mqtt/exposes/melodyEnumType.test.js
new file mode 100644
index 0000000000..5b770c0878
--- /dev/null
+++ b/server/test/services/zigbee2mqtt/exposes/melodyEnumType.test.js
@@ -0,0 +1,19 @@
+const { assert } = require('chai');
+
+const enumType = require('../../../../services/zigbee2mqtt/exposes/enumType');
+
+describe('zigbee2mqtt melody enumType', () => {
+ const expose = {
+ name: 'melody',
+ };
+
+ it('should write value', () => {
+ const result = enumType.writeValue(expose, 1);
+ assert.equal(result, 1);
+ });
+
+ it(`should read value`, () => {
+ const result = enumType.readValue(expose, '1');
+ assert.equal(result, 1);
+ });
+});
diff --git a/server/test/services/zigbee2mqtt/exposes/volumeEnumType.test.js b/server/test/services/zigbee2mqtt/exposes/volumeEnumType.test.js
new file mode 100644
index 0000000000..c642c73202
--- /dev/null
+++ b/server/test/services/zigbee2mqtt/exposes/volumeEnumType.test.js
@@ -0,0 +1,47 @@
+const { assert } = require('chai');
+
+const enumType = require('../../../../services/zigbee2mqtt/exposes/enumType');
+const { SIREN_LMH_VOLUME } = require('../../../../utils/constants');
+
+describe('zigbee2mqtt volume enumType', () => {
+ const expose = {
+ name: 'volume',
+ values: ['low', 'medium', 'high'],
+ };
+
+ [
+ { enumValue: 'low', intValue: SIREN_LMH_VOLUME.LOW },
+ { enumValue: 'medium', intValue: SIREN_LMH_VOLUME.MEDIUM },
+ { enumValue: 'high', intValue: SIREN_LMH_VOLUME.HIGH },
+ ].forEach((mapping) => {
+ const { enumValue, intValue } = mapping;
+
+ it(`should write ${enumValue} value as ${intValue} value`, () => {
+ const result = enumType.writeValue(expose, intValue);
+ assert.equal(result, enumValue);
+ });
+
+ it(`should read ${intValue} value as ${enumValue}`, () => {
+ const result = enumType.readValue(expose, enumValue);
+ assert.equal(result, intValue);
+ });
+ });
+
+ it('should write undefined value on missing enum', () => {
+ const missingEnumExpose = {
+ values: ['low', 'medium'],
+ };
+ const result = enumType.writeValue(missingEnumExpose, SIREN_LMH_VOLUME.HIGH);
+ assert.equal(result, undefined);
+ });
+
+ it('should write undefined value', () => {
+ const result = enumType.writeValue(expose, 7);
+ assert.equal(result, undefined);
+ });
+
+ it('should read enum value', () => {
+ const result = enumType.readValue(expose, 'unknown');
+ assert.equal(result, undefined);
+ });
+});
diff --git a/server/utils/constants.js b/server/utils/constants.js
index 908a28f72f..b6357880d7 100644
--- a/server/utils/constants.js
+++ b/server/utils/constants.js
@@ -63,6 +63,12 @@ const COVER_STATE = {
CLOSE: -1,
};
+const SIREN_LMH_VOLUME = {
+ LOW: 0,
+ MEDIUM: 1,
+ HIGH: 2,
+};
+
const AC_MODE = {
AUTO: 0,
COOLING: 1,
@@ -498,6 +504,8 @@ const DEVICE_FEATURE_TYPES = {
},
SIREN: {
BINARY: 'binary',
+ LMH_VOLUME: 'volume',
+ MELODY: 'melody',
},
CHILD_LOCK: {
BINARY: 'binary',
@@ -1111,6 +1119,7 @@ const ALARM_MODES_LIST = createList(ALARM_MODES);
module.exports.STATE = STATE;
module.exports.BUTTON_STATUS = BUTTON_STATUS;
module.exports.COVER_STATE = COVER_STATE;
+module.exports.SIREN_LMH_VOLUME = SIREN_LMH_VOLUME;
module.exports.AC_MODE = AC_MODE;
module.exports.EVENTS = EVENTS;
module.exports.LIFE_EVENTS = LIFE_EVENTS;