Skip to content

Commit c1fc9cd

Browse files
committed
Add MQTT max reconnect delay configuration and improve connection handling
fixes #428, #193
1 parent 8b4d399 commit c1fc9cd

File tree

4 files changed

+78
-2
lines changed

4 files changed

+78
-2
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ The MQTT configuration is specified in the `mqtt` section of `config.yaml`:
7474
| `haLegacy` | `MQTT_HALEGACY` | `boolean` | `false` | The way MqDockerUp creates the update entity, `false` for HA 2024.11+ and `true` for previous versions. |
7575
| `connectTimeout` | `MQTT_CONNECTTIMEOUT` | `int` | `60` | The maximum time, in seconds, to wait for a successful connection to the MQTT broker. |
7676
| `protocolVersion` | `MQTT_PROTOCOLVERSION` | `int` | `5` | The MQTT protocol version to use when connecting to the broker. |
77+
| `maxReconnectDelay` | `MQTT_MAXRECONNECTDELAY` | `int` | `300` | The maximum time, in seconds, between reconnection attempts when disconnected from the MQTT broker. |
7778

7879

7980

config.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ mqtt:
1515
haLegacy: false
1616
connectTimeout: 60
1717
protocolVersion: 5
18+
maxReconnectDelay: 300
1819

1920
# Access Tokens Configuration
2021
accessTokens:

src/index.ts

+75-2
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,40 @@ const _ = require('lodash');
1010
require('source-map-support').install();
1111

1212
const config = ConfigService.getConfig();
13+
const availabilityTopic = `${config.mqtt.topic}/availability`;
14+
1315
const client = mqtt.connect(config.mqtt.connectionUri, {
1416
username: config.mqtt.username,
1517
password: config.mqtt.password,
1618
protocolVersion: ConfigService.autoParseEnvVariable(config.mqtt.protocolVersion),
1719
connectTimeout: ConfigService.autoParseEnvVariable(config.mqtt.connectTimeout),
1820
clientId: config.mqtt.clientId,
21+
reconnectPeriod: 5000,
22+
rejectUnauthorized: false,
23+
will: {
24+
topic: availabilityTopic,
25+
payload: "offline",
26+
qos: 1,
27+
retain: true
28+
}
1929
});
30+
2031
logger.level = ConfigService?.getConfig()?.logs?.level;
2132

33+
// Track connection state
34+
let isConnected = false;
35+
let reconnectCount = 0;
36+
const MAX_RECONNECT_DELAY = ConfigService.autoParseEnvVariable(config.mqtt.maxReconnectDelay) * 1000 || 300000;
37+
2238
export const mqttClient = client;
2339

2440
// Check for new/old containers and publish updates
2541
const checkAndPublishContainerMessages = async (): Promise<void> => {
42+
if (!isConnected) {
43+
logger.warn("MQTT client not connected. Skipping container check.");
44+
return;
45+
}
46+
2647
logger.info("Checking for removed containers...");
2748
const containers = await DockerService.listContainers();
2849
const runningContainerIds = containers.map(container => container.Id);
@@ -69,6 +90,11 @@ const checkAndPublishContainerMessages = async (): Promise<void> => {
6990
};
7091

7192
const checkAndPublishImageUpdateMessages = async (): Promise<void> => {
93+
if (!isConnected) {
94+
logger.warn("MQTT client not connected. Skipping image update check.");
95+
return;
96+
}
97+
7298
logger.info("Checking for image updates...");
7399
await HomeassistantService.publishImageUpdateMessages(client);
74100

@@ -93,6 +119,11 @@ const startImageCheckingInterval = async () => {
93119
// Connected to MQTT broker
94120
client.on('connect', async function () {
95121
logger.info('MQTT client successfully connected');
122+
isConnected = true;
123+
reconnectCount = 0; // Reset reconnect counter on successful connection
124+
125+
// Publish availability as online
126+
await HomeassistantService.publishAvailability(client, true);
96127

97128
if (config?.ignore?.containers == "*") {
98129
logger.warn('Skipping setup of container checking cause all containers is ignored `ignore.containers="*"`.')
@@ -117,6 +148,28 @@ client.on('error', function (err) {
117148
logger.error('MQTT client connection error: ', err);
118149
});
119150

151+
// Handle disconnection
152+
client.on('offline', function () {
153+
logger.warn('MQTT client disconnected');
154+
isConnected = false;
155+
});
156+
157+
// Handle reconnection attempts
158+
client.on('reconnect', function () {
159+
reconnectCount++;
160+
const backoffDelay = Math.min(Math.pow(2, reconnectCount) * 1000, MAX_RECONNECT_DELAY);
161+
logger.info(`Attempting to reconnect to MQTT broker (attempt ${reconnectCount}). Next retry in ${backoffDelay/1000} seconds.`);
162+
163+
// Dynamically adjust reconnect period with exponential backoff
164+
client.options.reconnectPeriod = backoffDelay;
165+
});
166+
167+
// Handle connection close
168+
client.on('close', function () {
169+
logger.warn('MQTT connection closed');
170+
isConnected = false;
171+
});
172+
120173
// Update-Handler for the /update message from MQTT
121174
client.on("message", async (topic: string, message: any) => {
122175
if (topic == `${config.mqtt.topic}/update`) {
@@ -263,18 +316,38 @@ const exitHandler = async (exitCode: number, error?: any) => {
263316
isExiting = true;
264317

265318
try {
266-
await HomeassistantService.publishAvailability(client, false);
319+
logger.info("Shutting down MqDockerUp...");
320+
321+
if (isConnected) {
322+
await HomeassistantService.publishAvailability(client, false);
323+
}
324+
267325
const updatingContainers = DockerService.updatingContainers;
268326

269327
if (updatingContainers.length > 0) {
270328
logger.warn(
271329
`Stopping MqDockerUp while updating containers: ${updatingContainers.join(", ")}`
272330
);
273331
for (const containerId of updatingContainers) {
274-
await HomeassistantService.publishAbortUpdateMessage(containerId, client);
332+
if (isConnected) {
333+
await HomeassistantService.publishAbortUpdateMessage(containerId, client);
334+
}
275335
}
276336
}
277337

338+
logger.info("Closing MQTT connection...");
339+
await new Promise<void>((resolve) => {
340+
client.end(false, {}, () => {
341+
logger.info("MQTT connection closed successfully");
342+
resolve();
343+
});
344+
345+
setTimeout(() => {
346+
logger.warn("MQTT connection close timed out");
347+
resolve();
348+
}, 2000);
349+
});
350+
278351
let message = exitCode === 0 ? `MqDockerUp gracefully stopped` : `MqDockerUp stopped due to an error`;
279352

280353
if (error) {

src/services/ConfigService.ts

+1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export default class ConfigService {
5656
haLegacy: false,
5757
connectTimeout: 60,
5858
protocolVersion: 5,
59+
maxReconnectDelay: 300, // Maximum reconnect delay in seconds
5960
},
6061
accessTokens: {
6162
dockerhub: "",

0 commit comments

Comments
 (0)