Skip to content

Commit 5fecbe6

Browse files
authored
Merge pull request #42 from MrClock8163/feature-geocom-methods-refactor
GeoCom methods refactor
2 parents d3630d6 + 394b0de commit 5fecbe6

File tree

5 files changed

+188
-1142
lines changed

5 files changed

+188
-1142
lines changed

src/geocompy/protocols.py

Lines changed: 148 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,17 @@
2020
"""
2121
from __future__ import annotations
2222

23+
import re
2324
from enum import IntEnum
2425
from logging import Logger
26+
from traceback import format_exc
2527
from typing import (
2628
Any, Callable, Iterable, Literal,
2729
Generic, TypeVar, overload
2830
)
2931

32+
from serial import SerialException, SerialTimeoutException
33+
3034
from .communication import Connection, get_logger
3135
from .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+
4558
class 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

315448
class GeoComSubsystem:

0 commit comments

Comments
 (0)