@willieee802/zigbee-herdsman 0.14.112 → 0.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.release-please-manifest.json +4 -0
- package/CHANGELOG.md +27 -0
- package/dist/adapter/adapter.d.ts +61 -61
- package/dist/adapter/adapter.d.ts.map +1 -1
- package/dist/adapter/adapter.js +146 -100
- package/dist/adapter/adapter.js.map +1 -1
- package/dist/adapter/deconz/adapter/deconzAdapter.d.ts +68 -68
- package/dist/adapter/deconz/adapter/deconzAdapter.d.ts.map +1 -1
- package/dist/adapter/deconz/adapter/deconzAdapter.js +1060 -1063
- package/dist/adapter/deconz/adapter/deconzAdapter.js.map +1 -1
- package/dist/adapter/deconz/adapter/index.d.ts +2 -2
- package/dist/adapter/deconz/adapter/index.js +10 -10
- package/dist/adapter/deconz/driver/constants.d.ts +104 -104
- package/dist/adapter/deconz/driver/constants.js +55 -55
- package/dist/adapter/deconz/driver/driver.d.ts +81 -81
- package/dist/adapter/deconz/driver/driver.js +732 -732
- package/dist/adapter/deconz/driver/frame.d.ts +6 -6
- package/dist/adapter/deconz/driver/frame.js +13 -13
- package/dist/adapter/deconz/driver/frameParser.d.ts +2 -2
- package/dist/adapter/deconz/driver/frameParser.js +443 -443
- package/dist/adapter/deconz/driver/parser.d.ts +12 -12
- package/dist/adapter/deconz/driver/parser.js +61 -61
- package/dist/adapter/deconz/driver/writer.d.ts +8 -8
- package/dist/adapter/deconz/driver/writer.js +44 -44
- package/dist/adapter/events.d.ts +47 -47
- package/dist/adapter/events.js +14 -14
- package/dist/adapter/ezsp/adapter/backup.d.ts +9 -9
- package/dist/adapter/ezsp/adapter/backup.js +53 -53
- package/dist/adapter/ezsp/adapter/ezspAdapter.d.ts +59 -59
- package/dist/adapter/ezsp/adapter/ezspAdapter.js +585 -585
- package/dist/adapter/ezsp/adapter/index.d.ts +2 -2
- package/dist/adapter/ezsp/adapter/index.js +10 -10
- package/dist/adapter/ezsp/driver/commands.d.ts +36 -34
- package/dist/adapter/ezsp/driver/commands.d.ts.map +1 -1
- package/dist/adapter/ezsp/driver/commands.js +2359 -2357
- package/dist/adapter/ezsp/driver/commands.js.map +1 -1
- package/dist/adapter/ezsp/driver/consts.d.ts +10 -10
- package/dist/adapter/ezsp/driver/consts.js +13 -13
- package/dist/adapter/ezsp/driver/driver.d.ts +103 -103
- package/dist/adapter/ezsp/driver/driver.js +639 -639
- package/dist/adapter/ezsp/driver/driver.js.map +1 -1
- package/dist/adapter/ezsp/driver/ezsp.d.ts +96 -96
- package/dist/adapter/ezsp/driver/ezsp.d.ts.map +1 -1
- package/dist/adapter/ezsp/driver/ezsp.js +586 -577
- package/dist/adapter/ezsp/driver/ezsp.js.map +1 -1
- package/dist/adapter/ezsp/driver/index.d.ts +3 -3
- package/dist/adapter/ezsp/driver/index.js +8 -8
- package/dist/adapter/ezsp/driver/multicast.d.ts +12 -12
- package/dist/adapter/ezsp/driver/multicast.js +74 -74
- package/dist/adapter/ezsp/driver/parser.d.ts +12 -12
- package/dist/adapter/ezsp/driver/parser.js +111 -111
- package/dist/adapter/ezsp/driver/types/basic.d.ts +62 -62
- package/dist/adapter/ezsp/driver/types/basic.js +208 -208
- package/dist/adapter/ezsp/driver/types/basic.js.map +1 -1
- package/dist/adapter/ezsp/driver/types/index.d.ts +9 -9
- package/dist/adapter/ezsp/driver/types/index.js +133 -133
- package/dist/adapter/ezsp/driver/types/named.d.ts +697 -697
- package/dist/adapter/ezsp/driver/types/named.js +1726 -1726
- package/dist/adapter/ezsp/driver/types/named.js.map +1 -1
- package/dist/adapter/ezsp/driver/types/struct.d.ts +251 -251
- package/dist/adapter/ezsp/driver/types/struct.js +708 -708
- package/dist/adapter/ezsp/driver/types/struct.js.map +1 -1
- package/dist/adapter/ezsp/driver/uart.d.ts +44 -44
- package/dist/adapter/ezsp/driver/uart.d.ts.map +1 -1
- package/dist/adapter/ezsp/driver/uart.js +368 -366
- package/dist/adapter/ezsp/driver/uart.js.map +1 -1
- package/dist/adapter/ezsp/driver/utils/crc16ccitt.d.ts +2 -2
- package/dist/adapter/ezsp/driver/utils/crc16ccitt.js +55 -55
- package/dist/adapter/ezsp/driver/utils/index.d.ts +18 -18
- package/dist/adapter/ezsp/driver/utils/index.js +67 -67
- package/dist/adapter/ezsp/driver/writer.d.ts +13 -13
- package/dist/adapter/ezsp/driver/writer.js +88 -88
- package/dist/adapter/index.d.ts +4 -4
- package/dist/adapter/index.js +35 -35
- package/dist/adapter/serialPort.d.ts +8 -8
- package/dist/adapter/serialPort.js +22 -22
- package/dist/adapter/serialPort.js.map +1 -1
- package/dist/adapter/serialPortUtils.d.ts +12 -12
- package/dist/adapter/serialPortUtils.js +18 -18
- package/dist/adapter/socketPortUtils.d.ts +10 -10
- package/dist/adapter/socketPortUtils.js +16 -16
- package/dist/adapter/tstype.d.ts +85 -85
- package/dist/adapter/tstype.js +2 -2
- package/dist/adapter/z-stack/adapter/adapter-backup.d.ts +62 -62
- package/dist/adapter/z-stack/adapter/adapter-backup.js +441 -441
- package/dist/adapter/z-stack/adapter/adapter-nv-memory.d.ts +150 -150
- package/dist/adapter/z-stack/adapter/adapter-nv-memory.js +258 -258
- package/dist/adapter/z-stack/adapter/endpoints.d.ts +11 -11
- package/dist/adapter/z-stack/adapter/endpoints.js +73 -73
- package/dist/adapter/z-stack/adapter/index.d.ts +2 -2
- package/dist/adapter/z-stack/adapter/index.js +8 -8
- package/dist/adapter/z-stack/adapter/manager.d.ts +86 -86
- package/dist/adapter/z-stack/adapter/manager.js +476 -476
- package/dist/adapter/z-stack/adapter/tstype.d.ts +6 -6
- package/dist/adapter/z-stack/adapter/tstype.js +10 -10
- package/dist/adapter/z-stack/adapter/zStackAdapter.d.ts +81 -81
- package/dist/adapter/z-stack/adapter/zStackAdapter.js +868 -868
- package/dist/adapter/z-stack/constants/af.d.ts +23 -23
- package/dist/adapter/z-stack/constants/af.js +27 -27
- package/dist/adapter/z-stack/constants/common.d.ts +278 -278
- package/dist/adapter/z-stack/constants/common.js +289 -289
- package/dist/adapter/z-stack/constants/dbg.d.ts +22 -22
- package/dist/adapter/z-stack/constants/dbg.js +24 -24
- package/dist/adapter/z-stack/constants/index.d.ts +10 -10
- package/dist/adapter/z-stack/constants/index.js +47 -47
- package/dist/adapter/z-stack/constants/mac.d.ts +127 -127
- package/dist/adapter/z-stack/constants/mac.js +129 -129
- package/dist/adapter/z-stack/constants/sapi.d.ts +24 -24
- package/dist/adapter/z-stack/constants/sapi.js +26 -26
- package/dist/adapter/z-stack/constants/sys.d.ts +71 -71
- package/dist/adapter/z-stack/constants/sys.js +73 -73
- package/dist/adapter/z-stack/constants/util.d.ts +81 -81
- package/dist/adapter/z-stack/constants/util.js +83 -83
- package/dist/adapter/z-stack/constants/utils.d.ts +4 -4
- package/dist/adapter/z-stack/constants/utils.js +14 -14
- package/dist/adapter/z-stack/constants/zdo.d.ts +102 -102
- package/dist/adapter/z-stack/constants/zdo.js +104 -104
- package/dist/adapter/z-stack/models/index.d.ts +1 -1
- package/dist/adapter/z-stack/models/index.js +17 -17
- package/dist/adapter/z-stack/models/startup-options.d.ts +12 -12
- package/dist/adapter/z-stack/models/startup-options.js +2 -2
- package/dist/adapter/z-stack/structs/entries/address-manager-entry.d.ts +23 -23
- package/dist/adapter/z-stack/structs/entries/address-manager-entry.js +45 -45
- package/dist/adapter/z-stack/structs/entries/address-manager-table.d.ts +10 -10
- package/dist/adapter/z-stack/structs/entries/address-manager-table.js +22 -22
- package/dist/adapter/z-stack/structs/entries/aps-link-key-data-entry.d.ts +10 -10
- package/dist/adapter/z-stack/structs/entries/aps-link-key-data-entry.js +21 -21
- package/dist/adapter/z-stack/structs/entries/aps-link-key-data-table.d.ts +10 -10
- package/dist/adapter/z-stack/structs/entries/aps-link-key-data-table.js +23 -23
- package/dist/adapter/z-stack/structs/entries/aps-tc-link-key-entry.d.ts +10 -10
- package/dist/adapter/z-stack/structs/entries/aps-tc-link-key-entry.js +24 -24
- package/dist/adapter/z-stack/structs/entries/aps-tc-link-key-table.d.ts +10 -10
- package/dist/adapter/z-stack/structs/entries/aps-tc-link-key-table.js +23 -23
- package/dist/adapter/z-stack/structs/entries/channel-list.d.ts +8 -8
- package/dist/adapter/z-stack/structs/entries/channel-list.js +15 -15
- package/dist/adapter/z-stack/structs/entries/has-configured.d.ts +8 -8
- package/dist/adapter/z-stack/structs/entries/has-configured.js +16 -16
- package/dist/adapter/z-stack/structs/entries/index.d.ts +16 -16
- package/dist/adapter/z-stack/structs/entries/index.js +32 -32
- package/dist/adapter/z-stack/structs/entries/nib.d.ts +10 -10
- package/dist/adapter/z-stack/structs/entries/nib.js +68 -68
- package/dist/adapter/z-stack/structs/entries/nwk-key-descriptor.d.ts +10 -10
- package/dist/adapter/z-stack/structs/entries/nwk-key-descriptor.js +18 -18
- package/dist/adapter/z-stack/structs/entries/nwk-key.d.ts +8 -8
- package/dist/adapter/z-stack/structs/entries/nwk-key.js +15 -15
- package/dist/adapter/z-stack/structs/entries/nwk-pan-id.d.ts +8 -8
- package/dist/adapter/z-stack/structs/entries/nwk-pan-id.js +15 -15
- package/dist/adapter/z-stack/structs/entries/nwk-sec-material-descriptor-entry.d.ts +13 -13
- package/dist/adapter/z-stack/structs/entries/nwk-sec-material-descriptor-entry.js +23 -23
- package/dist/adapter/z-stack/structs/entries/nwk-sec-material-descriptor-table.d.ts +10 -10
- package/dist/adapter/z-stack/structs/entries/nwk-sec-material-descriptor-table.js +22 -22
- package/dist/adapter/z-stack/structs/entries/security-manager-entry.d.ts +20 -20
- package/dist/adapter/z-stack/structs/entries/security-manager-entry.js +36 -36
- package/dist/adapter/z-stack/structs/entries/security-manager-table.d.ts +10 -10
- package/dist/adapter/z-stack/structs/entries/security-manager-table.js +24 -24
- package/dist/adapter/z-stack/structs/index.d.ts +4 -4
- package/dist/adapter/z-stack/structs/index.js +20 -20
- package/dist/adapter/z-stack/structs/serializable-memory-object.d.ts +13 -13
- package/dist/adapter/z-stack/structs/serializable-memory-object.js +2 -2
- package/dist/adapter/z-stack/structs/struct.d.ts +99 -99
- package/dist/adapter/z-stack/structs/struct.js +295 -295
- package/dist/adapter/z-stack/structs/table.d.ts +94 -94
- package/dist/adapter/z-stack/structs/table.js +161 -161
- package/dist/adapter/z-stack/unpi/constants.d.ts +28 -28
- package/dist/adapter/z-stack/unpi/constants.js +41 -41
- package/dist/adapter/z-stack/unpi/frame.d.ts +16 -16
- package/dist/adapter/z-stack/unpi/frame.js +48 -48
- package/dist/adapter/z-stack/unpi/index.d.ts +5 -5
- package/dist/adapter/z-stack/unpi/index.js +37 -37
- package/dist/adapter/z-stack/unpi/parser.d.ts +10 -10
- package/dist/adapter/z-stack/unpi/parser.js +74 -74
- package/dist/adapter/z-stack/unpi/writer.d.ts +10 -10
- package/dist/adapter/z-stack/unpi/writer.js +44 -44
- package/dist/adapter/z-stack/utils/channel-list.d.ts +20 -20
- package/dist/adapter/z-stack/utils/channel-list.js +40 -40
- package/dist/adapter/z-stack/utils/index.d.ts +2 -2
- package/dist/adapter/z-stack/utils/index.js +18 -18
- package/dist/adapter/z-stack/utils/network-options.d.ts +8 -8
- package/dist/adapter/z-stack/utils/network-options.js +22 -22
- package/dist/adapter/z-stack/znp/buffaloZnp.d.ts +11 -11
- package/dist/adapter/z-stack/znp/buffaloZnp.js +113 -113
- package/dist/adapter/z-stack/znp/definition.d.ts +5 -5
- package/dist/adapter/z-stack/znp/definition.js +3050 -3050
- package/dist/adapter/z-stack/znp/index.d.ts +3 -3
- package/dist/adapter/z-stack/znp/index.js +10 -10
- package/dist/adapter/z-stack/znp/parameterType.d.ts +22 -22
- package/dist/adapter/z-stack/znp/parameterType.js +25 -25
- package/dist/adapter/z-stack/znp/tstype.d.ts +21 -21
- package/dist/adapter/z-stack/znp/tstype.js +2 -2
- package/dist/adapter/z-stack/znp/znp.d.ts +43 -43
- package/dist/adapter/z-stack/znp/znp.js +325 -325
- package/dist/adapter/z-stack/znp/zpiObject.d.ts +19 -19
- package/dist/adapter/z-stack/znp/zpiObject.js +96 -96
- package/dist/adapter/zigate/adapter/index.d.ts +2 -2
- package/dist/adapter/zigate/adapter/index.js +10 -10
- package/dist/adapter/zigate/adapter/zigateAdapter.d.ts +69 -69
- package/dist/adapter/zigate/adapter/zigateAdapter.js +678 -678
- package/dist/adapter/zigate/debug.d.ts +7 -7
- package/dist/adapter/zigate/debug.js +22 -22
- package/dist/adapter/zigate/driver/buffaloZiGate.d.ts +18 -18
- package/dist/adapter/zigate/driver/buffaloZiGate.js +139 -139
- package/dist/adapter/zigate/driver/commandType.d.ts +41 -41
- package/dist/adapter/zigate/driver/commandType.js +385 -385
- package/dist/adapter/zigate/driver/constants.d.ts +276 -276
- package/dist/adapter/zigate/driver/constants.js +371 -371
- package/dist/adapter/zigate/driver/frame.d.ts +26 -26
- package/dist/adapter/zigate/driver/frame.js +172 -172
- package/dist/adapter/zigate/driver/frame.js.map +1 -1
- package/dist/adapter/zigate/driver/messageType.d.ts +11 -11
- package/dist/adapter/zigate/driver/messageType.js +278 -278
- package/dist/adapter/zigate/driver/parameterType.d.ts +20 -20
- package/dist/adapter/zigate/driver/parameterType.js +23 -23
- package/dist/adapter/zigate/driver/ziGateObject.d.ts +23 -23
- package/dist/adapter/zigate/driver/ziGateObject.js +106 -106
- package/dist/adapter/zigate/driver/zigate.d.ts +49 -49
- package/dist/adapter/zigate/driver/zigate.js +303 -303
- package/dist/buffalo/buffalo.d.ts +50 -48
- package/dist/buffalo/buffalo.d.ts.map +1 -1
- package/dist/buffalo/buffalo.js +322 -307
- package/dist/buffalo/buffalo.js.map +1 -1
- package/dist/buffalo/index.d.ts +3 -3
- package/dist/buffalo/index.js +33 -33
- package/dist/buffalo/tstype.d.ts +8 -8
- package/dist/buffalo/tstype.js +2 -2
- package/dist/controller/controller.d.ts +110 -110
- package/dist/controller/controller.js +607 -607
- package/dist/controller/database.d.ts +18 -18
- package/dist/controller/database.js +93 -93
- package/dist/controller/events.d.ts +55 -55
- package/dist/controller/events.d.ts.map +1 -1
- package/dist/controller/events.js +101 -99
- package/dist/controller/events.js.map +1 -1
- package/dist/controller/greenPower.d.ts +12 -12
- package/dist/controller/greenPower.js +220 -220
- package/dist/controller/helpers/index.d.ts +2 -2
- package/dist/controller/helpers/index.js +28 -28
- package/dist/controller/helpers/request.d.ts +23 -0
- package/dist/controller/helpers/request.d.ts.map +1 -0
- package/dist/controller/helpers/request.js +72 -0
- package/dist/controller/helpers/request.js.map +1 -0
- package/dist/controller/helpers/zclFrameConverter.d.ts +7 -7
- package/dist/controller/helpers/zclFrameConverter.js +31 -31
- package/dist/controller/helpers/zclTransactionSequenceNumber.d.ts +5 -5
- package/dist/controller/helpers/zclTransactionSequenceNumber.js +13 -13
- package/dist/controller/index.d.ts +5 -5
- package/dist/controller/index.js +8 -8
- package/dist/controller/logger-stub.d.ts +6 -6
- package/dist/controller/logger-stub.js +2 -2
- package/dist/controller/model/device.d.ts +132 -127
- package/dist/controller/model/device.d.ts.map +1 -1
- package/dist/controller/model/device.js +708 -681
- package/dist/controller/model/device.js.map +1 -1
- package/dist/controller/model/endpoint.d.ts +131 -128
- package/dist/controller/model/endpoint.d.ts.map +1 -1
- package/dist/controller/model/endpoint.js +817 -665
- package/dist/controller/model/endpoint.js.map +1 -1
- package/dist/controller/model/entity.d.ts +14 -14
- package/dist/controller/model/entity.js +26 -26
- package/dist/controller/model/group.d.ts +39 -39
- package/dist/controller/model/group.js +221 -221
- package/dist/controller/model/index.d.ts +5 -5
- package/dist/controller/model/index.js +14 -14
- package/dist/controller/touchlink.d.ts +19 -19
- package/dist/controller/touchlink.js +157 -157
- package/dist/controller/tstype.d.ts +21 -20
- package/dist/controller/tstype.d.ts.map +1 -1
- package/dist/controller/tstype.js +9 -9
- package/dist/controller/tstype.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +33 -33
- package/dist/models/backup-storage-legacy.d.ts +26 -26
- package/dist/models/backup-storage-legacy.js +2 -2
- package/dist/models/backup-storage-unified.d.ts +49 -49
- package/dist/models/backup-storage-unified.js +2 -2
- package/dist/models/backup.d.ts +37 -37
- package/dist/models/backup.js +2 -2
- package/dist/models/index.d.ts +4 -4
- package/dist/models/index.js +20 -20
- package/dist/models/network-options.d.ts +12 -12
- package/dist/models/network-options.js +2 -2
- package/dist/utils/assertString.d.ts +2 -2
- package/dist/utils/assertString.js +8 -8
- package/dist/utils/backup.d.ts +20 -20
- package/dist/utils/backup.js +187 -187
- package/dist/utils/equalsPartial.d.ts +2 -2
- package/dist/utils/equalsPartial.js +11 -11
- package/dist/utils/index.d.ts +9 -9
- package/dist/utils/index.js +45 -45
- package/dist/utils/isNumberArray.d.ts +2 -2
- package/dist/utils/isNumberArray.js +6 -6
- package/dist/utils/queue.d.ts +11 -11
- package/dist/utils/queue.js +50 -50
- package/dist/utils/realpathSync.d.ts +2 -2
- package/dist/utils/realpathSync.js +12 -12
- package/dist/utils/wait.d.ts +2 -2
- package/dist/utils/wait.js +8 -8
- package/dist/utils/waitress.d.ts +21 -21
- package/dist/utils/waitress.js +61 -61
- package/dist/zcl/buffaloZcl.d.ts +41 -41
- package/dist/zcl/buffaloZcl.js +591 -591
- package/dist/zcl/definition/buffaloZclDataType.d.ts +17 -17
- package/dist/zcl/definition/buffaloZclDataType.js +20 -20
- package/dist/zcl/definition/cluster.d.ts +29 -29
- package/dist/zcl/definition/cluster.d.ts.map +1 -1
- package/dist/zcl/definition/cluster.js +5335 -5215
- package/dist/zcl/definition/cluster.js.map +1 -1
- package/dist/zcl/definition/dataType.d.ts +59 -59
- package/dist/zcl/definition/dataType.d.ts.map +1 -1
- package/dist/zcl/definition/dataType.js +64 -64
- package/dist/zcl/definition/dataType.js.map +1 -1
- package/dist/zcl/definition/direction.d.ts +5 -5
- package/dist/zcl/definition/direction.js +8 -8
- package/dist/zcl/definition/endpointDeviceType.d.ts +4 -4
- package/dist/zcl/definition/endpointDeviceType.js +15 -15
- package/dist/zcl/definition/foundation.d.ts +11 -11
- package/dist/zcl/definition/foundation.js +167 -167
- package/dist/zcl/definition/frameControl.d.ts +10 -10
- package/dist/zcl/definition/frameControl.js +2 -2
- package/dist/zcl/definition/frameType.d.ts +5 -5
- package/dist/zcl/definition/frameType.js +8 -8
- package/dist/zcl/definition/index.d.ts +13 -13
- package/dist/zcl/definition/index.js +51 -51
- package/dist/zcl/definition/manufacturerCode.d.ts +1074 -1074
- package/dist/zcl/definition/manufacturerCode.js +1079 -1079
- package/dist/zcl/definition/powerSource.d.ts +4 -4
- package/dist/zcl/definition/powerSource.js +12 -12
- package/dist/zcl/definition/status.d.ts +38 -38
- package/dist/zcl/definition/status.js +41 -41
- package/dist/zcl/definition/tstype.d.ts +16 -16
- package/dist/zcl/definition/tstype.js +2 -2
- package/dist/zcl/index.d.ts +15 -15
- package/dist/zcl/index.js +55 -55
- package/dist/zcl/tstype.d.ts +56 -56
- package/dist/zcl/tstype.js +10 -10
- package/dist/zcl/utils.d.ts +6 -6
- package/dist/zcl/utils.js +165 -165
- package/dist/zcl/zclFrame.d.ts +45 -45
- package/dist/zcl/zclFrame.js +347 -347
- package/dist/zcl/zclStatusError.d.ts +5 -5
- package/dist/zcl/zclStatusError.js +13 -13
- package/package.json +6 -5
- package/release-please-config.json +13 -0
- package/.github/ISSUE_TEMPLATE/config.yml +0 -5
- package/.github/dependabot.yml +0 -6
- package/.github/workflows/ci.yml +0 -31
- package/.github/workflows/release.yml +0 -56
- package/.github/workflows/stale.yml +0 -19
- package/.github/workflows/update_deps.yml +0 -24
|
@@ -1,869 +1,869 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
-
if (mod && mod.__esModule) return mod;
|
|
20
|
-
var result = {};
|
|
21
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
-
__setModuleDefault(result, mod);
|
|
23
|
-
return result;
|
|
24
|
-
};
|
|
25
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
-
};
|
|
28
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
-
const tstype_1 = require("./tstype");
|
|
30
|
-
const Events = __importStar(require("../../events"));
|
|
31
|
-
const adapter_1 = __importDefault(require("../../adapter"));
|
|
32
|
-
const znp_1 = require("../znp");
|
|
33
|
-
const unpi_1 = require("../unpi");
|
|
34
|
-
const zcl_1 = require("../../../zcl");
|
|
35
|
-
const utils_1 = require("../../../utils");
|
|
36
|
-
const Constants = __importStar(require("../constants"));
|
|
37
|
-
const debug_1 = __importDefault(require("debug"));
|
|
38
|
-
const debounce_1 = __importDefault(require("debounce"));
|
|
39
|
-
const manager_1 = require("./manager");
|
|
40
|
-
const assert_1 = __importDefault(require("assert"));
|
|
41
|
-
const debug = (0, debug_1.default)("zigbee-herdsman:adapter:zStack:adapter");
|
|
42
|
-
const Subsystem = unpi_1.Constants.Subsystem;
|
|
43
|
-
const Type = unpi_1.Constants.Type;
|
|
44
|
-
const { ZnpCommandStatus, AddressMode } = Constants.COMMON;
|
|
45
|
-
const DataConfirmTimeout = 9999; // Not an actual code
|
|
46
|
-
const DataConfirmErrorCodeLookup = {
|
|
47
|
-
[DataConfirmTimeout]: 'Timeout',
|
|
48
|
-
26: 'MAC no resources',
|
|
49
|
-
183: 'APS no ack',
|
|
50
|
-
205: 'No network route',
|
|
51
|
-
225: 'MAC channel access failure',
|
|
52
|
-
233: 'MAC no ack',
|
|
53
|
-
240: 'MAC transaction expired',
|
|
54
|
-
};
|
|
55
|
-
class DataConfirmError extends Error {
|
|
56
|
-
constructor(code) {
|
|
57
|
-
const message = `Data request failed with error: '${DataConfirmErrorCodeLookup[code]}' (${code})`;
|
|
58
|
-
super(message);
|
|
59
|
-
this.code = code;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
class ZStackAdapter extends adapter_1.default {
|
|
63
|
-
constructor(networkOptions, serialPortOptions, backupPath, adapterOptions, logger) {
|
|
64
|
-
super(networkOptions, serialPortOptions, backupPath, adapterOptions, logger);
|
|
65
|
-
this.supportsLED = null;
|
|
66
|
-
this.znp = new znp_1.Znp(this.serialPortOptions.path, this.serialPortOptions.baudRate, this.serialPortOptions.rtscts);
|
|
67
|
-
this.transactionID = 0;
|
|
68
|
-
this.deviceAnnounceRouteDiscoveryDebouncers = new Map();
|
|
69
|
-
this.interpanLock = false;
|
|
70
|
-
this.interpanEndpointRegistered = false;
|
|
71
|
-
this.closing = false;
|
|
72
|
-
this.waitress = new utils_1.Waitress(this.waitressValidator, this.waitressTimeoutFormatter);
|
|
73
|
-
this.znp.on('received', this.onZnpRecieved.bind(this));
|
|
74
|
-
this.znp.on('close', this.onZnpClose.bind(this));
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Adapter methods
|
|
78
|
-
*/
|
|
79
|
-
async start() {
|
|
80
|
-
await this.znp.open();
|
|
81
|
-
const attempts = 3;
|
|
82
|
-
for (let i = 0; i < attempts; i++) {
|
|
83
|
-
try {
|
|
84
|
-
await this.znp.request(Subsystem.SYS, 'ping', { capabilities: 1 });
|
|
85
|
-
break;
|
|
86
|
-
}
|
|
87
|
-
catch (e) {
|
|
88
|
-
if (attempts - 1 === i) {
|
|
89
|
-
throw new Error(`Failed to connect to the adapter (${e})`);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
// Old firmware did not support version, assume it's Z-Stack 1.2 for now.
|
|
94
|
-
try {
|
|
95
|
-
this.version = (await this.znp.request(Subsystem.SYS, 'version', {})).payload;
|
|
96
|
-
}
|
|
97
|
-
catch (e) {
|
|
98
|
-
debug(`Failed to get zStack version, assuming 1.2`);
|
|
99
|
-
this.version = { "transportrev": 2, "product": 0, "majorrel": 2, "minorrel": 0, "maintrel": 0, "revision": "" };
|
|
100
|
-
}
|
|
101
|
-
const concurrent = this.adapterOptions && this.adapterOptions.concurrent ?
|
|
102
|
-
this.adapterOptions.concurrent :
|
|
103
|
-
(this.version.product === tstype_1.ZnpVersion.zStack3x0 ? 16 : 2);
|
|
104
|
-
debug(`Adapter concurrent: ${concurrent}`);
|
|
105
|
-
this.queue = new utils_1.Queue(concurrent);
|
|
106
|
-
debug(`Detected znp version '${tstype_1.ZnpVersion[this.version.product]}' (${JSON.stringify(this.version)})`);
|
|
107
|
-
this.adapterManager = new manager_1.ZnpAdapterManager(this.znp, {
|
|
108
|
-
backupPath: this.backupPath,
|
|
109
|
-
version: this.version.product,
|
|
110
|
-
greenPowerGroup: this.greenPowerGroup,
|
|
111
|
-
networkOptions: this.networkOptions,
|
|
112
|
-
adapterOptions: this.adapterOptions,
|
|
113
|
-
}, this.logger);
|
|
114
|
-
const startResult = this.adapterManager.start();
|
|
115
|
-
if (this.adapterOptions.disableLED) {
|
|
116
|
-
// Wait a bit for adapter to startup, otherwise led doesn't disable (tested with CC2531)
|
|
117
|
-
await (0, utils_1.Wait)(200);
|
|
118
|
-
await this.setLED('disable');
|
|
119
|
-
}
|
|
120
|
-
return startResult;
|
|
121
|
-
}
|
|
122
|
-
async stop() {
|
|
123
|
-
this.closing = true;
|
|
124
|
-
await this.znp.close();
|
|
125
|
-
}
|
|
126
|
-
static async isValidPath(path) {
|
|
127
|
-
return znp_1.Znp.isValidPath(path);
|
|
128
|
-
}
|
|
129
|
-
static async autoDetectPath() {
|
|
130
|
-
return znp_1.Znp.autoDetectPath();
|
|
131
|
-
}
|
|
132
|
-
async getCoordinator() {
|
|
133
|
-
return this.queue.execute(async () => {
|
|
134
|
-
this.checkInterpanLock();
|
|
135
|
-
const activeEpRsp = this.znp.waitFor(unpi_1.Constants.Type.AREQ, Subsystem.ZDO, 'activeEpRsp');
|
|
136
|
-
await this.znp.request(Subsystem.ZDO, 'activeEpReq', { dstaddr: 0, nwkaddrofinterest: 0 }, activeEpRsp.ID);
|
|
137
|
-
const activeEp = await activeEpRsp.start().promise;
|
|
138
|
-
const deviceInfo = await this.znp.request(Subsystem.UTIL, 'getDeviceInfo', {});
|
|
139
|
-
const endpoints = [];
|
|
140
|
-
for (const endpoint of activeEp.payload.activeeplist) {
|
|
141
|
-
const simpleDescRsp = this.znp.waitFor(unpi_1.Constants.Type.AREQ, Subsystem.ZDO, 'simpleDescRsp', { endpoint });
|
|
142
|
-
await this.znp.request(Subsystem.ZDO, 'simpleDescReq', { dstaddr: 0, nwkaddrofinterest: 0, endpoint }, simpleDescRsp.ID);
|
|
143
|
-
const simpleDesc = await simpleDescRsp.start().promise;
|
|
144
|
-
endpoints.push({
|
|
145
|
-
ID: simpleDesc.payload.endpoint,
|
|
146
|
-
profileID: simpleDesc.payload.profileid,
|
|
147
|
-
deviceID: simpleDesc.payload.deviceid,
|
|
148
|
-
inputClusters: simpleDesc.payload.inclusterlist,
|
|
149
|
-
outputClusters: simpleDesc.payload.outclusterlist,
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
return {
|
|
153
|
-
networkAddress: 0,
|
|
154
|
-
manufacturerID: 0,
|
|
155
|
-
ieeeAddr: deviceInfo.payload.ieeeaddr,
|
|
156
|
-
endpoints,
|
|
157
|
-
};
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
async permitJoin(seconds, networkAddress) {
|
|
161
|
-
const addrmode = networkAddress === null ? 0x0F : 0x02;
|
|
162
|
-
const dstaddr = networkAddress || 0xFFFC;
|
|
163
|
-
await this.queue.execute(async () => {
|
|
164
|
-
this.checkInterpanLock();
|
|
165
|
-
const payload = { addrmode, dstaddr, duration: seconds, tcsignificance: 0 };
|
|
166
|
-
await this.znp.request(Subsystem.ZDO, 'mgmtPermitJoinReq', payload);
|
|
167
|
-
await this.setLED(seconds == 0 ? 'off' : 'on');
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
async getCoordinatorVersion() {
|
|
171
|
-
return { type: tstype_1.ZnpVersion[this.version.product], meta: this.version };
|
|
172
|
-
}
|
|
173
|
-
async reset(type) {
|
|
174
|
-
if (type === 'soft') {
|
|
175
|
-
await this.znp.request(Subsystem.SYS, 'resetReq', { type: Constants.SYS.resetType.SOFT });
|
|
176
|
-
}
|
|
177
|
-
else {
|
|
178
|
-
await this.znp.request(Subsystem.SYS, 'resetReq', { type: Constants.SYS.resetType.HARD });
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
async setLED(action) {
|
|
182
|
-
if (this.supportsLED == null) {
|
|
183
|
-
// Only zStack3x0 with 20210430 and greater support LED
|
|
184
|
-
const zStack3x0 = this.version.product === tstype_1.ZnpVersion.zStack3x0;
|
|
185
|
-
this.supportsLED = !zStack3x0 || (zStack3x0 && parseInt(this.version.revision) >= 20210430);
|
|
186
|
-
}
|
|
187
|
-
if (!this.supportsLED || (this.adapterOptions.disableLED && action !== 'disable')) {
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
// Firmwares build on and after 20211029 should handle LED themselves
|
|
191
|
-
const firmwareControlsLed = parseInt(this.version.revision) >= 20211029;
|
|
192
|
-
const lookup = {
|
|
193
|
-
'disable': firmwareControlsLed ? { ledid: 0xFF, mode: 5 } : { ledid: 3, mode: 0 },
|
|
194
|
-
'on': firmwareControlsLed ? null : { ledid: 3, mode: 1 },
|
|
195
|
-
'off': firmwareControlsLed ? null : { ledid: 3, mode: 0 },
|
|
196
|
-
};
|
|
197
|
-
const payload = lookup[action];
|
|
198
|
-
if (payload) {
|
|
199
|
-
this.znp.request(Subsystem.UTIL, 'ledControl', payload, null, 500).catch(() => {
|
|
200
|
-
// We cannot 100% correctly determine if an adapter supports LED. E.g. the zStack 1.2 20190608
|
|
201
|
-
// fw supports led on the CC2531 but not on the CC2530. Therefore if a led request fails never thrown
|
|
202
|
-
// an error but instead mark the led as unsupported.
|
|
203
|
-
// https://github.com/Koenkk/zigbee-herdsman/issues/377
|
|
204
|
-
// https://github.com/Koenkk/zigbee2mqtt/issues/7693
|
|
205
|
-
this.supportsLED = false;
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
async requestNetworkAddress(ieeeAddr) {
|
|
210
|
-
/**
|
|
211
|
-
* NOTE: There are cases where multiple nwkAddrRsp are recevied with different network addresses,
|
|
212
|
-
* this is currently not handled, the first nwkAddrRsp is taken.
|
|
213
|
-
*/
|
|
214
|
-
debug("Request network address of '%s'", ieeeAddr);
|
|
215
|
-
const response = this.znp.waitFor(unpi_1.Constants.Type.AREQ, Subsystem.ZDO, 'nwkAddrRsp', { ieeeaddr: ieeeAddr });
|
|
216
|
-
await this.znp.request(Subsystem.ZDO, 'nwkAddrReq', { ieeeaddr: ieeeAddr, reqtype: 0, startindex: 0 });
|
|
217
|
-
const result = await response.start().promise;
|
|
218
|
-
return result.payload.nwkaddr;
|
|
219
|
-
}
|
|
220
|
-
supportsAssocRemove() {
|
|
221
|
-
return this.version.product === tstype_1.ZnpVersion.zStack3x0 && parseInt(this.version.revision) >= 20200805;
|
|
222
|
-
}
|
|
223
|
-
supportsAssocAdd() {
|
|
224
|
-
return this.version.product === tstype_1.ZnpVersion.zStack3x0 && parseInt(this.version.revision) >= 20201026;
|
|
225
|
-
}
|
|
226
|
-
async discoverRoute(networkAddress, wait = true) {
|
|
227
|
-
debug('Discovering route to %d', networkAddress);
|
|
228
|
-
const payload = { dstAddr: networkAddress, options: 0, radius: Constants.AF.DEFAULT_RADIUS };
|
|
229
|
-
await this.znp.request(Subsystem.ZDO, 'extRouteDisc', payload);
|
|
230
|
-
if (wait) {
|
|
231
|
-
await (0, utils_1.Wait)(3000);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
async nodeDescriptor(networkAddress) {
|
|
235
|
-
return this.queue.execute(async () => {
|
|
236
|
-
this.checkInterpanLock();
|
|
237
|
-
try {
|
|
238
|
-
const result = await this.nodeDescriptorInternal(networkAddress);
|
|
239
|
-
return result;
|
|
240
|
-
}
|
|
241
|
-
catch (error) {
|
|
242
|
-
debug(`Node descriptor request for '${networkAddress}' failed (${error}), retry`);
|
|
243
|
-
// Doing a route discovery after simple descriptor request fails makes it succeed sometimes.
|
|
244
|
-
// https://github.com/Koenkk/zigbee2mqtt/issues/3276
|
|
245
|
-
await this.discoverRoute(networkAddress);
|
|
246
|
-
const result = await this.nodeDescriptorInternal(networkAddress);
|
|
247
|
-
return result;
|
|
248
|
-
}
|
|
249
|
-
}, networkAddress);
|
|
250
|
-
}
|
|
251
|
-
async nodeDescriptorInternal(networkAddress) {
|
|
252
|
-
const response = this.znp.waitFor(Type.AREQ, Subsystem.ZDO, 'nodeDescRsp', { nwkaddr: networkAddress });
|
|
253
|
-
const payload = { dstaddr: networkAddress, nwkaddrofinterest: networkAddress };
|
|
254
|
-
await this.znp.request(Subsystem.ZDO, 'nodeDescReq', payload, response.ID);
|
|
255
|
-
const descriptor = await response.start().promise;
|
|
256
|
-
let type = 'Unknown';
|
|
257
|
-
const logicalType = descriptor.payload.logicaltype_cmplxdescavai_userdescavai & 0x07;
|
|
258
|
-
for (const [key, value] of Object.entries(Constants.ZDO.deviceLogicalType)) {
|
|
259
|
-
if (value === logicalType) {
|
|
260
|
-
if (key === 'COORDINATOR')
|
|
261
|
-
type = 'Coordinator';
|
|
262
|
-
else if (key === 'ROUTER')
|
|
263
|
-
type = 'Router';
|
|
264
|
-
else if (key === 'ENDDEVICE')
|
|
265
|
-
type = 'EndDevice';
|
|
266
|
-
break;
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
return { manufacturerCode: descriptor.payload.manufacturercode, type };
|
|
270
|
-
}
|
|
271
|
-
async activeEndpoints(networkAddress) {
|
|
272
|
-
return this.queue.execute(async () => {
|
|
273
|
-
this.checkInterpanLock();
|
|
274
|
-
const response = this.znp.waitFor(Type.AREQ, Subsystem.ZDO, 'activeEpRsp', { nwkaddr: networkAddress });
|
|
275
|
-
const payload = { dstaddr: networkAddress, nwkaddrofinterest: networkAddress };
|
|
276
|
-
await this.znp.request(Subsystem.ZDO, 'activeEpReq', payload, response.ID);
|
|
277
|
-
const activeEp = await response.start().promise;
|
|
278
|
-
return { endpoints: activeEp.payload.activeeplist };
|
|
279
|
-
}, networkAddress);
|
|
280
|
-
}
|
|
281
|
-
async simpleDescriptor(networkAddress, endpointID) {
|
|
282
|
-
return this.queue.execute(async () => {
|
|
283
|
-
this.checkInterpanLock();
|
|
284
|
-
const responsePayload = { nwkaddr: networkAddress, endpoint: endpointID };
|
|
285
|
-
const response = this.znp.waitFor(Type.AREQ, Subsystem.ZDO, 'simpleDescRsp', responsePayload);
|
|
286
|
-
const payload = { dstaddr: networkAddress, nwkaddrofinterest: networkAddress, endpoint: endpointID };
|
|
287
|
-
await this.znp.request(Subsystem.ZDO, 'simpleDescReq', payload, response.ID);
|
|
288
|
-
const descriptor = await response.start().promise;
|
|
289
|
-
return {
|
|
290
|
-
profileID: descriptor.payload.profileid,
|
|
291
|
-
endpointID: descriptor.payload.endpoint,
|
|
292
|
-
deviceID: descriptor.payload.deviceid,
|
|
293
|
-
inputClusters: descriptor.payload.inclusterlist,
|
|
294
|
-
outputClusters: descriptor.payload.outclusterlist,
|
|
295
|
-
};
|
|
296
|
-
}, networkAddress);
|
|
297
|
-
}
|
|
298
|
-
async sendZclFrameToEndpoint(ieeeAddr, networkAddress, endpoint, zclFrame, timeout, disableResponse, disableRecovery, sourceEndpoint) {
|
|
299
|
-
return this.queue.execute(async () => {
|
|
300
|
-
this.checkInterpanLock();
|
|
301
|
-
return this.sendZclFrameToEndpointInternal(ieeeAddr, networkAddress, endpoint, sourceEndpoint || 1, zclFrame, timeout, disableResponse, disableRecovery, 0, 0, false, false, false, null);
|
|
302
|
-
}, networkAddress);
|
|
303
|
-
}
|
|
304
|
-
async sendZclFrameToEndpointInternal(ieeeAddr, networkAddress, endpoint, sourceEndpoint, zclFrame, timeout, disableResponse, disableRecovery, responseAttempt, dataRequestAttempt, checkedNetworkAddress, discoveredRoute, assocRemove, assocRestore) {
|
|
305
|
-
debug('sendZclFrameToEndpointInternal %s:%i/%i (%i,%i,%i)', ieeeAddr, networkAddress, endpoint, responseAttempt, dataRequestAttempt, this.queue.count());
|
|
306
|
-
let response = null;
|
|
307
|
-
const command = zclFrame.getCommand();
|
|
308
|
-
if (command.hasOwnProperty('response') && disableResponse === false) {
|
|
309
|
-
response = this.waitForInternal(networkAddress, endpoint, zclFrame.Header.frameControl.frameType, zcl_1.Direction.SERVER_TO_CLIENT, zclFrame.Header.transactionSequenceNumber, zclFrame.Cluster.ID, command.response, timeout);
|
|
310
|
-
}
|
|
311
|
-
else if (!zclFrame.Header.frameControl.disableDefaultResponse) {
|
|
312
|
-
response = this.waitForInternal(networkAddress, endpoint, zcl_1.FrameType.GLOBAL, zcl_1.Direction.SERVER_TO_CLIENT, zclFrame.Header.transactionSequenceNumber, zclFrame.Cluster.ID, zcl_1.Foundation.defaultRsp.ID, timeout);
|
|
313
|
-
}
|
|
314
|
-
const dataConfirmResult = await this.dataRequest(networkAddress, endpoint, sourceEndpoint, zclFrame.Cluster.ID, Constants.AF.DEFAULT_RADIUS, zclFrame.toBuffer(), timeout);
|
|
315
|
-
if (dataConfirmResult !== ZnpCommandStatus.SUCCESS) {
|
|
316
|
-
// In case dataConfirm timesout (= null) or gives an error, try to recover
|
|
317
|
-
debug('Data confirm error (%s:%d,%d,%d)', ieeeAddr, networkAddress, dataConfirmResult, dataRequestAttempt);
|
|
318
|
-
if (response !== null)
|
|
319
|
-
response.cancel();
|
|
320
|
-
/**
|
|
321
|
-
* In case we did an assocRemove in the previous attempt and it still fails after this, assume that the
|
|
322
|
-
* coordinator is still the parent of the device (but for some reason the device is not available now).
|
|
323
|
-
* Re-add the device to the assoc table, otherwise we will never be able to reach it anymore.
|
|
324
|
-
*/
|
|
325
|
-
if (assocRemove && assocRestore && this.supportsAssocAdd()) {
|
|
326
|
-
debug('assocAdd(%s)', assocRestore.ieeeadr);
|
|
327
|
-
await this.znp.request(Subsystem.UTIL, 'assocAdd', assocRestore);
|
|
328
|
-
assocRestore = null;
|
|
329
|
-
}
|
|
330
|
-
const recoverableErrors = [
|
|
331
|
-
ZnpCommandStatus.NWK_NO_ROUTE, ZnpCommandStatus.MAC_NO_ACK, ZnpCommandStatus.MAC_CHANNEL_ACCESS_FAILURE,
|
|
332
|
-
ZnpCommandStatus.MAC_TRANSACTION_EXPIRED, ZnpCommandStatus.BUFFER_FULL,
|
|
333
|
-
ZnpCommandStatus.MAC_NO_RESOURCES,
|
|
334
|
-
];
|
|
335
|
-
if (dataRequestAttempt >= 4 || !recoverableErrors.includes(dataConfirmResult) || disableRecovery) {
|
|
336
|
-
throw new DataConfirmError(dataConfirmResult);
|
|
337
|
-
}
|
|
338
|
-
if (dataConfirmResult === ZnpCommandStatus.MAC_CHANNEL_ACCESS_FAILURE ||
|
|
339
|
-
dataConfirmResult === ZnpCommandStatus.BUFFER_FULL ||
|
|
340
|
-
dataConfirmResult === ZnpCommandStatus.MAC_NO_RESOURCES) {
|
|
341
|
-
/**
|
|
342
|
-
* MAC_CHANNEL_ACCESS_FAILURE: When many commands at once are executed we can end up in a MAC
|
|
343
|
-
* channel access failure error. This is because there is too much traffic on the network.
|
|
344
|
-
* Retry this command once after a cooling down period.
|
|
345
|
-
* BUFFER_FULL: When many commands are executed at once the buffer can get full, wait
|
|
346
|
-
* some time and retry.
|
|
347
|
-
* MAC_NO_RESOURCES: Operation could not be completed because no memory resources are available,
|
|
348
|
-
* wait some time and retry.
|
|
349
|
-
*/
|
|
350
|
-
await (0, utils_1.Wait)(2000);
|
|
351
|
-
return this.sendZclFrameToEndpointInternal(ieeeAddr, networkAddress, endpoint, sourceEndpoint, zclFrame, timeout, disableResponse, disableRecovery, responseAttempt, dataRequestAttempt + 1, checkedNetworkAddress, discoveredRoute, assocRemove, assocRestore);
|
|
352
|
-
}
|
|
353
|
-
else {
|
|
354
|
-
let doAssocRemove = false;
|
|
355
|
-
if (!assocRemove && dataConfirmResult === ZnpCommandStatus.MAC_TRANSACTION_EXPIRED &&
|
|
356
|
-
dataRequestAttempt >= 1 && this.supportsAssocRemove()) {
|
|
357
|
-
const match = await this.znp.request(Subsystem.UTIL, 'assocGetWithAddress', { extaddr: ieeeAddr, nwkaddr: networkAddress });
|
|
358
|
-
if (match.payload.nwkaddr !== 0xFFFE && match.payload.noderelation !== 255) {
|
|
359
|
-
doAssocRemove = true;
|
|
360
|
-
assocRestore =
|
|
361
|
-
{ ieeeadr: ieeeAddr, nwkaddr: networkAddress, noderelation: match.payload.noderelation };
|
|
362
|
-
}
|
|
363
|
-
assocRemove = true;
|
|
364
|
-
}
|
|
365
|
-
// NWK_NO_ROUTE: no network route => rediscover route
|
|
366
|
-
// MAC_NO_ACK: route may be corrupted
|
|
367
|
-
// MAC_TRANSACTION_EXPIRED: Mac layer is sleeping
|
|
368
|
-
if (doAssocRemove) {
|
|
369
|
-
/**
|
|
370
|
-
* Since child aging is disabled on the firmware, when a end device is directly connected
|
|
371
|
-
* to the coordinator and changes parent and the coordinator does not recevie this update,
|
|
372
|
-
* it still thinks it's directly connected.
|
|
373
|
-
* A discoverRoute() is not send out in this case, therefore remove it from the associated device
|
|
374
|
-
* list and try again.
|
|
375
|
-
* Note: assocRemove is a custom command, not available by default, only available on recent
|
|
376
|
-
* z-stack-firmware firmware version. In case it's not supported by the coordinator we will
|
|
377
|
-
* automatically timeout after 60000ms.
|
|
378
|
-
*/
|
|
379
|
-
debug('assocRemove(%s)', ieeeAddr);
|
|
380
|
-
await this.znp.request(Subsystem.UTIL, 'assocRemove', { ieeeadr: ieeeAddr });
|
|
381
|
-
}
|
|
382
|
-
else if (!discoveredRoute && dataRequestAttempt >= 1) {
|
|
383
|
-
discoveredRoute = true;
|
|
384
|
-
await this.discoverRoute(networkAddress);
|
|
385
|
-
}
|
|
386
|
-
else if (!checkedNetworkAddress && dataRequestAttempt >= 1) {
|
|
387
|
-
// Figure out once if the network address has been changed.
|
|
388
|
-
try {
|
|
389
|
-
checkedNetworkAddress = true;
|
|
390
|
-
const actualNetworkAddress = await this.requestNetworkAddress(ieeeAddr);
|
|
391
|
-
if (networkAddress !== actualNetworkAddress) {
|
|
392
|
-
debug(`Failed because request was done with wrong network address`);
|
|
393
|
-
discoveredRoute = true;
|
|
394
|
-
networkAddress = actualNetworkAddress;
|
|
395
|
-
await this.discoverRoute(actualNetworkAddress);
|
|
396
|
-
}
|
|
397
|
-
else {
|
|
398
|
-
debug('Network address did not change');
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
catch (_a) { }
|
|
402
|
-
}
|
|
403
|
-
else {
|
|
404
|
-
debug('Wait 2000ms');
|
|
405
|
-
await (0, utils_1.Wait)(2000);
|
|
406
|
-
}
|
|
407
|
-
return this.sendZclFrameToEndpointInternal(ieeeAddr, networkAddress, endpoint, sourceEndpoint, zclFrame, timeout, disableResponse, disableRecovery, responseAttempt, dataRequestAttempt + 1, checkedNetworkAddress, discoveredRoute, assocRemove, assocRestore);
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
if (response !== null) {
|
|
411
|
-
try {
|
|
412
|
-
const result = await response.start().promise;
|
|
413
|
-
return result;
|
|
414
|
-
}
|
|
415
|
-
catch (error) {
|
|
416
|
-
debug('Response timeout (%s:%d,%d)', ieeeAddr, networkAddress, responseAttempt);
|
|
417
|
-
if (responseAttempt < 1 && !disableRecovery) {
|
|
418
|
-
// No response could be because the radio of the end device is turned off:
|
|
419
|
-
// Sometimes the coordinator does not properly set the PENDING flag.
|
|
420
|
-
// Try to rewrite the device entry in the association table, this fixes it sometimes.
|
|
421
|
-
const match = await this.znp.request(Subsystem.UTIL, 'assocGetWithAddress', { extaddr: ieeeAddr, nwkaddr: networkAddress });
|
|
422
|
-
debug(`Response timeout recovery: Node relation ${match.payload.noderelation} (${ieeeAddr} / ${match.payload.nwkaddr})`);
|
|
423
|
-
if (this.supportsAssocAdd() && this.supportsAssocRemove() &&
|
|
424
|
-
match.payload.nwkaddr !== 0xFFFE && match.payload.noderelation == 1) {
|
|
425
|
-
debug(`Response timeout recovery: Rewrite association table entry (${ieeeAddr})`);
|
|
426
|
-
await this.znp.request(Subsystem.UTIL, 'assocRemove', { ieeeadr: ieeeAddr });
|
|
427
|
-
await this.znp.request(Subsystem.UTIL, 'assocAdd', { ieeeadr: ieeeAddr, nwkaddr: networkAddress, noderelation: match.payload.noderelation });
|
|
428
|
-
}
|
|
429
|
-
// No response could be of invalid route, e.g. when message is send to wrong parent of end device.
|
|
430
|
-
await this.discoverRoute(networkAddress);
|
|
431
|
-
return this.sendZclFrameToEndpointInternal(ieeeAddr, networkAddress, endpoint, sourceEndpoint, zclFrame, timeout, disableResponse, disableRecovery, responseAttempt + 1, dataRequestAttempt, checkedNetworkAddress, discoveredRoute, assocRemove, assocRestore);
|
|
432
|
-
}
|
|
433
|
-
else {
|
|
434
|
-
throw error;
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
else {
|
|
439
|
-
return null;
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
async sendZclFrameToGroup(groupID, zclFrame, sourceEndpoint) {
|
|
443
|
-
return this.queue.execute(async () => {
|
|
444
|
-
this.checkInterpanLock();
|
|
445
|
-
await this.dataRequestExtended(AddressMode.ADDR_GROUP, groupID, 0xFF, 0, sourceEndpoint || 1, zclFrame.Cluster.ID, Constants.AF.DEFAULT_RADIUS, zclFrame.toBuffer(), 3000, true);
|
|
446
|
-
/**
|
|
447
|
-
* As a group command is not confirmed and thus immidiately returns
|
|
448
|
-
* (contrary to network address requests) we will give the
|
|
449
|
-
* command some time to 'settle' in the network.
|
|
450
|
-
*/
|
|
451
|
-
await (0, utils_1.Wait)(200);
|
|
452
|
-
});
|
|
453
|
-
}
|
|
454
|
-
async sendZclFrameToAll(endpoint, zclFrame, sourceEndpoint) {
|
|
455
|
-
return this.queue.execute(async () => {
|
|
456
|
-
this.checkInterpanLock();
|
|
457
|
-
await this.dataRequestExtended(AddressMode.ADDR_16BIT, 0xFFFD, endpoint, 0, sourceEndpoint, zclFrame.Cluster.ID, Constants.AF.DEFAULT_RADIUS, zclFrame.toBuffer(), 3000, false, 0);
|
|
458
|
-
/**
|
|
459
|
-
* As a broadcast command is not confirmed and thus immidiately returns
|
|
460
|
-
* (contrary to network address requests) we will give the
|
|
461
|
-
* command some time to 'settle' in the network.
|
|
462
|
-
*/
|
|
463
|
-
await (0, utils_1.Wait)(200);
|
|
464
|
-
});
|
|
465
|
-
}
|
|
466
|
-
async lqi(networkAddress) {
|
|
467
|
-
return this.queue.execute(async () => {
|
|
468
|
-
this.checkInterpanLock();
|
|
469
|
-
const neighbors = [];
|
|
470
|
-
// eslint-disable-next-line
|
|
471
|
-
const request = async (startIndex) => {
|
|
472
|
-
const response = this.znp.waitFor(Type.AREQ, Subsystem.ZDO, 'mgmtLqiRsp', { srcaddr: networkAddress });
|
|
473
|
-
await this.znp.request(Subsystem.ZDO, 'mgmtLqiReq', { dstaddr: networkAddress, startindex: startIndex }, response.ID);
|
|
474
|
-
const result = await response.start().promise;
|
|
475
|
-
if (result.payload.status !== ZnpCommandStatus.SUCCESS) {
|
|
476
|
-
throw new Error(`LQI for '${networkAddress}' failed with error: '${ZnpCommandStatus[result.payload.status]}' (${result.payload.status})`);
|
|
477
|
-
}
|
|
478
|
-
return result;
|
|
479
|
-
};
|
|
480
|
-
// eslint-disable-next-line
|
|
481
|
-
const add = (list) => {
|
|
482
|
-
for (const entry of list) {
|
|
483
|
-
neighbors.push({
|
|
484
|
-
linkquality: entry.lqi,
|
|
485
|
-
networkAddress: entry.nwkAddr,
|
|
486
|
-
ieeeAddr: entry.extAddr,
|
|
487
|
-
relationship: entry.relationship,
|
|
488
|
-
depth: entry.depth,
|
|
489
|
-
});
|
|
490
|
-
}
|
|
491
|
-
};
|
|
492
|
-
let response = await request(0);
|
|
493
|
-
add(response.payload.neighborlqilist);
|
|
494
|
-
const size = response.payload.neighbortableentries;
|
|
495
|
-
let nextStartIndex = response.payload.neighborlqilist.length;
|
|
496
|
-
while (neighbors.length < size) {
|
|
497
|
-
response = await request(nextStartIndex);
|
|
498
|
-
add(response.payload.neighborlqilist);
|
|
499
|
-
nextStartIndex += response.payload.neighborlqilist.length;
|
|
500
|
-
}
|
|
501
|
-
return { neighbors };
|
|
502
|
-
}, networkAddress);
|
|
503
|
-
}
|
|
504
|
-
async routingTable(networkAddress) {
|
|
505
|
-
return this.queue.execute(async () => {
|
|
506
|
-
this.checkInterpanLock();
|
|
507
|
-
const table = [];
|
|
508
|
-
// eslint-disable-next-line
|
|
509
|
-
const request = async (startIndex) => {
|
|
510
|
-
const response = this.znp.waitFor(Type.AREQ, Subsystem.ZDO, 'mgmtRtgRsp', { srcaddr: networkAddress });
|
|
511
|
-
await this.znp.request(Subsystem.ZDO, 'mgmtRtgReq', { dstaddr: networkAddress, startindex: startIndex }, response.ID);
|
|
512
|
-
const result = await response.start().promise;
|
|
513
|
-
if (result.payload.status !== ZnpCommandStatus.SUCCESS) {
|
|
514
|
-
throw new Error(`Routing table for '${networkAddress}' failed with error: '${ZnpCommandStatus[result.payload.status]}' (${result.payload.status})`);
|
|
515
|
-
}
|
|
516
|
-
return result;
|
|
517
|
-
};
|
|
518
|
-
// eslint-disable-next-line
|
|
519
|
-
const add = (list) => {
|
|
520
|
-
for (const entry of list) {
|
|
521
|
-
table.push({
|
|
522
|
-
destinationAddress: entry.destNwkAddr,
|
|
523
|
-
status: entry.routeStatus,
|
|
524
|
-
nextHop: entry.nextHopNwkAddr,
|
|
525
|
-
});
|
|
526
|
-
}
|
|
527
|
-
};
|
|
528
|
-
let response = await request(0);
|
|
529
|
-
add(response.payload.routingtablelist);
|
|
530
|
-
const size = response.payload.routingtableentries;
|
|
531
|
-
let nextStartIndex = response.payload.routingtablelist.length;
|
|
532
|
-
while (table.length < size) {
|
|
533
|
-
response = await request(nextStartIndex);
|
|
534
|
-
add(response.payload.routingtablelist);
|
|
535
|
-
nextStartIndex += response.payload.routingtablelist.length;
|
|
536
|
-
}
|
|
537
|
-
return { table };
|
|
538
|
-
}, networkAddress);
|
|
539
|
-
}
|
|
540
|
-
async addInstallCode(ieeeAddress, key) {
|
|
541
|
-
(0, assert_1.default)(this.version.product !== tstype_1.ZnpVersion.zStack12, 'Install code is not supported for ZStack 1.2 adapter');
|
|
542
|
-
const payload = { installCodeFormat: key.length === 18 ? 1 : 2, ieeeaddr: ieeeAddress, installCode: key };
|
|
543
|
-
await this.znp.request(Subsystem.APP_CNF, 'bdbAddInstallCode', payload);
|
|
544
|
-
}
|
|
545
|
-
async bind(destinationNetworkAddress, sourceIeeeAddress, sourceEndpoint, clusterID, destinationAddressOrGroup, type, destinationEndpoint) {
|
|
546
|
-
return this.queue.execute(async () => {
|
|
547
|
-
this.checkInterpanLock();
|
|
548
|
-
const responsePayload = { srcaddr: destinationNetworkAddress };
|
|
549
|
-
const response = this.znp.waitFor(Type.AREQ, Subsystem.ZDO, 'bindRsp', responsePayload);
|
|
550
|
-
const payload = {
|
|
551
|
-
dstaddr: destinationNetworkAddress,
|
|
552
|
-
srcaddr: sourceIeeeAddress,
|
|
553
|
-
srcendpoint: sourceEndpoint,
|
|
554
|
-
clusterid: clusterID,
|
|
555
|
-
dstaddrmode: type === 'group' ?
|
|
556
|
-
AddressMode.ADDR_GROUP : AddressMode.ADDR_64BIT,
|
|
557
|
-
dstaddress: this.toAddressString(destinationAddressOrGroup),
|
|
558
|
-
dstendpoint: type === 'group' ? 0xFF : destinationEndpoint,
|
|
559
|
-
};
|
|
560
|
-
await this.znp.request(Subsystem.ZDO, 'bindReq', payload, response.ID);
|
|
561
|
-
await response.start().promise;
|
|
562
|
-
}, destinationNetworkAddress);
|
|
563
|
-
}
|
|
564
|
-
async unbind(destinationNetworkAddress, sourceIeeeAddress, sourceEndpoint, clusterID, destinationAddressOrGroup, type, destinationEndpoint) {
|
|
565
|
-
return this.queue.execute(async () => {
|
|
566
|
-
this.checkInterpanLock();
|
|
567
|
-
const response = this.znp.waitFor(Type.AREQ, Subsystem.ZDO, 'unbindRsp', { srcaddr: destinationNetworkAddress });
|
|
568
|
-
const payload = {
|
|
569
|
-
dstaddr: destinationNetworkAddress,
|
|
570
|
-
srcaddr: sourceIeeeAddress,
|
|
571
|
-
srcendpoint: sourceEndpoint,
|
|
572
|
-
clusterid: clusterID,
|
|
573
|
-
dstaddrmode: type === 'group' ?
|
|
574
|
-
AddressMode.ADDR_GROUP : AddressMode.ADDR_64BIT,
|
|
575
|
-
dstaddress: this.toAddressString(destinationAddressOrGroup),
|
|
576
|
-
dstendpoint: type === 'group' ? 0xFF : destinationEndpoint,
|
|
577
|
-
};
|
|
578
|
-
await this.znp.request(Subsystem.ZDO, 'unbindReq', payload, response.ID);
|
|
579
|
-
await response.start().promise;
|
|
580
|
-
}, destinationNetworkAddress);
|
|
581
|
-
}
|
|
582
|
-
removeDevice(networkAddress, ieeeAddr) {
|
|
583
|
-
return this.queue.execute(async () => {
|
|
584
|
-
this.checkInterpanLock();
|
|
585
|
-
const response = this.znp.waitFor(unpi_1.Constants.Type.AREQ, Subsystem.ZDO, 'mgmtLeaveRsp', { srcaddr: networkAddress });
|
|
586
|
-
const payload = {
|
|
587
|
-
dstaddr: networkAddress,
|
|
588
|
-
deviceaddress: ieeeAddr,
|
|
589
|
-
removechildrenRejoin: 0,
|
|
590
|
-
};
|
|
591
|
-
await this.znp.request(Subsystem.ZDO, 'mgmtLeaveReq', payload, response.ID);
|
|
592
|
-
await response.start().promise;
|
|
593
|
-
}, networkAddress);
|
|
594
|
-
}
|
|
595
|
-
/**
|
|
596
|
-
* Event handlers
|
|
597
|
-
*/
|
|
598
|
-
onZnpClose() {
|
|
599
|
-
if (!this.closing) {
|
|
600
|
-
this.emit(Events.Events.disconnected);
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
onZnpRecieved(object) {
|
|
604
|
-
if (object.type !== unpi_1.Constants.Type.AREQ) {
|
|
605
|
-
return;
|
|
606
|
-
}
|
|
607
|
-
if (object.subsystem === Subsystem.ZDO) {
|
|
608
|
-
if (object.command === 'tcDeviceInd') {
|
|
609
|
-
const payload = {
|
|
610
|
-
networkAddress: object.payload.nwkaddr,
|
|
611
|
-
ieeeAddr: object.payload.extaddr,
|
|
612
|
-
};
|
|
613
|
-
this.emit(Events.Events.deviceJoined, payload);
|
|
614
|
-
}
|
|
615
|
-
else if (object.command === 'endDeviceAnnceInd') {
|
|
616
|
-
const payload = {
|
|
617
|
-
networkAddress: object.payload.nwkaddr,
|
|
618
|
-
ieeeAddr: object.payload.ieeeaddr,
|
|
619
|
-
};
|
|
620
|
-
// Only discover routes to end devices, if bit 1 of capabilities === 0 it's an end device.
|
|
621
|
-
const isEndDevice = (object.payload.capabilities & 1 << 1) === 0;
|
|
622
|
-
if (isEndDevice) {
|
|
623
|
-
if (!this.deviceAnnounceRouteDiscoveryDebouncers.has(payload.networkAddress)) {
|
|
624
|
-
// If a device announces multiple times in a very short time, it makes no sense
|
|
625
|
-
// to rediscover the route every time.
|
|
626
|
-
const debouncer = (0, debounce_1.default)(() => {
|
|
627
|
-
this.queue.execute(async () => {
|
|
628
|
-
/* istanbul ignore next */
|
|
629
|
-
this.discoverRoute(payload.networkAddress, false).catch(() => { });
|
|
630
|
-
}, payload.networkAddress);
|
|
631
|
-
}, 60 * 1000, true);
|
|
632
|
-
this.deviceAnnounceRouteDiscoveryDebouncers.set(payload.networkAddress, debouncer);
|
|
633
|
-
}
|
|
634
|
-
this.deviceAnnounceRouteDiscoveryDebouncers.get(payload.networkAddress)();
|
|
635
|
-
}
|
|
636
|
-
this.emit(Events.Events.deviceAnnounce, payload);
|
|
637
|
-
}
|
|
638
|
-
else if (object.command === 'nwkAddrRsp') {
|
|
639
|
-
const payload = {
|
|
640
|
-
networkAddress: object.payload.nwkaddr,
|
|
641
|
-
ieeeAddr: object.payload.ieeeaddr,
|
|
642
|
-
};
|
|
643
|
-
this.emit(Events.Events.networkAddress, payload);
|
|
644
|
-
}
|
|
645
|
-
else {
|
|
646
|
-
/* istanbul ignore else */
|
|
647
|
-
if (object.command === 'leaveInd') {
|
|
648
|
-
const payload = {
|
|
649
|
-
networkAddress: object.payload.srcaddr,
|
|
650
|
-
ieeeAddr: object.payload.extaddr,
|
|
651
|
-
};
|
|
652
|
-
this.emit(Events.Events.deviceLeave, payload);
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
else {
|
|
657
|
-
/* istanbul ignore else */
|
|
658
|
-
if (object.subsystem === Subsystem.AF) {
|
|
659
|
-
/* istanbul ignore else */
|
|
660
|
-
if (object.command === 'incomingMsg' || object.command === 'incomingMsgExt') {
|
|
661
|
-
try {
|
|
662
|
-
const payload = {
|
|
663
|
-
frame: zcl_1.ZclFrame.fromBuffer(object.payload.clusterid, object.payload.data),
|
|
664
|
-
address: object.payload.srcaddr,
|
|
665
|
-
endpoint: object.payload.srcendpoint,
|
|
666
|
-
linkquality: object.payload.linkquality,
|
|
667
|
-
groupID: object.payload.groupid,
|
|
668
|
-
wasBroadcast: object.payload.wasbroadcast === 1,
|
|
669
|
-
destinationEndpoint: object.payload.dstendpoint,
|
|
670
|
-
};
|
|
671
|
-
this.waitress.resolve(payload);
|
|
672
|
-
this.emit(Events.Events.zclData, payload);
|
|
673
|
-
}
|
|
674
|
-
catch (error) {
|
|
675
|
-
const payload = {
|
|
676
|
-
clusterID: object.payload.clusterid,
|
|
677
|
-
data: object.payload.data,
|
|
678
|
-
address: object.payload.srcaddr,
|
|
679
|
-
endpoint: object.payload.srcendpoint,
|
|
680
|
-
linkquality: object.payload.linkquality,
|
|
681
|
-
groupID: object.payload.groupid,
|
|
682
|
-
wasBroadcast: object.payload.wasbroadcast === 1,
|
|
683
|
-
destinationEndpoint: object.payload.dstendpoint,
|
|
684
|
-
};
|
|
685
|
-
this.emit(Events.Events.rawData, payload);
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
async getNetworkParameters() {
|
|
692
|
-
const result = await this.znp.request(Subsystem.ZDO, 'extNwkInfo', {});
|
|
693
|
-
return {
|
|
694
|
-
panID: result.payload.panid, extendedPanID: result.payload.extendedpanid,
|
|
695
|
-
channel: result.payload.channel
|
|
696
|
-
};
|
|
697
|
-
}
|
|
698
|
-
async supportsBackup() {
|
|
699
|
-
return true;
|
|
700
|
-
}
|
|
701
|
-
async backup() {
|
|
702
|
-
return this.adapterManager.backup.createBackup();
|
|
703
|
-
}
|
|
704
|
-
async setChannelInterPAN(channel) {
|
|
705
|
-
return this.queue.execute(async () => {
|
|
706
|
-
this.interpanLock = true;
|
|
707
|
-
await this.znp.request(Subsystem.AF, 'interPanCtl', { cmd: 1, data: [channel] });
|
|
708
|
-
if (!this.interpanEndpointRegistered) {
|
|
709
|
-
// Make sure that endpoint 12 is registered to proxy the InterPAN messages.
|
|
710
|
-
await this.znp.request(Subsystem.AF, 'interPanCtl', { cmd: 2, data: [12] });
|
|
711
|
-
this.interpanEndpointRegistered = true;
|
|
712
|
-
}
|
|
713
|
-
});
|
|
714
|
-
}
|
|
715
|
-
async sendZclFrameInterPANToIeeeAddr(zclFrame, ieeeAddr) {
|
|
716
|
-
return this.queue.execute(async () => {
|
|
717
|
-
await this.dataRequestExtended(AddressMode.ADDR_64BIT, ieeeAddr, 0xFE, 0xFFFF, 12, zclFrame.Cluster.ID, 30, zclFrame.toBuffer(), 10000, false);
|
|
718
|
-
});
|
|
719
|
-
}
|
|
720
|
-
async sendZclFrameInterPANBroadcast(zclFrame, timeout) {
|
|
721
|
-
return this.queue.execute(async () => {
|
|
722
|
-
const command = zclFrame.getCommand();
|
|
723
|
-
if (!command.hasOwnProperty('response')) {
|
|
724
|
-
throw new Error(`Command '${command.name}' has no response, cannot wait for response`);
|
|
725
|
-
}
|
|
726
|
-
const response = this.waitForInternal(null, 0xFE, zclFrame.Header.frameControl.frameType, zcl_1.Direction.SERVER_TO_CLIENT, null, zclFrame.Cluster.ID, command.response, timeout);
|
|
727
|
-
try {
|
|
728
|
-
await this.dataRequestExtended(AddressMode.ADDR_16BIT, 0xFFFF, 0xFE, 0xFFFF, 12, zclFrame.Cluster.ID, 30, zclFrame.toBuffer(), 10000, false);
|
|
729
|
-
}
|
|
730
|
-
catch (error) {
|
|
731
|
-
response.cancel();
|
|
732
|
-
throw error;
|
|
733
|
-
}
|
|
734
|
-
return response.start().promise;
|
|
735
|
-
});
|
|
736
|
-
}
|
|
737
|
-
async restoreChannelInterPAN() {
|
|
738
|
-
return this.queue.execute(async () => {
|
|
739
|
-
await this.znp.request(Subsystem.AF, 'interPanCtl', { cmd: 0, data: [] });
|
|
740
|
-
// Give adapter some time to restore, otherwise stuff crashes
|
|
741
|
-
await (0, utils_1.Wait)(3000);
|
|
742
|
-
this.interpanLock = false;
|
|
743
|
-
});
|
|
744
|
-
}
|
|
745
|
-
async setTransmitPower(value) {
|
|
746
|
-
return this.queue.execute(async () => {
|
|
747
|
-
await this.znp.request(Subsystem.SYS, 'stackTune', { operation: 0, value });
|
|
748
|
-
});
|
|
749
|
-
}
|
|
750
|
-
waitForInternal(networkAddress, endpoint, frameType, direction, transactionSequenceNumber, clusterID, commandIdentifier, timeout) {
|
|
751
|
-
const payload = {
|
|
752
|
-
address: networkAddress, endpoint, clusterID, commandIdentifier, frameType, direction,
|
|
753
|
-
transactionSequenceNumber,
|
|
754
|
-
};
|
|
755
|
-
const waiter = this.waitress.waitFor(payload, timeout);
|
|
756
|
-
const cancel = () => this.waitress.remove(waiter.ID);
|
|
757
|
-
return { start: waiter.start, cancel };
|
|
758
|
-
}
|
|
759
|
-
waitFor(networkAddress, endpoint, frameType, direction, transactionSequenceNumber, clusterID, commandIdentifier, timeout) {
|
|
760
|
-
const waiter = this.waitForInternal(networkAddress, endpoint, frameType, direction, transactionSequenceNumber, clusterID, commandIdentifier, timeout);
|
|
761
|
-
return { cancel: waiter.cancel, promise: waiter.start().promise };
|
|
762
|
-
}
|
|
763
|
-
/**
|
|
764
|
-
* Private methods
|
|
765
|
-
*/
|
|
766
|
-
async dataRequest(destinationAddress, destinationEndpoint, sourceEndpoint, clusterID, radius, data, timeout) {
|
|
767
|
-
const transactionID = this.nextTransactionID();
|
|
768
|
-
const response = this.znp.waitFor(Type.AREQ, Subsystem.AF, 'dataConfirm', { transid: transactionID }, timeout);
|
|
769
|
-
await this.znp.request(Subsystem.AF, 'dataRequest', {
|
|
770
|
-
dstaddr: destinationAddress,
|
|
771
|
-
destendpoint: destinationEndpoint,
|
|
772
|
-
srcendpoint: sourceEndpoint,
|
|
773
|
-
clusterid: clusterID,
|
|
774
|
-
transid: transactionID,
|
|
775
|
-
options: 0,
|
|
776
|
-
radius: radius,
|
|
777
|
-
len: data.length,
|
|
778
|
-
data: data,
|
|
779
|
-
}, response.ID);
|
|
780
|
-
let result = null;
|
|
781
|
-
try {
|
|
782
|
-
const dataConfirm = await response.start().promise;
|
|
783
|
-
result = dataConfirm.payload.status;
|
|
784
|
-
}
|
|
785
|
-
catch (_a) {
|
|
786
|
-
result = DataConfirmTimeout;
|
|
787
|
-
}
|
|
788
|
-
return result;
|
|
789
|
-
}
|
|
790
|
-
async dataRequestExtended(addressMode, destinationAddressOrGroupID, destinationEndpoint, panID, sourceEndpoint, clusterID, radius, data, timeout, confirmation, attemptsLeft = 5) {
|
|
791
|
-
const transactionID = this.nextTransactionID();
|
|
792
|
-
const response = confirmation ?
|
|
793
|
-
this.znp.waitFor(Type.AREQ, Subsystem.AF, 'dataConfirm', { transid: transactionID }, timeout) : null;
|
|
794
|
-
await this.znp.request(Subsystem.AF, 'dataRequestExt', {
|
|
795
|
-
dstaddrmode: addressMode,
|
|
796
|
-
dstaddr: this.toAddressString(destinationAddressOrGroupID),
|
|
797
|
-
destendpoint: destinationEndpoint,
|
|
798
|
-
dstpanid: panID,
|
|
799
|
-
srcendpoint: sourceEndpoint,
|
|
800
|
-
clusterid: clusterID,
|
|
801
|
-
transid: transactionID,
|
|
802
|
-
options: 0,
|
|
803
|
-
radius,
|
|
804
|
-
len: data.length,
|
|
805
|
-
data: data,
|
|
806
|
-
}, response ? response.ID : null);
|
|
807
|
-
if (confirmation) {
|
|
808
|
-
const dataConfirm = await response.start().promise;
|
|
809
|
-
if (dataConfirm.payload.status !== ZnpCommandStatus.SUCCESS) {
|
|
810
|
-
if (attemptsLeft > 0 &&
|
|
811
|
-
(dataConfirm.payload.status === ZnpCommandStatus.MAC_CHANNEL_ACCESS_FAILURE ||
|
|
812
|
-
dataConfirm.payload.status === ZnpCommandStatus.BUFFER_FULL)) {
|
|
813
|
-
/**
|
|
814
|
-
* 225: When many commands at once are executed we can end up in a MAC channel access failure
|
|
815
|
-
* error. This is because there is too much traffic on the network.
|
|
816
|
-
* Retry this command once after a cooling down period.
|
|
817
|
-
*/
|
|
818
|
-
await (0, utils_1.Wait)(2000);
|
|
819
|
-
return this.dataRequestExtended(addressMode, destinationAddressOrGroupID, destinationEndpoint, panID, sourceEndpoint, clusterID, radius, data, timeout, confirmation, attemptsLeft - 1);
|
|
820
|
-
}
|
|
821
|
-
else {
|
|
822
|
-
throw new DataConfirmError(dataConfirm.payload.status);
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
return dataConfirm;
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
nextTransactionID() {
|
|
829
|
-
this.transactionID++;
|
|
830
|
-
if (this.transactionID > 255) {
|
|
831
|
-
this.transactionID = 1;
|
|
832
|
-
}
|
|
833
|
-
return this.transactionID;
|
|
834
|
-
}
|
|
835
|
-
toAddressString(address) {
|
|
836
|
-
if (typeof address === 'number') {
|
|
837
|
-
let addressString = address.toString(16);
|
|
838
|
-
for (let i = addressString.length; i < 16; i++) {
|
|
839
|
-
addressString = '0' + addressString;
|
|
840
|
-
}
|
|
841
|
-
return `0x${addressString}`;
|
|
842
|
-
}
|
|
843
|
-
else {
|
|
844
|
-
return address.toString();
|
|
845
|
-
}
|
|
846
|
-
}
|
|
847
|
-
waitressTimeoutFormatter(matcher, timeout) {
|
|
848
|
-
return `Timeout - ${matcher.address} - ${matcher.endpoint}` +
|
|
849
|
-
` - ${matcher.transactionSequenceNumber} - ${matcher.clusterID}` +
|
|
850
|
-
` - ${matcher.commandIdentifier} after ${timeout}ms`;
|
|
851
|
-
}
|
|
852
|
-
waitressValidator(payload, matcher) {
|
|
853
|
-
const transactionSequenceNumber = payload.frame.Header.transactionSequenceNumber;
|
|
854
|
-
return (!matcher.address || payload.address === matcher.address) &&
|
|
855
|
-
payload.endpoint === matcher.endpoint &&
|
|
856
|
-
(!matcher.transactionSequenceNumber || transactionSequenceNumber === matcher.transactionSequenceNumber) &&
|
|
857
|
-
payload.frame.Cluster.ID === matcher.clusterID &&
|
|
858
|
-
matcher.frameType === payload.frame.Header.frameControl.frameType &&
|
|
859
|
-
matcher.commandIdentifier === payload.frame.Header.commandIdentifier &&
|
|
860
|
-
matcher.direction === payload.frame.Header.frameControl.direction;
|
|
861
|
-
}
|
|
862
|
-
checkInterpanLock() {
|
|
863
|
-
if (this.interpanLock) {
|
|
864
|
-
throw new Error(`Cannot execute command, in Inter-PAN mode`);
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
exports.default = ZStackAdapter;
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
const tstype_1 = require("./tstype");
|
|
30
|
+
const Events = __importStar(require("../../events"));
|
|
31
|
+
const adapter_1 = __importDefault(require("../../adapter"));
|
|
32
|
+
const znp_1 = require("../znp");
|
|
33
|
+
const unpi_1 = require("../unpi");
|
|
34
|
+
const zcl_1 = require("../../../zcl");
|
|
35
|
+
const utils_1 = require("../../../utils");
|
|
36
|
+
const Constants = __importStar(require("../constants"));
|
|
37
|
+
const debug_1 = __importDefault(require("debug"));
|
|
38
|
+
const debounce_1 = __importDefault(require("debounce"));
|
|
39
|
+
const manager_1 = require("./manager");
|
|
40
|
+
const assert_1 = __importDefault(require("assert"));
|
|
41
|
+
const debug = (0, debug_1.default)("zigbee-herdsman:adapter:zStack:adapter");
|
|
42
|
+
const Subsystem = unpi_1.Constants.Subsystem;
|
|
43
|
+
const Type = unpi_1.Constants.Type;
|
|
44
|
+
const { ZnpCommandStatus, AddressMode } = Constants.COMMON;
|
|
45
|
+
const DataConfirmTimeout = 9999; // Not an actual code
|
|
46
|
+
const DataConfirmErrorCodeLookup = {
|
|
47
|
+
[DataConfirmTimeout]: 'Timeout',
|
|
48
|
+
26: 'MAC no resources',
|
|
49
|
+
183: 'APS no ack',
|
|
50
|
+
205: 'No network route',
|
|
51
|
+
225: 'MAC channel access failure',
|
|
52
|
+
233: 'MAC no ack',
|
|
53
|
+
240: 'MAC transaction expired',
|
|
54
|
+
};
|
|
55
|
+
class DataConfirmError extends Error {
|
|
56
|
+
constructor(code) {
|
|
57
|
+
const message = `Data request failed with error: '${DataConfirmErrorCodeLookup[code]}' (${code})`;
|
|
58
|
+
super(message);
|
|
59
|
+
this.code = code;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
class ZStackAdapter extends adapter_1.default {
|
|
63
|
+
constructor(networkOptions, serialPortOptions, backupPath, adapterOptions, logger) {
|
|
64
|
+
super(networkOptions, serialPortOptions, backupPath, adapterOptions, logger);
|
|
65
|
+
this.supportsLED = null;
|
|
66
|
+
this.znp = new znp_1.Znp(this.serialPortOptions.path, this.serialPortOptions.baudRate, this.serialPortOptions.rtscts);
|
|
67
|
+
this.transactionID = 0;
|
|
68
|
+
this.deviceAnnounceRouteDiscoveryDebouncers = new Map();
|
|
69
|
+
this.interpanLock = false;
|
|
70
|
+
this.interpanEndpointRegistered = false;
|
|
71
|
+
this.closing = false;
|
|
72
|
+
this.waitress = new utils_1.Waitress(this.waitressValidator, this.waitressTimeoutFormatter);
|
|
73
|
+
this.znp.on('received', this.onZnpRecieved.bind(this));
|
|
74
|
+
this.znp.on('close', this.onZnpClose.bind(this));
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Adapter methods
|
|
78
|
+
*/
|
|
79
|
+
async start() {
|
|
80
|
+
await this.znp.open();
|
|
81
|
+
const attempts = 3;
|
|
82
|
+
for (let i = 0; i < attempts; i++) {
|
|
83
|
+
try {
|
|
84
|
+
await this.znp.request(Subsystem.SYS, 'ping', { capabilities: 1 });
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
catch (e) {
|
|
88
|
+
if (attempts - 1 === i) {
|
|
89
|
+
throw new Error(`Failed to connect to the adapter (${e})`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// Old firmware did not support version, assume it's Z-Stack 1.2 for now.
|
|
94
|
+
try {
|
|
95
|
+
this.version = (await this.znp.request(Subsystem.SYS, 'version', {})).payload;
|
|
96
|
+
}
|
|
97
|
+
catch (e) {
|
|
98
|
+
debug(`Failed to get zStack version, assuming 1.2`);
|
|
99
|
+
this.version = { "transportrev": 2, "product": 0, "majorrel": 2, "minorrel": 0, "maintrel": 0, "revision": "" };
|
|
100
|
+
}
|
|
101
|
+
const concurrent = this.adapterOptions && this.adapterOptions.concurrent ?
|
|
102
|
+
this.adapterOptions.concurrent :
|
|
103
|
+
(this.version.product === tstype_1.ZnpVersion.zStack3x0 ? 16 : 2);
|
|
104
|
+
debug(`Adapter concurrent: ${concurrent}`);
|
|
105
|
+
this.queue = new utils_1.Queue(concurrent);
|
|
106
|
+
debug(`Detected znp version '${tstype_1.ZnpVersion[this.version.product]}' (${JSON.stringify(this.version)})`);
|
|
107
|
+
this.adapterManager = new manager_1.ZnpAdapterManager(this.znp, {
|
|
108
|
+
backupPath: this.backupPath,
|
|
109
|
+
version: this.version.product,
|
|
110
|
+
greenPowerGroup: this.greenPowerGroup,
|
|
111
|
+
networkOptions: this.networkOptions,
|
|
112
|
+
adapterOptions: this.adapterOptions,
|
|
113
|
+
}, this.logger);
|
|
114
|
+
const startResult = this.adapterManager.start();
|
|
115
|
+
if (this.adapterOptions.disableLED) {
|
|
116
|
+
// Wait a bit for adapter to startup, otherwise led doesn't disable (tested with CC2531)
|
|
117
|
+
await (0, utils_1.Wait)(200);
|
|
118
|
+
await this.setLED('disable');
|
|
119
|
+
}
|
|
120
|
+
return startResult;
|
|
121
|
+
}
|
|
122
|
+
async stop() {
|
|
123
|
+
this.closing = true;
|
|
124
|
+
await this.znp.close();
|
|
125
|
+
}
|
|
126
|
+
static async isValidPath(path) {
|
|
127
|
+
return znp_1.Znp.isValidPath(path);
|
|
128
|
+
}
|
|
129
|
+
static async autoDetectPath() {
|
|
130
|
+
return znp_1.Znp.autoDetectPath();
|
|
131
|
+
}
|
|
132
|
+
async getCoordinator() {
|
|
133
|
+
return this.queue.execute(async () => {
|
|
134
|
+
this.checkInterpanLock();
|
|
135
|
+
const activeEpRsp = this.znp.waitFor(unpi_1.Constants.Type.AREQ, Subsystem.ZDO, 'activeEpRsp');
|
|
136
|
+
await this.znp.request(Subsystem.ZDO, 'activeEpReq', { dstaddr: 0, nwkaddrofinterest: 0 }, activeEpRsp.ID);
|
|
137
|
+
const activeEp = await activeEpRsp.start().promise;
|
|
138
|
+
const deviceInfo = await this.znp.request(Subsystem.UTIL, 'getDeviceInfo', {});
|
|
139
|
+
const endpoints = [];
|
|
140
|
+
for (const endpoint of activeEp.payload.activeeplist) {
|
|
141
|
+
const simpleDescRsp = this.znp.waitFor(unpi_1.Constants.Type.AREQ, Subsystem.ZDO, 'simpleDescRsp', { endpoint });
|
|
142
|
+
await this.znp.request(Subsystem.ZDO, 'simpleDescReq', { dstaddr: 0, nwkaddrofinterest: 0, endpoint }, simpleDescRsp.ID);
|
|
143
|
+
const simpleDesc = await simpleDescRsp.start().promise;
|
|
144
|
+
endpoints.push({
|
|
145
|
+
ID: simpleDesc.payload.endpoint,
|
|
146
|
+
profileID: simpleDesc.payload.profileid,
|
|
147
|
+
deviceID: simpleDesc.payload.deviceid,
|
|
148
|
+
inputClusters: simpleDesc.payload.inclusterlist,
|
|
149
|
+
outputClusters: simpleDesc.payload.outclusterlist,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
networkAddress: 0,
|
|
154
|
+
manufacturerID: 0,
|
|
155
|
+
ieeeAddr: deviceInfo.payload.ieeeaddr,
|
|
156
|
+
endpoints,
|
|
157
|
+
};
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
async permitJoin(seconds, networkAddress) {
|
|
161
|
+
const addrmode = networkAddress === null ? 0x0F : 0x02;
|
|
162
|
+
const dstaddr = networkAddress || 0xFFFC;
|
|
163
|
+
await this.queue.execute(async () => {
|
|
164
|
+
this.checkInterpanLock();
|
|
165
|
+
const payload = { addrmode, dstaddr, duration: seconds, tcsignificance: 0 };
|
|
166
|
+
await this.znp.request(Subsystem.ZDO, 'mgmtPermitJoinReq', payload);
|
|
167
|
+
await this.setLED(seconds == 0 ? 'off' : 'on');
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
async getCoordinatorVersion() {
|
|
171
|
+
return { type: tstype_1.ZnpVersion[this.version.product], meta: this.version };
|
|
172
|
+
}
|
|
173
|
+
async reset(type) {
|
|
174
|
+
if (type === 'soft') {
|
|
175
|
+
await this.znp.request(Subsystem.SYS, 'resetReq', { type: Constants.SYS.resetType.SOFT });
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
await this.znp.request(Subsystem.SYS, 'resetReq', { type: Constants.SYS.resetType.HARD });
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
async setLED(action) {
|
|
182
|
+
if (this.supportsLED == null) {
|
|
183
|
+
// Only zStack3x0 with 20210430 and greater support LED
|
|
184
|
+
const zStack3x0 = this.version.product === tstype_1.ZnpVersion.zStack3x0;
|
|
185
|
+
this.supportsLED = !zStack3x0 || (zStack3x0 && parseInt(this.version.revision) >= 20210430);
|
|
186
|
+
}
|
|
187
|
+
if (!this.supportsLED || (this.adapterOptions.disableLED && action !== 'disable')) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
// Firmwares build on and after 20211029 should handle LED themselves
|
|
191
|
+
const firmwareControlsLed = parseInt(this.version.revision) >= 20211029;
|
|
192
|
+
const lookup = {
|
|
193
|
+
'disable': firmwareControlsLed ? { ledid: 0xFF, mode: 5 } : { ledid: 3, mode: 0 },
|
|
194
|
+
'on': firmwareControlsLed ? null : { ledid: 3, mode: 1 },
|
|
195
|
+
'off': firmwareControlsLed ? null : { ledid: 3, mode: 0 },
|
|
196
|
+
};
|
|
197
|
+
const payload = lookup[action];
|
|
198
|
+
if (payload) {
|
|
199
|
+
this.znp.request(Subsystem.UTIL, 'ledControl', payload, null, 500).catch(() => {
|
|
200
|
+
// We cannot 100% correctly determine if an adapter supports LED. E.g. the zStack 1.2 20190608
|
|
201
|
+
// fw supports led on the CC2531 but not on the CC2530. Therefore if a led request fails never thrown
|
|
202
|
+
// an error but instead mark the led as unsupported.
|
|
203
|
+
// https://github.com/Koenkk/zigbee-herdsman/issues/377
|
|
204
|
+
// https://github.com/Koenkk/zigbee2mqtt/issues/7693
|
|
205
|
+
this.supportsLED = false;
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
async requestNetworkAddress(ieeeAddr) {
|
|
210
|
+
/**
|
|
211
|
+
* NOTE: There are cases where multiple nwkAddrRsp are recevied with different network addresses,
|
|
212
|
+
* this is currently not handled, the first nwkAddrRsp is taken.
|
|
213
|
+
*/
|
|
214
|
+
debug("Request network address of '%s'", ieeeAddr);
|
|
215
|
+
const response = this.znp.waitFor(unpi_1.Constants.Type.AREQ, Subsystem.ZDO, 'nwkAddrRsp', { ieeeaddr: ieeeAddr });
|
|
216
|
+
await this.znp.request(Subsystem.ZDO, 'nwkAddrReq', { ieeeaddr: ieeeAddr, reqtype: 0, startindex: 0 });
|
|
217
|
+
const result = await response.start().promise;
|
|
218
|
+
return result.payload.nwkaddr;
|
|
219
|
+
}
|
|
220
|
+
supportsAssocRemove() {
|
|
221
|
+
return this.version.product === tstype_1.ZnpVersion.zStack3x0 && parseInt(this.version.revision) >= 20200805;
|
|
222
|
+
}
|
|
223
|
+
supportsAssocAdd() {
|
|
224
|
+
return this.version.product === tstype_1.ZnpVersion.zStack3x0 && parseInt(this.version.revision) >= 20201026;
|
|
225
|
+
}
|
|
226
|
+
async discoverRoute(networkAddress, wait = true) {
|
|
227
|
+
debug('Discovering route to %d', networkAddress);
|
|
228
|
+
const payload = { dstAddr: networkAddress, options: 0, radius: Constants.AF.DEFAULT_RADIUS };
|
|
229
|
+
await this.znp.request(Subsystem.ZDO, 'extRouteDisc', payload);
|
|
230
|
+
if (wait) {
|
|
231
|
+
await (0, utils_1.Wait)(3000);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
async nodeDescriptor(networkAddress) {
|
|
235
|
+
return this.queue.execute(async () => {
|
|
236
|
+
this.checkInterpanLock();
|
|
237
|
+
try {
|
|
238
|
+
const result = await this.nodeDescriptorInternal(networkAddress);
|
|
239
|
+
return result;
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
debug(`Node descriptor request for '${networkAddress}' failed (${error}), retry`);
|
|
243
|
+
// Doing a route discovery after simple descriptor request fails makes it succeed sometimes.
|
|
244
|
+
// https://github.com/Koenkk/zigbee2mqtt/issues/3276
|
|
245
|
+
await this.discoverRoute(networkAddress);
|
|
246
|
+
const result = await this.nodeDescriptorInternal(networkAddress);
|
|
247
|
+
return result;
|
|
248
|
+
}
|
|
249
|
+
}, networkAddress);
|
|
250
|
+
}
|
|
251
|
+
async nodeDescriptorInternal(networkAddress) {
|
|
252
|
+
const response = this.znp.waitFor(Type.AREQ, Subsystem.ZDO, 'nodeDescRsp', { nwkaddr: networkAddress });
|
|
253
|
+
const payload = { dstaddr: networkAddress, nwkaddrofinterest: networkAddress };
|
|
254
|
+
await this.znp.request(Subsystem.ZDO, 'nodeDescReq', payload, response.ID);
|
|
255
|
+
const descriptor = await response.start().promise;
|
|
256
|
+
let type = 'Unknown';
|
|
257
|
+
const logicalType = descriptor.payload.logicaltype_cmplxdescavai_userdescavai & 0x07;
|
|
258
|
+
for (const [key, value] of Object.entries(Constants.ZDO.deviceLogicalType)) {
|
|
259
|
+
if (value === logicalType) {
|
|
260
|
+
if (key === 'COORDINATOR')
|
|
261
|
+
type = 'Coordinator';
|
|
262
|
+
else if (key === 'ROUTER')
|
|
263
|
+
type = 'Router';
|
|
264
|
+
else if (key === 'ENDDEVICE')
|
|
265
|
+
type = 'EndDevice';
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return { manufacturerCode: descriptor.payload.manufacturercode, type };
|
|
270
|
+
}
|
|
271
|
+
async activeEndpoints(networkAddress) {
|
|
272
|
+
return this.queue.execute(async () => {
|
|
273
|
+
this.checkInterpanLock();
|
|
274
|
+
const response = this.znp.waitFor(Type.AREQ, Subsystem.ZDO, 'activeEpRsp', { nwkaddr: networkAddress });
|
|
275
|
+
const payload = { dstaddr: networkAddress, nwkaddrofinterest: networkAddress };
|
|
276
|
+
await this.znp.request(Subsystem.ZDO, 'activeEpReq', payload, response.ID);
|
|
277
|
+
const activeEp = await response.start().promise;
|
|
278
|
+
return { endpoints: activeEp.payload.activeeplist };
|
|
279
|
+
}, networkAddress);
|
|
280
|
+
}
|
|
281
|
+
async simpleDescriptor(networkAddress, endpointID) {
|
|
282
|
+
return this.queue.execute(async () => {
|
|
283
|
+
this.checkInterpanLock();
|
|
284
|
+
const responsePayload = { nwkaddr: networkAddress, endpoint: endpointID };
|
|
285
|
+
const response = this.znp.waitFor(Type.AREQ, Subsystem.ZDO, 'simpleDescRsp', responsePayload);
|
|
286
|
+
const payload = { dstaddr: networkAddress, nwkaddrofinterest: networkAddress, endpoint: endpointID };
|
|
287
|
+
await this.znp.request(Subsystem.ZDO, 'simpleDescReq', payload, response.ID);
|
|
288
|
+
const descriptor = await response.start().promise;
|
|
289
|
+
return {
|
|
290
|
+
profileID: descriptor.payload.profileid,
|
|
291
|
+
endpointID: descriptor.payload.endpoint,
|
|
292
|
+
deviceID: descriptor.payload.deviceid,
|
|
293
|
+
inputClusters: descriptor.payload.inclusterlist,
|
|
294
|
+
outputClusters: descriptor.payload.outclusterlist,
|
|
295
|
+
};
|
|
296
|
+
}, networkAddress);
|
|
297
|
+
}
|
|
298
|
+
async sendZclFrameToEndpoint(ieeeAddr, networkAddress, endpoint, zclFrame, timeout, disableResponse, disableRecovery, sourceEndpoint) {
|
|
299
|
+
return this.queue.execute(async () => {
|
|
300
|
+
this.checkInterpanLock();
|
|
301
|
+
return this.sendZclFrameToEndpointInternal(ieeeAddr, networkAddress, endpoint, sourceEndpoint || 1, zclFrame, timeout, disableResponse, disableRecovery, 0, 0, false, false, false, null);
|
|
302
|
+
}, networkAddress);
|
|
303
|
+
}
|
|
304
|
+
async sendZclFrameToEndpointInternal(ieeeAddr, networkAddress, endpoint, sourceEndpoint, zclFrame, timeout, disableResponse, disableRecovery, responseAttempt, dataRequestAttempt, checkedNetworkAddress, discoveredRoute, assocRemove, assocRestore) {
|
|
305
|
+
debug('sendZclFrameToEndpointInternal %s:%i/%i (%i,%i,%i)', ieeeAddr, networkAddress, endpoint, responseAttempt, dataRequestAttempt, this.queue.count());
|
|
306
|
+
let response = null;
|
|
307
|
+
const command = zclFrame.getCommand();
|
|
308
|
+
if (command.hasOwnProperty('response') && disableResponse === false) {
|
|
309
|
+
response = this.waitForInternal(networkAddress, endpoint, zclFrame.Header.frameControl.frameType, zcl_1.Direction.SERVER_TO_CLIENT, zclFrame.Header.transactionSequenceNumber, zclFrame.Cluster.ID, command.response, timeout);
|
|
310
|
+
}
|
|
311
|
+
else if (!zclFrame.Header.frameControl.disableDefaultResponse) {
|
|
312
|
+
response = this.waitForInternal(networkAddress, endpoint, zcl_1.FrameType.GLOBAL, zcl_1.Direction.SERVER_TO_CLIENT, zclFrame.Header.transactionSequenceNumber, zclFrame.Cluster.ID, zcl_1.Foundation.defaultRsp.ID, timeout);
|
|
313
|
+
}
|
|
314
|
+
const dataConfirmResult = await this.dataRequest(networkAddress, endpoint, sourceEndpoint, zclFrame.Cluster.ID, Constants.AF.DEFAULT_RADIUS, zclFrame.toBuffer(), timeout);
|
|
315
|
+
if (dataConfirmResult !== ZnpCommandStatus.SUCCESS) {
|
|
316
|
+
// In case dataConfirm timesout (= null) or gives an error, try to recover
|
|
317
|
+
debug('Data confirm error (%s:%d,%d,%d)', ieeeAddr, networkAddress, dataConfirmResult, dataRequestAttempt);
|
|
318
|
+
if (response !== null)
|
|
319
|
+
response.cancel();
|
|
320
|
+
/**
|
|
321
|
+
* In case we did an assocRemove in the previous attempt and it still fails after this, assume that the
|
|
322
|
+
* coordinator is still the parent of the device (but for some reason the device is not available now).
|
|
323
|
+
* Re-add the device to the assoc table, otherwise we will never be able to reach it anymore.
|
|
324
|
+
*/
|
|
325
|
+
if (assocRemove && assocRestore && this.supportsAssocAdd()) {
|
|
326
|
+
debug('assocAdd(%s)', assocRestore.ieeeadr);
|
|
327
|
+
await this.znp.request(Subsystem.UTIL, 'assocAdd', assocRestore);
|
|
328
|
+
assocRestore = null;
|
|
329
|
+
}
|
|
330
|
+
const recoverableErrors = [
|
|
331
|
+
ZnpCommandStatus.NWK_NO_ROUTE, ZnpCommandStatus.MAC_NO_ACK, ZnpCommandStatus.MAC_CHANNEL_ACCESS_FAILURE,
|
|
332
|
+
ZnpCommandStatus.MAC_TRANSACTION_EXPIRED, ZnpCommandStatus.BUFFER_FULL,
|
|
333
|
+
ZnpCommandStatus.MAC_NO_RESOURCES,
|
|
334
|
+
];
|
|
335
|
+
if (dataRequestAttempt >= 4 || !recoverableErrors.includes(dataConfirmResult) || disableRecovery) {
|
|
336
|
+
throw new DataConfirmError(dataConfirmResult);
|
|
337
|
+
}
|
|
338
|
+
if (dataConfirmResult === ZnpCommandStatus.MAC_CHANNEL_ACCESS_FAILURE ||
|
|
339
|
+
dataConfirmResult === ZnpCommandStatus.BUFFER_FULL ||
|
|
340
|
+
dataConfirmResult === ZnpCommandStatus.MAC_NO_RESOURCES) {
|
|
341
|
+
/**
|
|
342
|
+
* MAC_CHANNEL_ACCESS_FAILURE: When many commands at once are executed we can end up in a MAC
|
|
343
|
+
* channel access failure error. This is because there is too much traffic on the network.
|
|
344
|
+
* Retry this command once after a cooling down period.
|
|
345
|
+
* BUFFER_FULL: When many commands are executed at once the buffer can get full, wait
|
|
346
|
+
* some time and retry.
|
|
347
|
+
* MAC_NO_RESOURCES: Operation could not be completed because no memory resources are available,
|
|
348
|
+
* wait some time and retry.
|
|
349
|
+
*/
|
|
350
|
+
await (0, utils_1.Wait)(2000);
|
|
351
|
+
return this.sendZclFrameToEndpointInternal(ieeeAddr, networkAddress, endpoint, sourceEndpoint, zclFrame, timeout, disableResponse, disableRecovery, responseAttempt, dataRequestAttempt + 1, checkedNetworkAddress, discoveredRoute, assocRemove, assocRestore);
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
let doAssocRemove = false;
|
|
355
|
+
if (!assocRemove && dataConfirmResult === ZnpCommandStatus.MAC_TRANSACTION_EXPIRED &&
|
|
356
|
+
dataRequestAttempt >= 1 && this.supportsAssocRemove()) {
|
|
357
|
+
const match = await this.znp.request(Subsystem.UTIL, 'assocGetWithAddress', { extaddr: ieeeAddr, nwkaddr: networkAddress });
|
|
358
|
+
if (match.payload.nwkaddr !== 0xFFFE && match.payload.noderelation !== 255) {
|
|
359
|
+
doAssocRemove = true;
|
|
360
|
+
assocRestore =
|
|
361
|
+
{ ieeeadr: ieeeAddr, nwkaddr: networkAddress, noderelation: match.payload.noderelation };
|
|
362
|
+
}
|
|
363
|
+
assocRemove = true;
|
|
364
|
+
}
|
|
365
|
+
// NWK_NO_ROUTE: no network route => rediscover route
|
|
366
|
+
// MAC_NO_ACK: route may be corrupted
|
|
367
|
+
// MAC_TRANSACTION_EXPIRED: Mac layer is sleeping
|
|
368
|
+
if (doAssocRemove) {
|
|
369
|
+
/**
|
|
370
|
+
* Since child aging is disabled on the firmware, when a end device is directly connected
|
|
371
|
+
* to the coordinator and changes parent and the coordinator does not recevie this update,
|
|
372
|
+
* it still thinks it's directly connected.
|
|
373
|
+
* A discoverRoute() is not send out in this case, therefore remove it from the associated device
|
|
374
|
+
* list and try again.
|
|
375
|
+
* Note: assocRemove is a custom command, not available by default, only available on recent
|
|
376
|
+
* z-stack-firmware firmware version. In case it's not supported by the coordinator we will
|
|
377
|
+
* automatically timeout after 60000ms.
|
|
378
|
+
*/
|
|
379
|
+
debug('assocRemove(%s)', ieeeAddr);
|
|
380
|
+
await this.znp.request(Subsystem.UTIL, 'assocRemove', { ieeeadr: ieeeAddr });
|
|
381
|
+
}
|
|
382
|
+
else if (!discoveredRoute && dataRequestAttempt >= 1) {
|
|
383
|
+
discoveredRoute = true;
|
|
384
|
+
await this.discoverRoute(networkAddress);
|
|
385
|
+
}
|
|
386
|
+
else if (!checkedNetworkAddress && dataRequestAttempt >= 1) {
|
|
387
|
+
// Figure out once if the network address has been changed.
|
|
388
|
+
try {
|
|
389
|
+
checkedNetworkAddress = true;
|
|
390
|
+
const actualNetworkAddress = await this.requestNetworkAddress(ieeeAddr);
|
|
391
|
+
if (networkAddress !== actualNetworkAddress) {
|
|
392
|
+
debug(`Failed because request was done with wrong network address`);
|
|
393
|
+
discoveredRoute = true;
|
|
394
|
+
networkAddress = actualNetworkAddress;
|
|
395
|
+
await this.discoverRoute(actualNetworkAddress);
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
debug('Network address did not change');
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
catch (_a) { }
|
|
402
|
+
}
|
|
403
|
+
else {
|
|
404
|
+
debug('Wait 2000ms');
|
|
405
|
+
await (0, utils_1.Wait)(2000);
|
|
406
|
+
}
|
|
407
|
+
return this.sendZclFrameToEndpointInternal(ieeeAddr, networkAddress, endpoint, sourceEndpoint, zclFrame, timeout, disableResponse, disableRecovery, responseAttempt, dataRequestAttempt + 1, checkedNetworkAddress, discoveredRoute, assocRemove, assocRestore);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
if (response !== null) {
|
|
411
|
+
try {
|
|
412
|
+
const result = await response.start().promise;
|
|
413
|
+
return result;
|
|
414
|
+
}
|
|
415
|
+
catch (error) {
|
|
416
|
+
debug('Response timeout (%s:%d,%d)', ieeeAddr, networkAddress, responseAttempt);
|
|
417
|
+
if (responseAttempt < 1 && !disableRecovery) {
|
|
418
|
+
// No response could be because the radio of the end device is turned off:
|
|
419
|
+
// Sometimes the coordinator does not properly set the PENDING flag.
|
|
420
|
+
// Try to rewrite the device entry in the association table, this fixes it sometimes.
|
|
421
|
+
const match = await this.znp.request(Subsystem.UTIL, 'assocGetWithAddress', { extaddr: ieeeAddr, nwkaddr: networkAddress });
|
|
422
|
+
debug(`Response timeout recovery: Node relation ${match.payload.noderelation} (${ieeeAddr} / ${match.payload.nwkaddr})`);
|
|
423
|
+
if (this.supportsAssocAdd() && this.supportsAssocRemove() &&
|
|
424
|
+
match.payload.nwkaddr !== 0xFFFE && match.payload.noderelation == 1) {
|
|
425
|
+
debug(`Response timeout recovery: Rewrite association table entry (${ieeeAddr})`);
|
|
426
|
+
await this.znp.request(Subsystem.UTIL, 'assocRemove', { ieeeadr: ieeeAddr });
|
|
427
|
+
await this.znp.request(Subsystem.UTIL, 'assocAdd', { ieeeadr: ieeeAddr, nwkaddr: networkAddress, noderelation: match.payload.noderelation });
|
|
428
|
+
}
|
|
429
|
+
// No response could be of invalid route, e.g. when message is send to wrong parent of end device.
|
|
430
|
+
await this.discoverRoute(networkAddress);
|
|
431
|
+
return this.sendZclFrameToEndpointInternal(ieeeAddr, networkAddress, endpoint, sourceEndpoint, zclFrame, timeout, disableResponse, disableRecovery, responseAttempt + 1, dataRequestAttempt, checkedNetworkAddress, discoveredRoute, assocRemove, assocRestore);
|
|
432
|
+
}
|
|
433
|
+
else {
|
|
434
|
+
throw error;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
else {
|
|
439
|
+
return null;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
async sendZclFrameToGroup(groupID, zclFrame, sourceEndpoint) {
|
|
443
|
+
return this.queue.execute(async () => {
|
|
444
|
+
this.checkInterpanLock();
|
|
445
|
+
await this.dataRequestExtended(AddressMode.ADDR_GROUP, groupID, 0xFF, 0, sourceEndpoint || 1, zclFrame.Cluster.ID, Constants.AF.DEFAULT_RADIUS, zclFrame.toBuffer(), 3000, true);
|
|
446
|
+
/**
|
|
447
|
+
* As a group command is not confirmed and thus immidiately returns
|
|
448
|
+
* (contrary to network address requests) we will give the
|
|
449
|
+
* command some time to 'settle' in the network.
|
|
450
|
+
*/
|
|
451
|
+
await (0, utils_1.Wait)(200);
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
async sendZclFrameToAll(endpoint, zclFrame, sourceEndpoint) {
|
|
455
|
+
return this.queue.execute(async () => {
|
|
456
|
+
this.checkInterpanLock();
|
|
457
|
+
await this.dataRequestExtended(AddressMode.ADDR_16BIT, 0xFFFD, endpoint, 0, sourceEndpoint, zclFrame.Cluster.ID, Constants.AF.DEFAULT_RADIUS, zclFrame.toBuffer(), 3000, false, 0);
|
|
458
|
+
/**
|
|
459
|
+
* As a broadcast command is not confirmed and thus immidiately returns
|
|
460
|
+
* (contrary to network address requests) we will give the
|
|
461
|
+
* command some time to 'settle' in the network.
|
|
462
|
+
*/
|
|
463
|
+
await (0, utils_1.Wait)(200);
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
async lqi(networkAddress) {
|
|
467
|
+
return this.queue.execute(async () => {
|
|
468
|
+
this.checkInterpanLock();
|
|
469
|
+
const neighbors = [];
|
|
470
|
+
// eslint-disable-next-line
|
|
471
|
+
const request = async (startIndex) => {
|
|
472
|
+
const response = this.znp.waitFor(Type.AREQ, Subsystem.ZDO, 'mgmtLqiRsp', { srcaddr: networkAddress });
|
|
473
|
+
await this.znp.request(Subsystem.ZDO, 'mgmtLqiReq', { dstaddr: networkAddress, startindex: startIndex }, response.ID);
|
|
474
|
+
const result = await response.start().promise;
|
|
475
|
+
if (result.payload.status !== ZnpCommandStatus.SUCCESS) {
|
|
476
|
+
throw new Error(`LQI for '${networkAddress}' failed with error: '${ZnpCommandStatus[result.payload.status]}' (${result.payload.status})`);
|
|
477
|
+
}
|
|
478
|
+
return result;
|
|
479
|
+
};
|
|
480
|
+
// eslint-disable-next-line
|
|
481
|
+
const add = (list) => {
|
|
482
|
+
for (const entry of list) {
|
|
483
|
+
neighbors.push({
|
|
484
|
+
linkquality: entry.lqi,
|
|
485
|
+
networkAddress: entry.nwkAddr,
|
|
486
|
+
ieeeAddr: entry.extAddr,
|
|
487
|
+
relationship: entry.relationship,
|
|
488
|
+
depth: entry.depth,
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
let response = await request(0);
|
|
493
|
+
add(response.payload.neighborlqilist);
|
|
494
|
+
const size = response.payload.neighbortableentries;
|
|
495
|
+
let nextStartIndex = response.payload.neighborlqilist.length;
|
|
496
|
+
while (neighbors.length < size) {
|
|
497
|
+
response = await request(nextStartIndex);
|
|
498
|
+
add(response.payload.neighborlqilist);
|
|
499
|
+
nextStartIndex += response.payload.neighborlqilist.length;
|
|
500
|
+
}
|
|
501
|
+
return { neighbors };
|
|
502
|
+
}, networkAddress);
|
|
503
|
+
}
|
|
504
|
+
async routingTable(networkAddress) {
|
|
505
|
+
return this.queue.execute(async () => {
|
|
506
|
+
this.checkInterpanLock();
|
|
507
|
+
const table = [];
|
|
508
|
+
// eslint-disable-next-line
|
|
509
|
+
const request = async (startIndex) => {
|
|
510
|
+
const response = this.znp.waitFor(Type.AREQ, Subsystem.ZDO, 'mgmtRtgRsp', { srcaddr: networkAddress });
|
|
511
|
+
await this.znp.request(Subsystem.ZDO, 'mgmtRtgReq', { dstaddr: networkAddress, startindex: startIndex }, response.ID);
|
|
512
|
+
const result = await response.start().promise;
|
|
513
|
+
if (result.payload.status !== ZnpCommandStatus.SUCCESS) {
|
|
514
|
+
throw new Error(`Routing table for '${networkAddress}' failed with error: '${ZnpCommandStatus[result.payload.status]}' (${result.payload.status})`);
|
|
515
|
+
}
|
|
516
|
+
return result;
|
|
517
|
+
};
|
|
518
|
+
// eslint-disable-next-line
|
|
519
|
+
const add = (list) => {
|
|
520
|
+
for (const entry of list) {
|
|
521
|
+
table.push({
|
|
522
|
+
destinationAddress: entry.destNwkAddr,
|
|
523
|
+
status: entry.routeStatus,
|
|
524
|
+
nextHop: entry.nextHopNwkAddr,
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
let response = await request(0);
|
|
529
|
+
add(response.payload.routingtablelist);
|
|
530
|
+
const size = response.payload.routingtableentries;
|
|
531
|
+
let nextStartIndex = response.payload.routingtablelist.length;
|
|
532
|
+
while (table.length < size) {
|
|
533
|
+
response = await request(nextStartIndex);
|
|
534
|
+
add(response.payload.routingtablelist);
|
|
535
|
+
nextStartIndex += response.payload.routingtablelist.length;
|
|
536
|
+
}
|
|
537
|
+
return { table };
|
|
538
|
+
}, networkAddress);
|
|
539
|
+
}
|
|
540
|
+
async addInstallCode(ieeeAddress, key) {
|
|
541
|
+
(0, assert_1.default)(this.version.product !== tstype_1.ZnpVersion.zStack12, 'Install code is not supported for ZStack 1.2 adapter');
|
|
542
|
+
const payload = { installCodeFormat: key.length === 18 ? 1 : 2, ieeeaddr: ieeeAddress, installCode: key };
|
|
543
|
+
await this.znp.request(Subsystem.APP_CNF, 'bdbAddInstallCode', payload);
|
|
544
|
+
}
|
|
545
|
+
async bind(destinationNetworkAddress, sourceIeeeAddress, sourceEndpoint, clusterID, destinationAddressOrGroup, type, destinationEndpoint) {
|
|
546
|
+
return this.queue.execute(async () => {
|
|
547
|
+
this.checkInterpanLock();
|
|
548
|
+
const responsePayload = { srcaddr: destinationNetworkAddress };
|
|
549
|
+
const response = this.znp.waitFor(Type.AREQ, Subsystem.ZDO, 'bindRsp', responsePayload);
|
|
550
|
+
const payload = {
|
|
551
|
+
dstaddr: destinationNetworkAddress,
|
|
552
|
+
srcaddr: sourceIeeeAddress,
|
|
553
|
+
srcendpoint: sourceEndpoint,
|
|
554
|
+
clusterid: clusterID,
|
|
555
|
+
dstaddrmode: type === 'group' ?
|
|
556
|
+
AddressMode.ADDR_GROUP : AddressMode.ADDR_64BIT,
|
|
557
|
+
dstaddress: this.toAddressString(destinationAddressOrGroup),
|
|
558
|
+
dstendpoint: type === 'group' ? 0xFF : destinationEndpoint,
|
|
559
|
+
};
|
|
560
|
+
await this.znp.request(Subsystem.ZDO, 'bindReq', payload, response.ID);
|
|
561
|
+
await response.start().promise;
|
|
562
|
+
}, destinationNetworkAddress);
|
|
563
|
+
}
|
|
564
|
+
async unbind(destinationNetworkAddress, sourceIeeeAddress, sourceEndpoint, clusterID, destinationAddressOrGroup, type, destinationEndpoint) {
|
|
565
|
+
return this.queue.execute(async () => {
|
|
566
|
+
this.checkInterpanLock();
|
|
567
|
+
const response = this.znp.waitFor(Type.AREQ, Subsystem.ZDO, 'unbindRsp', { srcaddr: destinationNetworkAddress });
|
|
568
|
+
const payload = {
|
|
569
|
+
dstaddr: destinationNetworkAddress,
|
|
570
|
+
srcaddr: sourceIeeeAddress,
|
|
571
|
+
srcendpoint: sourceEndpoint,
|
|
572
|
+
clusterid: clusterID,
|
|
573
|
+
dstaddrmode: type === 'group' ?
|
|
574
|
+
AddressMode.ADDR_GROUP : AddressMode.ADDR_64BIT,
|
|
575
|
+
dstaddress: this.toAddressString(destinationAddressOrGroup),
|
|
576
|
+
dstendpoint: type === 'group' ? 0xFF : destinationEndpoint,
|
|
577
|
+
};
|
|
578
|
+
await this.znp.request(Subsystem.ZDO, 'unbindReq', payload, response.ID);
|
|
579
|
+
await response.start().promise;
|
|
580
|
+
}, destinationNetworkAddress);
|
|
581
|
+
}
|
|
582
|
+
removeDevice(networkAddress, ieeeAddr) {
|
|
583
|
+
return this.queue.execute(async () => {
|
|
584
|
+
this.checkInterpanLock();
|
|
585
|
+
const response = this.znp.waitFor(unpi_1.Constants.Type.AREQ, Subsystem.ZDO, 'mgmtLeaveRsp', { srcaddr: networkAddress });
|
|
586
|
+
const payload = {
|
|
587
|
+
dstaddr: networkAddress,
|
|
588
|
+
deviceaddress: ieeeAddr,
|
|
589
|
+
removechildrenRejoin: 0,
|
|
590
|
+
};
|
|
591
|
+
await this.znp.request(Subsystem.ZDO, 'mgmtLeaveReq', payload, response.ID);
|
|
592
|
+
await response.start().promise;
|
|
593
|
+
}, networkAddress);
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Event handlers
|
|
597
|
+
*/
|
|
598
|
+
onZnpClose() {
|
|
599
|
+
if (!this.closing) {
|
|
600
|
+
this.emit(Events.Events.disconnected);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
onZnpRecieved(object) {
|
|
604
|
+
if (object.type !== unpi_1.Constants.Type.AREQ) {
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
if (object.subsystem === Subsystem.ZDO) {
|
|
608
|
+
if (object.command === 'tcDeviceInd') {
|
|
609
|
+
const payload = {
|
|
610
|
+
networkAddress: object.payload.nwkaddr,
|
|
611
|
+
ieeeAddr: object.payload.extaddr,
|
|
612
|
+
};
|
|
613
|
+
this.emit(Events.Events.deviceJoined, payload);
|
|
614
|
+
}
|
|
615
|
+
else if (object.command === 'endDeviceAnnceInd') {
|
|
616
|
+
const payload = {
|
|
617
|
+
networkAddress: object.payload.nwkaddr,
|
|
618
|
+
ieeeAddr: object.payload.ieeeaddr,
|
|
619
|
+
};
|
|
620
|
+
// Only discover routes to end devices, if bit 1 of capabilities === 0 it's an end device.
|
|
621
|
+
const isEndDevice = (object.payload.capabilities & 1 << 1) === 0;
|
|
622
|
+
if (isEndDevice) {
|
|
623
|
+
if (!this.deviceAnnounceRouteDiscoveryDebouncers.has(payload.networkAddress)) {
|
|
624
|
+
// If a device announces multiple times in a very short time, it makes no sense
|
|
625
|
+
// to rediscover the route every time.
|
|
626
|
+
const debouncer = (0, debounce_1.default)(() => {
|
|
627
|
+
this.queue.execute(async () => {
|
|
628
|
+
/* istanbul ignore next */
|
|
629
|
+
this.discoverRoute(payload.networkAddress, false).catch(() => { });
|
|
630
|
+
}, payload.networkAddress);
|
|
631
|
+
}, 60 * 1000, true);
|
|
632
|
+
this.deviceAnnounceRouteDiscoveryDebouncers.set(payload.networkAddress, debouncer);
|
|
633
|
+
}
|
|
634
|
+
this.deviceAnnounceRouteDiscoveryDebouncers.get(payload.networkAddress)();
|
|
635
|
+
}
|
|
636
|
+
this.emit(Events.Events.deviceAnnounce, payload);
|
|
637
|
+
}
|
|
638
|
+
else if (object.command === 'nwkAddrRsp') {
|
|
639
|
+
const payload = {
|
|
640
|
+
networkAddress: object.payload.nwkaddr,
|
|
641
|
+
ieeeAddr: object.payload.ieeeaddr,
|
|
642
|
+
};
|
|
643
|
+
this.emit(Events.Events.networkAddress, payload);
|
|
644
|
+
}
|
|
645
|
+
else {
|
|
646
|
+
/* istanbul ignore else */
|
|
647
|
+
if (object.command === 'leaveInd') {
|
|
648
|
+
const payload = {
|
|
649
|
+
networkAddress: object.payload.srcaddr,
|
|
650
|
+
ieeeAddr: object.payload.extaddr,
|
|
651
|
+
};
|
|
652
|
+
this.emit(Events.Events.deviceLeave, payload);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
else {
|
|
657
|
+
/* istanbul ignore else */
|
|
658
|
+
if (object.subsystem === Subsystem.AF) {
|
|
659
|
+
/* istanbul ignore else */
|
|
660
|
+
if (object.command === 'incomingMsg' || object.command === 'incomingMsgExt') {
|
|
661
|
+
try {
|
|
662
|
+
const payload = {
|
|
663
|
+
frame: zcl_1.ZclFrame.fromBuffer(object.payload.clusterid, object.payload.data),
|
|
664
|
+
address: object.payload.srcaddr,
|
|
665
|
+
endpoint: object.payload.srcendpoint,
|
|
666
|
+
linkquality: object.payload.linkquality,
|
|
667
|
+
groupID: object.payload.groupid,
|
|
668
|
+
wasBroadcast: object.payload.wasbroadcast === 1,
|
|
669
|
+
destinationEndpoint: object.payload.dstendpoint,
|
|
670
|
+
};
|
|
671
|
+
this.waitress.resolve(payload);
|
|
672
|
+
this.emit(Events.Events.zclData, payload);
|
|
673
|
+
}
|
|
674
|
+
catch (error) {
|
|
675
|
+
const payload = {
|
|
676
|
+
clusterID: object.payload.clusterid,
|
|
677
|
+
data: object.payload.data,
|
|
678
|
+
address: object.payload.srcaddr,
|
|
679
|
+
endpoint: object.payload.srcendpoint,
|
|
680
|
+
linkquality: object.payload.linkquality,
|
|
681
|
+
groupID: object.payload.groupid,
|
|
682
|
+
wasBroadcast: object.payload.wasbroadcast === 1,
|
|
683
|
+
destinationEndpoint: object.payload.dstendpoint,
|
|
684
|
+
};
|
|
685
|
+
this.emit(Events.Events.rawData, payload);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
async getNetworkParameters() {
|
|
692
|
+
const result = await this.znp.request(Subsystem.ZDO, 'extNwkInfo', {});
|
|
693
|
+
return {
|
|
694
|
+
panID: result.payload.panid, extendedPanID: result.payload.extendedpanid,
|
|
695
|
+
channel: result.payload.channel
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
async supportsBackup() {
|
|
699
|
+
return true;
|
|
700
|
+
}
|
|
701
|
+
async backup() {
|
|
702
|
+
return this.adapterManager.backup.createBackup();
|
|
703
|
+
}
|
|
704
|
+
async setChannelInterPAN(channel) {
|
|
705
|
+
return this.queue.execute(async () => {
|
|
706
|
+
this.interpanLock = true;
|
|
707
|
+
await this.znp.request(Subsystem.AF, 'interPanCtl', { cmd: 1, data: [channel] });
|
|
708
|
+
if (!this.interpanEndpointRegistered) {
|
|
709
|
+
// Make sure that endpoint 12 is registered to proxy the InterPAN messages.
|
|
710
|
+
await this.znp.request(Subsystem.AF, 'interPanCtl', { cmd: 2, data: [12] });
|
|
711
|
+
this.interpanEndpointRegistered = true;
|
|
712
|
+
}
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
async sendZclFrameInterPANToIeeeAddr(zclFrame, ieeeAddr) {
|
|
716
|
+
return this.queue.execute(async () => {
|
|
717
|
+
await this.dataRequestExtended(AddressMode.ADDR_64BIT, ieeeAddr, 0xFE, 0xFFFF, 12, zclFrame.Cluster.ID, 30, zclFrame.toBuffer(), 10000, false);
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
async sendZclFrameInterPANBroadcast(zclFrame, timeout) {
|
|
721
|
+
return this.queue.execute(async () => {
|
|
722
|
+
const command = zclFrame.getCommand();
|
|
723
|
+
if (!command.hasOwnProperty('response')) {
|
|
724
|
+
throw new Error(`Command '${command.name}' has no response, cannot wait for response`);
|
|
725
|
+
}
|
|
726
|
+
const response = this.waitForInternal(null, 0xFE, zclFrame.Header.frameControl.frameType, zcl_1.Direction.SERVER_TO_CLIENT, null, zclFrame.Cluster.ID, command.response, timeout);
|
|
727
|
+
try {
|
|
728
|
+
await this.dataRequestExtended(AddressMode.ADDR_16BIT, 0xFFFF, 0xFE, 0xFFFF, 12, zclFrame.Cluster.ID, 30, zclFrame.toBuffer(), 10000, false);
|
|
729
|
+
}
|
|
730
|
+
catch (error) {
|
|
731
|
+
response.cancel();
|
|
732
|
+
throw error;
|
|
733
|
+
}
|
|
734
|
+
return response.start().promise;
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
async restoreChannelInterPAN() {
|
|
738
|
+
return this.queue.execute(async () => {
|
|
739
|
+
await this.znp.request(Subsystem.AF, 'interPanCtl', { cmd: 0, data: [] });
|
|
740
|
+
// Give adapter some time to restore, otherwise stuff crashes
|
|
741
|
+
await (0, utils_1.Wait)(3000);
|
|
742
|
+
this.interpanLock = false;
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
async setTransmitPower(value) {
|
|
746
|
+
return this.queue.execute(async () => {
|
|
747
|
+
await this.znp.request(Subsystem.SYS, 'stackTune', { operation: 0, value });
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
waitForInternal(networkAddress, endpoint, frameType, direction, transactionSequenceNumber, clusterID, commandIdentifier, timeout) {
|
|
751
|
+
const payload = {
|
|
752
|
+
address: networkAddress, endpoint, clusterID, commandIdentifier, frameType, direction,
|
|
753
|
+
transactionSequenceNumber,
|
|
754
|
+
};
|
|
755
|
+
const waiter = this.waitress.waitFor(payload, timeout);
|
|
756
|
+
const cancel = () => this.waitress.remove(waiter.ID);
|
|
757
|
+
return { start: waiter.start, cancel };
|
|
758
|
+
}
|
|
759
|
+
waitFor(networkAddress, endpoint, frameType, direction, transactionSequenceNumber, clusterID, commandIdentifier, timeout) {
|
|
760
|
+
const waiter = this.waitForInternal(networkAddress, endpoint, frameType, direction, transactionSequenceNumber, clusterID, commandIdentifier, timeout);
|
|
761
|
+
return { cancel: waiter.cancel, promise: waiter.start().promise };
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* Private methods
|
|
765
|
+
*/
|
|
766
|
+
async dataRequest(destinationAddress, destinationEndpoint, sourceEndpoint, clusterID, radius, data, timeout) {
|
|
767
|
+
const transactionID = this.nextTransactionID();
|
|
768
|
+
const response = this.znp.waitFor(Type.AREQ, Subsystem.AF, 'dataConfirm', { transid: transactionID }, timeout);
|
|
769
|
+
await this.znp.request(Subsystem.AF, 'dataRequest', {
|
|
770
|
+
dstaddr: destinationAddress,
|
|
771
|
+
destendpoint: destinationEndpoint,
|
|
772
|
+
srcendpoint: sourceEndpoint,
|
|
773
|
+
clusterid: clusterID,
|
|
774
|
+
transid: transactionID,
|
|
775
|
+
options: 0,
|
|
776
|
+
radius: radius,
|
|
777
|
+
len: data.length,
|
|
778
|
+
data: data,
|
|
779
|
+
}, response.ID);
|
|
780
|
+
let result = null;
|
|
781
|
+
try {
|
|
782
|
+
const dataConfirm = await response.start().promise;
|
|
783
|
+
result = dataConfirm.payload.status;
|
|
784
|
+
}
|
|
785
|
+
catch (_a) {
|
|
786
|
+
result = DataConfirmTimeout;
|
|
787
|
+
}
|
|
788
|
+
return result;
|
|
789
|
+
}
|
|
790
|
+
async dataRequestExtended(addressMode, destinationAddressOrGroupID, destinationEndpoint, panID, sourceEndpoint, clusterID, radius, data, timeout, confirmation, attemptsLeft = 5) {
|
|
791
|
+
const transactionID = this.nextTransactionID();
|
|
792
|
+
const response = confirmation ?
|
|
793
|
+
this.znp.waitFor(Type.AREQ, Subsystem.AF, 'dataConfirm', { transid: transactionID }, timeout) : null;
|
|
794
|
+
await this.znp.request(Subsystem.AF, 'dataRequestExt', {
|
|
795
|
+
dstaddrmode: addressMode,
|
|
796
|
+
dstaddr: this.toAddressString(destinationAddressOrGroupID),
|
|
797
|
+
destendpoint: destinationEndpoint,
|
|
798
|
+
dstpanid: panID,
|
|
799
|
+
srcendpoint: sourceEndpoint,
|
|
800
|
+
clusterid: clusterID,
|
|
801
|
+
transid: transactionID,
|
|
802
|
+
options: 0,
|
|
803
|
+
radius,
|
|
804
|
+
len: data.length,
|
|
805
|
+
data: data,
|
|
806
|
+
}, response ? response.ID : null);
|
|
807
|
+
if (confirmation) {
|
|
808
|
+
const dataConfirm = await response.start().promise;
|
|
809
|
+
if (dataConfirm.payload.status !== ZnpCommandStatus.SUCCESS) {
|
|
810
|
+
if (attemptsLeft > 0 &&
|
|
811
|
+
(dataConfirm.payload.status === ZnpCommandStatus.MAC_CHANNEL_ACCESS_FAILURE ||
|
|
812
|
+
dataConfirm.payload.status === ZnpCommandStatus.BUFFER_FULL)) {
|
|
813
|
+
/**
|
|
814
|
+
* 225: When many commands at once are executed we can end up in a MAC channel access failure
|
|
815
|
+
* error. This is because there is too much traffic on the network.
|
|
816
|
+
* Retry this command once after a cooling down period.
|
|
817
|
+
*/
|
|
818
|
+
await (0, utils_1.Wait)(2000);
|
|
819
|
+
return this.dataRequestExtended(addressMode, destinationAddressOrGroupID, destinationEndpoint, panID, sourceEndpoint, clusterID, radius, data, timeout, confirmation, attemptsLeft - 1);
|
|
820
|
+
}
|
|
821
|
+
else {
|
|
822
|
+
throw new DataConfirmError(dataConfirm.payload.status);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
return dataConfirm;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
nextTransactionID() {
|
|
829
|
+
this.transactionID++;
|
|
830
|
+
if (this.transactionID > 255) {
|
|
831
|
+
this.transactionID = 1;
|
|
832
|
+
}
|
|
833
|
+
return this.transactionID;
|
|
834
|
+
}
|
|
835
|
+
toAddressString(address) {
|
|
836
|
+
if (typeof address === 'number') {
|
|
837
|
+
let addressString = address.toString(16);
|
|
838
|
+
for (let i = addressString.length; i < 16; i++) {
|
|
839
|
+
addressString = '0' + addressString;
|
|
840
|
+
}
|
|
841
|
+
return `0x${addressString}`;
|
|
842
|
+
}
|
|
843
|
+
else {
|
|
844
|
+
return address.toString();
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
waitressTimeoutFormatter(matcher, timeout) {
|
|
848
|
+
return `Timeout - ${matcher.address} - ${matcher.endpoint}` +
|
|
849
|
+
` - ${matcher.transactionSequenceNumber} - ${matcher.clusterID}` +
|
|
850
|
+
` - ${matcher.commandIdentifier} after ${timeout}ms`;
|
|
851
|
+
}
|
|
852
|
+
waitressValidator(payload, matcher) {
|
|
853
|
+
const transactionSequenceNumber = payload.frame.Header.transactionSequenceNumber;
|
|
854
|
+
return (!matcher.address || payload.address === matcher.address) &&
|
|
855
|
+
payload.endpoint === matcher.endpoint &&
|
|
856
|
+
(!matcher.transactionSequenceNumber || transactionSequenceNumber === matcher.transactionSequenceNumber) &&
|
|
857
|
+
payload.frame.Cluster.ID === matcher.clusterID &&
|
|
858
|
+
matcher.frameType === payload.frame.Header.frameControl.frameType &&
|
|
859
|
+
matcher.commandIdentifier === payload.frame.Header.commandIdentifier &&
|
|
860
|
+
matcher.direction === payload.frame.Header.frameControl.direction;
|
|
861
|
+
}
|
|
862
|
+
checkInterpanLock() {
|
|
863
|
+
if (this.interpanLock) {
|
|
864
|
+
throw new Error(`Cannot execute command, in Inter-PAN mode`);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
exports.default = ZStackAdapter;
|
|
869
869
|
//# sourceMappingURL=zStackAdapter.js.map
|