diff --git a/converters/fromZigbee.js b/converters/fromZigbee.js index 39899fc75ead2..44a300c1b02f3 100644 --- a/converters/fromZigbee.js +++ b/converters/fromZigbee.js @@ -89,6 +89,10 @@ const converters = { result[postfixWithEndpointName('control_sequence_of_operation', msg, model)] = constants.thermostatControlSequenceOfOperations[msg.data['ctrlSeqeOfOper']]; } + if (msg.data.hasOwnProperty('programingOperMode')) { + result[postfixWithEndpointName('programming_operation_mode', msg, model)] = + constants.thermostatProgrammingOperationModes[msg.data['programingOperMode']]; + } if (msg.data.hasOwnProperty('systemMode')) { result[postfixWithEndpointName('system_mode', msg, model)] = constants.thermostatSystemModes[msg.data['systemMode']]; } diff --git a/converters/toZigbee.js b/converters/toZigbee.js index 261d71a60f159..edffe79c66819 100644 --- a/converters/toZigbee.js +++ b/converters/toZigbee.js @@ -1154,6 +1154,20 @@ const converters = { await entity.read('hvacThermostat', ['ctrlSeqeOfOper']); }, }, + thermostat_programming_operation_mode: { + key: ['programming_operation_mode'], + convertSet: async (entity, key, value, meta) => { + const val = utils.getKey(constants.thermostatProgrammingOperationModes, value, undefined, Number); + if (val === undefined) { + throw new Error('Programming operation mode invalid, must be one of: ' + + Object.values(constants.thermostatProgrammingOperationModes).join(', ')); + } + await entity.write('hvacThermostat', {programingOperMode: val}); + }, + convertGet: async (entity, key, meta) => { + await entity.read('hvacThermostat', ['programingOperMode']); + }, + }, thermostat_temperature_display_mode: { key: ['temperature_display_mode'], convertSet: async (entity, key, value, meta) => { diff --git a/devices/danfoss.js b/devices/danfoss.js index 371ae13009e06..affbf2042c1f5 100644 --- a/devices/danfoss.js +++ b/devices/danfoss.js @@ -15,14 +15,15 @@ module.exports = [ model: '014G2461', vendor: 'Danfoss', description: 'Ally thermostat', - fromZigbee: [fz.battery, fz.thermostat, fz.hvac_user_interface, fz.danfoss_thermostat], + fromZigbee: [fz.battery, fz.thermostat, fz.thermostat_weekly_schedule, fz.hvac_user_interface, fz.danfoss_thermostat], toZigbee: [tz.danfoss_thermostat_occupied_heating_setpoint, tz.thermostat_local_temperature, tz.danfoss_mounted_mode_active, tz.danfoss_mounted_mode_control, tz.danfoss_thermostat_vertical_orientation, tz.danfoss_algorithm_scale_factor, tz.danfoss_heat_available, tz.danfoss_heat_required, tz.danfoss_day_of_week, tz.danfoss_trigger_time, tz.danfoss_window_open_internal, tz.danfoss_window_open_external, tz.danfoss_load_estimate, tz.danfoss_viewing_direction, tz.danfoss_external_measured_room_sensor, tz.danfoss_radiator_covered, - tz.thermostat_keypad_lockout, tz.thermostat_system_mode, tz.danfoss_load_balancing_enable, tz.danfoss_load_room_mean], - exposes: [e.battery(), e.keypad_lockout(), + tz.thermostat_keypad_lockout, tz.thermostat_system_mode, tz.danfoss_load_balancing_enable, tz.danfoss_load_room_mean, + tz.thermostat_weekly_schedule, tz.thermostat_clear_weekly_schedule, tz.thermostat_programming_operation_mode], + exposes: [e.battery(), e.keypad_lockout(), e.programming_operation_mode(), exposes.binary('mounted_mode_active', ea.STATE_GET, true, false) .withDescription('Is the unit in mounting mode. This is set to `false` for mounted (already on ' + 'the radiator) or `true` for not mounted (after factory reset)'), diff --git a/devices/hive.js b/devices/hive.js index 93f9a69ee7e75..73d651332fc1a 100644 --- a/devices/hive.js +++ b/devices/hive.js @@ -156,14 +156,15 @@ module.exports = [ model: 'UK7004240', vendor: 'Hive', description: 'Radiator valve based on Danfos Ally', - fromZigbee: [fz.battery, fz.thermostat, fz.hvac_user_interface, fz.danfoss_thermostat], + fromZigbee: [fz.battery, fz.thermostat, fz.thermostat_weekly_schedule, fz.hvac_user_interface, fz.danfoss_thermostat], toZigbee: [tz.danfoss_thermostat_occupied_heating_setpoint, tz.thermostat_local_temperature, tz.danfoss_mounted_mode_active, tz.danfoss_mounted_mode_control, tz.danfoss_thermostat_vertical_orientation, tz.danfoss_algorithm_scale_factor, tz.danfoss_heat_available, tz.danfoss_heat_required, tz.danfoss_day_of_week, tz.danfoss_trigger_time, tz.danfoss_window_open_internal, tz.danfoss_window_open_external, tz.danfoss_load_estimate, tz.danfoss_viewing_direction, tz.danfoss_external_measured_room_sensor, tz.thermostat_keypad_lockout, - tz.thermostat_system_mode, tz.danfoss_load_balancing_enable, tz.danfoss_load_room_mean], - exposes: [e.battery(), e.keypad_lockout(), + tz.thermostat_system_mode, tz.danfoss_load_balancing_enable, tz.danfoss_load_room_mean, + tz.thermostat_weekly_schedule, tz.thermostat_clear_weekly_schedule, tz.thermostat_programming_operation_mode], + exposes: [e.battery(), e.keypad_lockout(), e.programming_operation_mode(), exposes.binary('mounted_mode_active', ea.STATE_GET, true, false) .withDescription('Is the unit in mounting mode. This is set to `false` for mounted (already on ' + 'the radiator) or `true` for not mounted (after factory reset)'), diff --git a/devices/popp.js b/devices/popp.js index 8ae78a75dd92b..6364282e23fb0 100644 --- a/devices/popp.js +++ b/devices/popp.js @@ -15,14 +15,15 @@ module.exports = [ model: '701721', vendor: 'Popp', description: 'Smart thermostat', - fromZigbee: [fz.battery, fz.thermostat, fz.hvac_user_interface, fz.danfoss_thermostat], + fromZigbee: [fz.battery, fz.thermostat, fz.thermostat_weekly_schedule, fz.hvac_user_interface, fz.danfoss_thermostat], toZigbee: [tz.danfoss_thermostat_occupied_heating_setpoint, tz.thermostat_local_temperature, tz.danfoss_mounted_mode_active, tz.danfoss_mounted_mode_control, tz.danfoss_thermostat_vertical_orientation, tz.danfoss_algorithm_scale_factor, tz.danfoss_heat_available, tz.danfoss_heat_required, tz.danfoss_day_of_week, tz.danfoss_trigger_time, tz.danfoss_window_open_internal, tz.danfoss_window_open_external, tz.danfoss_load_estimate, tz.danfoss_viewing_direction, tz.danfoss_external_measured_room_sensor, tz.thermostat_keypad_lockout, - tz.thermostat_system_mode, tz.danfoss_load_balancing_enable, tz.danfoss_load_room_mean], - exposes: [e.battery(), e.keypad_lockout(), + tz.thermostat_system_mode, tz.danfoss_load_balancing_enable, tz.danfoss_load_room_mean, + tz.thermostat_weekly_schedule, tz.thermostat_clear_weekly_schedule, tz.thermostat_programming_operation_mode], + exposes: [e.battery(), e.keypad_lockout(), e.programming_operation_mode(), exposes.binary('mounted_mode_active', ea.STATE_GET, true, false) .withDescription('Is the unit in mounting mode. This is set to `false` for mounted (already on ' + 'the radiator) or `true` for not mounted (after factory reset)'), diff --git a/lib/constants.js b/lib/constants.js index 384dec76bf51f..508aff061be94 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -21,6 +21,11 @@ const thermostatControlSequenceOfOperations = { 5: 'cooling_and_heating_4-pipes_with_reheat', }; +const thermostatProgrammingOperationModes = { + 0: 'setpoint', + 1: 'schedule', +}; + const thermostatSystemModes = { 0: 'off', 1: 'auto', @@ -247,6 +252,7 @@ module.exports = { repInterval, defaultBindGroup, thermostatControlSequenceOfOperations, + thermostatProgrammingOperationModes, thermostatSystemModes, thermostatRunningStates, thermostatRunningMode, diff --git a/lib/exposes.js b/lib/exposes.js index ab46a207e0d66..2e17167af9a3d 100644 --- a/lib/exposes.js +++ b/lib/exposes.js @@ -574,6 +574,7 @@ module.exports = { power_outage_memory: () => new Binary('power_outage_memory', access.ALL, true, false).withDescription('Enable/disable the power outage memory, this recovers the on/off mode after power failure'), presence: () => new Binary('presence', access.STATE, true, false).withDescription('Indicates whether the device detected presence'), pressure: () => new Numeric('pressure', access.STATE).withUnit('hPa').withDescription('The measured atmospheric pressure'), + programming_operation_mode: () => new Enum('programming_operation_mode', access.ALL, ['setpoint', 'schedule']).withDescription('Controls how programming affects the thermostat. Possible values: setpoint (only use specified setpoint), schedule (follow programmed setpoint schedule). Changing this value does not clear programmed schedules.'), smoke: () => new Binary('smoke', access.STATE, true, false).withDescription('Indicates whether the device detected smoke'), soil_moisture: () => new Numeric('soil_moisture', access.STATE).withUnit('%').withDescription('Measured soil moisture value'), sos: () => new Binary('sos', access.STATE, true, false).withDescription('SOS alarm'),