signalk-daly-bms-ble 0.1.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/LICENSE +21 -0
- package/README.md +84 -0
- package/index.js +617 -0
- package/lib/daly.js +351 -0
- package/package.json +58 -0
- package/public/237.bundle.js +1 -0
- package/public/540.bundle.js +2 -0
- package/public/540.bundle.js.LICENSE.txt +9 -0
- package/public/bundle.js +1 -0
- package/public/remoteEntry.js +1 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 signalk-daly-bms-ble contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# signalk-daly-bms-ble
|
|
2
|
+
|
|
3
|
+
Signal K plugin for Daly Smart BMS devices over Bluetooth LE.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Reads Daly Smart BMS data over BLE
|
|
8
|
+
- Supports multiple Daly devices
|
|
9
|
+
- BLE scan from the Signal K plugin configuration
|
|
10
|
+
- Manual MAC address configuration
|
|
11
|
+
- Publishes battery data to Signal K
|
|
12
|
+
- Individual cell voltages
|
|
13
|
+
- Temperatures
|
|
14
|
+
- SOC, voltage, current and power
|
|
15
|
+
- MOSFET and BMS status
|
|
16
|
+
- Remaining capacity and cycle count
|
|
17
|
+
- Balancing status
|
|
18
|
+
- Alarm/error status
|
|
19
|
+
- Automatic reconnect
|
|
20
|
+
|
|
21
|
+
## Signal K paths
|
|
22
|
+
|
|
23
|
+
Each Daly device is assigned a Battery ID.
|
|
24
|
+
|
|
25
|
+
Examples:
|
|
26
|
+
|
|
27
|
+
house
|
|
28
|
+
house.balancer
|
|
29
|
+
starter.balancer
|
|
30
|
+
|
|
31
|
+
These result in paths such as:
|
|
32
|
+
|
|
33
|
+
electrical.batteries.house.voltage
|
|
34
|
+
electrical.batteries.house.cells.1.voltage
|
|
35
|
+
electrical.batteries.house.balancer.voltage
|
|
36
|
+
electrical.batteries.house.balancer.cells.1.voltage
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
This project is currently under development.
|
|
41
|
+
|
|
42
|
+
Requirements:
|
|
43
|
+
|
|
44
|
+
- Signal K Server
|
|
45
|
+
- Linux with BlueZ
|
|
46
|
+
- Node.js 22 or newer
|
|
47
|
+
- Bluetooth LE adapter
|
|
48
|
+
- Daly Smart BMS with BLE
|
|
49
|
+
|
|
50
|
+
## Development
|
|
51
|
+
|
|
52
|
+
Install dependencies:
|
|
53
|
+
|
|
54
|
+
npm install
|
|
55
|
+
|
|
56
|
+
Build the Signal K configuration UI:
|
|
57
|
+
|
|
58
|
+
npm run build
|
|
59
|
+
|
|
60
|
+
## Configuration
|
|
61
|
+
|
|
62
|
+
Open Signal K Admin and go to:
|
|
63
|
+
|
|
64
|
+
Server -> Plugin Config -> Daly BMS
|
|
65
|
+
|
|
66
|
+
Use **Scan** to discover Daly BLE devices.
|
|
67
|
+
|
|
68
|
+
Each device needs a unique Battery ID.
|
|
69
|
+
|
|
70
|
+
The Battery ID may contain dots, for example:
|
|
71
|
+
|
|
72
|
+
house.balancer
|
|
73
|
+
|
|
74
|
+
A custom Bluetooth MAC address can also be entered manually.
|
|
75
|
+
|
|
76
|
+
## Status
|
|
77
|
+
|
|
78
|
+
Early development version.
|
|
79
|
+
|
|
80
|
+
Tested with Daly BLE protocol commands 0x90-0x98.
|
|
81
|
+
|
|
82
|
+
## License
|
|
83
|
+
|
|
84
|
+
MIT
|
package/index.js
ADDED
|
@@ -0,0 +1,617 @@
|
|
|
1
|
+
const { DalyBMS, scanDalyDevices } = require('./lib/daly');
|
|
2
|
+
|
|
3
|
+
module.exports = function(app) {
|
|
4
|
+
const plugin = {};
|
|
5
|
+
|
|
6
|
+
plugin.id = 'signalk-daly-bms-ble';
|
|
7
|
+
plugin.name = 'Daly BMS';
|
|
8
|
+
plugin.description =
|
|
9
|
+
'Reads one or more Daly Smart BMS devices over Bluetooth LE and publishes them to Signal K.';
|
|
10
|
+
|
|
11
|
+
let running = false;
|
|
12
|
+
let workers = [];
|
|
13
|
+
|
|
14
|
+
plugin.registerWithRouter = function(router) {
|
|
15
|
+
router.get('/scan', async (req, res) => {
|
|
16
|
+
try {
|
|
17
|
+
app.setPluginStatus('Scanning for Daly BMS devices...');
|
|
18
|
+
|
|
19
|
+
const devices = await scanDalyDevices(8);
|
|
20
|
+
|
|
21
|
+
app.setPluginStatus(
|
|
22
|
+
`BLE scan complete: ${devices.length} Daly device(s) found`
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
res.json({ devices });
|
|
26
|
+
|
|
27
|
+
} catch (err) {
|
|
28
|
+
app.error(`Daly BLE scan failed: ${err.stack || err.message}`);
|
|
29
|
+
|
|
30
|
+
app.setPluginError(
|
|
31
|
+
`BLE scan failed: ${err.message}`
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
res.status(500).json({
|
|
35
|
+
error: err.message
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
plugin.schema = {
|
|
42
|
+
type: 'object',
|
|
43
|
+
|
|
44
|
+
properties: {
|
|
45
|
+
batteries: {
|
|
46
|
+
type: 'array',
|
|
47
|
+
title: 'Daly devices',
|
|
48
|
+
|
|
49
|
+
items: {
|
|
50
|
+
type: 'object',
|
|
51
|
+
|
|
52
|
+
properties: {
|
|
53
|
+
device: {
|
|
54
|
+
type: 'string',
|
|
55
|
+
title: 'Daly Bluetooth device'
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
name: {
|
|
59
|
+
type: 'string',
|
|
60
|
+
title: 'Bluetooth name'
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
batteryId: {
|
|
64
|
+
type: 'string',
|
|
65
|
+
title: 'Battery ID'
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
required: [
|
|
70
|
+
'device',
|
|
71
|
+
'batteryId'
|
|
72
|
+
]
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
default: []
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
pollInterval: {
|
|
79
|
+
type: 'number',
|
|
80
|
+
title: 'Poll interval',
|
|
81
|
+
minimum: 1,
|
|
82
|
+
maximum: 60,
|
|
83
|
+
default: 5
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
publishBasic: {
|
|
87
|
+
type: 'boolean',
|
|
88
|
+
default: true
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
publishCells: {
|
|
92
|
+
type: 'boolean',
|
|
93
|
+
default: true
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
publishTemperatures: {
|
|
97
|
+
type: 'boolean',
|
|
98
|
+
default: true
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
publishStatus: {
|
|
102
|
+
type: 'boolean',
|
|
103
|
+
default: true
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
publishCapacity: {
|
|
107
|
+
type: 'boolean',
|
|
108
|
+
default: true
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
publishBalancing: {
|
|
112
|
+
type: 'boolean',
|
|
113
|
+
default: true
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
publishAlarms: {
|
|
117
|
+
type: 'boolean',
|
|
118
|
+
default: true
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
function value(values, path, v) {
|
|
124
|
+
if (v !== undefined && v !== null) {
|
|
125
|
+
values.push({
|
|
126
|
+
path,
|
|
127
|
+
value: v
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function makeDelta(settings, batteryId, data) {
|
|
133
|
+
const base =
|
|
134
|
+
`electrical.batteries.${batteryId}`;
|
|
135
|
+
|
|
136
|
+
const values = [];
|
|
137
|
+
|
|
138
|
+
if (settings.publishBasic !== false) {
|
|
139
|
+
value(
|
|
140
|
+
values,
|
|
141
|
+
`${base}.voltage`,
|
|
142
|
+
data.voltage
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
value(
|
|
146
|
+
values,
|
|
147
|
+
`${base}.current`,
|
|
148
|
+
data.current
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
if (data.soc !== undefined) {
|
|
152
|
+
value(
|
|
153
|
+
values,
|
|
154
|
+
`${base}.capacity.stateOfCharge`,
|
|
155
|
+
data.soc / 100
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (
|
|
160
|
+
data.voltage !== undefined &&
|
|
161
|
+
data.current !== undefined
|
|
162
|
+
) {
|
|
163
|
+
value(
|
|
164
|
+
values,
|
|
165
|
+
`${base}.power`,
|
|
166
|
+
data.voltage * data.current
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (settings.publishCells !== false) {
|
|
172
|
+
for (
|
|
173
|
+
const [cell, voltage]
|
|
174
|
+
of Object.entries(data.cells || {})
|
|
175
|
+
) {
|
|
176
|
+
value(
|
|
177
|
+
values,
|
|
178
|
+
`${base}.cells.${cell}.voltage`,
|
|
179
|
+
voltage
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
value(
|
|
184
|
+
values,
|
|
185
|
+
`${base}.cells.minimumVoltage`,
|
|
186
|
+
data.minCellVoltage
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
value(
|
|
190
|
+
values,
|
|
191
|
+
`${base}.cells.maximumVoltage`,
|
|
192
|
+
data.maxCellVoltage
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
value(
|
|
196
|
+
values,
|
|
197
|
+
`${base}.cells.deltaVoltage`,
|
|
198
|
+
data.cellDelta
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
value(
|
|
202
|
+
values,
|
|
203
|
+
`${base}.cells.minimumCell`,
|
|
204
|
+
data.minCell
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
value(
|
|
208
|
+
values,
|
|
209
|
+
`${base}.cells.maximumCell`,
|
|
210
|
+
data.maxCell
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (settings.publishTemperatures !== false) {
|
|
215
|
+
for (
|
|
216
|
+
const [sensor, temperature]
|
|
217
|
+
of Object.entries(data.temperatures || {})
|
|
218
|
+
) {
|
|
219
|
+
value(
|
|
220
|
+
values,
|
|
221
|
+
`${base}.temperatures.${sensor}`,
|
|
222
|
+
temperature + 273.15
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (data.maxTemperature !== undefined) {
|
|
227
|
+
value(
|
|
228
|
+
values,
|
|
229
|
+
`${base}.bms.maximumTemperature`,
|
|
230
|
+
data.maxTemperature + 273.15
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (data.minTemperature !== undefined) {
|
|
235
|
+
value(
|
|
236
|
+
values,
|
|
237
|
+
`${base}.bms.minimumTemperature`,
|
|
238
|
+
data.minTemperature + 273.15
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (settings.publishStatus !== false) {
|
|
244
|
+
value(
|
|
245
|
+
values,
|
|
246
|
+
`${base}.bms.mode`,
|
|
247
|
+
data.mode
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
value(
|
|
251
|
+
values,
|
|
252
|
+
`${base}.bms.chargeMosfet`,
|
|
253
|
+
data.chargeMosfet
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
value(
|
|
257
|
+
values,
|
|
258
|
+
`${base}.bms.dischargeMosfet`,
|
|
259
|
+
data.dischargeMosfet
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
value(
|
|
263
|
+
values,
|
|
264
|
+
`${base}.bms.chargerConnected`,
|
|
265
|
+
data.chargerRunning
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
value(
|
|
269
|
+
values,
|
|
270
|
+
`${base}.bms.loadConnected`,
|
|
271
|
+
data.loadRunning
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (settings.publishCapacity !== false) {
|
|
276
|
+
value(
|
|
277
|
+
values,
|
|
278
|
+
`${base}.bms.remainingCapacity`,
|
|
279
|
+
data.remainingCapacityAh
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
value(
|
|
283
|
+
values,
|
|
284
|
+
`${base}.bms.cycles`,
|
|
285
|
+
data.cycles
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
value(
|
|
289
|
+
values,
|
|
290
|
+
`${base}.bms.cellCount`,
|
|
291
|
+
data.cellCount
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (settings.publishBalancing !== false) {
|
|
296
|
+
value(
|
|
297
|
+
values,
|
|
298
|
+
`${base}.bms.balancing`,
|
|
299
|
+
data.balancing || []
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
value(
|
|
303
|
+
values,
|
|
304
|
+
`${base}.bms.balancingActive`,
|
|
305
|
+
!!(
|
|
306
|
+
data.balancing &&
|
|
307
|
+
data.balancing.length
|
|
308
|
+
)
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (settings.publishAlarms !== false) {
|
|
313
|
+
value(
|
|
314
|
+
values,
|
|
315
|
+
`${base}.bms.errors`,
|
|
316
|
+
data.errors || []
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
value(
|
|
320
|
+
values,
|
|
321
|
+
`${base}.bms.alarmActive`,
|
|
322
|
+
!!(
|
|
323
|
+
data.errors &&
|
|
324
|
+
data.errors.length
|
|
325
|
+
)
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return {
|
|
330
|
+
updates: [
|
|
331
|
+
{
|
|
332
|
+
source: {
|
|
333
|
+
label: `daly-bms.${batteryId}`
|
|
334
|
+
},
|
|
335
|
+
|
|
336
|
+
timestamp:
|
|
337
|
+
new Date().toISOString(),
|
|
338
|
+
|
|
339
|
+
values
|
|
340
|
+
}
|
|
341
|
+
]
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function getConfiguredBatteries(settings) {
|
|
346
|
+
if (
|
|
347
|
+
Array.isArray(settings.batteries) &&
|
|
348
|
+
settings.batteries.length
|
|
349
|
+
) {
|
|
350
|
+
return settings.batteries.filter(
|
|
351
|
+
item =>
|
|
352
|
+
item &&
|
|
353
|
+
item.device &&
|
|
354
|
+
item.batteryId
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/*
|
|
359
|
+
* Backwards compatibility with our original
|
|
360
|
+
* single-device configuration.
|
|
361
|
+
*/
|
|
362
|
+
if (settings.device) {
|
|
363
|
+
return [
|
|
364
|
+
{
|
|
365
|
+
device: settings.device,
|
|
366
|
+
batteryId:
|
|
367
|
+
settings.batteryId || 'house'
|
|
368
|
+
}
|
|
369
|
+
];
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return [];
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function updateOverallStatus() {
|
|
376
|
+
if (!workers.length) {
|
|
377
|
+
app.setPluginStatus(
|
|
378
|
+
'No Daly devices configured'
|
|
379
|
+
);
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const connected =
|
|
384
|
+
workers.filter(
|
|
385
|
+
worker => worker.connected
|
|
386
|
+
).length;
|
|
387
|
+
|
|
388
|
+
const summaries =
|
|
389
|
+
workers
|
|
390
|
+
.filter(
|
|
391
|
+
worker =>
|
|
392
|
+
worker.connected &&
|
|
393
|
+
worker.lastData
|
|
394
|
+
)
|
|
395
|
+
.map(worker => {
|
|
396
|
+
const d = worker.lastData;
|
|
397
|
+
|
|
398
|
+
let text =
|
|
399
|
+
`${worker.config.batteryId}: ` +
|
|
400
|
+
`${d.voltage !== undefined ? d.voltage.toFixed(2) : '?'} V`;
|
|
401
|
+
|
|
402
|
+
if (d.soc !== undefined) {
|
|
403
|
+
text += ` ${d.soc.toFixed(0)}%`;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return text;
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
let status =
|
|
410
|
+
`${connected}/${workers.length} Daly device(s) connected`;
|
|
411
|
+
|
|
412
|
+
if (summaries.length) {
|
|
413
|
+
status += ` | ${summaries.join(' | ')}`;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
app.setPluginStatus(status);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
async function runWorker(worker, settings) {
|
|
420
|
+
const interval =
|
|
421
|
+
Math.max(
|
|
422
|
+
1,
|
|
423
|
+
Number(settings.pollInterval) || 5
|
|
424
|
+
) * 1000;
|
|
425
|
+
|
|
426
|
+
while (running && !worker.stop) {
|
|
427
|
+
try {
|
|
428
|
+
if (!worker.bms) {
|
|
429
|
+
worker.connected = false;
|
|
430
|
+
updateOverallStatus();
|
|
431
|
+
|
|
432
|
+
worker.bms =
|
|
433
|
+
new DalyBMS(
|
|
434
|
+
worker.config.device
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
await worker.bms.connect();
|
|
438
|
+
|
|
439
|
+
worker.connected = true;
|
|
440
|
+
updateOverallStatus();
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const data =
|
|
444
|
+
await worker.bms.readAll();
|
|
445
|
+
|
|
446
|
+
if (
|
|
447
|
+
data.voltage === undefined ||
|
|
448
|
+
data.soc === undefined
|
|
449
|
+
) {
|
|
450
|
+
throw new Error(
|
|
451
|
+
'No valid basic BMS data received'
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
worker.lastData = data;
|
|
456
|
+
worker.connected = true;
|
|
457
|
+
|
|
458
|
+
app.handleMessage(
|
|
459
|
+
plugin.id,
|
|
460
|
+
makeDelta(
|
|
461
|
+
settings,
|
|
462
|
+
worker.config.batteryId,
|
|
463
|
+
data
|
|
464
|
+
),
|
|
465
|
+
'v1'
|
|
466
|
+
);
|
|
467
|
+
|
|
468
|
+
updateOverallStatus();
|
|
469
|
+
|
|
470
|
+
await new Promise(resolve =>
|
|
471
|
+
setTimeout(resolve, interval)
|
|
472
|
+
);
|
|
473
|
+
|
|
474
|
+
} catch (err) {
|
|
475
|
+
worker.connected = false;
|
|
476
|
+
|
|
477
|
+
app.error(
|
|
478
|
+
`Daly ${worker.config.batteryId} ` +
|
|
479
|
+
`(${worker.config.device}): ` +
|
|
480
|
+
`${err.stack || err.message}`
|
|
481
|
+
);
|
|
482
|
+
|
|
483
|
+
if (worker.bms) {
|
|
484
|
+
try {
|
|
485
|
+
await worker.bms.disconnect();
|
|
486
|
+
} catch {}
|
|
487
|
+
|
|
488
|
+
worker.bms = null;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
updateOverallStatus();
|
|
492
|
+
|
|
493
|
+
if (
|
|
494
|
+
running &&
|
|
495
|
+
!worker.stop
|
|
496
|
+
) {
|
|
497
|
+
await new Promise(resolve =>
|
|
498
|
+
setTimeout(resolve, 5000)
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
plugin.start = function(settings) {
|
|
506
|
+
running = true;
|
|
507
|
+
workers = [];
|
|
508
|
+
|
|
509
|
+
const batteries =
|
|
510
|
+
getConfiguredBatteries(settings);
|
|
511
|
+
|
|
512
|
+
if (!batteries.length) {
|
|
513
|
+
app.setPluginStatus(
|
|
514
|
+
'No Daly devices configured'
|
|
515
|
+
);
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
const seenDevices = new Set();
|
|
520
|
+
const seenIds = new Set();
|
|
521
|
+
|
|
522
|
+
for (const config of batteries) {
|
|
523
|
+
const deviceKey =
|
|
524
|
+
String(config.device)
|
|
525
|
+
.trim()
|
|
526
|
+
.toUpperCase();
|
|
527
|
+
|
|
528
|
+
const idKey =
|
|
529
|
+
String(config.batteryId)
|
|
530
|
+
.trim();
|
|
531
|
+
|
|
532
|
+
if (
|
|
533
|
+
seenDevices.has(deviceKey)
|
|
534
|
+
) {
|
|
535
|
+
app.error(
|
|
536
|
+
`Duplicate Daly device ignored: ${config.device}`
|
|
537
|
+
);
|
|
538
|
+
continue;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
if (
|
|
542
|
+
seenIds.has(idKey)
|
|
543
|
+
) {
|
|
544
|
+
app.error(
|
|
545
|
+
`Duplicate Battery ID ignored: ${config.batteryId}`
|
|
546
|
+
);
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
seenDevices.add(deviceKey);
|
|
551
|
+
seenIds.add(idKey);
|
|
552
|
+
|
|
553
|
+
const worker = {
|
|
554
|
+
config: {
|
|
555
|
+
...config,
|
|
556
|
+
batteryId: idKey
|
|
557
|
+
},
|
|
558
|
+
|
|
559
|
+
bms: null,
|
|
560
|
+
connected: false,
|
|
561
|
+
lastData: null,
|
|
562
|
+
stop: false,
|
|
563
|
+
promise: null
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
workers.push(worker);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
updateOverallStatus();
|
|
570
|
+
|
|
571
|
+
for (const worker of workers) {
|
|
572
|
+
worker.promise =
|
|
573
|
+
runWorker(
|
|
574
|
+
worker,
|
|
575
|
+
settings
|
|
576
|
+
);
|
|
577
|
+
}
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
plugin.stop = async function() {
|
|
581
|
+
running = false;
|
|
582
|
+
|
|
583
|
+
for (const worker of workers) {
|
|
584
|
+
worker.stop = true;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
for (const worker of workers) {
|
|
588
|
+
if (worker.bms) {
|
|
589
|
+
try {
|
|
590
|
+
await worker.bms.disconnect();
|
|
591
|
+
} catch {}
|
|
592
|
+
|
|
593
|
+
worker.bms = null;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
await Promise.allSettled(
|
|
598
|
+
workers.map(worker =>
|
|
599
|
+
worker.promise
|
|
600
|
+
? Promise.race([
|
|
601
|
+
worker.promise,
|
|
602
|
+
|
|
603
|
+
new Promise(resolve =>
|
|
604
|
+
setTimeout(resolve, 2000)
|
|
605
|
+
)
|
|
606
|
+
])
|
|
607
|
+
: Promise.resolve()
|
|
608
|
+
)
|
|
609
|
+
);
|
|
610
|
+
|
|
611
|
+
workers = [];
|
|
612
|
+
|
|
613
|
+
app.setPluginStatus('Stopped');
|
|
614
|
+
};
|
|
615
|
+
|
|
616
|
+
return plugin;
|
|
617
|
+
};
|
package/lib/daly.js
ADDED
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
const { createBluetooth } = require('node-ble');
|
|
2
|
+
|
|
3
|
+
const SERVICE = '0000fff0-0000-1000-8000-00805f9b34fb';
|
|
4
|
+
const NOTIFY = '0000fff1-0000-1000-8000-00805f9b34fb';
|
|
5
|
+
const WRITE = '0000fff2-0000-1000-8000-00805f9b34fb';
|
|
6
|
+
|
|
7
|
+
function sleep(ms) {
|
|
8
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function normalizeAddress(input) {
|
|
12
|
+
const s = String(input || '').trim().toUpperCase();
|
|
13
|
+
|
|
14
|
+
if (/^DL-[0-9A-F]{12}$/.test(s)) {
|
|
15
|
+
const h = s.slice(3);
|
|
16
|
+
return h.match(/../g).join(':');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (/^[0-9A-F]{12}$/.test(s)) {
|
|
20
|
+
return s.match(/../g).join(':');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (/^([0-9A-F]{2}:){5}[0-9A-F]{2}$/.test(s)) {
|
|
24
|
+
return s;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
throw new Error(
|
|
28
|
+
'Invalid Daly device. Use DL-XXXXXXXXXXXX or XX:XX:XX:XX:XX:XX'
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function makeRequest(command) {
|
|
33
|
+
const b = Buffer.from([
|
|
34
|
+
0xA5, 0x80, command, 0x08,
|
|
35
|
+
0,0,0,0,0,0,0,0,
|
|
36
|
+
0
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
let sum = 0;
|
|
40
|
+
for (let i = 0; i < 12; i++) sum += b[i];
|
|
41
|
+
b[12] = sum & 0xff;
|
|
42
|
+
|
|
43
|
+
return b;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function validFrame(f) {
|
|
47
|
+
if (f.length !== 13 || f[0] !== 0xA5) return false;
|
|
48
|
+
|
|
49
|
+
let sum = 0;
|
|
50
|
+
for (let i = 0; i < 12; i++) sum += f[i];
|
|
51
|
+
|
|
52
|
+
return (sum & 0xff) === f[12];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
class DalyBMS {
|
|
56
|
+
constructor(deviceInput) {
|
|
57
|
+
this.address = normalizeAddress(deviceInput);
|
|
58
|
+
|
|
59
|
+
this.bluetoothContext = null;
|
|
60
|
+
this.device = null;
|
|
61
|
+
this.notifyChar = null;
|
|
62
|
+
this.writeChar = null;
|
|
63
|
+
|
|
64
|
+
this.data = this.newData();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
newData() {
|
|
68
|
+
return {
|
|
69
|
+
cells: {},
|
|
70
|
+
temperatures: {},
|
|
71
|
+
balancing: [],
|
|
72
|
+
errors: []
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
parseFrame(f) {
|
|
77
|
+
if (!validFrame(f)) return;
|
|
78
|
+
|
|
79
|
+
const d = this.data;
|
|
80
|
+
|
|
81
|
+
switch (f[2]) {
|
|
82
|
+
case 0x90:
|
|
83
|
+
d.voltage = f.readUInt16BE(4) / 10;
|
|
84
|
+
d.current = (f.readUInt16BE(8) - 30000) / 10;
|
|
85
|
+
d.soc = f.readUInt16BE(10) / 10;
|
|
86
|
+
break;
|
|
87
|
+
|
|
88
|
+
case 0x91:
|
|
89
|
+
d.maxCellVoltage = f.readUInt16BE(4) / 1000;
|
|
90
|
+
d.maxCell = f[6];
|
|
91
|
+
d.minCellVoltage = f.readUInt16BE(7) / 1000;
|
|
92
|
+
d.minCell = f[9];
|
|
93
|
+
d.cellDelta = d.maxCellVoltage - d.minCellVoltage;
|
|
94
|
+
break;
|
|
95
|
+
|
|
96
|
+
case 0x92:
|
|
97
|
+
d.maxTemperature = f[4] - 40;
|
|
98
|
+
d.maxTemperatureSensor = f[5];
|
|
99
|
+
d.minTemperature = f[6] - 40;
|
|
100
|
+
d.minTemperatureSensor = f[7];
|
|
101
|
+
break;
|
|
102
|
+
|
|
103
|
+
case 0x93: {
|
|
104
|
+
const state = f[4];
|
|
105
|
+
|
|
106
|
+
d.mode =
|
|
107
|
+
state === 0 ? 'stationary' :
|
|
108
|
+
state === 1 ? 'charging' :
|
|
109
|
+
state === 2 ? 'discharging' :
|
|
110
|
+
'unknown';
|
|
111
|
+
|
|
112
|
+
d.chargeMosfet = !!f[5];
|
|
113
|
+
d.dischargeMosfet = !!f[6];
|
|
114
|
+
d.heartbeat = f[7];
|
|
115
|
+
d.remainingCapacityAh = f.readUInt32BE(8) / 1000;
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
case 0x94:
|
|
120
|
+
d.cellCount = f[4];
|
|
121
|
+
d.temperatureSensorCount = f[5];
|
|
122
|
+
d.chargerRunning = !!f[6];
|
|
123
|
+
d.loadRunning = !!f[7];
|
|
124
|
+
d.digitalIO = f[8];
|
|
125
|
+
d.cycles = f.readUInt16BE(9);
|
|
126
|
+
break;
|
|
127
|
+
|
|
128
|
+
case 0x95: {
|
|
129
|
+
const frameNo = f[4];
|
|
130
|
+
|
|
131
|
+
for (let i = 0; i < 3; i++) {
|
|
132
|
+
const cell = (frameNo - 1) * 3 + i + 1;
|
|
133
|
+
const mv = f.readUInt16BE(5 + i * 2);
|
|
134
|
+
|
|
135
|
+
if (mv > 0 && (!d.cellCount || cell <= d.cellCount)) {
|
|
136
|
+
d.cells[cell] = mv / 1000;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
case 0x96: {
|
|
143
|
+
const frameNo = f[4];
|
|
144
|
+
|
|
145
|
+
for (let i = 0; i < 7; i++) {
|
|
146
|
+
const sensor = (frameNo - 1) * 7 + i + 1;
|
|
147
|
+
|
|
148
|
+
if (
|
|
149
|
+
d.temperatureSensorCount &&
|
|
150
|
+
sensor > d.temperatureSensorCount
|
|
151
|
+
) break;
|
|
152
|
+
|
|
153
|
+
const raw = f[5 + i];
|
|
154
|
+
|
|
155
|
+
if (raw !== 0) {
|
|
156
|
+
d.temperatures[sensor] = raw - 40;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
case 0x97: {
|
|
163
|
+
d.balancing = [];
|
|
164
|
+
const bits = f.subarray(4, 12);
|
|
165
|
+
|
|
166
|
+
for (let byte = 0; byte < bits.length; byte++) {
|
|
167
|
+
for (let bit = 0; bit < 8; bit++) {
|
|
168
|
+
const cell = byte * 8 + bit + 1;
|
|
169
|
+
|
|
170
|
+
if (d.cellCount && cell > d.cellCount) break;
|
|
171
|
+
|
|
172
|
+
if (bits[byte] & (1 << bit)) {
|
|
173
|
+
d.balancing.push(cell);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
case 0x98: {
|
|
181
|
+
const alarmBytes = f.subarray(4, 12);
|
|
182
|
+
d.errors = alarmBytes.some(v => v !== 0)
|
|
183
|
+
? [alarmBytes.toString('hex')]
|
|
184
|
+
: [];
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async connect() {
|
|
191
|
+
this.bluetoothContext = createBluetooth();
|
|
192
|
+
|
|
193
|
+
const { bluetooth } = this.bluetoothContext;
|
|
194
|
+
const adapter = await bluetooth.defaultAdapter();
|
|
195
|
+
|
|
196
|
+
if (!(await adapter.isPowered())) {
|
|
197
|
+
await adapter.setPowered(true);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (!(await adapter.isDiscovering())) {
|
|
201
|
+
await adapter.startDiscovery();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
this.device = await adapter.waitDevice(this.address);
|
|
205
|
+
await this.device.connect();
|
|
206
|
+
|
|
207
|
+
const gatt = await this.device.gatt();
|
|
208
|
+
const service = await gatt.getPrimaryService(SERVICE);
|
|
209
|
+
|
|
210
|
+
this.notifyChar = await service.getCharacteristic(NOTIFY);
|
|
211
|
+
this.writeChar = await service.getCharacteristic(WRITE);
|
|
212
|
+
|
|
213
|
+
this.notifyChar.on('valuechanged', packet => {
|
|
214
|
+
for (
|
|
215
|
+
let offset = 0;
|
|
216
|
+
offset + 13 <= packet.length;
|
|
217
|
+
offset += 13
|
|
218
|
+
) {
|
|
219
|
+
this.parseFrame(
|
|
220
|
+
packet.subarray(offset, offset + 13)
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
await this.notifyChar.startNotifications();
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async readAll() {
|
|
229
|
+
this.data = this.newData();
|
|
230
|
+
|
|
231
|
+
const commands = [
|
|
232
|
+
0x90, 0x91, 0x92, 0x93, 0x94,
|
|
233
|
+
0x95, 0x96, 0x97, 0x98
|
|
234
|
+
];
|
|
235
|
+
|
|
236
|
+
for (const cmd of commands) {
|
|
237
|
+
await this.writeChar.writeValueWithoutResponse(
|
|
238
|
+
makeRequest(cmd)
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
await sleep(
|
|
242
|
+
cmd === 0x95 || cmd === 0x96 ? 500 : 150
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
await sleep(300);
|
|
247
|
+
|
|
248
|
+
return JSON.parse(JSON.stringify(this.data));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async disconnect() {
|
|
252
|
+
if (this.notifyChar) {
|
|
253
|
+
try {
|
|
254
|
+
await this.notifyChar.stopNotifications();
|
|
255
|
+
} catch {}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (this.device) {
|
|
259
|
+
try {
|
|
260
|
+
await this.device.disconnect();
|
|
261
|
+
} catch {}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (this.bluetoothContext) {
|
|
265
|
+
try {
|
|
266
|
+
this.bluetoothContext.destroy();
|
|
267
|
+
} catch {}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
this.notifyChar = null;
|
|
271
|
+
this.writeChar = null;
|
|
272
|
+
this.device = null;
|
|
273
|
+
this.bluetoothContext = null;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
async function scanDalyDevices(scanSeconds = 8) {
|
|
279
|
+
const bluetoothContext = createBluetooth();
|
|
280
|
+
const { bluetooth } = bluetoothContext;
|
|
281
|
+
|
|
282
|
+
const found = new Map();
|
|
283
|
+
const inspected = new Set();
|
|
284
|
+
|
|
285
|
+
try {
|
|
286
|
+
const adapter = await bluetooth.defaultAdapter();
|
|
287
|
+
|
|
288
|
+
if (!(await adapter.isPowered())) {
|
|
289
|
+
await adapter.setPowered(true);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (!(await adapter.isDiscovering())) {
|
|
293
|
+
await adapter.startDiscovery();
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const end = Date.now() + scanSeconds * 1000;
|
|
297
|
+
|
|
298
|
+
while (Date.now() < end) {
|
|
299
|
+
const addresses = await adapter.devices();
|
|
300
|
+
|
|
301
|
+
for (const address of addresses) {
|
|
302
|
+
const normalizedAddress = address.toUpperCase();
|
|
303
|
+
|
|
304
|
+
if (inspected.has(normalizedAddress)) {
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
inspected.add(normalizedAddress);
|
|
309
|
+
|
|
310
|
+
try {
|
|
311
|
+
const device = await adapter.getDevice(address);
|
|
312
|
+
|
|
313
|
+
let name = '';
|
|
314
|
+
|
|
315
|
+
try {
|
|
316
|
+
name = await device.getName();
|
|
317
|
+
} catch {}
|
|
318
|
+
|
|
319
|
+
if (
|
|
320
|
+
name &&
|
|
321
|
+
(
|
|
322
|
+
name.toUpperCase().startsWith('DL-') ||
|
|
323
|
+
name.toUpperCase().startsWith('DALY')
|
|
324
|
+
)
|
|
325
|
+
) {
|
|
326
|
+
found.set(normalizedAddress, {
|
|
327
|
+
address: normalizedAddress,
|
|
328
|
+
name
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
} catch {}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
await sleep(500);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return Array.from(found.values());
|
|
339
|
+
|
|
340
|
+
} finally {
|
|
341
|
+
try {
|
|
342
|
+
bluetoothContext.destroy();
|
|
343
|
+
} catch {}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
module.exports = {
|
|
348
|
+
DalyBMS,
|
|
349
|
+
normalizeAddress,
|
|
350
|
+
scanDalyDevices
|
|
351
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "signalk-daly-bms-ble",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Signal K plugin for Daly Smart BMS devices over Bluetooth LE",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "node --check index.js && node --check lib/daly.js",
|
|
8
|
+
"build": "webpack --mode production"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"signalk-node-server-plugin",
|
|
12
|
+
"signalk-category-hardware",
|
|
13
|
+
"signalk-plugin-configurator",
|
|
14
|
+
"signalk",
|
|
15
|
+
"daly",
|
|
16
|
+
"bms",
|
|
17
|
+
"bluetooth",
|
|
18
|
+
"ble",
|
|
19
|
+
"battery"
|
|
20
|
+
],
|
|
21
|
+
"type": "commonjs",
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"node-ble": "^1.13.0"
|
|
24
|
+
},
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=22"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@babel/core": "^8.0.1",
|
|
30
|
+
"@babel/preset-react": "^8.0.1",
|
|
31
|
+
"babel-loader": "^10.1.1",
|
|
32
|
+
"react": "^19.2.8",
|
|
33
|
+
"react-dom": "^19.2.8",
|
|
34
|
+
"webpack": "^5.110.1",
|
|
35
|
+
"webpack-cli": "^7.2.3"
|
|
36
|
+
},
|
|
37
|
+
"author": {
|
|
38
|
+
"name": "Marvin Field"
|
|
39
|
+
},
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "git+https://github.com/marvin75/signalk-daly-bms-ble.git"
|
|
44
|
+
},
|
|
45
|
+
"bugs": {
|
|
46
|
+
"url": "https://github.com/marvin75/signalk-daly-bms-ble/issues"
|
|
47
|
+
},
|
|
48
|
+
"homepage": "https://github.com/marvin75/signalk-daly-bms-ble#readme",
|
|
49
|
+
"signalk-plugin-enabled-by-default": false,
|
|
50
|
+
"signalk": {
|
|
51
|
+
"displayName": "Daly BMS"
|
|
52
|
+
},
|
|
53
|
+
"files": [
|
|
54
|
+
"index.js",
|
|
55
|
+
"lib/daly.js",
|
|
56
|
+
"public"
|
|
57
|
+
]
|
|
58
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(self.webpackChunksignalk_daly_bms_ble=self.webpackChunksignalk_daly_bms_ble||[]).push([[237],{237(e,t,a){const i=a(641),{useState:l}=i,s=i.createElement;function n({configuration:e={},save:t}){const a=Array.isArray(e.batteries)&&e.batteries.length?e.batteries:e.device?[{device:e.device,name:"",batteryId:e.batteryId||"house"}]:[],[i,n]=l({batteries:a.map(e=>({device:e.device||"",name:e.name||"",batteryId:e.batteryId||""})),pollInterval:e.pollInterval||5,publishBasic:!1!==e.publishBasic,publishCells:!1!==e.publishCells,publishTemperatures:!1!==e.publishTemperatures,publishStatus:!1!==e.publishStatus,publishCapacity:!1!==e.publishCapacity,publishBalancing:!1!==e.publishBalancing,publishAlarms:!1!==e.publishAlarms}),[r,d]=l([]),[p,o]=l(!1),[c,u]=l(""),[y,b]=l(""),[m,g]=l(""),[h,v]=l("");function x(e,t){n(a=>({...a,[e]:t}))}const f={width:"100%",maxWidth:"550px",padding:"8px",marginTop:"5px"},C={width:"260px",padding:"8px"},_={display:"block",fontWeight:"600",marginBottom:"4px"},B={marginBottom:"24px"};return s("div",{style:{maxWidth:"850px"}},s("div",{style:B},s("label",{style:_},"Configured Daly devices"),0===i.batteries.length?s("div",{style:{marginBottom:"10px"}},"No Daly devices configured."):null,...i.batteries.map((e,t)=>s("div",{key:`${e.device}-${t}`,style:{display:"flex",gap:"10px",alignItems:"center",marginBottom:"10px",padding:"10px",border:"1px solid #ccc"}},s("div",{style:{flex:"1"}},s("div",{style:{fontWeight:"600"}},e.name?`${e.name} (${e.device})`:e.device)),s("input",{style:C,type:"text",value:e.batteryId,placeholder:"house.balancer",onChange:e=>function(e,t,a){n(i=>{const l=[...i.batteries];return l[e]={...l[e],[t]:a},{...i,batteries:l}})}(t,"batteryId",e.target.value)}),s("button",{type:"button",onClick:()=>function(e){n(t=>({...t,batteries:t.batteries.filter((t,a)=>a!==e)}))}(t),style:{padding:"8px 12px"}},"Remove")))),s("div",{style:B},s("label",{style:_},"Add Daly Bluetooth device"),s("button",{type:"button",onClick:async function(){o(!0),u("Scanning for Daly BMS devices...");try{const e=await fetch("/plugins/signalk-daly-bms-ble/scan",{credentials:"include"});if(!e.ok)throw new Error(`HTTP ${e.status}`);const t=(await e.json()).devices||[];d(t),t.length?(b(t[0].address),u(`Found ${t.length} Daly BMS device(s)`)):(b("__custom__"),u("No Daly BMS devices found"))}catch(e){u(`Scan failed: ${e.message}`)}finally{o(!1)}},disabled:p,style:{padding:"8px 18px",marginBottom:"10px"}},p?"Scanning...":"Scan"),r.length>0?s("div",{style:{marginBottom:"10px"}},s("select",{style:f,value:y,onChange:e=>b(e.target.value)},...r.map(e=>s("option",{key:e.address,value:e.address},`${e.name} (${e.address})`)),s("option",{value:"__custom__"},"Custom MAC"))):null,"__custom__"===y?s("input",{style:f,type:"text",value:m,placeholder:"XX:XX:XX:XX:XX:XX",onChange:e=>g(e.target.value)}):null,r.length>0||"__custom__"===y?s("div",{style:{display:"flex",gap:"8px",alignItems:"center",marginTop:"10px"}},s("input",{style:C,type:"text",value:h,placeholder:"Battery ID, e.g. house.balancer",onChange:e=>v(e.target.value)}),s("button",{type:"button",onClick:function(){const e="__custom__"===y?m.trim():y;if(!e)return void u("Select a Daly device or enter a custom MAC");const t=h.trim();if(!t)return void u("Enter Battery ID before adding the device");if(i.batteries.some(t=>t.device.toUpperCase()===e.toUpperCase()))return void u("That Daly device is already configured");if(i.batteries.some(e=>e.batteryId===t))return void u("That Battery ID is already in use");const a=r.find(t=>t.address===e);n(i=>({...i,batteries:[...i.batteries,{device:e,name:a?a.name:"",batteryId:t}]})),v(""),u(`Added ${a?a.name:e}`)},style:{padding:"8px 18px"}},"Add")):null,c?s("div",{style:{marginTop:"8px"}},c):null),s("div",{style:B},s("label",{style:_},"Poll interval"),s("input",{style:{...f,maxWidth:"120px"},type:"number",min:1,max:60,value:i.pollInterval,onChange:e=>x("pollInterval",Number(e.target.value))}),s("span",{style:{marginLeft:"8px"}},"seconds")),s("div",{style:B},s("label",{style:_},"Published data"),...[["publishBasic","Basic battery data"],["publishCells","Cell voltages"],["publishTemperatures","Temperatures"],["publishStatus","MOSFET / status"],["publishCapacity","Capacity / cycles"],["publishBalancing","Balancing"],["publishAlarms","Alarms / errors"]].map(([e,t])=>s("label",{key:e,style:{display:"block",margin:"7px 0"}},s("input",{type:"checkbox",checked:i[e],onChange:t=>x(e,t.target.checked)}),s("span",{style:{marginLeft:"8px"}},t)))),s("button",{type:"button",onClick:function(){const e={...i,batteries:i.batteries.map(e=>({device:String(e.device||"").trim(),name:String(e.name||"").trim(),batteryId:String(e.batteryId||"").trim()})).filter(e=>e.device&&e.batteryId)};t(e)},style:{padding:"9px 22px",fontWeight:"600"}},"Save"))}e.exports=n,e.exports.default=n}}]);
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/*! For license information please see 540.bundle.js.LICENSE.txt */
|
|
2
|
+
"use strict";(self.webpackChunksignalk_daly_bms_ble=self.webpackChunksignalk_daly_bms_ble||[]).push([[540],{869(e,t){var n=Symbol.for("react.transitional.element"),r=Symbol.for("react.portal"),o=Symbol.for("react.fragment"),u=Symbol.for("react.strict_mode"),s=Symbol.for("react.profiler"),c=Symbol.for("react.consumer"),i=Symbol.for("react.context"),a=Symbol.for("react.forward_ref"),f=Symbol.for("react.suspense"),l=Symbol.for("react.memo"),p=Symbol.for("react.lazy"),y=Symbol.for("react.activity"),d=Symbol.iterator,h={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},_=Object.assign,b={};function m(e,t,n){this.props=e,this.context=t,this.refs=b,this.updater=n||h}function v(){}function S(e,t,n){this.props=e,this.context=t,this.refs=b,this.updater=n||h}m.prototype.isReactComponent={},m.prototype.setState=function(e,t){if("object"!=typeof e&&"function"!=typeof e&&null!=e)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,e,t,"setState")},m.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,"forceUpdate")},v.prototype=m.prototype;var E=S.prototype=new v;E.constructor=S,_(E,m.prototype),E.isPureReactComponent=!0;var g=Array.isArray;function w(){}var k={H:null,A:null,T:null,S:null},H=Object.prototype.hasOwnProperty;function j(e,t,r){var o=r.ref;return{$$typeof:n,type:e,key:t,ref:void 0!==o?o:null,props:r}}function C(e){return"object"==typeof e&&null!==e&&e.$$typeof===n}var R=/\/+/g;function $(e,t){return"object"==typeof e&&null!==e&&null!=e.key?(n=""+e.key,r={"=":"=0",":":"=2"},"$"+n.replace(/[=:]/g,function(e){return r[e]})):t.toString(36);var n,r}function T(e,t,o,u,s){var c=typeof e;"undefined"!==c&&"boolean"!==c||(e=null);var i,a,f=!1;if(null===e)f=!0;else switch(c){case"bigint":case"string":case"number":f=!0;break;case"object":switch(e.$$typeof){case n:case r:f=!0;break;case p:return T((f=e._init)(e._payload),t,o,u,s)}}if(f)return s=s(e),f=""===u?"."+$(e,0):u,g(s)?(o="",null!=f&&(o=f.replace(R,"$&/")+"/"),T(s,t,o,"",function(e){return e})):null!=s&&(C(s)&&(i=s,a=o+(null==s.key||e&&e.key===s.key?"":(""+s.key).replace(R,"$&/")+"/")+f,s=j(i.type,a,i.props)),t.push(s)),1;f=0;var l,y=""===u?".":u+":";if(g(e))for(var h=0;h<e.length;h++)f+=T(u=e[h],t,o,c=y+$(u,h),s);else if("function"==typeof(h=null===(l=e)||"object"!=typeof l?null:"function"==typeof(l=d&&l[d]||l["@@iterator"])?l:null))for(e=h.call(e),h=0;!(u=e.next()).done;)f+=T(u=u.value,t,o,c=y+$(u,h++),s);else if("object"===c){if("function"==typeof e.then)return T(function(e){switch(e.status){case"fulfilled":return e.value;case"rejected":throw e.reason;default:switch("string"==typeof e.status?e.then(w,w):(e.status="pending",e.then(function(t){"pending"===e.status&&(e.status="fulfilled",e.value=t)},function(t){"pending"===e.status&&(e.status="rejected",e.reason=t)})),e.status){case"fulfilled":return e.value;case"rejected":throw e.reason}}throw e}(e),t,o,u,s);throw t=String(e),Error("Objects are not valid as a React child (found: "+("[object Object]"===t?"object with keys {"+Object.keys(e).join(", ")+"}":t)+"). If you meant to render a collection of children, use an array instead.")}return f}function x(e,t,n){if(null==e)return e;var r=[],o=0;return T(e,r,"","",function(e){return t.call(n,e,o++)}),r}function A(e){if(-1===e._status){var t=e._result;(t=t()).then(function(t){0!==e._status&&-1!==e._status||(e._status=1,e._result=t)},function(t){0!==e._status&&-1!==e._status||(e._status=2,e._result=t)}),-1===e._status&&(e._status=0,e._result=t)}if(1===e._status)return e._result.default;throw e._result}var O="function"==typeof reportError?reportError:function(e){if("object"==typeof window&&"function"==typeof window.ErrorEvent){var t=new window.ErrorEvent("error",{bubbles:!0,cancelable:!0,message:"object"==typeof e&&null!==e&&"string"==typeof e.message?String(e.message):String(e),error:e});if(!window.dispatchEvent(t))return}else if("object"==typeof process&&"function"==typeof process.emit)return void process.emit("uncaughtException",e);console.error(e)},I={map:x,forEach:function(e,t,n){x(e,function(){t.apply(this,arguments)},n)},count:function(e){var t=0;return x(e,function(){t++}),t},toArray:function(e){return x(e,function(e){return e})||[]},only:function(e){if(!C(e))throw Error("React.Children.only expected to receive a single React element child.");return e}};t.Activity=y,t.Children=I,t.Component=m,t.Fragment=o,t.Profiler=s,t.PureComponent=S,t.StrictMode=u,t.Suspense=f,t.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE=k,t.__COMPILER_RUNTIME={__proto__:null,c:function(e){return k.H.useMemoCache(e)}},t.cache=function(e){return function(){return e.apply(null,arguments)}},t.cacheSignal=function(){return null},t.cloneElement=function(e,t,n){if(null==e)throw Error("The argument must be a React element, but you passed "+e+".");var r=_({},e.props),o=e.key;if(null!=t)for(u in void 0!==t.key&&(o=""+t.key),t)!H.call(t,u)||"key"===u||"__self"===u||"__source"===u||"ref"===u&&void 0===t.ref||(r[u]=t[u]);var u=arguments.length-2;if(1===u)r.children=n;else if(1<u){for(var s=Array(u),c=0;c<u;c++)s[c]=arguments[c+2];r.children=s}return j(e.type,o,r)},t.createContext=function(e){return(e={$$typeof:i,_currentValue:e,_currentValue2:e,_threadCount:0,Provider:null,Consumer:null}).Provider=e,e.Consumer={$$typeof:c,_context:e},e},t.createElement=function(e,t,n){var r,o={},u=null;if(null!=t)for(r in void 0!==t.key&&(u=""+t.key),t)H.call(t,r)&&"key"!==r&&"__self"!==r&&"__source"!==r&&(o[r]=t[r]);var s=arguments.length-2;if(1===s)o.children=n;else if(1<s){for(var c=Array(s),i=0;i<s;i++)c[i]=arguments[i+2];o.children=c}if(e&&e.defaultProps)for(r in s=e.defaultProps)void 0===o[r]&&(o[r]=s[r]);return j(e,u,o)},t.createRef=function(){return{current:null}},t.forwardRef=function(e){return{$$typeof:a,render:e}},t.isValidElement=C,t.lazy=function(e){return{$$typeof:p,_payload:{_status:-1,_result:e},_init:A}},t.memo=function(e,t){return{$$typeof:l,type:e,compare:void 0===t?null:t}},t.startTransition=function(e){var t=k.T,n={};k.T=n;try{var r=e(),o=k.S;null!==o&&o(n,r),"object"==typeof r&&null!==r&&"function"==typeof r.then&&r.then(w,O)}catch(e){O(e)}finally{null!==t&&null!==n.types&&(t.types=n.types),k.T=t}},t.unstable_useCacheRefresh=function(){return k.H.useCacheRefresh()},t.use=function(e){return k.H.use(e)},t.useActionState=function(e,t,n){return k.H.useActionState(e,t,n)},t.useCallback=function(e,t){return k.H.useCallback(e,t)},t.useContext=function(e){return k.H.useContext(e)},t.useDebugValue=function(){},t.useDeferredValue=function(e,t){return k.H.useDeferredValue(e,t)},t.useEffect=function(e,t){return k.H.useEffect(e,t)},t.useEffectEvent=function(e){return k.H.useEffectEvent(e)},t.useId=function(){return k.H.useId()},t.useImperativeHandle=function(e,t,n){return k.H.useImperativeHandle(e,t,n)},t.useInsertionEffect=function(e,t){return k.H.useInsertionEffect(e,t)},t.useLayoutEffect=function(e,t){return k.H.useLayoutEffect(e,t)},t.useMemo=function(e,t){return k.H.useMemo(e,t)},t.useOptimistic=function(e,t){return k.H.useOptimistic(e,t)},t.useReducer=function(e,t,n){return k.H.useReducer(e,t,n)},t.useRef=function(e){return k.H.useRef(e)},t.useState=function(e){return k.H.useState(e)},t.useSyncExternalStore=function(e,t,n){return k.H.useSyncExternalStore(e,t,n)},t.useTransition=function(){return k.H.useTransition()},t.version="19.2.8"},540(e,t,n){e.exports=n(869)}}]);
|
package/public/bundle.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(()=>{var e={237(e,t,n){const r=n(641),{useState:a}=r,i=r.createElement;function s({configuration:e={},save:t}){const n=Array.isArray(e.batteries)&&e.batteries.length?e.batteries:e.device?[{device:e.device,name:"",batteryId:e.batteryId||"house"}]:[],[r,s]=a({batteries:n.map(e=>({device:e.device||"",name:e.name||"",batteryId:e.batteryId||""})),pollInterval:e.pollInterval||5,publishBasic:!1!==e.publishBasic,publishCells:!1!==e.publishCells,publishTemperatures:!1!==e.publishTemperatures,publishStatus:!1!==e.publishStatus,publishCapacity:!1!==e.publishCapacity,publishBalancing:!1!==e.publishBalancing,publishAlarms:!1!==e.publishAlarms}),[o,l]=a([]),[c,u]=a(!1),[p,d]=a(""),[h,f]=a(""),[b,m]=a(""),[g,y]=a("");function v(e,t){s(n=>({...n,[e]:t}))}const x={width:"100%",maxWidth:"550px",padding:"8px",marginTop:"5px"},k={width:"260px",padding:"8px"},C={display:"block",fontWeight:"600",marginBottom:"4px"},S={marginBottom:"24px"};return i("div",{style:{maxWidth:"850px"}},i("div",{style:S},i("label",{style:C},"Configured Daly devices"),0===r.batteries.length?i("div",{style:{marginBottom:"10px"}},"No Daly devices configured."):null,...r.batteries.map((e,t)=>i("div",{key:`${e.device}-${t}`,style:{display:"flex",gap:"10px",alignItems:"center",marginBottom:"10px",padding:"10px",border:"1px solid #ccc"}},i("div",{style:{flex:"1"}},i("div",{style:{fontWeight:"600"}},e.name?`${e.name} (${e.device})`:e.device)),i("input",{style:k,type:"text",value:e.batteryId,placeholder:"house.balancer",onChange:e=>function(e,t,n){s(r=>{const a=[...r.batteries];return a[e]={...a[e],[t]:n},{...r,batteries:a}})}(t,"batteryId",e.target.value)}),i("button",{type:"button",onClick:()=>function(e){s(t=>({...t,batteries:t.batteries.filter((t,n)=>n!==e)}))}(t),style:{padding:"8px 12px"}},"Remove")))),i("div",{style:S},i("label",{style:C},"Add Daly Bluetooth device"),i("button",{type:"button",onClick:async function(){u(!0),d("Scanning for Daly BMS devices...");try{const e=await fetch("/plugins/signalk-daly-bms-ble/scan",{credentials:"include"});if(!e.ok)throw new Error(`HTTP ${e.status}`);const t=(await e.json()).devices||[];l(t),t.length?(f(t[0].address),d(`Found ${t.length} Daly BMS device(s)`)):(f("__custom__"),d("No Daly BMS devices found"))}catch(e){d(`Scan failed: ${e.message}`)}finally{u(!1)}},disabled:c,style:{padding:"8px 18px",marginBottom:"10px"}},c?"Scanning...":"Scan"),o.length>0?i("div",{style:{marginBottom:"10px"}},i("select",{style:x,value:h,onChange:e=>f(e.target.value)},...o.map(e=>i("option",{key:e.address,value:e.address},`${e.name} (${e.address})`)),i("option",{value:"__custom__"},"Custom MAC"))):null,"__custom__"===h?i("input",{style:x,type:"text",value:b,placeholder:"XX:XX:XX:XX:XX:XX",onChange:e=>m(e.target.value)}):null,o.length>0||"__custom__"===h?i("div",{style:{display:"flex",gap:"8px",alignItems:"center",marginTop:"10px"}},i("input",{style:k,type:"text",value:g,placeholder:"Battery ID, e.g. house.balancer",onChange:e=>y(e.target.value)}),i("button",{type:"button",onClick:function(){const e="__custom__"===h?b.trim():h;if(!e)return void d("Select a Daly device or enter a custom MAC");const t=g.trim();if(!t)return void d("Enter Battery ID before adding the device");if(r.batteries.some(t=>t.device.toUpperCase()===e.toUpperCase()))return void d("That Daly device is already configured");if(r.batteries.some(e=>e.batteryId===t))return void d("That Battery ID is already in use");const n=o.find(t=>t.address===e);s(r=>({...r,batteries:[...r.batteries,{device:e,name:n?n.name:"",batteryId:t}]})),y(""),d(`Added ${n?n.name:e}`)},style:{padding:"8px 18px"}},"Add")):null,p?i("div",{style:{marginTop:"8px"}},p):null),i("div",{style:S},i("label",{style:C},"Poll interval"),i("input",{style:{...x,maxWidth:"120px"},type:"number",min:1,max:60,value:r.pollInterval,onChange:e=>v("pollInterval",Number(e.target.value))}),i("span",{style:{marginLeft:"8px"}},"seconds")),i("div",{style:S},i("label",{style:C},"Published data"),...[["publishBasic","Basic battery data"],["publishCells","Cell voltages"],["publishTemperatures","Temperatures"],["publishStatus","MOSFET / status"],["publishCapacity","Capacity / cycles"],["publishBalancing","Balancing"],["publishAlarms","Alarms / errors"]].map(([e,t])=>i("label",{key:e,style:{display:"block",margin:"7px 0"}},i("input",{type:"checkbox",checked:r[e],onChange:t=>v(e,t.target.checked)}),i("span",{style:{marginLeft:"8px"}},t)))),i("button",{type:"button",onClick:function(){const e={...r,batteries:r.batteries.map(e=>({device:String(e.device||"").trim(),name:String(e.name||"").trim(),batteryId:String(e.batteryId||"").trim()})).filter(e=>e.device&&e.batteryId)};t(e)},style:{padding:"9px 22px",fontWeight:"600"}},"Save"))}e.exports=s,e.exports.default=s},996(e,t,n){n(237)}};const t={};function n(r){const a=t[r];if(void 0!==a)return a.exports;const i=t[r]={exports:{}};return e[r](i,i.exports,n),i.exports}n.m=e,n.c=t,n.f={},n.e=e=>Promise.all(Object.keys(n.f).reduce((t,r)=>(n.f[r](e,t),t),[])),n.u=e=>e+".bundle.js",n.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),n.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),(()=>{const e={},t="signalk-daly-bms-ble:";n.l=(r,a,i,s)=>{if(e[r])return void e[r].push(a);let o,l;if(void 0!==i){const e=document.getElementsByTagName("script");for(var c=0;c<e.length;c++){const n=e[c];if(n.getAttribute("src")==r||n.getAttribute("data-webpack")==t+i){o=n;break}}}o||(l=!0,o=document.createElement("script"),o.charset="utf-8",n.nc&&o.setAttribute("nonce",n.nc),o.setAttribute("data-webpack",t+i),o.src=r),e[r]=[a];const u=(t,n)=>{o.onerror=o.onload=null,clearTimeout(p);const a=e[r];if(delete e[r],o.parentNode?.removeChild(o),a?.forEach(e=>e(n)),t)return t(n)},p=setTimeout(u.bind(null,void 0,{type:"timeout",target:o}),12e4);o.onerror=u.bind(null,o.onerror),o.onload=u.bind(null,o.onload),l&&document.head.appendChild(o)}})(),(()=>{n.S={};const e={},t={};n.I=(r,a)=>{a||(a=[]);let i=t[r];if(i||(i=t[r]={}),a.indexOf(i)>=0)return;if(a.push(i),e[r])return e[r];n.o(n.S,r)||(n.S[r]={});const s=n.S[r],o="signalk-daly-bms-ble",l=[];return"default"===r&&((e,t,r,a)=>{const i=s[e]=s[e]||{},l=i[t];(!l||!l.loaded&&(1!=!l.eager?a:o>l.from))&&(i[t]={get:()=>n.e(540).then(()=>()=>n(540)),from:o,eager:!1})})("react","19.2.8"),e[r]=l.length?Promise.all(l).then(()=>e[r]=1):1}})(),(()=>{let e;n.g.importScripts&&(e=n.g.location+"");const t=n.g.document;if(!e&&t&&("SCRIPT"===t.currentScript?.tagName.toUpperCase()&&(e=t.currentScript.src),!e)){const n=t.getElementsByTagName("script");if(n.length){let t=n.length-1;for(;t>-1&&(!e||!/^https?:/.test(e));)e=n[t--].src}}if(!e)throw new Error("Automatic publicPath is not supported in this browser");e=e.replace(/^blob:|[?#].*$/g,"").replace(/\/[^/]+$/,"/"),n.p=e})(),(()=>{var e=e=>{var t=e=>e.split(".").map(e=>+e==e?+e:e),n=/^([^-+]+)?(?:-([^+]+))?(?:\+(.+))?$/.exec(e),r=n[1]?t(n[1]):[];return n[2]&&(r.length++,r.push.apply(r,t(n[2]))),n[3]&&(r.push([]),r.push.apply(r,t(n[3]))),r},t=e=>{var n=e[0],r="";if(1===e.length)return"*";if(n+.5){r+=0==n?">=":-1==n?"<":1==n?"^":2==n?"~":n>0?"=":"!=";for(var a=1,i=1;i<e.length;i++)a--,r+="u"==(typeof(o=e[i]))[0]?"-":(a>0?".":"")+(a=2,o);return r}var s=[];for(i=1;i<e.length;i++){var o=e[i];s.push(0===o?"not("+l()+")":1===o?"("+l()+" || "+l()+")":2===o?s.pop()+" "+s.pop():t(o))}return l();function l(){return s.pop().replace(/^\((.+)\)$/,"$1")}},r=(t,n)=>{if(0 in t){n=e(n);var a=t[0],i=a<0;i&&(a=-a-1);for(var s=0,o=1,l=!0;;o++,s++){var c,u,p=o<t.length?(typeof t[o])[0]:"";if(s>=n.length||"o"==(u=(typeof(c=n[s]))[0]))return!l||("u"==p?o>a&&!i:""==p!=i);if("u"==u){if(!l||"u"!=p)return!1}else if(l)if(p==u)if(o<=a){if(c!=t[o])return!1}else{if(i?c>t[o]:c<t[o])return!1;c!=t[o]&&(l=!1)}else if("s"!=p&&"n"!=p){if(i||o<=a)return!1;l=!1,o--}else{if(o<=a||u<p!=i)return!1;l=!1}else"s"!=p&&"n"!=p&&(l=!1,o--)}}var d=[],h=d.pop.bind(d);for(s=1;s<t.length;s++){var f=t[s];d.push(1==f?h()|h():2==f?h()&h():f?r(f,n):!h())}return!!h()};const a=(t,n,r)=>{const a=r?(e=>Object.keys(e).reduce((t,n)=>(e[n].eager&&(t[n]=e[n]),t),{}))(t[n]):t[n];return Object.keys(a).reduce((t,n)=>!t||!a[t].loaded&&((t,n)=>{t=e(t),n=e(n);for(var r=0;;){if(r>=t.length)return r<n.length&&"u"!=(typeof n[r])[0];var a=t[r],i=(typeof a)[0];if(r>=n.length)return"u"==i;var s=n[r],o=(typeof s)[0];if(i!=o)return"o"==i&&"n"==o||"s"==o||"u"==i;if("o"!=i&&"u"!=i&&a!=s)return a<s;r++}})(t,n)?n:t,0)},i=(e,t,n)=>n?n():((e,t)=>(e=>{throw new Error(e)})("Shared module "+t+" doesn't exist in shared scope "+e))(e,t),s=(e=>function(t,r,a,i,s){const o=n.I(t);return o?.then&&!a?o.then(e.bind(e,t,n.S[t],r,!1,i,s)):e(t,n.S[t],r,a,i,s)})((e,s,o,l,c,u)=>{if(!((e,t)=>e&&n.o(e,t))(s,o))return i(e,o,u);const p=a(s,o,l);return r(c,p)||(h=((e,n,r,a)=>"Unsatisfied version "+r+" from "+(r&&e[n][r].from)+" of shared singleton module "+n+" (required "+t(a)+")")(s,o,p,c),"undefined"!=typeof console&&console.warn&&console.warn(h)),(d=s[o][p]).loaded=1,d.get();var d,h}),o={},l={641:()=>s("default","react",!1,[1,19,2,8],()=>n.e(540).then(()=>()=>n(540)))};[641].forEach(e=>{n.m[e]=t=>{o[e]=0,delete n.c[e];const r=l[e]();if("function"!=typeof r)throw new Error("Shared module is not available for eager consumption: "+e);t.exports=r()}});const c={792:[641]},u={};n.f.consumes=(e,t)=>{n.o(c,e)&&c[e].forEach(e=>{if(n.o(o,e))return t.push(o[e]);if(!u[e]){const r=t=>{o[e]=0,n.m[e]=r=>{delete n.c[e],r.exports=t()}};u[e]=!0;const a=t=>{delete o[e],n.m[e]=r=>{throw delete n.c[e],t}};try{const n=l[e]();n.then?t.push(o[e]=n.then(r).catch(a)):r(n)}catch(e){a(e)}}})}})(),(()=>{const e={237:0,792:0};n.f.j=(t,r)=>{let a=n.o(e,t)?e[t]:void 0;if(0!==a)if(a)r.push(a[2]);else{const i=new Promise((n,r)=>a=e[t]=[n,r]);r.push(a[2]=i);const s=new Error,o=r=>{if(n.o(e,t)&&(a=e[t],0!==a&&(e[t]=void 0),a)){const e=r&&("load"===r.type?"missing":r.type),n=r&&r.target&&r.target.src;s.message="Loading chunk "+t+" failed.\n("+e+": "+n+")",s.name="ChunkLoadError",s.type=e,s.request=n,s.event=r,a[1](s)}};n.l(n.p+n.u(t),o,"chunk-"+t,t)}};const t=(t,r)=>{let[a,i,s]=r;var o,l,c=0;if(a.some(t=>0!==e[t])){for(o in i)n.o(i,o)&&(n.m[o]=i[o]);s&&s(n)}for(t&&t(r);c<a.length;c++)l=a[c],n.o(e,l)&&e[l]&&e[l][0](),e[l]=0},r=self.webpackChunksignalk_daly_bms_ble=self.webpackChunksignalk_daly_bms_ble||[];r.forEach(t.bind(null,0)),r.push=t.bind(null,r.push.bind(r))})(),n(996)})();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var signalk_daly_bms_ble;(()=>{"use strict";var e={76(e,t,r){const n={"./PluginConfigurationPanel":()=>r.e(237).then(()=>()=>r(237))},o=(e,t)=>(r.R=t,t=r.o(n,e)?n[e]():Promise.resolve().then(()=>{throw new Error('Module "'+e+'" does not exist in container.')}),r.R=void 0,t),i=(e,t)=>{if(!r.S)return;const n="default",o=r.S[n];if(o&&o!==e)throw new Error("Container initialization failed as it has already been initialized with a different share scope");return r.S[n]=e,r.I(n,t)};r.d(t,{get:()=>o,init:()=>i})}};const t={};function r(n){const o=t[n];if(void 0!==o)return o.exports;const i=t[n]={exports:{}};return e[n](i,i.exports,r),i.exports}r.m=e,r.c=t,r.d=(e,t)=>{for(var n in t)r.o(t,n)&&!r.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce((t,n)=>(r.f[n](e,t),t),[])),r.u=e=>e+".bundle.js",r.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),(()=>{const e={},t="signalk-daly-bms-ble:";r.l=(n,o,i,s)=>{if(e[n])return void e[n].push(o);let a,l;if(void 0!==i){const e=document.getElementsByTagName("script");for(var u=0;u<e.length;u++){const r=e[u];if(r.getAttribute("src")==n||r.getAttribute("data-webpack")==t+i){a=r;break}}}a||(l=!0,a=document.createElement("script"),a.charset="utf-8",r.nc&&a.setAttribute("nonce",r.nc),a.setAttribute("data-webpack",t+i),a.src=n),e[n]=[o];const c=(t,r)=>{a.onerror=a.onload=null,clearTimeout(f);const o=e[n];if(delete e[n],a.parentNode?.removeChild(a),o?.forEach(e=>e(r)),t)return t(r)},f=setTimeout(c.bind(null,void 0,{type:"timeout",target:a}),12e4);a.onerror=c.bind(null,a.onerror),a.onload=c.bind(null,a.onload),l&&document.head.appendChild(a)}})(),(()=>{r.S={};const e={},t={};r.I=(n,o)=>{o||(o=[]);let i=t[n];if(i||(i=t[n]={}),o.indexOf(i)>=0)return;if(o.push(i),e[n])return e[n];r.o(r.S,n)||(r.S[n]={});const s=r.S[n],a="signalk-daly-bms-ble",l=[];return"default"===n&&((e,t,n,o)=>{const i=s[e]=s[e]||{},l=i[t];(!l||!l.loaded&&(1!=!l.eager?o:a>l.from))&&(i[t]={get:()=>r.e(540).then(()=>()=>r(540)),from:a,eager:!1})})("react","19.2.8"),e[n]=l.length?Promise.all(l).then(()=>e[n]=1):1}})(),(()=>{let e;r.g.importScripts&&(e=r.g.location+"");const t=r.g.document;if(!e&&t&&("SCRIPT"===t.currentScript?.tagName.toUpperCase()&&(e=t.currentScript.src),!e)){const r=t.getElementsByTagName("script");if(r.length){let t=r.length-1;for(;t>-1&&(!e||!/^https?:/.test(e));)e=r[t--].src}}if(!e)throw new Error("Automatic publicPath is not supported in this browser");e=e.replace(/^blob:|[?#].*$/g,"").replace(/\/[^/]+$/,"/"),r.p=e})(),(()=>{var e=e=>{var t=e=>e.split(".").map(e=>+e==e?+e:e),r=/^([^-+]+)?(?:-([^+]+))?(?:\+(.+))?$/.exec(e),n=r[1]?t(r[1]):[];return r[2]&&(n.length++,n.push.apply(n,t(r[2]))),r[3]&&(n.push([]),n.push.apply(n,t(r[3]))),n},t=e=>{var r=e[0],n="";if(1===e.length)return"*";if(r+.5){n+=0==r?">=":-1==r?"<":1==r?"^":2==r?"~":r>0?"=":"!=";for(var o=1,i=1;i<e.length;i++)o--,n+="u"==(typeof(a=e[i]))[0]?"-":(o>0?".":"")+(o=2,a);return n}var s=[];for(i=1;i<e.length;i++){var a=e[i];s.push(0===a?"not("+l()+")":1===a?"("+l()+" || "+l()+")":2===a?s.pop()+" "+s.pop():t(a))}return l();function l(){return s.pop().replace(/^\((.+)\)$/,"$1")}},n=(t,r)=>{if(0 in t){r=e(r);var o=t[0],i=o<0;i&&(o=-o-1);for(var s=0,a=1,l=!0;;a++,s++){var u,c,f=a<t.length?(typeof t[a])[0]:"";if(s>=r.length||"o"==(c=(typeof(u=r[s]))[0]))return!l||("u"==f?a>o&&!i:""==f!=i);if("u"==c){if(!l||"u"!=f)return!1}else if(l)if(f==c)if(a<=o){if(u!=t[a])return!1}else{if(i?u>t[a]:u<t[a])return!1;u!=t[a]&&(l=!1)}else if("s"!=f&&"n"!=f){if(i||a<=o)return!1;l=!1,a--}else{if(a<=o||c<f!=i)return!1;l=!1}else"s"!=f&&"n"!=f&&(l=!1,a--)}}var d=[],p=d.pop.bind(d);for(s=1;s<t.length;s++){var h=t[s];d.push(1==h?p()|p():2==h?p()&p():h?n(h,r):!p())}return!!p()};const o=(t,r,n)=>{const o=n?(e=>Object.keys(e).reduce((t,r)=>(e[r].eager&&(t[r]=e[r]),t),{}))(t[r]):t[r];return Object.keys(o).reduce((t,r)=>!t||!o[t].loaded&&((t,r)=>{t=e(t),r=e(r);for(var n=0;;){if(n>=t.length)return n<r.length&&"u"!=(typeof r[n])[0];var o=t[n],i=(typeof o)[0];if(n>=r.length)return"u"==i;var s=r[n],a=(typeof s)[0];if(i!=a)return"o"==i&&"n"==a||"s"==a||"u"==i;if("o"!=i&&"u"!=i&&o!=s)return o<s;n++}})(t,r)?r:t,0)},i=(e,t,r)=>r?r():((e,t)=>(e=>{throw new Error(e)})("Shared module "+t+" doesn't exist in shared scope "+e))(e,t),s=(e=>function(t,n,o,i,s){const a=r.I(t);return a?.then&&!o?a.then(e.bind(e,t,r.S[t],n,!1,i,s)):e(t,r.S[t],n,o,i,s)})((e,s,a,l,u,c)=>{if(!((e,t)=>e&&r.o(e,t))(s,a))return i(e,a,c);const f=o(s,a,l);return n(u,f)||(p=((e,r,n,o)=>"Unsatisfied version "+n+" from "+(n&&e[r][n].from)+" of shared singleton module "+r+" (required "+t(o)+")")(s,a,f,u),"undefined"!=typeof console&&console.warn&&console.warn(p)),(d=s[a][f]).loaded=1,d.get();var d,p}),a={},l={641:()=>s("default","react",!1,[1,19,2,8],()=>r.e(540).then(()=>()=>r(540)))},u={237:[641]},c={};r.f.consumes=(e,t)=>{r.o(u,e)&&u[e].forEach(e=>{if(r.o(a,e))return t.push(a[e]);if(!c[e]){const n=t=>{a[e]=0,r.m[e]=n=>{delete r.c[e],n.exports=t()}};c[e]=!0;const o=t=>{delete a[e],r.m[e]=n=>{throw delete r.c[e],t}};try{const r=l[e]();r.then?t.push(a[e]=r.then(n).catch(o)):n(r)}catch(e){o(e)}}})}})(),(()=>{const e={772:0};r.f.j=(t,n)=>{let o=r.o(e,t)?e[t]:void 0;if(0!==o)if(o)n.push(o[2]);else{const i=new Promise((r,n)=>o=e[t]=[r,n]);n.push(o[2]=i);const s=new Error,a=n=>{if(r.o(e,t)&&(o=e[t],0!==o&&(e[t]=void 0),o)){const e=n&&("load"===n.type?"missing":n.type),r=n&&n.target&&n.target.src;s.message="Loading chunk "+t+" failed.\n("+e+": "+r+")",s.name="ChunkLoadError",s.type=e,s.request=r,s.event=n,o[1](s)}};r.l(r.p+r.u(t),a,"chunk-"+t,t)}};const t=(t,n)=>{let[o,i,s]=n;var a,l,u=0;if(o.some(t=>0!==e[t])){for(a in i)r.o(i,a)&&(r.m[a]=i[a]);s&&s(r)}for(t&&t(n);u<o.length;u++)l=o[u],r.o(e,l)&&e[l]&&e[l][0](),e[l]=0},n=self.webpackChunksignalk_daly_bms_ble=self.webpackChunksignalk_daly_bms_ble||[];n.forEach(t.bind(null,0)),n.push=t.bind(null,n.push.bind(n))})();let n=r(76);signalk_daly_bms_ble=n})();
|