@willieee802/zigbee-herdsman 0.19.2 → 0.34.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.babelrc.js +0 -4
- package/.release-please-manifest.json +1 -2
- package/CHANGELOG.md +376 -0
- package/README.md +1 -1
- package/dist/adapter/adapter.d.ts +61 -61
- package/dist/adapter/adapter.d.ts.map +1 -1
- package/dist/adapter/adapter.js +158 -146
- 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 +1081 -1060
- 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.d.ts.map +1 -1
- package/dist/adapter/deconz/driver/driver.js +750 -732
- package/dist/adapter/deconz/driver/driver.js.map +1 -1
- 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/frameParser.js.map +1 -1
- package/dist/adapter/deconz/driver/parser.d.ts +12 -12
- package/dist/adapter/deconz/driver/parser.js +63 -61
- package/dist/adapter/deconz/driver/parser.js.map +1 -1
- package/dist/adapter/deconz/driver/writer.d.ts +8 -8
- package/dist/adapter/deconz/driver/writer.js +44 -44
- package/dist/adapter/ember/adapter/emberAdapter.d.ts +817 -0
- package/dist/adapter/ember/adapter/emberAdapter.d.ts.map +1 -0
- package/dist/adapter/ember/adapter/emberAdapter.js +2981 -0
- package/dist/adapter/ember/adapter/emberAdapter.js.map +1 -0
- package/dist/adapter/ember/adapter/endpoints.d.ts +25 -0
- package/dist/adapter/ember/adapter/endpoints.d.ts.map +1 -0
- package/dist/adapter/ember/adapter/endpoints.js +66 -0
- package/dist/adapter/ember/adapter/endpoints.js.map +1 -0
- package/dist/adapter/ember/adapter/index.d.ts +3 -0
- package/dist/adapter/ember/adapter/index.d.ts.map +1 -0
- package/dist/adapter/ember/adapter/index.js +6 -0
- package/dist/adapter/ember/adapter/index.js.map +1 -0
- package/dist/adapter/ember/adapter/oneWaitress.d.ts +97 -0
- package/dist/adapter/ember/adapter/oneWaitress.d.ts.map +1 -0
- package/dist/adapter/ember/adapter/oneWaitress.js +226 -0
- package/dist/adapter/ember/adapter/oneWaitress.js.map +1 -0
- package/dist/adapter/ember/adapter/requestQueue.d.ts +59 -0
- package/dist/adapter/ember/adapter/requestQueue.d.ts.map +1 -0
- package/dist/adapter/ember/adapter/requestQueue.js +144 -0
- package/dist/adapter/ember/adapter/requestQueue.js.map +1 -0
- package/dist/adapter/ember/adapter/tokensManager.d.ts +69 -0
- package/dist/adapter/ember/adapter/tokensManager.d.ts.map +1 -0
- package/dist/adapter/ember/adapter/tokensManager.js +685 -0
- package/dist/adapter/ember/adapter/tokensManager.js.map +1 -0
- package/dist/adapter/ember/consts.d.ts +198 -0
- package/dist/adapter/ember/consts.d.ts.map +1 -0
- package/dist/adapter/ember/consts.js +253 -0
- package/dist/adapter/ember/consts.js.map +1 -0
- package/dist/adapter/ember/enums.d.ts +2184 -0
- package/dist/adapter/ember/enums.d.ts.map +1 -0
- package/dist/adapter/ember/enums.js +2391 -0
- package/dist/adapter/ember/enums.js.map +1 -0
- package/dist/adapter/ember/ezsp/buffalo.d.ts +156 -0
- package/dist/adapter/ember/ezsp/buffalo.d.ts.map +1 -0
- package/dist/adapter/ember/ezsp/buffalo.js +1033 -0
- package/dist/adapter/ember/ezsp/buffalo.js.map +1 -0
- package/dist/adapter/ember/ezsp/consts.d.ts +116 -0
- package/dist/adapter/ember/ezsp/consts.d.ts.map +1 -0
- package/dist/adapter/ember/ezsp/consts.js +128 -0
- package/dist/adapter/ember/ezsp/consts.js.map +1 -0
- package/dist/adapter/ember/ezsp/enums.d.ts +879 -0
- package/dist/adapter/ember/ezsp/enums.d.ts.map +1 -0
- package/dist/adapter/ember/ezsp/enums.js +948 -0
- package/dist/adapter/ember/ezsp/enums.js.map +1 -0
- package/dist/adapter/ember/ezsp/ezsp.d.ts +2664 -0
- package/dist/adapter/ember/ezsp/ezsp.d.ts.map +1 -0
- package/dist/adapter/ember/ezsp/ezsp.js +6438 -0
- package/dist/adapter/ember/ezsp/ezsp.js.map +1 -0
- package/dist/adapter/ember/types.d.ts +733 -0
- package/dist/adapter/ember/types.d.ts.map +1 -0
- package/dist/adapter/ember/types.js +3 -0
- package/dist/adapter/ember/types.js.map +1 -0
- package/dist/adapter/ember/uart/ash.d.ts +451 -0
- package/dist/adapter/ember/uart/ash.d.ts.map +1 -0
- package/dist/adapter/ember/uart/ash.js +1584 -0
- package/dist/adapter/ember/uart/ash.js.map +1 -0
- package/dist/adapter/ember/uart/consts.d.ts +91 -0
- package/dist/adapter/ember/uart/consts.d.ts.map +1 -0
- package/dist/adapter/ember/uart/consts.js +100 -0
- package/dist/adapter/ember/uart/consts.js.map +1 -0
- package/dist/adapter/ember/uart/enums.d.ts +191 -0
- package/dist/adapter/ember/uart/enums.d.ts.map +1 -0
- package/dist/adapter/ember/uart/enums.js +197 -0
- package/dist/adapter/ember/uart/enums.js.map +1 -0
- package/dist/adapter/ember/uart/parser.d.ts +10 -0
- package/dist/adapter/ember/uart/parser.d.ts.map +1 -0
- package/dist/adapter/ember/uart/parser.js +41 -0
- package/dist/adapter/ember/uart/parser.js.map +1 -0
- package/dist/adapter/ember/uart/queues.d.ts +85 -0
- package/dist/adapter/ember/uart/queues.d.ts.map +1 -0
- package/dist/adapter/ember/uart/queues.js +212 -0
- package/dist/adapter/ember/uart/queues.js.map +1 -0
- package/dist/adapter/ember/uart/writer.d.ts +15 -0
- package/dist/adapter/ember/uart/writer.d.ts.map +1 -0
- package/dist/adapter/ember/uart/writer.js +48 -0
- package/dist/adapter/ember/uart/writer.js.map +1 -0
- package/dist/adapter/ember/utils/initters.d.ts +20 -0
- package/dist/adapter/ember/utils/initters.d.ts.map +1 -0
- package/dist/adapter/ember/utils/initters.js +58 -0
- package/dist/adapter/ember/utils/initters.js.map +1 -0
- package/dist/adapter/ember/utils/math.d.ts +51 -0
- package/dist/adapter/ember/utils/math.d.ts.map +1 -0
- package/dist/adapter/ember/utils/math.js +102 -0
- package/dist/adapter/ember/utils/math.js.map +1 -0
- package/dist/adapter/ember/zdo.d.ts +921 -0
- package/dist/adapter/ember/zdo.d.ts.map +1 -0
- package/dist/adapter/ember/zdo.js +723 -0
- package/dist/adapter/ember/zdo.js.map +1 -0
- package/dist/adapter/events.d.ts +47 -47
- package/dist/adapter/events.js +13 -14
- package/dist/adapter/events.js.map +1 -1
- package/dist/adapter/ezsp/adapter/backup.d.ts +9 -9
- package/dist/adapter/ezsp/adapter/backup.d.ts.map +1 -1
- package/dist/adapter/ezsp/adapter/backup.js +72 -53
- package/dist/adapter/ezsp/adapter/backup.js.map +1 -1
- package/dist/adapter/ezsp/adapter/ezspAdapter.d.ts +61 -59
- package/dist/adapter/ezsp/adapter/ezspAdapter.d.ts.map +1 -1
- package/dist/adapter/ezsp/adapter/ezspAdapter.js +629 -603
- package/dist/adapter/ezsp/adapter/ezspAdapter.js.map +1 -1
- 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 -36
- package/dist/adapter/ezsp/driver/commands.d.ts.map +1 -1
- package/dist/adapter/ezsp/driver/commands.js +2388 -2359
- 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 +106 -103
- package/dist/adapter/ezsp/driver/driver.d.ts.map +1 -1
- package/dist/adapter/ezsp/driver/driver.js +731 -639
- package/dist/adapter/ezsp/driver/driver.js.map +1 -1
- package/dist/adapter/ezsp/driver/ezsp.d.ts +105 -96
- package/dist/adapter/ezsp/driver/ezsp.d.ts.map +1 -1
- package/dist/adapter/ezsp/driver/ezsp.js +651 -586
- package/dist/adapter/ezsp/driver/ezsp.js.map +1 -1
- package/dist/adapter/ezsp/driver/frame.d.ts +40 -0
- package/dist/adapter/ezsp/driver/frame.d.ts.map +1 -0
- package/dist/adapter/ezsp/driver/frame.js +101 -0
- package/dist/adapter/ezsp/driver/frame.js.map +1 -0
- 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.d.ts.map +1 -1
- package/dist/adapter/ezsp/driver/multicast.js +77 -74
- package/dist/adapter/ezsp/driver/multicast.js.map +1 -1
- package/dist/adapter/ezsp/driver/parser.d.ts +11 -12
- package/dist/adapter/ezsp/driver/parser.d.ts.map +1 -1
- package/dist/adapter/ezsp/driver/parser.js +104 -111
- package/dist/adapter/ezsp/driver/parser.js.map +1 -1
- 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.d.ts.map +1 -1
- package/dist/adapter/ezsp/driver/types/index.js +138 -133
- package/dist/adapter/ezsp/driver/types/index.js.map +1 -1
- package/dist/adapter/ezsp/driver/types/named.d.ts +1287 -697
- package/dist/adapter/ezsp/driver/types/named.d.ts.map +1 -1
- package/dist/adapter/ezsp/driver/types/named.js +2329 -1726
- package/dist/adapter/ezsp/driver/types/named.js.map +1 -1
- package/dist/adapter/ezsp/driver/types/struct.d.ts +270 -251
- package/dist/adapter/ezsp/driver/types/struct.d.ts.map +1 -1
- package/dist/adapter/ezsp/driver/types/struct.js +803 -708
- package/dist/adapter/ezsp/driver/types/struct.js.map +1 -1
- package/dist/adapter/ezsp/driver/uart.d.ts +48 -44
- package/dist/adapter/ezsp/driver/uart.d.ts.map +1 -1
- package/dist/adapter/ezsp/driver/uart.js +382 -368
- 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/crc16ccitt.js.map +1 -1
- package/dist/adapter/ezsp/driver/utils/index.d.ts +18 -18
- package/dist/adapter/ezsp/driver/utils/index.js +72 -67
- package/dist/adapter/ezsp/driver/utils/index.js.map +1 -1
- package/dist/adapter/ezsp/driver/writer.d.ts +13 -13
- package/dist/adapter/ezsp/driver/writer.d.ts.map +1 -1
- package/dist/adapter/ezsp/driver/writer.js +85 -88
- package/dist/adapter/ezsp/driver/writer.js.map +1 -1
- package/dist/adapter/index.d.ts +4 -4
- package/dist/adapter/index.js +35 -35
- package/dist/adapter/serialPort.d.ts +10 -8
- package/dist/adapter/serialPort.d.ts.map +1 -1
- package/dist/adapter/serialPort.js +53 -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/serialPortUtils.js.map +1 -1
- 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.d.ts.map +1 -1
- 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.d.ts.map +1 -1
- package/dist/adapter/z-stack/adapter/adapter-backup.js +462 -460
- package/dist/adapter/z-stack/adapter/adapter-backup.js.map +1 -1
- 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/adapter-nv-memory.js.map +1 -1
- 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.d.ts.map +1 -1
- package/dist/adapter/z-stack/adapter/manager.js +482 -476
- package/dist/adapter/z-stack/adapter/manager.js.map +1 -1
- package/dist/adapter/z-stack/adapter/tstype.d.ts +6 -6
- package/dist/adapter/z-stack/adapter/tstype.js +9 -10
- package/dist/adapter/z-stack/adapter/tstype.js.map +1 -1
- package/dist/adapter/z-stack/adapter/zStackAdapter.d.ts +81 -81
- package/dist/adapter/z-stack/adapter/zStackAdapter.d.ts.map +1 -1
- package/dist/adapter/z-stack/adapter/zStackAdapter.js +891 -868
- package/dist/adapter/z-stack/adapter/zStackAdapter.js.map +1 -1
- 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.d.ts.map +1 -1
- package/dist/adapter/z-stack/constants/common.js +292 -289
- package/dist/adapter/z-stack/constants/common.js.map +1 -1
- 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-entry.js.map +1 -1
- 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-entry.js.map +1 -1
- 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 +296 -295
- package/dist/adapter/z-stack/structs/struct.js.map +1 -1
- package/dist/adapter/z-stack/structs/table.d.ts +94 -94
- package/dist/adapter/z-stack/structs/table.js +163 -161
- package/dist/adapter/z-stack/structs/table.js.map +1 -1
- package/dist/adapter/z-stack/unpi/constants.d.ts +28 -28
- package/dist/adapter/z-stack/unpi/constants.js +39 -41
- package/dist/adapter/z-stack/unpi/constants.js.map +1 -1
- package/dist/adapter/z-stack/unpi/frame.d.ts +16 -16
- package/dist/adapter/z-stack/unpi/frame.js +54 -48
- package/dist/adapter/z-stack/unpi/frame.js.map +1 -1
- 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 +75 -74
- package/dist/adapter/z-stack/unpi/parser.js.map +1 -1
- 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/channel-list.js.map +1 -1
- 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/buffaloZnp.js.map +1 -1
- 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 +44 -43
- package/dist/adapter/z-stack/znp/znp.d.ts.map +1 -1
- package/dist/adapter/z-stack/znp/znp.js +326 -325
- package/dist/adapter/z-stack/znp/znp.js.map +1 -1
- package/dist/adapter/z-stack/znp/zpiObject.d.ts +19 -19
- package/dist/adapter/z-stack/znp/zpiObject.js +102 -96
- package/dist/adapter/z-stack/znp/zpiObject.js.map +1 -1
- 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 +70 -69
- package/dist/adapter/zigate/adapter/zigateAdapter.d.ts.map +1 -1
- package/dist/adapter/zigate/adapter/zigateAdapter.js +689 -678
- package/dist/adapter/zigate/adapter/zigateAdapter.js.map +1 -1
- package/dist/adapter/zigate/debug.d.ts +7 -7
- package/dist/adapter/zigate/debug.d.ts.map +1 -1
- package/dist/adapter/zigate/debug.js +19 -22
- package/dist/adapter/zigate/debug.js.map +1 -1
- package/dist/adapter/zigate/driver/buffaloZiGate.d.ts +18 -18
- package/dist/adapter/zigate/driver/buffaloZiGate.js +139 -139
- package/dist/adapter/zigate/driver/buffaloZiGate.js.map +1 -1
- package/dist/adapter/zigate/driver/commandType.d.ts +41 -41
- package/dist/adapter/zigate/driver/commandType.js +385 -385
- package/dist/adapter/zigate/driver/commandType.js.map +1 -1
- package/dist/adapter/zigate/driver/constants.d.ts +276 -276
- package/dist/adapter/zigate/driver/constants.d.ts.map +1 -1
- package/dist/adapter/zigate/driver/constants.js +371 -371
- package/dist/adapter/zigate/driver/constants.js.map +1 -1
- 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/messageType.js.map +1 -1
- 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 +110 -106
- package/dist/adapter/zigate/driver/ziGateObject.js.map +1 -1
- package/dist/adapter/zigate/driver/zigate.d.ts +49 -49
- package/dist/adapter/zigate/driver/zigate.d.ts.map +1 -1
- package/dist/adapter/zigate/driver/zigate.js +296 -303
- package/dist/adapter/zigate/driver/zigate.js.map +1 -1
- package/dist/buffalo/buffalo.d.ts +50 -50
- package/dist/buffalo/buffalo.js +324 -322
- 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 +113 -113
- package/dist/controller/controller.d.ts.map +1 -1
- package/dist/controller/controller.js +641 -619
- package/dist/controller/controller.js.map +1 -1
- package/dist/controller/database.d.ts +18 -18
- package/dist/controller/database.js +96 -93
- package/dist/controller/database.js.map +1 -1
- package/dist/controller/events.d.ts +58 -58
- package/dist/controller/events.d.ts.map +1 -1
- package/dist/controller/events.js +108 -101
- package/dist/controller/events.js.map +1 -1
- package/dist/controller/greenPower.d.ts +12 -12
- package/dist/controller/greenPower.js +221 -220
- package/dist/controller/greenPower.js.map +1 -1
- package/dist/controller/helpers/index.d.ts +2 -2
- package/dist/controller/helpers/index.js +28 -28
- package/dist/controller/helpers/request.d.ts +21 -22
- package/dist/controller/helpers/request.d.ts.map +1 -1
- package/dist/controller/helpers/request.js +77 -71
- package/dist/controller/helpers/request.js.map +1 -1
- package/dist/controller/helpers/requestQueue.d.ts +13 -0
- package/dist/controller/helpers/requestQueue.d.ts.map +1 -0
- package/dist/controller/helpers/requestQueue.js +116 -0
- package/dist/controller/helpers/requestQueue.js.map +1 -0
- package/dist/controller/helpers/zclFrameConverter.d.ts +7 -7
- package/dist/controller/helpers/zclFrameConverter.d.ts.map +1 -1
- package/dist/controller/helpers/zclFrameConverter.js +50 -31
- package/dist/controller/helpers/zclFrameConverter.js.map +1 -1
- package/dist/controller/helpers/zclTransactionSequenceNumber.d.ts +5 -5
- package/dist/controller/helpers/zclTransactionSequenceNumber.js +13 -13
- package/dist/controller/helpers/zclTransactionSequenceNumber.js.map +1 -1
- 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 -133
- package/dist/controller/model/device.d.ts.map +1 -1
- package/dist/controller/model/device.js +724 -717
- package/dist/controller/model/device.js.map +1 -1
- package/dist/controller/model/endpoint.d.ts +128 -131
- package/dist/controller/model/endpoint.d.ts.map +1 -1
- package/dist/controller/model/endpoint.js +755 -816
- 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/entity.js.map +1 -1
- package/dist/controller/model/group.d.ts +38 -38
- package/dist/controller/model/group.d.ts.map +1 -1
- package/dist/controller/model/group.js +225 -221
- package/dist/controller/model/group.js.map +1 -1
- 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 +159 -157
- package/dist/controller/touchlink.js.map +1 -1
- package/dist/controller/tstype.d.ts +20 -21
- package/dist/controller/tstype.d.ts.map +1 -1
- package/dist/controller/tstype.js +8 -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/assertString.js.map +1 -1
- package/dist/utils/backup.d.ts +20 -20
- package/dist/utils/backup.d.ts.map +1 -1
- package/dist/utils/backup.js +189 -187
- package/dist/utils/backup.js.map +1 -1
- 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.d.ts.map +1 -1
- package/dist/utils/queue.js +61 -50
- package/dist/utils/queue.js.map +1 -1
- 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.d.ts.map +1 -1
- package/dist/utils/waitress.js +68 -61
- package/dist/utils/waitress.js.map +1 -1
- package/dist/zcl/buffaloZcl.d.ts +41 -41
- package/dist/zcl/buffaloZcl.d.ts.map +1 -1
- package/dist/zcl/buffaloZcl.js +594 -591
- package/dist/zcl/buffaloZcl.js.map +1 -1
- 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 +5520 -5335
- package/dist/zcl/definition/cluster.js.map +1 -1
- package/dist/zcl/definition/dataType.d.ts +59 -59
- package/dist/zcl/definition/dataType.js +64 -64
- 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 +1077 -1074
- package/dist/zcl/definition/manufacturerCode.d.ts.map +1 -1
- package/dist/zcl/definition/manufacturerCode.js +1082 -1079
- package/dist/zcl/definition/manufacturerCode.js.map +1 -1
- 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 +16 -15
- package/dist/zcl/index.d.ts.map +1 -1
- package/dist/zcl/index.js +55 -55
- package/dist/zcl/index.js.map +1 -1
- package/dist/zcl/tstype.d.ts +56 -56
- package/dist/zcl/tstype.js +9 -10
- package/dist/zcl/tstype.js.map +1 -1
- package/dist/zcl/utils.d.ts +6 -6
- package/dist/zcl/utils.js +164 -165
- package/dist/zcl/utils.js.map +1 -1
- package/dist/zcl/zclFrame.d.ts +40 -45
- package/dist/zcl/zclFrame.d.ts.map +1 -1
- package/dist/zcl/zclFrame.js +351 -347
- package/dist/zcl/zclFrame.js.map +1 -1
- package/dist/zcl/zclHeader.d.ts +9 -0
- package/dist/zcl/zclHeader.d.ts.map +1 -0
- package/dist/zcl/zclHeader.js +3 -0
- package/dist/zcl/zclHeader.js.map +1 -0
- package/dist/zcl/zclStatusError.d.ts +5 -5
- package/dist/zcl/zclStatusError.js +14 -13
- package/dist/zcl/zclStatusError.js.map +1 -1
- package/package.json +11 -11
- package/release-please-config.json +1 -5
- package/tsconfig.json +4 -2
|
@@ -0,0 +1,2981 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.EmberAdapter = void 0;
|
|
7
|
+
/* istanbul ignore file */
|
|
8
|
+
const debug_1 = __importDefault(require("debug"));
|
|
9
|
+
const es6_1 = __importDefault(require("fast-deep-equal/es6"));
|
|
10
|
+
const mz_1 = require("mz");
|
|
11
|
+
const serialPortUtils_1 = __importDefault(require("../../serialPortUtils"));
|
|
12
|
+
const socketPortUtils_1 = __importDefault(require("../../socketPortUtils"));
|
|
13
|
+
const utils_1 = require("../../../utils");
|
|
14
|
+
const __1 = require("../..");
|
|
15
|
+
const zcl_1 = require("../../../zcl");
|
|
16
|
+
const cluster_1 = __importDefault(require("../../../zcl/definition/cluster"));
|
|
17
|
+
const events_1 = require("../../events");
|
|
18
|
+
const math_1 = require("../utils/math");
|
|
19
|
+
const ezsp_1 = require("../ezsp/ezsp");
|
|
20
|
+
const consts_1 = require("../ezsp/consts");
|
|
21
|
+
const enums_1 = require("../ezsp/enums");
|
|
22
|
+
const buffalo_1 = require("../ezsp/buffalo");
|
|
23
|
+
const enums_2 = require("../enums");
|
|
24
|
+
const zdo_1 = require("../zdo");
|
|
25
|
+
const consts_2 = require("../consts");
|
|
26
|
+
const requestQueue_1 = require("./requestQueue");
|
|
27
|
+
const endpoints_1 = require("./endpoints");
|
|
28
|
+
const initters_1 = require("../utils/initters");
|
|
29
|
+
const crypto_1 = require("crypto");
|
|
30
|
+
const oneWaitress_1 = require("./oneWaitress");
|
|
31
|
+
const debug = (0, debug_1.default)('zigbee-herdsman:adapter:ember:adapter');
|
|
32
|
+
/** Enum to pass strings from numbers up to Z2M. */
|
|
33
|
+
var RoutingTableStatus;
|
|
34
|
+
(function (RoutingTableStatus) {
|
|
35
|
+
RoutingTableStatus[RoutingTableStatus["ACTIVE"] = 0] = "ACTIVE";
|
|
36
|
+
RoutingTableStatus[RoutingTableStatus["DISCOVERY_UNDERWAY"] = 1] = "DISCOVERY_UNDERWAY";
|
|
37
|
+
RoutingTableStatus[RoutingTableStatus["DISCOVERY_FAILED"] = 2] = "DISCOVERY_FAILED";
|
|
38
|
+
RoutingTableStatus[RoutingTableStatus["INACTIVE"] = 3] = "INACTIVE";
|
|
39
|
+
RoutingTableStatus[RoutingTableStatus["VALIDATION_UNDERWAY"] = 4] = "VALIDATION_UNDERWAY";
|
|
40
|
+
RoutingTableStatus[RoutingTableStatus["RESERVED1"] = 5] = "RESERVED1";
|
|
41
|
+
RoutingTableStatus[RoutingTableStatus["RESERVED2"] = 6] = "RESERVED2";
|
|
42
|
+
RoutingTableStatus[RoutingTableStatus["RESERVED3"] = 7] = "RESERVED3";
|
|
43
|
+
})(RoutingTableStatus || (RoutingTableStatus = {}));
|
|
44
|
+
;
|
|
45
|
+
/** Events specific to OneWaitress usage. */
|
|
46
|
+
var OneWaitressEvents;
|
|
47
|
+
(function (OneWaitressEvents) {
|
|
48
|
+
OneWaitressEvents["STACK_STATUS_NETWORK_UP"] = "STACK_STATUS_NETWORK_UP";
|
|
49
|
+
OneWaitressEvents["STACK_STATUS_NETWORK_DOWN"] = "STACK_STATUS_NETWORK_DOWN";
|
|
50
|
+
OneWaitressEvents["STACK_STATUS_NETWORK_OPENED"] = "STACK_STATUS_NETWORK_OPENED";
|
|
51
|
+
OneWaitressEvents["STACK_STATUS_NETWORK_CLOSED"] = "STACK_STATUS_NETWORK_CLOSED";
|
|
52
|
+
})(OneWaitressEvents || (OneWaitressEvents = {}));
|
|
53
|
+
;
|
|
54
|
+
var NetworkInitAction;
|
|
55
|
+
(function (NetworkInitAction) {
|
|
56
|
+
/** Ain't that nice! */
|
|
57
|
+
NetworkInitAction[NetworkInitAction["DONE"] = 0] = "DONE";
|
|
58
|
+
/** Config mismatch, must leave network. */
|
|
59
|
+
NetworkInitAction[NetworkInitAction["LEAVE"] = 1] = "LEAVE";
|
|
60
|
+
/** Config mismatched, left network. Will evaluate forming from backup or config next. */
|
|
61
|
+
NetworkInitAction[NetworkInitAction["LEFT"] = 2] = "LEFT";
|
|
62
|
+
/** Form the network using config. No backup, or backup mismatch. */
|
|
63
|
+
NetworkInitAction[NetworkInitAction["FORM_CONFIG"] = 3] = "FORM_CONFIG";
|
|
64
|
+
/** Re-form the network using full backed-up data. */
|
|
65
|
+
NetworkInitAction[NetworkInitAction["FORM_BACKUP"] = 4] = "FORM_BACKUP";
|
|
66
|
+
})(NetworkInitAction || (NetworkInitAction = {}));
|
|
67
|
+
;
|
|
68
|
+
/** NOTE: Drivers can override `manufacturer`. Verify logic doesn't work in most cases anyway. */
|
|
69
|
+
const autoDetectDefinitions = [
|
|
70
|
+
/** NOTE: Manuf code "0x1321" for "Shenzhen Sonoff Technologies Co., Ltd." */
|
|
71
|
+
{ manufacturer: 'ITEAD', vendorId: '1a86', productId: '55d4' }, // Sonoff ZBDongle-E
|
|
72
|
+
/** NOTE: Manuf code "0x134B" for "Nabu Casa, Inc." */
|
|
73
|
+
{ manufacturer: 'Nabu Casa', vendorId: '10c4', productId: 'ea60' }, // Home Assistant SkyConnect
|
|
74
|
+
];
|
|
75
|
+
/**
|
|
76
|
+
* Config for EMBER_LOW_RAM_CONCENTRATOR type concentrator.
|
|
77
|
+
*
|
|
78
|
+
* Based on ZigbeeMinimalHost/zigpc
|
|
79
|
+
*/
|
|
80
|
+
const LOW_RAM_CONCENTRATOR_CONFIG = {
|
|
81
|
+
minTime: 5, // zigpc: 10
|
|
82
|
+
maxTime: 60, // zigpc: 60
|
|
83
|
+
routeErrorThreshold: 3, // zigpc: 3
|
|
84
|
+
deliveryFailureThreshold: 1, // zigpc: 1, ZigbeeMinimalHost: 3
|
|
85
|
+
mapHops: 0, // zigpc: 0
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* Config for EMBER_HIGH_RAM_CONCENTRATOR type concentrator.
|
|
89
|
+
*
|
|
90
|
+
* XXX: For now, same as low, until proper values can be determined.
|
|
91
|
+
*/
|
|
92
|
+
const HIGH_RAM_CONCENTRATOR_CONFIG = {
|
|
93
|
+
minTime: 5,
|
|
94
|
+
maxTime: 60,
|
|
95
|
+
routeErrorThreshold: 3,
|
|
96
|
+
deliveryFailureThreshold: 1,
|
|
97
|
+
mapHops: 0,
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* Application generated ZDO messages use sequence numbers 0-127, and the stack
|
|
101
|
+
* uses sequence numbers 128-255. This simplifies life by eliminating the need
|
|
102
|
+
* for coordination between the two entities, and allows both to send ZDO
|
|
103
|
+
* messages with non-conflicting sequence numbers.
|
|
104
|
+
*/
|
|
105
|
+
const APPLICATION_ZDO_SEQUENCE_MASK = 0x7F;
|
|
106
|
+
/** Current revision of the spec by zigbee alliance. XXX: what are `Zigbee Pro 2023` devices reporting?? */
|
|
107
|
+
const CURRENT_ZIGBEE_SPEC_REVISION = 23;
|
|
108
|
+
/** Each scan period is 15.36ms. Scan for at least 200ms (2^4 + 1 periods) to pick up WiFi beacon frames. */
|
|
109
|
+
const ENERGY_SCAN_DURATION = 4;
|
|
110
|
+
/** Oldest supported EZSP version for backups. Don't take the risk to restore a broken network until older backup versions can be investigated. */
|
|
111
|
+
const BACKUP_OLDEST_SUPPORTED_EZSP_VERSION = 12;
|
|
112
|
+
/**
|
|
113
|
+
* 9sec is minimum recommended for `ezspBroadcastNextNetworkKey` to have propagated throughout network.
|
|
114
|
+
* NOTE: This is blocking the request queue, so we shouldn't go crazy high.
|
|
115
|
+
*/
|
|
116
|
+
const BROADCAST_NETWORK_KEY_SWITCH_WAIT_TIME = 15000;
|
|
117
|
+
/**
|
|
118
|
+
* Stack configuration values for various supported stacks.
|
|
119
|
+
*/
|
|
120
|
+
const STACK_CONFIGS = {
|
|
121
|
+
"default": {
|
|
122
|
+
/** <1-250> (Default: 2) @see EzspConfigId.ADDRESS_TABLE_SIZE */
|
|
123
|
+
ADDRESS_TABLE_SIZE: 16, // zigpc: 32, darkxst: 16
|
|
124
|
+
/** <0-4> (Default: 2) @see EzspConfigId.TRUST_CENTER_ADDRESS_CACHE_SIZE */
|
|
125
|
+
TRUST_CENTER_ADDRESS_CACHE_SIZE: 2,
|
|
126
|
+
/** (Default: USE_TOKEN) @see EzspConfigId.TX_POWER_MODE */
|
|
127
|
+
TX_POWER_MODE: enums_2.EmberTXPowerMode.USE_TOKEN,
|
|
128
|
+
/** <-> (Default: 1) @see EzspConfigId.SUPPORTED_NETWORKS */
|
|
129
|
+
SUPPORTED_NETWORKS: 1,
|
|
130
|
+
/** <-> (Default: ) @see EzspConfigId.STACK_PROFILE */
|
|
131
|
+
STACK_PROFILE: consts_2.STACK_PROFILE_ZIGBEE_PRO,
|
|
132
|
+
/** <-> (Default: ) @see EzspConfigId.SECURITY_LEVEL */
|
|
133
|
+
SECURITY_LEVEL: consts_2.SECURITY_LEVEL_Z3,
|
|
134
|
+
/** (Default: KEEP_ALIVE_SUPPORT_ALL) @see EzspValueId.END_DEVICE_KEEP_ALIVE_SUPPORT_MODE */
|
|
135
|
+
END_DEVICE_KEEP_ALIVE_SUPPORT_MODE: enums_2.EmberKeepAliveMode.KEEP_ALIVE_SUPPORT_ALL, // zigpc: KEEP_ALIVE_SUPPORT_ALL
|
|
136
|
+
/** <-> (Default: MAXIMUM_APS_PAYLOAD_LENGTH) @see EzspValueId.MAXIMUM_INCOMING_TRANSFER_SIZE */
|
|
137
|
+
MAXIMUM_INCOMING_TRANSFER_SIZE: consts_2.MAXIMUM_APS_PAYLOAD_LENGTH,
|
|
138
|
+
/** <-> (Default: MAXIMUM_APS_PAYLOAD_LENGTH) @see EzspValueId.MAXIMUM_OUTGOING_TRANSFER_SIZE */
|
|
139
|
+
MAXIMUM_OUTGOING_TRANSFER_SIZE: consts_2.MAXIMUM_APS_PAYLOAD_LENGTH,
|
|
140
|
+
/** <-> (Default: 10000) @see EzspValueId.TRANSIENT_DEVICE_TIMEOUT */
|
|
141
|
+
TRANSIENT_DEVICE_TIMEOUT: 10000,
|
|
142
|
+
/** <0-127> (Default: 2) @see EzspConfigId.BINDING_TABLE_SIZE */
|
|
143
|
+
BINDING_TABLE_SIZE: 5, // zigpc: 2, Z3GatewayGPCombo: 5
|
|
144
|
+
/** <0-127> (Default: 0) @see EzspConfigId.KEY_TABLE_SIZE */
|
|
145
|
+
KEY_TABLE_SIZE: 0, // zigpc: 4
|
|
146
|
+
/** <6-64> (Default: 6) @see EzspConfigId.MAX_END_DEVICE_CHILDREN */
|
|
147
|
+
MAX_END_DEVICE_CHILDREN: 6, // zigpc: 6
|
|
148
|
+
/** <1-255> (Default: 10) @see EzspConfigId.APS_UNICAST_MESSAGE_COUNT */
|
|
149
|
+
APS_UNICAST_MESSAGE_COUNT: 20, // zigpc: 10, darkxst: 20
|
|
150
|
+
/** <15-254> (Default: 15) @see EzspConfigId.BROADCAST_TABLE_SIZE */
|
|
151
|
+
BROADCAST_TABLE_SIZE: 15, // zigpc: 15, Z3GatewayGPCombo: 35 - NOTE: Sonoff Dongle-E fails at 35
|
|
152
|
+
/** [1, 16, 26] (Default: 16). @see EzspConfigId.NEIGHBOR_TABLE_SIZE */
|
|
153
|
+
NEIGHBOR_TABLE_SIZE: 26, // zigpc: 16, darkxst: 26
|
|
154
|
+
/** (Default: 8) @see EzspConfigId.END_DEVICE_POLL_TIMEOUT */
|
|
155
|
+
END_DEVICE_POLL_TIMEOUT: 8, // zigpc: 8
|
|
156
|
+
/** <0-65535> (Default: 300) @see EzspConfigId.TRANSIENT_KEY_TIMEOUT_S */
|
|
157
|
+
TRANSIENT_KEY_TIMEOUT_S: 300, // zigpc: 65535
|
|
158
|
+
/** <-> (Default: 16) @see EzspConfigId.RETRY_QUEUE_SIZE */
|
|
159
|
+
RETRY_QUEUE_SIZE: 16,
|
|
160
|
+
/** <0-255> (Default: 0) @see EzspConfigId.SOURCE_ROUTE_TABLE_SIZE */
|
|
161
|
+
SOURCE_ROUTE_TABLE_SIZE: 200, // Z3GatewayGPCombo: 100, darkxst: 200
|
|
162
|
+
/** <1-250> (Default: 8) @see EzspConfigId.MULTICAST_TABLE_SIZE */
|
|
163
|
+
MULTICAST_TABLE_SIZE: 16, // darkxst: 16
|
|
164
|
+
},
|
|
165
|
+
"zigbeed": {
|
|
166
|
+
ADDRESS_TABLE_SIZE: 128,
|
|
167
|
+
TRUST_CENTER_ADDRESS_CACHE_SIZE: 2,
|
|
168
|
+
TX_POWER_MODE: enums_2.EmberTXPowerMode.USE_TOKEN,
|
|
169
|
+
SUPPORTED_NETWORKS: 1,
|
|
170
|
+
STACK_PROFILE: consts_2.STACK_PROFILE_ZIGBEE_PRO,
|
|
171
|
+
SECURITY_LEVEL: consts_2.SECURITY_LEVEL_Z3,
|
|
172
|
+
END_DEVICE_KEEP_ALIVE_SUPPORT_MODE: enums_2.EmberKeepAliveMode.KEEP_ALIVE_SUPPORT_ALL,
|
|
173
|
+
MAXIMUM_INCOMING_TRANSFER_SIZE: consts_2.MAXIMUM_APS_PAYLOAD_LENGTH,
|
|
174
|
+
MAXIMUM_OUTGOING_TRANSFER_SIZE: consts_2.MAXIMUM_APS_PAYLOAD_LENGTH,
|
|
175
|
+
TRANSIENT_DEVICE_TIMEOUT: 10000,
|
|
176
|
+
BINDING_TABLE_SIZE: 128,
|
|
177
|
+
KEY_TABLE_SIZE: 0, // zigbeed 128
|
|
178
|
+
MAX_END_DEVICE_CHILDREN: 64,
|
|
179
|
+
APS_UNICAST_MESSAGE_COUNT: 32,
|
|
180
|
+
BROADCAST_TABLE_SIZE: 15,
|
|
181
|
+
NEIGHBOR_TABLE_SIZE: 26,
|
|
182
|
+
END_DEVICE_POLL_TIMEOUT: 8,
|
|
183
|
+
TRANSIENT_KEY_TIMEOUT_S: 300,
|
|
184
|
+
RETRY_QUEUE_SIZE: 16,
|
|
185
|
+
SOURCE_ROUTE_TABLE_SIZE: 254,
|
|
186
|
+
MULTICAST_TABLE_SIZE: 128,
|
|
187
|
+
/*
|
|
188
|
+
ROUTE_TABLE_SIZE: 254,
|
|
189
|
+
DISCOVERY_TABLE_SIZE: 64,
|
|
190
|
+
PACKET_BUFFER_COUNT: 255,
|
|
191
|
+
CUSTOM_MAC_FILTER_TABLE_SIZE: 64,
|
|
192
|
+
MAC_FILTER_TABLE_SIZE: 32,
|
|
193
|
+
CHILD_TABLE_SIZE: 64,
|
|
194
|
+
PLUGIN_ZIGBEE_PRO_STACK_CHILD_TABLE_SIZE: 64,
|
|
195
|
+
APS_MESSAGE_COUNT: 64,
|
|
196
|
+
*/
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
/**
|
|
200
|
+
* Enabling this allows to immediately reject requests that won't be able to get to their destination.
|
|
201
|
+
* However, it causes more NCP calls, notably to get the source route overhead.
|
|
202
|
+
* XXX: Needs further testing before enabling
|
|
203
|
+
*/
|
|
204
|
+
const CHECK_APS_PAYLOAD_LENGTH = false;
|
|
205
|
+
/** Time for a ZDO request to get a callback response. ASH is 2400*6 for ACK timeout. */
|
|
206
|
+
const DEFAULT_ZDO_REQUEST_TIMEOUT = 15000; // msec
|
|
207
|
+
/** Time for a ZCL request to get a callback response. ASH is 2400*6 for ACK timeout. */
|
|
208
|
+
const DEFAULT_ZCL_REQUEST_TIMEOUT = 15000; //msec
|
|
209
|
+
/** Time for a network-related request to get a response (usually via event). */
|
|
210
|
+
const DEFAULT_NETWORK_REQUEST_TIMEOUT = 10000; // nothing on the network to bother requests, should be much faster than this
|
|
211
|
+
/** Time between watchdog counters reading/clearing */
|
|
212
|
+
const WATCHDOG_COUNTERS_FEED_INTERVAL = 3600000; // every hour...
|
|
213
|
+
/**
|
|
214
|
+
* Relay calls between Z2M and EZSP-layer and handle any error that might occur via queue & waitress.
|
|
215
|
+
*
|
|
216
|
+
* Anything post `start` that requests anything from the EZSP layer must run through the request queue for proper execution flow.
|
|
217
|
+
*/
|
|
218
|
+
class EmberAdapter extends __1.Adapter {
|
|
219
|
+
/** Key in STACK_CONFIGS */
|
|
220
|
+
stackConfig;
|
|
221
|
+
/** EMBER_LOW_RAM_CONCENTRATOR or EMBER_HIGH_RAM_CONCENTRATOR. */
|
|
222
|
+
concentratorType;
|
|
223
|
+
ezsp;
|
|
224
|
+
version;
|
|
225
|
+
requestQueue;
|
|
226
|
+
oneWaitress;
|
|
227
|
+
/** Periodically retrieve counters then clear them. */
|
|
228
|
+
watchdogCountersHandle;
|
|
229
|
+
/** Hold ZDO request in process. */
|
|
230
|
+
zdoRequestBuffalo;
|
|
231
|
+
/** Sequence number used for ZDO requests. static uint8_t */
|
|
232
|
+
zdoRequestSequence;
|
|
233
|
+
/** Default radius used for broadcast ZDO requests. uint8_t */
|
|
234
|
+
zdoRequestRadius;
|
|
235
|
+
interpanLock;
|
|
236
|
+
/**
|
|
237
|
+
* Cached network params to avoid NCP calls. Prevents frequent EZSP transactions.
|
|
238
|
+
* NOTE: Do not use directly, use getter functions for it that check if valid or need retrieval from NCP.
|
|
239
|
+
*/
|
|
240
|
+
networkCache;
|
|
241
|
+
defaultApsOptions;
|
|
242
|
+
/**
|
|
243
|
+
* Mirrors the NCP multicast table. null === not in use.
|
|
244
|
+
* Index 0 is Green Power and must always remain there.
|
|
245
|
+
*/
|
|
246
|
+
multicastTable;
|
|
247
|
+
constructor(networkOptions, serialPortOptions, backupPath, adapterOptions, logger) {
|
|
248
|
+
super(networkOptions, serialPortOptions, backupPath, adapterOptions, logger);
|
|
249
|
+
// TODO config, should be fine like this for now?
|
|
250
|
+
this.stackConfig = socketPortUtils_1.default.isTcpPath(serialPortOptions.path) ? 'zigbeed' : 'default';
|
|
251
|
+
// TODO config
|
|
252
|
+
this.concentratorType = consts_2.EMBER_HIGH_RAM_CONCENTRATOR;
|
|
253
|
+
// TODO: config dispatch interval, tested at 100, 80, 60
|
|
254
|
+
this.requestQueue = new requestQueue_1.EmberRequestQueue(60);
|
|
255
|
+
this.oneWaitress = new oneWaitress_1.EmberOneWaitress();
|
|
256
|
+
this.zdoRequestBuffalo = new buffalo_1.EzspBuffalo(Buffer.alloc(consts_1.EZSP_MAX_FRAME_LENGTH));
|
|
257
|
+
// TODO: config tick interval, tested at 500, 300, 100, 60, 30, all work fine and only really noticeable with interviews
|
|
258
|
+
this.ezsp = new ezsp_1.Ezsp(60, serialPortOptions);
|
|
259
|
+
this.ezsp.on(ezsp_1.EzspEvents.STACK_STATUS, this.onStackStatus.bind(this));
|
|
260
|
+
this.ezsp.on(ezsp_1.EzspEvents.MESSAGE_SENT_DELIVERY_FAILED, this.onMessageSentDeliveryFailed.bind(this));
|
|
261
|
+
this.ezsp.on(ezsp_1.EzspEvents.ZDO_RESPONSE, this.onZDOResponse.bind(this));
|
|
262
|
+
this.ezsp.on(ezsp_1.EzspEvents.END_DEVICE_ANNOUNCE, this.onEndDeviceAnnounce.bind(this));
|
|
263
|
+
this.ezsp.on(ezsp_1.EzspEvents.INCOMING_MESSAGE, this.onIncomingMessage.bind(this));
|
|
264
|
+
this.ezsp.on(ezsp_1.EzspEvents.TOUCHLINK_MESSAGE, this.onTouchlinkMessage.bind(this));
|
|
265
|
+
this.ezsp.on(ezsp_1.EzspEvents.GREENPOWER_MESSAGE, this.onGreenpowerMessage.bind(this));
|
|
266
|
+
this.ezsp.on(ezsp_1.EzspEvents.TRUST_CENTER_JOIN, this.onTrustCenterJoin.bind(this));
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Emitted from @see Ezsp.ezspStackStatusHandler
|
|
270
|
+
* @param status
|
|
271
|
+
*/
|
|
272
|
+
async onStackStatus(status) {
|
|
273
|
+
// to be extra careful, should clear network cache upon receiving this.
|
|
274
|
+
this.clearNetworkCache();
|
|
275
|
+
switch (status) {
|
|
276
|
+
case enums_2.EmberStatus.NETWORK_UP: {
|
|
277
|
+
this.oneWaitress.resolveEvent(OneWaitressEvents.STACK_STATUS_NETWORK_UP);
|
|
278
|
+
console.log(`[STACK STATUS] Network up.`);
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
case enums_2.EmberStatus.NETWORK_DOWN: {
|
|
282
|
+
this.oneWaitress.resolveEvent(OneWaitressEvents.STACK_STATUS_NETWORK_DOWN);
|
|
283
|
+
console.log(`[STACK STATUS] Network down.`);
|
|
284
|
+
break;
|
|
285
|
+
}
|
|
286
|
+
case enums_2.EmberStatus.NETWORK_OPENED: {
|
|
287
|
+
this.oneWaitress.resolveEvent(OneWaitressEvents.STACK_STATUS_NETWORK_OPENED);
|
|
288
|
+
this.requestQueue.enqueue(async () => {
|
|
289
|
+
const setJPstatus = (await this.emberSetJoinPolicy(enums_2.EmberJoinDecision.USE_PRECONFIGURED_KEY));
|
|
290
|
+
if (setJPstatus !== enums_2.EzspStatus.SUCCESS) {
|
|
291
|
+
console.error(`[ZDO] Failed set join policy for with status=${enums_2.EzspStatus[setJPstatus]}.`);
|
|
292
|
+
return enums_2.EmberStatus.ERR_FATAL;
|
|
293
|
+
}
|
|
294
|
+
return enums_2.EmberStatus.SUCCESS;
|
|
295
|
+
}, console.error, // no reject, just log error if any
|
|
296
|
+
true);
|
|
297
|
+
console.log(`[STACK STATUS] Network opened.`);
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
300
|
+
case enums_2.EmberStatus.NETWORK_CLOSED: {
|
|
301
|
+
this.oneWaitress.resolveEvent(OneWaitressEvents.STACK_STATUS_NETWORK_CLOSED);
|
|
302
|
+
console.log(`[STACK STATUS] Network closed.`);
|
|
303
|
+
break;
|
|
304
|
+
}
|
|
305
|
+
default: {
|
|
306
|
+
debug(`[STACK STATUS] ${enums_2.EmberStatus[status]}.`);
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Emitted from @see Ezsp.ezspMessageSentHandler
|
|
313
|
+
* WARNING: Cannot rely on `ezspMessageSentHandler` > `ezspIncomingMessageHandler` order, some devices mix it up!
|
|
314
|
+
*
|
|
315
|
+
* @param type
|
|
316
|
+
* @param indexOrDestination
|
|
317
|
+
* @param apsFrame
|
|
318
|
+
* @param messageTag
|
|
319
|
+
*/
|
|
320
|
+
async onMessageSentDeliveryFailed(type, indexOrDestination, apsFrame, messageTag) {
|
|
321
|
+
switch (type) {
|
|
322
|
+
case enums_2.EmberOutgoingMessageType.BROADCAST:
|
|
323
|
+
case enums_2.EmberOutgoingMessageType.BROADCAST_WITH_ALIAS:
|
|
324
|
+
case enums_2.EmberOutgoingMessageType.MULTICAST:
|
|
325
|
+
case enums_2.EmberOutgoingMessageType.MULTICAST_WITH_ALIAS: {
|
|
326
|
+
// BC/MC not checking for message sent, avoid unnecessary waitress lookups
|
|
327
|
+
console.error(`Delivery of ${enums_2.EmberOutgoingMessageType[type]} failed for "${indexOrDestination}" `
|
|
328
|
+
+ `[apsFrame=${JSON.stringify(apsFrame)} messageTag=${messageTag}]`);
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
default: {
|
|
332
|
+
// reject any waitress early (don't wait for timeout if we know we're gonna get there eventually)
|
|
333
|
+
this.oneWaitress.deliveryFailedFor(indexOrDestination, apsFrame);
|
|
334
|
+
break;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Emitted from @see Ezsp.ezspIncomingMessageHandler
|
|
340
|
+
*
|
|
341
|
+
* @param clusterId The ZDO response cluster ID.
|
|
342
|
+
* @param sender The sender of the response. Should match `payload.nodeId` in many responses.
|
|
343
|
+
* @param payload If null, the response indicated a failure.
|
|
344
|
+
*/
|
|
345
|
+
async onZDOResponse(status, sender, apsFrame, payload) {
|
|
346
|
+
this.oneWaitress.resolveZDO(status, sender, apsFrame, payload);
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Emitted from @see Ezsp.ezspIncomingMessageHandler
|
|
350
|
+
*
|
|
351
|
+
* @param sender
|
|
352
|
+
* @param nodeId
|
|
353
|
+
* @param eui64
|
|
354
|
+
* @param macCapFlags
|
|
355
|
+
*/
|
|
356
|
+
async onEndDeviceAnnounce(sender, apsFrame, payload) {
|
|
357
|
+
// reduced function device
|
|
358
|
+
// if ((payload.capabilities.deviceType === 0)) {
|
|
359
|
+
// }
|
|
360
|
+
this.emit(events_1.Events.deviceAnnounce, { networkAddress: payload.nodeId, ieeeAddr: payload.eui64 });
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Emitted from @see Ezsp.ezspIncomingMessageHandler
|
|
364
|
+
*
|
|
365
|
+
* @param type
|
|
366
|
+
* @param apsFrame
|
|
367
|
+
* @param lastHopLqi
|
|
368
|
+
* @param sender
|
|
369
|
+
* @param messageContents
|
|
370
|
+
*/
|
|
371
|
+
async onIncomingMessage(type, apsFrame, lastHopLqi, sender, messageContents) {
|
|
372
|
+
try {
|
|
373
|
+
const payload = {
|
|
374
|
+
address: sender,
|
|
375
|
+
frame: zcl_1.ZclFrame.fromBuffer(apsFrame.clusterId, messageContents),
|
|
376
|
+
endpoint: apsFrame.sourceEndpoint,
|
|
377
|
+
linkquality: lastHopLqi,
|
|
378
|
+
groupID: apsFrame.groupId,
|
|
379
|
+
wasBroadcast: ((type === enums_2.EmberIncomingMessageType.BROADCAST) || (type === enums_2.EmberIncomingMessageType.BROADCAST_LOOPBACK)),
|
|
380
|
+
destinationEndpoint: apsFrame.destinationEndpoint,
|
|
381
|
+
};
|
|
382
|
+
this.oneWaitress.resolveZCL(payload);
|
|
383
|
+
this.emit(events_1.Events.zclData, payload);
|
|
384
|
+
}
|
|
385
|
+
catch (error) {
|
|
386
|
+
const payload = {
|
|
387
|
+
clusterID: apsFrame.clusterId,
|
|
388
|
+
address: sender,
|
|
389
|
+
data: messageContents,
|
|
390
|
+
endpoint: apsFrame.sourceEndpoint,
|
|
391
|
+
linkquality: lastHopLqi,
|
|
392
|
+
groupID: apsFrame.groupId,
|
|
393
|
+
wasBroadcast: ((type === enums_2.EmberIncomingMessageType.BROADCAST) || (type === enums_2.EmberIncomingMessageType.BROADCAST_LOOPBACK)),
|
|
394
|
+
destinationEndpoint: apsFrame.destinationEndpoint,
|
|
395
|
+
};
|
|
396
|
+
this.emit(events_1.Events.rawData, payload);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Emitted from @see Ezsp.ezspMacFilterMatchMessageHandler when the message is a valid InterPAN touchlink message.
|
|
401
|
+
*
|
|
402
|
+
* @param sourcePanId
|
|
403
|
+
* @param sourceAddress
|
|
404
|
+
* @param groupId
|
|
405
|
+
* @param lastHopLqi
|
|
406
|
+
* @param messageContents
|
|
407
|
+
*/
|
|
408
|
+
async onTouchlinkMessage(sourcePanId, sourceAddress, groupId, lastHopLqi, messageContents) {
|
|
409
|
+
const payload = {
|
|
410
|
+
frame: zcl_1.ZclFrame.fromBuffer(cluster_1.default.touchlink.ID, messageContents),
|
|
411
|
+
address: sourceAddress,
|
|
412
|
+
endpoint: 1, // arbitrary since not sent over-the-air
|
|
413
|
+
linkquality: lastHopLqi,
|
|
414
|
+
groupID: groupId,
|
|
415
|
+
wasBroadcast: true, // XXX: since always sent broadcast atm...
|
|
416
|
+
destinationEndpoint: endpoints_1.FIXED_ENDPOINTS[0].endpoint,
|
|
417
|
+
};
|
|
418
|
+
this.oneWaitress.resolveZCL(payload);
|
|
419
|
+
this.emit(events_1.Events.zclData, payload);
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Emitted from @see Ezsp.ezspGpepIncomingMessageHandler
|
|
423
|
+
*
|
|
424
|
+
* @param sequenceNumber
|
|
425
|
+
* @param commandIdentifier
|
|
426
|
+
* @param sourceId
|
|
427
|
+
* @param frameCounter
|
|
428
|
+
* @param gpdCommandId
|
|
429
|
+
* @param gpdCommandPayload
|
|
430
|
+
* @param gpdLink
|
|
431
|
+
*/
|
|
432
|
+
async onGreenpowerMessage(sequenceNumber, commandIdentifier, sourceId, frameCounter, gpdCommandId, gpdCommandPayload, gpdLink) {
|
|
433
|
+
try {
|
|
434
|
+
const gpdHeader = Buffer.alloc(15);
|
|
435
|
+
gpdHeader.writeUInt8(0b00000001, 0); // frameControl: FrameType.SPECIFIC + Direction.CLIENT_TO_SERVER + disableDefaultResponse=false
|
|
436
|
+
gpdHeader.writeUInt8(sequenceNumber, 1); // transactionSequenceNumber
|
|
437
|
+
gpdHeader.writeUInt8(commandIdentifier, 2); // commandIdentifier
|
|
438
|
+
gpdHeader.writeUInt16LE(0, 3); // options XXX: bypassed, same as deconz https://github.com/Koenkk/zigbee-herdsman/pull/536
|
|
439
|
+
gpdHeader.writeUInt32LE(sourceId, 5); // srcID
|
|
440
|
+
// omitted: gpdIEEEAddr ieeeAddr
|
|
441
|
+
// omitted: gpdEndpoint uint8
|
|
442
|
+
gpdHeader.writeUInt32LE(frameCounter, 9); // frameCounter
|
|
443
|
+
gpdHeader.writeUInt8(gpdCommandId, 13); // commandID
|
|
444
|
+
gpdHeader.writeUInt8(gpdCommandPayload.length, 14); // payloadSize
|
|
445
|
+
const gpFrame = zcl_1.ZclFrame.fromBuffer(cluster_1.default.greenPower.ID, Buffer.concat([gpdHeader, gpdCommandPayload]));
|
|
446
|
+
const payload = {
|
|
447
|
+
frame: gpFrame,
|
|
448
|
+
address: sourceId,
|
|
449
|
+
endpoint: consts_2.GP_ENDPOINT,
|
|
450
|
+
linkquality: gpdLink,
|
|
451
|
+
groupID: this.greenPowerGroup,
|
|
452
|
+
// XXX: upstream sends to `gppNwkAddr` if `wasBroadcast` is false, even if `gppNwkAddr` is null
|
|
453
|
+
wasBroadcast: (gpFrame.Payload.gppNwkAddr != null) ? false : true,
|
|
454
|
+
destinationEndpoint: consts_2.GP_ENDPOINT,
|
|
455
|
+
};
|
|
456
|
+
this.oneWaitress.resolveZCL(payload);
|
|
457
|
+
this.emit(events_1.Events.zclData, payload);
|
|
458
|
+
}
|
|
459
|
+
catch (err) {
|
|
460
|
+
console.error(`<~x~ [GP] Failed creating ZCL payload. Skipping. ${err}`);
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Emitted from @see Ezsp.ezspTrustCenterJoinHandler
|
|
466
|
+
*
|
|
467
|
+
* @param newNodeId
|
|
468
|
+
* @param newNodeEui64
|
|
469
|
+
* @param status
|
|
470
|
+
* @param policyDecision
|
|
471
|
+
* @param parentOfNewNodeId
|
|
472
|
+
*/
|
|
473
|
+
async onTrustCenterJoin(newNodeId, newNodeEui64, status, policyDecision, parentOfNewNodeId) {
|
|
474
|
+
if (status === enums_2.EmberDeviceUpdate.DEVICE_LEFT) {
|
|
475
|
+
// NOTE: `policyDecision` here is NO_ACTION and `parentOfNewNodeId` is 65535
|
|
476
|
+
const payload = {
|
|
477
|
+
networkAddress: newNodeId,
|
|
478
|
+
ieeeAddr: newNodeEui64,
|
|
479
|
+
};
|
|
480
|
+
this.emit(events_1.Events.deviceLeave, payload);
|
|
481
|
+
}
|
|
482
|
+
else {
|
|
483
|
+
if (policyDecision !== enums_2.EmberJoinDecision.DENY_JOIN) {
|
|
484
|
+
const payload = {
|
|
485
|
+
networkAddress: newNodeId,
|
|
486
|
+
ieeeAddr: newNodeEui64,
|
|
487
|
+
};
|
|
488
|
+
this.emit(events_1.Events.deviceJoined, payload);
|
|
489
|
+
}
|
|
490
|
+
else {
|
|
491
|
+
console.log(`[TRUST CENTER] Device ${newNodeId}:${newNodeEui64} was denied joining via ${parentOfNewNodeId}.`);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
async watchdogCounters() {
|
|
496
|
+
this.requestQueue.enqueue(async () => {
|
|
497
|
+
// listed as per EmberCounterType
|
|
498
|
+
const counters = (await this.ezsp.ezspReadAndClearCounters());
|
|
499
|
+
let countersLogString = "[NCP COUNTERS] ";
|
|
500
|
+
for (let i = 0; i < enums_2.EmberCounterType.COUNT; i++) {
|
|
501
|
+
countersLogString += `${enums_2.EmberCounterType[i]}: ${counters[i]} | `;
|
|
502
|
+
}
|
|
503
|
+
console.log(countersLogString);
|
|
504
|
+
return enums_2.EmberStatus.SUCCESS;
|
|
505
|
+
}, console.error);
|
|
506
|
+
}
|
|
507
|
+
initVariables() {
|
|
508
|
+
this.ezsp.removeAllListeners(ezsp_1.EzspEvents.ncpNeedsResetAndInit);
|
|
509
|
+
clearInterval(this.watchdogCountersHandle);
|
|
510
|
+
this.zdoRequestBuffalo.setPosition(0);
|
|
511
|
+
this.zdoRequestSequence = 0; // start at 1
|
|
512
|
+
this.zdoRequestRadius = 255;
|
|
513
|
+
this.interpanLock = false;
|
|
514
|
+
this.networkCache = (0, initters_1.initNetworkCache)();
|
|
515
|
+
this.defaultApsOptions = (enums_2.EmberApsOption.RETRY | enums_2.EmberApsOption.ENABLE_ROUTE_DISCOVERY | enums_2.EmberApsOption.ENABLE_ADDRESS_DISCOVERY);
|
|
516
|
+
// always at least length==1 because of allowed MULTICAST_TABLE_SIZE range
|
|
517
|
+
this.multicastTable = new Array(STACK_CONFIGS[this.stackConfig].MULTICAST_TABLE_SIZE).fill(null);
|
|
518
|
+
this.ezsp.once(ezsp_1.EzspEvents.ncpNeedsResetAndInit, this.onNcpNeedsResetAndInit.bind(this));
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Proceed to execute the long list of commands required to setup comms between Host<>NCP.
|
|
522
|
+
* This is called by start and on internal reset.
|
|
523
|
+
*/
|
|
524
|
+
async initEzsp() {
|
|
525
|
+
let result = "resumed";
|
|
526
|
+
await this.onNCPPreReset();
|
|
527
|
+
try {
|
|
528
|
+
// NOTE: something deep in this call can throw too
|
|
529
|
+
const result = (await this.ezsp.start());
|
|
530
|
+
if (result !== enums_2.EzspStatus.SUCCESS) {
|
|
531
|
+
throw new Error(`Failed to start EZSP layer with status=${enums_2.EzspStatus[result]}.`);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
catch (err) {
|
|
535
|
+
throw err;
|
|
536
|
+
}
|
|
537
|
+
// call before any other command, else fails
|
|
538
|
+
await this.emberVersion();
|
|
539
|
+
await this.initNCPPreConfiguration();
|
|
540
|
+
await this.initNCPAddressTable();
|
|
541
|
+
await this.initNCPConfiguration();
|
|
542
|
+
// WARNING: From here on EZSP commands that affect memory allocation on the NCP should no longer be called (like resizing tables)
|
|
543
|
+
await this.onNCPPostReset();
|
|
544
|
+
await this.registerFixedEndpoints();
|
|
545
|
+
this.clearNetworkCache();
|
|
546
|
+
result = (await this.initTrustCenter());
|
|
547
|
+
// after network UP, as per SDK, ensures clean slate
|
|
548
|
+
await this.initNCPConcentrator();
|
|
549
|
+
// await (this.emberStartEnergyScan());// TODO: via config of some kind, better off waiting for UI supports though
|
|
550
|
+
// populate network cache info
|
|
551
|
+
const [status, , parameters] = (await this.ezsp.ezspGetNetworkParameters());
|
|
552
|
+
if (status !== enums_2.EmberStatus.SUCCESS) {
|
|
553
|
+
throw new Error(`Failed to get network parameters with status=${enums_2.EmberStatus[status]}.`);
|
|
554
|
+
}
|
|
555
|
+
this.networkCache.parameters = parameters;
|
|
556
|
+
this.networkCache.status = (await this.ezsp.ezspNetworkState());
|
|
557
|
+
this.networkCache.eui64 = (await this.ezsp.ezspGetEui64());
|
|
558
|
+
debug(`[INIT] Network Ready! ${JSON.stringify(this.networkCache)}`);
|
|
559
|
+
return result;
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* NCP Config init. Should always be called first in the init stack (after version cmd).
|
|
563
|
+
* @returns
|
|
564
|
+
*/
|
|
565
|
+
async initNCPPreConfiguration() {
|
|
566
|
+
// this can only decrease, not increase, NCP-side value
|
|
567
|
+
await this.emberSetEzspConfigValue(enums_1.EzspConfigId.ADDRESS_TABLE_SIZE, STACK_CONFIGS[this.stackConfig].ADDRESS_TABLE_SIZE);
|
|
568
|
+
await this.emberSetEzspConfigValue(enums_1.EzspConfigId.TRUST_CENTER_ADDRESS_CACHE_SIZE, STACK_CONFIGS[this.stackConfig].TRUST_CENTER_ADDRESS_CACHE_SIZE);
|
|
569
|
+
if (STACK_CONFIGS[this.stackConfig].STACK_PROFILE === consts_2.STACK_PROFILE_ZIGBEE_PRO) {
|
|
570
|
+
// BUG 14222: If stack profile is 2 (ZigBee Pro), we need to enforce
|
|
571
|
+
// the standard stack configuration values for that feature set.
|
|
572
|
+
/** MAC indirect timeout should be 7.68 secs */
|
|
573
|
+
await this.emberSetEzspConfigValue(enums_1.EzspConfigId.INDIRECT_TRANSMISSION_TIMEOUT, 7680);
|
|
574
|
+
/** Max hops should be 2 * nwkMaxDepth, where nwkMaxDepth is 15 */
|
|
575
|
+
await this.emberSetEzspConfigValue(enums_1.EzspConfigId.MAX_HOPS, 30);
|
|
576
|
+
}
|
|
577
|
+
await this.emberSetEzspConfigValue(enums_1.EzspConfigId.TX_POWER_MODE, STACK_CONFIGS[this.stackConfig].TX_POWER_MODE);
|
|
578
|
+
await this.emberSetEzspConfigValue(enums_1.EzspConfigId.SUPPORTED_NETWORKS, STACK_CONFIGS[this.stackConfig].SUPPORTED_NETWORKS);
|
|
579
|
+
await this.emberSetEzspValue(enums_1.EzspValueId.END_DEVICE_KEEP_ALIVE_SUPPORT_MODE, 1, [STACK_CONFIGS[this.stackConfig].END_DEVICE_KEEP_ALIVE_SUPPORT_MODE]);
|
|
580
|
+
// allow other devices to modify the binding table
|
|
581
|
+
await this.emberSetEzspPolicy(enums_1.EzspPolicyId.BINDING_MODIFICATION_POLICY, enums_1.EzspDecisionId.CHECK_BINDING_MODIFICATIONS_ARE_VALID_ENDPOINT_CLUSTERS);
|
|
582
|
+
// return message tag and message contents in ezspMessageSentHandler()
|
|
583
|
+
await this.emberSetEzspPolicy(enums_1.EzspPolicyId.MESSAGE_CONTENTS_IN_CALLBACK_POLICY, enums_1.EzspDecisionId.MESSAGE_TAG_AND_CONTENTS_IN_CALLBACK);
|
|
584
|
+
await this.emberSetEzspValue(enums_1.EzspValueId.MAXIMUM_INCOMING_TRANSFER_SIZE, 2, (0, math_1.lowHighBytes)(STACK_CONFIGS[this.stackConfig].MAXIMUM_INCOMING_TRANSFER_SIZE));
|
|
585
|
+
await this.emberSetEzspValue(enums_1.EzspValueId.MAXIMUM_OUTGOING_TRANSFER_SIZE, 2, (0, math_1.lowHighBytes)(STACK_CONFIGS[this.stackConfig].MAXIMUM_OUTGOING_TRANSFER_SIZE));
|
|
586
|
+
await this.emberSetEzspValue(enums_1.EzspValueId.TRANSIENT_DEVICE_TIMEOUT, 2, (0, math_1.lowHighBytes)(STACK_CONFIGS[this.stackConfig].TRANSIENT_DEVICE_TIMEOUT));
|
|
587
|
+
// Set the manufacturing code. This is defined by ZigBee document 053874r10
|
|
588
|
+
// Ember's ID is 0x1002 and is the default, but this can be overridden in App Builder.
|
|
589
|
+
await this.ezsp.ezspSetManufacturerCode(consts_2.MANUFACTURER_CODE);
|
|
590
|
+
// network security init
|
|
591
|
+
await this.emberSetEzspConfigValue(enums_1.EzspConfigId.STACK_PROFILE, STACK_CONFIGS[this.stackConfig].STACK_PROFILE);
|
|
592
|
+
await this.emberSetEzspConfigValue(enums_1.EzspConfigId.SECURITY_LEVEL, STACK_CONFIGS[this.stackConfig].SECURITY_LEVEL);
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* NCP Address table init.
|
|
596
|
+
* @returns
|
|
597
|
+
*/
|
|
598
|
+
async initNCPAddressTable() {
|
|
599
|
+
const desiredTableSize = STACK_CONFIGS[this.stackConfig].ADDRESS_TABLE_SIZE;
|
|
600
|
+
// If the host and the ncp disagree on the address table size, explode.
|
|
601
|
+
const [status, addressTableSize] = (await this.ezsp.ezspGetConfigurationValue(enums_1.EzspConfigId.ADDRESS_TABLE_SIZE));
|
|
602
|
+
// After the change of ncp memory model in UC, we can not increase the default NCP table sizes anymore.
|
|
603
|
+
// Therefore, checking for desiredTableSize == (ncp)addressTableSize might not be always true anymore
|
|
604
|
+
// assert(desiredTableSize <= addressTableSize);
|
|
605
|
+
if ((status !== enums_2.EzspStatus.SUCCESS) || (addressTableSize > desiredTableSize)) {
|
|
606
|
+
throw new Error(`[INIT] NCP (${addressTableSize}) disagrees with Host (min ${desiredTableSize}) on table size. status=${enums_2.EzspStatus[status]}`);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* NCP configuration init
|
|
611
|
+
*/
|
|
612
|
+
async initNCPConfiguration() {
|
|
613
|
+
await this.emberSetEzspConfigValue(enums_1.EzspConfigId.BINDING_TABLE_SIZE, STACK_CONFIGS[this.stackConfig].BINDING_TABLE_SIZE);
|
|
614
|
+
await this.emberSetEzspConfigValue(enums_1.EzspConfigId.KEY_TABLE_SIZE, STACK_CONFIGS[this.stackConfig].KEY_TABLE_SIZE);
|
|
615
|
+
await this.emberSetEzspConfigValue(enums_1.EzspConfigId.MAX_END_DEVICE_CHILDREN, STACK_CONFIGS[this.stackConfig].MAX_END_DEVICE_CHILDREN);
|
|
616
|
+
await this.emberSetEzspConfigValue(enums_1.EzspConfigId.APS_UNICAST_MESSAGE_COUNT, STACK_CONFIGS[this.stackConfig].APS_UNICAST_MESSAGE_COUNT);
|
|
617
|
+
await this.emberSetEzspConfigValue(enums_1.EzspConfigId.BROADCAST_TABLE_SIZE, STACK_CONFIGS[this.stackConfig].BROADCAST_TABLE_SIZE);
|
|
618
|
+
await this.emberSetEzspConfigValue(enums_1.EzspConfigId.NEIGHBOR_TABLE_SIZE, STACK_CONFIGS[this.stackConfig].NEIGHBOR_TABLE_SIZE);
|
|
619
|
+
await this.emberSetEzspConfigValue(enums_1.EzspConfigId.END_DEVICE_POLL_TIMEOUT, STACK_CONFIGS[this.stackConfig].END_DEVICE_POLL_TIMEOUT);
|
|
620
|
+
await this.emberSetEzspConfigValue(enums_1.EzspConfigId.TRANSIENT_KEY_TIMEOUT_S, STACK_CONFIGS[this.stackConfig].TRANSIENT_KEY_TIMEOUT_S);
|
|
621
|
+
await this.emberSetEzspConfigValue(enums_1.EzspConfigId.RETRY_QUEUE_SIZE, STACK_CONFIGS[this.stackConfig].RETRY_QUEUE_SIZE);
|
|
622
|
+
await this.emberSetEzspConfigValue(enums_1.EzspConfigId.SOURCE_ROUTE_TABLE_SIZE, STACK_CONFIGS[this.stackConfig].SOURCE_ROUTE_TABLE_SIZE);
|
|
623
|
+
await this.emberSetEzspConfigValue(enums_1.EzspConfigId.MULTICAST_TABLE_SIZE, STACK_CONFIGS[this.stackConfig].MULTICAST_TABLE_SIZE);
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* NCP concentrator init. Also enables source route discovery mode with RESCHEDULE.
|
|
627
|
+
*
|
|
628
|
+
* From AN1233:
|
|
629
|
+
* To function correctly in a Zigbee PRO network, a trust center also requires that:
|
|
630
|
+
*
|
|
631
|
+
* 1. The trust center application must act as a concentrator (either high or low RAM).
|
|
632
|
+
* 2. The trust center application must have support for source routing.
|
|
633
|
+
* It must record the source routes and properly handle requests by the stack for a particular source route.
|
|
634
|
+
* 3. The trust center application must use an address cache for security, in order to maintain a mapping of IEEE address to short ID.
|
|
635
|
+
*
|
|
636
|
+
* Failure to satisfy all of the above requirements may result in failures when joining/rejoining devices to the network across multiple hops
|
|
637
|
+
* (through a target node that is neither the trust center nor one of its neighboring routers.)
|
|
638
|
+
*/
|
|
639
|
+
async initNCPConcentrator() {
|
|
640
|
+
const config = (this.concentratorType === consts_2.EMBER_HIGH_RAM_CONCENTRATOR) ? HIGH_RAM_CONCENTRATOR_CONFIG : LOW_RAM_CONCENTRATOR_CONFIG;
|
|
641
|
+
const status = (await this.ezsp.ezspSetConcentrator(true, this.concentratorType, config.minTime, config.maxTime, config.routeErrorThreshold, config.deliveryFailureThreshold, config.mapHops));
|
|
642
|
+
if (status !== enums_2.EmberStatus.SUCCESS) {
|
|
643
|
+
throw new Error(`[CONCENTRATOR] Failed to set concentrator with status=${status}.`);
|
|
644
|
+
}
|
|
645
|
+
const remainTilMTORR = (await this.ezsp.ezspSetSourceRouteDiscoveryMode(enums_2.EmberSourceRouteDiscoveryMode.RESCHEDULE));
|
|
646
|
+
console.log(`[CONCENTRATOR] Started source route discovery. ${remainTilMTORR}ms until next broadcast.`);
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Register fixed endpoints and set any related multicast entries that need to be.
|
|
650
|
+
*/
|
|
651
|
+
async registerFixedEndpoints() {
|
|
652
|
+
for (const ep of endpoints_1.FIXED_ENDPOINTS) {
|
|
653
|
+
if (ep.networkIndex !== 0x00) {
|
|
654
|
+
debug(`Multi-network not currently supported. Skipping endpoint ${JSON.stringify(ep)}.`);
|
|
655
|
+
continue;
|
|
656
|
+
}
|
|
657
|
+
const [epStatus,] = (await this.ezsp.ezspGetEndpointFlags(ep.endpoint));
|
|
658
|
+
// endpoint not already registered
|
|
659
|
+
if (epStatus !== enums_2.EzspStatus.SUCCESS) {
|
|
660
|
+
// check to see if ezspAddEndpoint needs to be called
|
|
661
|
+
// if ezspInit is called without NCP reset, ezspAddEndpoint is not necessary and will return an error
|
|
662
|
+
const status = (await this.ezsp.ezspAddEndpoint(ep.endpoint, ep.profileId, ep.deviceId, ep.deviceVersion, ep.inClusterList, ep.outClusterList));
|
|
663
|
+
if (status === enums_2.EzspStatus.SUCCESS) {
|
|
664
|
+
debug(`Registered endpoint "${ep.endpoint}" with status=${enums_2.EzspStatus[status]}.`);
|
|
665
|
+
}
|
|
666
|
+
else {
|
|
667
|
+
throw new Error(`Failed to register endpoint "${ep.endpoint}" with status=${enums_2.EzspStatus[status]}.`);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
else {
|
|
671
|
+
debug(`Endpoint "${ep.endpoint}" already registered.`);
|
|
672
|
+
}
|
|
673
|
+
if (ep.endpoint === consts_2.GP_ENDPOINT) {
|
|
674
|
+
const gpMulticastEntry = {
|
|
675
|
+
multicastId: this.greenPowerGroup,
|
|
676
|
+
endpoint: ep.endpoint,
|
|
677
|
+
networkIndex: ep.networkIndex,
|
|
678
|
+
};
|
|
679
|
+
const status = (await this.ezsp.ezspSetMulticastTableEntry(0, gpMulticastEntry));
|
|
680
|
+
if (status !== enums_2.EmberStatus.SUCCESS) {
|
|
681
|
+
throw new Error(`Failed to register group "Green Power" in multicast table with status=${enums_2.EmberStatus[status]}.`);
|
|
682
|
+
}
|
|
683
|
+
// NOTE: ensure GP is always added first in the table
|
|
684
|
+
this.multicastTable[0] = gpMulticastEntry;
|
|
685
|
+
debug(`Registered multicast table entry: ${JSON.stringify(gpMulticastEntry)}.`);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
*
|
|
691
|
+
* @returns True if the network needed to be formed.
|
|
692
|
+
*/
|
|
693
|
+
async initTrustCenter() {
|
|
694
|
+
// init TC policies
|
|
695
|
+
{
|
|
696
|
+
let status = (await this.emberSetEzspPolicy(enums_1.EzspPolicyId.TC_KEY_REQUEST_POLICY, enums_1.EzspDecisionId.ALLOW_TC_KEY_REQUESTS_AND_SEND_CURRENT_KEY));
|
|
697
|
+
if (status !== enums_2.EzspStatus.SUCCESS) {
|
|
698
|
+
throw new Error(`[INIT TC] Failed to set EzspPolicyId TC_KEY_REQUEST_POLICY to ALLOW_TC_KEY_REQUESTS_AND_SEND_CURRENT_KEY `
|
|
699
|
+
+ `with status=${enums_2.EzspStatus[status]}.`);
|
|
700
|
+
}
|
|
701
|
+
const appKeyPolicy = STACK_CONFIGS[this.stackConfig].KEY_TABLE_SIZE
|
|
702
|
+
? enums_1.EzspDecisionId.ALLOW_APP_KEY_REQUESTS : enums_1.EzspDecisionId.DENY_APP_KEY_REQUESTS;
|
|
703
|
+
status = (await this.emberSetEzspPolicy(enums_1.EzspPolicyId.APP_KEY_REQUEST_POLICY, appKeyPolicy));
|
|
704
|
+
if (status !== enums_2.EzspStatus.SUCCESS) {
|
|
705
|
+
throw new Error(`[INIT TC] Failed to set EzspPolicyId APP_KEY_REQUEST_POLICY to ${enums_1.EzspDecisionId[appKeyPolicy]} `
|
|
706
|
+
+ `with status=${enums_2.EzspStatus[status]}.`);
|
|
707
|
+
}
|
|
708
|
+
status = (await this.emberSetJoinPolicy(enums_2.EmberJoinDecision.USE_PRECONFIGURED_KEY));
|
|
709
|
+
if (status !== enums_2.EzspStatus.SUCCESS) {
|
|
710
|
+
throw new Error(`[INIT TC] Failed to set join policy to USE_PRECONFIGURED_KEY with status=${enums_2.EzspStatus[status]}.`);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
const configNetworkKey = Buffer.from(this.networkOptions.networkKey);
|
|
714
|
+
const networkInitStruct = {
|
|
715
|
+
bitmask: (enums_2.EmberNetworkInitBitmask.PARENT_INFO_IN_TOKEN | enums_2.EmberNetworkInitBitmask.END_DEVICE_REJOIN_ON_REBOOT)
|
|
716
|
+
};
|
|
717
|
+
const initStatus = (await this.ezsp.ezspNetworkInit(networkInitStruct));
|
|
718
|
+
debug(`[INIT TC] Network init status=${enums_2.EmberStatus[initStatus]}.`);
|
|
719
|
+
if ((initStatus !== enums_2.EmberStatus.SUCCESS) && (initStatus !== enums_2.EmberStatus.NOT_JOINED)) {
|
|
720
|
+
throw new Error(`[INIT TC] Failed network init request with status=${enums_2.EmberStatus[initStatus]}.`);
|
|
721
|
+
}
|
|
722
|
+
let action = NetworkInitAction.DONE;
|
|
723
|
+
if (initStatus === enums_2.EmberStatus.SUCCESS) {
|
|
724
|
+
// network
|
|
725
|
+
await this.oneWaitress.startWaitingForEvent({ eventName: OneWaitressEvents.STACK_STATUS_NETWORK_UP }, DEFAULT_NETWORK_REQUEST_TIMEOUT, '[INIT TC] Network init');
|
|
726
|
+
const [npStatus, nodeType, netParams] = (await this.ezsp.ezspGetNetworkParameters());
|
|
727
|
+
debug(`[INIT TC] Current network config=${JSON.stringify(this.networkOptions)}`);
|
|
728
|
+
debug(`[INIT TC] Current NCP network: nodeType=${enums_2.EmberNodeType[nodeType]} params=${JSON.stringify(netParams)}`);
|
|
729
|
+
// XXX: should not force a form when it's only a channel change, just change the channel, wait a sec, then continue the logic
|
|
730
|
+
if ((npStatus === enums_2.EmberStatus.SUCCESS) && (nodeType === enums_2.EmberNodeType.COORDINATOR) && (this.networkOptions.panID === netParams.panId)
|
|
731
|
+
&& ((0, es6_1.default)(this.networkOptions.extendedPanID, netParams.extendedPanId))
|
|
732
|
+
&& (this.networkOptions.channelList.includes(netParams.radioChannel))) {
|
|
733
|
+
// config matches adapter so far, no error, we can check the network key
|
|
734
|
+
const context = (0, initters_1.initSecurityManagerContext)();
|
|
735
|
+
context.coreKeyType = enums_2.SecManKeyType.NETWORK;
|
|
736
|
+
context.keyIndex = 0;
|
|
737
|
+
const [networkKey, nkStatus] = (await this.ezsp.ezspExportKey(context));
|
|
738
|
+
if (nkStatus !== enums_2.SLStatus.OK) {
|
|
739
|
+
throw new Error(`[BACKUP] Failed to export Network Key with status=${enums_2.SLStatus[nkStatus]}.`);
|
|
740
|
+
}
|
|
741
|
+
debug(`[INIT TC] Current NCP network: networkKey=${networkKey.contents.toString('hex')}`);
|
|
742
|
+
// config doesn't match adapter anymore
|
|
743
|
+
if (!networkKey.contents.equals(configNetworkKey)) {
|
|
744
|
+
action = NetworkInitAction.LEAVE;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
else {
|
|
748
|
+
// config doesn't match adapter
|
|
749
|
+
action = NetworkInitAction.LEAVE;
|
|
750
|
+
}
|
|
751
|
+
if (action === NetworkInitAction.LEAVE) {
|
|
752
|
+
console.log(`[INIT TC] NCP network does not match config. Leaving network...`);
|
|
753
|
+
const leaveStatus = (await this.ezsp.ezspLeaveNetwork());
|
|
754
|
+
if (leaveStatus !== enums_2.EmberStatus.SUCCESS) {
|
|
755
|
+
throw new Error(`[INIT TC] Failed leave network request with status=${enums_2.EmberStatus[leaveStatus]}.`);
|
|
756
|
+
}
|
|
757
|
+
await this.oneWaitress.startWaitingForEvent({ eventName: OneWaitressEvents.STACK_STATUS_NETWORK_DOWN }, DEFAULT_NETWORK_REQUEST_TIMEOUT, '[INIT TC] Leave network');
|
|
758
|
+
await (0, utils_1.Wait)(200); // settle down
|
|
759
|
+
action = NetworkInitAction.LEFT;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
const backup = (await this.getStoredBackup());
|
|
763
|
+
if ((initStatus === enums_2.EmberStatus.NOT_JOINED) || (action === NetworkInitAction.LEFT)) {
|
|
764
|
+
// no network
|
|
765
|
+
if (backup != null) {
|
|
766
|
+
if ((this.networkOptions.panID === backup.networkOptions.panId)
|
|
767
|
+
&& (Buffer.from(this.networkOptions.extendedPanID).equals(backup.networkOptions.extendedPanId))
|
|
768
|
+
&& (this.networkOptions.channelList.includes(backup.logicalChannel))
|
|
769
|
+
&& (configNetworkKey.equals(backup.networkOptions.networkKey))) {
|
|
770
|
+
// config matches backup
|
|
771
|
+
action = NetworkInitAction.FORM_BACKUP;
|
|
772
|
+
}
|
|
773
|
+
else {
|
|
774
|
+
// config doesn't match backup
|
|
775
|
+
console.log(`[INIT TC] Config does not match backup.`);
|
|
776
|
+
action = NetworkInitAction.FORM_CONFIG;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
else {
|
|
780
|
+
// no backup
|
|
781
|
+
console.log(`[INIT TC] No valid backup found.`);
|
|
782
|
+
action = NetworkInitAction.FORM_CONFIG;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
else {
|
|
786
|
+
action = NetworkInitAction.DONE; // just to be clear
|
|
787
|
+
}
|
|
788
|
+
//---- from here on, we assume everything is in place for whatever decision was taken above
|
|
789
|
+
let result = 'resumed';
|
|
790
|
+
switch (action) {
|
|
791
|
+
case NetworkInitAction.FORM_BACKUP: {
|
|
792
|
+
console.log(`[INIT TC] Forming from backup.`);
|
|
793
|
+
const keyList = backup.devices.map((device) => {
|
|
794
|
+
const octets = Array.from(device.ieeeAddress.reverse());
|
|
795
|
+
const deviceEui64 = '0x' + octets.map(octet => octet.toString(16).padStart(2, '0')).join("");
|
|
796
|
+
const key = {
|
|
797
|
+
deviceEui64,
|
|
798
|
+
key: { contents: device.linkKey.key },
|
|
799
|
+
outgoingFrameCounter: device.linkKey.txCounter,
|
|
800
|
+
incomingFrameCounter: device.linkKey.rxCounter,
|
|
801
|
+
};
|
|
802
|
+
return key;
|
|
803
|
+
});
|
|
804
|
+
// before forming
|
|
805
|
+
await this.importLinkKeys(keyList);
|
|
806
|
+
await this.formNetwork(true, /*from backup*/ backup.networkOptions.networkKey, backup.networkKeyInfo.sequenceNumber, backup.networkOptions.panId, Array.from(backup.networkOptions.extendedPanId), backup.logicalChannel, backup.ezsp.hashed_tclk);
|
|
807
|
+
result = 'restored';
|
|
808
|
+
break;
|
|
809
|
+
}
|
|
810
|
+
case NetworkInitAction.FORM_CONFIG: {
|
|
811
|
+
console.log(`[INIT TC] Forming from config.`);
|
|
812
|
+
await this.formNetwork(false, /*from config*/ configNetworkKey, 0, this.networkOptions.panID, this.networkOptions.extendedPanID, this.networkOptions.channelList[0], (0, crypto_1.randomBytes)(consts_1.EMBER_ENCRYPTION_KEY_SIZE));
|
|
813
|
+
result = 'reset';
|
|
814
|
+
break;
|
|
815
|
+
}
|
|
816
|
+
case NetworkInitAction.DONE: {
|
|
817
|
+
console.log(`[INIT TC] NCP network matches config.`);
|
|
818
|
+
break;
|
|
819
|
+
}
|
|
820
|
+
default: {
|
|
821
|
+
throw new Error(`[INIT TC] Invalid action "${NetworkInitAction[action]}" for final stage.`);
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
// can't let frame counter wrap to zero (uint32_t), will force a broadcast after init if getting too close
|
|
825
|
+
if (backup != null && (backup.networkKeyInfo.frameCounter > 0xFEEEEEEE)) {
|
|
826
|
+
// XXX: while this remains a pretty low occurrence in most (small) networks,
|
|
827
|
+
// currently Z2M won't support the key update because of one-way config...
|
|
828
|
+
// need to investigate handling this properly
|
|
829
|
+
// console.warn(`[INIT TC] Network key frame counter is reaching its limit. Scheduling broadcast to update network key. `
|
|
830
|
+
// + `This may result in some devices (especially battery-powered) temporarily losing connection.`);
|
|
831
|
+
// // XXX: no idea here on the proper timer value, but this will block the network for several seconds on exec
|
|
832
|
+
// // (probably have to take the behavior of sleepy-end devices into account to improve chances of reaching everyone right away?)
|
|
833
|
+
// setTimeout(async () => {
|
|
834
|
+
// this.requestQueue.enqueue(async (): Promise<EmberStatus> => {
|
|
835
|
+
// await this.broadcastNetworkKeyUpdate();
|
|
836
|
+
// return EmberStatus.SUCCESS;
|
|
837
|
+
// }, console.error, true);// no reject just log error if any, will retry next start, & prioritize so we know it'll run when expected
|
|
838
|
+
// }, 300000);
|
|
839
|
+
console.warn(`[INIT TC] Network key frame counter is reaching its limit. A new network key will have to be instaured soon.`);
|
|
840
|
+
}
|
|
841
|
+
return result;
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* Form a network using given parameters.
|
|
845
|
+
*/
|
|
846
|
+
async formNetwork(fromBackup, networkKey, networkKeySequenceNumber, panId, extendedPanId, radioChannel, tcLinkKey) {
|
|
847
|
+
const state = {
|
|
848
|
+
bitmask: (enums_2.EmberInitialSecurityBitmask.TRUST_CENTER_GLOBAL_LINK_KEY | enums_2.EmberInitialSecurityBitmask.HAVE_PRECONFIGURED_KEY
|
|
849
|
+
| enums_2.EmberInitialSecurityBitmask.HAVE_NETWORK_KEY | enums_2.EmberInitialSecurityBitmask.TRUST_CENTER_USES_HASHED_LINK_KEY
|
|
850
|
+
| enums_2.EmberInitialSecurityBitmask.REQUIRE_ENCRYPTED_KEY),
|
|
851
|
+
preconfiguredKey: { contents: tcLinkKey },
|
|
852
|
+
networkKey: { contents: networkKey },
|
|
853
|
+
networkKeySequenceNumber: networkKeySequenceNumber,
|
|
854
|
+
preconfiguredTrustCenterEui64: consts_2.BLANK_EUI64,
|
|
855
|
+
};
|
|
856
|
+
if (fromBackup) {
|
|
857
|
+
state.bitmask |= enums_2.EmberInitialSecurityBitmask.NO_FRAME_COUNTER_RESET;
|
|
858
|
+
}
|
|
859
|
+
let emberStatus = (await this.ezsp.ezspSetInitialSecurityState(state));
|
|
860
|
+
if (emberStatus !== enums_2.EmberStatus.SUCCESS) {
|
|
861
|
+
throw new Error(`[INIT FORM] Failed to set initial security state with status=${enums_2.EmberStatus[emberStatus]}.`);
|
|
862
|
+
}
|
|
863
|
+
const extended = (enums_2.EmberExtendedSecurityBitmask.JOINER_GLOBAL_LINK_KEY | enums_2.EmberExtendedSecurityBitmask.NWK_LEAVE_REQUEST_NOT_ALLOWED);
|
|
864
|
+
const extSecStatus = (await this.ezsp.ezspSetExtendedSecurityBitmask(extended));
|
|
865
|
+
if (extSecStatus !== enums_2.EzspStatus.SUCCESS) {
|
|
866
|
+
throw new Error(`[INIT FORM] Failed to set extended security bitmask to ${extended} with status=${enums_2.EzspStatus[extSecStatus]}.`);
|
|
867
|
+
}
|
|
868
|
+
if (!fromBackup && STACK_CONFIGS[this.stackConfig].KEY_TABLE_SIZE) {
|
|
869
|
+
emberStatus = await this.ezsp.ezspClearKeyTable();
|
|
870
|
+
if (emberStatus !== enums_2.EmberStatus.SUCCESS) {
|
|
871
|
+
throw new Error(`[INIT FORM] Failed to clear key table with status=${enums_2.EmberStatus[emberStatus]}.`);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
const netParams = {
|
|
875
|
+
panId,
|
|
876
|
+
extendedPanId,
|
|
877
|
+
radioTxPower: 5,
|
|
878
|
+
radioChannel,
|
|
879
|
+
joinMethod: enums_2.EmberJoinMethod.MAC_ASSOCIATION,
|
|
880
|
+
nwkManagerId: consts_2.ZIGBEE_COORDINATOR_ADDRESS,
|
|
881
|
+
nwkUpdateId: 0,
|
|
882
|
+
channels: consts_2.EMBER_ALL_802_15_4_CHANNELS_MASK,
|
|
883
|
+
};
|
|
884
|
+
console.log(`[INIT FORM] Forming new network with: ${JSON.stringify(netParams)}`);
|
|
885
|
+
emberStatus = (await this.ezsp.ezspFormNetwork(netParams));
|
|
886
|
+
if (emberStatus !== enums_2.EmberStatus.SUCCESS) {
|
|
887
|
+
throw new Error(`[INIT FORM] Failed form network request with status=${enums_2.EmberStatus[emberStatus]}.`);
|
|
888
|
+
}
|
|
889
|
+
await this.oneWaitress.startWaitingForEvent({ eventName: OneWaitressEvents.STACK_STATUS_NETWORK_UP }, DEFAULT_NETWORK_REQUEST_TIMEOUT, '[INIT FORM] Form network');
|
|
890
|
+
const stStatus = await this.ezsp.ezspStartWritingStackTokens();
|
|
891
|
+
debug(`[INIT FORM] Start writing stack tokens status=${enums_2.EzspStatus[stStatus]}.`);
|
|
892
|
+
console.log(`[INIT FORM] New network formed!`);
|
|
893
|
+
}
|
|
894
|
+
/**
|
|
895
|
+
* Loads currently stored backup and returns it in internal backup model.
|
|
896
|
+
*/
|
|
897
|
+
async getStoredBackup() {
|
|
898
|
+
try {
|
|
899
|
+
await mz_1.fs.access(this.backupPath);
|
|
900
|
+
}
|
|
901
|
+
catch (error) {
|
|
902
|
+
return null;
|
|
903
|
+
}
|
|
904
|
+
let data;
|
|
905
|
+
try {
|
|
906
|
+
data = JSON.parse((await mz_1.fs.readFile(this.backupPath)).toString());
|
|
907
|
+
}
|
|
908
|
+
catch (error) {
|
|
909
|
+
throw new Error(`[BACKUP] Coordinator backup is corrupted.`);
|
|
910
|
+
}
|
|
911
|
+
if (data.metadata?.format === "zigpy/open-coordinator-backup" && data.metadata?.version) {
|
|
912
|
+
if (data.metadata?.version !== 1) {
|
|
913
|
+
throw new Error(`[BACKUP] Unsupported open coordinator backup version (version=${data.metadata?.version}).`);
|
|
914
|
+
}
|
|
915
|
+
if (!data.stack_specific?.ezsp || !data.metadata.internal.ezspVersion) {
|
|
916
|
+
throw new Error(`[BACKUP] Specified backup is not EZSP.`);
|
|
917
|
+
}
|
|
918
|
+
if (data.metadata.internal.ezspVersion < BACKUP_OLDEST_SUPPORTED_EZSP_VERSION) {
|
|
919
|
+
throw new Error(`[BACKUP] Specified backup is not a supported EZSP version (min: ${BACKUP_OLDEST_SUPPORTED_EZSP_VERSION}).`);
|
|
920
|
+
}
|
|
921
|
+
return utils_1.BackupUtils.fromUnifiedBackup(data);
|
|
922
|
+
}
|
|
923
|
+
else {
|
|
924
|
+
throw new Error(`[BACKUP] Unknown backup format.`);
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
/**
|
|
928
|
+
* Export link keys for backup.
|
|
929
|
+
*
|
|
930
|
+
* @return List of keys data with AES hashed keys
|
|
931
|
+
*/
|
|
932
|
+
async exportLinkKeys() {
|
|
933
|
+
const [confStatus, keyTableSize] = (await this.ezsp.ezspGetConfigurationValue(enums_1.EzspConfigId.KEY_TABLE_SIZE));
|
|
934
|
+
if (confStatus !== enums_2.EzspStatus.SUCCESS) {
|
|
935
|
+
throw new Error(`[BACKUP] Failed to retrieve key table size from NCP with status=${enums_2.EzspStatus[confStatus]}.`);
|
|
936
|
+
}
|
|
937
|
+
let deviceEui64;
|
|
938
|
+
let plaintextKey;
|
|
939
|
+
let apsKeyMeta;
|
|
940
|
+
let status;
|
|
941
|
+
const keyList = [];
|
|
942
|
+
for (let i = 0; i < keyTableSize; i++) {
|
|
943
|
+
[deviceEui64, plaintextKey, apsKeyMeta, status] = (await this.ezsp.ezspExportLinkKeyByIndex(i));
|
|
944
|
+
debug(`[BACKUP] Export link key at index ${i}, status=${enums_2.SLStatus[status]}.`);
|
|
945
|
+
// only include key if we could retrieve one at index and hash it properly
|
|
946
|
+
if (status === enums_2.SLStatus.OK) {
|
|
947
|
+
// Rather than give the real link key, the backup contains a hashed version of the key.
|
|
948
|
+
// This is done to prevent a compromise of the backup data from compromising the current link keys.
|
|
949
|
+
// This is per the Smart Energy spec.
|
|
950
|
+
const [hashStatus, hashedKey] = (await this.emberAesHashSimple(plaintextKey.contents));
|
|
951
|
+
if (hashStatus === enums_2.EmberStatus.SUCCESS) {
|
|
952
|
+
keyList.push({
|
|
953
|
+
deviceEui64,
|
|
954
|
+
key: { contents: hashedKey },
|
|
955
|
+
outgoingFrameCounter: apsKeyMeta.outgoingFrameCounter,
|
|
956
|
+
incomingFrameCounter: apsKeyMeta.incomingFrameCounter,
|
|
957
|
+
});
|
|
958
|
+
}
|
|
959
|
+
else {
|
|
960
|
+
// this should never happen?
|
|
961
|
+
console.error(`[BACKUP] Failed to hash link key at index ${i} with status=${enums_2.EmberStatus[hashStatus]}. Omitting from backup.`);
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
console.log(`[BACKUP] Retrieved ${keyList.length} link keys.`);
|
|
966
|
+
return keyList;
|
|
967
|
+
}
|
|
968
|
+
/**
|
|
969
|
+
* Import link keys from backup.
|
|
970
|
+
*
|
|
971
|
+
* @param backupData
|
|
972
|
+
*/
|
|
973
|
+
async importLinkKeys(backupData) {
|
|
974
|
+
if (!backupData?.length) {
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
const [confStatus, keyTableSize] = (await this.ezsp.ezspGetConfigurationValue(enums_1.EzspConfigId.KEY_TABLE_SIZE));
|
|
978
|
+
if (confStatus !== enums_2.EzspStatus.SUCCESS) {
|
|
979
|
+
throw new Error(`[BACKUP] Failed to retrieve key table size from NCP with status=${enums_2.EzspStatus[confStatus]}.`);
|
|
980
|
+
}
|
|
981
|
+
if (backupData.length > keyTableSize) {
|
|
982
|
+
throw new Error(`[BACKUP] Current key table of ${keyTableSize} is too small to import backup of ${backupData.length}!`);
|
|
983
|
+
}
|
|
984
|
+
const networkStatus = (await this.emberNetworkState());
|
|
985
|
+
if (networkStatus !== enums_2.EmberNetworkStatus.NO_NETWORK) {
|
|
986
|
+
throw new Error(`[BACKUP] Cannot import TC data while network is up, networkStatus=${enums_2.EmberNetworkStatus[networkStatus]}.`);
|
|
987
|
+
}
|
|
988
|
+
let status;
|
|
989
|
+
for (let i = 0; i < keyTableSize; i++) {
|
|
990
|
+
if (i >= backupData.length) {
|
|
991
|
+
// erase any key index not present in backup but available on the NCP
|
|
992
|
+
status = (await this.ezsp.ezspEraseKeyTableEntry(i));
|
|
993
|
+
}
|
|
994
|
+
else {
|
|
995
|
+
const importStatus = (await this.ezsp.ezspImportLinkKey(i, backupData[i].deviceEui64, backupData[i].key));
|
|
996
|
+
status = ((importStatus === enums_2.SLStatus.OK) ? enums_2.EmberStatus.SUCCESS : enums_2.EmberStatus.KEY_TABLE_INVALID_ADDRESS);
|
|
997
|
+
}
|
|
998
|
+
if (status !== enums_2.EmberStatus.SUCCESS) {
|
|
999
|
+
throw new Error(`[BACKUP] Failed to ${((i >= backupData.length) ? "erase" : "set")} key table entry at index ${i} `
|
|
1000
|
+
+ `with status=${enums_2.EmberStatus[status]}`);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
debug(`[BACKUP] Imported ${backupData.length} keys.`);
|
|
1004
|
+
}
|
|
1005
|
+
/**
|
|
1006
|
+
* Routine to update the network key and broadcast the update to the network after a set time.
|
|
1007
|
+
* NOTE: This should run at a large interval, but before the uint32_t of the frame counter is able to reach all Fs (can't wrap to 0).
|
|
1008
|
+
* This may disrupt sleepy end devices that miss the update, but they should be able to TC rejoin (in most cases...).
|
|
1009
|
+
* On the other hand, the more often this runs, the more secure the network is...
|
|
1010
|
+
*/
|
|
1011
|
+
async broadcastNetworkKeyUpdate() {
|
|
1012
|
+
return new Promise((resolve, reject) => {
|
|
1013
|
+
this.requestQueue.enqueue(async () => {
|
|
1014
|
+
console.warn(`[TRUST CENTER] Performing a network key update. This might take a while and disrupt normal operation.`);
|
|
1015
|
+
// zero-filled = let stack generate new random network key
|
|
1016
|
+
let status = await this.ezsp.ezspBroadcastNextNetworkKey({ contents: Buffer.alloc(consts_1.EMBER_ENCRYPTION_KEY_SIZE) });
|
|
1017
|
+
if (status !== enums_2.EmberStatus.SUCCESS) {
|
|
1018
|
+
console.error(`[TRUST CENTER] Failed to broadcast next network key with status=${enums_2.EmberStatus[status]}.`);
|
|
1019
|
+
return status;
|
|
1020
|
+
}
|
|
1021
|
+
// XXX: this will block other requests for a while, but should ensure the key propagates without interference?
|
|
1022
|
+
// could also stop dispatching entirely and do this outside the queue if necessary/better
|
|
1023
|
+
await (0, utils_1.Wait)(BROADCAST_NETWORK_KEY_SWITCH_WAIT_TIME);
|
|
1024
|
+
status = (await this.ezsp.ezspBroadcastNetworkKeySwitch());
|
|
1025
|
+
if (status !== enums_2.EmberStatus.SUCCESS) {
|
|
1026
|
+
// XXX: Not sure how likely this is, but this is bad, probably should hard fail?
|
|
1027
|
+
console.error(`[TRUST CENTER] Failed to broadcast network key switch with status=${enums_2.EmberStatus[status]}.`);
|
|
1028
|
+
return status;
|
|
1029
|
+
}
|
|
1030
|
+
resolve();
|
|
1031
|
+
return status;
|
|
1032
|
+
}, reject);
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
/**
|
|
1036
|
+
* Received when EZSP layer alerts of a problem that needs the NCP to be reset.
|
|
1037
|
+
* @param status
|
|
1038
|
+
*/
|
|
1039
|
+
async onNcpNeedsResetAndInit(status) {
|
|
1040
|
+
console.error(`!!! NCP FATAL ERROR reason=${enums_2.EzspStatus[status]}. ATTEMPTING RESET... !!!`);
|
|
1041
|
+
try {
|
|
1042
|
+
await this.stop();
|
|
1043
|
+
await (0, utils_1.Wait)(500); // just because
|
|
1044
|
+
await this.start();
|
|
1045
|
+
}
|
|
1046
|
+
catch (err) {
|
|
1047
|
+
console.error(`Failed to reset and init NCP. ${err}`);
|
|
1048
|
+
this.emit(events_1.Events.disconnected);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
/**
|
|
1052
|
+
* Called right before a NCP reset.
|
|
1053
|
+
*/
|
|
1054
|
+
async onNCPPreReset() {
|
|
1055
|
+
this.requestQueue.stopDispatching();
|
|
1056
|
+
}
|
|
1057
|
+
/**
|
|
1058
|
+
* Called right after a NCP reset, right before the creation of endpoints.
|
|
1059
|
+
*/
|
|
1060
|
+
async onNCPPostReset() {
|
|
1061
|
+
this.requestQueue.startDispatching();
|
|
1062
|
+
this.watchdogCountersHandle = setInterval(this.watchdogCounters.bind(this), WATCHDOG_COUNTERS_FEED_INTERVAL);
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* Handle changes in groups that needs to be propagated to the NCP multicast table.
|
|
1066
|
+
*
|
|
1067
|
+
* XXX: Since Z2M doesn't explicitly check-in downstream when groups are created/removed, we look at outgoing genGroups commands.
|
|
1068
|
+
* If the NCP doesn't know about groups, it can miss messages from some devices (remotes for example), so we add it...
|
|
1069
|
+
*
|
|
1070
|
+
* @param commandId
|
|
1071
|
+
* @param groupId
|
|
1072
|
+
*/
|
|
1073
|
+
async onGroupChange(commandId, groupId) {
|
|
1074
|
+
switch (commandId) {
|
|
1075
|
+
case cluster_1.default.genGroups.commands.add.ID: {
|
|
1076
|
+
// check if group already in multicast table, should not happen...
|
|
1077
|
+
const existingIndex = this.multicastTable.findIndex((e) => ((e != null) && (e.multicastId === groupId)));
|
|
1078
|
+
if (existingIndex == -1) {
|
|
1079
|
+
// find first unused index
|
|
1080
|
+
const newEntryIndex = this.multicastTable.findIndex((e) => (!e));
|
|
1081
|
+
if (newEntryIndex != -1) {
|
|
1082
|
+
const newEntry = {
|
|
1083
|
+
multicastId: groupId,
|
|
1084
|
+
endpoint: endpoints_1.FIXED_ENDPOINTS[0].endpoint,
|
|
1085
|
+
networkIndex: endpoints_1.FIXED_ENDPOINTS[0].networkIndex,
|
|
1086
|
+
};
|
|
1087
|
+
const status = (await this.ezsp.ezspSetMulticastTableEntry(newEntryIndex, newEntry));
|
|
1088
|
+
if (status !== enums_2.EmberStatus.SUCCESS) {
|
|
1089
|
+
console.error(`Failed to register group "${groupId}" in multicast table at index "${newEntryIndex}" with status=${enums_2.EmberStatus[status]}.`);
|
|
1090
|
+
}
|
|
1091
|
+
else {
|
|
1092
|
+
debug(`Registered multicast table entry: ${JSON.stringify(newEntry)}.`);
|
|
1093
|
+
}
|
|
1094
|
+
// always assume "it worked" to keep sync with Z2M first, NCP second, otherwise trouble might arise... should always work anyway
|
|
1095
|
+
this.multicastTable[newEntryIndex] = newEntry;
|
|
1096
|
+
}
|
|
1097
|
+
else {
|
|
1098
|
+
console.warn(`Coordinator multicast table is full (max: ${STACK_CONFIGS[this.stackConfig].MULTICAST_TABLE_SIZE}). `
|
|
1099
|
+
+ `Some devices in new groups may not work properly, including in group "${groupId}". `
|
|
1100
|
+
+ `If that happens, please remove groups to be below the limit. `
|
|
1101
|
+
+ `Removed groups are only removed from coordinator after a Zigbee2MQTT restart.`);
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
else {
|
|
1105
|
+
debug(`Added group "${groupId}", but local table says it is already registered at index "${existingIndex}". Skipping.`);
|
|
1106
|
+
}
|
|
1107
|
+
break;
|
|
1108
|
+
}
|
|
1109
|
+
// NOTE: Can't remove groups, since we watch from command exec to group members, that would trigger from any removed member,
|
|
1110
|
+
// even though the group might still exist...
|
|
1111
|
+
// Leaving this here (since it's done...), just in case we get better notifications for groups from upstream.
|
|
1112
|
+
// case Cluster.genGroups.commands.remove.ID: {
|
|
1113
|
+
// const entryIndex = this.multicastTable.findIndex((e) => ((e != null) && (e.multicastId === groupId)));
|
|
1114
|
+
// // just in case, never remove GP at i zero, should never be the case...
|
|
1115
|
+
// if (entryIndex > 0) {
|
|
1116
|
+
// const entry = this.multicastTable[entryIndex];
|
|
1117
|
+
// entry.endpoint = 0;// signals "not in use" in the stack
|
|
1118
|
+
// const status = (await this.ezsp.ezspSetMulticastTableEntry(entryIndex, entry));
|
|
1119
|
+
// if (status !== EmberStatus.SUCCESS) {
|
|
1120
|
+
// console.error(`Failed to remove multicast table entry at index "${entryIndex}" for group "${groupId}".`);
|
|
1121
|
+
// } else {
|
|
1122
|
+
// debug(`Removed multicast table entry at index "${entryIndex}".`);
|
|
1123
|
+
// }
|
|
1124
|
+
// // always assume "it worked" to keep sync with Z2M first, NCP second, otherwise trouble might arise... should always work anyway
|
|
1125
|
+
// this.multicastTable[entryIndex] = null;
|
|
1126
|
+
// } else {
|
|
1127
|
+
// debug(`Removed group "${groupId}", but local table did not have a reference to it.`);
|
|
1128
|
+
// }
|
|
1129
|
+
// break;
|
|
1130
|
+
// }
|
|
1131
|
+
// case Cluster.genGroups.commands.removeAll.ID: {
|
|
1132
|
+
// // this can create quite a few NCP calls, but hopefully shouldn't happen often
|
|
1133
|
+
// // always skip green power at i==0
|
|
1134
|
+
// for (let i = 1; i < this.multicastTable.length; i++) {
|
|
1135
|
+
// const entry = this.multicastTable[i];
|
|
1136
|
+
// if (entry != null) {
|
|
1137
|
+
// entry.endpoint = 0;// signals "not in use" in the stack
|
|
1138
|
+
// const status = (await this.ezsp.ezspSetMulticastTableEntry(i, entry));
|
|
1139
|
+
// if (status !== EmberStatus.SUCCESS) {
|
|
1140
|
+
// console.error(`Failed to remove multicast entry at index "${i}" with status=${EmberStatus[status]}.`);
|
|
1141
|
+
// } else {
|
|
1142
|
+
// debug(`Removed multicast table entry at index "${i}".`);
|
|
1143
|
+
// }
|
|
1144
|
+
// }
|
|
1145
|
+
// this.multicastTable[i] = null;
|
|
1146
|
+
// }
|
|
1147
|
+
// break;
|
|
1148
|
+
// }
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
//---- START Events
|
|
1152
|
+
//---- END Events
|
|
1153
|
+
//---- START Cache-enabled EZSP wrappers
|
|
1154
|
+
/**
|
|
1155
|
+
* Clear the cached network values (set to invalid values).
|
|
1156
|
+
*/
|
|
1157
|
+
clearNetworkCache() {
|
|
1158
|
+
this.networkCache = (0, initters_1.initNetworkCache)();
|
|
1159
|
+
}
|
|
1160
|
+
/**
|
|
1161
|
+
* Return the current network state.
|
|
1162
|
+
* This call caches the results on the host to prevent frequent EZSP transactions.
|
|
1163
|
+
* Check against UNKNOWN_NETWORK_STATE for validity.
|
|
1164
|
+
*/
|
|
1165
|
+
async emberNetworkState() {
|
|
1166
|
+
if (this.networkCache.status === consts_2.UNKNOWN_NETWORK_STATE) {
|
|
1167
|
+
const networkStatus = (await this.ezsp.ezspNetworkState());
|
|
1168
|
+
this.networkCache.status = networkStatus;
|
|
1169
|
+
}
|
|
1170
|
+
return this.networkCache.status;
|
|
1171
|
+
}
|
|
1172
|
+
/**
|
|
1173
|
+
* Return the EUI 64 of the local node
|
|
1174
|
+
* This call caches the results on the host to prevent frequent EZSP transactions.
|
|
1175
|
+
* Check against BLANK_EUI64 for validity.
|
|
1176
|
+
*/
|
|
1177
|
+
async emberGetEui64() {
|
|
1178
|
+
if (this.networkCache.eui64 === consts_2.BLANK_EUI64) {
|
|
1179
|
+
this.networkCache.eui64 = (await this.ezsp.ezspGetEui64());
|
|
1180
|
+
}
|
|
1181
|
+
return this.networkCache.eui64;
|
|
1182
|
+
}
|
|
1183
|
+
/**
|
|
1184
|
+
* Return the PAN ID of the local node.
|
|
1185
|
+
* This call caches the results on the host to prevent frequent EZSP transactions.
|
|
1186
|
+
* Check against INVALID_PAN_ID for validity.
|
|
1187
|
+
*/
|
|
1188
|
+
async emberGetPanId() {
|
|
1189
|
+
if (this.networkCache.parameters.panId === consts_2.INVALID_PAN_ID) {
|
|
1190
|
+
const [status, , parameters] = (await this.ezsp.ezspGetNetworkParameters());
|
|
1191
|
+
if (status === enums_2.EmberStatus.SUCCESS) {
|
|
1192
|
+
this.networkCache.parameters = parameters;
|
|
1193
|
+
}
|
|
1194
|
+
else {
|
|
1195
|
+
console.error(`Failed to get PAN ID (via network parameters) with status=${enums_2.EmberStatus[status]}.`);
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
return this.networkCache.parameters.panId;
|
|
1199
|
+
}
|
|
1200
|
+
/**
|
|
1201
|
+
* Return the Extended PAN ID of the local node.
|
|
1202
|
+
* This call caches the results on the host to prevent frequent EZSP transactions.
|
|
1203
|
+
* Check against BLANK_EXTENDED_PAN_ID for validity.
|
|
1204
|
+
*/
|
|
1205
|
+
async emberGetExtendedPanId() {
|
|
1206
|
+
if ((0, es6_1.default)(this.networkCache.parameters.extendedPanId, consts_2.BLANK_EXTENDED_PAN_ID)) {
|
|
1207
|
+
const [status, , parameters] = (await this.ezsp.ezspGetNetworkParameters());
|
|
1208
|
+
if (status === enums_2.EmberStatus.SUCCESS) {
|
|
1209
|
+
this.networkCache.parameters = parameters;
|
|
1210
|
+
}
|
|
1211
|
+
else {
|
|
1212
|
+
console.error(`Failed to get Extended PAN ID (via network parameters) with status=${enums_2.EmberStatus[status]}.`);
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
return this.networkCache.parameters.extendedPanId;
|
|
1216
|
+
}
|
|
1217
|
+
/**
|
|
1218
|
+
* Return the radio channel (uint8_t) of the current network.
|
|
1219
|
+
* This call caches the results on the host to prevent frequent EZSP transactions.
|
|
1220
|
+
* Check against INVALID_RADIO_CHANNEL for validity.
|
|
1221
|
+
*/
|
|
1222
|
+
async emberGetRadioChannel() {
|
|
1223
|
+
if (this.networkCache.parameters.radioChannel === consts_2.INVALID_RADIO_CHANNEL) {
|
|
1224
|
+
const [status, , parameters] = (await this.ezsp.ezspGetNetworkParameters());
|
|
1225
|
+
if (status === enums_2.EmberStatus.SUCCESS) {
|
|
1226
|
+
this.networkCache.parameters = parameters;
|
|
1227
|
+
}
|
|
1228
|
+
else {
|
|
1229
|
+
console.error(`Failed to get radio channel (via network parameters) with status=${enums_2.EmberStatus[status]}.`);
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
return this.networkCache.parameters.radioChannel;
|
|
1233
|
+
}
|
|
1234
|
+
// queued
|
|
1235
|
+
async emberStartEnergyScan() {
|
|
1236
|
+
return new Promise((resolve, reject) => {
|
|
1237
|
+
this.requestQueue.enqueue(async () => {
|
|
1238
|
+
const status = (await this.ezsp.ezspStartScan(enums_2.EzspNetworkScanType.ENERGY_SCAN, consts_2.EMBER_ALL_802_15_4_CHANNELS_MASK, ENERGY_SCAN_DURATION));
|
|
1239
|
+
if (status !== enums_2.SLStatus.OK) {
|
|
1240
|
+
console.error(`Failed energy scan request with status=${enums_2.SLStatus[status]}.`);
|
|
1241
|
+
return enums_2.EmberStatus.ERR_FATAL;
|
|
1242
|
+
}
|
|
1243
|
+
// TODO: result in logs only atm, since UI doesn't support it
|
|
1244
|
+
resolve();
|
|
1245
|
+
return enums_2.EmberStatus.SUCCESS;
|
|
1246
|
+
}, reject);
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
1249
|
+
//---- END Cache-enabled EZSP wrappers
|
|
1250
|
+
//---- START EZSP wrappers
|
|
1251
|
+
/**
|
|
1252
|
+
* Ensure the Host & NCP are aligned on protocols using version.
|
|
1253
|
+
* Cache the retrieved information.
|
|
1254
|
+
*
|
|
1255
|
+
* NOTE: currently throws on mismatch until support for lower versions is implemented (not planned atm)
|
|
1256
|
+
*
|
|
1257
|
+
* Does nothing if ncpNeedsResetAndInit == true.
|
|
1258
|
+
*/
|
|
1259
|
+
async emberVersion() {
|
|
1260
|
+
// Note that NCP == Network Co-Processor
|
|
1261
|
+
// the EZSP protocol version that the Host is running, we are the host so we set this value
|
|
1262
|
+
const hostEzspProtocolVer = consts_1.EZSP_PROTOCOL_VERSION;
|
|
1263
|
+
// send the Host version number to the NCP.
|
|
1264
|
+
// The NCP returns the EZSP version that the NCP is running along with the stackType and stackVersion
|
|
1265
|
+
const [ncpEzspProtocolVer, ncpStackType, ncpStackVer] = (await this.ezsp.ezspVersion(hostEzspProtocolVer));
|
|
1266
|
+
// verify that the stack type is what is expected
|
|
1267
|
+
if (ncpStackType !== consts_1.EZSP_STACK_TYPE_MESH) {
|
|
1268
|
+
throw new Error(`Stack type ${ncpStackType} is not expected!`);
|
|
1269
|
+
}
|
|
1270
|
+
// verify that the NCP EZSP Protocol version is what is expected
|
|
1271
|
+
if (ncpEzspProtocolVer !== consts_1.EZSP_PROTOCOL_VERSION) {
|
|
1272
|
+
throw new Error(`NCP EZSP protocol version of ${ncpEzspProtocolVer} does not match Host version ${hostEzspProtocolVer}`);
|
|
1273
|
+
}
|
|
1274
|
+
debug(`NCP info: EZSPVersion=${ncpEzspProtocolVer} StackType=${ncpStackType} StackVersion=${ncpStackVer}`);
|
|
1275
|
+
const [status, versionStruct] = (await this.ezsp.ezspGetVersionStruct());
|
|
1276
|
+
if (status !== enums_2.EzspStatus.SUCCESS) {
|
|
1277
|
+
// NCP has old style version number
|
|
1278
|
+
debug(`NCP has old-style version number.`);
|
|
1279
|
+
this.version = {
|
|
1280
|
+
ezsp: ncpEzspProtocolVer,
|
|
1281
|
+
revision: `${ncpStackVer}`,
|
|
1282
|
+
major: ncpStackVer,
|
|
1283
|
+
minor: 0,
|
|
1284
|
+
patch: 0,
|
|
1285
|
+
special: 0,
|
|
1286
|
+
build: 0,
|
|
1287
|
+
type: enums_2.EmberVersionType.GA, // default...
|
|
1288
|
+
};
|
|
1289
|
+
}
|
|
1290
|
+
else {
|
|
1291
|
+
// NCP has new style version number
|
|
1292
|
+
this.version = {
|
|
1293
|
+
ezsp: ncpEzspProtocolVer,
|
|
1294
|
+
revision: `${versionStruct.major}.${versionStruct.minor}.${versionStruct.patch} [${enums_2.EmberVersionType[versionStruct.type]}]`,
|
|
1295
|
+
...versionStruct,
|
|
1296
|
+
};
|
|
1297
|
+
if (versionStruct.type !== enums_2.EmberVersionType.GA) {
|
|
1298
|
+
console.warn(`NCP is running a non-GA version (${enums_2.EmberVersionType[versionStruct.type]}).`);
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
debug(`NCP version info: ${JSON.stringify(this.version)}`);
|
|
1302
|
+
}
|
|
1303
|
+
/**
|
|
1304
|
+
* This function sets an EZSP config value.
|
|
1305
|
+
* WARNING: Do not call for values that cannot be set after init without first resetting NCP (like table sizes).
|
|
1306
|
+
* To avoid an extra NCP call, this does not check for it.
|
|
1307
|
+
* @param configId
|
|
1308
|
+
* @param value uint16_t
|
|
1309
|
+
* @returns
|
|
1310
|
+
*/
|
|
1311
|
+
async emberSetEzspConfigValue(configId, value) {
|
|
1312
|
+
const status = (await this.ezsp.ezspSetConfigurationValue(configId, value));
|
|
1313
|
+
debug(`[EzspConfigId] SET "${enums_1.EzspConfigId[configId]}" TO "${value}" with status=${enums_2.EzspStatus[status]}.`);
|
|
1314
|
+
if (status === enums_2.EzspStatus.ERROR_INVALID_ID) {
|
|
1315
|
+
// can be ZLL where not all NCPs need or support it.
|
|
1316
|
+
console.warn(`[EzspConfigId] Unsupported configuration ID ${enums_1.EzspConfigId[configId]} by NCP.`);
|
|
1317
|
+
}
|
|
1318
|
+
else if (status !== enums_2.EzspStatus.SUCCESS) {
|
|
1319
|
+
// don't fail in case a set value gets called "out of time"
|
|
1320
|
+
console.error(`[EzspConfigId] Failed to SET "${enums_1.EzspConfigId[configId]}" TO "${value}" with status=${enums_2.EzspStatus[status]}.`);
|
|
1321
|
+
}
|
|
1322
|
+
return status;
|
|
1323
|
+
}
|
|
1324
|
+
/**
|
|
1325
|
+
* This function sets an EZSP value.
|
|
1326
|
+
* @param valueId
|
|
1327
|
+
* @param valueLength uint8_t
|
|
1328
|
+
* @param value uint8_t *
|
|
1329
|
+
* @returns
|
|
1330
|
+
*/
|
|
1331
|
+
async emberSetEzspValue(valueId, valueLength, value) {
|
|
1332
|
+
const status = (await this.ezsp.ezspSetValue(valueId, valueLength, value));
|
|
1333
|
+
debug(`[EzspValueId] SET "${enums_1.EzspValueId[valueId]}" TO "${value}" with status=${enums_2.EzspStatus[status]}.`);
|
|
1334
|
+
return status;
|
|
1335
|
+
}
|
|
1336
|
+
/**
|
|
1337
|
+
* This function sets an EZSP policy.
|
|
1338
|
+
* @param policyId
|
|
1339
|
+
* @param decisionId Can be bitop
|
|
1340
|
+
* @returns
|
|
1341
|
+
*/
|
|
1342
|
+
async emberSetEzspPolicy(policyId, decisionId) {
|
|
1343
|
+
const status = (await this.ezsp.ezspSetPolicy(policyId, decisionId));
|
|
1344
|
+
debug(`[EzspPolicyId] SET "${enums_1.EzspPolicyId[policyId]}" TO "${decisionId}" with status=${enums_2.EzspStatus[status]}.`);
|
|
1345
|
+
return status;
|
|
1346
|
+
}
|
|
1347
|
+
/**
|
|
1348
|
+
* Here we convert the normal Ember AES hash call to the specialized EZSP call.
|
|
1349
|
+
* This came about because we cannot pass a block of data that is
|
|
1350
|
+
* both input and output into EZSP. The block must be broken up into two
|
|
1351
|
+
* elements. We unify the two pieces here to make it invisible to the users.
|
|
1352
|
+
* @param context EmberAesMmoHashContext *
|
|
1353
|
+
* @param finalize
|
|
1354
|
+
* @param data uint8_t * Expected of valid length (as in, not larger alloc)
|
|
1355
|
+
* @returns status
|
|
1356
|
+
* @returns result context or null
|
|
1357
|
+
*/
|
|
1358
|
+
async aesMmoHash(context, finalize, data) {
|
|
1359
|
+
if (data.length > 255) {
|
|
1360
|
+
throw new Error(enums_2.EzspStatus[enums_2.EzspStatus.ERROR_INVALID_CALL]);
|
|
1361
|
+
}
|
|
1362
|
+
const [status, reContext] = (await this.ezsp.ezspAesMmoHash(context, finalize, data));
|
|
1363
|
+
return [status, reContext];
|
|
1364
|
+
}
|
|
1365
|
+
/**
|
|
1366
|
+
* This routine processes the passed chunk of data and updates
|
|
1367
|
+
* the hash calculation based on it. The data passed in MUST
|
|
1368
|
+
* have a length that is a multiple of 16.
|
|
1369
|
+
*
|
|
1370
|
+
* @param context EmberAesMmoHashContext* A pointer to the location of the hash context to update.
|
|
1371
|
+
* @param data const uint8_t* A pointer to the location of the data to hash.
|
|
1372
|
+
*
|
|
1373
|
+
* @returns An ::EmberStatus value indicating EMBER_SUCCESS if the hash was
|
|
1374
|
+
* calculated successfully. EMBER_INVALID_CALL if the block size is not a
|
|
1375
|
+
* multiple of 16 bytes, and EMBER_INDEX_OUT_OF_RANGE is returned when the
|
|
1376
|
+
* data exceeds the maximum limits of the hash function.
|
|
1377
|
+
* @returns result context or null
|
|
1378
|
+
*/
|
|
1379
|
+
async emberAesMmoHashUpdate(context, data) {
|
|
1380
|
+
return this.aesMmoHash(context, false /*finalize?*/, data);
|
|
1381
|
+
}
|
|
1382
|
+
/**
|
|
1383
|
+
* This routine processes the passed chunk of data (if non-NULL)
|
|
1384
|
+
* and update the hash context that is passed in. In then performs
|
|
1385
|
+
* the final calculations on the hash and returns the final answer
|
|
1386
|
+
* in the result parameter of the ::EmberAesMmoHashContext structure.
|
|
1387
|
+
* The length of the data passed in may be any value, it does not have
|
|
1388
|
+
* to be a multiple of 16.
|
|
1389
|
+
*
|
|
1390
|
+
* @param context EmberAesMmoHashContext * A pointer to the location of the hash context to finalize.
|
|
1391
|
+
* @param data uint8_t * A pointer to the location of data to hash. May be NULL.
|
|
1392
|
+
*
|
|
1393
|
+
* @returns An ::EmberStatus value indicating EMBER_SUCCESS if the hash was
|
|
1394
|
+
* calculated successfully. EMBER_INVALID_CALL if the block size is not a
|
|
1395
|
+
* multiple of 16 bytes, and EMBER_INDEX_OUT_OF_RANGE is returned when the
|
|
1396
|
+
* data exceeds the maximum limits of the hash function.
|
|
1397
|
+
* @returns result context or null
|
|
1398
|
+
*/
|
|
1399
|
+
async emberAesMmoHashFinal(context, data) {
|
|
1400
|
+
return this.aesMmoHash(context, true /*finalize?*/, data);
|
|
1401
|
+
}
|
|
1402
|
+
/**
|
|
1403
|
+
* This is a convenience method when the hash data is less than 255
|
|
1404
|
+
* bytes. It inits, updates, and finalizes the hash in one function call.
|
|
1405
|
+
*
|
|
1406
|
+
* @param data const uint8_t* The data to hash. Expected of valid length (as in, not larger alloc)
|
|
1407
|
+
*
|
|
1408
|
+
* @returns An ::EmberStatus value indicating EMBER_SUCCESS if the hash was
|
|
1409
|
+
* calculated successfully. EMBER_INVALID_CALL if the block size is not a
|
|
1410
|
+
* multiple of 16 bytes, and EMBER_INDEX_OUT_OF_RANGE is returned when the
|
|
1411
|
+
* data exceeds the maximum limits of the hash function.
|
|
1412
|
+
* @returns result uint8_t* The location where the result of the hash will be written.
|
|
1413
|
+
*/
|
|
1414
|
+
async emberAesHashSimple(data) {
|
|
1415
|
+
const context = (0, initters_1.aesMmoHashInit)();
|
|
1416
|
+
const [status, reContext] = (await this.emberAesMmoHashFinal(context, data));
|
|
1417
|
+
return [status, reContext?.result];
|
|
1418
|
+
}
|
|
1419
|
+
/**
|
|
1420
|
+
* Enable local permit join and optionally broadcast the ZDO Mgmt_Permit_Join_req message.
|
|
1421
|
+
* This API can be called from any device type and still return EMBER_SUCCESS.
|
|
1422
|
+
* If the API is called from an end device, the permit association bit will just be left off.
|
|
1423
|
+
*
|
|
1424
|
+
* @param duration uint8_t The duration that the permit join bit will remain on
|
|
1425
|
+
* and other devices will be able to join the current network.
|
|
1426
|
+
* @param broadcastMgmtPermitJoin whether or not to broadcast the ZDO Mgmt_Permit_Join_req message.
|
|
1427
|
+
*
|
|
1428
|
+
* @returns status of whether or not permit join was enabled.
|
|
1429
|
+
* @returns apsFrame Will be null if not broadcasting.
|
|
1430
|
+
* @returns messageTag The tag passed to ezspSend${x} function.
|
|
1431
|
+
*/
|
|
1432
|
+
async emberPermitJoining(duration, broadcastMgmtPermitJoin) {
|
|
1433
|
+
let status = (await this.ezsp.ezspPermitJoining(duration));
|
|
1434
|
+
let apsFrame = null;
|
|
1435
|
+
let messageTag = null;
|
|
1436
|
+
debug(`Permit joining for ${duration} sec. status=${[status]}`);
|
|
1437
|
+
if (broadcastMgmtPermitJoin) {
|
|
1438
|
+
// `authentication`: TC significance always 1 (zb specs)
|
|
1439
|
+
[status, apsFrame, messageTag] = (await this.emberPermitJoiningRequest(consts_2.EMBER_BROADCAST_ADDRESS, duration, 1, this.defaultApsOptions));
|
|
1440
|
+
}
|
|
1441
|
+
return [status, apsFrame, messageTag];
|
|
1442
|
+
}
|
|
1443
|
+
/**
|
|
1444
|
+
* Set the trust center policy bitmask using decision.
|
|
1445
|
+
* @param decision
|
|
1446
|
+
* @returns
|
|
1447
|
+
*/
|
|
1448
|
+
async emberSetJoinPolicy(decision) {
|
|
1449
|
+
let policy = enums_1.EzspDecisionBitmask.DEFAULT_CONFIGURATION;
|
|
1450
|
+
if (decision == enums_2.EmberJoinDecision.USE_PRECONFIGURED_KEY) {
|
|
1451
|
+
policy = (enums_1.EzspDecisionBitmask.ALLOW_JOINS | enums_1.EzspDecisionBitmask.ALLOW_UNSECURED_REJOINS);
|
|
1452
|
+
}
|
|
1453
|
+
else if (decision == enums_2.EmberJoinDecision.SEND_KEY_IN_THE_CLEAR) {
|
|
1454
|
+
policy = (enums_1.EzspDecisionBitmask.ALLOW_JOINS | enums_1.EzspDecisionBitmask.ALLOW_UNSECURED_REJOINS | enums_1.EzspDecisionBitmask.SEND_KEY_IN_CLEAR);
|
|
1455
|
+
}
|
|
1456
|
+
else if (decision == enums_2.EmberJoinDecision.ALLOW_REJOINS_ONLY) {
|
|
1457
|
+
policy = enums_1.EzspDecisionBitmask.ALLOW_UNSECURED_REJOINS;
|
|
1458
|
+
}
|
|
1459
|
+
return this.emberSetEzspPolicy(enums_1.EzspPolicyId.TRUST_CENTER_POLICY, policy);
|
|
1460
|
+
}
|
|
1461
|
+
/**
|
|
1462
|
+
* Get Source Route Overhead
|
|
1463
|
+
*
|
|
1464
|
+
* Returns the number of bytes needed in a packet for source routing.
|
|
1465
|
+
* Since each hop consumes 2 bytes in the packet, this routine calculates the
|
|
1466
|
+
* total number of bytes needed based on number of hops to reach the destination.
|
|
1467
|
+
*
|
|
1468
|
+
* This function is called by the framework to determine the overhead required
|
|
1469
|
+
* in the network frame for source routing to a particular destination.
|
|
1470
|
+
*
|
|
1471
|
+
* @param destination The node id of the destination Ver.: always
|
|
1472
|
+
* @returns int8u The number of bytes needed for source routing in a packet.
|
|
1473
|
+
*/
|
|
1474
|
+
async emberGetSourceRouteOverhead(destination) {
|
|
1475
|
+
const [status, value] = (await this.ezsp.ezspGetSourceRouteOverhead(destination));
|
|
1476
|
+
if (status === enums_2.EzspStatus.SUCCESS) {
|
|
1477
|
+
return value;
|
|
1478
|
+
}
|
|
1479
|
+
else {
|
|
1480
|
+
debug(`Failed to get source route overhead (via extended value), status=${enums_2.EzspStatus[status]}.`);
|
|
1481
|
+
}
|
|
1482
|
+
return 0;
|
|
1483
|
+
}
|
|
1484
|
+
/**
|
|
1485
|
+
* Return the maximum size of the payload that the Application Support sub-layer will accept for
|
|
1486
|
+
* the given message type, destination, and APS frame.
|
|
1487
|
+
*
|
|
1488
|
+
* The size depends on multiple factors, including the security level in use and additional information
|
|
1489
|
+
* added to the message to support the various options.
|
|
1490
|
+
*
|
|
1491
|
+
* @param type The outgoing message type.
|
|
1492
|
+
* @param indexOrDestination uint16_t Depending on the message type, this is either the
|
|
1493
|
+
* EmberNodeId of the destination, an index into the address table, an index
|
|
1494
|
+
* into the binding table, the multicast identifier, or a broadcast address.
|
|
1495
|
+
* @param apsFrame EmberApsFrame *The APS frame for the message.
|
|
1496
|
+
* @return uint8_t The maximum APS payload length for the given message.
|
|
1497
|
+
*/
|
|
1498
|
+
async maximumApsPayloadLength(type, indexOrDestination, apsFrame) {
|
|
1499
|
+
let destination = consts_2.EMBER_UNKNOWN_NODE_ID;
|
|
1500
|
+
let max = consts_2.MAXIMUM_APS_PAYLOAD_LENGTH; // uint8_t
|
|
1501
|
+
if ((apsFrame.options & enums_2.EmberApsOption.ENCRYPTION) !== 0) {
|
|
1502
|
+
max -= consts_2.APS_ENCRYPTION_OVERHEAD;
|
|
1503
|
+
}
|
|
1504
|
+
if ((apsFrame.options & enums_2.EmberApsOption.SOURCE_EUI64) !== 0) {
|
|
1505
|
+
max -= consts_1.EUI64_SIZE;
|
|
1506
|
+
}
|
|
1507
|
+
if ((apsFrame.options & enums_2.EmberApsOption.DESTINATION_EUI64) !== 0) {
|
|
1508
|
+
max -= consts_1.EUI64_SIZE;
|
|
1509
|
+
}
|
|
1510
|
+
if ((apsFrame.options & enums_2.EmberApsOption.FRAGMENT) !== 0) {
|
|
1511
|
+
max -= consts_2.APS_FRAGMENTATION_OVERHEAD;
|
|
1512
|
+
}
|
|
1513
|
+
switch (type) {
|
|
1514
|
+
case enums_2.EmberOutgoingMessageType.DIRECT:
|
|
1515
|
+
destination = indexOrDestination;
|
|
1516
|
+
break;
|
|
1517
|
+
case enums_2.EmberOutgoingMessageType.VIA_ADDRESS_TABLE:
|
|
1518
|
+
destination = (await this.ezsp.ezspGetAddressTableRemoteNodeId(indexOrDestination));
|
|
1519
|
+
break;
|
|
1520
|
+
case enums_2.EmberOutgoingMessageType.VIA_BINDING:
|
|
1521
|
+
destination = (await this.ezsp.ezspGetBindingRemoteNodeId(indexOrDestination));
|
|
1522
|
+
break;
|
|
1523
|
+
case enums_2.EmberOutgoingMessageType.MULTICAST:
|
|
1524
|
+
// APS multicast messages include the two-byte group id and exclude the one-byte destination endpoint,
|
|
1525
|
+
// for a net loss of an extra byte.
|
|
1526
|
+
max--;
|
|
1527
|
+
break;
|
|
1528
|
+
case enums_2.EmberOutgoingMessageType.BROADCAST:
|
|
1529
|
+
break;
|
|
1530
|
+
default:
|
|
1531
|
+
break;
|
|
1532
|
+
}
|
|
1533
|
+
max -= (await this.emberGetSourceRouteOverhead(destination));
|
|
1534
|
+
return max;
|
|
1535
|
+
}
|
|
1536
|
+
//---- END EZSP wrappers
|
|
1537
|
+
//---- START Ember ZDO
|
|
1538
|
+
/**
|
|
1539
|
+
* ZDO
|
|
1540
|
+
* Change the default radius for broadcast ZDO requests
|
|
1541
|
+
*
|
|
1542
|
+
* @param radius uint8_t The radius to be used for future ZDO request broadcasts.
|
|
1543
|
+
*/
|
|
1544
|
+
setZDORequestRadius(radius) {
|
|
1545
|
+
this.zdoRequestRadius = radius;
|
|
1546
|
+
}
|
|
1547
|
+
/**
|
|
1548
|
+
* ZDO
|
|
1549
|
+
* Retrieve the default radius for broadcast ZDO requests
|
|
1550
|
+
*
|
|
1551
|
+
* @return uint8_t The radius to be used for future ZDO request broadcasts.
|
|
1552
|
+
*/
|
|
1553
|
+
getZDORequestRadius() {
|
|
1554
|
+
return this.zdoRequestRadius;
|
|
1555
|
+
}
|
|
1556
|
+
/**
|
|
1557
|
+
* ZDO
|
|
1558
|
+
* Get the next device request sequence number.
|
|
1559
|
+
*
|
|
1560
|
+
* Requests have sequence numbers so that they can be matched up with the
|
|
1561
|
+
* responses. To avoid complexities, the library uses numbers with the high
|
|
1562
|
+
* bit clear and the stack uses numbers with the high bit set.
|
|
1563
|
+
*
|
|
1564
|
+
* @return uint8_t The next device request sequence number
|
|
1565
|
+
*/
|
|
1566
|
+
nextZDORequestSequence() {
|
|
1567
|
+
return (this.zdoRequestSequence = ((++this.zdoRequestSequence) & APPLICATION_ZDO_SEQUENCE_MASK));
|
|
1568
|
+
}
|
|
1569
|
+
/**
|
|
1570
|
+
* ZDO
|
|
1571
|
+
*
|
|
1572
|
+
* @param destination
|
|
1573
|
+
* @param clusterId uint16_t
|
|
1574
|
+
* @param options
|
|
1575
|
+
* @param length uint8_t
|
|
1576
|
+
* @returns status Indicates success or failure (with reason) of send
|
|
1577
|
+
* @returns apsFrame The APS Frame resulting of the request being built and sent (`sequence` set from stack-given value).
|
|
1578
|
+
* @returns messageTag The tag passed to ezspSend${x} function.
|
|
1579
|
+
*/
|
|
1580
|
+
async sendZDORequestBuffer(destination, clusterId, options) {
|
|
1581
|
+
if (this.zdoRequestBuffalo.getPosition() > consts_1.EZSP_MAX_FRAME_LENGTH) {
|
|
1582
|
+
return [enums_2.EmberStatus.MESSAGE_TOO_LONG, null, null];
|
|
1583
|
+
}
|
|
1584
|
+
const messageTag = this.nextZDORequestSequence();
|
|
1585
|
+
this.zdoRequestBuffalo.setCommandByte(0, messageTag);
|
|
1586
|
+
const apsFrame = {
|
|
1587
|
+
profileId: zdo_1.ZDO_PROFILE_ID,
|
|
1588
|
+
clusterId: clusterId,
|
|
1589
|
+
sourceEndpoint: zdo_1.ZDO_ENDPOINT,
|
|
1590
|
+
destinationEndpoint: zdo_1.ZDO_ENDPOINT,
|
|
1591
|
+
options: options,
|
|
1592
|
+
groupId: 0,
|
|
1593
|
+
sequence: 0, // set by stack
|
|
1594
|
+
};
|
|
1595
|
+
const messageContents = this.zdoRequestBuffalo.getWritten();
|
|
1596
|
+
if (destination === consts_2.EMBER_BROADCAST_ADDRESS || destination === consts_2.EMBER_RX_ON_WHEN_IDLE_BROADCAST_ADDRESS
|
|
1597
|
+
|| destination === consts_2.EMBER_SLEEPY_BROADCAST_ADDRESS) {
|
|
1598
|
+
debug(`~~~> [ZDO BROADCAST apsFrame=${JSON.stringify(apsFrame)} messageTag=${messageTag}]`);
|
|
1599
|
+
const [status, apsSequence] = (await this.ezsp.ezspSendBroadcast(destination, apsFrame, this.getZDORequestRadius(), messageTag, messageContents));
|
|
1600
|
+
apsFrame.sequence = apsSequence;
|
|
1601
|
+
debug(`~~~> [SENT ZDO type=BROADCAST apsFrame=${JSON.stringify(apsFrame)} messageTag=${messageTag} status=${enums_2.EmberStatus[status]}]`);
|
|
1602
|
+
return [status, apsFrame, messageTag];
|
|
1603
|
+
}
|
|
1604
|
+
else {
|
|
1605
|
+
debug(`~~~> [ZDO UNICAST apsFrame=${JSON.stringify(apsFrame)} messageTag=${messageTag}]`);
|
|
1606
|
+
const [status, apsSequence] = (await this.ezsp.ezspSendUnicast(enums_2.EmberOutgoingMessageType.DIRECT, destination, apsFrame, messageTag, messageContents));
|
|
1607
|
+
apsFrame.sequence = apsSequence;
|
|
1608
|
+
debug(`~~~> [SENT ZDO type=DIRECT apsFrame=${JSON.stringify(apsFrame)} messageTag=${messageTag} status=${enums_2.EmberStatus[status]}]`);
|
|
1609
|
+
return [status, apsFrame, messageTag];
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
/**
|
|
1613
|
+
* ZDO
|
|
1614
|
+
* Service Discovery Functions
|
|
1615
|
+
* Request the specified node to send a list of its endpoints that
|
|
1616
|
+
* match the specified application profile and, optionally, lists of input
|
|
1617
|
+
* and/or output clusters.
|
|
1618
|
+
* @param target The node whose matching endpoints are desired. The request can
|
|
1619
|
+
* be sent unicast or broadcast ONLY to the "RX-on-when-idle-address" (0xFFFD)
|
|
1620
|
+
* If sent as a broadcast, any node that has matching endpoints will send a
|
|
1621
|
+
* response.
|
|
1622
|
+
* @param profile uint16_t The application profile to match.
|
|
1623
|
+
* @param inCount uint8_t The number of input clusters. To not match any input
|
|
1624
|
+
* clusters, set this value to 0.
|
|
1625
|
+
* @param outCount uint8_t The number of output clusters. To not match any output
|
|
1626
|
+
* clusters, set this value to 0.
|
|
1627
|
+
* @param inClusters uint16_t * The list of input clusters.
|
|
1628
|
+
* @param outClusters uint16_t * The list of output clusters.
|
|
1629
|
+
* @param options The options to use when sending the unicast request. See
|
|
1630
|
+
* emberSendUnicast() for a description. This parameter is ignored if the target
|
|
1631
|
+
* is a broadcast address.
|
|
1632
|
+
* @returns An EmberStatus value. EMBER_SUCCESS, MESSAGE_TOO_LONG,
|
|
1633
|
+
* EMBER_NETWORK_DOWN or EMBER_NETWORK_BUSY.
|
|
1634
|
+
*/
|
|
1635
|
+
async emberMatchDescriptorsRequest(target, profile, inClusters, outClusters, options) {
|
|
1636
|
+
// 2 bytes for NWK Address + 2 bytes for Profile Id + 1 byte for in Cluster Count
|
|
1637
|
+
// + in times 2 for 2 byte Clusters + out Cluster Count + out times 2 for 2 byte Clusters
|
|
1638
|
+
const length = (zdo_1.ZDO_MESSAGE_OVERHEAD + 2 + 2 + 1 + (inClusters.length * 2) + 1 + (outClusters.length * 2));
|
|
1639
|
+
// sanity check
|
|
1640
|
+
if (length > consts_1.EZSP_MAX_FRAME_LENGTH) {
|
|
1641
|
+
return [enums_2.EmberStatus.MESSAGE_TOO_LONG, null, null];
|
|
1642
|
+
}
|
|
1643
|
+
this.zdoRequestBuffalo.setPosition(zdo_1.ZDO_MESSAGE_OVERHEAD);
|
|
1644
|
+
this.zdoRequestBuffalo.writeUInt16(target);
|
|
1645
|
+
this.zdoRequestBuffalo.writeUInt16(profile);
|
|
1646
|
+
this.zdoRequestBuffalo.writeUInt8(inClusters.length);
|
|
1647
|
+
this.zdoRequestBuffalo.writeListUInt16(inClusters);
|
|
1648
|
+
this.zdoRequestBuffalo.writeUInt8(outClusters.length);
|
|
1649
|
+
this.zdoRequestBuffalo.writeListUInt16(outClusters);
|
|
1650
|
+
debug(`~~~> [ZDO MATCH DESCRIPTOR target=${target} profile=${profile} inClusters=${inClusters} outClusters=${outClusters}]`);
|
|
1651
|
+
return this.sendZDORequestBuffer(target, zdo_1.MATCH_DESCRIPTORS_REQUEST, options);
|
|
1652
|
+
}
|
|
1653
|
+
/**
|
|
1654
|
+
* ZDO
|
|
1655
|
+
* Device Discovery Functions
|
|
1656
|
+
* Request the 16 bit network address of a node whose EUI64 is known.
|
|
1657
|
+
*
|
|
1658
|
+
* @param target The EUI64 of the node.
|
|
1659
|
+
* @param reportKids true to request that the target list their children
|
|
1660
|
+
* in the response.
|
|
1661
|
+
* @param childStartIndex uint8_t The index of the first child to list in the response.
|
|
1662
|
+
* Ignored if @c reportKids is false.
|
|
1663
|
+
*
|
|
1664
|
+
* @return An ::EmberStatus value.
|
|
1665
|
+
* - ::EMBER_SUCCESS - The request was transmitted successfully.
|
|
1666
|
+
* - ::EMBER_NO_BUFFERS - Insufficient message buffers were available to construct the request.
|
|
1667
|
+
* - ::EMBER_NETWORK_DOWN - The node is not part of a network.
|
|
1668
|
+
* - ::EMBER_NETWORK_BUSY - Transmission of the request failed.
|
|
1669
|
+
*/
|
|
1670
|
+
async emberNetworkAddressRequest(target, reportKids, childStartIndex) {
|
|
1671
|
+
this.zdoRequestBuffalo.setPosition(zdo_1.ZDO_MESSAGE_OVERHEAD);
|
|
1672
|
+
this.zdoRequestBuffalo.writeIeeeAddr(target);
|
|
1673
|
+
this.zdoRequestBuffalo.writeUInt8(reportKids ? 1 : 0);
|
|
1674
|
+
this.zdoRequestBuffalo.writeUInt8(childStartIndex);
|
|
1675
|
+
debug(`~~~> [ZDO NETWORK ADDRESS target=${target} reportKids=${reportKids} childStartIndex=${childStartIndex}]`);
|
|
1676
|
+
return this.sendZDORequestBuffer(consts_2.EMBER_RX_ON_WHEN_IDLE_BROADCAST_ADDRESS, zdo_1.NETWORK_ADDRESS_REQUEST, enums_2.EmberApsOption.SOURCE_EUI64);
|
|
1677
|
+
}
|
|
1678
|
+
/**
|
|
1679
|
+
* ZDO
|
|
1680
|
+
* Device Discovery Functions
|
|
1681
|
+
* @brief Request the EUI64 of a node whose 16 bit network address is known.
|
|
1682
|
+
*
|
|
1683
|
+
* @param target uint16_t The network address of the node.
|
|
1684
|
+
* @param reportKids uint8_t true to request that the target list their children
|
|
1685
|
+
* in the response.
|
|
1686
|
+
* @param childStartIndex uint8_t The index of the first child to list in the response.
|
|
1687
|
+
* Ignored if reportKids is false.
|
|
1688
|
+
* @param options The options to use when sending the request. See ::emberSendUnicast() for a description.
|
|
1689
|
+
*
|
|
1690
|
+
* @return An ::EmberStatus value.
|
|
1691
|
+
* - ::EMBER_SUCCESS
|
|
1692
|
+
* - ::EMBER_NO_BUFFERS
|
|
1693
|
+
* - ::EMBER_NETWORK_DOWN
|
|
1694
|
+
* - ::EMBER_NETWORK_BUSY
|
|
1695
|
+
*/
|
|
1696
|
+
async emberIeeeAddressRequest(target, reportKids, childStartIndex, options) {
|
|
1697
|
+
this.zdoRequestBuffalo.setPosition(zdo_1.ZDO_MESSAGE_OVERHEAD);
|
|
1698
|
+
this.zdoRequestBuffalo.writeUInt16(target);
|
|
1699
|
+
this.zdoRequestBuffalo.writeUInt8(reportKids ? 1 : 0);
|
|
1700
|
+
this.zdoRequestBuffalo.writeUInt8(childStartIndex);
|
|
1701
|
+
debug(`~~~> [ZDO IEEE ADDRESS target=${target} reportKids=${reportKids} childStartIndex=${childStartIndex}]`);
|
|
1702
|
+
return this.sendZDORequestBuffer(target, zdo_1.IEEE_ADDRESS_REQUEST, options);
|
|
1703
|
+
}
|
|
1704
|
+
/**
|
|
1705
|
+
* ZDO
|
|
1706
|
+
* @param discoveryNodeId uint16_t
|
|
1707
|
+
* @param reportKids uint8_t
|
|
1708
|
+
* @param childStartIndex uint8_t
|
|
1709
|
+
* @param options
|
|
1710
|
+
* @param targetNodeIdOfRequest
|
|
1711
|
+
*/
|
|
1712
|
+
async emberIeeeAddressRequestToTarget(discoveryNodeId, reportKids, childStartIndex, options, targetNodeIdOfRequest) {
|
|
1713
|
+
this.zdoRequestBuffalo.setPosition(zdo_1.ZDO_MESSAGE_OVERHEAD);
|
|
1714
|
+
this.zdoRequestBuffalo.writeUInt16(discoveryNodeId);
|
|
1715
|
+
this.zdoRequestBuffalo.writeUInt8(reportKids ? 1 : 0);
|
|
1716
|
+
this.zdoRequestBuffalo.writeUInt8(childStartIndex);
|
|
1717
|
+
debug(`~~~> [ZDO IEEE ADDRESS targetNodeIdOfRequest=${targetNodeIdOfRequest} discoveryNodeId=${discoveryNodeId} `
|
|
1718
|
+
+ `reportKids=${reportKids} childStartIndex=${childStartIndex}]`);
|
|
1719
|
+
return this.sendZDORequestBuffer(targetNodeIdOfRequest, zdo_1.IEEE_ADDRESS_REQUEST, options);
|
|
1720
|
+
}
|
|
1721
|
+
/**
|
|
1722
|
+
* ZDO
|
|
1723
|
+
*
|
|
1724
|
+
* @param target uint16_t
|
|
1725
|
+
* @param clusterId uint16_t
|
|
1726
|
+
* @param options
|
|
1727
|
+
* @returns
|
|
1728
|
+
*/
|
|
1729
|
+
async emberSendZigDevRequestTarget(target, clusterId, options) {
|
|
1730
|
+
this.zdoRequestBuffalo.setPosition(zdo_1.ZDO_MESSAGE_OVERHEAD);
|
|
1731
|
+
this.zdoRequestBuffalo.writeUInt16(target);
|
|
1732
|
+
return this.sendZDORequestBuffer(target, clusterId, options);
|
|
1733
|
+
}
|
|
1734
|
+
/**
|
|
1735
|
+
* ZDO
|
|
1736
|
+
* @brief Request the specified node to send the simple descriptor for
|
|
1737
|
+
* the specified endpoint.
|
|
1738
|
+
* The simple descriptor contains information specific
|
|
1739
|
+
* to a single endpoint. It describes the application profile identifier,
|
|
1740
|
+
* application device identifier, application device version, application flags,
|
|
1741
|
+
* application input clusters and application output clusters. It is defined in
|
|
1742
|
+
* the ZigBee Application Framework Specification.
|
|
1743
|
+
*
|
|
1744
|
+
* @param target uint16_t The node of interest.
|
|
1745
|
+
* @param targetEndpoint uint8_t The endpoint on the target node whose simple
|
|
1746
|
+
* descriptor is desired.
|
|
1747
|
+
* @param options The options to use when sending the request. See
|
|
1748
|
+
* emberSendUnicast() for a description.
|
|
1749
|
+
*
|
|
1750
|
+
* @return An EmberStatus value. ::EMBER_SUCCESS, ::EMBER_NO_BUFFERS,
|
|
1751
|
+
* ::EMBER_NETWORK_DOWN or ::EMBER_NETWORK_BUSY.
|
|
1752
|
+
*/
|
|
1753
|
+
async emberSimpleDescriptorRequest(target, targetEndpoint, options) {
|
|
1754
|
+
this.zdoRequestBuffalo.setPosition(zdo_1.ZDO_MESSAGE_OVERHEAD);
|
|
1755
|
+
this.zdoRequestBuffalo.writeUInt16(target);
|
|
1756
|
+
this.zdoRequestBuffalo.writeUInt8(targetEndpoint);
|
|
1757
|
+
debug(`~~~> [ZDO SIMPLE DESCRIPTOR target=${target} targetEndpoint=${targetEndpoint}]`);
|
|
1758
|
+
return this.sendZDORequestBuffer(target, zdo_1.SIMPLE_DESCRIPTOR_REQUEST, options);
|
|
1759
|
+
}
|
|
1760
|
+
/**
|
|
1761
|
+
* ZDO
|
|
1762
|
+
* @brief Send a request to remove a binding entry with the specified
|
|
1763
|
+
* contents from the specified node.
|
|
1764
|
+
*
|
|
1765
|
+
* @param target The node on which the binding will be removed.
|
|
1766
|
+
* @param source The source EUI64 in the binding entry.
|
|
1767
|
+
* @param sourceEndpoint The source endpoint in the binding entry.
|
|
1768
|
+
* @param clusterId The cluster ID in the binding entry.
|
|
1769
|
+
* @param type The type of binding, either ::UNICAST_BINDING,
|
|
1770
|
+
* ::MULTICAST_BINDING, or ::UNICAST_MANY_TO_ONE_BINDING.
|
|
1771
|
+
* ::UNICAST_MANY_TO_ONE_BINDING is an Ember-specific extension
|
|
1772
|
+
* and should be used only when the target is an Ember device.
|
|
1773
|
+
* @param destination The destination EUI64 in the binding entry for the
|
|
1774
|
+
* ::UNICAST_BINDING or ::UNICAST_MANY_TO_ONE_BINDING.
|
|
1775
|
+
* @param groupAddress The group address for the ::MULTICAST_BINDING.
|
|
1776
|
+
* @param destinationEndpoint The destination endpoint in the binding entry for
|
|
1777
|
+
* the ::UNICAST_BINDING or ::UNICAST_MANY_TO_ONE_BINDING.
|
|
1778
|
+
* @param options The options to use when sending the request. See
|
|
1779
|
+
* emberSendUnicast() for a description.
|
|
1780
|
+
*
|
|
1781
|
+
* @return An ::EmberStatus value.
|
|
1782
|
+
* - ::EMBER_SUCCESS
|
|
1783
|
+
* - ::EMBER_NO_BUFFERS
|
|
1784
|
+
* _ ::EMBER_NETWORK_DOWN
|
|
1785
|
+
* - ::EMBER_NETWORK_BUSY
|
|
1786
|
+
* @param target
|
|
1787
|
+
* @param bindClusterId
|
|
1788
|
+
* @param source
|
|
1789
|
+
* @param sourceEndpoint uint8_t
|
|
1790
|
+
* @param clusterId uint16_t
|
|
1791
|
+
* @param type uint8_t
|
|
1792
|
+
* @param destination
|
|
1793
|
+
* @param groupAddress uint16_t
|
|
1794
|
+
* @param destinationEndpoint uint8_t
|
|
1795
|
+
* @param options
|
|
1796
|
+
*/
|
|
1797
|
+
async emberSendZigDevBindRequest(target, bindClusterId, source, sourceEndpoint, clusterId, type, destination, groupAddress, destinationEndpoint, options) {
|
|
1798
|
+
this.zdoRequestBuffalo.setPosition(zdo_1.ZDO_MESSAGE_OVERHEAD);
|
|
1799
|
+
this.zdoRequestBuffalo.writeIeeeAddr(source);
|
|
1800
|
+
this.zdoRequestBuffalo.writeUInt8(sourceEndpoint);
|
|
1801
|
+
this.zdoRequestBuffalo.writeUInt16(clusterId);
|
|
1802
|
+
this.zdoRequestBuffalo.writeUInt8(type);
|
|
1803
|
+
switch (type) {
|
|
1804
|
+
case zdo_1.UNICAST_BINDING:
|
|
1805
|
+
this.zdoRequestBuffalo.writeIeeeAddr(destination);
|
|
1806
|
+
this.zdoRequestBuffalo.writeUInt8(destinationEndpoint);
|
|
1807
|
+
break;
|
|
1808
|
+
case zdo_1.MULTICAST_BINDING:
|
|
1809
|
+
this.zdoRequestBuffalo.writeUInt16(groupAddress);
|
|
1810
|
+
break;
|
|
1811
|
+
default:
|
|
1812
|
+
return [enums_2.EmberStatus.ERR_FATAL, null, null];
|
|
1813
|
+
}
|
|
1814
|
+
return this.sendZDORequestBuffer(target, bindClusterId, options);
|
|
1815
|
+
}
|
|
1816
|
+
/**
|
|
1817
|
+
* ZDO
|
|
1818
|
+
* Send a request to create a binding entry with the specified
|
|
1819
|
+
* contents on the specified node.
|
|
1820
|
+
*
|
|
1821
|
+
* @param target The node on which the binding will be created.
|
|
1822
|
+
* @param source The source EUI64 in the binding entry.
|
|
1823
|
+
* @param sourceEndpoint The source endpoint in the binding entry.
|
|
1824
|
+
* @param clusterId The cluster ID in the binding entry.
|
|
1825
|
+
* @param type The type of binding, either ::UNICAST_BINDING,
|
|
1826
|
+
* ::MULTICAST_BINDING, or ::UNICAST_MANY_TO_ONE_BINDING.
|
|
1827
|
+
* ::UNICAST_MANY_TO_ONE_BINDING is an Ember-specific extension
|
|
1828
|
+
* and should be used only when the target is an Ember device.
|
|
1829
|
+
* @param destination The destination EUI64 in the binding entry for
|
|
1830
|
+
* ::UNICAST_BINDING or ::UNICAST_MANY_TO_ONE_BINDING.
|
|
1831
|
+
* @param groupAddress The group address for the ::MULTICAST_BINDING.
|
|
1832
|
+
* @param destinationEndpoint The destination endpoint in the binding entry for
|
|
1833
|
+
* the ::UNICAST_BINDING or ::UNICAST_MANY_TO_ONE_BINDING.
|
|
1834
|
+
* @param options The options to use when sending the request. See
|
|
1835
|
+
* emberSendUnicast() for a description.
|
|
1836
|
+
*
|
|
1837
|
+
* @return An EmberStatus value. ::EMBER_SUCCESS, ::EMBER_NO_BUFFERS,
|
|
1838
|
+
* ::EMBER_NETWORK_DOWN or ::EMBER_NETWORK_BUSY.
|
|
1839
|
+
*/
|
|
1840
|
+
async emberBindRequest(target, source, sourceEndpoint, clusterId, type, destination, groupAddress, destinationEndpoint, options) {
|
|
1841
|
+
debug(`~~~> [ZDO BIND target=${target} source=${source} sourceEndpoint=${sourceEndpoint} clusterId=${clusterId} type=${type} `
|
|
1842
|
+
+ `destination=${destination} groupAddress=${groupAddress} destinationEndpoint=${destinationEndpoint}]`);
|
|
1843
|
+
return this.emberSendZigDevBindRequest(target, zdo_1.BIND_REQUEST, source, sourceEndpoint, clusterId, type, destination, groupAddress, destinationEndpoint, options);
|
|
1844
|
+
}
|
|
1845
|
+
/**
|
|
1846
|
+
* ZDO
|
|
1847
|
+
* Send a request to remove a binding entry with the specified
|
|
1848
|
+
* contents from the specified node.
|
|
1849
|
+
*
|
|
1850
|
+
* @param target The node on which the binding will be removed.
|
|
1851
|
+
* @param source The source EUI64 in the binding entry.
|
|
1852
|
+
* @param sourceEndpoint uint8_t The source endpoint in the binding entry.
|
|
1853
|
+
* @param clusterId uint16_t The cluster ID in the binding entry.
|
|
1854
|
+
* @param type uint8_t The type of binding, either ::UNICAST_BINDING,
|
|
1855
|
+
* ::MULTICAST_BINDING, or ::UNICAST_MANY_TO_ONE_BINDING.
|
|
1856
|
+
* ::UNICAST_MANY_TO_ONE_BINDING is an Ember-specific extension
|
|
1857
|
+
* and should be used only when the target is an Ember device.
|
|
1858
|
+
* @param destination The destination EUI64 in the binding entry for the
|
|
1859
|
+
* ::UNICAST_BINDING or ::UNICAST_MANY_TO_ONE_BINDING.
|
|
1860
|
+
* @param groupAddress The group address for the ::MULTICAST_BINDING.
|
|
1861
|
+
* @param destinationEndpoint uint8_t The destination endpoint in the binding entry for
|
|
1862
|
+
* the ::UNICAST_BINDING or ::UNICAST_MANY_TO_ONE_BINDING.
|
|
1863
|
+
* @param options The options to use when sending the request. See
|
|
1864
|
+
* emberSendUnicast() for a description.
|
|
1865
|
+
*
|
|
1866
|
+
* @return An ::EmberStatus value.
|
|
1867
|
+
* - ::EMBER_SUCCESS
|
|
1868
|
+
* - ::EMBER_NO_BUFFERS
|
|
1869
|
+
* _ ::EMBER_NETWORK_DOWN
|
|
1870
|
+
* - ::EMBER_NETWORK_BUSY
|
|
1871
|
+
*/
|
|
1872
|
+
async emberUnbindRequest(target, source, sourceEndpoint, clusterId, type, destination, groupAddress, destinationEndpoint, options) {
|
|
1873
|
+
debug(`~~~> [ZDO UNBIND target=${target} source=${source} sourceEndpoint=${sourceEndpoint} clusterId=${clusterId} type=${type} `
|
|
1874
|
+
+ `destination=${destination} groupAddress=${groupAddress} destinationEndpoint=${destinationEndpoint}]`);
|
|
1875
|
+
return this.emberSendZigDevBindRequest(target, zdo_1.UNBIND_REQUEST, source, sourceEndpoint, clusterId, type, destination, groupAddress, destinationEndpoint, options);
|
|
1876
|
+
}
|
|
1877
|
+
/**
|
|
1878
|
+
* ZDO
|
|
1879
|
+
* Request the specified node to send a list of its active
|
|
1880
|
+
* endpoints. An active endpoint is one for which a simple descriptor is
|
|
1881
|
+
* available.
|
|
1882
|
+
*
|
|
1883
|
+
* @param target The node whose active endpoints are desired.
|
|
1884
|
+
* @param options The options to use when sending the request. See
|
|
1885
|
+
* emberSendUnicast() for a description.
|
|
1886
|
+
*
|
|
1887
|
+
* @return An EmberStatus value. ::EMBER_SUCCESS, ::EMBER_NO_BUFFERS,
|
|
1888
|
+
* ::EMBER_NETWORK_DOWN or ::EMBER_NETWORK_BUSY.
|
|
1889
|
+
*/
|
|
1890
|
+
async emberActiveEndpointsRequest(target, options) {
|
|
1891
|
+
debug(`~~~> [ZDO ACTIVE ENDPOINTS target=${target}]`);
|
|
1892
|
+
return this.emberSendZigDevRequestTarget(target, zdo_1.ACTIVE_ENDPOINTS_REQUEST, options);
|
|
1893
|
+
}
|
|
1894
|
+
/**
|
|
1895
|
+
* ZDO
|
|
1896
|
+
* Request the specified node to send its power descriptor.
|
|
1897
|
+
* The power descriptor gives a dynamic indication of the power
|
|
1898
|
+
* status of the node. It describes current power mode,
|
|
1899
|
+
* available power sources, current power source and
|
|
1900
|
+
* current power source level. It is defined in the ZigBee
|
|
1901
|
+
* Application Framework Specification.
|
|
1902
|
+
*
|
|
1903
|
+
* @param target The node whose power descriptor is desired.
|
|
1904
|
+
* @param options The options to use when sending the request. See
|
|
1905
|
+
* emberSendUnicast() for a description.
|
|
1906
|
+
*
|
|
1907
|
+
* @return An EmberStatus value. ::EMBER_SUCCESS, ::EMBER_NO_BUFFERS,
|
|
1908
|
+
* ::EMBER_NETWORK_DOWN or ::EMBER_NETWORK_BUSY.
|
|
1909
|
+
*/
|
|
1910
|
+
async emberPowerDescriptorRequest(target, options) {
|
|
1911
|
+
debug(`~~~> [ZDO POWER DESCRIPTOR target=${target}]`);
|
|
1912
|
+
return this.emberSendZigDevRequestTarget(target, zdo_1.POWER_DESCRIPTOR_REQUEST, options);
|
|
1913
|
+
}
|
|
1914
|
+
/**
|
|
1915
|
+
* ZDO
|
|
1916
|
+
* Request the specified node to send its node descriptor.
|
|
1917
|
+
* The node descriptor contains information about the capabilities of the ZigBee
|
|
1918
|
+
* node. It describes logical type, APS flags, frequency band, MAC capabilities
|
|
1919
|
+
* flags, manufacturer code and maximum buffer size. It is defined in the ZigBee
|
|
1920
|
+
* Application Framework Specification.
|
|
1921
|
+
*
|
|
1922
|
+
* @param target The node whose node descriptor is desired.
|
|
1923
|
+
* @param options The options to use when sending the request. See
|
|
1924
|
+
* emberSendUnicast() for a description.
|
|
1925
|
+
*
|
|
1926
|
+
* @return An ::EmberStatus value. ::EMBER_SUCCESS, ::EMBER_NO_BUFFERS,
|
|
1927
|
+
* ::EMBER_NETWORK_DOWN or ::EMBER_NETWORK_BUSY.
|
|
1928
|
+
*/
|
|
1929
|
+
async emberNodeDescriptorRequest(target, options) {
|
|
1930
|
+
debug(`~~~> [ZDO NODE DESCRIPTOR target=${target}]`);
|
|
1931
|
+
return this.emberSendZigDevRequestTarget(target, zdo_1.NODE_DESCRIPTOR_REQUEST, options);
|
|
1932
|
+
}
|
|
1933
|
+
/**
|
|
1934
|
+
* ZDO
|
|
1935
|
+
* Request the specified node to send its LQI (neighbor) table.
|
|
1936
|
+
* The response gives PAN ID, EUI64, node ID and cost for each neighbor. The
|
|
1937
|
+
* EUI64 is only available if security is enabled. The other fields in the
|
|
1938
|
+
* response are set to zero. The response format is defined in the ZigBee Device
|
|
1939
|
+
* Profile Specification.
|
|
1940
|
+
*
|
|
1941
|
+
* @param target The node whose LQI table is desired.
|
|
1942
|
+
* @param startIndex uint8_t The index of the first neighbor to include in the
|
|
1943
|
+
* response.
|
|
1944
|
+
* @param options The options to use when sending the request. See
|
|
1945
|
+
* emberSendUnicast() for a description.
|
|
1946
|
+
*
|
|
1947
|
+
* @return An EmberStatus value. ::EMBER_SUCCESS, ::EMBER_NO_BUFFERS,
|
|
1948
|
+
* ::EMBER_NETWORK_DOWN or ::EMBER_NETWORK_BUSY.
|
|
1949
|
+
*/
|
|
1950
|
+
async emberLqiTableRequest(target, startIndex, options) {
|
|
1951
|
+
debug(`~~~> [ZDO LQI TABLE target=${target} startIndex=${startIndex}]`);
|
|
1952
|
+
return this.emberTableRequest(zdo_1.LQI_TABLE_REQUEST, target, startIndex, options);
|
|
1953
|
+
}
|
|
1954
|
+
/**
|
|
1955
|
+
* ZDO
|
|
1956
|
+
* Request the specified node to send its routing table.
|
|
1957
|
+
* The response gives destination node ID, status and many-to-one flags,
|
|
1958
|
+
* and the next hop node ID.
|
|
1959
|
+
* The response format is defined in the ZigBee Device
|
|
1960
|
+
* Profile Specification.
|
|
1961
|
+
*
|
|
1962
|
+
* @param target The node whose routing table is desired.
|
|
1963
|
+
* @param startIndex uint8_t The index of the first route entry to include in the
|
|
1964
|
+
* response.
|
|
1965
|
+
* @param options The options to use when sending the request. See
|
|
1966
|
+
* emberSendUnicast() for a description.
|
|
1967
|
+
*
|
|
1968
|
+
* @return An EmberStatus value. ::EMBER_SUCCESS, ::EMBER_NO_BUFFERS,
|
|
1969
|
+
* ::EMBER_NETWORK_DOWN or ::EMBER_NETWORK_BUSY.
|
|
1970
|
+
*/
|
|
1971
|
+
async emberRoutingTableRequest(target, startIndex, options) {
|
|
1972
|
+
debug(`~~~> [ZDO ROUTING TABLE target=${target} startIndex=${startIndex}]`);
|
|
1973
|
+
return this.emberTableRequest(zdo_1.ROUTING_TABLE_REQUEST, target, startIndex, options);
|
|
1974
|
+
}
|
|
1975
|
+
/**
|
|
1976
|
+
* ZDO
|
|
1977
|
+
* Request the specified node to send its nonvolatile bindings.
|
|
1978
|
+
* The response gives source address, source endpoint, cluster ID, destination
|
|
1979
|
+
* address and destination endpoint for each binding entry. The response format
|
|
1980
|
+
* is defined in the ZigBee Device Profile Specification.
|
|
1981
|
+
* Note that bindings that have the Ember-specific ::UNICAST_MANY_TO_ONE_BINDING
|
|
1982
|
+
* type are reported as having the standard ::UNICAST_BINDING type.
|
|
1983
|
+
*
|
|
1984
|
+
* @param target The node whose binding table is desired.
|
|
1985
|
+
* @param startIndex uint8_t The index of the first binding entry to include in the
|
|
1986
|
+
* response.
|
|
1987
|
+
* @param options The options to use when sending the request. See
|
|
1988
|
+
* emberSendUnicast() for a description.
|
|
1989
|
+
*
|
|
1990
|
+
* @return An EmberStatus value. ::EMBER_SUCCESS, ::EMBER_NO_BUFFERS,
|
|
1991
|
+
* ::EMBER_NETWORK_DOWN or ::EMBER_NETWORK_BUSY.
|
|
1992
|
+
*/
|
|
1993
|
+
async emberBindingTableRequest(target, startIndex, options) {
|
|
1994
|
+
debug(`~~~> [ZDO BINDING TABLE target=${target} startIndex=${startIndex}]`);
|
|
1995
|
+
return this.emberTableRequest(zdo_1.BINDING_TABLE_REQUEST, target, startIndex, options);
|
|
1996
|
+
}
|
|
1997
|
+
/**
|
|
1998
|
+
* ZDO
|
|
1999
|
+
*
|
|
2000
|
+
* @param clusterId uint16_t
|
|
2001
|
+
* @param target
|
|
2002
|
+
* @param startIndex uint8_t
|
|
2003
|
+
* @param options
|
|
2004
|
+
* @returns
|
|
2005
|
+
*/
|
|
2006
|
+
async emberTableRequest(clusterId, target, startIndex, options) {
|
|
2007
|
+
this.zdoRequestBuffalo.setPosition(zdo_1.ZDO_MESSAGE_OVERHEAD);
|
|
2008
|
+
this.zdoRequestBuffalo.writeUInt8(startIndex);
|
|
2009
|
+
return this.sendZDORequestBuffer(target, clusterId, options);
|
|
2010
|
+
}
|
|
2011
|
+
/**
|
|
2012
|
+
* ZDO
|
|
2013
|
+
* Request the specified node to remove the specified device from
|
|
2014
|
+
* the network. The device to be removed must be the node to which the request
|
|
2015
|
+
* is sent or one of its children.
|
|
2016
|
+
*
|
|
2017
|
+
* @param target The node which will remove the device.
|
|
2018
|
+
* @param deviceAddress All zeros if the target is to remove itself from
|
|
2019
|
+
* the network or the EUI64 of a child of the target device to remove
|
|
2020
|
+
* that child.
|
|
2021
|
+
* @param leaveRequestFlags uint8_t A bitmask of leave options.
|
|
2022
|
+
* Include ::AND_REJOIN if the target is to rejoin the network immediately after leaving.
|
|
2023
|
+
* @param options The options to use when sending the request. See
|
|
2024
|
+
* emberSendUnicast() for a description.
|
|
2025
|
+
*
|
|
2026
|
+
* @return An EmberStatus value. ::EMBER_SUCCESS, ::EMBER_NO_BUFFERS,
|
|
2027
|
+
* ::EMBER_NETWORK_DOWN or ::EMBER_NETWORK_BUSY.
|
|
2028
|
+
*/
|
|
2029
|
+
async emberLeaveRequest(target, deviceAddress, leaveRequestFlags, options) {
|
|
2030
|
+
this.zdoRequestBuffalo.setPosition(zdo_1.ZDO_MESSAGE_OVERHEAD);
|
|
2031
|
+
this.zdoRequestBuffalo.writeIeeeAddr(deviceAddress);
|
|
2032
|
+
this.zdoRequestBuffalo.writeUInt8(leaveRequestFlags);
|
|
2033
|
+
debug(`~~~> [ZDO LEAVE target=${target} deviceAddress=${deviceAddress} leaveRequestFlags=${leaveRequestFlags}]`);
|
|
2034
|
+
return this.sendZDORequestBuffer(target, zdo_1.LEAVE_REQUEST, options);
|
|
2035
|
+
}
|
|
2036
|
+
/**
|
|
2037
|
+
* ZDO
|
|
2038
|
+
* Request the specified node to allow or disallow association.
|
|
2039
|
+
*
|
|
2040
|
+
* @param target The node which will allow or disallow association. The request
|
|
2041
|
+
* can be broadcast by using a broadcast address (0xFFFC/0xFFFD/0xFFFF). No
|
|
2042
|
+
* response is sent if the request is broadcast.
|
|
2043
|
+
* @param duration uint8_t A value of 0x00 disables joining. A value of 0xFF enables
|
|
2044
|
+
* joining. Any other value enables joining for that number of seconds.
|
|
2045
|
+
* @param authentication uint8_t Controls Trust Center authentication behavior.
|
|
2046
|
+
* @param options The options to use when sending the request. See
|
|
2047
|
+
* emberSendUnicast() for a description. This parameter is ignored if the target
|
|
2048
|
+
* is a broadcast address.
|
|
2049
|
+
*
|
|
2050
|
+
* @return An EmberStatus value. ::EMBER_SUCCESS, ::EMBER_NO_BUFFERS,
|
|
2051
|
+
* ::EMBER_NETWORK_DOWN or ::EMBER_NETWORK_BUSY.
|
|
2052
|
+
*/
|
|
2053
|
+
async emberPermitJoiningRequest(target, duration, authentication, options) {
|
|
2054
|
+
this.zdoRequestBuffalo.setPosition(zdo_1.ZDO_MESSAGE_OVERHEAD);
|
|
2055
|
+
this.zdoRequestBuffalo.writeUInt8(duration);
|
|
2056
|
+
this.zdoRequestBuffalo.writeUInt8(authentication);
|
|
2057
|
+
debug(`~~~> [ZDO PERMIT JOINING target=${target} duration=${duration} authentication=${authentication}]`);
|
|
2058
|
+
return this.sendZDORequestBuffer(target, zdo_1.PERMIT_JOINING_REQUEST, options);
|
|
2059
|
+
}
|
|
2060
|
+
//---- END Ember ZDO
|
|
2061
|
+
//-- START Adapter implementation
|
|
2062
|
+
static async isValidPath(path) {
|
|
2063
|
+
// For TCP paths we cannot get device information, therefore we cannot validate it.
|
|
2064
|
+
if (socketPortUtils_1.default.isTcpPath(path)) {
|
|
2065
|
+
return false;
|
|
2066
|
+
}
|
|
2067
|
+
try {
|
|
2068
|
+
return serialPortUtils_1.default.is((0, utils_1.RealpathSync)(path), autoDetectDefinitions);
|
|
2069
|
+
}
|
|
2070
|
+
catch (error) {
|
|
2071
|
+
debug(`Failed to determine if path is valid: '${error}'`);
|
|
2072
|
+
return false;
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
static async autoDetectPath() {
|
|
2076
|
+
const paths = await serialPortUtils_1.default.find(autoDetectDefinitions);
|
|
2077
|
+
paths.sort((a, b) => (a < b) ? -1 : 1);
|
|
2078
|
+
return paths.length > 0 ? paths[0] : null;
|
|
2079
|
+
}
|
|
2080
|
+
async start() {
|
|
2081
|
+
console.log(`======== Ember Adapter Starting ========`);
|
|
2082
|
+
this.initVariables();
|
|
2083
|
+
debug(`Starting EZSP with stack configuration: "${this.stackConfig}".`);
|
|
2084
|
+
const result = await this.initEzsp();
|
|
2085
|
+
return result;
|
|
2086
|
+
}
|
|
2087
|
+
async stop() {
|
|
2088
|
+
await this.ezsp.stop();
|
|
2089
|
+
this.initVariables();
|
|
2090
|
+
console.log(`======== Ember Adapter Stopped ========`);
|
|
2091
|
+
}
|
|
2092
|
+
// queued, non-InterPAN
|
|
2093
|
+
async getCoordinator() {
|
|
2094
|
+
return new Promise((resolve, reject) => {
|
|
2095
|
+
this.requestQueue.enqueue(async () => {
|
|
2096
|
+
this.checkInterpanLock();
|
|
2097
|
+
// in all likelihood this will be retrieved from cache
|
|
2098
|
+
const ieeeAddr = (await this.emberGetEui64());
|
|
2099
|
+
resolve({
|
|
2100
|
+
ieeeAddr,
|
|
2101
|
+
networkAddress: consts_2.ZIGBEE_COORDINATOR_ADDRESS,
|
|
2102
|
+
manufacturerID: consts_2.MANUFACTURER_CODE,
|
|
2103
|
+
endpoints: endpoints_1.FIXED_ENDPOINTS.map((ep) => {
|
|
2104
|
+
return {
|
|
2105
|
+
profileID: ep.profileId,
|
|
2106
|
+
ID: ep.endpoint,
|
|
2107
|
+
deviceID: ep.deviceId,
|
|
2108
|
+
inputClusters: ep.inClusterList,
|
|
2109
|
+
outputClusters: ep.outClusterList,
|
|
2110
|
+
};
|
|
2111
|
+
}),
|
|
2112
|
+
});
|
|
2113
|
+
return enums_2.EmberStatus.SUCCESS;
|
|
2114
|
+
}, reject);
|
|
2115
|
+
});
|
|
2116
|
+
}
|
|
2117
|
+
async getCoordinatorVersion() {
|
|
2118
|
+
return { type: `EZSP v${this.version.ezsp}`, meta: this.version };
|
|
2119
|
+
}
|
|
2120
|
+
// queued
|
|
2121
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2122
|
+
async reset(type) {
|
|
2123
|
+
return Promise.reject(new Error("Not supported"));
|
|
2124
|
+
// NOTE: although this function is legacy atm, a couple of new untested EZSP functions that could also prove useful:
|
|
2125
|
+
// this.ezsp.ezspTokenFactoryReset(true/*excludeOutgoingFC*/, true/*excludeBootCounter*/);
|
|
2126
|
+
// this.ezsp.ezspResetNode()
|
|
2127
|
+
}
|
|
2128
|
+
async supportsBackup() {
|
|
2129
|
+
return true;
|
|
2130
|
+
}
|
|
2131
|
+
// queued
|
|
2132
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2133
|
+
async backup(ieeeAddressesInDatabase) {
|
|
2134
|
+
return new Promise((resolve, reject) => {
|
|
2135
|
+
this.requestQueue.enqueue(async () => {
|
|
2136
|
+
// grab fresh version here, bypass cache
|
|
2137
|
+
const [netStatus, , netParams] = (await this.ezsp.ezspGetNetworkParameters());
|
|
2138
|
+
if (netStatus !== enums_2.EmberStatus.SUCCESS) {
|
|
2139
|
+
console.error(`[BACKUP] Failed to get network parameters.`);
|
|
2140
|
+
return netStatus;
|
|
2141
|
+
}
|
|
2142
|
+
// update cache
|
|
2143
|
+
this.networkCache.parameters = netParams;
|
|
2144
|
+
this.networkCache.eui64 = (await this.ezsp.ezspGetEui64());
|
|
2145
|
+
const [netKeyStatus, netKeyInfo] = (await this.ezsp.ezspGetNetworkKeyInfo());
|
|
2146
|
+
if (netKeyStatus !== enums_2.SLStatus.OK) {
|
|
2147
|
+
console.error(`[BACKUP] Failed to get network keys info.`);
|
|
2148
|
+
return ((netKeyStatus === enums_2.SLStatus.BUSY) || (netKeyStatus === enums_2.SLStatus.NOT_READY))
|
|
2149
|
+
? enums_2.EmberStatus.NETWORK_BUSY : enums_2.EmberStatus.ERR_FATAL; // allow retry on statuses that should be temporary
|
|
2150
|
+
}
|
|
2151
|
+
if (!netKeyInfo.networkKeySet) {
|
|
2152
|
+
throw new Error(`[BACKUP] No network key set.`);
|
|
2153
|
+
}
|
|
2154
|
+
let keyList = [];
|
|
2155
|
+
if (STACK_CONFIGS[this.stackConfig].KEY_TABLE_SIZE) {
|
|
2156
|
+
keyList = (await this.exportLinkKeys());
|
|
2157
|
+
}
|
|
2158
|
+
// XXX: this only makes sense on stop (if that), not hourly/on start, plus network needs to be at near-standstill @see AN1387
|
|
2159
|
+
// const tokensBuf = (await EmberTokensManager.saveTokens(
|
|
2160
|
+
// this.ezsp,
|
|
2161
|
+
// Buffer.from(this.networkCache.eui64.substring(2/*0x*/), 'hex').reverse()
|
|
2162
|
+
// ));
|
|
2163
|
+
// console.log(tokensBuf.toString('hex'));
|
|
2164
|
+
let context = (0, initters_1.initSecurityManagerContext)();
|
|
2165
|
+
context.coreKeyType = enums_2.SecManKeyType.TC_LINK;
|
|
2166
|
+
const [tcLinkKey, tclkStatus] = (await this.ezsp.ezspExportKey(context));
|
|
2167
|
+
if (tclkStatus !== enums_2.SLStatus.OK) {
|
|
2168
|
+
throw new Error(`[BACKUP] Failed to export TC Link Key with status=${enums_2.SLStatus[tclkStatus]}.`);
|
|
2169
|
+
}
|
|
2170
|
+
context = (0, initters_1.initSecurityManagerContext)(); // make sure it's back to zeroes
|
|
2171
|
+
context.coreKeyType = enums_2.SecManKeyType.NETWORK;
|
|
2172
|
+
context.keyIndex = 0;
|
|
2173
|
+
const [networkKey, nkStatus] = (await this.ezsp.ezspExportKey(context));
|
|
2174
|
+
if (nkStatus !== enums_2.SLStatus.OK) {
|
|
2175
|
+
throw new Error(`[BACKUP] Failed to export Network Key with status=${enums_2.SLStatus[nkStatus]}.`);
|
|
2176
|
+
}
|
|
2177
|
+
const zbChannels = Array.from(Array(consts_2.EMBER_NUM_802_15_4_CHANNELS), (e, i) => i + consts_2.EMBER_MIN_802_15_4_CHANNEL_NUMBER);
|
|
2178
|
+
resolve({
|
|
2179
|
+
networkOptions: {
|
|
2180
|
+
panId: netParams.panId, // uint16_t
|
|
2181
|
+
extendedPanId: Buffer.from(netParams.extendedPanId),
|
|
2182
|
+
channelList: zbChannels.map((c) => ((2 ** c) & netParams.channels) ? c : null).filter((x) => x),
|
|
2183
|
+
networkKey: networkKey.contents,
|
|
2184
|
+
networkKeyDistribute: false,
|
|
2185
|
+
},
|
|
2186
|
+
logicalChannel: netParams.radioChannel,
|
|
2187
|
+
networkKeyInfo: {
|
|
2188
|
+
sequenceNumber: netKeyInfo.networkKeySequenceNumber,
|
|
2189
|
+
frameCounter: netKeyInfo.networkKeyFrameCounter,
|
|
2190
|
+
},
|
|
2191
|
+
securityLevel: STACK_CONFIGS[this.stackConfig].SECURITY_LEVEL,
|
|
2192
|
+
networkUpdateId: netParams.nwkUpdateId,
|
|
2193
|
+
coordinatorIeeeAddress: Buffer.from(this.networkCache.eui64.substring(2) /*take out 0x*/, 'hex').reverse(),
|
|
2194
|
+
devices: keyList.map((key) => ({
|
|
2195
|
+
networkAddress: null, // not used for restore, no reason to make NCP calls for nothing
|
|
2196
|
+
ieeeAddress: Buffer.from(key.deviceEui64.substring(2) /*take out 0x*/, 'hex').reverse(),
|
|
2197
|
+
isDirectChild: false, // not used
|
|
2198
|
+
linkKey: {
|
|
2199
|
+
key: key.key.contents,
|
|
2200
|
+
rxCounter: key.incomingFrameCounter,
|
|
2201
|
+
txCounter: key.outgoingFrameCounter,
|
|
2202
|
+
},
|
|
2203
|
+
})),
|
|
2204
|
+
ezsp: {
|
|
2205
|
+
version: this.version.ezsp,
|
|
2206
|
+
hashed_tclk: tcLinkKey.contents,
|
|
2207
|
+
// tokens: tokensBuf.toString('hex'),
|
|
2208
|
+
// altNetworkKey: altNetworkKey.contents,
|
|
2209
|
+
}
|
|
2210
|
+
});
|
|
2211
|
+
return enums_2.EmberStatus.SUCCESS;
|
|
2212
|
+
}, reject, true);
|
|
2213
|
+
});
|
|
2214
|
+
}
|
|
2215
|
+
// queued, non-InterPAN
|
|
2216
|
+
async getNetworkParameters() {
|
|
2217
|
+
return new Promise((resolve, reject) => {
|
|
2218
|
+
this.requestQueue.enqueue(async () => {
|
|
2219
|
+
this.checkInterpanLock();
|
|
2220
|
+
// first call will cache for the others, but in all likelihood, it will all be from freshly cached after init
|
|
2221
|
+
// since Controller caches this also.
|
|
2222
|
+
const panID = (await this.emberGetPanId());
|
|
2223
|
+
const extendedPanID = (await this.emberGetExtendedPanId());
|
|
2224
|
+
const channel = (await this.emberGetRadioChannel());
|
|
2225
|
+
resolve({
|
|
2226
|
+
panID: panID,
|
|
2227
|
+
extendedPanID: parseInt(Buffer.from(extendedPanID).toString('hex'), 16),
|
|
2228
|
+
channel: channel,
|
|
2229
|
+
});
|
|
2230
|
+
return enums_2.EmberStatus.SUCCESS;
|
|
2231
|
+
}, reject);
|
|
2232
|
+
});
|
|
2233
|
+
}
|
|
2234
|
+
// queued
|
|
2235
|
+
async setTransmitPower(value) {
|
|
2236
|
+
if (typeof value !== 'number') {
|
|
2237
|
+
console.error(`Tried to set transmit power to non-number. Value ${value} of type ${typeof value}.`);
|
|
2238
|
+
return;
|
|
2239
|
+
}
|
|
2240
|
+
return new Promise((resolve, reject) => {
|
|
2241
|
+
this.requestQueue.enqueue(async () => {
|
|
2242
|
+
const status = await this.ezsp.ezspSetRadioPower(value);
|
|
2243
|
+
if (status !== enums_2.EmberStatus.SUCCESS) {
|
|
2244
|
+
console.error(`Failed to set transmit power to ${value} status=${enums_2.EmberStatus[status]}.`);
|
|
2245
|
+
return status;
|
|
2246
|
+
}
|
|
2247
|
+
resolve();
|
|
2248
|
+
return enums_2.EmberStatus.SUCCESS;
|
|
2249
|
+
}, reject);
|
|
2250
|
+
});
|
|
2251
|
+
}
|
|
2252
|
+
// queued
|
|
2253
|
+
async addInstallCode(ieeeAddress, key) {
|
|
2254
|
+
if (!key) {
|
|
2255
|
+
throw new Error(`[ADD INSTALL CODE] Failed for "${ieeeAddress}"; no code given.`);
|
|
2256
|
+
}
|
|
2257
|
+
let validInstallCodeSize = false;
|
|
2258
|
+
for (const validCodeSize of consts_2.EMBER_INSTALL_CODE_SIZES) {
|
|
2259
|
+
if (key.length === validCodeSize) {
|
|
2260
|
+
validInstallCodeSize = true;
|
|
2261
|
+
break;
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
if (!validInstallCodeSize) {
|
|
2265
|
+
throw new Error(`[ADD INSTALL CODE] Failed for "${ieeeAddress}"; invalid code size.`);
|
|
2266
|
+
}
|
|
2267
|
+
// Reverse the bits in a byte
|
|
2268
|
+
const reverse = (b) => {
|
|
2269
|
+
return ((b * 0x0802 & 0x22110) | (b * 0x8020 & 0x88440)) * 0x10101 >> 16;
|
|
2270
|
+
};
|
|
2271
|
+
let crc = 0xFFFF; // uint16_t
|
|
2272
|
+
// Compute the CRC and verify that it matches.
|
|
2273
|
+
// The bit reversals, byte swap, and ones' complement are due to differences between halCommonCrc16 and the Smart Energy version.
|
|
2274
|
+
for (let index = 0; index < (key.length - consts_2.EMBER_INSTALL_CODE_CRC_SIZE); index++) {
|
|
2275
|
+
crc = (0, math_1.halCommonCrc16)(reverse(key[index]), crc);
|
|
2276
|
+
}
|
|
2277
|
+
crc = ~(0, math_1.highLowToInt)(reverse((0, math_1.lowByte)(crc)), reverse((0, math_1.highByte)(crc)));
|
|
2278
|
+
if (key[key.length - consts_2.EMBER_INSTALL_CODE_CRC_SIZE] !== (0, math_1.lowByte)(crc) || key[key.length - consts_2.EMBER_INSTALL_CODE_CRC_SIZE + 1] !== (0, math_1.highByte)(crc)) {
|
|
2279
|
+
throw new Error(`[ADD INSTALL CODE] Failed for "${ieeeAddress}"; invalid code CRC.`);
|
|
2280
|
+
}
|
|
2281
|
+
return new Promise((resolve, reject) => {
|
|
2282
|
+
this.requestQueue.enqueue(async () => {
|
|
2283
|
+
// Compute the key from the install code and CRC.
|
|
2284
|
+
const [aesStatus, keyContents] = (await this.emberAesHashSimple(key));
|
|
2285
|
+
if (aesStatus !== enums_2.EmberStatus.SUCCESS) {
|
|
2286
|
+
console.error(`[ADD INSTALL CODE] Failed AES hash for "${ieeeAddress}" with status=${enums_2.EmberStatus[aesStatus]}.`);
|
|
2287
|
+
return aesStatus;
|
|
2288
|
+
}
|
|
2289
|
+
// Add the key to the transient key table.
|
|
2290
|
+
// This will be used while the DUT joins.
|
|
2291
|
+
const impStatus = (await this.ezsp.ezspImportTransientKey(ieeeAddress, { contents: keyContents }, enums_2.SecManFlag.NONE));
|
|
2292
|
+
if (impStatus == enums_2.SLStatus.OK) {
|
|
2293
|
+
debug(`[ADD INSTALL CODE] Success for "${ieeeAddress}".`);
|
|
2294
|
+
}
|
|
2295
|
+
else {
|
|
2296
|
+
console.error(`[ADD INSTALL CODE] Failed for "${ieeeAddress}" with status=${enums_2.SLStatus[impStatus]}.`);
|
|
2297
|
+
return enums_2.EmberStatus.ERR_FATAL;
|
|
2298
|
+
}
|
|
2299
|
+
resolve();
|
|
2300
|
+
return enums_2.EmberStatus.SUCCESS;
|
|
2301
|
+
}, reject);
|
|
2302
|
+
});
|
|
2303
|
+
}
|
|
2304
|
+
/** WARNING: Adapter impl. Starts timer immediately upon returning */
|
|
2305
|
+
waitFor(networkAddress, endpoint, frameType, direction, transactionSequenceNumber, clusterID, commandIdentifier, timeout) {
|
|
2306
|
+
const waiter = this.oneWaitress.waitFor({
|
|
2307
|
+
target: networkAddress,
|
|
2308
|
+
apsFrame: {
|
|
2309
|
+
clusterId: clusterID,
|
|
2310
|
+
profileId: consts_2.HA_PROFILE_ID, // XXX: ok? only used by OTA upstream
|
|
2311
|
+
sequence: 0, // set by stack
|
|
2312
|
+
sourceEndpoint: endpoint,
|
|
2313
|
+
destinationEndpoint: 0,
|
|
2314
|
+
groupId: 0,
|
|
2315
|
+
options: enums_2.EmberApsOption.NONE,
|
|
2316
|
+
},
|
|
2317
|
+
zclSequence: transactionSequenceNumber,
|
|
2318
|
+
}, timeout || DEFAULT_ZCL_REQUEST_TIMEOUT * 3); // XXX: since this is used by OTA..?
|
|
2319
|
+
return {
|
|
2320
|
+
cancel: () => this.oneWaitress.remove(waiter.id),
|
|
2321
|
+
promise: waiter.start().promise,
|
|
2322
|
+
};
|
|
2323
|
+
}
|
|
2324
|
+
//---- ZDO
|
|
2325
|
+
// queued, non-InterPAN
|
|
2326
|
+
async permitJoin(seconds, networkAddress) {
|
|
2327
|
+
const preJoining = async () => {
|
|
2328
|
+
if (seconds) {
|
|
2329
|
+
const plaintextKey = { contents: Buffer.from(consts_2.ZIGBEE_PROFILE_INTEROPERABILITY_LINK_KEY) };
|
|
2330
|
+
const impKeyStatus = (await this.ezsp.ezspImportTransientKey(consts_2.BLANK_EUI64, plaintextKey, enums_2.SecManFlag.NONE));
|
|
2331
|
+
debug(`[ZDO] Pre joining import transient key status=${enums_2.SLStatus[impKeyStatus]}.`);
|
|
2332
|
+
return impKeyStatus === enums_2.SLStatus.OK ? enums_2.EmberStatus.SUCCESS : enums_2.EmberStatus.ERR_FATAL;
|
|
2333
|
+
}
|
|
2334
|
+
else {
|
|
2335
|
+
await this.ezsp.ezspClearTransientLinkKeys();
|
|
2336
|
+
const setJPstatus = (await this.emberSetJoinPolicy(enums_2.EmberJoinDecision.ALLOW_REJOINS_ONLY));
|
|
2337
|
+
if (setJPstatus !== enums_2.EzspStatus.SUCCESS) {
|
|
2338
|
+
console.error(`[ZDO] Failed set join policy for with status=${enums_2.EzspStatus[setJPstatus]}.`);
|
|
2339
|
+
return enums_2.EmberStatus.ERR_FATAL;
|
|
2340
|
+
}
|
|
2341
|
+
return enums_2.EmberStatus.SUCCESS;
|
|
2342
|
+
}
|
|
2343
|
+
};
|
|
2344
|
+
// NOTE: can't ZDO PJ on coordinator, so if network address is null or zero (coordinator), using local permit join
|
|
2345
|
+
if (networkAddress) {
|
|
2346
|
+
return new Promise((resolve, reject) => {
|
|
2347
|
+
this.requestQueue.enqueue(async () => {
|
|
2348
|
+
this.checkInterpanLock();
|
|
2349
|
+
const pjStatus = (await preJoining());
|
|
2350
|
+
if (pjStatus !== enums_2.EmberStatus.SUCCESS) {
|
|
2351
|
+
console.error(`[ZDO] Failed pre joining request for "${networkAddress}" with status=${enums_2.EmberStatus[pjStatus]}.`);
|
|
2352
|
+
return pjStatus;
|
|
2353
|
+
}
|
|
2354
|
+
// `authentication`: TC significance always 1 (zb specs)
|
|
2355
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2356
|
+
const [status, apsFrame, messageTag] = (await this.emberPermitJoiningRequest(networkAddress, seconds, 1, 0));
|
|
2357
|
+
if (status !== enums_2.EmberStatus.SUCCESS) {
|
|
2358
|
+
console.error(`[ZDO] Failed permit joining request for "${networkAddress}" with status=${enums_2.EmberStatus[status]}.`);
|
|
2359
|
+
return status;
|
|
2360
|
+
}
|
|
2361
|
+
(await this.oneWaitress.startWaitingFor({
|
|
2362
|
+
target: networkAddress,
|
|
2363
|
+
apsFrame,
|
|
2364
|
+
responseClusterId: zdo_1.PERMIT_JOINING_RESPONSE,
|
|
2365
|
+
}, DEFAULT_ZDO_REQUEST_TIMEOUT));
|
|
2366
|
+
resolve();
|
|
2367
|
+
return enums_2.EmberStatus.SUCCESS;
|
|
2368
|
+
}, reject);
|
|
2369
|
+
});
|
|
2370
|
+
}
|
|
2371
|
+
else {
|
|
2372
|
+
// no device specified to open, open coordinator + broadcast
|
|
2373
|
+
return new Promise((resolve, reject) => {
|
|
2374
|
+
this.requestQueue.enqueue(async () => {
|
|
2375
|
+
this.checkInterpanLock();
|
|
2376
|
+
const pjStatus = (await preJoining());
|
|
2377
|
+
if (pjStatus !== enums_2.EmberStatus.SUCCESS) {
|
|
2378
|
+
console.error(`[ZDO] Failed pre joining request for "${networkAddress}" with status=${enums_2.EmberStatus[pjStatus]}.`);
|
|
2379
|
+
return pjStatus;
|
|
2380
|
+
}
|
|
2381
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2382
|
+
const [status, apsFrame, messageTag] = (await this.emberPermitJoining(seconds, true /*broadcast*/));
|
|
2383
|
+
if (status !== enums_2.EmberStatus.SUCCESS) {
|
|
2384
|
+
console.error(`[ZDO] Failed permit joining request with status=${enums_2.EmberStatus[status]}.`);
|
|
2385
|
+
return status;
|
|
2386
|
+
}
|
|
2387
|
+
// NOTE: because Z2M is refreshing the permit join duration early to prevent it from closing
|
|
2388
|
+
// (every 200sec, even if only opened for 254sec), we can't wait for the stack opened status,
|
|
2389
|
+
// as it won't trigger again if already opened... so instead we assume it worked
|
|
2390
|
+
// NOTE2: with EZSP, 255=forever, and 254=max, but since upstream logic uses fixed 254 with interval refresh,
|
|
2391
|
+
// we can't simply bypass upstream calls if called for "forever" to prevent useless NCP calls (3-4 each time),
|
|
2392
|
+
// until called with 0 (disable), since we don't know if it was requested for forever or not...
|
|
2393
|
+
// TLDR: upstream logic change required to allow this
|
|
2394
|
+
// if (seconds) {
|
|
2395
|
+
// await this.oneWaitress.startWaitingForEvent(
|
|
2396
|
+
// {eventName: OneWaitressEvents.STACK_STATUS_NETWORK_OPENED},
|
|
2397
|
+
// DEFAULT_ZCL_REQUEST_TIMEOUT,
|
|
2398
|
+
// '[ZDO] Permit Joining',
|
|
2399
|
+
// );
|
|
2400
|
+
// } else {
|
|
2401
|
+
// // NOTE: CLOSED stack status is not triggered if the network was not OPENED in the first place, so don't wait for it
|
|
2402
|
+
// // same kind of problem as described above (upstream always tries to close after start, but EZSP already is)
|
|
2403
|
+
// }
|
|
2404
|
+
resolve();
|
|
2405
|
+
return enums_2.EmberStatus.SUCCESS;
|
|
2406
|
+
}, reject);
|
|
2407
|
+
});
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
// queued, non-InterPAN
|
|
2411
|
+
async lqi(networkAddress) {
|
|
2412
|
+
const neighbors = [];
|
|
2413
|
+
const request = async (startIndex) => {
|
|
2414
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2415
|
+
const [reqStatus, apsFrame, messageTag] = (await this.emberLqiTableRequest(networkAddress, startIndex, this.defaultApsOptions));
|
|
2416
|
+
if (reqStatus !== enums_2.EmberStatus.SUCCESS) {
|
|
2417
|
+
console.error(`[ZDO] Failed LQI request for "${networkAddress}" (index "${startIndex}") with status=${enums_2.EmberStatus[reqStatus]}.`);
|
|
2418
|
+
return [reqStatus, null, null];
|
|
2419
|
+
}
|
|
2420
|
+
const result = (await this.oneWaitress.startWaitingFor({
|
|
2421
|
+
target: networkAddress,
|
|
2422
|
+
apsFrame,
|
|
2423
|
+
responseClusterId: zdo_1.LQI_TABLE_RESPONSE,
|
|
2424
|
+
}, DEFAULT_ZDO_REQUEST_TIMEOUT));
|
|
2425
|
+
for (const entry of result.entryList) {
|
|
2426
|
+
neighbors.push({
|
|
2427
|
+
ieeeAddr: entry.eui64,
|
|
2428
|
+
networkAddress: entry.nodeId,
|
|
2429
|
+
linkquality: entry.lqi,
|
|
2430
|
+
relationship: entry.relationship,
|
|
2431
|
+
depth: entry.depth,
|
|
2432
|
+
});
|
|
2433
|
+
}
|
|
2434
|
+
return [enums_2.EmberStatus.SUCCESS, result.neighborTableEntries, result.entryList.length];
|
|
2435
|
+
};
|
|
2436
|
+
return new Promise((resolve, reject) => {
|
|
2437
|
+
this.requestQueue.enqueue(async () => {
|
|
2438
|
+
this.checkInterpanLock();
|
|
2439
|
+
let [status, tableEntries, entryCount] = (await request(0));
|
|
2440
|
+
if (status !== enums_2.EmberStatus.SUCCESS) {
|
|
2441
|
+
return status;
|
|
2442
|
+
}
|
|
2443
|
+
const size = tableEntries;
|
|
2444
|
+
let nextStartIndex = entryCount;
|
|
2445
|
+
while (neighbors.length < size) {
|
|
2446
|
+
[status, tableEntries, entryCount] = (await request(nextStartIndex));
|
|
2447
|
+
if (status !== enums_2.EmberStatus.SUCCESS) {
|
|
2448
|
+
return status;
|
|
2449
|
+
}
|
|
2450
|
+
nextStartIndex += entryCount;
|
|
2451
|
+
}
|
|
2452
|
+
resolve({ neighbors });
|
|
2453
|
+
return status;
|
|
2454
|
+
}, reject);
|
|
2455
|
+
});
|
|
2456
|
+
}
|
|
2457
|
+
// queued, non-InterPAN
|
|
2458
|
+
async routingTable(networkAddress) {
|
|
2459
|
+
const table = [];
|
|
2460
|
+
const request = async (startIndex) => {
|
|
2461
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2462
|
+
const [reqStatus, apsFrame, messageTag] = (await this.emberRoutingTableRequest(networkAddress, startIndex, this.defaultApsOptions));
|
|
2463
|
+
if (reqStatus !== enums_2.EmberStatus.SUCCESS) {
|
|
2464
|
+
console.error(`[ZDO] Failed routing table request for "${networkAddress}" (index "${startIndex}") with status=${enums_2.EmberStatus[reqStatus]}.`);
|
|
2465
|
+
return [reqStatus, null, null];
|
|
2466
|
+
}
|
|
2467
|
+
const result = (await this.oneWaitress.startWaitingFor({
|
|
2468
|
+
target: networkAddress,
|
|
2469
|
+
apsFrame,
|
|
2470
|
+
responseClusterId: zdo_1.ROUTING_TABLE_RESPONSE,
|
|
2471
|
+
}, DEFAULT_ZDO_REQUEST_TIMEOUT));
|
|
2472
|
+
for (const entry of result.entryList) {
|
|
2473
|
+
table.push({
|
|
2474
|
+
destinationAddress: entry.destinationAddress,
|
|
2475
|
+
status: RoutingTableStatus[entry.status], // get str value from enum to satisfy upstream's needs
|
|
2476
|
+
nextHop: entry.nextHopAddress,
|
|
2477
|
+
});
|
|
2478
|
+
}
|
|
2479
|
+
return [enums_2.EmberStatus.SUCCESS, result.routingTableEntries, result.entryList.length];
|
|
2480
|
+
};
|
|
2481
|
+
return new Promise((resolve, reject) => {
|
|
2482
|
+
this.requestQueue.enqueue(async () => {
|
|
2483
|
+
this.checkInterpanLock();
|
|
2484
|
+
let [status, tableEntries, entryCount] = (await request(0));
|
|
2485
|
+
if (status !== enums_2.EmberStatus.SUCCESS) {
|
|
2486
|
+
return status;
|
|
2487
|
+
}
|
|
2488
|
+
const size = tableEntries;
|
|
2489
|
+
let nextStartIndex = entryCount;
|
|
2490
|
+
while (table.length < size) {
|
|
2491
|
+
[status, tableEntries, entryCount] = (await request(nextStartIndex));
|
|
2492
|
+
if (status !== enums_2.EmberStatus.SUCCESS) {
|
|
2493
|
+
return status;
|
|
2494
|
+
}
|
|
2495
|
+
nextStartIndex += entryCount;
|
|
2496
|
+
}
|
|
2497
|
+
resolve({ table });
|
|
2498
|
+
return enums_2.EmberStatus.SUCCESS;
|
|
2499
|
+
}, reject);
|
|
2500
|
+
});
|
|
2501
|
+
}
|
|
2502
|
+
// queued, non-InterPAN
|
|
2503
|
+
async nodeDescriptor(networkAddress) {
|
|
2504
|
+
return new Promise((resolve, reject) => {
|
|
2505
|
+
this.requestQueue.enqueue(async () => {
|
|
2506
|
+
this.checkInterpanLock();
|
|
2507
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
2508
|
+
const [status, apsFrame, messageTag] = (await this.emberNodeDescriptorRequest(networkAddress, this.defaultApsOptions));
|
|
2509
|
+
if (status !== enums_2.EmberStatus.SUCCESS) {
|
|
2510
|
+
console.error(`[ZDO] Failed node descriptor for "${networkAddress}" with status=${enums_2.EmberStatus[status]}.`);
|
|
2511
|
+
return status;
|
|
2512
|
+
}
|
|
2513
|
+
const result = (await this.oneWaitress.startWaitingFor({
|
|
2514
|
+
target: networkAddress,
|
|
2515
|
+
apsFrame,
|
|
2516
|
+
responseClusterId: zdo_1.NODE_DESCRIPTOR_RESPONSE,
|
|
2517
|
+
}, DEFAULT_ZDO_REQUEST_TIMEOUT));
|
|
2518
|
+
let type = 'Unknown';
|
|
2519
|
+
switch (result.logicalType) {
|
|
2520
|
+
case 0x0:
|
|
2521
|
+
type = 'Coordinator';
|
|
2522
|
+
break;
|
|
2523
|
+
case 0x1:
|
|
2524
|
+
type = 'Router';
|
|
2525
|
+
break;
|
|
2526
|
+
case 0x2:
|
|
2527
|
+
type = 'EndDevice';
|
|
2528
|
+
break;
|
|
2529
|
+
}
|
|
2530
|
+
// always 0 before rev. 21 where field was added
|
|
2531
|
+
if (result.stackRevision < CURRENT_ZIGBEE_SPEC_REVISION) {
|
|
2532
|
+
console.warn(`[ZDO] Node descriptor for "${networkAddress}" reports device is only compliant to revision `
|
|
2533
|
+
+ `"${(result.stackRevision < 21) ? 'pre-21' : result.stackRevision}" of the ZigBee specification `
|
|
2534
|
+
+ `(current revision: ${CURRENT_ZIGBEE_SPEC_REVISION}).`);
|
|
2535
|
+
}
|
|
2536
|
+
resolve({ type, manufacturerCode: result.manufacturerCode });
|
|
2537
|
+
return enums_2.EmberStatus.SUCCESS;
|
|
2538
|
+
}, reject);
|
|
2539
|
+
});
|
|
2540
|
+
}
|
|
2541
|
+
// queued, non-InterPAN
|
|
2542
|
+
async activeEndpoints(networkAddress) {
|
|
2543
|
+
return new Promise((resolve, reject) => {
|
|
2544
|
+
this.requestQueue.enqueue(async () => {
|
|
2545
|
+
this.checkInterpanLock();
|
|
2546
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2547
|
+
const [status, apsFrame, messageTag] = (await this.emberActiveEndpointsRequest(networkAddress, this.defaultApsOptions));
|
|
2548
|
+
if (status !== enums_2.EmberStatus.SUCCESS) {
|
|
2549
|
+
console.error(`[ZDO] Failed active endpoints request for "${networkAddress}" with status=${enums_2.EmberStatus[status]}.`);
|
|
2550
|
+
return status;
|
|
2551
|
+
}
|
|
2552
|
+
const result = (await this.oneWaitress.startWaitingFor({
|
|
2553
|
+
target: networkAddress,
|
|
2554
|
+
apsFrame,
|
|
2555
|
+
responseClusterId: zdo_1.ACTIVE_ENDPOINTS_RESPONSE,
|
|
2556
|
+
}, DEFAULT_ZDO_REQUEST_TIMEOUT));
|
|
2557
|
+
resolve({ endpoints: result.endpointList });
|
|
2558
|
+
return enums_2.EmberStatus.SUCCESS;
|
|
2559
|
+
}, reject);
|
|
2560
|
+
});
|
|
2561
|
+
}
|
|
2562
|
+
// queued, non-InterPAN
|
|
2563
|
+
async simpleDescriptor(networkAddress, endpointID) {
|
|
2564
|
+
return new Promise((resolve, reject) => {
|
|
2565
|
+
this.requestQueue.enqueue(async () => {
|
|
2566
|
+
this.checkInterpanLock();
|
|
2567
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2568
|
+
const [status, apsFrame, messageTag] = (await this.emberSimpleDescriptorRequest(networkAddress, endpointID, this.defaultApsOptions));
|
|
2569
|
+
if (status !== enums_2.EmberStatus.SUCCESS) {
|
|
2570
|
+
console.error(`[ZDO] Failed simple descriptor request for "${networkAddress}" endpoint "${endpointID}" `
|
|
2571
|
+
+ `with status=${enums_2.EmberStatus[status]}.`);
|
|
2572
|
+
return status;
|
|
2573
|
+
}
|
|
2574
|
+
const result = (await this.oneWaitress.startWaitingFor({
|
|
2575
|
+
target: networkAddress,
|
|
2576
|
+
apsFrame,
|
|
2577
|
+
responseClusterId: zdo_1.SIMPLE_DESCRIPTOR_RESPONSE,
|
|
2578
|
+
}, DEFAULT_ZDO_REQUEST_TIMEOUT));
|
|
2579
|
+
resolve({
|
|
2580
|
+
profileID: result.profileId,
|
|
2581
|
+
endpointID: result.endpoint,
|
|
2582
|
+
deviceID: result.deviceId,
|
|
2583
|
+
inputClusters: result.inClusterList,
|
|
2584
|
+
outputClusters: result.outClusterList,
|
|
2585
|
+
});
|
|
2586
|
+
return enums_2.EmberStatus.SUCCESS;
|
|
2587
|
+
}, reject);
|
|
2588
|
+
});
|
|
2589
|
+
}
|
|
2590
|
+
// queued, non-InterPAN
|
|
2591
|
+
async bind(destinationNetworkAddress, sourceIeeeAddress, sourceEndpoint, clusterID, destinationAddressOrGroup, type, destinationEndpoint) {
|
|
2592
|
+
if (typeof destinationAddressOrGroup === 'string' && type === 'endpoint') {
|
|
2593
|
+
// dest address is EUI64 (str), so type should always be endpoint (unicast)
|
|
2594
|
+
return new Promise((resolve, reject) => {
|
|
2595
|
+
this.requestQueue.enqueue(async () => {
|
|
2596
|
+
this.checkInterpanLock();
|
|
2597
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2598
|
+
const [status, apsFrame, messageTag] = (await this.emberBindRequest(destinationNetworkAddress, sourceIeeeAddress, sourceEndpoint, clusterID, zdo_1.UNICAST_BINDING, destinationAddressOrGroup, null, // doesn't matter
|
|
2599
|
+
destinationEndpoint, this.defaultApsOptions));
|
|
2600
|
+
if (status !== enums_2.EmberStatus.SUCCESS) {
|
|
2601
|
+
console.error(`[ZDO] Failed bind request for "${destinationNetworkAddress}" destination "${destinationAddressOrGroup}" `
|
|
2602
|
+
+ `endpoint "${destinationEndpoint}" with status=${enums_2.EmberStatus[status]}.`);
|
|
2603
|
+
return status;
|
|
2604
|
+
}
|
|
2605
|
+
await this.oneWaitress.startWaitingFor({
|
|
2606
|
+
target: destinationNetworkAddress,
|
|
2607
|
+
apsFrame,
|
|
2608
|
+
responseClusterId: zdo_1.BIND_RESPONSE,
|
|
2609
|
+
}, DEFAULT_ZDO_REQUEST_TIMEOUT);
|
|
2610
|
+
resolve();
|
|
2611
|
+
return enums_2.EmberStatus.SUCCESS;
|
|
2612
|
+
}, reject);
|
|
2613
|
+
});
|
|
2614
|
+
}
|
|
2615
|
+
else if (typeof destinationAddressOrGroup === 'number' && type === 'group') {
|
|
2616
|
+
// dest is group num, so type should always be group (multicast)
|
|
2617
|
+
return new Promise((resolve, reject) => {
|
|
2618
|
+
this.requestQueue.enqueue(async () => {
|
|
2619
|
+
this.checkInterpanLock();
|
|
2620
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2621
|
+
const [status, apsFrame, messageTag] = (await this.emberBindRequest(destinationNetworkAddress, sourceIeeeAddress, sourceEndpoint, clusterID, zdo_1.MULTICAST_BINDING, null, // doesn't matter
|
|
2622
|
+
destinationAddressOrGroup, destinationEndpoint, // doesn't matter
|
|
2623
|
+
this.defaultApsOptions));
|
|
2624
|
+
if (status !== enums_2.EmberStatus.SUCCESS) {
|
|
2625
|
+
console.error(`[ZDO] Failed bind request for "${destinationNetworkAddress}" group "${destinationAddressOrGroup}" `
|
|
2626
|
+
+ `with status=${enums_2.EmberStatus[status]}.`);
|
|
2627
|
+
return status;
|
|
2628
|
+
}
|
|
2629
|
+
await this.oneWaitress.startWaitingFor({
|
|
2630
|
+
target: destinationNetworkAddress,
|
|
2631
|
+
apsFrame,
|
|
2632
|
+
responseClusterId: zdo_1.BIND_RESPONSE,
|
|
2633
|
+
}, DEFAULT_ZDO_REQUEST_TIMEOUT);
|
|
2634
|
+
resolve();
|
|
2635
|
+
return enums_2.EmberStatus.SUCCESS;
|
|
2636
|
+
}, reject);
|
|
2637
|
+
});
|
|
2638
|
+
}
|
|
2639
|
+
}
|
|
2640
|
+
// queued, non-InterPAN
|
|
2641
|
+
async unbind(destinationNetworkAddress, sourceIeeeAddress, sourceEndpoint, clusterID, destinationAddressOrGroup, type, destinationEndpoint) {
|
|
2642
|
+
if (typeof destinationAddressOrGroup === 'string' && type === 'endpoint') {
|
|
2643
|
+
// dest address is EUI64 (str), so type should always be endpoint (unicast)
|
|
2644
|
+
return new Promise((resolve, reject) => {
|
|
2645
|
+
this.requestQueue.enqueue(async () => {
|
|
2646
|
+
this.checkInterpanLock();
|
|
2647
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2648
|
+
const [status, apsFrame, messageTag] = (await this.emberUnbindRequest(destinationNetworkAddress, sourceIeeeAddress, sourceEndpoint, clusterID, zdo_1.UNICAST_BINDING, destinationAddressOrGroup, null, // doesn't matter
|
|
2649
|
+
destinationEndpoint, this.defaultApsOptions));
|
|
2650
|
+
if (status !== enums_2.EmberStatus.SUCCESS) {
|
|
2651
|
+
console.error(`[ZDO] Failed unbind request for "${destinationNetworkAddress}" destination "${destinationAddressOrGroup}" `
|
|
2652
|
+
+ `endpoint "${destinationEndpoint}" with status=${enums_2.EmberStatus[status]}.`);
|
|
2653
|
+
return status;
|
|
2654
|
+
}
|
|
2655
|
+
await this.oneWaitress.startWaitingFor({
|
|
2656
|
+
target: destinationNetworkAddress,
|
|
2657
|
+
apsFrame,
|
|
2658
|
+
responseClusterId: zdo_1.UNBIND_RESPONSE,
|
|
2659
|
+
}, DEFAULT_ZDO_REQUEST_TIMEOUT);
|
|
2660
|
+
resolve();
|
|
2661
|
+
return enums_2.EmberStatus.SUCCESS;
|
|
2662
|
+
}, reject);
|
|
2663
|
+
});
|
|
2664
|
+
}
|
|
2665
|
+
else if (typeof destinationAddressOrGroup === 'number' && type === 'group') {
|
|
2666
|
+
// dest is group num, so type should always be group (multicast)
|
|
2667
|
+
return new Promise((resolve, reject) => {
|
|
2668
|
+
this.requestQueue.enqueue(async () => {
|
|
2669
|
+
this.checkInterpanLock();
|
|
2670
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2671
|
+
const [status, apsFrame, messageTag] = (await this.emberUnbindRequest(destinationNetworkAddress, sourceIeeeAddress, sourceEndpoint, clusterID, zdo_1.MULTICAST_BINDING, null, // doesn't matter
|
|
2672
|
+
destinationAddressOrGroup, destinationEndpoint, // doesn't matter
|
|
2673
|
+
this.defaultApsOptions));
|
|
2674
|
+
if (status !== enums_2.EmberStatus.SUCCESS) {
|
|
2675
|
+
console.error(`[ZDO] Failed unbind request for "${destinationNetworkAddress}" group "${destinationAddressOrGroup}" `
|
|
2676
|
+
+ `with status=${enums_2.EmberStatus[status]}.`);
|
|
2677
|
+
return status;
|
|
2678
|
+
}
|
|
2679
|
+
await this.oneWaitress.startWaitingFor({
|
|
2680
|
+
target: destinationNetworkAddress,
|
|
2681
|
+
apsFrame,
|
|
2682
|
+
responseClusterId: zdo_1.UNBIND_RESPONSE,
|
|
2683
|
+
}, DEFAULT_ZDO_REQUEST_TIMEOUT);
|
|
2684
|
+
resolve();
|
|
2685
|
+
return enums_2.EmberStatus.SUCCESS;
|
|
2686
|
+
}, reject);
|
|
2687
|
+
});
|
|
2688
|
+
}
|
|
2689
|
+
}
|
|
2690
|
+
// queued, non-InterPAN
|
|
2691
|
+
async removeDevice(networkAddress, ieeeAddr) {
|
|
2692
|
+
return new Promise((resolve, reject) => {
|
|
2693
|
+
this.requestQueue.enqueue(async () => {
|
|
2694
|
+
this.checkInterpanLock();
|
|
2695
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2696
|
+
const [status, apsFrame, messageTag] = (await this.emberLeaveRequest(networkAddress, ieeeAddr, enums_2.EmberLeaveRequestFlags.WITHOUT_REJOIN, this.defaultApsOptions));
|
|
2697
|
+
if (status !== enums_2.EmberStatus.SUCCESS) {
|
|
2698
|
+
console.error(`[ZDO] Failed remove device request for "${networkAddress}" target "${ieeeAddr}" `
|
|
2699
|
+
+ `with status=${enums_2.EmberStatus[status]}.`);
|
|
2700
|
+
return status;
|
|
2701
|
+
}
|
|
2702
|
+
await this.oneWaitress.startWaitingFor({
|
|
2703
|
+
target: networkAddress,
|
|
2704
|
+
apsFrame,
|
|
2705
|
+
responseClusterId: zdo_1.LEAVE_RESPONSE,
|
|
2706
|
+
}, DEFAULT_ZDO_REQUEST_TIMEOUT);
|
|
2707
|
+
resolve();
|
|
2708
|
+
return enums_2.EmberStatus.SUCCESS;
|
|
2709
|
+
}, reject);
|
|
2710
|
+
});
|
|
2711
|
+
}
|
|
2712
|
+
//---- ZCL
|
|
2713
|
+
// queued, non-InterPAN
|
|
2714
|
+
async sendZclFrameToEndpoint(ieeeAddr, networkAddress, endpoint, zclFrame, timeout, disableResponse, disableRecovery, sourceEndpoint) {
|
|
2715
|
+
const sourceEndpointInfo = typeof sourceEndpoint === 'number' ?
|
|
2716
|
+
endpoints_1.FIXED_ENDPOINTS.find((epi) => (epi.endpoint === sourceEndpoint)) : endpoints_1.FIXED_ENDPOINTS[0];
|
|
2717
|
+
const command = zclFrame.getCommand();
|
|
2718
|
+
let commandResponseId = null;
|
|
2719
|
+
if (command.hasOwnProperty('response') && disableResponse === false) {
|
|
2720
|
+
commandResponseId = command.response;
|
|
2721
|
+
}
|
|
2722
|
+
else if (!zclFrame.Header.frameControl.disableDefaultResponse) {
|
|
2723
|
+
commandResponseId = zcl_1.Foundation.defaultRsp.ID;
|
|
2724
|
+
}
|
|
2725
|
+
const apsFrame = {
|
|
2726
|
+
profileId: sourceEndpointInfo.profileId,
|
|
2727
|
+
clusterId: zclFrame.Cluster.ID,
|
|
2728
|
+
sourceEndpoint: sourceEndpointInfo.endpoint,
|
|
2729
|
+
destinationEndpoint: (typeof endpoint === 'number') ? endpoint : endpoints_1.FIXED_ENDPOINTS[0].endpoint,
|
|
2730
|
+
options: this.defaultApsOptions,
|
|
2731
|
+
groupId: 0,
|
|
2732
|
+
sequence: 0, // set by stack
|
|
2733
|
+
};
|
|
2734
|
+
// don't RETRY if no response expected
|
|
2735
|
+
if (commandResponseId == null) {
|
|
2736
|
+
apsFrame.options &= ~enums_2.EmberApsOption.RETRY;
|
|
2737
|
+
}
|
|
2738
|
+
const data = zclFrame.toBuffer();
|
|
2739
|
+
return new Promise((resolve, reject) => {
|
|
2740
|
+
this.requestQueue.enqueue(async () => {
|
|
2741
|
+
this.checkInterpanLock();
|
|
2742
|
+
if (CHECK_APS_PAYLOAD_LENGTH) {
|
|
2743
|
+
const maxPayloadLength = (await this.maximumApsPayloadLength(enums_2.EmberOutgoingMessageType.DIRECT, networkAddress, apsFrame));
|
|
2744
|
+
if (data.length > maxPayloadLength) {
|
|
2745
|
+
return enums_2.EmberStatus.MESSAGE_TOO_LONG; // queue will reject
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
// track group changes in NCP multicast table
|
|
2749
|
+
if (apsFrame.clusterId === cluster_1.default.genGroups.ID) {
|
|
2750
|
+
await this.onGroupChange(command.ID, zclFrame.Payload.groupid);
|
|
2751
|
+
}
|
|
2752
|
+
debug(`~~~> [ZCL to=${networkAddress} apsFrame=${JSON.stringify(apsFrame)} header=${JSON.stringify(zclFrame.Header)}]`);
|
|
2753
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2754
|
+
const [status, messageTag] = (await this.ezsp.send(enums_2.EmberOutgoingMessageType.DIRECT, networkAddress, apsFrame, data, 0, // alias
|
|
2755
|
+
0));
|
|
2756
|
+
if (status !== enums_2.EmberStatus.SUCCESS) {
|
|
2757
|
+
console.error(`~x~> [ZCL to=${networkAddress}] Failed to send request with status=${enums_2.EmberStatus[status]}.`);
|
|
2758
|
+
return status; // let queue handle retry based on status
|
|
2759
|
+
}
|
|
2760
|
+
if (commandResponseId != null) {
|
|
2761
|
+
// NOTE: aps sequence number will have been set by send function
|
|
2762
|
+
const result = (await this.oneWaitress.startWaitingFor({
|
|
2763
|
+
target: networkAddress,
|
|
2764
|
+
apsFrame,
|
|
2765
|
+
zclSequence: zclFrame.Header.transactionSequenceNumber,
|
|
2766
|
+
}, timeout || DEFAULT_ZCL_REQUEST_TIMEOUT));
|
|
2767
|
+
resolve(result);
|
|
2768
|
+
}
|
|
2769
|
+
else {
|
|
2770
|
+
resolve(null); // don't expect a response
|
|
2771
|
+
return enums_2.EmberStatus.SUCCESS;
|
|
2772
|
+
}
|
|
2773
|
+
}, reject);
|
|
2774
|
+
});
|
|
2775
|
+
}
|
|
2776
|
+
// queued, non-InterPAN
|
|
2777
|
+
async sendZclFrameToGroup(groupID, zclFrame, sourceEndpoint) {
|
|
2778
|
+
const sourceEndpointInfo = typeof sourceEndpoint === 'number' ?
|
|
2779
|
+
endpoints_1.FIXED_ENDPOINTS.find((epi) => (epi.endpoint === sourceEndpoint)) : endpoints_1.FIXED_ENDPOINTS[0];
|
|
2780
|
+
const apsFrame = {
|
|
2781
|
+
profileId: sourceEndpointInfo.profileId,
|
|
2782
|
+
clusterId: zclFrame.Cluster.ID,
|
|
2783
|
+
sourceEndpoint: sourceEndpointInfo.endpoint,
|
|
2784
|
+
destinationEndpoint: endpoints_1.FIXED_ENDPOINTS[0].endpoint,
|
|
2785
|
+
options: this.defaultApsOptions,
|
|
2786
|
+
groupId: groupID,
|
|
2787
|
+
sequence: 0, // set by stack
|
|
2788
|
+
};
|
|
2789
|
+
const data = zclFrame.toBuffer();
|
|
2790
|
+
return new Promise((resolve, reject) => {
|
|
2791
|
+
this.requestQueue.enqueue(async () => {
|
|
2792
|
+
this.checkInterpanLock();
|
|
2793
|
+
if (CHECK_APS_PAYLOAD_LENGTH) {
|
|
2794
|
+
const maxPayloadLength = (await this.maximumApsPayloadLength(enums_2.EmberOutgoingMessageType.MULTICAST, groupID, apsFrame));
|
|
2795
|
+
if (data.length > maxPayloadLength) {
|
|
2796
|
+
return enums_2.EmberStatus.MESSAGE_TOO_LONG; // queue will reject
|
|
2797
|
+
}
|
|
2798
|
+
}
|
|
2799
|
+
debug(`~~~> [ZCL GROUP apsFrame=${JSON.stringify(apsFrame)} header=${JSON.stringify(zclFrame.Header)}]`);
|
|
2800
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2801
|
+
const [status, messageTag] = (await this.ezsp.send(enums_2.EmberOutgoingMessageType.MULTICAST, apsFrame.groupId, // not used for MC
|
|
2802
|
+
apsFrame, data, 0, // alias
|
|
2803
|
+
0));
|
|
2804
|
+
if (status !== enums_2.EmberStatus.SUCCESS) {
|
|
2805
|
+
console.error(`~x~> [ZCL GROUP] Failed to send with status=${enums_2.EmberStatus[status]}.`);
|
|
2806
|
+
return status; // let queue handle retry based on status
|
|
2807
|
+
}
|
|
2808
|
+
// NOTE: since ezspMessageSentHandler could take a while here, we don't block, it'll just be logged if the delivery failed
|
|
2809
|
+
resolve();
|
|
2810
|
+
return enums_2.EmberStatus.SUCCESS;
|
|
2811
|
+
}, reject);
|
|
2812
|
+
});
|
|
2813
|
+
}
|
|
2814
|
+
// queued, non-InterPAN
|
|
2815
|
+
async sendZclFrameToAll(endpoint, zclFrame, sourceEndpoint) {
|
|
2816
|
+
const sourceEndpointInfo = typeof sourceEndpoint === 'number' ?
|
|
2817
|
+
endpoints_1.FIXED_ENDPOINTS.find((epi) => (epi.endpoint === sourceEndpoint)) : endpoints_1.FIXED_ENDPOINTS[0];
|
|
2818
|
+
const apsFrame = {
|
|
2819
|
+
profileId: sourceEndpointInfo.profileId,
|
|
2820
|
+
clusterId: zclFrame.Cluster.ID,
|
|
2821
|
+
sourceEndpoint: sourceEndpointInfo.endpoint,
|
|
2822
|
+
destinationEndpoint: (typeof endpoint === 'number') ? endpoint : endpoints_1.FIXED_ENDPOINTS[0].endpoint,
|
|
2823
|
+
options: this.defaultApsOptions,
|
|
2824
|
+
groupId: consts_2.EMBER_RX_ON_WHEN_IDLE_BROADCAST_ADDRESS,
|
|
2825
|
+
sequence: 0, // set by stack
|
|
2826
|
+
};
|
|
2827
|
+
const data = zclFrame.toBuffer();
|
|
2828
|
+
return new Promise((resolve, reject) => {
|
|
2829
|
+
this.requestQueue.enqueue(async () => {
|
|
2830
|
+
this.checkInterpanLock();
|
|
2831
|
+
if (CHECK_APS_PAYLOAD_LENGTH) {
|
|
2832
|
+
const maxPayloadLength = (await this.maximumApsPayloadLength(enums_2.EmberOutgoingMessageType.BROADCAST, consts_2.EMBER_RX_ON_WHEN_IDLE_BROADCAST_ADDRESS, apsFrame));
|
|
2833
|
+
if (data.length > maxPayloadLength) {
|
|
2834
|
+
return enums_2.EmberStatus.MESSAGE_TOO_LONG; // queue will reject
|
|
2835
|
+
}
|
|
2836
|
+
}
|
|
2837
|
+
debug(`~~~> [ZCL BROADCAST apsFrame=${JSON.stringify(apsFrame)} header=${JSON.stringify(zclFrame.Header)}]`);
|
|
2838
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2839
|
+
const [status, messageTag] = (await this.ezsp.send(enums_2.EmberOutgoingMessageType.BROADCAST, consts_2.EMBER_RX_ON_WHEN_IDLE_BROADCAST_ADDRESS, apsFrame, data, 0, // alias
|
|
2840
|
+
0));
|
|
2841
|
+
if (status !== enums_2.EmberStatus.SUCCESS) {
|
|
2842
|
+
console.error(`~x~> [ZCL BROADCAST] Failed to send with status=${enums_2.EmberStatus[status]}.`);
|
|
2843
|
+
return status; // let queue handle retry based on status
|
|
2844
|
+
}
|
|
2845
|
+
// NOTE: since ezspMessageSentHandler could take a while here, we don't block, it'll just be logged if the delivery failed
|
|
2846
|
+
resolve();
|
|
2847
|
+
return enums_2.EmberStatus.SUCCESS;
|
|
2848
|
+
}, reject);
|
|
2849
|
+
});
|
|
2850
|
+
}
|
|
2851
|
+
//---- InterPAN for Touchlink
|
|
2852
|
+
// XXX: There might be a better way to handle touchlink with ZLL ezsp functions, but I don't have any device to test so, didn't look into it...
|
|
2853
|
+
// TODO: check all this touchlink/interpan stuff
|
|
2854
|
+
// queued
|
|
2855
|
+
async setChannelInterPAN(channel) {
|
|
2856
|
+
if (typeof channel !== 'number') {
|
|
2857
|
+
console.error(`Tried to set channel InterPAN to non-number. Channel ${channel} of type ${typeof channel}.`);
|
|
2858
|
+
return;
|
|
2859
|
+
}
|
|
2860
|
+
return new Promise((resolve, reject) => {
|
|
2861
|
+
this.requestQueue.enqueue(async () => {
|
|
2862
|
+
this.interpanLock = true;
|
|
2863
|
+
const status = (await this.ezsp.ezspSetLogicalAndRadioChannel(channel));
|
|
2864
|
+
if (status !== enums_2.EmberStatus.SUCCESS) {
|
|
2865
|
+
this.interpanLock = false; // XXX: ok?
|
|
2866
|
+
console.error(`Failed to set InterPAN channel to ${channel} with status=${enums_2.EmberStatus[status]}.`);
|
|
2867
|
+
return status;
|
|
2868
|
+
}
|
|
2869
|
+
resolve();
|
|
2870
|
+
return status;
|
|
2871
|
+
}, reject);
|
|
2872
|
+
});
|
|
2873
|
+
}
|
|
2874
|
+
// queued
|
|
2875
|
+
async sendZclFrameInterPANToIeeeAddr(zclFrame, ieeeAddress) {
|
|
2876
|
+
return new Promise((resolve, reject) => {
|
|
2877
|
+
this.requestQueue.enqueue(async () => {
|
|
2878
|
+
const msgBuffalo = new buffalo_1.EzspBuffalo(Buffer.alloc(consts_2.MAXIMUM_INTERPAN_LENGTH));
|
|
2879
|
+
// cache-enabled getters
|
|
2880
|
+
const sourcePanId = (await this.emberGetPanId());
|
|
2881
|
+
const sourceEui64 = (await this.emberGetEui64());
|
|
2882
|
+
msgBuffalo.writeUInt16((consts_2.LONG_DEST_FRAME_CONTROL | consts_2.MAC_ACK_REQUIRED)); // macFrameControl
|
|
2883
|
+
msgBuffalo.writeUInt8(0); // sequence Skip Sequence number, stack sets the sequence number.
|
|
2884
|
+
msgBuffalo.writeUInt16(consts_2.INVALID_PAN_ID); // destPanId
|
|
2885
|
+
msgBuffalo.writeIeeeAddr(ieeeAddress); // destAddress (longAddress)
|
|
2886
|
+
msgBuffalo.writeUInt16(sourcePanId); // sourcePanId
|
|
2887
|
+
msgBuffalo.writeIeeeAddr(sourceEui64); // sourceAddress
|
|
2888
|
+
msgBuffalo.writeUInt16(consts_2.STUB_NWK_FRAME_CONTROL); // nwkFrameControl
|
|
2889
|
+
msgBuffalo.writeUInt8((enums_2.EmberInterpanMessageType.UNICAST | consts_2.INTERPAN_APS_FRAME_TYPE)); // apsFrameControl
|
|
2890
|
+
msgBuffalo.writeUInt16(zclFrame.Cluster.ID);
|
|
2891
|
+
msgBuffalo.writeUInt16(consts_2.TOUCHLINK_PROFILE_ID);
|
|
2892
|
+
debug(`~~~> [ZCL TOUCHLINK to=${ieeeAddress} header=${JSON.stringify(zclFrame.Header)}]`);
|
|
2893
|
+
const status = (await this.ezsp.ezspSendRawMessage(Buffer.concat([msgBuffalo.getWritten(), zclFrame.toBuffer()])));
|
|
2894
|
+
if (status !== enums_2.EmberStatus.SUCCESS) {
|
|
2895
|
+
console.error(`~x~> [ZCL TOUCHLINK to=${ieeeAddress}] Failed to send with status=${enums_2.EmberStatus[status]}.`);
|
|
2896
|
+
return status;
|
|
2897
|
+
}
|
|
2898
|
+
// NOTE: can use ezspRawTransmitCompleteHandler if needed here
|
|
2899
|
+
resolve();
|
|
2900
|
+
return status;
|
|
2901
|
+
}, reject);
|
|
2902
|
+
});
|
|
2903
|
+
}
|
|
2904
|
+
// queued
|
|
2905
|
+
async sendZclFrameInterPANBroadcast(zclFrame, timeout) {
|
|
2906
|
+
const command = zclFrame.getCommand();
|
|
2907
|
+
if (!command.hasOwnProperty('response')) {
|
|
2908
|
+
throw new Error(`Command '${command.name}' has no response, cannot wait for response.`);
|
|
2909
|
+
}
|
|
2910
|
+
// just for waitress
|
|
2911
|
+
const apsFrame = {
|
|
2912
|
+
profileId: consts_2.TOUCHLINK_PROFILE_ID,
|
|
2913
|
+
clusterId: zclFrame.Cluster.ID,
|
|
2914
|
+
sourceEndpoint: 0,
|
|
2915
|
+
destinationEndpoint: 0,
|
|
2916
|
+
options: enums_2.EmberApsOption.NONE,
|
|
2917
|
+
groupId: consts_2.EMBER_SLEEPY_BROADCAST_ADDRESS,
|
|
2918
|
+
sequence: 0, // set by stack
|
|
2919
|
+
};
|
|
2920
|
+
return new Promise((resolve, reject) => {
|
|
2921
|
+
this.requestQueue.enqueue(async () => {
|
|
2922
|
+
const msgBuffalo = new buffalo_1.EzspBuffalo(Buffer.alloc(consts_2.MAXIMUM_INTERPAN_LENGTH));
|
|
2923
|
+
// cache-enabled getters
|
|
2924
|
+
const sourcePanId = (await this.emberGetPanId());
|
|
2925
|
+
const sourceEui64 = (await this.emberGetEui64());
|
|
2926
|
+
msgBuffalo.writeUInt16(consts_2.SHORT_DEST_FRAME_CONTROL); // macFrameControl
|
|
2927
|
+
msgBuffalo.writeUInt8(0); // sequence Skip Sequence number, stack sets the sequence number.
|
|
2928
|
+
msgBuffalo.writeUInt16(consts_2.INVALID_PAN_ID); // destPanId
|
|
2929
|
+
msgBuffalo.writeUInt16(apsFrame.groupId); // destAddress (longAddress)
|
|
2930
|
+
msgBuffalo.writeUInt16(sourcePanId); // sourcePanId
|
|
2931
|
+
msgBuffalo.writeIeeeAddr(sourceEui64); // sourceAddress
|
|
2932
|
+
msgBuffalo.writeUInt16(consts_2.STUB_NWK_FRAME_CONTROL); // nwkFrameControl
|
|
2933
|
+
msgBuffalo.writeUInt8((enums_2.EmberInterpanMessageType.BROADCAST | consts_2.INTERPAN_APS_FRAME_TYPE)); // apsFrameControl
|
|
2934
|
+
msgBuffalo.writeUInt16(apsFrame.clusterId);
|
|
2935
|
+
msgBuffalo.writeUInt16(apsFrame.profileId);
|
|
2936
|
+
const data = Buffer.concat([msgBuffalo.getWritten(), zclFrame.toBuffer()]);
|
|
2937
|
+
debug(`~~~> [ZCL TOUCHLINK BROADCAST header=${JSON.stringify(zclFrame.Header)}]`);
|
|
2938
|
+
const status = (await this.ezsp.ezspSendRawMessage(data));
|
|
2939
|
+
if (status !== enums_2.EmberStatus.SUCCESS) {
|
|
2940
|
+
console.error(`~x~> [ZCL TOUCHLINK BROADCAST] Failed to send with status=${enums_2.EmberStatus[status]}.`);
|
|
2941
|
+
return status;
|
|
2942
|
+
}
|
|
2943
|
+
// NOTE: can use ezspRawTransmitCompleteHandler if needed here
|
|
2944
|
+
const result = (await this.oneWaitress.startWaitingFor({
|
|
2945
|
+
target: null,
|
|
2946
|
+
apsFrame: apsFrame,
|
|
2947
|
+
zclSequence: zclFrame.Header.transactionSequenceNumber,
|
|
2948
|
+
}, timeout || DEFAULT_ZCL_REQUEST_TIMEOUT * 2)); // XXX: touchlink timeout?
|
|
2949
|
+
resolve(result);
|
|
2950
|
+
return enums_2.EmberStatus.SUCCESS;
|
|
2951
|
+
}, reject);
|
|
2952
|
+
});
|
|
2953
|
+
}
|
|
2954
|
+
// queued
|
|
2955
|
+
async restoreChannelInterPAN() {
|
|
2956
|
+
return new Promise((resolve, reject) => {
|
|
2957
|
+
this.requestQueue.enqueue(async () => {
|
|
2958
|
+
const status = (await this.ezsp.ezspSetLogicalAndRadioChannel(this.networkOptions.channelList[0]));
|
|
2959
|
+
if (status !== enums_2.EmberStatus.SUCCESS) {
|
|
2960
|
+
console.error(`Failed to restore InterPAN channel to ${this.networkOptions.channelList[0]} with status=${enums_2.EmberStatus[status]}.`);
|
|
2961
|
+
return status;
|
|
2962
|
+
}
|
|
2963
|
+
// let adapter settle down
|
|
2964
|
+
await (0, utils_1.Wait)(3000);
|
|
2965
|
+
this.interpanLock = false;
|
|
2966
|
+
resolve();
|
|
2967
|
+
return status;
|
|
2968
|
+
}, reject);
|
|
2969
|
+
});
|
|
2970
|
+
}
|
|
2971
|
+
//-- END Adapter implementation
|
|
2972
|
+
checkInterpanLock() {
|
|
2973
|
+
if (this.interpanLock) {
|
|
2974
|
+
console.error(`[INTERPAN MODE] Cannot execute non-InterPAN commands.`);
|
|
2975
|
+
// will be caught by request queue and rejected internally.
|
|
2976
|
+
throw new Error(enums_2.EzspStatus[enums_2.EzspStatus.ERROR_INVALID_CALL]);
|
|
2977
|
+
}
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
exports.EmberAdapter = EmberAdapter;
|
|
2981
|
+
//# sourceMappingURL=emberAdapter.js.map
|