Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions data/configuration.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ frontend:

# MQTT settings
mqtt:
# Enable MQTT connection
enabled: true
# MQTT base topic for zigbee2mqtt MQTT messages
base_topic: zigbee2mqtt
# MQTT server URL
Expand Down
25 changes: 18 additions & 7 deletions lib/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,12 +165,21 @@ export class Controller {
logger.info(`Currently ${deviceCount} devices are joined.`);

// MQTT
try {
await this.mqtt.connect();
} catch (error) {
logger.error(`MQTT failed to connect, exiting... (${(error as Error).message})`);
await this.zigbee.stop();
return await this.exit(1);
if (settings.get().mqtt.enabled) {
try {
await this.mqtt.connect();
} catch (error) {
logger.error(`MQTT failed to connect, exiting... (${(error as Error).message})`);
await this.zigbee.stop();
return await this.exit(1);
}
} else {
if (!settings.get().frontend.enabled) {
logger.error("MQTT and Frontend are both disabled, process is unable to start, exiting...");
await this.zigbee.stop();
return await this.exit(1);
}
logger.info("MQTT is disabled, skipping connection");
}

if (abortSignal.aborted) {
Expand Down Expand Up @@ -360,7 +369,9 @@ export class Controller {

// Wrap-up
this.state.stop();
await this.mqtt.disconnect();
if (settings.get().mqtt.enabled) {
await this.mqtt.disconnect();
}

try {
await this.zigbee.stop();
Expand Down
10 changes: 8 additions & 2 deletions lib/mqtt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,19 @@ export default class Mqtt {
public retainedMessages: {[s: string]: {topic: string; payload: string; options: MqttPublishOptions}} = {};

get info() {
if (!settings.get().mqtt.enabled) {
return {version: undefined, server: ""};
}
return {
version: this.client.options.protocolVersion,
server: `${this.client.options.protocol}://${this.client.options.host}:${this.client.options.port}`,
};
}

get stats() {
if (!settings.get().mqtt.enabled) {
return {connected: false, queued: 0};
}
return {
connected: this.isConnected(),
queued: this.client.queue.length,
Expand Down Expand Up @@ -140,7 +146,7 @@ export default class Mqtt {

// Set timer at interval to check if connected to MQTT server.
this.connectionTimer = setInterval(() => {
if (!this.isConnected()) {
if (settings.get().mqtt.enabled && !this.isConnected()) {
logger.error("Not connected to MQTT server!");
}
}, utils.seconds(10));
Expand Down Expand Up @@ -223,7 +229,7 @@ export default class Mqtt {
this.eventBus.emitMQTTMessagePublished({topic, payload, options: finalOptions});

if (!this.isConnected()) {
if (!finalOptions.skipLog) {
if (!finalOptions.skipLog && settings.get().mqtt.enabled) {
logger.error("Not connected to MQTT server!");
logger.error(`Cannot send message: topic: '${topic}', payload: '${payload}`);
}
Expand Down
1 change: 1 addition & 0 deletions lib/types/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ export interface Zigbee2MQTTSettings {
passive: {timeout: number};
};
mqtt: {
enabled: boolean;
base_topic: string;
include_device_information: boolean;
force_disable_retain: boolean;
Expand Down
10 changes: 9 additions & 1 deletion lib/util/settings.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,13 @@
"type": "object",
"title": "MQTT",
"properties": {
"enabled": {
"type": "boolean",
"title": "Enabled",
"description": "Enable MQTT connection. When false, the application runs without connecting to any MQTT broker.",
"default": true,
"requiresRestart": true
},
"base_topic": {
"type": "string",
"title": "Base topic",
Expand All @@ -130,6 +137,7 @@
"title": "MQTT server",
"requiresRestart": true,
"description": "MQTT server URL (use mqtts:// for SSL/TLS connection)",
"default": "mqtt://localhost",
"examples": ["mqtt://localhost:1883"]
},
"keepalive": {
Expand Down Expand Up @@ -219,7 +227,7 @@
"maximum": 268435456
}
},
"required": ["server"]
"required": []
},
"serial": {
"type": "object",
Expand Down
3 changes: 3 additions & 0 deletions lib/util/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ export const defaults = {
base_url: "/",
},
mqtt: {
enabled: true,
base_topic: "zigbee2mqtt",
server: "mqtt://localhost",
include_device_information: false,
force_disable_retain: false,
// 1MB = roughly 3.5KB per device * 300 devices for `/bridge/devices`
Expand Down Expand Up @@ -158,6 +160,7 @@ export function writeMinimalDefaults(): void {
const minimal = {
version: CURRENT_VERSION,
mqtt: {
enabled: true,
base_topic: defaults.mqtt.base_topic,
server: "mqtt://localhost:1883",
},
Expand Down
21 changes: 21 additions & 0 deletions test/controller.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,27 @@ describe("Controller", () => {
expect(settings.get().onboarding).toStrictEqual(true);
});

it("Start controller with MQTT disabled should be successful", async () => {
settings.set(["mqtt", "enabled"], false);
settings.set(["frontend", "enabled"], true);
await controller.start();
await flushPromises();
await controller.stop();
expect(mockZHController.stop).toHaveBeenCalledTimes(1);
expect(mockExit).toHaveBeenCalledTimes(1);
expect(mockExit).toHaveBeenCalledWith(0, false);
});

it("Start controller fails due to MQTT and frontend being disabled at the same time", async () => {
settings.set(["mqtt", "enabled"], false);
settings.set(["frontend", "enabled"], false);
await controller.start();
await flushPromises();
expect(mockLogger.error).toHaveBeenCalledWith("MQTT and Frontend are both disabled, process is unable to start, exiting...");
expect(mockExit).toHaveBeenCalledTimes(1);
expect(mockExit).toHaveBeenCalledWith(1, false);
});

it("Start controller and stop with restart", async () => {
await controller.start();
await controller.stop(true);
Expand Down
1 change: 1 addition & 0 deletions test/extensions/bridge.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,7 @@ describe("Extension: Bridge", () => {
},
},
mqtt: {
enabled: true,
base_topic: "zigbee2mqtt",
force_disable_retain: false,
include_device_information: false,
Expand Down
6 changes: 6 additions & 0 deletions test/extensions/health.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -312,4 +312,10 @@ describe("Extension: Health", () => {
});
expect(calls[0][2]).toStrictEqual({retain: true, qos: 1});
});

it("reports mqtt stats as disconnected when MQTT is disabled", () => {
settings.set(["mqtt", "enabled"], false);

expect(controller.mqtt.stats).toStrictEqual({connected: false, queued: 0});
});
});
21 changes: 18 additions & 3 deletions test/onboarding.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ vi.mock("zigbee2mqtt-windfront", () => ({
const SETTINGS_MINIMAL_DEFAULTS = {
version: settings.CURRENT_VERSION,
mqtt: {
enabled: settings.defaults.mqtt!.enabled,
base_topic: settings.defaults.mqtt!.base_topic,
server: "mqtt://localhost:1883",
},
Expand Down Expand Up @@ -133,6 +134,7 @@ const SAMPLE_SETTINGS_INIT = {
const SAMPLE_SETTINGS_SAVE = {
version: settings.CURRENT_VERSION,
mqtt: {
enabled: true,
base_topic: "zigbee2mqtt2",
server: "mqtt://192.168.1.200:1883",
},
Expand Down Expand Up @@ -252,7 +254,11 @@ describe("Onboarding", () => {
if (expectWriteMinimal) {
const minimal = process.env.ZIGBEE2MQTT_CONFIG_MQTT_SERVER
? Object.assign({}, SETTINGS_MINIMAL_DEFAULTS, {
mqtt: {server: process.env.ZIGBEE2MQTT_CONFIG_MQTT_SERVER, base_topic: SETTINGS_MINIMAL_DEFAULTS.mqtt.base_topic},
mqtt: {
enabled: SETTINGS_MINIMAL_DEFAULTS.mqtt.enabled,
server: process.env.ZIGBEE2MQTT_CONFIG_MQTT_SERVER,
base_topic: SETTINGS_MINIMAL_DEFAULTS.mqtt.base_topic,
},
})
: SETTINGS_MINIMAL_DEFAULTS;

Expand Down Expand Up @@ -978,6 +984,7 @@ describe("Onboarding", () => {
port: SETTINGS_MINIMAL_DEFAULTS.frontend.port,
},
mqtt: {
enabled: SETTINGS_MINIMAL_DEFAULTS.mqtt.enabled,
base_topic: SETTINGS_MINIMAL_DEFAULTS.mqtt.base_topic,
server: process.env.ZIGBEE2MQTT_CONFIG_MQTT_SERVER,
user: "abcd",
Expand Down Expand Up @@ -1200,7 +1207,11 @@ describe("Onboarding", () => {
await expect(p).resolves.toStrictEqual(true);
expect(data.read()).toStrictEqual(
Object.assign({}, SAMPLE_SETTINGS_SAVE, {
mqtt: {server: process.env.ZIGBEE2MQTT_CONFIG_MQTT_SERVER, base_topic: SAMPLE_SETTINGS_SAVE.mqtt.base_topic},
mqtt: {
enabled: SAMPLE_SETTINGS_SAVE.mqtt.enabled,
server: process.env.ZIGBEE2MQTT_CONFIG_MQTT_SERVER,
base_topic: SAMPLE_SETTINGS_SAVE.mqtt.base_topic,
},
}),
);
});
Expand All @@ -1216,7 +1227,11 @@ describe("Onboarding", () => {
await expect(p).resolves.toStrictEqual(true);

const expected = Object.assign({}, SETTINGS_MINIMAL_DEFAULTS, {
mqtt: {server: process.env.ZIGBEE2MQTT_CONFIG_MQTT_SERVER, base_topic: SETTINGS_MINIMAL_DEFAULTS.mqtt.base_topic},
mqtt: {
enabled: SETTINGS_MINIMAL_DEFAULTS.mqtt.enabled,
server: process.env.ZIGBEE2MQTT_CONFIG_MQTT_SERVER,
base_topic: SETTINGS_MINIMAL_DEFAULTS.mqtt.base_topic,
},
});
// @ts-expect-error mock
delete expected.onboarding;
Expand Down
11 changes: 11 additions & 0 deletions test/settings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ describe("Settings", () => {
enabled: true,
},
mqtt: {
enabled: true,
base_topic: "zigbee2mqtt",
server: "mqtt://localhost",
},
Expand Down Expand Up @@ -309,6 +310,7 @@ describe("Settings", () => {
write(configurationFile, contentConfiguration);

const expected = {
enabled: true,
base_topic: "zigbee2mqtt",
include_device_information: false,
maximum_packet_size: 1048576,
Expand Down Expand Up @@ -357,6 +359,7 @@ describe("Settings", () => {
write(configurationFile, contentConfiguration);

const expected = {
enabled: true,
base_topic: "zigbee2mqtt",
include_device_information: false,
maximum_packet_size: 1048576,
Expand Down Expand Up @@ -678,6 +681,7 @@ describe("Settings", () => {
},
};
const expected = {
enabled: true,
base_topic: "zigbee2mqtt",
include_device_information: false,
maximum_packet_size: 1048576,
Expand All @@ -692,6 +696,13 @@ describe("Settings", () => {
expect(settings.get().mqtt).toStrictEqual(expected);
});

it("Should disable MQTT when mqtt.enabled is false", () => {
write(configurationFile, {
mqtt: {enabled: false, server: "mqtt://localhost"},
});
expect(settings.get().mqtt.enabled).toStrictEqual(false);
});

it("Should add groups with specific ID", () => {
write(configurationFile, {});

Expand Down