@willieee802/zigbee-herdsman 0.49.4 → 0.50.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. package/.github/dependabot.yml +0 -3
  2. package/.github/workflows/ci.yml +1 -2
  3. package/.github/workflows/release-please.yml +1 -1
  4. package/.github/workflows/typedoc.yaml +3 -3
  5. package/.release-please-manifest.json +1 -1
  6. package/CHANGELOG.md +143 -0
  7. package/biome.json +1 -1
  8. package/dist/adapter/adapter.d.ts +14 -1
  9. package/dist/adapter/adapter.d.ts.map +1 -1
  10. package/dist/adapter/adapter.js +17 -0
  11. package/dist/adapter/adapter.js.map +1 -1
  12. package/dist/adapter/adapterDiscovery.d.ts.map +1 -1
  13. package/dist/adapter/adapterDiscovery.js.map +1 -1
  14. package/dist/adapter/deconz/adapter/deconzAdapter.d.ts +1 -3
  15. package/dist/adapter/deconz/adapter/deconzAdapter.d.ts.map +1 -1
  16. package/dist/adapter/deconz/adapter/deconzAdapter.js +14 -29
  17. package/dist/adapter/deconz/adapter/deconzAdapter.js.map +1 -1
  18. package/dist/adapter/deconz/driver/constants.d.ts +1 -1
  19. package/dist/adapter/deconz/driver/constants.d.ts.map +1 -1
  20. package/dist/adapter/ember/adapter/emberAdapter.d.ts +1 -1
  21. package/dist/adapter/ember/adapter/emberAdapter.d.ts.map +1 -1
  22. package/dist/adapter/ember/adapter/emberAdapter.js +19 -10
  23. package/dist/adapter/ember/adapter/emberAdapter.js.map +1 -1
  24. package/dist/adapter/ember/adapter/oneWaitress.d.ts +2 -0
  25. package/dist/adapter/ember/adapter/oneWaitress.d.ts.map +1 -1
  26. package/dist/adapter/ember/adapter/oneWaitress.js +13 -5
  27. package/dist/adapter/ember/adapter/oneWaitress.js.map +1 -1
  28. package/dist/adapter/ezsp/adapter/ezspAdapter.d.ts +1 -3
  29. package/dist/adapter/ezsp/adapter/ezspAdapter.d.ts.map +1 -1
  30. package/dist/adapter/ezsp/adapter/ezspAdapter.js +17 -30
  31. package/dist/adapter/ezsp/adapter/ezspAdapter.js.map +1 -1
  32. package/dist/adapter/ezsp/driver/index.d.ts +1 -1
  33. package/dist/adapter/ezsp/driver/index.d.ts.map +1 -1
  34. package/dist/adapter/ezsp/driver/index.js +1 -1
  35. package/dist/adapter/ezsp/driver/index.js.map +1 -1
  36. package/dist/adapter/ezsp/driver/types/index.d.ts +1 -1
  37. package/dist/adapter/ezsp/driver/types/index.d.ts.map +1 -1
  38. package/dist/adapter/ezsp/driver/types/index.js +3 -3
  39. package/dist/adapter/ezsp/driver/types/index.js.map +1 -1
  40. package/dist/adapter/serialPort.d.ts.map +1 -1
  41. package/dist/adapter/serialPort.js +7 -0
  42. package/dist/adapter/serialPort.js.map +1 -1
  43. package/dist/adapter/z-stack/adapter/adapter-backup.js +1 -1
  44. package/dist/adapter/z-stack/adapter/adapter-backup.js.map +1 -1
  45. package/dist/adapter/z-stack/adapter/adapter-nv-memory.js +1 -1
  46. package/dist/adapter/z-stack/adapter/adapter-nv-memory.js.map +1 -1
  47. package/dist/adapter/z-stack/adapter/manager.d.ts.map +1 -1
  48. package/dist/adapter/z-stack/adapter/manager.js +12 -2
  49. package/dist/adapter/z-stack/adapter/manager.js.map +1 -1
  50. package/dist/adapter/z-stack/adapter/tstype.d.ts.map +1 -1
  51. package/dist/adapter/z-stack/adapter/zStackAdapter.d.ts +1 -3
  52. package/dist/adapter/z-stack/adapter/zStackAdapter.d.ts.map +1 -1
  53. package/dist/adapter/z-stack/adapter/zStackAdapter.js +20 -34
  54. package/dist/adapter/z-stack/adapter/zStackAdapter.js.map +1 -1
  55. package/dist/adapter/z-stack/constants/index.d.ts +1 -1
  56. package/dist/adapter/z-stack/constants/index.d.ts.map +1 -1
  57. package/dist/adapter/z-stack/constants/index.js +1 -1
  58. package/dist/adapter/z-stack/constants/index.js.map +1 -1
  59. package/dist/adapter/z-stack/unpi/constants.d.ts +1 -1
  60. package/dist/adapter/z-stack/unpi/constants.d.ts.map +1 -1
  61. package/dist/adapter/z-stack/unpi/constants.js +1 -1
  62. package/dist/adapter/z-stack/unpi/constants.js.map +1 -1
  63. package/dist/adapter/zboss/adapter/zbossAdapter.d.ts +7 -8
  64. package/dist/adapter/zboss/adapter/zbossAdapter.d.ts.map +1 -1
  65. package/dist/adapter/zboss/adapter/zbossAdapter.js +12 -30
  66. package/dist/adapter/zboss/adapter/zbossAdapter.js.map +1 -1
  67. package/dist/adapter/zboss/driver.d.ts.map +1 -1
  68. package/dist/adapter/zboss/driver.js +8 -1
  69. package/dist/adapter/zboss/driver.js.map +1 -1
  70. package/dist/adapter/zboss/uart.d.ts.map +1 -1
  71. package/dist/adapter/zboss/uart.js +14 -2
  72. package/dist/adapter/zboss/uart.js.map +1 -1
  73. package/dist/adapter/zigate/adapter/zigateAdapter.d.ts +1 -3
  74. package/dist/adapter/zigate/adapter/zigateAdapter.d.ts.map +1 -1
  75. package/dist/adapter/zigate/adapter/zigateAdapter.js +8 -29
  76. package/dist/adapter/zigate/adapter/zigateAdapter.js.map +1 -1
  77. package/dist/adapter/zoh/adapter/zohAdapter.d.ts +1 -3
  78. package/dist/adapter/zoh/adapter/zohAdapter.d.ts.map +1 -1
  79. package/dist/adapter/zoh/adapter/zohAdapter.js +18 -33
  80. package/dist/adapter/zoh/adapter/zohAdapter.js.map +1 -1
  81. package/dist/controller/controller.d.ts.map +1 -1
  82. package/dist/controller/controller.js +10 -2
  83. package/dist/controller/controller.js.map +1 -1
  84. package/dist/controller/greenPower.d.ts.map +1 -1
  85. package/dist/controller/greenPower.js +15 -9
  86. package/dist/controller/greenPower.js.map +1 -1
  87. package/dist/controller/helpers/ota.d.ts +4 -4
  88. package/dist/controller/helpers/ota.d.ts.map +1 -1
  89. package/dist/controller/helpers/ota.js +28 -9
  90. package/dist/controller/helpers/ota.js.map +1 -1
  91. package/dist/controller/helpers/zclFrameConverter.d.ts.map +1 -1
  92. package/dist/controller/helpers/zclFrameConverter.js +17 -16
  93. package/dist/controller/helpers/zclFrameConverter.js.map +1 -1
  94. package/dist/controller/model/device.d.ts +14 -3
  95. package/dist/controller/model/device.d.ts.map +1 -1
  96. package/dist/controller/model/device.js +155 -68
  97. package/dist/controller/model/device.js.map +1 -1
  98. package/dist/controller/model/endpoint.d.ts +7 -3
  99. package/dist/controller/model/endpoint.d.ts.map +1 -1
  100. package/dist/controller/model/endpoint.js +34 -21
  101. package/dist/controller/model/endpoint.js.map +1 -1
  102. package/dist/controller/model/group.js +4 -4
  103. package/dist/controller/model/group.js.map +1 -1
  104. package/dist/controller/touchlink.js +3 -3
  105. package/dist/controller/touchlink.js.map +1 -1
  106. package/dist/utils/timeService.js +2 -2
  107. package/dist/utils/timeService.js.map +1 -1
  108. package/dist/zspec/zcl/buffaloZcl.d.ts +3 -3
  109. package/dist/zspec/zcl/buffaloZcl.d.ts.map +1 -1
  110. package/dist/zspec/zcl/buffaloZcl.js +198 -96
  111. package/dist/zspec/zcl/buffaloZcl.js.map +1 -1
  112. package/dist/zspec/zcl/definition/cluster.d.ts +2 -2
  113. package/dist/zspec/zcl/definition/cluster.d.ts.map +1 -1
  114. package/dist/zspec/zcl/definition/cluster.js +2699 -2808
  115. package/dist/zspec/zcl/definition/cluster.js.map +1 -1
  116. package/dist/zspec/zcl/definition/clusters-types.d.ts +63 -1109
  117. package/dist/zspec/zcl/definition/clusters-types.d.ts.map +1 -1
  118. package/dist/zspec/zcl/definition/enums.d.ts +0 -1
  119. package/dist/zspec/zcl/definition/enums.d.ts.map +1 -1
  120. package/dist/zspec/zcl/definition/enums.js +0 -1
  121. package/dist/zspec/zcl/definition/enums.js.map +1 -1
  122. package/dist/zspec/zcl/definition/foundation.d.ts +306 -7
  123. package/dist/zspec/zcl/definition/foundation.d.ts.map +1 -1
  124. package/dist/zspec/zcl/definition/foundation.js +552 -207
  125. package/dist/zspec/zcl/definition/foundation.js.map +1 -1
  126. package/dist/zspec/zcl/definition/status.d.ts +21 -10
  127. package/dist/zspec/zcl/definition/status.d.ts.map +1 -1
  128. package/dist/zspec/zcl/definition/status.js +11 -0
  129. package/dist/zspec/zcl/definition/status.js.map +1 -1
  130. package/dist/zspec/zcl/definition/tstype.d.ts +57 -48
  131. package/dist/zspec/zcl/definition/tstype.d.ts.map +1 -1
  132. package/dist/zspec/zcl/utils.d.ts +7 -4
  133. package/dist/zspec/zcl/utils.d.ts.map +1 -1
  134. package/dist/zspec/zcl/utils.js +133 -240
  135. package/dist/zspec/zcl/utils.js.map +1 -1
  136. package/dist/zspec/zcl/zclFrame.d.ts +4 -4
  137. package/dist/zspec/zcl/zclFrame.d.ts.map +1 -1
  138. package/dist/zspec/zcl/zclFrame.js +19 -103
  139. package/dist/zspec/zcl/zclFrame.js.map +1 -1
  140. package/dist/zspec/zcl/zclStatusError.d.ts +1 -1
  141. package/dist/zspec/zcl/zclStatusError.d.ts.map +1 -1
  142. package/dist/zspec/zcl/zclStatusError.js +2 -2
  143. package/dist/zspec/zcl/zclStatusError.js.map +1 -1
  144. package/package.json +1 -1
  145. package/scripts/clusters-typegen.ts +44 -139
  146. package/src/adapter/adapter.ts +38 -3
  147. package/src/adapter/adapterDiscovery.ts +2 -1
  148. package/src/adapter/deconz/adapter/deconzAdapter.ts +24 -51
  149. package/src/adapter/deconz/driver/constants.ts +1 -1
  150. package/src/adapter/ember/adapter/emberAdapter.ts +23 -10
  151. package/src/adapter/ember/adapter/oneWaitress.ts +16 -6
  152. package/src/adapter/ezsp/adapter/ezspAdapter.ts +27 -48
  153. package/src/adapter/ezsp/driver/index.ts +1 -1
  154. package/src/adapter/ezsp/driver/types/index.ts +99 -99
  155. package/src/adapter/serialPort.ts +9 -0
  156. package/src/adapter/z-stack/adapter/adapter-backup.ts +1 -1
  157. package/src/adapter/z-stack/adapter/adapter-nv-memory.ts +1 -1
  158. package/src/adapter/z-stack/adapter/manager.ts +16 -2
  159. package/src/adapter/z-stack/adapter/tstype.ts +1 -0
  160. package/src/adapter/z-stack/adapter/zStackAdapter.ts +34 -81
  161. package/src/adapter/z-stack/constants/index.ts +1 -1
  162. package/src/adapter/z-stack/unpi/constants.ts +1 -1
  163. package/src/adapter/zboss/adapter/zbossAdapter.ts +23 -54
  164. package/src/adapter/zboss/driver.ts +8 -1
  165. package/src/adapter/zboss/uart.ts +14 -1
  166. package/src/adapter/zigate/adapter/zigateAdapter.ts +17 -48
  167. package/src/adapter/zoh/adapter/zohAdapter.ts +27 -50
  168. package/src/controller/controller.ts +12 -2
  169. package/src/controller/greenPower.ts +16 -9
  170. package/src/controller/helpers/ota.ts +37 -11
  171. package/src/controller/helpers/zclFrameConverter.ts +20 -17
  172. package/src/controller/model/device.ts +192 -79
  173. package/src/controller/model/endpoint.ts +36 -24
  174. package/src/controller/model/group.ts +4 -4
  175. package/src/controller/touchlink.ts +3 -3
  176. package/src/utils/timeService.ts +2 -2
  177. package/src/zspec/zcl/buffaloZcl.ts +226 -100
  178. package/src/zspec/zcl/definition/cluster.ts +2713 -2822
  179. package/src/zspec/zcl/definition/clusters-types.ts +80 -1135
  180. package/src/zspec/zcl/definition/enums.ts +0 -1
  181. package/src/zspec/zcl/definition/foundation.ts +703 -216
  182. package/src/zspec/zcl/definition/status.ts +22 -11
  183. package/src/zspec/zcl/definition/tstype.ts +59 -58
  184. package/src/zspec/zcl/utils.ts +137 -264
  185. package/src/zspec/zcl/zclFrame.ts +25 -130
  186. package/src/zspec/zcl/zclStatusError.ts +2 -2
  187. package/test/adapter/ember/emberAdapter.test.ts +191 -4
  188. package/test/adapter/ezsp/uart.test.ts +10 -10
  189. package/test/adapter/z-stack/adapter.test.ts +88 -32
  190. package/test/adapter/zoh/zohAdapter.test.ts +4 -4
  191. package/test/controller.test.ts +822 -248
  192. package/test/device-ota.test.ts +141 -16
  193. package/test/device.test.ts +731 -0
  194. package/test/requests.bench.ts +2 -0
  195. package/test/zcl.test.ts +70 -95
  196. package/test/zspec/zcl/buffalo.test.ts +251 -11
  197. package/test/zspec/zcl/foundation.test.ts +990 -0
  198. package/test/zspec/zcl/frame.test.ts +84 -69
  199. package/test/zspec/zcl/utils.test.ts +105 -81
  200. package/tsconfig.json +0 -1
  201. package/scripts/check-clusters-changes.ts +0 -328
  202. package/scripts/clusters-changes.log +0 -584
  203. package/scripts/utils.ts +0 -88
  204. package/scripts/zap-update-clusters-report.json +0 -303
  205. package/scripts/zap-update-clusters.ts +0 -1520
  206. package/scripts/zap-update-types.ts +0 -707
  207. package/scripts/zap-xml-clusters-overrides-data.ts +0 -52
  208. package/scripts/zap-xml-clusters-overrides.ts +0 -400
  209. package/scripts/zap-xml-types.ts +0 -146
@@ -0,0 +1,731 @@
1
+ import {beforeEach, describe, expect, it, type MockInstance, vi} from "vitest";
2
+ import type {ZclPayload} from "../src/adapter/events";
3
+ import Database from "../src/controller/database";
4
+ import {type Endpoint, Entity} from "../src/controller/model";
5
+ import {Device, InterviewState} from "../src/controller/model/device";
6
+ import {Clusters, DataType, Direction, FrameType, Status} from "../src/zspec/zcl";
7
+ import {ZclFrame} from "../src/zspec/zcl/zclFrame";
8
+
9
+ const database = Database.open("dummy");
10
+ // no-op
11
+ vi.spyOn(database, "write").mockImplementation(vi.fn());
12
+
13
+ Entity.injectDatabase(database);
14
+
15
+ describe("Device", () => {
16
+ let device: Device;
17
+ let endpoint: Endpoint;
18
+ let readResponseSpy: MockInstance;
19
+ let commandSpy: MockInstance;
20
+ let readSpy: MockInstance;
21
+ let defaultResponseSpy: MockInstance;
22
+
23
+ beforeEach(() => {
24
+ database.clear();
25
+ Device.resetCache();
26
+
27
+ device = Device.create(
28
+ "Router",
29
+ "0xfe34ac2385ff8311",
30
+ 0x0001,
31
+ 0x0102,
32
+ "Herdsman",
33
+ "Mains (single phase)",
34
+ "Herd-01",
35
+ InterviewState.Successful,
36
+ undefined,
37
+ );
38
+ endpoint = device.createEndpoint(1);
39
+ readResponseSpy = vi.spyOn(endpoint, "readResponse").mockImplementation(vi.fn());
40
+ commandSpy = vi.spyOn(endpoint, "command").mockImplementation(vi.fn());
41
+ readSpy = vi.spyOn(endpoint, "read").mockImplementation(vi.fn());
42
+ defaultResponseSpy = vi.spyOn(endpoint, "defaultResponse").mockImplementation(vi.fn());
43
+ });
44
+
45
+ it("replies to basic read", async () => {
46
+ const frame = ZclFrame.create(
47
+ FrameType.GLOBAL,
48
+ Direction.CLIENT_TO_SERVER,
49
+ false,
50
+ undefined,
51
+ 1,
52
+ "read",
53
+ "genBasic",
54
+ [{attrId: 0x0000}, {attrId: 0xfffa}],
55
+ {},
56
+ );
57
+ const dataPayload: ZclPayload = {
58
+ clusterID: frame.cluster.ID,
59
+ address: 0x1234,
60
+ header: frame.header,
61
+ data: frame.toBuffer(),
62
+ endpoint: 1,
63
+ linkquality: 150,
64
+ groupID: 0,
65
+ wasBroadcast: false,
66
+ destinationEndpoint: 1,
67
+ };
68
+
69
+ await device.onZclData(dataPayload, frame, endpoint, undefined);
70
+
71
+ expect(readResponseSpy).toHaveBeenCalledTimes(1);
72
+ expect(commandSpy).toHaveBeenCalledTimes(0);
73
+ expect(readSpy).toHaveBeenCalledTimes(0);
74
+ expect(defaultResponseSpy).toHaveBeenCalledTimes(0);
75
+ expect(readResponseSpy).toHaveBeenCalledWith(
76
+ Clusters.genBasic.ID,
77
+ frame.header.transactionSequenceNumber,
78
+ {
79
+ zclVersion: Clusters.genBasic.attributes.zclVersion.default,
80
+ 65530: {value: undefined, type: DataType.NO_DATA},
81
+ },
82
+ {srcEndpoint: 1},
83
+ );
84
+ });
85
+
86
+ it("replies to time read", async () => {
87
+ // time is auto-generated, no saved attrs required
88
+ const frame = ZclFrame.create(
89
+ FrameType.GLOBAL,
90
+ Direction.CLIENT_TO_SERVER,
91
+ false,
92
+ undefined,
93
+ 1,
94
+ "read",
95
+ "genTime",
96
+ [{attrId: 0x0000}, {attrId: 0xfffa}],
97
+ {},
98
+ );
99
+ const dataPayload: ZclPayload = {
100
+ clusterID: frame.cluster.ID,
101
+ address: 0x1234,
102
+ header: frame.header,
103
+ data: frame.toBuffer(),
104
+ endpoint: 1,
105
+ linkquality: 150,
106
+ groupID: 0,
107
+ wasBroadcast: false,
108
+ destinationEndpoint: 1,
109
+ };
110
+
111
+ await device.onZclData(dataPayload, frame, endpoint, undefined);
112
+
113
+ expect(readResponseSpy).toHaveBeenCalledTimes(1);
114
+ expect(commandSpy).toHaveBeenCalledTimes(0);
115
+ expect(readSpy).toHaveBeenCalledTimes(0);
116
+ expect(defaultResponseSpy).toHaveBeenCalledTimes(0);
117
+ expect(readResponseSpy).toHaveBeenCalledWith(
118
+ Clusters.genTime.ID,
119
+ frame.header.transactionSequenceNumber,
120
+ {
121
+ time: expect.any(Number),
122
+ 65530: {value: undefined, type: DataType.NO_DATA},
123
+ },
124
+ {srcEndpoint: 1},
125
+ );
126
+ });
127
+
128
+ it("replies to unsupported read", async () => {
129
+ const frame = ZclFrame.create(FrameType.GLOBAL, Direction.CLIENT_TO_SERVER, false, undefined, 1, "read", "genGroups", [{attrId: 0x0000}], {});
130
+ const dataPayload: ZclPayload = {
131
+ clusterID: frame.cluster.ID,
132
+ address: 0x1234,
133
+ header: frame.header,
134
+ data: frame.toBuffer(),
135
+ endpoint: 1,
136
+ linkquality: 150,
137
+ groupID: 0,
138
+ wasBroadcast: false,
139
+ destinationEndpoint: 1,
140
+ };
141
+
142
+ await device.onZclData(dataPayload, frame, endpoint, undefined);
143
+
144
+ expect(readResponseSpy).toHaveBeenCalledTimes(1);
145
+ expect(commandSpy).toHaveBeenCalledTimes(0);
146
+ expect(readSpy).toHaveBeenCalledTimes(0);
147
+ expect(defaultResponseSpy).toHaveBeenCalledTimes(0);
148
+ expect(readResponseSpy).toHaveBeenCalledWith(
149
+ Clusters.genGroups.ID,
150
+ frame.header.transactionSequenceNumber,
151
+ {
152
+ 0: {value: undefined, type: DataType.NO_DATA},
153
+ },
154
+ {srcEndpoint: 1},
155
+ );
156
+ });
157
+
158
+ it("replies to read with custom response", async () => {
159
+ device.customReadResponse = (_frame, _endpoint) => false;
160
+ const frame = ZclFrame.create(FrameType.GLOBAL, Direction.CLIENT_TO_SERVER, false, undefined, 1, "read", "genBasic", [{attrId: 0x0000}], {});
161
+ const dataPayload: ZclPayload = {
162
+ clusterID: frame.cluster.ID,
163
+ address: 0x1234,
164
+ header: frame.header,
165
+ data: frame.toBuffer(),
166
+ endpoint: 1,
167
+ linkquality: 150,
168
+ groupID: 0,
169
+ wasBroadcast: false,
170
+ destinationEndpoint: 1,
171
+ };
172
+
173
+ await device.onZclData(dataPayload, frame, endpoint, undefined);
174
+
175
+ expect(readResponseSpy).toHaveBeenCalledTimes(1);
176
+ expect(commandSpy).toHaveBeenCalledTimes(0);
177
+ expect(readSpy).toHaveBeenCalledTimes(0);
178
+ expect(defaultResponseSpy).toHaveBeenCalledTimes(0);
179
+ });
180
+
181
+ it("replies to read with override custom response", async () => {
182
+ device.customReadResponse = (_frame, _endpoint) => true;
183
+ const frame = ZclFrame.create(
184
+ FrameType.GLOBAL,
185
+ Direction.CLIENT_TO_SERVER,
186
+ false,
187
+ undefined,
188
+ 1,
189
+ "read",
190
+ "genBasic",
191
+ [{attrId: 0x0000, type: DataType.UINT8}],
192
+ {},
193
+ );
194
+ const dataPayload: ZclPayload = {
195
+ clusterID: frame.cluster.ID,
196
+ address: 0x1234,
197
+ header: frame.header,
198
+ data: frame.toBuffer(),
199
+ endpoint: 1,
200
+ linkquality: 150,
201
+ groupID: 0,
202
+ wasBroadcast: false,
203
+ destinationEndpoint: 1,
204
+ };
205
+
206
+ await device.onZclData(dataPayload, frame, endpoint, undefined);
207
+
208
+ expect(readResponseSpy).toHaveBeenCalledTimes(0);
209
+ expect(commandSpy).toHaveBeenCalledTimes(0);
210
+ expect(readSpy).toHaveBeenCalledTimes(0);
211
+ expect(defaultResponseSpy).toHaveBeenCalledTimes(0);
212
+ });
213
+
214
+ it("does not send default response for default response", async () => {
215
+ const frame = ZclFrame.create(
216
+ FrameType.GLOBAL,
217
+ Direction.CLIENT_TO_SERVER,
218
+ false,
219
+ undefined,
220
+ 1,
221
+ "defaultRsp",
222
+ "genBasic",
223
+ {cmdId: 0x00, statusCode: Status.SUCCESS},
224
+ {},
225
+ );
226
+ const dataPayload: ZclPayload = {
227
+ clusterID: frame.cluster.ID,
228
+ address: 0x1234,
229
+ header: frame.header,
230
+ data: frame.toBuffer(),
231
+ endpoint: 1,
232
+ linkquality: 150,
233
+ groupID: 0,
234
+ wasBroadcast: false,
235
+ destinationEndpoint: 1,
236
+ };
237
+
238
+ await device.onZclData(dataPayload, frame, endpoint, undefined);
239
+
240
+ expect(readResponseSpy).toHaveBeenCalledTimes(0);
241
+ expect(commandSpy).toHaveBeenCalledTimes(0);
242
+ expect(readSpy).toHaveBeenCalledTimes(0);
243
+ expect(defaultResponseSpy).toHaveBeenCalledTimes(0);
244
+ });
245
+
246
+ it("replies to IAS Zone Enroll request", async () => {
247
+ const frame = ZclFrame.create(
248
+ FrameType.SPECIFIC,
249
+ Direction.SERVER_TO_CLIENT,
250
+ false,
251
+ undefined,
252
+ 1,
253
+ "enrollReq",
254
+ "ssIasZone",
255
+ {zonetype: 0x4567, manucode: 0x1234},
256
+ {},
257
+ );
258
+ const dataPayload: ZclPayload = {
259
+ clusterID: frame.cluster.ID,
260
+ address: 0x1234,
261
+ header: frame.header,
262
+ data: frame.toBuffer(),
263
+ endpoint: 1,
264
+ linkquality: 150,
265
+ groupID: 0,
266
+ wasBroadcast: false,
267
+ destinationEndpoint: 1,
268
+ };
269
+
270
+ await device.onZclData(dataPayload, frame, endpoint, undefined);
271
+
272
+ expect(readResponseSpy).toHaveBeenCalledTimes(0);
273
+ expect(commandSpy).toHaveBeenCalledTimes(1);
274
+ expect(commandSpy).toHaveBeenCalledWith(
275
+ "ssIasZone",
276
+ "enrollRsp",
277
+ {enrollrspcode: 0, zoneid: 23},
278
+ {transactionSequenceNumber: 1, disableDefaultResponse: true},
279
+ );
280
+ expect(readSpy).toHaveBeenCalledTimes(0);
281
+ expect(defaultResponseSpy).toHaveBeenCalledTimes(0);
282
+ });
283
+
284
+ it("fails to reply to IAS Zone Enroll request", async () => {
285
+ const frame = ZclFrame.create(
286
+ FrameType.SPECIFIC,
287
+ Direction.SERVER_TO_CLIENT,
288
+ false,
289
+ undefined,
290
+ 1,
291
+ "enrollReq",
292
+ "ssIasZone",
293
+ {zonetype: 0x4567, manucode: 0x1234},
294
+ {},
295
+ );
296
+ const dataPayload: ZclPayload = {
297
+ clusterID: frame.cluster.ID,
298
+ address: 0x1234,
299
+ header: frame.header,
300
+ data: frame.toBuffer(),
301
+ endpoint: 1,
302
+ linkquality: 150,
303
+ groupID: 0,
304
+ wasBroadcast: false,
305
+ destinationEndpoint: 1,
306
+ };
307
+
308
+ commandSpy.mockRejectedValueOnce(new Error("failure"));
309
+ await device.onZclData(dataPayload, frame, endpoint, undefined);
310
+
311
+ expect(readResponseSpy).toHaveBeenCalledTimes(0);
312
+ expect(commandSpy).toHaveBeenCalledTimes(1);
313
+ expect(commandSpy).toHaveBeenCalledWith(
314
+ "ssIasZone",
315
+ "enrollRsp",
316
+ {enrollrspcode: 0, zoneid: 23},
317
+ {transactionSequenceNumber: 1, disableDefaultResponse: true},
318
+ );
319
+ expect(readSpy).toHaveBeenCalledTimes(0);
320
+ expect(defaultResponseSpy).toHaveBeenCalledTimes(1);
321
+ expect(defaultResponseSpy).toHaveBeenCalledWith(frame.command.ID, Status.FAILURE, frame.cluster.ID, 1, {
322
+ direction: Direction.CLIENT_TO_SERVER,
323
+ });
324
+ });
325
+
326
+ it("replies to Poll Control Check-in", async () => {
327
+ device.checkinInterval = undefined;
328
+ const frame = ZclFrame.create(FrameType.SPECIFIC, Direction.SERVER_TO_CLIENT, false, undefined, 1, "checkin", "genPollCtrl", {}, {});
329
+ const dataPayload: ZclPayload = {
330
+ clusterID: frame.cluster.ID,
331
+ address: 0x1234,
332
+ header: frame.header,
333
+ data: frame.toBuffer(),
334
+ endpoint: 1,
335
+ linkquality: 150,
336
+ groupID: 0,
337
+ wasBroadcast: false,
338
+ destinationEndpoint: 1,
339
+ };
340
+
341
+ readSpy.mockResolvedValueOnce({checkinInterval: 100});
342
+ await device.onZclData(dataPayload, frame, endpoint, undefined);
343
+
344
+ expect(readResponseSpy).toHaveBeenCalledTimes(0);
345
+ expect(commandSpy).toHaveBeenCalledTimes(2);
346
+ expect(commandSpy).toHaveBeenNthCalledWith(
347
+ 1,
348
+ "genPollCtrl",
349
+ "checkinRsp",
350
+ {startFastPolling: 1, fastPollTimeout: 0},
351
+ {transactionSequenceNumber: 1, disableDefaultResponse: true, sendPolicy: "immediate"},
352
+ );
353
+ expect(commandSpy).toHaveBeenNthCalledWith(2, "genPollCtrl", "fastPollStop", {}, {sendPolicy: "immediate"});
354
+ expect(readSpy).toHaveBeenCalledTimes(1);
355
+ expect(readSpy).toHaveBeenCalledWith("genPollCtrl", ["checkinInterval"], {sendPolicy: "immediate"});
356
+ expect(defaultResponseSpy).toHaveBeenCalledTimes(0);
357
+
358
+ readResponseSpy.mockClear();
359
+ commandSpy.mockClear();
360
+ commandSpy.mockClear();
361
+ readSpy.mockClear();
362
+ defaultResponseSpy.mockClear();
363
+ await device.onZclData(dataPayload, frame, endpoint, undefined);
364
+
365
+ expect(readResponseSpy).toHaveBeenCalledTimes(0);
366
+ expect(commandSpy).toHaveBeenCalledTimes(1);
367
+ expect(commandSpy).toHaveBeenCalledWith(
368
+ "genPollCtrl",
369
+ "checkinRsp",
370
+ {startFastPolling: 0, fastPollTimeout: 0},
371
+ {transactionSequenceNumber: 1, disableDefaultResponse: true, sendPolicy: "immediate"},
372
+ );
373
+ expect(readSpy).toHaveBeenCalledTimes(0);
374
+ expect(defaultResponseSpy).toHaveBeenCalledTimes(0);
375
+ });
376
+
377
+ it("fails to reply to Poll Control Check-in", async () => {
378
+ device.checkinInterval = undefined;
379
+ const frame = ZclFrame.create(FrameType.SPECIFIC, Direction.SERVER_TO_CLIENT, false, undefined, 1, "checkin", "genPollCtrl", {}, {});
380
+ const dataPayload: ZclPayload = {
381
+ clusterID: frame.cluster.ID,
382
+ address: 0x1234,
383
+ header: frame.header,
384
+ data: frame.toBuffer(),
385
+ endpoint: 1,
386
+ linkquality: 150,
387
+ groupID: 0,
388
+ wasBroadcast: false,
389
+ destinationEndpoint: 1,
390
+ };
391
+
392
+ commandSpy.mockRejectedValueOnce(new Error("failure"));
393
+ await device.onZclData(dataPayload, frame, endpoint, undefined);
394
+
395
+ expect(readResponseSpy).toHaveBeenCalledTimes(0);
396
+ expect(commandSpy).toHaveBeenCalledTimes(1);
397
+ expect(commandSpy).toHaveBeenCalledWith(
398
+ "genPollCtrl",
399
+ "checkinRsp",
400
+ {startFastPolling: 1, fastPollTimeout: 0},
401
+ {transactionSequenceNumber: 1, disableDefaultResponse: true, sendPolicy: "immediate"},
402
+ );
403
+ expect(readSpy).toHaveBeenCalledTimes(0);
404
+ expect(defaultResponseSpy).toHaveBeenCalledTimes(1);
405
+ expect(defaultResponseSpy).toHaveBeenCalledWith(frame.command.ID, Status.FAILURE, frame.cluster.ID, 1, {
406
+ direction: Direction.CLIENT_TO_SERVER,
407
+ });
408
+ });
409
+
410
+ it("fails to stop fast poll", async () => {
411
+ device.checkinInterval = undefined;
412
+ const frame = ZclFrame.create(FrameType.SPECIFIC, Direction.SERVER_TO_CLIENT, false, undefined, 1, "checkin", "genPollCtrl", {}, {});
413
+ const dataPayload: ZclPayload = {
414
+ clusterID: frame.cluster.ID,
415
+ address: 0x1234,
416
+ header: frame.header,
417
+ data: frame.toBuffer(),
418
+ endpoint: 1,
419
+ linkquality: 150,
420
+ groupID: 0,
421
+ wasBroadcast: false,
422
+ destinationEndpoint: 1,
423
+ };
424
+
425
+ readSpy.mockResolvedValueOnce({checkinInterval: 100});
426
+ commandSpy.mockImplementationOnce(() => {}).mockRejectedValueOnce(new Error("failure"));
427
+ await device.onZclData(dataPayload, frame, endpoint, undefined);
428
+
429
+ expect(readResponseSpy).toHaveBeenCalledTimes(0);
430
+ expect(commandSpy).toHaveBeenCalledTimes(2);
431
+ expect(commandSpy).toHaveBeenCalledWith(
432
+ "genPollCtrl",
433
+ "checkinRsp",
434
+ {startFastPolling: 1, fastPollTimeout: 0},
435
+ {transactionSequenceNumber: 1, disableDefaultResponse: true, sendPolicy: "immediate"},
436
+ );
437
+ expect(commandSpy).toHaveBeenNthCalledWith(2, "genPollCtrl", "fastPollStop", {}, {sendPolicy: "immediate"});
438
+ expect(readSpy).toHaveBeenCalledTimes(1);
439
+ expect(readSpy).toHaveBeenCalledWith("genPollCtrl", ["checkinInterval"], {sendPolicy: "immediate"});
440
+ expect(defaultResponseSpy).toHaveBeenCalledTimes(0);
441
+ });
442
+
443
+ it("replies to GLOBAL with SUCCESS default response by default", async () => {
444
+ const frame = ZclFrame.create(
445
+ FrameType.GLOBAL,
446
+ Direction.SERVER_TO_CLIENT,
447
+ false,
448
+ undefined,
449
+ 1,
450
+ "readRsp",
451
+ "genOnOff",
452
+ [{attrId: 0x0000, status: Status.SUCCESS, dataType: DataType.BOOLEAN, attrData: 1}],
453
+ {},
454
+ );
455
+ const dataPayload: ZclPayload = {
456
+ clusterID: frame.cluster.ID,
457
+ address: 0x1234,
458
+ header: frame.header,
459
+ data: frame.toBuffer(),
460
+ endpoint: 1,
461
+ linkquality: 150,
462
+ groupID: 0,
463
+ wasBroadcast: false,
464
+ destinationEndpoint: 1,
465
+ };
466
+
467
+ await device.onZclData(dataPayload, frame, endpoint, undefined);
468
+
469
+ expect(readResponseSpy).toHaveBeenCalledTimes(0);
470
+ expect(commandSpy).toHaveBeenCalledTimes(0);
471
+ expect(readSpy).toHaveBeenCalledTimes(0);
472
+ expect(defaultResponseSpy).toHaveBeenCalledTimes(1);
473
+ expect(defaultResponseSpy).toHaveBeenCalledWith(frame.command.ID, Status.SUCCESS, frame.cluster.ID, 1, {
474
+ direction: Direction.CLIENT_TO_SERVER,
475
+ });
476
+ });
477
+
478
+ it("replies to SPECIFIC from SERVER with SUCCESS default response by default", async () => {
479
+ const frame = ZclFrame.create(
480
+ FrameType.SPECIFIC,
481
+ Direction.SERVER_TO_CLIENT,
482
+ false,
483
+ undefined,
484
+ 1,
485
+ "removeRsp",
486
+ "genScenes",
487
+ {status: 0, groupid: 1, sceneid: 2},
488
+ {},
489
+ );
490
+ const dataPayload: ZclPayload = {
491
+ clusterID: frame.cluster.ID,
492
+ address: 0x1234,
493
+ header: frame.header,
494
+ data: frame.toBuffer(),
495
+ endpoint: 1,
496
+ linkquality: 150,
497
+ groupID: 0,
498
+ wasBroadcast: false,
499
+ destinationEndpoint: 1,
500
+ };
501
+
502
+ await device.onZclData(dataPayload, frame, endpoint, undefined);
503
+
504
+ expect(readResponseSpy).toHaveBeenCalledTimes(0);
505
+ expect(commandSpy).toHaveBeenCalledTimes(0);
506
+ expect(readSpy).toHaveBeenCalledTimes(0);
507
+ expect(defaultResponseSpy).toHaveBeenCalledTimes(1);
508
+ expect(defaultResponseSpy).toHaveBeenCalledWith(frame.command.ID, Status.SUCCESS, frame.cluster.ID, 1, {
509
+ direction: Direction.CLIENT_TO_SERVER,
510
+ });
511
+ });
512
+
513
+ it("replies to SPECIFIC from CLIENT with SUCCESS default response by default", async () => {
514
+ const frame = ZclFrame.create(
515
+ FrameType.SPECIFIC,
516
+ Direction.CLIENT_TO_SERVER,
517
+ false,
518
+ undefined,
519
+ 1,
520
+ "identify",
521
+ "genIdentify",
522
+ {identifytime: 1},
523
+ {},
524
+ );
525
+ const dataPayload: ZclPayload = {
526
+ clusterID: frame.cluster.ID,
527
+ address: 0x1234,
528
+ header: frame.header,
529
+ data: frame.toBuffer(),
530
+ endpoint: 1,
531
+ linkquality: 150,
532
+ groupID: 0,
533
+ wasBroadcast: false,
534
+ destinationEndpoint: 1,
535
+ };
536
+
537
+ await device.onZclData(dataPayload, frame, endpoint, undefined);
538
+
539
+ expect(readResponseSpy).toHaveBeenCalledTimes(0);
540
+ expect(commandSpy).toHaveBeenCalledTimes(0);
541
+ expect(readSpy).toHaveBeenCalledTimes(0);
542
+ expect(defaultResponseSpy).toHaveBeenCalledTimes(1);
543
+ expect(defaultResponseSpy).toHaveBeenCalledWith(frame.command.ID, Status.SUCCESS, frame.cluster.ID, 1, {
544
+ direction: Direction.SERVER_TO_CLIENT,
545
+ });
546
+ });
547
+
548
+ it("replies with default response from caller", async () => {
549
+ const frame = ZclFrame.create(
550
+ FrameType.GLOBAL,
551
+ Direction.SERVER_TO_CLIENT,
552
+ false,
553
+ undefined,
554
+ 1,
555
+ "readRsp",
556
+ "genOnOff",
557
+ [{attrId: 0x0000, status: Status.SUCCESS, dataType: DataType.BOOLEAN, attrData: 1}],
558
+ {},
559
+ );
560
+ const dataPayload: ZclPayload = {
561
+ clusterID: frame.cluster.ID,
562
+ address: 0x1234,
563
+ header: frame.header,
564
+ data: frame.toBuffer(),
565
+ endpoint: 1,
566
+ linkquality: 150,
567
+ groupID: 0,
568
+ wasBroadcast: false,
569
+ destinationEndpoint: 1,
570
+ };
571
+
572
+ await device.onZclData(dataPayload, frame, endpoint, Status.INVALID_VALUE);
573
+
574
+ expect(readResponseSpy).toHaveBeenCalledTimes(0);
575
+ expect(commandSpy).toHaveBeenCalledTimes(0);
576
+ expect(readSpy).toHaveBeenCalledTimes(0);
577
+ expect(defaultResponseSpy).toHaveBeenCalledTimes(1);
578
+ expect(defaultResponseSpy).toHaveBeenCalledWith(frame.command.ID, Status.INVALID_VALUE, frame.cluster.ID, 1, {
579
+ direction: Direction.CLIENT_TO_SERVER,
580
+ });
581
+ });
582
+
583
+ it("replies with non-SUCCESS default response even when disable default response is ON", async () => {
584
+ const frame = ZclFrame.create(
585
+ FrameType.GLOBAL,
586
+ Direction.SERVER_TO_CLIENT,
587
+ true,
588
+ undefined,
589
+ 1,
590
+ "report",
591
+ "genOnOff",
592
+ [{attrId: 0x0000, dataType: DataType.BOOLEAN, attrData: 0}],
593
+ {},
594
+ );
595
+ const dataPayload: ZclPayload = {
596
+ clusterID: frame.cluster.ID,
597
+ address: 0x1234,
598
+ header: frame.header,
599
+ data: frame.toBuffer(),
600
+ endpoint: 1,
601
+ linkquality: 150,
602
+ groupID: 0,
603
+ wasBroadcast: false,
604
+ destinationEndpoint: 1,
605
+ };
606
+
607
+ await device.onZclData(dataPayload, frame, endpoint, Status.INVALID_VALUE);
608
+
609
+ expect(readResponseSpy).toHaveBeenCalledTimes(0);
610
+ expect(commandSpy).toHaveBeenCalledTimes(0);
611
+ expect(readSpy).toHaveBeenCalledTimes(0);
612
+ expect(defaultResponseSpy).toHaveBeenCalledTimes(1);
613
+ expect(defaultResponseSpy).toHaveBeenCalledWith(frame.command.ID, Status.INVALID_VALUE, frame.cluster.ID, 1, {
614
+ direction: Direction.CLIENT_TO_SERVER,
615
+ });
616
+ });
617
+
618
+ it("skips replying with default response when deviced option set", async () => {
619
+ device.skipDefaultResponse = true;
620
+ const frame = ZclFrame.create(
621
+ FrameType.GLOBAL,
622
+ Direction.SERVER_TO_CLIENT,
623
+ false,
624
+ undefined,
625
+ 1,
626
+ "readRsp",
627
+ "genOnOff",
628
+ [{attrId: 0x0000, status: Status.SUCCESS, dataType: DataType.BOOLEAN, attrData: 1}],
629
+ {},
630
+ );
631
+ const dataPayload: ZclPayload = {
632
+ clusterID: frame.cluster.ID,
633
+ address: 0x1234,
634
+ header: frame.header,
635
+ data: frame.toBuffer(),
636
+ endpoint: 1,
637
+ linkquality: 150,
638
+ groupID: 0,
639
+ wasBroadcast: false,
640
+ destinationEndpoint: 1,
641
+ };
642
+
643
+ await device.onZclData(dataPayload, frame, endpoint, undefined);
644
+
645
+ expect(readResponseSpy).toHaveBeenCalledTimes(0);
646
+ expect(commandSpy).toHaveBeenCalledTimes(0);
647
+ expect(readSpy).toHaveBeenCalledTimes(0);
648
+ expect(defaultResponseSpy).toHaveBeenCalledTimes(0);
649
+ });
650
+
651
+ it("de-duplicates replying with default response", async () => {
652
+ const frame = ZclFrame.create(
653
+ FrameType.GLOBAL,
654
+ Direction.SERVER_TO_CLIENT,
655
+ false,
656
+ undefined,
657
+ 0,
658
+ "readRsp",
659
+ "genOnOff",
660
+ [{attrId: 0x0000, status: Status.SUCCESS, dataType: DataType.BOOLEAN, attrData: 1}],
661
+ {},
662
+ );
663
+ const dataPayload: ZclPayload = {
664
+ clusterID: frame.cluster.ID,
665
+ address: 0x1234,
666
+ header: frame.header,
667
+ data: frame.toBuffer(),
668
+ endpoint: 1,
669
+ linkquality: 150,
670
+ groupID: 0,
671
+ wasBroadcast: false,
672
+ destinationEndpoint: 1,
673
+ };
674
+
675
+ await device.onZclData(dataPayload, frame, endpoint, undefined);
676
+ await device.onZclData(dataPayload, frame, endpoint, undefined);
677
+
678
+ expect(readResponseSpy).toHaveBeenCalledTimes(0);
679
+ expect(commandSpy).toHaveBeenCalledTimes(0);
680
+ expect(readSpy).toHaveBeenCalledTimes(0);
681
+ expect(defaultResponseSpy).toHaveBeenCalledTimes(1);
682
+ expect(defaultResponseSpy).toHaveBeenNthCalledWith(1, frame.command.ID, Status.SUCCESS, frame.cluster.ID, 0, {
683
+ direction: Direction.CLIENT_TO_SERVER,
684
+ });
685
+
686
+ device.resetTransient(false);
687
+ await device.onZclData(dataPayload, frame, endpoint, undefined);
688
+ await device.onZclData(dataPayload, frame, endpoint, undefined);
689
+
690
+ expect(defaultResponseSpy).toHaveBeenCalledTimes(2);
691
+ expect(defaultResponseSpy).toHaveBeenNthCalledWith(2, frame.command.ID, Status.SUCCESS, frame.cluster.ID, 0, {
692
+ direction: Direction.CLIENT_TO_SERVER,
693
+ });
694
+ });
695
+
696
+ it("skips replying with default response when Tuya device option set", async () => {
697
+ process.env.DISABLE_TUYA_DEFAULT_RESPONSE = "1";
698
+ device.manufacturerName = "_TZ1234_5678";
699
+ const frame = ZclFrame.create(
700
+ FrameType.GLOBAL,
701
+ Direction.SERVER_TO_CLIENT,
702
+ false,
703
+ undefined,
704
+ 1,
705
+ "readRsp",
706
+ "genOnOff",
707
+ [{attrId: 0x0000, status: Status.SUCCESS, dataType: DataType.BOOLEAN, attrData: 1}],
708
+ {},
709
+ );
710
+ const dataPayload: ZclPayload = {
711
+ clusterID: frame.cluster.ID,
712
+ address: 0x1234,
713
+ header: frame.header,
714
+ data: frame.toBuffer(),
715
+ endpoint: 1,
716
+ linkquality: 150,
717
+ groupID: 0,
718
+ wasBroadcast: false,
719
+ destinationEndpoint: 1,
720
+ };
721
+
722
+ await device.onZclData(dataPayload, frame, endpoint, undefined);
723
+
724
+ expect(readResponseSpy).toHaveBeenCalledTimes(0);
725
+ expect(commandSpy).toHaveBeenCalledTimes(0);
726
+ expect(readSpy).toHaveBeenCalledTimes(0);
727
+ expect(defaultResponseSpy).toHaveBeenCalledTimes(0);
728
+
729
+ delete process.env.DISABLE_TUYA_DEFAULT_RESPONSE;
730
+ });
731
+ });