@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
@@ -1,6 +1,9 @@
1
- import {BuffaloZclDataType, DataType, DataTypeClass, Direction, ParameterCondition} from "./enums";
1
+ import type {BuffaloZcl} from "../buffaloZcl";
2
+ import {isAnalogDataType} from "../utils";
3
+ import {ZclStatusError} from "../zclStatusError";
4
+ import {BuffaloZclDataType, DataType, Direction} from "./enums";
2
5
  import {Status} from "./status";
3
- import type {Parameter} from "./tstype";
6
+ import type {Command, StructuredSelector} from "./tstype";
4
7
 
5
8
  export type FoundationCommandName =
6
9
  | "read"
@@ -27,316 +30,800 @@ export type FoundationCommandName =
27
30
  | "discoverExt"
28
31
  | "discoverExtRsp";
29
32
 
30
- export interface FoundationDefinition {
31
- ID: number;
32
- parseStrategy: "repetitive" | "flat" | "oneof";
33
- parameters: readonly Parameter[];
34
- response?: number;
33
+ export interface FoundationDefinition<
34
+ // biome-ignore lint/suspicious/noExplicitAny: TODO: currently low level ZCL payloads are typed `any` which makes a mess
35
+ T extends Record<string, any> | Record<string, any>[] = Record<string, any> | Record<string, any>[],
36
+ > extends Pick<Command, "ID" | "name" | "response"> {
37
+ parse: (buffalo: BuffaloZcl) => T;
38
+ write: (buffalo: BuffaloZcl, payload: T) => void;
35
39
  }
36
40
 
37
- export const Foundation: Readonly<Record<FoundationCommandName, Readonly<FoundationDefinition>>> = {
41
+ export const Foundation = {
38
42
  /** Read Attributes */
39
43
  read: {
44
+ name: "read",
40
45
  ID: 0x00,
41
- parseStrategy: "repetitive",
42
- parameters: [{name: "attrId", type: DataType.DATA16}],
43
46
  response: 0x01, // readRsp
47
+ parse(buffalo) {
48
+ const payload: {attrId: number}[] = [];
49
+
50
+ do {
51
+ const attrId = buffalo.readUInt16();
52
+
53
+ payload.push({attrId});
54
+ } while (buffalo.isMore());
55
+
56
+ return payload;
57
+ },
58
+ write(buffalo, payload) {
59
+ if (!Array.isArray(payload)) {
60
+ throw new ZclStatusError(Status.MALFORMED_COMMAND);
61
+ }
62
+
63
+ for (const entry of payload) {
64
+ buffalo.writeUInt16(entry.attrId);
65
+ }
66
+ },
44
67
  },
45
68
  /** Read Attributes Response */
46
69
  readRsp: {
70
+ name: "readRsp",
47
71
  ID: 0x01,
48
- parseStrategy: "repetitive",
49
- parameters: [
50
- {name: "attrId", type: DataType.DATA16},
51
- {name: "status", type: DataType.DATA8},
52
- {name: "dataType", type: DataType.DATA8, conditions: [{type: ParameterCondition.FIELD_EQUAL, field: "status", value: Status.SUCCESS}]},
53
- {
54
- name: "attrData",
55
- type: BuffaloZclDataType.USE_DATA_TYPE,
56
- conditions: [{type: ParameterCondition.FIELD_EQUAL, field: "status", value: Status.SUCCESS}],
57
- },
58
- ],
72
+ parse(buffalo) {
73
+ const payload: {attrId: number; status: number; dataType?: number; attrData?: unknown}[] = [];
74
+
75
+ do {
76
+ const attrId = buffalo.readUInt16();
77
+ const status = buffalo.readUInt8();
78
+ const rec: (typeof payload)[number] = {attrId, status};
79
+
80
+ if (status === Status.SUCCESS) {
81
+ const dataType = buffalo.readUInt8();
82
+ rec.dataType = dataType;
83
+ // [workaround] parse char str as Xiaomi struct for attribute 0xff01 (65281)
84
+ rec.attrData = buffalo.read(attrId === 0xff01 && dataType === DataType.CHAR_STR ? BuffaloZclDataType.MI_STRUCT : dataType, {});
85
+ }
86
+
87
+ payload.push(rec);
88
+ } while (buffalo.isMore());
89
+
90
+ return payload;
91
+ },
92
+ write(buffalo, payload) {
93
+ if (!Array.isArray(payload)) {
94
+ throw new ZclStatusError(Status.MALFORMED_COMMAND);
95
+ }
96
+
97
+ for (const entry of payload) {
98
+ buffalo.writeUInt16(entry.attrId);
99
+ buffalo.writeUInt8(entry.status);
100
+
101
+ if (entry.status === Status.SUCCESS) {
102
+ buffalo.writeUInt8(entry.dataType);
103
+ buffalo.write(entry.dataType, entry.attrData, {});
104
+ }
105
+ }
106
+ },
59
107
  },
60
108
  /** Write Attributes */
61
109
  write: {
110
+ name: "write",
62
111
  ID: 0x02,
63
- parseStrategy: "repetitive",
64
- parameters: [
65
- {name: "attrId", type: DataType.DATA16},
66
- {name: "dataType", type: DataType.DATA8},
67
- {name: "attrData", type: BuffaloZclDataType.USE_DATA_TYPE},
68
- ],
69
112
  response: 0x04, // writeRsp
113
+ parse(buffalo) {
114
+ const payload: {attrId: number; dataType: number; attrData: unknown}[] = [];
115
+
116
+ do {
117
+ const attrId = buffalo.readUInt16();
118
+ const dataType = buffalo.readUInt8();
119
+ const attrData = buffalo.read(dataType, {});
120
+
121
+ payload.push({attrId, dataType, attrData});
122
+ } while (buffalo.isMore());
123
+
124
+ return payload;
125
+ },
126
+ write(buffalo, payload) {
127
+ if (!Array.isArray(payload)) {
128
+ throw new ZclStatusError(Status.MALFORMED_COMMAND);
129
+ }
130
+
131
+ for (const entry of payload) {
132
+ buffalo.writeUInt16(entry.attrId);
133
+ buffalo.writeUInt8(entry.dataType);
134
+ buffalo.write(entry.dataType, entry.attrData, {});
135
+ }
136
+ },
70
137
  },
71
138
  /** Write Attributes Undivided */
72
139
  writeUndiv: {
140
+ name: "writeUndiv",
73
141
  ID: 0x03,
74
- parseStrategy: "repetitive",
75
- parameters: [
76
- {name: "attrId", type: DataType.DATA16},
77
- {name: "dataType", type: DataType.DATA8},
78
- {name: "attrData", type: BuffaloZclDataType.USE_DATA_TYPE},
79
- ],
142
+ parse(buffalo) {
143
+ const payload: {attrId: number; dataType: number; attrData: unknown}[] = [];
144
+
145
+ do {
146
+ const attrId = buffalo.readUInt16();
147
+ const dataType = buffalo.readUInt8();
148
+ const attrData = buffalo.read(dataType, {});
149
+
150
+ payload.push({attrId, dataType, attrData});
151
+ } while (buffalo.isMore());
152
+
153
+ return payload;
154
+ },
155
+ write(buffalo, payload) {
156
+ if (!Array.isArray(payload)) {
157
+ throw new ZclStatusError(Status.MALFORMED_COMMAND);
158
+ }
159
+
160
+ for (const entry of payload) {
161
+ buffalo.writeUInt16(entry.attrId);
162
+ buffalo.writeUInt8(entry.dataType);
163
+ buffalo.write(entry.dataType, entry.attrData, {});
164
+ }
165
+ },
80
166
  },
81
167
  /** Write Attributes Response */
82
168
  writeRsp: {
169
+ name: "writeRsp",
83
170
  ID: 0x04,
84
- parseStrategy: "repetitive",
85
- parameters: [
86
- {name: "status", type: DataType.ENUM8},
87
- {
88
- name: "attrId",
89
- type: DataType.DATA16,
90
- conditions: [{type: ParameterCondition.FIELD_EQUAL, field: "status", reversed: true, value: Status.SUCCESS}],
91
- },
92
- ],
171
+ /**
172
+ * Note that write attribute status records are not included for successfully written attributes, to save bandwidth.
173
+ * In the case of successful writing of all attributes, only a single write attribute status record SHALL be included in the command,
174
+ * with the status field set to SUCCESS and the attribute identifier field omitted.
175
+ */
176
+ parse(buffalo) {
177
+ const firstStatus = buffalo.readUInt8();
178
+
179
+ if (firstStatus === Status.SUCCESS) {
180
+ return [{status: firstStatus}];
181
+ }
182
+
183
+ const payload: {status: number; attrId?: number}[] = [];
184
+ const attrId = buffalo.readUInt16();
185
+
186
+ payload.push({status: firstStatus, attrId});
187
+
188
+ while (buffalo.isMore()) {
189
+ const status = buffalo.readUInt8();
190
+ const attrId = buffalo.readUInt16();
191
+
192
+ payload.push({status, attrId});
193
+ }
194
+
195
+ return payload;
196
+ },
197
+ write(buffalo, payload) {
198
+ if (!Array.isArray(payload)) {
199
+ throw new ZclStatusError(Status.MALFORMED_COMMAND);
200
+ }
201
+
202
+ const nonSuccessPayload = payload.filter((entry) => entry.status !== Status.SUCCESS);
203
+
204
+ if (nonSuccessPayload.length === 0) {
205
+ buffalo.writeUInt8(Status.SUCCESS);
206
+ } else {
207
+ for (const entry of nonSuccessPayload) {
208
+ buffalo.writeUInt8(entry.status);
209
+ buffalo.writeUInt16(entry.attrId);
210
+ }
211
+ }
212
+ },
93
213
  },
94
214
  /** Write Attributes No Response */
95
215
  writeNoRsp: {
216
+ name: "writeNoRsp",
96
217
  ID: 0x05,
97
- parseStrategy: "repetitive",
98
- parameters: [
99
- {name: "attrId", type: DataType.DATA16},
100
- {name: "dataType", type: DataType.DATA8},
101
- {name: "attrData", type: BuffaloZclDataType.USE_DATA_TYPE},
102
- ],
218
+ parse(buffalo) {
219
+ const payload: {attrId: number; dataType: number; attrData: unknown}[] = [];
220
+
221
+ do {
222
+ const attrId = buffalo.readUInt16();
223
+ const dataType = buffalo.readUInt8();
224
+ const attrData = buffalo.read(dataType, {});
225
+
226
+ payload.push({attrId, dataType, attrData});
227
+ } while (buffalo.isMore());
228
+
229
+ return payload;
230
+ },
231
+ write(buffalo, payload) {
232
+ if (!Array.isArray(payload)) {
233
+ throw new ZclStatusError(Status.MALFORMED_COMMAND);
234
+ }
235
+
236
+ for (const entry of payload) {
237
+ buffalo.writeUInt16(entry.attrId);
238
+ buffalo.writeUInt8(entry.dataType);
239
+ buffalo.write(entry.dataType, entry.attrData, {});
240
+ }
241
+ },
103
242
  },
104
243
  /** Configure Reporting */
105
244
  configReport: {
245
+ name: "configReport",
106
246
  ID: 0x06,
107
- parseStrategy: "repetitive",
108
- parameters: [
109
- {name: "direction", type: DataType.DATA8},
110
- {name: "attrId", type: DataType.DATA16},
111
- {
112
- name: "dataType",
113
- type: DataType.DATA8,
114
- conditions: [{type: ParameterCondition.FIELD_EQUAL, field: "direction", value: Direction.CLIENT_TO_SERVER}],
115
- },
116
- {
117
- name: "minRepIntval",
118
- type: DataType.DATA16,
119
- conditions: [{type: ParameterCondition.FIELD_EQUAL, field: "direction", value: Direction.CLIENT_TO_SERVER}],
120
- },
121
- {
122
- name: "maxRepIntval",
123
- type: DataType.DATA16,
124
- conditions: [{type: ParameterCondition.FIELD_EQUAL, field: "direction", value: Direction.CLIENT_TO_SERVER}],
125
- },
126
- {
127
- name: "repChange",
128
- type: BuffaloZclDataType.USE_DATA_TYPE,
129
- conditions: [
130
- {type: ParameterCondition.FIELD_EQUAL, field: "direction", value: Direction.CLIENT_TO_SERVER},
131
- {type: ParameterCondition.DATA_TYPE_CLASS_EQUAL, value: DataTypeClass.ANALOG},
132
- ],
133
- },
134
- {
135
- name: "timeout",
136
- type: DataType.DATA16,
137
- conditions: [{type: ParameterCondition.FIELD_EQUAL, field: "direction", value: Direction.SERVER_TO_CLIENT}],
138
- },
139
- ],
140
247
  response: 0x07, // configReportRsp
248
+ parse(buffalo) {
249
+ const payload: {
250
+ direction: number;
251
+ attrId: number;
252
+ dataType?: number;
253
+ /**
254
+ * - If this value is set to 0x0000, then there is no minimum limit,
255
+ * unless one is imposed by the specification of the cluster using this reporting mechanism or by the application.
256
+ */
257
+ minRepIntval?: number;
258
+ /**
259
+ * - If this value is set to 0xffff, then the device SHALL not issue reports for the specified attribute,
260
+ * and the configuration information for that attribute need not be maintained.
261
+ * - If this value is set to 0x0000, and the minimum reporting interval field does not equal 0xffff
262
+ * there SHALL be no periodic reporting, but change based reporting SHALL still be operational.
263
+ * - If this value is set to 0x0000 and the Minimum Reporting Interval Field equals 0xffff,
264
+ * then the device SHALL revert to its default reporting configuration.
265
+ * The reportable change field, if present, SHALL be set to zero.
266
+ */
267
+ maxRepIntval?: number;
268
+ /**
269
+ * - If the Maximum Reporting Interval Field is set to 0xffff (terminate reporting configuration),
270
+ * or the Maximum Reporting Interval Field is set to 0x0000 and the Minimum Reporting Interval Field equals 0xffff,
271
+ * indicating a (default reporting configuration) then if this field is present,
272
+ * it SHALL be set to zero upon transmission and ignored upon reception.
273
+ */
274
+ repChange?: number;
275
+ /**
276
+ * - If this value is set to 0x0000, reports of the attribute are not subject to timeout.
277
+ */
278
+ timeout?: number;
279
+ }[] = [];
280
+
281
+ do {
282
+ const direction = buffalo.readUInt8();
283
+ const attrId = buffalo.readUInt16();
284
+ const rec: (typeof payload)[number] = {direction, attrId};
285
+
286
+ if (direction === Direction.CLIENT_TO_SERVER) {
287
+ const dataType = buffalo.readUInt8();
288
+ rec.dataType = dataType;
289
+ rec.minRepIntval = buffalo.readUInt16();
290
+ rec.maxRepIntval = buffalo.readUInt16();
291
+
292
+ if (isAnalogDataType(dataType)) {
293
+ rec.repChange = buffalo.read(dataType, {});
294
+ }
295
+ } else if (direction === Direction.SERVER_TO_CLIENT) {
296
+ rec.timeout = buffalo.readUInt16();
297
+ }
298
+
299
+ payload.push(rec);
300
+ } while (buffalo.isMore());
301
+
302
+ return payload;
303
+ },
304
+ write(buffalo, payload) {
305
+ if (!Array.isArray(payload)) {
306
+ throw new ZclStatusError(Status.MALFORMED_COMMAND);
307
+ }
308
+
309
+ for (const entry of payload) {
310
+ buffalo.writeUInt8(entry.direction);
311
+ buffalo.writeUInt16(entry.attrId);
312
+
313
+ if (entry.direction === Direction.CLIENT_TO_SERVER) {
314
+ buffalo.writeUInt8(entry.dataType);
315
+ buffalo.writeUInt16(entry.minRepIntval);
316
+ buffalo.writeUInt16(entry.maxRepIntval);
317
+
318
+ if (isAnalogDataType(entry.dataType)) {
319
+ buffalo.write(entry.dataType, entry.repChange, {});
320
+ }
321
+ } else if (entry.direction === Direction.SERVER_TO_CLIENT) {
322
+ buffalo.writeUInt16(entry.timeout);
323
+ }
324
+ }
325
+ },
141
326
  },
142
327
  /** Configure Reporting Response */
143
328
  configReportRsp: {
329
+ name: "configReportRsp",
144
330
  ID: 0x07,
145
- parseStrategy: "repetitive",
146
- parameters: [
147
- {name: "status", type: DataType.ENUM8},
148
- // minimumRemainingBufferBytes: if direction is present, attrId is also present
149
- // https://github.com/Koenkk/zigbee-herdsman/pull/115
150
- {name: "direction", type: DataType.DATA8, conditions: [{type: ParameterCondition.MINIMUM_REMAINING_BUFFER_BYTES, value: 3}]},
151
- {name: "attrId", type: DataType.DATA16, conditions: [{type: ParameterCondition.MINIMUM_REMAINING_BUFFER_BYTES, value: 2}]},
152
- ],
331
+ /**
332
+ * Note that attribute status records are not included for successfully configured attributes, to save bandwidth.
333
+ * In the case of successful configuration of all attributes, only a single attribute status record SHALL be included in the command,
334
+ * with the status field set to SUCCESS and the direction and attribute identifier fields omitted.
335
+ */
336
+ parse(buffalo) {
337
+ const firstStatus = buffalo.readUInt8();
338
+
339
+ if (firstStatus === Status.SUCCESS) {
340
+ return [{status: firstStatus}];
341
+ }
342
+
343
+ const payload: {status: number; direction?: number; attrId?: number}[] = [];
344
+ const direction = buffalo.readUInt8();
345
+ const attrId = buffalo.readUInt16();
346
+
347
+ payload.push({status: firstStatus, direction, attrId});
348
+
349
+ while (buffalo.isMore()) {
350
+ const status = buffalo.readUInt8();
351
+ const direction = buffalo.readUInt8();
352
+ const attrId = buffalo.readUInt16();
353
+
354
+ payload.push({status, direction, attrId});
355
+ }
356
+
357
+ return payload;
358
+ },
359
+ write(buffalo, payload) {
360
+ if (!Array.isArray(payload)) {
361
+ throw new ZclStatusError(Status.MALFORMED_COMMAND);
362
+ }
363
+
364
+ const nonSuccessPayload = payload.filter((entry) => entry.status !== Status.SUCCESS);
365
+
366
+ if (nonSuccessPayload.length === 0) {
367
+ buffalo.writeUInt8(Status.SUCCESS);
368
+ } else {
369
+ for (const entry of nonSuccessPayload) {
370
+ buffalo.writeUInt8(entry.status);
371
+ buffalo.writeUInt8(entry.direction);
372
+ buffalo.writeUInt16(entry.attrId);
373
+ }
374
+ }
375
+ },
153
376
  },
154
377
  /** Read Reporting Configuration */
155
378
  readReportConfig: {
379
+ name: "readReportConfig",
156
380
  ID: 0x08,
157
- parseStrategy: "repetitive",
158
- parameters: [
159
- {name: "direction", type: DataType.DATA8},
160
- {name: "attrId", type: DataType.DATA16},
161
- ],
162
381
  response: 0x09, // readReportConfigRsp
382
+ parse(buffalo) {
383
+ const payload: {direction: number; attrId: number}[] = [];
384
+
385
+ do {
386
+ const direction = buffalo.readUInt8();
387
+ const attrId = buffalo.readUInt16();
388
+
389
+ payload.push({direction, attrId});
390
+ } while (buffalo.isMore());
391
+
392
+ return payload;
393
+ },
394
+ write(buffalo, payload) {
395
+ if (!Array.isArray(payload)) {
396
+ throw new ZclStatusError(Status.MALFORMED_COMMAND);
397
+ }
398
+
399
+ for (const entry of payload) {
400
+ buffalo.writeUInt8(entry.direction);
401
+ buffalo.writeUInt16(entry.attrId);
402
+ }
403
+ },
163
404
  },
164
405
  /** Read Reporting Configuration Response */
165
406
  readReportConfigRsp: {
407
+ name: "readReportConfigRsp",
166
408
  ID: 0x09,
167
- parseStrategy: "repetitive",
168
- parameters: [
169
- {name: "status", type: DataType.ENUM8},
170
- {name: "direction", type: DataType.DATA8},
171
- {name: "attrId", type: DataType.DATA16},
172
- {
173
- name: "dataType",
174
- type: DataType.DATA8,
175
- conditions: [
176
- {type: ParameterCondition.FIELD_EQUAL, field: "status", value: Status.SUCCESS},
177
- {type: ParameterCondition.FIELD_EQUAL, field: "direction", value: Direction.CLIENT_TO_SERVER},
178
- ],
179
- },
180
- {
181
- name: "minRepIntval",
182
- type: DataType.DATA16,
183
- conditions: [
184
- {type: ParameterCondition.FIELD_EQUAL, field: "status", value: Status.SUCCESS},
185
- {type: ParameterCondition.FIELD_EQUAL, field: "direction", value: Direction.CLIENT_TO_SERVER},
186
- ],
187
- },
188
- {
189
- name: "maxRepIntval",
190
- type: DataType.DATA16,
191
- conditions: [
192
- {type: ParameterCondition.FIELD_EQUAL, field: "status", value: Status.SUCCESS},
193
- {type: ParameterCondition.FIELD_EQUAL, field: "direction", value: Direction.CLIENT_TO_SERVER},
194
- ],
195
- },
196
- {
197
- name: "repChange",
198
- type: BuffaloZclDataType.USE_DATA_TYPE,
199
- conditions: [
200
- {type: ParameterCondition.FIELD_EQUAL, field: "status", value: Status.SUCCESS},
201
- {type: ParameterCondition.FIELD_EQUAL, field: "direction", value: Direction.CLIENT_TO_SERVER},
202
- {type: ParameterCondition.DATA_TYPE_CLASS_EQUAL, value: DataTypeClass.ANALOG},
203
- ],
204
- },
205
- {
206
- name: "timeout",
207
- type: DataType.DATA16,
208
- conditions: [
209
- {type: ParameterCondition.FIELD_EQUAL, field: "status", value: Status.SUCCESS},
210
- {type: ParameterCondition.FIELD_EQUAL, field: "direction", value: Direction.SERVER_TO_CLIENT},
211
- ],
212
- },
213
- ],
409
+ parse(buffalo) {
410
+ const payload: {
411
+ status: number;
412
+ direction: number;
413
+ attrId: number;
414
+ dataType?: number;
415
+ minRepIntval?: number;
416
+ maxRepIntval?: number;
417
+ repChange?: number;
418
+ timeout?: number;
419
+ }[] = [];
420
+
421
+ do {
422
+ const status = buffalo.readUInt8();
423
+ const direction = buffalo.readUInt8();
424
+ const attrId = buffalo.readUInt16();
425
+ const rec: (typeof payload)[number] = {status, direction, attrId};
426
+
427
+ if (status === Status.SUCCESS) {
428
+ if (direction === Direction.CLIENT_TO_SERVER) {
429
+ const dataType = buffalo.readUInt8();
430
+ rec.dataType = dataType;
431
+ rec.minRepIntval = buffalo.readUInt16();
432
+ rec.maxRepIntval = buffalo.readUInt16();
433
+
434
+ if (isAnalogDataType(dataType)) {
435
+ rec.repChange = buffalo.read(dataType, {});
436
+ }
437
+ } else if (direction === Direction.SERVER_TO_CLIENT) {
438
+ rec.timeout = buffalo.readUInt16();
439
+ }
440
+ }
441
+
442
+ payload.push(rec);
443
+ } while (buffalo.isMore());
444
+
445
+ return payload;
446
+ },
447
+ write(buffalo, payload) {
448
+ if (!Array.isArray(payload)) {
449
+ throw new ZclStatusError(Status.MALFORMED_COMMAND);
450
+ }
451
+
452
+ for (const entry of payload) {
453
+ buffalo.writeUInt8(entry.status);
454
+ buffalo.writeUInt8(entry.direction);
455
+ buffalo.writeUInt16(entry.attrId);
456
+
457
+ if (entry.status === Status.SUCCESS) {
458
+ if (entry.direction === Direction.CLIENT_TO_SERVER) {
459
+ buffalo.writeUInt8(entry.dataType);
460
+ buffalo.writeUInt16(entry.minRepIntval);
461
+ buffalo.writeUInt16(entry.maxRepIntval);
462
+
463
+ if (isAnalogDataType(entry.dataType)) {
464
+ buffalo.write(entry.dataType, entry.repChange, {});
465
+ }
466
+ } else if (entry.direction === Direction.SERVER_TO_CLIENT) {
467
+ buffalo.writeUInt16(entry.timeout);
468
+ }
469
+ }
470
+ }
471
+ },
214
472
  },
215
473
  /** Report attributes */
216
474
  report: {
475
+ name: "report",
217
476
  ID: 0x0a,
218
- parseStrategy: "repetitive",
219
- parameters: [
220
- {name: "attrId", type: DataType.DATA16},
221
- {name: "dataType", type: DataType.DATA8},
222
- {name: "attrData", type: BuffaloZclDataType.USE_DATA_TYPE},
223
- ],
477
+ parse(buffalo) {
478
+ const payload: {attrId: number; dataType: number; attrData: unknown}[] = [];
479
+
480
+ do {
481
+ const attrId = buffalo.readUInt16();
482
+ const dataType = buffalo.readUInt8();
483
+ // [workaround] parse char str as Xiaomi struct for attribute 0xff01 (65281)
484
+ const attrData = buffalo.read(attrId === 0xff01 && dataType === DataType.CHAR_STR ? BuffaloZclDataType.MI_STRUCT : dataType, {});
485
+
486
+ payload.push({attrId, dataType, attrData});
487
+ } while (buffalo.isMore());
488
+
489
+ return payload;
490
+ },
491
+ write(buffalo, payload) {
492
+ if (!Array.isArray(payload)) {
493
+ throw new ZclStatusError(Status.MALFORMED_COMMAND);
494
+ }
495
+
496
+ for (const entry of payload) {
497
+ buffalo.writeUInt16(entry.attrId);
498
+ buffalo.writeUInt8(entry.dataType);
499
+ buffalo.write(entry.dataType, entry.attrData, {});
500
+ }
501
+ },
224
502
  },
225
503
  /** Default Response */
226
504
  defaultRsp: {
505
+ name: "defaultRsp",
227
506
  ID: 0x0b,
228
- parseStrategy: "flat",
229
- parameters: [
230
- {name: "cmdId", type: DataType.DATA8},
231
- {name: "statusCode", type: DataType.ENUM8},
232
- ],
507
+ parse(buffalo) {
508
+ const cmdId = buffalo.readUInt8();
509
+ const statusCode = buffalo.readUInt8();
510
+
511
+ return {cmdId, statusCode};
512
+ },
513
+ write(buffalo, payload) {
514
+ if (Array.isArray(payload)) {
515
+ throw new ZclStatusError(Status.MALFORMED_COMMAND);
516
+ }
517
+
518
+ buffalo.writeUInt8(payload.cmdId);
519
+ buffalo.writeUInt8(payload.statusCode);
520
+ },
233
521
  },
234
522
  /** Discover Attributes */
235
523
  discover: {
524
+ name: "discover",
236
525
  ID: 0x0c,
237
- parseStrategy: "flat",
238
- parameters: [
239
- {name: "startAttrId", type: DataType.DATA16},
240
- {name: "maxAttrIds", type: DataType.DATA8},
241
- ],
526
+ parse(buffalo) {
527
+ const startAttrId = buffalo.readUInt16();
528
+ const maxAttrIds = buffalo.readUInt8();
529
+
530
+ return {startAttrId, maxAttrIds};
531
+ },
532
+ write(buffalo, payload) {
533
+ if (Array.isArray(payload)) {
534
+ throw new ZclStatusError(Status.MALFORMED_COMMAND);
535
+ }
536
+
537
+ buffalo.writeUInt16(payload.startAttrId);
538
+ buffalo.writeUInt8(payload.maxAttrIds);
539
+ },
242
540
  },
243
541
  /** Discover Attributes Response */
244
542
  discoverRsp: {
543
+ name: "discoverRsp",
245
544
  ID: 0x0d,
246
- parseStrategy: "oneof",
247
- parameters: [
248
- {name: "attrId", type: DataType.DATA16},
249
- {name: "dataType", type: DataType.DATA8},
250
- ],
545
+ parse(buffalo) {
546
+ const discComplete = buffalo.readUInt8();
547
+ const attrInfos: {attrId: number; dataType: number}[] = [];
548
+ const payload: {discComplete: number; attrInfos: typeof attrInfos} = {discComplete, attrInfos};
549
+
550
+ do {
551
+ const attrId = buffalo.readUInt16();
552
+ const dataType = buffalo.readUInt8();
553
+
554
+ attrInfos.push({attrId, dataType});
555
+ } while (buffalo.isMore());
556
+
557
+ return payload;
558
+ },
559
+ write(buffalo, payload) {
560
+ if (Array.isArray(payload)) {
561
+ throw new ZclStatusError(Status.MALFORMED_COMMAND);
562
+ }
563
+
564
+ buffalo.writeUInt8(payload.discComplete);
565
+
566
+ for (const entry of payload.attrInfos) {
567
+ buffalo.writeUInt16(entry.attrId);
568
+ buffalo.writeUInt8(entry.dataType);
569
+ }
570
+ },
251
571
  },
252
572
  /** Read Attributes Structured */
253
573
  readStructured: {
574
+ name: "readStructured",
254
575
  ID: 0x0e,
255
- parseStrategy: "repetitive",
256
- parameters: [
257
- {name: "attrId", type: DataType.DATA16},
258
- {name: "selector", type: BuffaloZclDataType.STRUCTURED_SELECTOR},
259
- ],
576
+ parse(buffalo) {
577
+ const payload: {attrId: number; selector: StructuredSelector}[] = [];
578
+
579
+ do {
580
+ const attrId = buffalo.readUInt16();
581
+ const selector = buffalo.readStructuredSelector();
582
+
583
+ payload.push({attrId, selector});
584
+ } while (buffalo.isMore());
585
+
586
+ return payload;
587
+ },
588
+ write(buffalo, payload) {
589
+ if (!Array.isArray(payload)) {
590
+ throw new ZclStatusError(Status.MALFORMED_COMMAND);
591
+ }
592
+
593
+ for (const entry of payload) {
594
+ buffalo.writeUInt16(entry.attrId);
595
+ buffalo.writeStructuredSelector(entry.selector);
596
+ }
597
+ },
260
598
  },
261
599
  /** Write Attributes Structured */
262
600
  writeStructured: {
601
+ name: "writeStructured",
263
602
  ID: 0x0f,
264
- parseStrategy: "repetitive",
265
- parameters: [
266
- {name: "attrId", type: DataType.DATA16},
267
- {name: "selector", type: BuffaloZclDataType.STRUCTURED_SELECTOR},
268
- {name: "dataType", type: DataType.DATA8},
269
- {name: "elementData", type: BuffaloZclDataType.USE_DATA_TYPE},
270
- ],
271
603
  response: 0x10, // writeStructuredRsp
604
+ parse(buffalo) {
605
+ const payload: {attrId: number; selector: StructuredSelector; dataType: number; elementData: unknown}[] = [];
606
+
607
+ do {
608
+ const attrId = buffalo.readUInt16();
609
+ const selector = buffalo.readStructuredSelector();
610
+ const dataType = buffalo.readUInt8();
611
+ const elementData = buffalo.read(dataType, {});
612
+
613
+ payload.push({attrId, selector, dataType, elementData});
614
+ } while (buffalo.isMore());
615
+
616
+ return payload;
617
+ },
618
+ write(buffalo, payload) {
619
+ if (!Array.isArray(payload)) {
620
+ throw new ZclStatusError(Status.MALFORMED_COMMAND);
621
+ }
622
+
623
+ for (const entry of payload) {
624
+ buffalo.writeUInt16(entry.attrId);
625
+ buffalo.writeStructuredSelector(entry.selector);
626
+ buffalo.writeUInt8(entry.dataType);
627
+ buffalo.write(entry.dataType, entry.elementData, {});
628
+ }
629
+ },
272
630
  },
273
631
  /** Write Attributes Structured response */
274
632
  writeStructuredRsp: {
633
+ name: "writeStructuredRsp",
275
634
  ID: 0x10,
276
- parseStrategy: "repetitive",
277
- // contains only one SUCCESS record for all written attributes if all written successfully
278
- parameters: [
279
- {name: "status", type: DataType.ENUM8},
280
- {
281
- name: "attrId",
282
- type: DataType.DATA16,
283
- conditions: [{type: ParameterCondition.FIELD_EQUAL, field: "status", reversed: true, value: Status.SUCCESS}],
284
- },
285
- // always one zero-octet if failed attribute not of type array or structure, otherwise can also be zero if no info on which element caused failure
286
- {
287
- name: "selector",
288
- type: BuffaloZclDataType.STRUCTURED_SELECTOR,
289
- conditions: [{type: ParameterCondition.FIELD_EQUAL, field: "status", reversed: true, value: Status.SUCCESS}],
290
- },
291
- ],
635
+ /**
636
+ * Note that write attribute status records are not included for successfully written attributes, to save bandwidth.
637
+ * In the case of successful writing of all attributes, only a single write attribute status record SHALL be included in the command,
638
+ * with the status field set to SUCCESS and the attribute identifier and selector fields omitted.
639
+ */
640
+ parse(buffalo) {
641
+ const firstStatus = buffalo.readUInt8();
642
+
643
+ if (firstStatus === Status.SUCCESS) {
644
+ return [{status: firstStatus}];
645
+ }
646
+
647
+ const payload: {status: number; attrId?: number; selector?: StructuredSelector}[] = [];
648
+ const attrId = buffalo.readUInt16();
649
+ const selector = buffalo.readStructuredSelector();
650
+
651
+ payload.push({status: firstStatus, attrId, selector});
652
+
653
+ while (buffalo.isMore()) {
654
+ const status = buffalo.readUInt8();
655
+ const attrId = buffalo.readUInt16();
656
+ const selector = buffalo.readStructuredSelector();
657
+
658
+ payload.push({status, attrId, selector});
659
+ }
660
+
661
+ return payload;
662
+ },
663
+ write(buffalo, payload) {
664
+ if (!Array.isArray(payload)) {
665
+ throw new ZclStatusError(Status.MALFORMED_COMMAND);
666
+ }
667
+
668
+ const nonSuccessPayload = payload.filter((entry) => entry.status !== Status.SUCCESS);
669
+
670
+ if (nonSuccessPayload.length === 0) {
671
+ buffalo.writeUInt8(Status.SUCCESS);
672
+ } else {
673
+ for (const entry of nonSuccessPayload) {
674
+ buffalo.writeUInt8(entry.status);
675
+ buffalo.writeUInt16(entry.attrId);
676
+ buffalo.writeStructuredSelector(entry.selector);
677
+ }
678
+ }
679
+ },
292
680
  },
293
681
  /** Discover Commands Received */
294
682
  discoverCommands: {
683
+ name: "discoverCommands",
295
684
  ID: 0x11,
296
- parseStrategy: "flat",
297
- parameters: [
298
- {name: "startCmdId", type: DataType.DATA8},
299
- {name: "maxCmdIds", type: DataType.DATA8},
300
- ],
685
+ parse(buffalo) {
686
+ const startCmdId = buffalo.readUInt8();
687
+ const maxCmdIds = buffalo.readUInt8();
688
+
689
+ return {startCmdId, maxCmdIds};
690
+ },
691
+ write(buffalo, payload) {
692
+ if (Array.isArray(payload)) {
693
+ throw new ZclStatusError(Status.MALFORMED_COMMAND);
694
+ }
695
+
696
+ buffalo.writeUInt8(payload.startCmdId);
697
+ buffalo.writeUInt8(payload.maxCmdIds);
698
+ },
301
699
  },
302
700
  /** Discover Commands Received Response */
303
701
  discoverCommandsRsp: {
702
+ name: "discoverCommandsRsp",
304
703
  ID: 0x12,
305
- parseStrategy: "oneof",
306
- parameters: [{name: "cmdId", type: DataType.DATA8}],
704
+ parse(buffalo) {
705
+ const discComplete = buffalo.readUInt8();
706
+ const commandIds: number[] = [];
707
+ const payload: {discComplete: number; commandIds: typeof commandIds} = {discComplete, commandIds};
708
+
709
+ do {
710
+ const commandId = buffalo.readUInt8();
711
+
712
+ commandIds.push(commandId);
713
+ } while (buffalo.isMore());
714
+
715
+ return payload;
716
+ },
717
+ write(buffalo, payload) {
718
+ if (Array.isArray(payload)) {
719
+ throw new ZclStatusError(Status.MALFORMED_COMMAND);
720
+ }
721
+
722
+ buffalo.writeUInt8(payload.discComplete);
723
+
724
+ for (const commandId of payload.commandIds) {
725
+ buffalo.writeUInt8(commandId);
726
+ }
727
+ },
307
728
  },
308
729
  /** Discover Commands Generated */
309
730
  discoverCommandsGen: {
731
+ name: "discoverCommandsGen",
310
732
  ID: 0x13,
311
- parseStrategy: "flat",
312
- parameters: [
313
- {name: "startCmdId", type: DataType.DATA8},
314
- {name: "maxCmdIds", type: DataType.DATA8},
315
- ],
733
+ parse(buffalo) {
734
+ const startCmdId = buffalo.readUInt8();
735
+ const maxCmdIds = buffalo.readUInt8();
736
+
737
+ return {startCmdId, maxCmdIds};
738
+ },
739
+ write(buffalo, payload) {
740
+ if (Array.isArray(payload)) {
741
+ throw new ZclStatusError(Status.MALFORMED_COMMAND);
742
+ }
743
+
744
+ buffalo.writeUInt8(payload.startCmdId);
745
+ buffalo.writeUInt8(payload.maxCmdIds);
746
+ },
316
747
  },
317
748
  /** Discover Commands Generated Response */
318
749
  discoverCommandsGenRsp: {
750
+ name: "discoverCommandsGenRsp",
319
751
  ID: 0x14,
320
- parseStrategy: "oneof",
321
- parameters: [{name: "cmdId", type: DataType.DATA8}],
752
+ parse(buffalo) {
753
+ const discComplete = buffalo.readUInt8();
754
+ const commandIds: number[] = [];
755
+ const payload: {discComplete: number; commandIds: typeof commandIds} = {discComplete, commandIds};
756
+
757
+ do {
758
+ const commandId = buffalo.readUInt8();
759
+
760
+ commandIds.push(commandId);
761
+ } while (buffalo.isMore());
762
+
763
+ return payload;
764
+ },
765
+ write(buffalo, payload) {
766
+ if (Array.isArray(payload)) {
767
+ throw new ZclStatusError(Status.MALFORMED_COMMAND);
768
+ }
769
+
770
+ buffalo.writeUInt8(payload.discComplete);
771
+
772
+ for (const commandId of payload.commandIds) {
773
+ buffalo.writeUInt8(commandId);
774
+ }
775
+ },
322
776
  },
323
777
  /** Discover Attributes Extended */
324
778
  discoverExt: {
779
+ name: "discoverExt",
325
780
  ID: 0x15,
326
- parseStrategy: "flat",
327
- parameters: [
328
- {name: "startAttrId", type: DataType.DATA16},
329
- {name: "maxAttrIds", type: DataType.DATA8},
330
- ],
781
+ parse(buffalo) {
782
+ const startAttrId = buffalo.readUInt16();
783
+ const maxAttrIds = buffalo.readUInt8();
784
+
785
+ return {startAttrId, maxAttrIds};
786
+ },
787
+ write(buffalo, payload) {
788
+ if (Array.isArray(payload)) {
789
+ throw new ZclStatusError(Status.MALFORMED_COMMAND);
790
+ }
791
+
792
+ buffalo.writeUInt16(payload.startAttrId);
793
+ buffalo.writeUInt8(payload.maxAttrIds);
794
+ },
331
795
  },
332
796
  /** Discover Attributes Extended Response */
333
797
  discoverExtRsp: {
798
+ name: "discoverExtRsp",
334
799
  ID: 0x16,
335
- parseStrategy: "oneof",
336
- parameters: [
337
- {name: "attrId", type: DataType.DATA16},
338
- {name: "dataType", type: DataType.DATA8},
339
- {name: "access", type: DataType.DATA8},
340
- ],
800
+ parse(buffalo) {
801
+ const discComplete = buffalo.readUInt8();
802
+ const attrInfos: {attrId: number; dataType: number; access: number}[] = [];
803
+ const payload: {discComplete: number; attrInfos: typeof attrInfos} = {discComplete, attrInfos};
804
+
805
+ do {
806
+ const attrId = buffalo.readUInt16();
807
+ const dataType = buffalo.readUInt8();
808
+ const access = buffalo.readUInt8();
809
+
810
+ attrInfos.push({attrId, dataType, access});
811
+ } while (buffalo.isMore());
812
+
813
+ return payload;
814
+ },
815
+ write(buffalo, payload) {
816
+ if (Array.isArray(payload)) {
817
+ throw new ZclStatusError(Status.MALFORMED_COMMAND);
818
+ }
819
+
820
+ buffalo.writeUInt8(payload.discComplete);
821
+
822
+ for (const entry of payload.attrInfos) {
823
+ buffalo.writeUInt16(entry.attrId);
824
+ buffalo.writeUInt8(entry.dataType);
825
+ buffalo.writeUInt8(entry.access);
826
+ }
827
+ },
341
828
  },
342
- };
829
+ } satisfies Readonly<Record<FoundationCommandName, Readonly<FoundationDefinition>>>;