Skip to content

Commit 1ad3dcf

Browse files
NerivecMstrodl
andauthored
feat: Add actions (#10800)
Co-authored-by: Mary Strodl <ipadlover8322@gmail.com>
1 parent 601d3cb commit 1ad3dcf

2 files changed

Lines changed: 135 additions & 2 deletions

File tree

src/converters/actions.ts

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import assert from "node:assert";
2+
import {type Controller, Zcl} from "zigbee-herdsman";
3+
import type {RawPayload} from "zigbee-herdsman/dist/controller/tstype";
4+
import type {CustomClusters} from "zigbee-herdsman/dist/zspec/zcl/definition/tstype";
5+
import {logger} from "../lib/logger";
6+
import * as utils from "../lib/utils";
7+
8+
const NS = "zhc:actions";
9+
10+
/** biome-ignore-start lint/style/useNamingConvention: MQTT convention */
11+
export interface MqttRawPayload {
12+
ieee_address?: string;
13+
network_address?: number;
14+
group_id?: number;
15+
dst_endpoint?: number;
16+
/** defaults to `ZSpec.HA_ENDPOINT` */
17+
src_endpoint?: number;
18+
/** defaults to false */
19+
interpan?: boolean;
20+
/** defaults to `ZSpec.HA_PROFILE_ID` */
21+
profile_id?: number;
22+
/** Expected as `number` for ZDO */
23+
cluster_key?: number | string;
24+
/** Only used for ZDO */
25+
zdo_params?: unknown[];
26+
/** Only used for ZCL */
27+
zcl?: {
28+
frame_type?: number;
29+
direction?: number;
30+
disable_default_response?: boolean;
31+
manufacturer_code?: number;
32+
tsn?: number;
33+
command_key: string;
34+
payload?: Record<string, unknown> | Record<string, unknown>[];
35+
};
36+
/** defaults to false */
37+
disable_response?: boolean;
38+
/** defaults to 10000 */
39+
timeout?: number;
40+
}
41+
/** biome-ignore-end lint/style/useNamingConvention: MQTT convention */
42+
43+
const ClusterHueTouchlink: CustomClusters = {
44+
manuSpecificPhilipsPairing: {
45+
ID: 0x1000,
46+
manufacturerCode: Zcl.ManufacturerCode.SIGNIFY_NETHERLANDS_B_V,
47+
attributes: {},
48+
commands: {
49+
hueResetRequest: {
50+
ID: 0,
51+
parameters: [
52+
{name: "extendedPanId", type: Zcl.DataType.IEEE_ADDR},
53+
{name: "serialCount", type: Zcl.DataType.UINT8},
54+
{name: "serialNumbers", type: Zcl.BuffaloZclDataType.LIST_UINT32},
55+
],
56+
},
57+
},
58+
commandsResponse: {},
59+
},
60+
};
61+
62+
/** biome-ignore-start lint/style/useNamingConvention: MQTT convention */
63+
export const ACTIONS: Record<string, (controller: Controller, args: Record<string, unknown>) => ReturnType<typeof controller.sendRaw>> = {
64+
raw: async (controller, args) => {
65+
const payload = args as MqttRawPayload;
66+
const rawPayload: RawPayload = {
67+
ieeeAddress: payload.ieee_address,
68+
networkAddress: payload.network_address,
69+
groupId: payload.group_id,
70+
dstEndpoint: payload.dst_endpoint,
71+
srcEndpoint: payload.src_endpoint,
72+
interPan: payload.interpan,
73+
profileId: payload.profile_id,
74+
clusterKey: payload.cluster_key,
75+
zdoParams: payload.zdo_params,
76+
disableResponse: payload.disable_response,
77+
timeout: payload.timeout,
78+
};
79+
80+
if (payload.zcl && typeof payload.zcl === "object") {
81+
rawPayload.zcl = {
82+
frameType: payload.zcl.frame_type,
83+
direction: payload.zcl.direction,
84+
disableDefaultResponse: payload.zcl.disable_default_response,
85+
manufacturerCode: payload.zcl.manufacturer_code,
86+
tsn: payload.zcl.tsn,
87+
commandKey: payload.zcl.command_key,
88+
payload: payload.zcl.payload,
89+
};
90+
}
91+
92+
return await controller.sendRaw(rawPayload);
93+
},
94+
hue_factory_reset: async (controller, args): Promise<undefined> => {
95+
assert(typeof args.extended_pan_id === "string" && /^0x[a-f0-9]{16}$/.test(args.extended_pan_id));
96+
assert(Array.isArray(args.serial_numbers) && args.serial_numbers.length > 0);
97+
98+
const rawPayload: RawPayload = {
99+
clusterKey: "manuSpecificPhilipsPairing",
100+
interPan: true,
101+
zcl: {
102+
frameType: Zcl.FrameType.SPECIFIC,
103+
direction: Zcl.Direction.CLIENT_TO_SERVER,
104+
disableDefaultResponse: true,
105+
manufacturerCode: Zcl.ManufacturerCode.SIGNIFY_NETHERLANDS_B_V,
106+
tsn: 0,
107+
commandKey: "hueResetRequest",
108+
payload: {extendedPanId: args.extended_pan_id, serialCount: args.serial_numbers.length, serialNumbers: args.serial_numbers},
109+
},
110+
disableResponse: true,
111+
};
112+
113+
controller.touchlink.lock(true);
114+
115+
try {
116+
for (const channel of [11, 15, 20, 25, 12, 13, 14, 16, 17, 18, 19, 21, 22, 23, 24, 26]) {
117+
await controller.touchlink.setChannelInterPAN(channel);
118+
119+
try {
120+
await controller.sendRaw(rawPayload, ClusterHueTouchlink);
121+
122+
// Try not to completely flood the airspace
123+
await utils.sleep(1000);
124+
} catch (error) {
125+
logger.warning(`Hue reset request failed to send: '${error}'`, NS);
126+
}
127+
}
128+
} finally {
129+
controller.touchlink.restoreChannelInterPAN();
130+
controller.touchlink.lock(false);
131+
}
132+
},
133+
};
134+
/** biome-ignore-end lint/style/useNamingConvention: MQTT convention */

src/index.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import assert from "node:assert";
2-
32
import {Zcl} from "zigbee-herdsman";
4-
53
import * as fromZigbee from "./converters/fromZigbee";
64
import * as toZigbee from "./converters/toZigbee";
75
import * as exposesLib from "./lib/exposes";
@@ -49,6 +47,7 @@ type ModelIndex = [module: string, index: number];
4947

5048
const MODELS_INDEX = modelsIndexJson as Record<string, ModelIndex[]>;
5149

50+
export {ACTIONS, MqttRawPayload} from "./converters/actions";
5251
export type {Ota} from "./lib/types";
5352
export {
5453
DefinitionWithExtend,

0 commit comments

Comments
 (0)