diff --git a/src/adapter/ember/adapter/emberAdapter.ts b/src/adapter/ember/adapter/emberAdapter.ts index 296c4a62d6..a40a030849 100644 --- a/src/adapter/ember/adapter/emberAdapter.ts +++ b/src/adapter/ember/adapter/emberAdapter.ts @@ -19,8 +19,6 @@ import SocketPortUtils from '../../socketPortUtils'; import { EMBER_INSTALL_CODE_CRC_SIZE, EMBER_INSTALL_CODE_SIZES, - EMBER_NUM_802_15_4_CHANNELS, - EMBER_MIN_802_15_4_CHANNEL_NUMBER, LONG_DEST_FRAME_CONTROL, MAC_ACK_REQUIRED, MAXIMUM_INTERPAN_LENGTH, @@ -32,7 +30,6 @@ import { STACK_PROFILE_ZIGBEE_PRO, SECURITY_LEVEL_Z3, INVALID_RADIO_CHANNEL, - EMBER_ALL_802_15_4_CHANNELS_MASK, ZIGBEE_PROFILE_INTEROPERABILITY_LINK_KEY, EMBER_MIN_BROADCAST_ADDRESS, } from '../consts'; @@ -790,6 +787,10 @@ export class EmberAdapter extends Adapter { throw new Error(`Failed to get network parameters with status=${SLStatus[status]}.`); } + if (this.adapterOptions.transmitPower != null && parameters.radioTxPower !== this.adapterOptions.transmitPower) { + await this.setTransmitPower(this.adapterOptions.transmitPower); + } + this.networkCache.parameters = parameters; this.networkCache.eui64 = await this.ezsp.ezspGetEui64(); @@ -1143,12 +1144,12 @@ export class EmberAdapter extends Adapter { const netParams: EmberNetworkParameters = { panId, extendedPanId, - radioTxPower: 5, + radioTxPower: this.adapterOptions.transmitPower || 5, radioChannel, joinMethod: EmberJoinMethod.MAC_ASSOCIATION, nwkManagerId: ZSpec.COORDINATOR_ADDRESS, nwkUpdateId: 0, - channels: EMBER_ALL_802_15_4_CHANNELS_MASK, + channels: ZSpec.ALL_802_15_4_CHANNELS_MASK, }; logger.info(`[INIT FORM] Forming new network with: ${JSON.stringify(netParams)}`, NS); @@ -1853,7 +1854,7 @@ export class EmberAdapter extends Adapter { throw new Error(`[BACKUP] Failed to export Network Key with status=${SLStatus[nkStatus]}.`); } - const zbChannels = Array.from(Array(EMBER_NUM_802_15_4_CHANNELS), (e, i) => i + EMBER_MIN_802_15_4_CHANNEL_NUMBER); + const zbChannels = Array.from(Array(ZSpec.NUM_802_15_4_CHANNELS), (e, i) => i + ZSpec.MIN_802_15_4_CHANNEL_NUMBER); return { networkOptions: { diff --git a/src/adapter/ember/consts.ts b/src/adapter/ember/consts.ts index d12dc79ffa..38db6e595b 100644 --- a/src/adapter/ember/consts.ts +++ b/src/adapter/ember/consts.ts @@ -42,35 +42,6 @@ export const SOURCE_ROUTE_OVERHEAD_UNKNOWN = 0xff; // 0xFFF8 indicates a memory-constrained many-to-one route request export const EMBER_MIN_BROADCAST_ADDRESS = 0xfff8; -/** The maximum 802.15.4 channel number is 26. */ -export const EMBER_MAX_802_15_4_CHANNEL_NUMBER = 26; -/** The minimum 2.4GHz 802.15.4 channel number is 11. */ -export const EMBER_MIN_802_15_4_CHANNEL_NUMBER = 11; -/** The minimum SubGhz channel number is 0. */ -export const EMBER_MIN_SUBGHZ_CHANNEL_NUMBER = 0; - -/** - * ZigBee protocol specifies that active scans have a duration of 3 (138 msec). - * See documentation for emberStartScan in include/network-formation.h - * for more info on duration values. - */ -export const EMBER_ACTIVE_SCAN_DURATION = 3; -/** The SubGhz scan duration is 5. */ -export const EMBER_SUB_GHZ_SCAN_DURATION = 5; -/** There are sixteen 802.15.4 channels. */ -export const EMBER_NUM_802_15_4_CHANNELS = EMBER_MAX_802_15_4_CHANNEL_NUMBER - EMBER_MIN_802_15_4_CHANNEL_NUMBER + 1; -/** A bitmask to scan all 2.4 GHz 802.15.4 channels. */ -export const EMBER_ALL_802_15_4_CHANNELS_MASK = 0x07fff800; -/** The channels that the plugin will preferentially scan when forming and joining. */ -export const NETWORK_FIND_CHANNEL_MASK = 0x0318c800; -/** - * Cut-off value (dBm) <-128..127> - * The maximum noise allowed on a channel to consider for forming a network. - * If the noise on all preferred channels is above this limit and "Enable scanning all channels" is ticked, the scan continues on all channels. - * Use emberAfPluginNetworkFindGetEnergyThresholdForChannelCallback() to override this value. - */ -export const NETWORK_FIND_CUT_OFF_VALUE = -48; - /** * The additional overhead required for network source routing (relay count = 1, relay index = 1). * This does not include the size of the relay list itself. diff --git a/src/adapter/ember/utils/initters.ts b/src/adapter/ember/utils/initters.ts index e7efe56fbf..d827cb5e34 100644 --- a/src/adapter/ember/utils/initters.ts +++ b/src/adapter/ember/utils/initters.ts @@ -1,7 +1,7 @@ /* istanbul ignore file */ import * as ZSpec from '../../../zspec'; import {NetworkCache} from '../adapter/emberAdapter'; -import {ZB_PSA_ALG, INVALID_RADIO_CHANNEL, EMBER_ALL_802_15_4_CHANNELS_MASK} from '../consts'; +import {ZB_PSA_ALG, INVALID_RADIO_CHANNEL} from '../consts'; import {EmberJoinMethod, SecManDerivedKeyType, SecManFlag, SecManKeyType} from '../enums'; import {EMBER_AES_HASH_BLOCK_SIZE} from '../ezsp/consts'; import {EmberAesMmoHashContext, SecManContext} from '../types'; @@ -21,7 +21,7 @@ export const initNetworkCache = (): NetworkCache => { joinMethod: EmberJoinMethod.MAC_ASSOCIATION, nwkManagerId: ZSpec.NULL_NODE_ID, nwkUpdateId: 0, - channels: EMBER_ALL_802_15_4_CHANNELS_MASK, + channels: ZSpec.ALL_802_15_4_CHANNELS_MASK, }, }; }; diff --git a/src/adapter/tstype.ts b/src/adapter/tstype.ts index 9e31a34864..bdddbb4bca 100644 --- a/src/adapter/tstype.ts +++ b/src/adapter/tstype.ts @@ -17,6 +17,7 @@ interface AdapterOptions { concurrent?: number; delay?: number; disableLED: boolean; + transmitPower?: number; forceStartWithInconsistentAdapterConfiguration?: boolean; } diff --git a/src/adapter/z-stack/adapter/zStackAdapter.ts b/src/adapter/z-stack/adapter/zStackAdapter.ts index 7658d820bf..2e79e4b073 100644 --- a/src/adapter/z-stack/adapter/zStackAdapter.ts +++ b/src/adapter/z-stack/adapter/zStackAdapter.ts @@ -157,6 +157,10 @@ class ZStackAdapter extends Adapter { await this.setLED('disable'); } + if (this.adapterOptions.transmitPower != null) { + await this.setTransmitPower(this.adapterOptions.transmitPower); + } + return startResult; } diff --git a/test/adapter/ember/emberAdapter.test.ts b/test/adapter/ember/emberAdapter.test.ts index 88e7f04cad..563bbc8e97 100644 --- a/test/adapter/ember/emberAdapter.test.ts +++ b/test/adapter/ember/emberAdapter.test.ts @@ -16,6 +16,7 @@ import { EmberDeviceUpdate, EmberIncomingMessageType, EmberJoinDecision, + EmberJoinMethod, EmberKeyStructBitmask, EmberNetworkStatus, EmberNodeType, @@ -622,20 +623,17 @@ describe('Ember Adapter Layer', () => { it('Starts with restored when network key mismatch but backup available', async () => { adapter = new EmberAdapter(DEFAULT_NETWORK_OPTIONS, DEFAULT_SERIAL_PORT_OPTIONS, backupPath, DEFAULT_ADAPTER_OPTIONS); - mockEzspGetNetworkParameters.mockResolvedValueOnce([ - SLStatus.OK, - EmberNodeType.COORDINATOR, - { - extendedPanId: DEFAULT_NETWORK_OPTIONS.extendedPanID!, - panId: DEFAULT_NETWORK_OPTIONS.panID, - radioTxPower: 5, - radioChannel: DEFAULT_NETWORK_OPTIONS.channelList[0], - joinMethod: 0, - nwkManagerId: 0, - nwkUpdateId: 0, - channels: ZSpec.ALL_802_15_4_CHANNELS_MASK, - } as EmberNetworkParameters, - ]); + const expectedNetParams: EmberNetworkParameters = { + extendedPanId: DEFAULT_NETWORK_OPTIONS.extendedPanID!, + panId: DEFAULT_NETWORK_OPTIONS.panID, + radioTxPower: 5, + radioChannel: DEFAULT_NETWORK_OPTIONS.channelList[0], + joinMethod: 0, + nwkManagerId: 0, + nwkUpdateId: 0, + channels: ZSpec.ALL_802_15_4_CHANNELS_MASK, + }; + mockEzspGetNetworkParameters.mockResolvedValueOnce([SLStatus.OK, EmberNodeType.COORDINATOR, expectedNetParams]); const contents = Buffer.from(DEFAULT_BACKUP.network_key.key, 'hex').fill(0xff); mockEzspExportKey.mockResolvedValueOnce([SLStatus.OK, {contents} as SecManKey]); @@ -643,6 +641,7 @@ describe('Ember Adapter Layer', () => { await jest.advanceTimersByTimeAsync(5000); await expect(result).resolves.toStrictEqual('restored'); + expect(mockEzspFormNetwork).toHaveBeenCalledWith(expectedNetParams); }); it('Starts with reset when networks mismatch but no backup available', async () => { @@ -682,6 +681,86 @@ describe('Ember Adapter Layer', () => { await jest.advanceTimersByTimeAsync(5000); await expect(result).resolves.toStrictEqual('reset'); + expect(mockEzspFormNetwork).toHaveBeenCalledWith({ + panId: 1234, + extendedPanId: DEFAULT_NETWORK_OPTIONS.extendedPanID!, + radioTxPower: 5, // default when setting `transmitPower` is null/zero + radioChannel: DEFAULT_NETWORK_OPTIONS.channelList[0], + joinMethod: EmberJoinMethod.MAC_ASSOCIATION, + nwkManagerId: ZSpec.COORDINATOR_ADDRESS, + nwkUpdateId: 0, + channels: ZSpec.ALL_802_15_4_CHANNELS_MASK, + } as EmberNetworkParameters); + }); + + it('Starts with reset and forms with given transmit power', async () => { + adapter = new EmberAdapter( + Object.assign({}, DEFAULT_NETWORK_OPTIONS, {panID: 1234}), + DEFAULT_SERIAL_PORT_OPTIONS, + backupPath, + Object.assign({}, DEFAULT_ADAPTER_OPTIONS, {transmitPower: 10}), + ); + + const result = adapter.start(); + + await jest.advanceTimersByTimeAsync(5000); + await expect(result).resolves.toStrictEqual('reset'); + expect(mockEzspFormNetwork).toHaveBeenCalledWith({ + panId: 1234, + extendedPanId: DEFAULT_NETWORK_OPTIONS.extendedPanID!, + radioTxPower: 10, + radioChannel: DEFAULT_NETWORK_OPTIONS.channelList[0], + joinMethod: EmberJoinMethod.MAC_ASSOCIATION, + nwkManagerId: ZSpec.COORDINATOR_ADDRESS, + nwkUpdateId: 0, + channels: ZSpec.ALL_802_15_4_CHANNELS_MASK, + } as EmberNetworkParameters); + }); + + it('Starts with mismatching transmit power', async () => { + adapter = new EmberAdapter( + DEFAULT_NETWORK_OPTIONS, + DEFAULT_SERIAL_PORT_OPTIONS, + backupPath, + Object.assign({}, DEFAULT_ADAPTER_OPTIONS, {transmitPower: 10}), + ); + + const result = adapter.start(); + + await jest.advanceTimersByTimeAsync(5000); + await expect(result).resolves.toStrictEqual('resumed'); + expect(mockEzspSetRadioPower).toHaveBeenCalledTimes(1); + expect(mockEzspSetRadioPower).toHaveBeenCalledWith(10); + }); + + it('Starts with matching transmit power after form', async () => { + adapter = new EmberAdapter( + DEFAULT_NETWORK_OPTIONS, + DEFAULT_SERIAL_PORT_OPTIONS, + backupPath, + Object.assign({}, DEFAULT_ADAPTER_OPTIONS, {transmitPower: 10}), + ); + mockEzspNetworkInit.mockResolvedValueOnce(SLStatus.NOT_JOINED); + mockEzspGetNetworkParameters.mockResolvedValueOnce([ + SLStatus.OK, + EmberNodeType.COORDINATOR, + { + extendedPanId: DEFAULT_NETWORK_OPTIONS.extendedPanID!, + panId: DEFAULT_NETWORK_OPTIONS.panID, + radioTxPower: 10, + radioChannel: DEFAULT_NETWORK_OPTIONS.channelList[0], + joinMethod: 0, + nwkManagerId: 0, + nwkUpdateId: 0, + channels: ZSpec.ALL_802_15_4_CHANNELS_MASK, + } as EmberNetworkParameters, + ]); + + const result = adapter.start(); + + await jest.advanceTimersByTimeAsync(5000); + await expect(result).resolves.toStrictEqual('restored'); + expect(mockEzspSetRadioPower).toHaveBeenCalledTimes(0); }); it('Fails to start when EZSP layer fails to start', async () => { diff --git a/test/adapter/z-stack/adapter.test.ts b/test/adapter/z-stack/adapter.test.ts index f4fb11ea98..e809fb9eb3 100644 --- a/test/adapter/z-stack/adapter.test.ts +++ b/test/adapter/z-stack/adapter.test.ts @@ -2313,6 +2313,13 @@ describe('zstack-adapter', () => { }); }); + it('Start with transmit power set', async () => { + basicMocks(); + adapter = new ZStackAdapter(networkOptions, serialPortOptions, 'backup.json', {transmitPower: 2, disableLED: false}); + await adapter.start(); + expect(mockZnpRequest).toBeCalledWith(Subsystem.SYS, 'stackTune', {operation: 0, value: 2}); + }); + it('Set transmit power', async () => { basicMocks(); await adapter.start();