@rhizomatics/signalk-bluetti-plugin 1.1.0-alpha → 1.1.2-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 CHANGED
@@ -214,16 +214,18 @@ module.exports = function (app) {
214
214
  scanner.startScan(30000);
215
215
  };
216
216
 
217
- // Search $HOME for a file named <bleName>.csv (case-insensitive).
217
+ // Search $HOME for an encryption CSV for bleName.
218
+ // Priority: 1) <bleName>.csv 2) bluetti_device_licence.csv
218
219
  function findEncryptionCsvInHome(bleName) {
219
220
  const homeDir = os.homedir();
220
- const target = `${bleName.toLowerCase()}.csv`;
221
221
  try {
222
- const match = fs.readdirSync(homeDir).find(f => f.toLowerCase() === target);
223
- return match ? path.join(homeDir, match) : null;
224
- } catch (_) {
225
- return null;
226
- }
222
+ const files = fs.readdirSync(homeDir);
223
+ const specific = files.find(f => f.toLowerCase() === `${bleName.toLowerCase()}.csv`);
224
+ if (specific) return path.join(homeDir, specific);
225
+ const generic = files.find(f => f.toLowerCase() === 'bluetti_device_licence.csv');
226
+ if (generic) return path.join(homeDir, generic);
227
+ } catch (_) {}
228
+ return null;
227
229
  }
228
230
 
229
231
  function validateEncryptionCsvPath(csvPath, deviceName) {
package/lib/device.js CHANGED
@@ -12,8 +12,9 @@ const WRITE_UUID = 'ff02';
12
12
  const ALT_SERVICE_UUID = 'ffe0';
13
13
  const ALT_CHAR_UUID = 'ffe1';
14
14
 
15
- const RECONNECT_DELAYS = [5000, 10000, 20000, 30000, 60000]; // ms, capped at last value
16
- const CONNECT_TIMEOUT_MS = 30000;
15
+ const RECONNECT_DELAYS = [5000, 10000, 20000, 30000, 60000]; // ms, capped at last value
16
+ const CONNECT_TIMEOUT_MS = 30000;
17
+ const DISCOVER_TIMEOUT_MS = 20000;
17
18
 
18
19
  class BluettiDevice extends EventEmitter {
19
20
  constructor({ address, name, device, fields, pollIntervalMs = 10000, xorKey = null, log }) {
@@ -63,6 +64,11 @@ class BluettiDevice extends EventEmitter {
63
64
 
64
65
  async _connect() {
65
66
  if (this._stopped) return;
67
+ // Cancel any lingering BlueZ connection state (e.g. a previous attempt that
68
+ // timed out on our side but is still in progress inside BlueZ).
69
+ if (!this._connected) {
70
+ try { await this._device.disconnect(); } catch (_) {}
71
+ }
66
72
  this._log(`[${this._name}] Connecting to ${this._address} …`);
67
73
 
68
74
  // Clear any stale disconnect handler from a previous attempt.
@@ -110,16 +116,26 @@ class BluettiDevice extends EventEmitter {
110
116
  };
111
117
  this._device.once('disconnect', this._disconnectHandler);
112
118
 
113
- await this._discoverServices();
119
+ let discoverSettled = false;
120
+ const discoverTimer = setTimeout(() => {
121
+ if (discoverSettled) return;
122
+ discoverSettled = true;
123
+ this._log(`[${this._name}] Service discovery timed out — will retry`);
124
+ this._scheduleReconnect();
125
+ }, DISCOVER_TIMEOUT_MS);
126
+
127
+ await this._discoverServices(() => !discoverSettled);
128
+ discoverSettled = true;
129
+ clearTimeout(discoverTimer);
114
130
  }
115
131
 
116
- async _discoverServices() {
132
+ async _discoverServices(isActive) {
117
133
  let gatt;
118
134
  try {
119
135
  gatt = await this._device.gatt();
120
136
  } catch (err) {
121
137
  this._log(`[${this._name}] GATT error: ${err.message}`);
122
- this._scheduleReconnect();
138
+ if (isActive()) this._scheduleReconnect();
123
139
  return;
124
140
  }
125
141
 
@@ -132,7 +148,7 @@ class BluettiDevice extends EventEmitter {
132
148
  }
133
149
  if (!service) {
134
150
  this._log(`[${this._name}] Could not find BLE service (tried ${SERVICE_UUID}, ${ALT_SERVICE_UUID})`);
135
- this._scheduleReconnect();
151
+ if (isActive()) this._scheduleReconnect();
136
152
  return;
137
153
  }
138
154
 
@@ -148,7 +164,7 @@ class BluettiDevice extends EventEmitter {
148
164
 
149
165
  if (!notifyChar || !writeChar) {
150
166
  this._log(`[${this._name}] Could not find required GATT characteristics`);
151
- this._scheduleReconnect();
167
+ if (isActive()) this._scheduleReconnect();
152
168
  return;
153
169
  }
154
170
 
@@ -156,7 +172,7 @@ class BluettiDevice extends EventEmitter {
156
172
  await notifyChar.startNotifications();
157
173
  } catch (err) {
158
174
  this._log(`[${this._name}] Subscribe error: ${err.message}`);
159
- this._scheduleReconnect();
175
+ if (isActive()) this._scheduleReconnect();
160
176
  return;
161
177
  }
162
178
 
package/lib/scanner.js CHANGED
@@ -70,17 +70,22 @@ class Scanner extends EventEmitter {
70
70
  return;
71
71
  }
72
72
  for (const addr of addresses) {
73
+ if (!this._scanning) break; // scanner stopped while we were iterating
73
74
  const normAddr = addr.toLowerCase().replace(/:/g, '');
74
75
  if (this._found.has(normAddr)) continue;
76
+ // Reserve the slot immediately to prevent concurrent _poll() calls from
77
+ // both processing the same address before either stores the device.
78
+ this._found.set(normAddr, null);
75
79
  try {
76
80
  const device = await this._adapter.getDevice(addr);
77
81
  const name = await Promise.resolve(device.getName()).catch(() => '') || '';
78
- if (!isBluettiDevice(name)) continue;
82
+ if (!this._scanning) break;
83
+ if (!isBluettiDevice(name)) { this._found.delete(normAddr); continue; }
79
84
  this._found.set(normAddr, { address: addr, name, device });
80
85
  this._log(`Discovered Bluetti device: ${name} [${addr}]`);
81
86
  this.emit('discovered', { address: addr, name, device });
82
87
  } catch (_) {
83
- // Device may have disappeared before we could read it; skip
88
+ this._found.delete(normAddr); // allow retry on transient D-Bus error
84
89
  }
85
90
  }
86
91
  }
@@ -92,8 +97,9 @@ class Scanner extends EventEmitter {
92
97
  this._scanning = false;
93
98
  // Decrement BlueZ discovery reference count — other plugins' sessions keep scanning.
94
99
  if (this._adapter) this._adapter.stopDiscovery().catch(() => {});
95
- this._log(`Scan complete. Found ${this._found.size} Bluetti device(s).`);
96
- this.emit('scanComplete', [...this._found.values()].map(({ address, name }) => ({ address, name })));
100
+ const found = [...this._found.values()].filter(v => v !== null);
101
+ this._log(`Scan complete. Found ${found.length} Bluetti device(s).`);
102
+ this.emit('scanComplete', found.map(({ address, name }) => ({ address, name })));
97
103
  }
98
104
 
99
105
  stopAll() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rhizomatics/signalk-bluetti-plugin",
3
- "version": "1.1.0-alpha",
3
+ "version": "1.1.2-alpha",
4
4
  "signalk": {
5
5
  "displayName": "Bluetti Monitoring"
6
6
  },