Skip to content

Commit db20369

Browse files
authored
fix: Various fixes to align behaviors (+add benchmarks for dev) (#28011)
1 parent 0e0a561 commit db20369

29 files changed

+1032
-98
lines changed

.github/workflows/ci.yml

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ name: CI
33
on:
44
push:
55
tags:
6-
- '**'
7-
branches-ignore:
8-
- dev-types
6+
- '[0-9]+.[0-9]+.[0-9]+'
7+
branches:
8+
- dev
9+
- master
910
pull_request:
1011

1112
permissions:
@@ -47,6 +48,13 @@ jobs:
4748
- name: Test
4849
run: pnpm run test:coverage
4950

51+
- name: Bench
52+
if: github.ref == 'refs/heads/dev' || github.event_name == 'pull_request'
53+
uses: CodSpeedHQ/action@v3
54+
with:
55+
run: pnpm run bench
56+
token: ${{ secrets.CODSPEED_TOKEN }}
57+
5058
- name: Log in to the Docker container registry
5159
if: (github.ref == 'refs/heads/dev' || startsWith(github.ref, 'refs/tags/')) && github.event_name == 'push'
5260
uses: docker/login-action@v3

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ tsconfig.types.tsbuildinfo
7777
# Ignore config
7878
data/*
7979
!data/configuration.example.yaml
80+
data-bench
8081

8182
# commit-user-lookup.json
8283
scripts/commit-user-lookup.json

lib/controller.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,10 @@ import utils from "./util/utils";
2828
import Zigbee from "./zigbee";
2929

3030
export class Controller {
31-
private eventBus: EventBus;
32-
private zigbee: Zigbee;
33-
private state: State;
34-
private mqtt: Mqtt;
31+
public readonly eventBus: EventBus;
32+
public readonly zigbee: Zigbee;
33+
public readonly state: State;
34+
public readonly mqtt: Mqtt;
3535
private restartCallback: () => Promise<void>;
3636
private exitCallback: (code: number, restart: boolean) => Promise<void>;
3737
public readonly extensions: Set<Extension>;

lib/extension/bind.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,9 @@ import Group from "../model/group";
99
import type {Zigbee2MQTTAPI, Zigbee2MQTTResponseEndpoints} from "../types/api";
1010
import logger from "../util/logger";
1111
import * as settings from "../util/settings";
12-
import utils from "../util/utils";
12+
import utils, {DEFAULT_BIND_GROUP_ID} from "../util/utils";
1313
import Extension from "./extension";
1414

15-
const TOPIC_REGEX = new RegExp(`^${settings.get().mqtt.base_topic}/bridge/request/device/(bind|unbind)`);
1615
const ALL_CLUSTER_CANDIDATES: readonly ClusterName[] = [
1716
"genScenes",
1817
"genOnOff",
@@ -28,7 +27,7 @@ const ALL_CLUSTER_CANDIDATES: readonly ClusterName[] = [
2827
];
2928

3029
// See zigbee-herdsman-converters
31-
const DEFAULT_BIND_GROUP = {type: "group_number", ID: 901, name: "default_bind_group"};
30+
const DEFAULT_BIND_GROUP = {type: "group_number", ID: DEFAULT_BIND_GROUP_ID, name: "default_bind_group"};
3231
const DEFAULT_REPORT_CONFIG = {minimumReportInterval: 5, maximumReportInterval: 3600, reportableChange: 1};
3332

3433
const getColorCapabilities = async (endpoint: zh.Endpoint): Promise<{colorTemperature: boolean; colorXY: boolean}> => {
@@ -204,6 +203,7 @@ interface ParsedMQTTMessage {
204203
}
205204

206205
export default class Bind extends Extension {
206+
#topicRegex = new RegExp(`^${settings.get().mqtt.base_topic}/bridge/request/device/(bind|unbind)`);
207207
private pollDebouncers: {[s: string]: () => void} = {};
208208

209209
// biome-ignore lint/suspicious/useAwait: API
@@ -216,7 +216,7 @@ export default class Bind extends Extension {
216216
private parseMQTTMessage(
217217
data: eventdata.MQTTMessage,
218218
): [raw: KeyValue | undefined, parsed: ParsedMQTTMessage | undefined, error: string | undefined] {
219-
if (data.topic.match(TOPIC_REGEX)) {
219+
if (data.topic.match(this.#topicRegex)) {
220220
const type = data.topic.endsWith("unbind") ? "unbind" : "bind";
221221
let skipDisableReporting = false;
222222
const message = JSON.parse(data.message) as Zigbee2MQTTAPI["bridge/request/device/bind"];

lib/extension/bridge.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,11 @@ import type {Zigbee2MQTTAPI, Zigbee2MQTTDevice, Zigbee2MQTTResponse, Zigbee2MQTT
1414
import data from "../util/data";
1515
import logger from "../util/logger";
1616
import * as settings from "../util/settings";
17-
import utils, {assertString} from "../util/utils";
17+
import utils, {assertString, DEFAULT_BIND_GROUP_ID} from "../util/utils";
1818
import Extension from "./extension";
1919

20-
const REQUEST_REGEX = new RegExp(`${settings.get().mqtt.base_topic}/bridge/request/(.*)`);
21-
2220
export default class Bridge extends Extension {
21+
#requestRegex = new RegExp(`${settings.get().mqtt.base_topic}/bridge/request/(.*)`);
2322
// set on `start`
2423
#osInfo!: Zigbee2MQTTAPI["bridge/info"]["os"];
2524
private zigbee2mqttVersion!: {commitHash?: string; version: string};
@@ -197,7 +196,7 @@ export default class Bridge extends Extension {
197196
}
198197

199198
@bind async onMQTTMessage(data: eventdata.MQTTMessage): Promise<void> {
200-
const match = data.topic.match(REQUEST_REGEX);
199+
const match = data.topic.match(this.#requestRegex);
201200

202201
if (!match) {
203202
return;
@@ -797,7 +796,7 @@ export default class Bridge extends Extension {
797796

798797
groups.push({
799798
id: group.ID,
800-
friendly_name: group.ID === 901 ? "default_bind_group" : group.name,
799+
friendly_name: group.ID === DEFAULT_BIND_GROUP_ID ? "default_bind_group" : group.name,
801800
description: group.options.description,
802801
scenes: utils.getScenes(group.zh),
803802
members,

lib/extension/groups.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ import * as settings from "../util/settings";
1111
import utils, {isLightExpose} from "../util/utils";
1212
import Extension from "./extension";
1313

14-
const TOPIC_REGEX = new RegExp(`^${settings.get().mqtt.base_topic}/bridge/request/group/members/(remove|add|remove_all)$`);
15-
1614
const STATE_PROPERTIES: Readonly<Record<string, (value: string, exposes: zhc.Expose[]) => boolean>> = {
1715
state: () => true,
1816
brightness: (_value, exposes) => exposes.some((e) => isLightExpose(e) && e.features.some((f) => f.name === "brightness")),
@@ -38,6 +36,7 @@ interface ParsedMQTTMessage {
3836
}
3937

4038
export default class Groups extends Extension {
39+
#topicRegex = new RegExp(`^${settings.get().mqtt.base_topic}/bridge/request/group/members/(remove|add|remove_all)$`);
4140
private lastOptimisticState: {[s: string]: KeyValue} = {};
4241

4342
// biome-ignore lint/suspicious/useAwait: API
@@ -185,7 +184,7 @@ export default class Groups extends Extension {
185184
private parseMQTTMessage(
186185
data: eventdata.MQTTMessage,
187186
): [raw: KeyValue | undefined, parsed: ParsedMQTTMessage | undefined, error: string | undefined] {
188-
const topicRegexMatch = data.topic.match(TOPIC_REGEX);
187+
const topicRegexMatch = data.topic.match(this.#topicRegex);
189188

190189
if (topicRegexMatch) {
191190
const type = topicRegexMatch[1] as "remove" | "add" | "remove_all";
@@ -273,23 +272,23 @@ export default class Groups extends Extension {
273272
try {
274273
if (type === "add") {
275274
assert(resolvedGroup, "`resolvedGroup` is missing");
276-
logger.info(`Adding '${resolvedDevice.name}' to '${resolvedGroup.name}'`);
275+
logger.info(`Adding endpoint '${resolvedEndpoint.ID}' of device '${resolvedDevice.name}' to group '${resolvedGroup.name}'`);
277276
await resolvedEndpoint.addToGroup(resolvedGroup.zh);
278277
changedGroups.push(resolvedGroup);
279278
// biome-ignore lint/style/noNonNullAssertion: valid from resolved asserts
280279
const respPayload = {device: deviceKey!, endpoint: endpointKey!, group: groupKey!};
281280
await this.publishResponse<"bridge/response/group/members/add">(parsed.type, raw, respPayload);
282281
} else if (type === "remove") {
283282
assert(resolvedGroup, "`resolvedGroup` is missing");
284-
logger.info(`Removing '${resolvedDevice.name}' from '${resolvedGroup.name}'`);
283+
logger.info(`Removing endpoint '${resolvedEndpoint.ID}' of device '${resolvedDevice.name}' from group '${resolvedGroup.name}'`);
285284
await resolvedEndpoint.removeFromGroup(resolvedGroup.zh);
286285
changedGroups.push(resolvedGroup);
287286
// biome-ignore lint/style/noNonNullAssertion: valid from resolved asserts
288287
const respPayload = {device: deviceKey!, endpoint: endpointKey!, group: groupKey!};
289288
await this.publishResponse<"bridge/response/group/members/remove">(parsed.type, raw, respPayload);
290289
} else {
291290
// remove_all
292-
logger.info(`Removing '${resolvedDevice.name}' from all groups`);
291+
logger.info(`Removing endpoint '${resolvedEndpoint.ID}' of device '${resolvedDevice.name}' from all groups`);
293292

294293
for (const group of this.zigbee.groupsIterator((g) => g.members.includes(resolvedEndpoint))) {
295294
changedGroups.push(group);
@@ -301,7 +300,7 @@ export default class Groups extends Extension {
301300
await this.publishResponse<"bridge/response/group/members/remove_all">(parsed.type, raw, respPayload);
302301
}
303302
} catch (e) {
304-
const errorMsg = `Failed to ${type} from group (${(e as Error).message})`;
303+
const errorMsg = `Failed to ${type} ${type === "add" ? "to" : "from"} group (${(e as Error).message})`;
305304
await this.publishResponse(parsed.type, raw, {}, errorMsg);
306305
// biome-ignore lint/style/noNonNullAssertion: always Error
307306
logger.debug((e as Error).stack!);

lib/extension/networkMap.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@ const SUPPORTED_FORMATS = ["raw", "graphviz", "plantuml"];
1313
* This extension creates a network map
1414
*/
1515
export default class NetworkMap extends Extension {
16-
private topic = `${settings.get().mqtt.base_topic}/bridge/request/networkmap`;
16+
#topic = `${settings.get().mqtt.base_topic}/bridge/request/networkmap`;
1717

1818
// biome-ignore lint/suspicious/useAwait: API
1919
override async start(): Promise<void> {
2020
this.eventBus.onMQTTMessage(this, this.onMQTTMessage);
2121
}
2222

2323
@bind async onMQTTMessage(data: eventdata.MQTTMessage): Promise<void> {
24-
if (data.topic === this.topic) {
24+
if (data.topic === this.#topic) {
2525
const message = utils.parseJSON(data.message, data.message) as Zigbee2MQTTAPI["bridge/request/networkmap"];
2626

2727
try {

lib/extension/otaUpdate.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,11 @@ export interface UpdatePayload {
2525
};
2626
}
2727

28-
const topicRegex = new RegExp(
29-
`^${settings.get().mqtt.base_topic}/bridge/request/device/ota_update/(update|check|schedule|unschedule)/?(downgrade)?`,
30-
"i",
31-
);
32-
3328
export default class OTAUpdate extends Extension {
29+
#topicRegex = new RegExp(
30+
`^${settings.get().mqtt.base_topic}/bridge/request/device/ota_update/(update|check|schedule|unschedule)/?(downgrade)?`,
31+
"i",
32+
);
3433
private inProgress = new Set<string>();
3534
private lastChecked = new Map<string, number>();
3635
private scheduledUpgrades = new Set<string>();
@@ -252,7 +251,7 @@ export default class OTAUpdate extends Extension {
252251
}
253252

254253
@bind async onMQTTMessage(data: eventdata.MQTTMessage): Promise<void> {
255-
const topicMatch = data.topic.match(topicRegex);
254+
const topicMatch = data.topic.match(this.#topicRegex);
256255

257256
if (!topicMatch) {
258257
return;

lib/extension/publish.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ import * as settings from "../util/settings";
99
import utils from "../util/utils";
1010
import Extension from "./extension";
1111

12+
// TODO: get rid of this, use class member
1213
let topicGetSetRegex: RegExp;
13-
// Used by `publish.test.js` to reload regex when changing `mqtt.base_topic`.
14+
// Used by `publish.test.ts` to reload regex when changing `mqtt.base_topic`.
1415
export const loadTopicGetSetRegex = (): void => {
1516
topicGetSetRegex = new RegExp(`^${settings.get().mqtt.base_topic}/(?!bridge)(.+?)/(get|set)(?:/(.+))?$`);
1617
};
17-
loadTopicGetSetRegex();
1818

1919
const STATE_VALUES: ReadonlyArray<string> = ["on", "off", "toggle", "open", "close", "stop", "lock", "unlock"];
2020
const SCENE_CONVERTER_KEYS: ReadonlyArray<string> = ["scene_store", "scene_add", "scene_remove", "scene_remove_all", "scene_rename"];
@@ -29,6 +29,7 @@ interface ParsedTopic {
2929
export default class Publish extends Extension {
3030
// biome-ignore lint/suspicious/useAwait: API
3131
override async start(): Promise<void> {
32+
loadTopicGetSetRegex();
3233
this.eventBus.onMQTTMessage(this, this.onMQTTMessage);
3334
}
3435

lib/model/group.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type * as zhc from "zigbee-herdsman-converters";
2-
32
import * as settings from "../util/settings";
3+
import {DEFAULT_BIND_GROUP_ID} from "../util/utils";
44

55
export default class Group {
66
public zh: zh.Group;
@@ -23,6 +23,12 @@ export default class Group {
2323
this.resolveDevice = resolveDevice;
2424
}
2525

26+
ensureInSettings(): void {
27+
if (this.ID !== DEFAULT_BIND_GROUP_ID && !settings.getGroup(this.ID)) {
28+
settings.addGroup(this.name, this.ID.toString());
29+
}
30+
}
31+
2632
hasMember(device: Device): boolean {
2733
return !!device.zh.endpoints.find((e) => this.zh.members.includes(e));
2834
}

0 commit comments

Comments
 (0)