Skip to content

Commit

Permalink
fix: Ember: fix CCA issues in busy environments (broadcast errors) (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
Nerivec committed Aug 17, 2024
1 parent 5a8499b commit 81d828b
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 2 deletions.
18 changes: 18 additions & 0 deletions src/adapter/ember/adapter/emberAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import {
EmberDeviceUpdate,
EmberIncomingMessageType,
EmberTransmitPriority,
IEEE802154CcaMode,
} from '../enums';
import {EzspBuffalo} from '../ezsp/buffalo';
import {EMBER_ENCRYPTION_KEY_SIZE, EZSP_MIN_PROTOCOL_VERSION, EZSP_PROTOCOL_VERSION, EZSP_STACK_TYPE_MESH} from '../ezsp/consts';
Expand Down Expand Up @@ -194,6 +195,8 @@ type StackConfig = {
END_DEVICE_POLL_TIMEOUT: number;
/** <0-65535> (Default: 300) @see EzspConfigId.TRANSIENT_KEY_TIMEOUT_S */
TRANSIENT_KEY_TIMEOUT_S: number;
/**@see Ezsp.ezspSetRadioIeee802154CcaMode */
CCA_MODE?: keyof typeof IEEE802154CcaMode;
};

/**
Expand All @@ -215,6 +218,7 @@ export const DEFAULT_STACK_CONFIG: Readonly<StackConfig> = {
TRANSIENT_DEVICE_TIMEOUT: 10000,
END_DEVICE_POLL_TIMEOUT: 8, // zigpc: 8
TRANSIENT_KEY_TIMEOUT_S: 300, // zigpc: 65535
CCA_MODE: undefined, // not set by default
};
/** Default behavior is to disable app key requests */
const ALLOW_APP_KEY_REQUESTS = false;
Expand Down Expand Up @@ -378,6 +382,13 @@ export class EmberAdapter extends Adapter {
logger.error(`[STACK CONFIG] Invalid TRANSIENT_KEY_TIMEOUT_S, using default.`, NS);
}

config.CCA_MODE = config.CCA_MODE ?? undefined; // always default to undefined

if (config.CCA_MODE && IEEE802154CcaMode[config.CCA_MODE] === undefined) {
config.CCA_MODE = undefined;
logger.error(`[STACK CONFIG] Invalid CCA_MODE, ignoring.`, NS);
}

logger.info(`Using stack config ${JSON.stringify(config)}.`, NS);
return config;
} catch {}
Expand Down Expand Up @@ -713,6 +724,13 @@ export class EmberAdapter extends Adapter {
await this.emberSetEzspConfigValue(EzspConfigId.MAX_END_DEVICE_CHILDREN, this.stackConfig.MAX_END_DEVICE_CHILDREN);
await this.emberSetEzspConfigValue(EzspConfigId.END_DEVICE_POLL_TIMEOUT, this.stackConfig.END_DEVICE_POLL_TIMEOUT);
await this.emberSetEzspConfigValue(EzspConfigId.TRANSIENT_KEY_TIMEOUT_S, this.stackConfig.TRANSIENT_KEY_TIMEOUT_S);
// XXX: temp-fix: forces a side-effect in the firmware that prevents broadcast issues in environments with unusual interferences
await this.emberSetEzspValue(EzspValueId.CCA_THRESHOLD, 1, [0]);

if (this.stackConfig.CCA_MODE) {
// validated in `loadStackConfig`
await this.ezsp.ezspSetRadioIeee802154CcaMode(IEEE802154CcaMode[this.stackConfig.CCA_MODE]);
}

// WARNING: From here on EZSP commands that affect memory allocation on the NCP should no longer be called (like resizing tables)

Expand Down
23 changes: 23 additions & 0 deletions src/adapter/ember/enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2098,3 +2098,26 @@ export enum EmberTransmitPriority {
NORMAL = 1,
SCAN_OKAY = 2,
}

export enum IEEE802154CcaMode {
/** RSSI-based CCA. CCA reports a busy medium upon detecting any energy above -75 (default RAIL_CsmaConfig_t.ccaThreshold). */
RSSI = 0,
/**
* Signal Identifier-based CCA. CCA reports a busy medium only upon the detection of a signal compliant with this standard
* with the same modulation and spreading characteristics of the PHY that is currently in use.
*/
SIGNAL = 1,
/**
* RSSI or signal identifier-based CCA. CCA reports a busy medium on either detecting any energy above
* -75 (default RAIL_CsmaConfig_t.ccaThreshold) or detection of a signal compliant with this standard with the same modulation
* and spreading characteristics of the PHY that is currently in use.
*/
SIGNAL_OR_RSSI = 2,
/**
* RSSI and signal identifier-based CCA. CCA reports a busy medium only on detecting any energy above -75 (default RAIL_CsmaConfig_t.ccaThreshold)
* of a signal compliant with this standard with the same modulation and spreading characteristics of the PHY that is currently in use.
*/
SIGNAL_AND_RSSI = 3,
/** ALOHA. Always transmit CCA=1. CCA always reports an idle medium. */
ALWAYS_TRANSMIT = 4,
}
3 changes: 2 additions & 1 deletion src/adapter/ember/ezsp/ezsp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import {
EmberGPStatus,
EmberApsOption,
EmberTransmitPriority,
IEEE802154CcaMode,
} from '../enums';
import {EzspError} from '../ezspError';
import {
Expand Down Expand Up @@ -3808,7 +3809,7 @@ export class Ezsp extends EventEmitter<EmberEzspEventMap> {
* @param ccaMode uint8_t A RAIL_IEEE802154_CcaMode_t value.
* @returns An SLStatus value indicating the success or failure of the command.
*/
async ezspSetRadioIeee802154CcaMode(ccaMode: number): Promise<SLStatus> {
async ezspSetRadioIeee802154CcaMode(ccaMode: IEEE802154CcaMode): Promise<SLStatus> {
const sendBuffalo = this.startCommand(EzspFrameID.SET_RADIO_IEEE802154_CCA_MODE);
sendBuffalo.writeUInt8(ccaMode);

Expand Down
42 changes: 41 additions & 1 deletion test/adapter/ember/emberAdapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
EmberOutgoingMessageType,
EmberVersionType,
EzspStatus,
IEEE802154CcaMode,
SecManDerivedKeyType,
SecManFlag,
SecManKeyType,
Expand Down Expand Up @@ -159,6 +160,7 @@ const mockEzspNetworkState = jest.fn().mockResolvedValue(EmberNetworkStatus.JOIN
const mockEzspGetEui64 = jest.fn().mockResolvedValue(DEFAULT_COORDINATOR_IEEE);
const mockEzspSetConcentrator = jest.fn().mockResolvedValue(SLStatus.OK);
const mockEzspSetSourceRouteDiscoveryMode = jest.fn().mockResolvedValue(1240 /* ms */);
const mockEzspSetRadioIeee802154CcaMode = jest.fn().mockResolvedValue(SLStatus.OK);
// not OK by default since used to detected unreged EP
const mockEzspGetEndpointFlags = jest.fn().mockResolvedValue([SLStatus.NOT_FOUND, EzspEndpointFlag.DISABLED]);
const mockEzspAddEndpoint = jest.fn().mockResolvedValue(SLStatus.OK);
Expand Down Expand Up @@ -282,6 +284,7 @@ jest.mock('../../../src/adapter/ember/ezsp/ezsp', () => ({
ezspGetEui64: mockEzspGetEui64,
ezspSetConcentrator: mockEzspSetConcentrator,
ezspSetSourceRouteDiscoveryMode: mockEzspSetSourceRouteDiscoveryMode,
ezspSetRadioIeee802154CcaMode: mockEzspSetRadioIeee802154CcaMode,
ezspGetEndpointFlags: mockEzspGetEndpointFlags,
ezspAddEndpoint: mockEzspAddEndpoint,
ezspNetworkInit: mockEzspNetworkInit,
Expand Down Expand Up @@ -333,6 +336,7 @@ const ezspMocks = [
mockEzspGetEui64,
mockEzspSetConcentrator,
mockEzspSetSourceRouteDiscoveryMode,
mockEzspSetRadioIeee802154CcaMode,
mockEzspGetEndpointFlags,
mockEzspAddEndpoint,
mockEzspNetworkInit,
Expand Down Expand Up @@ -473,6 +477,7 @@ describe('Ember Adapter Layer', () => {
TRANSIENT_DEVICE_TIMEOUT: 1000,
END_DEVICE_POLL_TIMEOUT: 12,
TRANSIENT_KEY_TIMEOUT_S: 500,
CCA_MODE: 'SIGNAL_AND_RSSI',
};

writeFileSync(STACK_CONFIG_PATH, JSON.stringify(config, undefined, 2));
Expand All @@ -497,6 +502,22 @@ describe('Ember Adapter Layer', () => {
TRANSIENT_DEVICE_TIMEOUT: 65536,
END_DEVICE_POLL_TIMEOUT: 15,
TRANSIENT_KEY_TIMEOUT_S: 65536,
CCA_MODE: 'abcd',
};

writeFileSync(STACK_CONFIG_PATH, JSON.stringify(config, undefined, 2));

adapter = new EmberAdapter(DEFAULT_NETWORK_OPTIONS, DEFAULT_SERIAL_PORT_OPTIONS, backupPath, DEFAULT_ADAPTER_OPTIONS);

expect(adapter.stackConfig).toStrictEqual(DEFAULT_STACK_CONFIG);

// cleanup
unlinkSync(STACK_CONFIG_PATH);
});

it('Loads only valid custom stack config - null CCA_MODE', () => {
const config = {
CCA_MODE: null,
};

writeFileSync(STACK_CONFIG_PATH, JSON.stringify(config, undefined, 2));
Expand Down Expand Up @@ -546,7 +567,6 @@ describe('Ember Adapter Layer', () => {
});

it('Starts with custom stack config', async () => {
adapter = new EmberAdapter(DEFAULT_NETWORK_OPTIONS, DEFAULT_SERIAL_PORT_OPTIONS, backupPath, DEFAULT_ADAPTER_OPTIONS);
const config = {
CONCENTRATOR_RAM_TYPE: 'low',
CONCENTRATOR_MIN_TIME: 1,
Expand All @@ -558,6 +578,7 @@ describe('Ember Adapter Layer', () => {
TRANSIENT_DEVICE_TIMEOUT: 1000,
END_DEVICE_POLL_TIMEOUT: 12,
TRANSIENT_KEY_TIMEOUT_S: 500,
CCA_MODE: 'SIGNAL_AND_RSSI',
};

writeFileSync(STACK_CONFIG_PATH, JSON.stringify(config, undefined, 2));
Expand All @@ -580,6 +601,25 @@ describe('Ember Adapter Layer', () => {
config.CONCENTRATOR_DELIVERY_FAILURE_THRESHOLD,
config.CONCENTRATOR_MAX_HOPS,
);
expect(mockEzspSetRadioIeee802154CcaMode).toHaveBeenCalledWith(IEEE802154CcaMode.SIGNAL_AND_RSSI);

// cleanup
unlinkSync(STACK_CONFIG_PATH);
});

it('Starts with custom stack config invalid CCA_MODE', async () => {
const config = {
CCA_MODE: 'abcd',
};

writeFileSync(STACK_CONFIG_PATH, JSON.stringify(config, undefined, 2));

adapter = new EmberAdapter(DEFAULT_NETWORK_OPTIONS, DEFAULT_SERIAL_PORT_OPTIONS, backupPath, DEFAULT_ADAPTER_OPTIONS);
const result = adapter.start();

await jest.advanceTimersByTimeAsync(5000);
await expect(result).resolves.toStrictEqual('resumed');
expect(mockEzspSetRadioIeee802154CcaMode).toHaveBeenCalledTimes(0);

// cleanup
unlinkSync(STACK_CONFIG_PATH);
Expand Down

0 comments on commit 81d828b

Please sign in to comment.