freitag
This commit is contained in:
@@ -22,6 +22,10 @@ FALLBACK_HEADER = (
|
||||
|
||||
SENSOR_CHANNELS = [2, 3, 4, 5]
|
||||
|
||||
# If no bytes at all arrive for this long, treat the device as hung (e.g. an
|
||||
# I2C bus lockup freezing the Arduino) rather than looping forever in silence.
|
||||
DEFAULT_STALL_TIMEOUT_S = 20.0
|
||||
|
||||
_FAN_PCT_RE = re.compile(r"\((\d+)%\)")
|
||||
_FAN_PWM_RE = re.compile(r"^(\d+)/")
|
||||
|
||||
@@ -206,6 +210,11 @@ def enable_dryer_logging(ser, retries: int = 3) -> None:
|
||||
print("WARN: did not see 'OK csv logging on' — continuing anyway", file=sys.stderr)
|
||||
|
||||
|
||||
def log_notice(message: str) -> None:
|
||||
stamp = datetime.now(timezone.utc).isoformat(timespec="seconds")
|
||||
print(f"{stamp} {message}", file=sys.stderr)
|
||||
|
||||
|
||||
def cmd_log(args: argparse.Namespace) -> int:
|
||||
from dryer_tui import parse_status
|
||||
|
||||
@@ -214,6 +223,7 @@ def cmd_log(args: argparse.Namespace) -> int:
|
||||
if out is None:
|
||||
out = args.log_dir / f"dryer_{datetime.now():%Y%m%d_%H%M%S}.csv"
|
||||
|
||||
stall_timeout = args.stall_timeout
|
||||
print(f"Logging {port} -> {out}", file=sys.stderr)
|
||||
if args.auto_log_on:
|
||||
print("Will send 'log on' after connect", file=sys.stderr)
|
||||
@@ -223,6 +233,7 @@ def cmd_log(args: argparse.Namespace) -> int:
|
||||
if args.auto_log_on:
|
||||
enable_dryer_logging(ser)
|
||||
|
||||
last_activity = time.monotonic()
|
||||
while True:
|
||||
try:
|
||||
raw = ser.readline()
|
||||
@@ -230,10 +241,26 @@ def cmd_log(args: argparse.Namespace) -> int:
|
||||
print(f"\nStopped ({session.row_count} rows).", file=sys.stderr)
|
||||
session.close()
|
||||
return 0
|
||||
except Exception as exc:
|
||||
log_notice(
|
||||
f"ERROR: serial read failed ({exc}) — closing after "
|
||||
f"{session.row_count} rows"
|
||||
)
|
||||
session.close()
|
||||
return 1
|
||||
|
||||
if not raw:
|
||||
if time.monotonic() - last_activity >= stall_timeout:
|
||||
log_notice(
|
||||
f"WARN: no data from {port} for {stall_timeout:.0f}s — "
|
||||
f"device likely hung (e.g. I2C bus lockup on the Arduino) "
|
||||
f"— closing after {session.row_count} rows"
|
||||
)
|
||||
session.close()
|
||||
return 1
|
||||
continue
|
||||
|
||||
last_activity = time.monotonic()
|
||||
line = decode_line(raw)
|
||||
parsed = parse_status(line)
|
||||
if parsed:
|
||||
@@ -277,6 +304,15 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
default=True,
|
||||
help="Send 'log on' after connect in log mode (default: on)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--stall-timeout",
|
||||
type=float,
|
||||
default=DEFAULT_STALL_TIMEOUT_S,
|
||||
help=(
|
||||
"log mode: seconds without any data before treating the device as "
|
||||
f"hung and exiting (default: {DEFAULT_STALL_TIMEOUT_S:.0f})"
|
||||
),
|
||||
)
|
||||
|
||||
subparsers = parser.add_subparsers(dest="action")
|
||||
log_p = subparsers.add_parser("log", help="Headless CSV capture", add_help=False)
|
||||
@@ -285,6 +321,7 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
log_p.add_argument("-o", "--output", type=Path)
|
||||
log_p.add_argument("--log-dir", type=Path, default=Path("logs"))
|
||||
log_p.add_argument("--auto-log-on", action=argparse.BooleanOptionalAction, default=True)
|
||||
log_p.add_argument("--stall-timeout", type=float, default=DEFAULT_STALL_TIMEOUT_S)
|
||||
|
||||
tui_p = subparsers.add_parser("tui", help="Interactive curses dashboard")
|
||||
tui_p.add_argument("-p", "--port")
|
||||
|
||||
Reference in New Issue
Block a user