88
99from jsonschema import validate , ValidationError
1010from signalduino .exceptions import CommandValidationError , SignalduinoCommandTimeout
11+ from .constants import SDUINO_CMD_TIMEOUT
1112
1213if TYPE_CHECKING :
1314 # Importiere SignalduinoController nur für Type Hinting zur Kompilierzeit
@@ -39,11 +40,11 @@ def _parse_decoder_config(self, response: str) -> Dict[str, int]:
3940 pass
4041 return config
4142
42- async def get_version (self , timeout : float = 2.0 ) -> str :
43+ async def get_version (self , timeout : float = SDUINO_CMD_TIMEOUT ) -> str :
4344 """Firmware version (V)"""
4445 return await self ._send_command (command = "V" , expect_response = True , timeout = timeout )
4546
46- async def get_free_ram (self , timeout : float = 2.0 ) -> int :
47+ async def get_free_ram (self , timeout : float = SDUINO_CMD_TIMEOUT ) -> int :
4748 """Free RAM (R)"""
4849 # Firmware typically responds with a numeric value (e.g., "1234")
4950 response_pattern = re .compile (r'^(\d+)$' )
@@ -54,7 +55,7 @@ async def get_free_ram(self, timeout: float = 2.0) -> int:
5455 return int (match .group (1 ))
5556 raise ValueError (f"Unexpected response format for Free RAM: { response } " )
5657
57- async def get_uptime (self , timeout : float = 2.0 ) -> int :
58+ async def get_uptime (self , timeout : float = SDUINO_CMD_TIMEOUT ) -> int :
5859 """System uptime (t)"""
5960 # Firmware typically responds with a numeric value (e.g., "1234")
6061 response_pattern = re .compile (r'^(\d+)$' )
@@ -65,17 +66,17 @@ async def get_uptime(self, timeout: float = 2.0) -> int:
6566 return int (match .group (1 ))
6667 raise ValueError (f"Unexpected response format for Uptime: { response } " )
6768
68- async def get_cmds (self , timeout : float = 2.0 ) -> str :
69+ async def get_cmds (self , timeout : float = SDUINO_CMD_TIMEOUT ) -> str :
6970 """Available commands (?)"""
7071 return await self ._send_command (command = "?" , expect_response = True , timeout = timeout )
7172
72- async def ping (self , timeout : float = 2.0 ) -> str :
73+ async def ping (self , timeout : float = SDUINO_CMD_TIMEOUT ) -> str :
7374 """Ping (P)"""
7475 return await self ._send_command (command = "P" , expect_response = True , timeout = timeout )
7576
76- async def get_config (self , timeout : float = 2.0 ) -> Dict [str , int ]:
77+ async def get_config (self , timeout : float = SDUINO_CMD_TIMEOUT ) -> Dict [str , int ]:
7778 """Decoder configuration (CG) - Returns parsed dictionary."""
78- config_pattern = re .compile (r'^MS=[01];MU=[01];MC=[01];Mred=[01](;M [A-Za-z0-9]+=[01]) *$' )
79+ config_pattern = re .compile (r'^\s*( [A-Za-z0-9]+=\d+;?)+\s *$' , re . IGNORECASE )
7980 response = await self ._send_command (
8081 command = "CG" ,
8182 expect_response = True ,
@@ -84,19 +85,20 @@ async def get_config(self, timeout: float = 2.0) -> Dict[str, int]:
8485 )
8586 return self ._parse_decoder_config (response )
8687
87- async def get_ccconf (self , timeout : float = 2.0 ) -> Dict [str , str ]:
88+ async def get_ccconf (self , timeout : float = SDUINO_CMD_TIMEOUT ) -> Dict [str , str ]:
8889 """CC1101 configuration registers (C0DnF). Returns a dictionary with the raw string."""
8990 # Response-Pattern aus 00_SIGNALduino.pm, Zeile 86, angepasst an Python regex
90- response = await self ._send_command (command = "C0DnF" , expect_response = True , timeout = timeout , response_pattern = re .compile (r'C0Dn11 \s*=\s*[a-f0-9]+ ' , re .IGNORECASE ))
91+ response = await self ._send_command (command = "C0DnF" , expect_response = True , timeout = timeout , response_pattern = re .compile (r'^ \s*C0D\w*\s* =\s*.*$ ' , re .IGNORECASE ))
9192 # Kapselt den rohen String, um die MQTT-Antwort konsistent als Dict zurückzugeben
9293 return {"cc1101_config_string" : response }
9394
94- async def get_ccpatable (self , timeout : float = 2.0 ) -> str :
95+ async def get_ccpatable (self , timeout : float = SDUINO_CMD_TIMEOUT ) -> Dict [ str , str ] :
9596 """CC1101 PA table (C3E)"""
9697 # Response-Pattern aus 00_SIGNALduino.pm, Zeile 88
97- return await self ._send_command (command = "C3E" , expect_response = True , timeout = timeout , response_pattern = re .compile (r'C3E\s*=\s*.*' ))
98+ response = await self ._send_command (command = "C3E" , expect_response = True , timeout = timeout , response_pattern = re .compile (r'^\s*C3E\s*=\s*.*\s*$' , re .IGNORECASE ))
99+ return {"pa_table_hex" : response }
98100
99- async def factory_reset (self , timeout : float = 5.0 ) -> Dict [str , str ]:
101+ async def factory_reset (self , timeout : float = SDUINO_CMD_TIMEOUT ) -> Dict [str , str ]:
100102 """Sets EEPROM defaults, effectively a factory reset (e).
101103
102104 This command does not send a response unless debug mode is active. We treat the command
@@ -113,33 +115,35 @@ async def get_cc1101_settings(self, payload: Optional[Dict[str, Any]] = None) ->
113115 """
114116 # Alle benötigten Getter existieren bereits in SignalduinoCommands
115117 freq_result = await self .get_frequency (payload )
116- bandwidth = await self .get_bandwidth (payload )
117- rampl = await self .get_rampl (payload )
118- sens = await self .get_sensitivity (payload )
119- datarate = await self .get_data_rate (payload )
118+ bandwidth_result = await self .get_bandwidth (payload )
119+ rampl_result = await self .get_rampl (payload )
120+ sens_result = await self .get_sensitivity (payload )
121+ datarate_result = await self .get_data_rate (payload )
120122
121123 return {
122- # Flatten the frequency structure
123- "frequency_mhz" : freq_result ["frequency_mhz" ],
124- "bandwidth" : bandwidth ,
125- "rampl" : rampl ,
126- "sens" : sens ,
127- "datarate" : datarate ,
124+ "frequency_mhz" : freq_result ["frequency" ],
125+ "bandwidth" : bandwidth_result ["bandwidth" ],
126+ "rampl" : rampl_result ["rampl" ],
127+ "sensitivity" : sens_result ["sensitivity" ],
128+ "datarate" : datarate_result ["datarate" ],
128129 }
129130
130131 # --- CC1101 Hardware Status GET-Methoden (Basierend auf 00_SIGNALduino.pm) ---
131132
132133 async def _read_register_value (self , register_address : int ) -> int :
133134 """Liest einen CC1101-Registerwert und gibt ihn als Integer zurück."""
134- response = await self .read_cc1101_register (register_address )
135- # Stellt sicher, dass wir nur den Wert nach 'C[A-Fa-f0-9]{2} = ' extrahieren
136- match = re .search (r'C[A-Fa-f0-9]{2}\s*=\s*([0-9A-Fa-f]+)' , response , re .IGNORECASE )
135+ response_dict = await self .read_cc1101_register (register_address )
136+ response = response_dict ["register_value" ]
137+
138+ # Stellt sicher, dass wir nur den Wert nach 'C[A-Fa-f0-9]{2}\s*=\s*([0-9A-Fa-f]+)' extrahieren
139+ # Hinzufügen von \s* um die Werte herum, um Whitespace-Toleranz zu erhöhen.
140+ match = re .search (r'C[A-Fa-f0-9]{2}\s*=\s*([0-9A-Fa-f]+)\s*' , response , re .IGNORECASE )
137141 if match :
138142 return int (match .group (1 ), 16 )
139143 # Fängt auch den Fall 'ccreg 00:' (default-Antwort) oder andere unerwartete Antworten ab
140144 raise ValueError (f"Unexpected response format for CC1101 register read: { response } " )
141145
142- async def get_bandwidth (self , payload : Optional [Dict [str , Any ]] = None ) -> float :
146+ async def get_bandwidth (self , payload : Optional [Dict [str , Any ]] = None ) -> Dict [ str , float ] :
143147 """Liest die CC1101 Bandbreitenregister (MDMCFG4/0x10) und berechnet die Bandbreite in kHz."""
144148 r10 = await self ._read_register_value (0x10 ) # MDMCFG4
145149
@@ -150,9 +154,9 @@ async def get_bandwidth(self, payload: Optional[Dict[str, Any]] = None) -> float
150154 # Frequenz (FXOSC) ist 26 MHz (26000 kHz)
151155 bandwidth_khz = 26000.0 / (8.0 * (4.0 + mant_b ) * (1 << exp_b ))
152156
153- return round (bandwidth_khz , 3 )
157+ return { "bandwidth" : round (bandwidth_khz , 3 )}
154158
155- async def get_rampl (self , payload : Optional [Dict [str , Any ]] = None ) -> int :
159+ async def get_rampl (self , payload : Optional [Dict [str , Any ]] = None ) -> Dict [ str , int ] :
156160 """Liest die CC1101 Verstärkungsregister (AGCCTRL0/0x1B) und gibt die Verstärkung in dB zurück."""
157161 r1b = await self ._read_register_value (0x1B ) # AGCCTRL0
158162
@@ -164,23 +168,25 @@ async def get_rampl(self, payload: Optional[Dict[str, Any]] = None) -> int:
164168 index = r1b & 7
165169
166170 if index < len (ampllist ):
167- return ampllist [index ]
171+ rampl_db = ampllist [index ]
168172 else :
169173 # Dies sollte nicht passieren, wenn die CC1101-Registerwerte korrekt sind
170174 logger .warning ("Invalid AGC_LNA_GAIN setting found in 0x1B: %s" , index )
171- return - 1 # Fehlerwert
175+ rampl_db = - 1 # Fehlerwert
176+
177+ return {"rampl" : rampl_db }
172178
173- async def get_sensitivity (self , payload : Optional [Dict [str , Any ]] = None ) -> int :
179+ async def get_sensitivity (self , payload : Optional [Dict [str , Any ]] = None ) -> Dict [ str , int ] :
174180 """Liest die CC1101 Empfindlichkeitsregister (RSSIAGC/0x1D) und gibt die Empfindlichkeit in dB zurück."""
175181 r1d = await self ._read_register_value (0x1D ) # RSSIAGC (0x1D)
176182
177183 # Sens (dB) = 4 + 4 * (r1d & 3)
178184 # Die unteren 2 Bits enthalten den LNA-Modus (LNA_PD_BUF)
179185 sens_db = 4 + 4 * (r1d & 3 )
180186
181- return sens_db
187+ return { "sensitivity" : sens_db }
182188
183- async def get_data_rate (self , payload : Optional [Dict [str , Any ]] = None ) -> float :
189+ async def get_data_rate (self , payload : Optional [Dict [str , Any ]] = None ) -> Dict [ str , float ] :
184190 """Liest die CC1101 Datenratenregister (MDMCFG4/0x10 und MDMCFG3/0x11) und berechnet die Datenrate in kBaud."""
185191 r10 = await self ._read_register_value (0x10 ) # MDMCFG4
186192 r11 = await self ._read_register_value (0x11 ) # MDMCFG3
@@ -201,7 +207,7 @@ async def get_data_rate(self, payload: Optional[Dict[str, Any]] = None) -> float
201207 # Umrechnung in kBaud (kiloBaud = kiloBits pro Sekunde)
202208 data_rate_kbaud = data_rate_hz / 1000.0
203209
204- return round (data_rate_kbaud , 2 )
210+ return { "datarate" : round (data_rate_kbaud , 2 )}
205211
206212 def _calculate_datarate_registers (self , datarate_kbaud : float ) -> tuple [int , int ]:
207213 """
@@ -260,11 +266,17 @@ def _calculate_datarate_registers(self, datarate_kbaud: float) -> tuple[int, int
260266
261267 return best_drate_e , best_drate_m
262268
263- async def read_cc1101_register (self , register_address : int , timeout : float = 2.0 ) -> str :
269+ async def read_cc1101_register (self , register_address : int , timeout : float = SDUINO_CMD_TIMEOUT ) -> Dict [ str , str ] :
264270 """Read CC1101 register (C<reg>)"""
265271 hex_addr = f"{ register_address :02X} "
266272 # Response-Pattern: ccreg 00: oder Cxx = yy (aus 00_SIGNALduino.pm, Zeile 87)
267- return await self ._send_command (command = f"C{ hex_addr } " , expect_response = True , timeout = timeout , response_pattern = re .compile (r'C[a-f0-9]{2}\s*=\s*[a-f0-9]+|ccreg 00:' , re .IGNORECASE ))
273+ # Die Regex muss an den Anfang und das Ende der Zeile gebunden werden (re.match wird verwendet)
274+ # ^(C[a-f0-9]{2}\s*=\s*[a-f0-9]+|ccreg 00:.*)\s*$
275+ # Hinweis: *Der Controller verwendet re.match*, was implizit ^ bindet.
276+ # Wir müssen den Regex also an das Ende binden, um Leerzeichen zu erlauben.
277+ response_pattern = re .compile (r'^\s*(C[a-f0-9]{2}\s*=\s*[a-f0-9]+|ccreg [a-f0-9]{2}:.*)\s*$' , re .IGNORECASE )
278+ response = await self ._send_command (command = f"C{ hex_addr } " , expect_response = True , timeout = timeout , response_pattern = response_pattern )
279+ return {"register_value" : response }
268280
269281 async def _get_frequency_registers (self ) -> int :
270282 """Liest die CC1101 Frequenzregister (FREQ2, FREQ1, FREQ0) und kombiniert sie zu einem 24-Bit-Wert (F_REG)."""
@@ -276,24 +288,24 @@ async def _get_frequency_registers(self) -> int:
276288
277289 # Funktion zum Extrahieren des Hex-Werts aus der Antwort: Cxx = <hex>
278290 def extract_hex_value (response : str ) -> int :
279- # Stellt sicher, dass wir nur den Wert nach 'C[A-Fa-f0-9]{2} = ' extrahieren
280- match = re .search (r'C[A-Fa-f0-9]{2}\s=\s([0-9A-Fa-f]+)$' , response )
291+ # Stellt sicher, dass wir nur den Wert nach 'C[A-Fa-f0-9]{2}\s=\s([0-9A-Fa-f]+)$ ' extrahieren
292+ match = re .search (r'C[A-Fa-f0-9]{2}\s* =\s* ([0-9A-Fa-f]+)\s* $' , response )
281293 if match :
282294 return int (match .group (1 ), 16 )
283295 # Fängt auch den Fall 'ccreg 00:' (default-Antwort) oder andere unerwartete Antworten ab
284296 raise ValueError (f"Unexpected response format for CC1101 register read: { response } " )
285297
286298 # FREQ2 (0D)
287299 response2 = await self .read_cc1101_register (FREQ2 )
288- freq2 = extract_hex_value (response2 )
300+ freq2 = extract_hex_value (response2 [ "register_value" ] )
289301
290302 # FREQ1 (0E)
291303 response1 = await self .read_cc1101_register (FREQ1 )
292- freq1 = extract_hex_value (response1 )
304+ freq1 = extract_hex_value (response1 [ "register_value" ] )
293305
294306 # FREQ0 (0F)
295307 response0 = await self .read_cc1101_register (FREQ0 )
296- freq0 = extract_hex_value (response0 )
308+ freq0 = extract_hex_value (response0 [ "register_value" ] )
297309
298310 # Die Register bilden eine 24-Bit-Zahl: (FREQ2 << 16) | (FREQ1 << 8) | FREQ0
299311 f_reg = (freq2 << 16 ) | (freq1 << 8 ) | freq0
@@ -317,14 +329,14 @@ async def get_frequency(self, payload: Optional[Dict[str, Any]] = None) -> Dict[
317329
318330 # Rückgabe des gekapselten und auf 4 Dezimalstellen gerundeten Wertes, wie in tests/test_mqtt.py erwartet.
319331 return {
320- "frequency_mhz " : round (frequency_mhz , 4 )
332+ "frequency " : round (frequency_mhz , 4 )
321333 }
322334
323- async def send_raw_message (self , command : str , timeout : float = 2.0 ) -> str :
335+ async def send_raw_message (self , command : str , timeout : float = SDUINO_CMD_TIMEOUT ) -> str :
324336 """Send raw message (M...)"""
325337 return await self ._send_command (command = command , expect_response = True , timeout = timeout )
326338
327- async def send_message (self , message : str , timeout : float = 2.0 ) -> None :
339+ async def send_message (self , message : str , timeout : float = SDUINO_CMD_TIMEOUT ) -> None :
328340 """Send a pre-encoded message (P...#R...). This is typically used for 'set raw' commands where the message is already fully formatted.
329341
330342 NOTE: This sends the message AS IS, without any wrapping like 'set raw '.
0 commit comments