Skip to content

Instantly share code, notes, and snippets.

@marazmarci
Created April 13, 2025 09:22
Show Gist options
  • Select an option

  • Save marazmarci/a80ca373d247863983cef2266fb2914e to your computer and use it in GitHub Desktop.

Select an option

Save marazmarci/a80ca373d247863983cef2266fb2914e to your computer and use it in GitHub Desktop.
SODA S8 Window Handle Zigbee2MQTT external converter v1: cached state
const exposes = require("zigbee-herdsman-converters/lib/exposes");
const tuya = require("zigbee-herdsman-converters/lib/tuya");
const e = exposes.presets;
const ea = exposes.access;
const globalStore = require("zigbee-herdsman-converters/lib/store");
const fz = {
TZE200_j7sgd8po_SODA_S8: {
...tuya.fz.datapoints,
convert: (model, msg, publish, options, meta) => {
// The SODA S8 window handle reports its state in two consecutive messages.
// The first includes button states, while the second does not.
// To prevent button states from being lost in the published state, we store the last known values in `globalStore`
// and reapply them when the second message arrives without button state data.
// Note: Button press/release events send a single message containing only the relevant datapoint.
const result = tuya.fz.datapoints.convert(model, msg, publish, options, meta);
console.debug("[SODA S8 Window Handle] result of tuya.fz.datapoints.convert:", result);
if (!result) return;
const cachedState = globalStore.getValue(msg.device, "cachedState", {
button_left: "released", button_right: "released", duration: 180
});
// Update stored cached states if included in the message
if (result.button_left !== undefined) {
cachedState.button_left = result.button_left;
console.debug("[SODA S8 Window Handle] result.button_left !== undefined");
}
if (result.button_right !== undefined) {
cachedState.button_right = result.button_right;
console.debug("[SODA S8 Window Handle] result.button_right !== undefined");
}
if (result.duration !== undefined) {
cachedState.duration = result.duration;
console.debug("[SODA S8 Window Handle] result.duration !== undefined");
}
// Save updated button states back to global store
globalStore.putValue(msg.device, "cachedState", cachedState);
// Merge stored button states with the current message
const mergedResult = {
...result,
button_left: cachedState.button_left,
button_right: cachedState.button_right,
duration: cachedState.duration,
};
console.debug("[SODA S8 Window Handle] mergedResult", mergedResult);
return mergedResult;
},
},
};
module.exports = {
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_j7sgd8po"]),
model: "S8",
vendor: "SODA",
description: "S8 premium window handle EXT v1: cached state",
toZigbee: [tuya.tz.datapoints],
fromZigbee: [fz.TZE200_j7sgd8po_SODA_S8],
configure: tuya.configureMagicPacket,
exposes: [
e.battery(),
e.battery_low(),
e.binary("vacation", ea.STATE_SET, "ON", "OFF").withDescription("Vacation mode"),
e.enum("alarm", ea.STATE, ["ALARM", "IDLE"]).withDescription("Alarm"),
e.binary("alarm_switch", ea.STATE_SET, "ON", "OFF").withDescription("Alarm enable"),
e.binary("handlesound", ea.STATE_SET, "ON", "OFF").withDescription("Handle closed sound"),
e.enum("opening_mode", ea.STATE, ["closed", "tilted"]).withDescription("Window tilt"),
e.temperature(),
e.humidity(),
e.binary("keysound", ea.STATE_SET, "ON", "OFF").withDescription("Key beep sound"),
e.enum("sensitivity", ea.STATE_SET, ["off", "low", "medium", "high", "max"]).withDescription("Sensitivity of the alarm sensor"),
e.enum("position", ea.STATE, ["up", "right", "down", "left"]),
e.enum("button_left", ea.STATE, ["released", "pressed"]),
e.enum("button_right", ea.STATE, ["released", "pressed"]),
e.numeric("duration", ea.STATE_SET).withValueMin(0).withValueMax(300).withValueStep(1).withUnit("sec").withDescription("Duration of the alarm").withPreset("default", 180, "Default value"),
e.numeric("update_frequency", ea.STATE_SET).withUnit("min").withDescription("Update frequency").withValueMin(0).withValueMax(700).withPreset("default", 20, "Default value"),
e.enum("calibrate", ea.STATE_SET, ["clear", "execute"]),
],
meta: {
tuyaDatapoints: [
[3, "battery", tuya.valueConverter.raw],
[8, "temperature", tuya.valueConverter.divideBy10],
[101, "humidity", tuya.valueConverter.raw],
[102, "alarm", tuya.valueConverterBasic.lookup({IDLE: tuya.enum(0), ALARM: tuya.enum(1),}),],
[103, "opening_mode", tuya.valueConverterBasic.lookup({closed: tuya.enum(0), tilted: tuya.enum(1),}),],
[104, "position", tuya.valueConverterBasic.lookup({left: tuya.enum(4), up: tuya.enum(1), down: tuya.enum(2), right: tuya.enum(3),}),],
[105, "button_left", tuya.valueConverterBasic.lookup({released: tuya.enum(0), pressed: tuya.enum(1),}),],
[106, "button_right", tuya.valueConverterBasic.lookup({released: tuya.enum(0), pressed: tuya.enum(1),}),],
[107, "vacation", tuya.valueConverterBasic.lookup({OFF: tuya.enum(0), ON: tuya.enum(1),}),],
[108, "sensitivity", tuya.valueConverterBasic.lookup({off: tuya.enum(0), low: tuya.enum(1), medium: tuya.enum(2), high: tuya.enum(3), max: tuya.enum(4),}),],
[109, "alarm_switch", tuya.valueConverterBasic.lookup({OFF: tuya.enum(0), ON: tuya.enum(1),}),],
[110, "update_frequency", tuya.valueConverter.raw],
[111, "keysound", tuya.valueConverterBasic.lookup({OFF: tuya.enum(0), ON: tuya.enum(1),}),],
[112, "battery_low", tuya.valueConverterBasic.lookup({ON: tuya.enum(0), OFF: tuya.enum(1),}),],
[113, "duration", tuya.valueConverter.raw],
[114, "handlesound", tuya.valueConverterBasic.lookup({OFF: tuya.enum(0), ON: tuya.enum(1),}),],
[120, "calibrate", tuya.valueConverterBasic.lookup({clear: tuya.enum(0), execute: tuya.enum(1),}),],
],
},
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment