zigbee-herdsman 6.0.1 → 6.0.3

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 (260) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/adapter/ezsp/driver/uart.js +1 -1
  3. package/dist/adapter/ezsp/driver/uart.js.map +1 -1
  4. package/dist/adapter/z-stack/adapter/zStackAdapter.js +4 -4
  5. package/dist/adapter/z-stack/adapter/zStackAdapter.js.map +1 -1
  6. package/dist/adapter/zigate/adapter/zigateAdapter.js +4 -4
  7. package/dist/adapter/zigate/adapter/zigateAdapter.js.map +1 -1
  8. package/dist/controller/model/device.d.ts.map +1 -1
  9. package/dist/controller/model/device.js +1 -0
  10. package/dist/controller/model/device.js.map +1 -1
  11. package/package.json +14 -6
  12. package/.github/ISSUE_TEMPLATE/config.yml +0 -5
  13. package/.github/dependabot.yml +0 -22
  14. package/.github/workflows/ci.yml +0 -64
  15. package/.github/workflows/release-please.yml +0 -18
  16. package/.github/workflows/stale.yml +0 -20
  17. package/.github/workflows/typedoc.yaml +0 -47
  18. package/.release-please-manifest.json +0 -3
  19. package/.vscode/extensions.json +0 -3
  20. package/.vscode/settings.json +0 -11
  21. package/biome.json +0 -98
  22. package/examples/join-and-log.js +0 -24
  23. package/release-please-config.json +0 -9
  24. package/src/adapter/adapter.ts +0 -189
  25. package/src/adapter/adapterDiscovery.ts +0 -666
  26. package/src/adapter/const.ts +0 -12
  27. package/src/adapter/deconz/adapter/deconzAdapter.ts +0 -877
  28. package/src/adapter/deconz/driver/constants.ts +0 -246
  29. package/src/adapter/deconz/driver/driver.ts +0 -1540
  30. package/src/adapter/deconz/driver/frame.ts +0 -11
  31. package/src/adapter/deconz/driver/frameParser.ts +0 -753
  32. package/src/adapter/deconz/driver/parser.ts +0 -45
  33. package/src/adapter/deconz/driver/writer.ts +0 -22
  34. package/src/adapter/deconz/types.d.ts +0 -13
  35. package/src/adapter/ember/adapter/emberAdapter.ts +0 -2265
  36. package/src/adapter/ember/adapter/endpoints.ts +0 -86
  37. package/src/adapter/ember/adapter/oneWaitress.ts +0 -324
  38. package/src/adapter/ember/adapter/tokensManager.ts +0 -782
  39. package/src/adapter/ember/consts.ts +0 -178
  40. package/src/adapter/ember/enums.ts +0 -1746
  41. package/src/adapter/ember/ezsp/buffalo.ts +0 -1392
  42. package/src/adapter/ember/ezsp/consts.ts +0 -148
  43. package/src/adapter/ember/ezsp/enums.ts +0 -1114
  44. package/src/adapter/ember/ezsp/ezsp.ts +0 -9061
  45. package/src/adapter/ember/ezspError.ts +0 -10
  46. package/src/adapter/ember/types.ts +0 -866
  47. package/src/adapter/ember/uart/ash.ts +0 -1960
  48. package/src/adapter/ember/uart/consts.ts +0 -109
  49. package/src/adapter/ember/uart/enums.ts +0 -192
  50. package/src/adapter/ember/uart/parser.ts +0 -48
  51. package/src/adapter/ember/uart/queues.ts +0 -247
  52. package/src/adapter/ember/uart/writer.ts +0 -53
  53. package/src/adapter/ember/utils/initters.ts +0 -58
  54. package/src/adapter/ember/utils/math.ts +0 -73
  55. package/src/adapter/events.ts +0 -21
  56. package/src/adapter/ezsp/adapter/backup.ts +0 -109
  57. package/src/adapter/ezsp/adapter/ezspAdapter.ts +0 -614
  58. package/src/adapter/ezsp/driver/commands.ts +0 -2497
  59. package/src/adapter/ezsp/driver/consts.ts +0 -11
  60. package/src/adapter/ezsp/driver/driver.ts +0 -1002
  61. package/src/adapter/ezsp/driver/ezsp.ts +0 -802
  62. package/src/adapter/ezsp/driver/frame.ts +0 -101
  63. package/src/adapter/ezsp/driver/index.ts +0 -4
  64. package/src/adapter/ezsp/driver/multicast.ts +0 -78
  65. package/src/adapter/ezsp/driver/parser.ts +0 -81
  66. package/src/adapter/ezsp/driver/types/basic.ts +0 -201
  67. package/src/adapter/ezsp/driver/types/index.ts +0 -239
  68. package/src/adapter/ezsp/driver/types/named.ts +0 -2330
  69. package/src/adapter/ezsp/driver/types/struct.ts +0 -844
  70. package/src/adapter/ezsp/driver/uart.ts +0 -460
  71. package/src/adapter/ezsp/driver/utils/crc16ccitt.ts +0 -44
  72. package/src/adapter/ezsp/driver/utils/index.ts +0 -32
  73. package/src/adapter/ezsp/driver/writer.ts +0 -64
  74. package/src/adapter/index.ts +0 -3
  75. package/src/adapter/serialPort.ts +0 -58
  76. package/src/adapter/socketPortUtils.ts +0 -16
  77. package/src/adapter/tstype.ts +0 -78
  78. package/src/adapter/z-stack/adapter/adapter-backup.ts +0 -519
  79. package/src/adapter/z-stack/adapter/adapter-nv-memory.ts +0 -457
  80. package/src/adapter/z-stack/adapter/endpoints.ts +0 -57
  81. package/src/adapter/z-stack/adapter/manager.ts +0 -543
  82. package/src/adapter/z-stack/adapter/tstype.ts +0 -6
  83. package/src/adapter/z-stack/adapter/zStackAdapter.ts +0 -1190
  84. package/src/adapter/z-stack/constants/af.ts +0 -27
  85. package/src/adapter/z-stack/constants/common.ts +0 -285
  86. package/src/adapter/z-stack/constants/dbg.ts +0 -23
  87. package/src/adapter/z-stack/constants/index.ts +0 -11
  88. package/src/adapter/z-stack/constants/mac.ts +0 -128
  89. package/src/adapter/z-stack/constants/sapi.ts +0 -25
  90. package/src/adapter/z-stack/constants/sys.ts +0 -72
  91. package/src/adapter/z-stack/constants/util.ts +0 -82
  92. package/src/adapter/z-stack/constants/utils.ts +0 -14
  93. package/src/adapter/z-stack/constants/zdo.ts +0 -103
  94. package/src/adapter/z-stack/models/startup-options.ts +0 -13
  95. package/src/adapter/z-stack/structs/entries/address-manager-entry.ts +0 -44
  96. package/src/adapter/z-stack/structs/entries/address-manager-table.ts +0 -19
  97. package/src/adapter/z-stack/structs/entries/aps-link-key-data-entry.ts +0 -12
  98. package/src/adapter/z-stack/structs/entries/aps-link-key-data-table.ts +0 -21
  99. package/src/adapter/z-stack/structs/entries/aps-tc-link-key-entry.ts +0 -19
  100. package/src/adapter/z-stack/structs/entries/aps-tc-link-key-table.ts +0 -21
  101. package/src/adapter/z-stack/structs/entries/channel-list.ts +0 -8
  102. package/src/adapter/z-stack/structs/entries/has-configured.ts +0 -16
  103. package/src/adapter/z-stack/structs/entries/index.ts +0 -16
  104. package/src/adapter/z-stack/structs/entries/nib.ts +0 -66
  105. package/src/adapter/z-stack/structs/entries/nwk-key-descriptor.ts +0 -15
  106. package/src/adapter/z-stack/structs/entries/nwk-key.ts +0 -13
  107. package/src/adapter/z-stack/structs/entries/nwk-pan-id.ts +0 -8
  108. package/src/adapter/z-stack/structs/entries/nwk-sec-material-descriptor-entry.ts +0 -20
  109. package/src/adapter/z-stack/structs/entries/nwk-sec-material-descriptor-table.ts +0 -19
  110. package/src/adapter/z-stack/structs/entries/security-manager-entry.ts +0 -33
  111. package/src/adapter/z-stack/structs/entries/security-manager-table.ts +0 -22
  112. package/src/adapter/z-stack/structs/index.ts +0 -4
  113. package/src/adapter/z-stack/structs/serializable-memory-object.ts +0 -14
  114. package/src/adapter/z-stack/structs/struct.ts +0 -367
  115. package/src/adapter/z-stack/structs/table.ts +0 -198
  116. package/src/adapter/z-stack/unpi/constants.ts +0 -33
  117. package/src/adapter/z-stack/unpi/frame.ts +0 -62
  118. package/src/adapter/z-stack/unpi/index.ts +0 -4
  119. package/src/adapter/z-stack/unpi/parser.ts +0 -56
  120. package/src/adapter/z-stack/unpi/writer.ts +0 -21
  121. package/src/adapter/z-stack/utils/channel-list.ts +0 -40
  122. package/src/adapter/z-stack/utils/index.ts +0 -2
  123. package/src/adapter/z-stack/utils/network-options.ts +0 -26
  124. package/src/adapter/z-stack/znp/buffaloZnp.ts +0 -175
  125. package/src/adapter/z-stack/znp/definition.ts +0 -2713
  126. package/src/adapter/z-stack/znp/index.ts +0 -2
  127. package/src/adapter/z-stack/znp/parameterType.ts +0 -22
  128. package/src/adapter/z-stack/znp/tstype.ts +0 -44
  129. package/src/adapter/z-stack/znp/utils.ts +0 -10
  130. package/src/adapter/z-stack/znp/znp.ts +0 -342
  131. package/src/adapter/z-stack/znp/zpiObject.ts +0 -148
  132. package/src/adapter/zboss/adapter/zbossAdapter.ts +0 -526
  133. package/src/adapter/zboss/commands.ts +0 -1184
  134. package/src/adapter/zboss/consts.ts +0 -9
  135. package/src/adapter/zboss/driver.ts +0 -422
  136. package/src/adapter/zboss/enums.ts +0 -360
  137. package/src/adapter/zboss/frame.ts +0 -227
  138. package/src/adapter/zboss/reader.ts +0 -65
  139. package/src/adapter/zboss/types.ts +0 -0
  140. package/src/adapter/zboss/uart.ts +0 -428
  141. package/src/adapter/zboss/utils.ts +0 -58
  142. package/src/adapter/zboss/writer.ts +0 -49
  143. package/src/adapter/zigate/adapter/patchZdoBuffaloBE.ts +0 -27
  144. package/src/adapter/zigate/adapter/zigateAdapter.ts +0 -618
  145. package/src/adapter/zigate/driver/LICENSE +0 -17
  146. package/src/adapter/zigate/driver/buffaloZiGate.ts +0 -212
  147. package/src/adapter/zigate/driver/commandType.ts +0 -418
  148. package/src/adapter/zigate/driver/constants.ts +0 -150
  149. package/src/adapter/zigate/driver/frame.ts +0 -197
  150. package/src/adapter/zigate/driver/messageType.ts +0 -287
  151. package/src/adapter/zigate/driver/parameterType.ts +0 -32
  152. package/src/adapter/zigate/driver/ziGateObject.ts +0 -146
  153. package/src/adapter/zigate/driver/zigate.ts +0 -423
  154. package/src/adapter/zoh/adapter/utils.ts +0 -27
  155. package/src/adapter/zoh/adapter/zohAdapter.ts +0 -838
  156. package/src/buffalo/buffalo.ts +0 -342
  157. package/src/buffalo/index.ts +0 -1
  158. package/src/controller/controller.ts +0 -1022
  159. package/src/controller/database.ts +0 -124
  160. package/src/controller/events.ts +0 -52
  161. package/src/controller/greenPower.ts +0 -603
  162. package/src/controller/helpers/index.ts +0 -1
  163. package/src/controller/helpers/installCodes.ts +0 -107
  164. package/src/controller/helpers/request.ts +0 -96
  165. package/src/controller/helpers/requestQueue.ts +0 -125
  166. package/src/controller/helpers/zclFrameConverter.ts +0 -47
  167. package/src/controller/helpers/zclTransactionSequenceNumber.ts +0 -19
  168. package/src/controller/index.ts +0 -6
  169. package/src/controller/model/device.ts +0 -1248
  170. package/src/controller/model/endpoint.ts +0 -1105
  171. package/src/controller/model/entity.ts +0 -23
  172. package/src/controller/model/group.ts +0 -424
  173. package/src/controller/model/index.ts +0 -5
  174. package/src/controller/model/zigbeeEntity.ts +0 -30
  175. package/src/controller/touchlink.ts +0 -189
  176. package/src/controller/tstype.ts +0 -274
  177. package/src/index.ts +0 -12
  178. package/src/models/backup-storage-legacy.ts +0 -48
  179. package/src/models/backup-storage-unified.ts +0 -47
  180. package/src/models/backup.ts +0 -37
  181. package/src/models/index.ts +0 -5
  182. package/src/models/network-options.ts +0 -11
  183. package/src/utils/backup.ts +0 -152
  184. package/src/utils/index.ts +0 -5
  185. package/src/utils/logger.ts +0 -20
  186. package/src/utils/patchBigIntSerialization.ts +0 -8
  187. package/src/utils/queue.ts +0 -76
  188. package/src/utils/types.d.ts +0 -3
  189. package/src/utils/utils.ts +0 -19
  190. package/src/utils/wait.ts +0 -5
  191. package/src/utils/waitress.ts +0 -96
  192. package/src/zspec/consts.ts +0 -84
  193. package/src/zspec/enums.ts +0 -22
  194. package/src/zspec/index.ts +0 -3
  195. package/src/zspec/tstypes.ts +0 -18
  196. package/src/zspec/utils.ts +0 -247
  197. package/src/zspec/zcl/buffaloZcl.ts +0 -1220
  198. package/src/zspec/zcl/definition/cluster.ts +0 -5915
  199. package/src/zspec/zcl/definition/clusters-typegen.ts +0 -588
  200. package/src/zspec/zcl/definition/clusters-types.ts +0 -7331
  201. package/src/zspec/zcl/definition/consts.ts +0 -24
  202. package/src/zspec/zcl/definition/enums.ts +0 -203
  203. package/src/zspec/zcl/definition/foundation.ts +0 -329
  204. package/src/zspec/zcl/definition/manufacturerCode.ts +0 -729
  205. package/src/zspec/zcl/definition/status.ts +0 -69
  206. package/src/zspec/zcl/definition/tstype.ts +0 -377
  207. package/src/zspec/zcl/index.ts +0 -11
  208. package/src/zspec/zcl/utils.ts +0 -321
  209. package/src/zspec/zcl/zclFrame.ts +0 -356
  210. package/src/zspec/zcl/zclHeader.ts +0 -102
  211. package/src/zspec/zcl/zclStatusError.ts +0 -10
  212. package/src/zspec/zdo/buffaloZdo.ts +0 -2336
  213. package/src/zspec/zdo/definition/clusters.ts +0 -722
  214. package/src/zspec/zdo/definition/consts.ts +0 -16
  215. package/src/zspec/zdo/definition/enums.ts +0 -99
  216. package/src/zspec/zdo/definition/status.ts +0 -105
  217. package/src/zspec/zdo/definition/tstypes.ts +0 -1062
  218. package/src/zspec/zdo/index.ts +0 -7
  219. package/src/zspec/zdo/utils.ts +0 -76
  220. package/src/zspec/zdo/zdoStatusError.ts +0 -10
  221. package/test/adapter/adapter.test.ts +0 -1062
  222. package/test/adapter/ember/ash.test.ts +0 -337
  223. package/test/adapter/ember/consts.ts +0 -131
  224. package/test/adapter/ember/emberAdapter.test.ts +0 -3449
  225. package/test/adapter/ember/ezsp.test.ts +0 -385
  226. package/test/adapter/ember/ezspBuffalo.test.ts +0 -93
  227. package/test/adapter/ember/ezspError.test.ts +0 -12
  228. package/test/adapter/ember/math.test.ts +0 -206
  229. package/test/adapter/ezsp/frame.test.ts +0 -30
  230. package/test/adapter/ezsp/uart.test.ts +0 -181
  231. package/test/adapter/z-stack/adapter.test.ts +0 -3984
  232. package/test/adapter/z-stack/constants.test.ts +0 -33
  233. package/test/adapter/z-stack/structs.test.ts +0 -115
  234. package/test/adapter/z-stack/unpi.test.ts +0 -213
  235. package/test/adapter/z-stack/znp.test.ts +0 -1284
  236. package/test/adapter/zboss/fixZdoResponse.test.ts +0 -179
  237. package/test/adapter/zigate/patchZdoBuffaloBE.test.ts +0 -81
  238. package/test/adapter/zigate/zdo.test.ts +0 -187
  239. package/test/adapter/zoh/utils.test.ts +0 -36
  240. package/test/adapter/zoh/zohAdapter.test.ts +0 -1307
  241. package/test/buffalo.test.ts +0 -431
  242. package/test/controller.bench.ts +0 -193
  243. package/test/controller.test.ts +0 -8702
  244. package/test/greenpower.test.ts +0 -1408
  245. package/test/mockAdapters.ts +0 -65
  246. package/test/mockDevices.ts +0 -598
  247. package/test/requests.bench.ts +0 -206
  248. package/test/testUtils.ts +0 -20
  249. package/test/tsconfig.json +0 -9
  250. package/test/utils/math.ts +0 -19
  251. package/test/utils.test.ts +0 -279
  252. package/test/vitest.config.mts +0 -27
  253. package/test/zcl.test.ts +0 -2831
  254. package/test/zspec/utils.test.ts +0 -68
  255. package/test/zspec/zcl/buffalo.test.ts +0 -1374
  256. package/test/zspec/zcl/frame.test.ts +0 -960
  257. package/test/zspec/zcl/utils.test.ts +0 -273
  258. package/test/zspec/zdo/buffalo.test.ts +0 -1850
  259. package/test/zspec/zdo/utils.test.ts +0 -241
  260. package/tsconfig.json +0 -24
@@ -1,1022 +0,0 @@
1
- import assert from "node:assert";
2
- import events from "node:events";
3
- import fs from "node:fs";
4
- import mixinDeep from "mixin-deep";
5
- import {Adapter, type Events as AdapterEvents, type TsType as AdapterTsType} from "../adapter";
6
- import {BackupUtils, wait} from "../utils";
7
- import {logger} from "../utils/logger";
8
- import {isNumberArrayOfLength} from "../utils/utils";
9
- import * as ZSpec from "../zspec";
10
- import type {Eui64} from "../zspec/tstypes";
11
- import * as Zcl from "../zspec/zcl";
12
- import type {TPartialClusterAttributes} from "../zspec/zcl/definition/clusters-types";
13
- import type {FrameControl} from "../zspec/zcl/definition/tstype";
14
- import * as Zdo from "../zspec/zdo";
15
- import type * as ZdoTypes from "../zspec/zdo/definition/tstypes";
16
- import Database from "./database";
17
- import type * as Events from "./events";
18
- import GreenPower from "./greenPower";
19
- import {ZclFrameConverter} from "./helpers";
20
- import {checkInstallCode, parseInstallCode} from "./helpers/installCodes";
21
- import {Device, Entity} from "./model";
22
- import {InterviewState} from "./model/device";
23
- import Group from "./model/group";
24
- import Touchlink from "./touchlink";
25
- import type {DeviceType, GreenPowerDeviceJoinedPayload} from "./tstype";
26
-
27
- const NS = "zh:controller";
28
-
29
- interface Options {
30
- network: AdapterTsType.NetworkOptions;
31
- serialPort: AdapterTsType.SerialPortOptions;
32
- databasePath: string;
33
- databaseBackupPath: string;
34
- backupPath: string;
35
- adapter: AdapterTsType.AdapterOptions;
36
- /**
37
- * This lambda can be used by an application to explictly reject or accept an incoming device.
38
- * When false is returned zigbee-herdsman will not start the interview process and immidiately
39
- * try to remove the device from the network.
40
- */
41
- acceptJoiningDeviceHandler: (ieeeAddr: string) => Promise<boolean>;
42
- }
43
-
44
- const DefaultOptions: Pick<Options, "network" | "serialPort" | "adapter"> = {
45
- network: {
46
- networkKeyDistribute: false,
47
- networkKey: [0x01, 0x03, 0x05, 0x07, 0x09, 0x0b, 0x0d, 0x0f, 0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0d],
48
- panID: 0x1a62,
49
- extendedPanID: [0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd],
50
- channelList: [11],
51
- },
52
- serialPort: {},
53
- adapter: {disableLED: false},
54
- };
55
-
56
- export interface ControllerEventMap {
57
- message: [data: Events.MessagePayload];
58
- adapterDisconnected: [];
59
- deviceJoined: [data: Events.DeviceJoinedPayload];
60
- deviceInterview: [data: Events.DeviceInterviewPayload];
61
- deviceAnnounce: [data: Events.DeviceAnnouncePayload];
62
- deviceNetworkAddressChanged: [data: Events.DeviceNetworkAddressChangedPayload];
63
- deviceLeave: [data: Events.DeviceLeavePayload];
64
- permitJoinChanged: [data: Events.PermitJoinChangedPayload];
65
- lastSeenChanged: [data: Events.LastSeenChangedPayload];
66
- }
67
-
68
- /**
69
- * @noInheritDoc
70
- */
71
- export class Controller extends events.EventEmitter<ControllerEventMap> {
72
- private options: Options;
73
- private database!: Database;
74
- private adapter!: Adapter;
75
- private greenPower!: GreenPower;
76
- private touchlink!: Touchlink;
77
-
78
- private permitJoinTimer: NodeJS.Timeout | undefined;
79
- private permitJoinEnd?: number;
80
- private backupTimer: NodeJS.Timeout | undefined;
81
- private databaseSaveTimer: NodeJS.Timeout | undefined;
82
- private stopping: boolean;
83
- private adapterDisconnected: boolean;
84
- private networkParametersCached: AdapterTsType.NetworkParameters | undefined;
85
- /** List of unknown devices detected during a single runtime session. Serves as de-dupe and anti-spam. */
86
- private unknownDevices: Set<number>;
87
-
88
- /**
89
- * Create a controller
90
- *
91
- * To auto detect the port provide `null` for `options.serialPort.path`
92
- */
93
- public constructor(options: Options) {
94
- super();
95
- this.stopping = false;
96
- this.adapterDisconnected = true; // set false after adapter.start() is successfully called
97
- this.options = mixinDeep(JSON.parse(JSON.stringify(DefaultOptions)), options);
98
- this.unknownDevices = new Set();
99
-
100
- // Validate options
101
- for (const channel of this.options.network.channelList) {
102
- if (channel < 11 || channel > 26) {
103
- throw new Error(`'${channel}' is an invalid channel, use a channel between 11 - 26.`);
104
- }
105
- }
106
-
107
- if (!isNumberArrayOfLength(this.options.network.networkKey, 16)) {
108
- throw new Error(`Network key must be a 16 digits long array, got ${this.options.network.networkKey}.`);
109
- }
110
-
111
- if (!isNumberArrayOfLength(this.options.network.extendedPanID, 8)) {
112
- throw new Error(`ExtendedPanID must be an 8 digits long array, got ${this.options.network.extendedPanID}.`);
113
- }
114
-
115
- if (this.options.network.panID < 1 || this.options.network.panID >= 0xffff) {
116
- throw new Error(`PanID must have a value of 0x0001 (1) - 0xFFFE (65534), got ${this.options.network.panID}.`);
117
- }
118
- }
119
-
120
- /**
121
- * Start the Herdsman controller
122
- */
123
- public async start(): Promise<AdapterTsType.StartResult> {
124
- // Database (create end inject)
125
- this.database = Database.open(this.options.databasePath);
126
- Entity.injectDatabase(this.database);
127
-
128
- // Adapter (create and inject)
129
- this.adapter = await Adapter.create(this.options.network, this.options.serialPort, this.options.backupPath, this.options.adapter);
130
-
131
- const stringifiedOptions = JSON.stringify(this.options).replaceAll(JSON.stringify(this.options.network.networkKey), '"HIDDEN"');
132
- logger.debug(`Starting with options '${stringifiedOptions}'`, NS);
133
- const startResult = await this.adapter.start();
134
- logger.debug(`Started with result '${startResult}'`, NS);
135
- this.adapterDisconnected = false;
136
-
137
- // Check if we have to change the channel, only do this when adapter `resumed` because:
138
- // - `getNetworkParameters` might be return wrong info because it needs to propogate after backup restore
139
- // - If result is not `resumed` (`reset` or `restored`), the adapter should comission with the channel from `this.options.network`
140
- if (startResult === "resumed") {
141
- const netParams = await this.getNetworkParameters();
142
- const configuredChannel = this.options.network.channelList[0];
143
- const adapterChannel = netParams.channel;
144
- const nwkUpdateID = netParams.nwkUpdateID;
145
-
146
- if (configuredChannel !== adapterChannel) {
147
- logger.info(`Configured channel '${configuredChannel}' does not match adapter channel '${adapterChannel}', changing channel`, NS);
148
- await this.changeChannel(adapterChannel, configuredChannel, nwkUpdateID);
149
- }
150
- }
151
-
152
- Entity.injectAdapter(this.adapter);
153
-
154
- // log injection
155
- logger.debug(`Injected database: ${this.database !== undefined}, adapter: ${this.adapter !== undefined}`, NS);
156
-
157
- this.greenPower = new GreenPower(this.adapter);
158
- this.greenPower.on("deviceJoined", this.onDeviceJoinedGreenPower.bind(this));
159
- this.greenPower.on("deviceLeave", this.onDeviceLeaveGreenPower.bind(this));
160
-
161
- // Register adapter events
162
- this.adapter.on("deviceJoined", this.onDeviceJoined.bind(this));
163
- this.adapter.on("zclPayload", this.onZclPayload.bind(this));
164
- this.adapter.on("zdoResponse", this.onZdoResponse.bind(this));
165
- this.adapter.on("disconnected", this.onAdapterDisconnected.bind(this));
166
- this.adapter.on("deviceLeave", this.onDeviceLeave.bind(this));
167
-
168
- if (startResult === "reset") {
169
- if (this.options.databaseBackupPath && fs.existsSync(this.options.databasePath)) {
170
- fs.copyFileSync(this.options.databasePath, this.options.databaseBackupPath);
171
- }
172
-
173
- logger.debug("Clearing database...", NS);
174
- for (const group of Group.allIterator()) {
175
- group.removeFromDatabase();
176
- }
177
-
178
- for (const device of Device.allIterator()) {
179
- device.removeFromDatabase();
180
- }
181
- Device.resetCache();
182
- }
183
-
184
- if (startResult === "reset" || (this.options.backupPath && !fs.existsSync(this.options.backupPath))) {
185
- await this.backup();
186
- }
187
-
188
- // Add coordinator to the database if it is not there yet.
189
- const coordinatorIEEE = await this.adapter.getCoordinatorIEEE();
190
-
191
- if (Device.byType("Coordinator").length === 0) {
192
- logger.debug("No coordinator in database, querying...", NS);
193
- const coordinator = Device.create(
194
- "Coordinator",
195
- coordinatorIEEE,
196
- ZSpec.COORDINATOR_ADDRESS,
197
- this.adapter.manufacturerID,
198
- undefined,
199
- undefined,
200
- undefined,
201
- InterviewState.Successful,
202
- undefined,
203
- );
204
-
205
- await coordinator.updateActiveEndpoints();
206
-
207
- for (const endpoint of coordinator.endpoints) {
208
- await endpoint.updateSimpleDescriptor();
209
- }
210
-
211
- coordinator.save();
212
- }
213
-
214
- // Update coordinator ieeeAddr if changed, can happen due to e.g. reflashing
215
- const databaseCoordinator = Device.byType("Coordinator")[0];
216
- if (databaseCoordinator.ieeeAddr !== coordinatorIEEE) {
217
- logger.info(`Coordinator address changed, updating to '${coordinatorIEEE}'`, NS);
218
- databaseCoordinator.changeIeeeAddress(coordinatorIEEE);
219
- }
220
-
221
- // Set backup timer to 1 day.
222
- this.backupTimer = setInterval(() => this.backup(), 86400000);
223
-
224
- // Set database save timer to 1 hour.
225
- this.databaseSaveTimer = setInterval(() => this.databaseSave(), 3600000);
226
-
227
- this.touchlink = new Touchlink(this.adapter);
228
-
229
- return startResult;
230
- }
231
-
232
- public async touchlinkIdentify(ieeeAddr: string, channel: number): Promise<void> {
233
- await this.touchlink.identify(ieeeAddr, channel);
234
- }
235
-
236
- public async touchlinkScan(): Promise<{ieeeAddr: string; channel: number}[]> {
237
- return await this.touchlink.scan();
238
- }
239
-
240
- public async touchlinkFactoryReset(ieeeAddr: string, channel: number): Promise<boolean> {
241
- return await this.touchlink.factoryReset(ieeeAddr, channel);
242
- }
243
-
244
- public async touchlinkFactoryResetFirst(): Promise<boolean> {
245
- return await this.touchlink.factoryResetFirst();
246
- }
247
-
248
- public async addInstallCode(installCode: string): Promise<void> {
249
- // will throw if code cannot be parsed
250
- const [ieeeAddr, keyStr] = parseInstallCode(installCode);
251
- // biome-ignore lint/style/noNonNullAssertion: valid from above parsing
252
- const key = Buffer.from(keyStr.match(/.{1,2}/g)!.map((d) => Number.parseInt(d, 16)));
253
- // will throw if code cannot be fixed and is invalid
254
- const [adjustedKey, adjusted] = checkInstallCode(key, true);
255
-
256
- if (adjusted) {
257
- logger.info(`Install code was adjusted for reason '${adjusted}'.`, NS);
258
- }
259
-
260
- logger.info(`Adding install code for ${ieeeAddr}.`, NS);
261
-
262
- await this.adapter.addInstallCode(ieeeAddr, adjustedKey, false);
263
-
264
- if (adjusted === "missing CRC") {
265
- // in case the CRC was missing, could also be a "already-hashed" key, send both
266
- // XXX: seems to be the case for old HA1.2 devices
267
- await this.adapter.addInstallCode(ieeeAddr, key, true);
268
- }
269
- }
270
-
271
- public async permitJoin(time: number, device?: Device): Promise<void> {
272
- clearTimeout(this.permitJoinTimer);
273
- this.permitJoinTimer = undefined;
274
- this.permitJoinEnd = undefined;
275
-
276
- if (time > 0) {
277
- // never permit more than uint8, and never permit 255 that is often equal to "forever"
278
- assert(time <= 254, "Cannot permit join for more than 254 seconds.");
279
-
280
- await this.adapter.permitJoin(time, device?.networkAddress);
281
- await this.greenPower.permitJoin(time, device?.networkAddress);
282
-
283
- const timeMs = time * 1000;
284
- this.permitJoinEnd = Date.now() + timeMs;
285
- this.permitJoinTimer = setTimeout((): void => {
286
- this.emit("permitJoinChanged", {permitted: false});
287
-
288
- this.permitJoinTimer = undefined;
289
- this.permitJoinEnd = undefined;
290
- }, timeMs);
291
-
292
- this.emit("permitJoinChanged", {permitted: true, time});
293
- } else {
294
- logger.debug("Disable joining", NS);
295
-
296
- await this.greenPower.permitJoin(0);
297
- await this.adapter.permitJoin(0);
298
-
299
- this.emit("permitJoinChanged", {permitted: false});
300
- }
301
- }
302
-
303
- public getPermitJoin(): boolean {
304
- return this.permitJoinTimer !== undefined;
305
- }
306
-
307
- public getPermitJoinEnd(): number | undefined {
308
- return this.permitJoinEnd;
309
- }
310
-
311
- public isStopping(): boolean {
312
- return this.stopping;
313
- }
314
-
315
- public isAdapterDisconnected(): boolean {
316
- return this.adapterDisconnected;
317
- }
318
-
319
- public async stop(): Promise<void> {
320
- this.stopping = true;
321
-
322
- // Unregister adapter events
323
- this.adapter.removeAllListeners();
324
-
325
- clearInterval(this.backupTimer);
326
- clearInterval(this.databaseSaveTimer);
327
-
328
- if (this.adapterDisconnected) {
329
- this.databaseSave();
330
- } else {
331
- try {
332
- await this.permitJoin(0);
333
- } catch (error) {
334
- logger.error(`Failed to disable join on stop: ${error}`, NS);
335
- }
336
-
337
- await this.backup(); // always calls databaseSave()
338
- await this.adapter.stop();
339
-
340
- this.adapterDisconnected = true;
341
- }
342
-
343
- Device.resetCache();
344
- Group.resetCache();
345
- }
346
-
347
- private databaseSave(): void {
348
- for (const device of Device.allIterator()) {
349
- device.save(false);
350
- }
351
-
352
- for (const group of Group.allIterator()) {
353
- group.save(false);
354
- }
355
-
356
- this.database.write();
357
- }
358
-
359
- public async backup(): Promise<void> {
360
- this.databaseSave();
361
- if (this.options.backupPath && (await this.adapter.supportsBackup())) {
362
- logger.debug("Creating coordinator backup", NS);
363
- const backup = await this.adapter.backup(this.getDeviceIeeeAddresses());
364
- const unifiedBackup = BackupUtils.toUnifiedBackup(backup);
365
- const tmpBackupPath = `${this.options.backupPath}.tmp`;
366
- fs.writeFileSync(tmpBackupPath, JSON.stringify(unifiedBackup, null, 2));
367
- fs.renameSync(tmpBackupPath, this.options.backupPath);
368
- logger.info(`Wrote coordinator backup to '${this.options.backupPath}'`, NS);
369
- }
370
- }
371
-
372
- public async coordinatorCheck(): Promise<{missingRouters: Device[]}> {
373
- if (await this.adapter.supportsBackup()) {
374
- const backup = await this.adapter.backup(this.getDeviceIeeeAddresses());
375
- const devicesInBackup = backup.devices.map((d) => ZSpec.Utils.eui64BEBufferToHex(d.ieeeAddress));
376
- const missingRouters = [];
377
-
378
- for (const device of this.getDevicesIterator((d) => d.type === "Router" && !devicesInBackup.includes(d.ieeeAddr as Eui64))) {
379
- missingRouters.push(device);
380
- }
381
-
382
- return {missingRouters};
383
- }
384
-
385
- throw new Error("Coordinator does not coordinator check because it doesn't support backups");
386
- }
387
-
388
- public async reset(type: "soft" | "hard"): Promise<void> {
389
- await this.adapter.reset(type);
390
- }
391
-
392
- public async getCoordinatorVersion(): Promise<AdapterTsType.CoordinatorVersion> {
393
- return await this.adapter.getCoordinatorVersion();
394
- }
395
-
396
- public async getNetworkParameters(): Promise<AdapterTsType.NetworkParameters> {
397
- // Cache network parameters as they don't change anymore after start.
398
- if (!this.networkParametersCached) {
399
- this.networkParametersCached = await this.adapter.getNetworkParameters();
400
- }
401
-
402
- return this.networkParametersCached;
403
- }
404
-
405
- /**
406
- * Get all devices
407
- * @deprecated use getDevicesIterator()
408
- */
409
- public getDevices(): Device[] {
410
- return Device.all();
411
- }
412
-
413
- /**
414
- * Get iterator for all devices
415
- */
416
- public getDevicesIterator(predicate?: (value: Device) => boolean): Generator<Device> {
417
- return Device.allIterator(predicate);
418
- }
419
-
420
- /**
421
- * Get all devices with a specific type
422
- */
423
- public getDevicesByType(type: DeviceType): Device[] {
424
- return Device.byType(type);
425
- }
426
-
427
- /**
428
- * Get device by ieeeAddr
429
- */
430
- public getDeviceByIeeeAddr(ieeeAddr: string): Device | undefined {
431
- return Device.byIeeeAddr(ieeeAddr);
432
- }
433
-
434
- /**
435
- * Get device by networkAddress
436
- */
437
- public getDeviceByNetworkAddress(networkAddress: number): Device | undefined {
438
- return Device.byNetworkAddress(networkAddress);
439
- }
440
-
441
- /**
442
- * Get IEEE address for all devices
443
- */
444
- public getDeviceIeeeAddresses(): string[] {
445
- const deviceIeeeAddresses = [];
446
-
447
- for (const device of Device.allIterator()) {
448
- deviceIeeeAddresses.push(device.ieeeAddr);
449
- }
450
-
451
- return deviceIeeeAddresses;
452
- }
453
-
454
- /**
455
- * Get group by ID
456
- */
457
- public getGroupByID(groupID: number): Group | undefined {
458
- return Group.byGroupID(groupID);
459
- }
460
-
461
- /**
462
- * Get all groups
463
- * @deprecated use getGroupsIterator()
464
- */
465
- public getGroups(): Group[] {
466
- return Group.all();
467
- }
468
-
469
- /**
470
- * Get iterator for all groups
471
- */
472
- public getGroupsIterator(predicate?: (value: Group) => boolean): Generator<Group> {
473
- return Group.allIterator(predicate);
474
- }
475
-
476
- /**
477
- * Create a Group
478
- */
479
- public createGroup(groupID: number): Group {
480
- return Group.create(groupID);
481
- }
482
-
483
- /**
484
- * Broadcast a network-wide channel change.
485
- */
486
- private async changeChannel(oldChannel: number, newChannel: number, nwkUpdateID: number): Promise<void> {
487
- logger.warning(`Changing channel from '${oldChannel}' to '${newChannel}'`, NS);
488
-
489
- // According to the Zigbee specification:
490
- // When broadcasting a Mgmt_NWK_Update_req to notify devices of a new channel, the nwkUpdateId parameter should be incremented in the NIB and included in the Mgmt_NWK_Update_req.
491
- // The valid range of nwkUpdateId is 0x00 to 0xFF, and it should wrap back to 0 if necessary.
492
- if (++nwkUpdateID > 0xff) {
493
- nwkUpdateID = 0x00;
494
- }
495
-
496
- const clusterId = Zdo.ClusterId.NWK_UPDATE_REQUEST;
497
- const zdoPayload = Zdo.Buffalo.buildRequest(
498
- this.adapter.hasZdoMessageOverhead,
499
- clusterId,
500
- [newChannel],
501
- 0xfe,
502
- undefined,
503
- nwkUpdateID,
504
- undefined,
505
- );
506
-
507
- await this.adapter.sendZdo(ZSpec.BLANK_EUI64, ZSpec.BroadcastAddress.SLEEPY, clusterId, zdoPayload, true);
508
- logger.info(`Channel changed to '${newChannel}'`, NS);
509
-
510
- this.networkParametersCached = undefined; // invalidate cache
511
- // wait for the broadcast to propagate and the adapter to actually change
512
- // NOTE: observed to ~9sec on `ember` with actual stack event
513
- await wait(12000);
514
- }
515
-
516
- public async identifyUnknownDevice(nwkAddress: number): Promise<Device | undefined> {
517
- if (this.unknownDevices.has(nwkAddress)) {
518
- // prevent duplicate triggering
519
- return;
520
- }
521
-
522
- logger.debug(`Trying to identify unknown device with address '${nwkAddress}'`, NS);
523
- this.unknownDevices.add(nwkAddress);
524
- const clusterId = Zdo.ClusterId.IEEE_ADDRESS_REQUEST;
525
- const zdoPayload = Zdo.Buffalo.buildRequest(this.adapter.hasZdoMessageOverhead, clusterId, nwkAddress, false, 0);
526
-
527
- try {
528
- const response = await this.adapter.sendZdo(ZSpec.BLANK_EUI64, nwkAddress, clusterId, zdoPayload, false);
529
-
530
- if (Zdo.Buffalo.checkStatus<Zdo.ClusterId.IEEE_ADDRESS_RESPONSE>(response)) {
531
- const payload = response[1];
532
- const device = Device.byIeeeAddr(payload.eui64);
533
-
534
- if (device) {
535
- this.checkDeviceNetworkAddress(device, payload.eui64, payload.nwkAddress);
536
- this.unknownDevices.delete(payload.nwkAddress);
537
- }
538
-
539
- return device;
540
- }
541
-
542
- throw new Zdo.StatusError(response[0]);
543
- } catch (error) {
544
- // Catches 2 types of exception: Zdo.StatusError and no response from `adapter.sendZdo()`.
545
- logger.debug(`Failed to retrieve IEEE address for device '${nwkAddress}': ${error}`, NS);
546
- }
547
-
548
- // NOTE: by keeping nwkAddress in `this.unknownDevices` on fail, it prevents a non-responding device from potentially spamming identify.
549
- // This only lasts until next reboot (runtime Set), allowing to 'force' another trigger if necessary.
550
- }
551
-
552
- private checkDeviceNetworkAddress(device: Device, ieeeAddress: string, nwkAddress: number): void {
553
- if (device.networkAddress !== nwkAddress) {
554
- logger.debug(`Device '${ieeeAddress}' got new networkAddress '${nwkAddress}'`, NS);
555
- device.networkAddress = nwkAddress;
556
- device.save();
557
-
558
- this.selfAndDeviceEmit(device, "deviceNetworkAddressChanged", {device});
559
- }
560
- }
561
-
562
- private onNetworkAddress(payload: ZdoTypes.NetworkAddressResponse): void {
563
- logger.debug(`Network address from '${payload.eui64}:${payload.nwkAddress}'`, NS);
564
- const device = Device.byIeeeAddr(payload.eui64);
565
-
566
- if (!device) {
567
- logger.debug(`Network address is from unknown device '${payload.eui64}:${payload.nwkAddress}'`, NS);
568
- return;
569
- }
570
-
571
- device.updateLastSeen();
572
- this.selfAndDeviceEmit(device, "lastSeenChanged", {device, reason: "networkAddress"});
573
- this.checkDeviceNetworkAddress(device, payload.eui64, payload.nwkAddress);
574
- }
575
-
576
- private onIEEEAddress(payload: ZdoTypes.IEEEAddressResponse): void {
577
- logger.debug(`IEEE address from '${payload.eui64}:${payload.nwkAddress}'`, NS);
578
- const device = Device.byIeeeAddr(payload.eui64);
579
-
580
- if (!device) {
581
- logger.debug(`IEEE address is from unknown device '${payload.eui64}:${payload.nwkAddress}'`, NS);
582
- return;
583
- }
584
-
585
- device.updateLastSeen();
586
- this.selfAndDeviceEmit(device, "lastSeenChanged", {device, reason: "networkAddress"});
587
- this.checkDeviceNetworkAddress(device, payload.eui64, payload.nwkAddress);
588
- }
589
-
590
- private onDeviceAnnounce(payload: ZdoTypes.EndDeviceAnnounce): void {
591
- logger.debug(`Device announce from '${payload.eui64}:${payload.nwkAddress}'`, NS);
592
- const device = Device.byIeeeAddr(payload.eui64);
593
-
594
- if (!device) {
595
- logger.debug(`Device announce is from unknown device '${payload.eui64}:${payload.nwkAddress}'`, NS);
596
- return;
597
- }
598
-
599
- device.updateLastSeen();
600
- this.selfAndDeviceEmit(device, "lastSeenChanged", {device, reason: "deviceAnnounce"});
601
- device.implicitCheckin();
602
- this.checkDeviceNetworkAddress(device, payload.eui64, payload.nwkAddress);
603
- this.selfAndDeviceEmit(device, "deviceAnnounce", {device});
604
- }
605
-
606
- private onDeviceLeave(payload: AdapterEvents.DeviceLeavePayload): void {
607
- logger.debug(`Device leave '${payload.ieeeAddr}'`, NS);
608
-
609
- // XXX: seems type is not properly detected?
610
- // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
611
- const device = payload.ieeeAddr ? Device.byIeeeAddr(payload.ieeeAddr) : Device.byNetworkAddress(payload.networkAddress!);
612
-
613
- if (!device) {
614
- logger.debug(`Device leave is from unknown or already deleted device '${payload.ieeeAddr ?? payload.networkAddress}'`, NS);
615
- return;
616
- }
617
-
618
- logger.debug(`Removing device from database '${device.ieeeAddr}'`, NS);
619
- device.removeFromDatabase();
620
-
621
- this.selfAndDeviceEmit(device, "deviceLeave", {ieeeAddr: device.ieeeAddr});
622
- }
623
-
624
- private async onAdapterDisconnected(): Promise<void> {
625
- logger.debug("Adapter disconnected", NS);
626
-
627
- this.adapterDisconnected = true;
628
-
629
- try {
630
- await this.adapter.stop();
631
- } catch (error) {
632
- logger.error(`Failed to stop adapter on disconnect: ${error}`, NS);
633
- }
634
-
635
- this.emit("adapterDisconnected");
636
- }
637
-
638
- private onDeviceJoinedGreenPower(payload: GreenPowerDeviceJoinedPayload): void {
639
- logger.debug(() => `Green power device '${JSON.stringify(payload).replaceAll(/\[[\d,]+\]/g, "HIDDEN")}' joined`, NS);
640
-
641
- // Green power devices don't have an ieeeAddr, the sourceID is unique and static so use this.
642
- const ieeeAddr = GreenPower.sourceIdToIeeeAddress(payload.sourceID);
643
- // Green power devices dont' have a modelID, create a modelID based on the deviceID (=type)
644
- const modelID = `GreenPower_${payload.deviceID}`;
645
- let device = Device.byIeeeAddr(ieeeAddr, true);
646
-
647
- if (!device) {
648
- logger.debug(`New green power device '${ieeeAddr}' joined`, NS);
649
- logger.debug(`Creating device '${ieeeAddr}'`, NS);
650
- device = Device.create(
651
- "GreenPower",
652
- ieeeAddr,
653
- payload.networkAddress,
654
- undefined,
655
- undefined,
656
- undefined,
657
- modelID,
658
- InterviewState.Successful,
659
- payload.securityKey ? Array.from(payload.securityKey) : /* v8 ignore next */ undefined,
660
- );
661
-
662
- device.save();
663
-
664
- this.selfAndDeviceEmit(device, "deviceJoined", {device});
665
- this.selfAndDeviceEmit(device, "deviceInterview", {status: "successful", device});
666
- } else if (device.isDeleted) {
667
- logger.debug(`Deleted green power device '${ieeeAddr}' joined, undeleting`, NS);
668
-
669
- device.undelete();
670
-
671
- this.selfAndDeviceEmit(device, "deviceJoined", {device});
672
- this.selfAndDeviceEmit(device, "deviceInterview", {status: "successful", device});
673
- }
674
- }
675
-
676
- private onDeviceLeaveGreenPower(sourceID: number): void {
677
- logger.debug(`Green power device '${sourceID}' left`, NS);
678
-
679
- // Green power devices don't have an ieeeAddr, the sourceID is unique and static so use this.
680
- const ieeeAddr = GreenPower.sourceIdToIeeeAddress(sourceID);
681
- const device = Device.byIeeeAddr(ieeeAddr);
682
-
683
- if (!device) {
684
- logger.debug(`Green power device leave is from unknown or already deleted device '${ieeeAddr}'`, NS);
685
- return;
686
- }
687
-
688
- logger.debug(`Removing green power device from database '${device.ieeeAddr}'`, NS);
689
- device.removeFromDatabase();
690
-
691
- this.selfAndDeviceEmit(device, "deviceLeave", {ieeeAddr: device.ieeeAddr});
692
- }
693
-
694
- private selfAndDeviceEmit<K extends keyof ControllerEventMap>(
695
- device: Device,
696
- event: K,
697
- ...args: K extends keyof ControllerEventMap ? ControllerEventMap[K] : never
698
- ): void {
699
- device.emit(event, ...args);
700
- this.emit(event, ...args);
701
- }
702
-
703
- private async onDeviceJoined(payload: AdapterEvents.DeviceJoinedPayload): Promise<void> {
704
- logger.debug(`Device '${payload.ieeeAddr}' joined`, NS);
705
-
706
- if (this.options.acceptJoiningDeviceHandler) {
707
- if (!(await this.options.acceptJoiningDeviceHandler(payload.ieeeAddr))) {
708
- logger.debug(`Device '${payload.ieeeAddr}' rejected by handler, removing it`, NS);
709
-
710
- // XXX: GP devices? see Device.removeFromNetwork
711
- try {
712
- const clusterId = Zdo.ClusterId.LEAVE_REQUEST;
713
- const zdoPayload = Zdo.Buffalo.buildRequest(
714
- this.adapter.hasZdoMessageOverhead,
715
- clusterId,
716
- payload.ieeeAddr as Eui64,
717
- Zdo.LeaveRequestFlags.WITHOUT_REJOIN,
718
- );
719
- const response = await this.adapter.sendZdo(payload.ieeeAddr, payload.networkAddress, clusterId, zdoPayload, false);
720
-
721
- if (!Zdo.Buffalo.checkStatus<Zdo.ClusterId.LEAVE_RESPONSE>(response)) {
722
- throw new Zdo.StatusError(response[0]);
723
- }
724
- } catch (error) {
725
- logger.error(`Failed to remove rejected device: ${(error as Error).message}`, NS);
726
- }
727
-
728
- return;
729
- }
730
-
731
- logger.debug(`Device '${payload.ieeeAddr}' accepted by handler`, NS);
732
- }
733
-
734
- let device = Device.byIeeeAddr(payload.ieeeAddr, true);
735
- if (!device) {
736
- logger.debug(`New device '${payload.ieeeAddr}' joined`, NS);
737
- logger.debug(`Creating device '${payload.ieeeAddr}'`, NS);
738
- device = Device.create(
739
- "Unknown",
740
- payload.ieeeAddr,
741
- payload.networkAddress,
742
- undefined,
743
- undefined,
744
- undefined,
745
- undefined,
746
- InterviewState.Pending,
747
- undefined,
748
- );
749
- this.selfAndDeviceEmit(device, "deviceJoined", {device});
750
- } else if (device.isDeleted) {
751
- logger.debug(`Deleted device '${payload.ieeeAddr}' joined, undeleting`, NS);
752
- device.undelete();
753
- this.selfAndDeviceEmit(device, "deviceJoined", {device});
754
- }
755
-
756
- if (device.networkAddress !== payload.networkAddress) {
757
- logger.debug(`Device '${payload.ieeeAddr}' is already in database with different network address, updating network address`, NS);
758
- device.networkAddress = payload.networkAddress;
759
- device.save();
760
- }
761
-
762
- device.updateLastSeen();
763
- this.selfAndDeviceEmit(device, "lastSeenChanged", {device, reason: "deviceJoined"});
764
- device.implicitCheckin();
765
-
766
- if (device.interviewState === InterviewState.Pending || device.interviewState === InterviewState.Failed) {
767
- logger.info(`Interview for '${device.ieeeAddr}' started`, NS);
768
- this.selfAndDeviceEmit(device, "deviceInterview", {status: "started", device});
769
-
770
- try {
771
- await device.interview();
772
- logger.info(`Succesfully interviewed '${device.ieeeAddr}'`, NS);
773
- this.selfAndDeviceEmit(device, "deviceInterview", {status: "successful", device});
774
- } catch (error) {
775
- logger.error(`Interview failed for '${device.ieeeAddr} with error '${error}'`, NS);
776
- this.selfAndDeviceEmit(device, "deviceInterview", {status: "failed", device});
777
- }
778
- } else {
779
- logger.debug(`Not interviewing '${payload.ieeeAddr}', interviewState=${device.interviewState}'`, NS);
780
- }
781
- }
782
-
783
- private onZdoResponse(clusterId: Zdo.ClusterId, response: ZdoTypes.GenericZdoResponse): void {
784
- logger.debug(
785
- `Received ZDO response: clusterId=${Zdo.ClusterId[clusterId]}, status=${Zdo.Status[response[0]]}, payload=${JSON.stringify(response[1])}`,
786
- NS,
787
- );
788
-
789
- switch (clusterId) {
790
- case Zdo.ClusterId.NETWORK_ADDRESS_RESPONSE: {
791
- if (Zdo.Buffalo.checkStatus<typeof clusterId>(response)) {
792
- this.onNetworkAddress(response[1]);
793
- }
794
- break;
795
- }
796
-
797
- case Zdo.ClusterId.IEEE_ADDRESS_RESPONSE: {
798
- if (Zdo.Buffalo.checkStatus<typeof clusterId>(response)) {
799
- this.onIEEEAddress(response[1]);
800
- }
801
- break;
802
- }
803
-
804
- case Zdo.ClusterId.END_DEVICE_ANNOUNCE: {
805
- if (Zdo.Buffalo.checkStatus<typeof clusterId>(response)) {
806
- this.onDeviceAnnounce(response[1]);
807
- }
808
- break;
809
- }
810
- }
811
- }
812
-
813
- private async onZclPayload(payload: AdapterEvents.ZclPayload): Promise<void> {
814
- let frame: Zcl.Frame | undefined;
815
- let device: Device | undefined;
816
-
817
- if (payload.clusterID === Zcl.Clusters.touchlink.ID) {
818
- // This is handled by touchlink
819
- return;
820
- }
821
-
822
- if (payload.clusterID === Zcl.Clusters.greenPower.ID) {
823
- try {
824
- // Custom clusters are not supported for Green Power since we need to parse the frame to get the device.
825
- frame = Zcl.Frame.fromBuffer(payload.clusterID, payload.header, payload.data, {});
826
- } catch (error) {
827
- logger.debug(`Failed to parse frame green power frame, ignoring it: ${error}`, NS);
828
- return;
829
- }
830
-
831
- if (frame.payload.commandID === undefined) {
832
- // can be from gpd or gpp
833
- // can be:
834
- // - greenPower.commandsResponse.commissioningMode
835
- // - Foundation.defaultRsp for greenPower.commandsResponse.pairing with status INVALID_FIELD or INSUFFICIENT_SPACE
836
- // - ...
837
- device = Device.find(payload.address);
838
- } else {
839
- if (frame.payload.srcID === undefined) {
840
- logger.debug("Data is from unsupported green power device with IEEE addressing, skipping...", NS);
841
- return;
842
- }
843
-
844
- const ieeeAddr = GreenPower.sourceIdToIeeeAddress(frame.payload.srcID);
845
- device = Device.byIeeeAddr(ieeeAddr);
846
- frame = await this.greenPower.processCommand(payload, frame, device?.gpSecurityKey ? Buffer.from(device.gpSecurityKey) : undefined);
847
-
848
- // lookup encapsulated gpDevice for further processing (re-fetch, may have been created by above call)
849
- device = Device.byIeeeAddr(ieeeAddr);
850
-
851
- if (!device) {
852
- logger.debug(`Data is from unknown green power device with address '${ieeeAddr}' (${frame.payload.srcID}), skipping...`, NS);
853
- return;
854
- }
855
- }
856
- } else {
857
- /**
858
- * Handling of re-transmitted Xiaomi messages.
859
- * https://github.com/Koenkk/zigbee2mqtt/issues/1238
860
- * https://github.com/Koenkk/zigbee2mqtt/issues/3592
861
- *
862
- * Some Xiaomi router devices re-transmit messages from Xiaomi end devices.
863
- * The network address of these message is set to the one of the Xiaomi router.
864
- * Therefore it looks like if the message came from the Xiaomi router, while in
865
- * fact it came from the end device.
866
- * Handling these message would result in false state updates.
867
- * The group ID attribute of these message defines the network address of the end device.
868
- */
869
- device = Device.find(payload.address);
870
-
871
- if (device?.manufacturerName === "LUMI" && device?.type === "Router" && payload.groupID) {
872
- logger.debug(`Handling re-transmitted Xiaomi message ${device.networkAddress} -> ${payload.groupID}`, NS);
873
- device = Device.byNetworkAddress(payload.groupID);
874
- }
875
-
876
- try {
877
- frame = Zcl.Frame.fromBuffer(payload.clusterID, payload.header, payload.data, device ? device.customClusters : {});
878
- } catch (error) {
879
- logger.debug(`Failed to parse frame: ${error}`, NS);
880
- }
881
- }
882
-
883
- if (!device) {
884
- if (typeof payload.address === "number") {
885
- device = await this.identifyUnknownDevice(payload.address);
886
- }
887
-
888
- if (!device) {
889
- logger.debug(`Data is from unknown device with address '${payload.address}', skipping...`, NS);
890
- return;
891
- }
892
- }
893
-
894
- logger.debug(
895
- `Received payload: clusterID=${payload.clusterID}, address=${payload.address}, groupID=${payload.groupID}, ` +
896
- `endpoint=${payload.endpoint}, destinationEndpoint=${payload.destinationEndpoint}, wasBroadcast=${payload.wasBroadcast}, ` +
897
- `linkQuality=${payload.linkquality}, frame=${frame?.toString()}`,
898
- NS,
899
- );
900
-
901
- device.updateLastSeen();
902
-
903
- //no implicit checkin for genPollCtrl data because it might interfere with the explicit checkin
904
- if (!frame?.isCluster("genPollCtrl")) {
905
- device.implicitCheckin();
906
- }
907
-
908
- device.linkquality = payload.linkquality;
909
- let endpoint = device.getEndpoint(payload.endpoint);
910
-
911
- if (!endpoint) {
912
- logger.debug(
913
- `Data is from unknown endpoint '${payload.endpoint}' from device with network address '${payload.address}', creating it...`,
914
- NS,
915
- );
916
-
917
- endpoint = device.createEndpoint(payload.endpoint);
918
- }
919
-
920
- // Parse command for event
921
- let type: Events.MessagePayload["type"] | undefined;
922
- let data: Events.MessagePayload["data"] = {};
923
- let clusterName: Events.MessagePayload["cluster"];
924
- const meta: {
925
- zclTransactionSequenceNumber?: number;
926
- manufacturerCode?: number;
927
- frameControl?: FrameControl;
928
- rawData: Buffer;
929
- } = {rawData: payload.data};
930
-
931
- if (frame) {
932
- const command = frame.command;
933
- clusterName = frame.cluster.name;
934
- meta.zclTransactionSequenceNumber = frame.header.transactionSequenceNumber;
935
- meta.manufacturerCode = frame.header.manufacturerCode;
936
- meta.frameControl = frame.header.frameControl;
937
-
938
- if (frame.header.isGlobal) {
939
- switch (frame.command.name) {
940
- case "report": {
941
- type = "attributeReport";
942
- data = ZclFrameConverter.attributeKeyValue(frame, device.manufacturerID, device.customClusters);
943
- break;
944
- }
945
-
946
- case "read": {
947
- type = "read";
948
- data = ZclFrameConverter.attributeList(frame, device.manufacturerID, device.customClusters);
949
- break;
950
- }
951
-
952
- case "write": {
953
- type = "write";
954
- data = ZclFrameConverter.attributeKeyValue(frame, device.manufacturerID, device.customClusters);
955
- break;
956
- }
957
-
958
- case "readRsp": {
959
- type = "readResponse";
960
- data = ZclFrameConverter.attributeKeyValue(frame, device.manufacturerID, device.customClusters);
961
- break;
962
- }
963
-
964
- case "defaultRsp": {
965
- if (frame.payload.statusCode !== Zcl.Status.SUCCESS) {
966
- logger.debug(
967
- `Failure default response from '${payload.address}': clusterID=${payload.clusterID} cmdId=${frame.payload.cmdId} status=${Zcl.Status[frame.payload.statusCode]}`,
968
- NS,
969
- );
970
- }
971
-
972
- break;
973
- }
974
- }
975
-
976
- if (type === "readResponse" || type === "attributeReport") {
977
- // devices report attributes through readRsp or attributeReport
978
- if (frame.isCluster("genBasic")) {
979
- device.updateGenBasic(data as TPartialClusterAttributes<"genBasic">);
980
- }
981
-
982
- endpoint.saveClusterAttributeKeyValue(frame.cluster.ID, data);
983
- }
984
- } else {
985
- if (frame.header.isSpecific) {
986
- type = `command${command.name.charAt(0).toUpperCase()}${command.name.slice(1)}`;
987
- data = frame.payload;
988
- }
989
- }
990
- } else {
991
- type = "raw";
992
- data = payload.data;
993
- const name = Zcl.Utils.getCluster(payload.clusterID, device.manufacturerID, device.customClusters).name;
994
- clusterName = Number.isNaN(Number(name)) ? name : Number(name);
995
- }
996
-
997
- if (type && data) {
998
- const linkquality = payload.linkquality;
999
- const groupID = payload.groupID;
1000
-
1001
- this.selfAndDeviceEmit(device, "message", {
1002
- type,
1003
- device,
1004
- endpoint,
1005
- data,
1006
- linkquality,
1007
- groupID,
1008
- cluster: clusterName,
1009
- meta,
1010
- });
1011
- this.selfAndDeviceEmit(device, "lastSeenChanged", {device, reason: "messageEmitted"});
1012
- } else {
1013
- this.selfAndDeviceEmit(device, "lastSeenChanged", {device, reason: "messageNonEmitted"});
1014
- }
1015
-
1016
- if (frame) {
1017
- await device.onZclData(payload, frame, endpoint);
1018
- }
1019
- }
1020
- }
1021
-
1022
- export default Controller;