zigbee2mqtt icon indicating copy to clipboard operation
zigbee2mqtt copied to clipboard

[New device support]: Namron Zigbee Thermostat 16A White

Open ThaProZac opened this issue 1 year ago • 37 comments

Link

https://www.namron.com/products/namron-zigbee-termostat-16a-hvit/

Database entry

{"id":4,"type":"Router","ieeeAddr":"0x70ac08fffe6d0475","nwkAddr":49234,"manufId":4098,"manufName":"Namron AS","powerSource":"Mains (single phase)","modelId":"4512758","epList":[1],"endpoints":{"1":{"profId":260,"epId":1,"devId":769,"inClusterList":[0,3,4,5,6,513,516,1794,2820],"outClusterList":[3,10,25],"clusters":{"genOnOff":{"attributes":{"onOff":1}},"genBasic":{"attributes":{"modelId":"4512758","manufacturerName":"Namron AS","appVersion":0,"stackVersion":0,"hwVersion":0}}},"binds":[],"configuredReportings":[],"meta":{}}},"appVersion":0,"stackVersion":0,"hwVersion":0,"dateCode":"20230711","swBuildId":"1.07","zclVersion":8,"interviewCompleted":true,"meta":{},"lastSeen":1702597418555,"defaultSendRequestWhen":"immediate"}

Comments

Completely new to HA and this Thermostat had way too many options. A fellow tester (all creds to him) did some testing with this and he made the code linked below. Make note, for this to work you also need to modify the zigbee2mqtt conf yaml file and add the following after friendly name: homeassistant: climate: action_template: |- {% if value_json.system_mode != 'off' or value_json.power > 0 %} {% set values = {'off': 'idle','idle':'idle','heat':'heating','cool':'cooling'} %} {{ values[value_json.running_mode] }} {% else %} off {% endif %}

External converter

const fz = require('zigbee-herdsman-converters/converters/fromZigbee');
const tz = require('zigbee-herdsman-converters/converters/toZigbee');
const exposes = require('zigbee-herdsman-converters/lib/exposes');
const reporting = require('zigbee-herdsman-converters/lib/reporting');
const extend = require('zigbee-herdsman-converters/lib/extend');
const constants = require('zigbee-herdsman-converters/lib/constants');
const e = exposes.presets;
const ea = exposes.access;
 
const utils = require('zigbee-herdsman-converters/lib/utils');
const {
    Zcl
} = require('zigbee-herdsman');
 
const local = {
    fz: {
        namron_thermostat_new: {
            cluster: 'hvacThermostat',
            type: ['readResponse', 'attributeReport'],
            convert: (model, msg, publish, options, meta) => {
                const result = KeyValue = {};
                const data = msg.data;
                if (data.hasOwnProperty('pIHeatingDemand')) { // regulator_percentage
                    result['pi_heating_demand'] = data['pIHeatingDemand'];
                }
                if (data.hasOwnProperty('runningMode')) { // runningMode
                    const lookup = {0: 'off', 3: 'cool', 4: 'heat', 16: 'idle'};
                    result['running_mode'] = utils.getFromLookup(data['runningMode'], lookup);
                }
                if (data.hasOwnProperty(0x8000)) { // windowCheck
                    result['window_check'] = data[0x8000];
                }
                if (data.hasOwnProperty(0x8001)) { // frost
                    result['frost'] = data[0x8001];
                }
                if (data.hasOwnProperty(0x8002)) { // windowState
                    result['window_state'] = data[0x8002] ? 'opened' : 'closed';
                }
                if (data.hasOwnProperty(0x8004)) { // sensorMode
                    const lookup = { 0: 'a', 1: 'f', 2: 'af', 3: 'a2', 4: 'a2f', 5: 'fp', 6: 'p'};
                    result['sensor_mode'] = utils.getFromLookup(data[0x8004], lookup);
                }
                if (data.hasOwnProperty(0x8005)) { // backlight
                    result['backlight'] = parseFloat(data[0x8005]);
                }
                if (data.hasOwnProperty(0x8006)) { // fault
                    const lookup = {0: 'none', 1: 'er1', 2: 'er2', 4: 'er3', 8: 'er4', 16: 'er5', 32: 'er6', 64: 'er7', 128: 'er8'};
                    result['fault'] = utils.getFromLookup(data[0x8006], lookup);
                }
                if (data.hasOwnProperty(0x8007)) { // regulator
                    result['regulator'] = data[0x8007];
                }
                return result;
            },
        },
    },
 
    tz: {
        namron_thermostat_new: {
            key: ['pi_heating_demand', 'running_mode', 'window_check', 'frost', 'window_state',
            'sensor_mode', 'backlight', 'fault', 'regulator'],
            convertSet: async(entity, key, value, meta) => {
                if (key === 'pi_heating_demand') {
                    const payload = {'pIHeatingDemand': {value: parseInt(value), type: Zcl.DataType.int8}};
                    await entity.write('hvacThermostat', payload);
                }
                else if (key === 'running_mode') {
                    const lookup = {'off': 0, 'cool': 3, 'heat': 4, 'idle': 16};
                    const payload = {'runningMode': {value: utils.getFromLookup(value, lookup), type: Zcl.DataType.enum8}};
                    await entity.write('hvacThermostat', payload);
                }
                else if (key === 'window_check') {
                    const lookup = {'disabled': false, 'enabled': true};
                    const payload = {0x8000: {value: utils.getFromLookup(value, lookup), type: Zcl.DataType.boolean}};
                    await entity.write('hvacThermostat', payload);
                }
                else if (key === 'frost') {
                    const lookup = {'disabled': false, 'enabled': true};
                    const payload = {0x8001: {value: utils.getFromLookup(value, lookup), type: Zcl.DataType.boolean}};
                    await entity.write('hvacThermostat', payload);
                }
                else if (key === 'window_state') {
                    const lookup = {'closed': false, 'opened': true};
                    const payload = {0x8002: {value: utils.getFromLookup(value, lookup), type: Zcl.DataType.boolean}};
                    await entity.write('hvacThermostat', payload);
                }
                else if (key === 'sensor_mode') {
                    const lookup = {'a': 0, 'f': 1, 'af': 2, 'a2': 3, 'a2f': 4, 'fp': 5, 'p': 6};
                    const payload = {0x8004: {value: utils.getFromLookup(value, lookup), type: Zcl.DataType.enum8}};
                    await entity.write('hvacThermostat', payload);
                }
                else if (key === 'backlight') {
                    const payload = {0x8005: {value: value, type: Zcl.DataType.uint8}};
                    await entity.write('hvacThermostat', payload);
                }
                else if (key === 'fault') {
                    const lookup = {'none': 0, 'er1': 1, 'er2': 2, 'er3': 3, 'er4': 4, 'er5': 5, 'er6': 6, 'er7': 7, 'er8': 8};
                    const payload = {0x8006: {value: utils.getFromLookup(value, lookup), type: Zcl.DataType.enum8}};
                    await entity.write('hvacThermostat', payload);
                }
                else if (key === 'regulator') {
                    const payload = {0x8007: {value: value, type: Zcl.DataType.uint8}};
                    await entity.write('hvacThermostat', payload);
                }
            },
            convertGet: async (entity, key, meta) => {
                switch (key) {
                case 'pi_heating_demand':
                    await entity.read('hvacThermostat', ['pIHeatingDemand']);
                    break;
                case 'running_mode':
                    await entity.read('hvacThermostat', ['runningMode']);
                    break;
                case 'window_check':
                    await entity.read('hvacThermostat', [0x8000]);
                    break;
                case 'frost':
                    await entity.read('hvacThermostat', [0x8001]);
                    break;
                case 'window_state':
                    await entity.read('hvacThermostat', [0x8002]);
                    break;
                case 'sensor_mode':
                    await entity.read('hvacThermostat', [0x8004]);
                    break;
                case 'backlight':
                    await entity.read('hvacThermostat', [0x8005]);
                    break;
                case 'fault':
                    await entity.read('hvacThermostat', [0x8006]);
                    break;
                case 'regulator':
                    await entity.read('hvacThermostat', [0x8007]);
                    break;
                default: // Unknown key
                    throw new Error(`Unhandled key local.tz.namron_thermostat_new.convertGet ${key}`);
                }
            },
        },
    }
};
 
const definition = {
    zigbeeModel: ['4512758'],
    model: '4512758',
    vendor: 'Namron',
    description: 'Namron thermostat and power regulator',
 
    fromZigbee: [fz.thermostat, fz.metering, fz.electrical_measurement,
        fz.namron_hvac_user_interface, fz.namron_thermostat, local.fz.namron_thermostat_new],
    toZigbee: [tz.thermostat_occupied_heating_setpoint, tz.thermostat_unoccupied_heating_setpoint, tz.thermostat_occupancy,
        tz.thermostat_local_temperature_calibration, tz.thermostat_local_temperature, tz.thermostat_outdoor_temperature,
        tz.thermostat_system_mode, tz.thermostat_control_sequence_of_operation,
        tz.namron_thermostat_child_lock, tz.namron_thermostat, local.tz.namron_thermostat_new],
 
    exposes: [
        e.climate()
            .withSetpoint('occupied_heating_setpoint', 5, 40, 0.5)
            .withLocalTemperature()
            .withLocalTemperatureCalibration(-3, 3, 0.1)
            .withSystemMode(['off', 'cool', 'heat'])
            .withRunningState(['idle', 'cool', 'heat'], ea.STATE),
 
        e.power(), e.current(),
 
        e.binary('child_lock', ea.ALL, 'LOCK', 'UNLOCK')
            .withDescription('Enables/disables physical input on the device'),
 
        e.binary('window_check', ea.ALL, 'enabled', 'disabled'),
        e.binary('window_state', ea.STATE_GET, 'opened', 'closed'),
        e.binary('frost', ea.ALL, 'enabled', 'disabled'),
        e.enum('sensor_mode', ea.ALL, ['a', 'f', 'af', 'a2', 'a2f', 'fp', 'p']),
        e.enum('fault', ea.STATE_GET, ['none', 'er1', 'er2', 'er3', 'er4', 'er5', 'er6', 'er7', 'er8']),
 
        e.numeric('backlight', ea.ALL)
            .withValueMin(0).withValueMax(100).withValueStep(5)
            .withUnit('%'),
        e.numeric('regulator', ea.ALL)
            .withValueMin(0).withValueMax(30).withValueStep(1)
            .withUnit('minutes'),
        e.numeric('pi_heating_demand', ea.ALL)
            .withValueMin(0).withValueMax(100).withValueStep(10)
            .withUnit('%'),
    ],
 
    configure: async (device, coordinatorEndpoint, logger) => {
        const endpoint = device.getEndpoint(1);
        const binds = [
            'seMetering', 'haElectricalMeasurement', 'hvacThermostat', 'hvacUserInterfaceCfg',
        ];
        await reporting.bind(endpoint, coordinatorEndpoint, binds);
        // Metering
        await endpoint.read('haElectricalMeasurement', ['acCurrentMultiplier', 'acCurrentDivisor', 'acPowerMultiplier', 'acPowerDivisor']);
        await reporting.readMeteringMultiplierDivisor(endpoint);
        await reporting.rmsCurrent(endpoint, {min: 5, change: 10});
        await reporting.activePower(endpoint, {min: 5, change: 15});
 
        await reporting.thermostatTemperature(endpoint, {min: 0, change: 50});
        await reporting.thermostatOccupiedHeatingSetpoint(endpoint, {min: 5});
        await reporting.thermostatKeypadLockMode(endpoint, {min: 5});
 
        await reporting.thermostatSystemMode(endpoint, {min: 5});
        await reporting.thermostatPIHeatingDemand(endpoint, {min: 5});
        await reporting.thermostatRunningMode(endpoint);
 
        const options = {};
 
        // Custom
        await endpoint.configureReporting('hvacThermostat', [{ // windowCheck
            attribute: {ID: 0x8000, type: 16},
            minimumReportInterval: 5,
            maximumReportInterval: constants.repInterval.HOUR,
            reportableChange: null}],
        options);
        await endpoint.configureReporting('hvacThermostat', [{ // frost
            attribute: {ID: 0x8001, type: 16},
            minimumReportInterval: 5,
            maximumReportInterval: constants.repInterval.HOUR,
            reportableChange: null}],
        options);
        await endpoint.configureReporting('hvacThermostat', [{ // windowState
            attribute: {ID: 0x8002, type: 16},
            minimumReportInterval: 0,
            maximumReportInterval: constants.repInterval.HOUR,
            reportableChange: null}],
        options);
        await endpoint.configureReporting('hvacThermostat', [{ // sensorMode
            attribute: {ID: 0x8004, type: 48},
            minimumReportInterval: 5,
            maximumReportInterval: constants.repInterval.HOUR,
            reportableChange: null}],
        options);
        await endpoint.configureReporting('hvacThermostat', [{ // backlight
            attribute: {ID: 0x8005, type: 32},
            minimumReportInterval: 5,
            maximumReportInterval: constants.repInterval.HOUR,
            reportableChange: null}],
        options);
        await endpoint.configureReporting('hvacThermostat', [{ // fault
            attribute: {ID: 0x8006, type: 24},
            minimumReportInterval: 0,
            maximumReportInterval: constants.repInterval.HOUR,
            reportableChange: null}],
        options);
        await endpoint.configureReporting('hvacThermostat', [{ // regulator
            attribute: {ID: 0x8007, type: 32},
            minimumReportInterval: 0,
            maximumReportInterval: constants.repInterval.HOUR,
            reportableChange: null}],
        options);
 
        await endpoint.read('hvacThermostat', ['systemMode', 'runningMode', 'occupiedHeatingSetpoint']);
        await endpoint.read('hvacThermostat', [0x8000, 0x8001, 0x8002, 0x8004, 0x8005, 0x8006, 0x8007]);
    },
};
 
module.exports = definition;

Supported color modes

No response

Color temperature range

No response

ThaProZac avatar Dec 15 '23 00:12 ThaProZac