diff --git a/src/adapter/adapter.ts b/src/adapter/adapter.ts index a84e03a004..3e5a2a7882 100644 --- a/src/adapter/adapter.ts +++ b/src/adapter/adapter.ts @@ -37,10 +37,13 @@ abstract class Adapter extends events.EventEmitter { const {ZStackAdapter} = await import('./z-stack/adapter'); const {DeconzAdapter} = await import('./deconz/adapter'); const {ZiGateAdapter} = await import('./zigate/adapter'); - type AdapterImplementation = typeof ZStackAdapter | typeof DeconzAdapter | typeof ZiGateAdapter; + const {EZSPAdapter} = await import('./ezsp/adapter'); + type AdapterImplementation = (typeof ZStackAdapter | typeof DeconzAdapter | typeof ZiGateAdapter + | typeof EZSPAdapter); let adapters: AdapterImplementation[]; - const adapterLookup = {zstack: ZStackAdapter, deconz: DeconzAdapter, zigate: ZiGateAdapter}; + const adapterLookup = {zstack: ZStackAdapter, deconz: DeconzAdapter, zigate: ZiGateAdapter, + ezsp: EZSPAdapter}; if (serialPortOptions.adapter) { if (adapterLookup.hasOwnProperty(serialPortOptions.adapter)) { adapters = [adapterLookup[serialPortOptions.adapter]]; diff --git a/src/adapter/ezsp/adapter/ezspAdapter.ts b/src/adapter/ezsp/adapter/ezspAdapter.ts new file mode 100644 index 0000000000..6dedd3b915 --- /dev/null +++ b/src/adapter/ezsp/adapter/ezspAdapter.ts @@ -0,0 +1,516 @@ +/* istanbul ignore file */ +/* eslint-disable */ +import { + NetworkOptions, SerialPortOptions, Coordinator, CoordinatorVersion, NodeDescriptor, + DeviceType, ActiveEndpoints, SimpleDescriptor, LQI, RoutingTable, Backup as BackupType, NetworkParameters, + StartResult, LQINeighbor, RoutingTableEntry, AdapterOptions +} from '../../tstype'; +import Debug from "debug"; +import Adapter from '../../adapter'; + +const debug = Debug("zigbee-herdsman:adapter:ezsp"); +import {Driver} from '../driver'; +import {EmberZDOCmd, EmberApsOption, uint16_t, EmberEUI64, EmberStatus} from '../driver/types'; +import {ZclFrame, FrameType, Direction, Foundation} from '../../../zcl'; +import * as Events from '../../events'; +import {Waitress} from '../../../utils'; + + +interface WaitressMatcher { + address: number | string; + endpoint: number; + transactionSequenceNumber?: number; + clusterID: number; + commandIdentifier: number; +} + +class EZSPAdapter extends Adapter { + private driver: Driver; + private port: SerialPortOptions; + private waitress: Waitress; + + public constructor(networkOptions: NetworkOptions, + serialPortOptions: SerialPortOptions, backupPath: string, adapterOptions: AdapterOptions) { + super(networkOptions, serialPortOptions, backupPath, adapterOptions); + this.port = serialPortOptions; + this.waitress = new Waitress( + this.waitressValidator, this.waitressTimeoutFormatter + ); + this.driver = new Driver(); + this.driver.on('deviceJoined', this.handleDeviceJoin.bind(this)); + this.driver.on('deviceLeft', this.handleDeviceLeft.bind(this)); + this.driver.on('incomingMessage', this.processMessage.bind(this)); + } + + private async processMessage(frame: any) { + // todo + debug(`processMessage: ${JSON.stringify(frame)}`); + if (!frame.senderEui64) { + frame.senderEui64 = await this.driver.networkIdToEUI64(frame.sender) + } + if (frame.apsFrame.profileId == 0) { + if ( + frame.apsFrame.clusterId == EmberZDOCmd.Device_annce && + frame.apsFrame.destinationEndpoint == 0) { + let nwk, rst, ieee; + [nwk, rst] = uint16_t.deserialize(uint16_t, frame.message.slice(1)); + [ieee, rst] = EmberEUI64.deserialize(EmberEUI64, rst as Buffer); + ieee = new EmberEUI64(ieee); + debug("ZDO Device announce: %s, %s", nwk, ieee.toString()); + this.handleDeviceJoin([nwk, ieee]); + } + } else if (frame.apsFrame.profileId == 260) { + try { + const payload: Events.ZclDataPayload = { + frame: ZclFrame.fromBuffer(frame.apsFrame.clusterId, frame.message), + address: frame.sender, + endpoint: frame.apsFrame.sourceEndpoint, + linkquality: frame.lqi, + groupID: frame.apsFrame.groupId, + }; + + this.waitress.resolve(payload); + this.emit(Events.Events.zclData, payload); + } catch (error) { + const payload: Events.RawDataPayload = { + clusterID: frame.apsFrame.clusterId, + data: frame.message, + address: frame.sender, + endpoint: frame.apsFrame.sourceEndpoint, + linkquality: frame.lqi, + groupID: frame.apsFrame.groupId, + }; + + this.emit(Events.Events.rawData, payload); + } + } + this.emit('event', frame); + } + + private async handleDeviceJoin(arr: any[]) { + // todo + let [nwk, ieee] = arr; + debug('Device join request received: %s %s', nwk, ieee.toString('hex')); + const payload: Events.DeviceJoinedPayload = { + networkAddress: nwk, + ieeeAddr: `0x${ieee.toString('hex')}`, + }; + + if (nwk == 0) { + const nd = await this.nodeDescriptor(nwk); + } else { + this.emit(Events.Events.deviceJoined, payload); + } + } + + private handleDeviceLeft(arr: any[]) { + // todo + let [nwk, ieee] = arr; + debug('Device left network request received: %s %s', nwk, ieee); + + const payload: Events.DeviceLeavePayload = { + networkAddress: nwk, + ieeeAddr: `0x${ieee.toString('hex')}`, + }; + this.emit(Events.Events.deviceLeave, payload); + } + + /** + * Adapter methods + */ + public async start(): Promise { + await this.driver.startup(this.port.path, { + baudRate: this.port.baudRate || 115200, + parity: 'none', + stopBits: 1, + xon: true, + xoff: true + }, this.networkOptions); + return Promise.resolve("resumed"); + } + + public async stop(): Promise { + await this.driver.stop(); + } + + public static async isValidPath(path: string): Promise { + // todo + return false; + } + + public static async autoDetectPath(): Promise { + // todo + return ''; + } + + public async getCoordinator(): Promise { + return this.driver.queue.execute(async () => { + const networkAddress = 0x0000; + const message = await this.driver.zdoRequest( + networkAddress, EmberZDOCmd.Active_EP_req, EmberZDOCmd.Active_EP_rsp, + networkAddress + ); + const activeEndpoints = [...message[3]]; + + const endpoints = []; + for (const endpoint of activeEndpoints) { + const descriptor = await this.driver.zdoRequest( + networkAddress, EmberZDOCmd.Simple_Desc_req, EmberZDOCmd.Simple_Desc_rsp, + networkAddress, endpoint + ); + endpoints.push({ + profileID: descriptor[4].profileid, + ID: descriptor[4].endpoint, + deviceID: descriptor[4].deviceid, + inputClusters: descriptor[4].inclusterlist, + outputClusters: descriptor[4].outclusterlist, + }); + } + + return { + networkAddress: networkAddress, + manufacturerID: 0, + ieeeAddr: `0x${this.driver.ieee.toString()}`, + endpoints, + }; + }); + } + + public async permitJoin(seconds: number, networkAddress: number): Promise { + // todo + await this.driver.permitJoining(seconds); + return Promise.resolve(); + } + + public async getCoordinatorVersion(): Promise { + // todo + return {type: 'EmberZNet', meta: this.driver.version}; + } + + public async reset(type: 'soft' | 'hard'): Promise { + return Promise.reject(); + } + + public async supportsLED(): Promise { + return false; + } + + public async setLED(enabled: boolean): Promise { + return Promise.reject(); + } + + public async lqi(networkAddress: number): Promise { + return this.driver.queue.execute(async (): Promise => { + const neighbors: LQINeighbor[] = []; + + const request = async (startIndex: number): Promise => { + const result = await this.driver.zdoRequest( + networkAddress, EmberZDOCmd.Mgmt_Lqi_req, EmberZDOCmd.Mgmt_Lqi_rsp, + startIndex + ); + if (result[1] !== EmberStatus.SUCCESS) { + throw new Error(`LQI for '${networkAddress}' failed`); + } + + return result; + }; + + // eslint-disable-next-line + const add = (list: any) => { + for (const entry of list) { + neighbors.push({ + linkquality: entry.lqi, + networkAddress: entry.nodeid, + ieeeAddr: `0x${new EmberEUI64(entry.ieee).toString()}`, + relationship: (entry.packed >> 4) & 0x7, + depth: entry.depth, + }); + } + }; + + let response = await request(0); + add(response[2].neighbors); + const size = response[2].entries; + let nextStartIndex = response[2].neighbors.length; + + while (neighbors.length < size) { + response = await request(nextStartIndex); + add(response[2].neighbors); + nextStartIndex += response[2].neighbors.length; + } + + return {neighbors}; + }, networkAddress); + } + + public async routingTable(networkAddress: number): Promise { + // todo + return Promise.reject(); + } + + public async nodeDescriptor(networkAddress: number): Promise { + try { + debug(`Requesting 'Node Descriptor' for '${networkAddress}'`); + const result = await this.nodeDescriptorInternal(networkAddress); + return result; + } catch (error) { + debug(`Node descriptor request for '${networkAddress}' failed (${error}), retry`); + throw error; + } + } + + private async nodeDescriptorInternal(networkAddress: number): Promise { + return this.driver.queue.execute(async () => { + const descriptor = await this.driver.zdoRequest( + networkAddress, EmberZDOCmd.Node_Desc_req, EmberZDOCmd.Node_Desc_rsp, + networkAddress + ); + return { + manufacturerCode: descriptor[2].manufacturer_code, + type: (descriptor[1] == 0) ? 'Coordinator' : 'EndDevice' + }; + }); + } + + public async activeEndpoints(networkAddress: number): Promise { + debug(`Requesting 'Active endpoints' for '${networkAddress}'`); + return this.driver.queue.execute(async () => { + const endpoints = await this.driver.zdoRequest( + networkAddress, EmberZDOCmd.Active_EP_req, EmberZDOCmd.Active_EP_rsp, + networkAddress + ); + return {endpoints: [...endpoints[3]]}; + }, networkAddress); + } + + public async simpleDescriptor(networkAddress: number, endpointID: number): Promise { + // todo + debug(`Requesting 'Simple Descriptor' for '${networkAddress}' endpoint ${endpointID}`); + return this.driver.queue.execute(async () => { + const descriptor = await this.driver.zdoRequest( + networkAddress, EmberZDOCmd.Simple_Desc_req, EmberZDOCmd.Simple_Desc_rsp, + networkAddress, endpointID + ); + return { + profileID: descriptor[4].profileid, + endpointID: descriptor[4].endpoint, + deviceID: descriptor[4].deviceid, + inputClusters: descriptor[4].inclusterlist, + outputClusters: descriptor[4].outclusterlist, + }; + }, networkAddress); + } + + public async sendZclFrameToEndpoint( + ieeeAddr: string, networkAddress: number, endpoint: number, zclFrame: ZclFrame, timeout: number, + disableResponse: boolean, disableRecovery: boolean, sourceEndpoint?: number, + ): Promise { + return this.driver.queue.execute(async () => { + return this.sendZclFrameToEndpointInternal( + ieeeAddr, networkAddress, endpoint, sourceEndpoint || 1, zclFrame, timeout, disableResponse, + disableRecovery, 0, 0, false, false, false, null + ); + }, networkAddress); + } + + private async sendZclFrameToEndpointInternal( + ieeeAddr: string, networkAddress: number, endpoint: number, sourceEndpoint: number, zclFrame: ZclFrame, + timeout: number, disableResponse: boolean, disableRecovery: boolean, responseAttempt: number, + dataRequestAttempt: number, checkedNetworkAddress: boolean, discoveredRoute: boolean, assocRemove: boolean, + assocRestore: { ieeeadr: string, nwkaddr: number, noderelation: number } + ): Promise { + debug('sendZclFrameToEndpointInternal %s:%i/%i (%i,%i,%i)', + ieeeAddr, networkAddress, endpoint, responseAttempt, dataRequestAttempt, this.driver.queue.count()); + let response = null; + const command = zclFrame.getCommand(); + if (command.hasOwnProperty('response') && disableResponse === false) { + response = this.waitForInternal( + networkAddress, endpoint, + zclFrame.Header.transactionSequenceNumber, zclFrame.Cluster.ID, command.response, timeout + ); + } else if (!zclFrame.Header.frameControl.disableDefaultResponse) { + response = this.waitForInternal( + networkAddress, endpoint, + zclFrame.Header.transactionSequenceNumber, zclFrame.Cluster.ID, Foundation.defaultRsp.ID, + timeout, + ); + } + + const frame = this.driver.makeApsFrame(zclFrame.Cluster.ID); + frame.profileId = 0x0104; + frame.sourceEndpoint = sourceEndpoint || 0x01; + frame.destinationEndpoint = endpoint; + frame.groupId = 0; + frame.options = EmberApsOption.APS_OPTION_ENABLE_ROUTE_DISCOVERY | EmberApsOption.APS_OPTION_RETRY; + + const dataConfirmResult = await this.driver.request(networkAddress, frame, zclFrame.toBuffer()); + + if (response !== null) { + try { + const result = await response.start().promise; + return result; + } catch (error) { + debug('Response timeout (%s:%d,%d)', ieeeAddr, networkAddress, responseAttempt); + if (responseAttempt < 1 && !disableRecovery) { + return this.sendZclFrameToEndpointInternal( + ieeeAddr, networkAddress, endpoint, sourceEndpoint, zclFrame, timeout, disableResponse, + disableRecovery, responseAttempt + 1, dataRequestAttempt, checkedNetworkAddress, + discoveredRoute, assocRemove, assocRestore, + ); + } else { + throw error; + } + } + } else { + return null; + } + } + + public async sendZclFrameToGroup(groupID: number, zclFrame: ZclFrame): Promise { + // todo + return Promise.reject(); + } + + public async sendZclFrameToAll(endpoint: number, zclFrame: ZclFrame, sourceEndpoint: number): Promise { + // todo + return Promise.resolve(); + } + + public async bind( + destinationNetworkAddress: number, sourceIeeeAddress: string, sourceEndpoint: number, + clusterID: number, destinationAddressOrGroup: string | number, type: 'endpoint' | 'group', + destinationEndpoint?: number + ): Promise { + return this.driver.queue.execute(async () => { + const ieee = new EmberEUI64(sourceIeeeAddress); + const ieeeDst = new EmberEUI64(destinationAddressOrGroup as string); + await this.driver.zdoRequest( + destinationNetworkAddress, EmberZDOCmd.Bind_req, EmberZDOCmd.Bind_rsp, + ieee, sourceEndpoint, clusterID, + {addrmode: 0x03, ieee: ieeeDst, endpoint: destinationEndpoint} + ); + }, destinationNetworkAddress); + } + + public async unbind( + destinationNetworkAddress: number, sourceIeeeAddress: string, sourceEndpoint: number, + clusterID: number, destinationAddressOrGroup: string | number, type: 'endpoint' | 'group', + destinationEndpoint: number + ): Promise { + return this.driver.queue.execute(async () => { + const ieee = new EmberEUI64(sourceIeeeAddress); + const ieeeDst = new EmberEUI64(destinationAddressOrGroup as string); + await this.driver.zdoRequest( + destinationNetworkAddress, EmberZDOCmd.Unbind_req, EmberZDOCmd.Unbind_rsp, + ieee, sourceEndpoint, clusterID, + {addrmode: 0x03, ieee: ieeeDst, endpoint: destinationEndpoint} + ); + }, destinationNetworkAddress); + } + + public removeDevice(networkAddress: number, ieeeAddr: string): Promise { + return this.driver.queue.execute(async () => { + const ieee = new EmberEUI64(ieeeAddr); + await this.driver.zdoRequest( + networkAddress, EmberZDOCmd.Mgmt_Leave_req, EmberZDOCmd.Mgmt_Leave_rsp, + ieee, 0x00 + ); + }, networkAddress); + } + + public async getNetworkParameters(): Promise { + return { + panID: this.driver.networkParams.panId, + extendedPanID: this.driver.networkParams.extendedPanId[0], + channel: this.driver.networkParams.radioChannel + }; + } + + public async supportsBackup(): Promise { + //todo + return false; + } + + public async backup(): Promise { + // todo + return Promise.reject(); + } + + public async restoreChannelInterPAN(): Promise { + // todo + throw new Error("not supported"); + } + + public async sendZclFrameInterPANToIeeeAddr(zclFrame: ZclFrame, ieeeAddr: string): Promise { + // todo + throw new Error("not supported"); + } + + public async sendZclFrameInterPANBroadcast( + zclFrame: ZclFrame, timeout: number + ): Promise { + // todo + throw new Error("not supported"); + } + + public async sendZclFrameInterPANBroadcastWithResponse( + zclFrame: ZclFrame, timeout: number + ): Promise { + throw new Error("not supported"); + } + + public async sendZclFrameInterPANIeeeAddr(zclFrame: ZclFrame, ieeeAddr: any): Promise { + throw new Error("not supported"); + } + + public async setTransmitPower(value: number): Promise { + // todo + } + + public async setChannelInterPAN(channel: number): Promise { + //todo + } + + private waitForInternal( + networkAddress: number, endpoint: number, transactionSequenceNumber: number, clusterID: number, commandIdentifier: number, timeout: number, + ): { start: () => { promise: Promise }; cancel: () => void } { + const payload = { + address: networkAddress, endpoint, clusterID, commandIdentifier, + transactionSequenceNumber, + }; + + const waiter = this.waitress.waitFor(payload, timeout); + const cancel = (): void => this.waitress.remove(waiter.ID); + return {start: waiter.start, cancel}; + } + + public waitFor( + networkAddress: number, endpoint: number, frameType: FrameType, direction: Direction, + transactionSequenceNumber: number, clusterID: number, commandIdentifier: number, timeout: number, + ): { promise: Promise; cancel: () => void } { + const waiter = this.waitForInternal( + networkAddress, endpoint, transactionSequenceNumber, clusterID, + commandIdentifier, timeout, + ); + + return {cancel: waiter.cancel, promise: waiter.start().promise}; + } + + private waitressTimeoutFormatter(matcher: WaitressMatcher, timeout: number): string { + return `Timeout - ${matcher.address} - ${matcher.endpoint}` + + ` - ${matcher.transactionSequenceNumber} - ${matcher.clusterID}` + + ` - ${matcher.commandIdentifier} after ${timeout}ms`; + } + + private waitressValidator(payload: Events.ZclDataPayload, matcher: WaitressMatcher): boolean { + const transactionSequenceNumber = payload.frame.Header.transactionSequenceNumber; + return (!matcher.address || payload.address === matcher.address) && + payload.endpoint === matcher.endpoint && + (!matcher.transactionSequenceNumber || transactionSequenceNumber === matcher.transactionSequenceNumber) && + payload.frame.Cluster.ID === matcher.clusterID && + matcher.commandIdentifier === payload.frame.Header.commandIdentifier; + } +} + + +export default EZSPAdapter; diff --git a/src/adapter/ezsp/adapter/index.ts b/src/adapter/ezsp/adapter/index.ts new file mode 100644 index 0000000000..7be4f6b70f --- /dev/null +++ b/src/adapter/ezsp/adapter/index.ts @@ -0,0 +1,7 @@ +/* istanbul ignore file */ +/* eslint-disable */ +import EZSPAdapter from './ezspAdapter'; + +export { + EZSPAdapter, +}; diff --git a/src/adapter/ezsp/driver/commands.ts b/src/adapter/ezsp/driver/commands.ts new file mode 100644 index 0000000000..f7bf40444b --- /dev/null +++ b/src/adapter/ezsp/driver/commands.ts @@ -0,0 +1,775 @@ +/* istanbul ignore file */ +import {/* Basic Types */ + int8s, + uint8_t, + uint16_t, + uint32_t, + LVBytes, + fixed_list, + WordList, + + /* Named Types */ + EmberRf4ceTxOption, + EmberRf4ceNodeCapabilities, + EmberNodeId, + EmberPanId, + EmberEUI64, + EmberLibraryStatus, + SecureEzspSecurityType, + SecureEzspSecurityLevel, + EmberGpSecurityLevel, + EmberGpKeyType, + SecureEzspRandomNumber, + Bool, + EzspConfigId, + EzspValueId, + EzspExtendedValueId, + EzspPolicyId, + EzspDecisionId, + EzspMfgTokenId, + EzspStatus, + EmberStatus, + EmberEventUnits, + EmberNodeType, + EmberNetworkStatus, + EmberIncomingMessageType, + EmberOutgoingMessageType, + EmberMacPassthroughType, + EzspNetworkScanType, + EmberJoinDecision, + EmberKeyType, + EmberDeviceUpdate, + EmberKeyStatus, + EmberCounterType, + EzspZllNetworkOperation, + + /* Structs */ + EmberNetworkParameters, + EmberZigbeeNetwork, + EmberApsFrame, + EmberBindingTableEntry, + EmberMulticastTableEntry, + EmberKeyData, + EmberCertificateData, + EmberPublicKeyData, + EmberPrivateKeyData, + EmberSmacData, + EmberSignatureData, + EmberCertificate283k1Data, + EmberPublicKey283k1Data, + EmberPrivateKey283k1Data, + EmberSignature283k1Data, + EmberMessageDigest, + EmberAesMmoHashContext, + EmberNeighborTableEntry, + EmberRouteTableEntry, + EmberInitialSecurityState, + EmberCurrentSecurityState, + EmberKeyStruct, + EmberNetworkInitStruct, + EmberZllNetwork, + EmberZllInitialSecurityState, + EmberZllDeviceInfoRecord, + EmberZllAddressAssignment, + EmberTokTypeStackZllData, + EmberTokTypeStackZllSecurity, + EmberRf4ceVendorInfo, + EmberRf4ceApplicationInfo, + EmberRf4cePairingTableEntry, + EmberGpAddress, + EmberGpSinkListEntry, + EmberNodeDescriptor, + EmberSimpleDescriptor, + EmberMultiAddress, + EmberNeighbors, +} from './types'; + +/* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ +export const COMMANDS: { [key: string]: [number, any[], any[]] } = { + "version": [0, [uint8_t], + [uint8_t, uint8_t, uint16_t] + ], + "getConfigurationValue": [82, [EzspConfigId], + [EzspStatus, uint16_t] + ], + "setConfigurationValue": [83, [EzspConfigId, uint16_t], + [EzspStatus] + ], + "addEndpoint": [0x0002, [ + uint8_t, + uint16_t, + uint16_t, + uint8_t, + uint8_t, + uint8_t, + WordList, + WordList, + ], + [EzspStatus], + ], + "setPolicy": [85, [EzspPolicyId, EzspDecisionId], + [EzspStatus] + ], + "getPolicy": [86, [EzspPolicyId], + [EzspStatus, EzspDecisionId] + ], + "getValue": [170, [EzspValueId], + [EzspStatus, LVBytes] + ], + "getExtendedValue": [3, [EzspExtendedValueId, uint32_t], + [EzspStatus, LVBytes] + ], + "setValue": [171, [EzspValueId, LVBytes], + [EzspStatus] + ], + "setGpioCurrentConfiguration": [172, [uint8_t, uint8_t, uint8_t], + [EzspStatus] + ], + "setGpioPowerUpDownConfiguration": [173, [uint8_t, uint8_t, uint8_t, uint8_t, uint8_t], + [EzspStatus] + ], + "setGpioRadioPowerMask": [174, [uint32_t], + [] + ], + "setCtune": [245, [uint16_t], + [] + ], + "getCtune": [246, [], + [uint16_t] + ], + "setChannelMap": [247, [uint8_t, uint8_t], + [] + ], + "nop": [5, [], + [] + ], + "echo": [129, [LVBytes], + [LVBytes] + ], + "invalidCommand": [88, [], + [EzspStatus] + ], + "callback": [6, [], + [] + ], + "noCallbacks": [7, [], + [] + ], + "setToken": [9, [uint8_t, fixed_list(8, uint8_t)], + [EmberStatus] + ], + "getToken": [10, [uint8_t], + [EmberStatus, fixed_list(8, uint8_t)] + ], + "getMfgToken": [11, [EzspMfgTokenId], + [LVBytes] + ], + "setMfgToken": [12, [EzspMfgTokenId, LVBytes], + [EmberStatus] + ], + "stackTokenChangedHandler": [13, [], + [uint16_t] + ], + "getRandomNumber": [73, [], + [EmberStatus, uint16_t] + ], + "setTimer": [14, [uint8_t, uint16_t, EmberEventUnits, Bool], + [EmberStatus] + ], + "getTimer": [78, [uint8_t], + [uint16_t, EmberEventUnits, Bool] + ], + "timerHandler": [15, [], + [uint8_t] + ], + "debugWrite": [18, [Bool, LVBytes], + [EmberStatus] + ], + "readAndClearCounters": [101, [], + [fixed_list(EmberCounterType.COUNTER_TYPE_COUNT, uint16_t)] + ], + "readCounters": [241, [], + [fixed_list(EmberCounterType.COUNTER_TYPE_COUNT, uint16_t)] + ], + "counterRolloverHandler": [242, [], + [EmberCounterType] + ], + "delayTest": [157, [uint16_t], + [] + ], + "getLibraryStatus": [1, [uint8_t], + [EmberLibraryStatus] + ], + "getXncpInfo": [19, [], + [EmberStatus, uint16_t, uint16_t] + ], + "customFrame": [71, [LVBytes], + [EmberStatus, LVBytes] + ], + "customFrameHandler": [84, [], + [LVBytes] + ], + "getEui64": [38, [], + [EmberEUI64] + ], + "getNodeId": [39, [], + [EmberNodeId] + ], + "networkInit": [23, [], + [EmberStatus] + ], + "setManufacturerCode": [21, [uint16_t], + [] + ], + "setPowerDescriptor": [22, [uint16_t], + [] + ], + "networkInitExtended": [112, [EmberNetworkInitStruct], + [EmberStatus] + ], + "networkState": [24, [], + [EmberNetworkStatus] + ], + "stackStatusHandler": [25, [], + [EmberStatus] + ], + "startScan": [26, [EzspNetworkScanType, uint32_t, uint8_t], + [EmberStatus] + ], + "energyScanResultHandler": [72, [], + [uint8_t, int8s] + ], + "networkFoundHandler": [27, [], + [EmberZigbeeNetwork, uint8_t, int8s] + ], + "scanCompleteHandler": [28, [], + [uint8_t, EmberStatus] + ], + "stopScan": [29, [], + [EmberStatus] + ], + "formNetwork": [30, [EmberNetworkParameters], + [EmberStatus] + ], + "joinNetwork": [31, [EmberNodeType, EmberNetworkParameters], + [EmberStatus] + ], + "leaveNetwork": [32, [], + [EmberStatus] + ], + "findAndRejoinNetwork": [33, [Bool, uint32_t], + [EmberStatus] + ], + "permitJoining": [34, [uint8_t], + [EmberStatus] + ], + "childJoinHandler": [35, [], + [uint8_t, Bool, EmberNodeId, EmberEUI64, EmberNodeType] + ], + "energyScanRequest": [156, [EmberNodeId, uint32_t, uint8_t, uint16_t], + [EmberStatus] + ], + "getNetworkParameters": [40, [], + [EmberStatus, EmberNodeType, EmberNetworkParameters] + ], + "getParentChildParameters": [41, [], + [uint8_t, EmberEUI64, EmberNodeId] + ], + "getChildData": [74, [uint8_t], + [EmberStatus, EmberNodeId, EmberEUI64, EmberNodeType] + ], + "getNeighbor": [121, [uint8_t], + [EmberStatus, EmberNeighborTableEntry] + ], + "neighborCount": [122, [], + [uint8_t] + ], + "getRouteTableEntry": [123, [uint8_t], + [EmberStatus, EmberRouteTableEntry] + ], + "setRadioPower": [153, [int8s], + [EmberStatus] + ], + "setRadioChannel": [154, [uint8_t], + [EmberStatus] + ], + "setConcentrator": [16, [Bool, uint16_t, uint16_t, uint16_t, uint8_t, uint8_t, uint8_t], + [EmberStatus] + ], + "clearBindingTable": [42, [], + [EmberStatus] + ], + "setBinding": [43, [uint8_t, EmberBindingTableEntry], + [EmberStatus] + ], + "getBinding": [44, [uint8_t], + [EmberStatus, EmberBindingTableEntry] + ], + "deleteBinding": [45, [uint8_t], + [EmberStatus] + ], + "bindingIsActive": [46, [uint8_t], + [Bool] + ], + "getBindingRemoteNodeId": [47, [uint8_t], + [EmberNodeId] + ], + "setBindingRemoteNodeId": [48, [uint8_t], + [] + ], + "remoteSetBindingHandler": [49, [], + [EmberBindingTableEntry] + ], + "remoteDeleteBindingHandler": [50, [], + [uint8_t, EmberStatus] + ], + "maximumPayloadLength": [51, [], + [uint8_t] + ], + "sendUnicast": [52, [EmberOutgoingMessageType, EmberNodeId, EmberApsFrame, uint8_t, LVBytes], + [EmberStatus, uint8_t] + ], + "sendBroadcast": [54, [EmberNodeId, EmberApsFrame, uint8_t, uint8_t, LVBytes], + [EmberStatus, uint8_t] + ], + "proxyBroadcast": [55, [EmberNodeId, EmberNodeId, uint8_t, EmberApsFrame, uint8_t, uint8_t, LVBytes], + [EmberStatus, uint8_t] + ], + "sendMulticast": [56, [EmberApsFrame, uint8_t, uint8_t, uint8_t, LVBytes], + [EmberStatus, uint8_t] + ], + "sendReply": [57, [EmberNodeId, EmberApsFrame, LVBytes], + [EmberStatus] + ], + "messageSentHandler": [63, [], + [EmberOutgoingMessageType, uint16_t, EmberApsFrame, uint8_t, EmberStatus, LVBytes] + ], + "sendManyToOneRouteRequest": [65, [uint16_t, uint8_t], + [EmberStatus] + ], + "pollForData": [66, [uint16_t, EmberEventUnits, uint8_t], + [EmberStatus] + ], + "pollCompleteHandler": [67, [], + [EmberStatus] + ], + "pollHandler": [68, [], + [EmberNodeId] + ], + "incomingSenderEui64Handler": [98, [], + [EmberEUI64] + ], + "incomingMessageHandler": [69, [], + [EmberIncomingMessageType, EmberApsFrame, uint8_t, int8s, EmberNodeId, uint8_t, uint8_t, LVBytes] + ], + "incomingRouteRecordHandler": [89, [], + [EmberNodeId, EmberEUI64, uint8_t, int8s, LVBytes] + ], + "incomingManyToOneRouteRequestHandler": [125, [], + [EmberNodeId, EmberEUI64, uint8_t] + ], + "incomingRouteErrorHandler": [128, [], + [EmberStatus, EmberNodeId] + ], + "addressTableEntryIsActive": [91, [uint8_t], + [Bool] + ], + "setAddressTableRemoteEui64": [92, [uint8_t, EmberEUI64], + [EmberStatus] + ], + "setAddressTableRemoteNodeId": [93, [uint8_t, EmberNodeId], + [] + ], + "getAddressTableRemoteEui64": [94, [uint8_t], + [EmberEUI64] + ], + "getAddressTableRemoteNodeId": [95, [uint8_t], + [EmberNodeId] + ], + "setExtendedTimeout": [126, [EmberEUI64, Bool], + [] + ], + "getExtendedTimeout": [127, [EmberEUI64], + [Bool] + ], + "replaceAddressTableEntry": [130, [uint8_t, EmberEUI64, EmberNodeId, Bool], + [EmberStatus, EmberEUI64, EmberNodeId, Bool] + ], + "lookupNodeIdByEui64": [96, [EmberEUI64], + [EmberNodeId] + ], + "lookupEui64ByNodeId": [97, [EmberNodeId], + [EmberStatus, EmberEUI64] + ], + "getMulticastTableEntry": [99, [uint8_t], + [EmberStatus, EmberMulticastTableEntry] + ], + "setMulticastTableEntry": [100, [uint8_t, EmberMulticastTableEntry], + [EmberStatus] + ], + "idConflictHandler": [124, [], + [EmberNodeId] + ], + "sendRawMessage": [150, [LVBytes], + [EmberStatus] + ], + "macPassthroughMessageHandler": [151, [], + [EmberMacPassthroughType, uint8_t, int8s, LVBytes] + ], + "macFilterMatchMessageHandler": [70, [], + [uint8_t, EmberMacPassthroughType, uint8_t, int8s, LVBytes] + ], + "rawTransmitCompleteHandler": [152, [], + [EmberStatus] + ], + "setInitialSecurityState": [104, [EmberInitialSecurityState], + [EmberStatus] + ], + "getCurrentSecurityState": [105, [], + [EmberStatus, EmberCurrentSecurityState] + ], + "getKey": [106, [EmberKeyType], + [EmberStatus, EmberKeyStruct] + ], + "switchNetworkKeyHandler": [110, [], + [uint8_t] + ], + "getKeyTableEntry": [113, [uint8_t], + [EmberStatus, EmberKeyStruct] + ], + "setKeyTableEntry": [114, [uint8_t, EmberEUI64, Bool, EmberKeyData], + [EmberStatus] + ], + "findKeyTableEntry": [117, [EmberEUI64, Bool], + [uint8_t] + ], + "addOrUpdateKeyTableEntry": [102, [EmberEUI64, Bool, EmberKeyData], + [EmberStatus] + ], + "eraseKeyTableEntry": [118, [uint8_t], + [EmberStatus] + ], + "clearKeyTable": [177, [], + [EmberStatus] + ], + "requestLinkKey": [20, [EmberEUI64], + [EmberStatus] + ], + "zigbeeKeyEstablishmentHandler": [155, [], + [EmberEUI64, EmberKeyStatus] + ], + "addTransientLinkKey": [175, [EmberEUI64, EmberKeyData], + [EmberStatus] + ], + "clearTransientLinkKeys": [107, [], + [] + ], + "setSecurityKey": [202, [EmberKeyData, SecureEzspSecurityType], + [EzspStatus] + ], + "setSecurityParameters": [203, [SecureEzspSecurityLevel, SecureEzspRandomNumber], + [EzspStatus, SecureEzspRandomNumber] + ], + "resetToFactoryDefaults": [204, [], + [EzspStatus] + ], + "getSecurityKeyStatus": [205, [], + [EzspStatus, SecureEzspSecurityType] + ], + "trustCenterJoinHandler": [36, [], + [EmberNodeId, EmberEUI64, EmberDeviceUpdate, EmberJoinDecision, EmberNodeId] + ], + "broadcastNextNetworkKey": [115, [EmberKeyData], + [EmberStatus] + ], + "broadcastNetworkKeySwitch": [116, [], + [EmberStatus] + ], + "becomeTrustCenter": [119, [EmberKeyData], + [EmberStatus] + ], + "aesMmoHash": [111, [EmberAesMmoHashContext, Bool, LVBytes], + [EmberStatus, EmberAesMmoHashContext] + ], + "removeDevice": [168, [EmberNodeId, EmberEUI64, EmberEUI64], + [EmberStatus] + ], + "unicastNwkKeyUpdate": [169, [EmberNodeId, EmberEUI64, EmberKeyData], + [EmberStatus] + ], + "generateCbkeKeys": [164, [], + [EmberStatus] + ], + "generateCbkeKeysHandler": [158, [], + [EmberStatus, EmberPublicKeyData] + ], + "calculateSmacs": [159, [Bool, EmberCertificateData, EmberPublicKeyData], + [EmberStatus] + ], + "calculateSmacsHandler": [160, [], + [EmberStatus, EmberSmacData, EmberSmacData] + ], + "generateCbkeKeys283k1": [232, [], + [EmberStatus] + ], + "generateCbkeKeysHandler283k1": [233, [], + [EmberStatus, EmberPublicKey283k1Data] + ], + "calculateSmacs283k1": [234, [Bool, EmberCertificate283k1Data, EmberPublicKey283k1Data], + [EmberStatus] + ], + "calculateSmacsHandler283k1": [235, [], + [EmberStatus, EmberSmacData, EmberSmacData] + ], + "clearTemporaryDataMaybeStoreLinkKey": [161, [Bool], + [EmberStatus] + ], + "clearTemporaryDataMaybeStoreLinkKey283k1": [238, [Bool], + [EmberStatus] + ], + "getCertificate": [165, [], + [EmberStatus, EmberCertificateData] + ], + "getCertificate283k1": [236, [], + [EmberStatus, EmberCertificate283k1Data] + ], + "dsaSign": [166, [LVBytes], + [EmberStatus] + ], + "dsaSignHandler": [167, [], + [EmberStatus, LVBytes] + ], + "dsaVerify": [163, [EmberMessageDigest, EmberCertificateData, EmberSignatureData], + [EmberStatus] + ], + "dsaVerifyHandler": [120, [], + [EmberStatus] + ], + "dsaVerify283k1": [176, [EmberMessageDigest, EmberCertificate283k1Data, EmberSignature283k1Data], + [EmberStatus] + ], + "setPreinstalledCbkeData": [162, [EmberPublicKeyData, EmberCertificateData, EmberPrivateKeyData], + [EmberStatus] + ], + "setPreinstalledCbkeData283k1": [237, + [EmberPublicKey283k1Data, EmberCertificate283k1Data, EmberPrivateKey283k1Data], + [EmberStatus] + ], + "mfglibStart": [131, [Bool], + [EmberStatus] + ], + "mfglibEnd": [132, [], + [EmberStatus] + ], + "mfglibStartTone": [133, [], + [EmberStatus] + ], + "mfglibStopTone": [134, [], + [EmberStatus] + ], + "mfglibStartStream": [135, [], + [EmberStatus] + ], + "mfglibStopStream": [136, [], + [EmberStatus] + ], + "mfglibSendPacket": [137, [LVBytes], + [EmberStatus] + ], + "mfglibSetChannel": [138, [uint8_t], + [EmberStatus] + ], + "mfglibGetChannel": [139, [], + [uint8_t] + ], + "mfglibSetPower": [140, [uint16_t, int8s], + [EmberStatus] + ], + "mfglibGetPower": [141, [], + [int8s] + ], + "mfglibRxHandler": [142, [], + [uint8_t, int8s, LVBytes] + ], + "launchStandaloneBootloader": [143, [uint8_t], + [EmberStatus] + ], + "sendBootloadMessage": [144, [Bool, EmberEUI64, LVBytes], + [EmberStatus] + ], + "getStandaloneBootloaderVersionPlatMicroPhy": [145, [], + [uint16_t, uint8_t, uint8_t, uint8_t] + ], + "incomingBootloadMessageHandler": [146, [], + [EmberEUI64, uint8_t, int8s, LVBytes] + ], + "bootloadTransmitCompleteHandler": [147, [], + [EmberStatus, LVBytes] + ], + "aesEncrypt": [148, [fixed_list(16, uint8_t), fixed_list(16, uint8_t)], + [fixed_list(16, uint8_t)] + ], + "overrideCurrentChannel": [149, [uint8_t], + [EmberStatus] + ], + "zllNetworkOps": [178, [EmberZllNetwork, EzspZllNetworkOperation, int8s], + [EmberStatus] + ], + "zllSetInitialSecurityState": [179, [EmberKeyData, EmberZllInitialSecurityState], + [EmberStatus] + ], + "zllStartScan": [180, [uint32_t, int8s, EmberNodeType], + [EmberStatus] + ], + "zllSetRxOnWhenIdle": [181, [uint16_t], + [EmberStatus] + ], + "zllNetworkFoundHandler": [182, [], + [EmberZllNetwork, Bool, EmberZllDeviceInfoRecord, uint8_t, int8s] + ], + "zllScanCompleteHandler": [183, [], + [EmberStatus] + ], + "zllAddressAssignmentHandler": [184, [], + [EmberZllAddressAssignment, uint8_t, int8s] + ], + "setLogicalAndRadioChannel": [185, [uint8_t], + [EmberStatus] + ], + "getLogicalChannel": [186, [], + [uint8_t] + ], + "zllTouchLinkTargetHandler": [187, [], + [EmberZllNetwork] + ], + "zllGetTokens": [188, [], + [EmberTokTypeStackZllData, EmberTokTypeStackZllSecurity] + ], + "zllSetDataToken": [189, [EmberTokTypeStackZllData], + [] + ], + "zllSetNonZllNetwork": [191, [], + [] + ], + "isZllNetwork": [190, [], + [Bool] + ], + "rf4ceSetPairingTableEntry": [208, [uint8_t, EmberRf4cePairingTableEntry], + [EmberStatus] + ], + "rf4ceGetPairingTableEntry": [209, [uint8_t], + [EmberStatus, EmberRf4cePairingTableEntry] + ], + "rf4ceDeletePairingTableEntry": [210, [uint8_t], + [EmberStatus] + ], + "rf4ceKeyUpdate": [211, [uint8_t, EmberKeyData], + [EmberStatus] + ], + "rf4ceSend": [212, [uint8_t, uint8_t, uint16_t, EmberRf4ceTxOption, uint8_t, LVBytes], + [EmberStatus] + ], + "rf4ceIncomingMessageHandler": [213, [], + [uint8_t, uint8_t, uint16_t, EmberRf4ceTxOption, LVBytes] + ], + "rf4ceMessageSentHandler": [214, [], + [EmberStatus, uint8_t, EmberRf4ceTxOption, uint8_t, uint16_t, uint8_t, LVBytes] + ], + "rf4ceStart": [215, [EmberRf4ceNodeCapabilities, EmberRf4ceVendorInfo, int8s], + [EmberStatus] + ], + "rf4ceStop": [216, [], + [EmberStatus] + ], + "rf4ceDiscovery": [217, [EmberPanId, EmberNodeId, uint8_t, uint16_t, LVBytes], + [EmberStatus] + ], + "rf4ceDiscoveryCompleteHandler": [218, [], + [EmberStatus] + ], + "rf4ceDiscoveryRequestHandler": [219, [], + [EmberEUI64, uint8_t, EmberRf4ceVendorInfo, EmberRf4ceApplicationInfo, uint8_t, uint8_t] + ], + "rf4ceDiscoveryResponseHandler": [220, [], + [Bool, uint8_t, EmberPanId, EmberEUI64, uint8_t, EmberRf4ceVendorInfo, + EmberRf4ceApplicationInfo, uint8_t, uint8_t] + ], + "rf4ceEnableAutoDiscoveryResponse": [221, [uint16_t], + [EmberStatus] + ], + "rf4ceAutoDiscoveryResponseCompleteHandler": [222, [], + [EmberStatus, EmberEUI64, uint8_t, EmberRf4ceVendorInfo, EmberRf4ceApplicationInfo, uint8_t] + ], + "rf4cePair": [223, [uint8_t, EmberPanId, EmberEUI64, uint8_t], + [EmberStatus] + ], + "rf4cePairCompleteHandler": [224, [], + [EmberStatus, uint8_t, EmberRf4ceVendorInfo, EmberRf4ceApplicationInfo] + ], + "rf4cePairRequestHandler": [225, [], + [EmberStatus, uint8_t, EmberEUI64, uint8_t, EmberRf4ceVendorInfo, EmberRf4ceApplicationInfo, uint8_t] + ], + "rf4ceUnpair": [226, [uint8_t], + [EmberStatus] + ], + "rf4ceUnpairHandler": [227, [], + [uint8_t] + ], + "rf4ceUnpairCompleteHandler": [228, [], + [uint8_t] + ], + "rf4ceSetPowerSavingParameters": [229, [uint32_t, uint32_t], + [EmberStatus] + ], + "rf4ceSetFrequencyAgilityParameters": [230, [uint8_t, uint8_t, int8s, uint16_t, uint8_t], + [EmberStatus] + ], + "rf4ceSetApplicationInfo": [231, [EmberRf4ceApplicationInfo], + [EmberStatus] + ], + "rf4ceGetApplicationInfo": [239, [], + [EmberStatus, EmberRf4ceApplicationInfo] + ], + "rf4ceGetMaxPayload": [243, [uint8_t, EmberRf4ceTxOption], + [uint8_t] + ], + "rf4ceGetNetworkParameters": [244, [], + [EmberStatus, EmberNodeType, EmberNetworkParameters] + ], + "gpProxyTableProcessGpPairing": [201, + [uint32_t, EmberGpAddress, uint8_t, uint16_t, uint16_t, uint16_t, + fixed_list(8, uint8_t), EmberKeyData], + [] + ], + "dGpSend": [198, [Bool, Bool, EmberGpAddress, uint8_t, LVBytes, uint8_t, uint16_t], + [EmberStatus] + ], + "dGpSentHandler": [199, [], + [EmberStatus, uint8_t] + ], + "gpepIncomingMessageHandler": [197, [], + [EmberStatus, uint8_t, uint8_t, EmberGpAddress, EmberGpSecurityLevel, EmberGpKeyType, + Bool, Bool, uint32_t, uint8_t, uint32_t, EmberGpSinkListEntry, LVBytes] + ], + "changeSourceRouteHandler": [196, [], [EmberNodeId, EmberNodeId]], //Bool + "setSourceRouteDiscoveryMode": [0x005A, [uint8_t,], [uint32_t,]], +}; + +//// EmberZDOCmd +/* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ +export const ZDO_COMMANDS: { [key: string]: [number, any[], any[]] } = { + "Node_Desc_req": [0x0002, [uint8_t, EmberNodeId], [EmberStatus]], + "Node_Desc_rsp": [0x8002, [EmberStatus, EmberNodeId, EmberNodeDescriptor], []], + "Simple_Desc_req": [0x0004, [uint8_t, EmberNodeId, uint8_t], [EmberStatus]], + "Simple_Desc_rsp": [0x8004, [uint8_t, EmberStatus, EmberNodeId, uint8_t, EmberSimpleDescriptor], []], + "Active_EP_req": [0x0005, [uint8_t, EmberNodeId], [EmberStatus]], + "Active_EP_rsp": [0x8005, [EmberStatus, uint8_t, EmberNodeId, LVBytes], []], + "Bind_req": [0x0021, [uint8_t, EmberEUI64, uint8_t, uint16_t, EmberMultiAddress], [EmberStatus]], + "Bind_rsp": [0x8021, [EmberStatus], []], + "Unbind_req": [0x0022, [uint8_t, EmberEUI64, uint8_t, uint16_t, EmberMultiAddress], [EmberStatus]], + "Unbind_rsp": [0x8022, [EmberStatus], []], + "Mgmt_Leave_req": [0x0034, [uint8_t, EmberEUI64, uint8_t], [EmberStatus]], + "Mgmt_Leave_rsp": [0x8034, [EmberStatus], []], + "Mgmt_Lqi_req": [0x0031, [uint8_t, uint8_t], [EmberStatus]], + "Mgmt_Lqi_rsp": [0x8031, [uint8_t, EmberStatus, EmberNeighbors], [EmberStatus]], +}; diff --git a/src/adapter/ezsp/driver/driver.ts b/src/adapter/ezsp/driver/driver.ts new file mode 100644 index 0000000000..458fcb3717 --- /dev/null +++ b/src/adapter/ezsp/driver/driver.ts @@ -0,0 +1,422 @@ +/* istanbul ignore file */ +import * as TsType from './../../tstype'; +import {Ezsp} from './ezsp'; +import {EmberStatus, EmberNodeType, uint16_t, uint8_t, EmberZDOCmd, EmberApsOption} from './types'; +import {EventEmitter} from "events"; +import {EmberApsFrame, EmberNetworkParameters, EmberInitialSecurityState} from './types/struct'; +import {ember_security} from './utils'; +import { + EmberOutgoingMessageType, + EmberEUI64, + EmberJoinMethod, + EmberDeviceUpdate, + EzspValueId, + EzspPolicyId, + EzspDecisionBitmask, + EmberNetworkStatus, + EmberKeyType +} from './types/named'; +import {Multicast} from './multicast'; +import {Queue, Waitress} from '../../../utils'; +import Debug from "debug"; +import equals from 'fast-deep-equal/es6'; + +const debug = { + error: Debug('zigbee-herdsman:adapter:driver:error'), + log: Debug('zigbee-herdsman:adapter:driver:log'), +}; + +interface AddEndpointParameters { + endpoint?: number, + profileId?: number, + deviceId?: number, + appFlags?: number, + inputClusters?: number[], + outputClusters?: number[], +} + +type EmberFrame = { + address: number; + payload: Buffer; + frame: EmberApsFrame; +}; + +type EmberWaitressMatcher = { + address: number, + clusterId: number, + sequence: number +}; + +export class Driver extends EventEmitter { + private direct = EmberOutgoingMessageType.OUTGOING_DIRECT; + public ezsp: Ezsp; + private nwkOpt: TsType.NetworkOptions; + public networkParams: EmberNetworkParameters; + public version: { + product: number; majorrel: string; minorrel: string; maintrel: string; revision: string; + }; + private eui64ToNodeId = new Map(); + private eui64ToRelays = new Map(); + public ieee: EmberEUI64; + private multicast: Multicast; + private waitress: Waitress; + public queue: Queue; + private transactionID: number; + + constructor() { + super(); + this.transactionID = 1; + this.queue = new Queue(); + + this.waitress = new Waitress( + this.waitressValidator, this.waitressTimeoutFormatter); + } + + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + public async startup(port: string, serialOpt: Record, nwkOpt: TsType.NetworkOptions): Promise { + this.nwkOpt = nwkOpt; + const ezsp = this.ezsp = new Ezsp(); + await ezsp.connect(port, serialOpt); + await ezsp.version(); + + await ezsp.updateConfig(); + + await ezsp.updatePolicies(); + + await this.ezsp.setValue(EzspValueId.VALUE_MAXIMUM_OUTGOING_TRANSFER_SIZE, 82); + await this.ezsp.setValue(EzspValueId.VALUE_MAXIMUM_INCOMING_TRANSFER_SIZE, 82); + await this.ezsp.setValue(EzspValueId.VALUE_END_DEVICE_KEEP_ALIVE_SUPPORT_MODE, 3); + + await ezsp.setSourceRouting(); + + //const count = await ezsp.getConfigurationValue(EzspConfigId.CONFIG_APS_UNICAST_MESSAGE_COUNT); + //debug.log("APS_UNICAST_MESSAGE_COUNT is set to %s", count); + + //await this.addEndpoint({outputClusters: [0x0500]}); + await this.addEndpoint({ + inputClusters: [0x0000, 0x0003, 0x0006, 0x000A, 0x0019, 0x001A, 0x0300], + outputClusters: [0x0000, 0x0003, 0x0004, 0x0005, 0x0006, 0x0008, 0x0020, + 0x0300, 0x0400, 0x0402, 0x0405, 0x0406, 0x0500, 0x0B01, 0x0B03, + 0x0B04, 0x0702, 0x1000, 0xFC01, 0xFC02] + }); + await this.addEndpoint({ + endpoint: 242, profileId: 0xA10E, deviceId: 0x61, + outputClusters: [0x0021] + }); + + // getting MFG_STRING token + //const mfgName = await ezsp.execCommand('getMfgToken', EzspMfgTokenId.MFG_STRING); + // getting MFG_BOARD_NAME token + //const boardName = await ezsp.execCommand('getMfgToken', EzspMfgTokenId.MFG_BOARD_NAME); + /* eslint-disable prefer-const */ + let verInfo = await ezsp.getValue(EzspValueId.VALUE_VERSION_INFO); + let build, major, minor, patch, special; + [build, verInfo] = uint16_t.deserialize(uint16_t, verInfo); + [major, verInfo] = uint8_t.deserialize(uint8_t, verInfo); + [minor, verInfo] = uint8_t.deserialize(uint8_t, verInfo); + [patch, verInfo] = uint8_t.deserialize(uint8_t, verInfo); + [special, verInfo] = uint8_t.deserialize(uint8_t, verInfo); + /* eslint-enable prefer-const */ + const vers = `${major}.${minor}.${patch}.${special} build ${build}`; + debug.log(`EmberZNet version: ${vers}`); + this.version = { + product: this.ezsp.ezspV, + majorrel: `${major}`, + minorrel: `${minor}`, + maintrel: `${patch} `, + revision: vers + }; + + if (await this.needsToBeInitialised(nwkOpt)) { + const currentState = await ezsp.execCommand('networkState'); + debug.log('Network state', currentState); + if (currentState == EmberNetworkStatus.JOINED_NETWORK) { + debug.log(`Leaving current network and forming new network`); + const st = await this.ezsp.leaveNetwork(); + console.assert(st == EmberStatus.NETWORK_DOWN); + } + await this.form_network(); + } + const state = await ezsp.execCommand('networkState'); + debug.log('Network state', state); + + const [status, nodeType, networkParams] = await ezsp.execCommand('getNetworkParameters'); + console.assert(status == EmberStatus.SUCCESS); + this.networkParams = networkParams; + debug.log("Node type: %s, Network parameters: %s", nodeType, networkParams); + + const [nwk] = await ezsp.execCommand('getNodeId'); + const [ieee] = await this.ezsp.execCommand('getEui64'); + this.ieee = new EmberEUI64(ieee); + debug.log('Network ready'); + ezsp.on('frame', this.handleFrame.bind(this)); + this.handleNodeJoined(nwk, this.ieee); + debug.log(`EZSP nwk=${nwk}, IEEE=0x${this.ieee}`); + + this.multicast = new Multicast(this); + await this.multicast.startup([]); + } + + private async needsToBeInitialised(options: TsType.NetworkOptions): Promise { + let valid = true; + valid = valid && (await this.ezsp.networkInit()); + const [status, nodeType, networkParams] = await this.ezsp.execCommand('getNetworkParameters'); + debug.log("Current Node type: %s, Network parameters: %s", nodeType, networkParams); + valid = valid && (status == EmberStatus.SUCCESS); + valid = valid && (nodeType == EmberNodeType.COORDINATOR); + valid = valid && (options.panID == networkParams.panId); + valid = valid && (options.channelList.includes(networkParams.radioChannel)); + valid = valid && (equals(options.extendedPanID, networkParams.extendedPanId)); + return !valid; + } + + private async form_network(): Promise { + let status; + [status] = await this.ezsp.execCommand('clearKeyTable'); + console.assert(status == EmberStatus.SUCCESS); + + const panID = this.nwkOpt.panID; + const extendedPanID = this.nwkOpt.extendedPanID; + const initial_security_state: EmberInitialSecurityState = ember_security(this.nwkOpt); + [status] = await this.ezsp.setInitialSecurityState(initial_security_state); + const parameters: EmberNetworkParameters = new EmberNetworkParameters(); + parameters.panId = panID; + parameters.extendedPanId = extendedPanID; + parameters.radioTxPower = 20; + parameters.radioChannel = this.nwkOpt.channelList[0]; + parameters.joinMethod = EmberJoinMethod.USE_MAC_ASSOCIATION; + parameters.nwkManagerId = 0; + parameters.nwkUpdateId = 0; + parameters.channels = 0x07FFF800; // all channels + + await this.ezsp.formNetwork(parameters); + await this.ezsp.setValue(EzspValueId.VALUE_STACK_TOKEN_WRITING, 1); + + await this.ezsp.execCommand('getKey', EmberKeyType.TRUST_CENTER_LINK_KEY); + await this.ezsp.execCommand('getKey', EmberKeyType.CURRENT_NETWORK_KEY); + } + + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + private handleFrame(frameName: string, ...args: any[]): void { + switch (true) { + case (frameName === 'incomingMessageHandler'): { + const [messageType, apsFrame, lqi, rssi, sender, bindingIndex, addressIndex, message] = args; + const eui64 = this.eui64ToNodeId.get(sender); + + const handled = this.waitress.resolve({address: sender, payload: message, frame: apsFrame}); + if (!handled) { + this.emit('incomingMessage', { + messageType, apsFrame, lqi, rssi, sender, bindingIndex, addressIndex, message, + senderEui64: eui64 + }); + } + break; + } + case (frameName === 'trustCenterJoinHandler'): { + /* eslint-disable-next-line @typescript-eslint/no-unused-vars */ + const [nwk, ieee, devUpdate, joinDecision, parent] = args; + if (devUpdate === EmberDeviceUpdate.DEVICE_LEFT) { + this.handleNodeLeft(nwk, ieee); + } else { + this.handleNodeJoined(nwk, ieee); + } + break; + } + case (frameName === 'incomingRouteRecordHandler'): { + const [nwk, ieee, lqi, rssi, relays] = args; + this.handleRouteRecord(nwk, ieee, lqi, rssi, relays); + break; + } + case (frameName === 'incomingRouteErrorHandler'): { + const [status, nwk] = args; + this.handleRouteError(status, nwk); + break; + } + case (frameName === 'messageSentHandler'): { + // todo + const status = args[4]; + if (status != 0) { + // send failure + } else { + // send success + } + break; + } + default: + debug.log(`Unhandled frame ${frameName}`); + } + } + + /* eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars */ + private handleRouteRecord(nwk: number, ieee: EmberEUI64 | number[], lqi: number, rssi: number, relays: any): void { + debug.log(`handleRouteRecord: nwk=${nwk}, ieee=${ieee}, lqi=${lqi}, rssi=${rssi}, relays=${relays}`); + // todo + } + + /* eslint-disable-next-line @typescript-eslint/no-unused-vars */ + private handleRouteError(status: EmberStatus, nwk: number): void { + debug.log(`handleRouteError: number=${status}, nwk=${nwk}`); + // todo + } + + /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ + private handleNodeLeft(nwk: number, ieee: EmberEUI64 | number[]): void { + if (ieee && !(ieee instanceof EmberEUI64)) { + ieee = new EmberEUI64(ieee); + } + this.eui64ToNodeId.delete(ieee.toString()); + this.emit('deviceLeft', [nwk, ieee]); + } + + /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ + private handleNodeJoined(nwk: number, ieee: EmberEUI64 | number[]): void { + if (ieee && !(ieee instanceof EmberEUI64)) { + ieee = new EmberEUI64(ieee); + } + this.eui64ToNodeId.set(ieee.toString(), nwk); + this.emit('deviceJoined', [nwk, ieee]); + } + + public async request(nwk: number | EmberEUI64, apsFrame: EmberApsFrame, + /* eslint-disable-next-line @typescript-eslint/no-unused-vars */ + data: Buffer, timeout = 30000): Promise { + try { + const seq = apsFrame.sequence + 1; + let eui64: EmberEUI64; + if (typeof nwk !== 'number') { + eui64 = nwk as EmberEUI64; + const strEui64 = eui64.toString(); + let nodeId = this.eui64ToNodeId.get(strEui64); + if (nodeId === undefined) { + nodeId = await this.ezsp.execCommand('lookupNodeIdByEui64', eui64); + if (nodeId && nodeId !== 0xFFFF) { + this.eui64ToNodeId.set(strEui64, nodeId); + } else { + throw new Error('Unknown EUI64:' + strEui64); + } + } + nwk = nodeId; + } else { + eui64 = await this.networkIdToEUI64(nwk); + } + await this.ezsp.execCommand('setExtendedTimeout', eui64, true); + + await this.ezsp.sendUnicast(this.direct, nwk, apsFrame, seq, data); + return true; + } catch (e) { + return false; + } + } + + private nextTransactionID(): number { + this.transactionID = (this.transactionID + 1) & 0xFF; + return this.transactionID; + } + + public makeApsFrame(clusterId: number): EmberApsFrame { + const frame = new EmberApsFrame(); + frame.clusterId = clusterId; + frame.profileId = 0; + frame.sequence = this.nextTransactionID(); + frame.sourceEndpoint = 0; + frame.destinationEndpoint = 0; + frame.groupId = 0; + //frame.options = EmberApsOption.APS_OPTION_ENABLE_ROUTE_DISCOVERY|EmberApsOption.APS_OPTION_RETRY; + frame.options = EmberApsOption.APS_OPTION_NONE; + return frame; + } + + public async zdoRequest(networkAddress: number, requestCmd: EmberZDOCmd, + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + responseCmd: EmberZDOCmd, ...args: any[]): Promise { + const requestName = EmberZDOCmd.valueName(EmberZDOCmd, requestCmd); + const responseName = EmberZDOCmd.valueName(EmberZDOCmd, responseCmd); + debug.log(`${requestName} params: ${[...args]}`); + const frame = this.makeApsFrame(requestCmd as number); + const payload = this.makeZDOframe(requestName, frame.sequence, ...args); + debug.log(`${requestName} frame: ${payload}`); + const response = this.waitFor(networkAddress, responseCmd as number, frame.sequence); + await this.request(networkAddress, frame, payload); + const message = await response.start().promise; + debug.log(`${responseName} frame: ${JSON.stringify(message.payload)}`); + const result = this.parse_frame_payload(responseName, message.payload); + debug.log(`${responseName} parsed: ${JSON.stringify(result)}`); + return result; + } + + public async stop(): Promise { + return this.ezsp.close(); + } + + public getLocalEUI64(): Promise { + return this.ezsp.execCommand('getEui64'); + } + + public async networkIdToEUI64(nwk: number): Promise { + for (const [eUI64, value] of this.eui64ToNodeId) { + if (value === nwk) return new EmberEUI64(eUI64); + } + const value = await this.ezsp.execCommand('lookupEui64ByNodeId', nwk); + if (value[0] === EmberStatus.SUCCESS) { + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + const eUI64 = new EmberEUI64(value[1] as any); + this.eui64ToNodeId.set(eUI64.toString(), nwk); + return eUI64; + } else { + throw new Error('Unrecognized nodeId:' + nwk); + } + } + + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + public async permitJoining(seconds: number): Promise { + await this.ezsp.setPolicy(EzspPolicyId.TRUST_CENTER_POLICY, + EzspDecisionBitmask.IGNORE_UNSECURED_REJOINS | EzspDecisionBitmask.ALLOW_JOINS); + return this.ezsp.execCommand('permitJoining', seconds); + } + + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + public makeZDOframe(name: string, ...args: any[]): Buffer { + return this.ezsp.makeZDOframe(name, ...args); + } + + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + public parse_frame_payload(name: string, obj: Buffer): any[] { + return this.ezsp.parse_frame_payload(name, obj); + } + + public async addEndpoint({ + endpoint = 1, + profileId = 260, + deviceId = 0xBEEF, + appFlags = 0, + inputClusters = [], + outputClusters = [] + }: AddEndpointParameters): Promise { + const res = await this.ezsp.execCommand('addEndpoint', + endpoint, + profileId, + deviceId, + appFlags, + inputClusters.length, + outputClusters.length, + inputClusters, + outputClusters, + ); + debug.log("Ezsp adding endpoint: %s", res); + } + + public waitFor(address: number, clusterId: number, sequence: number, timeout = 30000) + : { start: () => { promise: Promise; ID: number }; ID: number } { + return this.waitress.waitFor({address, clusterId, sequence}, timeout); + } + + private waitressTimeoutFormatter(matcher: EmberWaitressMatcher, timeout: number): string { + return `${JSON.stringify(matcher)} after ${timeout}ms`; + } + + private waitressValidator(payload: EmberFrame, matcher: EmberWaitressMatcher): boolean { + return (!matcher.address || payload.address === matcher.address) && + payload.frame.clusterId === matcher.clusterId && + payload.payload[0] === matcher.sequence; + } +} diff --git a/src/adapter/ezsp/driver/ezsp.ts b/src/adapter/ezsp/driver/ezsp.ts new file mode 100644 index 0000000000..d023b4b523 --- /dev/null +++ b/src/adapter/ezsp/driver/ezsp.ts @@ -0,0 +1,423 @@ +/* istanbul ignore file */ +import * as t from './types'; +import {SerialDriver} from './uart'; +import {COMMANDS, ZDO_COMMANDS} from './commands'; + +import {Deferred} from './utils'; +import { + EmberStatus, + EmberOutgoingMessageType, + EzspPolicyId, + EzspDecisionId, + EzspDecisionBitmask, + EmberConcentratorType, + EzspConfigId, + EmberZdoConfigurationFlags +} from './types/named'; +import {EventEmitter} from 'events'; +import {EmberApsFrame} from './types/struct'; +import {Queue, Waitress} from '../../../utils'; +import Debug from "debug"; + + +const debug = { + error: Debug('zigbee-herdsman:adapter:ezsp:error'), + log: Debug('zigbee-herdsman:adapter:ezsp:log'), +}; + + +const MTOR_MIN_INTERVAL = 10; +const MTOR_MAX_INTERVAL = 90; +const MTOR_ROUTE_ERROR_THRESHOLD = 4; +const MTOR_DELIVERY_FAIL_THRESHOLD = 3; + +type EZSPFrame = { + sequence: number, + frameId: number, + frameName: string, + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + payload: any +}; + +type EZSPWaitressMatcher = { + sequence: number, + frameId: number +}; + +export class Ezsp extends EventEmitter { + ezspV = 4; + cmdSeq = 0; // command sequence + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + COMMANDS_BY_ID = new Map(); + private serialDriver: SerialDriver; + private waitress: Waitress; + private queue: Queue; + + constructor() { + super(); + for (const name in COMMANDS) { + const details = COMMANDS[name]; + this.COMMANDS_BY_ID.set(details[0], {name, inArgs: details[1], outArgs: details[2]}); + } + this.queue = new Queue(); + this.waitress = new Waitress( + this.waitressValidator, this.waitressTimeoutFormatter); + + this.serialDriver = new SerialDriver(); + this.serialDriver.on('received', this.onFrameReceived.bind(this)); + } + + public async connect(path: string, options: Record): Promise { + await this.serialDriver.connect(path, options); + } + + public close(): Promise { + return this.serialDriver.close(); + } + + private onFrameReceived(data: Buffer): void { + /*Handle a received EZSP frame + + The protocol has taken care of UART specific framing etc, so we should + just have EZSP application stuff here, with all escaping/stuffing and + data randomization removed. + */ + debug.log(`<=== Frame: ${data.toString('hex')}`); + let frame_id: number, result, sequence; + if ((this.ezspV < 8)) { + [sequence, frame_id, data] = [data[0], data[2], data.slice(3)]; + } else { + sequence = data[0]; + [[frame_id], data] = t.deserialize(data.slice(3), [t.uint16_t]); + } + if ((frame_id === 255)) { + frame_id = 0; + if ((data.length > 1)) { + frame_id = data[1]; + data = data.slice(2); + } + } + const cmd = this.COMMANDS_BY_ID.get(frame_id); + if (!cmd) throw new Error('Unrecognized command from FrameID' + frame_id); + const frameName = cmd.name; + debug.log("<=== Application frame %s (%s) received: %s", frame_id, frameName, data.toString('hex')); + const schema = cmd.outArgs; + [result, data] = t.deserialize(data, schema); + debug.log(`<=== Application frame ${frame_id} (${frameName}) parsed: ${result}`); + const handled = this.waitress.resolve({ + frameId: frame_id, + frameName: frameName, + sequence: sequence, + payload: result + }); + + if (!handled) this.emit('frame', frameName, ...result); + + if ((frame_id === 0)) { + this.ezspV = result[0]; + } + } + + async version(): Promise { + const version = this.ezspV; + const result = await this.command("version", version); + if ((result[0] !== version)) { + debug.log("Switching to eszp version %d", result[0]); + await this.command("version", result[0]); + } + return result[0]; + } + + async networkInit(): Promise { + const fut = new Deferred(); + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + this.on('frame', (frameName: string, response: any) => { + if ((frameName === "stackStatusHandler")) { + fut.resolve(response); + } + }); + + const [result] = await this.command("networkInit"); + debug.log('network init result', result); + if ((result !== EmberStatus.SUCCESS)) { + debug.log("Failure to init network:" + result); + return false; + } + const v = await fut.promise; + return (v === EmberStatus.NETWORK_UP); + } + + async leaveNetwork(): Promise { + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + let v; + const fut = new Deferred(); + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + this.on('frame', (frameName: string, response: any) => { + if ((frameName === "stackStatusHandler")) { + fut.resolve(response); + } + }); + v = await this.command("leaveNetwork"); + if ((v[0] !== EmberStatus.SUCCESS)) { + debug.log("Failure to leave network:" + v); + throw new Error(("Failure to leave network:" + v)); + } + v = await fut.promise; + if ((v !== EmberStatus.NETWORK_DOWN)) { + debug.log("Failure to leave network:" + v); + throw new Error(("Failure to leave network:" + v)); + } + return v; + } + + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + async setConfigurationValue(configId: number, value: any): Promise { + debug.log('Set %s = %s', EzspConfigId.valueToName(EzspConfigId, configId), value); + const [ret] = await this.execCommand('setConfigurationValue', configId, value); + console.assert(ret === EmberStatus.SUCCESS); + } + + async getConfigurationValue(configId: number): Promise { + debug.log('Get %s', EzspConfigId.valueToName(EzspConfigId, configId)); + const [ret, value] = await this.execCommand('getConfigurationValue', configId); + console.assert(ret === EmberStatus.SUCCESS); + debug.log('Got %s = %s', EzspConfigId.valueToName(EzspConfigId, configId), value); + return value; + } + + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + async getMulticastTableEntry(index: number): Promise { + const [ret, value] = await this.execCommand('getMulticastTableEntry', index); + console.assert(ret === EmberStatus.SUCCESS); + return [ret, value]; + } + + async setMulticastTableEntry(index: number, entry: t.EmberMulticastTableEntry): Promise { + const [ret] = await this.execCommand('setMulticastTableEntry', index, entry); + console.assert(ret === EmberStatus.SUCCESS); + return [ret]; + } + + async setInitialSecurityState(entry: t.EmberInitialSecurityState): Promise{ + const [ret] = await this.execCommand('setInitialSecurityState', entry); + console.assert(ret === EmberStatus.SUCCESS); + return [ret]; + } + + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + async getCurrentSecurityState(): Promise { + const [ret, res] = await this.execCommand('getCurrentSecurityState'); + console.assert(ret === EmberStatus.SUCCESS); + return [ret, res]; + } + + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + async setValue(valueId: t.EzspValueId, value: any): Promise { + debug.log('Set %s = %s', t.EzspValueId.valueToName(t.EzspValueId, valueId), value); + const [ret] = await this.execCommand('setValue', valueId, value); + console.assert(ret === EmberStatus.SUCCESS); + return [ret]; + } + + async getValue(valueId: t.EzspValueId): Promise { + debug.log('Get %s', t.EzspValueId.valueToName(t.EzspValueId, valueId)); + const [ret, value] = await this.execCommand('getValue', valueId); + console.assert(ret === EmberStatus.SUCCESS); + debug.log('Got %s = %s', t.EzspValueId.valueToName(t.EzspValueId, valueId), value); + return value; + } + + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + async setPolicy(policyId: EzspPolicyId, value: any): Promise { + debug.log('Set %s = %s', EzspPolicyId.valueToName(EzspPolicyId, policyId), value); + const [ret] = await this.execCommand('setPolicy', policyId, value); + console.assert(ret === EmberStatus.SUCCESS); + return [ret]; + } + + async updateConfig(): Promise { + const config = [ + [EzspConfigId.CONFIG_FRAGMENT_DELAY_MS, 50], + [EzspConfigId.CONFIG_TX_POWER_MODE, 3], + [EzspConfigId.CONFIG_FRAGMENT_WINDOW_SIZE, 1], + //[EzspConfigId.CONFIG_BEACON_JITTER_DURATION, 0], + [EzspConfigId.CONFIG_NEIGHBOR_TABLE_SIZE, 16], + [EzspConfigId.CONFIG_ROUTE_TABLE_SIZE, 16], + [EzspConfigId.CONFIG_BINDING_TABLE_SIZE, 0], + [EzspConfigId.CONFIG_KEY_TABLE_SIZE, 12], + [EzspConfigId.CONFIG_ZLL_GROUP_ADDRESSES, 0], + [EzspConfigId.CONFIG_ZLL_RSSI_THRESHOLD, 215], // -40 + [EzspConfigId.CONFIG_TRANSIENT_KEY_TIMEOUT_S, 180], + [EzspConfigId.CONFIG_APS_UNICAST_MESSAGE_COUNT, 15], + [EzspConfigId.CONFIG_BROADCAST_TABLE_SIZE, 15], + [EzspConfigId.CONFIG_MAX_HOPS, 30], + + [EzspConfigId.CONFIG_INDIRECT_TRANSMISSION_TIMEOUT, 7680], // 30000 + [EzspConfigId.CONFIG_SOURCE_ROUTE_TABLE_SIZE, 16], // 61 + [EzspConfigId.CONFIG_MULTICAST_TABLE_SIZE, 16], + [EzspConfigId.CONFIG_ADDRESS_TABLE_SIZE, 16], // 8 + [EzspConfigId.CONFIG_TRUST_CENTER_ADDRESS_CACHE_SIZE, 2], + [EzspConfigId.CONFIG_SUPPORTED_NETWORKS, 1], + [EzspConfigId.CONFIG_TC_REJOINS_USING_WELL_KNOWN_KEY_TIMEOUT_S, 90], + [EzspConfigId.CONFIG_APPLICATION_ZDO_FLAGS, + EmberZdoConfigurationFlags.APP_RECEIVES_SUPPORTED_ZDO_REQUESTS + | EmberZdoConfigurationFlags.APP_HANDLES_UNSUPPORTED_ZDO_REQUESTS], + [EzspConfigId.CONFIG_SECURITY_LEVEL, 5], + [EzspConfigId.CONFIG_END_DEVICE_POLL_TIMEOUT, 8], // 14 + [EzspConfigId.CONFIG_PAN_ID_CONFLICT_REPORT_THRESHOLD, 2], + [EzspConfigId.CONFIG_MAX_END_DEVICE_CHILDREN, 32], + [EzspConfigId.CONFIG_STACK_PROFILE, 2], + [EzspConfigId.CONFIG_PACKET_BUFFER_COUNT, 255], + ]; + + for (const [confName, value] of config) { + await this.setConfigurationValue(confName, value); + } + } + + async updatePolicies(): Promise { + // Set up the policies for what the NCP should do. + const policies = [ + [EzspPolicyId.BINDING_MODIFICATION_POLICY, + EzspDecisionId.CHECK_BINDING_MODIFICATIONS_ARE_VALID_ENDPOINT_CLUSTERS], + [EzspPolicyId.UNICAST_REPLIES_POLICY, EzspDecisionId.HOST_WILL_NOT_SUPPLY_REPLY], + [EzspPolicyId.POLL_HANDLER_POLICY, EzspDecisionId.POLL_HANDLER_IGNORE], + [EzspPolicyId.MESSAGE_CONTENTS_IN_CALLBACK_POLICY, + EzspDecisionId.MESSAGE_TAG_ONLY_IN_CALLBACK], + [EzspPolicyId.PACKET_VALIDATE_LIBRARY_POLICY, + EzspDecisionId.PACKET_VALIDATE_LIBRARY_CHECKS_ENABLED], + [EzspPolicyId.ZLL_POLICY, EzspDecisionId.ALLOW_JOINS], + [EzspPolicyId.TC_REJOINS_USING_WELL_KNOWN_KEY_POLICY, EzspDecisionId.ALLOW_JOINS], + + [EzspPolicyId.APP_KEY_REQUEST_POLICY, EzspDecisionId.DENY_APP_KEY_REQUESTS], + [EzspPolicyId.TRUST_CENTER_POLICY, EzspDecisionBitmask.IGNORE_UNSECURED_REJOINS + | EzspDecisionBitmask.ALLOW_JOINS], + [EzspPolicyId.TC_KEY_REQUEST_POLICY, EzspDecisionId.ALLOW_TC_KEY_REQUESTS], + ]; + + for (const [policy, value] of policies) { + await this.setPolicy(policy, value); + } + } + + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + public makeZDOframe(name: string, ...args: any[]): Buffer { + const c = ZDO_COMMANDS[name]; + const data = t.serialize(args, c[1]); + return data; + } + + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + private makeFrame(name: string, ...args: any[]): Buffer { + const c = COMMANDS[name]; + const data = t.serialize(args, c[1]); + const frame = [(this.cmdSeq & 255)]; + if ((this.ezspV < 8)) { + if ((this.ezspV >= 5)) { + frame.push(0x00, 0xFF, 0x00, c[0]); + } else { + frame.push(0x00, c[0]); + } + } else { + const cmd_id = t.serialize([c[0]], [t.uint16_t]); + frame.push(0x00, 0x01, ...cmd_id); + } + return Buffer.concat([Buffer.from(frame), data]); + } + + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + private command(name: string, ...args: any[]): Promise { + debug.log(`===> Send command ${name}: (${args})`); + return this.queue.execute(async (): Promise => { + const data = this.makeFrame(name, ...args); + debug.log(`===> Send data ${name}: (${data.toString('hex')})`); + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + const c = COMMANDS[name]; + const waiter = this.waitFor(c[0], this.cmdSeq); + this.cmdSeq = (this.cmdSeq + 1 % 256); + this.serialDriver.sendDATA(data); + const response = await waiter.start().promise; + return response.payload; + }); + } + + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + async formNetwork(...args: any[]): Promise { + let v; + const fut = new Deferred(); + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + this.on('frame', (frameName: string, response: any) => { + if ((frameName === "stackStatusHandler")) { + fut.resolve(response); + } + }); + v = await this.command("formNetwork", ...args); + if ((v[0] !== EmberStatus.SUCCESS)) { + debug.log("Failure forming network:" + v); + throw new Error(("Failure forming network:" + v)); + } + v = await fut.promise; + if ((v !== EmberStatus.NETWORK_UP)) { + debug.log("Failure forming network:" + v); + throw new Error(("Failure forming network:" + v)); + } + return v; + } + + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + execCommand(name: string, ...args: any[]): any { + if (Object.keys(COMMANDS).indexOf(name) < 0) { + throw new Error('Unknown command: ' + name); + } + return this.command(name, ...args); + } + + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + public parse_frame_payload(name: string, data: Buffer): any[] { + if (Object.keys(ZDO_COMMANDS).indexOf(name) < 0) { + throw new Error('Unknown ZDO command: ' + name); + } + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + const c = ZDO_COMMANDS[name]; + const result = t.deserialize(data, c[1])[0]; + return result; + } + + /* eslint-disable @typescript-eslint/no-explicit-any*/ + public sendUnicast(direct: EmberOutgoingMessageType, nwk: number, apsFrame: + EmberApsFrame, seq: number, data: Buffer): any { + return this.execCommand('sendUnicast', direct, nwk, apsFrame, seq, data); + } + /* eslint-enable @typescript-eslint/no-explicit-any*/ + + public async setSourceRouting(): Promise { + const [res] = await this.execCommand('setConcentrator', + true, + EmberConcentratorType.HIGH_RAM_CONCENTRATOR, + MTOR_MIN_INTERVAL, + MTOR_MAX_INTERVAL, + MTOR_ROUTE_ERROR_THRESHOLD, + MTOR_DELIVERY_FAIL_THRESHOLD, + 0, + ); + debug.log("Set concentrator type: %s", res); + if (res != EmberStatus.SUCCESS) { + debug.log("Couldn't set concentrator type %s: %s", true, res); + } + await this.execCommand('setSourceRouteDiscoveryMode', 1); + } + + public waitFor(frameId: number, sequence: number, timeout = 30000) + : { start: () => { promise: Promise; ID: number }; ID: number } { + return this.waitress.waitFor({frameId, sequence}, timeout); + } + + private waitressTimeoutFormatter(matcher: EZSPWaitressMatcher, timeout: number): string { + return `${JSON.stringify(matcher)} after ${timeout}ms`; + } + + private waitressValidator(payload: EZSPFrame, matcher: EZSPWaitressMatcher): boolean { + return (payload.sequence === matcher.sequence && + payload.frameId === matcher.frameId); + } +} diff --git a/src/adapter/ezsp/driver/index.ts b/src/adapter/ezsp/driver/index.ts new file mode 100644 index 0000000000..9a43190873 --- /dev/null +++ b/src/adapter/ezsp/driver/index.ts @@ -0,0 +1,5 @@ +/* istanbul ignore file */ +import {Ezsp} from './ezsp'; +import {Driver} from './driver'; + +export {Ezsp, Driver}; diff --git a/src/adapter/ezsp/driver/multicast.ts b/src/adapter/ezsp/driver/multicast.ts new file mode 100644 index 0000000000..7542db2bff --- /dev/null +++ b/src/adapter/ezsp/driver/multicast.ts @@ -0,0 +1,96 @@ +/* istanbul ignore file */ +import {Driver} from './driver'; +import {EzspConfigId} from './types'; +import {EmberStatus} from './types/named'; +import {EmberMulticastTableEntry} from './types/struct'; +import Debug from "debug"; + +const debug = { + log: Debug('zigbee-herdsman:adapter:ezsp:multicast'), +}; + + +export class Multicast { + TABLE_SIZE = 16; + private driver: Driver; + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + private _multicast: any; + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + private _available: Array; + + constructor(driver: Driver) { + this.driver = driver; + this._multicast = {}; + this._available = []; + } + + private async _initialize(): Promise { + const size = await this.driver.ezsp.getConfigurationValue( + EzspConfigId.CONFIG_MULTICAST_TABLE_SIZE + ); + for (let i = 0; i < size; i++) { + const [st, entry] = await this.driver.ezsp.getMulticastTableEntry(i); + if (st !== EmberStatus.SUCCESS) { + debug.log("Couldn't get MulticastTableEntry #%s: %s", i, st); + continue; + } + debug.log("MulticastTableEntry[%s] = %s", i, entry); + if (entry.endpoint !== 0) { + this._multicast[entry.multicastId] = [entry, i]; + } else { + this._available.push(i); + } + } + } + + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + async startup(enpoints: Array): Promise { + return this.driver.queue.execute(async () => { + await this._initialize(); + for (const ep of enpoints) { + if (!ep.id) continue; + for (const group_id of ep.member_of) { + await this.subscribe(group_id); + } + } + }); + } + + async subscribe(group_id: number): Promise { + if (this._multicast.indexOf(group_id) >= 0) { + debug.log("%s is already subscribed", group_id); + return EmberStatus.SUCCESS; + } + + try { + const idx = this._available.pop(); + const entry: EmberMulticastTableEntry = new EmberMulticastTableEntry(); + entry.endpoint = 1; + entry.multicastId = group_id; + entry.networkIndex = 0; + const [status] = await this.driver.ezsp.setMulticastTableEntry(idx, entry); + if (status !== EmberStatus.SUCCESS) { + debug.log( + "Set MulticastTableEntry #%s for %s multicast id: %s", + idx, + entry.multicastId, + status, + ); + this._available.push(idx); + return status; + } + + this._multicast[entry.multicastId] = [entry, idx]; + debug.log( + "Set MulticastTableEntry #%s for %s multicast id: %s", + idx, + entry.multicastId, + status, + ); + return status; + } catch (e) { + debug.log("No more available slots MulticastId subscription"); + return EmberStatus.INDEX_OUT_OF_RANGE; + } + } +} diff --git a/src/adapter/ezsp/driver/types/basic.ts b/src/adapter/ezsp/driver/types/basic.ts new file mode 100644 index 0000000000..310bba03ea --- /dev/null +++ b/src/adapter/ezsp/driver/types/basic.ts @@ -0,0 +1,207 @@ +/* istanbul ignore file */ +export class int_t { + static _signed = true; + + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + static serialize(cls: any, value: number): Buffer { + const buffer = Buffer.alloc(cls._size, 0); + if (cls._signed) { + buffer.writeIntLE(value, 0, cls._size); + } else { + buffer.writeUIntLE(value, 0, cls._size); + } + return buffer; + } + + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + static deserialize(cls: any, data: Buffer): any[] { + return [cls._signed ? data.readIntLE(0, cls._size) : data.readUIntLE(0, cls._size), data.slice(cls._size)]; + } + + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + static valueToName(cls: any, value: any): string { + for (const prop of Object.getOwnPropertyNames(cls)) { + const desc = Object.getOwnPropertyDescriptor(cls, prop); + if (desc !== undefined && desc.enumerable && desc.writable && value == desc.value) { + return `${cls.name}.${prop}`; + } + } + return ''; + } + + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + static valueName(cls: any, value: any): string { + for (const prop of Object.getOwnPropertyNames(cls)) { + const desc = Object.getOwnPropertyDescriptor(cls, prop); + if (desc !== undefined && desc.enumerable && desc.writable && value == desc.value) { + return `${prop}`; + } + } + return ''; + } +} + +export class int8s extends int_t { + static _size = 1; +} + +export class int16s extends int_t { + static _size = 2; +} + +export class int24s extends int_t { + static _size = 3; +} + +export class int32s extends int_t { + static _size = 4; +} + +export class int64s extends int_t { + static _size = 8; +} + +export class uint_t extends int_t { + static _signed = false; +} + +export class uint8_t extends uint_t { + static _size = 1; +} + +export class uint16_t extends uint_t { + static _size = 2; +} + +export class uint24_t extends uint_t { + static _size = 3; +} + +export class uint32_t extends uint_t { + static _size = 4; +} + +export class uint64_t extends uint_t { + static _size = 8; +} + +export class LVBytes { + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + static serialize(cls: any, value: any[]): Buffer { + if (Buffer.isBuffer(value)) { + const ret = Buffer.alloc(1); + ret.writeUInt8(value.length, 0); + return Buffer.concat([ret, value]); + } + return Buffer.from([value.length].concat(value)); + } + + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + static deserialize(cls: any, data: Buffer): any[] { + const l = data.readIntLE(0, 1); + const s = data.slice(1, (l + 1)); + return [s, data.slice((l + 1))]; + } +} + +export abstract class List { + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + static serialize(cls: any, value: any[]): Buffer { + console.assert(((cls._length === null) || (cls.length === cls._length))); + return Buffer.from(value.map(i => i.serialize(cls, i))); + } + + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + static deserialize(cls: any, data: Buffer): any[] { + let item; + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + const r: any[] = []; + while (data) { + [item, data] = cls.itemtype.deserialize(cls.itemtype, data); + r.push(item); + } + return [r, data]; + } +} + +class _LVList extends List { + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + static serialize(cls: any, value: any[]): Buffer { + const head = [cls.length]; + const data = super.serialize(cls, value); + return Buffer.from(head.concat(data)); + } + + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + static deserialize(cls: any, data: Buffer): any[] { + let item, length; + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + const r: any[] = []; + [length, data] = [data[0], data.slice(1)]; + for (let i = 0; i < length; i++) { + [item, data] = cls.itemtype.deserialize(cls.itemtype, data); + r.push(item); + } + return [r, data]; + } +} + +/* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ +export function list(itemtype: any): List { + class ConreteList extends List { + static itemtype = itemtype; + } + + return ConreteList; +} + +/* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ +export function LVList(itemtype: any): List { + class LVList extends _LVList { + static itemtype = itemtype; + } + + return LVList; +} + +export class WordList extends List { + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + static serialize(cls: any, value: any[]): Buffer { + const data = value.map(i => Buffer.from(uint16_t.serialize(uint16_t, i))); + return Buffer.concat(data); + } +} + +class _FixedList extends List { + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + static serialize(cls: any, value: any[]): Buffer { + const data = value.map(i => cls.itemtype.serialize(cls.itemtype, i)[0]); + return Buffer.from(data); + } + + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + static deserialize(cls: any, data: Buffer): any[] { + let item; + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + const r: any[] = []; + for (let i = 0; i < cls._length; i++) { + [item, data] = cls.itemtype.deserialize(cls.itemtype, data); + r.push(item); + } + return [r, data]; + } +} + +/* eslint-disable @typescript-eslint/no-explicit-any*/ +export function fixed_list(length: number, itemtype: any): { + new(): any; + deserialize(cls: any, data: Buffer): any; +} { + class FixedList extends _FixedList { + static itemtype = itemtype; + static _length = length; + } + + return FixedList; +} +/* eslint-enable @typescript-eslint/no-explicit-any*/ \ No newline at end of file diff --git a/src/adapter/ezsp/driver/types/index.ts b/src/adapter/ezsp/driver/types/index.ts new file mode 100644 index 0000000000..69453dd63b --- /dev/null +++ b/src/adapter/ezsp/driver/types/index.ts @@ -0,0 +1,241 @@ +/* istanbul ignore file */ +import { + int8s, + uint_t, + uint8_t, + uint16_t, + uint24_t, + uint32_t, + uint64_t, + LVBytes, + list, + LVList, + fixed_list, + WordList, +} from './basic'; + +import { + NcpResetCode, + EmberRf4ceTxOption, + EmberRf4ceNodeCapabilities, + EmberRf4ceApplicationCapabilities, + EmberNodeId, + EmberPanId, + EmberMulticastId, + EmberEUI64, + EmberLibraryStatus, + SecureEzspSecurityType, + SecureEzspSecurityLevel, + EmberGpSecurityLevel, + EmberGpKeyType, + SecureEzspRandomNumber, + SecureEzspSessionId, + Bool, + EzspConfigId, + EzspValueId, + EzspExtendedValueId, + EzspEndpointFlags, + EmberConfigTxPowerMode, + EzspPolicyId, + EzspDecisionId, + EzspMfgTokenId, + EzspStatus, + EmberStatus, + EmberEventUnits, + EmberNodeType, + EmberNetworkStatus, + EmberIncomingMessageType, + EmberOutgoingMessageType, + EmberMacPassthroughType, + EmberBindingType, + EmberApsOption, + EzspNetworkScanType, + EmberJoinDecision, + EmberInitialSecurityBitmask, + EmberCurrentSecurityBitmask, + EmberKeyType, + EmberKeyStructBitmask, + EmberDeviceUpdate, + EmberKeyStatus, + EmberCounterType, + EmberJoinMethod, + EmberZdoConfigurationFlags, + EmberConcentratorType, + EmberZllState, + EmberZllKeyIndex, + EzspZllNetworkOperation, + EzspSourceRouteOverheadInformation, + EmberNetworkInitBitmask, + EmberZDOCmd, +} from './named'; + +import { + EzspStruct, + EmberNetworkParameters, + EmberZigbeeNetwork, + EmberApsFrame, + EmberBindingTableEntry, + EmberMulticastTableEntry, + EmberKeyData, + EmberCertificateData, + EmberPublicKeyData, + EmberPrivateKeyData, + EmberSmacData, + EmberSignatureData, + EmberCertificate283k1Data, + EmberPublicKey283k1Data, + EmberPrivateKey283k1Data, + EmberSignature283k1Data, + EmberMessageDigest, + EmberAesMmoHashContext, + EmberNeighborTableEntry, + EmberRouteTableEntry, + EmberInitialSecurityState, + EmberCurrentSecurityState, + EmberKeyStruct, + EmberNetworkInitStruct, + EmberZllSecurityAlgorithmData, + EmberZllNetwork, + EmberZllInitialSecurityState, + EmberZllDeviceInfoRecord, + EmberZllAddressAssignment, + EmberTokTypeStackZllData, + EmberTokTypeStackZllSecurity, + EmberRf4ceVendorInfo, + EmberRf4ceApplicationInfo, + EmberRf4cePairingTableEntry, + EmberGpAddress, + EmberGpSinkListEntry, + EmberNodeDescriptor, + EmberSimpleDescriptor, + EmberMultiAddress, + EmberNeighbors, +} from './struct'; + +/* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ +export function deserialize(payload: any, schema: any[]): any[] { + const result = []; + let value, data = payload; + for (const type of schema) { + [value, data] = type.deserialize(type, data); + result.push(value); + } + return [result, data]; +} + +/* eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types*/ +export function serialize(data: any[], schema: { serialize: Function }[]): Buffer { + return Buffer.concat(schema.map((s, idx) => s.serialize(s, data[idx]))); +} + +export { + /* Basic Types */ + int8s, + uint_t, + uint8_t, + uint16_t, + uint24_t, + uint32_t, + uint64_t, + LVBytes, + list, + LVList, + fixed_list, + WordList, + + /* Named Types */ + NcpResetCode, + EmberRf4ceTxOption, + EmberRf4ceNodeCapabilities, + EmberRf4ceApplicationCapabilities, + EmberNodeId, + EmberPanId, + EmberMulticastId, + EmberEUI64, + EmberLibraryStatus, + SecureEzspSecurityType, + SecureEzspSecurityLevel, + EmberGpSecurityLevel, + EmberGpKeyType, + SecureEzspRandomNumber, + SecureEzspSessionId, + Bool, + EzspConfigId, + EzspValueId, + EzspExtendedValueId, + EzspEndpointFlags, + EmberConfigTxPowerMode, + EzspPolicyId, + EzspDecisionId, + EzspMfgTokenId, + EzspStatus, + EmberStatus, + EmberEventUnits, + EmberNodeType, + EmberNetworkStatus, + EmberIncomingMessageType, + EmberOutgoingMessageType, + EmberMacPassthroughType, + EmberBindingType, + EmberApsOption, + EzspNetworkScanType, + EmberJoinDecision, + EmberInitialSecurityBitmask, + EmberCurrentSecurityBitmask, + EmberKeyType, + EmberKeyStructBitmask, + EmberDeviceUpdate, + EmberKeyStatus, + EmberCounterType, + EmberJoinMethod, + EmberZdoConfigurationFlags, + EmberConcentratorType, + EmberZllState, + EmberZllKeyIndex, + EzspZllNetworkOperation, + EzspSourceRouteOverheadInformation, + EmberNetworkInitBitmask, + EmberZDOCmd, + + /* Structs */ + EzspStruct, + EmberNetworkParameters, + EmberZigbeeNetwork, + EmberApsFrame, + EmberBindingTableEntry, + EmberMulticastTableEntry, + EmberKeyData, + EmberCertificateData, + EmberPublicKeyData, + EmberPrivateKeyData, + EmberSmacData, + EmberSignatureData, + EmberCertificate283k1Data, + EmberPublicKey283k1Data, + EmberPrivateKey283k1Data, + EmberSignature283k1Data, + EmberMessageDigest, + EmberAesMmoHashContext, + EmberNeighborTableEntry, + EmberRouteTableEntry, + EmberInitialSecurityState, + EmberCurrentSecurityState, + EmberKeyStruct, + EmberNetworkInitStruct, + EmberZllSecurityAlgorithmData, + EmberZllNetwork, + EmberZllInitialSecurityState, + EmberZllDeviceInfoRecord, + EmberZllAddressAssignment, + EmberTokTypeStackZllData, + EmberTokTypeStackZllSecurity, + EmberRf4ceVendorInfo, + EmberRf4ceApplicationInfo, + EmberRf4cePairingTableEntry, + EmberGpAddress, + EmberGpSinkListEntry, + EmberNodeDescriptor, + EmberSimpleDescriptor, + EmberMultiAddress, + EmberNeighbors, +}; diff --git a/src/adapter/ezsp/driver/types/named.ts b/src/adapter/ezsp/driver/types/named.ts new file mode 100644 index 0000000000..fe0dfa487c --- /dev/null +++ b/src/adapter/ezsp/driver/types/named.ts @@ -0,0 +1,1768 @@ +/* istanbul ignore file */ +import * as basic from './basic'; +import {fixed_list} from './basic'; + +console.assert(basic.uint8_t); + +export class NcpResetCode extends basic.uint8_t { + //Reset and Error Codes for NCP + static RESET_UNKNOWN_REASON = 0x00; + static RESET_EXTERNAL = 0x01; + static RESET_POWER_ON = 0x02; + static RESET_WATCHDOG = 0x03; + static RESET_ASSERT = 0x06; + static RESET_BOOTLOADER = 0x09; + static RESET_SOFTWARE = 0x0B; + static ERROR_EXCEEDED_MAXIMUM_ACK_TIMEOUT_COUNT = 0x51; + static ERROR_UNKNOWN_EM3XX_ERROR = 0x80; +} + +export class EmberRf4ceTxOption extends basic.uint8_t { +} + +export class EmberRf4ceNodeCapabilities extends basic.uint8_t { +} + +export class EmberRf4ceApplicationCapabilities extends basic.uint8_t { +} + +export class EmberNodeId extends basic.uint16_t { +} + +export class EmberPanId extends basic.uint16_t { +} + +export class EmberMulticastId extends basic.uint8_t { +} + +export class EmberEUI64 extends fixed_list(8, basic.uint8_t) { + + constructor(private _value: ArrayLike | string) { + super(); + if (typeof (_value) === 'string') { + if (_value.startsWith('0x')) + _value = _value.slice(2); + if ((_value as string).length !== 16) { + throw new Error('Incorrect value passed'); + } + this._value = Buffer.from(_value, 'hex'); + } else { + if (_value.length !== 8) { + throw new Error('Incorrect value passed'); + } + this._value = _value; + } + } + + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + static deserialize(cls: any, data: Buffer): any[] { + const arr = super.deserialize(cls, data); + const r = arr[0]; + data = arr[1] as Buffer; + return [(r as number[]).reverse(), data]; + } + + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + static serialize(cls: any, value: number[] | EmberEUI64): Buffer { + if (value instanceof EmberEUI64) { + value = (value as EmberEUI64).value as number[]; + } + console.assert(cls._length === value.length); + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + const val = (value as any[]).reverse().map(i => basic.uint8_t.serialize(basic.uint8_t, i)[0]); + return Buffer.from(val); + } + + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + public get value(): any { + return this._value; + } + + public toString(): string { + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + return Buffer.from(this._value as any).toString('hex'); + } +} + +export class EmberLibraryStatus extends basic.uint8_t { +} + +export class SecureEzspSecurityType extends basic.uint32_t { +} + +export class SecureEzspSecurityLevel extends basic.uint8_t { +} + +export class EmberGpSecurityLevel extends basic.uint8_t { +} + +export class EmberGpKeyType extends basic.uint8_t { +} + +export class SecureEzspRandomNumber extends basic.uint64_t { +} + +export class SecureEzspSessionId extends basic.uint64_t { +} + +export class Bool extends basic.uint8_t { + static false = 0x00; // An alias for zero, used for clarity. + static true = 0x01; // An alias for one, used for clarity. +} + +export class EzspConfigId extends basic.uint8_t { + // Identifies a configuration value. + + // The number of packet buffers available to the stack. When set to the + // special value 0xFF, the NCP will allocate all remaining configuration RAM + // towards packet buffers, such that the resulting count will be the largest + // whole number of packet buffers that can fit into the available memory. + static CONFIG_PACKET_BUFFER_COUNT = 0x01; + // The maximum number of router neighbors the stack can keep track of. A + // neighbor is a node within radio range. + static CONFIG_NEIGHBOR_TABLE_SIZE = 0x02; + // The maximum number of APS retried messages the stack can be transmitting + // at any time. + static CONFIG_APS_UNICAST_MESSAGE_COUNT = 0x03; + // The maximum number of non-volatile bindings supported by the stack. + static CONFIG_BINDING_TABLE_SIZE = 0x04; + // The maximum number of EUI64 to network address associations that the + // stack can maintain for the application. (Note, the total number of such + // address associations maintained by the NCP is the sum of the value of + // this setting and the value of ::CONFIG_TRUST_CENTER_ADDRESS_CACHE_SIZE). + static CONFIG_ADDRESS_TABLE_SIZE = 0x05; + // The maximum number of multicast groups that the device may be a member + // of. + static CONFIG_MULTICAST_TABLE_SIZE = 0x06; + // The maximum number of destinations to which a node can route messages. + // This includes both messages originating at this node and those relayed + // for others. + static CONFIG_ROUTE_TABLE_SIZE = 0x07; + // The number of simultaneous route discoveries that a node will support. + static CONFIG_DISCOVERY_TABLE_SIZE = 0x08; + // The size of the alarm broadcast buffer. + static CONFIG_BROADCAST_ALARM_DATA_SIZE = 0x09; + // The size of the unicast alarm buffers allocated for end device children. + static CONFIG_UNICAST_ALARM_DATA_SIZE = 0x0A; + // Specifies the stack profile. + static CONFIG_STACK_PROFILE = 0x0C; + // The security level used for security at the MAC and network layers. The + // supported values are 0 (no security) and 5 (payload is encrypted and a + // four-byte MIC is used for authentication). + static CONFIG_SECURITY_LEVEL = 0x0D; + // The maximum number of hops for a message. + static CONFIG_MAX_HOPS = 0x10; + // The maximum number of end device children that a router will support. + static CONFIG_MAX_END_DEVICE_CHILDREN = 0x11; + // The maximum amount of time that the MAC will hold a message for indirect + // transmission to a child. + static CONFIG_INDIRECT_TRANSMISSION_TIMEOUT = 0x12; + // The maximum amount of time that an end device child can wait between + // polls. If no poll is heard within this timeout, then the parent removes + // the end device from its tables. + static CONFIG_END_DEVICE_POLL_TIMEOUT = 0x13; + // The maximum amount of time that a mobile node can wait between polls. If + // no poll is heard within this timeout, then the parent removes the mobile + // node from its tables. + static CONFIG_MOBILE_NODE_POLL_TIMEOUT = 0x14; + // The number of child table entries reserved for use only by mobile nodes. + static CONFIG_RESERVED_MOBILE_CHILD_ENTRIES = 0x15; + // Enables boost power mode and/or the alternate transmitter output. + static CONFIG_TX_POWER_MODE = 0x17; + // 0: Allow this node to relay messages. 1: Prevent this node from relaying + // messages. + static CONFIG_DISABLE_RELAY = 0x18; + // The maximum number of EUI64 to network address associations that the + // Trust Center can maintain. These address cache entries are reserved for + // and reused by the Trust Center when processing device join/rejoin + // authentications. This cache size limits the number of overlapping joins + // the Trust Center can process within a narrow time window (e.g. two + // seconds), and thus should be set to the maximum number of near + // simultaneous joins the Trust Center is expected to accommodate. (Note, + // the total number of such address associations maintained by the NCP is + // the sum of the value of this setting and the value of + // ::CONFIG_ADDRESS_TABLE_SIZE.) + static CONFIG_TRUST_CENTER_ADDRESS_CACHE_SIZE = 0x19; + // The size of the source route table. + static CONFIG_SOURCE_ROUTE_TABLE_SIZE = 0x1A; + // The units used for timing out end devices on their parents. + static CONFIG_END_DEVICE_POLL_TIMEOUT_SHIFT = 0x1B; + // The number of blocks of a fragmented message that can be sent in a single + // window. + static CONFIG_FRAGMENT_WINDOW_SIZE = 0x1C; + // The time the stack will wait (in milliseconds) between sending blocks of + // a fragmented message. + static CONFIG_FRAGMENT_DELAY_MS = 0x1D; + // The size of the Key Table used for storing individual link keys (if the + // device is a Trust Center) or Application Link Keys (if the device is a + // normal node). + static CONFIG_KEY_TABLE_SIZE = 0x1E; + // The APS ACK timeout value. The stack waits this amount of time between + // resends of APS retried messages. + static CONFIG_APS_ACK_TIMEOUT = 0x1F; + // The duration of an active scan, in the units used by the 15.4 scan + // parameter (((1 << duration) + 1) * 15ms). This also controls the jitter + // used when responding to a beacon request. + static CONFIG_ACTIVE_SCAN_DURATION = 0x20; + // The time the coordinator will wait (in seconds) for a second end device + // bind request to arrive. + static CONFIG_END_DEVICE_BIND_TIMEOUT = 0x21; + // The number of PAN id conflict reports that must be received by the + // network manager within one minute to trigger a PAN id change. + static CONFIG_PAN_ID_CONFLICT_REPORT_THRESHOLD = 0x22; + // The timeout value in minutes for how long the Trust Center or a normal + // node waits for the ZigBee Request Key to complete. On the Trust Center + // this controls whether or not the device buffers the request, waiting for + // a matching pair of ZigBee Request Key. If the value is non-zero, the + // Trust Center buffers and waits for that amount of time. If the value is + // zero, the Trust Center does not buffer the request and immediately + // responds to the request. Zero is the most compliant behavior. + static CONFIG_REQUEST_KEY_TIMEOUT = 0x24; + // This value indicates the size of the runtime modifiable certificate + // table. Normally certificates are stored in MFG tokens but this table can + // be used to field upgrade devices with new Smart Energy certificates. + // This value cannot be set, it can only be queried. + static CONFIG_CERTIFICATE_TABLE_SIZE = 0x29; + // This is a bitmask that controls which incoming ZDO request messages are + // passed to the application. The bits are defined in the + // EmberZdoConfigurationFlags enumeration. To see if the application is + // required to send a ZDO response in reply to an incoming message, the + // application must check the APS options bitfield within the + // incomingMessageHandler callback to see if the + // APS_OPTION_ZDO_RESPONSE_REQUIRED flag is set. + static CONFIG_APPLICATION_ZDO_FLAGS = 0x2A; + // The maximum number of broadcasts during a single broadcast timeout + // period. + static CONFIG_BROADCAST_TABLE_SIZE = 0x2B; + // The size of the MAC filter list table. + static CONFIG_MAC_FILTER_TABLE_SIZE = 0x2C; + // The number of supported networks. + static CONFIG_SUPPORTED_NETWORKS = 0x2D; + // Whether multicasts are sent to the RxOnWhenIdle=true address (0xFFFD) or + // the sleepy broadcast address (0xFFFF). The RxOnWhenIdle=true address is + // the ZigBee compliant destination for multicasts. + static CONFIG_SEND_MULTICASTS_TO_SLEEPY_ADDRESS = 0x2E; + // ZLL group address initial configuration. + static CONFIG_ZLL_GROUP_ADDRESSES = 0x2F; + // ZLL rssi threshold initial configuration. + static CONFIG_ZLL_RSSI_THRESHOLD = 0x30; + // The maximum number of pairings supported by the stack. Controllers + // must support at least one pairing table entry while targets must + // support at least five. + static CONFIG_RF4CE_PAIRING_TABLE_SIZE = 0x31; + // The maximum number of outgoing RF4CE packets supported by the stack. + static CONFIG_RF4CE_PENDING_OUTGOING_PACKET_TABLE_SIZE = 0x32; + // Toggles the mtorr flow control in the stack. + static CONFIG_MTORR_FLOW_CONTROL = 0x33; + // Setting the retry queue size. + static CONFIG_RETRY_QUEUE_SIZE = 0x34; + // Setting the new broadcast entry threshold. + static CONFIG_NEW_BROADCAST_ENTRY_THRESHOLD = 0x35; + // The length of time, in seconds, that a trust center will store a + // transient link key that a device can use to join its network. A transient + // key is added with a call to emberAddTransientLinkKey. After the transient + // key is added, it will be removed once this amount of time has passed. A + // joining device will not be able to use that key to join until it is added + // again on the trust center. The default value is 300 seconds, i.e., 5 + // minutes. + static CONFIG_TRANSIENT_KEY_TIMEOUT_S = 0x36; + // The number of passive acknowledgements to record from neighbors before we stop + // re-transmitting broadcasts + static CONFIG_BROADCAST_MIN_ACKS_NEEDED = 0x37; + // The length of time, in seconds, that a trust center will allow a Trust Center + // (insecure) rejoin for a device that is using the well-known link key. This timeout + // takes effect once rejoins using the well-known key has been allowed. This command + // updates the emAllowTcRejoinsUsingWellKnownKeyTimeoutSec value. + static CONFIG_TC_REJOINS_USING_WELL_KNOWN_KEY_TIMEOUT_S = 0x38; +} + + +export class EzspValueId extends basic.uint8_t { + // Identifies a value. + + // The contents of the node data stack token. + static VALUE_TOKEN_STACK_NODE_DATA = 0x00; + // The types of MAC passthrough messages that the host wishes to receive. + static VALUE_MAC_PASSTHROUGH_FLAGS = 0x01; + // The source address used to filter legacy EmberNet messages when the + // MAC_PASSTHROUGH_EMBERNET_SOURCE flag is set in + // VALUE_MAC_PASSTHROUGH_FLAGS. + static VALUE_EMBERNET_PASSTHROUGH_SOURCE_ADDRESS = 0x02; + // The number of available message buffers. + static VALUE_FREE_BUFFERS = 0x03; + // Selects sending synchronous callbacks in ezsp-uart. + static VALUE_UART_SYNCH_CALLBACKS = 0x04; + // The maximum incoming transfer size for the local node. + static VALUE_MAXIMUM_INCOMING_TRANSFER_SIZE = 0x05; + // The maximum outgoing transfer size for the local node. + static VALUE_MAXIMUM_OUTGOING_TRANSFER_SIZE = 0x06; + // A boolean indicating whether stack tokens are written to persistent + // storage as they change. + static VALUE_STACK_TOKEN_WRITING = 0x07; + // A read-only value indicating whether the stack is currently performing a + // rejoin. + static VALUE_STACK_IS_PERFORMING_REJOIN = 0x08; + // A list of EmberMacFilterMatchData values. + static VALUE_MAC_FILTER_LIST = 0x09; + // The Ember Extended Security Bitmask. + static VALUE_EXTENDED_SECURITY_BITMASK = 0x0A; + // The node short ID. + static VALUE_NODE_SHORT_ID = 0x0B; + // The descriptor capability of the local node. + static VALUE_DESCRIPTOR_CAPABILITY = 0x0C; + // The stack device request sequence number of the local node. + static VALUE_STACK_DEVICE_REQUEST_SEQUENCE_NUMBER = 0x0D; + // Enable or disable radio hold-off. + static VALUE_RADIO_HOLD_OFF = 0x0E; + // The flags field associated with the endpoint data. + static VALUE_ENDPOINT_FLAGS = 0x0F; + // Enable/disable the Mfg security config key settings. + static VALUE_MFG_SECURITY_CONFIG = 0x10; + // Retrieves the version information from the stack on the NCP. + static VALUE_VERSION_INFO = 0x11; + // This will get/set the rejoin reason noted by the host for a subsequent + // call to emberFindAndRejoinNetwork(). After a call to + // emberFindAndRejoinNetwork() the host's rejoin reason will be set to + // REJOIN_REASON_NONE. The NCP will store the rejoin reason used by the + // call to emberFindAndRejoinNetwork() + static VALUE_NEXT_HOST_REJOIN_REASON = 0x12; + // This is the reason that the last rejoin took place. This value may only + // be retrieved, not set. The rejoin may have been initiated by the stack + // (NCP) or the application (host). If a host initiated a rejoin the reason + // will be set by default to REJOIN_DUE_TO_APP_EVENT_1. If the application + // wishes to denote its own rejoin reasons it can do so by calling + // ezspSetValue(VALUE_HOST_REJOIN_REASON, REJOIN_DUE_TO_APP_EVENT_X). X is a + // number corresponding to one of the app events defined. If the NCP + // initiated a rejoin it will record this value internally for retrieval by + // ezspGetValue(VALUE_REAL_REJOIN_REASON). + static VALUE_LAST_REJOIN_REASON = 0x13; + // The next ZigBee sequence number. + static VALUE_NEXT_ZIGBEE_SEQUENCE_NUMBER = 0x14; + // CCA energy detect threshold for radio. + static VALUE_CCA_THRESHOLD = 0x15; + // The threshold value for a counter + static VALUE_SET_COUNTER_THRESHOLD = 0x17; + // Resets all counters thresholds to 0xFF + static VALUE_RESET_COUNTER_THRESHOLDS = 0x18; + // Clears all the counters + static VALUE_CLEAR_COUNTERS = 0x19; + // The device RF4CE base channel + static VALUE_RF4CE_BASE_CHANNEL = 0x1A; + // The RF4CE device types supported by the node + static VALUE_RF4CE_SUPPORTED_DEVICE_TYPES_LIST = 0x1B; + // The RF4CE profiles supported by the node + static VALUE_RF4CE_SUPPORTED_PROFILES_LIST = 0x1C; + // Setting this byte enables R21 behavior on the NCP. + static VALUE_ENABLE_R21_BEHAVIOR = 0x29; + // Configure the antenna mode(0-primary,1-secondary,2- toggle on tx ack + // fail). + static VALUE_ANTENNA_MODE = 0x30; + // The GDP binding recipient parameters + static VALUE_RF4CE_GDP_BINDING_RECIPIENT_PARAMETERS = 0x1D; + // The GDP binding push button stimulus received pending flag + static VALUE_RF4CE_GDP_PUSH_BUTTON_STIMULUS_RECEIVED_PENDING_FLAG = 0x1E; + // The GDP originator proxy flag in the advanced binding options + static VALUE_RF4CE_GDP_BINDING_PROXY_FLAG = 0x1F; + // The GDP application specific user s join unti_VALUE_RF4CE_MSO_USER_STRING + // 0x21 The MSO user string + static VALUE_RF4CE_GDP_APPLICATION_SPECIFIC_USER_STRING = 0x20; + // The MSO user string + static VALUE_RF4CE_MSO_USER_STRING = 0x21; + // The MSO binding recipient parameters + static VALUE_RF4CE_MSO_BINDING_RECIPIENT_PARAMETERS = 0x22; + // The NWK layer security frame counter value + static VALUE_NWK_FRAME_COUNTER = 0x23; + // The APS layer security frame counter value + static VALUE_APS_FRAME_COUNTER = 0x24; + // Sets the device type to use on the next rejoin using device type + static VALUE_RETRY_DEVICE_TYPE = 0x25; + // The device RF4CE base channel + static VALUE_RF4CE_BASE_CHANNEL2 = 0x26; + // The RF4CE device types supported by the node + static VALUE_RF4CE_SUPPORTED_DEVICE_TYPES_LIST2 = 0x27; + // The RF4CE profiles supported by the node + static VALUE_RF4CE_SUPPORTED_PROFILES_LIST2 = 0x28; + // Enable or disable packet traffic arbitration. + static VALUE_ENABLE_PTA = 0x31; + // Set packet traffic arbitration configuration options. + static VALUE_PTA_OPTIONS = 0x32; + // Configure manufacturing library options(0-non-CSMA transmits,1-CSMA transmits). + static VALUE_MFGLIB_OPTIONS = 0x33; + + static VALUE_END_DEVICE_KEEP_ALIVE_SUPPORT_MODE = 0x3F; +} + +export class EzspExtendedValueId extends basic.uint8_t { + // Identifies a value based on specified characteristics. Each set of + // characteristics is unique to that value and is specified during the call + // to get the extended value. + + // The flags field associated with the specified endpoint. + static EXTENDED_VALUE_ENDPOINT_FLAGS = 0x00; + // This is the reason for the node to leave the network as well as the + // device that told it to leave. The leave reason is the 1st byte of the + // value while the node ID is the 2nd and 3rd byte. If the leave was caused + // due to an API call rather than an over the air message, the node ID will + // be UNKNOWN_NODE_ID (0xFFFD). + static EXTENDED_VALUE_LAST_LEAVE_REASON = 0x01; + // This number of bytes of overhead required in the network frame for source + // routing to a particular destination. + static EXTENDED_VALUE_GET_SOURCE_ROUTE_OVERHEAD = 0x02; +} + + +export class EzspEndpointFlags extends basic.uint16_t { + // Flags associated with the endpoint data configured on the NCP. + + // Indicates that the endpoint is disabled and NOT discoverable via ZDO. + static ENDPOINT_DISABLED = 0x00; + // Indicates that the endpoint is enabled and discoverable via ZDO. + static ENDPOINT_ENABLED = 0x01; +} + + +export class EmberConfigTxPowerMode extends basic.uint16_t { + // Values for CONFIG_TX_POWER_MODE. + + // Normal power mode and bi-directional RF transmitter output. + static TX_POWER_MODE_DEFAULT = 0x00; + // Enable boost power mode. This is a high performance radio mode which + // offers increased receive sensitivity and transmit power at the cost of an + // increase in power consumption. + static TX_POWER_MODE_BOOST = 0x01; + // Enable the alternate transmitter output. This allows for simplified + // connection to an external power amplifier via the RF_TX_ALT_P and + // RF_TX_ALT_N pins. TX_POWER_MODE_BOOST_AND_ALTERNATE 0x03 Enable both + // boost mode and the alternate transmitter output. + static TX_POWER_MODE_ALTERNATE = 0x02; +} + + +export class EzspPolicyId extends basic.uint8_t { + // Identifies a policy. + + // Controls trust center behavior. + static TRUST_CENTER_POLICY = 0x00; + // Controls how external binding modification requests are handled. + static BINDING_MODIFICATION_POLICY = 0x01; + // Controls whether the Host supplies unicast replies. + static UNICAST_REPLIES_POLICY = 0x02; + // Controls whether pollHandler callbacks are generated. + static POLL_HANDLER_POLICY = 0x03; + // Controls whether the message contents are included in the + // messageSentHandler callback. + static MESSAGE_CONTENTS_IN_CALLBACK_POLICY = 0x04; + // Controls whether the Trust Center will respond to Trust Center link key + // requests. + static TC_KEY_REQUEST_POLICY = 0x05; + // Controls whether the Trust Center will respond to application link key + // requests. + static APP_KEY_REQUEST_POLICY = 0x06; + // Controls whether ZigBee packets that appear invalid are automatically + // dropped by the stack. A counter will be incremented when this occurs. + static PACKET_VALIDATE_LIBRARY_POLICY = 0x07; + // Controls whether the stack will process ZLL messages. + static ZLL_POLICY = 0x08; + + static TC_REJOINS_USING_WELL_KNOWN_KEY_POLICY = 0x09; +} + + +export class EzspDecisionId extends basic.uint16_t { + // Identifies a policy decision. + + // Send the network key in the clear to all joining and rejoining devices. + static ALLOW_JOINS = 0x00; + // Send the network key in the clear to all joining devices. Rejoining + // devices are sent the network key encrypted with their trust center link + // key. The trust center and any rejoining device are assumed to share a + // link key, either preconfigured or obtained under a previous policy. + static ALLOW_JOINS_REJOINS_HAVE_LINK_KEY = 0x04; + // Send the network key encrypted with the joining or rejoining device's + // trust center link key. The trust center and any joining or rejoining + // device are assumed to share a link key, either preconfigured or obtained + // under a previous policy. This is the default value for the + // TRUST_CENTER_POLICY. + static ALLOW_PRECONFIGURED_KEY_JOINS = 0x01; + // Send the network key encrypted with the rejoining device's trust center + // link key. The trust center and any rejoining device are assumed to share + // a link key, either preconfigured or obtained under a previous policy. No + // new devices are allowed to join. + static ALLOW_REJOINS_ONLY = 0x02; + // Reject all unsecured join and rejoin attempts. + static DISALLOW_ALL_JOINS_AND_REJOINS = 0x03; + // Take no action on trust center rejoin attempts. + static IGNORE_TRUST_CENTER_REJOINS = 0x05; + // BINDING_MODIFICATION_POLICY default decision. Do not allow the local + // binding table to be changed by remote nodes. + static DISALLOW_BINDING_MODIFICATION = 0x10; + // BINDING_MODIFICATION_POLICY decision. Allow remote nodes to change + // the local binding table. + static ALLOW_BINDING_MODIFICATION = 0x11; + // BINDING_MODIFICATION_POLICY decision. Allows remote nodes to set local + // binding entries only if the entries correspond to endpoints defined on + // the device, and for output clusters bound to those endpoints. + static CHECK_BINDING_MODIFICATIONS_ARE_VALID_ENDPOINT_CLUSTERS = 0x12; + // UNICAST_REPLIES_POLICY default decision. The NCP will automatically send + // an empty reply (containing no payload) for every unicast received. + static HOST_WILL_NOT_SUPPLY_REPLY = 0x20; + // UNICAST_REPLIES_POLICY decision. The NCP will only send a reply if it + // receives a sendReply command from the Host. + static HOST_WILL_SUPPLY_REPLY = 0x21; + // POLL_HANDLER_POLICY default decision. Do not inform the Host when a child + // polls. + static POLL_HANDLER_IGNORE = 0x30; + // POLL_HANDLER_POLICY decision. Generate a pollHandler callback when a + // child polls. + static POLL_HANDLER_CALLBACK = 0x31; + // MESSAGE_CONTENTS_IN_CALLBACK_POLICY default decision. Include only the + // message tag in the messageSentHandler callback. + static MESSAGE_TAG_ONLY_IN_CALLBACK = 0x40; + // MESSAGE_CONTENTS_IN_CALLBACK_POLICY decision. Include both the message + // tag and the message contents in the messageSentHandler callback. + static MESSAGE_TAG_AND_CONTENTS_IN_CALLBACK = 0x41; + // TC_KEY_REQUEST_POLICY decision. When the Trust Center receives a request + // for a Trust Center link key, it will be ignored. + static DENY_TC_KEY_REQUESTS = 0x50; + // TC_KEY_REQUEST_POLICY decision. When the Trust Center receives a request + // for a Trust Center link key, it will reply to it with the corresponding + // key. + static ALLOW_TC_KEY_REQUESTS = 0x51; + // TC_KEY_REQUEST_POLICY decision. When the Trust Center receives a request + // for a Trust Center link key, it will generate a key to send to the + // joiner. + static GENERATE_NEW_TC_LINK_KEY = 0x52; + // APP_KEY_REQUEST_POLICY decision. When the Trust Center receives a request + // for an application link key, it will be ignored. + static DENY_APP_KEY_REQUESTS = 0x60; + // APP_KEY_REQUEST_POLICY decision. When the Trust Center receives a request + // for an application link key, it will randomly generate a key and send it + // to both partners. + static ALLOW_APP_KEY_REQUESTS = 0x61; + // Indicates that packet validate library checks are enabled on the NCP. + static PACKET_VALIDATE_LIBRARY_CHECKS_ENABLED = 0x62; + // Indicates that packet validate library checks are NOT enabled on the NCP. + static PACKET_VALIDATE_LIBRARY_CHECKS_DISABLED = 0x63; +} + + +export class EzspMfgTokenId extends basic.uint8_t { + // Manufacturing token IDs used by ezspGetMfgToken(). + + // Custom version (2 bytes). + static MFG_CUSTOM_VERSION = 0x00; + // Manufacturing string (16 bytes). + static MFG_STRING = 0x01; + // Board name (16 bytes). + static MFG_BOARD_NAME = 0x02; + // Manufacturing ID (2 bytes). + static MFG_MANUF_ID = 0x03; + // Radio configuration (2 bytes). + static MFG_PHY_CONFIG = 0x04; + // Bootload AES key (16 bytes). + static MFG_BOOTLOAD_AES_KEY = 0x05; + // ASH configuration (40 bytes). + static MFG_ASH_CONFIG = 0x06; + // EZSP storage (8 bytes). + static MFG_STORAGE = 0x07; + // Radio calibration data (64 bytes). 4 bytes are stored for each of the 16 + // channels. This token is not stored in the Flash Information Area. It is + // updated by the stack each time a calibration is performed. + static STACK_CAL_DATA = 0x08; + // Certificate Based Key Exchange (CBKE) data (92 bytes). + static MFG_CBKE_DATA = 0x09; + // Installation code (20 bytes). + static MFG_INSTALLATION_CODE = 0x0A; + // Radio channel filter calibration data (1 byte). This token is not stored + // in the Flash Information Area. It is updated by the stack each time a + // calibration is performed. + static STACK_CAL_FILTER = 0x0B; + // Custom EUI64 MAC address (8 bytes). + static MFG_CUSTOM_EUI_64 = 0x0C; + // CTUNE value (2 byte). + static MFG_CTUNE = 0x0D; +} + +export class EzspStatus extends basic.uint8_t { + // Status values used by EZSP. + + // Success. + static SUCCESS = 0x00; + // Fatal error. + static SPI_ERR_FATAL = 0x10; + // The Response frame of the current transaction indicates the NCP has + // reset. + static SPI_ERR_NCP_RESET = 0x11; + // The NCP is reporting that the Command frame of the current transaction is + // oversized (the length byte is too large). + static SPI_ERR_OVERSIZED_FRAME = 0x12; + // The Response frame of the current transaction indicates the previous + // transaction was aborted (nSSEL deasserted too soon). + static SPI_ERR_ABORTED_TRANSACTION = 0x13; + // The Response frame of the current transaction indicates the frame + // terminator is missing from the Command frame. + static SPI_ERR_MISSING_FRAME_TERMINATOR = 0x14; + // The NCP has not provided a Response within the time limit defined by + // WAIT_SECTION_TIMEOUT. + static SPI_ERR_WAIT_SECTION_TIMEOUT = 0x15; + // The Response frame from the NCP is missing the frame terminator. + static SPI_ERR_NO_FRAME_TERMINATOR = 0x16; + // The Host attempted to send an oversized Command (the length byte is too + // large) and the AVR's spi-protocol.c blocked the transmission. + static SPI_ERR_COMMAND_OVERSIZED = 0x17; + // The NCP attempted to send an oversized Response (the length byte is too + // large) and the AVR's spi-protocol.c blocked the reception. + static SPI_ERR_RESPONSE_OVERSIZED = 0x18; + // The Host has sent the Command and is still waiting for the NCP to send a + // Response. + static SPI_WAITING_FOR_RESPONSE = 0x19; + // The NCP has not asserted nHOST_INT within the time limit defined by + // WAKE_HANDSHAKE_TIMEOUT. + static SPI_ERR_HANDSHAKE_TIMEOUT = 0x1A; + // The NCP has not asserted nHOST_INT after an NCP reset within the time + // limit defined by STARTUP_TIMEOUT. + static SPI_ERR_STARTUP_TIMEOUT = 0x1B; + // The Host attempted to verify the SPI Protocol activity and version + // number, and the verification failed. + static SPI_ERR_STARTUP_FAIL = 0x1C; + // The Host has sent a command with a SPI Byte that is unsupported by the + // current mode the NCP is operating in. + static SPI_ERR_UNSUPPORTED_SPI_COMMAND = 0x1D; + // Operation not yet complete. + static ASH_IN_PROGRESS = 0x20; + // Fatal error detected by host. + static HOST_FATAL_ERROR = 0x21; + // Fatal error detected by NCP. + static ASH_NCP_FATAL_ERROR = 0x22; + // Tried to send DATA frame too long. + static DATA_FRAME_TOO_LONG = 0x23; + // Tried to send DATA frame too short. + static DATA_FRAME_TOO_SHORT = 0x24; + // No space for tx'ed DATA frame. + static NO_TX_SPACE = 0x25; + // No space for rec'd DATA frame. + static NO_RX_SPACE = 0x26; + // No receive data available. + static NO_RX_DATA = 0x27; + // Not in Connected state. + static NOT_CONNECTED = 0x28; + // The NCP received a command before the EZSP version had been set. + static ERROR_VERSION_NOT_SET = 0x30; + // The NCP received a command containing an unsupported frame ID. + static ERROR_INVALID_FRAME_ID = 0x31; + // The direction flag in the frame control field was incorrect. + static ERROR_WRONG_DIRECTION = 0x32; + // The truncated flag in the frame control field was set, indicating there + // was not enough memory available to complete the response or that the + // response would have exceeded the maximum EZSP frame length. + static ERROR_TRUNCATED = 0x33; + // The overflow flag in the frame control field was set, indicating one or + // more callbacks occurred since the previous response and there was not + // enough memory available to report them to the Host. + static ERROR_OVERFLOW = 0x34; + // Insufficient memory was available. + static ERROR_OUT_OF_MEMORY = 0x35; + // The value was out of bounds. + static ERROR_INVALID_VALUE = 0x36; + // The configuration id was not recognized. + static ERROR_INVALID_ID = 0x37; + // Configuration values can no longer be modified. + static ERROR_INVALID_CALL = 0x38; + // The NCP failed to respond to a command. + static ERROR_NO_RESPONSE = 0x39; + // The length of the command exceeded the maximum EZSP frame length. + static ERROR_COMMAND_TOO_LONG = 0x40; + // The UART receive queue was full causing a callback response to be + // dropped. + static ERROR_QUEUE_FULL = 0x41; + // The command has been filtered out by NCP. + static ERROR_COMMAND_FILTERED = 0x42; + // EZSP Security Key is already set + static ERROR_SECURITY_KEY_ALREADY_SET = 0x43; + // EZSP Security Type is invalid + static ERROR_SECURITY_TYPE_INVALID = 0x44; + // EZSP Security Parameters are invalid + static ERROR_SECURITY_PARAMETERS_INVALID = 0x45; + // EZSP Security Parameters are already set + static ERROR_SECURITY_PARAMETERS_ALREADY_SET = 0x46; + // EZSP Security Key is not set + static ERROR_SECURITY_KEY_NOT_SET = 0x47; + // EZSP Security Parameters are not set + static ERROR_SECURITY_PARAMETERS_NOT_SET = 0x48; + // Received frame with unsupported control byte + static ERROR_UNSUPPORTED_CONTROL = 0x49; + // Received frame is unsecure, when security is established + static ERROR_UNSECURE_FRAME = 0x4A; + // Incompatible ASH version + static ASH_ERROR_VERSION = 0x50; + // Exceeded max ACK timeouts + static ASH_ERROR_TIMEOUTS = 0x51; + // Timed out waiting for RSTACK + static ASH_ERROR_RESET_FAIL = 0x52; + // Unexpected ncp reset + static ASH_ERROR_NCP_RESET = 0x53; + // Serial port initialization failed + static ERROR_SERIAL_INIT = 0x54; + // Invalid ncp processor type + static ASH_ERROR_NCP_TYPE = 0x55; + // Invalid ncp reset method + static ASH_ERROR_RESET_METHOD = 0x56; + // XON/XOFF not supported by host driver + static ASH_ERROR_XON_XOFF = 0x57; + // ASH protocol started + static ASH_STARTED = 0x70; + // ASH protocol connected + static ASH_CONNECTED = 0x71; + // ASH protocol disconnected + static ASH_DISCONNECTED = 0x72; + // Timer expired waiting for ack + static ASH_ACK_TIMEOUT = 0x73; + // Frame in progress cancelled + static ASH_CANCELLED = 0x74; + // Received frame out of sequence + static ASH_OUT_OF_SEQUENCE = 0x75; + // Received frame with CRC error + static ASH_BAD_CRC = 0x76; + // Received frame with comm error + static ASH_COMM_ERROR = 0x77; + // Received frame with bad ackNum + static ASH_BAD_ACKNUM = 0x78; + // Received frame shorter than minimum + static ASH_TOO_SHORT = 0x79; + // Received frame longer than maximum + static ASH_TOO_LONG = 0x7A; + // Received frame with illegal control byte + static ASH_BAD_CONTROL = 0x7B; + // Received frame with illegal length for its type + static ASH_BAD_LENGTH = 0x7C; + // Received ASH Ack + static ASH_ACK_RECEIVED = 0x7D; + // Sent ASH Ack + static ASH_ACK_SENT = 0x7E; + // No reset or error + static NO_ERROR = 0xFF; +} + +export class EmberStatus extends basic.uint8_t { + // Return type for stack functions. + + // The generic 'no error' message. + static SUCCESS = 0x00; + // The generic 'fatal error' message. + static ERR_FATAL = 0x01; + // An invalid value was passed as an argument to a function + static BAD_ARGUMENT = 0x02; + // The manufacturing and stack token format in nonvolatile memory is + // different than what the stack expects (returned at initialization). + static EEPROM_MFG_STACK_VERSION_MISMATCH = 0x04; + // The static memory definitions in ember-staticmemory.h are incompatible + // with this stack version. + static INCOMPATIBLE_STATIC_MEMORY_DEFINITIONS = 0x05; + // The manufacturing token format in non-volatile memory is different than + // what the stack expects (returned at initialization). + static EEPROM_MFG_VERSION_MISMATCH = 0x06; + // The stack token format in non-volatile memory is different than what the + // stack expects (returned at initialization). + static EEPROM_STACK_VERSION_MISMATCH = 0x07; + // There are no more buffers. + static NO_BUFFERS = 0x18; + // Specified an invalid baud rate. + static SERIAL_INVALID_BAUD_RATE = 0x20; + // Specified an invalid serial port. + static SERIAL_INVALID_PORT = 0x21; + // Tried to send too much data. + static SERIAL_TX_OVERFLOW = 0x22; + // There was not enough space to store a received character and the + // character was dropped. + static SERIAL_RX_OVERFLOW = 0x23; + // Detected a UART framing error. + static SERIAL_RX_FRAME_ERROR = 0x24; + // Detected a UART parity error. + static SERIAL_RX_PARITY_ERROR = 0x25; + // There is no received data to process. + static SERIAL_RX_EMPTY = 0x26; + // The receive interrupt was not handled in time, and a character was + // dropped. + static SERIAL_RX_OVERRUN_ERROR = 0x27; + // The MAC transmit queue is full. + static MAC_TRANSMIT_QUEUE_FULL = 0x39; + // MAC header FCR error on receive. + static MAC_UNKNOWN_HEADER_TYPE = 0x3A; + // The MAC can't complete this task because it is scanning. + static MAC_SCANNING = 0x3D; + // No pending data exists for device doing a data poll. + static MAC_NO_DATA = 0x31; + // Attempt to scan when we are joined to a network. + static MAC_JOINED_NETWORK = 0x32; + // Scan duration must be 0 to 14 inclusive. Attempt was made to scan with an + // incorrect duration value. + static MAC_BAD_SCAN_DURATION = 0x33; + // emberStartScan was called with an incorrect scan type. + static MAC_INCORRECT_SCAN_TYPE = 0x34; + // emberStartScan was called with an invalid channel mask. + static MAC_INVALID_CHANNEL_MASK = 0x35; + // Failed to scan current channel because we were unable to transmit the + // relevant MAC command. + static MAC_COMMAND_TRANSMIT_FAILURE = 0x36; + // We expected to receive an ACK following the transmission, but the MAC + // level ACK was never received. + static MAC_NO_ACK_RECEIVED = 0x40; + // Indirect data message timed out before polled. + static MAC_INDIRECT_TIMEOUT = 0x42; + // The Simulated EEPROM is telling the application that there is at least + // one flash page to be erased. The GREEN status means the current page has + // not filled above the ERASE_CRITICAL_THRESHOLD. The application should + // call the function halSimEepromErasePage when it can to erase a page. + static SIM_EEPROM_ERASE_PAGE_GREEN = 0x43; + // The Simulated EEPROM is telling the application that there is at least + // one flash page to be erased. The RED status means the current page has + // filled above the ERASE_CRITICAL_THRESHOLD. Due to the shrinking + // availability of write space, there is a danger of data loss. The + // application must call the function halSimEepromErasePage as soon as + // possible to erase a page. + static SIM_EEPROM_ERASE_PAGE_RED = 0x44; + // The Simulated EEPROM has run out of room to write any new data and the + // data trying to be set has been lost. This error code is the result of + // ignoring the SIM_EEPROM_ERASE_PAGE_RED error code. The application must + // call the function halSimEepromErasePage to make room for any further + // calls to set a token. + static SIM_EEPROM_FULL = 0x45; + // A fatal error has occurred while trying to write data to the Flash. The + // target memory attempting to be programmed is already programmed. The + // flash write routines were asked to flip a bit from a 0 to 1, which is + // physically impossible and the write was therefore inhibited. The data in + // the flash cannot be trusted after this error. + static ERR_FLASH_WRITE_INHIBITED = 0x46; + // A fatal error has occurred while trying to write data to the Flash and + // the write verification has failed. The data in the flash cannot be + // trusted after this error, and it is possible this error is the result of + // exceeding the life cycles of the flash. + static ERR_FLASH_VERIFY_FAILED = 0x47; + // Attempt 1 to initialize the Simulated EEPROM has failed. This failure + // means the information already stored in Flash (or a lack thereof), is + // fatally incompatible with the token information compiled into the code + // image being run. + static SIM_EEPROM_INIT_1_FAILED = 0x48; + // Attempt 2 to initialize the Simulated EEPROM has failed. This failure + // means Attempt 1 failed, and the token system failed to properly reload + // default tokens and reset the Simulated EEPROM. + static SIM_EEPROM_INIT_2_FAILED = 0x49; + // Attempt 3 to initialize the Simulated EEPROM has failed. This failure + // means one or both of the tokens TOKEN_MFG_NVDATA_VERSION or + // TOKEN_STACK_NVDATA_VERSION were incorrect and the token system failed to + // properly reload default tokens and reset the Simulated EEPROM. + static SIM_EEPROM_INIT_3_FAILED = 0x4A; + // A fatal error has occurred while trying to write data to the flash, + // possibly due to write protection or an invalid address. The data in the + // flash cannot be trusted after this error, and it is possible this error + // is the result of exceeding the life cycles of the flash. + static ERR_FLASH_PROG_FAIL = 0x4B; + // A fatal error has occurred while trying to erase flash, possibly due to + // write protection. The data in the flash cannot be trusted after this + // error, and it is possible this error is the result of exceeding the life + // cycles of the flash. + static ERR_FLASH_ERASE_FAIL = 0x4C; + // The bootloader received an invalid message (failed attempt to go into + // bootloader). + static ERR_BOOTLOADER_TRAP_TABLE_BAD = 0x58; + // Bootloader received an invalid message (failed attempt to go into + // bootloader). + static ERR_BOOTLOADER_TRAP_UNKNOWN = 0x59; + // The bootloader cannot complete the bootload operation because either an + // image was not found or the image exceeded memory bounds. + static ERR_BOOTLOADER_NO_IMAGE = 0x5A; + // The APS layer attempted to send or deliver a message, but it failed. + static DELIVERY_FAILED = 0x66; + // This binding index is out of range of the current binding table. + static BINDING_INDEX_OUT_OF_RANGE = 0x69; + // This address table index is out of range for the current address table. + static ADDRESS_TABLE_INDEX_OUT_OF_RANGE = 0x6A; + // An invalid binding table index was given to a function. + static INVALID_BINDING_INDEX = 0x6C; + // The API call is not allowed given the current state of the stack. + static INVALID_CALL = 0x70; + // The link cost to a node is not known. + static COST_NOT_KNOWN = 0x71; + // The maximum number of in-flight messages (i.e. + // APS_UNICAST_MESSAGE_COUNT) has been reached. + static MAX_MESSAGE_LIMIT_REACHED = 0x72; + // The message to be transmitted is too big to fit into a single over-the- + // air packet. + static MESSAGE_TOO_LONG = 0x74; + // The application is trying to delete or overwrite a binding that is in + // use. + static BINDING_IS_ACTIVE = 0x75; + // The application is trying to overwrite an address table entry that is in + // use. + static ADDRESS_TABLE_ENTRY_IS_ACTIVE = 0x76; + // Conversion is complete. + static ADC_CONVERSION_DONE = 0x80; + // Conversion cannot be done because a request is being processed. + static ADC_CONVERSION_BUSY = 0x81; + // Conversion is deferred until the current request has been processed. + static ADC_CONVERSION_DEFERRED = 0x82; + // No results are pending. + static ADC_NO_CONVERSION_PENDING = 0x84; + // Sleeping (for a duration) has been abnormally interrupted and exited + // prematurely. + static SLEEP_INTERRUPTED = 0x85; + // The transmit hardware buffer underflowed. + static PHY_TX_UNDERFLOW = 0x88; + // The transmit hardware did not finish transmitting a packet. + static PHY_TX_INCOMPLETE = 0x89; + // An unsupported channel setting was specified. + static PHY_INVALID_CHANNEL = 0x8A; + // An unsupported power setting was specified. + static PHY_INVALID_POWER = 0x8B; + // The packet cannot be transmitted because the physical MAC layer is + // currently transmitting a packet. (This is used for the MAC backoff + // algorithm.) PHY_TX_CCA_FAIL 0x8D The transmit attempt failed because all + // CCA attempts indicated that the channel was busy + static PHY_TX_BUSY = 0x8C; + // The software installed on the hardware doesn't recognize the hardware + // radio type. + static PHY_OSCILLATOR_CHECK_FAILED = 0x8E; + // The expected ACK was received after the last transmission. + static PHY_ACK_RECEIVED = 0x8F; + // The stack software has completed initialization and is ready to send and + // receive packets over the air. + static NETWORK_UP = 0x90; + // The network is not operating. + static NETWORK_DOWN = 0x91; + // An attempt to join a network failed. + static JOIN_FAILED = 0x94; + // After moving, a mobile node's attempt to re-establish contact with the + // network failed. + static MOVE_FAILED = 0x96; + // An attempt to join as a router failed due to a ZigBee versus ZigBee Pro + // incompatibility. ZigBee devices joining ZigBee Pro networks (or vice + // versa) must join as End Devices, not Routers. + static CANNOT_JOIN_AS_ROUTER = 0x98; + // The local node ID has changed. The application can obtain the new node ID + // by calling emberGetNodeId(). + static NODE_ID_CHANGED = 0x99; + // The local PAN ID has changed. The application can obtain the new PAN ID + // by calling emberGetPanId(). + static PAN_ID_CHANGED = 0x9A; + // An attempt to join or rejoin the network failed because no router beacons + // could be heard by the joining node. + static NO_BEACONS = 0xAB; + // An attempt was made to join a Secured Network using a pre-configured key, + // but the Trust Center sent back a Network Key in-the-clear when an + // encrypted Network Key was required. + static RECEIVED_KEY_IN_THE_CLEAR = 0xAC; + // An attempt was made to join a Secured Network, but the device did not + // receive a Network Key. + static NO_NETWORK_KEY_RECEIVED = 0xAD; + // After a device joined a Secured Network, a Link Key was requested but no + // response was ever received. + static NO_LINK_KEY_RECEIVED = 0xAE; + // An attempt was made to join a Secured Network without a pre-configured + // key, but the Trust Center sent encrypted data using a pre-configured key. + static PRECONFIGURED_KEY_REQUIRED = 0xAF; + // The node has not joined a network. + static NOT_JOINED = 0x93; + // The chosen security level (the value of SECURITY_LEVEL) is not supported + // by the stack. + static INVALID_SECURITY_LEVEL = 0x95; + // A message cannot be sent because the network is currently overloaded. + static NETWORK_BUSY = 0xA1; + // The application tried to send a message using an endpoint that it has not + // defined. + static INVALID_ENDPOINT = 0xA3; + // The application tried to use a binding that has been remotely modified + // and the change has not yet been reported to the application. + static BINDING_HAS_CHANGED = 0xA4; + // An attempt to generate random bytes failed because of insufficient random + // data from the radio. + static INSUFFICIENT_RANDOM_DATA = 0xA5; + // There was an error in trying to encrypt at the APS Level. This could + // result from either an inability to determine the long address of the + // recipient from the short address (no entry in the binding table) or there + // is no link key entry in the table associated with the destination, or + // there was a failure to load the correct key into the encryption core. + // TRUST_CENTER_MASTER_KEY_NOT_SET 0xA7 There was an attempt to form a + // network using commercial security without setting the Trust Center master + // key first. + static APS_ENCRYPTION_ERROR = 0xA6; + // There was an attempt to form or join a network with security without + // calling emberSetInitialSecurityState() first. + static SECURITY_STATE_NOT_SET = 0xA8; + // There was an attempt to set an entry in the key table using an invalid + // long address. An entry cannot be set using either the local device's or + // Trust Center's IEEE address. Or an entry already exists in the table with + // the same IEEE address. An Address of all zeros or all F's are not valid + // addresses in 802.15.4. + static KEY_TABLE_INVALID_ADDRESS = 0xB3; + // There was an attempt to set a security configuration that is not valid + // given the other security settings. + static SECURITY_CONFIGURATION_INVALID = 0xB7; + // There was an attempt to broadcast a key switch too quickly after + // broadcasting the next network key. The Trust Center must wait at least a + // period equal to the broadcast timeout so that all routers have a chance + // to receive the broadcast of the new network key. + static TOO_SOON_FOR_SWITCH_KEY = 0xB8; + // The message could not be sent because the link key corresponding to the + // destination is not authorized for use in APS data messages. APS Commands + // (sent by the stack) are allowed. To use it for encryption of APS data + // messages it must be authorized using a key agreement protocol (such as + // CBKE). + static KEY_NOT_AUTHORIZED = 0xBB; + // The security data provided was not valid, or an integrity check failed. + static SECURITY_DATA_INVALID = 0xBD; + // A ZigBee route error command frame was received indicating that a source + // routed message from this node failed en route. + static SOURCE_ROUTE_FAILURE = 0xA9; + // A ZigBee route error command frame was received indicating that a message + // sent to this node along a many-to-one route failed en route. The route + // error frame was delivered by an ad-hoc search for a functioning route. + static MANY_TO_ONE_ROUTE_FAILURE = 0xAA; + // A critical and fatal error indicating that the version of the stack + // trying to run does not match with the chip it is running on. The software + // (stack) on the chip must be replaced with software that is compatible + // with the chip. + static STACK_AND_HARDWARE_MISMATCH = 0xB0; + // An index was passed into the function that was larger than the valid + // range. + static INDEX_OUT_OF_RANGE = 0xB1; + // There are no empty entries left in the table. + static TABLE_FULL = 0xB4; + // The requested table entry has been erased and contains no valid data. + static TABLE_ENTRY_ERASED = 0xB6; + // The requested function cannot be executed because the library that + // contains the necessary functionality is not present. + static LIBRARY_NOT_PRESENT = 0xB5; + // The stack accepted the command and is currently processing the request. + // The results will be returned via an appropriate handler. + static OPERATION_IN_PROGRESS = 0xBA; + // This error is reserved for customer application use. This will never be + // returned from any portion of the network stack or HAL. + static APPLICATION_ERROR_0 = 0xF0; + // This error is reserved for customer application use. This will never be + // returned from any portion of the network stack or HAL. + static APPLICATION_ERROR_1 = 0xF1; + // This error is reserved for customer application use. This will never be + // returned from any portion of the network stack or HAL. + static APPLICATION_ERROR_2 = 0xF2; + // This error is reserved for customer application use. This will never be + // returned from any portion of the network stack or HAL. + static APPLICATION_ERROR_3 = 0xF3; + // This error is reserved for customer application use. This will never be + // returned from any portion of the network stack or HAL. + static APPLICATION_ERROR_4 = 0xF4; + // This error is reserved for customer application use. This will never be + // returned from any portion of the network stack or HAL. + static APPLICATION_ERROR_5 = 0xF5; + // This error is reserved for customer application use. This will never be + // returned from any portion of the network stack or HAL. + static APPLICATION_ERROR_6 = 0xF6; + // This error is reserved for customer application use. This will never be + // returned from any portion of the network stack or HAL. + static APPLICATION_ERROR_7 = 0xF7; + // This error is reserved for customer application use. This will never be + // returned from any portion of the network stack or HAL. + static APPLICATION_ERROR_8 = 0xF8; + // This error is reserved for customer application use. This will never be + // returned from any portion of the network stack or HAL. + static APPLICATION_ERROR_9 = 0xF9; + // This error is reserved for customer application use. This will never be + // returned from any portion of the network stack or HAL. + static APPLICATION_ERROR_10 = 0xFA; + // This error is reserved for customer application use. This will never be + // returned from any portion of the network stack or HAL. + static APPLICATION_ERROR_11 = 0xFB; + // This error is reserved for customer application use. This will never be + // returned from any portion of the network stack or HAL. + static APPLICATION_ERROR_12 = 0xFC; + // This error is reserved for customer application use. This will never be + // returned from any portion of the network stack or HAL. + static APPLICATION_ERROR_13 = 0xFD; + // This error is reserved for customer application use. This will never be + // returned from any portion of the network stack or HAL. + static APPLICATION_ERROR_14 = 0xFE; + // This error is reserved for customer application use. This will never be + // returned from any portion of the network stack or HAL. + static APPLICATION_ERROR_15 = 0xFF; +} + + +export class EmberEventUnits extends basic.uint8_t { + // Either marks an event as inactive or specifies the units for the event + // execution time. + + // The event is not scheduled to run. + static EVENT_INACTIVE = 0x00; + // The execution time is in approximate milliseconds. + static EVENT_MS_TIME = 0x01; + // The execution time is in 'binary' quarter seconds (256 approximate + // milliseconds each). + static EVENT_QS_TIME = 0x02; + // The execution time is in 'binary' minutes (65536 approximate milliseconds + // each). + static EVENT_MINUTE_TIME = 0x03; +} + + +export class EmberNodeType extends basic.uint8_t { + // The type of the node. + + // Device is not joined. + static UNKNOWN_DEVICE = 0x00; + // Will relay messages and can act as a parent to other nodes. + static COORDINATOR = 0x01; + // Will relay messages and can act as a parent to other nodes. + static ROUTER = 0x02; + // Communicates only with its parent and will not relay messages. + static END_DEVICE = 0x03; + // An end device whose radio can be turned off to save power. The + // application must poll to receive messages. + static SLEEPY_END_DEVICE = 0x04; + // A sleepy end device that can move through the network. + static MOBILE_END_DEVICE = 0x05; +} + + +export class EmberNetworkStatus extends basic.uint8_t { + // The possible join states for a node. + + // The node is not associated with a network in any way. + static NO_NETWORK = 0x00; + // The node is currently attempting to join a network. + static JOINING_NETWORK = 0x01; + // The node is joined to a network. + static JOINED_NETWORK = 0x02; + // The node is an end device joined to a network but its parent is not + // responding. + static JOINED_NETWORK_NO_PARENT = 0x03; + // The node is in the process of leaving its current network. + static LEAVING_NETWORK = 0x04; +} + + +export class EmberIncomingMessageType extends basic.uint8_t { + // Incoming message types. + + // Unicast. + static INCOMING_UNICAST = 0x00; + // Unicast reply. + static INCOMING_UNICAST_REPLY = 0x01; + // Multicast. + static INCOMING_MULTICAST = 0x02; + // Multicast sent by the local device. + static INCOMING_MULTICAST_LOOPBACK = 0x03; + // Broadcast. + static INCOMING_BROADCAST = 0x04; + // Broadcast sent by the local device. + static INCOMING_BROADCAST_LOOPBACK = 0x05; + // Many to one route request. + static INCOMING_MANY_TO_ONE_ROUTE_REQUEST = 0x06; +} + + +export class EmberOutgoingMessageType extends basic.uint8_t { + // Outgoing message types. + + // Unicast sent directly to an EmberNodeId. + static OUTGOING_DIRECT = 0x00; + // Unicast sent using an entry in the address table. + static OUTGOING_VIA_ADDRESS_TABLE = 0x01; + // Unicast sent using an entry in the binding table. + static OUTGOING_VIA_BINDING = 0x02; + // Multicast message. This value is passed to emberMessageSentHandler() + // only. It may not be passed to emberSendUnicast(). + static OUTGOING_MULTICAST = 0x03; + // Broadcast message. This value is passed to emberMessageSentHandler() + // only. It may not be passed to emberSendUnicast(). + static OUTGOING_BROADCAST = 0x04; +} + + +export class EmberMacPassthroughType extends basic.uint8_t { + // MAC passthrough message type flags. + + // No MAC passthrough messages. + static MAC_PASSTHROUGH_NONE = 0x00; + // SE InterPAN messages. + static MAC_PASSTHROUGH_SE_INTERPAN = 0x01; + // Legacy EmberNet messages. + static MAC_PASSTHROUGH_EMBERNET = 0x02; + // Legacy EmberNet messages filtered by their source address. + static MAC_PASSTHROUGH_EMBERNET_SOURCE = 0x04; + static MAC_PASSTHROUGH_APPLICATION = 0x08; + static MAC_PASSTHROUGH_CUSTOM = 0x10; + static MAC_PASSTHROUGH_INTERNAL = 0x80; +} + + +export class EmberBindingType extends basic.uint8_t { + // Binding types. + + // A binding that is currently not in use. + static UNUSED_BINDING = 0x00; + // A unicast binding whose 64-bit identifier is the destination EUI64. + static UNICAST_BINDING = 0x01; + // A unicast binding whose 64-bit identifier is the aggregator EUI64. + static MANY_TO_ONE_BINDING = 0x02; + // A multicast binding whose 64-bit identifier is the group address. A + // multicast binding can be used to send messages to the group and to + // receive messages sent to the group. + static MULTICAST_BINDING = 0x03; +} + + +export class EmberApsOption extends basic.uint16_t { + // Options to use when sending a message. + + // No options. + static APS_OPTION_NONE = 0x0000; + // UNKNOWN: Discovered while receiving data + static APS_OPTION_UNKNOWN = 0x0008; + // Send the message using APS Encryption, using the Link Key shared with the + // destination node to encrypt the data at the APS Level. + static APS_OPTION_ENCRYPTION = 0x0020; + // Resend the message using the APS retry mechanism. + static APS_OPTION_RETRY = 0x0040; + // Causes a route discovery to be initiated if no route to the destination + // is known. + static APS_OPTION_ENABLE_ROUTE_DISCOVERY = 0x0100; + // Causes a route discovery to be initiated even if one is known. + static APS_OPTION_FORCE_ROUTE_DISCOVERY = 0x0200; + // Include the source EUI64 in the network frame. + static APS_OPTION_SOURCE_EUI64 = 0x0400; + // Include the destination EUI64 in the network frame. + static APS_OPTION_DESTINATION_EUI64 = 0x0800; + // Send a ZDO request to discover the node ID of the destination, if it is + // not already know. + static APS_OPTION_ENABLE_ADDRESS_DISCOVERY = 0x1000; + // Reserved. + static APS_OPTION_POLL_RESPONSE = 0x2000; + // This incoming message is a ZDO request not handled by the EmberZNet + // stack, and the application is responsible for sending a ZDO response. + // This flag is used only when the ZDO is configured to have requests + // handled by the application. See the CONFIG_APPLICATION_ZDO_FLAGS + // configuration parameter for more information. + static APS_OPTION_ZDO_RESPONSE_REQUIRED = 0x4000; + // This message is part of a fragmented message. This option may only be set + // for unicasts. The groupId field gives the index of this fragment in the + // low-order byte. If the low-order byte is zero this is the first fragment + // and the high-order byte contains the number of fragments in the message. + static APS_OPTION_FRAGMENT = 0x8000; +} + + +export class EzspNetworkScanType extends basic.uint8_t { + // Network scan types. + + // An energy scan scans each channel for its RSSI value. + static ENERGY_SCAN = 0x00; + // An active scan scans each channel for available networks. + static ACTIVE_SCAN = 0x01; +} + + +export class EmberJoinDecision extends basic.uint8_t { + // Decision made by the trust center when a node attempts to join. + + // Allow the node to join. The joining node should have a pre-configured + // key. The security data sent to it will be encrypted with that key. + static USE_PRECONFIGURED_KEY = 0x00; + // Allow the node to join. Send the necessary key (the Network Key in + // Standard Security mode, the Trust Center Master in High Security mode) + // in-the-clear to the joining device. + static SEND_KEY_IN_THE_CLEAR = 0x01; + // Deny join. + static DENY_JOIN = 0x02; + // Take no action. + static NO_ACTION = 0x03; +} + + +export class EmberInitialSecurityBitmask extends basic.uint16_t { + // This is the Initial Security Bitmask that controls the use of various + // security features. + + // This enables ZigBee Standard Security on the node. + static STANDARD_SECURITY_MODE = 0x0000; + // This enables Distributed Trust Center Mode for the device forming the + // network. (Previously known as NO_TRUST_CENTER_MODE) + static DISTRIBUTED_TRUST_CENTER_MODE = 0x0002; + // This enables a Global Link Key for the Trust Center. All nodes will share + // the same Trust Center Link Key. + static TRUST_CENTER_GLOBAL_LINK_KEY = 0x0004; + // This enables devices that perform MAC Association with a pre-configured + // Network Key to join the network. It is only set on the Trust Center. + static PRECONFIGURED_NETWORK_KEY_MODE = 0x0008; + // This denotes that the preconfiguredKey is not the actual Link Key but a + // Secret Key known only to the Trust Center. It is hashed with the IEEE + // Address of the destination device in order to create the actual Link Key + // used in encryption. This is bit is only used by the Trust Center. The + // joining device need not set this. + static TRUST_CENTER_USES_HASHED_LINK_KEY = 0x0084; + // This denotes that the preconfiguredKey element has valid data that should + // be used to configure the initial security state. + static HAVE_PRECONFIGURED_KEY = 0x0100; + // This denotes that the networkKey element has valid data that should be + // used to configure the initial security state. + static HAVE_NETWORK_KEY = 0x0200; + // This denotes to a joining node that it should attempt to acquire a Trust + // Center Link Key during joining. This is only necessary if the device does + // not have a pre-configured key. + static GET_LINK_KEY_WHEN_JOINING = 0x0400; + // This denotes that a joining device should only accept an encrypted + // network key from the Trust Center (using its preconfigured key). A key + // sent in-the-clear by the Trust Center will be rejected and the join will + // fail. This option is only valid when utilizing a pre-configured key. + static REQUIRE_ENCRYPTED_KEY = 0x0800; + // This denotes whether the device should NOT reset its outgoing frame + // counters (both NWK and APS) when ::emberSetInitialSecurityState() is + // called. Normally it is advised to reset the frame counter before joining + // a new network. However in cases where a device is joining to the same + // network a again (but not using ::emberRejoinNetwork()) it should keep the + // NWK and APS frame counters stored in its tokens. + static NO_FRAME_COUNTER_RESET = 0x1000; + // This denotes that the device should obtain its preconfigured key from an + // installation code stored in the manufacturing token. The token contains a + // value that will be hashed to obtain the actual preconfigured key. If that + // token is not valid, then the call to emberSetInitialSecurityState() will + // fail. + static GET_PRECONFIGURED_KEY_FROM_INSTALL_CODE = 0x2000; + // This denotes that the + // ::EmberInitialSecurityState::preconfiguredTrustCenterEui64 has a value in + // it containing the trust center EUI64. The device will only join a network + // and accept commands from a trust center with that EUI64. Normally this + // bit is NOT set, and the EUI64 of the trust center is learned during the + // join process. When commissioning a device to join onto an existing + // network, which is using a trust center, and without sending any messages, + // this bit must be set and the field + // ::EmberInitialSecurityState::preconfiguredTrustCenterEui64 must be + // populated with the appropriate EUI64. + static HAVE_TRUST_CENTER_EUI64 = 0x0040; +} + + +export class EmberCurrentSecurityBitmask extends basic.uint16_t { + // This is the Current Security Bitmask that details the use of various + // security features. + + // This denotes that the device is running in a network with ZigBee Standard + // Security. + static STANDARD_SECURITY_MODE = 0x0000; + // This denotes that the device is running in a network with ZigBee High + // Security. + static HIGH_SECURITY_MODE = 0x0001; + // This denotes that the device is running in a network without a + // centralized Trust Center. + static DISTRIBUTED_TRUST_CENTER_MODE = 0x0002; + // This denotes that the device has a Global Link Key. The Trust Center Link + // Key is the same across multiple nodes. + static GLOBAL_LINK_KEY = 0x0004; + // This denotes that the node has a Trust Center Link Key. + static HAVE_TRUST_CENTER_LINK_KEY = 0x0010; + // This denotes that the Trust Center is using a Hashed Link Key. + static TRUST_CENTER_USES_HASHED_LINK_KEY = 0x0084; +} + + +export class EmberKeyType extends basic.uint8_t { + // Describes the type of ZigBee security key. + + // A shared key between the Trust Center and a device. + static TRUST_CENTER_LINK_KEY = 0x01; + // A shared secret used for deriving keys between the Trust Center and a + // device + static TRUST_CENTER_MASTER_KEY = 0x02; + // The current active Network Key used by all devices in the network. + static CURRENT_NETWORK_KEY = 0x03; + // The alternate Network Key that was previously in use, or the newer key + // that will be switched to. + static NEXT_NETWORK_KEY = 0x04; + // An Application Link Key shared with another (non-Trust Center) device. + static APPLICATION_LINK_KEY = 0x05; + // An Application Master Key shared secret used to derive an Application + // Link Key. + static APPLICATION_MASTER_KEY = 0x06; +} + +export class EmberKeyStructBitmask extends basic.uint16_t { + // Describes the presence of valid data within the EmberKeyStruct structure. + + // The key has a sequence number associated with it. + static KEY_HAS_SEQUENCE_NUMBER = 0x0001; + // The key has an outgoing frame counter associated with it. + static KEY_HAS_OUTGOING_FRAME_COUNTER = 0x0002; + // The key has an incoming frame counter associated with it. + static KEY_HAS_INCOMING_FRAME_COUNTER = 0x0004; + // The key has a Partner IEEE address associated with it. + static KEY_HAS_PARTNER_EUI64 = 0x0008; +} + + +export class EmberDeviceUpdate extends basic.uint8_t { + // The status of the device update. + + static STANDARD_SECURITY_SECURED_REJOIN = 0x0; + static STANDARD_SECURITY_UNSECURED_JOIN = 0x1; + static DEVICE_LEFT = 0x2; + static STANDARD_SECURITY_UNSECURED_REJOIN = 0x3; + static HIGH_SECURITY_SECURED_REJOIN = 0x4; + static HIGH_SECURITY_UNSECURED_JOIN = 0x5; + static HIGH_SECURITY_UNSECURED_REJOIN = 0x7; +} + + +export class EmberKeyStatus extends basic.uint8_t { + // The status of the attempt to establish a key. + + static APP_LINK_KEY_ESTABLISHED = 0x01; + static APP_MASTER_KEY_ESTABLISHED = 0x02; + static TRUST_CENTER_LINK_KEY_ESTABLISHED = 0x03; + static KEY_ESTABLISHMENT_TIMEOUT = 0x04; + static KEY_TABLE_FULL = 0x05; + static TC_RESPONDED_TO_KEY_REQUEST = 0x06; + static TC_APP_KEY_SENT_TO_REQUESTER = 0x07; + static TC_RESPONSE_TO_KEY_REQUEST_FAILED = 0x08; + static TC_REQUEST_KEY_TYPE_NOT_SUPPORTED = 0x09; + static TC_NO_LINK_KEY_FOR_REQUESTER = 0x0A; + static TC_REQUESTER_EUI64_UNKNOWN = 0x0B; + static TC_RECEIVED_FIRST_APP_KEY_REQUEST = 0x0C; + static TC_TIMEOUT_WAITING_FOR_SECOND_APP_KEY_REQUEST = 0x0D; + static TC_NON_MATCHING_APP_KEY_REQUEST_RECEIVED = 0x0E; + static TC_FAILED_TO_SEND_APP_KEYS = 0x0F; + static TC_FAILED_TO_STORE_APP_KEY_REQUEST = 0x10; + static TC_REJECTED_APP_KEY_REQUEST = 0x11; +} + +export class EmberCounterType extends basic.uint8_t { + // Defines the events reported to the application by the + // readAndClearCounters command. + + // The MAC received a broadcast. + static COUNTER_MAC_RX_BROADCAST = 0; + // The MAC transmitted a broadcast. + static COUNTER_MAC_TX_BROADCAST = 1; + // The MAC received a unicast. + static COUNTER_MAC_RX_UNICAST = 2; + // The MAC successfully transmitted a unicast. + static COUNTER_MAC_TX_UNICAST_SUCCESS = 3; + // The MAC retried a unicast. + static COUNTER_MAC_TX_UNICAST_RETRY = 4; + // The MAC unsuccessfully transmitted a unicast. + static COUNTER_MAC_TX_UNICAST_FAILED = 5; + // The APS layer received a data broadcast. + static COUNTER_APS_DATA_RX_BROADCAST = 6; + // The APS layer transmitted a data broadcast. + static COUNTER_APS_DATA_TX_BROADCAST = 7; + // The APS layer received a data unicast. + static COUNTER_APS_DATA_RX_UNICAST = 8; + // The APS layer successfully transmitted a data unicast. + static COUNTER_APS_DATA_TX_UNICAST_SUCCESS = 9; + // The APS layer retried a data unicast. + static COUNTER_APS_DATA_TX_UNICAST_RETRY = 10; + // The APS layer unsuccessfully transmitted a data unicast. + static COUNTER_APS_DATA_TX_UNICAST_FAILED = 11; + // The network layer successfully submitted a new route discovery to the + // MAC. + static COUNTER_ROUTE_DISCOVERY_INITIATED = 12; + // An entry was added to the neighbor table. + static COUNTER_NEIGHBOR_ADDED = 13; + // An entry was removed from the neighbor table. + static COUNTER_NEIGHBOR_REMOVED = 14; + // A neighbor table entry became stale because it had not been heard from. + static COUNTER_NEIGHBOR_STALE = 15; + // A node joined or rejoined to the network via this node. + static COUNTER_JOIN_INDICATION = 16; + // An entry was removed from the child table. + static COUNTER_CHILD_REMOVED = 17; + // EZSP-UART only. An overflow error occurred in the UART. + static COUNTER_ASH_OVERFLOW_ERROR = 18; + // EZSP-UART only. A framing error occurred in the UART. + static COUNTER_ASH_FRAMING_ERROR = 19; + // EZSP-UART only. An overrun error occurred in the UART. + static COUNTER_ASH_OVERRUN_ERROR = 20; + // A message was dropped at the network layer because the NWK frame counter + // was not higher than the last message seen from that source. + static COUNTER_NWK_FRAME_COUNTER_FAILURE = 21; + // A message was dropped at the APS layer because the APS frame counter was + // not higher than the last message seen from that source. + static COUNTER_APS_FRAME_COUNTER_FAILURE = 22; + // Utility counter for general debugging use. + static COUNTER_UTILITY = 23; + // A message was dropped at the APS layer because it had APS encryption but + // the key associated with the sender has not been authenticated, and thus + // the key is not authorized for use in APS data messages. + static COUNTER_APS_LINK_KEY_NOT_AUTHORIZED = 24; + // A NWK encrypted message was received but dropped because decryption + // failed. + static COUNTER_NWK_DECRYPTION_FAILURE = 25; + // An APS encrypted message was received but dropped because decryption + // failed. + static COUNTER_APS_DECRYPTION_FAILURE = 26; + // The number of times we failed to allocate a set of linked packet buffers. + // This doesn't necessarily mean that the packet buffer count was 0 at the + // time, but that the number requested was greater than the number free. + static COUNTER_ALLOCATE_PACKET_BUFFER_FAILURE = 27; + // The number of relayed unicast packets. + static COUNTER_RELAYED_UNICAST = 28; + // The number of times we dropped a packet due to reaching + // the preset PHY to MAC queue limit (emMaxPhyToMacQueueLength). + static COUNTER_PHY_TO_MAC_QUEUE_LIMIT_REACHED = 29; + // The number of times we dropped a packet due to the + // packet-validate library checking a packet and rejecting it + // due to length or other formatting problems. + static COUNTER_PACKET_VALIDATE_LIBRARY_DROPPED_COUNT = 30; + // The number of times the NWK retry queue is full and a + // new message failed to be added. + static COUNTER_TYPE_NWK_RETRY_OVERFLOW = 31; + // The number of times the PHY layer was unable to transmit + // due to a failed CCA. + static COUNTER_PHY_CCA_FAIL_COUNT = 32; + // The number of times a NWK broadcast was dropped because + // the broadcast table was full. + static COUNTER_BROADCAST_TABLE_FULL = 33; + // The number of low priority packet traffic arbitration requests. + static COUNTER_PTA_LO_PRI_REQUESTED = 34; + // The number of high priority packet traffic arbitration requests. + static COUNTER_PTA_HI_PRI_REQUESTED = 35; + // The number of low priority packet traffic arbitration requests denied. + static COUNTER_PTA_LO_PRI_DENIED = 36; + // The number of high priority packet traffic arbitration requests denied. + static COUNTER_PTA_HI_PRI_DENIED = 37; + // The number of aborted low priority packet traffic arbitration transmissions. + static COUNTER_PTA_LO_PRI_TX_ABORTED = 38; + // The number of aborted high priority packet traffic arbitration transmissions. + static COUNTER_PTA_HI_PRI_TX_ABORTED = 39; + // A placeholder giving the number of Ember counter types. + static COUNTER_TYPE_COUNT = 40; +} + + +export class EmberJoinMethod extends basic.uint8_t { + // The type of method used for joining. + + // Normally devices use MAC Association to join a network, which respects + // the "permit joining" flag in the MAC Beacon. For mobile nodes this value + // causes the device to use an Ember Mobile Node Join, which is functionally + // equivalent to a MAC association. This value should be used by default. + static USE_MAC_ASSOCIATION = 0x0; + // For those networks where the "permit joining" flag is never turned on, + // they will need to use a ZigBee NWK Rejoin. This value causes the rejoin + // to be sent without NWK security and the Trust Center will be asked to + // send the NWK key to the device. The NWK key sent to the device can be + // encrypted with the device's corresponding Trust Center link key. That is + // determined by the ::EmberJoinDecision on the Trust Center returned by the + // ::emberTrustCenterJoinHandler(). For a mobile node this value will cause + // it to use an Ember Mobile node rejoin, which is functionally equivalent. + static USE_NWK_REJOIN = 0x1; + // For those networks where the "permit joining" flag is never turned on, + // they will need to use a NWK Rejoin. If those devices have been + // preconfigured with the NWK key (including sequence number) they can use a + // secured rejoin. This is only necessary for end devices since they need a + // parent. Routers can simply use the ::USE_NWK_COMMISSIONING join method + // below. + static USE_NWK_REJOIN_HAVE_NWK_KEY = 0x2; + // For those networks where all network and security information is known + // ahead of time, a router device may be commissioned such that it does not + // need to send any messages to begin communicating on the network. + static USE_NWK_COMMISSIONING = 0x3; +} + + +export class EmberZdoConfigurationFlags extends basic.uint8_t { + // Flags for controlling which incoming ZDO requests are passed to the + // application. To see if the application is required to send a ZDO response + // to an incoming message, the application must check the APS options + // bitfield within the incomingMessageHandler callback to see if the + // APS_OPTION_ZDO_RESPONSE_REQUIRED flag is set. + + // Set this flag in order to receive supported ZDO request messages via the + // incomingMessageHandler callback. A supported ZDO request is one that is + // handled by the EmberZNet stack. The stack will continue to handle the + // request and send the appropriate ZDO response even if this configuration + // option is enabled. + static APP_RECEIVES_SUPPORTED_ZDO_REQUESTS = 0x01; + // Set this flag in order to receive unsupported ZDO request messages via + // the incomingMessageHandler callback. An unsupported ZDO request is one + // that is not handled by the EmberZNet stack, other than to send a 'not + // supported' ZDO response. If this configuration option is enabled, the + // stack will no longer send any ZDO response, and it is the application's + // responsibility to do so. + static APP_HANDLES_UNSUPPORTED_ZDO_REQUESTS = 0x02; + // Set this flag in order to receive the following ZDO request messages via + // the incomingMessageHandler callback: SIMPLE_DESCRIPTOR_REQUEST, + // MATCH_DESCRIPTORS_REQUEST, and ACTIVE_ENDPOINTS_REQUEST. If this + // configuration option is enabled, the stack will no longer send any ZDO + // response for these requests, and it is the application's responsibility + // to do so. + static APP_HANDLES_ZDO_ENDPOINT_REQUESTS = 0x04; + // Set this flag in order to receive the following ZDO request messages via + // the incomingMessageHandler callback: BINDING_TABLE_REQUEST, BIND_REQUEST, + // and UNBIND_REQUEST. If this configuration option is enabled, the stack + // will no longer send any ZDO response for these requests, and it is the + // application's responsibility to do so. + static APP_HANDLES_ZDO_BINDING_REQUESTS = 0x08; +} + + +export class EmberConcentratorType extends basic.uint16_t { + // Type of concentrator. + + // A concentrator with insufficient memory to store source routes for the + // entire network. Route records are sent to the concentrator prior to every + // inbound APS unicast. + static LOW_RAM_CONCENTRATOR = 0xFFF8; + // A concentrator with sufficient memory to store source routes for the + // entire network. Remote nodes stop sending route records once the + // concentrator has successfully received one. + static HIGH_RAM_CONCENTRATOR = 0xFFF9; +} + + +export class EmberZllState extends basic.uint16_t { + // ZLL device state identifier. + + // No state. + static ZLL_STATE_NONE = 0x0000; + // The device is factory new. + static ZLL_STATE_FACTORY_NEW = 0x0001; + // The device is capable of assigning addresses to other devices. + static ZLL_STATE_ADDRESS_ASSIGNMENT_CAPABLE = 0x0002; + // The device is initiating a link operation. + static ZLL_STATE_LINK_INITIATOR = 0x0010; + // The device is requesting link priority. + static ZLL_STATE_LINK_PRIORITY_REQUEST = 0x0020; + // The device is on a non-ZLL network. + static ZLL_STATE_NON_ZLL_NETWORK = 0x0100; +} + + +export class EmberZllKeyIndex extends basic.uint8_t { + // ZLL key encryption algorithm enumeration. + + // Key encryption algorithm for use during development. + static ZLL_KEY_INDEX_DEVELOPMENT = 0x00; + // Key encryption algorithm shared by all certified devices. + static ZLL_KEY_INDEX_MASTER = 0x04; + // Key encryption algorithm for use during development and certification. + static ZLL_KEY_INDEX_CERTIFICATION = 0x0F; +} + +export class EzspZllNetworkOperation extends basic.uint8_t { + // Differentiates among ZLL network operations. + + static ZLL_FORM_NETWORK = 0x00; // ZLL form network command. + static ZLL_JOIN_TARGET = 0x01; // ZLL join target command. +} + + +export class EzspSourceRouteOverheadInformation extends basic.uint8_t { + // Validates Source Route Overhead Information cached. + + // Ezsp source route overhead unknown + static SOURCE_ROUTE_OVERHEAD_UNKNOWN = 0xFF; +} + +export class EmberNetworkInitBitmask extends basic.uint16_t { + // Bitmask options for emberNetworkInit(). + + // No options for Network Init + static NETWORK_INIT_NO_OPTIONS = 0x0000; + // Save parent info (node ID and EUI64) in a token during joining/rejoin, + // and restore on reboot. + static NETWORK_INIT_PARENT_INFO_IN_TOKEN = 0x0001; +} + +export class EmberZDOCmd extends basic.uint16_t { + // Device and Service Discovery Server Requests + static NWK_addr_req = 0x0000; + static IEEE_addr_req = 0x0001; + static Node_Desc_req = 0x0002; + static Power_Desc_req = 0x0003; + static Simple_Desc_req = 0x0004; + static Active_EP_req = 0x0005; + static Match_Desc_req = 0x0006; + static Complex_Desc_req = 0x0010; + static User_Desc_req = 0x0011; + static Discovery_Cache_req = 0x0012; + static Device_annce = 0x0013; + static User_Desc_set = 0x0014; + static System_Server_Discovery_req = 0x0015; + static Discovery_store_req = 0x0016; + static Node_Desc_store_req = 0x0017; + static Active_EP_store_req = 0x0019; + static Simple_Desc_store_req = 0x001A; + static Remove_node_cache_req = 0x001B; + static Find_node_cache_req = 0x001C; + static Extended_Simple_Desc_req = 0x001D; + static Extended_Active_EP_req = 0x001E; + static Parent_annce = 0x001F; + // Bind Management Server Services Responses + static End_Device_Bind_req = 0x0020; + static Bind_req = 0x0021; + static Unbind_req = 0x0022; + // Network Management Server Services Requests + // ... TODO optional stuff ... + static Mgmt_Lqi_req = 0x0031; + static Mgmt_Rtg_req = 0x0032; + // ... TODO optional stuff ... + static Mgmt_Leave_req = 0x0034; + static Mgmt_Permit_Joining_req = 0x0036; + static Mgmt_NWK_Update_req = 0x0038; + // ... TODO optional stuff ... + + // Responses + // Device and Service Discovery Server Responses + static NWK_addr_rsp = 0x8000; + static IEEE_addr_rsp = 0x8001; + static Node_Desc_rsp = 0x8002; + static Power_Desc_rsp = 0x8003; + static Simple_Desc_rsp = 0x8004; + static Active_EP_rsp = 0x8005; + static Match_Desc_rsp = 0x8006; + static Complex_Desc_rsp = 0x8010; + static User_Desc_rsp = 0x8011; + static Discovery_Cache_rsp = 0x8012; + static User_Desc_conf = 0x8014; + static System_Server_Discovery_rsp = 0x8015; + static Discovery_Store_rsp = 0x8016; + static Node_Desc_store_rsp = 0x8017; + static Power_Desc_store_rsp = 0x8018; + static Active_EP_store_rsp = 0x8019; + static Simple_Desc_store_rsp = 0x801A; + static Remove_node_cache_rsp = 0x801B; + static Find_node_cache_rsp = 0x801C; + static Extended_Simple_Desc_rsp = 0x801D; + static Extended_Active_EP_rsp = 0x801E; + static Parent_annce_rsp = 0x801F; + // Bind Management Server Services Responses + static End_Device_Bind_rsp = 0x8020; + static Bind_rsp = 0x8021; + static Unbind_rsp = 0x8022; + // ... TODO optional stuff ... + // Network Management Server Services Responses + static Mgmt_Lqi_rsp = 0x8031; + static Mgmt_Rtg_rsp = 0x8032; + // ... TODO optional stuff ... + static Mgmt_Leave_rsp = 0x8034; + static Mgmt_Permit_Joining_rsp = 0x8036; + // ... TODO optional stuff ... + static Mgmt_NWK_Update_rsp = 0x8038; +} + +export class EzspDecisionBitmask extends basic.uint16_t { + // EZSP Decision bitmask. + + // Disallow joins and rejoins. + static DEFAULT_CONFIGURATION = 0x0000; + // Send the network key to all joining devices. + static ALLOW_JOINS = 0x0001; + // Send the network key to all rejoining devices. + static ALLOW_UNSECURED_REJOINS = 0x0002; + // Send the network key in the clear. + static SEND_KEY_IN_CLEAR = 0x0004; + // Do nothing for unsecured rejoins. + static IGNORE_UNSECURED_REJOINS = 0x0008; + // Allow joins if there is an entry in the transient key table. + static JOINS_USE_INSTALL_CODE_KEY = 0x0010; + // Delay sending the network key to a new joining device. + static DEFER_JOINS = 0x0020; +} diff --git a/src/adapter/ezsp/driver/types/struct.ts b/src/adapter/ezsp/driver/types/struct.ts new file mode 100644 index 0000000000..55a488701f --- /dev/null +++ b/src/adapter/ezsp/driver/types/struct.ts @@ -0,0 +1,654 @@ +/* istanbul ignore file */ +import * as basic from './basic'; +import * as named from './named'; + +export class EzspStruct { + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + static serialize(cls: any, obj: any): Buffer { + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + return Buffer.concat(cls._fields.map((field: any[]) => { + const value = obj[field[0]]; + console.assert(field[1]); + return field[1].serialize(field[1], value); + })); + } + + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + static deserialize(cls: any, data: Buffer): any[] { + const r = new cls(); + for (const [field_name, field_type] of cls._fields) { + let v; + [v, data] = field_type.deserialize(field_type, data); + r[field_name] = v; + } + return [r, data]; + } + + public toString(): string { + return `${this.constructor.name}: ${JSON.stringify(this)}`; + } +} + +export class EmberNetworkParameters extends EzspStruct { + public extendedPanId: number[]; + public panId: number; + public radioTxPower: number; + public radioChannel: number; + public joinMethod: named.EmberJoinMethod; + public nwkManagerId: named.EmberNodeId; + public nwkUpdateId: number; + public channels: number; + + static _fields = [ + // The network's extended PAN identifier. + ['extendedPanId', basic.fixed_list(8, basic.uint8_t)], + // The network's PAN identifier. + ['panId', basic.uint16_t], + // A power setting, in dBm. + ['radioTxPower', basic.uint8_t], + // A radio channel. + ['radioChannel', basic.uint8_t], + // The method used to initially join the network. + ['joinMethod', named.EmberJoinMethod], + // NWK Manager ID. The ID of the network manager in the current network. + // This may only be set at joining when using USE_NWK_COMMISSIONING as + // the join method. + ['nwkManagerId', named.EmberNodeId], + // NWK Update ID. The value of the ZigBee nwkUpdateId known by the + // stack. This is used to determine the newest instance of the network + // after a PAN ID or channel change. This may only be set at joining + // when using USE_NWK_COMMISSIONING as the join method. + ['nwkUpdateId', basic.uint8_t], + // NWK channel mask. The list of preferred channels that the NWK manager + // has told this device to use when searching for the network. This may + // only be set at joining when using USE_NWK_COMMISSIONING as the join + // method. + ['channels', basic.uint32_t], + ]; +} + +export class EmberZigbeeNetwork extends EzspStruct { + // The parameters of a ZigBee network. + static _fields = [ + // The 802.15.4 channel associated with the network. + ['channel', basic.uint8_t], + // The network's PAN identifier. + ['panId', basic.uint16_t], + // The network's extended PAN identifier. + ['extendedPanId', basic.fixed_list(8, basic.uint8_t)], + // Whether the network is allowing MAC associations. + ['allowingJoin', named.Bool], + // The Stack Profile associated with the network. + ['stackProfile', basic.uint8_t], + // The instance of the Network. + ['nwkUpdateId', basic.uint8_t], + ]; +} + +export class EmberApsFrame extends EzspStruct { + public profileId: number; + public sequence: number; + public clusterId: number; + public sourceEndpoint: number; + public destinationEndpoint: number; + public groupId?: number; + public options?: named.EmberApsOption; + + + // ZigBee APS frame parameters. + static _fields = [ + // The application profile ID that describes the format of the message. + ['profileId', basic.uint16_t], + // The cluster ID for this message. + ['clusterId', basic.uint16_t], + // The source endpoint. + ['sourceEndpoint', basic.uint8_t], + // The destination endpoint. + ['destinationEndpoint', basic.uint8_t], + // A bitmask of options. + ['options', named.EmberApsOption], + // The group ID for this message, if it is multicast mode. + ['groupId', basic.uint16_t], + // The sequence number. + ['sequence', basic.uint8_t], + ]; +} + +export class EmberBindingTableEntry extends EzspStruct { + // An entry in the binding table. + static _fields = [ + // The type of binding. + ['type', named.EmberBindingType], + // The endpoint on the local node. + ['local', basic.uint8_t], + // A cluster ID that matches one from the local endpoint's simple + // descriptor.This cluster ID is set by the provisioning application to + // indicate which part an endpoint's functionality is bound to this + // particular remote node and is used to distinguish between unicast and + // multicast bindings.Note that a binding can be used to send messages + // with any cluster ID, not just that listed in the binding. + ['clusterId', basic.uint16_t], + // The endpoint on the remote node [specified by identifier]. + ['remote', basic.uint8_t], + // A 64- bit identifier.This is either the destination EUI64 [for + // unicasts] or the 64- bit group address [for multicasts]. + ['identifier', named.EmberEUI64], + // The index of the network the binding belongs to. + ['networkIndex', basic.uint8_t], + ]; +} + +export class EmberMulticastTableEntry extends EzspStruct { + public multicastId: number; + public endpoint: number; + public networkIndex: number; + // A multicast table entry indicates that a particular endpoint is a member + // of a particular multicast group.Only devices with an endpoint in a + // multicast group will receive messages sent to that multicast group. + static _fields = [ + // The multicast group ID. + ['multicastId', named.EmberMulticastId], + // The endpoint that is a member, or 0 if this entry is not in use[the + // ZDO is not a member of any multicast groups.] + ['endpoint', basic.uint8_t], + // The network index of the network the entry is related to. + ['networkIndex', basic.uint8_t], + ]; +} + +export class EmberKeyData extends EzspStruct { + public contents: Buffer; + // A 128- bit key. + static _fields = [ + // The key data. + ['contents', basic.fixed_list(16, basic.uint8_t)], + ]; +} + +export class EmberCertificateData extends EzspStruct { + public contents: Buffer; + // The implicit certificate used in CBKE. + static _fields = [ + // The certificate data. + ['contents', basic.fixed_list(48, basic.uint8_t)], + ]; + +} + +export class EmberPublicKeyData extends EzspStruct { + public contents: Buffer; + // The public key data used in CBKE. + static _fields = [ + // The public key data. + ['contents', basic.fixed_list(22, basic.uint8_t)], + ]; +} + +export class EmberPrivateKeyData extends EzspStruct { + public contents: Buffer; + // The private key data used in CBKE. + static _fields = [ + // The private key data. + ['contents', basic.fixed_list(21, basic.uint8_t)], + ]; +} + +export class EmberSmacData extends EzspStruct { + // The Shared Message Authentication Code data used in CBKE. + static _fields = [ + // The Shared Message Authentication Code data. + ['contents', basic.fixed_list(16, basic.uint8_t)], + ]; +} + +export class EmberSignatureData extends EzspStruct { + // An ECDSA signature + static _fields = [ + // The signature data. + ['contents', basic.fixed_list(42, basic.uint8_t)], + ]; +} + +export class EmberCertificate283k1Data extends EzspStruct { + // The implicit certificate used in CBKE. + static _fields = [ + // The 283k1 certificate data. + ['contents', basic.fixed_list(74, basic.uint8_t)], + ]; +} + +export class EmberPublicKey283k1Data extends EzspStruct { + // The public key data used in CBKE. + static _fields = [ + // The 283k1 public key data. + ['contents', basic.fixed_list(37, basic.uint8_t)], + ]; +} + +export class EmberPrivateKey283k1Data extends EzspStruct { + // The private key data used in CBKE. + static _fields = [ + // The 283k1 private key data. + ['contents', basic.fixed_list(36, basic.uint8_t)], + ]; +} + +export class EmberSignature283k1Data extends EzspStruct { + // An ECDSA signature + static _fields = [ + // The 283k1 signature data. + ['contents', basic.fixed_list(72, basic.uint8_t)], + ]; +} + +export class EmberMessageDigest extends EzspStruct { + // The calculated digest of a message + static _fields = [ + // The calculated digest of a message. + ['contents', basic.fixed_list(16, basic.uint8_t)], + ]; +} + +export class EmberAesMmoHashContext extends EzspStruct { + // The hash context for an ongoing hash operation. + static _fields = [ + // The result of ongoing the hash operation. + ['result', basic.fixed_list(16, basic.uint8_t)], + // The total length of the data that has been hashed so far. + ['length', basic.uint32_t], + ]; +} + +export class EmberNeighborTableEntry extends EzspStruct { + // A neighbor table entry stores information about the reliability of RF + // links to and from neighboring nodes. + static _fields = [ + // The neighbor's two byte network id + ['shortId', basic.uint16_t], + // An exponentially weighted moving average of the link quality values + // of incoming packets from this neighbor as reported by the PHY. + ['averageLqi', basic.uint8_t], + // The incoming cost for this neighbor, computed from the average LQI. + // Values range from 1 for a good link to 7 for a bad link. + ['inCost', basic.uint8_t], + // The outgoing cost for this neighbor, obtained from the most recently + // received neighbor exchange message from the neighbor. A value of zero + // means that a neighbor exchange message from the neighbor has not been + // received recently enough, or that our id was not present in the most + // recently received one. + ['outCost', basic.uint8_t], + // The number of aging periods elapsed since a link status message was + // last received from this neighbor. The aging period is 16 seconds. + ['age', basic.uint8_t], + // The 8 byte EUI64 of the neighbor. + ['longId', named.EmberEUI64], + ]; +} + +export class EmberRouteTableEntry extends EzspStruct { + // A route table entry stores information about the next hop along the route + // to the destination. + static _fields = [ + // The short id of the destination. A value of 0xFFFF indicates the + // entry is unused. + ['destination', basic.uint16_t], + // The short id of the next hop to this destination. + ['nextHop', basic.uint16_t], + // Indicates whether this entry is active [0], being discovered [1]], + // unused [3], or validating [4]. + ['status', basic.uint8_t], + // The number of seconds since this route entry was last used to send a + // packet. + ['age', basic.uint8_t], + // Indicates whether this destination is a High RAM Concentrator [2], a + // Low RAM Concentrator [1], or not a concentrator [0]. + ['concentratorType', basic.uint8_t], + // For a High RAM Concentrator, indicates whether a route record is + // needed [2], has been sent [1], or is no long needed [0] because a + // source routed message from the concentrator has been received. + ['routeRecordState', basic.uint8_t], + ]; +} + +export class EmberInitialSecurityState extends EzspStruct { + public bitmask: number; + public preconfiguredKey: EmberKeyData; + public networkKey: EmberKeyData; + public networkKeySequenceNumber: number; + public preconfiguredTrustCenterEui64: named.EmberEUI64; + + // The security data used to set the configuration for the stack, or the + // retrieved configuration currently in use. + static _fields = [ + // A bitmask indicating the security state used to indicate what the + // security configuration will be when the device forms or joins the + // network. + ['bitmask', named.EmberInitialSecurityBitmask], + // The pre-configured Key data that should be used when forming or + // joining the network. The security bitmask must be set with the + // HAVE_PRECONFIGURED_KEY bit to indicate that the key contains valid + // data. + ['preconfiguredKey', EmberKeyData], + // The Network Key that should be used by the Trust Center when it forms + // the network, or the Network Key currently in use by a joined device. + // The security bitmask must be set with HAVE_NETWORK_KEY to indicate + // that the key contains valid data. + ['networkKey', EmberKeyData], + // The sequence number associated with the network key. This is only + // valid if the HAVE_NETWORK_KEY has been set in the security bitmask. + ['networkKeySequenceNumber', basic.uint8_t], + // This is the long address of the trust center on the network that will + // be joined. It is usually NOT set prior to joining the network and + // instead it is learned during the joining message exchange. This field + // is only examined if HAVE_TRUST_CENTER_EUI64 is set in the + // EmberInitialSecurityState::bitmask. Most devices should clear that + // bit and leave this field alone. This field must be set when using + // commissioning mode. + ['preconfiguredTrustCenterEui64', named.EmberEUI64], + ]; +} + +export class EmberCurrentSecurityState extends EzspStruct { + // The security options and information currently used by the stack. + static _fields = [ + // A bitmask indicating the security options currently in use by a + // device joined in the network. + ['bitmask', named.EmberCurrentSecurityBitmask], + // The IEEE Address of the Trust Center device. + ['trustCenterLongAddress', named.EmberEUI64], + ]; +} + +export class EmberKeyStruct extends EzspStruct { + // A structure containing a key and its associated data. + static _fields = [ + // A bitmask indicating the presence of data within the various fields + // in the structure. + ['bitmask', named.EmberKeyStructBitmask], + // The type of the key. + ['type', named.EmberKeyType], + // The actual key data. + ['key', EmberKeyData], + // The outgoing frame counter associated with the key. + ['outgoingFrameCounter', basic.uint32_t], + // The frame counter of the partner device associated with the key. + ['incomingFrameCounter', basic.uint32_t], + // The sequence number associated with the key. + ['sequenceNumber', basic.uint8_t], + // The IEEE address of the partner device also in possession of the key. + ['partnerEUI64', named.EmberEUI64], + ]; +} + +export class EmberNetworkInitStruct extends EzspStruct { + // Network Initialization parameters. + static _fields = [ + // Configuration options for network init. + ['bitmask', named.EmberNetworkInitBitmask], + ]; +} + +export class EmberZllSecurityAlgorithmData extends EzspStruct { + // Data associated with the ZLL security algorithm. + static _fields = [ + // Transaction identifier. + ['transactionId', basic.uint32_t], + // Response identifier. + ['responseId', basic.uint32_t], + // Bitmask. + ['bitmask', basic.uint16_t], + ]; +} + +export class EmberZllNetwork extends EzspStruct { + // The parameters of a ZLL network. + static _fields = [ + // The parameters of a ZigBee network. + ['zigbeeNetwork', EmberZigbeeNetwork], + // Data associated with the ZLL security algorithm. + ['securityAlgorithm', EmberZllSecurityAlgorithmData], + // Associated EUI64. + ['eui64', named.EmberEUI64], + // The node id. + ['nodeId', named.EmberNodeId], + // The ZLL state. + ['state', named.EmberZllState], + // The node type. + ['nodeType', named.EmberNodeType], + // The number of sub devices. + ['numberSubDevices', basic.uint8_t], + // The total number of group identifiers. + ['totalGroupIdentifiers', basic.uint8_t], + // RSSI correction value. + ['rssiCorrection', basic.uint8_t], + ]; +} + +export class EmberZllInitialSecurityState extends EzspStruct { + // Describes the initial security features and requirements that will be + // used when forming or joining ZLL networks. + static _fields = [ + // Unused bitmask; reserved for future use. + ['bitmask', basic.uint32_t], + // The key encryption algorithm advertised by the application. + ['keyIndex', named.EmberZllKeyIndex], + // The encryption key for use by algorithms that require it. + ['encryptionKey', EmberKeyData], + // The pre-configured link key used during classical ZigBee + // commissioning. + ['preconfiguredKey', EmberKeyData], + ]; +} + +export class EmberZllDeviceInfoRecord extends EzspStruct { + // Information about a specific ZLL Device. + static _fields = [ + // EUI64 associated with the device. + ['ieeeAddress', named.EmberEUI64], + // Endpoint id. + ['endpointId', basic.uint8_t], + // Profile id. + ['profileId', basic.uint16_t], + // Device id. + ['deviceId', basic.uint16_t], + // Associated version. + ['version', basic.uint8_t], + // Number of relevant group ids. + ['groupIdCount', basic.uint8_t], + ]; +} + +export class EmberZllAddressAssignment extends EzspStruct { + // ZLL address assignment data. + static _fields = [ + // Relevant node id. + ['nodeId', named.EmberNodeId], + // Minimum free node id. + ['freeNodeIdMin', named.EmberNodeId], + // Maximum free node id. + ['freeNodeIdMax', named.EmberNodeId], + // Minimum group id. + ['groupIdMin', named.EmberMulticastId], + // Maximum group id. + ['groupIdMax', named.EmberMulticastId], + // Minimum free group id. + ['freeGroupIdMin', named.EmberMulticastId], + // Maximum free group id. + ['freeGroupIdMax', named.EmberMulticastId], + ]; +} + +export class EmberTokTypeStackZllData extends EzspStruct { + // Public API for ZLL stack data token. + static _fields = [ + // Token bitmask. + ['bitmask', basic.uint32_t], + // Minimum free node id. + ['freeNodeIdMin', basic.uint16_t], + // Maximum free node id. + ['freeNodeIdMax', basic.uint16_t], + // Local minimum group id. + ['myGroupIdMin', basic.uint16_t], + // Minimum free group id. + ['freeGroupIdMin', basic.uint16_t], + // Maximum free group id. + ['freeGroupIdMax', basic.uint16_t], + // RSSI correction value. + ['rssiCorrection', basic.uint8_t], + ]; +} + +export class EmberTokTypeStackZllSecurity extends EzspStruct { + // Public API for ZLL stack security token. + static _fields = [ + // Token bitmask. + ['bitmask', basic.uint32_t], + // Key index. + ['keyIndex', basic.uint8_t], + // Encryption key. + ['encryptionKey', basic.fixed_list(16, basic.uint8_t)], + // Preconfigured key. + ['preconfiguredKey', basic.fixed_list(16, basic.uint8_t)], + ]; +} + +export class EmberRf4ceVendorInfo extends EzspStruct { + // The RF4CE vendor information block. + static _fields = [ + // The vendor identifier field shall contain the vendor identifier of + // the node. + ['vendorId', basic.uint16_t], + // The vendor string field shall contain the vendor string of the node. + ['vendorString', basic.fixed_list(7, basic.uint8_t)], + ]; +} + +export class EmberRf4ceApplicationInfo extends EzspStruct { + // The RF4CE application information block. + static _fields = [ + // The application capabilities field shall contain information relating + // to the capabilities of the application of the node. + ['capabilities', named.EmberRf4ceApplicationCapabilities], + // The user string field shall contain the user specified identification + // string. + ['userString', basic.fixed_list(15, basic.uint8_t)], + // The device type list field shall contain the list of device types + // supported by the node. + ['deviceTypeList', basic.fixed_list(3, basic.uint8_t)], + // The profile ID list field shall contain the list of profile + // identifiers disclosed as supported by the node. + ['profileIdList', basic.fixed_list(7, basic.uint8_t)], + ]; +} + +export class EmberRf4cePairingTableEntry extends EzspStruct { + // The internal representation of an RF4CE pairing table entry. + static _fields = [ + // The link key to be used to secure this pairing link. + ['securityLinkKey', EmberKeyData], + // The IEEE address of the destination device. + ['destLongId', named.EmberEUI64], + // The frame counter last received from the recipient node. + ['frameCounter', basic.uint32_t], + // The network address to be assumed by the source device. + ['sourceNodeId', named.EmberNodeId], + // The PAN identifier of the destination device. + ['destPanId', named.EmberPanId], + // The network address of the destination device. + ['destNodeId', named.EmberNodeId], + // The vendor ID of the destination device. + ['destVendorId', basic.uint16_t], + // The list of profiles supported by the destination device. + ['destProfileIdList', basic.fixed_list(7, basic.uint8_t)], + // The length of the list of supported profiles. + ['destProfileIdListLength', basic.uint8_t], + // Info byte. + ['info', basic.uint8_t], + // The expected channel of the destination device. + ['channel', basic.uint8_t], + // The node capabilities of the recipient node. + ['capabilities', basic.uint8_t], + // Last MAC sequence number seen on this pairing link. + ['lastSeqn', basic.uint8_t], + ]; +} + +export class EmberGpAddress extends EzspStruct { + // A GP address structure. + static _fields = [ + // The GPD's EUI64. + ['gpdIeeeAddress', named.EmberEUI64], + // The GPD's source ID. + ['sourceId', basic.uint32_t], + // The GPD Application ID. + ['applicationId', basic.uint8_t], + // The GPD endpoint. + ['endpoint', basic.uint8_t], + ]; +} + +export class EmberGpSinkListEntry extends EzspStruct { + // A sink list entry + static _fields = [ + // The sink list type. + ['type', basic.uint8_t], + // The EUI64 of the target sink. + ['sinkEUI', named.EmberEUI64], + // The short address of the target sink. + ['sinkNodeId', named.EmberNodeId], + ]; +} + +export class EmberNodeDescriptor extends EzspStruct { + static _fields = [ + ['byte1', basic.uint8_t], + ['byte2', basic.uint8_t], + ['mac_capability_flags', basic.uint8_t], + ['manufacturer_code', basic.uint16_t], + ['maximum_buffer_size', basic.uint8_t], + ['maximum_incoming_transfer_size', basic.uint16_t], + ['server_mask', basic.uint16_t], + ['maximum_outgoing_transfer_size', basic.uint16_t], + ['descriptor_capability_field', basic.uint8_t], + ]; +} + + +export class EmberSimpleDescriptor extends EzspStruct { + static _fields = [ + ['endpoint', basic.uint8_t], + ['profileid', basic.uint16_t], + ['deviceid', basic.uint16_t], + ['deviceversion', basic.uint8_t], + ['inclusterlist', basic.LVList(basic.uint16_t)], + ['outclusterlist', basic.LVList(basic.uint16_t)], + ]; +} + +export class EmberMultiAddress extends EzspStruct { + static _fields = [ + ['addrmode', basic.uint8_t], // 0x3 + ['ieee', named.EmberEUI64], + ['endpoint', basic.uint8_t], + ]; +} + +export class EmberNeighbor extends EzspStruct { + static _fields = [ + ['extendedpanid', basic.fixed_list(8, basic.uint8_t)], + ['ieee', named.EmberEUI64], + ['nodeid', named.EmberNodeId], + ['packed', basic.uint8_t], + ['permitjoining', basic.uint8_t], + ['depth', basic.uint8_t], + ['lqi', basic.uint8_t], + ]; +} + +export class EmberNeighbors extends EzspStruct { + static _fields = [ + ['entries', basic.uint8_t], + ['startindex', basic.uint8_t], + ['neighbors', basic.LVList(EmberNeighbor)], + ]; +} diff --git a/src/adapter/ezsp/driver/uart.ts b/src/adapter/ezsp/driver/uart.ts new file mode 100644 index 0000000000..3d79470739 --- /dev/null +++ b/src/adapter/ezsp/driver/uart.ts @@ -0,0 +1,517 @@ +/* istanbul ignore file */ +import {EventEmitter} from 'events'; +import SerialPort from 'serialport'; +import net from 'net'; +import SocketPortUtils from '../../socketPortUtils'; +import {Deferred, crc16ccitt} from './utils'; +import * as stream from 'stream'; +import {Queue, Waitress} from '../../../utils'; +import Debug from "debug"; + +const debug = Debug('zigbee-herdsman:adapter:ezsp:uart'); + + +const FLAG = 0x7E; // Marks end of frame +const ESCAPE = 0x7D; // Indicates that the following byte is escaped +const CANCEL = 0x1A; // Terminates a frame in progress +const XON = 0x11; // Resume transmission +const XOFF = 0x13; // Stop transmission +const SUBSTITUTE = 0x18; // Replaces a byte received with a low-level communication error +const STUFF = 0x20; +const RESERVED = [FLAG, ESCAPE, XON, XOFF, SUBSTITUTE, CANCEL]; +const RANDOMIZE_START = 0x42; +const RANDOMIZE_SEQ = 0xB8; + + +enum NcpResetCode { + RESET_UNKNOWN_REASON = 0x00, + RESET_EXTERNAL = 0x01, + RESET_POWER_ON = 0x02, + RESET_WATCHDOG = 0x03, + RESET_ASSERT = 0x06, + RESET_BOOTLOADER = 0x09, + RESET_SOFTWARE = 0x0B, + ERROR_EXCEEDED_MAXIMUM_ACK_TIMEOUT_COUNT = 0x51, + ERROR_UNKNOWN_EM3XX_ERROR = 0x80, +} + + +class Parser extends stream.Transform { + private buffer: Buffer; + + public constructor() { + super(); + this.buffer = Buffer.from([]); + } + + public _transform(chunk: Buffer, _: string, cb: () => void): void { + //debug(`<-- [${[...chunk]}]`); + if (chunk.indexOf(CANCEL) >= 0) { + this.buffer = Buffer.from([]); + chunk = chunk.slice((chunk.lastIndexOf(CANCEL) + 1)); + } + if (chunk.indexOf(SUBSTITUTE) >= 0) { + this.buffer = Buffer.from([]); + chunk = chunk.slice((chunk.indexOf(FLAG) + 1)); + } + this.buffer = Buffer.concat([this.buffer, chunk]); + this.parseNext(); + cb(); + } + + private parseNext(): void { + //debug(`--- parseNext [${[...this.buffer]}]`); + if (this.buffer.length && this.buffer.indexOf(FLAG) >= 0) { + debug(`<-- [${this.buffer.toString('hex')}] [${[...this.buffer]}]`); + try { + const frame = this.extract_frame(); + if (frame) { + this.emit('parsed', frame); + } + } catch (error) { + debug(`<-- error ${error.stack}`); + } + this.parseNext(); + } + } + + private extract_frame(): Buffer { + /* Extract a frame from the data buffer */ + const place = this.buffer.indexOf(FLAG); + if (place >= 0) { + // todo: check crc data + const result = this.unstuff(this.buffer.slice(0, (place + 1))); + this.buffer = this.buffer.slice((place + 1)); + return result; + } else { + return null; + } + } + + private unstuff(s: Buffer): Buffer { + /* Unstuff (unescape) a string after receipt */ + let escaped = false; + const out = Buffer.alloc(s.length); + let outIdx = 0; + for (let idx = 0; idx < s.length; idx += 1) { + const c = s[idx]; + if (escaped) { + out.writeUInt8(c ^ STUFF, outIdx++); + escaped = false; + } else { + if ((c === ESCAPE)) { + escaped = true; + } else { + out.writeUInt8(c, outIdx++); + } + } + } + return out; + } +} + +class Writer extends stream.Readable { + public writeBuffer(buffer: Buffer): void { + debug(`--> [${buffer.toString('hex')}] [${[...buffer]}]`); + this.push(buffer); + } + + public _read(): void { + } + + public stuff(s: Iterable): Buffer { + /* Byte stuff (escape) a string for transmission */ + const out = Buffer.alloc(256); + let outIdx = 0; + for (const c of s) { + if (RESERVED.includes(c)) { + out.writeUInt8(ESCAPE, outIdx++); + out.writeUInt8(c ^ STUFF, outIdx++); + } else { + out.writeUInt8(c, outIdx++); + } + } + return out.slice(0, outIdx); + } +} + +type EZSPPacket = { + sequence: number +}; + +type EZSPPacketMatcher = { + sequence: number +}; + + +export class SerialDriver extends EventEmitter { + private serialPort: SerialPort; + private socketPort: net.Socket; + private writer: Writer; + private parser: Parser; + private initialized: boolean; + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + private resetDeferred: Deferred; + private portType: 'serial' | 'socket'; + private sendSeq = 0; // next frame number to send + private recvSeq = 0; // next frame number to receive + private ackSeq = 0; // next number after the last accepted frame + private waitress: Waitress; + private queue: Queue; + + constructor() { + super(); + this.initialized = false; + this.queue = new Queue(); + this.waitress = new Waitress( + this.waitressValidator, this.waitressTimeoutFormatter); + } + + async connect(path: string, options: Record): Promise { + this.portType = SocketPortUtils.isTcpPath(path) ? 'socket' : 'serial'; + if (this.portType === 'serial') { + await this.openSerialPort(path, options); + } else { + await this.openSocketPort(path); + } + } + + private async openSerialPort(path: string, opt: Record): Promise { + const options = {baudRate: opt.baudRate, rtscts: false, autoOpen: false}; + + debug(`Opening SerialPort with ${path} and ${JSON.stringify(options)}`); + this.serialPort = new SerialPort(path, options); + + this.writer = new Writer(); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + this.writer.pipe(this.serialPort); + + this.parser = new Parser(); + this.serialPort.pipe(this.parser); + this.parser.on('parsed', this.onParsed.bind(this)); + + return new Promise((resolve, reject): void => { + this.serialPort.open(async (error): Promise => { + if (error) { + reject(new Error(`Error while opening serialport '${error}'`)); + this.initialized = false; + if (this.serialPort.isOpen) { + this.serialPort.close(); + } + } else { + debug('Serialport opened'); + this.serialPort.once('close', this.onPortClose.bind(this)); + this.serialPort.once('error', (error) => { + debug(`Serialport error: ${error}`); + }); + // reset + await this.reset(); + this.initialized = true; + resolve(); + } + }); + }); + } + + private async openSocketPort(path: string): Promise { + const info = SocketPortUtils.parseTcpPath(path); + debug(`Opening TCP socket with ${info.host}:${info.port}`); + + this.socketPort = new net.Socket(); + this.socketPort.setNoDelay(true); + this.socketPort.setKeepAlive(true, 15000); + + this.writer = new Writer(); + this.writer.pipe(this.socketPort); + + this.parser = new Parser(); + this.socketPort.pipe(this.parser); + this.parser.on('parsed', this.onParsed.bind(this)); + + return new Promise((resolve, reject): void => { + this.socketPort.on('connect', function () { + debug('Socket connected'); + }); + + // eslint-disable-next-line + const self = this; + this.socketPort.on('ready', async (): Promise => { + debug('Socket ready'); + // reset + await this.reset(); + self.initialized = true; + resolve(); + }); + + this.socketPort.once('close', this.onPortClose.bind(this)); + + this.socketPort.on('error', function () { + debug('Socket error'); + reject(new Error(`Error while opening socket`)); + self.initialized = false; + }); + + this.socketPort.connect(info.port, info.host); + }); + } + + private onParsed(data: Buffer): void { + try { + /* Frame receive handler */ + switch (true) { + case ((data[0] & 0x80) === 0): + debug(`Recv DATA frame (${(data[0] & 0x70) >> 4}, + ${data[0] & 0x07},${(data[0] & 0x08) >> 3}): ${data.toString('hex')}`); + this.handleDATA(data); + break; + + case ((data[0] & 0xE0) === 0x80): + debug(`Recv ACK frame (${data[0] & 0x07}): ${data.toString('hex')}`); + this.handleACK(data[0]); + break; + + case ((data[0] & 0xE0) === 0xA0): + debug(`Recv NAK frame (${data[0] & 0x07}): ${data.toString('hex')}`); + this.handleNAK(data[0]); + break; + + case (data[0] === 0xC0): + debug(`Recv RST frame: ${data.toString('hex')}`); + break; + + case (data[0] === 0xC1): + debug(`RSTACK frame: ${data.toString('hex')}`); + this.rstack_frame_received(data); + break; + + case (data[0] === 0xC2): + debug(`Error frame : ${data.toString('hex')}`); + break; + default: + debug("UNKNOWN FRAME RECEIVED: %r", data); + } + + } catch (error) { + debug(`Error while parsing to ZpiObject '${error.stack}'`); + } + } + + private handleDATA(data: Buffer): void { + /* Data frame receive handler */ + const frmNum = (data[0] & 0x70) >> 4; + //const ackNum = data[0] & 0x07; + //const reTx = (data[0] & 0x08) >> 3; + // if (seq !== this.recvSeq) { + // debug('NAK-NAK'); + // } + this.recvSeq = (frmNum + 1) & 7; // next + this.sendACK(this.recvSeq); + this.handleACK(data[0]); + data = data.slice(1, (-3)); + const frame = this.randomize(data); + this.emit('received', frame); + } + + private handleACK(control: number): void { + /* Handle an acknowledgement frame */ + // next number after the last accepted frame + this.ackSeq = control & 0x07; + const handled = this.waitress.resolve({sequence: this.ackSeq}); + if (!handled) { + debug(`Unexpected packet sequence ${this.ackSeq}`); + } else { + debug(`Expected packet sequence ${this.ackSeq}`); + } + // var ack, pending; + // ack = (((control & 7) - 1) % 8); + // if ((ack === this._pending[0])) { + // [pending, this._pending] = [this._pending, [(- 1), null]]; + // pending[1].set_result(true); + // } + } + + private handleNAK(control: number): void { + /* Handle negative acknowledgment frame */ + const nakNum = control & 0x07; + const handled = this.waitress.reject({sequence: nakNum}, 'Recv NAK frame'); + if (!handled) { + // send NAK + debug(`NAK Unexpected packet sequence ${nakNum}`); + } else { + debug(`NAK Expected packet sequence ${nakNum}`); + } + // if ((nak === this._pending[0])) { + // this._pending[1].set_result(false); + // } + } + + private rstack_frame_received(data: Buffer): void { + /* Reset acknowledgement frame receive handler */ + let code; + this.sendSeq = 0; + this.recvSeq = 0; + try { + code = NcpResetCode[data[2]]; + } catch (e) { + code = NcpResetCode.ERROR_UNKNOWN_EM3XX_ERROR; + } + debug("RSTACK Version: %d Reason: %s frame: %s", data[1], code.toString(), data.toString('hex')); + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + if (NcpResetCode[code].toString() !== NcpResetCode.RESET_SOFTWARE.toString()) { + return; + } + if ((!this.resetDeferred)) { + debug("Reset future is None"); + return; + } + this.resetDeferred.resolve(true); + } + + private make_frame(control: ArrayLike, data?: ArrayLike): Buffer { + /* Construct a frame */ + const ctrlArr: Array = Array.from(control); + const dataArr: Array = (data && Array.from(data)) || []; + + const sum = ctrlArr.concat(dataArr); + + const crc = crc16ccitt(Buffer.from(sum), 65535); + const crcArr = [(crc >> 8), (crc % 256)]; + return Buffer.concat([this.writer.stuff(sum.concat(crcArr)), Buffer.from([FLAG])]); + } + + private randomize(s: Buffer): Buffer { + /*XOR s with a pseudo-random sequence for transmission + Used only in data frames + */ + let rand = RANDOMIZE_START; + const out = Buffer.alloc(s.length); + let outIdx = 0; + for (const c of s) { + out.writeUInt8(c ^ rand, outIdx++); + if ((rand % 2)) { + rand = ((rand >> 1) ^ RANDOMIZE_SEQ); + } else { + rand = (rand >> 1); + } + } + return out; + } + + private makeDataFrame(data: Buffer, seq: number, rxmit: number, ackSeq: number): Buffer { + /* Construct a data frame */ + const control = (((seq << 4) | (rxmit << 3)) | ackSeq); + return this.make_frame([control], this.randomize(data)); + } + + async reset(): Promise { + // return this._gw.reset(); + debug('uart reseting'); + if ((this.resetDeferred)) { + throw new TypeError("reset can only be called on a new connection"); + } + /* Construct a reset frame */ + const rst_frame = Buffer.concat([Buffer.from([CANCEL]), this.make_frame([0xC0])]); + debug(`Write reset`); + this.writer.writeBuffer(rst_frame); + this.resetDeferred = new Deferred(); + return this.resetDeferred.promise; + } + + public close(): Promise { + return new Promise((resolve, reject): void => { + if (this.initialized) { + if (this.portType === 'serial') { + this.serialPort.flush((): void => { + this.serialPort.close((error): void => { + this.initialized = false; + error == null ? + resolve() : + reject(new Error(`Error while closing serialport '${error}'`)); + this.emit('close'); + }); + }); + } else { + this.socketPort.destroy(); + resolve(); + } + } else { + resolve(); + this.emit('close'); + } + }); + } + + private onPortClose(): void { + debug('Port closed'); + this.initialized = false; + this.emit('close'); + } + + public isInitialized(): boolean { + return this.initialized; + } + + private sendACK(ackNum: number): void { + /* Construct a acknowledgement frame */ + const ackFrame = this.make_frame([(0b10000000 | ackNum)]); + debug(`Send ACK frame (${ackNum})`); + this.writer.writeBuffer(ackFrame); + } + + public async sendDATA(data: Buffer): Promise { + const seq = this.sendSeq; + this.sendSeq = ((seq + 1) % 8); // next + const nextSeq = this.sendSeq; + const ackSeq = this.recvSeq; + let pack; + + return this.queue.execute(async (): Promise => { + debug(`Send DATA frame (${seq},${ackSeq},0): ${data.toString('hex')}`); + pack = this.makeDataFrame(data, seq, 0, ackSeq); + const waiter = this.waitFor(nextSeq); + debug(`waiting (${nextSeq})`); + this.writer.writeBuffer(pack); + await waiter.start().promise.catch(async () => { + debug(`break waiting (${nextSeq})`); + debug(`Can't send DATA frame (${seq},${ackSeq},0): ${data.toString('hex')}`); + debug(`Resend DATA frame (${seq},${ackSeq},1): ${data.toString('hex')}`); + pack = this.makeDataFrame(data, seq, 1, ackSeq); + const waiter = this.waitFor(nextSeq); + debug(`rewaiting (${nextSeq})`); + this.writer.writeBuffer(pack); + await waiter.start().promise.catch((e) => { + debug(`break rewaiting (${nextSeq})`); + debug(`Can't send DATA frame (${seq},${ackSeq},0): ${data.toString('hex')}`); + throw new Error(`sendDATA error: ${e}`); + }); + debug(`rewaiting (${nextSeq}) success`); + }); + debug(`waiting (${nextSeq}) success`); + }); + + + // try { + // debug(`Send DATA frame (${seq},${this.recvSeq},0): ${data.toString('hex')}`); + // pack = this.data_frame(data, seq, 0); + // this.writer.writeBuffer(pack); + // } catch (e) { + // debug(`Send DATA frame (${seq},${this.recvSeq},1): ${data.toString('hex')}`); + // pack = this.data_frame(data, seq, 1); + // this.writer.writeBuffer(pack); + // } + } + + public waitFor(sequence: number, timeout = 10000) + : { start: () => { promise: Promise; ID: number }; ID: number } { + return this.waitress.waitFor({sequence}, timeout); + } + + private waitressTimeoutFormatter(matcher: EZSPPacketMatcher, timeout: number): string { + return `${JSON.stringify(matcher)} after ${timeout}ms`; + } + + private waitressValidator(payload: EZSPPacket, matcher: EZSPPacketMatcher): boolean { + return (payload.sequence === matcher.sequence); + } +} diff --git a/src/adapter/ezsp/driver/utils/crc16ccitt.ts b/src/adapter/ezsp/driver/utils/crc16ccitt.ts new file mode 100644 index 0000000000..bcab95adb4 --- /dev/null +++ b/src/adapter/ezsp/driver/utils/crc16ccitt.ts @@ -0,0 +1,65 @@ +/* istanbul ignore file */ +import {Buffer} from 'buffer'; +/* eslint-disable-next-line @typescript-eslint/ban-types*/ +function defineCrc(model: string, calc: Function): Function { + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + const fn = (buf: any, previous: any): number => calc(buf, previous) >>> 0; + fn.signed = calc; + fn.unsigned = fn; + fn.model = model; + + return fn; +} + +// Generated by `./pycrc.py --algorithm=table-driven --model=ccitt --generate=c` +// prettier-ignore +const TABLE: number[] = [ + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, + 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, + 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, + 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, + 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, + 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, + 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, + 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, + 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, + 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, + 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, + 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, + 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, + 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, + 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, + 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, + 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, + 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, + 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, + 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, + 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, + 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, + 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 +]; + +/* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ +const crc16ccitt = defineCrc('ccitt', function (buf: any, previous: any): number { + if (!Buffer.isBuffer(buf)) buf = Buffer.from(buf); + + let crc = typeof previous !== 'undefined' ? ~~previous : 0xffff; + + for (let index = 0; index < buf.length; index++) { + const byte = buf[index]; + crc = (TABLE[((crc >> 8) ^ byte) & 0xff] ^ (crc << 8)) & 0xffff; + } + + return crc; +}); + +export default crc16ccitt; diff --git a/src/adapter/ezsp/driver/utils/index.ts b/src/adapter/ezsp/driver/utils/index.ts new file mode 100644 index 0000000000..27276b2f69 --- /dev/null +++ b/src/adapter/ezsp/driver/utils/index.ts @@ -0,0 +1,67 @@ +/* istanbul ignore file */ +import crc16ccitt from './crc16ccitt'; +import {EmberInitialSecurityState, EmberKeyData} from '../types/struct'; +import {EmberInitialSecurityBitmask, EmberEUI64} from '../types/named'; + +if (!Symbol.asyncIterator) { + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + (Symbol).asyncIterator = Symbol.for("Symbol.asyncIterator"); +} + +export class Deferred { + + public promise: Promise; + /* eslint-disable-next-line @typescript-eslint/ban-types*/ + public _resolve: Function; + /* eslint-disable-next-line @typescript-eslint/ban-types*/ + public _reject: Function; + _isResolved = false; + _isRejected = false; + + constructor() { + this.promise = new Promise((resolve, reject) => { + this._resolve = resolve; + this._reject = reject; + }); + } + + public resolve(value: T): void { + this._isResolved = true; + this._resolve(value); + } + + public reject(value: T): void { + this._isResolved = true; + this.reject(value); + } + + get isResolved(): boolean { + return this._isResolved; + } + + get isRejected(): boolean { + return this._isRejected; + } + + get isFullfilled(): boolean { + return this._isResolved || this._isRejected; + } +} + +/* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ +function ember_security(config: Record): EmberInitialSecurityState { + const isc: EmberInitialSecurityState = new EmberInitialSecurityState(); + isc.bitmask = (EmberInitialSecurityBitmask.HAVE_PRECONFIGURED_KEY | + EmberInitialSecurityBitmask.TRUST_CENTER_GLOBAL_LINK_KEY | + EmberInitialSecurityBitmask.HAVE_NETWORK_KEY | + EmberInitialSecurityBitmask.PRECONFIGURED_NETWORK_KEY_MODE); + isc.preconfiguredKey = new EmberKeyData(); + isc.preconfiguredKey.contents = Buffer.from("ZigBeeAlliance09"); + isc.networkKey = new EmberKeyData(); + isc.networkKey.contents = config.networkKey; + isc.networkKeySequenceNumber = 0; + isc.preconfiguredTrustCenterEui64 = new EmberEUI64([0, 0, 0, 0, 0, 0, 0, 0]); + return isc; +} + +export {crc16ccitt, ember_security}; diff --git a/src/utils/waitress.ts b/src/utils/waitress.ts index ac8079aeef..c9da44d114 100644 --- a/src/utils/waitress.ts +++ b/src/utils/waitress.ts @@ -25,7 +25,8 @@ class Waitress { this.currentID = 0; } - public resolve(payload: TPayload): void { + public resolve(payload: TPayload): boolean { + let result = false; for (const entry of this.waiters.entries()) { const index = entry[0]; const waiter = entry[1]; @@ -36,8 +37,10 @@ class Waitress { waiter.resolved = true; waiter.resolve(payload); this.waiters.delete(index); + result = true; } } + return result; } public remove(ID: number): void { @@ -51,6 +54,24 @@ class Waitress { } } + public reject(payload: TPayload, message: string): boolean { + let result = false; + for (const entry of this.waiters.entries()) { + const index = entry[0]; + const waiter = entry[1]; + if (waiter.timedout) { + this.waiters.delete(index); + } else if (this.validator(payload, waiter.matcher)) { + clearTimeout(waiter.timer); + waiter.resolved = true; + this.waiters.delete(index); + waiter.reject(new Error(message)); + result = true; + } + } + return result; + } + public waitFor( matcher: TMatcher, timeout: number ): {ID: number; start: () => {promise: Promise; ID: number}} { diff --git a/test/controller.test.ts b/test/controller.test.ts index afafd8c891..56ddcac408 100755 --- a/test/controller.test.ts +++ b/test/controller.test.ts @@ -2902,7 +2902,7 @@ describe('Controller', () => { mockDeconzAdapterAutoDetectPath.mockReturnValueOnce('/dev/test'); let error; try {await Adapter.create(null, {path: null, baudRate: 100, rtscts: false, adapter: 'efr'}, null, null)} catch (e) {error = e;} - expect(error).toStrictEqual(new Error(`Adapter 'efr' does not exists, possible options: zstack, deconz, zigate`)); + expect(error).toStrictEqual(new Error(`Adapter 'efr' does not exists, possible options: zstack, deconz, zigate, ezsp`)); }); it('Emit read from device', async () => { diff --git a/test/utils.test.ts b/test/utils.test.ts index 31559c8cfb..31003149c9 100644 --- a/test/utils.test.ts +++ b/test/utils.test.ts @@ -67,6 +67,25 @@ describe('Utils', () => { expect(error3).toStrictEqual(new Error("Timedout '5000'")); jest.useRealTimers(); + + // reject test + const wait1_ = waitress.waitFor(1, 5000).start(); + let error1_; + Wait(1000).then(() => {waitress.reject('one', 'drop');}); + try {await wait1_.promise} catch (e) { error1_ = e}; + expect(error1_).toStrictEqual(new Error("drop")); + + jest.useFakeTimers(); + const wait2_ = waitress.waitFor(2, 5000).start(); + let handled1 = waitress.reject('tree', 'drop'); + expect(handled1).toBe(false); + let error2_; + jest.runTimersToTime(6000); + try {await wait2_.promise} catch (e) { error2_ = e}; + expect(error2_).toStrictEqual(new Error("Timedout '5000'")); + let handled2 = waitress.reject('two', 'drop'); + expect(handled2).toBe(false); + jest.useRealTimers(); }); it('Test queue', async () => {