feat: integrate NFC infrared peripherals and orchestration
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
|
||||
from cognitive_agent.infrastructure.nfc.reader import NfcReaderService
|
||||
|
||||
|
||||
@dataclass
|
||||
class FakePort:
|
||||
device: str
|
||||
description: str = ""
|
||||
hwid: str = ""
|
||||
|
||||
|
||||
class FakeReader:
|
||||
def __init__(self, port: str) -> None:
|
||||
self.port = port
|
||||
self.closed = False
|
||||
self.sam_configured = False
|
||||
|
||||
def close(self) -> None:
|
||||
self.closed = True
|
||||
|
||||
def get_firmware_version(self) -> tuple[int, int]:
|
||||
return (1, 6)
|
||||
|
||||
def sam_configuration(self) -> bool:
|
||||
self.sam_configured = True
|
||||
return True
|
||||
|
||||
def read_uid(self) -> bytes:
|
||||
return bytes.fromhex("04A1B2C3")
|
||||
|
||||
|
||||
def wait_until(predicate, timeout: float = 1.0) -> bool:
|
||||
deadline = time.monotonic() + timeout
|
||||
while time.monotonic() < deadline:
|
||||
if predicate():
|
||||
return True
|
||||
time.sleep(0.01)
|
||||
return predicate()
|
||||
|
||||
|
||||
def test_resolve_port_prefers_a_single_ch340_reader() -> None:
|
||||
ports = [
|
||||
FakePort("COM3", "Bluetooth serial"),
|
||||
FakePort("COM5", "USB-SERIAL CH340", "USB VID:PID=1A86:7523"),
|
||||
]
|
||||
|
||||
assert NfcReaderService.resolve_port("auto", ports) == "COM5"
|
||||
assert NfcReaderService.resolve_port("COM8", ports) == "COM8"
|
||||
|
||||
|
||||
def test_reader_service_publishes_reader_and_card_state() -> None:
|
||||
created: list[FakeReader] = []
|
||||
|
||||
def create_reader(port: str) -> FakeReader:
|
||||
reader = FakeReader(port)
|
||||
created.append(reader)
|
||||
return reader
|
||||
|
||||
service = NfcReaderService(
|
||||
reader_factory=create_reader,
|
||||
ports_provider=lambda: [FakePort("COM5", "PCR532 CH340")],
|
||||
retry_seconds=0.01,
|
||||
poll_seconds=0.01,
|
||||
mqtt_enabled=False,
|
||||
)
|
||||
service.start()
|
||||
try:
|
||||
assert wait_until(lambda: service.snapshot()["card"] is not None)
|
||||
snapshot = service.snapshot()
|
||||
assert snapshot["reader"] == {
|
||||
"online": True,
|
||||
"state": "ready",
|
||||
"port": "COM5",
|
||||
"firmware": "1.6",
|
||||
"error": None,
|
||||
"source": "serial",
|
||||
}
|
||||
assert snapshot["card"]["uid"] == "04A1B2C3"
|
||||
assert snapshot["card"]["action"] == "present"
|
||||
assert created[0].sam_configured is True
|
||||
finally:
|
||||
service.close()
|
||||
assert created[0].closed is True
|
||||
|
||||
|
||||
def test_disabled_reader_is_non_blocking_and_reported() -> None:
|
||||
service = NfcReaderService(enabled=False, mqtt_enabled=False)
|
||||
service.start()
|
||||
|
||||
snapshot = service.snapshot()
|
||||
assert snapshot["reader"]["online"] is False
|
||||
assert snapshot["reader"]["state"] == "disabled"
|
||||
assert snapshot["card"] is None
|
||||
|
||||
|
||||
def test_existing_mqtt_bridge_is_reused_when_it_owns_the_reader() -> None:
|
||||
service = NfcReaderService(enabled=True, mqtt_enabled=False)
|
||||
|
||||
service._handle_bridge_status(
|
||||
{"online": True, "state": "ready", "port": "COM5", "firmware": "1.6"}
|
||||
)
|
||||
service._handle_bridge_card(
|
||||
{"action": "present", "uid": "1a53dd95"}
|
||||
)
|
||||
service._handle_bridge_profiles(
|
||||
{
|
||||
"profiles": [
|
||||
{
|
||||
"id": "ambient-light",
|
||||
"name": "第三方氛围灯",
|
||||
"cardUid": "1a53dd95",
|
||||
"actions": [
|
||||
{"id": "red", "slot": 7, "name": "红色", "learned": True}
|
||||
],
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
service._handle_local_bridge_status({"addresses": ["172.20.10.8"]})
|
||||
|
||||
snapshot = service.snapshot()
|
||||
assert snapshot["reader"] == {
|
||||
"online": True,
|
||||
"state": "ready",
|
||||
"port": "COM5",
|
||||
"firmware": "1.6",
|
||||
"error": None,
|
||||
"source": "mqtt_bridge",
|
||||
}
|
||||
assert snapshot["card"]["uid"] == "1A53DD95"
|
||||
assert snapshot["legacy_profiles"][0]["name"] == "第三方氛围灯"
|
||||
assert snapshot["legacy_profiles"][0]["cardUid"] == "1A53DD95"
|
||||
assert snapshot["legacy_profiles"][0]["actions"][0]["slot"] == 7
|
||||
assert snapshot["legacy_bridge_addresses"] == ["172.20.10.8"]
|
||||
|
||||
service._handle_bridge_card({"action": "removed", "uid": "1A53DD95"})
|
||||
assert service.snapshot()["card"] is None
|
||||
Reference in New Issue
Block a user