@turquoisebay/mqtt 0.1.13
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/.env.example +10 -0
- package/CHANGELOG.md +103 -0
- package/README.md +157 -0
- package/config.example.json +18 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/src/__mocks__/mqtt.d.ts +35 -0
- package/dist/src/__mocks__/mqtt.d.ts.map +1 -0
- package/dist/src/__mocks__/mqtt.js +61 -0
- package/dist/src/channel.d.ts +10 -0
- package/dist/src/channel.d.ts.map +1 -0
- package/dist/src/channel.js +235 -0
- package/dist/src/client.d.ts +23 -0
- package/dist/src/client.d.ts.map +1 -0
- package/dist/src/client.js +192 -0
- package/dist/src/config-schema.d.ts +73 -0
- package/dist/src/config-schema.d.ts.map +1 -0
- package/dist/src/config-schema.js +40 -0
- package/dist/src/env.d.ts +18 -0
- package/dist/src/env.d.ts.map +1 -0
- package/dist/src/env.js +34 -0
- package/dist/src/onboarding.d.ts +73 -0
- package/dist/src/onboarding.d.ts.map +1 -0
- package/dist/src/onboarding.js +138 -0
- package/dist/src/runtime.d.ts +4 -0
- package/dist/src/runtime.d.ts.map +1 -0
- package/dist/src/runtime.js +10 -0
- package/dist/src/types.d.ts +22 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +1 -0
- package/openclaw.plugin.json +11 -0
- package/package.json +76 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { MqttConfig } from "./config-schema.js";
|
|
2
|
+
export interface MqttClientManager {
|
|
3
|
+
connect(): Promise<void>;
|
|
4
|
+
disconnect(): Promise<void>;
|
|
5
|
+
publish(topic: string, message: string, qos?: 0 | 1 | 2): Promise<void>;
|
|
6
|
+
subscribe(topic: string, handler: MessageHandler): void;
|
|
7
|
+
isConnected(): boolean;
|
|
8
|
+
}
|
|
9
|
+
export type MessageHandler = (topic: string, payload: Buffer) => void;
|
|
10
|
+
interface Logger {
|
|
11
|
+
debug(msg: string): void;
|
|
12
|
+
info(msg: string): void;
|
|
13
|
+
warn(msg: string): void;
|
|
14
|
+
error(msg: string): void;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* MQTT Client Manager
|
|
18
|
+
*
|
|
19
|
+
* Handles connection lifecycle, reconnection, and message routing.
|
|
20
|
+
*/
|
|
21
|
+
export declare function createMqttClient(rawConfig: Partial<MqttConfig>, logger: Logger): MqttClientManager;
|
|
22
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAGrD,MAAM,WAAW,iBAAiB;IAChC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxE,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,IAAI,CAAC;IACxD,WAAW,IAAI,OAAO,CAAC;CACxB;AAED,MAAM,MAAM,cAAc,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;AAEtE,UAAU,MAAM;IACd,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAKD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,OAAO,CAAC,UAAU,CAAC,EAC9B,MAAM,EAAE,MAAM,GACb,iBAAiB,CAoLnB"}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import mqtt from "mqtt";
|
|
2
|
+
import { mergeWithEnv } from "./env.js";
|
|
3
|
+
const DEFAULT_RECONNECT_MS = 5000;
|
|
4
|
+
const MAX_RECONNECT_MS = 60000;
|
|
5
|
+
/**
|
|
6
|
+
* MQTT Client Manager
|
|
7
|
+
*
|
|
8
|
+
* Handles connection lifecycle, reconnection, and message routing.
|
|
9
|
+
*/
|
|
10
|
+
export function createMqttClient(rawConfig, logger) {
|
|
11
|
+
const config = mergeWithEnv(rawConfig);
|
|
12
|
+
let client = null;
|
|
13
|
+
let messageHandlers = new Map();
|
|
14
|
+
let reconnectAttempts = 0;
|
|
15
|
+
function getClientOptions() {
|
|
16
|
+
const options = {
|
|
17
|
+
clientId: config.clientId ?? `openclaw-${Math.random().toString(36).slice(2, 10)}`,
|
|
18
|
+
clean: true,
|
|
19
|
+
connectTimeout: 10000,
|
|
20
|
+
reconnectPeriod: DEFAULT_RECONNECT_MS,
|
|
21
|
+
};
|
|
22
|
+
// Auth
|
|
23
|
+
if (config.username) {
|
|
24
|
+
options.username = config.username;
|
|
25
|
+
}
|
|
26
|
+
if (config.password) {
|
|
27
|
+
options.password = config.password;
|
|
28
|
+
}
|
|
29
|
+
// TLS
|
|
30
|
+
if (config.tls?.enabled) {
|
|
31
|
+
options.rejectUnauthorized = config.tls.rejectUnauthorized ?? true;
|
|
32
|
+
if (config.tls.ca) {
|
|
33
|
+
// Note: In production, read the CA file
|
|
34
|
+
// options.ca = fs.readFileSync(config.tls.ca);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return options;
|
|
38
|
+
}
|
|
39
|
+
async function connect() {
|
|
40
|
+
if (client?.connected) {
|
|
41
|
+
logger.debug("MQTT already connected");
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
return new Promise((resolve, reject) => {
|
|
45
|
+
logger.info(`Connecting to MQTT broker: ${config.brokerUrl}`);
|
|
46
|
+
const options = getClientOptions();
|
|
47
|
+
client = mqtt.connect(config.brokerUrl, options);
|
|
48
|
+
client.on("connect", () => {
|
|
49
|
+
logger.info("MQTT connected");
|
|
50
|
+
reconnectAttempts = 0;
|
|
51
|
+
// Resubscribe to all topics
|
|
52
|
+
for (const topic of messageHandlers.keys()) {
|
|
53
|
+
client?.subscribe(topic, { qos: config.qos }, (err) => {
|
|
54
|
+
if (err) {
|
|
55
|
+
logger.error(`Failed to subscribe to ${topic}: ${err.message}`);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
logger.debug(`Subscribed to ${topic}`);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
resolve();
|
|
63
|
+
});
|
|
64
|
+
client.on("message", (topic, payload) => {
|
|
65
|
+
logger.debug(`Received message on ${topic}: ${payload.length} bytes`);
|
|
66
|
+
const handlers = [...(messageHandlers.get(topic) ?? [])];
|
|
67
|
+
// Also check wildcard subscriptions (skip exact match to avoid duplicates)
|
|
68
|
+
for (const [pattern, patternHandlers] of messageHandlers) {
|
|
69
|
+
if (pattern === topic)
|
|
70
|
+
continue;
|
|
71
|
+
if (topicMatches(pattern, topic)) {
|
|
72
|
+
handlers.push(...patternHandlers);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
for (const handler of handlers) {
|
|
76
|
+
try {
|
|
77
|
+
handler(topic, payload);
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
logger.error(`Message handler error: ${err}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
client.on("error", (err) => {
|
|
85
|
+
logger.error(`MQTT error: ${err.message}`);
|
|
86
|
+
reject(err);
|
|
87
|
+
});
|
|
88
|
+
client.on("close", () => {
|
|
89
|
+
logger.warn("MQTT connection closed");
|
|
90
|
+
});
|
|
91
|
+
client.on("reconnect", () => {
|
|
92
|
+
reconnectAttempts++;
|
|
93
|
+
const backoff = Math.min(DEFAULT_RECONNECT_MS * Math.pow(2, reconnectAttempts), MAX_RECONNECT_MS);
|
|
94
|
+
logger.info(`MQTT reconnecting (attempt ${reconnectAttempts}, backoff ${backoff}ms)`);
|
|
95
|
+
});
|
|
96
|
+
client.on("offline", () => {
|
|
97
|
+
logger.warn("MQTT client offline");
|
|
98
|
+
});
|
|
99
|
+
// Timeout for initial connection
|
|
100
|
+
setTimeout(() => {
|
|
101
|
+
if (!client?.connected) {
|
|
102
|
+
reject(new Error("MQTT connection timeout"));
|
|
103
|
+
}
|
|
104
|
+
}, 15000);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
async function disconnect() {
|
|
108
|
+
if (!client)
|
|
109
|
+
return;
|
|
110
|
+
return new Promise((resolve) => {
|
|
111
|
+
logger.info("Disconnecting from MQTT broker");
|
|
112
|
+
client?.end(false, {}, () => {
|
|
113
|
+
client = null;
|
|
114
|
+
messageHandlers.clear();
|
|
115
|
+
logger.info("MQTT disconnected");
|
|
116
|
+
resolve();
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
async function publish(topic, message, qos = config.qos) {
|
|
121
|
+
if (!client?.connected) {
|
|
122
|
+
throw new Error("MQTT not connected");
|
|
123
|
+
}
|
|
124
|
+
return new Promise((resolve, reject) => {
|
|
125
|
+
client.publish(topic, message, { qos }, (err) => {
|
|
126
|
+
if (err) {
|
|
127
|
+
logger.error(`Failed to publish to ${topic}: ${err.message}`);
|
|
128
|
+
reject(err);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
logger.debug(`Published to ${topic}: ${message.slice(0, 100)}...`);
|
|
132
|
+
resolve();
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
function subscribe(topic, handler) {
|
|
138
|
+
const handlers = messageHandlers.get(topic) ?? [];
|
|
139
|
+
handlers.push(handler);
|
|
140
|
+
messageHandlers.set(topic, handlers);
|
|
141
|
+
// If already connected, subscribe immediately
|
|
142
|
+
if (client?.connected) {
|
|
143
|
+
client.subscribe(topic, { qos: config.qos }, (err) => {
|
|
144
|
+
if (err) {
|
|
145
|
+
logger.error(`Failed to subscribe to ${topic}: ${err.message}`);
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
logger.debug(`Subscribed to ${topic}`);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function isConnected() {
|
|
154
|
+
return client?.connected ?? false;
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
connect,
|
|
158
|
+
disconnect,
|
|
159
|
+
publish,
|
|
160
|
+
subscribe,
|
|
161
|
+
isConnected,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Check if a topic matches a subscription pattern.
|
|
166
|
+
* Supports MQTT wildcards: + (single level) and # (multi level)
|
|
167
|
+
*/
|
|
168
|
+
function topicMatches(pattern, topic) {
|
|
169
|
+
if (pattern === topic)
|
|
170
|
+
return true;
|
|
171
|
+
if (!pattern.includes("+") && !pattern.includes("#"))
|
|
172
|
+
return false;
|
|
173
|
+
const patternParts = pattern.split("/");
|
|
174
|
+
const topicParts = topic.split("/");
|
|
175
|
+
for (let i = 0; i < patternParts.length; i++) {
|
|
176
|
+
const p = patternParts[i];
|
|
177
|
+
if (p === "#") {
|
|
178
|
+
// # matches everything from here
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
if (p === "+") {
|
|
182
|
+
// + matches exactly one level
|
|
183
|
+
if (i >= topicParts.length)
|
|
184
|
+
return false;
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (p !== topicParts[i]) {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return patternParts.length === topicParts.length;
|
|
192
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* MQTT Configuration Schema
|
|
4
|
+
*
|
|
5
|
+
* Values can come from:
|
|
6
|
+
* 1. ~/.openclaw/openclaw.json (channels.mqtt.*)
|
|
7
|
+
* 2. Environment variables (MQTT_*)
|
|
8
|
+
*
|
|
9
|
+
* Environment variables take precedence for secrets.
|
|
10
|
+
*/
|
|
11
|
+
export declare const mqttConfigSchema: z.ZodObject<{
|
|
12
|
+
brokerUrl: z.ZodString;
|
|
13
|
+
username: z.ZodOptional<z.ZodString>;
|
|
14
|
+
password: z.ZodOptional<z.ZodString>;
|
|
15
|
+
clientId: z.ZodOptional<z.ZodString>;
|
|
16
|
+
topics: z.ZodDefault<z.ZodObject<{
|
|
17
|
+
inbound: z.ZodDefault<z.ZodString>;
|
|
18
|
+
outbound: z.ZodDefault<z.ZodString>;
|
|
19
|
+
}, "strip", z.ZodTypeAny, {
|
|
20
|
+
inbound: string;
|
|
21
|
+
outbound: string;
|
|
22
|
+
}, {
|
|
23
|
+
inbound?: string | undefined;
|
|
24
|
+
outbound?: string | undefined;
|
|
25
|
+
}>>;
|
|
26
|
+
qos: z.ZodDefault<z.ZodUnion<[z.ZodLiteral<0>, z.ZodLiteral<1>, z.ZodLiteral<2>]>>;
|
|
27
|
+
tls: z.ZodOptional<z.ZodObject<{
|
|
28
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
29
|
+
rejectUnauthorized: z.ZodDefault<z.ZodBoolean>;
|
|
30
|
+
ca: z.ZodOptional<z.ZodString>;
|
|
31
|
+
}, "strip", z.ZodTypeAny, {
|
|
32
|
+
enabled: boolean;
|
|
33
|
+
rejectUnauthorized: boolean;
|
|
34
|
+
ca?: string | undefined;
|
|
35
|
+
}, {
|
|
36
|
+
enabled?: boolean | undefined;
|
|
37
|
+
rejectUnauthorized?: boolean | undefined;
|
|
38
|
+
ca?: string | undefined;
|
|
39
|
+
}>>;
|
|
40
|
+
}, "strip", z.ZodTypeAny, {
|
|
41
|
+
brokerUrl: string;
|
|
42
|
+
topics: {
|
|
43
|
+
inbound: string;
|
|
44
|
+
outbound: string;
|
|
45
|
+
};
|
|
46
|
+
qos: 0 | 1 | 2;
|
|
47
|
+
username?: string | undefined;
|
|
48
|
+
password?: string | undefined;
|
|
49
|
+
clientId?: string | undefined;
|
|
50
|
+
tls?: {
|
|
51
|
+
enabled: boolean;
|
|
52
|
+
rejectUnauthorized: boolean;
|
|
53
|
+
ca?: string | undefined;
|
|
54
|
+
} | undefined;
|
|
55
|
+
}, {
|
|
56
|
+
brokerUrl: string;
|
|
57
|
+
username?: string | undefined;
|
|
58
|
+
password?: string | undefined;
|
|
59
|
+
clientId?: string | undefined;
|
|
60
|
+
topics?: {
|
|
61
|
+
inbound?: string | undefined;
|
|
62
|
+
outbound?: string | undefined;
|
|
63
|
+
} | undefined;
|
|
64
|
+
qos?: 0 | 1 | 2 | undefined;
|
|
65
|
+
tls?: {
|
|
66
|
+
enabled?: boolean | undefined;
|
|
67
|
+
rejectUnauthorized?: boolean | undefined;
|
|
68
|
+
ca?: string | undefined;
|
|
69
|
+
} | undefined;
|
|
70
|
+
}>;
|
|
71
|
+
export type MqttConfig = z.infer<typeof mqttConfigSchema>;
|
|
72
|
+
export declare const defaultConfig: Partial<MqttConfig>;
|
|
73
|
+
//# sourceMappingURL=config-schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-schema.d.ts","sourceRoot":"","sources":["../../src/config-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAsB3B,CAAC;AAEH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D,eAAO,MAAM,aAAa,EAAE,OAAO,CAAC,UAAU,CAM7C,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* MQTT Configuration Schema
|
|
4
|
+
*
|
|
5
|
+
* Values can come from:
|
|
6
|
+
* 1. ~/.openclaw/openclaw.json (channels.mqtt.*)
|
|
7
|
+
* 2. Environment variables (MQTT_*)
|
|
8
|
+
*
|
|
9
|
+
* Environment variables take precedence for secrets.
|
|
10
|
+
*/
|
|
11
|
+
export const mqttConfigSchema = z.object({
|
|
12
|
+
// Connection - env: MQTT_BROKER_URL
|
|
13
|
+
brokerUrl: z.string().url().describe("MQTT broker URL"),
|
|
14
|
+
// Auth - env: MQTT_USERNAME, MQTT_PASSWORD (recommended for secrets)
|
|
15
|
+
username: z.string().optional().describe("Broker username"),
|
|
16
|
+
password: z.string().optional().describe("Broker password"),
|
|
17
|
+
// Client - env: MQTT_CLIENT_ID
|
|
18
|
+
clientId: z.string().optional().describe("MQTT client ID"),
|
|
19
|
+
topics: z
|
|
20
|
+
.object({
|
|
21
|
+
inbound: z.string().default("openclaw/inbound"),
|
|
22
|
+
outbound: z.string().default("openclaw/outbound"),
|
|
23
|
+
})
|
|
24
|
+
.default({}),
|
|
25
|
+
qos: z.union([z.literal(0), z.literal(1), z.literal(2)]).default(1),
|
|
26
|
+
tls: z
|
|
27
|
+
.object({
|
|
28
|
+
enabled: z.boolean().default(false),
|
|
29
|
+
rejectUnauthorized: z.boolean().default(true),
|
|
30
|
+
ca: z.string().optional(),
|
|
31
|
+
})
|
|
32
|
+
.optional(),
|
|
33
|
+
});
|
|
34
|
+
export const defaultConfig = {
|
|
35
|
+
topics: {
|
|
36
|
+
inbound: "openclaw/inbound",
|
|
37
|
+
outbound: "openclaw/outbound",
|
|
38
|
+
},
|
|
39
|
+
qos: 1,
|
|
40
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { MqttConfig } from "./config-schema.js";
|
|
2
|
+
/**
|
|
3
|
+
* Environment variable names for MQTT config.
|
|
4
|
+
* These override values from openclaw.json for sensitive data.
|
|
5
|
+
*/
|
|
6
|
+
export declare const ENV_VARS: {
|
|
7
|
+
readonly BROKER_URL: "MQTT_BROKER_URL";
|
|
8
|
+
readonly USERNAME: "MQTT_USERNAME";
|
|
9
|
+
readonly PASSWORD: "MQTT_PASSWORD";
|
|
10
|
+
readonly CLIENT_ID: "MQTT_CLIENT_ID";
|
|
11
|
+
readonly CA_PATH: "MQTT_CA_PATH";
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Merge config from openclaw.json with environment variables.
|
|
15
|
+
* Environment variables take precedence (recommended for secrets).
|
|
16
|
+
*/
|
|
17
|
+
export declare function mergeWithEnv(config: Partial<MqttConfig>): MqttConfig;
|
|
18
|
+
//# sourceMappingURL=env.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../src/env.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAErD;;;GAGG;AACH,eAAO,MAAM,QAAQ;;;;;;CAMX,CAAC;AAEX;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,UAAU,CAAC,GAAG,UAAU,CAkBpE"}
|
package/dist/src/env.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment variable names for MQTT config.
|
|
3
|
+
* These override values from openclaw.json for sensitive data.
|
|
4
|
+
*/
|
|
5
|
+
export const ENV_VARS = {
|
|
6
|
+
BROKER_URL: "MQTT_BROKER_URL",
|
|
7
|
+
USERNAME: "MQTT_USERNAME",
|
|
8
|
+
PASSWORD: "MQTT_PASSWORD",
|
|
9
|
+
CLIENT_ID: "MQTT_CLIENT_ID",
|
|
10
|
+
CA_PATH: "MQTT_CA_PATH",
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Merge config from openclaw.json with environment variables.
|
|
14
|
+
* Environment variables take precedence (recommended for secrets).
|
|
15
|
+
*/
|
|
16
|
+
export function mergeWithEnv(config) {
|
|
17
|
+
return {
|
|
18
|
+
brokerUrl: process.env[ENV_VARS.BROKER_URL] ?? config.brokerUrl ?? "",
|
|
19
|
+
username: process.env[ENV_VARS.USERNAME] ?? config.username,
|
|
20
|
+
password: process.env[ENV_VARS.PASSWORD] ?? config.password,
|
|
21
|
+
clientId: process.env[ENV_VARS.CLIENT_ID] ?? config.clientId,
|
|
22
|
+
topics: config.topics ?? {
|
|
23
|
+
inbound: "openclaw/inbound",
|
|
24
|
+
outbound: "openclaw/outbound",
|
|
25
|
+
},
|
|
26
|
+
qos: config.qos ?? 1,
|
|
27
|
+
tls: config.tls
|
|
28
|
+
? {
|
|
29
|
+
...config.tls,
|
|
30
|
+
ca: process.env[ENV_VARS.CA_PATH] ?? config.tls.ca,
|
|
31
|
+
}
|
|
32
|
+
: undefined,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MQTT Channel Onboarding Adapter
|
|
3
|
+
* Provides interactive setup via `openclaw configure channels`
|
|
4
|
+
*/
|
|
5
|
+
interface MqttConfig {
|
|
6
|
+
channels?: {
|
|
7
|
+
mqtt?: {
|
|
8
|
+
enabled?: boolean;
|
|
9
|
+
brokerUrl?: string;
|
|
10
|
+
username?: string;
|
|
11
|
+
password?: string;
|
|
12
|
+
topics?: {
|
|
13
|
+
inbound?: string;
|
|
14
|
+
outbound?: string;
|
|
15
|
+
};
|
|
16
|
+
tls?: {
|
|
17
|
+
enabled?: boolean;
|
|
18
|
+
rejectUnauthorized?: boolean;
|
|
19
|
+
};
|
|
20
|
+
qos?: 0 | 1 | 2;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
interface Prompter {
|
|
25
|
+
text(opts: {
|
|
26
|
+
message: string;
|
|
27
|
+
placeholder?: string;
|
|
28
|
+
initialValue?: string;
|
|
29
|
+
validate?: (value: string | undefined) => string | undefined;
|
|
30
|
+
}): Promise<string>;
|
|
31
|
+
confirm(opts: {
|
|
32
|
+
message: string;
|
|
33
|
+
initialValue?: boolean;
|
|
34
|
+
}): Promise<boolean>;
|
|
35
|
+
select<T>(opts: {
|
|
36
|
+
message: string;
|
|
37
|
+
options: Array<{
|
|
38
|
+
value: T;
|
|
39
|
+
label: string;
|
|
40
|
+
hint?: string;
|
|
41
|
+
}>;
|
|
42
|
+
initialValue?: T;
|
|
43
|
+
}): Promise<T>;
|
|
44
|
+
note(message: string, title?: string): Promise<void>;
|
|
45
|
+
}
|
|
46
|
+
interface OnboardingStatus {
|
|
47
|
+
channel: string;
|
|
48
|
+
configured: boolean;
|
|
49
|
+
statusLines: string[];
|
|
50
|
+
selectionHint: string;
|
|
51
|
+
quickstartScore: number;
|
|
52
|
+
}
|
|
53
|
+
interface ConfigureParams {
|
|
54
|
+
cfg: MqttConfig;
|
|
55
|
+
prompter: Prompter;
|
|
56
|
+
accountOverrides?: Record<string, string>;
|
|
57
|
+
shouldPromptAccountIds?: boolean;
|
|
58
|
+
forceAllowFrom?: boolean;
|
|
59
|
+
}
|
|
60
|
+
interface ConfigureResult {
|
|
61
|
+
cfg: MqttConfig;
|
|
62
|
+
accountId: string;
|
|
63
|
+
}
|
|
64
|
+
export declare const mqttOnboardingAdapter: {
|
|
65
|
+
channel: string;
|
|
66
|
+
getStatus: ({ cfg }: {
|
|
67
|
+
cfg: MqttConfig;
|
|
68
|
+
}) => Promise<OnboardingStatus>;
|
|
69
|
+
configure: ({ cfg, prompter, }: ConfigureParams) => Promise<ConfigureResult>;
|
|
70
|
+
disable: (cfg: MqttConfig) => MqttConfig;
|
|
71
|
+
};
|
|
72
|
+
export {};
|
|
73
|
+
//# sourceMappingURL=onboarding.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"onboarding.d.ts","sourceRoot":"","sources":["../../src/onboarding.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,UAAU,UAAU;IAClB,QAAQ,CAAC,EAAE;QACT,IAAI,CAAC,EAAE;YACL,OAAO,CAAC,EAAE,OAAO,CAAC;YAClB,SAAS,CAAC,EAAE,MAAM,CAAC;YACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;YAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;YAClB,MAAM,CAAC,EAAE;gBACP,OAAO,CAAC,EAAE,MAAM,CAAC;gBACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;aACnB,CAAC;YACF,GAAG,CAAC,EAAE;gBACJ,OAAO,CAAC,EAAE,OAAO,CAAC;gBAClB,kBAAkB,CAAC,EAAE,OAAO,CAAC;aAC9B,CAAC;YACF,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;SACjB,CAAC;KACH,CAAC;CACH;AAED,UAAU,QAAQ;IAChB,IAAI,CAAC,IAAI,EAAE;QACT,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,KAAK,MAAM,GAAG,SAAS,CAAC;KAC9D,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACpB,OAAO,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7E,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE;QACd,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,KAAK,CAAC;YAAE,KAAK,EAAE,CAAC,CAAC;YAAC,KAAK,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QAC3D,YAAY,CAAC,EAAE,CAAC,CAAC;KAClB,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACf,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACtD;AAED,UAAU,gBAAgB;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,OAAO,CAAC;IACpB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,UAAU,eAAe;IACvB,GAAG,EAAE,UAAU,CAAC;IAChB,QAAQ,EAAE,QAAQ,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,UAAU,eAAe;IACvB,GAAG,EAAE,UAAU,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,eAAO,MAAM,qBAAqB;;yBAGL;QAAE,GAAG,EAAE,UAAU,CAAA;KAAE,KAAG,OAAO,CAAC,gBAAgB,CAAC;oCAoBvE,eAAe,KAAG,OAAO,CAAC,eAAe,CAAC;mBA6I9B,UAAU,KAAG,UAAU;CAOvC,CAAC"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MQTT Channel Onboarding Adapter
|
|
3
|
+
* Provides interactive setup via `openclaw configure channels`
|
|
4
|
+
*/
|
|
5
|
+
const channel = "mqtt";
|
|
6
|
+
export const mqttOnboardingAdapter = {
|
|
7
|
+
channel,
|
|
8
|
+
getStatus: async ({ cfg }) => {
|
|
9
|
+
const mqtt = cfg.channels?.mqtt;
|
|
10
|
+
const configured = Boolean(mqtt?.brokerUrl && mqtt?.enabled !== false);
|
|
11
|
+
return {
|
|
12
|
+
channel,
|
|
13
|
+
configured,
|
|
14
|
+
statusLines: [
|
|
15
|
+
`MQTT: ${configured ? `configured (${mqtt?.brokerUrl})` : "not configured"}`,
|
|
16
|
+
],
|
|
17
|
+
selectionHint: configured
|
|
18
|
+
? "configured"
|
|
19
|
+
: "IoT / home automation integration",
|
|
20
|
+
quickstartScore: configured ? 1 : 50, // Lower priority than chat channels
|
|
21
|
+
};
|
|
22
|
+
},
|
|
23
|
+
configure: async ({ cfg, prompter, }) => {
|
|
24
|
+
let next = { ...cfg };
|
|
25
|
+
// Show help note
|
|
26
|
+
await prompter.note([
|
|
27
|
+
"MQTT connects OpenClaw to IoT devices and home automation systems.",
|
|
28
|
+
"",
|
|
29
|
+
"Common brokers:",
|
|
30
|
+
" • Mosquitto: mqtt://localhost:1883",
|
|
31
|
+
" • EMQX: mqtt://localhost:1883",
|
|
32
|
+
" • HiveMQ Cloud: mqtts://broker.hivemq.com:8883",
|
|
33
|
+
"",
|
|
34
|
+
"You can also use environment variables:",
|
|
35
|
+
" MQTT_BROKER_URL, MQTT_USERNAME, MQTT_PASSWORD",
|
|
36
|
+
].join("\n"), "MQTT Setup");
|
|
37
|
+
// Prompt for broker URL
|
|
38
|
+
const existingUrl = cfg.channels?.mqtt?.brokerUrl;
|
|
39
|
+
const brokerUrl = String(await prompter.text({
|
|
40
|
+
message: "MQTT broker URL",
|
|
41
|
+
placeholder: "mqtt://localhost:1883",
|
|
42
|
+
initialValue: existingUrl || process.env.MQTT_BROKER_URL || "",
|
|
43
|
+
validate: (value) => {
|
|
44
|
+
if (!value?.trim())
|
|
45
|
+
return "Required";
|
|
46
|
+
if (!/^mqtts?:\/\/.+/.test(value.trim())) {
|
|
47
|
+
return "Must start with mqtt:// or mqtts://";
|
|
48
|
+
}
|
|
49
|
+
return undefined;
|
|
50
|
+
},
|
|
51
|
+
})).trim();
|
|
52
|
+
// Check if auth is needed
|
|
53
|
+
const needsAuth = await prompter.confirm({
|
|
54
|
+
message: "Does your broker require authentication?",
|
|
55
|
+
initialValue: Boolean(cfg.channels?.mqtt?.username || process.env.MQTT_USERNAME),
|
|
56
|
+
});
|
|
57
|
+
let username;
|
|
58
|
+
let password;
|
|
59
|
+
if (needsAuth) {
|
|
60
|
+
username = String(await prompter.text({
|
|
61
|
+
message: "MQTT username",
|
|
62
|
+
initialValue: cfg.channels?.mqtt?.username || process.env.MQTT_USERNAME || "",
|
|
63
|
+
validate: (value) => (value?.trim() ? undefined : "Required"),
|
|
64
|
+
})).trim();
|
|
65
|
+
password = String(await prompter.text({
|
|
66
|
+
message: "MQTT password",
|
|
67
|
+
initialValue: cfg.channels?.mqtt?.password || "",
|
|
68
|
+
validate: (value) => (value?.trim() ? undefined : "Required"),
|
|
69
|
+
})).trim();
|
|
70
|
+
}
|
|
71
|
+
// TLS settings for mqtts://
|
|
72
|
+
let tls;
|
|
73
|
+
if (brokerUrl.startsWith("mqtts://")) {
|
|
74
|
+
const rejectUnauthorized = await prompter.confirm({
|
|
75
|
+
message: "Verify TLS certificate? (disable for self-signed certs)",
|
|
76
|
+
initialValue: true,
|
|
77
|
+
});
|
|
78
|
+
tls = { enabled: true, rejectUnauthorized };
|
|
79
|
+
}
|
|
80
|
+
// Topics
|
|
81
|
+
await prompter.note([
|
|
82
|
+
"Topics define where OpenClaw listens and publishes:",
|
|
83
|
+
"",
|
|
84
|
+
" • Inbound: messages TO OpenClaw (e.g., alerts, commands)",
|
|
85
|
+
" • Outbound: messages FROM OpenClaw (e.g., responses)",
|
|
86
|
+
"",
|
|
87
|
+
"Wildcards supported: + (single level), # (multi level)",
|
|
88
|
+
"Example: home/+/alerts, sensors/#",
|
|
89
|
+
].join("\n"), "MQTT Topics");
|
|
90
|
+
const inboundTopic = String(await prompter.text({
|
|
91
|
+
message: "Inbound topic (messages to OpenClaw)",
|
|
92
|
+
placeholder: "openclaw/inbound",
|
|
93
|
+
initialValue: cfg.channels?.mqtt?.topics?.inbound || "openclaw/inbound",
|
|
94
|
+
})).trim();
|
|
95
|
+
const outboundTopic = String(await prompter.text({
|
|
96
|
+
message: "Outbound topic (messages from OpenClaw)",
|
|
97
|
+
placeholder: "openclaw/outbound",
|
|
98
|
+
initialValue: cfg.channels?.mqtt?.topics?.outbound || "openclaw/outbound",
|
|
99
|
+
})).trim();
|
|
100
|
+
// QoS level
|
|
101
|
+
const qos = (await prompter.select({
|
|
102
|
+
message: "QoS level",
|
|
103
|
+
options: [
|
|
104
|
+
{ value: 0, label: "0 - At most once", hint: "fire and forget" },
|
|
105
|
+
{ value: 1, label: "1 - At least once", hint: "recommended" },
|
|
106
|
+
{ value: 2, label: "2 - Exactly once", hint: "highest overhead" },
|
|
107
|
+
],
|
|
108
|
+
initialValue: cfg.channels?.mqtt?.qos ?? 1,
|
|
109
|
+
}));
|
|
110
|
+
// Build config
|
|
111
|
+
next = {
|
|
112
|
+
...next,
|
|
113
|
+
channels: {
|
|
114
|
+
...next.channels,
|
|
115
|
+
mqtt: {
|
|
116
|
+
enabled: true,
|
|
117
|
+
brokerUrl,
|
|
118
|
+
...(username && { username }),
|
|
119
|
+
...(password && { password }),
|
|
120
|
+
...(tls && { tls }),
|
|
121
|
+
topics: {
|
|
122
|
+
inbound: inboundTopic,
|
|
123
|
+
outbound: outboundTopic,
|
|
124
|
+
},
|
|
125
|
+
qos,
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
return { cfg: next, accountId: "default" };
|
|
130
|
+
},
|
|
131
|
+
disable: (cfg) => ({
|
|
132
|
+
...cfg,
|
|
133
|
+
channels: {
|
|
134
|
+
...cfg.channels,
|
|
135
|
+
mqtt: { ...cfg.channels?.mqtt, enabled: false },
|
|
136
|
+
},
|
|
137
|
+
}),
|
|
138
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAIzD,wBAAgB,cAAc,CAAC,CAAC,EAAE,aAAa,QAE9C;AAED,wBAAgB,cAAc,IAAI,aAAa,CAK9C"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { CoreConfig } from "openclaw/plugin-sdk";
|
|
2
|
+
import type { MqttConfig } from "./config-schema.js";
|
|
3
|
+
export interface MqttChannelConfig {
|
|
4
|
+
channels?: {
|
|
5
|
+
mqtt?: MqttConfig;
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
export type MqttCoreConfig = CoreConfig & MqttChannelConfig;
|
|
9
|
+
export interface MqttInboundMessage {
|
|
10
|
+
topic: string;
|
|
11
|
+
payload: string | Buffer;
|
|
12
|
+
qos: 0 | 1 | 2;
|
|
13
|
+
retain: boolean;
|
|
14
|
+
timestamp: number;
|
|
15
|
+
}
|
|
16
|
+
export interface MqttOutboundMessage {
|
|
17
|
+
topic?: string;
|
|
18
|
+
payload: string;
|
|
19
|
+
qos?: 0 | 1 | 2;
|
|
20
|
+
retain?: boolean;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAErD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE;QACT,IAAI,CAAC,EAAE,UAAU,CAAC;KACnB,CAAC;CACH;AAED,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,iBAAiB,CAAC;AAE5D,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACf,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|