65 lines
2.3 KiB
Python
65 lines
2.3 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
"""Sanity-check byte-level probe for EG4 LifePower4 V2 over USB-RS485.
|
||
|
|
|
||
|
|
Sends the 'general status' frame and prints the raw reply. If you see bytes
|
||
|
|
starting with 0x7E coming back, the protocol is confirmed and eg4_lifepower.py
|
||
|
|
will decode them.
|
||
|
|
|
||
|
|
Usage:
|
||
|
|
python3 probe.py # auto-detect likely USB-serial port
|
||
|
|
python3 probe.py /dev/tty.usbserial-XXX # explicit port
|
||
|
|
python3 probe.py COM5 # Windows
|
||
|
|
"""
|
||
|
|
import sys
|
||
|
|
import time
|
||
|
|
import glob
|
||
|
|
import serial # pip install pyserial
|
||
|
|
|
||
|
|
BAUD = 9600
|
||
|
|
CMDS = {
|
||
|
|
"general": bytes.fromhex("7E01010000FE0D"[:-2] + "0D"), # 7E 01 01 00 FE 0D
|
||
|
|
"hw_ver": bytes.fromhex("7E01420000FC0D"[:-2] + "0D"), # 7E 01 42 00 FC 0D
|
||
|
|
"fw_ver": bytes.fromhex("7E01330000FE0D"[:-2] + "0D"), # 7E 01 33 00 FE 0D
|
||
|
|
}
|
||
|
|
# (the [:-2]+"0D" dance above is just to keep the literal aligned with the
|
||
|
|
# canonical 6-byte frames documented upstream; simpler form below)
|
||
|
|
CMDS = {
|
||
|
|
"general": bytes([0x7E, 0x01, 0x01, 0x00, 0xFE, 0x0D]),
|
||
|
|
"hw_ver": bytes([0x7E, 0x01, 0x42, 0x00, 0xFC, 0x0D]),
|
||
|
|
"fw_ver": bytes([0x7E, 0x01, 0x33, 0x00, 0xFE, 0x0D]),
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
def autodetect() -> str | None:
|
||
|
|
candidates = (
|
||
|
|
glob.glob("/dev/tty.usbserial*") # macOS FTDI/CH340
|
||
|
|
+ glob.glob("/dev/tty.usbmodem*") # macOS CDC-ACM
|
||
|
|
+ glob.glob("/dev/ttyUSB*") # Linux FTDI/CH340
|
||
|
|
+ glob.glob("/dev/ttyACM*") # Linux CDC-ACM
|
||
|
|
)
|
||
|
|
return candidates[0] if candidates else None
|
||
|
|
|
||
|
|
|
||
|
|
def probe(port: str) -> None:
|
||
|
|
print(f"opening {port} @ {BAUD} 8N1")
|
||
|
|
with serial.Serial(port, BAUD, bytesize=8, parity="N", stopbits=1, timeout=1.5) as s:
|
||
|
|
for name, cmd in CMDS.items():
|
||
|
|
s.reset_input_buffer()
|
||
|
|
s.write(cmd)
|
||
|
|
time.sleep(0.2)
|
||
|
|
reply = s.read(256)
|
||
|
|
print(f"\n[{name}] sent {cmd.hex(' ')}")
|
||
|
|
if reply:
|
||
|
|
print(f" got ({len(reply)} bytes) {reply.hex(' ')}")
|
||
|
|
if reply[0] == 0x7E and reply[-1] == 0x0D:
|
||
|
|
print(" -> frame looks valid (7E ... 0D)")
|
||
|
|
else:
|
||
|
|
print(" got (nothing — timeout)")
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
port = sys.argv[1] if len(sys.argv) > 1 else autodetect()
|
||
|
|
if not port:
|
||
|
|
sys.exit("no serial port found; pass one explicitly: python3 probe.py /dev/tty.usbserial-XXXX")
|
||
|
|
probe(port)
|