brp-lib 4.0.2__tar.gz → 4.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: brp_lib
3
- Version: 4.0.2
3
+ Version: 4.2.0
4
4
  Summary: Low-level C library wrapper for Baltech RFID readers.
5
5
  Author-email: Baltech AG <info@baltech.de>
6
6
  Requires-Python: >=3.9
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "brp_lib"
3
- version = "4.0.2"
3
+ version = "4.2.0"
4
4
  description = "Low-level C library wrapper for Baltech RFID readers."
5
5
  authors = [{ name = "Baltech AG", email = "info@baltech.de" }]
6
6
  requires-python = ">=3.9"
@@ -3,16 +3,24 @@ import importlib.resources
3
3
  from pathlib import Path
4
4
  from typing import Optional, Union
5
5
 
6
- from .protocols import BrpStack, UsbHid, RS232, SecureChannel
6
+ from .protocols import (
7
+ BrpProtocol, BrpStack, CryptoProtocol, CustomProtocol, IoProtocol, Layer,
8
+ RS232, SecureChannel, UsbHid,
9
+ )
7
10
  from .dll_wrapper import c_brp_lib
8
11
  from . import err, protocols
9
12
 
10
13
  __all__ = [
11
14
  "c_brp_lib",
15
+ "BrpProtocol",
16
+ "IoProtocol",
17
+ "CryptoProtocol",
12
18
  "BrpStack",
13
19
  "UsbHid",
14
20
  "RS232",
15
21
  "SecureChannel",
22
+ "CustomProtocol",
23
+ "Layer",
16
24
  "protocols",
17
25
  "get_brp_lib_path",
18
26
  "set_brp_lib_path",
@@ -1,6 +1,8 @@
1
1
  import ctypes
2
2
  import typing
3
3
  import functools
4
+ import os
5
+ import sys
4
6
  from contextlib import suppress
5
7
  from pathlib import Path
6
8
  from typing import Any, Protocol, TypeVar, Callable, Optional
@@ -105,6 +107,23 @@ class CBrpLibFunction(Protocol[P, R]):
105
107
  ...
106
108
 
107
109
 
110
+ def _decode_path(raw: bytes) -> str:
111
+ # the DLL builds paths via ANSI Win32 APIs, so they arrive in the current
112
+ # ANSI code page ("mbcs"), not UTF-8; strict on purpose: never fabricate a
113
+ # wrong path - callers degrade to None instead
114
+ if sys.platform == "win32":
115
+ return raw.decode("mbcs")
116
+ return os.fsdecode(raw)
117
+
118
+
119
+ def _encode_path(path: str) -> bytes:
120
+ # strict: a path the ANSI code page cannot represent must fail at set time
121
+ # instead of being silently rewritten by Windows' best-fit mapping
122
+ if sys.platform == "win32":
123
+ return path.encode("mbcs")
124
+ return os.fsencode(path)
125
+
126
+
108
127
  CharP = Annotated[ctypes.Array[ctypes.c_char], ctypes.c_char_p]
109
128
  CharPP = Annotated[Any, ctypes.POINTER(ctypes.c_char_p)]
110
129
  CInt = Annotated[int, ctypes.c_int]
@@ -125,6 +144,7 @@ CMempool = Annotated[Any, mempool]
125
144
  CIoiter = Annotated[Any, ioiter]
126
145
  CIoiterP = Annotated[Any, ioiter_p]
127
146
  CUShort = Annotated[int, ctypes.c_ushort]
147
+ CPath = Annotated[Optional[str], ctypes.c_char_p, (_decode_path, _encode_path)]
128
148
 
129
149
  ErrorCode = Annotated[None, errcode]
130
150
 
@@ -140,48 +160,81 @@ def _to_ctype(t: Any) -> Any:
140
160
  return t
141
161
 
142
162
 
143
- def _get_log_link(prot: Any) -> Optional[str]:
144
- path_ptr = c_brp_lib.brp_get_log_path(prot, None)
145
- if not path_ptr:
163
+ def _get_codec(t: Any) -> Any:
164
+ # optional third Annotated arg: a (decode, encode) pair converting this type
165
+ # at the C boundary (decode: C result -> python, encode: python arg -> C)
166
+ args = typing.get_args(t)
167
+ return args[2] if len(args) > 2 else None
168
+
169
+
170
+ def _with_codecs(
171
+ fn: Callable[..., Any], argtypes: Any, restype: Any
172
+ ) -> Callable[..., Any]:
173
+ arg_codecs = [_get_codec(t) for t in argtypes]
174
+ result_codec = _get_codec(restype)
175
+ if not any(arg_codecs) and result_codec is None:
176
+ return fn
177
+
178
+ @functools.wraps(fn)
179
+ def wrapper(*args: Any) -> Any:
180
+ converted = [
181
+ codec[1](arg) if codec is not None and arg is not None else arg
182
+ for codec, arg in zip(arg_codecs, args)
183
+ ]
184
+ result = fn(*converted)
185
+ if result_codec is not None and result is not None:
186
+ return result_codec[0](result)
187
+ return result
188
+
189
+ return wrapper
190
+
191
+
192
+ def _get_log_link(lib: "CBrpLib", prot: Any) -> Optional[str]:
193
+ log_path = lib.brp_get_log_path(prot, None)
194
+ if not log_path:
146
195
  return None
147
196
 
148
- path = Path(ctypes.string_at(path_ptr).decode('utf-8'))
197
+ path = Path(log_path)
149
198
  if not path.exists():
150
199
  return None
151
200
  url = f"{path.as_uri()}"
152
201
 
153
202
  anchor_ptr = ctypes.c_char_p()
154
- c_brp_lib.brp_get_current_log_anchor(prot, ctypes.byref(anchor_ptr), None)
203
+ lib.brp_get_current_log_anchor(prot, ctypes.byref(anchor_ptr), None)
155
204
  if anchor_ptr.value:
156
205
  return f"{url}{anchor_ptr.value.decode('utf-8')}"
157
206
  return url
158
207
 
159
208
 
160
- def with_error_handling(fn: Callable[P, int]) -> Callable[P, None]:
209
+ def with_error_handling(lib: "CBrpLib", fn: Callable[P, int]) -> Callable[P, None]:
210
+ """Map nonzero errcodes to BrpError, attaching the log link looked up via
211
+ ``lib`` — the instance the wrapped function belongs to, not the global."""
161
212
  @functools.wraps(fn)
162
213
  def wrapper(*args: P.args, **kwargs: P.kwargs) -> None:
163
214
  result = fn(*args, **kwargs)
164
215
  if result:
165
216
  log_url = None
166
- with suppress():
217
+ with suppress(Exception):
167
218
  if len(args) > 0 and isinstance(args[0], protocol):
168
- log_url = _get_log_link(args[0])
219
+ log_url = _get_log_link(lib, args[0])
169
220
  raise BrpError.from_error_code(result, log_url=log_url)
170
221
 
171
222
  return wrapper
172
223
 
173
224
 
174
- def _not_implemented(name: str, version: str, *args: object, **kwargs: object) -> Never:
175
- raise NotImplementedError(f"{name} is not implemented in brp_lib {version}")
176
-
177
-
178
225
  class CBrpLib:
179
226
  brp_get_version: CBrpLibFunction[[], CharP]
180
227
  brp_create: CBrpLibFunction[[], CProtocol]
228
+ brp_create_custom: CBrpLibFunction[[CInt], CProtocol]
181
229
  brp_destroy: CBrpLibFunction[[CProtocol], ErrorCode]
230
+ brp_protocol_free: CBrpLibFunction[[CProtocol], None]
182
231
  brp_set_io: CBrpLibFunction[[CProtocol, CProtocol], ErrorCode]
232
+ brp_set_layer: CBrpLibFunction[[CProtocol, CInt, CProtocol], ErrorCode]
233
+ brp_get_layer: CBrpLibFunction[[CProtocol, CInt], CProtocol]
234
+ brp_detach_layer: CBrpLibFunction[[CProtocol, CInt], CProtocol]
183
235
  brp_open: CBrpLibFunction[[CProtocol], ErrorCode]
184
236
  brp_close: CBrpLibFunction[[CProtocol], ErrorCode]
237
+ brp_flush: CBrpLibFunction[[CProtocol], ErrorCode]
185
238
  brp_suppress_monitoring: CBrpLibFunction[[CProtocol], ErrorCode]
186
239
  brp_get_io_id: CBrpLibFunction[[CProtocol, CharPP, CFrame], ErrorCode]
187
240
  brp_get_friendly_name: CBrpLibFunction[[CProtocol, CharPP], ErrorCode]
@@ -209,9 +262,9 @@ class CBrpLib:
209
262
  brp_frame_read_eof: CBrpLibFunction[[CFrameReader], CBool]
210
263
  brp_annotation_start: CBrpLibFunction[[CProtocol], None]
211
264
  brp_annotation_end: CBrpLibFunction[[CProtocol, CBool, CharP], None]
212
- brp_set_log_path: CBrpLibFunction[[CProtocol, CharP], ErrorCode]
265
+ brp_set_log_path: CBrpLibFunction[[CProtocol, CPath], ErrorCode]
213
266
  brp_get_current_log_anchor: CBrpLibFunction[[CProtocol, CharPP, CMempool], ErrorCode]
214
- brp_get_log_path: CBrpLibFunction[[CProtocol, CMempool], CharP]
267
+ brp_get_log_path: CBrpLibFunction[[CProtocol, CMempool], CPath]
215
268
 
216
269
  def __init__(self) -> None:
217
270
  self.__dll_path: Optional[Path] = None
@@ -222,28 +275,29 @@ class CBrpLib:
222
275
 
223
276
  @dll_path.setter
224
277
  def dll_path(self, dll_path: Path) -> None:
225
- self.__dll_path = dll_path.absolute()
226
- dll = ctypes.CDLL(str(self.__dll_path))
278
+ abs_path = dll_path.absolute()
279
+ dll = ctypes.CDLL(str(abs_path))
280
+ if getattr(dll, "brp_get_version", None) is None:
281
+ raise ValueError(
282
+ f"{abs_path} is not a brp_lib library (does not export brp_get_version)"
283
+ )
284
+ self.__dll_path = abs_path
227
285
 
228
286
  for name, annotation in type(self).__annotations__.items():
229
287
  if typing.get_origin(annotation) == CBrpLibFunction:
230
288
  argtypes, restype = typing.get_args(annotation)
231
- fn = getattr(dll, name, None)
232
- if fn is None:
233
- fn = functools.partial(
234
- _not_implemented,
235
- name=name,
236
- version=bytes(self.brp_get_version()).decode("ascii")
237
- if name != "brp_get_version"
238
- else "<unknown>"
239
- )
240
- else:
241
- fn.restype = _to_ctype(restype)
242
- fn.argtypes = tuple(map(_to_ctype, argtypes))
243
- if restype == ErrorCode:
244
- fn = with_error_handling(fn)
289
+ fn: Any = getattr(dll, name, functools.partial(self._raise_not_implemented, name))
290
+ fn.restype = _to_ctype(restype)
291
+ fn.argtypes = tuple(map(_to_ctype, argtypes))
292
+ fn = _with_codecs(fn, argtypes, restype)
293
+ if restype == ErrorCode:
294
+ fn = with_error_handling(self, fn)
245
295
  setattr(self, name, fn)
246
296
 
297
+ def _raise_not_implemented(self, name: str, *args: object, **kwargs: object) -> Never:
298
+ version = bytes(self.brp_get_version()).decode("ascii")
299
+ raise NotImplementedError(f"{name} is not implemented in brp_lib {version}")
300
+
247
301
  def __getattr__(self, item: str) -> Any:
248
302
  if item in type(self).__annotations__:
249
303
  raise RuntimeError("Please initialize the brp_lib path calling `brp_lib.set_brp_lib_path('.../libbrp_lib.so')`.")
@@ -3,6 +3,7 @@ from typing import Dict, Type, Optional
3
3
 
4
4
  __all__ = [
5
5
  "BrpError",
6
+ "UnknownError",
6
7
  "DeviceError",
7
8
  "CommunicationError",
8
9
  "CommUnsupported",
@@ -63,8 +64,10 @@ class BrpError(Exception, metaclass=abc.ABCMeta):
63
64
 
64
65
  @classmethod
65
66
  def from_error_code(cls, ec: int, *, log_url: Optional[str] = None) -> "BrpError":
66
- error_class: Type["BrpError"] = cls.ErrorCodeMap.get(ec, cls)
67
- return error_class(log_url=log_url)
67
+ error_class = cls.ErrorCodeMap.get(ec)
68
+ if error_class is not None:
69
+ return error_class(log_url=log_url)
70
+ return UnknownError(ec, log_url=log_url)
68
71
 
69
72
  def __init__(self, message: str = "", *, log_url: Optional[str] = None) -> None:
70
73
  self.log_url = log_url
@@ -75,7 +78,7 @@ class BrpError(Exception, metaclass=abc.ABCMeta):
75
78
  map(str,
76
79
  filter(
77
80
  lambda i: i is not None, [
78
- f"0x{self.ErrorCode:08X} {super()!s}\n",
81
+ f"0x{self.ErrorCode:08X} {super().__str__()}\n",
79
82
  f"DESC: {self.__doc__.strip() if self.__doc__ else None}",
80
83
  f"DOCS: {self.URL}" if self.URL else None,
81
84
  f"LOGS: {self.log_url}" if self.log_url else None,
@@ -85,6 +88,13 @@ class BrpError(Exception, metaclass=abc.ABCMeta):
85
88
  )
86
89
 
87
90
 
91
+ class UnknownError(BrpError):
92
+ """An error with an error code not known to the SDK."""
93
+ def __init__(self, error_code: int, *, log_url: Optional[str] = None) -> None:
94
+ self.ErrorCode = error_code
95
+ super().__init__(log_url=log_url)
96
+
97
+
88
98
  class CommunicationError(BrpError, metaclass=abc.ABCMeta):
89
99
  """Any kind of device <-> host communication problem"""
90
100