Added 3 new batteries
This commit is contained in:
@@ -433,6 +433,12 @@ class ModbusActivePoller:
|
|||||||
bytesize=8, parity="N", stopbits=1,
|
bytesize=8, parity="N", stopbits=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Some pack/FTDI combos (observed on 2026-05-11 batch) emit one or two
|
||||||
|
# line-idle bytes before the real Modbus frame, e.g. "00 f0 40 03 4e …".
|
||||||
|
# Tolerate up to this many leading non-frame bytes by sliding the
|
||||||
|
# window and CRC-checking each candidate offset.
|
||||||
|
_MAX_LEADING_IDLE = 4
|
||||||
|
|
||||||
def _read_block(self, start: int, count: int) -> list[int]:
|
def _read_block(self, start: int, count: int) -> list[int]:
|
||||||
body = bytes([self._address, 0x03,
|
body = bytes([self._address, 0x03,
|
||||||
start >> 8, start & 0xFF, count >> 8, count & 0xFF])
|
start >> 8, start & 0xFF, count >> 8, count & 0xFF])
|
||||||
@@ -443,7 +449,8 @@ class ModbusActivePoller:
|
|||||||
self._ser.reset_input_buffer()
|
self._ser.reset_input_buffer()
|
||||||
self._ser.write(frame)
|
self._ser.write(frame)
|
||||||
|
|
||||||
expected = 3 + count * 2 + 2
|
# Read enough bytes to also cover any leading idle bytes.
|
||||||
|
expected = 3 + count * 2 + 2 + self._MAX_LEADING_IDLE
|
||||||
buf = bytearray()
|
buf = bytearray()
|
||||||
deadline = time.monotonic() + self._timeout_s
|
deadline = time.monotonic() + self._timeout_s
|
||||||
while time.monotonic() < deadline and len(buf) < expected:
|
while time.monotonic() < deadline and len(buf) < expected:
|
||||||
@@ -454,15 +461,21 @@ class ModbusActivePoller:
|
|||||||
log.debug("pack 0x%02x [%d@%d] tx=%s rx=%s",
|
log.debug("pack 0x%02x [%d@%d] tx=%s rx=%s",
|
||||||
self._address, count, start, frame.hex(" "), raw.hex(" "))
|
self._address, count, start, frame.hex(" "), raw.hex(" "))
|
||||||
|
|
||||||
if len(raw) < 5 or raw[0] != self._address or raw[1] != 0x03:
|
# Locate the real frame: address byte + fn=0x03 + valid CRC, at
|
||||||
raise RuntimeError(f"no/bad response ({len(raw)} B) for read({count}@{start})")
|
# offset 0..MAX_LEADING_IDLE. Reject any candidate whose CRC fails.
|
||||||
bc = raw[2]
|
for off in range(min(self._MAX_LEADING_IDLE + 1, max(len(raw) - 4, 0))):
|
||||||
if len(raw) < 3 + bc + 2:
|
if raw[off] != self._address or raw[off + 1] != 0x03:
|
||||||
raise RuntimeError(f"truncated response for read({count}@{start})")
|
continue
|
||||||
if not _crc_ok(raw, 0, 3 + bc + 2):
|
bc = raw[off + 2]
|
||||||
raise RuntimeError(f"CRC mismatch for read({count}@{start})")
|
frame_end = off + 3 + bc + 2
|
||||||
data = raw[3:3 + bc]
|
if frame_end > len(raw):
|
||||||
return [(data[i] << 8) | data[i + 1] for i in range(0, len(data), 2)]
|
continue
|
||||||
|
if not _crc_ok(raw, off, 3 + bc + 2):
|
||||||
|
continue
|
||||||
|
data = raw[off + 3:off + 3 + bc]
|
||||||
|
return [(data[i] << 8) | data[i + 1] for i in range(0, len(data), 2)]
|
||||||
|
|
||||||
|
raise RuntimeError(f"no valid frame in {len(raw)} bytes for read({count}@{start})")
|
||||||
|
|
||||||
def poll(self) -> list[int]:
|
def poll(self) -> list[int]:
|
||||||
self._open()
|
self._open()
|
||||||
|
|||||||
Reference in New Issue
Block a user