Una repositoy che integra un plugin custom su SignalK per ottenere tutti i dati del BMS della batteria
- Introdotta l'implementazione JavaScript per la comunicazione BMS in bmscore.js, inclusi i metodi per il recupero dati e la gestione degli errori. - Creato errors.js per mappare i codici di errore dal formato Python a quello JavaScript.
This commit is contained in:
150
index.js
Normal file
150
index.js
Normal file
@@ -0,0 +1,150 @@
|
||||
// SignalK plugin — Daly BMS via USB/RS485 (solo USB).
|
||||
// Polling periodico → delta SignalK. POST read-only on-demand.
|
||||
'use strict';
|
||||
|
||||
const DalyProtocol = require('./lib/daly-protocol');
|
||||
|
||||
const DEFAULTS = Object.freeze({
|
||||
device: '/dev/ttyUSB0',
|
||||
batteryId: 'house',
|
||||
pollSocMs: 2000,
|
||||
pollStatusMs: 30000,
|
||||
pollCellsMs: 10000,
|
||||
retries: 5
|
||||
});
|
||||
|
||||
module.exports = function (app) {
|
||||
const plugin = {
|
||||
id: 'signalk-daly-bms',
|
||||
name: 'Daly BMS (USB)',
|
||||
description: 'Legge un BMS Daly via seriale USB/RS485 e pubblica su SignalK'
|
||||
};
|
||||
|
||||
plugin.schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
device: { type: 'string', default: DEFAULTS.device, title: 'Serial device (USB)' },
|
||||
batteryId: { type: 'string', default: DEFAULTS.batteryId, title: 'SignalK battery id' },
|
||||
pollSocMs: { type: 'integer', default: DEFAULTS.pollSocMs, title: 'Polling SOC/V/A (ms)' },
|
||||
pollStatusMs: { type: 'integer', default: DEFAULTS.pollStatusMs, title: 'Polling status (ms)' },
|
||||
pollCellsMs: { type: 'integer', default: DEFAULTS.pollCellsMs, title: 'Polling celle/temperature (ms)' },
|
||||
retries: { type: 'integer', default: DEFAULTS.retries, title: 'Retry per richiesta' }
|
||||
}
|
||||
};
|
||||
|
||||
let bms = null;
|
||||
let opts = null;
|
||||
const timers = new Set();
|
||||
|
||||
const sk = (path, value) => ({ path: `electrical.batteries.${opts.batteryId}.${path}`, value });
|
||||
const sendDelta = values => values && values.length && app.handleMessage(plugin.id, { updates: [{ values }] });
|
||||
|
||||
const safe = async (label, fn) => {
|
||||
try { return await fn(); }
|
||||
catch (e) { app.error(`${label}: ${e.message}`); return null; }
|
||||
};
|
||||
|
||||
// --- pollers ---
|
||||
async function pollSoc() {
|
||||
const s = await safe('pollSoc', () => bms.getSoc());
|
||||
if (!s) return;
|
||||
sendDelta([
|
||||
sk('voltage', s.total_voltage),
|
||||
sk('current', s.current), // SignalK: + = uscita (scarica), come Daly
|
||||
sk('capacity.stateOfCharge', s.soc_percent / 100)
|
||||
]);
|
||||
}
|
||||
|
||||
async function pollStatus() {
|
||||
const s = await safe('pollStatus', () => bms.getStatus());
|
||||
if (!s) return;
|
||||
sendDelta([
|
||||
sk('cycles', s.cycles),
|
||||
sk('cellsCount', s.cells),
|
||||
sk('chargerRunning', s.charger_running),
|
||||
sk('loadRunning', s.load_running)
|
||||
]);
|
||||
}
|
||||
|
||||
async function pollCells() {
|
||||
const [cells, temps, errs] = await Promise.all([
|
||||
safe('getCellVoltages', () => bms.getCellVoltages()),
|
||||
safe('getTemperatures', () => bms.getTemperatures()),
|
||||
safe('getErrors', () => bms.getErrors())
|
||||
]);
|
||||
const values = [];
|
||||
if (cells) for (const [k, v] of Object.entries(cells)) values.push(sk(`cells.${k}.voltage`, v));
|
||||
if (temps) {
|
||||
const list = Object.values(temps);
|
||||
const toK = c => Math.round((c + 273.15) * 100) / 100; // °C → K, 2 decimali
|
||||
if (list.length) values.push(sk('temperature', toK(list[0])));
|
||||
for (const [k, v] of Object.entries(temps)) values.push(sk(`temperatures.${k}`, toK(v)));
|
||||
}
|
||||
if (errs) values.push(sk('errors', errs));
|
||||
sendDelta(values);
|
||||
}
|
||||
|
||||
const startTimer = (fn, ms) => {
|
||||
const t = setInterval(() => { fn(); }, ms);
|
||||
timers.add(t);
|
||||
};
|
||||
|
||||
plugin.start = async function (options = {}) {
|
||||
opts = { ...DEFAULTS, ...options };
|
||||
app.setPluginStatus(`Apertura ${opts.device}…`);
|
||||
|
||||
bms = new DalyProtocol({
|
||||
device: opts.device,
|
||||
retries: opts.retries,
|
||||
log: msg => app.debug(msg)
|
||||
});
|
||||
|
||||
try {
|
||||
await bms.open();
|
||||
await bms.getStatus(); // necessario per cell/temp
|
||||
app.setPluginStatus(`Connesso a ${opts.device}`);
|
||||
} catch (e) {
|
||||
app.setPluginError(`init failed: ${e.message}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// primo giro immediato + polling periodico
|
||||
await Promise.all([pollStatus(), pollSoc(), pollCells()]);
|
||||
startTimer(pollSoc, opts.pollSocMs);
|
||||
startTimer(pollStatus, opts.pollStatusMs);
|
||||
startTimer(pollCells, opts.pollCellsMs);
|
||||
};
|
||||
|
||||
plugin.stop = async function () {
|
||||
for (const t of timers) clearInterval(t);
|
||||
timers.clear();
|
||||
if (bms) { await bms.close().catch(() => {}); bms = null; }
|
||||
app.setPluginStatus('stopped');
|
||||
};
|
||||
|
||||
// --- API REST: solo letture on-demand ---
|
||||
plugin.registerWithRouter = function (router) {
|
||||
const wrap = fn => async (_req, res) => {
|
||||
if (!bms) return res.status(503).json({ error: 'plugin not started' });
|
||||
try { res.json(await fn()); }
|
||||
catch (e) { res.status(500).json({ error: e.message }); }
|
||||
};
|
||||
router.post('/soc', wrap(() => bms.getSoc()));
|
||||
router.post('/status', wrap(() => bms.getStatus()));
|
||||
router.post('/temperatures', wrap(() => bms.getTemperatures()));
|
||||
router.post('/cell-voltages', wrap(() => bms.getCellVoltages()));
|
||||
router.post('/cell-range', wrap(() => bms.getCellVoltageRange()));
|
||||
router.post('/temp-range', wrap(() => bms.getTemperatureRange()));
|
||||
router.post('/mosfet', wrap(() => bms.getMosfetStatus()));
|
||||
router.post('/errors', wrap(() => bms.getErrors()));
|
||||
router.post('/all', wrap(async () => {
|
||||
const [soc, status, cell_voltages, temperatures, mosfet, errors] = await Promise.all([
|
||||
bms.getSoc(), bms.getStatus(), bms.getCellVoltages(),
|
||||
bms.getTemperatures(), bms.getMosfetStatus(), bms.getErrors()
|
||||
]);
|
||||
return { soc, status, cell_voltages, temperatures, mosfet, errors };
|
||||
}));
|
||||
};
|
||||
|
||||
return plugin;
|
||||
};
|
||||
Reference in New Issue
Block a user