Skip to content

Commit 3e8f446

Browse files
committed
blu assist: add mqtt config script
1 parent de2b69c commit 3e8f446

File tree

1 file changed

+351
-0
lines changed

1 file changed

+351
-0
lines changed

blu-assistant-scripts/config-mqtt.js

Lines changed: 351 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,351 @@
1+
/************************************************
2+
* CONFIGURATION
3+
************************************************/
4+
var CONFIG = {
5+
FILTERED_BLE_ID: '', // default BLE ID
6+
ALLTERCO_MFD_ID: 'a90b',
7+
8+
MQTT_SERVER: '',
9+
MQTT_CLIENT_ID: '',
10+
MQTT_PREFIX: '',
11+
12+
URL_CA_BUNDLE: '',
13+
URL_CLIENT_CERT: '',
14+
URL_CLIENT_KEY: '',
15+
16+
SYS_BTN: 'pair',
17+
VIRTUAL_BTN: 'button:202', // MQTT deploy button
18+
19+
// --- IDs of your virtual UI components ---
20+
UI_FILTERED_BLE_ID: 'text:200', // BLE ID
21+
UI_MQTT_SERVER: 'text:205', // MQTT Server
22+
UI_MQTT_CLIENT_ID: 'text:206', // MQTT Client ID
23+
UI_MQTT_PREFIX: 'text:207', // MQTT Prefix
24+
UI_URL_CA_BUNDLE: 'text:208', // CA Bundle URL
25+
UI_URL_CLIENT_CERT: 'text:209', // Client Cert URL
26+
UI_URL_CLIENT_KEY: 'text:210', // Client Key URL
27+
}
28+
29+
var BLE_SCAN = { active: false, duration_ms: 750, window_ms: 95, interval_ms: 100, rssi_thr: -60 }
30+
31+
/************************************************
32+
* OVERRIDE CONFIG WITH UI FIELDS
33+
************************************************/
34+
var uiBleId = Virtual.getHandle(CONFIG.UI_FILTERED_BLE_ID)
35+
var uiServer = Virtual.getHandle(CONFIG.UI_MQTT_SERVER)
36+
var uiClientId = Virtual.getHandle(CONFIG.UI_MQTT_CLIENT_ID)
37+
var uiPrefix = Virtual.getHandle(CONFIG.UI_MQTT_PREFIX)
38+
var uiCaBundle = Virtual.getHandle(CONFIG.UI_URL_CA_BUNDLE)
39+
var uiCert = Virtual.getHandle(CONFIG.UI_URL_CLIENT_CERT)
40+
var uiKey = Virtual.getHandle(CONFIG.UI_URL_CLIENT_KEY)
41+
var uiDeployBtn = Virtual.getHandle(CONFIG.VIRTUAL_BTN)
42+
43+
function refreshConfig() {
44+
var v
45+
if (uiBleId) {
46+
v = uiBleId.getValue()
47+
// parse hex or decimal input
48+
CONFIG.FILTERED_BLE_ID = (v.indexOf('0x') === 0 ? parseInt(v, 16) : parseInt(v, 10)) || CONFIG.FILTERED_BLE_ID
49+
}
50+
if (uiServer && (v = uiServer.getValue())) CONFIG.MQTT_SERVER = v
51+
if (uiClientId && (v = uiClientId.getValue())) CONFIG.MQTT_CLIENT_ID = v
52+
if (uiPrefix && (v = uiPrefix.getValue())) CONFIG.MQTT_PREFIX = v
53+
if (uiCaBundle && (v = uiCaBundle.getValue())) CONFIG.URL_CA_BUNDLE = v
54+
if (uiCert && (v = uiCert.getValue())) CONFIG.URL_CLIENT_CERT = v
55+
if (uiKey && (v = uiKey.getValue())) CONFIG.URL_CLIENT_KEY = v
56+
57+
console.log(
58+
'CONFIG ← UI:',
59+
JSON.stringify({
60+
FILTERED_BLE_ID: CONFIG.FILTERED_BLE_ID,
61+
MQTT_SERVER: CONFIG.MQTT_SERVER,
62+
MQTT_CLIENT_ID: CONFIG.MQTT_CLIENT_ID,
63+
MQTT_PREFIX: CONFIG.MQTT_PREFIX,
64+
URL_CA_BUNDLE: CONFIG.URL_CA_BUNDLE,
65+
URL_CLIENT_CERT: CONFIG.URL_CLIENT_CERT,
66+
URL_CLIENT_KEY: CONFIG.URL_CLIENT_KEY,
67+
})
68+
)
69+
}
70+
71+
/************************************************
72+
* HTTP GET helper – accepts body / body_b64 / body_base64
73+
************************************************/
74+
function fetch(url, cb) {
75+
console.log('HTTP GET', url)
76+
Shelly.call('HTTP.GET', { url: url, binary: true }, function (res, ec, em) {
77+
if (ec || !res || res.code !== 200) {
78+
console.log('HTTP GET failed', ec, em)
79+
cb(null)
80+
return
81+
}
82+
83+
var txt = null
84+
85+
if (typeof res.body === 'string') {
86+
/* plain text */
87+
txt = res.body
88+
console.log('HTTP GET -> body', txt.length, 'bytes')
89+
} else if (typeof res.body_b64 === 'string') {
90+
txt = atob(res.body_b64)
91+
console.log('HTTP GET -> body_b64', txt.length, 'bytes')
92+
} else if (typeof res.body_base64 === 'string') {
93+
txt = atob(res.body_base64)
94+
console.log('HTTP GET -> body_base64', txt.length, 'bytes')
95+
} else {
96+
console.log('HTTP GET unknown payload keys:', JSON.stringify(res))
97+
}
98+
99+
cb(txt)
100+
})
101+
}
102+
103+
/************************************************
104+
* upload PEM with progress
105+
************************************************/
106+
function putPem(addr, method, text, cb) {
107+
if (!text) {
108+
console.log(method, 'input NULL – abort')
109+
cb(false)
110+
return
111+
}
112+
var lines = text.split('\n'), i = 0, bytes = 0
113+
function next(app) {
114+
var chunk = lines[i++] + '\n'
115+
bytes += chunk.length
116+
if (i % 10 === 0 || i === lines.length) console.log(method, '…', i, '/', lines.length)
117+
Shelly.call(
118+
'GATTC.call',
119+
{
120+
addr: addr,
121+
method: method,
122+
params: { data: chunk, append: app },
123+
},
124+
function (r, e, m) {
125+
if (e) {
126+
console.log(method, 'error', m)
127+
cb(false)
128+
} else if (i < lines.length) next(true)
129+
else {
130+
console.log(method, 'DONE', bytes, 'bytes')
131+
cb(true)
132+
}
133+
}
134+
)
135+
}
136+
next(false)
137+
}
138+
139+
/************************************************
140+
* push MQTT config
141+
************************************************/
142+
function mqttConfig(addr, cb) {
143+
var cfg = {
144+
enable: true,
145+
server: CONFIG.MQTT_SERVER,
146+
client_id: CONFIG.MQTT_CLIENT_ID,
147+
topic_prefix: CONFIG.MQTT_PREFIX,
148+
ssl_ca: 'user_ca.pem',
149+
rpc_ntf: true,
150+
status_ntf: true,
151+
enable_control: true,
152+
enable_rpc: true,
153+
use_client_cert: true,
154+
}
155+
console.log('MQTT.SetConfig sending…')
156+
Shelly.call(
157+
'GATTC.call',
158+
{
159+
addr: addr,
160+
method: 'MQTT.SetConfig',
161+
params: { config: cfg },
162+
},
163+
function (r, e, m) {
164+
if (e) {
165+
console.log('MQTT.SetConfig ERROR', m)
166+
cb(false)
167+
} else {
168+
console.log('MQTT.SetConfig OK – restart required')
169+
cb(true)
170+
}
171+
}
172+
)
173+
}
174+
175+
/************************************************
176+
* full deployment chain
177+
************************************************/
178+
function fetchCa(context) {
179+
fetch(CONFIG.URL_CA_BUNDLE, function (ca) {
180+
if (!ca) {
181+
console.log('CA download NULL – abort')
182+
return
183+
}
184+
context.ca = ca
185+
fetchClientCert(context)
186+
})
187+
}
188+
189+
function fetchClientCert(context) {
190+
fetch(CONFIG.URL_CLIENT_CERT, function (cc) {
191+
if (!cc) {
192+
console.log('Client-cert download NULL – abort')
193+
return
194+
}
195+
context.cc = cc
196+
fetchClientKey(context)
197+
})
198+
}
199+
200+
function fetchClientKey(context) {
201+
fetch(CONFIG.URL_CLIENT_KEY, function (ck) {
202+
if (!ck) {
203+
console.log('Client-key download NULL – abort')
204+
return
205+
}
206+
context.ck = ck
207+
putUserCa(context)
208+
})
209+
}
210+
211+
function putUserCa(context) {
212+
putPem(context.addr, 'Shelly.PutUserCA', context.ca, function (ok1) {
213+
if (!ok1) {
214+
console.log('CA upload failed – abort')
215+
return
216+
}
217+
putClientCert(context)
218+
})
219+
}
220+
221+
function putClientCert(context) {
222+
putPem(context.addr, 'Shelly.PutTLSClientCert', context.cc, function (ok2) {
223+
if (!ok2) {
224+
console.log('Cert upload failed – abort')
225+
return
226+
}
227+
putClientKey(context)
228+
})
229+
}
230+
231+
function putClientKey(context) {
232+
putPem(context.addr, 'Shelly.PutTLSClientKey', context.ck, function (ok3) {
233+
if (!ok3) {
234+
console.log('Key upload failed – abort')
235+
return
236+
}
237+
mqttConfigProcess(context)
238+
})
239+
}
240+
241+
function mqttConfigProcess(context) {
242+
console.log('All certificates uploaded OK')
243+
mqttConfig(context.addr, function (ok4) {
244+
if (!ok4) {
245+
console.log('MQTT config failed – abort')
246+
return
247+
}
248+
reboot(context)
249+
})
250+
}
251+
252+
function reboot(context) {
253+
Shelly.call('GATTC.call', { addr: context.addr, method: 'Shelly.Reboot', params: {} }, function (r, e, m) {
254+
if (e) {
255+
console.log('Reboot RPC error', m)
256+
return
257+
}
258+
console.log('Rebooting … wait 10 s')
259+
Timer.set(10000, false, function () {
260+
Shelly.call('GATTC.call', { addr: context.addr, method: 'MQTT.GetStatus', params: {} }, function (res, ec, em) {
261+
if (ec) console.log('MQTT.GetStatus error', em)
262+
else console.log('MQTT connected?', res && res.connected)
263+
})
264+
})
265+
})
266+
}
267+
268+
function deploy(addr) {
269+
console.log('=== provisioning', addr, '===')
270+
var context = { addr: addr }
271+
fetchCa(context)
272+
}
273+
274+
/************************************************
275+
* BLE scan selecting strongest RSSI
276+
************************************************/
277+
function idFromAdv(a) {
278+
return parseInt(a.substr(22, 2), 16) + (parseInt(a.substr(24, 2), 16) << 8)
279+
}
280+
281+
function sortRSSI(devices) {
282+
for (var i = 0; i < devices.length; i++) {
283+
for (var j = i + 1; j < devices.length; j++) {
284+
if (devices[i].rssi < devices[j].rssi) {
285+
var temp = devices[i]
286+
devices[i] = devices[j]
287+
devices[j] = temp
288+
}
289+
}
290+
}
291+
}
292+
293+
function scanCb(res) {
294+
if (!res || !Array.isArray(res.results)) {
295+
console.log('BLE scan invalid')
296+
return
297+
}
298+
var matchedDevices = res.results.filter(function (dev) {
299+
return (
300+
typeof dev.adv_data === 'string' &&
301+
dev.adv_data.indexOf(CONFIG.ALLTERCO_MFD_ID) === 10 &&
302+
idFromAdv(dev.adv_data) === CONFIG.FILTERED_BLE_ID
303+
)
304+
})
305+
if (!matchedDevices.length) {
306+
console.log('No matching devices')
307+
return
308+
}
309+
sortRSSI(matchedDevices)
310+
var target = matchedDevices[0]
311+
console.log('Target', target.addr, 'RSSI', target.rssi)
312+
deploy(target.addr)
313+
}
314+
315+
function scan() {
316+
console.log('BLE scan…')
317+
Shelly.call('GATTC.Scan', BLE_SCAN, scanCb)
318+
}
319+
320+
/************************************************
321+
* triggers
322+
************************************************/
323+
Shelly.addEventHandler(function (ev) {
324+
if (ev.info && ev.info.component === 'sys' && ev.info.event === 'brief_btn_down' && ev.info.name === CONFIG.SYS_BTN) {
325+
console.log('System button -> scan')
326+
scan()
327+
}
328+
})
329+
330+
function init() {
331+
// apply overrides at startup
332+
refreshConfig()
333+
334+
// re-apply on any text-field change
335+
var uiHandles = [uiBleId, uiServer, uiClientId, uiPrefix, uiCaBundle, uiCert, uiKey]
336+
uiHandles.forEach(function (h) {
337+
if (h && h.on) h.on('change', refreshConfig)
338+
})
339+
340+
// hook the virtual “MQTT” button
341+
if (uiDeployBtn) {
342+
uiDeployBtn.on('single_push', function () {
343+
console.log('Virtual deploy button pressed → scan')
344+
scan()
345+
})
346+
}
347+
348+
console.log('Ready – press the physical or virtual button to provision MQTT')
349+
}
350+
351+
init()

0 commit comments

Comments
 (0)