Skip to content

Commit 968cadd

Browse files
committed
fix: some command responses with tests
1 parent ac34f18 commit 968cadd

File tree

2 files changed

+76
-7
lines changed

2 files changed

+76
-7
lines changed

signalduino/commands.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,15 @@ def get_help(self) -> str:
3535

3636
def get_free_ram(self) -> str:
3737
"""Query free RAM (R)."""
38-
return self._send("R", expect_response=True, timeout=2.0, response_pattern=None)
38+
# Response is typically a number (bytes)
39+
pattern = re.compile(r"^\d+$")
40+
return self._send("R", expect_response=True, timeout=2.0, response_pattern=pattern)
3941

4042
def get_uptime(self) -> str:
4143
"""Query uptime in seconds (t)."""
42-
return self._send("t", expect_response=True, timeout=2.0, response_pattern=None)
44+
# Response is a number (seconds)
45+
pattern = re.compile(r"^\d+$")
46+
return self._send("t", expect_response=True, timeout=2.0, response_pattern=pattern)
4347

4448
def ping(self) -> str:
4549
"""Ping device (P)."""
@@ -65,7 +69,9 @@ def factory_reset(self) -> str:
6569

6670
def get_config(self) -> str:
6771
"""Read configuration (CG)."""
68-
return self._send("CG", expect_response=True, timeout=2.0, response_pattern=None)
72+
# Response format: MS=1;MU=1;...
73+
pattern = re.compile(r"^MS=.*")
74+
return self._send("CG", expect_response=True, timeout=2.0, response_pattern=pattern)
6975

7076
def set_decoder_state(self, decoder: str, enabled: bool) -> None:
7177
"""
@@ -133,12 +139,16 @@ def init_wmbus(self) -> str:
133139
def read_eeprom(self, address: int) -> str:
134140
"""Read EEPROM byte (r<addr>)."""
135141
addr_hex = f"{address:02X}"
136-
return self._send(f"r{addr_hex}", expect_response=True, timeout=2.0, response_pattern=None)
142+
# Response format: EEPROM <addr> = <val>
143+
pattern = re.compile(r"EEPROM.*", re.IGNORECASE)
144+
return self._send(f"r{addr_hex}", expect_response=True, timeout=2.0, response_pattern=pattern)
137145

138146
def read_eeprom_block(self, address: int) -> str:
139147
"""Read EEPROM block (r<addr>n)."""
140148
addr_hex = f"{address:02X}"
141-
return self._send(f"r{addr_hex}n", expect_response=True, timeout=2.0, response_pattern=None)
149+
# Response format: EEPROM <addr> : <val> ...
150+
pattern = re.compile(r"EEPROM.*", re.IGNORECASE)
151+
return self._send(f"r{addr_hex}n", expect_response=True, timeout=2.0, response_pattern=pattern)
142152

143153
def set_patable(self, value: str | int) -> str:
144154
"""Write PA Table (x<val>)."""

tests/test_controller.py

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,22 +95,81 @@ def readline_side_effect():
9595
controller = SignalduinoController(transport=mock_transport, parser=mock_parser)
9696
controller.connect()
9797
try:
98-
response = controller.send_command("V", expect_response=True, timeout=1)
98+
response = controller.commands.get_version(timeout=1)
9999
mock_transport.write_line.assert_called_with("V")
100100
assert response is not None
101101
assert "SIGNALduino" in response
102102
finally:
103103
controller.disconnect()
104104

105105

106+
def test_send_command_with_interleaved_message(mock_transport, mock_parser):
107+
"""
108+
Test sending a command and receiving an irrelevant message before the
109+
expected command response. The irrelevant message must not be consumed
110+
as the response, and the correct response must still be received.
111+
"""
112+
# Queue for all messages from the device
113+
response_q = queue.Queue()
114+
115+
# The irrelevant message (e.g., an asynchronous received signal)
116+
interleaved_message = "MU;P0=353;P1=-184;D=0123456789;CP=1;SP=0;R=248;\n"
117+
# The expected command response
118+
command_response = "V 3.5.0-dev SIGNALduino\n"
119+
120+
def write_line_side_effect(payload):
121+
# When the controller writes "V", simulate the device responding with
122+
# an interleaved message *then* the command response.
123+
if payload == "V":
124+
# 1. Interleaved message
125+
response_q.put(interleaved_message)
126+
# 2. Command response
127+
response_q.put(command_response)
128+
129+
def readline_side_effect():
130+
# Simulate blocking read that gets a value from the queue.
131+
try:
132+
return response_q.get(timeout=0.5)
133+
except queue.Empty:
134+
return None
135+
136+
mock_transport.write_line.side_effect = write_line_side_effect
137+
mock_transport.readline.side_effect = readline_side_effect
138+
139+
# Mock the parser to track if the interleaved message is passed to it
140+
mock_parser.parse_line = Mock(wraps=mock_parser.parse_line)
141+
142+
controller = SignalduinoController(transport=mock_transport, parser=mock_parser)
143+
controller.connect()
144+
try:
145+
response = controller.commands.get_version(timeout=1)
146+
mock_transport.write_line.assert_called_with("V")
147+
148+
# 1. Verify that the correct command response was received by send_command
149+
assert response is not None
150+
assert "SIGNALduino" in response
151+
assert response.strip() == command_response.strip()
152+
153+
# 2. Verify that the interleaved message was passed to the parser
154+
# The parser loop (_parser_loop) should attempt to parse the interleaved_message
155+
# because _handle_as_command_response should return False for it.
156+
mock_parser.parse_line.assert_called_with(interleaved_message.strip())
157+
158+
# Give the parser thread a moment to process the message
159+
time.sleep(0.1)
160+
161+
finally:
162+
controller.disconnect()
163+
164+
106165
def test_send_command_timeout(mock_transport, mock_parser):
107166
"""Test that a command times out if no response is received."""
108167
mock_transport.readline.return_value = None
109168
controller = SignalduinoController(transport=mock_transport, parser=mock_parser)
110169
controller.connect()
111170
try:
112171
with pytest.raises(SignalduinoCommandTimeout):
113-
controller.send_command("V", expect_response=True, timeout=0.2)
172+
controller.commands.get_version(timeout=0.2)
114173
finally:
115174
controller.disconnect()
116175

0 commit comments

Comments
 (0)