Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enforce TS strict type checking. #23601

Merged
merged 27 commits into from
Sep 7, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 29 additions & 13 deletions lib/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
constructor(restartCallback: () => Promise<void>, exitCallback: (code: number, restart: boolean) => Promise<void>) {
logger.init();
zhSetLogger(logger);
zhcSetLogger(logger);

Check failure on line 88 in lib/controller.ts

View workflow job for this annotation

GitHub Actions / tests (macos-latest, 20)

Argument of type 'Logger' is not assignable to parameter of type 'import("/Users/runner/work/zigbee2mqtt/zigbee2mqtt/node_modules/zigbee-herdsman-converters/lib/types").Logger'.

Check failure on line 88 in lib/controller.ts

View workflow job for this annotation

GitHub Actions / tests (ubuntu-latest, 20)

Argument of type 'Logger' is not assignable to parameter of type 'import("/home/runner/work/zigbee2mqtt/zigbee2mqtt/node_modules/zigbee-herdsman-converters/lib/types").Logger'.

Check failure on line 88 in lib/controller.ts

View workflow job for this annotation

GitHub Actions / tests (ubuntu-latest, 18)

Argument of type 'Logger' is not assignable to parameter of type 'import("/home/runner/work/zigbee2mqtt/zigbee2mqtt/node_modules/zigbee-herdsman-converters/lib/types").Logger'.

Check failure on line 88 in lib/controller.ts

View workflow job for this annotation

GitHub Actions / ci

Argument of type 'Logger' is not assignable to parameter of type 'import("/home/runner/work/zigbee2mqtt/zigbee2mqtt/node_modules/zigbee-herdsman-converters/lib/types").Logger'.

Check failure on line 88 in lib/controller.ts

View workflow job for this annotation

GitHub Actions / tests (macos-latest, 22)

Argument of type 'Logger' is not assignable to parameter of type 'import("/Users/runner/work/zigbee2mqtt/zigbee2mqtt/node_modules/zigbee-herdsman-converters/lib/types").Logger'.

Check failure on line 88 in lib/controller.ts

View workflow job for this annotation

GitHub Actions / tests (ubuntu-latest, 22)

Argument of type 'Logger' is not assignable to parameter of type 'import("/home/runner/work/zigbee2mqtt/zigbee2mqtt/node_modules/zigbee-herdsman-converters/lib/types").Logger'.

Check failure on line 88 in lib/controller.ts

View workflow job for this annotation

GitHub Actions / tests (macos-latest, 18)

Argument of type 'Logger' is not assignable to parameter of type 'import("/Users/runner/work/zigbee2mqtt/zigbee2mqtt/node_modules/zigbee-herdsman-converters/lib/types").Logger'.

Check failure on line 88 in lib/controller.ts

View workflow job for this annotation

GitHub Actions / tests (windows-latest, 22)

Argument of type 'Logger' is not assignable to parameter of type 'import("D:/a/zigbee2mqtt/zigbee2mqtt/node_modules/zigbee-herdsman-converters/lib/types").Logger'.

Check failure on line 88 in lib/controller.ts

View workflow job for this annotation

GitHub Actions / tests (windows-latest, 18)

Argument of type 'Logger' is not assignable to parameter of type 'import("D:/a/zigbee2mqtt/zigbee2mqtt/node_modules/zigbee-herdsman-converters/lib/types").Logger'.

Check failure on line 88 in lib/controller.ts

View workflow job for this annotation

GitHub Actions / tests (windows-latest, 20)

Argument of type 'Logger' is not assignable to parameter of type 'import("D:/a/zigbee2mqtt/zigbee2mqtt/node_modules/zigbee-herdsman-converters/lib/types").Logger'.
this.eventBus = new EventBus();
this.zigbee = new Zigbee(this.eventBus);
this.mqtt = new MQTT(this.eventBus);
Expand Down Expand Up @@ -119,13 +119,28 @@
new ExtensionReport(...this.extensionArgs),
new ExtensionExternalExtension(...this.extensionArgs),
new ExtensionAvailability(...this.extensionArgs),
settings.get().frontend && new ExtensionFrontend(...this.extensionArgs),
settings.get().advanced.legacy_api && new ExtensionBridgeLegacy(...this.extensionArgs),
settings.get().external_converters.length && new ExtensionExternalConverters(...this.extensionArgs),
settings.get().homeassistant && new ExtensionHomeAssistant(...this.extensionArgs),
/* istanbul ignore next */
settings.get().advanced.soft_reset_timeout !== 0 && new ExtensionSoftReset(...this.extensionArgs),
].filter((n) => n);
];

if (settings.get().frontend) {
this.extensions.push(new ExtensionFrontend(...this.extensionArgs));
}

if (settings.get().advanced.legacy_api) {
this.extensions.push(new ExtensionBridgeLegacy(...this.extensionArgs));
}

if (settings.get().external_converters.length) {
this.extensions.push(new ExtensionExternalConverters(...this.extensionArgs));
}

if (settings.get().homeassistant) {
this.extensions.push(new ExtensionHomeAssistant(...this.extensionArgs));
}

/* istanbul ignore next */
if (settings.get().advanced.soft_reset_timeout !== 0) {
this.extensions.push(new ExtensionSoftReset(...this.extensionArgs));
}
}

async start(): Promise<void> {
Expand All @@ -143,7 +158,7 @@
logger.error('Failed to start zigbee');
logger.error('Check https://www.zigbee2mqtt.io/guide/installation/20_zigbee2mqtt-fails-to-start.html for possible solutions');
logger.error('Exiting...');
logger.error(error.stack);
logger.error((error as Error).stack!);
return this.exit(1);
}

Expand All @@ -160,8 +175,9 @@
let deviceCount = 0;

for (const device of this.zigbee.devicesIterator(utils.deviceNotCoordinator)) {
// `definition` validated by `isSupported`
const model = device.isSupported
? `${device.definition.model} - ${device.definition.vendor} ${device.definition.description}`
? `${device.definition!.model} - ${device.definition!.vendor} ${device.definition!.description}`
: 'Not supported';
logger.info(`${device.name} (${device.ieeeAddr}): ${model} (${device.zh.type})`);

Expand All @@ -180,14 +196,14 @@

await this.zigbee.permitJoin(settings.get().permit_join);
} catch (error) {
logger.error(`Failed to set permit join to ${settings.get().permit_join} (${error.message})`);
logger.error(`Failed to set permit join to ${settings.get().permit_join} (${(error as Error).message})`);
}

// MQTT
try {
await this.mqtt.connect();
} catch (error) {
logger.error(`MQTT failed to connect, exiting... (${error.message})`);
logger.error(`MQTT failed to connect, exiting... (${(error as Error).message})`);
await this.zigbee.stop();
return this.exit(1);
}
Expand Down Expand Up @@ -252,7 +268,7 @@
await this.zigbee.stop();
logger.info('Stopped Zigbee2MQTT');
} catch (error) {
logger.error(`Failed to stop Zigbee2MQTT (${error.message})`);
logger.error(`Failed to stop Zigbee2MQTT (${(error as Error).message})`);
code = 1;
}

Expand Down Expand Up @@ -377,7 +393,7 @@
await extension[method]?.();
} catch (error) {
/* istanbul ignore next */
logger.error(`Failed to call '${extension.constructor.name}' '${method}' (${error.stack})`);
logger.error(`Failed to call '${extension.constructor.name}' '${method}' (${(error as Error).stack})`);
}
}
}
Expand Down
50 changes: 42 additions & 8 deletions lib/eventBus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,39 @@ import logger from './util/logger';
// eslint-disable-next-line
type ListenerKey = object;

interface EventBusMap {
adapterDisconnected: [];
permitJoinChanged: [data: eventdata.PermitJoinChanged];
publishAvailability: [];
deviceRenamed: [data: eventdata.EntityRenamed];
deviceRemoved: [data: eventdata.EntityRemoved];
lastSeenChanged: [data: eventdata.LastSeenChanged];
deviceNetworkAddressChanged: [data: eventdata.DeviceNetworkAddressChanged];
deviceAnnounce: [data: eventdata.DeviceAnnounce];
deviceInterview: [data: eventdata.DeviceInterview];
deviceJoined: [data: eventdata.DeviceJoined];
entityOptionsChanged: [data: eventdata.EntityOptionsChanged];
exposesChanged: [data: eventdata.ExposesChanged];
deviceLeave: [data: eventdata.DeviceLeave];
deviceMessage: [data: eventdata.DeviceMessage];
mqttMessage: [data: eventdata.MQTTMessage];
mqttMessagePublished: [data: eventdata.MQTTMessagePublished];
publishEntityState: [data: eventdata.PublishEntityState];
groupMembersChanged: [data: eventdata.GroupMembersChanged];
devicesChanged: [];
scenesChanged: [data: eventdata.ScenesChanged];
reconfigure: [data: eventdata.Reconfigure];
stateChange: [data: eventdata.StateChange];
}
type EventBusListener<K> = K extends keyof EventBusMap
? EventBusMap[K] extends unknown[]
? (...args: EventBusMap[K]) => Promise<void> | void
: never
: never;

export default class EventBus {
private callbacksByExtension: {[s: string]: {event: string; callback: (...args: unknown[]) => void}[]} = {};
private emitter = new events.EventEmitter();
private callbacksByExtension: {[s: string]: {event: keyof EventBusMap; callback: EventBusListener<keyof EventBusMap>}[]} = {};
private emitter = new events.EventEmitter<EventBusMap>();

constructor() {
this.emitter.setMaxListeners(100);
Expand Down Expand Up @@ -167,18 +197,22 @@ export default class EventBus {
this.on('stateChange', callback, key);
}

private on(event: string, callback: (...args: unknown[]) => Promise<void> | void, key: ListenerKey): void {
if (!this.callbacksByExtension[key.constructor.name]) this.callbacksByExtension[key.constructor.name] = [];
const wrappedCallback = async (...args: unknown[]): Promise<void> => {
private on<K extends keyof EventBusMap>(event: K, callback: EventBusListener<K>, key: ListenerKey): void {
if (!this.callbacksByExtension[key.constructor.name]) {
this.callbacksByExtension[key.constructor.name] = [];
}

const wrappedCallback = async (...args: never[]): Promise<void> => {
try {
await callback(...args);
} catch (error) {
logger.error(`EventBus error '${key.constructor.name}/${event}': ${error.message}`);
logger.debug(error.stack);
logger.error(`EventBus error '${key.constructor.name}/${event}': ${(error as Error).message}`);
logger.debug((error as Error).stack!);
}
};

this.callbacksByExtension[key.constructor.name].push({event, callback: wrappedCallback});
this.emitter.on(event, wrappedCallback);
this.emitter.on(event, wrappedCallback as EventBusListener<K>);
}

public removeListeners(key: ListenerKey): void {
Expand Down
27 changes: 17 additions & 10 deletions lib/extension/availability.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import assert from 'assert';
import bind from 'bind-decorator';
import debounce from 'debounce';
import * as zhc from 'zigbee-herdsman-converters';
Expand Down Expand Up @@ -41,9 +42,12 @@
}

private isAvailable(entity: Device | Group): boolean {
return entity.isDevice()
? Date.now() - entity.zh.lastSeen < this.getTimeout(entity)
: entity.membersDevices().length === 0 || entity.membersDevices().some((d) => this.availabilityCache[d.ieeeAddr]);
if (entity.isDevice()) {
return Date.now() - entity.zh.lastSeen < this.getTimeout(entity);

Check failure on line 46 in lib/extension/availability.ts

View workflow job for this annotation

GitHub Actions / tests (macos-latest, 20)

'entity.zh.lastSeen' is possibly 'undefined'.

Check failure on line 46 in lib/extension/availability.ts

View workflow job for this annotation

GitHub Actions / tests (ubuntu-latest, 20)

'entity.zh.lastSeen' is possibly 'undefined'.

Check failure on line 46 in lib/extension/availability.ts

View workflow job for this annotation

GitHub Actions / tests (ubuntu-latest, 18)

'entity.zh.lastSeen' is possibly 'undefined'.

Check failure on line 46 in lib/extension/availability.ts

View workflow job for this annotation

GitHub Actions / ci

'entity.zh.lastSeen' is possibly 'undefined'.

Check failure on line 46 in lib/extension/availability.ts

View workflow job for this annotation

GitHub Actions / tests (macos-latest, 22)

'entity.zh.lastSeen' is possibly 'undefined'.

Check failure on line 46 in lib/extension/availability.ts

View workflow job for this annotation

GitHub Actions / tests (ubuntu-latest, 22)

'entity.zh.lastSeen' is possibly 'undefined'.

Check failure on line 46 in lib/extension/availability.ts

View workflow job for this annotation

GitHub Actions / tests (macos-latest, 18)

'entity.zh.lastSeen' is possibly 'undefined'.

Check failure on line 46 in lib/extension/availability.ts

View workflow job for this annotation

GitHub Actions / tests (windows-latest, 22)

'entity.zh.lastSeen' is possibly 'undefined'.

Check failure on line 46 in lib/extension/availability.ts

View workflow job for this annotation

GitHub Actions / tests (windows-latest, 18)

'entity.zh.lastSeen' is possibly 'undefined'.

Check failure on line 46 in lib/extension/availability.ts

View workflow job for this annotation

GitHub Actions / tests (windows-latest, 20)

'entity.zh.lastSeen' is possibly 'undefined'.
} else {
const membersDevices = entity.membersDevices();
return membersDevices.length === 0 || membersDevices.some((d) => this.availabilityCache[d.ieeeAddr]);
}
}

private resetTimer(device: Device): void {
Expand Down Expand Up @@ -92,7 +96,7 @@
logger.debug(`Successfully pinged '${device.name}' (attempt ${i}/${attempts})`);
break;
} catch (error) {
logger.warning(`Failed to ping '${device.name}' (attempt ${i}/${attempts}, ${error.message})`);
logger.warning(`Failed to ping '${device.name}' (attempt ${i}/${attempts}, ${(error as Error).message})`);

// Try again in 3 seconds.
if (i !== attempts) {
Expand Down Expand Up @@ -125,7 +129,7 @@

this.eventBus.onEntityRenamed(this, async (data) => {
if (utils.isAvailabilityEnabledForEntity(data.entity, settings.get())) {
await this.mqtt.publish(`${data.from}/availability`, null, {retain: true, qos: 1});
await this.mqtt.publish(`${data.from}/availability`, '', {retain: true, qos: 1});
await this.publishAvailability(data.entity, false, true);
}
});
Expand Down Expand Up @@ -162,7 +166,8 @@

private async publishAvailability(entity: Device | Group, logLastSeen: boolean, forcePublish = false, skipGroups = false): Promise<void> {
if (logLastSeen && entity.isDevice()) {
const ago = Date.now() - entity.zh.lastSeen;

Check failure on line 169 in lib/extension/availability.ts

View workflow job for this annotation

GitHub Actions / tests (macos-latest, 20)

'entity.zh.lastSeen' is possibly 'undefined'.

Check failure on line 169 in lib/extension/availability.ts

View workflow job for this annotation

GitHub Actions / tests (ubuntu-latest, 20)

'entity.zh.lastSeen' is possibly 'undefined'.

Check failure on line 169 in lib/extension/availability.ts

View workflow job for this annotation

GitHub Actions / tests (ubuntu-latest, 18)

'entity.zh.lastSeen' is possibly 'undefined'.

Check failure on line 169 in lib/extension/availability.ts

View workflow job for this annotation

GitHub Actions / ci

'entity.zh.lastSeen' is possibly 'undefined'.

Check failure on line 169 in lib/extension/availability.ts

View workflow job for this annotation

GitHub Actions / tests (macos-latest, 22)

'entity.zh.lastSeen' is possibly 'undefined'.

Check failure on line 169 in lib/extension/availability.ts

View workflow job for this annotation

GitHub Actions / tests (ubuntu-latest, 22)

'entity.zh.lastSeen' is possibly 'undefined'.

Check failure on line 169 in lib/extension/availability.ts

View workflow job for this annotation

GitHub Actions / tests (macos-latest, 18)

'entity.zh.lastSeen' is possibly 'undefined'.

Check failure on line 169 in lib/extension/availability.ts

View workflow job for this annotation

GitHub Actions / tests (windows-latest, 22)

'entity.zh.lastSeen' is possibly 'undefined'.

Check failure on line 169 in lib/extension/availability.ts

View workflow job for this annotation

GitHub Actions / tests (windows-latest, 18)

'entity.zh.lastSeen' is possibly 'undefined'.

Check failure on line 169 in lib/extension/availability.ts

View workflow job for this annotation

GitHub Actions / tests (windows-latest, 20)

'entity.zh.lastSeen' is possibly 'undefined'.

if (this.isActiveDevice(entity)) {
logger.debug(`Active device '${entity.name}' was last seen '${(ago / utils.minutes(1)).toFixed(2)}' minutes ago.`);
} else {
Expand Down Expand Up @@ -230,22 +235,24 @@
continue;
}

const converter = device.definition.toZigbee.find((c) => !c.key || c.key.find((k) => item.keys.includes(k)));
const converter = device.definition!.toZigbee.find((c) => !c.key || c.key.find((k) => item.keys.includes(k)));

Check failure on line 238 in lib/extension/availability.ts

View workflow job for this annotation

GitHub Actions / tests (macos-latest, 20)

Object is possibly 'undefined'.

Check failure on line 238 in lib/extension/availability.ts

View workflow job for this annotation

GitHub Actions / tests (ubuntu-latest, 20)

Object is possibly 'undefined'.

Check failure on line 238 in lib/extension/availability.ts

View workflow job for this annotation

GitHub Actions / tests (ubuntu-latest, 18)

Object is possibly 'undefined'.

Check failure on line 238 in lib/extension/availability.ts

View workflow job for this annotation

GitHub Actions / ci

Object is possibly 'undefined'.

Check failure on line 238 in lib/extension/availability.ts

View workflow job for this annotation

GitHub Actions / tests (macos-latest, 22)

Object is possibly 'undefined'.

Check failure on line 238 in lib/extension/availability.ts

View workflow job for this annotation

GitHub Actions / tests (ubuntu-latest, 22)

Object is possibly 'undefined'.

Check failure on line 238 in lib/extension/availability.ts

View workflow job for this annotation

GitHub Actions / tests (macos-latest, 18)

Object is possibly 'undefined'.

Check failure on line 238 in lib/extension/availability.ts

View workflow job for this annotation

GitHub Actions / tests (windows-latest, 22)

Object is possibly 'undefined'.

Check failure on line 238 in lib/extension/availability.ts

View workflow job for this annotation

GitHub Actions / tests (windows-latest, 18)

Object is possibly 'undefined'.

Check failure on line 238 in lib/extension/availability.ts

View workflow job for this annotation

GitHub Actions / tests (windows-latest, 20)

Object is possibly 'undefined'.
const options: KeyValue = device.options;
const state = this.state.get(device);
const meta: zhc.Tz.Meta = {
message: this.state.get(device),
mapped: device.definition,
endpoint_name: null,
mapped: device.definition!,
endpoint_name: undefined,

Check failure on line 244 in lib/extension/availability.ts

View workflow job for this annotation

GitHub Actions / tests (macos-latest, 20)

Type 'undefined' is not assignable to type 'string'.

Check failure on line 244 in lib/extension/availability.ts

View workflow job for this annotation

GitHub Actions / tests (ubuntu-latest, 20)

Type 'undefined' is not assignable to type 'string'.

Check failure on line 244 in lib/extension/availability.ts

View workflow job for this annotation

GitHub Actions / tests (ubuntu-latest, 18)

Type 'undefined' is not assignable to type 'string'.

Check failure on line 244 in lib/extension/availability.ts

View workflow job for this annotation

GitHub Actions / ci

Type 'undefined' is not assignable to type 'string'.

Check failure on line 244 in lib/extension/availability.ts

View workflow job for this annotation

GitHub Actions / tests (macos-latest, 22)

Type 'undefined' is not assignable to type 'string'.

Check failure on line 244 in lib/extension/availability.ts

View workflow job for this annotation

GitHub Actions / tests (ubuntu-latest, 22)

Type 'undefined' is not assignable to type 'string'.

Check failure on line 244 in lib/extension/availability.ts

View workflow job for this annotation

GitHub Actions / tests (macos-latest, 18)

Type 'undefined' is not assignable to type 'string'.

Check failure on line 244 in lib/extension/availability.ts

View workflow job for this annotation

GitHub Actions / tests (windows-latest, 22)

Type 'undefined' is not assignable to type 'string'.

Check failure on line 244 in lib/extension/availability.ts

View workflow job for this annotation

GitHub Actions / tests (windows-latest, 18)

Type 'undefined' is not assignable to type 'string'.

Check failure on line 244 in lib/extension/availability.ts

View workflow job for this annotation

GitHub Actions / tests (windows-latest, 20)

Type 'undefined' is not assignable to type 'string'.
options,
state,
device: device.zh,
};

try {
await converter?.convertGet?.(device.endpoint(), item.keys[0], meta);
const endpoint = device.endpoint();
assert(endpoint);
await converter?.convertGet?.(endpoint, item.keys[0], meta);
} catch (error) {
logger.error(`Failed to read state of '${device.name}' after reconnect (${error.message})`);
logger.error(`Failed to read state of '${device.name}' after reconnect (${(error as Error).message})`);
}

await utils.sleep(500);
Expand Down
Loading