Switched from CAN connection to UART for correctly fetch the datas.

This commit is contained in:
Giuseppe Raffa
2026-06-06 13:42:32 +02:00
parent 91161d2d2c
commit 1d5bb340d9
6 changed files with 420 additions and 285 deletions

View File

@@ -1,6 +1,4 @@
/*
Modello di un singolo controller MPPT Poweren Boost.
Ogni istanza mantiene lo stato corrente del dispositivo (tensioni, correnti,
temperature, flag operativi, warnings) aggiornato dai frame CAN broadcast
(MSG1 status, MSG2 power) e dalle risposte alle richieste di lettura registro.
@@ -15,7 +13,25 @@ const {
registerAddresses,
} = require('./constants');
// Arrotondamenti convenzionali per esporre dati leggibili nei getter
/*
Mappa indirizzo registro -> campo di stato e fattore di conversione.
Usata da updateRegister per convertire il valore raw a 16 bit nel valore fisico.
I registri di flag (status1, warning) sono gestiti a parte perche' vanno decodificati a bit.
signed=true indica un intero a 16 bit con segno (complemento a 2), usato per le temperature.
*/
const numericRegisterMap = {
[registerAddresses.voltageInput]: { field: 'voltageInput', factor: conversionsFactors.voltageInput, signed: false },
[registerAddresses.currentInput]: { field: 'currentInput', factor: conversionsFactors.currentInput, signed: false },
[registerAddresses.voltageOutput]: { field: 'voltageOutput', factor: conversionsFactors.voltageOutput, signed: false },
[registerAddresses.currentOutput]: { field: 'currentOutput', factor: conversionsFactors.currentOutput, signed: false },
[registerAddresses.powerInput]: { field: 'powerInput', factor: conversionsFactors.powerInput, signed: false },
[registerAddresses.powerOutput]: { field: 'powerOutput', factor: conversionsFactors.powerOutput, signed: false },
[registerAddresses.chargeCapacity]: { field: 'chargeCapacity', factor: conversionsFactors.chargeCapacity, signed: false },
[registerAddresses.temperature1]: { field: 'temperature1', factor: conversionsFactors.temperaturePhase1, signed: true },
[registerAddresses.temperature2]: { field: 'temperature2', factor: conversionsFactors.temperaturePhase2, signed: true },
};
// Arrotondamenti
const roundedTo1 = (v) => Number(v.toFixed(1));
const roundedTo2 = (v) => Number(v.toFixed(2));
const roundedTo3 = (v) => Number(v.toFixed(3));
@@ -32,27 +48,14 @@ const decodeFlags = (value, map) => {
return active;
};
// Parsing del payload broadcast: 8 byte -> 4 uint16 big-endian
function parsePayload(data) {
if (!data || data.length < 8) return null;
return [
(data[0] << 8) | data[1],
(data[2] << 8) | data[3],
(data[4] << 8) | data[5],
(data[6] << 8) | data[7],
];
}
class MPPT {
constructor({
name,
rxID,
txIdBase,
address,
log = () => {},
} = {}) {
this.name = name; // identificativo logico (es. 'port', 'starboard')
this.rxID = rxID; // indirizzo CAN del dispositivo (registro Addr)
this.txIdBase = txIdBase; // ID base di trasmissione (broadcast su +1 e +2)
this.address = address; // indirizzo UART/DST del dispositivo (registro Addr)
this.log = log;
this.state = {
@@ -95,51 +98,34 @@ class MPPT {
return 'bulk';
}
// Aggiorna lo stato da un frame broadcast (MSG1=status, MSG2=power)
updateFromBroadcast(type, data) {
const regs = parsePayload(data);
if (!regs) return;
if (type === 'status') {
const [st1, , warn, chgCap] = regs;
this.state.status1Raw = st1;
this.state.warningRaw = warn;
this.state.chargeCapacity = chgCap / conversionsFactors.chargeCapacity;
this.state.flags = decodeFlags(st1, status1Flags);
this.state.warnings = decodeFlags(warn, errorsFlags);
/*
Aggiorna lo stato a partire dalla lettura UART di un singolo registro.
regAddr = indirizzo del registro letto
rawValue = valore raw a 16 bit ricevuto nella risposta (big-endian, gia' decodificato)
*/
updateRegister(regAddr, rawValue) {
if (regAddr === registerAddresses.status1) {
// Registro di stato St1: decodifica i bit in flag operativi
this.state.status1Raw = rawValue;
this.state.flags = decodeFlags(rawValue, status1Flags);
} else if (regAddr === registerAddresses.warning) {
// Registro Warning: decodifica i bit in codici di errore/warning
this.state.warningRaw = rawValue;
this.state.warnings = decodeFlags(rawValue, errorsFlags);
if (this.state.warnings.length) {
this.log(`[${this.name}] WARN: ${this.state.warnings.join(',')}`);
}
} else if (type === 'power') {
const [vi, ii, vo, io] = regs;
this.state.voltageInput = vi / conversionsFactors.voltageInput;
this.state.currentInput = ii / conversionsFactors.currentInput;
this.state.voltageOutput = vo / conversionsFactors.voltageOutput;
this.state.currentOutput = io / conversionsFactors.currentOutput;
} else {
// Registri numerici (tensioni, correnti, temperature, capacita')
const target = numericRegisterMap[regAddr];
if (!target) return;
let value = rawValue;
if (target.signed && value >= 0x8000) value -= 0x10000; // intero con segno
this.state[target.field] = value / target.factor;
}
this.state.lastUpdate = Date.now();
}
// Aggiorna lo stato a partire da una risposta a richiesta di registro
// Payload reply: [reg_addr, value_high, value_low]
updateFromRegisterReply(data) {
if (!data || data.length < 3) return;
const regAddr = data[0];
const rawValue = (data[1] << 8) | data[2];
const mapping = {
[registerAddresses.powerInput]: { field: 'powerInput', factor: conversionsFactors.powerInput },
[registerAddresses.powerOutput]: { field: 'powerOutput', factor: conversionsFactors.powerOutput },
[registerAddresses.temperature1]: { field: 'temperature1', factor: conversionsFactors.temperaturePhase1 },
[registerAddresses.temperature2]: { field: 'temperature2', factor: conversionsFactors.temperaturePhase2 },
};
const target = mapping[regAddr];
if (!target) return;
this.state[target.field] = rawValue / target.factor;
this.state.lastUpdate = Date.now();
}
// Costruisce gli update SignalK (path + value) a partire dallo stato corrente
// Tutti i valori sono in unita' SI (V, A, W, K, ratio, Ah)
buildSignalKUpdates() {
@@ -200,6 +186,5 @@ class MPPT {
module.exports = {
MPPT,
parsePayload,
decodeFlags,
};