Skip to content

Commit 3cd11cc

Browse files
authored
fix: Tuya TYZGTH1CH-D1RF: auto settings (#9091)
1 parent b8cc0e5 commit 3cd11cc

File tree

4 files changed

+119
-99
lines changed

4 files changed

+119
-99
lines changed

src/devices/tuya.ts

+2-98
Original file line numberDiff line numberDiff line change
@@ -769,102 +769,6 @@ const fzLocal = {
769769
} satisfies Fz.Converter,
770770
};
771771

772-
const modernExtendLocal = {
773-
dpTHZBSettings(): ModernExtend {
774-
const exp = e
775-
.composite("auto_settings", "auto_settings", ea.STATE_SET)
776-
.withFeature(e.enum("enabled", ea.STATE_SET, ["on", "off", "none"]).withDescription("Enable auto settings"))
777-
.withFeature(e.enum("temp_greater_then", ea.STATE_SET, ["on", "off", "none"]).withDescription("Greater action"))
778-
.withFeature(
779-
e
780-
.numeric("temp_greater_value", ea.STATE_SET)
781-
.withValueMin(-20)
782-
.withValueMax(80)
783-
.withValueStep(0.1)
784-
.withUnit("*C")
785-
.withDescription("Temperature greater than value"),
786-
)
787-
.withFeature(e.enum("temp_lower_then", ea.STATE_SET, ["on", "off", "none"]).withDescription("Lower action"))
788-
.withFeature(
789-
e
790-
.numeric("temp_lower_value", ea.STATE_SET)
791-
.withValueMin(-20)
792-
.withValueMax(80)
793-
.withValueStep(0.1)
794-
.withUnit("*C")
795-
.withDescription("Temperature lower than value"),
796-
);
797-
798-
const handlers: [Fz.Converter[], Tz.Converter[]] = tuya.getHandlersForDP("auto_settings", 0x77, tuya.dataTypes.string, {
799-
from: (value: string) => {
800-
let result = {
801-
enabled: "none",
802-
temp_greater_then: "none",
803-
temp_greater_value: 0,
804-
temp_lower_then: "none",
805-
temp_lower_value: 0,
806-
};
807-
const buf = Buffer.from(value, "hex");
808-
if (buf.length > 0) {
809-
const enabled = buf[0];
810-
const gr = buf[1];
811-
const grValue = buf.readInt32LE(2) / 10;
812-
const grAction = buf[6];
813-
const lo = buf[7];
814-
const loValue = buf.readInt32LE(8) / 10;
815-
const loAction = buf[13];
816-
result = {
817-
enabled: {0: "on", 128: "off"}[enabled],
818-
temp_greater_then: gr !== 0xff ? {1: "on", 0: "off"}[grAction] : "none",
819-
temp_greater_value: grValue,
820-
temp_lower_then: lo !== 0xff ? {1: "on", 0: "off"}[loAction] : "none",
821-
temp_lower_value: loValue,
822-
};
823-
}
824-
return result;
825-
},
826-
to: (value: KeyValueAny) => {
827-
let result = "";
828-
if (value.enabled !== "none") {
829-
const enabled = utils.getFromLookup(value.enabled, {
830-
on: 0x00,
831-
off: 0x80,
832-
});
833-
const gr = value.temp_greater_then === "none" ? 0xff : 0x00;
834-
const grAction = utils.getFromLookup(value.temp_greater_then, {
835-
on: 0x01,
836-
off: 0x00,
837-
none: 0x00,
838-
});
839-
const lo = value.temp_lower_then === "none" ? 0xff : 0x00;
840-
const loAction = utils.getFromLookup(value.temp_lower_then, {
841-
on: 0x01,
842-
off: 0x00,
843-
none: 0x00,
844-
});
845-
const buf = Buffer.alloc(13);
846-
buf.writeUInt8(enabled, 0);
847-
buf.writeUInt8(gr, 1);
848-
buf.writeInt32LE(value.temp_greater_value * 10, 2);
849-
buf.writeUInt8(grAction, 6);
850-
buf.writeUInt8(lo, 7);
851-
buf.writeInt32LE(value.temp_lower_value * 10, 8);
852-
buf.writeUInt8(loAction, 12);
853-
result = buf.toString("hex");
854-
}
855-
return result;
856-
},
857-
});
858-
859-
return {
860-
exposes: [exp],
861-
fromZigbee: handlers[0],
862-
toZigbee: handlers[1],
863-
isModernExtend: true,
864-
};
865-
},
866-
};
867-
868772
export const definitions: DefinitionWithExtend[] = [
869773
{
870774
zigbeeModel: ["TS0204"],
@@ -14364,9 +14268,9 @@ export const definitions: DefinitionWithExtend[] = [
1436414268
type: tuya.dataTypes.enum,
1436514269
valueOn: ["ON", 1],
1436614270
valueOff: ["OFF", 0],
14367-
description: "Manual mode or automatic",
14271+
description: "Manual mode, ON = auto settings disabled, OFF = auto settings enabled",
1436814272
}),
14369-
modernExtendLocal.dpTHZBSettings(),
14273+
tuya.modernExtend.dpTHZBSettings(),
1437014274
],
1437114275
},
1437214276
{

src/lib/tuya.ts

+61-1
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ function dpValueFromEnum(dp: number, value: number) {
269269
return {dp, datatype: dataTypes.enum, data: [value]};
270270
}
271271

272-
function dpValueFromString(dp: number, string: string) {
272+
export function dpValueFromString(dp: number, string: string) {
273273
return {dp, datatype: dataTypes.string, data: convertStringToHexArray(string)};
274274
}
275275

@@ -2072,6 +2072,66 @@ export interface TuyaDPLightArgs {
20722072
}
20732073

20742074
const tuyaModernExtend = {
2075+
dpTHZBSettings(): ModernExtend {
2076+
const exp = e
2077+
.composite("auto_settings", "auto_settings", ea.STATE_SET)
2078+
.withDescription("Automatically switch ON/OFF, make sure manual mode is turned OFF otherwise auto settings are not applied.")
2079+
.withFeature(e.binary("enabled", ea.STATE_SET, true, false).withDescription("Enable auto settings"))
2080+
.withFeature(e.enum("temp_greater_then", ea.STATE_SET, ["ON", "OFF"]).withDescription("Greater action"))
2081+
.withFeature(
2082+
e
2083+
.numeric("temp_greater_value", ea.STATE_SET)
2084+
.withValueMin(-20)
2085+
.withValueMax(80)
2086+
.withValueStep(0.1)
2087+
.withUnit("°C")
2088+
.withDescription("Temperature greater than value"),
2089+
)
2090+
.withFeature(e.enum("temp_lower_then", ea.STATE_SET, ["ON", "OFF"]).withDescription("Lower action"))
2091+
.withFeature(
2092+
e
2093+
.numeric("temp_lower_value", ea.STATE_SET)
2094+
.withValueMin(-20)
2095+
.withValueMax(80)
2096+
.withValueStep(0.1)
2097+
.withUnit("°C")
2098+
.withDescription("Temperature lower than value"),
2099+
);
2100+
2101+
const handlers: [Fz.Converter[], Tz.Converter[]] = getHandlersForDP("auto_settings", 0x77, dataTypes.string, {
2102+
from: (value: string) => {
2103+
const buffer = Buffer.from(value, "hex");
2104+
if (buffer.length > 0) {
2105+
return {
2106+
enabled: buffer.readUint16LE(0) === 0x80,
2107+
temp_greater_value: buffer.readInt32LE(2) / 10,
2108+
temp_greater_then: buffer.readUint8(6) ? "ON" : "OFF",
2109+
temp_lower_value: buffer.readInt32LE(8) / 10,
2110+
temp_lower_then: buffer.readUint8(12) ? "ON" : "OFF",
2111+
};
2112+
}
2113+
},
2114+
to: async (value: KeyValueAny, meta) => {
2115+
const buffer = Buffer.alloc(13);
2116+
buffer.writeUint16LE(value.enabled ? 0x80 : 0x00, 0);
2117+
buffer.writeInt32LE(value.temp_greater_value * 10, 2);
2118+
buffer.writeUInt8(value.temp_greater_then === "ON" ? 1 : 0, 6);
2119+
buffer.writeUInt8(1, 7);
2120+
buffer.writeInt32LE(value.temp_lower_value * 10, 8);
2121+
buffer.writeUInt8(value.temp_lower_then === "ON" ? 1 : 0, 12);
2122+
// Disable manual mode, otherwise auto settings is not applied.
2123+
await sendDataPointEnum(meta.device.endpoints[0], 0x65, 0, "sendData", 1);
2124+
return buffer.toString("hex");
2125+
},
2126+
});
2127+
2128+
return {
2129+
exposes: [exp],
2130+
fromZigbee: handlers[0],
2131+
toZigbee: handlers[1],
2132+
isModernExtend: true,
2133+
};
2134+
},
20752135
tuyaBase(args?: {onEvent?: OnEventArgs; dp: true}): ModernExtend {
20762136
const result: ModernExtend = {
20772137
configure: [configureMagicPacket],

test/tuya.test.ts

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import type {Fz} from "src/lib/types";
2+
import {type Tz, findByDevice} from "../src/index";
3+
import * as tuya from "../src/lib/tuya";
4+
import {mockDevice} from "./utils";
5+
6+
describe("lib/tuya", () => {
7+
describe("dpTHZBSettings", async () => {
8+
const {toZigbee, fromZigbee} = tuya.modernExtend.dpTHZBSettings();
9+
const device = mockDevice({modelID: "TS000F", manufacturerName: "_TZ3218_7fiyo3kv", endpoints: [{}]});
10+
const definition = await findByDevice(device);
11+
12+
// 0000 disable writeInt32LE(temp_greater_value * 10) 01 on unknown writeInt32LE(temp_lower_value * 10) 01 on
13+
// 8000 enable 00 off 01 00 off
14+
// 2 bytes 4 bytes 1 byte 1 byte 4 byte 1 byte
15+
16+
const enable20OnMinus10Off = {
17+
to: tuya.dpValueFromString(119, "8000" + "c8000000" + "0101" + "9cffffff" + "00"),
18+
from: {auto_settings: {enabled: true, temp_greater_then: "ON", temp_greater_value: 20, temp_lower_value: -10, temp_lower_then: "OFF"}},
19+
};
20+
21+
const disable0Off0Dot2On = {
22+
to: tuya.dpValueFromString(119, "0000" + "00000000" + "0001" + "02000000" + "01"),
23+
from: {auto_settings: {enabled: false, temp_greater_then: "OFF", temp_greater_value: 0, temp_lower_value: 0.2, temp_lower_then: "ON"}},
24+
};
25+
26+
it.each([enable20OnMinus10Off, disable0Off0Dot2On])("toZigbee", async (data) => {
27+
const meta: Tz.Meta = {state: {}, device, message: null, mapped: definition, options: null, publish: null, endpoint_name: null};
28+
await toZigbee[0].convertSet(device.endpoints[0], "auto_settings", data.from.auto_settings, {
29+
...meta,
30+
message: data.from,
31+
});
32+
// Should disable manual mode
33+
expect(device.endpoints[0].command).toHaveBeenNthCalledWith(
34+
1,
35+
"manuSpecificTuya",
36+
"sendData",
37+
{seq: 1, dpValues: [{data: [0], datatype: 4, dp: 101}]},
38+
{disableDefaultResponse: true},
39+
);
40+
expect(device.endpoints[0].command).toHaveBeenNthCalledWith(
41+
2,
42+
"manuSpecificTuya",
43+
"sendData",
44+
{seq: 1, dpValues: [data.to]},
45+
{disableDefaultResponse: true},
46+
);
47+
});
48+
49+
it.each([enable20OnMinus10Off, disable0Off0Dot2On])("fromZigbee", async (data) => {
50+
const msg = {data: {dpValues: [data.to]}} as Fz.Message;
51+
const result = await fromZigbee[0].convert(definition, msg, null, null, null);
52+
expect(result).toStrictEqual(data.from);
53+
});
54+
});
55+
});

test/utils.ts

+1
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ function mockEndpoint(args: MockEndpointArgs, device: Zh.Device | undefined): Zh
7272
bind: vi.fn(),
7373
configureReporting: vi.fn(),
7474
read: vi.fn(),
75+
command: vi.fn(),
7576
getDevice: () => device,
7677
inputClusters,
7778
outputClusters,

0 commit comments

Comments
 (0)