Rework radar system

This commit is contained in:
Ayzen
2026-10-06 12:32:26 +03:00
parent 552cdc688a
commit 5bb86b7165
299 changed files with 13537 additions and 27763 deletions
+30 -48
View File
@@ -2,14 +2,16 @@
from __future__ import annotations
from contextlib import ExitStack
import unittest
from unittest.mock import patch
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.models.run_config_model import RunConfigModel
from python_app.models.run_config_schema import RunConfigModel
DEVICE_MAIN_MANUAL_HEX = "1111ff37ffa518ab000000000000000a8000000a8000ff003d2acc2c163f"
@@ -35,34 +37,10 @@ class _FakeProtocol:
return b"\x00\x00" if length == 2 else b""
class _FakeLaserController:
instances: list["_FakeLaserController"] = []
def __init__(self, **kwargs: object) -> None:
self.kwargs = kwargs
self.calls: list[tuple[str, object]] = []
_FakeLaserController.instances.append(self)
def connect(self) -> bool:
self.calls.append(("connect", None))
return True
def reset(self) -> None:
self.calls.append(("reset", None))
def set_manual_mode(self, **kwargs: object) -> None:
self.calls.append(("set_manual_mode", kwargs))
def start_variation(self, **kwargs: object) -> None:
self.calls.append(("start_variation", kwargs))
def disconnect(self) -> None:
self.calls.append(("disconnect", None))
class LaserControlProtocolCompatibilityTest(unittest.TestCase):
def setUp(self) -> None:
_FakeLaserController.instances.clear()
self.controller = Mock(spec=LaserController)
self.controller.connect.return_value = True
@staticmethod
def _kamil_config(laser_payload: dict[str, object]) -> RunConfigModel:
@@ -133,19 +111,21 @@ class LaserControlProtocolCompatibilityTest(unittest.TestCase):
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.
params = dict(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)
ld1 = Protocol.encode_task_enable(task_type=TaskType.CHANGE_CURRENT_LD1, **params)
ld2 = Protocol.encode_task_enable(task_type=TaskType.CHANGE_CURRENT_LD2, **params)
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._protocol = fake_protocol
cleanup.enter_context(patch.object(controller, "_protocol", fake_protocol))
with patch("python_app.hardware_full.laser_control.controller.time.sleep", return_value=None):
controller.reset()
@@ -181,9 +161,11 @@ 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)
controller._protocol = fake_protocol
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(
@@ -229,16 +211,16 @@ class LaserControlProtocolCompatibilityTest(unittest.TestCase):
}
)
with patch("python_app.hardware_full.laser_control.controller.LaserController", _FakeLaserController):
with patch("python_app.hardware_full.laser_control.controller.LaserController", return_value=self.controller):
applied = apply_kamil_adc_laser_control(config)
self.assertTrue(applied)
controller = _FakeLaserController.instances[0]
controller = self.controller
self.assertEqual(
[name for name, _payload in controller.calls],
[call[0] for call in controller.method_calls],
["connect", "reset", "set_manual_mode", "start_variation", "disconnect"],
)
manual_payload = controller.calls[2][1]
manual_payload = controller.set_manual_mode.call_args.kwargs
self.assertEqual(
manual_payload,
{
@@ -250,7 +232,7 @@ class LaserControlProtocolCompatibilityTest(unittest.TestCase):
},
)
# The variation handoff itself (type + params), not just call order, must be right.
variation_payload = controller.calls[3][1]
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)
@@ -265,30 +247,30 @@ 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", _FakeLaserController):
with patch("python_app.hardware_full.laser_control.controller.LaserController", return_value=self.controller):
applied = apply_kamil_adc_laser_control(config)
self.assertTrue(applied)
controller = _FakeLaserController.instances[0]
self.assertEqual([name for name, _ in controller.calls], ["connect", "reset", "set_manual_mode", "disconnect"])
self.assertEqual(controller.calls[2][1]["current1"], 30.0)
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(
{"enabled": True, "port": "/dev/ttyUSB0", "mode": "variation",
"variation": {"variation_type": "NOT_A_REAL_TYPE"}}
)
with patch("python_app.hardware_full.laser_control.controller.LaserController", _FakeLaserController):
with patch("python_app.hardware_full.laser_control.controller.LaserController", return_value=self.controller):
with self.assertRaisesRegex(ValueError, "variation_type"):
apply_kamil_adc_laser_control(config)
def test_apply_radar_skips_disabled_laser_control(self) -> None:
config = self._kamil_config({"enabled": False})
with patch("python_app.hardware_full.laser_control.controller.LaserController", _FakeLaserController):
with patch("python_app.hardware_full.laser_control.controller.LaserController", return_value=self.controller) as factory:
applied = apply_kamil_adc_laser_control(config)
self.assertFalse(applied)
self.assertEqual(_FakeLaserController.instances, [])
factory.assert_not_called()
if __name__ == "__main__":