feat(debug): add debug read with CSV export

- Parse F1 debug data via family-independent temp formula
  (bits 4-14 scaled by 2**(i-8), sign at bit 0)
- Save sensor+debug temps side-by-side to debug_info.csv
- Wire READ DEBUG button in StatusActionsPanel
- Rename ReplayChart → RunChart; move into GraphTab
- Extract PanelBox/SectionTitle/PanelSlider in ReadoutPanel
  to cut ~300 lines of duplicated styling
- Add rightRailWidth token to Theme for consistent rail sizing
- Wrap ReadoutPanel in ScrollView so Thresholds card stays
  reachable when E-C delta rows appear
This commit is contained in:
jack
2026-07-11 18:39:29 -07:00
parent fed4d9b590
commit 9e3bec9031
13 changed files with 349 additions and 451 deletions
+69
View File
@@ -107,6 +107,23 @@ def _convert_aep(binary_bits: list[int]) -> float:
return result
def _convert_debug_temp(hex_str: str) -> float:
"""Convert a 4-char hex string to a debug/cold-junction temperature.
Mirrors C#'s ConvertBinaryArrayToDebugTemp: bits 4-14 scaled by
2**(i-8), sign bit at bit 0. No family-code branching — this formula
applies regardless of wafer family, unlike _convert_hex_to_temp.
"""
bits = _hex_to_binary(hex_str)
value = 0.0
for i in range(4, 15):
if bits[i]:
value += 2.0 ** (i - 8)
if bits[0]:
value = -value
return round(value, 2)
def _convert_hex_to_temp(hex_str: str, family_code: str) -> float:
"""Convert a singi hale 4-char hex string to a float temperature."""
bits = _hex_to_binary(hex_str)
@@ -225,6 +242,15 @@ def convert_to_temperatures(
return temp_value
def convert_to_debug_temperatures(hex_data: list[list[str]]) -> list[list[str]]:
"""Convert debug/cold-junction hex values to temperature strings.
Same shape contract as convert_to_temperatures, but uses the
family-independent debug formula (_convert_debug_temp).
"""
return [[str(_convert_debug_temp(h)) for h in row] for row in hex_data]
def remove_trailing_zeros(data: list[list[str]]) -> None:
"""Remove rows of all-zero values from the end of data (in-place).
@@ -294,3 +320,46 @@ def save_to_csv(
except Exception as exc:
log.error("CSV save failed: %s", exc)
return None
def save_debug_csv(
temp_data: list[list[str]],
debug_data: list[list[str]],
output_dir: str,
) -> Optional[str]:
"""Save sensor + debug temperatures side-by-side to debug_info.csv.
Mirrors C#'s writeDebugCSV: alternating Sensor{j+1},Debug{j+1} columns,
truncated to the shorter of the two row/column counts. Fixed filename
(not timestamped), unlike save_to_csv.
Args:
temp_data: 2D array of sensor temperature strings (D1, decoded via
convert_to_temperatures).
debug_data: 2D array of debug temperature strings (F1, decoded via
convert_to_debug_temperatures).
output_dir: Directory to save the CSV file.
Returns:
Full file path on success, None on empty input or write failure.
"""
if not temp_data or not debug_data:
log.error("Debug CSV save failed: temp or debug data is empty")
return None
try:
os.makedirs(output_dir, exist_ok=True)
num_cols = min(len(temp_data[0]), len(debug_data[0]))
num_rows = min(len(temp_data), len(debug_data))
filepath = os.path.join(output_dir, "debug_info.csv")
with open(filepath, "w", encoding="utf-8") as f:
headers = [f"Sensor{j + 1},Debug{j + 1}" for j in range(num_cols)]
f.write(",".join(headers) + "\n")
for i in range(num_rows):
row = [f"{temp_data[i][j]},{debug_data[i][j]}" for j in range(num_cols)]
f.write(",".join(row) + "\n")
log.info("Saved debug CSV: %d rows × %d cols to %s", num_rows, num_cols, filepath)
return filepath
except Exception as exc:
log.error("Debug CSV save failed: %s", exc)
return None