@rhizomatics/signalk-bluetti-plugin 1.0.6-alpha → 1.0.8-alpha
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/index.js +37 -1
- package/lib/device.js +12 -0
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -169,6 +169,17 @@ module.exports = function (app) {
|
|
|
169
169
|
return;
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
+
// Validate any explicitly-configured encryption CSV paths up front.
|
|
173
|
+
for (const cfg of devices) {
|
|
174
|
+
if (cfg.encryptionCsvPath) {
|
|
175
|
+
const err = validateEncryptionCsvPath(cfg.encryptionCsvPath, cfg.name);
|
|
176
|
+
if (err) {
|
|
177
|
+
app.setPluginError(err);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
172
183
|
// Build address → cfg lookup (normalise to lowercase, no colons)
|
|
173
184
|
const normalise = (addr) => addr.toLowerCase().replace(/:/g, '');
|
|
174
185
|
const pending = new Map(devices.map(cfg => [normalise(cfg.address), cfg]));
|
|
@@ -180,7 +191,11 @@ module.exports = function (app) {
|
|
|
180
191
|
if (cfg) {
|
|
181
192
|
pending.delete(normalise(address));
|
|
182
193
|
app.setPluginStatus(`Found ${name} [${address}] — connecting …`);
|
|
183
|
-
|
|
194
|
+
// Noble cannot scan and connect simultaneously — stop scan first.
|
|
195
|
+
// A short delay lets the BlueZ adapter finish scanning before we connect.
|
|
196
|
+
// The scanComplete handler will restart scanning for any remaining pending devices.
|
|
197
|
+
scanner.stopScan();
|
|
198
|
+
setTimeout(() => startDevice(cfg, peripheral, name, deps), 500);
|
|
184
199
|
} else {
|
|
185
200
|
log(`Discovered unconfigured Bluetti device: ${name} [${address}]`);
|
|
186
201
|
}
|
|
@@ -212,6 +227,27 @@ module.exports = function (app) {
|
|
|
212
227
|
}
|
|
213
228
|
}
|
|
214
229
|
|
|
230
|
+
function validateEncryptionCsvPath(csvPath, deviceName) {
|
|
231
|
+
try {
|
|
232
|
+
fs.accessSync(csvPath, fs.constants.R_OK);
|
|
233
|
+
} catch (_) {
|
|
234
|
+
return `[${deviceName}] Encryption CSV not found or not readable: ${csvPath}`;
|
|
235
|
+
}
|
|
236
|
+
let lines;
|
|
237
|
+
try {
|
|
238
|
+
lines = fs.readFileSync(csvPath, 'utf8').split('\n').map(l => l.trim()).filter(Boolean);
|
|
239
|
+
} catch (err) {
|
|
240
|
+
return `[${deviceName}] Could not read encryption CSV "${csvPath}": ${err.message}`;
|
|
241
|
+
}
|
|
242
|
+
if (lines[0] !== 'bluetti') {
|
|
243
|
+
return `[${deviceName}] Encryption CSV does not look like a Bluetti key file (expected first line "bluetti"): ${csvPath}`;
|
|
244
|
+
}
|
|
245
|
+
if (lines.length < 4) {
|
|
246
|
+
return `[${deviceName}] Encryption CSV is missing the key line (expected 4 lines): ${csvPath}`;
|
|
247
|
+
}
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
|
|
215
251
|
function resolveRegisterMapPath(cfg) {
|
|
216
252
|
const { builtinModel, csvPath } = cfg;
|
|
217
253
|
if (!builtinModel || builtinModel === 'custom') {
|
package/lib/device.js
CHANGED
|
@@ -13,6 +13,7 @@ const ALT_SERVICE_UUID = 'ffe0';
|
|
|
13
13
|
const ALT_CHAR_UUID = 'ffe1';
|
|
14
14
|
|
|
15
15
|
const RECONNECT_DELAYS = [5000, 10000, 20000, 30000, 60000]; // ms, capped at last value
|
|
16
|
+
const CONNECT_TIMEOUT_MS = 15000;
|
|
16
17
|
|
|
17
18
|
class BluettiDevice extends EventEmitter {
|
|
18
19
|
constructor({ address, name, peripheral, fields, pollIntervalMs = 10000, xorKey = null, log }) {
|
|
@@ -60,7 +61,18 @@ class BluettiDevice extends EventEmitter {
|
|
|
60
61
|
if (this._stopped) return;
|
|
61
62
|
this._log(`[${this._name}] Connecting to ${this._address} …`);
|
|
62
63
|
|
|
64
|
+
let settled = false;
|
|
65
|
+
const connectTimer = setTimeout(() => {
|
|
66
|
+
if (settled) return;
|
|
67
|
+
settled = true;
|
|
68
|
+
this._log(`[${this._name}] Connect timed out after ${CONNECT_TIMEOUT_MS / 1000}s — will retry`);
|
|
69
|
+
this._scheduleReconnect();
|
|
70
|
+
}, CONNECT_TIMEOUT_MS);
|
|
71
|
+
|
|
63
72
|
this._peripheral.connect((err) => {
|
|
73
|
+
if (settled) return;
|
|
74
|
+
settled = true;
|
|
75
|
+
clearTimeout(connectTimer);
|
|
64
76
|
if (err) {
|
|
65
77
|
this._log(`[${this._name}] Connect error: ${err.message}`);
|
|
66
78
|
this._scheduleReconnect();
|