Skip to content

Commit

Permalink
Add EER40030, EER42000, EER53000, EER51000 and EER50000 (Koenkk#2589)
Browse files Browse the repository at this point in the history
* Wiser Smart Europe Device Support

- Added Room and Radiator Thermostats, Heating and Load Actuators and 2nd Gen Smartplug
- All devices excluding Smartplug require holding set button for 5 seconds to join
- Smartplug will join with short press, but only on channels 11,15,20,25

* Use numbers in place of attribute names

- These are manufacturer specific but don't use the MS bitfield, so they
need to be decoded and handled directly in the converters
- Cleaned out some unnecessary attributes
- Removed unnecessary handling for non-battery device for zoneMode

* User saveClusterAttributeKeyValue for client requests

* Use sendWhenActive to defer messages for sleepy devices

* Add empty payload so options are passed in correct position

* Update schneider_electric.js

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
  • Loading branch information
pklokke and Koenkk authored May 22, 2021
1 parent 0ddb1d2 commit 099be24
Show file tree
Hide file tree
Showing 3 changed files with 247 additions and 0 deletions.
78 changes: 78 additions & 0 deletions converters/fromZigbee.js
Original file line number Diff line number Diff line change
Expand Up @@ -5570,6 +5570,84 @@ const converters = {
return result;
},
},
wiser_smart_thermostat_client: {
cluster: 'hvacThermostat',
type: 'read',
convert: async (model, msg, publish, options, meta) => {
const response = {};
if (msg.data[0] == 0xe010) {
// Zone Mode
const lookup = {'manual': 1, 'schedule': 2, 'energy_saver': 3, 'holiday': 6};
const zonemodeNum = lookup[meta.state.zone_mode];
response[0xe010] = {value: zonemodeNum, type: 0x30};
await msg.endpoint.readResponse(msg.cluster, msg.meta.zclTransactionSequenceNumber, response, {srcEndpoint: 11});
}
},
},
wiser_smart_thermostat: {
cluster: 'hvacThermostat',
type: ['attributeReport', 'readResponse'],
convert: async (model, msg, publish, options, meta) => {
const result = converters.thermostat.convert(model, msg, publish, options, meta);

if (msg.data.hasOwnProperty(0xe010)) {
// wiserSmartZoneMode
const lookup = {1: 'manual', 2: 'schedule', 3: 'energy_saver', 6: 'holiday'};
result['zone_mode'] = lookup[msg.data[0xe010]];
}
if (msg.data.hasOwnProperty(0xe030)) {
// wiserSmartValvePosition
result['pi_heating_demand'] = msg.data[0xe030];
}
if (msg.data.hasOwnProperty(0xe031)) {
// wiserSmartValveCalibrationStatus
const lookup = {0: 'ongoing', 1: 'successful', 2: 'uncalibrated', 3: 'failed_e1', 4: 'failed_e2', 5: 'failed_e3'};
result['valve_calibration_status'] = lookup[msg.data[0xe031]];
}
// Radiator thermostats command changes from UI, but report value periodically for sync,
// force an update of the value if it doesn't match the current existing value
if (meta.device.modelID === 'EH-ZB-VACT' &&
msg.data.hasOwnProperty('occupiedHeatingSetpoint') &&
meta.state.hasOwnProperty('occupied_heating_setpoint')) {
if (result.occupied_heating_setpoint != meta.state.occupied_heating_setpoint) {
const lookup = {'manual': 1, 'schedule': 2, 'energy_saver': 3, 'holiday': 6};
const zonemodeNum = lookup[meta.state.zone_mode];
const setpoint = (Math.round((meta.state.occupied_heating_setpoint * 2).toFixed(1)) / 2).toFixed(1) * 100;
const payload = {
operatingmode: 0,
zonemode: zonemodeNum,
setpoint: setpoint,
reserved: 0xff,
};
await msg.endpoint.command('hvacThermostat', 'wiserSmartSetSetpoint', payload,
{srcEndpoint: 11, disableDefaultResponse: true});

meta.logger.debug(`syncing vact setpoint was: '${result.occupied_heating_setpoint}'` +
` now: '${meta.state.occupied_heating_setpoint}'`);
}
} else {
publish(result);
}
},
},
wiser_smart_setpoint_command_client: {
cluster: 'hvacThermostat',
type: ['command', 'commandWiserSmartSetSetpoint'],
convert: (model, msg, publish, options, meta) => {
const attribute = {};
const result = {};

// The UI client on the thermostat also updates the server, so no need to readback/send again on next sync.
// This also ensures the next client read of setpoint is in sync with the latest commanded value.
attribute['occupiedHeatingSetpoint'] = msg.data['setpoint'];
msg.endpoint.saveClusterAttributeKeyValue('hvacThermostat', attribute);

result['occupied_heating_setpoint'] = parseFloat(msg.data['setpoint']) / 100.0;

meta.logger.debug(`received wiser setpoint command with value: '${msg.data['setpoint']}'`);
return result;
},
},

// #endregion

Expand Down
39 changes: 39 additions & 0 deletions converters/toZigbee.js
Original file line number Diff line number Diff line change
Expand Up @@ -5016,6 +5016,45 @@ const converters = {
}
},
},
wiser_vact_calibrate_valve: {
key: ['calibrate_valve'],
convertSet: async (entity, key, value, meta) => {
await entity.command('hvacThermostat', 'wiserSmartCalibrateValve', {},
{srcEndpoint: 11, disableDefaultResponse: true, sendWhenActive: true});
return {state: {'calibrate_valve': value}};
},
},
wiser_sed_zone_mode: {
key: ['zone_mode'],
convertSet: async (entity, key, value, meta) => {
return {state: {'zone_mode': value}};
},
},
wiser_sed_occupied_heating_setpoint: {
key: ['occupied_heating_setpoint'],
convertSet: async (entity, key, value, meta) => {
const occupiedHeatingSetpoint = (Math.round((value * 2).toFixed(1)) / 2).toFixed(1) * 100;
entity.saveClusterAttributeKeyValue('hvacThermostat', {occupiedHeatingSetpoint});
return {state: {'occupied_heating_setpoint': value}};
},
},
wiser_sed_thermostat_local_temperature_calibration: {
key: ['local_temperature_calibration'],
convertSet: (entity, key, value, meta) => {
entity.write('hvacThermostat', {localTemperatureCalibration: Math.round(value * 10)},
{srcEndpoint: 11, disableDefaultResponse: true, sendWhenActive: true});
return {state: {local_temperature_calibration: value}};
},
},
wiser_sed_thermostat_keypad_lockout: {
key: ['keypad_lockout'],
convertSet: async (entity, key, value, meta) => {
const keypadLockout = utils.getKey(constants.keypadLockoutMode, value, value, Number);
await entity.write('hvacUserInterfaceCfg', {keypadLockout},
{srcEndpoint: 11, disableDefaultResponse: true, sendWhenActive: true});
return {state: {keypad_lockout: value}};
},
},

// #endregion

Expand Down
130 changes: 130 additions & 0 deletions devices/schneider_electric.js
Original file line number Diff line number Diff line change
Expand Up @@ -214,4 +214,134 @@ module.exports = [
await reporting.currentSummDelivered(endpoint2, {min: 0, max: 60, change: 1});
},
},
{
zigbeeModel: ['EH-ZB-SPD-V2'],
model: 'EER40030',
vendor: 'Schneider Electric',
description: 'Zigbee smart plug with power meter',
fromZigbee: [fz.on_off, fz.metering],
toZigbee: [tz.on_off],
exposes: [e.switch(), e.power(), e.energy()],
configure: async (device, coordinatorEndpoint, logger) => {
const endpoint = device.getEndpoint(11);
const options = {disableDefaultResponse: true};
await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'seMetering']);
await reporting.onOff(endpoint);
await reporting.readMeteringMultiplierDivisor(endpoint);
await reporting.instantaneousDemand(endpoint);
await endpoint.write('genBasic', {0xe050: {value: 1, type: 0x10}}, options);
},
},
{
zigbeeModel: ['EH-ZB-LMACT'],
model: 'EER42000',
vendor: 'Schneider Electric',
description: 'Zigbee load actuator with power meter',
fromZigbee: [fz.on_off, fz.metering],
toZigbee: [tz.on_off],
exposes: [e.switch(), e.power(), e.energy()],
configure: async (device, coordinatorEndpoint, logger) => {
const endpoint = device.getEndpoint(11);
await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'seMetering']);
await reporting.onOff(endpoint);
await reporting.readMeteringMultiplierDivisor(endpoint);
await reporting.instantaneousDemand(endpoint);
},
},
{
zigbeeModel: ['EH-ZB-VACT'],
model: 'EER53000',
vendor: 'Schneider Electric',
description: 'Wiser radiator thermostat (VACT)',
fromZigbee: [fz.ignore_basic_report, fz.ignore_genOta, fz.ignore_zclversion_read, fz.battery, fz.hvac_user_interface,
fz.wiser_smart_thermostat, fz.wiser_smart_thermostat_client, fz.wiser_smart_setpoint_command_client],
toZigbee: [tz.thermostat_local_temperature, tz.wiser_sed_thermostat_local_temperature_calibration,
tz.wiser_sed_occupied_heating_setpoint, tz.wiser_sed_thermostat_keypad_lockout, tz.wiser_vact_calibrate_valve,
tz.wiser_sed_zone_mode],
exposes: [e.battery(),
exposes.binary('keypad_lockout', ea.STATE_SET, 'lock1', 'unlock')
.withDescription('Enables/disables physical input on the device'),
exposes.binary('calibrate_valve', ea.STATE_SET, 'calibrate', 'idle')
.withDescription('Calibrates valve on next wakeup'),
exposes.enum('valve_calibration_status',
ea.STATE, ['ongoing', 'successful', 'uncalibrated', 'failed_e1', 'failed_e2', 'failed_e3']),
exposes.enum('zone_mode',
ea.STATE_SET, ['manual', 'schedule', 'energy_saver', 'holiday'])
.withDescription('Icon shown on device displays'),
exposes.climate()
.withSetpoint('occupied_heating_setpoint', 7, 30, 0.5, ea.STATE_SET)
.withLocalTemperature(ea.STATE_GET)
.withLocalTemperatureCalibration(ea.STATE_SET)
.withPiHeatingDemand()],
meta: {battery: {voltageToPercentage: '3V_2500'}},
configure: async (device, coordinatorEndpoint, logger) => {
const endpoint = device.getEndpoint(11);
// Insert default values for client requested attributes
endpoint.saveClusterAttributeKeyValue('hvacThermostat', {minHeatSetpointLimit: 7*100});
endpoint.saveClusterAttributeKeyValue('hvacThermostat', {maxHeatSetpointLimit: 30*100});
endpoint.saveClusterAttributeKeyValue('hvacThermostat', {occupiedHeatingSetpoint: 20*100});
endpoint.saveClusterAttributeKeyValue('hvacThermostat', {systemMode: 4});
// VACT needs binding to endpoint 11 due to some hardcoding in the device
const coordinatorEndpointB = coordinatorEndpoint.getDevice().getEndpoint(11);
const binds = ['genBasic', 'genPowerCfg', 'hvacThermostat'];
await reporting.bind(endpoint, coordinatorEndpointB, binds);
await reporting.batteryVoltage(endpoint);
await reporting.thermostatTemperature(endpoint, {min: constants.repInterval.MINUTE,
max: constants.repInterval.MINUTES_15, change: 50});
await reporting.thermostatOccupiedHeatingSetpoint(endpoint, {min: 0, max: constants.repInterval.MINUTES_15, change: 25});
await endpoint.configureReporting('hvacUserInterfaceCfg', [{attribute: 'keypadLockout',
minimumReportInterval: constants.repInterval.MINUTE,
maximumReportInterval: constants.repInterval.HOUR,
reportableChange: 1}]);
},
},
{
zigbeeModel: ['EH-ZB-RTS'],
model: 'EER51000',
vendor: 'Schneider Electric',
description: 'Wiser thermostat (RTS)',
fromZigbee: [fz.ignore_basic_report, fz.ignore_genOta, fz.ignore_zclversion_read, fz.battery, fz.hvac_user_interface,
fz.wiser_smart_thermostat_client, fz.wiser_smart_setpoint_command_client, fz.temperature],
toZigbee: [tz.wiser_sed_zone_mode, tz.wiser_sed_occupied_heating_setpoint],
exposes: [e.battery(), e.temperature(),
exposes.climate().withSetpoint('occupied_heating_setpoint', 7, 30, 0.5, ea.STATE_SET),
exposes.enum('zone_mode',
ea.STATE_SET, ['manual', 'schedule', 'energy_saver', 'holiday'])
.withDescription('Icon shown on device displays')],
meta: {battery: {voltageToPercentage: '4LR6AA1_5v'}},
configure: async (device, coordinatorEndpoint, logger) => {
const endpoint = device.getEndpoint(11);
// Insert default values for client requested attributes
endpoint.saveClusterAttributeKeyValue('hvacThermostat', {minHeatSetpointLimit: 7*100});
endpoint.saveClusterAttributeKeyValue('hvacThermostat', {maxHeatSetpointLimit: 30*100});
endpoint.saveClusterAttributeKeyValue('hvacThermostat', {occupiedHeatingSetpoint: 20*100});
endpoint.saveClusterAttributeKeyValue('hvacThermostat', {systemMode: 4});
// RTS needs binding to endpoint 11 due to some hardcoding in the device
const coordinatorEndpointB = coordinatorEndpoint.getDevice().getEndpoint(11);
const binds = ['genBasic', 'genPowerCfg', 'genIdentify', 'genAlarms', 'genOta', 'hvacThermostat',
'hvacUserInterfaceCfg', 'msTemperatureMeasurement'];
await reporting.bind(endpoint, coordinatorEndpointB, binds);
// Battery reports without config once a day, do the first read manually
await endpoint.read('genPowerCfg', ['batteryVoltage']);
await endpoint.configureReporting('msTemperatureMeasurement', [{attribute: 'measuredValue',
minimumReportInterval: constants.repInterval.MINUTE,
maximumReportInterval: constants.repInterval.MINUTES_10,
reportableChange: 50}]);
},
},
{
zigbeeModel: ['EH-ZB-HACT'],
model: 'EER50000',
vendor: 'Schneider Electric',
description: 'Wiser H-Relay (HACT)',
fromZigbee: [fz.ignore_basic_report, fz.ignore_genOta, fz.ignore_zclversion_read, fz.wiser_smart_thermostat],
toZigbee: [tz.thermostat_occupied_heating_setpoint, tz.wiser_sed_zone_mode],
exposes: [exposes.climate().withSetpoint('occupied_heating_setpoint', 7, 30, 0.5)],
configure: async (device, coordinatorEndpoint, logger) => {
const endpoint = device.getEndpoint(11);
const binds = ['genBasic', 'genPowerCfg', 'hvacThermostat', 'msTemperatureMeasurement'];
await reporting.bind(endpoint, coordinatorEndpoint, binds);
await reporting.thermostatOccupiedHeatingSetpoint(endpoint, {min: 0, max: constants.repInterval.MINUTES_15, change: 25});
},
},
];

0 comments on commit 099be24

Please sign in to comment.