Add G2 bootloader and EEPROM exploration scripts
boot_baud_probe.py: Probe all standard baud rates during G2 power-on to detect bootloader UART configuration differences. boot_capture.py: Capture and analyze G2 boot sequence output, with optional interrupt sequence injection to explore bootloader entry. ee_dump.py: Dump EEPROM indices from EE> submenu, identifying initialized vs. uninitialized (0x10101 sentinel) entries.
This commit is contained in:
parent
145763fcfb
commit
13a9d804c6
197
scripts/boot_baud_probe.py
Normal file
197
scripts/boot_baud_probe.py
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Probe G2 bootloader at different baud rates and try interrupt sequences.
|
||||||
|
|
||||||
|
The bootloader phase is <50ms. This script:
|
||||||
|
1. Shows the raw hex of the garbage bytes after "Application is running..."
|
||||||
|
2. Tries rebooting at different baud rates to decode the bootloader's native rate
|
||||||
|
3. Tries sending interrupt sequences during the bootloader window
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
uv run scripts/boot_baud_probe.py
|
||||||
|
"""
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
import serial
|
||||||
|
|
||||||
|
PORT = "/dev/ttyUSB2"
|
||||||
|
APP_BAUD = 115200
|
||||||
|
|
||||||
|
|
||||||
|
def reboot_and_capture(
|
||||||
|
port: str,
|
||||||
|
capture_baud: int,
|
||||||
|
duration: float = 5.0,
|
||||||
|
pre_interrupt: bytes | None = None,
|
||||||
|
interrupt_delay: float = 0.01,
|
||||||
|
) -> bytes:
|
||||||
|
"""Reboot at app baud, optionally switch to capture_baud, read everything."""
|
||||||
|
|
||||||
|
# Open at application baud rate to send reboot command
|
||||||
|
ser = serial.Serial(port, APP_BAUD, timeout=0.5)
|
||||||
|
ser.reset_input_buffer()
|
||||||
|
|
||||||
|
# Make sure we're at a prompt — send CR
|
||||||
|
ser.write(b"\r")
|
||||||
|
time.sleep(0.3)
|
||||||
|
resp = ser.read(4096).decode("utf-8", errors="replace")
|
||||||
|
|
||||||
|
if "OS>" not in resp:
|
||||||
|
if "TRK>" in resp:
|
||||||
|
ser.write(b"os\r")
|
||||||
|
time.sleep(0.3)
|
||||||
|
ser.read(4096)
|
||||||
|
else:
|
||||||
|
ser.write(b"q\r")
|
||||||
|
time.sleep(0.3)
|
||||||
|
ser.read(4096)
|
||||||
|
ser.write(b"os\r")
|
||||||
|
time.sleep(0.3)
|
||||||
|
ser.read(4096)
|
||||||
|
|
||||||
|
ser.reset_input_buffer()
|
||||||
|
ser.write(b"reboot\r")
|
||||||
|
|
||||||
|
# If we need to switch baud rate, do it immediately
|
||||||
|
if capture_baud != APP_BAUD:
|
||||||
|
time.sleep(0.01) # tiny delay for reboot cmd to be sent
|
||||||
|
ser.baudrate = capture_baud
|
||||||
|
|
||||||
|
# Send interrupt if requested
|
||||||
|
if pre_interrupt:
|
||||||
|
time.sleep(interrupt_delay)
|
||||||
|
if pre_interrupt == b"BREAK":
|
||||||
|
ser.send_break(duration=0.25)
|
||||||
|
else:
|
||||||
|
ser.write(pre_interrupt)
|
||||||
|
|
||||||
|
# Capture everything
|
||||||
|
buf = bytearray()
|
||||||
|
start = time.monotonic()
|
||||||
|
ser.timeout = 0.05
|
||||||
|
while time.monotonic() - start < duration:
|
||||||
|
data = ser.read(4096)
|
||||||
|
if data:
|
||||||
|
buf.extend(data)
|
||||||
|
|
||||||
|
ser.close()
|
||||||
|
return bytes(buf)
|
||||||
|
|
||||||
|
|
||||||
|
def hex_dump(data: bytes, prefix: str = " ") -> None:
|
||||||
|
"""Print hex dump with ASCII sidebar."""
|
||||||
|
for i in range(0, len(data), 16):
|
||||||
|
chunk = data[i : i + 16]
|
||||||
|
hex_part = " ".join(f"{b:02x}" for b in chunk)
|
||||||
|
ascii_part = "".join(chr(b) if 32 <= b < 127 else "." for b in chunk)
|
||||||
|
print(f"{prefix}{i:04x}: {hex_part:<48s} {ascii_part}")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("=" * 70)
|
||||||
|
print("G2 BOOTLOADER BAUD RATE PROBE")
|
||||||
|
print("=" * 70)
|
||||||
|
|
||||||
|
# --- Phase 1: Capture raw boot at 115200 and show hex ---
|
||||||
|
print("\n--- Phase 1: Raw boot capture at 115200 (hex dump) ---\n")
|
||||||
|
data = reboot_and_capture(PORT, APP_BAUD, duration=2.0)
|
||||||
|
print(f"Captured {len(data)} bytes:\n")
|
||||||
|
hex_dump(data)
|
||||||
|
|
||||||
|
# Find the garbage bytes between "running..." and "Application Starting"
|
||||||
|
idx1 = data.find(b"running...")
|
||||||
|
idx2 = data.find(b"Application Starting")
|
||||||
|
if idx1 >= 0 and idx2 >= 0:
|
||||||
|
between = data[idx1 + len(b"running...") : idx2]
|
||||||
|
print("\nBytes between 'running...' and 'Application Starting':")
|
||||||
|
print(f" Raw: {between!r}")
|
||||||
|
print(f" Hex: {between.hex(' ')}")
|
||||||
|
print(f" Len: {len(between)}")
|
||||||
|
|
||||||
|
# Wait for boot to complete
|
||||||
|
print("\n Waiting for boot to complete...")
|
||||||
|
time.sleep(12)
|
||||||
|
|
||||||
|
# --- Phase 2: Try different baud rates during bootloader ---
|
||||||
|
test_bauds = [9600, 19200, 38400, 57600, 115200, 230400, 460800]
|
||||||
|
|
||||||
|
print("\n--- Phase 2: Bootloader at different baud rates ---\n")
|
||||||
|
for baud in test_bauds:
|
||||||
|
print(f"\n Trying {baud} baud...")
|
||||||
|
data = reboot_and_capture(PORT, baud, duration=2.0)
|
||||||
|
|
||||||
|
# Filter to just the first ~200 bytes (bootloader phase)
|
||||||
|
preview = data[:200]
|
||||||
|
# Check if it looks like readable ASCII
|
||||||
|
ascii_count = sum(1 for b in preview if 32 <= b < 127 or b in (10, 13))
|
||||||
|
ratio = ascii_count / max(len(preview), 1)
|
||||||
|
|
||||||
|
print(f" Captured {len(data)} bytes, ASCII ratio: {ratio:.0%}")
|
||||||
|
if ratio > 0.5:
|
||||||
|
text = preview.decode("utf-8", errors="replace")
|
||||||
|
print(f" Preview: {text[:120]!r}")
|
||||||
|
else:
|
||||||
|
if preview:
|
||||||
|
print(f" Hex: {preview[:32].hex(' ')}")
|
||||||
|
|
||||||
|
# Wait for boot
|
||||||
|
time.sleep(12)
|
||||||
|
|
||||||
|
# --- Phase 3: Try interrupt sequences at 115200 ---
|
||||||
|
print("\n--- Phase 3: Interrupt sequences at 115200 ---\n")
|
||||||
|
|
||||||
|
interrupts = [
|
||||||
|
(0.005, "5ms CR", b"\r\r\r\r\r"),
|
||||||
|
(0.005, "5ms ESC", b"\x1b\x1b\x1b"),
|
||||||
|
(0.005, "5ms BREAK", b"BREAK"),
|
||||||
|
(0.005, "5ms 0x55 autobaud", b"\x55\x55\x55\x55\x55"),
|
||||||
|
(0.005, "5ms 0x7F (DEL)", b"\x7f\x7f\x7f"),
|
||||||
|
(0.005, "5ms 'bl'", b"bl\r"),
|
||||||
|
(0.01, "10ms CR burst", b"\r" * 20),
|
||||||
|
(0.02, "20ms '?' burst", b"?\r?\r?\r"),
|
||||||
|
(0.03, "30ms CR", b"\r\r\r"),
|
||||||
|
]
|
||||||
|
|
||||||
|
for delay, desc, payload in interrupts:
|
||||||
|
print(f"\n [{desc}] (delay={delay}s)")
|
||||||
|
data = reboot_and_capture(
|
||||||
|
PORT, APP_BAUD, duration=3.0, pre_interrupt=payload, interrupt_delay=delay
|
||||||
|
)
|
||||||
|
|
||||||
|
text = data.decode("utf-8", errors="replace")
|
||||||
|
|
||||||
|
# Look for anything unusual (not normal boot)
|
||||||
|
normal_markers = ["Application Starting", "MotorInit", "BCM4515"]
|
||||||
|
unusual = False
|
||||||
|
|
||||||
|
# Check if we got a response BEFORE normal boot messages
|
||||||
|
bl_idx = text.find("Bootloader version")
|
||||||
|
app_idx = text.find("Application Starting")
|
||||||
|
|
||||||
|
if bl_idx >= 0 and app_idx >= 0:
|
||||||
|
between = text[bl_idx:app_idx]
|
||||||
|
if len(between) > 80: # more than just "Bootloader version: 1.01\r\n..."
|
||||||
|
unusual = True
|
||||||
|
print(" *** EXTRA DATA in bootloader phase! ***")
|
||||||
|
print(f" Between BL and App: {between!r}")
|
||||||
|
|
||||||
|
if "BL>" in text or "CMD>" in text or "download" in text.lower():
|
||||||
|
unusual = True
|
||||||
|
print(" *** BOOTLOADER PROMPT DETECTED ***")
|
||||||
|
|
||||||
|
if not unusual:
|
||||||
|
# Check if boot proceeded normally
|
||||||
|
if any(m in text for m in normal_markers):
|
||||||
|
print(" Normal boot (no intercept)")
|
||||||
|
else:
|
||||||
|
print(f" Unexpected: {text[:150]!r}")
|
||||||
|
|
||||||
|
time.sleep(12)
|
||||||
|
|
||||||
|
print("\n" + "=" * 70)
|
||||||
|
print("PROBE COMPLETE")
|
||||||
|
print("=" * 70)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
229
scripts/boot_capture.py
Normal file
229
scripts/boot_capture.py
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Reboot G2 firmware and capture boot output with timestamps.
|
||||||
|
|
||||||
|
Pass 1: Observe boot sequence timing.
|
||||||
|
Pass 2: Try interrupt sequences during bootloader phase.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
uv run scripts/boot_capture.py [--interrupt]
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import time
|
||||||
|
|
||||||
|
import serial
|
||||||
|
|
||||||
|
PORT = "/dev/ttyUSB2"
|
||||||
|
BAUD = 115200
|
||||||
|
BOOT_CAPTURE_SECS = 30 # how long to capture after reboot
|
||||||
|
|
||||||
|
|
||||||
|
def timestamp() -> str:
|
||||||
|
return f"{time.monotonic():.4f}"
|
||||||
|
|
||||||
|
|
||||||
|
def capture_boot(ser: serial.Serial, duration: float) -> list[tuple[float, bytes]]:
|
||||||
|
"""Read all data for `duration` seconds, returning (time, data) pairs."""
|
||||||
|
chunks = []
|
||||||
|
start = time.monotonic()
|
||||||
|
ser.timeout = 0.05 # 50ms polling
|
||||||
|
while time.monotonic() - start < duration:
|
||||||
|
data = ser.read(4096)
|
||||||
|
if data:
|
||||||
|
chunks.append((time.monotonic() - start, data))
|
||||||
|
return chunks
|
||||||
|
|
||||||
|
|
||||||
|
def enter_os_and_reboot(ser: serial.Serial) -> None:
|
||||||
|
"""Navigate to OS menu and send reboot command."""
|
||||||
|
ser.reset_input_buffer()
|
||||||
|
|
||||||
|
# We left the port in the OS> menu before closing mcserial.
|
||||||
|
# But the port close/reopen may have reset state. Try sending
|
||||||
|
# a bare CR first to see where we are.
|
||||||
|
ser.write(b"\r")
|
||||||
|
time.sleep(0.3)
|
||||||
|
response = ser.read(4096)
|
||||||
|
decoded = response.decode("utf-8", errors="replace")
|
||||||
|
print(f" Prompt check: {decoded.strip()!r}")
|
||||||
|
|
||||||
|
if "OS>" in decoded:
|
||||||
|
print(" Already in OS menu.")
|
||||||
|
elif "TRK>" in decoded:
|
||||||
|
print(" At root prompt, entering OS menu...")
|
||||||
|
ser.write(b"os\r")
|
||||||
|
time.sleep(0.3)
|
||||||
|
ser.read(4096) # consume echo
|
||||||
|
elif "MOT>" in decoded or "DVB>" in decoded:
|
||||||
|
print(" In a submenu, exiting to root first...")
|
||||||
|
ser.write(b"q\r")
|
||||||
|
time.sleep(0.3)
|
||||||
|
ser.read(4096)
|
||||||
|
ser.write(b"os\r")
|
||||||
|
time.sleep(0.3)
|
||||||
|
ser.read(4096)
|
||||||
|
else:
|
||||||
|
# Unknown state — try brute force: q to root, then os
|
||||||
|
print(" Unknown state, trying q -> os...")
|
||||||
|
ser.write(b"q\r")
|
||||||
|
time.sleep(0.3)
|
||||||
|
ser.read(4096)
|
||||||
|
ser.write(b"os\r")
|
||||||
|
time.sleep(0.3)
|
||||||
|
ser.read(4096)
|
||||||
|
|
||||||
|
# Flush buffer
|
||||||
|
ser.reset_input_buffer()
|
||||||
|
|
||||||
|
print(" Sending 'reboot' command...")
|
||||||
|
ser.write(b"reboot\r")
|
||||||
|
|
||||||
|
|
||||||
|
def pass1_observe(ser: serial.Serial) -> list[tuple[float, bytes]]:
|
||||||
|
"""Reboot and passively observe boot output."""
|
||||||
|
print("\n=== PASS 1: OBSERVE BOOT SEQUENCE ===\n")
|
||||||
|
enter_os_and_reboot(ser)
|
||||||
|
|
||||||
|
print(f" Capturing for {BOOT_CAPTURE_SECS}s...\n")
|
||||||
|
chunks = capture_boot(ser, BOOT_CAPTURE_SECS)
|
||||||
|
|
||||||
|
print("--- Boot Output (with timestamps) ---\n")
|
||||||
|
full_output = bytearray()
|
||||||
|
for t, data in chunks:
|
||||||
|
full_output.extend(data)
|
||||||
|
text = (
|
||||||
|
data.decode("utf-8", errors="replace")
|
||||||
|
.replace("\r\n", "\n")
|
||||||
|
.replace("\r", "\n")
|
||||||
|
)
|
||||||
|
for line in text.split("\n"):
|
||||||
|
if line.strip():
|
||||||
|
print(f" [{t:7.3f}s] {line}")
|
||||||
|
|
||||||
|
print(f"\n--- Total: {len(full_output)} bytes in {len(chunks)} chunks ---")
|
||||||
|
return chunks
|
||||||
|
|
||||||
|
|
||||||
|
def pass2_interrupt(ser: serial.Serial) -> None:
|
||||||
|
"""Reboot and try interrupt sequences during bootloader phase."""
|
||||||
|
print("\n=== PASS 2: INTERRUPT BOOT ===\n")
|
||||||
|
|
||||||
|
# Interrupt sequences to try, with timing (seconds after reboot)
|
||||||
|
interrupts = [
|
||||||
|
# (delay_after_reboot, description, bytes_to_send)
|
||||||
|
(0.05, "immediate CR", b"\r"),
|
||||||
|
(0.05, "immediate ESC", b"\x1b"),
|
||||||
|
(0.05, "immediate BREAK", "BREAK"), # special handling
|
||||||
|
(0.05, "immediate 0x55 (autobaud)", b"\x55\x55\x55\x55\x55"),
|
||||||
|
(0.05, "immediate space", b" "),
|
||||||
|
(0.1, "100ms CR", b"\r"),
|
||||||
|
(0.1, "100ms ESC", b"\x1b"),
|
||||||
|
(0.2, "200ms CR", b"\r"),
|
||||||
|
(0.5, "500ms CR+ESC", b"\r\x1b\r"),
|
||||||
|
(1.0, "1s CR", b"\r"),
|
||||||
|
(1.0, "1s 'bl'", b"bl\r"),
|
||||||
|
(1.0, "1s 'boot'", b"boot\r"),
|
||||||
|
(2.0, "2s CR", b"\r"),
|
||||||
|
(2.0, "2s '?'", b"?\r"),
|
||||||
|
]
|
||||||
|
|
||||||
|
for delay, desc, payload in interrupts:
|
||||||
|
print(f"\n--- Trying: {desc} (at +{delay}s) ---")
|
||||||
|
enter_os_and_reboot(ser)
|
||||||
|
|
||||||
|
# Wait for the specified delay
|
||||||
|
time.sleep(delay)
|
||||||
|
|
||||||
|
# Send the interrupt
|
||||||
|
if payload == "BREAK":
|
||||||
|
ser.send_break(duration=0.25)
|
||||||
|
print(" Sent BREAK signal (250ms)")
|
||||||
|
else:
|
||||||
|
ser.write(payload)
|
||||||
|
print(f" Sent: {payload!r}")
|
||||||
|
|
||||||
|
# Capture response for 5 seconds
|
||||||
|
chunks = capture_boot(ser, 8)
|
||||||
|
|
||||||
|
full = bytearray()
|
||||||
|
for _, data in chunks:
|
||||||
|
full.extend(data)
|
||||||
|
text = full.decode("utf-8", errors="replace")
|
||||||
|
|
||||||
|
# Check for interesting responses
|
||||||
|
interesting = False
|
||||||
|
for keyword in [
|
||||||
|
"boot",
|
||||||
|
"loader",
|
||||||
|
"BL>",
|
||||||
|
"CMD>",
|
||||||
|
">>",
|
||||||
|
"ready",
|
||||||
|
"download",
|
||||||
|
"upload",
|
||||||
|
"flash",
|
||||||
|
"update",
|
||||||
|
"xmodem",
|
||||||
|
"ymodem",
|
||||||
|
"zmodem",
|
||||||
|
"srec",
|
||||||
|
"hex",
|
||||||
|
"binary",
|
||||||
|
]:
|
||||||
|
if keyword.lower() in text.lower():
|
||||||
|
interesting = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if interesting:
|
||||||
|
print(" *** INTERESTING RESPONSE ***")
|
||||||
|
for t, data in chunks:
|
||||||
|
line = data.decode("utf-8", errors="replace").strip()
|
||||||
|
if line:
|
||||||
|
print(f" [{t:7.3f}s] {line}")
|
||||||
|
else:
|
||||||
|
# Print condensed summary
|
||||||
|
lines = [ln.strip() for ln in text.split("\n") if ln.strip()]
|
||||||
|
if lines:
|
||||||
|
print(f" Response: {len(full)} bytes, first: {lines[0][:80]!r}")
|
||||||
|
if len(lines) > 1:
|
||||||
|
print(f" last: {lines[-1][:80]!r}")
|
||||||
|
else:
|
||||||
|
print(f" No response ({len(full)} bytes)")
|
||||||
|
|
||||||
|
# Wait for boot to complete before next attempt
|
||||||
|
print(" Waiting for full boot...")
|
||||||
|
time.sleep(max(0, BOOT_CAPTURE_SECS - 8 - delay))
|
||||||
|
ser.reset_input_buffer()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description="G2 bootloader capture")
|
||||||
|
parser.add_argument(
|
||||||
|
"--interrupt", action="store_true", help="Pass 2: try interrupt sequences"
|
||||||
|
)
|
||||||
|
parser.add_argument("--port", default=PORT)
|
||||||
|
parser.add_argument("--baud", type=int, default=BAUD)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
print(f"Opening {args.port} @ {args.baud}...")
|
||||||
|
ser = serial.Serial(args.port, args.baud, timeout=1)
|
||||||
|
ser.reset_input_buffer()
|
||||||
|
|
||||||
|
try:
|
||||||
|
pass1_observe(ser)
|
||||||
|
|
||||||
|
if args.interrupt:
|
||||||
|
# Wait for boot to fully complete
|
||||||
|
print("\n Waiting for boot to settle...")
|
||||||
|
time.sleep(5)
|
||||||
|
ser.reset_input_buffer()
|
||||||
|
pass2_interrupt(ser)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n\nInterrupted by user.")
|
||||||
|
finally:
|
||||||
|
ser.close()
|
||||||
|
print("\nPort closed.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
76
scripts/ee_dump.py
Normal file
76
scripts/ee_dump.py
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Dump all EEPROM indices from Winegard G2 firmware via RS-422."""
|
||||||
|
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
import serial
|
||||||
|
|
||||||
|
PORT = "/dev/ttyUSB2"
|
||||||
|
BAUD = 115200
|
||||||
|
PROMPT = b">"
|
||||||
|
MAX_INDEX = 100 # scan up to this index
|
||||||
|
|
||||||
|
|
||||||
|
def send_cmd(ser: serial.Serial, cmd: str) -> str:
|
||||||
|
"""Send command + CR, read until prompt '>'."""
|
||||||
|
ser.reset_input_buffer()
|
||||||
|
ser.write(f"{cmd}\r".encode("ascii"))
|
||||||
|
buf = bytearray()
|
||||||
|
while True:
|
||||||
|
b = ser.read(1)
|
||||||
|
if len(b) == 0:
|
||||||
|
break # timeout
|
||||||
|
buf.append(b[0])
|
||||||
|
if b[0] == ord(">"):
|
||||||
|
break
|
||||||
|
return buf.decode("utf-8", errors="ignore")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ser = serial.Serial(PORT, BAUD, timeout=3)
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
# Navigate to EE submenu
|
||||||
|
send_cmd(ser, "q") # ensure root
|
||||||
|
resp = send_cmd(ser, "eeprom")
|
||||||
|
if "EE>" not in resp:
|
||||||
|
print(f"Failed to enter EEPROM menu: {resp!r}", file=sys.stderr)
|
||||||
|
ser.close()
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
print(f"{'Index':>5} {'Decimal':>10} {'Hex':>10} Status")
|
||||||
|
print("-" * 50)
|
||||||
|
|
||||||
|
valid_count = 0
|
||||||
|
for idx in range(MAX_INDEX + 1):
|
||||||
|
resp = send_cmd(ser, f"ee {idx}")
|
||||||
|
|
||||||
|
# Parse response
|
||||||
|
if "Read value" in resp:
|
||||||
|
match = re.search(r"Read value = (\d+)", resp)
|
||||||
|
if match:
|
||||||
|
val = int(match.group(1))
|
||||||
|
print(f"{idx:>5} {val:>10} 0x{val:08X} OK")
|
||||||
|
valid_count += 1
|
||||||
|
elif "Failed to read" in resp:
|
||||||
|
match = re.search(r"val:(\d+)", resp)
|
||||||
|
val_str = match.group(1) if match else "?"
|
||||||
|
val = int(val_str) if val_str != "?" else 0
|
||||||
|
print(f"{idx:>5} {val:>10} 0x{val:08X} INVALID")
|
||||||
|
else:
|
||||||
|
# Unknown response - might be end of range
|
||||||
|
clean = resp.strip().replace("\r\n", " | ")
|
||||||
|
print(f"{idx:>5} {'':>10} {'':>10} ERROR: {clean}")
|
||||||
|
|
||||||
|
print("-" * 50)
|
||||||
|
print(f"Valid entries: {valid_count} / {MAX_INDEX + 1}")
|
||||||
|
|
||||||
|
# Return to root
|
||||||
|
send_cmd(ser, "q")
|
||||||
|
ser.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Loading…
x
Reference in New Issue
Block a user