From 96337269877beddee847e09475ea4eb4975a4d60 Mon Sep 17 00:00:00 2001 From: tigum27 Date: Sun, 12 Jul 2026 18:02:41 -0400 Subject: [PATCH 1/4] Update tuya.ts (tuya smart floodlight support added ) --- src/devices/tuya.ts | 60 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/src/devices/tuya.ts b/src/devices/tuya.ts index 52085be8e49d6..4caa771fe5a6d 100644 --- a/src/devices/tuya.ts +++ b/src/devices/tuya.ts @@ -28614,4 +28614,64 @@ export const definitions: DefinitionWithExtend[] = [ ], }, }, + { + fingerprint: tuya.fingerprint("TS0601", ["_TZE284_oa1odmga"]), + model: "TS0601_floodlight", + vendor: "Tuya", + description: "Smart RGBCW floodlight", + fromZigbee: [tuya.fz.datapoints], + toZigbee: [tuya.tz.datapoints], + configure: tuya.configureMagicPacket, + exposes: [e.light().withBrightness().withColorTemp([153, 500]).withColor(["hs"])], + meta: { + tuyaDatapoints: [ + [1, "state", tuya.valueConverter.onOff], + // Standard Tuya light DP layout puts a work_mode enum on DP2 - + // writing a brightness number there gets silently ignored. + [ + 2, + "work_mode", + tuya.valueConverterBasic.lookup({ + white: tuya.enum(0), + colour: tuya.enum(1), + scene: tuya.enum(2), + music: tuya.enum(3), + }), + ], + // Brightness is 0-1000 on the device but 0-254 in Z2M. + [3, "brightness", tuya.valueConverter.scale0_254to0_1000], + [4, "color_temp", tuya.valueConverter.raw], + [ + 5, + "color", + { + to: (value) => { + // The exposed 'hs' composite uses the property + // names 'hue' and 'saturation', not 'h' and 's'. + if (value.hue !== undefined && value.saturation !== undefined) { + const hue = Math.round(value.hue); + const sat = Math.round(value.saturation * 10); // 0-100 -> 0-1000 + // Keep current brightness (V) instead of forcing full, + // fall back to full brightness if we don't know it yet. + const val = value.brightness !== undefined ? Math.round(value.brightness * 10) : 1000; + const hHex = hue.toString(16).padStart(4, "0"); + const sHex = sat.toString(16).padStart(4, "0"); + const vHex = val.toString(16).padStart(4, "0"); + return hHex + sHex + vHex; + } + return value; + }, + from: (value) => { + if (typeof value === "string" && value.length >= 12) { + const hue = Number.parseInt(value.substring(0, 4), 16); + const saturation = Number.parseInt(value.substring(4, 8), 16) / 10; + return {hue, saturation}; + } + return value; + }, + }, + ], + ], + }, +}, ]; From 1f1e98011391bee68c514a178075c45e085bb710 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sun, 12 Jul 2026 22:03:45 +0000 Subject: [PATCH 2/4] [autofix.ci] apply automated fixes --- src/devices/tuya.ts | 112 ++++++++++++++++++++++---------------------- 1 file changed, 56 insertions(+), 56 deletions(-) diff --git a/src/devices/tuya.ts b/src/devices/tuya.ts index 4caa771fe5a6d..58861b2df037f 100644 --- a/src/devices/tuya.ts +++ b/src/devices/tuya.ts @@ -28615,63 +28615,63 @@ export const definitions: DefinitionWithExtend[] = [ }, }, { - fingerprint: tuya.fingerprint("TS0601", ["_TZE284_oa1odmga"]), - model: "TS0601_floodlight", - vendor: "Tuya", - description: "Smart RGBCW floodlight", - fromZigbee: [tuya.fz.datapoints], - toZigbee: [tuya.tz.datapoints], - configure: tuya.configureMagicPacket, - exposes: [e.light().withBrightness().withColorTemp([153, 500]).withColor(["hs"])], - meta: { - tuyaDatapoints: [ - [1, "state", tuya.valueConverter.onOff], - // Standard Tuya light DP layout puts a work_mode enum on DP2 - - // writing a brightness number there gets silently ignored. - [ - 2, - "work_mode", - tuya.valueConverterBasic.lookup({ - white: tuya.enum(0), - colour: tuya.enum(1), - scene: tuya.enum(2), - music: tuya.enum(3), - }), - ], - // Brightness is 0-1000 on the device but 0-254 in Z2M. - [3, "brightness", tuya.valueConverter.scale0_254to0_1000], - [4, "color_temp", tuya.valueConverter.raw], - [ - 5, - "color", - { - to: (value) => { - // The exposed 'hs' composite uses the property - // names 'hue' and 'saturation', not 'h' and 's'. - if (value.hue !== undefined && value.saturation !== undefined) { - const hue = Math.round(value.hue); - const sat = Math.round(value.saturation * 10); // 0-100 -> 0-1000 - // Keep current brightness (V) instead of forcing full, - // fall back to full brightness if we don't know it yet. - const val = value.brightness !== undefined ? Math.round(value.brightness * 10) : 1000; - const hHex = hue.toString(16).padStart(4, "0"); - const sHex = sat.toString(16).padStart(4, "0"); - const vHex = val.toString(16).padStart(4, "0"); - return hHex + sHex + vHex; - } - return value; - }, - from: (value) => { - if (typeof value === "string" && value.length >= 12) { - const hue = Number.parseInt(value.substring(0, 4), 16); - const saturation = Number.parseInt(value.substring(4, 8), 16) / 10; - return {hue, saturation}; - } - return value; + fingerprint: tuya.fingerprint("TS0601", ["_TZE284_oa1odmga"]), + model: "TS0601_floodlight", + vendor: "Tuya", + description: "Smart RGBCW floodlight", + fromZigbee: [tuya.fz.datapoints], + toZigbee: [tuya.tz.datapoints], + configure: tuya.configureMagicPacket, + exposes: [e.light().withBrightness().withColorTemp([153, 500]).withColor(["hs"])], + meta: { + tuyaDatapoints: [ + [1, "state", tuya.valueConverter.onOff], + // Standard Tuya light DP layout puts a work_mode enum on DP2 - + // writing a brightness number there gets silently ignored. + [ + 2, + "work_mode", + tuya.valueConverterBasic.lookup({ + white: tuya.enum(0), + colour: tuya.enum(1), + scene: tuya.enum(2), + music: tuya.enum(3), + }), + ], + // Brightness is 0-1000 on the device but 0-254 in Z2M. + [3, "brightness", tuya.valueConverter.scale0_254to0_1000], + [4, "color_temp", tuya.valueConverter.raw], + [ + 5, + "color", + { + to: (value) => { + // The exposed 'hs' composite uses the property + // names 'hue' and 'saturation', not 'h' and 's'. + if (value.hue !== undefined && value.saturation !== undefined) { + const hue = Math.round(value.hue); + const sat = Math.round(value.saturation * 10); // 0-100 -> 0-1000 + // Keep current brightness (V) instead of forcing full, + // fall back to full brightness if we don't know it yet. + const val = value.brightness !== undefined ? Math.round(value.brightness * 10) : 1000; + const hHex = hue.toString(16).padStart(4, "0"); + const sHex = sat.toString(16).padStart(4, "0"); + const vHex = val.toString(16).padStart(4, "0"); + return hHex + sHex + vHex; + } + return value; + }, + from: (value) => { + if (typeof value === "string" && value.length >= 12) { + const hue = Number.parseInt(value.substring(0, 4), 16); + const saturation = Number.parseInt(value.substring(4, 8), 16) / 10; + return {hue, saturation}; + } + return value; + }, }, - }, + ], ], - ], + }, }, -}, ]; From f9102a0ec64d2470bf24fc02366ec9f12e761531 Mon Sep 17 00:00:00 2001 From: tigum27 Date: Sun, 12 Jul 2026 21:43:21 -0400 Subject: [PATCH 3/4] tuya.ts (floodlight ) --- src/devices/tuya.ts | 136 ++++++++++++++++++++++++++------------------ 1 file changed, 80 insertions(+), 56 deletions(-) diff --git a/src/devices/tuya.ts b/src/devices/tuya.ts index 58861b2df037f..067f2e451f23d 100644 --- a/src/devices/tuya.ts +++ b/src/devices/tuya.ts @@ -28615,63 +28615,87 @@ export const definitions: DefinitionWithExtend[] = [ }, }, { - fingerprint: tuya.fingerprint("TS0601", ["_TZE284_oa1odmga"]), - model: "TS0601_floodlight", - vendor: "Tuya", - description: "Smart RGBCW floodlight", - fromZigbee: [tuya.fz.datapoints], - toZigbee: [tuya.tz.datapoints], - configure: tuya.configureMagicPacket, - exposes: [e.light().withBrightness().withColorTemp([153, 500]).withColor(["hs"])], - meta: { - tuyaDatapoints: [ - [1, "state", tuya.valueConverter.onOff], - // Standard Tuya light DP layout puts a work_mode enum on DP2 - - // writing a brightness number there gets silently ignored. - [ - 2, - "work_mode", - tuya.valueConverterBasic.lookup({ - white: tuya.enum(0), - colour: tuya.enum(1), - scene: tuya.enum(2), - music: tuya.enum(3), - }), - ], - // Brightness is 0-1000 on the device but 0-254 in Z2M. - [3, "brightness", tuya.valueConverter.scale0_254to0_1000], - [4, "color_temp", tuya.valueConverter.raw], - [ - 5, - "color", - { - to: (value) => { - // The exposed 'hs' composite uses the property - // names 'hue' and 'saturation', not 'h' and 's'. - if (value.hue !== undefined && value.saturation !== undefined) { - const hue = Math.round(value.hue); - const sat = Math.round(value.saturation * 10); // 0-100 -> 0-1000 - // Keep current brightness (V) instead of forcing full, - // fall back to full brightness if we don't know it yet. - const val = value.brightness !== undefined ? Math.round(value.brightness * 10) : 1000; - const hHex = hue.toString(16).padStart(4, "0"); - const sHex = sat.toString(16).padStart(4, "0"); - const vHex = val.toString(16).padStart(4, "0"); - return hHex + sHex + vHex; - } - return value; - }, - from: (value) => { - if (typeof value === "string" && value.length >= 12) { - const hue = Number.parseInt(value.substring(0, 4), 16); - const saturation = Number.parseInt(value.substring(4, 8), 16) / 10; - return {hue, saturation}; - } - return value; - }, + fingerprint: tuya.fingerprint("TS0601", ["_TZE284_oa1odmga"]), + model: "TS0601_floodlight", + vendor: "Tuya", + description: "Smart RGBCW floodlight", + fromZigbee: [tuya.fz.datapoints], + toZigbee: [tuya.tz.datapoints], + configure: tuya.configureMagicPacket, + exposes: [e.light().withBrightness().withColorTemp([153, 500]).withColor(["hs"])], + meta: { + tuyaDatapoints: [ + [1, "state", tuya.valueConverter.onOff], + // Standard Tuya light DP layout puts a work_mode enum on DP2 - + // writing a brightness number there gets silently ignored. + [ + 2, + "work_mode", + tuya.valueConverterBasic.lookup({ + white: tuya.enum(0), + colour: tuya.enum(1), + scene: tuya.enum(2), + music: tuya.enum(3), + }), + ], + // Brightness is 0-1000 on the device but 0-254 in Z2M + [ + 3, + "brightness", + { + ...tuya.valueConverter.scale0_254to0_1000, + to: async (value, meta) => { + const val = Math.round((Number(value) / 254) * 1000); + if (meta.state.color_mode === "hs" && meta.state.color) { + const hue = Math.round(meta.state.color.hue ?? 0); + const sat = Math.round((meta.state.color.saturation ?? 100) * 10); + const hHex = hue.toString(16).padStart(4, "0"); + const sHex = sat.toString(16).padStart(4, "0"); + const vHex = val.toString(16).padStart(4, "0"); + const endpoint = meta.device.getEndpoint(1); + await tuya.sendDataPointStringBuffer(endpoint, 5, hHex + sHex + vHex); + return undefined; // conversion handled here, skip the default DP3 write + } + return val; // white/color_temp mode: normal DP3 write }, - ], + }, ], - }, + [4, "color_temp", tuya.valueConverter.raw], + [ + 5, + "color", + { + to: (value) => { + // Home Assistant sends {h, s} (short keys) for its + // hs_color field, while Z2M's own frontend sends + // {hue, saturation} (matching the exposed feature + // names). Accept both. + const hueRaw = value.hue !== undefined ? value.hue : value.h; + const satRaw = value.saturation !== undefined ? value.saturation : value.s; + if (hueRaw !== undefined && satRaw !== undefined) { + const hue = Math.round(hueRaw); + const sat = Math.round(satRaw * 10); // 0-100 -> 0-1000 + // Keep current brightness (V) instead of forcing full, + // fall back to full brightness if we don't know it yet. + const val = value.brightness !== undefined ? Math.round(value.brightness * 10) : 1000; + const hHex = hue.toString(16).padStart(4, "0"); + const sHex = sat.toString(16).padStart(4, "0"); + const vHex = val.toString(16).padStart(4, "0"); + return hHex + sHex + vHex; + } + return value; + }, + from: (value) => { + if (typeof value === "string" && value.length >= 12) { + const hue = Number.parseInt(value.substring(0, 4), 16); + const saturation = Number.parseInt(value.substring(4, 8), 16) / 10; + return {hue, saturation}; + } + return value; + }, + }, + ], + ], }, +}, ]; From 61db8af13627ee80f73df6bc60925c654e8d9047 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Mon, 13 Jul 2026 01:43:50 +0000 Subject: [PATCH 4/4] [autofix.ci] apply automated fixes --- src/devices/tuya.ts | 158 ++++++++++++++++++++++---------------------- 1 file changed, 79 insertions(+), 79 deletions(-) diff --git a/src/devices/tuya.ts b/src/devices/tuya.ts index 067f2e451f23d..3e34211d93f05 100644 --- a/src/devices/tuya.ts +++ b/src/devices/tuya.ts @@ -28615,87 +28615,87 @@ export const definitions: DefinitionWithExtend[] = [ }, }, { - fingerprint: tuya.fingerprint("TS0601", ["_TZE284_oa1odmga"]), - model: "TS0601_floodlight", - vendor: "Tuya", - description: "Smart RGBCW floodlight", - fromZigbee: [tuya.fz.datapoints], - toZigbee: [tuya.tz.datapoints], - configure: tuya.configureMagicPacket, - exposes: [e.light().withBrightness().withColorTemp([153, 500]).withColor(["hs"])], - meta: { - tuyaDatapoints: [ - [1, "state", tuya.valueConverter.onOff], - // Standard Tuya light DP layout puts a work_mode enum on DP2 - - // writing a brightness number there gets silently ignored. - [ - 2, - "work_mode", - tuya.valueConverterBasic.lookup({ - white: tuya.enum(0), - colour: tuya.enum(1), - scene: tuya.enum(2), - music: tuya.enum(3), - }), - ], - // Brightness is 0-1000 on the device but 0-254 in Z2M - [ - 3, - "brightness", - { - ...tuya.valueConverter.scale0_254to0_1000, - to: async (value, meta) => { - const val = Math.round((Number(value) / 254) * 1000); - if (meta.state.color_mode === "hs" && meta.state.color) { - const hue = Math.round(meta.state.color.hue ?? 0); - const sat = Math.round((meta.state.color.saturation ?? 100) * 10); - const hHex = hue.toString(16).padStart(4, "0"); - const sHex = sat.toString(16).padStart(4, "0"); - const vHex = val.toString(16).padStart(4, "0"); - const endpoint = meta.device.getEndpoint(1); - await tuya.sendDataPointStringBuffer(endpoint, 5, hHex + sHex + vHex); - return undefined; // conversion handled here, skip the default DP3 write - } - return val; // white/color_temp mode: normal DP3 write - }, - }, - ], - [4, "color_temp", tuya.valueConverter.raw], - [ - 5, - "color", - { - to: (value) => { - // Home Assistant sends {h, s} (short keys) for its - // hs_color field, while Z2M's own frontend sends - // {hue, saturation} (matching the exposed feature - // names). Accept both. - const hueRaw = value.hue !== undefined ? value.hue : value.h; - const satRaw = value.saturation !== undefined ? value.saturation : value.s; - if (hueRaw !== undefined && satRaw !== undefined) { - const hue = Math.round(hueRaw); - const sat = Math.round(satRaw * 10); // 0-100 -> 0-1000 - // Keep current brightness (V) instead of forcing full, - // fall back to full brightness if we don't know it yet. - const val = value.brightness !== undefined ? Math.round(value.brightness * 10) : 1000; - const hHex = hue.toString(16).padStart(4, "0"); - const sHex = sat.toString(16).padStart(4, "0"); - const vHex = val.toString(16).padStart(4, "0"); - return hHex + sHex + vHex; - } - return value; + fingerprint: tuya.fingerprint("TS0601", ["_TZE284_oa1odmga"]), + model: "TS0601_floodlight", + vendor: "Tuya", + description: "Smart RGBCW floodlight", + fromZigbee: [tuya.fz.datapoints], + toZigbee: [tuya.tz.datapoints], + configure: tuya.configureMagicPacket, + exposes: [e.light().withBrightness().withColorTemp([153, 500]).withColor(["hs"])], + meta: { + tuyaDatapoints: [ + [1, "state", tuya.valueConverter.onOff], + // Standard Tuya light DP layout puts a work_mode enum on DP2 - + // writing a brightness number there gets silently ignored. + [ + 2, + "work_mode", + tuya.valueConverterBasic.lookup({ + white: tuya.enum(0), + colour: tuya.enum(1), + scene: tuya.enum(2), + music: tuya.enum(3), + }), + ], + // Brightness is 0-1000 on the device but 0-254 in Z2M + [ + 3, + "brightness", + { + ...tuya.valueConverter.scale0_254to0_1000, + to: async (value, meta) => { + const val = Math.round((Number(value) / 254) * 1000); + if (meta.state.color_mode === "hs" && meta.state.color) { + const hue = Math.round(meta.state.color.hue ?? 0); + const sat = Math.round((meta.state.color.saturation ?? 100) * 10); + const hHex = hue.toString(16).padStart(4, "0"); + const sHex = sat.toString(16).padStart(4, "0"); + const vHex = val.toString(16).padStart(4, "0"); + const endpoint = meta.device.getEndpoint(1); + await tuya.sendDataPointStringBuffer(endpoint, 5, hHex + sHex + vHex); + return undefined; // conversion handled here, skip the default DP3 write + } + return val; // white/color_temp mode: normal DP3 write + }, }, - from: (value) => { - if (typeof value === "string" && value.length >= 12) { - const hue = Number.parseInt(value.substring(0, 4), 16); - const saturation = Number.parseInt(value.substring(4, 8), 16) / 10; - return {hue, saturation}; - } - return value; + ], + [4, "color_temp", tuya.valueConverter.raw], + [ + 5, + "color", + { + to: (value) => { + // Home Assistant sends {h, s} (short keys) for its + // hs_color field, while Z2M's own frontend sends + // {hue, saturation} (matching the exposed feature + // names). Accept both. + const hueRaw = value.hue !== undefined ? value.hue : value.h; + const satRaw = value.saturation !== undefined ? value.saturation : value.s; + if (hueRaw !== undefined && satRaw !== undefined) { + const hue = Math.round(hueRaw); + const sat = Math.round(satRaw * 10); // 0-100 -> 0-1000 + // Keep current brightness (V) instead of forcing full, + // fall back to full brightness if we don't know it yet. + const val = value.brightness !== undefined ? Math.round(value.brightness * 10) : 1000; + const hHex = hue.toString(16).padStart(4, "0"); + const sHex = sat.toString(16).padStart(4, "0"); + const vHex = val.toString(16).padStart(4, "0"); + return hHex + sHex + vHex; + } + return value; + }, + from: (value) => { + if (typeof value === "string" && value.length >= 12) { + const hue = Number.parseInt(value.substring(0, 4), 16); + const saturation = Number.parseInt(value.substring(4, 8), 16) / 10; + return {hue, saturation}; + } + return value; + }, }, - }, + ], ], - ], + }, }, -}, ];