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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user