@willieee802/zigbee-herdsman 0.49.0 → 0.49.1

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 (114) hide show
  1. package/.release-please-manifest.json +1 -1
  2. package/CHANGELOG.md +38 -0
  3. package/biome.json +1 -1
  4. package/dist/adapter/adapter.d.ts.map +1 -0
  5. package/dist/adapter/adapterDiscovery.d.ts.map +1 -0
  6. package/dist/adapter/const.d.ts.map +1 -0
  7. package/dist/adapter/deconz/adapter/deconzAdapter.d.ts.map +1 -0
  8. package/dist/adapter/ember/adapter/emberAdapter.d.ts.map +1 -0
  9. package/dist/adapter/ember/adapter/endpoints.d.ts.map +1 -0
  10. package/dist/adapter/ember/adapter/oneWaitress.d.ts.map +1 -0
  11. package/dist/adapter/ember/adapter/tokensManager.d.ts.map +1 -0
  12. package/dist/adapter/ember/ezsp/ezsp.d.ts.map +1 -0
  13. package/dist/adapter/ember/utils/initters.d.ts.map +1 -0
  14. package/dist/adapter/events.d.ts.map +1 -0
  15. package/dist/adapter/ezsp/adapter/backup.d.ts.map +1 -0
  16. package/dist/adapter/ezsp/adapter/ezspAdapter.d.ts.map +1 -0
  17. package/dist/adapter/ezsp/driver/driver.d.ts.map +1 -0
  18. package/dist/adapter/ezsp/driver/index.d.ts.map +1 -0
  19. package/dist/adapter/ezsp/driver/multicast.d.ts.map +1 -0
  20. package/dist/adapter/index.d.ts.map +1 -0
  21. package/dist/adapter/z-stack/adapter/endpoints.d.ts.map +1 -0
  22. package/dist/adapter/z-stack/adapter/manager.d.ts.map +1 -0
  23. package/dist/adapter/z-stack/adapter/zStackAdapter.d.ts.map +1 -0
  24. package/dist/adapter/z-stack/models/startup-options.d.ts.map +1 -0
  25. package/dist/adapter/zboss/adapter/zbossAdapter.d.ts.map +1 -0
  26. package/dist/adapter/zboss/driver.d.ts.map +1 -0
  27. package/dist/adapter/zboss/frame.d.ts.map +1 -0
  28. package/dist/adapter/zboss/frame.js +200 -0
  29. package/dist/adapter/zboss/frame.js.map +1 -0
  30. package/dist/adapter/zboss/uart.d.ts.map +1 -0
  31. package/dist/adapter/zigate/adapter/zigateAdapter.d.ts.map +1 -0
  32. package/dist/adapter/zigate/driver/buffaloZiGate.d.ts.map +1 -0
  33. package/dist/adapter/zigate/driver/buffaloZiGate.js +198 -0
  34. package/dist/adapter/zigate/driver/buffaloZiGate.js.map +1 -0
  35. package/dist/adapter/zigate/driver/ziGateObject.d.ts.map +1 -0
  36. package/dist/adapter/zigate/driver/zigate.d.ts.map +1 -0
  37. package/dist/adapter/zoh/adapter/zohAdapter.d.ts.map +1 -0
  38. package/dist/controller/controller.d.ts.map +1 -0
  39. package/dist/controller/controller.js +874 -0
  40. package/dist/controller/controller.js.map +1 -0
  41. package/dist/controller/database.d.ts.map +1 -0
  42. package/dist/controller/events.d.ts.map +1 -0
  43. package/dist/controller/events.js +3 -0
  44. package/dist/controller/events.js.map +1 -0
  45. package/dist/controller/greenPower.d.ts.map +1 -0
  46. package/dist/controller/greenPower.js +425 -0
  47. package/dist/controller/greenPower.js.map +1 -0
  48. package/dist/controller/helpers/index.d.ts.map +1 -0
  49. package/dist/controller/helpers/ota.d.ts.map +1 -0
  50. package/dist/controller/helpers/ota.js +467 -0
  51. package/dist/controller/helpers/ota.js.map +1 -0
  52. package/dist/controller/helpers/request.d.ts.map +1 -0
  53. package/dist/controller/helpers/requestQueue.d.ts.map +1 -0
  54. package/dist/controller/helpers/zclFrameConverter.d.ts.map +1 -0
  55. package/dist/controller/helpers/zclFrameConverter.js +84 -0
  56. package/dist/controller/helpers/zclFrameConverter.js.map +1 -0
  57. package/dist/controller/index.d.ts.map +1 -0
  58. package/dist/controller/model/device.d.ts.map +1 -0
  59. package/dist/controller/model/device.js +1396 -0
  60. package/dist/controller/model/device.js.map +1 -0
  61. package/dist/controller/model/endpoint.d.ts.map +1 -0
  62. package/dist/controller/model/endpoint.js +822 -0
  63. package/dist/controller/model/endpoint.js.map +1 -0
  64. package/dist/controller/model/entity.d.ts.map +1 -0
  65. package/dist/controller/model/group.d.ts.map +1 -0
  66. package/dist/controller/model/group.js +343 -0
  67. package/dist/controller/model/group.js.map +1 -0
  68. package/dist/controller/model/index.d.ts.map +1 -0
  69. package/dist/controller/model/zigbeeEntity.d.ts.map +1 -0
  70. package/dist/controller/touchlink.d.ts.map +1 -0
  71. package/dist/controller/tstype.d.ts.map +1 -0
  72. package/dist/controller/tstype.js +3 -0
  73. package/dist/controller/tstype.js.map +1 -0
  74. package/dist/index.d.ts.map +1 -0
  75. package/dist/utils/timeService.d.ts.map +1 -0
  76. package/dist/utils/timeService.js +127 -0
  77. package/dist/utils/timeService.js.map +1 -0
  78. package/dist/zspec/zcl/buffaloZcl.d.ts.map +1 -0
  79. package/dist/zspec/zcl/buffaloZcl.js +969 -0
  80. package/dist/zspec/zcl/buffaloZcl.js.map +1 -0
  81. package/dist/zspec/zcl/definition/cluster.d.ts.map +1 -0
  82. package/dist/zspec/zcl/definition/cluster.js +7507 -0
  83. package/dist/zspec/zcl/definition/cluster.js.map +1 -0
  84. package/dist/zspec/zcl/definition/clusters-types.d.ts +8135 -0
  85. package/dist/zspec/zcl/definition/clusters-types.d.ts.map +1 -0
  86. package/dist/zspec/zcl/definition/clusters-types.js +3 -0
  87. package/dist/zspec/zcl/definition/clusters-types.js.map +1 -0
  88. package/dist/zspec/zcl/definition/foundation.d.ts.map +1 -0
  89. package/dist/zspec/zcl/definition/foundation.js +312 -0
  90. package/dist/zspec/zcl/definition/foundation.js.map +1 -0
  91. package/dist/zspec/zcl/definition/tstype.d.ts +273 -0
  92. package/dist/zspec/zcl/definition/tstype.d.ts.map +1 -0
  93. package/dist/zspec/zcl/definition/tstype.js +3 -0
  94. package/dist/zspec/zcl/definition/tstype.js.map +1 -0
  95. package/dist/zspec/zcl/index.d.ts.map +1 -0
  96. package/dist/zspec/zcl/index.js +57 -0
  97. package/dist/zspec/zcl/index.js.map +1 -0
  98. package/dist/zspec/zcl/utils.d.ts.map +1 -0
  99. package/dist/zspec/zcl/utils.js +419 -0
  100. package/dist/zspec/zcl/utils.js.map +1 -0
  101. package/dist/zspec/zcl/zclFrame.d.ts.map +1 -0
  102. package/dist/zspec/zcl/zclFrame.js +328 -0
  103. package/dist/zspec/zcl/zclFrame.js.map +1 -0
  104. package/dist/zspec/zcl/zclHeader.d.ts.map +1 -0
  105. package/dist/zspec/zcl/zclHeader.js +88 -0
  106. package/dist/zspec/zcl/zclHeader.js.map +1 -0
  107. package/package.json +90 -83
  108. package/src/controller/helpers/ota.ts +5 -2
  109. package/src/zspec/zcl/definition/cluster.ts +3 -294
  110. package/src/zspec/zcl/definition/clusters-types.ts +6 -355
  111. package/src/zspec/zcl/definition/tstype.ts +0 -14
  112. package/src/zspec/zcl/utils.ts +14 -31
  113. package/test/controller.test.ts +16 -8
  114. package/test/zcl.test.ts +36 -7
@@ -0,0 +1,822 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.Endpoint = void 0;
40
+ const node_assert_1 = __importDefault(require("node:assert"));
41
+ const logger_1 = require("../../utils/logger");
42
+ const ZSpec = __importStar(require("../../zspec"));
43
+ const enums_1 = require("../../zspec/enums");
44
+ const Zcl = __importStar(require("../../zspec/zcl"));
45
+ const Zdo = __importStar(require("../../zspec/zdo"));
46
+ const request_1 = __importDefault(require("../helpers/request"));
47
+ const requestQueue_1 = __importDefault(require("../helpers/requestQueue"));
48
+ const ZclFrameConverter = __importStar(require("../helpers/zclFrameConverter"));
49
+ const zclTransactionSequenceNumber_1 = __importDefault(require("../helpers/zclTransactionSequenceNumber"));
50
+ const device_1 = __importDefault(require("./device"));
51
+ const entity_1 = __importDefault(require("./entity"));
52
+ const group_1 = __importDefault(require("./group"));
53
+ const zigbeeEntity_1 = require("./zigbeeEntity");
54
+ const NS = "zh:controller:endpoint";
55
+ class Endpoint extends zigbeeEntity_1.ZigbeeEntity {
56
+ databaseID;
57
+ deviceID;
58
+ inputClusters;
59
+ outputClusters;
60
+ profileID;
61
+ // biome-ignore lint/style/useNamingConvention: cross-repo impact
62
+ ID;
63
+ clusters;
64
+ deviceIeeeAddress;
65
+ deviceNetworkAddress;
66
+ _binds;
67
+ _configuredReportings;
68
+ meta;
69
+ pendingRequests;
70
+ // Getters/setters
71
+ get binds() {
72
+ const binds = [];
73
+ for (const bind of this._binds) {
74
+ // XXX: properties assumed valid when associated to `type`
75
+ const target =
76
+ // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
77
+ bind.type === "endpoint" ? device_1.default.byIeeeAddr(this.databaseID, bind.deviceIeeeAddress)?.getEndpoint(bind.endpointID) : group_1.default.byGroupID(bind.groupID, this.databaseID);
78
+ if (target) {
79
+ binds.push({ target, cluster: this.getCluster(bind.cluster) });
80
+ }
81
+ }
82
+ return binds;
83
+ }
84
+ get configuredReportings() {
85
+ const device = this.getDevice();
86
+ return this._configuredReportings.map((entry, index) => {
87
+ const cluster = Zcl.Utils.getCluster(entry.cluster, entry.manufacturerCode, device.customClusters);
88
+ const attribute = cluster.getAttribute(entry.attrId) ?? {
89
+ ID: entry.attrId,
90
+ name: `attr${index}`,
91
+ type: Zcl.DataType.UNKNOWN,
92
+ manufacturerCode: undefined,
93
+ };
94
+ return {
95
+ cluster,
96
+ attribute,
97
+ minimumReportInterval: entry.minRepIntval,
98
+ maximumReportInterval: entry.maxRepIntval,
99
+ reportableChange: entry.repChange,
100
+ };
101
+ });
102
+ }
103
+ constructor(databaseID, id, profileID, deviceID, inputClusters, outputClusters, deviceNetworkAddress, deviceIeeeAddress, clusters, binds, configuredReportings, meta) {
104
+ super();
105
+ this.databaseID = databaseID;
106
+ this.ID = id;
107
+ this.profileID = profileID;
108
+ this.deviceID = deviceID;
109
+ this.inputClusters = inputClusters;
110
+ this.outputClusters = outputClusters;
111
+ this.deviceNetworkAddress = deviceNetworkAddress;
112
+ this.deviceIeeeAddress = deviceIeeeAddress;
113
+ this.clusters = clusters;
114
+ this._binds = binds;
115
+ this._configuredReportings = configuredReportings;
116
+ this.meta = meta;
117
+ this.pendingRequests = new requestQueue_1.default(this);
118
+ }
119
+ /**
120
+ * Get device of this endpoint
121
+ */
122
+ getDevice() {
123
+ const device = device_1.default.byIeeeAddr(this.databaseID, this.deviceIeeeAddress);
124
+ if (!device) {
125
+ logger_1.logger.error(`Tried to get unknown/deleted device ${this.deviceIeeeAddress} from endpoint ${this.ID}.`, NS);
126
+ // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
127
+ logger_1.logger.debug(new Error().stack, NS);
128
+ }
129
+ // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
130
+ return device;
131
+ }
132
+ /**
133
+ * @param {number|string} clusterKey
134
+ * @returns {boolean}
135
+ */
136
+ supportsInputCluster(clusterKey) {
137
+ const cluster = this.getCluster(clusterKey);
138
+ return this.inputClusters.includes(cluster.ID);
139
+ }
140
+ /**
141
+ * @param {number|string} clusterKey
142
+ * @returns {boolean}
143
+ */
144
+ supportsOutputCluster(clusterKey) {
145
+ const cluster = this.getCluster(clusterKey);
146
+ return this.outputClusters.includes(cluster.ID);
147
+ }
148
+ /**
149
+ * @returns {ZclTypes.Cluster[]}
150
+ */
151
+ getInputClusters() {
152
+ return this.clusterNumbersToClusters(this.inputClusters);
153
+ }
154
+ /**
155
+ * @returns {ZclTypes.Cluster[]}
156
+ */
157
+ getOutputClusters() {
158
+ return this.clusterNumbersToClusters(this.outputClusters);
159
+ }
160
+ clusterNumbersToClusters(clusterNumbers) {
161
+ return clusterNumbers.map((c) => this.getCluster(c));
162
+ }
163
+ /*
164
+ * CRUD
165
+ */
166
+ static fromDatabaseRecord(record, deviceNetworkAddress, deviceIeeeAddress, databaseID) {
167
+ // Migrate attrs to attributes
168
+ for (const entryKey in record.clusters) {
169
+ const entry = record.clusters[entryKey];
170
+ if (entry.attrs != null) {
171
+ entry.attributes = entry.attrs;
172
+ delete entry.attrs;
173
+ }
174
+ }
175
+ // Migrate cluster renames from https://github.com/Koenkk/zigbee-herdsman/pull/1503 @deprecated 3.0
176
+ /* v8 ignore start */
177
+ if (record.clusters.piRetailTunnel) {
178
+ record.clusters.retailTunnel = record.clusters.piRetailTunnel;
179
+ delete record.clusters.piRetailTunnel;
180
+ }
181
+ if (record.clusters.tunneling) {
182
+ record.clusters.seTunneling = record.clusters.tunneling;
183
+ delete record.clusters.tunneling;
184
+ }
185
+ if (record.clusters.haMeterIdentification) {
186
+ record.clusters.seMeterIdentification = record.clusters.haMeterIdentification;
187
+ delete record.clusters.haMeterIdentification;
188
+ }
189
+ /* v8 ignore stop */
190
+ return new Endpoint(databaseID, record.epId, record.profId, record.devId, record.inClusterList, record.outClusterList, deviceNetworkAddress, deviceIeeeAddress, record.clusters, record.binds || [], record.configuredReportings || [], record.meta || {});
191
+ }
192
+ toDatabaseRecord() {
193
+ return {
194
+ profId: this.profileID,
195
+ epId: this.ID,
196
+ devId: this.deviceID,
197
+ inClusterList: this.inputClusters,
198
+ outClusterList: this.outputClusters,
199
+ clusters: this.clusters,
200
+ binds: this._binds,
201
+ configuredReportings: this._configuredReportings,
202
+ meta: this.meta,
203
+ };
204
+ }
205
+ static create(databaseID, id, profileID, deviceID, inputClusters, outputClusters, deviceNetworkAddress, deviceIeeeAddress) {
206
+ return new Endpoint(databaseID, id, profileID, deviceID, inputClusters, outputClusters, deviceNetworkAddress, deviceIeeeAddress, {}, [], [], {});
207
+ }
208
+ saveClusterAttributeKeyValue(clusterKey, list) {
209
+ const cluster = this.getCluster(clusterKey);
210
+ if (!this.clusters[cluster.name]) {
211
+ this.clusters[cluster.name] = { attributes: {} };
212
+ }
213
+ for (const attribute in list) {
214
+ this.clusters[cluster.name].attributes[attribute] = list[attribute];
215
+ }
216
+ }
217
+ getClusterAttributeValue(clusterKey, attributeKey) {
218
+ const cluster = this.getCluster(clusterKey);
219
+ if (this.clusters[cluster.name] && this.clusters[cluster.name].attributes) {
220
+ // XXX: used to throw (behavior changed in #1455)
221
+ const attribute = cluster.getAttribute(attributeKey);
222
+ if (attribute) {
223
+ return this.clusters[cluster.name].attributes[attribute.name];
224
+ }
225
+ }
226
+ return undefined;
227
+ }
228
+ saveClusterAttributeReportConfig(clusterId, manufacturerCode, reportConfigs) {
229
+ for (const entry of reportConfigs) {
230
+ if (entry.direction === Zcl.Direction.SERVER_TO_CLIENT) {
231
+ continue;
232
+ }
233
+ const existingConfigIdx = this._configuredReportings.findIndex((r) => r.cluster === clusterId &&
234
+ r.attrId === entry.attrId &&
235
+ (manufacturerCode === undefined || manufacturerCode === r.manufacturerCode));
236
+ if (entry.status === Zcl.Status.SUCCESS) {
237
+ if (existingConfigIdx > -1) {
238
+ this._configuredReportings[existingConfigIdx].minRepIntval = entry.minRepIntval;
239
+ this._configuredReportings[existingConfigIdx].maxRepIntval = entry.maxRepIntval;
240
+ this._configuredReportings[existingConfigIdx].repChange = entry.repChange;
241
+ }
242
+ else {
243
+ this._configuredReportings.push({
244
+ cluster: clusterId,
245
+ attrId: entry.attrId,
246
+ minRepIntval: entry.minRepIntval,
247
+ maxRepIntval: entry.maxRepIntval,
248
+ repChange: entry.repChange,
249
+ manufacturerCode,
250
+ });
251
+ }
252
+ }
253
+ else {
254
+ // UNSUPPORTED_ATTRIBUTE, UNREPORTABLE_ATTRIBUTE, NOT_FOUND
255
+ if (existingConfigIdx > -1) {
256
+ this._configuredReportings.splice(existingConfigIdx, 1);
257
+ }
258
+ }
259
+ }
260
+ this.save();
261
+ }
262
+ saveBindings(binds) {
263
+ this._binds = binds;
264
+ this.save();
265
+ }
266
+ clearBindings() {
267
+ this._binds.length = 0;
268
+ this.save();
269
+ }
270
+ hasPendingRequests() {
271
+ return this.pendingRequests.size > 0;
272
+ }
273
+ async sendPendingRequests(fastPolling) {
274
+ return await this.pendingRequests.send(fastPolling);
275
+ }
276
+ async sendRequest(frame, options, func = () => {
277
+ return entity_1.default.getAdapterByID(this.databaseID)?.sendZclFrameToEndpoint(this.deviceIeeeAddress, this.deviceNetworkAddress, this.ID, frame, options.timeout, options.disableResponse, options.disableRecovery, options.srcEndpoint, options.profileId);
278
+ }) {
279
+ const logPrefix = `Request Queue (${this.deviceIeeeAddress}/${this.ID}): `;
280
+ const device = this.getDevice();
281
+ const request = new request_1.default(func, frame, device.pendingRequestTimeout, options.sendPolicy);
282
+ if (request.sendPolicy !== "bulk") {
283
+ // Check if such a request is already in the queue and remove the old one(s) if necessary
284
+ this.pendingRequests.filter(request);
285
+ }
286
+ // send without queueing if sendPolicy is 'immediate' or if the device has no timeout set
287
+ if (request.sendPolicy === "immediate" || !device.pendingRequestTimeout) {
288
+ if (device.pendingRequestTimeout > 0) {
289
+ logger_1.logger.debug(`${logPrefix}send ${frame.command.name} request immediately (sendPolicy=${options.sendPolicy})`, NS);
290
+ }
291
+ return await request.send();
292
+ }
293
+ // If this is a bulk message, we queue directly.
294
+ if (request.sendPolicy === "bulk") {
295
+ logger_1.logger.debug(`${logPrefix}queue request (${this.pendingRequests.size})`, NS);
296
+ return await this.pendingRequests.queue(request);
297
+ }
298
+ try {
299
+ logger_1.logger.debug(`${logPrefix}send request`, NS);
300
+ return await request.send();
301
+ }
302
+ catch (error) {
303
+ // If we got a failed transaction, the device is likely sleeping.
304
+ // Queue for transmission later.
305
+ logger_1.logger.debug(`${logPrefix}queue request (transaction failed) (${error})`, NS);
306
+ return await this.pendingRequests.queue(request);
307
+ }
308
+ }
309
+ /*
310
+ * Zigbee functions
311
+ */
312
+ checkStatus(payload) {
313
+ const codes = Array.isArray(payload) ? payload.map((i) => i.status) : [payload.statusCode];
314
+ const invalid = codes.find((c) => c !== Zcl.Status.SUCCESS);
315
+ if (invalid)
316
+ throw new Zcl.StatusError(invalid);
317
+ }
318
+ async report(clusterKey, attributes, options) {
319
+ const cluster = this.getCluster(clusterKey, undefined, options?.manufacturerCode);
320
+ const payload = [];
321
+ // TODO: handle `attr.report !== true`
322
+ for (const nameOrID in attributes) {
323
+ const attribute = cluster.getAttribute(nameOrID);
324
+ if (attribute) {
325
+ payload.push({ attrId: attribute.ID, attrData: attributes[nameOrID], dataType: attribute.type });
326
+ }
327
+ else if (!Number.isNaN(Number(nameOrID))) {
328
+ const value = attributes[nameOrID];
329
+ payload.push({ attrId: Number(nameOrID), attrData: value.value, dataType: value.type });
330
+ }
331
+ else {
332
+ throw new Error(`Unknown attribute '${nameOrID}', specify either an existing attribute or a number`);
333
+ }
334
+ }
335
+ await this.zclCommand(cluster, "report", payload, options, attributes);
336
+ }
337
+ async write(clusterKey, attributes, options) {
338
+ const cluster = this.getCluster(clusterKey, undefined, options?.manufacturerCode);
339
+ const optionsWithDefaults = this.getOptionsWithDefaults(options, true, Zcl.Direction.CLIENT_TO_SERVER, cluster.manufacturerCode);
340
+ optionsWithDefaults.manufacturerCode = this.ensureManufacturerCodeIsUniqueAndGet(cluster, Object.keys(attributes), optionsWithDefaults.manufacturerCode, "write");
341
+ const payload = [];
342
+ for (const nameOrID in attributes) {
343
+ const attribute = cluster.getAttribute(nameOrID);
344
+ if (attribute) {
345
+ // TODO: handle `attr.writeOptional !== true`
346
+ const attrData = Zcl.Utils.processAttributeWrite(attribute, attributes[nameOrID]);
347
+ payload.push({ attrId: attribute.ID, attrData, dataType: attribute.type });
348
+ }
349
+ else if (!Number.isNaN(Number(nameOrID))) {
350
+ const value = attributes[nameOrID];
351
+ payload.push({ attrId: Number(nameOrID), attrData: value.value, dataType: value.type });
352
+ }
353
+ else {
354
+ throw new Error(`Unknown attribute '${nameOrID}', specify either an existing attribute or a number`);
355
+ }
356
+ }
357
+ await this.zclCommand(cluster, optionsWithDefaults.writeUndiv ? "writeUndiv" : "write", payload, optionsWithDefaults, attributes, true);
358
+ }
359
+ async writeResponse(clusterKey, transactionSequenceNumber, attributes, options) {
360
+ (0, node_assert_1.default)(options?.transactionSequenceNumber === undefined, "Use parameter");
361
+ const cluster = this.getCluster(clusterKey, undefined, options?.manufacturerCode);
362
+ const payload = [];
363
+ for (const nameOrID in attributes) {
364
+ // biome-ignore lint/style/noNonNullAssertion: from loop
365
+ const value = attributes[nameOrID];
366
+ if (value.status !== undefined) {
367
+ const attribute = cluster.getAttribute(nameOrID);
368
+ if (attribute) {
369
+ payload.push({ attrId: attribute.ID, status: value.status });
370
+ }
371
+ else if (!Number.isNaN(Number(nameOrID))) {
372
+ payload.push({ attrId: Number(nameOrID), status: value.status });
373
+ }
374
+ else {
375
+ throw new Error(`Unknown attribute '${nameOrID}', specify either an existing attribute or a number`);
376
+ }
377
+ }
378
+ else {
379
+ throw new Error(`Missing attribute 'status'`);
380
+ }
381
+ }
382
+ await this.zclCommand(cluster, "writeRsp", payload, { direction: Zcl.Direction.SERVER_TO_CLIENT, ...options, transactionSequenceNumber }, attributes);
383
+ }
384
+ // XXX: ideally, the return type should limit to the contents of the `attributes` param
385
+ async read(clusterKey, attributes, options) {
386
+ const device = this.getDevice();
387
+ const cluster = this.getCluster(clusterKey, device, options?.manufacturerCode);
388
+ const optionsWithDefaults = this.getOptionsWithDefaults(options, true, Zcl.Direction.CLIENT_TO_SERVER, cluster.manufacturerCode);
389
+ optionsWithDefaults.manufacturerCode = this.ensureManufacturerCodeIsUniqueAndGet(cluster, attributes, optionsWithDefaults.manufacturerCode, "read");
390
+ const payload = [];
391
+ // TODO: handle `attr.required !== true` => should not throw
392
+ for (const attribute of attributes) {
393
+ if (typeof attribute === "number") {
394
+ payload.push({ attrId: attribute });
395
+ }
396
+ else {
397
+ const attr = cluster.getAttribute(attribute);
398
+ if (attr) {
399
+ Zcl.Utils.processAttributePreRead(attr);
400
+ payload.push({ attrId: attr.ID });
401
+ }
402
+ else {
403
+ logger_1.logger.warning(`Ignoring unknown attribute ${attribute} in cluster ${cluster.name}`, NS);
404
+ }
405
+ }
406
+ }
407
+ // TODO: could be sending empty array payload
408
+ const resultFrame = await this.zclCommand(cluster, "read", payload, optionsWithDefaults, attributes, true);
409
+ return resultFrame
410
+ ? ZclFrameConverter.attributeKeyValue(resultFrame, device.manufacturerID, device.customClusters)
411
+ : {};
412
+ }
413
+ async readResponse(clusterKey, transactionSequenceNumber, attributes, options) {
414
+ (0, node_assert_1.default)(options?.transactionSequenceNumber === undefined, "Use parameter");
415
+ const cluster = this.getCluster(clusterKey, undefined, options?.manufacturerCode);
416
+ const payload = [];
417
+ for (const nameOrID in attributes) {
418
+ const attribute = cluster.getAttribute(nameOrID);
419
+ if (attribute) {
420
+ payload.push({ attrId: attribute.ID, attrData: attributes[nameOrID], dataType: attribute.type, status: 0 });
421
+ }
422
+ else if (!Number.isNaN(Number(nameOrID))) {
423
+ const value = attributes[nameOrID];
424
+ payload.push({ attrId: Number(nameOrID), attrData: value.value, dataType: value.type, status: 0 });
425
+ }
426
+ else {
427
+ throw new Error(`Unknown attribute '${nameOrID}', specify either an existing attribute or a number`);
428
+ }
429
+ }
430
+ await this.zclCommand(cluster, "readRsp", payload, { direction: Zcl.Direction.SERVER_TO_CLIENT, ...options, transactionSequenceNumber }, attributes);
431
+ }
432
+ async updateSimpleDescriptor() {
433
+ const clusterId = Zdo.ClusterId.SIMPLE_DESCRIPTOR_REQUEST;
434
+ const adapter = entity_1.default.getAdapterByID(this.databaseID);
435
+ if (!adapter) {
436
+ throw new Error(`No adapter found for database ID ${this.databaseID}`);
437
+ }
438
+ const zdoPayload = Zdo.Buffalo.buildRequest(adapter.hasZdoMessageOverhead ?? false, clusterId, this.deviceNetworkAddress, this.ID);
439
+ const response = await adapter.sendZdo(this.deviceIeeeAddress, this.deviceNetworkAddress, clusterId, zdoPayload, false);
440
+ if (!Zdo.Buffalo.checkStatus(response)) {
441
+ throw new Zdo.StatusError(response[0]);
442
+ }
443
+ const simpleDescriptor = response[1];
444
+ this.profileID = simpleDescriptor.profileId;
445
+ this.deviceID = simpleDescriptor.deviceId;
446
+ this.inputClusters = simpleDescriptor.inClusterList;
447
+ this.outputClusters = simpleDescriptor.outClusterList;
448
+ }
449
+ hasBind(clusterId, target) {
450
+ return this.getBindIndex(clusterId, target) !== -1;
451
+ }
452
+ getBindIndex(clusterId, target) {
453
+ return this.binds.findIndex((b) => b.cluster.ID === clusterId && b.target === target);
454
+ }
455
+ addBinding(clusterKey, target) {
456
+ const cluster = this.getCluster(clusterKey);
457
+ if (typeof target === "number") {
458
+ target = group_1.default.byGroupID(target, this.databaseID) || group_1.default.create(target, this.databaseID);
459
+ }
460
+ this.addBindingInternal(cluster, target);
461
+ }
462
+ addBindingInternal(cluster, target) {
463
+ if (!this.hasBind(cluster.ID, target)) {
464
+ if (target instanceof group_1.default) {
465
+ this._binds.push({ cluster: cluster.ID, groupID: target.groupID, type: "group" });
466
+ }
467
+ else {
468
+ this._binds.push({
469
+ cluster: cluster.ID,
470
+ type: "endpoint",
471
+ deviceIeeeAddress: target.deviceIeeeAddress,
472
+ endpointID: target.ID,
473
+ });
474
+ }
475
+ this.save();
476
+ }
477
+ }
478
+ async bind(clusterKey, target) {
479
+ const cluster = this.getCluster(clusterKey);
480
+ if (typeof target === "number") {
481
+ target = group_1.default.byGroupID(target, this.databaseID) || group_1.default.create(target, this.databaseID);
482
+ }
483
+ const destinationAddress = target instanceof Endpoint ? target.deviceIeeeAddress : target.groupID;
484
+ const log = `Bind ${this.deviceIeeeAddress}/${this.ID} ${cluster.name} from '${target instanceof Endpoint ? `${destinationAddress}/${target.ID}` : destinationAddress}'`;
485
+ logger_1.logger.debug(log, NS);
486
+ try {
487
+ const zdoClusterId = Zdo.ClusterId.BIND_REQUEST;
488
+ const zdoPayload = Zdo.Buffalo.buildRequest(entity_1.default.getAdapterByID(this.databaseID)?.hasZdoMessageOverhead ?? false, zdoClusterId, this.deviceIeeeAddress, this.ID, cluster.ID, target instanceof Endpoint ? Zdo.UNICAST_BINDING : Zdo.MULTICAST_BINDING, target instanceof Endpoint ? target.deviceIeeeAddress : ZSpec.BLANK_EUI64, target instanceof group_1.default ? target.groupID : 0, target instanceof Endpoint ? target.ID : 0xff);
489
+ const adapter = entity_1.default.getAdapterByID(this.databaseID);
490
+ if (!adapter) {
491
+ throw new Error(`No adapter found for database ID ${this.databaseID}`);
492
+ }
493
+ const response = await adapter.sendZdo(this.deviceIeeeAddress, this.deviceNetworkAddress, zdoClusterId, zdoPayload, false);
494
+ if (!Zdo.Buffalo.checkStatus(response)) {
495
+ throw new Zdo.StatusError(response[0]);
496
+ }
497
+ this.addBindingInternal(cluster, target);
498
+ }
499
+ catch (error) {
500
+ const err = error;
501
+ err.message = `${log} failed (${err.message})`;
502
+ // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
503
+ logger_1.logger.debug(err.stack, NS);
504
+ throw error;
505
+ }
506
+ }
507
+ save() {
508
+ this.getDevice().save();
509
+ }
510
+ async unbind(clusterKey, target, force = false) {
511
+ // When force is true the unbind is done even when the bind is not in the bind list, additionally when the target is a number
512
+ // it will not check if the group exists.
513
+ const cluster = this.getCluster(clusterKey);
514
+ const action = `Unbind ${this.deviceIeeeAddress}/${this.ID} ${cluster.name}`;
515
+ if (typeof target === "number") {
516
+ const groupTarget = group_1.default.byGroupID(target, this.databaseID);
517
+ if (groupTarget) {
518
+ target = groupTarget;
519
+ }
520
+ else if (!force) {
521
+ throw new Error(`${action} invalid target '${target}' (no group with this ID exists).`);
522
+ }
523
+ }
524
+ const destinationAddress = target instanceof Endpoint ? target.deviceIeeeAddress : target instanceof group_1.default ? target.groupID : target;
525
+ const log = `${action} from '${target instanceof Endpoint ? `${destinationAddress}/${target.ID}` : destinationAddress}'`;
526
+ const index = target instanceof Endpoint || target instanceof group_1.default ? this.getBindIndex(cluster.ID, target) : -1;
527
+ if (index === -1 && !force) {
528
+ logger_1.logger.debug(`${log} no bind present, skipping.`, NS);
529
+ return;
530
+ }
531
+ logger_1.logger.debug(log, NS);
532
+ try {
533
+ const zdoClusterId = Zdo.ClusterId.UNBIND_REQUEST;
534
+ const adapter = entity_1.default.getAdapterByID(this.databaseID);
535
+ if (!adapter) {
536
+ throw new Error(`No adapter found for database ID ${this.databaseID}`);
537
+ }
538
+ const zdoPayload = Zdo.Buffalo.buildRequest(adapter.hasZdoMessageOverhead, zdoClusterId, this.deviceIeeeAddress, this.ID, cluster.ID, target instanceof Endpoint ? Zdo.UNICAST_BINDING : Zdo.MULTICAST_BINDING, target instanceof Endpoint ? target.deviceIeeeAddress : ZSpec.BLANK_EUI64, target instanceof group_1.default ? target.groupID : typeof target === "number" ? target : 0, target instanceof Endpoint ? target.ID : 0xff);
539
+ const response = await adapter.sendZdo(this.deviceIeeeAddress, this.deviceNetworkAddress, zdoClusterId, zdoPayload, false);
540
+ if (!Zdo.Buffalo.checkStatus(response)) {
541
+ if (response[0] === Zdo.Status.NO_ENTRY) {
542
+ logger_1.logger.debug(`${log} no entry on device, removing entry from database.`, NS);
543
+ }
544
+ else {
545
+ throw new Zdo.StatusError(response[0]);
546
+ }
547
+ }
548
+ if (index !== -1) {
549
+ this._binds.splice(index, 1);
550
+ this.save();
551
+ }
552
+ }
553
+ catch (error) {
554
+ const err = error;
555
+ err.message = `${log} failed (${err.message})`;
556
+ // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
557
+ logger_1.logger.debug(err.stack, NS);
558
+ throw error;
559
+ }
560
+ }
561
+ async defaultResponse(commandID, status, clusterID, transactionSequenceNumber, options) {
562
+ (0, node_assert_1.default)(options?.transactionSequenceNumber === undefined, "Use parameter");
563
+ const payload = { cmdId: commandID, statusCode: status };
564
+ await this.zclCommand(clusterID, "defaultRsp", payload, { direction: Zcl.Direction.SERVER_TO_CLIENT, ...options, transactionSequenceNumber });
565
+ }
566
+ async configureReporting(clusterKey, items, options) {
567
+ const cluster = this.getCluster(clusterKey, undefined, options?.manufacturerCode);
568
+ const optionsWithDefaults = this.getOptionsWithDefaults(options, true, Zcl.Direction.CLIENT_TO_SERVER, cluster.manufacturerCode);
569
+ optionsWithDefaults.manufacturerCode = this.ensureManufacturerCodeIsUniqueAndGet(cluster, items, optionsWithDefaults.manufacturerCode, "configureReporting");
570
+ const payload = items.map((item) => {
571
+ let dataType;
572
+ let attrId;
573
+ if (typeof item.attribute === "object") {
574
+ dataType = item.attribute.type;
575
+ attrId = item.attribute.ID;
576
+ }
577
+ else {
578
+ const attribute = cluster.getAttribute(item.attribute);
579
+ if (attribute) {
580
+ dataType = attribute.type;
581
+ attrId = attribute.ID;
582
+ }
583
+ else {
584
+ throw new Error(`Invalid attribute '${item.attribute}' for cluster '${clusterKey}'`);
585
+ }
586
+ }
587
+ return {
588
+ direction: Zcl.Direction.CLIENT_TO_SERVER,
589
+ attrId,
590
+ dataType,
591
+ minRepIntval: item.minimumReportInterval,
592
+ maxRepIntval: item.maximumReportInterval,
593
+ repChange: item.reportableChange,
594
+ };
595
+ });
596
+ await this.zclCommand(cluster, "configReport", payload, optionsWithDefaults, items, true);
597
+ for (const e of payload) {
598
+ this._configuredReportings = this._configuredReportings.filter((c) => !(c.attrId === e.attrId &&
599
+ c.cluster === cluster.ID &&
600
+ (!("manufacturerCode" in c) || c.manufacturerCode === optionsWithDefaults.manufacturerCode)));
601
+ }
602
+ for (const entry of payload) {
603
+ if (entry.maxRepIntval !== 0xffff) {
604
+ this._configuredReportings.push({
605
+ cluster: cluster.ID,
606
+ attrId: entry.attrId,
607
+ minRepIntval: entry.minRepIntval,
608
+ maxRepIntval: entry.maxRepIntval,
609
+ // expects items[].attribute to always point to a number DataType
610
+ repChange: entry.repChange,
611
+ manufacturerCode: optionsWithDefaults.manufacturerCode,
612
+ });
613
+ }
614
+ }
615
+ this.save();
616
+ }
617
+ async readReportingConfig(clusterKey, items, options) {
618
+ const cluster = this.getCluster(clusterKey, undefined, options?.manufacturerCode);
619
+ const optionsWithDefaults = this.getOptionsWithDefaults(options, true, Zcl.Direction.CLIENT_TO_SERVER, cluster.manufacturerCode);
620
+ optionsWithDefaults.manufacturerCode = this.ensureManufacturerCodeIsUniqueAndGet(cluster, items, optionsWithDefaults.manufacturerCode, "readReportingConfig");
621
+ const payload = [];
622
+ for (const item of items) {
623
+ if (typeof item.attribute === "object") {
624
+ payload.push({ direction: item.direction ?? Zcl.Direction.CLIENT_TO_SERVER, attrId: item.attribute.ID });
625
+ }
626
+ else {
627
+ const attribute = cluster.getAttribute(item.attribute);
628
+ if (attribute) {
629
+ payload.push({ direction: item.direction ?? Zcl.Direction.CLIENT_TO_SERVER, attrId: attribute.ID });
630
+ }
631
+ else {
632
+ logger_1.logger.warning(`Ignoring unknown attribute ${item.attribute} in cluster ${cluster.name}`, NS);
633
+ }
634
+ }
635
+ }
636
+ // TODO: could be sending empty array payload
637
+ // don't check status otherwise whole command fails (we want to cherry-pick here)
638
+ const response = await this.zclCommand(cluster, "readReportConfig", payload, optionsWithDefaults, items, false);
639
+ if (response) {
640
+ this.saveClusterAttributeReportConfig(response.cluster.ID, optionsWithDefaults.manufacturerCode, response.payload);
641
+ return response.payload;
642
+ }
643
+ throw new Error("No response received");
644
+ }
645
+ async writeStructured(clusterKey, payload, options) {
646
+ await this.zclCommand(clusterKey, "writeStructured", payload, options);
647
+ // TODO: support `writeStructuredResponse`
648
+ }
649
+ async command(clusterKey, commandKey, payload, options) {
650
+ const frame = await this.zclCommand(clusterKey, commandKey, payload, options, undefined, false, Zcl.FrameType.SPECIFIC);
651
+ if (frame) {
652
+ return frame.payload;
653
+ }
654
+ }
655
+ async commandResponse(clusterKey, commandKey, payload, options, transactionSequenceNumber) {
656
+ (0, node_assert_1.default)(options?.transactionSequenceNumber === undefined, "Use parameter");
657
+ const device = this.getDevice();
658
+ const cluster = this.getCluster(clusterKey, device, options?.manufacturerCode);
659
+ const command = cluster.getCommandResponse(commandKey);
660
+ transactionSequenceNumber = transactionSequenceNumber ?? zclTransactionSequenceNumber_1.default.next();
661
+ const optionsWithDefaults = this.getOptionsWithDefaults(options, true, Zcl.Direction.SERVER_TO_CLIENT, cluster.manufacturerCode);
662
+ const frame = Zcl.Frame.create(Zcl.FrameType.SPECIFIC, optionsWithDefaults.direction, optionsWithDefaults.disableDefaultResponse, optionsWithDefaults.manufacturerCode, transactionSequenceNumber, command, cluster, payload, device.customClusters, optionsWithDefaults.reservedBits);
663
+ const createLogMessage = () => `CommandResponse ${this.deviceIeeeAddress}/${this.ID} ` +
664
+ `${cluster.name}.${command.name}(${JSON.stringify(payload)}, ${JSON.stringify(optionsWithDefaults)})`;
665
+ logger_1.logger.debug(createLogMessage, NS);
666
+ try {
667
+ // Broadcast Green Power responses
668
+ if (this.ID === 242) {
669
+ await this.sendRequest(frame, optionsWithDefaults, async () => {
670
+ await entity_1.default.getAdapterByID(this.databaseID)?.sendZclFrameToAll(242, frame, 242, enums_1.BroadcastAddress.RX_ON_WHEN_IDLE);
671
+ });
672
+ }
673
+ else {
674
+ await this.sendRequest(frame, optionsWithDefaults);
675
+ }
676
+ }
677
+ catch (error) {
678
+ const err = error;
679
+ err.message = `${createLogMessage()} failed (${err.message})`;
680
+ // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
681
+ logger_1.logger.debug(err.stack, NS);
682
+ throw error;
683
+ }
684
+ }
685
+ getOptionsWithDefaults(options, disableDefaultResponse, direction, manufacturerCode) {
686
+ return {
687
+ timeout: 10000,
688
+ disableResponse: false,
689
+ disableRecovery: false,
690
+ disableDefaultResponse,
691
+ direction,
692
+ srcEndpoint: undefined,
693
+ reservedBits: 0,
694
+ manufacturerCode,
695
+ transactionSequenceNumber: undefined,
696
+ writeUndiv: false,
697
+ ...(options || {}),
698
+ };
699
+ }
700
+ ensureManufacturerCodeIsUniqueAndGet(cluster, attributes, fallbackManufacturerCode, // XXX: problematic undefined for a "fallback"?
701
+ caller) {
702
+ let firstManufacturerCode;
703
+ let codeSet = false;
704
+ for (const nameOrID of attributes) {
705
+ let attributeID;
706
+ if (typeof nameOrID === "object") {
707
+ // ConfigureReportingItem
708
+ if (typeof nameOrID.attribute !== "object") {
709
+ attributeID = nameOrID.attribute;
710
+ }
711
+ else {
712
+ if (!codeSet) {
713
+ firstManufacturerCode = fallbackManufacturerCode;
714
+ codeSet = true;
715
+ }
716
+ else if (firstManufacturerCode !== fallbackManufacturerCode) {
717
+ throw new Error(`Cannot have attributes with different manufacturerCode in single '${caller}' call`);
718
+ }
719
+ continue;
720
+ }
721
+ }
722
+ else {
723
+ // string || number
724
+ attributeID = nameOrID;
725
+ }
726
+ // we fall back to caller|cluster provided manufacturerCode
727
+ const attribute = cluster.getAttribute(attributeID);
728
+ const manufacturerCode = attribute
729
+ ? attribute.manufacturerCode === undefined
730
+ ? fallbackManufacturerCode
731
+ : attribute.manufacturerCode
732
+ : fallbackManufacturerCode;
733
+ if (!codeSet) {
734
+ firstManufacturerCode = manufacturerCode;
735
+ codeSet = true;
736
+ }
737
+ else if (firstManufacturerCode !== manufacturerCode) {
738
+ throw new Error(`Cannot have attributes with different manufacturerCode in single '${caller}' call`);
739
+ }
740
+ }
741
+ return firstManufacturerCode;
742
+ }
743
+ async addToGroup(group) {
744
+ await this.zclCommand("genGroups", "add", { groupid: group.groupID, groupname: "" }, undefined, undefined, true, Zcl.FrameType.SPECIFIC);
745
+ group.addMember(this);
746
+ }
747
+ getCluster(clusterKey, device = undefined, manufacturerCode = undefined) {
748
+ if (!device) {
749
+ device = this.getDevice();
750
+ }
751
+ return Zcl.Utils.getCluster(clusterKey, manufacturerCode ?? device.manufacturerID, device.customClusters);
752
+ }
753
+ /**
754
+ * Remove endpoint from a group, accepts both a Group and number as parameter.
755
+ * The number parameter type should only be used when removing from a group which is not known
756
+ * to zigbee-herdsman.
757
+ */
758
+ async removeFromGroup(group) {
759
+ await this.zclCommand("genGroups", "remove", { groupid: group instanceof group_1.default ? group.groupID : group }, undefined, undefined, true, Zcl.FrameType.SPECIFIC);
760
+ if (group instanceof group_1.default) {
761
+ group.removeMember(this);
762
+ }
763
+ }
764
+ async removeFromAllGroups() {
765
+ await this.zclCommand("genGroups", "removeAll", {}, { disableDefaultResponse: true }, undefined, false, Zcl.FrameType.SPECIFIC);
766
+ this.removeFromAllGroupsDatabase();
767
+ }
768
+ removeFromAllGroupsDatabase() {
769
+ for (const group of group_1.default.allByDatabaseID(this.databaseID)) {
770
+ if (group.hasMember(this)) {
771
+ group.removeMember(this);
772
+ }
773
+ }
774
+ }
775
+ async zclCommand(clusterKey, commandKey, payload, options, logPayload, checkStatus = false, frameType = Zcl.FrameType.GLOBAL) {
776
+ const device = this.getDevice();
777
+ const cluster = typeof clusterKey === "object" ? clusterKey : this.getCluster(clusterKey, device, options?.manufacturerCode);
778
+ const command = typeof commandKey === "object"
779
+ ? commandKey
780
+ : frameType === Zcl.FrameType.GLOBAL
781
+ ? Zcl.Utils.getGlobalCommand(commandKey)
782
+ : cluster.getCommand(commandKey);
783
+ const hasResponse = frameType === Zcl.FrameType.GLOBAL ? true : command.response !== undefined;
784
+ const optionsWithDefaults = this.getOptionsWithDefaults(options, hasResponse, Zcl.Direction.CLIENT_TO_SERVER, cluster.manufacturerCode);
785
+ const frame = Zcl.Frame.create(frameType, optionsWithDefaults.direction, optionsWithDefaults.disableDefaultResponse, optionsWithDefaults.manufacturerCode, optionsWithDefaults.transactionSequenceNumber ?? zclTransactionSequenceNumber_1.default.next(), command, cluster, payload, device.customClusters, optionsWithDefaults.reservedBits);
786
+ const createLogMessage = () => `ZCL command ${this.deviceIeeeAddress}/${this.ID} ` +
787
+ `${cluster.name}.${command.name}(${JSON.stringify(logPayload ? logPayload : payload)}, ${JSON.stringify(optionsWithDefaults)})`;
788
+ logger_1.logger.debug(createLogMessage, NS);
789
+ try {
790
+ const result = await this.sendRequest(frame, optionsWithDefaults);
791
+ if (result) {
792
+ const resultFrame = Zcl.Frame.fromBuffer(result.clusterID, result.header, result.data, device.customClusters);
793
+ if (checkStatus && !optionsWithDefaults.disableResponse) {
794
+ this.checkStatus(resultFrame.payload);
795
+ }
796
+ return resultFrame;
797
+ }
798
+ }
799
+ catch (error) {
800
+ const err = error;
801
+ err.message = `${createLogMessage()} failed (${err.message})`;
802
+ // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
803
+ logger_1.logger.debug(err.stack, NS);
804
+ throw error;
805
+ }
806
+ }
807
+ async zclCommandBroadcast(endpoint, destination, clusterKey, commandKey, payload, options) {
808
+ const device = this.getDevice();
809
+ const cluster = this.getCluster(clusterKey, device, options?.manufacturerCode);
810
+ const command = cluster.getCommand(commandKey);
811
+ const optionsWithDefaults = this.getOptionsWithDefaults(options, true, Zcl.Direction.CLIENT_TO_SERVER, cluster.manufacturerCode);
812
+ const sourceEndpoint = optionsWithDefaults.srcEndpoint ?? this.ID;
813
+ const frame = Zcl.Frame.create(Zcl.FrameType.SPECIFIC, optionsWithDefaults.direction, true, optionsWithDefaults.manufacturerCode, optionsWithDefaults.transactionSequenceNumber ?? zclTransactionSequenceNumber_1.default.next(), command, cluster, payload, device.customClusters, optionsWithDefaults.reservedBits);
814
+ logger_1.logger.debug(() => `ZCL command broadcast ${this.deviceIeeeAddress}/${sourceEndpoint} to ${destination}/${endpoint} ` +
815
+ `${cluster.name}.${command.name}(${JSON.stringify({ payload, optionsWithDefaults })})`, NS);
816
+ // if endpoint===0xFF ("broadcast endpoint"), deliver to all endpoints supporting cluster, should be avoided whenever possible
817
+ await entity_1.default.getAdapterByID(this.databaseID)?.sendZclFrameToAll(endpoint, frame, sourceEndpoint, destination);
818
+ }
819
+ }
820
+ exports.Endpoint = Endpoint;
821
+ exports.default = Endpoint;
822
+ //# sourceMappingURL=endpoint.js.map