zigbee-herdsman 6.0.2 → 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.
- package/CHANGELOG.md +7 -0
- package/package.json +12 -3
- package/.github/ISSUE_TEMPLATE/config.yml +0 -5
- package/.github/dependabot.yml +0 -22
- package/.github/workflows/ci.yml +0 -69
- package/.github/workflows/release-please.yml +0 -18
- package/.github/workflows/stale.yml +0 -20
- package/.github/workflows/typedoc.yaml +0 -47
- package/.release-please-manifest.json +0 -3
- package/.vscode/extensions.json +0 -3
- package/.vscode/settings.json +0 -11
- package/biome.json +0 -98
- package/examples/join-and-log.js +0 -24
- package/release-please-config.json +0 -9
- package/src/adapter/adapter.ts +0 -189
- package/src/adapter/adapterDiscovery.ts +0 -666
- package/src/adapter/const.ts +0 -12
- package/src/adapter/deconz/adapter/deconzAdapter.ts +0 -877
- package/src/adapter/deconz/driver/constants.ts +0 -246
- package/src/adapter/deconz/driver/driver.ts +0 -1540
- package/src/adapter/deconz/driver/frame.ts +0 -11
- package/src/adapter/deconz/driver/frameParser.ts +0 -753
- package/src/adapter/deconz/driver/parser.ts +0 -45
- package/src/adapter/deconz/driver/writer.ts +0 -22
- package/src/adapter/deconz/types.d.ts +0 -13
- package/src/adapter/ember/adapter/emberAdapter.ts +0 -2265
- package/src/adapter/ember/adapter/endpoints.ts +0 -86
- package/src/adapter/ember/adapter/oneWaitress.ts +0 -324
- package/src/adapter/ember/adapter/tokensManager.ts +0 -782
- package/src/adapter/ember/consts.ts +0 -178
- package/src/adapter/ember/enums.ts +0 -1746
- package/src/adapter/ember/ezsp/buffalo.ts +0 -1392
- package/src/adapter/ember/ezsp/consts.ts +0 -148
- package/src/adapter/ember/ezsp/enums.ts +0 -1114
- package/src/adapter/ember/ezsp/ezsp.ts +0 -9061
- package/src/adapter/ember/ezspError.ts +0 -10
- package/src/adapter/ember/types.ts +0 -866
- package/src/adapter/ember/uart/ash.ts +0 -1960
- package/src/adapter/ember/uart/consts.ts +0 -109
- package/src/adapter/ember/uart/enums.ts +0 -192
- package/src/adapter/ember/uart/parser.ts +0 -48
- package/src/adapter/ember/uart/queues.ts +0 -247
- package/src/adapter/ember/uart/writer.ts +0 -53
- package/src/adapter/ember/utils/initters.ts +0 -58
- package/src/adapter/ember/utils/math.ts +0 -73
- package/src/adapter/events.ts +0 -21
- package/src/adapter/ezsp/adapter/backup.ts +0 -109
- package/src/adapter/ezsp/adapter/ezspAdapter.ts +0 -614
- package/src/adapter/ezsp/driver/commands.ts +0 -2497
- package/src/adapter/ezsp/driver/consts.ts +0 -11
- package/src/adapter/ezsp/driver/driver.ts +0 -1002
- package/src/adapter/ezsp/driver/ezsp.ts +0 -802
- package/src/adapter/ezsp/driver/frame.ts +0 -101
- package/src/adapter/ezsp/driver/index.ts +0 -4
- package/src/adapter/ezsp/driver/multicast.ts +0 -78
- package/src/adapter/ezsp/driver/parser.ts +0 -81
- package/src/adapter/ezsp/driver/types/basic.ts +0 -201
- package/src/adapter/ezsp/driver/types/index.ts +0 -239
- package/src/adapter/ezsp/driver/types/named.ts +0 -2330
- package/src/adapter/ezsp/driver/types/struct.ts +0 -844
- package/src/adapter/ezsp/driver/uart.ts +0 -460
- package/src/adapter/ezsp/driver/utils/crc16ccitt.ts +0 -44
- package/src/adapter/ezsp/driver/utils/index.ts +0 -32
- package/src/adapter/ezsp/driver/writer.ts +0 -64
- package/src/adapter/index.ts +0 -3
- package/src/adapter/serialPort.ts +0 -58
- package/src/adapter/socketPortUtils.ts +0 -16
- package/src/adapter/tstype.ts +0 -78
- package/src/adapter/z-stack/adapter/adapter-backup.ts +0 -519
- package/src/adapter/z-stack/adapter/adapter-nv-memory.ts +0 -457
- package/src/adapter/z-stack/adapter/endpoints.ts +0 -57
- package/src/adapter/z-stack/adapter/manager.ts +0 -543
- package/src/adapter/z-stack/adapter/tstype.ts +0 -6
- package/src/adapter/z-stack/adapter/zStackAdapter.ts +0 -1190
- package/src/adapter/z-stack/constants/af.ts +0 -27
- package/src/adapter/z-stack/constants/common.ts +0 -285
- package/src/adapter/z-stack/constants/dbg.ts +0 -23
- package/src/adapter/z-stack/constants/index.ts +0 -11
- package/src/adapter/z-stack/constants/mac.ts +0 -128
- package/src/adapter/z-stack/constants/sapi.ts +0 -25
- package/src/adapter/z-stack/constants/sys.ts +0 -72
- package/src/adapter/z-stack/constants/util.ts +0 -82
- package/src/adapter/z-stack/constants/utils.ts +0 -14
- package/src/adapter/z-stack/constants/zdo.ts +0 -103
- package/src/adapter/z-stack/models/startup-options.ts +0 -13
- package/src/adapter/z-stack/structs/entries/address-manager-entry.ts +0 -44
- package/src/adapter/z-stack/structs/entries/address-manager-table.ts +0 -19
- package/src/adapter/z-stack/structs/entries/aps-link-key-data-entry.ts +0 -12
- package/src/adapter/z-stack/structs/entries/aps-link-key-data-table.ts +0 -21
- package/src/adapter/z-stack/structs/entries/aps-tc-link-key-entry.ts +0 -19
- package/src/adapter/z-stack/structs/entries/aps-tc-link-key-table.ts +0 -21
- package/src/adapter/z-stack/structs/entries/channel-list.ts +0 -8
- package/src/adapter/z-stack/structs/entries/has-configured.ts +0 -16
- package/src/adapter/z-stack/structs/entries/index.ts +0 -16
- package/src/adapter/z-stack/structs/entries/nib.ts +0 -66
- package/src/adapter/z-stack/structs/entries/nwk-key-descriptor.ts +0 -15
- package/src/adapter/z-stack/structs/entries/nwk-key.ts +0 -13
- package/src/adapter/z-stack/structs/entries/nwk-pan-id.ts +0 -8
- package/src/adapter/z-stack/structs/entries/nwk-sec-material-descriptor-entry.ts +0 -20
- package/src/adapter/z-stack/structs/entries/nwk-sec-material-descriptor-table.ts +0 -19
- package/src/adapter/z-stack/structs/entries/security-manager-entry.ts +0 -33
- package/src/adapter/z-stack/structs/entries/security-manager-table.ts +0 -22
- package/src/adapter/z-stack/structs/index.ts +0 -4
- package/src/adapter/z-stack/structs/serializable-memory-object.ts +0 -14
- package/src/adapter/z-stack/structs/struct.ts +0 -367
- package/src/adapter/z-stack/structs/table.ts +0 -198
- package/src/adapter/z-stack/unpi/constants.ts +0 -33
- package/src/adapter/z-stack/unpi/frame.ts +0 -62
- package/src/adapter/z-stack/unpi/index.ts +0 -4
- package/src/adapter/z-stack/unpi/parser.ts +0 -56
- package/src/adapter/z-stack/unpi/writer.ts +0 -21
- package/src/adapter/z-stack/utils/channel-list.ts +0 -40
- package/src/adapter/z-stack/utils/index.ts +0 -2
- package/src/adapter/z-stack/utils/network-options.ts +0 -26
- package/src/adapter/z-stack/znp/buffaloZnp.ts +0 -175
- package/src/adapter/z-stack/znp/definition.ts +0 -2713
- package/src/adapter/z-stack/znp/index.ts +0 -2
- package/src/adapter/z-stack/znp/parameterType.ts +0 -22
- package/src/adapter/z-stack/znp/tstype.ts +0 -44
- package/src/adapter/z-stack/znp/utils.ts +0 -10
- package/src/adapter/z-stack/znp/znp.ts +0 -342
- package/src/adapter/z-stack/znp/zpiObject.ts +0 -148
- package/src/adapter/zboss/adapter/zbossAdapter.ts +0 -526
- package/src/adapter/zboss/commands.ts +0 -1184
- package/src/adapter/zboss/consts.ts +0 -9
- package/src/adapter/zboss/driver.ts +0 -422
- package/src/adapter/zboss/enums.ts +0 -360
- package/src/adapter/zboss/frame.ts +0 -227
- package/src/adapter/zboss/reader.ts +0 -65
- package/src/adapter/zboss/types.ts +0 -0
- package/src/adapter/zboss/uart.ts +0 -428
- package/src/adapter/zboss/utils.ts +0 -58
- package/src/adapter/zboss/writer.ts +0 -49
- package/src/adapter/zigate/adapter/patchZdoBuffaloBE.ts +0 -27
- package/src/adapter/zigate/adapter/zigateAdapter.ts +0 -618
- package/src/adapter/zigate/driver/LICENSE +0 -17
- package/src/adapter/zigate/driver/buffaloZiGate.ts +0 -212
- package/src/adapter/zigate/driver/commandType.ts +0 -418
- package/src/adapter/zigate/driver/constants.ts +0 -150
- package/src/adapter/zigate/driver/frame.ts +0 -197
- package/src/adapter/zigate/driver/messageType.ts +0 -287
- package/src/adapter/zigate/driver/parameterType.ts +0 -32
- package/src/adapter/zigate/driver/ziGateObject.ts +0 -146
- package/src/adapter/zigate/driver/zigate.ts +0 -423
- package/src/adapter/zoh/adapter/utils.ts +0 -27
- package/src/adapter/zoh/adapter/zohAdapter.ts +0 -838
- package/src/buffalo/buffalo.ts +0 -342
- package/src/buffalo/index.ts +0 -1
- package/src/controller/controller.ts +0 -1022
- package/src/controller/database.ts +0 -124
- package/src/controller/events.ts +0 -52
- package/src/controller/greenPower.ts +0 -603
- package/src/controller/helpers/index.ts +0 -1
- package/src/controller/helpers/installCodes.ts +0 -107
- package/src/controller/helpers/request.ts +0 -96
- package/src/controller/helpers/requestQueue.ts +0 -125
- package/src/controller/helpers/zclFrameConverter.ts +0 -47
- package/src/controller/helpers/zclTransactionSequenceNumber.ts +0 -19
- package/src/controller/index.ts +0 -6
- package/src/controller/model/device.ts +0 -1249
- package/src/controller/model/endpoint.ts +0 -1105
- package/src/controller/model/entity.ts +0 -23
- package/src/controller/model/group.ts +0 -424
- package/src/controller/model/index.ts +0 -5
- package/src/controller/model/zigbeeEntity.ts +0 -30
- package/src/controller/touchlink.ts +0 -189
- package/src/controller/tstype.ts +0 -274
- package/src/index.ts +0 -12
- package/src/models/backup-storage-legacy.ts +0 -48
- package/src/models/backup-storage-unified.ts +0 -47
- package/src/models/backup.ts +0 -37
- package/src/models/index.ts +0 -5
- package/src/models/network-options.ts +0 -11
- package/src/utils/backup.ts +0 -152
- package/src/utils/index.ts +0 -5
- package/src/utils/logger.ts +0 -20
- package/src/utils/patchBigIntSerialization.ts +0 -8
- package/src/utils/queue.ts +0 -76
- package/src/utils/types.d.ts +0 -3
- package/src/utils/utils.ts +0 -19
- package/src/utils/wait.ts +0 -5
- package/src/utils/waitress.ts +0 -96
- package/src/zspec/consts.ts +0 -84
- package/src/zspec/enums.ts +0 -22
- package/src/zspec/index.ts +0 -3
- package/src/zspec/tstypes.ts +0 -18
- package/src/zspec/utils.ts +0 -247
- package/src/zspec/zcl/buffaloZcl.ts +0 -1220
- package/src/zspec/zcl/definition/cluster.ts +0 -5915
- package/src/zspec/zcl/definition/clusters-typegen.ts +0 -588
- package/src/zspec/zcl/definition/clusters-types.ts +0 -7331
- package/src/zspec/zcl/definition/consts.ts +0 -24
- package/src/zspec/zcl/definition/enums.ts +0 -203
- package/src/zspec/zcl/definition/foundation.ts +0 -329
- package/src/zspec/zcl/definition/manufacturerCode.ts +0 -729
- package/src/zspec/zcl/definition/status.ts +0 -69
- package/src/zspec/zcl/definition/tstype.ts +0 -377
- package/src/zspec/zcl/index.ts +0 -11
- package/src/zspec/zcl/utils.ts +0 -321
- package/src/zspec/zcl/zclFrame.ts +0 -356
- package/src/zspec/zcl/zclHeader.ts +0 -102
- package/src/zspec/zcl/zclStatusError.ts +0 -10
- package/src/zspec/zdo/buffaloZdo.ts +0 -2336
- package/src/zspec/zdo/definition/clusters.ts +0 -722
- package/src/zspec/zdo/definition/consts.ts +0 -16
- package/src/zspec/zdo/definition/enums.ts +0 -99
- package/src/zspec/zdo/definition/status.ts +0 -105
- package/src/zspec/zdo/definition/tstypes.ts +0 -1062
- package/src/zspec/zdo/index.ts +0 -7
- package/src/zspec/zdo/utils.ts +0 -76
- package/src/zspec/zdo/zdoStatusError.ts +0 -10
- package/test/adapter/adapter.test.ts +0 -1062
- package/test/adapter/ember/ash.test.ts +0 -337
- package/test/adapter/ember/consts.ts +0 -131
- package/test/adapter/ember/emberAdapter.test.ts +0 -3449
- package/test/adapter/ember/ezsp.test.ts +0 -385
- package/test/adapter/ember/ezspBuffalo.test.ts +0 -93
- package/test/adapter/ember/ezspError.test.ts +0 -12
- package/test/adapter/ember/math.test.ts +0 -206
- package/test/adapter/ezsp/frame.test.ts +0 -30
- package/test/adapter/ezsp/uart.test.ts +0 -181
- package/test/adapter/z-stack/adapter.test.ts +0 -3984
- package/test/adapter/z-stack/constants.test.ts +0 -33
- package/test/adapter/z-stack/structs.test.ts +0 -115
- package/test/adapter/z-stack/unpi.test.ts +0 -213
- package/test/adapter/z-stack/znp.test.ts +0 -1284
- package/test/adapter/zboss/fixZdoResponse.test.ts +0 -179
- package/test/adapter/zigate/patchZdoBuffaloBE.test.ts +0 -81
- package/test/adapter/zigate/zdo.test.ts +0 -187
- package/test/adapter/zoh/utils.test.ts +0 -36
- package/test/adapter/zoh/zohAdapter.test.ts +0 -1307
- package/test/benchOptions.ts +0 -14
- package/test/buffalo.test.ts +0 -431
- package/test/controller.bench.ts +0 -214
- package/test/controller.test.ts +0 -8702
- package/test/greenpower.test.ts +0 -1408
- package/test/mockAdapters.ts +0 -65
- package/test/mockDevices.ts +0 -598
- package/test/requests.bench.ts +0 -229
- package/test/testUtils.ts +0 -20
- package/test/tsconfig.json +0 -9
- package/test/utils/math.ts +0 -19
- package/test/utils.test.ts +0 -279
- package/test/vitest.config.mts +0 -26
- package/test/zcl.test.ts +0 -2831
- package/test/zspec/utils.test.ts +0 -68
- package/test/zspec/zcl/buffalo.test.ts +0 -1374
- package/test/zspec/zcl/frame.test.ts +0 -960
- package/test/zspec/zcl/utils.test.ts +0 -273
- package/test/zspec/zdo/buffalo.test.ts +0 -1850
- package/test/zspec/zdo/utils.test.ts +0 -241
- 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;
|