Skip to content

Instantly share code, notes, and snippets.

@Koenkk

Koenkk/ext.js Secret

Last active January 17, 2024 19:46
Show Gist options
  • Save Koenkk/3e55da9665e4a50570af7bff210f5816 to your computer and use it in GitHub Desktop.
Save Koenkk/3e55da9665e4a50570af7bff210f5816 to your computer and use it in GitHub Desktop.
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 legacy = require('zigbee-herdsman-converters/lib/legacy');
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 m = require('zigbee-herdsman-converters/lib/modernExtend');
const e = exposes.presets;
const ea = exposes.access;
const skip = {
stateOnAndBrightnessPresent: (meta) => {
const convertedKey = meta.mapped.meta.multiEndpoint && meta.endpoint_name ? `state_${meta.endpoint_name}` : 'state';
return meta.message.hasOwnProperty('brightness') && meta.state[convertedKey] === meta.message.state;
},
}
const tzLocal = {
datapoints: {
key: [
'temperature_unit', 'temperature_calibration', 'humidity_calibration', 'alarm_switch', 'tamper_alarm_switch',
'state', 'brightness', 'min_brightness', 'max_brightness', 'power_on_behavior', 'position', 'alarm_melody', 'alarm_mode',
'countdown', 'light_type', 'silence', 'self_test', 'child_lock', 'open_window', 'open_window_temperature', 'frost_protection',
'system_mode', 'heating_stop', 'current_heating_setpoint', 'local_temperature_calibration', 'preset', 'boost_timeset_countdown',
'holiday_start_stop', 'holiday_temperature', 'comfort_temperature', 'eco_temperature', 'working_day',
'week_schedule_programming', 'online', 'holiday_mode_date', 'schedule', 'schedule_monday', 'schedule_tuesday',
'schedule_wednesday', 'schedule_thursday', 'schedule_friday', 'schedule_saturday', 'schedule_sunday', 'clear_fault',
'scale_protection', 'error', 'radar_scene', 'radar_sensitivity', 'tumble_alarm_time', 'tumble_switch', 'fall_sensitivity',
'min_temperature', 'max_temperature', 'window_detection', 'boost_heating', 'alarm_ringtone', 'alarm_time', 'fan_speed',
'reverse_direction', 'border', 'click_control', 'motor_direction', 'opening_mode', 'factory_reset', 'set_upper_limit', 'set_bottom_limit',
'motor_speed', 'timer', 'reset_frost_lock', 'schedule_periodic', 'schedule_weekday', 'schedule_holiday', 'backlight_mode', 'calibration',
'motor_steering', 'mode', 'lower', 'upper', 'delay', 'reverse', 'touch', 'program', 'light_mode', 'switch_mode',
...[1, 2, 3, 4, 5, 6].map((no) => `schedule_slot_${no}`), 'minimum_range', 'maximum_range', 'detection_delay', 'fading_time',
'radar_sensitivity', 'entry_sensitivity', 'illuminance_threshold', 'detection_range', 'shield_range', 'entry_distance_indentation',
'entry_filter_time', 'departure_delay', 'block_time', 'status_indication', 'breaker_mode', 'breaker_status',
'alarm', 'alarm_time', 'alarm_volume', 'type', 'volume', 'ringtone', 'duration', 'medium_motion_detection_distance',
'large_motion_detection_distance', 'large_motion_detection_sensitivity', 'small_motion_detection_distance',
'small_motion_detection_sensitivity', 'static_detection_distance', 'static_detection_sensitivity', 'keep_time', 'indicator',
'motion_sensitivity', 'detection_distance_max', 'detection_distance_min', 'presence_sensitivity', 'sensitivity', 'illuminance_interval',
'medium_motion_detection_sensitivity', 'small_detection_distance', 'small_detection_sensitivity', 'fan_mode', 'deadzone_temperature',
'eco_mode', 'max_temperature_limit', 'min_temperature_limit', 'manual_mode',
'medium_motion_detection_sensitivity', 'small_detection_distance', 'small_detection_sensitivity', 'switch_type',
'ph_max', 'ph_min', 'ec_max', 'ec_min', 'orp_max', 'orp_min', 'free_chlorine_max', 'free_chlorine_min', 'target_distance',
'illuminance_treshold_max', 'illuminance_treshold_min', 'presence_illuminance_switch', 'light_switch', 'light_linkage',
'indicator_light', 'find_switch', 'detection_method', 'sensor', 'hysteresis', 'max_temperature_protection', 'display_brightness',
'screen_orientation', 'regulator_period', 'regulator_set_point', 'upper_stroke_limit', 'middle_stroke_limit', 'lower_stroke_limit',
],
convertSet: async (entity, key, value, meta) => {
// A set converter is only called once; therefore we need to loop
const state = {};
if (Array.isArray(meta.mapped)) throw new Error(`Not supported for groups`);
const datapoints = meta.mapped.meta?.tuyaDatapoints;
if (!datapoints) throw new Error('No datapoints map defined');
for (const [attr, value] of Object.entries(meta.message)) {
const convertedKey = meta.mapped.meta.multiEndpoint && meta.endpoint_name && !attr.startsWith(`${key}_`) ?
`${attr}_${meta.endpoint_name}` : attr;
const dpEntry = datapoints.find((d) => d[1] === convertedKey);
if (!dpEntry?.[1] || !dpEntry?.[2].to) {
throw new Error(`No datapoint defined for '${attr}'`);
}
if (dpEntry[3] && dpEntry[3].skip && dpEntry[3].skip(meta)) continue;
const dpId = dpEntry[0];
const convertedValue = await dpEntry[2].to(value, meta);
const sendCommand = utils.getMetaValue(entity, meta.mapped, 'tuyaSendCommand', undefined, 'dataRequest');
if (convertedValue === undefined) {
// conversion done inside converter, ignore.
} else if (typeof convertedValue === 'boolean') {
await tuya.sendDataPointBool(entity, dpId, convertedValue, sendCommand, 1);
} else if (typeof convertedValue === 'number') {
await tuya.sendDataPointValue(entity, dpId, convertedValue, sendCommand, 1);
} else if (typeof convertedValue === 'string') {
await tuya.sendDataPointStringBuffer(entity, dpId, convertedValue, sendCommand, 1);
} else if (Array.isArray(convertedValue)) {
await tuya.sendDataPointRaw(entity, dpId, convertedValue, sendCommand, 1);
} else if (convertedValue instanceof Enum) {
await tuya.sendDataPointEnum(entity, dpId, convertedValue.valueOf(), sendCommand, 1);
} else if (convertedValue instanceof Bitmap) {
await tuya.sendDataPointBitmap(entity, dpId, convertedValue.valueOf(), sendCommand, 1);
} else {
throw new Error(`Don't know how to send type '${typeof convertedValue}'`);
}
if (dpEntry[3] && dpEntry[3].optimistic === false) continue;
state[key] = value;
await utils.sleep(1000); // <- added this
}
return {state};
},
}
}
const definition = {
fingerprint: tuya.fingerprint('TS0601', ['_TZE200_fjjbhx9d', '_TZE200_e3oitdyu', '_TZE200_gwkapsoq', '_TZE204_zenj4lxv']),
model: 'TS0601_dimmer_2',
vendor: 'TuYa',
description: '2 gang smart dimmer',
fromZigbee: [tuya.fz.datapoints],
toZigbee: [tzLocal.datapoints],
configure: tuya.configureMagicPacket,
exposes: [
tuya.exposes.lightBrightnessWithMinMax().withEndpoint('l1'),
tuya.exposes.lightBrightnessWithMinMax().withEndpoint('l2'),
tuya.exposes.countdown().withEndpoint('l1'),
tuya.exposes.countdown().withEndpoint('l2'),
e.power_on_behavior().withAccess(ea.STATE_SET),
tuya.exposes.backlightModeOffNormalInverted().withAccess(ea.STATE_SET),
],
meta: {
multiEndpoint: true,
tuyaDatapoints: [
[1, 'state_l1', tuya.valueConverter.onOff, {skip: skip.stateOnAndBrightnessPresent}],
[2, 'brightness_l1', tuya.valueConverter.scale0_254to0_1000],
[3, 'min_brightness_l1', tuya.valueConverter.scale0_254to0_1000],
[5, 'max_brightness_l1', tuya.valueConverter.scale0_254to0_1000],
[6, 'countdown_l1', tuya.valueConverter.countdown],
[7, 'state_l2', tuya.valueConverter.onOff, {skip: skip.stateOnAndBrightnessPresent}],
[8, 'brightness_l2', tuya.valueConverter.scale0_254to0_1000],
[9, 'min_brightness_l2', tuya.valueConverter.scale0_254to0_1000],
[11, 'max_brightness_l2', tuya.valueConverter.scale0_254to0_1000],
[12, 'countdown_l2', tuya.valueConverter.countdown],
[14, 'power_on_behavior', tuya.valueConverter.powerOnBehaviorEnum],
[21, 'backlight_mode', tuya.valueConverter.backlightModeOffNormalInverted],
],
},
endpoint: (device) => {
return {'l1': 1, 'l2': 1};
},
whiteLabel: [
{vendor: 'Moes', model: 'ZS-EUD_2gang'},
{vendor: 'Moes', model: 'MS-105B'}, // _TZE200_e3oitdyu
tuya.whitelabel('Moes', 'ZS-SR-EUD-2', 'Star ring smart dimmer switch 2 gangs', ['_TZE204_zenj4lxv']),
],
};
module.exports = definition;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment