Skip to content

Instantly share code, notes, and snippets.

@steinuil
Last active January 9, 2024 01:25

Revisions

  1. steinuil renamed this gist Jan 9, 2024. 1 changed file with 6 additions and 2 deletions.
    8 changes: 6 additions & 2 deletions HY01BW.js → LL0211Z.js
    Original file line number Diff line number Diff line change
    @@ -29,8 +29,12 @@ const localTemperatureCalibrationConverter = {

    const definition = {
    fingerprint: tuya.fingerprint('TS0601', ['_TZE200_mhswlizg']),
    model: 'HY01BW',
    vendor: 'Hysen',
    whiteLabel: [
    { model: 'HY01BW', vendor: 'Hysen' },
    { model: 'LL0211Z', vendor: 'LEDLUX' },
    ],
    model: 'LL0211Z',
    vendor: 'LEDLUX',
    description: 'Wall-mount thermostat',
    fromZigbee: [
    tuya.fz.datapoints,
  2. steinuil created this gist Jan 9, 2024.
    106 changes: 106 additions & 0 deletions HY01BW.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,106 @@
    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 extend = require('zigbee-herdsman-converters/lib/extend');
    // const ota = require('zigbee-herdsman-converters/lib/ota');
    const tuya = require('zigbee-herdsman-converters/lib/tuya');
    // const utils = require('zigbee-herdsman-converters/lib/utils');
    // const globalStore = require('zigbee-herdsman-converters/lib/store');


    const e = exposes.presets;
    const ea = exposes.access;


    const localTemperatureCalibrationConverter = {
    from: (v) => Math.round(v / 10),
    to: (v) => {
    const value = Math.round(v * 10);
    if (value < 0) return 0xFFFFFFFF + value + 1;
    return value;
    },
    };


    // Unhandled non-datapoint messages:
    // manuSpecificTuya.commandMcuSyncTime
    // manuSpecificTuya.commandMcuVersionResponse


    const definition = {
    fingerprint: tuya.fingerprint('TS0601', ['_TZE200_mhswlizg']),
    model: 'HY01BW',
    vendor: 'Hysen',
    description: 'Wall-mount thermostat',
    fromZigbee: [
    tuya.fz.datapoints,
    ],
    toZigbee: [
    tuya.tz.datapoints,
    ],
    // Says you need to use this if you're getting no converter for commandMcuSyncTime,
    // but I'm still getting it.
    onEvent: tuya.onEventSetTime,
    configure: tuya.configureMagicPacket,
    exposes: [
    e.climate()
    .withLocalTemperature(ea.STATE_GET)
    .withSetpoint('current_heating_setpoint', 5, 35, 0.5, ea.STATE_SET)
    .withLocalTemperatureCalibration(-9, 9, 1, ea.STATE_SET)
    .withRunningState(['idle', 'heat'])
    .withRunningMode(['off', 'heat'], ea.STATE_SET),
    e.max_temperature()
    .withValueMin(20)
    .withValueMax(70),
    e.min_temperature()
    .withValueMin(1)
    .withValueMax(20),
    e.min_temperature_limit()
    .withValueMin(1)
    .withValueMax(10),
    e.binary('min_temperature_limit_status', ea.STATE_SET, 'ON', 'OFF')
    .withDescription('Enables/disables the minimum temperature limit'),
    e.binary('max_temperature_limit_status', ea.STATE_SET, 'ON', 'OFF')
    .withDescription('Enables/disables the maximum temperature limit'),
    e.child_lock(),
    e.enum('programming_operation_mode', ea.ALL, ['manual', 'schedule', 'away', 'temporary_manual'])
    .withDescription('Controls how programing affects the thermostat. Possible values: setpoint (only use specified setpoint), schedule (follow programmed setpoint schedule), away (only use specified away setpoint), temporary_manual (use specified setpoint until the local temperature reaches the setpoint, then switch back to schedule mode). Changing this value does not clear programmed schedules.'),
    e.away_preset_days(),
    e.away_preset_temperature(),
    e.week(),
    tuya.exposes.powerOutageMemory(),
    ],
    meta: {
    // Look at src/devices/tuya.ts:2947 for property names about holidays
    // Almost the same as src/devices/tuya.ts:4254
    tuyaDatapoints: [
    [102, 'running_state', tuya.valueConverterBasic.lookup({'idle': false, 'heat': true})],
    [103, 'external_temperature', tuya.valueConverter.divideBy10], // always reports 0 when sensor_type is internal
    [104, 'away_preset_days', tuya.valueConverter.raw],
    [105, 'away_preset_temperature', tuya.valueConverter.raw],
    [106, 'max_temperature_protection', tuya.valueConverter.onOff],
    [107, 'min_temperature_protection', tuya.valueConverter.onOff],
    // 108: unknown
    [109, 'local_temperature_calibration', localTemperatureCalibrationConverter],
    [110, 'hysteresis', tuya.valueConverter.divideBy10],
    [111, 'hysteresis_for_protection', tuya.valueConverter.divideBy10],
    [112, 'max_temperature_limit', tuya.valueConverter.raw],
    [113, 'min_temperature_limit', tuya.valueConverter.raw],
    [114, 'max_temperature', tuya.valueConverter.raw],
    [115, 'min_temperature', tuya.valueConverter.raw],
    [116, 'sensor_type', tuya.valueConverterBasic.lookup({'internal': 0, 'external': 1, 'both':2})],
    [117, 'power_outage_memory', tuya.valueConverterBasic.lookup({'restore': 0, 'off': 1, 'on': 2})],
    [118, 'week', tuya.valueConverterBasic.lookup({'5+2': 0, '6+1': 1, '7': 2})],
    // 119, 120, 121, 122: sent when min/max/limit temperature change. supposedly workday/holiday schedule.
    // 123-124: unknown
    [125, 'running_mode', tuya.valueConverterBasic.lookup({'off': false, 'heat': true})], // should be system_mode off/heat?
    [126, 'current_heating_setpoint', tuya.valueConverter.divideBy10],
    [127, 'local_temperature', tuya.valueConverter.divideBy10],
    [128, 'programming_operation_mode', tuya.valueConverterBasic.lookup({'manual': 0, 'schedule': 1, 'away': 2, 'temporary_manual': 3})],
    [129, 'child_lock', tuya.valueConverter.lockUnlock],
    [130, 'alarm', tuya.valueConverter.raw],
    ],
    },
    };

    module.exports = definition;