diff --git a/converters/fromZigbee.js b/converters/fromZigbee.js index 1b3de125607da..eee13a66e1ac6 100644 --- a/converters/fromZigbee.js +++ b/converters/fromZigbee.js @@ -231,6 +231,7 @@ const converters = { lock_pin_code_response: { cluster: 'closuresDoorLock', type: ['commandGetPinCodeRsp'], + options: [exposes.options.expose_pin()], convert: (model, msg, publish, options, meta) => { const {data} = msg; let status = constants.lockUserStatus[data.userstatus]; @@ -251,6 +252,7 @@ const converters = { lock_user_status_response: { cluster: 'closuresDoorLock', type: ['commandGetUserStatusRsp'], + options: [exposes.options.expose_pin()], convert: (model, msg, publish, options, meta) => { const {data} = msg; @@ -331,6 +333,7 @@ const converters = { humidity: { cluster: 'msRelativeHumidity', type: ['attributeReport', 'readResponse'], + options: [exposes.options.precision('humidity'), exposes.options.calibration('humidity')], convert: (model, msg, publish, options, meta) => { const humidity = parseFloat(msg.data['measuredValue']) / 100.0; @@ -345,6 +348,7 @@ const converters = { soil_moisture: { cluster: 'msSoilMoisture', type: ['attributeReport', 'readResponse'], + options: [exposes.options.precision('soil_moisture'), exposes.options.calibration('soil_moisture')], convert: (model, msg, publish, options, meta) => { const soilMoisture = parseFloat(msg.data['measuredValue']) / 100.0; return {soil_moisture: calibrateAndPrecisionRoundOptions(soilMoisture, options, 'soil_moisture')}; @@ -353,6 +357,8 @@ const converters = { illuminance: { cluster: 'msIlluminanceMeasurement', type: ['attributeReport', 'readResponse'], + options: [exposes.options.precision('illuminance'), exposes.options.calibration('illuminance', 'percentual'), + exposes.options.precision('illuminance_lux'), exposes.options.calibration('illuminance_lux', 'percentual')], convert: (model, msg, publish, options, meta) => { // DEPRECATED: only return lux here (change illuminance_lux -> illuminance) const illuminance = msg.data['measuredValue']; @@ -366,6 +372,7 @@ const converters = { pressure: { cluster: 'msPressureMeasurement', type: ['attributeReport', 'readResponse'], + options: [exposes.options.precision('pressure'), exposes.options.calibration('pressure')], convert: (model, msg, publish, options, meta) => { let pressure = 0; if (msg.data.hasOwnProperty('scaledValue')) { @@ -400,6 +407,7 @@ const converters = { // Therefore we need to publish the no_motion detected by ourselves. cluster: 'msOccupancySensing', type: ['attributeReport', 'readResponse'], + options: [exposes.options.occupancy_timeout(), exposes.options.no_occupancy_since()], convert: (model, msg, publish, options, meta) => { if (msg.data.occupancy !== 1) { // In case of 0 no occupancy is reported. @@ -510,6 +518,7 @@ const converters = { color_colortemp: { cluster: 'lightingColorCtrl', type: ['attributeReport', 'readResponse'], + options: [exposes.options.color_sync()], convert: (model, msg, publish, options, meta) => { const result = {}; @@ -757,6 +766,7 @@ const converters = { ias_vibration_alarm_1_with_timeout: { cluster: 'ssIasZone', type: 'commandStatusChangeNotification', + options: [exposes.options.vibration_timeout()], convert: (model, msg, publish, options, meta) => { const zoneStatus = msg.data.zonestatus; @@ -941,6 +951,7 @@ const converters = { ias_occupancy_alarm_1_with_timeout: { cluster: 'ssIasZone', type: 'commandStatusChangeNotification', + options: [exposes.options.occupancy_timeout()], convert: (model, msg, publish, options, meta) => { const zoneStatus = msg.data.zonestatus; const timeout = options && options.hasOwnProperty('occupancy_timeout') ? @@ -1068,6 +1079,7 @@ const converters = { command_move_to_level: { cluster: 'genLevelCtrl', type: ['commandMoveToLevel', 'commandMoveToLevelWithOnOff'], + options: [exposes.options.simulated_brightness()], convert: (model, msg, publish, options, meta) => { const payload = { action: postfixWithEndpointName(`brightness_move_to_level`, msg, model), @@ -1087,6 +1099,7 @@ const converters = { command_move: { cluster: 'genLevelCtrl', type: ['commandMove', 'commandMoveWithOnOff'], + options: [exposes.options.simulated_brightness()], convert: (model, msg, publish, options, meta) => { const direction = msg.data.movemode === 1 ? 'down' : 'up'; const action = postfixWithEndpointName(`brightness_move_${direction}`, msg, model); @@ -1120,6 +1133,7 @@ const converters = { command_step: { cluster: 'genLevelCtrl', type: ['commandStep', 'commandStepWithOnOff'], + options: [exposes.options.simulated_brightness()], convert: (model, msg, publish, options, meta) => { const direction = msg.data.stepmode === 1 ? 'down' : 'up'; const payload = { @@ -1144,6 +1158,7 @@ const converters = { command_stop: { cluster: 'genLevelCtrl', type: ['commandStop', 'commandStopWithOnOff'], + options: [exposes.options.simulated_brightness()], convert: (model, msg, publish, options, meta) => { if (options.simulated_brightness) { clearInterval(globalStore.getValue(msg.endpoint, 'simulated_brightness_timer')); @@ -1360,6 +1375,7 @@ const converters = { cover_position_tilt: { cluster: 'closuresWindowCovering', type: ['attributeReport', 'readResponse'], + options: [exposes.options.invert_cover()], convert: (model, msg, publish, options, meta) => { const result = {}; // Zigbee officially expects 'open' to be 0 and 'closed' to be 100 whereas @@ -1381,6 +1397,7 @@ const converters = { cover_position_via_brightness: { cluster: 'genLevelCtrl', type: ['attributeReport', 'readResponse'], + options: [exposes.options.invert_cover()], convert: (model, msg, publish, options, meta) => { const currentLevel = Number(msg.data['currentLevel']); let position = mapNumberRange(currentLevel, 0, 255, 0, 100); @@ -1401,6 +1418,7 @@ const converters = { curtain_position_analog_output: { cluster: 'genAnalogOutput', type: ['attributeReport', 'readResponse'], + options: [exposes.options.invert_cover()], convert: (model, msg, publish, options, meta) => { let position = precisionRound(msg.data['presentValue'], 2); position = options.invert_cover ? 100 - position : position; @@ -1469,6 +1487,7 @@ const converters = { checkin_presence: { cluster: 'genPollCtrl', type: ['commandCheckIn'], + options: [exposes.options.presence_timeout()], convert: (model, msg, publish, options, meta) => { const useOptionsTimeout = options && options.hasOwnProperty('presence_timeout'); const timeout = useOptionsTimeout ? options.presence_timeout : 100; // 100 seconds by default @@ -1625,6 +1644,7 @@ const converters = { develco_voc: { cluster: 'develcoSpecificAirQuality', type: ['attributeReport', 'readResponse'], + options: [exposes.options.precision('voc'), exposes.options.calibration('voc')], convert: (model, msg, publish, options, meta) => { const voc = parseFloat(msg.data['measuredValue']); const vocProperty = postfixWithEndpointName('voc', msg, model); @@ -1667,6 +1687,8 @@ const converters = { tuya_temperature_humidity_sensor: { cluster: 'manuSpecificTuya', type: ['commandSetDataResponse', 'commandGetData'], + options: [exposes.options.precision('temperature'), exposes.options.calibration('temperature'), + exposes.options.precision('humidity'), exposes.options.calibration('humidity')], convert: (model, msg, publish, options, meta) => { const dp = msg.data.dp; const value = tuya.getDataValue(msg.data.datatype, msg.data.data); @@ -1846,6 +1868,7 @@ const converters = { ias_ace_occupancy_with_timeout: { cluster: 'ssIasAce', type: 'commandGetPanelStatus', + options: [exposes.options.occupancy_timeout()], convert: (model, msg, publish, options, meta) => { msg.data.occupancy = 1; return converters.occupancy_with_timeout.convert(model, msg, publish, options, meta); @@ -1854,6 +1877,7 @@ const converters = { tuya_led_controller: { cluster: 'lightingColorCtrl', type: ['attributeReport', 'readResponse'], + options: [exposes.options.color_sync()], convert: (model, msg, publish, options, meta) => { const result = {}; @@ -1892,6 +1916,7 @@ const converters = { tuya_cover: { cluster: 'manuSpecificTuya', type: ['commandSetDataResponse', 'commandGetData'], + options: [exposes.options.invert_cover()], convert: (model, msg, publish, options, meta) => { // Protocol description // https://github.com/Koenkk/zigbee-herdsman-converters/issues/1159#issuecomment-614659802 @@ -2214,6 +2239,8 @@ const converters = { neo_nas_pd07: { cluster: 'manuSpecificTuya', type: ['commandSetDataResponse', 'commandGetData'], + options: [exposes.options.precision('temperature'), exposes.options.calibration('temperature'), + exposes.options.precision('humidity'), exposes.options.calibration('humidity')], convert: (model, msg, publish, options, meta) => { const dp = msg.data.dp; if (dp === 101) return {occupancy: msg.data.data[0] > 0}; @@ -2291,6 +2318,7 @@ const converters = { terncy_temperature: { cluster: 'msTemperatureMeasurement', type: ['attributeReport', 'readResponse'], + options: [exposes.options.precision('temperature'), exposes.options.calibration('temperature')], convert: (model, msg, publish, options, meta) => { const temperature = parseFloat(msg.data['measuredValue']) / 10.0; return {temperature: calibrateAndPrecisionRoundOptions(temperature, options, 'temperature')}; @@ -3090,6 +3118,7 @@ const converters = { ZNMS11LM_closuresDoorLock_report: { cluster: 'closuresDoorLock', type: ['attributeReport', 'readResponse'], + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { const result = {}; const lockStatusLookup = { @@ -3156,6 +3185,7 @@ const converters = { ZNMS12LM_ZNMS13LM_closuresDoorLock_report: { cluster: 'closuresDoorLock', type: ['attributeReport', 'readResponse'], + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { const result = {}; const lockStatusLookup = { @@ -3429,6 +3459,11 @@ const converters = { tuya_air_quality: { cluster: 'manuSpecificTuya', type: ['commandSetDataResponse', 'commandGetData'], + options: [exposes.options.precision('temperature'), exposes.options.calibration('temperature'), + exposes.options.precision('humidity'), exposes.options.calibration('humidity'), + exposes.options.precision('co2'), exposes.options.calibration('co2'), + exposes.options.precision('voc'), exposes.options.calibration('voc'), + exposes.options.precision('formaldehyd'), exposes.options.calibration('formaldehyd')], convert: (model, msg, publish, options, meta) => { const dp = msg.data.dp; const value = tuya.getDataValue(msg.data.datatype, msg.data.data); @@ -3837,6 +3872,7 @@ const converters = { ikea_arrow_release: { cluster: 'genScenes', type: 'commandTradfriArrowRelease', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { const direction = globalStore.getValue(msg.endpoint, 'direction'); if (direction) { @@ -3985,6 +4021,8 @@ const converters = { lifecontrolVoc: { cluster: 'msTemperatureMeasurement', type: ['attributeReport', 'readResponse'], + options: [exposes.options.precision('temperature'), exposes.options.calibration('temperature'), + exposes.options.precision('humidity'), exposes.options.calibration('humidity')], convert: (model, msg, publish, options, meta) => { const temperature = parseFloat(msg.data['measuredValue']) / 100.0; const humidity = parseFloat(msg.data['minMeasuredValue']) / 100.0; @@ -4082,6 +4120,7 @@ const converters = { _3310_humidity: { cluster: 'manuSpecificCentraliteHumidity', type: ['attributeReport', 'readResponse'], + options: [exposes.options.precision('humidity'), exposes.options.calibration('humidity')], convert: (model, msg, publish, options, meta) => { const humidity = parseFloat(msg.data['measuredValue']) / 100.0; return {humidity: calibrateAndPrecisionRoundOptions(humidity, options, 'humidity')}; @@ -4267,6 +4306,7 @@ const converters = { bticino_4027C_binary_input_moving: { cluster: 'genBinaryInput', type: ['attributeReport', 'readResponse'], + options: [exposes.options.no_position_support()], convert: (model, msg, publish, options, meta) => { return options.no_position_support ? {action: msg.data.presentValue ? 'stopped' : 'moving', position: 50} : @@ -4375,6 +4415,7 @@ const converters = { xiaomi_switch_basic: { cluster: 'genBasic', type: ['attributeReport', 'readResponse'], + options: [exposes.options.precision('temperature'), exposes.options.calibration('temperature')], convert: (model, msg, publish, options, meta) => { if (msg.data['65281']) { const data = msg.data['65281']; @@ -4415,6 +4456,7 @@ const converters = { xiaomi_switch_opple_basic: { cluster: 'aqaraOpple', type: ['attributeReport', 'readResponse'], + options: [exposes.options.precision('temperature'), exposes.options.calibration('temperature')], convert: (model, msg, publish, options, meta) => { const payload = {}; if (msg.data.hasOwnProperty('247')) { @@ -4552,6 +4594,7 @@ const converters = { xiaomi_on_off_action: { cluster: 'genOnOff', type: ['attributeReport'], + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (['QBKG04LM', 'QBKG11LM', 'QBKG21LM', 'QBKG03LM', 'QBKG12LM', 'QBKG22LM'].includes(model.model) && msg.data['61440']) { return; @@ -4617,6 +4660,9 @@ const converters = { RTCGQ11LM_interval: { cluster: 'genBasic', type: ['attributeReport', 'readResponse'], + options: [exposes.options.precision('temperature'), exposes.options.calibration('temperature'), + exposes.options.precision('illuminance'), exposes.options.calibration('illuminance', 'percentual'), + exposes.options.precision('illuminance_lux'), exposes.options.calibration('illuminance_lux', 'percentual')], convert: (model, msg, publish, options, meta) => { if (msg.data['65281']) { const result = {}; @@ -4640,6 +4686,8 @@ const converters = { RTCGQ11LM_illuminance: { cluster: 'msIlluminanceMeasurement', type: ['attributeReport', 'readResponse'], + options: [exposes.options.precision('illuminance'), exposes.options.calibration('illuminance', 'percentual'), + exposes.options.precision('illuminance_lux'), exposes.options.calibration('illuminance_lux', 'percentual')], convert: (model, msg, publish, options, meta) => { // also trigger movement, because there is no illuminance without movement // https://github.com/Koenkk/zigbee-herdsman-converters/issues/1925 @@ -4655,6 +4703,15 @@ const converters = { xiaomi_WXKG01LM_action: { cluster: 'genOnOff', type: ['attributeReport', 'readResponse'], + options: [ + exposes.numeric('hold_timeout').withValueMin(0).withDescription(`The WXKG01LM only reports a button press and release.` + + `By default, a hold action is published when there is at least 1000 ms between both events. It could be that due to ` + + `delays in the network the release message is received late. This causes a single click to be identified as a hold ` + + `action. If you are experiencing this you can try experimenting with this option (e.g. set it to 2000) (value is in ms).`), + exposes.numeric('hold_timeout_expire').withValueMin(0).withDescription(`Sometimes it happens that the button does not send a ` + + `release. To avoid problems a release is automatically send after a timeout. The default timeout is 4000 ms, you can ` + + `increase it with this option (value is in ms).`), + ], convert: (model, msg, publish, options, meta) => { if (hasAlreadyProcessedMessage(msg)) return; const state = msg.data['onOff']; @@ -4702,6 +4759,7 @@ const converters = { xiaomi_contact_interval: { cluster: 'genBasic', type: ['attributeReport', 'readResponse'], + options: [exposes.options.precision('temperature'), exposes.options.calibration('temperature')], convert: (model, msg, publish, options, meta) => { if (msg.data.hasOwnProperty('65281')) { const result = {}; @@ -4722,6 +4780,7 @@ const converters = { WSDCGQ11LM_pressure: { cluster: 'msPressureMeasurement', type: ['attributeReport', 'readResponse'], + options: [exposes.options.precision('pressure'), exposes.options.calibration('pressure')], convert: (model, msg, publish, options, meta) => { const pressure = msg.data.hasOwnProperty('16') ? parseFloat(msg.data['16']) / 10 : parseFloat(msg.data['measuredValue']); return {pressure: calibrateAndPrecisionRoundOptions(pressure, options, 'pressure')}; @@ -4740,6 +4799,9 @@ const converters = { WSDCGQ01LM_WSDCGQ11LM_interval: { cluster: 'genBasic', type: ['attributeReport', 'readResponse'], + options: [exposes.options.precision('pressure'), exposes.options.calibration('pressure'), + exposes.options.precision('temperature'), exposes.options.calibration('temperature'), + exposes.options.precision('humidity'), exposes.options.calibration('humidity')], convert: (model, msg, publish, options, meta) => { if (msg.data['65281']) { const result = {}; @@ -4888,6 +4950,7 @@ const converters = { xiaomi_curtain_position: { cluster: 'genAnalogOutput', type: ['attributeReport', 'readResponse'], + options: [exposes.options.invert_cover()], convert: (model, msg, publish, options, meta) => { let running = false; @@ -5050,6 +5113,7 @@ const converters = { aqara_opple_stop: { cluster: 'genLevelCtrl', type: 'commandStop', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (hasAlreadyProcessedMessage(msg)) return; if (globalStore.hasValue(msg.endpoint, 'button')) { @@ -5090,6 +5154,7 @@ const converters = { aqara_opple_move_color_temp: { cluster: 'lightingColorCtrl', type: 'commandMoveColorTemp', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (hasAlreadyProcessedMessage(msg)) return; const stop = msg.data.movemode === 0; @@ -5130,6 +5195,7 @@ const converters = { keen_home_smart_vent_pressure: { cluster: 'msPressureMeasurement', type: ['attributeReport', 'readResponse'], + options: [exposes.options.precision('pressure'), exposes.options.calibration('pressure')], convert: (model, msg, publish, options, meta) => { const pressure = msg.data.hasOwnProperty('measuredValue') ? msg.data.measuredValue : parseFloat(msg.data['32']) / 1000.0; return {pressure: calibrateAndPrecisionRoundOptions(pressure, options, 'pressure')}; @@ -5220,6 +5286,7 @@ const converters = { MFKZQ01LM_action_multistate: { cluster: 'genMultistateInput', type: ['attributeReport', 'readResponse'], + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { /* Source: https://github.com/kirovilya/ioBroker.zigbee @@ -5268,6 +5335,7 @@ const converters = { MFKZQ01LM_action_analog: { cluster: 'genAnalogInput', type: ['attributeReport', 'readResponse'], + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { /* Source: https://github.com/kirovilya/ioBroker.zigbee @@ -5287,6 +5355,7 @@ const converters = { tradfri_occupancy: { cluster: 'genOnOff', type: 'commandOnWithTimedOff', + options: [exposes.options.occupancy_timeout()], convert: (model, msg, publish, options, meta) => { if (msg.data.ctrlbits === 1) return; @@ -5352,6 +5421,7 @@ const converters = { ZMCSW032D_cover_position: { cluster: 'closuresWindowCovering', type: ['attributeReport', 'readResponse'], + options: [exposes.options.invert_cover()], convert: (model, msg, publish, options, meta) => { const result = {}; const timeCoverSetMiddle = 60; @@ -5434,6 +5504,7 @@ const converters = { PGC410EU_presence: { cluster: 'manuSpecificSmartThingsArrivalSensor', type: 'commandArrivalSensorNotify', + options: [exposes.options.presence_timeout()], convert: (model, msg, publish, options, meta) => { const useOptionsTimeout = options && options.hasOwnProperty('presence_timeout'); const timeout = useOptionsTimeout ? options.presence_timeout : 100; // 100 seconds by default @@ -5450,6 +5521,7 @@ const converters = { STS_PRS_251_presence: { cluster: 'genBinaryInput', type: ['attributeReport', 'readResponse'], + options: [exposes.options.presence_timeout()], convert: (model, msg, publish, options, meta) => { const useOptionsTimeout = options && options.hasOwnProperty('presence_timeout'); const timeout = useOptionsTimeout ? options.presence_timeout : 100; // 100 seconds by default @@ -5854,6 +5926,7 @@ const converters = { DNCKAT_S00X_buttons: { cluster: 'genOnOff', type: ['attributeReport', 'readResponse'], + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { const action = msg.data['onOff'] === 1 ? 'release' : 'hold'; const payload = {action: postfixWithEndpointName(action, msg, model)}; @@ -5918,6 +5991,7 @@ const converters = { }, CCTSwitch_D0001_levelctrl: { cluster: 'genLevelCtrl', + options: [exposes.options.legacy()], type: ['commandMoveToLevel', 'commandMoveToLevelWithOnOff', 'commandMove', 'commandStop'], convert: (model, msg, publish, options, meta) => { const payload = {}; @@ -5990,6 +6064,7 @@ const converters = { CCTSwitch_D0001_lighting: { cluster: 'lightingColorCtrl', type: ['commandMoveToColorTemp', 'commandMoveColorTemp'], + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { const payload = {}; if (msg.type === 'commandMoveColorTemp') { @@ -6085,6 +6160,7 @@ const converters = { hue_dimmer_switch: { cluster: 'manuSpecificPhilips', type: 'commandHueNotification', + options: [exposes.options.simulated_brightness()], convert: (model, msg, publish, options, meta) => { const buttonLookup = {1: 'on', 2: 'up', 3: 'down', 4: 'off'}; const button = buttonLookup[msg.data['button']]; @@ -6239,14 +6315,15 @@ const converters = { ZB003X: { cluster: 'manuSpecificTuya', type: ['commandActiveStatusReport'], + options: [exposes.options.precision('temperature'), exposes.options.calibration('temperature'), + exposes.options.precision('humidity'), exposes.options.calibration('humidity')], convert: (model, msg, publish, options, meta) => { const dp = msg.data.dp; const value = tuya.getDataValue(msg.data.datatype, msg.data.data); switch (dp) { case tuya.dataPoints.fantemTemp: - return {temperature: calibrateAndPrecisionRoundOptions( - (value / 10).toFixed(1), options, 'temperature')}; + return {temperature: calibrateAndPrecisionRoundOptions((value / 10).toFixed(1), options, 'temperature')}; case tuya.dataPoints.fantemHumidity: return {humidity: calibrateAndPrecisionRoundOptions(value, options, 'humidity')}; case tuya.dataPoints.fantemBattery: @@ -6434,6 +6511,7 @@ const converters = { schneider_temperature: { cluster: 'msTemperatureMeasurement', type: ['attributeReport', 'readResponse'], + options: [exposes.options.precision('temperature'), exposes.options.calibration('temperature')], convert: (model, msg, publish, options, meta) => { const temperature = parseFloat(msg.data['measuredValue']) / 100.0; const property = postfixWithEndpointName('local_temperature', msg, model); diff --git a/converters/toZigbee.js b/converters/toZigbee.js index 3314cefc35ed4..a232e2c1a5431 100644 --- a/converters/toZigbee.js +++ b/converters/toZigbee.js @@ -591,6 +591,7 @@ const converters = { }, light_brightness_step: { key: ['brightness_step', 'brightness_step_onoff'], + options: [exposes.options.transition()], convertSet: async (entity, key, value, meta) => { const onOff = key.endsWith('_onoff'); const command = onOff ? 'stepWithOnOff' : 'step'; @@ -653,6 +654,7 @@ const converters = { }, light_colortemp_step: { key: ['color_temp_step'], + options: [exposes.options.transition()], convertSet: async (entity, key, value, meta) => { value = Number(value); if (isNaN(value)) { @@ -718,6 +720,7 @@ const converters = { }, light_hue_saturation_step: { key: ['hue_step', 'saturation_step'], + options: [exposes.options.transition()], convertSet: async (entity, key, value, meta) => { value = Number(value); if (isNaN(value)) { @@ -775,6 +778,7 @@ const converters = { }, light_onoff_brightness: { key: ['state', 'brightness', 'brightness_percent'], + options: [exposes.options.transition()], convertSet: async (entity, key, value, meta) => { const {message} = meta; const transition = utils.getTransition(entity, 'brightness', meta); @@ -879,7 +883,7 @@ const converters = { }, light_colortemp: { key: ['color_temp', 'color_temp_percent'], - options: [exposes.options.color_sync()], + options: [exposes.options.color_sync(), exposes.options.transition()], convertSet: async (entity, key, value, meta) => { const [colorTempMin, colorTempMax] = light.findColorTempRange(entity, meta.logger); const preset = {'warmest': colorTempMax, 'warm': 454, 'neutral': 370, 'cool': 250, 'coolest': colorTempMin}; @@ -946,7 +950,7 @@ const converters = { }, light_color: { key: ['color'], - options: [exposes.options.color_sync()], + options: [exposes.options.color_sync(), exposes.options.transition()], convertSet: async (entity, key, value, meta) => { let command; const newColor = libColor.Color.fromConverterArg(value); @@ -1044,6 +1048,7 @@ const converters = { * converter is used to do just that. */ key: ['color', 'color_temp', 'color_temp_percent'], + options: [exposes.options.color_sync(), exposes.options.transition()], convertSet: async (entity, key, value, meta) => { if (key == 'color') { const result = await converters.light_color.convertSet(entity, key, value, meta); @@ -1662,6 +1667,7 @@ const converters = { }, gledopto_light_onoff_brightness: { key: ['state', 'brightness', 'brightness_percent'], + options: [exposes.options.transition()], convertSet: async (entity, key, value, meta) => { if (meta.message && meta.message.hasOwnProperty('transition')) { meta.message.transition = meta.message.transition * 3.3; @@ -1684,6 +1690,7 @@ const converters = { }, gledopto_light_colortemp: { key: ['color_temp', 'color_temp_percent'], + options: [exposes.options.color_sync(), exposes.options.transition()], convertSet: async (entity, key, value, meta) => { if (meta.message && meta.message.hasOwnProperty('transition')) { meta.message.transition = meta.message.transition * 3.3; @@ -1703,6 +1710,7 @@ const converters = { }, gledopto_light_color: { key: ['color'], + options: [exposes.options.color_sync(), exposes.options.transition()], convertSet: async (entity, key, value, meta) => { if (meta.message && meta.message.hasOwnProperty('transition')) { meta.message.transition = meta.message.transition * 3.3; @@ -1727,6 +1735,7 @@ const converters = { }, gledopto_light_color_colortemp: { key: ['color', 'color_temp', 'color_temp_percent'], + options: [exposes.options.color_sync(), exposes.options.transition()], convertSet: async (entity, key, value, meta) => { if (key == 'color') { const result = await converters.gledopto_light_color.convertSet(entity, key, value, meta); @@ -2278,6 +2287,7 @@ const converters = { * This uses the stored state of the device to restore to the previous brightness level when turning on */ key: ['state', 'brightness', 'brightness_percent'], + options: [exposes.options.transition()], convertSet: async (entity, key, value, meta) => { const deviceState = meta.state || {}; const message = meta.message; @@ -2928,6 +2938,7 @@ const converters = { }, RM01_light_onoff_brightness: { key: ['state', 'brightness', 'brightness_percent'], + options: [exposes.options.transition()], convertSet: async (entity, key, value, meta) => { if (utils.hasEndpoints(meta.device, [0x12])) { const endpoint = meta.device.getEndpoint(0x12); @@ -2946,6 +2957,7 @@ const converters = { }, }, RM01_light_brightness_step: { + options: [exposes.options.transition()], key: ['brightness_step', 'brightness_step_onoff'], convertSet: async (entity, key, value, meta) => { if (utils.hasEndpoints(meta.device, [0x12])) { @@ -3396,6 +3408,7 @@ const converters = { }, ptvo_switch_light_brightness: { key: ['brightness', 'brightness_percent', 'transition'], + options: [exposes.options.transition()], convertSet: async (entity, key, value, meta) => { if (key === 'transition') { return; @@ -3873,9 +3886,7 @@ const converters = { }, bticino_4027C_cover_position: { key: ['position'], - options: [exposes.options.invert_cover(), exposes.binary('no_position_support', null, true, false) - // eslint-disable-next-line - .withDescription('Set to true when your device only reports position 0, 100 and 50 (in this case your device has an older firmware) (default false)')], + options: [exposes.options.invert_cover(), exposes.options.no_position_support()], convertSet: async (entity, key, value, meta) => { const invert = !(utils.getMetaValue(entity, meta.mapped, 'coverInverted', 'allEqual', false) ? !meta.options.invert_cover : meta.options.invert_cover); diff --git a/devices/heiman.js b/devices/heiman.js index 103dd86361a1e..b3eb20f990922 100644 --- a/devices/heiman.js +++ b/devices/heiman.js @@ -58,6 +58,7 @@ module.exports = [ vendor: 'HEIMAN', fromZigbee: [fz.on_off, fz.electrical_measurement], toZigbee: [tz.on_off], + options: [exposes.options.measurement_poll_interval()], configure: async (device, coordinatorEndpoint, logger) => { const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'haElectricalMeasurement']); diff --git a/devices/tuya.js b/devices/tuya.js index ad0a1d8db9263..7f71a9fec6beb 100644 --- a/devices/tuya.js +++ b/devices/tuya.js @@ -853,6 +853,7 @@ module.exports = [ endpoint.saveClusterAttributeKeyValue('seMetering', {divisor: 100, multiplier: 1}); device.save(); }, + options: [exposes.options.measurement_poll_interval()], exposes: [e.switch(), e.power(), e.current(), e.voltage().withAccess(ea.STATE), e.energy(), exposes.enum('power_outage_memory', ea.STATE_SET, ['on', 'off', 'restore']) .withDescription('Recover state after power outage')], diff --git a/index.js b/index.js index 7c0271d54b9dc..8829785d10db4 100644 --- a/index.js +++ b/index.js @@ -86,6 +86,19 @@ function addDefinition(definition) { validateDefinition(definition); definitions.splice(0, 0, definition); + if (!definition.options) definition.options = []; + const optionKeys = definition.options.map((o) => o.name); + for (const converter of [...definition.toZigbee, ...definition.fromZigbee]) { + if (converter.options) { + for (const option of converter.options) { + if (!optionKeys.includes(option.name)) { + definition.options.push(option); + optionKeys.push(option.name); + } + } + } + } + if (definition.hasOwnProperty('fingerprint')) { for (const fingerprint of definition.fingerprint) { addToLookup(fingerprint.modelID, definition); diff --git a/lib/exposes.js b/lib/exposes.js index 47ca2cb31774a..34ba158e745aa 100644 --- a/lib/exposes.js +++ b/lib/exposes.js @@ -113,6 +113,17 @@ class Binary extends Base { } } +class List extends Base { + constructor(name, access) { + super(); + this.type = 'list'; + this.name = name; + this.property = name; + this.access = access; + this.item_type = 'number'; + } +} + class Numeric extends Base { constructor(name, access) { super(); @@ -451,11 +462,24 @@ module.exports = { switch: () => new Switch(), text: (name, access) => new Text(name, access), options: { - precision: (name) => new Numeric(`${name}_precision`).withValueMin(0).withValueMax(3).withDescription(`Number of digits after decimal point for ${name}`), - calibration: (name) => new Numeric(`${name}_calibration`).withDescription(`Calibrates the ${name} value (offset)`), - invert_cover: () => new Binary(`invert_cover`, null, true, false).withDescription(`Inverts the cover position, false: open=100,close=0, true: open=0,close=100 (default false)`), - color_sync: () => new Binary(`color_sync`, null, true, false).withDescription(`When enabled colors will be synced, e.g. if the light supports both color x/y and color temperature a conversion from color x/y to color temperature will be done when setting the x/y color (default false)`), - thermostat_unit: () => new Enum('thermostat_unit', null, ['celsius', 'fahrenheit']).withDescription('Controls the temperature unit of the themrostat (default celsius'), + calibration: (name, type='absolute') => new Numeric(`${name}_calibration`, access.SET).withDescription(`Calibrates the ${name} value (${type} offset), takes into effect on next report of device.`), + precision: (name) => new Numeric(`${name}_precision`, access.SET).withValueMin(0).withValueMax(3).withDescription(`Number of digits after decimal point for ${name}, takes into effect on next report of device.`), + invert_cover: () => new Binary(`invert_cover`, access.SET, true, false).withDescription(`Inverts the cover position, false: open=100,close=0, true: open=0,close=100 (default false).`), + color_sync: () => new Binary(`color_sync`, access.SET, true, false).withDescription(`When enabled colors will be synced, e.g. if the light supports both color x/y and color temperature a conversion from color x/y to color temperature will be done when setting the x/y color (default true).`), + thermostat_unit: () => new Enum('thermostat_unit', access.SET, ['celsius', 'fahrenheit']).withDescription('Controls the temperature unit of the themrostat (default celsius).'), + expose_pin: () => new Binary(`expose_pin`, access.SET, true, false).withDescription(`Expose pin of this lock in the published payload (default false).`), + occupancy_timeout: () => new Numeric(`occupancy_timeout`, access.SET).withValueMin(0).withDescription('Time in seconds after which occupancy is cleared after detecting it (default 90 seconds).'), + vibration_timeout: () => new Numeric(`vibration_timeout`, access.SET).withValueMin(0).withDescription('Time in seconds after which vibration is cleared after detecting it (default 90 seconds).'), + simulated_brightness: () => new Composite('simulated_brightness', 'simulated_brightness') + .withDescription('Simulate a brightness value. If this device provides a brightness_move_up or brightness_move_down action it is possible to specify the update interval and delta.') + .withFeature(new Numeric('delta', access.SET).withValueMin(0).withDescription('Delta per interval, 20 by default')) + .withFeature(new Numeric('interval', access.SET).withValueMin(0).withUnit('ms').withDescription('Interval duration')), + no_occupancy_since: () => new List(`no_occupancy_since`, access.SET).withDescription('Sends a message the last time occupancy was detected. When setting this for example to [10, 60] a `{"no_occupancy_since": 10}` will be send after 10 seconds and a `{"no_occupancy_since": 60}` after 60 seconds.'), + presence_timeout: () => new Numeric(`presence_timeout`).withValueMin(0).withDescription('Time in seconds after which presence is cleared after detecting it (default 100 seconds).'), + no_position_support: () => new Binary('no_position_support', access.SET, true, false).withDescription('Set to true when your device only reports position 0, 100 and 50 (in this case your device has an older firmware) (default false).'), + transition: () => new Numeric(`transition`, access.SET).withValueMin(0).withDescription('Controls the transition time (in seconds) of on/off, brightness, color temperature (if applicable) and color (if applicable) changes. Defaults to `0` (no transition).'), + legacy: () => new Binary(`legacy`, access.SET, true, false).withDescription(`Set to false to disable the legacy integration (highly recommended), will change structure of the published payload (default true).`), + measurement_poll_interval: () => new Numeric(`measurement_poll_interval`, access.SET).withValueMin(0).withDescription(`This device does not support reporting electric measurements so it is polled instead. The default poll interval is 60 seconds.`), }, presets: { action: (values) => new Enum('action', access.STATE, values).withDescription('Triggered action (e.g. a button click)'), diff --git a/lib/legacy.js b/lib/legacy.js index ac3350c91c178..29cca85330f82 100644 --- a/lib/legacy.js +++ b/lib/legacy.js @@ -4,6 +4,7 @@ const {isLegacyEnabled, precisionRound} = require('./utils'); const fromZigbeeConverters = require('../converters/fromZigbee'); const fromZigbeeStore = {}; const tuya = require('./tuya'); +const exposes = require('./exposes'); const constants = require('./constants'); // get object property name (key) by it's value @@ -211,6 +212,7 @@ const fromZigbee = { WXKG11LM_click: { cluster: 'genOnOff', type: ['attributeReport', 'readResponse'], + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { const data = msg.data; @@ -232,6 +234,7 @@ const fromZigbee = { SmartButton_skip: { cluster: 'genLevelCtrl', type: 'commandStep', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { const direction = msg.data.stepmode === 1 ? 'backward' : 'forward'; @@ -248,6 +251,7 @@ const fromZigbee = { konke_click: { cluster: 'genOnOff', type: ['attributeReport', 'readResponse'], + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { const value = msg.data['onOff']; @@ -264,6 +268,7 @@ const fromZigbee = { xiaomi_action_click_multistate: { cluster: 'genMultistateInput', type: ['attributeReport', 'readResponse'], + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { const value = msg.data['presentValue']; @@ -279,6 +284,7 @@ const fromZigbee = { WXKG12LM_action_click_multistate: { cluster: 'genMultistateInput', type: ['attributeReport', 'readResponse'], + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { const value = msg.data['presentValue']; @@ -294,6 +300,7 @@ const fromZigbee = { terncy_raw: { cluster: 'manuSpecificClusterAduroSmart', type: 'raw', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { // 13,40,18,104, 0,8,1 - click @@ -329,6 +336,7 @@ const fromZigbee = { CCTSwitch_D0001_on_off: { cluster: 'genOnOff', type: ['commandOn', 'commandOff'], + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { return {click: 'power'}; @@ -338,6 +346,7 @@ const fromZigbee = { ptvo_switch_buttons: { cluster: 'genMultistateInput', type: ['attributeReport', 'readResponse'], + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { const button = getKey(model.endpoint(msg.device), msg.endpoint.ID); @@ -362,6 +371,7 @@ const fromZigbee = { ZGRC013_brightness_onoff: { cluster: 'genLevelCtrl', type: 'commandMoveWithOnOff', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { const button = msg.endpoint.ID; @@ -375,6 +385,7 @@ const fromZigbee = { ZGRC013_brightness_stop: { cluster: 'genLevelCtrl', type: 'commandStopWithOnOff', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { const button = msg.endpoint.ID; @@ -387,6 +398,7 @@ const fromZigbee = { ZGRC013_scene: { cluster: 'genScenes', type: 'commandRecall', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { return {click: `scene_${msg.data.groupid}_${msg.data.sceneid}`}; @@ -396,6 +408,7 @@ const fromZigbee = { ZGRC013_cmdOn: { cluster: 'genOnOff', type: 'commandOn', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { const button = msg.endpoint.ID; @@ -408,6 +421,7 @@ const fromZigbee = { ZGRC013_cmdOff: { cluster: 'genOnOff', type: 'commandOff', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { const button = msg.endpoint.ID; @@ -420,6 +434,7 @@ const fromZigbee = { ZGRC013_brightness: { cluster: 'genLevelCtrl', type: 'commandMove', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { const button = msg.endpoint.ID; @@ -433,6 +448,7 @@ const fromZigbee = { CTR_U_scene: { cluster: 'genScenes', type: 'commandRecall', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { return {click: `scene_${msg.data.groupid}_${msg.data.sceneid}`}; @@ -442,6 +458,7 @@ const fromZigbee = { st_button_state: { cluster: 'ssIasZone', type: 'commandStatusChangeNotification', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { const buttonStates = { @@ -464,6 +481,7 @@ const fromZigbee = { QBKG11LM_click: { cluster: 'genMultistateInput', type: ['attributeReport', 'readResponse'], + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { if ([1, 2].includes(msg.data.presentValue)) { @@ -476,6 +494,7 @@ const fromZigbee = { QBKG12LM_click: { cluster: 'genMultistateInput', type: ['attributeReport', 'readResponse'], + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { if ([1, 2].includes(msg.data.presentValue)) { @@ -490,6 +509,7 @@ const fromZigbee = { QBKG03LM_QBKG12LM_click: { cluster: 'genOnOff', type: ['attributeReport'], + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { if (!msg.data['61440']) { @@ -503,6 +523,7 @@ const fromZigbee = { QBKG04LM_QBKG11LM_click: { cluster: 'genOnOff', type: ['attributeReport'], + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { if (!msg.data['61440']) { @@ -514,6 +535,7 @@ const fromZigbee = { cover_stop: { cluster: 'closuresWindowCovering', type: 'commandStop', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { return {click: 'release'}; @@ -523,6 +545,7 @@ const fromZigbee = { cover_open: { cluster: 'closuresWindowCovering', type: 'commandUpOpen', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { return {click: 'open'}; @@ -532,6 +555,7 @@ const fromZigbee = { cover_close: { cluster: 'closuresWindowCovering', type: 'commandDownClose', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { return {click: 'close'}; @@ -541,6 +565,7 @@ const fromZigbee = { WXKG03LM_click: { cluster: 'genOnOff', type: ['attributeReport', 'readResponse'], + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { return {click: 'single'}; @@ -550,6 +575,7 @@ const fromZigbee = { TS0218_click: { cluster: 'ssIasAce', type: 'commandEmergency', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { return {action: 'click'}; @@ -561,6 +587,7 @@ const fromZigbee = { xiaomi_on_off_action: { cluster: 'genOnOff', type: ['attributeReport'], + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { return {action: getKey(model.endpoint(msg.device), msg.endpoint.ID)}; @@ -572,6 +599,7 @@ const fromZigbee = { WXKG02LM_click: { cluster: 'genOnOff', type: ['attributeReport', 'readResponse'], + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { const lookup = {1: 'left', 2: 'right', 3: 'both'}; @@ -582,6 +610,7 @@ const fromZigbee = { WXKG02LM_click_multistate: { cluster: 'genMultistateInput', type: ['attributeReport', 'readResponse'], + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { // Somestime WXKG02LM sends multiple messages on a single click, this prevents handling // of a message with the same transaction sequence number twice. @@ -611,6 +640,7 @@ const fromZigbee = { WXKG01LM_click: { cluster: 'genOnOff', type: ['attributeReport', 'readResponse'], + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { const deviceID = msg.device.ieeeAddr; @@ -659,6 +689,7 @@ const fromZigbee = { scenes_recall_click: { cluster: 'genScenes', type: 'commandRecall', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { return {click: msg.data.sceneid}; @@ -668,6 +699,7 @@ const fromZigbee = { AV2010_34_click: { cluster: 'genScenes', type: 'commandRecall', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { return {click: msg.data.groupid}; @@ -677,6 +709,7 @@ const fromZigbee = { E1743_brightness_down: { cluster: 'genLevelCtrl', type: 'commandMove', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { return {click: 'brightness_down'}; @@ -686,6 +719,7 @@ const fromZigbee = { E1743_brightness_up: { cluster: 'genLevelCtrl', type: 'commandMoveWithOnOff', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { return {click: 'brightness_up'}; @@ -695,6 +729,7 @@ const fromZigbee = { E1743_brightness_stop: { cluster: 'genLevelCtrl', type: 'commandStopWithOnOff', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { return {click: 'brightness_stop'}; @@ -704,6 +739,7 @@ const fromZigbee = { genOnOff_cmdOn: { cluster: 'genOnOff', type: 'commandOn', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { return {click: 'on'}; @@ -713,6 +749,7 @@ const fromZigbee = { genOnOff_cmdOff: { cluster: 'genOnOff', type: 'commandOff', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { return {click: 'off'}; @@ -722,6 +759,7 @@ const fromZigbee = { RM01_on_click: { cluster: 'genOnOff', type: 'commandOn', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { const button = getKey(model.endpoint(msg.device), msg.endpoint.ID); @@ -734,6 +772,7 @@ const fromZigbee = { RM01_off_click: { cluster: 'genOnOff', type: 'commandOff', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { const button = getKey(model.endpoint(msg.device), msg.endpoint.ID); @@ -746,6 +785,7 @@ const fromZigbee = { RM01_down_hold: { cluster: 'genLevelCtrl', type: 'commandStep', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { const button = getKey(model.endpoint(msg.device), msg.endpoint.ID); @@ -763,6 +803,7 @@ const fromZigbee = { RM01_up_hold: { cluster: 'genLevelCtrl', type: 'commandStepWithOnOff', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { const button = getKey(model.endpoint(msg.device), msg.endpoint.ID); @@ -780,6 +821,7 @@ const fromZigbee = { RM01_stop: { cluster: 'genLevelCtrl', type: 'commandStop', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { const button = getKey(model.endpoint(msg.device), msg.endpoint.ID); @@ -792,6 +834,7 @@ const fromZigbee = { xiaomi_multistate_action: { cluster: 'genMultistateInput', type: ['attributeReport', 'readResponse'], + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { // refactor to xiaomi_multistate_action] if (isLegacyEnabled(options)) { @@ -811,6 +854,7 @@ const fromZigbee = { E1744_play_pause: { cluster: 'genOnOff', type: 'commandToggle', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (hasAlreadyProcessedMessage(msg)) return; if (isLegacyEnabled(options)) { @@ -823,6 +867,7 @@ const fromZigbee = { E1744_skip: { cluster: 'genLevelCtrl', type: 'commandStep', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (hasAlreadyProcessedMessage(msg)) return; if (isLegacyEnabled(options)) { @@ -840,6 +885,7 @@ const fromZigbee = { cmd_move: { cluster: 'genLevelCtrl', type: 'commandMove', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (hasAlreadyProcessedMessage(msg)) return; if (isLegacyEnabled(options)) { @@ -854,6 +900,7 @@ const fromZigbee = { cmd_move_with_onoff: { cluster: 'genLevelCtrl', type: 'commandMoveWithOnOff', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { ictcg1(model, msg, publish, options, 'move'); @@ -867,6 +914,7 @@ const fromZigbee = { cmd_stop: { cluster: 'genLevelCtrl', type: 'commandStop', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (hasAlreadyProcessedMessage(msg)) return; if (isLegacyEnabled(options)) { @@ -880,6 +928,7 @@ const fromZigbee = { cmd_stop_with_onoff: { cluster: 'genLevelCtrl', type: 'commandStopWithOnOff', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { const value = ictcg1(model, msg, publish, options, 'stop'); @@ -892,6 +941,7 @@ const fromZigbee = { cmd_move_to_level_with_onoff: { cluster: 'genLevelCtrl', type: 'commandMoveToLevelWithOnOff', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { const value = ictcg1(model, msg, publish, options, 'level'); @@ -905,6 +955,7 @@ const fromZigbee = { immax_07046L_arm: { cluster: 'ssIasAce', type: 'commandArm', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { const action = msg.data['armmode']; @@ -923,6 +974,7 @@ const fromZigbee = { KEF1PA_arm: { cluster: 'ssIasAce', type: 'commandArm', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { const action = msg.data['armmode']; @@ -941,6 +993,7 @@ const fromZigbee = { QBKG25LM_click: { cluster: 'genMultistateInput', type: ['attributeReport', 'readResponse'], + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { if ([1, 2, 3, 0, 255].includes(msg.data.presentValue)) { @@ -957,6 +1010,7 @@ const fromZigbee = { QBKG03LM_buttons: { cluster: 'genOnOff', type: ['attributeReport'], + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { const mapping = {4: 'left', 5: 'right'}; const button = mapping[msg.endpoint.ID]; @@ -970,6 +1024,7 @@ const fromZigbee = { CTR_U_brightness_updown_click: { cluster: 'genLevelCtrl', type: 'commandStep', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { const deviceID = msg.device.ieeeAddr; @@ -994,6 +1049,7 @@ const fromZigbee = { CTR_U_brightness_updown_hold: { cluster: 'genLevelCtrl', type: 'commandMove', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { const deviceID = msg.device.ieeeAddr; @@ -1017,6 +1073,7 @@ const fromZigbee = { CTR_U_brightness_updown_release: { cluster: 'genLevelCtrl', type: 'commandStop', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { const deviceID = msg.device.ieeeAddr; @@ -1036,6 +1093,7 @@ const fromZigbee = { osram_lightify_switch_cmdOn: { cluster: 'genOnOff', type: 'commandOn', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { return {action: 'up'}; @@ -1047,6 +1105,7 @@ const fromZigbee = { osram_lightify_switch_cmdOff: { cluster: 'genOnOff', type: 'commandOff', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { return {action: 'down'}; @@ -1058,6 +1117,7 @@ const fromZigbee = { osram_lightify_switch_cmdMoveWithOnOff: { cluster: 'genLevelCtrl', type: 'commandMoveWithOnOff', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { const deviceID = msg.device.ieeeAddr; @@ -1074,6 +1134,7 @@ const fromZigbee = { osram_lightify_switch_AC0251100NJ_cmdStop: { cluster: 'genLevelCtrl', type: 'commandStop', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { const map = { @@ -1090,6 +1151,7 @@ const fromZigbee = { osram_lightify_switch_cmdMove: { cluster: 'genLevelCtrl', type: 'commandMove', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { const deviceID = msg.device.ieeeAddr; @@ -1106,6 +1168,7 @@ const fromZigbee = { osram_lightify_switch_cmdMoveHue: { cluster: 'lightingColorCtrl', type: 'commandMoveHue', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { if (msg.data.movemode === 0) { @@ -1119,6 +1182,7 @@ const fromZigbee = { osram_lightify_switch_cmdMoveToSaturation: { cluster: 'lightingColorCtrl', type: 'commandMoveToSaturation', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { return {action: 'circle_hold'}; @@ -1130,6 +1194,7 @@ const fromZigbee = { osram_lightify_switch_cmdMoveToLevelWithOnOff: { cluster: 'genLevelCtrl', type: 'commandMoveToLevelWithOnOff', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { return {action: 'circle_click'}; @@ -1141,6 +1206,7 @@ const fromZigbee = { osram_lightify_switch_cmdMoveToColorTemp: { cluster: 'lightingColorCtrl', type: 'commandMoveToColorTemp', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { return null; @@ -1152,6 +1218,7 @@ const fromZigbee = { osram_lightify_switch_73743_cmdStop: { cluster: 'genLevelCtrl', type: 'commandStop', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { const deviceID = msg.device.ieeeAddr; @@ -1171,6 +1238,7 @@ const fromZigbee = { osram_lightify_switch_AB371860355_cmdOn: { cluster: 'genOnOff', type: 'commandOn', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { return {action: 'left_top_click'}; @@ -1182,6 +1250,7 @@ const fromZigbee = { osram_lightify_switch_AB371860355_cmdOff: { cluster: 'genOnOff', type: 'commandOff', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { return {action: 'left_bottom_click'}; @@ -1193,6 +1262,7 @@ const fromZigbee = { osram_lightify_switch_AB371860355_cmdStepColorTemp: { cluster: 'lightingColorCtrl', type: 'commandStepColorTemp', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { const pos = (msg.data.stepmode === 1) ? 'top' : 'bottom'; @@ -1205,6 +1275,7 @@ const fromZigbee = { osram_lightify_switch_AB371860355_cmdMoveWithOnOff: { cluster: 'genLevelCtrl', type: 'commandMoveWithOnOff', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { return {action: 'left_top_hold'}; @@ -1216,6 +1287,7 @@ const fromZigbee = { osram_lightify_switch_AB371860355_cmdMove: { cluster: 'genLevelCtrl', type: 'commandMove', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { return {action: 'left_bottom_hold'}; @@ -1227,6 +1299,7 @@ const fromZigbee = { osram_lightify_switch_AB371860355_cmdStop: { cluster: 'genLevelCtrl', type: 'commandStop', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { const pos = (msg.endpoint.ID === 1) ? 'top' : 'bottom'; @@ -1239,6 +1312,7 @@ const fromZigbee = { osram_lightify_switch_AB371860355_cmdMoveHue: { cluster: 'lightingColorCtrl', type: 'commandMoveHue', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { const pos = (msg.endpoint.ID === 2) ? 'top' : 'bottom'; @@ -1252,6 +1326,7 @@ const fromZigbee = { osram_lightify_switch_AB371860355_cmdMoveSat: { cluster: 'lightingColorCtrl', type: 'commandMoveToSaturation', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { const pos = (msg.endpoint.ID === 2) ? 'top' : 'bottom'; @@ -1264,6 +1339,7 @@ const fromZigbee = { insta_scene_click: { cluster: 'genScenes', type: 'commandRecall', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { return { @@ -1277,6 +1353,7 @@ const fromZigbee = { insta_down_hold: { cluster: 'genLevelCtrl', type: 'commandStep', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { return { @@ -1293,6 +1370,7 @@ const fromZigbee = { insta_up_hold: { cluster: 'genLevelCtrl', type: 'commandStepWithOnOff', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { return { @@ -1309,6 +1387,7 @@ const fromZigbee = { insta_stop: { cluster: 'genLevelCtrl', type: 'commandStop', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { return { @@ -1322,6 +1401,7 @@ const fromZigbee = { tint404011_brightness_updown_click: { cluster: 'genLevelCtrl', type: 'commandStep', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { const direction = msg.data.stepmode === 1 ? 'down' : 'up'; @@ -1340,6 +1420,7 @@ const fromZigbee = { tint404011_brightness_updown_hold: { cluster: 'genLevelCtrl', type: 'commandMove', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { const deviceID = msg.device.ieeeAddr; @@ -1365,6 +1446,7 @@ const fromZigbee = { tint404011_brightness_updown_release: { cluster: 'genLevelCtrl', type: 'commandStop', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { const deviceID = msg.device.ieeeAddr; @@ -1384,6 +1466,7 @@ const fromZigbee = { tint404011_move_to_color_temp: { cluster: 'lightingColorCtrl', type: 'commandMoveToColorTemp', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { const payload = { @@ -1401,6 +1484,7 @@ const fromZigbee = { tint404011_move_to_color: { cluster: 'lightingColorCtrl', type: 'commandMoveToColor', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { const payload = { @@ -1421,6 +1505,7 @@ const fromZigbee = { heiman_smart_controller_armmode: { cluster: 'ssIasAce', type: 'commandArm', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { if (msg.data.armmode != null) { @@ -1441,6 +1526,7 @@ const fromZigbee = { LZL4B_onoff: { cluster: 'genLevelCtrl', type: 'commandMoveToLevelWithOnOff', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { return { @@ -1455,6 +1541,7 @@ const fromZigbee = { eria_81825_updown: { cluster: 'genLevelCtrl', type: 'commandStep', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { const direction = msg.data.stepmode === 0 ? 'up' : 'down'; @@ -1467,6 +1554,7 @@ const fromZigbee = { ZYCT202_stop: { cluster: 'genLevelCtrl', type: 'commandStop', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { return {action: 'stop', action_group: msg.groupID}; @@ -1478,6 +1566,7 @@ const fromZigbee = { ZYCT202_up_down: { cluster: 'genLevelCtrl', type: 'commandMove', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { const value = msg.data['movemode']; @@ -1493,6 +1582,7 @@ const fromZigbee = { STS_PRS_251_beeping: { cluster: 'genIdentify', type: ['attributeReport', 'readResponse'], + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { return {action: 'beeping'}; @@ -1504,6 +1594,7 @@ const fromZigbee = { dimmer_passthru_brightness: { cluster: 'genLevelCtrl', type: 'commandMoveToLevelWithOnOff', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (isLegacyEnabled(options)) { ratelimitedDimmer(model, msg, publish, options, meta); @@ -1515,6 +1606,7 @@ const fromZigbee = { bitron_thermostat_att_report: { cluster: 'hvacThermostat', type: ['attributeReport', 'readResponse'], + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (!isLegacyEnabled(options)) { return fromZigbeeConverters.thermostat.convert(model, msg, publish, options, meta); @@ -1544,6 +1636,7 @@ const fromZigbee = { thermostat_att_report: { cluster: 'hvacThermostat', type: ['attributeReport', 'readResponse'], + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (!isLegacyEnabled(options)) { return fromZigbeeConverters.thermostat.convert(model, msg, publish, options, meta); @@ -1626,6 +1719,7 @@ const fromZigbee = { sinope_thermostat_att_report: { cluster: 'hvacThermostat', type: ['attributeReport', 'readResponse'], + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (!isLegacyEnabled(options)) { return fromZigbeeConverters.sinope_thermostat.convert(model, msg, publish, options, meta); @@ -1642,6 +1736,7 @@ const fromZigbee = { stelpro_thermostat: { cluster: 'hvacThermostat', type: ['attributeReport', 'readResponse'], + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (!isLegacyEnabled(options)) { return fromZigbeeConverters.stelpro_thermostat.convert(model, msg, publish, options, meta); @@ -1670,6 +1765,7 @@ const fromZigbee = { viessmann_thermostat_att_report: { cluster: 'hvacThermostat', type: ['attributeReport', 'readResponse'], + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (!isLegacyEnabled(options)) { return fromZigbeeConverters.viessmann_thermostat.convert(model, msg, publish, options, meta); @@ -1691,6 +1787,7 @@ const fromZigbee = { eurotronic_thermostat: { cluster: 'hvacThermostat', type: ['attributeReport', 'readResponse'], + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (!isLegacyEnabled(options)) { return fromZigbeeConverters.eurotronic_thermostat.convert(model, msg, publish, options, meta); @@ -1752,6 +1849,7 @@ const fromZigbee = { sinope_thermostat_state: { cluster: 'hvacThermostat', type: ['attributeReport', 'readResponse'], + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { const result = {}; const piHeatingDemand = msg.data['pIHeatingDemand']; @@ -1766,6 +1864,7 @@ const fromZigbee = { wiser_thermostat: { cluster: 'hvacThermostat', type: ['attributeReport', 'readResponse'], + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (!isLegacyEnabled(options)) { return fromZigbeeConverters.thermostat.convert(model, msg, publish, options, meta); @@ -1788,6 +1887,7 @@ const fromZigbee = { hvac_user_interface: { cluster: 'hvacUserInterfaceCfg', type: ['attributeReport', 'readResponse'], + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (!isLegacyEnabled(options)) { return fromZigbeeConverters.hvac_user_interface.convert(model, msg, publish, options, meta); @@ -1804,6 +1904,7 @@ const fromZigbee = { thermostat_weekly_schedule_rsp: { cluster: 'hvacThermostat', type: ['commandGetWeeklyScheduleRsp'], + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (!isLegacyEnabled(options)) { return fromZigbeeConverters.thermostat_weekly_schedule.convert(model, msg, publish, options, meta); @@ -1829,6 +1930,7 @@ const fromZigbee = { terncy_knob: { cluster: 'manuSpecificClusterAduroSmart', type: ['attributeReport', 'readResponse'], + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (!isLegacyEnabled(options)) { return fromZigbeeConverters.terncy_knob.convert(model, msg, publish, options, meta); @@ -1845,6 +1947,7 @@ const fromZigbee = { wiser_itrv_battery: { cluster: 'genPowerCfg', type: ['attributeReport', 'readResponse'], + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (!isLegacyEnabled(options)) { return fromZigbeeConverters.battery.convert(model, msg, publish, options, meta); @@ -1871,6 +1974,7 @@ const fromZigbee = { ubisys_c4_scenes: { cluster: 'genScenes', type: 'commandRecall', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (!isLegacyEnabled(options)) { return fromZigbeeConverters.command_recall.convert(model, msg, publish, options, meta); @@ -1881,6 +1985,7 @@ const fromZigbee = { ubisys_c4_onoff: { cluster: 'genOnOff', type: ['commandOn', 'commandOff', 'commandToggle'], + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (!isLegacyEnabled(options)) { if (msg.type === 'commandOn') { @@ -1897,6 +2002,7 @@ const fromZigbee = { ubisys_c4_level: { cluster: 'genLevelCtrl', type: ['commandMoveWithOnOff', 'commandStopWithOnOff'], + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (!isLegacyEnabled(options)) { if (msg.type === 'commandMoveWithOnOff') { @@ -1917,6 +2023,7 @@ const fromZigbee = { ubisys_c4_cover: { cluster: 'closuresWindowCovering', type: ['commandUpOpen', 'commandDownClose', 'commandStop'], + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (!isLegacyEnabled(options)) { if (msg.type === 'commandUpOpen') { @@ -1939,6 +2046,7 @@ const fromZigbee = { tuya_thermostat_weekly_schedule: { cluster: 'manuSpecificTuya', type: ['commandGetData', 'commandSetDataResponse'], + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (!isLegacyEnabled(options)) { return fromZigbeeConverters.tuya_thermostat_weekly_schedule.convert(model, msg, publish, options, meta); @@ -1996,6 +2104,7 @@ const fromZigbee = { hue_dimmer_switch: { cluster: 'manuSpecificPhilips', type: 'commandHueNotification', + options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (!isLegacyEnabled(options)) { return fromZigbeeConverters.hue_dimmer_switch.convert(model, msg, publish, options, meta);