Skip to content

Commit b7706f7

Browse files
author
Wojciech Marmurowicz
committed
feat: allow starting z2m without mqtt (Frontend+WebSocket / WebSocket only mode)
1 parent f4f97d1 commit b7706f7

12 files changed

Lines changed: 185 additions & 16 deletions

data/configuration.example.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Indicates the configuration version (used by configuration migrations)
2-
version: 5
2+
version: 6
33

44
# Home Assistant integration (MQTT discovery)
55
homeassistant:
@@ -12,6 +12,8 @@ frontend:
1212

1313
# MQTT settings
1414
mqtt:
15+
# Enable MQTT connection
16+
enabled: true
1517
# MQTT base topic for zigbee2mqtt MQTT messages
1618
base_topic: zigbee2mqtt
1719
# MQTT server URL

lib/controller.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -165,12 +165,21 @@ export class Controller {
165165
logger.info(`Currently ${deviceCount} devices are joined.`);
166166

167167
// MQTT
168-
try {
169-
await this.mqtt.connect();
170-
} catch (error) {
171-
logger.error(`MQTT failed to connect, exiting... (${(error as Error).message})`);
172-
await this.zigbee.stop();
173-
return await this.exit(1);
168+
if (settings.get().mqtt.enabled) {
169+
try {
170+
await this.mqtt.connect();
171+
} catch (error) {
172+
logger.error(`MQTT failed to connect, exiting... (${(error as Error).message})`);
173+
await this.zigbee.stop();
174+
return await this.exit(1);
175+
}
176+
} else {
177+
if (!settings.get().frontend.enabled) {
178+
logger.error("MQTT and Frontend are both disabled, process is unable to start, exiting...");
179+
await this.zigbee.stop();
180+
return await this.exit(1);
181+
}
182+
logger.info("MQTT is disabled, skipping connection");
174183
}
175184

176185
if (abortSignal.aborted) {
@@ -360,7 +369,9 @@ export class Controller {
360369

361370
// Wrap-up
362371
this.state.stop();
363-
await this.mqtt.disconnect();
372+
if (settings.get().mqtt.enabled) {
373+
await this.mqtt.disconnect();
374+
}
364375

365376
try {
366377
await this.zigbee.stop();

lib/mqtt.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ export default class Mqtt {
2828
public retainedMessages: {[s: string]: {topic: string; payload: string; options: MqttPublishOptions}} = {};
2929

3030
get info() {
31+
if (!settings.get().mqtt.enabled) {
32+
return {version: undefined, server: ""};
33+
}
3134
return {
3235
version: this.client.options.protocolVersion,
3336
server: `${this.client.options.protocol}://${this.client.options.host}:${this.client.options.port}`,
@@ -140,7 +143,7 @@ export default class Mqtt {
140143

141144
// Set timer at interval to check if connected to MQTT server.
142145
this.connectionTimer = setInterval(() => {
143-
if (!this.isConnected()) {
146+
if (settings.get().mqtt.enabled && !this.isConnected()) {
144147
logger.error("Not connected to MQTT server!");
145148
}
146149
}, utils.seconds(10));
@@ -223,7 +226,7 @@ export default class Mqtt {
223226
this.eventBus.emitMQTTMessagePublished({topic, payload, options: finalOptions});
224227

225228
if (!this.isConnected()) {
226-
if (!finalOptions.skipLog) {
229+
if (!finalOptions.skipLog && settings.get().mqtt.enabled) {
227230
logger.error("Not connected to MQTT server!");
228231
logger.error(`Cannot send message: topic: '${topic}', payload: '${payload}`);
229232
}

lib/types/api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ export interface Zigbee2MQTTSettings {
120120
passive: {timeout: number};
121121
};
122122
mqtt: {
123+
enabled: boolean;
123124
base_topic: string;
124125
include_device_information: boolean;
125126
force_disable_retain: boolean;

lib/util/settings.schema.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,13 @@
117117
"type": "object",
118118
"title": "MQTT",
119119
"properties": {
120+
"enabled": {
121+
"type": "boolean",
122+
"title": "Enabled",
123+
"description": "Enable MQTT connection. When false, the application runs without connecting to any MQTT broker.",
124+
"default": true,
125+
"requiresRestart": true
126+
},
120127
"base_topic": {
121128
"type": "string",
122129
"title": "Base topic",
@@ -130,6 +137,7 @@
130137
"title": "MQTT server",
131138
"requiresRestart": true,
132139
"description": "MQTT server URL (use mqtts:// for SSL/TLS connection)",
140+
"default": "mqtt://localhost",
133141
"examples": ["mqtt://localhost:1883"]
134142
},
135143
"keepalive": {
@@ -219,7 +227,7 @@
219227
"maximum": 268435456
220228
}
221229
},
222-
"required": ["server"]
230+
"required": []
223231
},
224232
"serial": {
225233
"type": "object",

lib/util/settings.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import yaml from "./yaml";
1010
export {schemaJson};
1111
// When updating also update:
1212
// - https://github.com/Koenkk/zigbee2mqtt/blob/dev/data/configuration.example.yaml#L2
13-
export const CURRENT_VERSION = 5;
13+
export const CURRENT_VERSION = 6;
1414
/** NOTE: by order of priority, lower index is lower level (more important) */
1515
export const LOG_LEVELS: readonly string[] = ["error", "warning", "info", "debug"] as const;
1616
export type LogLevel = "error" | "warning" | "info" | "debug";
@@ -45,7 +45,9 @@ export const defaults = {
4545
base_url: "/",
4646
},
4747
mqtt: {
48+
enabled: true,
4849
base_topic: "zigbee2mqtt",
50+
server: "mqtt://localhost",
4951
include_device_information: false,
5052
force_disable_retain: false,
5153
// 1MB = roughly 3.5KB per device * 300 devices for `/bridge/devices`
@@ -158,6 +160,7 @@ export function writeMinimalDefaults(): void {
158160
const minimal = {
159161
version: CURRENT_VERSION,
160162
mqtt: {
163+
enabled: true,
161164
base_topic: defaults.mqtt.base_topic,
162165
server: "mqtt://localhost:1883",
163166
},

lib/util/settingsMigration.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ interface SettingsCustomHandler extends Omit<SettingsMigration, "path"> {
2828
execute: (currentSettings: Partial<Settings>) => [validPath: boolean, previousValue: unknown, changed: boolean];
2929
}
3030

31-
const SUPPORTED_VERSIONS: Settings["version"][] = [undefined, 2, 3, 4, settings.CURRENT_VERSION];
31+
const SUPPORTED_VERSIONS: Settings["version"][] = [undefined, 2, 3, 4, 5, settings.CURRENT_VERSION];
3232

3333
function backupSettings(version: number): void {
3434
const filePath = data.joinPath("configuration.yaml");
@@ -532,6 +532,40 @@ function migrateToFive(
532532
});
533533
}
534534

535+
function migrateToSix(
536+
_currentSettings: Partial<Settings>,
537+
transfers: SettingsTransfer[],
538+
changes: SettingsChange[],
539+
additions: SettingsAdd[],
540+
removals: SettingsRemove[],
541+
customHandlers: SettingsCustomHandler[],
542+
): void {
543+
transfers.push();
544+
changes.push({
545+
path: ["version"],
546+
note: "Migrated settings to version 6",
547+
newValue: 6,
548+
});
549+
additions.push();
550+
removals.push();
551+
552+
customHandlers.push({
553+
note: "Added mqtt.enabled option (defaults to true).",
554+
noteIf: () => true,
555+
execute: (currentSettings) => {
556+
const [validPath, previousValue] = getValue(currentSettings, ["mqtt", "enabled"]);
557+
558+
if (!validPath) {
559+
setValue(currentSettings, ["mqtt", "enabled"], true, true);
560+
561+
return [true, undefined, true];
562+
}
563+
564+
return [true, previousValue, false];
565+
},
566+
});
567+
}
568+
535569
/**
536570
* Order of execution:
537571
* - Transfer
@@ -588,6 +622,10 @@ export function migrateIfNecessary(): void {
588622
migrationNotesFileName = "migration-4-to-5.log";
589623

590624
migrateToFive(currentSettings, transfers, changes, additions, removals, customHandlers);
625+
} else if (currentSettings.version === 5) {
626+
migrationNotesFileName = "migration-5-to-6.log";
627+
628+
migrateToSix(currentSettings, transfers, changes, additions, removals, customHandlers);
591629
}
592630

593631
for (const transfer of transfers) {

test/controller.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,27 @@ describe("Controller", () => {
381381
expect(settings.get().onboarding).toStrictEqual(true);
382382
});
383383

384+
it("Start controller with MQTT disabled should be successful", async () => {
385+
settings.set(["mqtt", "enabled"], false);
386+
settings.set(["frontend", "enabled"], true);
387+
await controller.start();
388+
await flushPromises();
389+
await controller.stop();
390+
expect(mockZHController.stop).toHaveBeenCalledTimes(1);
391+
expect(mockExit).toHaveBeenCalledTimes(1);
392+
expect(mockExit).toHaveBeenCalledWith(0, false);
393+
});
394+
395+
it("Start controller fails due to MQTT and frontend being disabled at the same time", async () => {
396+
settings.set(["mqtt", "enabled"], false);
397+
settings.set(["frontend", "enabled"], false);
398+
await controller.start();
399+
await flushPromises();
400+
expect(mockLogger.error).toHaveBeenCalledWith("MQTT and Frontend are both disabled, process is unable to start, exiting...");
401+
expect(mockExit).toHaveBeenCalledTimes(1);
402+
expect(mockExit).toHaveBeenCalledWith(1, false);
403+
});
404+
384405
it("Start controller and stop with restart", async () => {
385406
await controller.start();
386407
await controller.stop(true);

test/extensions/bridge.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,7 @@ describe("Extension: Bridge", () => {
311311
},
312312
},
313313
mqtt: {
314+
enabled: true,
314315
base_topic: "zigbee2mqtt",
315316
force_disable_retain: false,
316317
include_device_information: false,

test/onboarding.test.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ vi.mock("zigbee2mqtt-windfront", () => ({
8080
const SETTINGS_MINIMAL_DEFAULTS = {
8181
version: settings.CURRENT_VERSION,
8282
mqtt: {
83+
enabled: settings.defaults.mqtt!.enabled,
8384
base_topic: settings.defaults.mqtt!.base_topic,
8485
server: "mqtt://localhost:1883",
8586
},
@@ -133,6 +134,7 @@ const SAMPLE_SETTINGS_INIT = {
133134
const SAMPLE_SETTINGS_SAVE = {
134135
version: settings.CURRENT_VERSION,
135136
mqtt: {
137+
enabled: true,
136138
base_topic: "zigbee2mqtt2",
137139
server: "mqtt://192.168.1.200:1883",
138140
},
@@ -252,7 +254,11 @@ describe("Onboarding", () => {
252254
if (expectWriteMinimal) {
253255
const minimal = process.env.ZIGBEE2MQTT_CONFIG_MQTT_SERVER
254256
? Object.assign({}, SETTINGS_MINIMAL_DEFAULTS, {
255-
mqtt: {server: process.env.ZIGBEE2MQTT_CONFIG_MQTT_SERVER, base_topic: SETTINGS_MINIMAL_DEFAULTS.mqtt.base_topic},
257+
mqtt: {
258+
enabled: SETTINGS_MINIMAL_DEFAULTS.mqtt.enabled,
259+
server: process.env.ZIGBEE2MQTT_CONFIG_MQTT_SERVER,
260+
base_topic: SETTINGS_MINIMAL_DEFAULTS.mqtt.base_topic,
261+
},
256262
})
257263
: SETTINGS_MINIMAL_DEFAULTS;
258264

@@ -978,6 +984,7 @@ describe("Onboarding", () => {
978984
port: SETTINGS_MINIMAL_DEFAULTS.frontend.port,
979985
},
980986
mqtt: {
987+
enabled: SETTINGS_MINIMAL_DEFAULTS.mqtt.enabled,
981988
base_topic: SETTINGS_MINIMAL_DEFAULTS.mqtt.base_topic,
982989
server: process.env.ZIGBEE2MQTT_CONFIG_MQTT_SERVER,
983990
user: "abcd",
@@ -1200,7 +1207,11 @@ describe("Onboarding", () => {
12001207
await expect(p).resolves.toStrictEqual(true);
12011208
expect(data.read()).toStrictEqual(
12021209
Object.assign({}, SAMPLE_SETTINGS_SAVE, {
1203-
mqtt: {server: process.env.ZIGBEE2MQTT_CONFIG_MQTT_SERVER, base_topic: SAMPLE_SETTINGS_SAVE.mqtt.base_topic},
1210+
mqtt: {
1211+
enabled: SAMPLE_SETTINGS_SAVE.mqtt.enabled,
1212+
server: process.env.ZIGBEE2MQTT_CONFIG_MQTT_SERVER,
1213+
base_topic: SAMPLE_SETTINGS_SAVE.mqtt.base_topic,
1214+
},
12041215
}),
12051216
);
12061217
});
@@ -1216,7 +1227,11 @@ describe("Onboarding", () => {
12161227
await expect(p).resolves.toStrictEqual(true);
12171228

12181229
const expected = Object.assign({}, SETTINGS_MINIMAL_DEFAULTS, {
1219-
mqtt: {server: process.env.ZIGBEE2MQTT_CONFIG_MQTT_SERVER, base_topic: SETTINGS_MINIMAL_DEFAULTS.mqtt.base_topic},
1230+
mqtt: {
1231+
enabled: SETTINGS_MINIMAL_DEFAULTS.mqtt.enabled,
1232+
server: process.env.ZIGBEE2MQTT_CONFIG_MQTT_SERVER,
1233+
base_topic: SETTINGS_MINIMAL_DEFAULTS.mqtt.base_topic,
1234+
},
12201235
});
12211236
// @ts-expect-error mock
12221237
delete expected.onboarding;

0 commit comments

Comments
 (0)