@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,707 +0,0 @@
1
- /**
2
- * Script: Generate enums and interfaces from ZCL XML data type definitions.
3
- *
4
- * Goals:
5
- * - Parse XML cluster definition files (library.xml and its includes).
6
- * - Respect XSD-described structure (type.xsd).
7
- * - For <type:type> with <restriction><type:enumeration>, create a TS enum.
8
- * - For <type:type> with <bitmap><element>, create a TS enum (for flags).
9
- * - For <type:type> with <restriction><sequence>, create a TS interface.
10
- * - Parse inline type definitions within attributes and command fields.
11
- * - Write the generated code to a new file.
12
- *
13
- * Requirements:
14
- * pnpm i xml2js @types/xml2js
15
- *
16
- * Usage:
17
- * tsx scripts/zap-update-types.ts ../zap/zcl-builtin/dotdot
18
- */
19
-
20
- import {promises as fs} from "node:fs";
21
- import path from "node:path";
22
- import process from "node:process";
23
- import ts from "typescript";
24
- import {parseStringPromise} from "xml2js";
25
- import type {XMLAttr, XMLBitmapDefinition, XMLClusterSide, XMLEnumeration, XMLRestriction, XMLRoot, XMLSequence, XMLTypeType} from "./zap-xml-types";
26
-
27
- // #region Type Definitions
28
-
29
- interface ParsedXMLTypeType extends XMLTypeType {
30
- clusterName?: string;
31
- }
32
-
33
- interface ParsedType {
34
- name: string;
35
- node: ts.EnumDeclaration | ts.InterfaceDeclaration | ts.TypeAliasDeclaration | ts.VariableStatement;
36
- }
37
-
38
- // #endregion
39
-
40
- // #region Helpers
41
-
42
- const NUMBER_TO_WORD: Record<string, string> = {
43
- "0": "Zero",
44
- "1": "One",
45
- "2": "Two",
46
- "3": "Three",
47
- "4": "Four",
48
- "5": "Five",
49
- "6": "Six",
50
- "7": "Seven",
51
- "8": "Eight",
52
- "9": "Nine",
53
- };
54
-
55
- const NAME_OVERRIDES: Record<string, string> = {
56
- CCColorOptions: "ColorControlColorOptions",
57
- CCDirection: "ColorControlDirection",
58
- CCMoveMode: "ColorControlMoveMode",
59
- CCStepMode: "ColorControlStepMode",
60
- CCColorLoopDirection: "ColorControlColorLoopDirection",
61
- };
62
-
63
- /**
64
- * Converts a string to PascalCase.
65
- * Handles various delimiters like spaces, hyphens, and underscores.
66
- * Sanitizes names that start with a number.
67
- * @param str The input string.
68
- * @returns The PascalCased string.
69
- */
70
- function pascalCase(str: string): string {
71
- if (!str) {
72
- return "";
73
- }
74
-
75
- let sanitized = str.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[^0-9a-zA-Z]/g, " ");
76
- const firstChar = sanitized.charAt(0);
77
-
78
- if (NUMBER_TO_WORD[firstChar]) {
79
- sanitized = `${NUMBER_TO_WORD[firstChar]}${sanitized.slice(1)}`;
80
- }
81
-
82
- return sanitized
83
- .trim()
84
- .split(" ")
85
- .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
86
- .join("");
87
- }
88
-
89
- /** Only uppercase first char after sanitized */
90
- function simplePascalCase(str: string): string {
91
- if (!str) {
92
- return "";
93
- }
94
-
95
- const override = NAME_OVERRIDES[str];
96
-
97
- if (override) {
98
- return override;
99
- }
100
-
101
- const sanitized = str.replace(/[^0-9a-zA-Z]/g, "");
102
-
103
- return `${sanitized.charAt(0).toUpperCase()}${sanitized.slice(1)}`;
104
- }
105
-
106
- /**
107
- * Converts a string to camelCase.
108
- * @param str The input string.
109
- * @returns The camelCased string.
110
- */
111
- function camelCase(str: string): string {
112
- if (!str) {
113
- return "";
114
- }
115
-
116
- const pascal = simplePascalCase(str);
117
-
118
- return pascal.charAt(0).toLowerCase() + pascal.slice(1);
119
- }
120
-
121
- /**
122
- * Converts a hex or decimal string to a 0x-prefixed hex string.
123
- * @param value The input string (e.g., "01", "ff", "255").
124
- * @param pad4 If true pad to 4-digit instead of 2.
125
- * @returns The formatted hex string (e.g., "0x01", "0xFF").
126
- */
127
- function toHex(value: string, pad4 = false): string {
128
- if (!value) {
129
- return "0x00";
130
- }
131
-
132
- const num = Number.parseInt(value, 16);
133
-
134
- if (Number.isNaN(num)) {
135
- return "0x00";
136
- }
137
-
138
- return `0x${num.toString(16).padStart(pad4 ? 4 : 2, "0")}`;
139
- }
140
-
141
- // #endregion
142
-
143
- // #region XML Parsing and Type Collection
144
-
145
- const createSyntheticType = (
146
- clusterName: string,
147
- name: string,
148
- parent: XMLAttr<{type: string}> & {restriction?: XMLRestriction[]; bitmap?: XMLBitmapDefinition[]},
149
- ): ParsedXMLTypeType | undefined => {
150
- if (parent.restriction || parent.bitmap) {
151
- return {
152
- // biome-ignore lint/style/useNamingConvention: API
153
- $: {id: "", name: name, short: name, inheritsFrom: parent.$.type},
154
- clusterName: clusterName,
155
- restriction: parent.restriction,
156
- bitmap: parent.bitmap,
157
- };
158
- }
159
-
160
- return undefined;
161
- };
162
-
163
- async function collectAllTypes(startFile: string): Promise<ParsedXMLTypeType[]> {
164
- const allTypes: ParsedXMLTypeType[] = [];
165
- const visitedFiles = new Set<string>();
166
- const filesToProcess: string[] = [startFile];
167
-
168
- const processSide = (clusterName: string, side: XMLClusterSide | undefined) => {
169
- if (!side) {
170
- return;
171
- }
172
-
173
- if (side.attributes) {
174
- for (const attr of side.attributes[0].attribute) {
175
- const synthetic = createSyntheticType(clusterName, attr.$.name, attr);
176
-
177
- if (synthetic) {
178
- allTypes.push(synthetic);
179
- }
180
- }
181
- }
182
-
183
- if (side.commands) {
184
- for (const cmd of side.commands[0].command) {
185
- if (cmd.fields) {
186
- for (const field of cmd.fields[0].field) {
187
- const synthetic = createSyntheticType(clusterName, field.$.name, field);
188
-
189
- if (synthetic) {
190
- allTypes.push(synthetic);
191
- }
192
- }
193
- }
194
- }
195
- }
196
- };
197
-
198
- while (filesToProcess.length > 0) {
199
- const currentFile = filesToProcess.shift();
200
-
201
- if (!currentFile || visitedFiles.has(currentFile)) {
202
- continue;
203
- }
204
-
205
- visitedFiles.add(currentFile);
206
-
207
- try {
208
- const xmlContent = await fs.readFile(currentFile, "utf-8");
209
- const parsed = (await parseStringPromise(xmlContent, {
210
- explicitArray: true,
211
- mergeAttrs: false,
212
- explicitRoot: true,
213
- })) as XMLRoot;
214
- const libraries = parsed["zcl:library"];
215
- const global = parsed["zcl:global"];
216
- const clusters = parsed["zcl:cluster"];
217
-
218
- if (libraries) {
219
- for (const lib of Array.isArray(libraries) ? libraries : [libraries]) {
220
- if (lib["type:type"]) {
221
- allTypes.push(...lib["type:type"]);
222
- }
223
-
224
- if (lib["xi:include"]) {
225
- const baseDir = path.dirname(currentFile);
226
-
227
- for (const include of lib["xi:include"]) {
228
- if (include.$.href && include.$.parse === "xml") {
229
- filesToProcess.push(path.resolve(baseDir, include.$.href));
230
- }
231
- }
232
- }
233
- }
234
- }
235
-
236
- if (global) {
237
- for (const lib of Array.isArray(global) ? global : [global]) {
238
- if (lib["type:type"]) {
239
- allTypes.push(...lib["type:type"]);
240
- }
241
- }
242
- }
243
-
244
- if (clusters) {
245
- for (const cluster of Array.isArray(clusters) ? clusters : [clusters]) {
246
- if (cluster["type:type"]) {
247
- for (const type of cluster["type:type"]) {
248
- allTypes.push({...type, clusterName: cluster.$.name});
249
- }
250
- }
251
-
252
- if (cluster.server) {
253
- processSide(cluster.$.name, cluster.server[0]);
254
- }
255
-
256
- if (cluster.client) {
257
- processSide(cluster.$.name, cluster.client[0]);
258
- }
259
- }
260
- }
261
- } catch (error) {
262
- console.error(`Error processing file ${currentFile}:`, error);
263
- }
264
- }
265
-
266
- return allTypes;
267
- }
268
-
269
- class TypeResolver {
270
- #typesByShort = new Map<string, ParsedXMLTypeType>();
271
-
272
- add(types: ParsedXMLTypeType[]): void {
273
- for (const type of types) {
274
- const short = type.$.short.toLowerCase();
275
-
276
- if (!this.#typesByShort.has(short)) {
277
- this.#typesByShort.set(short, type);
278
- }
279
- }
280
- }
281
-
282
- resolve(short: string, factory: ts.NodeFactory): ts.TypeNode {
283
- const type = this.#typesByShort.get(short.toLowerCase());
284
-
285
- if (type?.$.inheritsFrom && !type.restriction?.some((r) => r["type:sequence"])) {
286
- return this.resolve(type.$.inheritsFrom, factory);
287
- }
288
-
289
- switch (short.toLowerCase()) {
290
- case "data8":
291
- case "data16":
292
- case "data24":
293
- case "data32":
294
- case "data40":
295
- case "data48":
296
- case "data56":
297
- case "data64":
298
- case "bool":
299
- case "map8":
300
- case "map16":
301
- case "map24":
302
- case "map32":
303
- case "map40":
304
- case "map48":
305
- case "map56":
306
- case "map64":
307
- case "uint8":
308
- case "uint16":
309
- case "uint24":
310
- case "uint32":
311
- case "uint40":
312
- case "uint48":
313
- case "uint56":
314
- case "uint64":
315
- case "int8":
316
- case "int16":
317
- case "int24":
318
- case "int32":
319
- case "int40":
320
- case "int48":
321
- case "int56":
322
- case "int64":
323
- case "enum8":
324
- case "enum16":
325
- case "semi":
326
- case "single":
327
- case "double":
328
- case "utc":
329
- case "clusterid":
330
- case "attribid":
331
- case "bacoid":
332
- return factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
333
-
334
- case "octstr":
335
- case "string":
336
- case "octstr16":
337
- case "string16":
338
- return factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
339
-
340
- case "array":
341
- case "set":
342
- case "bag":
343
- return factory.createArrayTypeNode(factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword));
344
-
345
- case "eui64":
346
- return factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
347
- case "key128":
348
- return factory.createTypeReferenceNode("Buffer");
349
-
350
- default:
351
- if (type && type.$.short !== "unk") {
352
- return factory.createTypeReferenceNode(simplePascalCase(type.$.short));
353
- }
354
-
355
- return factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword);
356
- }
357
- }
358
- }
359
-
360
- // #endregion
361
-
362
- // #region AST Generation
363
-
364
- function addComments(_factory: ts.NodeFactory, node: ts.Node, type: ParsedXMLTypeType, noType = false): void {
365
- const comments: string[] = [];
366
-
367
- if (type.clusterName) {
368
- comments.push(`@cluster ${type.clusterName}`);
369
- }
370
-
371
- if (type.$.inheritsFrom && !noType) {
372
- comments.push(`@type ${type.$.inheritsFrom}`);
373
- }
374
-
375
- if (comments.length > 0) {
376
- const commentText = `*\n * ${comments.join("\n * ")}\n `;
377
-
378
- ts.addSyntheticLeadingComment(node, ts.SyntaxKind.MultiLineCommentTrivia, commentText, true);
379
- }
380
- }
381
-
382
- function createEnumFromEnumeration(factory: ts.NodeFactory, type: ParsedXMLTypeType, enumerations: XMLEnumeration[]): ts.EnumDeclaration {
383
- const memberNames = new Map<string, number>();
384
- const members: ts.EnumMember[] = [];
385
-
386
- for (const e of enumerations) {
387
- let memberName = pascalCase(e.$.name);
388
- const count = memberNames.get(memberName);
389
-
390
- if (count !== undefined) {
391
- memberNames.set(memberName, count + 1);
392
-
393
- memberName = `${memberName}${count + 1}`;
394
- } else {
395
- memberNames.set(memberName, 1);
396
- }
397
-
398
- const memberValue = factory.createNumericLiteral(toHex(e.$.value, type.$.inheritsFrom?.endsWith("16")));
399
-
400
- members.push(factory.createEnumMember(memberName, memberValue));
401
- }
402
-
403
- const enumDeclaration = factory.createEnumDeclaration(
404
- [factory.createToken(ts.SyntaxKind.ExportKeyword)],
405
- simplePascalCase(type.$.short),
406
- members,
407
- );
408
-
409
- addComments(factory, enumDeclaration, type);
410
-
411
- return enumDeclaration;
412
- }
413
-
414
- function createEnumFromBitmap(factory: ts.NodeFactory, type: ParsedXMLTypeType, bitmap: XMLBitmapDefinition, shiftRight = false): ts.EnumDeclaration {
415
- const memberNames = new Map<string, number>();
416
- const members: ts.EnumMember[] = [];
417
-
418
- for (const e of bitmap.element) {
419
- if (shiftRight && !e.$.shiftRight) {
420
- continue;
421
- }
422
-
423
- let memberName = pascalCase(e.$.name);
424
- const count = memberNames.get(memberName);
425
-
426
- if (count !== undefined) {
427
- memberNames.set(memberName, count + 1);
428
-
429
- memberName = `${memberName}${count + 1}`;
430
- } else {
431
- memberNames.set(memberName, 1);
432
- }
433
-
434
- const memberValue = factory.createNumericLiteral(
435
- // biome-ignore lint/style/noNonNullAssertion: valid from top of loop
436
- toHex(shiftRight ? e.$.shiftRight! : e.$.mask, !shiftRight && type.$.inheritsFrom?.endsWith("16")),
437
- );
438
-
439
- members.push(factory.createEnumMember(memberName, memberValue));
440
- }
441
-
442
- const enumDeclaration = factory.createEnumDeclaration(
443
- [factory.createToken(ts.SyntaxKind.ExportKeyword)],
444
- simplePascalCase(
445
- shiftRight
446
- ? type.$.short.endsWith("ShiftRight")
447
- ? type.$.short
448
- : `${type.$.short.endsWith("Mask") ? type.$.short.slice(0, -4) : type.$.short}ShiftRight`
449
- : type.$.short.endsWith("Mask")
450
- ? type.$.short
451
- : `${type.$.short}Mask`,
452
- ),
453
- members,
454
- );
455
-
456
- addComments(factory, enumDeclaration, type, shiftRight);
457
-
458
- return enumDeclaration;
459
- }
460
-
461
- function createInterfaceFromSequence(
462
- factory: ts.NodeFactory,
463
- type: ParsedXMLTypeType,
464
- sequence: XMLSequence,
465
- resolver: TypeResolver,
466
- ): ts.InterfaceDeclaration {
467
- const memberNames = new Map<string, number>();
468
- const members: ts.PropertySignature[] = [];
469
-
470
- for (const f of sequence.field) {
471
- let propertyName = camelCase(f.$.name);
472
- const count = memberNames.get(propertyName);
473
-
474
- if (count !== undefined) {
475
- memberNames.set(propertyName, count + 1);
476
-
477
- propertyName = `${propertyName}${count + 1}`;
478
- } else {
479
- memberNames.set(propertyName, 1);
480
- }
481
-
482
- const propertyType = resolver.resolve(f.$.type, factory);
483
-
484
- members.push(
485
- factory.createPropertySignature(
486
- undefined,
487
- propertyName,
488
- f.$.presentIf ? factory.createToken(ts.SyntaxKind.QuestionToken) : undefined,
489
- propertyType,
490
- ),
491
- );
492
- }
493
-
494
- const interfaceDeclaration = factory.createInterfaceDeclaration(
495
- [factory.createToken(ts.SyntaxKind.ExportKeyword)],
496
- simplePascalCase(type.$.short),
497
- undefined,
498
- undefined,
499
- members,
500
- );
501
-
502
- addComments(factory, interfaceDeclaration, type);
503
-
504
- return interfaceDeclaration;
505
- }
506
-
507
- function createSafeNumericLiteral(value: string | number, bigInt: boolean, factory: ts.NodeFactory): ts.Expression {
508
- if (bigInt) {
509
- const num = BigInt(value);
510
- const str = String(value);
511
-
512
- return factory.createBigIntLiteral({base10Value: num < 0 ? str.slice(1) : str, negative: num < 0});
513
- }
514
-
515
- const num = Number(value);
516
-
517
- if (num < 0) {
518
- return factory.createPrefixUnaryExpression(ts.SyntaxKind.MinusToken, factory.createNumericLiteral(Math.abs(num)));
519
- }
520
-
521
- return factory.createNumericLiteral(num);
522
- }
523
-
524
- function createZclTypeInvalidConstants(
525
- factory: ts.NodeFactory,
526
- types: ParsedXMLTypeType[],
527
- ): [byName: ts.VariableStatement, byType: ts.VariableStatement] | undefined {
528
- const zclType = types.find((t) => t.$.short === "zclType");
529
-
530
- if (!zclType?.restriction?.[0]?.["type:enumeration"]) {
531
- return undefined;
532
- }
533
-
534
- const zclTypeEnumMap = new Map(zclType.restriction[0]["type:enumeration"].map((e) => [e.$.name, e.$.value]));
535
- const byNameProperties: ts.PropertyAssignment[] = [];
536
- const byTypeProperties: ts.PropertyAssignment[] = [];
537
-
538
- for (const type of types) {
539
- const invalidValue = type.restriction?.[0]?.["type:invalid"]?.[0]?.$?.value;
540
- const typeId = zclTypeEnumMap.get(type.$.short);
541
-
542
- if (invalidValue && typeId) {
543
- const name = pascalCase(type.$.short);
544
- const invalidValueLiteral = createSafeNumericLiteral(invalidValue, name.endsWith("56") || name.endsWith("64"), factory);
545
-
546
- byNameProperties.push(factory.createPropertyAssignment(factory.createIdentifier(name), invalidValueLiteral));
547
-
548
- const byTypeProp = factory.createPropertyAssignment(factory.createNumericLiteral(Number.parseInt(typeId, 16)), invalidValueLiteral);
549
- const commentText = `* ${name} `;
550
-
551
- ts.addSyntheticLeadingComment(byTypeProp, ts.SyntaxKind.MultiLineCommentTrivia, commentText, true);
552
- byTypeProperties.push(byTypeProp);
553
- }
554
- }
555
-
556
- const invalidVal = factory.createUnionTypeNode([
557
- factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword),
558
- factory.createKeywordTypeNode(ts.SyntaxKind.BigIntKeyword),
559
- ]);
560
- const byNameCommentText = "* ZCL non-values by type name (key of `ZclType`). ";
561
- const byNameConst = factory.createVariableStatement(
562
- [factory.createToken(ts.SyntaxKind.ExportKeyword)],
563
- factory.createVariableDeclarationList(
564
- [
565
- factory.createVariableDeclaration(
566
- "ZCL_TYPE_INVALID_BY_TYPE_NAME",
567
- undefined,
568
- factory.createTypeReferenceNode("Readonly", [
569
- factory.createTypeReferenceNode("Record", [factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), invalidVal]),
570
- ]),
571
- factory.createObjectLiteralExpression(byNameProperties, true),
572
- ),
573
- ],
574
- ts.NodeFlags.Const,
575
- ),
576
- );
577
- const byTypeCommentText = "* ZCL non-values by type ID (value of `ZclType`). ";
578
- const byTypeConst = factory.createVariableStatement(
579
- [factory.createToken(ts.SyntaxKind.ExportKeyword)],
580
- factory.createVariableDeclarationList(
581
- [
582
- factory.createVariableDeclaration(
583
- "ZCL_TYPE_INVALID_BY_TYPE",
584
- undefined,
585
- factory.createTypeReferenceNode("Readonly", [
586
- factory.createTypeReferenceNode("Record", [factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword), invalidVal]),
587
- ]),
588
- factory.createObjectLiteralExpression(byTypeProperties, true),
589
- ),
590
- ],
591
- ts.NodeFlags.Const,
592
- ),
593
- );
594
-
595
- ts.addSyntheticLeadingComment(byNameConst, ts.SyntaxKind.MultiLineCommentTrivia, byNameCommentText, true);
596
- ts.addSyntheticLeadingComment(byTypeConst, ts.SyntaxKind.MultiLineCommentTrivia, byTypeCommentText, true);
597
-
598
- return [byNameConst, byTypeConst];
599
- }
600
-
601
- function processTypes(factory: ts.NodeFactory, types: ParsedXMLTypeType[], resolver: TypeResolver): ParsedType[] {
602
- const processed: ParsedType[] = [];
603
- const processedNames = new Set<string>();
604
-
605
- for (const type of types) {
606
- const name = simplePascalCase(type.$.short);
607
-
608
- if (processedNames.has(name)) {
609
- continue;
610
- }
611
-
612
- const restriction = type.restriction?.[0];
613
- const bitmap = type.bitmap?.[0];
614
-
615
- if (restriction?.["type:enumeration"]) {
616
- const createdNode = createEnumFromEnumeration(factory, type, restriction["type:enumeration"]);
617
-
618
- if (createdNode.members.length > 0) {
619
- processed.push({name, node: createdNode});
620
- processedNames.add(name);
621
- }
622
- } else if (bitmap?.element) {
623
- const createdNode = createEnumFromBitmap(factory, type, bitmap);
624
-
625
- if (createdNode.members.length > 0) {
626
- processed.push({name: `${name}Mask`, node: createdNode});
627
- processedNames.add(name);
628
- }
629
-
630
- const createdNodeShiftRight = createEnumFromBitmap(factory, type, bitmap, true);
631
-
632
- if (createdNodeShiftRight.members.length > 0) {
633
- processed.push({name: `${name}ShiftRight`, node: createdNodeShiftRight});
634
- processedNames.add(name);
635
- }
636
- } else if (restriction?.["type:sequence"]) {
637
- const createdNode = createInterfaceFromSequence(factory, type, restriction["type:sequence"][0], resolver);
638
-
639
- if (createdNode.members.length > 0) {
640
- processed.push({name, node: createdNode});
641
- processedNames.add(name);
642
- }
643
- }
644
- }
645
-
646
- const zclTypeInvalidConsts = createZclTypeInvalidConstants(factory, types);
647
-
648
- if (zclTypeInvalidConsts) {
649
- processed.push({name: "ZCL_TYPE_INVALID_BY_TYPE_NAME", node: zclTypeInvalidConsts[0]});
650
- processed.push({name: "ZCL_TYPE_INVALID_BY_TYPE", node: zclTypeInvalidConsts[1]});
651
- }
652
-
653
- return processed;
654
- }
655
-
656
- // #endregion
657
-
658
- // #region Main
659
-
660
- async function main(): Promise<void> {
661
- const args = process.argv.slice(2);
662
-
663
- if (args.length !== 1) {
664
- throw new Error("Usage: tsx scripts/zap-update-types.ts <path-to-xml-files>");
665
- }
666
-
667
- const xmlPath = args[0];
668
- const libraryFile = path.join(xmlPath, "library.xml");
669
- const outputFile = "src/zspec/zcl/definition/datatypes.ts";
670
-
671
- console.log(`Starting type generation from ${libraryFile}...`);
672
-
673
- const allTypes = await collectAllTypes(libraryFile);
674
-
675
- if (allTypes.length === 0) {
676
- console.log("No types found to process.");
677
-
678
- return;
679
- }
680
-
681
- const resolver = new TypeResolver();
682
-
683
- resolver.add(allTypes);
684
-
685
- const factory = ts.factory;
686
- const processedTypes = processTypes(factory, allTypes, resolver);
687
- const nodesToPrint = processedTypes.map((p) => p.node);
688
- const sourceFile = ts.createSourceFile(outputFile, "", ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
689
- const printer = ts.createPrinter({newLine: ts.NewLineKind.LineFeed, removeComments: false});
690
- const content = nodesToPrint.map((node) => printer.printNode(ts.EmitHint.Unspecified, node, sourceFile)).join("\n\n");
691
- const fileHeader = `/**
692
- * This file was automatically generated by scripts/zap-update-types.ts. Do NOT edit manually.
693
- *
694
- * ZCL data type definitions.
695
- */\n\n`;
696
-
697
- await fs.writeFile(outputFile, `${fileHeader + content}\n`, "utf8");
698
-
699
- console.log(`Successfully generated ${processedTypes.length} types to ${outputFile}.`);
700
- }
701
-
702
- main().catch((err) => {
703
- console.error(err);
704
- process.exit(1);
705
- });
706
-
707
- // #endregion