2020"""
2121from __future__ import annotations
2222
23+ import re
2324from enum import IntEnum
2425from logging import Logger
26+ from traceback import format_exc
2527from typing import (
2628 Any , Callable , Iterable , Literal ,
2729 Generic , TypeVar , overload
2830)
2931
32+ from serial import SerialException , SerialTimeoutException
33+
3034from .communication import Connection , get_logger
3135from .data import Angle , Byte
3236
@@ -42,6 +46,15 @@ def __bool__(self) -> bool:
4246 return self == 0
4347
4448
49+ class _FallbackReturnCodes (GeoComReturnCode ):
50+ OK = 0
51+ UNDEFINED = 1
52+ COM_CANT_DECODE = 3074
53+ COM_CANT_SEND = 3075
54+ COM_TIMEDOUT = 3077
55+ COM_FAILED = 3085
56+
57+
4558class GeoComResponse (Generic [_P ]):
4659 """
4760 Container class for parsed GeoCom responses.
@@ -166,6 +179,21 @@ class GeoComProtocol:
166179 Base class for GeoCom protocol versions.
167180
168181 """
182+ _R1P : re .Pattern = re .compile (
183+ r"^%R1P,"
184+ r"(?P<comrc>\d+),"
185+ r"(?P<tr>\d+):"
186+ r"(?P<rc>\d+)"
187+ r"(?:,(?P<params>.*))?$"
188+ )
189+ _RPCNAMES : dict [int , str ] = {}
190+ _CODES : type [GeoComReturnCode ] = _FallbackReturnCodes
191+ _OK : GeoComReturnCode = _FallbackReturnCodes .OK
192+ _FAILED : GeoComReturnCode = _FallbackReturnCodes .COM_FAILED
193+ _CANTDECODE : GeoComReturnCode = _FallbackReturnCodes .COM_CANT_DECODE
194+ _CANTSEND : GeoComReturnCode = _FallbackReturnCodes .COM_CANT_SEND
195+ _TIMEOUT : GeoComReturnCode = _FallbackReturnCodes .COM_TIMEDOUT
196+ _UNDEF : GeoComReturnCode = _FallbackReturnCodes .UNDEFINED
169197
170198 REF_VERSION = (0 , 0 )
171199 """
@@ -197,6 +225,7 @@ def __init__(
197225 if logger is None :
198226 logger = get_logger ("/dev/null" )
199227 self ._logger : Logger = logger
228+ self ._precision = 15
200229
201230 @overload
202231 def request (
@@ -248,32 +277,78 @@ def request(
248277 GeoComResponse
249278 Parsed return codes and parameters from the RPC response.
250279
251- Raises
252- ------
253- NotImplementedError
254- If the method is not implemented on the class.
255-
256280 """
257- raise NotImplementedError ()
281+ strparams : list [str ] = []
282+ for item in params :
283+ match item :
284+ case Angle ():
285+ value = f"{ round (float (item ), self ._precision ):f} "
286+ value = value .rstrip ("0" )
287+ if value [- 1 ] == "." :
288+ value += "0"
289+ case Byte ():
290+ value = str (item )
291+ case float ():
292+ value = f"{ round (item , self ._precision ):f} " .rstrip ("0" )
293+ if value [- 1 ] == "." :
294+ value += "0"
295+ case int ():
296+ value = f"{ item :d} "
297+ case str ():
298+ value = f"\" { item } \" "
299+ case _:
300+ raise TypeError (f"unexpected parameter type: { type (item )} " )
301+
302+ strparams .append (value )
303+
304+ cmd = f"%R1Q,{ rpc } :{ ',' .join (strparams )} "
305+ try :
306+ answer = self ._conn .exchange (cmd )
307+ except SerialTimeoutException :
308+ self ._logger .error (format_exc ())
309+ answer = (
310+ f"%R1P,{ self ._TIMEOUT :d} ,"
311+ f"0:{ self ._OK :d} "
312+ )
313+ except SerialException :
314+ self ._logger .error (format_exc ())
315+ answer = (
316+ f"%R1P,{ self ._CANTSEND :d} ,"
317+ f"0:{ self ._OK :d} "
318+ )
319+ except Exception :
320+ self ._logger .error (format_exc ())
321+ answer = (
322+ f"%R1P,{ self ._FAILED :d} ,"
323+ f"0:{ self ._OK :d} "
324+ )
325+
326+ response = self .parse_response (
327+ cmd ,
328+ answer ,
329+ parsers
330+ )
331+ self ._logger .debug (response )
332+ return response
258333
259334 @overload
260335 def parse_response (
261- cls ,
336+ self ,
262337 cmd : str ,
263338 response : str ,
264339 parsers : Callable [[str ], _T ] | None = None
265340 ) -> GeoComResponse [_T ]: ...
266341
267342 @overload
268343 def parse_response (
269- cls ,
344+ self ,
270345 cmd : str ,
271346 response : str ,
272347 parsers : Iterable [Callable [[str ], Any ]] | None = None
273348 ) -> GeoComResponse [tuple ]: ...
274349
275350 def parse_response (
276- cls ,
351+ self ,
277352 cmd : str ,
278353 response : str ,
279354 parsers : (
@@ -303,13 +378,71 @@ def parse_response(
303378 GeoComResponse
304379 Parsed return codes and parameters from the RPC response.
305380
306- Raises
307- ------
308- NotImplementedError
309- If the method is not implemented on the class.
310-
311381 """
312- raise NotImplementedError ()
382+ m = self ._R1P .match (response )
383+ rpc = int (cmd .split (":" )[0 ].split ("," )[1 ])
384+ rpcname = self ._RPCNAMES .get (rpc , str (rpc ))
385+ if not m :
386+ return GeoComResponse (
387+ rpcname ,
388+ cmd ,
389+ response ,
390+ self ._CANTDECODE ,
391+ self ._OK ,
392+ 0
393+ )
394+
395+ groups = m .groupdict ()
396+ values = groups .get ("params" , "" )
397+ if values is None :
398+ values = ""
399+
400+ if parsers is None :
401+ parsers = ()
402+ elif not isinstance (parsers , Iterable ):
403+ parsers = (parsers ,)
404+
405+ params : list = []
406+ try :
407+ for func , value in zip (parsers , values .split ("," )):
408+ params .append (func (value ))
409+ except Exception :
410+ return GeoComResponse (
411+ rpcname ,
412+ cmd ,
413+ response ,
414+ self ._CANTDECODE ,
415+ self ._OK ,
416+ 0
417+ )
418+
419+ try :
420+ comrc = self ._CODES (int (groups ["comrc" ]))
421+ except Exception :
422+ comrc = self ._UNDEF
423+
424+ try :
425+ rc = self ._CODES (int (groups ["rc" ]))
426+ except Exception :
427+ rc = self ._UNDEF
428+
429+ match len (params ):
430+ case 0 :
431+ params_final = None
432+ case 1 :
433+ params_final = params [0 ]
434+ case _:
435+ params_final = tuple (params )
436+
437+ return GeoComResponse (
438+ rpcname ,
439+ cmd ,
440+ response ,
441+ comrc ,
442+ rc ,
443+ int (groups ["tr" ]),
444+ params_final
445+ )
313446
314447
315448class GeoComSubsystem :
0 commit comments