Rename kamil_adc to rph_radar and fix its data path

This commit is contained in:
Ayzen
2026-10-06 18:15:18 +03:00
parent aef549ea76
commit a185e61495
94 changed files with 2851 additions and 9485 deletions
+44 -84
View File
@@ -8,9 +8,9 @@ import unittest
from unittest.mock import Mock, patch
from python_app.hardware_full.laser_control.controller import DEVICE_MAIN_MESSAGE_ID, LaserController
from python_app.hardware_full.laser_control.models import VariationType
from python_app.hardware_full.laser_control.protocol import Protocol, TaskType
from python_app.hardware_full.kamil_adc import apply_kamil_adc_laser_control
from python_app.hardware_full.laser_control.exceptions import CommunicationError, ValidationError
from python_app.hardware_full.laser_control.protocol import Protocol
from python_app.hardware_full.rph_radar.laser import apply_rph_radar_laser_control
from python_app.models.run_config_schema import RunConfigModel
@@ -18,11 +18,6 @@ DEVICE_MAIN_MANUAL_HEX = "1111ff37ffa518ab000000000000000a8000000a8000ff003d2acc
DEVICE_MAIN_CHANGE_CURRENT_LD1_HEX = (
"7777ff3701003d2acc2c10008813ffa5cc2c18ab0a00000a8000000a8000b600"
)
DEVICE_MAIN_CHANGE_CURRENT_LD2_HEX = (
# Word 5 (step) = current_ma_to_n(0.05)=0x0010, matching LD1 and min/max — see the
# step-encoding fix in protocol.py (was the inconsistent int(step*100)=0x0005).
"7777ff3702003d2acc2c1000881318ab3d2affa50a00000a8000000a80004406"
)
class _FakeProtocol:
@@ -43,11 +38,11 @@ class LaserControlProtocolCompatibilityTest(unittest.TestCase):
self.controller.connect.return_value = True
@staticmethod
def _kamil_config(laser_payload: dict[str, object]) -> RunConfigModel:
def _rph_config(laser_payload: dict[str, object]) -> RunConfigModel:
return RunConfigModel.from_dict(
{
"radar": {
"model": "kamil_adc",
"model": "rph_radar",
"driver_mode": "native",
"laser_control": laser_payload,
}
@@ -71,7 +66,6 @@ class LaserControlProtocolCompatibilityTest(unittest.TestCase):
def test_variation_commands_match_device_main_bytes(self) -> None:
ld1_command = Protocol.encode_task_enable(
task_type=TaskType.CHANGE_CURRENT_LD1,
static_temp1=28.0,
static_temp2=28.9,
static_current1=33.0,
@@ -81,50 +75,18 @@ class LaserControlProtocolCompatibilityTest(unittest.TestCase):
step=0.05,
time_step=50,
delay_time=10,
message_id=DEVICE_MAIN_MESSAGE_ID,
pi_coeff1_p=2560,
pi_coeff1_i=128,
pi_coeff2_p=2560,
pi_coeff2_i=128,
)
ld2_command = Protocol.encode_task_enable(
task_type=TaskType.CHANGE_CURRENT_LD2,
static_temp1=28.0,
static_temp2=28.9,
static_current1=33.0,
static_current2=35.0,
min_value=33.0,
max_value=35.0,
step=0.05,
time_step=50,
delay_time=10,
message_id=DEVICE_MAIN_MESSAGE_ID,
pi_coeff1_p=2560,
pi_coeff1_i=128,
pi_coeff2_p=2560,
pi_coeff2_i=128,
)
self.assertEqual(ld1_command.hex(), DEVICE_MAIN_CHANGE_CURRENT_LD1_HEX)
self.assertEqual(ld2_command.hex(), DEVICE_MAIN_CHANGE_CURRENT_LD2_HEX)
def test_ld1_and_ld2_encode_current_step_identically(self) -> None:
# Regression guard for the LD2 step-scale bug: both current-variation channels
# must encode the step with the same scale (current_ma_to_n), like min/max.
ld1, ld2 = [Protocol.encode_task_enable(
task_type=task_type, static_temp1=28.0, static_temp2=28.9,
static_current1=33.0, static_current2=35.0, min_value=33.0, max_value=35.0,
step=0.05, time_step=50, delay_time=10, message_id=DEVICE_MAIN_MESSAGE_ID,
pi_coeff1_p=2560, pi_coeff1_i=128, pi_coeff2_p=2560, pi_coeff2_i=128,
) for task_type in (TaskType.CHANGE_CURRENT_LD1, TaskType.CHANGE_CURRENT_LD2)]
# Word 5 (step) is at bytes 10:12 in both frames (sync, header, task, min, max, step).
self.assertEqual(ld1[10:12], ld2[10:12])
def test_start_sequence_matches_device_main_order(self) -> None:
cleanup = ExitStack()
self.addCleanup(cleanup.close)
fake_protocol = _FakeProtocol()
controller = LaserController(pi_coeff1_p=2560, pi_coeff1_i=128, pi_coeff2_p=2560, pi_coeff2_i=128)
controller = LaserController("/dev/ttyLASER", pi_coeff1_p=2560, pi_coeff1_i=128, pi_coeff2_p=2560, pi_coeff2_i=128)
cleanup.enter_context(patch.object(controller, "_protocol", fake_protocol))
with patch("python_app.hardware_full.laser_control.controller.time.sleep", return_value=None):
@@ -137,7 +99,6 @@ class LaserControlProtocolCompatibilityTest(unittest.TestCase):
message_id=DEVICE_MAIN_MESSAGE_ID,
)
controller.start_variation(
variation_type=VariationType.CHANGE_CURRENT_LD1,
params={
"static_temp1": 28.0,
"static_temp2": 28.9,
@@ -160,34 +121,8 @@ class LaserControlProtocolCompatibilityTest(unittest.TestCase):
],
)
def test_stop_sequence_restores_device_main_manual_bytes(self) -> None:
cleanup = ExitStack()
self.addCleanup(cleanup.close)
fake_protocol = _FakeProtocol()
controller = LaserController(pi_coeff1_p=2560, pi_coeff1_i=128, pi_coeff2_p=2560, pi_coeff2_i=128)
cleanup.enter_context(patch.object(controller, "_protocol", fake_protocol))
with patch("python_app.hardware_full.laser_control.controller.time.sleep", return_value=None):
controller.set_manual_mode(
temp1=28.0,
temp2=28.9,
current1=33.0,
current2=35.0,
message_id=DEVICE_MAIN_MESSAGE_ID,
)
fake_protocol.sent.clear()
controller.stop_task(restore_message_id=DEVICE_MAIN_MESSAGE_ID)
self.assertEqual(
[command.hex() for command in fake_protocol.sent],
[
"2222",
DEVICE_MAIN_MANUAL_HEX,
],
)
def test_apply_radar_variation_sequence_matches_device_main_order(self) -> None:
config = self._kamil_config(
config = self._rph_config(
{
"enabled": True,
"port": "/dev/ttyUSB0",
@@ -211,8 +146,8 @@ class LaserControlProtocolCompatibilityTest(unittest.TestCase):
}
)
with patch("python_app.hardware_full.laser_control.controller.LaserController", return_value=self.controller):
applied = apply_kamil_adc_laser_control(config)
with patch("python_app.hardware_full.rph_radar.laser.LaserController", return_value=self.controller):
applied = apply_rph_radar_laser_control(config)
self.assertTrue(applied)
controller = self.controller
@@ -233,13 +168,12 @@ class LaserControlProtocolCompatibilityTest(unittest.TestCase):
)
# The variation handoff itself (type + params), not just call order, must be right.
variation_payload = controller.start_variation.call_args.kwargs
self.assertEqual(variation_payload["variation_type"], VariationType.CHANGE_CURRENT_LD1)
self.assertEqual(variation_payload["params"]["step"], 0.05)
self.assertEqual(variation_payload["params"]["min_value"], 33.0)
self.assertEqual(variation_payload["params"]["max_value"], 35.0)
def test_apply_radar_manual_mode_sets_manual_without_variation(self) -> None:
config = self._kamil_config(
config = self._rph_config(
{
"enabled": True,
"port": "/dev/ttyUSB0",
@@ -247,27 +181,53 @@ class LaserControlProtocolCompatibilityTest(unittest.TestCase):
"manual": {"temp1": 26.0, "temp2": 27.0, "current1": 30.0, "current2": 31.0},
}
)
with patch("python_app.hardware_full.laser_control.controller.LaserController", return_value=self.controller):
applied = apply_kamil_adc_laser_control(config)
with patch("python_app.hardware_full.rph_radar.laser.LaserController", return_value=self.controller):
applied = apply_rph_radar_laser_control(config)
self.assertTrue(applied)
controller = self.controller
self.assertEqual([call[0] for call in controller.method_calls], ["connect", "reset", "set_manual_mode", "disconnect"])
self.assertEqual(controller.set_manual_mode.call_args.kwargs["current1"], 30.0)
def test_apply_radar_unknown_variation_type_raises(self) -> None:
config = self._kamil_config(
config = self._rph_config(
{"enabled": True, "port": "/dev/ttyUSB0", "mode": "variation",
"variation": {"variation_type": "NOT_A_REAL_TYPE"}}
)
with patch("python_app.hardware_full.laser_control.controller.LaserController", return_value=self.controller):
with patch("python_app.hardware_full.rph_radar.laser.LaserController", return_value=self.controller):
with self.assertRaisesRegex(ValueError, "variation_type"):
apply_kamil_adc_laser_control(config)
apply_rph_radar_laser_control(config)
def test_state_failure_cannot_report_command_success(self) -> None:
controller = LaserController("/dev/ttyLASER")
for reply in (b"", b"\x00", b"\x04\x00", b"\x03\x00"):
with self.subTest(reply=reply), patch.object(controller, "_protocol") as protocol:
protocol.receive_raw.return_value = reply
with patch("python_app.hardware_full.laser_control.controller.time.sleep"):
with self.assertRaises(CommunicationError):
controller.reset()
def test_invalid_settings_are_rejected_before_connect_or_reset(self) -> None:
for mode, field, value in (("manual", "temp1", 100.0),
("variation", "static_current2", 100.0),
("variation", "step", 0.0),
("variation", "step", 0.002),
("variation", "variation_type", "CHANGE_TEMPERATURE_LD1"),
("pi", "pi_coeff1_p", 65536)):
config = self._rph_config({"enabled": True, "port": "/dev/ttyUSB0", "mode": "manual" if mode == "pi" else mode})
target = config.radar.laser_control
setattr(target if mode == "pi" else getattr(target, mode), field, value)
with self.subTest(mode=mode, field=field), patch(
"python_app.hardware_full.rph_radar.laser.LaserController", return_value=self.controller
) as factory:
with self.assertRaises((ValueError, ValidationError)):
apply_rph_radar_laser_control(config)
factory.assert_not_called()
def test_apply_radar_skips_disabled_laser_control(self) -> None:
config = self._kamil_config({"enabled": False})
config = self._rph_config({"enabled": False})
with patch("python_app.hardware_full.laser_control.controller.LaserController", return_value=self.controller) as factory:
applied = apply_kamil_adc_laser_control(config)
with patch("python_app.hardware_full.rph_radar.laser.LaserController", return_value=self.controller) as factory:
applied = apply_rph_radar_laser_control(config)
self.assertFalse(applied)
factory.assert_not_called()