homebridge-plugin-utils 1.15.3 → 1.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -1
- package/build/eslint-rules.mjs +1 -0
- package/dist/featureoptions.d.ts +83 -5
- package/dist/featureoptions.js +65 -6
- package/dist/featureoptions.js.map +1 -1
- package/dist/ffmpeg/codecs.d.ts +172 -0
- package/dist/ffmpeg/codecs.js +374 -0
- package/dist/ffmpeg/codecs.js.map +1 -0
- package/dist/ffmpeg/exec.d.ts +108 -0
- package/dist/ffmpeg/exec.js +122 -0
- package/dist/ffmpeg/exec.js.map +1 -0
- package/dist/ffmpeg/index.d.ts +8 -0
- package/dist/ffmpeg/index.js +13 -0
- package/dist/ffmpeg/index.js.map +1 -0
- package/dist/ffmpeg/options.d.ts +345 -0
- package/dist/ffmpeg/options.js +750 -0
- package/dist/ffmpeg/options.js.map +1 -0
- package/dist/ffmpeg/process.d.ts +155 -0
- package/dist/ffmpeg/process.js +344 -0
- package/dist/ffmpeg/process.js.map +1 -0
- package/dist/ffmpeg/record.d.ts +237 -0
- package/dist/ffmpeg/record.js +512 -0
- package/dist/ffmpeg/record.js.map +1 -0
- package/dist/ffmpeg/rtp.d.ts +205 -0
- package/dist/ffmpeg/rtp.js +335 -0
- package/dist/ffmpeg/rtp.js.map +1 -0
- package/dist/ffmpeg/settings.d.ts +6 -0
- package/dist/ffmpeg/settings.js +17 -0
- package/dist/ffmpeg/settings.js.map +1 -0
- package/dist/ffmpeg/stream.d.ts +143 -0
- package/dist/ffmpeg/stream.js +186 -0
- package/dist/ffmpeg/stream.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mqttclient.d.ts +161 -1
- package/dist/mqttclient.js +161 -9
- package/dist/mqttclient.js.map +1 -1
- package/dist/service.d.ts +9 -2
- package/dist/service.js +6 -0
- package/dist/service.js.map +1 -1
- package/dist/ui/featureoptions.js +65 -6
- package/dist/ui/featureoptions.js.map +1 -1
- package/dist/ui/webUi-featureoptions.mjs +4 -4
- package/dist/util.d.ts +203 -12
- package/dist/util.js +95 -12
- package/dist/util.js.map +1 -1
- package/package.json +14 -10
- package/dist/rtp.d.ts +0 -32
- package/dist/rtp.js +0 -178
- package/dist/rtp.js.map +0 -1
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FFmpeg process management and socket handling to support HomeKit livestreaming sessions.
|
|
3
|
+
*
|
|
4
|
+
* This module defines the `FfmpegStreamingProcess` class and related interfaces for orchestrating and monitoring FFmpeg-powered video streams. It manages process
|
|
5
|
+
* lifecycle, handles UDP socket creation for video health monitoring, and enables integration with Homebridge streaming delegates for robust error handling, stream
|
|
6
|
+
* cleanup, and automatic tuning.
|
|
7
|
+
*
|
|
8
|
+
* Key features:
|
|
9
|
+
*
|
|
10
|
+
* - Automated start, monitoring, and termination of HomeKit-compatible FFmpeg video streams.
|
|
11
|
+
* - Integration with Homebridge’s CameraStreamingDelegate for custom error hooks and lifecycle control.
|
|
12
|
+
* - UDP socket creation and management for realtime video stream liveness detection.
|
|
13
|
+
* - Intelligent error handling, including automatic tuning for FFmpeg’s stream probing requirements.
|
|
14
|
+
* - Exposes access to the underlying FFmpeg child process for advanced scenarios.
|
|
15
|
+
*
|
|
16
|
+
* Designed for plugin developers and advanced users who require fine-grained control and diagnostics for HomeKit livestreaming, with seamless Homebridge integration.
|
|
17
|
+
*
|
|
18
|
+
* @module
|
|
19
|
+
*/
|
|
20
|
+
import type { CameraController, CameraStreamingDelegate, StreamRequestCallback } from "homebridge";
|
|
21
|
+
import type { ChildProcessWithoutNullStreams } from "child_process";
|
|
22
|
+
import type { FfmpegOptions } from "./options.js";
|
|
23
|
+
import { FfmpegProcess } from "./process.js";
|
|
24
|
+
import type { Nullable } from "../util.js";
|
|
25
|
+
/**
|
|
26
|
+
* Extension of the Homebridge CameraStreamingDelegate with additional streaming controls and error handling hooks.
|
|
27
|
+
*
|
|
28
|
+
* @property adjustProbeSize - Optional. Invoked to adjust probe size after stream startup errors.
|
|
29
|
+
* @property controller - The Homebridge CameraController instance managing the stream.
|
|
30
|
+
* @property ffmpegErrorCheck - Optional. Returns a user-friendly error message for specific FFmpeg errors, if detected.
|
|
31
|
+
* @property stopStream - Optional. Invoked to force stop a specific stream session by ID.
|
|
32
|
+
*
|
|
33
|
+
* @see CameraController
|
|
34
|
+
* @see CameraStreamingDelegate
|
|
35
|
+
*
|
|
36
|
+
* @category FFmpeg
|
|
37
|
+
*/
|
|
38
|
+
export interface HomebridgeStreamingDelegate extends CameraStreamingDelegate {
|
|
39
|
+
adjustProbeSize?: () => void;
|
|
40
|
+
controller: CameraController;
|
|
41
|
+
ffmpegErrorCheck?: (logEntry: string[]) => string | undefined;
|
|
42
|
+
stopStream?: (sessionId: string) => void;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Provides FFmpeg process management and socket handling to support HomeKit livestreaming sessions.
|
|
46
|
+
*
|
|
47
|
+
* This class extends `FfmpegProcess` to create, monitor, and terminate HomeKit-compatible video streams. Additionally, it invokes delegate hooks for error processing and
|
|
48
|
+
* stream lifecycle management.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
*
|
|
52
|
+
* ```ts
|
|
53
|
+
* const streamingDelegate: HomebridgeStreamingDelegate = {
|
|
54
|
+
*
|
|
55
|
+
* controller,
|
|
56
|
+
* stopStream: (sessionId) => { ... } // End-of-session cleanup code.
|
|
57
|
+
* };
|
|
58
|
+
*
|
|
59
|
+
* const process = new FfmpegStreamingProcess(
|
|
60
|
+
*
|
|
61
|
+
* streamingDelegate,
|
|
62
|
+
* sessionId,
|
|
63
|
+
* ffmpegOptions,
|
|
64
|
+
* commandLineArgs,
|
|
65
|
+
* { addressVersion: "ipv4", port: 5000 }
|
|
66
|
+
* );
|
|
67
|
+
* ```
|
|
68
|
+
*
|
|
69
|
+
* @see HomebridgeStreamingDelegate
|
|
70
|
+
* @see FfmpegProcess
|
|
71
|
+
*
|
|
72
|
+
* @category FFmpeg
|
|
73
|
+
*/
|
|
74
|
+
export declare class FfmpegStreamingProcess extends FfmpegProcess {
|
|
75
|
+
private delegate;
|
|
76
|
+
/**
|
|
77
|
+
* The unique session identifier for this streaming process.
|
|
78
|
+
*/
|
|
79
|
+
private sessionId;
|
|
80
|
+
/**
|
|
81
|
+
* The timeout reference used to monitor UDP stream health.
|
|
82
|
+
*/
|
|
83
|
+
private streamTimeout?;
|
|
84
|
+
/**
|
|
85
|
+
* Constructs a new FFmpeg streaming process for a HomeKit session.
|
|
86
|
+
*
|
|
87
|
+
* Sets up required delegate hooks, creates UDP return sockets if needed, and starts the FFmpeg process. Automatically handles FFmpeg process errors and cleans up on
|
|
88
|
+
* failures.
|
|
89
|
+
*
|
|
90
|
+
* @param delegate - The Homebridge streaming delegate for this session.
|
|
91
|
+
* @param sessionId - The HomeKit session identifier for this stream.
|
|
92
|
+
* @param ffmpegOptions - The FFmpeg configuration options.
|
|
93
|
+
* @param commandLineArgs - FFmpeg command-line arguments.
|
|
94
|
+
* @param returnPort - Optional. UDP port info for the return stream (used except for two-way audio).
|
|
95
|
+
* @param callback - Optional. Callback invoked when the stream is ready or errors occur.
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
*
|
|
99
|
+
* ```ts
|
|
100
|
+
* const process = new FfmpegStreamingProcess(delegate, sessionId, ffmpegOptions, commandLineArgs, { addressVersion: "ipv6", port: 6000 });
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
constructor(delegate: HomebridgeStreamingDelegate, sessionId: string, ffmpegOptions: FfmpegOptions, commandLineArgs: string[], returnPort?: {
|
|
104
|
+
addressVersion: string;
|
|
105
|
+
port: number;
|
|
106
|
+
}, callback?: StreamRequestCallback);
|
|
107
|
+
/**
|
|
108
|
+
* Creates and binds a UDP socket for monitoring the health of the outgoing video stream.
|
|
109
|
+
*
|
|
110
|
+
* Listens for UDP "message" events, sets and clears timeouts, and handles error/cleanup scenarios. If no messages are received within 5 seconds, forcibly stops the
|
|
111
|
+
* stream and informs the delegate.
|
|
112
|
+
*
|
|
113
|
+
* @param portInfo - Object containing the address version ("ipv4" or "ipv6") and port number.
|
|
114
|
+
*/
|
|
115
|
+
private createSocket;
|
|
116
|
+
/**
|
|
117
|
+
* Returns the underlying FFmpeg child process, or null if the process is not running.
|
|
118
|
+
*
|
|
119
|
+
* @returns The current FFmpeg process, or `null` if not running.
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
*
|
|
123
|
+
* ```ts
|
|
124
|
+
* const ffmpeg = process.ffmpegProcess;
|
|
125
|
+
*
|
|
126
|
+
* if(ffmpeg) {
|
|
127
|
+
*
|
|
128
|
+
* // Interact directly with the child process if necessary.
|
|
129
|
+
* }
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
132
|
+
get ffmpegProcess(): Nullable<ChildProcessWithoutNullStreams>;
|
|
133
|
+
/**
|
|
134
|
+
* Handle and logs FFmpeg process errors.
|
|
135
|
+
*
|
|
136
|
+
* If a known error condition is detected by the delegate, logs the custom message and returns. For "not enough frames to estimate rate; consider increasing probesize"
|
|
137
|
+
* errors, invokes the delegate's `adjustProbeSize` hook for automatic tuning. Otherwise, falls back to the parent class's logging.
|
|
138
|
+
*
|
|
139
|
+
* @param exitCode - The exit code from FFmpeg.
|
|
140
|
+
* @param signal - The signal, if any, used to terminate the process.
|
|
141
|
+
*/
|
|
142
|
+
protected logFfmpegError(exitCode: number, signal: NodeJS.Signals): void;
|
|
143
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/* Copyright(C) 2017-2025, HJD (https://github.com/hjdhjd). All rights reserved.
|
|
2
|
+
*
|
|
3
|
+
* ffmpeg/stream.ts: Provide FFmpeg process control to support HomeKit livestreaming.
|
|
4
|
+
*/
|
|
5
|
+
import { FfmpegProcess } from "./process.js";
|
|
6
|
+
import { createSocket } from "node:dgram";
|
|
7
|
+
/**
|
|
8
|
+
* Provides FFmpeg process management and socket handling to support HomeKit livestreaming sessions.
|
|
9
|
+
*
|
|
10
|
+
* This class extends `FfmpegProcess` to create, monitor, and terminate HomeKit-compatible video streams. Additionally, it invokes delegate hooks for error processing and
|
|
11
|
+
* stream lifecycle management.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
*
|
|
15
|
+
* ```ts
|
|
16
|
+
* const streamingDelegate: HomebridgeStreamingDelegate = {
|
|
17
|
+
*
|
|
18
|
+
* controller,
|
|
19
|
+
* stopStream: (sessionId) => { ... } // End-of-session cleanup code.
|
|
20
|
+
* };
|
|
21
|
+
*
|
|
22
|
+
* const process = new FfmpegStreamingProcess(
|
|
23
|
+
*
|
|
24
|
+
* streamingDelegate,
|
|
25
|
+
* sessionId,
|
|
26
|
+
* ffmpegOptions,
|
|
27
|
+
* commandLineArgs,
|
|
28
|
+
* { addressVersion: "ipv4", port: 5000 }
|
|
29
|
+
* );
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @see HomebridgeStreamingDelegate
|
|
33
|
+
* @see FfmpegProcess
|
|
34
|
+
*
|
|
35
|
+
* @category FFmpeg
|
|
36
|
+
*/
|
|
37
|
+
export class FfmpegStreamingProcess extends FfmpegProcess {
|
|
38
|
+
/*
|
|
39
|
+
* The streaming delegate instance responsible for handling stream events and errors.
|
|
40
|
+
*/
|
|
41
|
+
delegate;
|
|
42
|
+
/**
|
|
43
|
+
* The unique session identifier for this streaming process.
|
|
44
|
+
*/
|
|
45
|
+
sessionId;
|
|
46
|
+
/**
|
|
47
|
+
* The timeout reference used to monitor UDP stream health.
|
|
48
|
+
*/
|
|
49
|
+
streamTimeout;
|
|
50
|
+
/**
|
|
51
|
+
* Constructs a new FFmpeg streaming process for a HomeKit session.
|
|
52
|
+
*
|
|
53
|
+
* Sets up required delegate hooks, creates UDP return sockets if needed, and starts the FFmpeg process. Automatically handles FFmpeg process errors and cleans up on
|
|
54
|
+
* failures.
|
|
55
|
+
*
|
|
56
|
+
* @param delegate - The Homebridge streaming delegate for this session.
|
|
57
|
+
* @param sessionId - The HomeKit session identifier for this stream.
|
|
58
|
+
* @param ffmpegOptions - The FFmpeg configuration options.
|
|
59
|
+
* @param commandLineArgs - FFmpeg command-line arguments.
|
|
60
|
+
* @param returnPort - Optional. UDP port info for the return stream (used except for two-way audio).
|
|
61
|
+
* @param callback - Optional. Callback invoked when the stream is ready or errors occur.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
*
|
|
65
|
+
* ```ts
|
|
66
|
+
* const process = new FfmpegStreamingProcess(delegate, sessionId, ffmpegOptions, commandLineArgs, { addressVersion: "ipv6", port: 6000 });
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
constructor(delegate, sessionId, ffmpegOptions, commandLineArgs, returnPort, callback) {
|
|
70
|
+
// Initialize our parent.
|
|
71
|
+
super(ffmpegOptions);
|
|
72
|
+
this.delegate = delegate;
|
|
73
|
+
this.delegate.adjustProbeSize ??= () => { };
|
|
74
|
+
this.delegate.ffmpegErrorCheck ??= () => undefined;
|
|
75
|
+
this.delegate.stopStream ??= () => { };
|
|
76
|
+
this.sessionId = sessionId;
|
|
77
|
+
// Create the return port for FFmpeg, if requested to do so. The only time we don't do this is when we're standing up
|
|
78
|
+
// a two-way audio stream - in that case, the audio work is done through RtpSplitter and not here.
|
|
79
|
+
if (returnPort) {
|
|
80
|
+
this.createSocket(returnPort);
|
|
81
|
+
}
|
|
82
|
+
// Start it up, with appropriate error handling.
|
|
83
|
+
this.start(commandLineArgs, callback, (errorMessage) => {
|
|
84
|
+
// Stop the stream.
|
|
85
|
+
this.delegate.stopStream?.(this.sessionId);
|
|
86
|
+
// Let homebridge know what happened and stop the stream if we've already started.
|
|
87
|
+
if (!this.isStarted && this.callback) {
|
|
88
|
+
this.callback(new Error(errorMessage));
|
|
89
|
+
this.callback = null;
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
// Tell Homebridge to forcibly stop the streaming session.
|
|
93
|
+
this.delegate.controller.forceStopStreamingSession(this.sessionId);
|
|
94
|
+
this.delegate.stopStream?.(this.sessionId);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Creates and binds a UDP socket for monitoring the health of the outgoing video stream.
|
|
99
|
+
*
|
|
100
|
+
* Listens for UDP "message" events, sets and clears timeouts, and handles error/cleanup scenarios. If no messages are received within 5 seconds, forcibly stops the
|
|
101
|
+
* stream and informs the delegate.
|
|
102
|
+
*
|
|
103
|
+
* @param portInfo - Object containing the address version ("ipv4" or "ipv6") and port number.
|
|
104
|
+
*/
|
|
105
|
+
createSocket(portInfo) {
|
|
106
|
+
let errorListener;
|
|
107
|
+
let messageListener;
|
|
108
|
+
const socket = createSocket(portInfo.addressVersion === "ipv6" ? "udp6" : "udp4");
|
|
109
|
+
// Cleanup after ourselves when the socket closes.
|
|
110
|
+
socket.once("close", () => {
|
|
111
|
+
if (this.streamTimeout) {
|
|
112
|
+
clearTimeout(this.streamTimeout);
|
|
113
|
+
}
|
|
114
|
+
socket.off("error", errorListener);
|
|
115
|
+
socket.off("message", messageListener);
|
|
116
|
+
});
|
|
117
|
+
// Handle potential network errors.
|
|
118
|
+
socket.on("error", errorListener = (error) => {
|
|
119
|
+
this.log.error("Socket error: %s.", error.name);
|
|
120
|
+
void this.delegate.stopStream?.(this.sessionId);
|
|
121
|
+
});
|
|
122
|
+
// Manage our video streams in case we haven't received a stop request, but we're in fact dead zombies.
|
|
123
|
+
socket.on("message", messageListener = () => {
|
|
124
|
+
// Clear our last canary.
|
|
125
|
+
if (this.streamTimeout) {
|
|
126
|
+
clearTimeout(this.streamTimeout);
|
|
127
|
+
}
|
|
128
|
+
// Set our new canary.
|
|
129
|
+
this.streamTimeout = setTimeout(() => {
|
|
130
|
+
this.log.debug("Video stream appears to be inactive for 5 seconds. Stopping stream.");
|
|
131
|
+
this.delegate.controller.forceStopStreamingSession(this.sessionId);
|
|
132
|
+
void this.delegate.stopStream?.(this.sessionId);
|
|
133
|
+
}, 5000);
|
|
134
|
+
});
|
|
135
|
+
// Bind to the port we're opening.
|
|
136
|
+
socket.bind(portInfo.port, (portInfo.addressVersion === "ipv6") ? "::1" : "127.0.0.1");
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Returns the underlying FFmpeg child process, or null if the process is not running.
|
|
140
|
+
*
|
|
141
|
+
* @returns The current FFmpeg process, or `null` if not running.
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
*
|
|
145
|
+
* ```ts
|
|
146
|
+
* const ffmpeg = process.ffmpegProcess;
|
|
147
|
+
*
|
|
148
|
+
* if(ffmpeg) {
|
|
149
|
+
*
|
|
150
|
+
* // Interact directly with the child process if necessary.
|
|
151
|
+
* }
|
|
152
|
+
* ```
|
|
153
|
+
*/
|
|
154
|
+
get ffmpegProcess() {
|
|
155
|
+
return this.process;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Handle and logs FFmpeg process errors.
|
|
159
|
+
*
|
|
160
|
+
* If a known error condition is detected by the delegate, logs the custom message and returns. For "not enough frames to estimate rate; consider increasing probesize"
|
|
161
|
+
* errors, invokes the delegate's `adjustProbeSize` hook for automatic tuning. Otherwise, falls back to the parent class's logging.
|
|
162
|
+
*
|
|
163
|
+
* @param exitCode - The exit code from FFmpeg.
|
|
164
|
+
* @param signal - The signal, if any, used to terminate the process.
|
|
165
|
+
*/
|
|
166
|
+
logFfmpegError(exitCode, signal) {
|
|
167
|
+
// We want to process known streaming-related errors due to the performance and latency tweaks we've made to the FFmpeg command line. In some cases we may inform the
|
|
168
|
+
// user and take no action, in others, we tune our own internal parameters.
|
|
169
|
+
// Process any specific errors our caller is interested in.
|
|
170
|
+
const errTest = this.delegate.ffmpegErrorCheck?.(this.stderrLog);
|
|
171
|
+
if (errTest) {
|
|
172
|
+
this.log.error(errTest);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
// Test for probesize errors.
|
|
176
|
+
const probesizeRegex = new RegExp("not enough frames to estimate rate; consider increasing probesize");
|
|
177
|
+
if (this.stderrLog.some(logEntry => probesizeRegex.test(logEntry))) {
|
|
178
|
+
// Let the streaming delegate know to adjust it's parameters for the next run and inform the user.
|
|
179
|
+
this.delegate.adjustProbeSize?.();
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
// Otherwise, revert to our default logging in our parent.
|
|
183
|
+
super.logFfmpegError(exitCode, signal);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
//# sourceMappingURL=stream.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream.js","sourceRoot":"","sources":["../../src/ffmpeg/stream.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,cAAc,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,mBAAmB,CAAC"}
|
package/dist/mqttclient.d.ts
CHANGED
|
@@ -1,4 +1,42 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* MQTT connectivity and topic management for Homebridge plugins.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
import type { HomebridgePluginLogging } from "./util.js";
|
|
7
|
+
/**
|
|
8
|
+
* MQTT connectivity and topic management class for Homebridge plugins.
|
|
9
|
+
*
|
|
10
|
+
* This class manages connection, publishing, subscription, and message handling for an MQTT broker, and provides convenience methods for Homebridge accessories to
|
|
11
|
+
* interact with MQTT topics using a standard topic prefix.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
*
|
|
15
|
+
* ```ts
|
|
16
|
+
* const mqtt = new MqttClient("mqtt://localhost:1883", "homebridge", log);
|
|
17
|
+
*
|
|
18
|
+
* // Publish a message to a topic.
|
|
19
|
+
* mqtt.publish("device1", "status", "on");
|
|
20
|
+
*
|
|
21
|
+
* // Subscribe to a topic.
|
|
22
|
+
* mqtt.subscribe("device1", "status", (msg) => {
|
|
23
|
+
*
|
|
24
|
+
* console.log(msg.toString());
|
|
25
|
+
* });
|
|
26
|
+
*
|
|
27
|
+
* // Subscribe to a 'get' topic and automatically publish a value in response.
|
|
28
|
+
* mqtt.subscribeGet("device1", "temperature", "Temperature", () => "21.5");
|
|
29
|
+
*
|
|
30
|
+
* // Subscribe to a 'set' topic and handle value changes.
|
|
31
|
+
* mqtt.subscribeSet("device1", "switch", "Switch", (value) => {
|
|
32
|
+
*
|
|
33
|
+
* console.log("Switch set to", value);
|
|
34
|
+
* });
|
|
35
|
+
*
|
|
36
|
+
* // Unsubscribe from a topic.
|
|
37
|
+
* mqtt.unsubscribe("device1", "status");
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
2
40
|
export declare class MqttClient {
|
|
3
41
|
private brokerUrl;
|
|
4
42
|
private isConnected;
|
|
@@ -7,12 +45,134 @@ export declare class MqttClient {
|
|
|
7
45
|
private mqtt;
|
|
8
46
|
private subscriptions;
|
|
9
47
|
private topicPrefix;
|
|
48
|
+
/**
|
|
49
|
+
* Creates a new MQTT client for connecting to a broker and managing topics with a given prefix.
|
|
50
|
+
*
|
|
51
|
+
* @param brokerUrl - The MQTT broker URL (e.g., "mqtt://localhost:1883").
|
|
52
|
+
* @param topicPrefix - Prefix to use for all MQTT topics (e.g., "homebridge").
|
|
53
|
+
* @param log - Logger for debug and info messages.
|
|
54
|
+
* @param reconnectInterval - Optional. Interval (in seconds) to wait between reconnection attempts. Defaults to 60 seconds.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
*
|
|
58
|
+
* ```ts
|
|
59
|
+
* const mqtt = new MqttClient("mqtt://localhost", "homebridge", log);
|
|
60
|
+
* ```
|
|
61
|
+
*
|
|
62
|
+
* @remarks URL must conform to formats supported by {@link https://github.com/mqttjs/MQTT.js | MQTT.js}.
|
|
63
|
+
*/
|
|
10
64
|
constructor(brokerUrl: string, topicPrefix: string, log: HomebridgePluginLogging, reconnectInterval?: number);
|
|
65
|
+
/**
|
|
66
|
+
* Initializes and connects the MQTT client to the broker, setting up event handlers for connection, messages, and errors.
|
|
67
|
+
*
|
|
68
|
+
* Catches invalid broker URLs and logs errors. Handles all major MQTT client events internally.
|
|
69
|
+
*/
|
|
11
70
|
private configure;
|
|
71
|
+
/**
|
|
72
|
+
* Publishes a message to a topic for a specific device.
|
|
73
|
+
*
|
|
74
|
+
* Expands the topic using the topic prefix and device ID, then publishes the provided message string.
|
|
75
|
+
*
|
|
76
|
+
* @param id - The device or accessory identifier.
|
|
77
|
+
* @param topic - The topic name to publish to.
|
|
78
|
+
* @param message - The message payload to publish.
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
*
|
|
82
|
+
* ```ts
|
|
83
|
+
* mqtt.publish("device1", "status", "on");
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
12
86
|
publish(id: string, topic: string, message: string): void;
|
|
87
|
+
/**
|
|
88
|
+
* Subscribes to a topic for a specific device and registers a handler for incoming messages.
|
|
89
|
+
*
|
|
90
|
+
* The topic is expanded using the prefix and device ID, and the callback will be called for each message received.
|
|
91
|
+
*
|
|
92
|
+
* @param id - The device or accessory identifier.
|
|
93
|
+
* @param topic - The topic name to subscribe to.
|
|
94
|
+
* @param callback - Handler function called with the message buffer.
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
*
|
|
98
|
+
* ```ts
|
|
99
|
+
* mqtt.subscribe("device1", "status", (msg) => {
|
|
100
|
+
*
|
|
101
|
+
* console.log(msg.toString());
|
|
102
|
+
* });
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
13
105
|
subscribe(id: string, topic: string, callback: (cbBuffer: Buffer) => void): void;
|
|
106
|
+
/**
|
|
107
|
+
* Subscribes to a '<topic>/get' topic and publishes a value in response to "true" messages.
|
|
108
|
+
*
|
|
109
|
+
* When a message "true" is received on the '<topic>/get' topic, this method will publish the result of `getValue()` on the main topic. The log will record each status
|
|
110
|
+
* publication event.
|
|
111
|
+
*
|
|
112
|
+
* @param id - The device or accessory identifier.
|
|
113
|
+
* @param topic - The topic name to use.
|
|
114
|
+
* @param type - A human-readable label for log messages (e.g., "Temperature").
|
|
115
|
+
* @param getValue - Function to get the value to publish as a string.
|
|
116
|
+
* @param log - Optional logger for status output. Defaults to the class logger.
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
*
|
|
120
|
+
* ```ts
|
|
121
|
+
* mqtt.subscribeGet("device1", "temperature", "Temperature", () => "21.5");
|
|
122
|
+
* ```
|
|
123
|
+
*/
|
|
14
124
|
subscribeGet(id: string, topic: string, type: string, getValue: () => string, log?: HomebridgePluginLogging): void;
|
|
125
|
+
/**
|
|
126
|
+
* Subscribes to a '<topic>/set' topic and calls a setter when a message is received.
|
|
127
|
+
*
|
|
128
|
+
* The `setValue` function is called with both a normalized value and the raw string. Handles both synchronous and promise-based setters. Logs when set messages are
|
|
129
|
+
* received and when errors occur.
|
|
130
|
+
*
|
|
131
|
+
* @param id - The device or accessory identifier.
|
|
132
|
+
* @param topic - The topic name to use.
|
|
133
|
+
* @param type - A human-readable label for log messages (e.g., "Switch").
|
|
134
|
+
* @param setValue - Function to call when a value is set. Can be synchronous or return a Promise.
|
|
135
|
+
* @param log - Optional logger for status output. Defaults to the class logger.
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
*
|
|
139
|
+
* ```ts
|
|
140
|
+
* mqtt.subscribeSet("device1", "switch", "Switch", (value) => {
|
|
141
|
+
*
|
|
142
|
+
* console.log("Switch set to", value);
|
|
143
|
+
* });
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
15
146
|
subscribeSet(id: string, topic: string, type: string, setValue: (value: string, rawValue: string) => Promise<void> | void, log?: HomebridgePluginLogging): void;
|
|
147
|
+
/**
|
|
148
|
+
* Unsubscribes from a topic for a specific device, removing its message handler.
|
|
149
|
+
*
|
|
150
|
+
* @param id - The device or accessory identifier.
|
|
151
|
+
* @param topic - The topic name to unsubscribe from.
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
*
|
|
155
|
+
* ```ts
|
|
156
|
+
* mqtt.unsubscribe("device1", "status");
|
|
157
|
+
* ```
|
|
158
|
+
*/
|
|
16
159
|
unsubscribe(id: string, topic: string): void;
|
|
160
|
+
/**
|
|
161
|
+
* Expands a topic string into a fully-formed topic path including the prefix and device ID.
|
|
162
|
+
*
|
|
163
|
+
* Returns `null` if the device ID is missing or empty.
|
|
164
|
+
*
|
|
165
|
+
* @param id - The device or accessory identifier.
|
|
166
|
+
* @param topic - The topic name to expand.
|
|
167
|
+
*
|
|
168
|
+
* @returns The expanded topic string, or `null` if the ID is missing.
|
|
169
|
+
*
|
|
170
|
+
* @example
|
|
171
|
+
*
|
|
172
|
+
* ```ts
|
|
173
|
+
* const topic = mqtt['expandTopic']("device1", "status");
|
|
174
|
+
* // topic = "homebridge/device1/status"
|
|
175
|
+
* ```
|
|
176
|
+
*/
|
|
17
177
|
private expandTopic;
|
|
18
178
|
}
|