Files
SC-F001/logtool/cli_view.py
Thaddeus Hughes e2451fce78 logtool
2026-03-04 17:41:58 -06:00

158 lines
5.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
CLI table output for SC-F001 logtool.
"""
from parser import LOG_TYPE_BAT, LOG_TYPE_CRASH, LOG_TYPE_BOOT, LOG_TYPE_TIME_SET
try:
from tabulate import tabulate
_TABULATE_OK = True
except ImportError:
_TABULATE_OK = False
_SENSOR_BITS = ['SAFETY', 'JACK', 'DRIVE', 'AUX2']
def _sensor_str(nibble: int) -> str:
active = [_SENSOR_BITS[i] for i in range(4) if (nibble >> i) & 1]
return '+'.join(active) if active else '-'
def _row(e: dict) -> list:
t = e.get('entry_type', -1)
name = e.get('state_name', '?')
if 0 <= t <= 12:
return [
e.get('time_str', ''),
name,
f"{e.get('bat_V', 0):.3f}",
f"{e.get('drive_A', 0):.2f}",
f"{e.get('jack_A', 0):.2f}",
f"{e.get('aux_A', 0):.2f}",
str(e.get('counter', 0)),
_sensor_str(e.get('sensors_stable', 0)),
_sensor_str(e.get('sensors_raw', 0)),
f"{e.get('drive_heat', 0):.1f}",
f"{e.get('jack_heat', 0):.1f}",
f"{e.get('aux_heat', 0):.1f}",
]
elif t == LOG_TYPE_BAT:
return [
e.get('time_str', ''),
'BAT',
f"{e.get('bat_V', 0):.3f}",
'', '', '', '', '', '', '', '', '',
]
elif t == LOG_TYPE_CRASH:
return [
e.get('time_str', ''),
f"*** CRASH: {e.get('reason_str', '?')}",
'', '', '', '', '', '', '', '', '', '',
]
elif t == LOG_TYPE_BOOT:
return [
e.get('time_str', ''),
f"BOOT rst={e.get('reason_str', '?')} wake={e.get('wake_str', '?')}",
'', '', '', '', '', '', '', '', '', '',
]
elif t == LOG_TYPE_TIME_SET:
return [
e.get('time_str', ''),
'TIME_SET',
'', '', '', '', '', '', '', '', '', '',
]
else:
return [
e.get('time_str', ''),
name,
'', '', '', '', '', '', '', '', '', '',
]
_HEADERS = ['Time', 'State', 'Bat(V)', 'Drive(A)', 'Jack(A)', 'Aux(A)',
'Counter', 'Stable', 'Raw', 'DrHeat', 'JkHeat', 'AxHeat']
def print_table(entries: list, type_filter: str = None):
"""Print a tabulate table of log entries to stdout."""
if type_filter:
tf = type_filter.lower()
if tf == 'fsm':
entries = [e for e in entries if 0 <= e.get('entry_type', -1) <= 12]
elif tf == 'bat':
entries = [e for e in entries if e.get('entry_type') == LOG_TYPE_BAT]
elif tf == 'crash':
entries = [e for e in entries if e.get('entry_type') == LOG_TYPE_CRASH]
rows = [_row(e) for e in entries]
if not rows:
print("(no entries)")
return
if _TABULATE_OK:
print(tabulate(rows, headers=_HEADERS, tablefmt='simple'))
else:
# Manual fallback
widths = [max(len(str(r[i])) for r in [_HEADERS] + rows) for i in range(len(_HEADERS))]
fmt = ' '.join(f'{{:<{w}}}' for w in widths)
print(fmt.format(*_HEADERS))
print(' '.join('-' * w for w in widths))
for row in rows:
print(fmt.format(*row))
def print_summary(entries: list):
"""Print a brief summary: time range, entry counts, voltage range."""
if not entries:
print("(empty log)")
return
fsm_entries = [e for e in entries if 0 <= e.get('entry_type', -1) <= 12]
bat_entries = [e for e in entries if e.get('entry_type') == LOG_TYPE_BAT]
crash_entries = [e for e in entries if e.get('entry_type') == LOG_TYPE_CRASH]
boot_entries = [e for e in entries if e.get('entry_type') == LOG_TYPE_BOOT]
time_set_entries = [e for e in entries if e.get('entry_type') == LOG_TYPE_TIME_SET]
all_ts = [e.get('ts_ms', 0) for e in entries if e.get('ts_ms')]
ts_min = min(all_ts) if all_ts else 0
ts_max = max(all_ts) if all_ts else 0
all_bat = [e['bat_V'] for e in entries if 'bat_V' in e]
print(f"Entries : {len(entries)} total "
f"({len(fsm_entries)} FSM, {len(bat_entries)} BAT, "
f"{len(crash_entries)} CRASH, {len(boot_entries)} BOOT, "
f"{len(time_set_entries)} TIME_SET)")
if all_ts:
from parser import _ts_to_str
print(f"Time : {_ts_to_str(ts_min)}{_ts_to_str(ts_max)}")
dur_s = (ts_max - ts_min) / 1000
print(f"Duration: {dur_s:.1f} s ({dur_s/60:.1f} min)")
if all_bat:
print(f"Battery : {min(all_bat):.3f} V {max(all_bat):.3f} V")
if boot_entries:
print(f"\nBOOT events:")
for e in boot_entries:
print(f" {e.get('time_str', '?')} rst={e.get('reason_str', '?')} wake={e.get('wake_str', '?')}")
if crash_entries:
print(f"\nCRASH events:")
for e in crash_entries:
print(f" {e.get('time_str', '?')} reason={e.get('reason_str', '?')}")
if time_set_entries:
print(f"\nTIME_SET events:")
for e in time_set_entries:
print(f" {e.get('time_str', '?')}")
def append_rows(new_entries: list):
"""Print new rows without header (for streaming append mode)."""
for e in new_entries:
row = _row(e)
if _TABULATE_OK:
print(tabulate([row], tablefmt='plain'))
else:
print(' '.join(str(c) for c in row))