Fixed for initial handshake to fetch data from panels.
This commit is contained in:
@@ -58,6 +58,7 @@ const errorsFlags = {
|
||||
qui i numeri. I valori confermati dalla documentazione sono indicati come "(confermato)".
|
||||
*/
|
||||
const registerAddresses = {
|
||||
deviceAddress: 1, // Addr - indirizzo del dispositivo (confermato: usato per discovery)
|
||||
status1: 4, // St1 - flag di stato (confermato: "registro 4")
|
||||
warning: 6, // Warn - flag di warning/errore (confermato: "registro 6")
|
||||
chargeCapacity: 7, // Chg_Cap - Ah caricati (da verificare)
|
||||
@@ -102,6 +103,7 @@ const serialProtocol = {
|
||||
startOfFrame: 0x42, // SOF
|
||||
sourceHost: 0xFF, // SRC host/GUI
|
||||
endOfFrame: 0x0D, // EOF
|
||||
addressAny: 0x00, // DST = qualsiasi dispositivo (usato per discovery)
|
||||
headerLength: 5, // SOF + DST + SRC + 0x00 + DLEN
|
||||
tailLength: 3, // CHK_HI + CHK_LO + EOF
|
||||
// Comandi (scritti nel registro 2)
|
||||
@@ -122,6 +124,12 @@ const serialDefaults = {
|
||||
dataBits: 8,
|
||||
parity: 'none',
|
||||
stopBits: 1,
|
||||
// Linee di controllo: alcuni adapter (es. FTDI FT232R) resettano il target all'apertura.
|
||||
// L'esempio di discovery Poweren funzionante le imposta entrambe a true.
|
||||
dtr: true,
|
||||
rts: true,
|
||||
// Tempo di assestamento dopo l'apertura della porta prima di iniziare a trasmettere
|
||||
openSettleMs: 250,
|
||||
};
|
||||
|
||||
// Timeout e retry per le transazioni sincrone (richiesta -> risposta)
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
Formato pacchetto Poweren:
|
||||
[SOF=0x42][DST][SRC=0xFF][0x00][DLEN][DATA...][CHK_HI][CHK_LO][EOF=0x0D]
|
||||
- Richiesta lettura registro: DATA = [reg] (DLEN=1)
|
||||
- Risposta: DATA = [val_hi, val_lo] (valore a 16 bit big-endian)
|
||||
- Risposta: DATA = [val_hi, val_lo] oppure [reg, val_hi, val_lo]
|
||||
- CHK = sum(DATA) & 0xFFFF in big-endian
|
||||
|
||||
Eventi emessi:
|
||||
@@ -26,6 +26,7 @@ const { SerialPort } = require('serialport');
|
||||
const { MPPT } = require('./mpptscore');
|
||||
const {
|
||||
pollRegisters,
|
||||
registerAddresses,
|
||||
serialProtocol,
|
||||
serialDefaults,
|
||||
transactionTiming,
|
||||
@@ -48,6 +49,8 @@ class MPPTReader extends EventEmitter {
|
||||
pollIntervalMs = 1000,
|
||||
timeoutMs = transactionTiming.timeoutMs,
|
||||
retries = transactionTiming.retries,
|
||||
dtr = serialDefaults.dtr,
|
||||
rts = serialDefaults.rts,
|
||||
log = () => {},
|
||||
} = {}) {
|
||||
super();
|
||||
@@ -56,6 +59,8 @@ class MPPTReader extends EventEmitter {
|
||||
this.pollIntervalMs = pollIntervalMs;
|
||||
this.timeoutMs = timeoutMs;
|
||||
this.retries = retries;
|
||||
this.dtr = dtr;
|
||||
this.rts = rts;
|
||||
this.log = log;
|
||||
|
||||
this.port = null;
|
||||
@@ -116,10 +121,14 @@ class MPPTReader extends EventEmitter {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// Disattiva DTR/RTS per non tenere l'MPPT in reset (alcuni adapter lo resettano)
|
||||
await this._setControlLines(false, false);
|
||||
// Imposta le linee DTR/RTS come da protocollo Poweren e attende l'assestamento
|
||||
// dell'adapter (l'FT232R può resettare il target all'apertura della porta).
|
||||
await this._setControlLines(this.dtr, this.rts);
|
||||
await this._delay(serialDefaults.openSettleMs);
|
||||
this.isOpen = true;
|
||||
this.reconnectAttempts = 0;
|
||||
// Invia la prima richiesta UART: l'MPPT Poweren risponde solo dopo una lettura.
|
||||
await this._discoverDevices();
|
||||
this.emit('open');
|
||||
this._startPolling();
|
||||
resolve();
|
||||
@@ -232,6 +241,34 @@ class MPPTReader extends EventEmitter {
|
||||
|
||||
// ---- transazione sincrona di lettura registro ----
|
||||
|
||||
/*
|
||||
Esegue una lettura iniziale del registro ADDR con DST=0x00, equivalente al test Python:
|
||||
42 00 ff 00 01 01 00 01 0d
|
||||
La discovery non deve impedire il polling degli indirizzi configurati: su bus con piu'
|
||||
MPPT, l'indirizzo broadcast puo' non essere utilizzabile per collisione delle risposte.
|
||||
*/
|
||||
async _discoverDevices() {
|
||||
try {
|
||||
const discoveredDeviceAddress = await this._readRegister(
|
||||
serialProtocol.addressAny,
|
||||
registerAddresses.deviceAddress,
|
||||
);
|
||||
this.log(`[reader] richiesta iniziale UART OK, indirizzo MPPT rilevato: ${discoveredDeviceAddress}`);
|
||||
|
||||
if (this.mppts.size === 1) {
|
||||
const [configuredMppt] = this.mppts.values();
|
||||
if (configuredMppt.address !== discoveredDeviceAddress) {
|
||||
this.log(
|
||||
`[reader] MPPT ${configuredMppt.name}: indirizzo configurato ${configuredMppt.address}, ` +
|
||||
`indirizzo rilevato ${discoveredDeviceAddress}; continuo con la configurazione SignalK`,
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
this.log(`[reader] richiesta iniziale UART fallita: ${err.message}; continuo con il polling configurato`);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Legge un singolo registro dall'MPPT indicato (DST=address).
|
||||
Ritorna il valore raw a 16 bit. Esegue retry in caso di timeout.
|
||||
@@ -241,10 +278,7 @@ class MPPTReader extends EventEmitter {
|
||||
for (let attempt = 0; attempt <= this.retries; attempt++) {
|
||||
try {
|
||||
const responseData = await this._transact(address, this._buildReadPacket(address, regAddr));
|
||||
if (!responseData || responseData.length < 2) {
|
||||
throw new SerialFrameParseError('risposta troppo corta');
|
||||
}
|
||||
return (responseData[0] << 8) | responseData[1];
|
||||
return this._parseRegisterValue(responseData, regAddr);
|
||||
} catch (err) {
|
||||
lastError = err;
|
||||
if (attempt < this.retries) await this._delay(transactionTiming.retryDelayMs);
|
||||
@@ -253,6 +287,23 @@ class MPPTReader extends EventEmitter {
|
||||
throw lastError || new RegisterReadTimeoutError(address, regAddr);
|
||||
}
|
||||
|
||||
/*
|
||||
Estrae il valore raw dalla risposta Poweren.
|
||||
Alcuni firmware rispondono con DATA=[hi, lo], altri includono il registro letto:
|
||||
DATA=[reg, hi, lo]. Supportare entrambi evita di pubblicare valori sfalsati.
|
||||
*/
|
||||
_parseRegisterValue(responseData, requestedRegisterAddress) {
|
||||
if (!responseData || responseData.length < 2) {
|
||||
throw new SerialFrameParseError('risposta troppo corta');
|
||||
}
|
||||
|
||||
if (responseData.length >= 3 && responseData[0] === (requestedRegisterAddress & 0xFF)) {
|
||||
return (responseData[1] << 8) | responseData[2];
|
||||
}
|
||||
|
||||
return (responseData[0] << 8) | responseData[1];
|
||||
}
|
||||
|
||||
// Invia un pacchetto e attende il prossimo frame valido (con timeout)
|
||||
_transact(address, packet) {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -277,6 +328,16 @@ class MPPTReader extends EventEmitter {
|
||||
clearTimeout(timer);
|
||||
this.pendingTransaction = null;
|
||||
reject(new SerialDriverError(err.message, err));
|
||||
return;
|
||||
}
|
||||
if (typeof this.port.drain === 'function') {
|
||||
this.port.drain((drainErr) => {
|
||||
if (drainErr && this.pendingTransaction && this.pendingTransaction.timer === timer) {
|
||||
clearTimeout(timer);
|
||||
this.pendingTransaction = null;
|
||||
reject(new SerialDriverError(drainErr.message, drainErr));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user