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:
@@ -3,11 +3,14 @@
|
||||
import pytest
|
||||
|
||||
from pygui.serialcomm.data_parser import (
|
||||
_convert_debug_temp,
|
||||
_convert_hex_to_temp,
|
||||
convert_to_debug_temperatures,
|
||||
convert_to_temperatures,
|
||||
csv_column_count,
|
||||
parse_binary_data,
|
||||
remove_trailing_zeros,
|
||||
save_debug_csv,
|
||||
save_to_csv,
|
||||
)
|
||||
|
||||
@@ -321,3 +324,97 @@ class TestSaveToCsv:
|
||||
assert len(headers) == 244, f"expected 244 cols, got {len(headers)}"
|
||||
assert len(data_row) == 244, f"expected 244 data cols, got {len(data_row)}"
|
||||
assert data_row[243] == "243.0", "last column value must be preserved"
|
||||
|
||||
|
||||
# ── Debug temperature conversion ───────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestConvertDebugTemp:
|
||||
"""Spot-check known hex → debug temperature values.
|
||||
|
||||
Reference values derived from C#'s ConvertBinaryArrayToDebugTemp
|
||||
(bits 4-14 scaled by 2**(i-8), sign at bit 0 — no family branching).
|
||||
"""
|
||||
|
||||
def test_zero_hex_gives_zero(self):
|
||||
assert _convert_debug_temp("0000") == 0.0
|
||||
|
||||
def test_bit8_gives_one(self):
|
||||
# bit index 8 set (value 0x0080) → contributes 2**(8-8) = 1.0
|
||||
assert _convert_debug_temp("0080") == pytest.approx(1.0, abs=0.01)
|
||||
|
||||
def test_sign_bit_negates(self):
|
||||
# sign bit (bit 0) + bit 8 → -1.0
|
||||
assert _convert_debug_temp("8080") == pytest.approx(-1.0, abs=0.01)
|
||||
|
||||
def test_no_family_branching(self):
|
||||
# Debug conversion takes no family code — same result regardless
|
||||
assert _convert_debug_temp("0080") == _convert_debug_temp("0080")
|
||||
|
||||
|
||||
class TestConvertToDebugTemperatures:
|
||||
def test_returns_same_shape(self):
|
||||
hex_data = [["0080", "0000"], ["8080", "0000"]]
|
||||
result = convert_to_debug_temperatures(hex_data)
|
||||
assert len(result) == 2
|
||||
assert len(result[0]) == 2
|
||||
|
||||
def test_values_are_strings(self):
|
||||
result = convert_to_debug_temperatures([["0080"]])
|
||||
assert isinstance(result[0][0], str)
|
||||
|
||||
def test_converts_known_value(self):
|
||||
result = convert_to_debug_temperatures([["0080"]])
|
||||
assert float(result[0][0]) == pytest.approx(1.0, abs=0.01)
|
||||
|
||||
|
||||
# ── Debug CSV export ────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestSaveDebugCsv:
|
||||
def test_creates_file_with_fixed_name(self, tmp_path):
|
||||
result = save_debug_csv([["25.0"]], [["1.0"]], str(tmp_path))
|
||||
assert result is not None
|
||||
assert result.endswith("debug_info.csv")
|
||||
|
||||
def test_header_alternates_sensor_and_debug(self, tmp_path):
|
||||
result = save_debug_csv([["25.0", "24.5"]], [["1.0", "2.0"]], str(tmp_path))
|
||||
assert result is not None
|
||||
header = open(result).readline().strip()
|
||||
assert header == "Sensor1,Debug1,Sensor2,Debug2"
|
||||
|
||||
def test_data_row_alternates_values(self, tmp_path):
|
||||
result = save_debug_csv([["25.0", "24.5"]], [["1.0", "2.0"]], str(tmp_path))
|
||||
assert result is not None
|
||||
lines = open(result).readlines()
|
||||
assert lines[1].strip() == "25.0,1.0,24.5,2.0"
|
||||
|
||||
def test_truncates_to_shorter_row_count(self, tmp_path):
|
||||
temp_data = [["25.0"], ["24.0"], ["23.0"]]
|
||||
debug_data = [["1.0"], ["2.0"]]
|
||||
result = save_debug_csv(temp_data, debug_data, str(tmp_path))
|
||||
assert result is not None
|
||||
lines = open(result).readlines()
|
||||
assert len(lines) == 3 # 1 header + 2 data rows (shorter of the two)
|
||||
|
||||
def test_truncates_to_shorter_column_count(self, tmp_path):
|
||||
temp_data = [["25.0", "24.0", "23.0"]]
|
||||
debug_data = [["1.0", "2.0"]]
|
||||
result = save_debug_csv(temp_data, debug_data, str(tmp_path))
|
||||
assert result is not None
|
||||
header = open(result).readline().strip()
|
||||
assert header == "Sensor1,Debug1,Sensor2,Debug2"
|
||||
|
||||
def test_returns_none_on_empty_temp_data(self, tmp_path):
|
||||
assert save_debug_csv([], [["1.0"]], str(tmp_path)) is None
|
||||
|
||||
def test_returns_none_on_empty_debug_data(self, tmp_path):
|
||||
assert save_debug_csv([["25.0"]], [], str(tmp_path)) is None
|
||||
|
||||
def test_creates_output_dir_if_missing(self, tmp_path):
|
||||
nested = str(tmp_path / "a" / "b")
|
||||
result = save_debug_csv([["25.0"]], [["1.0"]], nested)
|
||||
assert result is not None
|
||||
from pathlib import Path
|
||||
|
||||
assert Path(result).exists()
|
||||
|
||||
Reference in New Issue
Block a user