brp-lib 4.0.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.
- brp_lib-4.0.0/PKG-INFO +7 -0
- brp_lib-4.0.0/pyproject.toml +27 -0
- brp_lib-4.0.0/setup.cfg +4 -0
- brp_lib-4.0.0/src/brp_lib/__init__.py +44 -0
- brp_lib-4.0.0/src/brp_lib/brp_lib.win32.dll +0 -0
- brp_lib-4.0.0/src/brp_lib/brp_lib.win64.dll +0 -0
- brp_lib-4.0.0/src/brp_lib/dll_wrapper.py +253 -0
- brp_lib-4.0.0/src/brp_lib/err.py +328 -0
- brp_lib-4.0.0/src/brp_lib/protocols.py +355 -0
- brp_lib-4.0.0/src/brp_lib/py.typed +0 -0
- brp_lib-4.0.0/src/brp_lib.egg-info/PKG-INFO +7 -0
- brp_lib-4.0.0/src/brp_lib.egg-info/SOURCES.txt +13 -0
- brp_lib-4.0.0/src/brp_lib.egg-info/dependency_links.txt +1 -0
- brp_lib-4.0.0/src/brp_lib.egg-info/requires.txt +1 -0
- brp_lib-4.0.0/src/brp_lib.egg-info/top_level.txt +1 -0
brp_lib-4.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "brp_lib"
|
|
3
|
+
version = "4.00.00"
|
|
4
|
+
description = "Low-level C library wrapper for Baltech RFID readers."
|
|
5
|
+
authors = [{ name = "Baltech AG", email = "info@baltech.de" }]
|
|
6
|
+
requires-python = ">=3.9"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"typing-extensions",
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
[build-system]
|
|
12
|
+
requires = ['setuptools']
|
|
13
|
+
build-backend = 'setuptools.build_meta'
|
|
14
|
+
|
|
15
|
+
[tool.setuptools]
|
|
16
|
+
include-package-data = true
|
|
17
|
+
package-dir = {"" = "src"}
|
|
18
|
+
|
|
19
|
+
[tool.setuptools.package-data]
|
|
20
|
+
brp_lib = ["*.dll", "py.typed"]
|
|
21
|
+
|
|
22
|
+
[tool.uv]
|
|
23
|
+
python-preference = "only-managed"
|
|
24
|
+
|
|
25
|
+
[tool.mypy]
|
|
26
|
+
strict = true
|
|
27
|
+
python_version = "3.9"
|
brp_lib-4.0.0/setup.cfg
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import importlib.resources
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Optional, Union
|
|
5
|
+
|
|
6
|
+
from .protocols import BrpStack, UsbHid, RS232, SecureChannel
|
|
7
|
+
from .dll_wrapper import c_brp_lib
|
|
8
|
+
from . import err, protocols
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"c_brp_lib",
|
|
12
|
+
"BrpStack",
|
|
13
|
+
"UsbHid",
|
|
14
|
+
"RS232",
|
|
15
|
+
"SecureChannel",
|
|
16
|
+
"protocols",
|
|
17
|
+
"get_brp_lib_path",
|
|
18
|
+
"set_brp_lib_path",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
def get_brp_lib_path() -> Optional[Path]:
|
|
22
|
+
return c_brp_lib.dll_path
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def set_brp_lib_path(path: Union[Path, str]) -> None:
|
|
26
|
+
c_brp_lib.dll_path = Path(path)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _autodetect_brp_lib() -> None:
|
|
30
|
+
arch = 64 if sys.maxsize > 2 ** 32 else 32
|
|
31
|
+
platform = sys.platform.replace("win32", "win")
|
|
32
|
+
extension = {
|
|
33
|
+
"win": "dll"
|
|
34
|
+
}.get(platform, "unknown")
|
|
35
|
+
dll_name = f"brp_lib.{platform}{arch}.{extension}"
|
|
36
|
+
|
|
37
|
+
root_resource = importlib.resources.files("brp_lib")
|
|
38
|
+
dll_resource = root_resource / dll_name
|
|
39
|
+
dll_path = Path(str(dll_resource))
|
|
40
|
+
if dll_path.exists():
|
|
41
|
+
set_brp_lib_path(dll_path)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
_autodetect_brp_lib()
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import ctypes
|
|
2
|
+
import typing
|
|
3
|
+
import functools
|
|
4
|
+
from contextlib import suppress
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, Protocol, TypeVar, Callable, Optional
|
|
7
|
+
from typing_extensions import Annotated, ParamSpec, Never
|
|
8
|
+
|
|
9
|
+
from .err import BrpError
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
buf = ctypes.POINTER(ctypes.c_char)
|
|
13
|
+
time = ctypes.c_ulong
|
|
14
|
+
errcode = ctypes.c_uint
|
|
15
|
+
layer_id = ctypes.c_int
|
|
16
|
+
socket = ctypes.c_int
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class protocol_t(ctypes.Structure):
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
protocol = ctypes.POINTER(protocol_t)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class frame_t(ctypes.Structure):
|
|
27
|
+
_fields_ = [
|
|
28
|
+
("ptr", buf), # type is brp_buf, but in python
|
|
29
|
+
# buf is defined in the scope of
|
|
30
|
+
# this class definition...
|
|
31
|
+
("act_size", ctypes.c_size_t),
|
|
32
|
+
("total_size", ctypes.c_size_t),
|
|
33
|
+
] # last read/write of frame failed
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
frame = ctypes.POINTER(frame_t)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class frame_reader_t(ctypes.Structure):
|
|
40
|
+
_fields_ = [("frame", frame), ("ptr", buf), ("err", ctypes.c_bool)]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
frame_reader = ctypes.POINTER(frame_reader_t)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class mempool_object_t(ctypes.Structure):
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
mempool = ctypes.POINTER(mempool_object_t)
|
|
51
|
+
|
|
52
|
+
mempool_object_t._fields_ = [
|
|
53
|
+
("prev", ctypes.POINTER(mempool_object_t)),
|
|
54
|
+
("next", ctypes.POINTER(mempool_object_t)),
|
|
55
|
+
("buf", ctypes.c_char_p),
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class ioiter_t(ctypes.Structure):
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
ioiter = ctypes.POINTER(ioiter_t)
|
|
64
|
+
ioiter_p = ctypes.POINTER(ioiter)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
cb_generic_t = ctypes.CFUNCTYPE(errcode, protocol)
|
|
68
|
+
cb_recv_any_frame_t = ctypes.CFUNCTYPE(errcode, protocol, time)
|
|
69
|
+
cb_fmt_spec_t = ctypes.CFUNCTYPE(ctypes.c_int, frame, ctypes.c_void_p)
|
|
70
|
+
cb_recv_frame_t = ctypes.CFUNCTYPE(
|
|
71
|
+
errcode, protocol, time, cb_fmt_spec_t, ctypes.c_void_p
|
|
72
|
+
)
|
|
73
|
+
cb_get_id_t = ctypes.CFUNCTYPE(
|
|
74
|
+
errcode, protocol, ctypes.POINTER(ctypes.c_char_p), frame
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
protocol_t._fields_ = [
|
|
78
|
+
("protocol_id", ctypes.c_int),
|
|
79
|
+
("layer_id", layer_id),
|
|
80
|
+
("base_protocol", protocol),
|
|
81
|
+
("cb_open", cb_generic_t),
|
|
82
|
+
("cb_close", cb_generic_t),
|
|
83
|
+
("cb_send_frame", cb_generic_t),
|
|
84
|
+
("cb_recv_any_frame", cb_recv_any_frame_t),
|
|
85
|
+
("cb_recv_frame", cb_recv_frame_t),
|
|
86
|
+
("cb_flush", cb_generic_t),
|
|
87
|
+
("cb_destroy", cb_generic_t),
|
|
88
|
+
("cb_get_id", cb_get_id_t),
|
|
89
|
+
("opened", ctypes.c_bool),
|
|
90
|
+
("send_frame", frame_t),
|
|
91
|
+
("recv_frame", frame_t),
|
|
92
|
+
("recv_delay", time),
|
|
93
|
+
("mempool", mempool_object_t),
|
|
94
|
+
("friendly_name", ctypes.c_char_p),
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
R = TypeVar("R", covariant=True)
|
|
99
|
+
P = ParamSpec("P")
|
|
100
|
+
C = TypeVar("C", bound=type)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class CBrpLibFunction(Protocol[P, R]):
|
|
104
|
+
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R:
|
|
105
|
+
...
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
CharP = Annotated[ctypes.Array[ctypes.c_char], ctypes.c_char_p]
|
|
109
|
+
CharPP = Annotated[Any, ctypes.POINTER(ctypes.c_char_p)]
|
|
110
|
+
CInt = Annotated[int, ctypes.c_int]
|
|
111
|
+
CSize = Annotated[int, ctypes.c_size_t]
|
|
112
|
+
CSizeP = Annotated[Any, ctypes.POINTER(ctypes.c_size_t)]
|
|
113
|
+
CUShortP = Annotated[Any, ctypes.POINTER(ctypes.c_ushort)]
|
|
114
|
+
CChar = Annotated[bytes, ctypes.c_char]
|
|
115
|
+
CBool = Annotated[bool, ctypes.c_bool]
|
|
116
|
+
CBuf = Annotated[bytes, buf]
|
|
117
|
+
CLongLong = Annotated[int, ctypes.c_longlong]
|
|
118
|
+
CTime = Annotated[int, ctypes.c_ulong]
|
|
119
|
+
CProtocol = Annotated[Any, protocol]
|
|
120
|
+
CProtocolP = Annotated[Any, ctypes.POINTER(protocol)]
|
|
121
|
+
CFrame = Annotated[Any, frame]
|
|
122
|
+
CFrameReader = Annotated[Any, frame_reader]
|
|
123
|
+
CSocket = Annotated[int, socket]
|
|
124
|
+
CMempool = Annotated[Any, mempool]
|
|
125
|
+
CIoiter = Annotated[Any, ioiter]
|
|
126
|
+
CIoiterP = Annotated[Any, ioiter_p]
|
|
127
|
+
CUShort = Annotated[int, ctypes.c_ushort]
|
|
128
|
+
|
|
129
|
+
ErrorCode = Annotated[None, errcode]
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _to_ctype(t: Any) -> Any:
|
|
133
|
+
if t is type(None):
|
|
134
|
+
return None
|
|
135
|
+
|
|
136
|
+
args = typing.get_args(t)
|
|
137
|
+
if args:
|
|
138
|
+
return args[1]
|
|
139
|
+
|
|
140
|
+
return t
|
|
141
|
+
|
|
142
|
+
|
|
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:
|
|
146
|
+
return None
|
|
147
|
+
|
|
148
|
+
path = Path(ctypes.string_at(path_ptr).decode('utf-8'))
|
|
149
|
+
if not path.exists():
|
|
150
|
+
return None
|
|
151
|
+
url = f"{path.as_uri()}"
|
|
152
|
+
|
|
153
|
+
anchor_ptr = ctypes.c_char_p()
|
|
154
|
+
c_brp_lib.brp_get_current_log_anchor(prot, ctypes.byref(anchor_ptr), None)
|
|
155
|
+
if anchor_ptr.value:
|
|
156
|
+
return f"{url}{anchor_ptr.value.decode('utf-8')}"
|
|
157
|
+
return url
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def with_error_handling(fn: Callable[P, int]) -> Callable[P, None]:
|
|
161
|
+
@functools.wraps(fn)
|
|
162
|
+
def wrapper(*args: P.args, **kwargs: P.kwargs) -> None:
|
|
163
|
+
result = fn(*args, **kwargs)
|
|
164
|
+
if result:
|
|
165
|
+
log_url = None
|
|
166
|
+
with suppress():
|
|
167
|
+
if len(args) > 0 and isinstance(args[0], protocol):
|
|
168
|
+
log_url = _get_log_link(args[0])
|
|
169
|
+
raise BrpError.from_error_code(result, log_url=log_url)
|
|
170
|
+
|
|
171
|
+
return wrapper
|
|
172
|
+
|
|
173
|
+
|
|
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
|
+
class CBrpLib:
|
|
179
|
+
brp_get_version: CBrpLibFunction[[], CharP]
|
|
180
|
+
brp_create: CBrpLibFunction[[], CProtocol]
|
|
181
|
+
brp_destroy: CBrpLibFunction[[CProtocol], ErrorCode]
|
|
182
|
+
brp_set_io: CBrpLibFunction[[CProtocol, CProtocol], ErrorCode]
|
|
183
|
+
brp_open: CBrpLibFunction[[CProtocol], ErrorCode]
|
|
184
|
+
brp_close: CBrpLibFunction[[CProtocol], ErrorCode]
|
|
185
|
+
brp_suppress_monitoring: CBrpLibFunction[[CProtocol], ErrorCode]
|
|
186
|
+
brp_get_io_id: CBrpLibFunction[[CProtocol, CharPP, CFrame], ErrorCode]
|
|
187
|
+
brp_get_friendly_name: CBrpLibFunction[[CProtocol, CharPP], ErrorCode]
|
|
188
|
+
brp_ioiter_create: CBrpLibFunction[[], CIoiter]
|
|
189
|
+
brp_ioiter_create_usb_hid: CBrpLibFunction[[], CIoiter]
|
|
190
|
+
brp_ioiter_next: CBrpLibFunction[[CIoiter, CProtocolP], ErrorCode]
|
|
191
|
+
brp_ioiter_destroy: CBrpLibFunction[[CIoiterP], None]
|
|
192
|
+
brp_create_usb_hid: CBrpLibFunction[[CLongLong], CProtocol]
|
|
193
|
+
brp_create_rs232: CBrpLibFunction[[CharP, CInt, CChar], CProtocol]
|
|
194
|
+
brp_create_secure_channel: CBrpLibFunction[[CInt, CBuf, CInt], CProtocol]
|
|
195
|
+
brp_set_crypto: CBrpLibFunction[[CProtocol, CProtocol], ErrorCode]
|
|
196
|
+
brp_set_monitor: CBrpLibFunction[[CProtocol, CInt], ErrorCode]
|
|
197
|
+
brp_send_frame: CBrpLibFunction[[CProtocol], ErrorCode]
|
|
198
|
+
brp_recv_any_frame: CBrpLibFunction[[CProtocol, CInt], ErrorCode]
|
|
199
|
+
brp_exec_cmd: CBrpLibFunction[[CProtocol, CInt, CBuf, CSize, CBuf, CSize, CSizeP, CTime], ErrorCode]
|
|
200
|
+
brp_frame_rest: CBrpLibFunction[[CFrameReader], CSize]
|
|
201
|
+
brp_frame_init: CBrpLibFunction[[CFrame], None]
|
|
202
|
+
brp_frame_deinit: CBrpLibFunction[[CFrame], None]
|
|
203
|
+
brp_frame_write_start: CBrpLibFunction[[CFrame], None]
|
|
204
|
+
brp_frame_write: CBrpLibFunction[[CFrame, CBuf, CSize], None]
|
|
205
|
+
brp_frame_write_err: CBrpLibFunction[[CFrame], CBool]
|
|
206
|
+
brp_frame_read_start: CBrpLibFunction[[CFrameReader, CFrame], None]
|
|
207
|
+
brp_frame_read: CBrpLibFunction[[CFrameReader, CBuf, CSize], None]
|
|
208
|
+
brp_frame_read_err: CBrpLibFunction[[CFrameReader], CBool]
|
|
209
|
+
brp_frame_read_eof: CBrpLibFunction[[CFrameReader], CBool]
|
|
210
|
+
brp_annotation_start: CBrpLibFunction[[CProtocol], None]
|
|
211
|
+
brp_annotation_end: CBrpLibFunction[[CProtocol, CBool, CharP], None]
|
|
212
|
+
brp_set_log_path: CBrpLibFunction[[CProtocol, CharP], ErrorCode]
|
|
213
|
+
brp_get_current_log_anchor: CBrpLibFunction[[CProtocol, CharPP, CMempool], ErrorCode]
|
|
214
|
+
brp_get_log_path: CBrpLibFunction[[CProtocol, CMempool], CharP]
|
|
215
|
+
|
|
216
|
+
def __init__(self) -> None:
|
|
217
|
+
self.__dll_path: Optional[Path] = None
|
|
218
|
+
|
|
219
|
+
@property
|
|
220
|
+
def dll_path(self) -> Optional[Path]:
|
|
221
|
+
return self.__dll_path
|
|
222
|
+
|
|
223
|
+
@dll_path.setter
|
|
224
|
+
def dll_path(self, dll_path: Path) -> None:
|
|
225
|
+
self.__dll_path = dll_path.absolute()
|
|
226
|
+
dll = ctypes.CDLL(str(self.__dll_path))
|
|
227
|
+
|
|
228
|
+
for name, annotation in type(self).__annotations__.items():
|
|
229
|
+
if typing.get_origin(annotation) == CBrpLibFunction:
|
|
230
|
+
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)
|
|
245
|
+
setattr(self, name, fn)
|
|
246
|
+
|
|
247
|
+
def __getattr__(self, item: str) -> Any:
|
|
248
|
+
if item in type(self).__annotations__:
|
|
249
|
+
raise RuntimeError("Please initialize the brp_lib path calling `brp_lib.set_brp_lib_path('.../libbrp_lib.so')`.")
|
|
250
|
+
return super().__getattribute__(item)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
c_brp_lib = CBrpLib()
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
from typing import Dict, Type, Optional
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
"BrpError",
|
|
6
|
+
"DeviceError",
|
|
7
|
+
"CommunicationError",
|
|
8
|
+
"CommUnsupported",
|
|
9
|
+
"CommAccessDeniedError",
|
|
10
|
+
"CommTimeoutError",
|
|
11
|
+
"CommFrameFormatError",
|
|
12
|
+
"CommUndefined",
|
|
13
|
+
"LibError",
|
|
14
|
+
"LibInvalidCallError",
|
|
15
|
+
"LibNonRecoverableError",
|
|
16
|
+
"LibOsError",
|
|
17
|
+
"LibUndefinedError",
|
|
18
|
+
"InternalError",
|
|
19
|
+
"CommandTimeoutError",
|
|
20
|
+
"FrameFormatError",
|
|
21
|
+
"InvalidApiCallError",
|
|
22
|
+
"OutOfMemoryError",
|
|
23
|
+
"BrpNotImplementedError",
|
|
24
|
+
"BusyError",
|
|
25
|
+
"ClosedError",
|
|
26
|
+
"BufferOverflowError",
|
|
27
|
+
"OpenIOError",
|
|
28
|
+
"WriteIOError",
|
|
29
|
+
"WaitIOError",
|
|
30
|
+
"ReadIOError",
|
|
31
|
+
"CloseIOError",
|
|
32
|
+
"PayloadFormatError",
|
|
33
|
+
"CryptoFormatError",
|
|
34
|
+
"SecurityLevelNotSupportedError",
|
|
35
|
+
"UnsupportedCommandError",
|
|
36
|
+
"CommandDeniedError",
|
|
37
|
+
"UnexpectedFrameError",
|
|
38
|
+
"BrpTimeoutError",
|
|
39
|
+
"CalledInvalidFrameError",
|
|
40
|
+
"ExistingLayerError",
|
|
41
|
+
"GenRandomDataError",
|
|
42
|
+
"InvalidKeyError",
|
|
43
|
+
"NoDeviceFoundError",
|
|
44
|
+
"AmbiguousDeviceError",
|
|
45
|
+
"PayloadTooLongError",
|
|
46
|
+
"PayloadTooShortError",
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
class BrpError(Exception, metaclass=abc.ABCMeta):
|
|
50
|
+
ErrorCodeMap: Dict[int, Type["BrpError"]] = {}
|
|
51
|
+
|
|
52
|
+
ErrorCode: Optional[int] = None
|
|
53
|
+
URL: Optional[str] = None
|
|
54
|
+
|
|
55
|
+
def __init_subclass__(cls) -> None:
|
|
56
|
+
if cls.ErrorCode is not None:
|
|
57
|
+
already_registered_class = cls.ErrorCodeMap.get(cls.ErrorCode)
|
|
58
|
+
if already_registered_class is not None:
|
|
59
|
+
if issubclass(cls, already_registered_class):
|
|
60
|
+
return
|
|
61
|
+
raise TypeError(f"multiple exceptions defined for same status code {hex(cls.ErrorCode)} ({cls} and {already_registered_class})")
|
|
62
|
+
cls.ErrorCodeMap[cls.ErrorCode] = cls
|
|
63
|
+
|
|
64
|
+
@classmethod
|
|
65
|
+
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)
|
|
68
|
+
|
|
69
|
+
def __init__(self, message: str = "", *, log_url: Optional[str] = None) -> None:
|
|
70
|
+
self.log_url = log_url
|
|
71
|
+
super().__init__(message)
|
|
72
|
+
|
|
73
|
+
def __str__(self) -> str:
|
|
74
|
+
return "\n".join(
|
|
75
|
+
map(str,
|
|
76
|
+
filter(
|
|
77
|
+
lambda i: i is not None, [
|
|
78
|
+
f"0x{self.ErrorCode:08X} {super()!s}\n",
|
|
79
|
+
f"DESC: {self.__doc__.strip() if self.__doc__ else None}",
|
|
80
|
+
f"DOCS: {self.URL}" if self.URL else None,
|
|
81
|
+
f"LOGS: {self.log_url}" if self.log_url else None,
|
|
82
|
+
]
|
|
83
|
+
)
|
|
84
|
+
)
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class CommunicationError(BrpError, metaclass=abc.ABCMeta):
|
|
89
|
+
"""Any kind of device <-> host communication problem"""
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class CommUnsupported(CommunicationError, metaclass=abc.ABCMeta):
|
|
93
|
+
"""A command cannot be executed as the device does not support the requested feature"""
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class CommAccessDeniedError(CommunicationError, metaclass=abc.ABCMeta):
|
|
97
|
+
"""A command cannot be executed as the device does not allow access this features in the current operations mode"""
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class CommTimeoutError(CommunicationError, metaclass=abc.ABCMeta):
|
|
101
|
+
"""A the device or host exceeded the allowed time for sending a frame"""
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class CommFrameFormatError(CommunicationError, metaclass=abc.ABCMeta):
|
|
105
|
+
"""The device or host send a frame that does not correspond the expected frame"""
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class CommUndefined(CommunicationError, metaclass=abc.ABCMeta):
|
|
109
|
+
"""any device <-> host communication problem which is not categoriezed by one of the BRP_ERRGRP_COMM_* macros"""
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class LibError(BrpError, metaclass=abc.ABCMeta):
|
|
113
|
+
"""Any error which occured in the library"""
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class LibInvalidCallError(LibError, metaclass=abc.ABCMeta):
|
|
117
|
+
"""The libraries API was used in an invalid manner"""
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class LibNonRecoverableError(LibError, metaclass=abc.ABCMeta):
|
|
121
|
+
"""A fatal error occured, which the library cannot recover from. The application should be closed"""
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class LibOsError(LibError, metaclass=abc.ABCMeta):
|
|
125
|
+
"""A error in the operating system layer (i.e. hardware drivers) occured"""
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class LibUndefinedError(LibError, metaclass=abc.ABCMeta):
|
|
129
|
+
"""any library specific error which is not one of the BRP_ERRGRP_LIB_* macros"""
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class DeviceError(BrpError, metaclass=abc.ABCMeta):
|
|
133
|
+
"""
|
|
134
|
+
A command failed to execute due to a error on the device side which is
|
|
135
|
+
not host communication related (card error, ...)
|
|
136
|
+
|
|
137
|
+
The lower 16bits of the error code will contains the command group code and
|
|
138
|
+
the status code (see also #BRP_ERR_STATUS() on how to construct a specific
|
|
139
|
+
error code).
|
|
140
|
+
The exact list of error codes returned by the device can be found
|
|
141
|
+
in the @ref baltech_sdk.
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class InternalError(LibNonRecoverableError):
|
|
146
|
+
"""This error must not be returned if the library operates correctly.
|
|
147
|
+
It is only returned, if a bug in the implementation was detected."""
|
|
148
|
+
ErrorCode = 0x20000001
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class CommandTimeoutError(CommTimeoutError):
|
|
152
|
+
"""A BRP command that was send by brp_send_frame() exceeded the specified
|
|
153
|
+
timeout (see also brp_send_cmd() / brp_exec_cmd()).
|
|
154
|
+
|
|
155
|
+
**Attention:** must not be intermixed with #BRP_ERR_TIMEOUT."""
|
|
156
|
+
ErrorCode = 0x02000002
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class FrameFormatError(CommFrameFormatError):
|
|
160
|
+
"""The frame returned from the reader does not match the format expected
|
|
161
|
+
by the BRP protocol."""
|
|
162
|
+
ErrorCode = 0x04000003
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class InvalidApiCallError(LibInvalidCallError):
|
|
166
|
+
"""Returned if the a parameter contains an invalid value.
|
|
167
|
+
I.e. if a required pointer is set to NULL."""
|
|
168
|
+
ErrorCode = 0x10000004
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class OutOfMemoryError(LibNonRecoverableError):
|
|
172
|
+
"""The heap has not enough memory to allocate a buffer that is required
|
|
173
|
+
for the wanted operation."""
|
|
174
|
+
ErrorCode = 0x20000005
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
class BrpNotImplementedError(LibInvalidCallError):
|
|
178
|
+
"""The requested feature is not implemented yet"""
|
|
179
|
+
ErrorCode = 0x10000006
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class BusyError(LibInvalidCallError):
|
|
183
|
+
"""The Library is not ready to send/receive data. Probably it is still
|
|
184
|
+
processing another request."""
|
|
185
|
+
ErrorCode = 0x10000007
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class ClosedError(LibInvalidCallError):
|
|
189
|
+
"""The protocol/device is not opened"""
|
|
190
|
+
ErrorCode = 0x10000008
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
class BufferOverflowError(CommFrameFormatError):
|
|
194
|
+
"""A Command's response is bigger than the buffer provided for the response."""
|
|
195
|
+
ErrorCode = 0x04000009
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
class OpenIOError(LibOsError):
|
|
199
|
+
"""Failed to open the I/O connection"""
|
|
200
|
+
ErrorCode = 0x4000000A
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class WriteIOError(LibOsError):
|
|
204
|
+
"""Failed to send data via IO connection"""
|
|
205
|
+
ErrorCode = 0x4000000B
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
class WaitIOError(LibOsError):
|
|
209
|
+
"""Failed to wait for data via IO connection"""
|
|
210
|
+
ErrorCode = 0x4000000C
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
class ReadIOError(LibOsError):
|
|
214
|
+
"""Failed to wait for data via IO connection"""
|
|
215
|
+
ErrorCode = 0x4000000D
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
class CloseIOError(LibOsError):
|
|
219
|
+
"""Failed to open the I/O connection"""
|
|
220
|
+
ErrorCode = 0x4000000E
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
class PayloadFormatError(CommFrameFormatError):
|
|
224
|
+
"""The payload of a frame has invalid format"""
|
|
225
|
+
ErrorCode = 0x0400000F
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
class CryptoFormatError(CommFrameFormatError):
|
|
229
|
+
"""The payload of an encrypted command had invalid format/response."""
|
|
230
|
+
ErrorCode = 0x04000010
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
class SecurityLevelNotSupportedError(CommAccessDeniedError):
|
|
234
|
+
"""The given securitylevel is not supported"""
|
|
235
|
+
ErrorCode = 0x01000016
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
class UnsupportedCommandError(CommUnsupported):
|
|
239
|
+
"""The specified BRP command is not supported by the connected device
|
|
240
|
+
|
|
241
|
+
Internally the driver maps the status code BRP_ERR_STATUS(xxxx, 0x41) to this
|
|
242
|
+
(cmdcode independant) error code."""
|
|
243
|
+
ErrorCode = 0x00800020
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
class CommandDeniedError(CommAccessDeniedError):
|
|
247
|
+
"""The BRP command is not allowed to be executed from the current security level
|
|
248
|
+
|
|
249
|
+
Internally the driver maps the status code BRP_ERR_STATUS(xxxx, 0x42) to this
|
|
250
|
+
(cmdcode independant) error code."""
|
|
251
|
+
ErrorCode = 0x01000021
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
class UnexpectedFrameError(CommFrameFormatError):
|
|
255
|
+
"""The received frame is a valid BRP frame but not expected in this protocol
|
|
256
|
+
state."""
|
|
257
|
+
ErrorCode = 0x04000022
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
class BrpTimeoutError(CommTimeoutError):
|
|
261
|
+
"""The response to a brp_recv_frame() / brp_recv_any_frame() is not returned
|
|
262
|
+
within the specified timeout.
|
|
263
|
+
|
|
264
|
+
**Attention:** Must not be intermixed with #BRP_ERR_CMD_TIMEOUT, which is
|
|
265
|
+
returned if the timeout of a BRP command is exceeded (see brp_send_cmd())."""
|
|
266
|
+
ErrorCode = 0x02000023
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
class CalledInvalidFrameError(LibInvalidCallError):
|
|
270
|
+
"""Returned if the a frame passed to a function to be transferred to the device
|
|
271
|
+
has invalid format"""
|
|
272
|
+
ErrorCode = 0x10000024
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
class ExistingLayerError(LibInvalidCallError):
|
|
276
|
+
"""The layer is tried to be added to a composite protocol
|
|
277
|
+
(see brp_add_layer() / brp_set_layer()) although already added earlier.
|
|
278
|
+
Or it is tried to be removed (brp_detach_layer()) also not part of the
|
|
279
|
+
composite protocol."""
|
|
280
|
+
ErrorCode = 0x10000025
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
class GenRandomDataError(LibOsError):
|
|
284
|
+
"""The OS failed to generate a random number"""
|
|
285
|
+
ErrorCode = 0x40000026
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
class InvalidKeyError(CommAccessDeniedError):
|
|
289
|
+
"""The readers KEY does not match the key of the host."""
|
|
290
|
+
ErrorCode = 0x01000027
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
class NoDeviceFoundError(OpenIOError):
|
|
294
|
+
"""No device was found when trying to open a connection."""
|
|
295
|
+
ErrorCode = 0x40000028
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
class AmbiguousDeviceError(OpenIOError):
|
|
299
|
+
"""Multiple devices found but connection string does not uniquely identify one."""
|
|
300
|
+
ErrorCode = 0x40000029
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
##############################################################################
|
|
304
|
+
# PYTHON ONLY EXCEPTIONS #
|
|
305
|
+
##############################################################################
|
|
306
|
+
|
|
307
|
+
class PayloadTooLongError(PayloadFormatError):
|
|
308
|
+
"""Raised when payload contains more data than expected."""
|
|
309
|
+
|
|
310
|
+
def __init__(self, additional_data: bytes) -> None:
|
|
311
|
+
super().__init__()
|
|
312
|
+
self.additional_data = additional_data
|
|
313
|
+
|
|
314
|
+
def __str__(self) -> str:
|
|
315
|
+
return f'{len(self.additional_data)} bytes of unexpected data'
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
class PayloadTooShortError(PayloadFormatError):
|
|
319
|
+
"""Raised when payload contains less data than expected."""
|
|
320
|
+
|
|
321
|
+
def __init__(self, missing_bytes: int) -> None:
|
|
322
|
+
super().__init__()
|
|
323
|
+
self.missing_bytes = missing_bytes
|
|
324
|
+
|
|
325
|
+
def __str__(self) -> str:
|
|
326
|
+
if self.missing_bytes == 1:
|
|
327
|
+
return "1 byte is missing"
|
|
328
|
+
return f'{self.missing_bytes} bytes are missing'
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import ctypes
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, Optional, TypeVar, Literal, Type, Union, Iterator
|
|
6
|
+
from contextlib import contextmanager
|
|
7
|
+
|
|
8
|
+
from .dll_wrapper import c_brp_lib, buf, frame_reader_t, frame_t, protocol
|
|
9
|
+
|
|
10
|
+
T = TypeVar("T")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CProtocolWrapper(metaclass=abc.ABCMeta):
|
|
14
|
+
def __init__(self, c_protocol: Any) -> None:
|
|
15
|
+
self._c_protocol = c_protocol
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def __c_protocol__(self) -> Any:
|
|
19
|
+
if self._c_protocol is None:
|
|
20
|
+
raise RuntimeError("Protocol ownership has been transferred to a parent protocol")
|
|
21
|
+
return self._c_protocol
|
|
22
|
+
|
|
23
|
+
def __take_c_protocol__(self) -> Any:
|
|
24
|
+
if self._c_protocol is None:
|
|
25
|
+
raise RuntimeError("Protocol ownership has already been transferred")
|
|
26
|
+
c_protocol = self._c_protocol
|
|
27
|
+
self._c_protocol = None
|
|
28
|
+
return c_protocol
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def closed(self) -> bool:
|
|
32
|
+
return not self.__c_protocol__.contents.opened
|
|
33
|
+
|
|
34
|
+
def open(self) -> None:
|
|
35
|
+
c_brp_lib.brp_open(self.__c_protocol__)
|
|
36
|
+
|
|
37
|
+
def close(self) -> None:
|
|
38
|
+
c_brp_lib.brp_close(self.__c_protocol__)
|
|
39
|
+
|
|
40
|
+
def __enter__(self) -> Any:
|
|
41
|
+
if self.closed:
|
|
42
|
+
self.open()
|
|
43
|
+
return self
|
|
44
|
+
|
|
45
|
+
def __exit__(self, exc_type: Type[Exception], exc_val: Exception, exc_tb: Any) -> None:
|
|
46
|
+
if not self.closed:
|
|
47
|
+
self.close()
|
|
48
|
+
|
|
49
|
+
def __del__(self) -> None:
|
|
50
|
+
# Check if _c_protocol exists and is not None (ownership not transferred)
|
|
51
|
+
if hasattr(self, '_c_protocol') and self._c_protocol is not None:
|
|
52
|
+
if not self.closed:
|
|
53
|
+
self.close()
|
|
54
|
+
c_brp_lib.brp_destroy(self._c_protocol)
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def id(self) -> str:
|
|
58
|
+
"""Device ID (e.g., serial number for USB HID devices)."""
|
|
59
|
+
intf_name_ptr = ctypes.c_char_p()
|
|
60
|
+
device_id_frame = frame_t()
|
|
61
|
+
c_brp_lib.brp_frame_init(ctypes.pointer(device_id_frame))
|
|
62
|
+
try:
|
|
63
|
+
c_brp_lib.brp_get_io_id(self.__c_protocol__, ctypes.byref(intf_name_ptr), ctypes.pointer(device_id_frame))
|
|
64
|
+
if device_id_frame.ptr:
|
|
65
|
+
return ctypes.string_at(device_id_frame.ptr, device_id_frame.act_size).decode('utf-8', errors='replace').rstrip('\x00')
|
|
66
|
+
return ""
|
|
67
|
+
finally:
|
|
68
|
+
c_brp_lib.brp_frame_deinit(ctypes.pointer(device_id_frame))
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def friendly_name(self) -> Optional[str]:
|
|
72
|
+
"""Human-readable device name (e.g., product name for USB HID devices)."""
|
|
73
|
+
friendly_name_ptr = ctypes.c_char_p()
|
|
74
|
+
c_brp_lib.brp_get_friendly_name(self.__c_protocol__, ctypes.byref(friendly_name_ptr))
|
|
75
|
+
if friendly_name_ptr.value:
|
|
76
|
+
return friendly_name_ptr.value.decode('utf-8', errors='replace')
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
def __str__(self) -> str:
|
|
80
|
+
"""Return a string representation of the protocol."""
|
|
81
|
+
name = self.friendly_name
|
|
82
|
+
if name:
|
|
83
|
+
return f"{name} {self.id} ({self.__class__.__name__})"
|
|
84
|
+
return f"{self.id} ({self.__class__.__name__})"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class IoProtocol(CProtocolWrapper, metaclass=abc.ABCMeta):
|
|
88
|
+
pass
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class UsbHid(IoProtocol):
|
|
92
|
+
def __init__(self, serialnumber: Optional[int] = None) -> None:
|
|
93
|
+
self.serialnumber = serialnumber
|
|
94
|
+
super().__init__(c_brp_lib.brp_create_usb_hid(serialnumber or 0))
|
|
95
|
+
|
|
96
|
+
@classmethod
|
|
97
|
+
def enumerate(cls) -> Iterator['UsbHid']:
|
|
98
|
+
"""
|
|
99
|
+
Enumerate all connected Baltech USB HID devices.
|
|
100
|
+
|
|
101
|
+
Yields UsbHid instances for each connected device. The caller is responsible
|
|
102
|
+
for managing the lifecycle of yielded devices (e.g., using context managers).
|
|
103
|
+
|
|
104
|
+
Example:
|
|
105
|
+
>>> for device in UsbHid.enumerate():
|
|
106
|
+
... with device:
|
|
107
|
+
... print(f"Device: {device.friendly_name or device.id}")
|
|
108
|
+
... # Use device for communication
|
|
109
|
+
"""
|
|
110
|
+
# Create USB HID-specific iterator
|
|
111
|
+
iter_handle = c_brp_lib.brp_ioiter_create_usb_hid()
|
|
112
|
+
if not iter_handle:
|
|
113
|
+
return
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
while True:
|
|
117
|
+
# Get next protocol
|
|
118
|
+
protocol_ptr = protocol()
|
|
119
|
+
c_brp_lib.brp_ioiter_next(iter_handle, ctypes.byref(protocol_ptr))
|
|
120
|
+
|
|
121
|
+
# Check if we reached the end
|
|
122
|
+
if not protocol_ptr:
|
|
123
|
+
break
|
|
124
|
+
|
|
125
|
+
# Wrap the protocol from iterator in a UsbHid instance
|
|
126
|
+
# The protocol already has friendly_name populated from the iterator
|
|
127
|
+
device = cls.__new__(cls)
|
|
128
|
+
device._c_protocol = protocol_ptr
|
|
129
|
+
yield device
|
|
130
|
+
finally:
|
|
131
|
+
# Clean up iterator
|
|
132
|
+
iter_ref = ctypes.pointer(iter_handle)
|
|
133
|
+
c_brp_lib.brp_ioiter_destroy(iter_ref)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class RS232(IoProtocol):
|
|
137
|
+
def __init__(self, comport: str, baudrate: int = 115200, parity: Literal[b"O", b"E", b"N"] = b"N") -> None:
|
|
138
|
+
self.comport = comport
|
|
139
|
+
comport_cstr = ctypes.create_string_buffer(comport.encode("ascii"))
|
|
140
|
+
super().__init__(c_brp_lib.brp_create_rs232(comport_cstr, baudrate, parity))
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class CryptoProtocol(CProtocolWrapper, metaclass=abc.ABCMeta):
|
|
144
|
+
pass
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class SecureChannel(CryptoProtocol):
|
|
148
|
+
def __init__(self, *, security_level: int, key: bytes, security_mode: Literal["std", "plain", "stateless"] = "std") -> None:
|
|
149
|
+
secmode = dict(std=0, plain=1, stateless=2).get(security_mode, 0)
|
|
150
|
+
super().__init__(c_brp_lib.brp_create_secure_channel(security_level, key, secmode))
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
MonitorMode = Union[Literal["disabled", "enabled", "plaintext"], bool]
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class AnnotationLogger:
|
|
157
|
+
|
|
158
|
+
def __init__(self) -> None:
|
|
159
|
+
self._message = ""
|
|
160
|
+
self._fail = False
|
|
161
|
+
|
|
162
|
+
def info(self, message: str) -> None:
|
|
163
|
+
self._message += message
|
|
164
|
+
|
|
165
|
+
def fail(self, message: str = "") -> None:
|
|
166
|
+
self._fail = True
|
|
167
|
+
self._message += message
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class BrpStack(CProtocolWrapper):
|
|
171
|
+
def __init__(
|
|
172
|
+
self,
|
|
173
|
+
io: IoProtocol,
|
|
174
|
+
*,
|
|
175
|
+
crypto: Optional[CryptoProtocol] = None,
|
|
176
|
+
monitor: MonitorMode = "enabled",
|
|
177
|
+
log_path: Optional[Union[str, Path]] = None,
|
|
178
|
+
open: bool = False,
|
|
179
|
+
) -> None:
|
|
180
|
+
super().__init__(c_brp_lib.brp_create())
|
|
181
|
+
|
|
182
|
+
c_brp_lib.brp_set_io(self.__c_protocol__, io.__take_c_protocol__())
|
|
183
|
+
|
|
184
|
+
if crypto:
|
|
185
|
+
c_brp_lib.brp_set_crypto(self.__c_protocol__, crypto.__take_c_protocol__())
|
|
186
|
+
|
|
187
|
+
if monitor not in (True, "enabled"):
|
|
188
|
+
self.monitor = monitor
|
|
189
|
+
|
|
190
|
+
if log_path is not None:
|
|
191
|
+
self.log_path = Path(log_path)
|
|
192
|
+
|
|
193
|
+
if open:
|
|
194
|
+
self.open()
|
|
195
|
+
|
|
196
|
+
def send_frame(self, frame: bytes) -> None:
|
|
197
|
+
send_frame = ctypes.pointer(self.__c_protocol__.contents.send_frame)
|
|
198
|
+
|
|
199
|
+
c_brp_lib.brp_frame_write_start(send_frame)
|
|
200
|
+
c_brp_lib.brp_frame_write(send_frame, frame, len(frame))
|
|
201
|
+
c_brp_lib.brp_frame_write_err(send_frame)
|
|
202
|
+
c_brp_lib.brp_send_frame(self.__c_protocol__)
|
|
203
|
+
|
|
204
|
+
def recv_any_frame(self, timeout: int = 0xFFFFFFFF) -> bytes:
|
|
205
|
+
reader = frame_reader_t()
|
|
206
|
+
c_brp_lib.brp_recv_any_frame(self.__c_protocol__, timeout)
|
|
207
|
+
c_brp_lib.brp_frame_read_start(
|
|
208
|
+
ctypes.pointer(reader), ctypes.pointer(self.__c_protocol__.contents.recv_frame)
|
|
209
|
+
)
|
|
210
|
+
rest = c_brp_lib.brp_frame_rest(ctypes.pointer(reader))
|
|
211
|
+
|
|
212
|
+
result = ctypes.create_string_buffer(rest)
|
|
213
|
+
c_brp_lib.brp_frame_read(ctypes.pointer(reader), buf(result), rest) # type: ignore[call-overload]
|
|
214
|
+
c_brp_lib.brp_frame_read_err(ctypes.pointer(reader))
|
|
215
|
+
|
|
216
|
+
eof = c_brp_lib.brp_frame_read_eof(ctypes.pointer(reader))
|
|
217
|
+
if not eof:
|
|
218
|
+
raise RuntimeError("UnexpectedData")
|
|
219
|
+
|
|
220
|
+
return result.raw
|
|
221
|
+
|
|
222
|
+
def process_frame(self, frame: bytes) -> bytes:
|
|
223
|
+
self.send_frame(frame)
|
|
224
|
+
return self.recv_any_frame()
|
|
225
|
+
|
|
226
|
+
def exec_cmd(self, cmd_code: int, params: bytes, *, timeout: int, max_resp_len: int = 65536) -> bytes:
|
|
227
|
+
resp_buf = ctypes.create_string_buffer(max_resp_len)
|
|
228
|
+
resp_len = ctypes.c_size_t()
|
|
229
|
+
|
|
230
|
+
c_brp_lib.brp_exec_cmd(
|
|
231
|
+
self.__c_protocol__,
|
|
232
|
+
cmd_code,
|
|
233
|
+
params,
|
|
234
|
+
len(params),
|
|
235
|
+
buf(resp_buf), # type: ignore[call-overload]
|
|
236
|
+
max_resp_len,
|
|
237
|
+
ctypes.pointer(resp_len),
|
|
238
|
+
timeout
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
return resp_buf.raw[:resp_len.value]
|
|
242
|
+
|
|
243
|
+
def set_monitor(self, new_val: MonitorMode) -> None:
|
|
244
|
+
map = {"disabled": 0, "enabled": 1, "plaintext": 2, False:0, True:1}
|
|
245
|
+
c_brp_lib.brp_set_monitor(self.__c_protocol__, map[new_val])
|
|
246
|
+
|
|
247
|
+
@contextmanager
|
|
248
|
+
def annotate_log(self, comment:str="") -> Iterator[AnnotationLogger]:
|
|
249
|
+
"""
|
|
250
|
+
Adds an annotations/comment to all brplogs that are executed within
|
|
251
|
+
this context.
|
|
252
|
+
|
|
253
|
+
Returns a contextmanager that allows to add further log-messages
|
|
254
|
+
(log.info(...) or log.fail(...)). if at least one failure message is
|
|
255
|
+
send the whole block is marked as failed.
|
|
256
|
+
|
|
257
|
+
If an exception occurs the block will be marked as failed, too.
|
|
258
|
+
If no log.fail() was called the exception text will be added to
|
|
259
|
+
the log annotation.
|
|
260
|
+
|
|
261
|
+
:param comment: A string that shall be added as annotation to the logs
|
|
262
|
+
:return: a contextmanager of type AnnotationLogger()
|
|
263
|
+
"""
|
|
264
|
+
annotation_logger = AnnotationLogger()
|
|
265
|
+
annotation_logger.info(comment)
|
|
266
|
+
try:
|
|
267
|
+
c_brp_lib.brp_annotation_start(self.__c_protocol__)
|
|
268
|
+
yield annotation_logger
|
|
269
|
+
except Exception as exc:
|
|
270
|
+
if not annotation_logger._fail:
|
|
271
|
+
exc_text = f"{type(exc).__name__}: {str(exc)}"
|
|
272
|
+
annotation_logger.fail(exc_text.splitlines()[0])
|
|
273
|
+
raise
|
|
274
|
+
finally:
|
|
275
|
+
message_utf8 = annotation_logger._message.encode('utf8')
|
|
276
|
+
message_cstr = ctypes.create_string_buffer(message_utf8)
|
|
277
|
+
fail = annotation_logger._fail
|
|
278
|
+
c_brp_lib.brp_annotation_end(
|
|
279
|
+
self.__c_protocol__, not fail, message_cstr)
|
|
280
|
+
|
|
281
|
+
monitor = property(fset=set_monitor)
|
|
282
|
+
|
|
283
|
+
@property
|
|
284
|
+
def current_log_anchor(self) -> Optional[str]:
|
|
285
|
+
"""
|
|
286
|
+
Get the anchor ID of the current (last written) log entry.
|
|
287
|
+
|
|
288
|
+
The anchor can be used to create direct links to specific log entries,
|
|
289
|
+
e.g., ``f"file://{log_path}#{brp.current_log_anchor}"``
|
|
290
|
+
|
|
291
|
+
:return: The anchor string (e.g., "_a3") in hex format, or None
|
|
292
|
+
if no log entries have been written yet.
|
|
293
|
+
"""
|
|
294
|
+
anchor_ptr = ctypes.c_char_p()
|
|
295
|
+
c_brp_lib.brp_get_current_log_anchor(self.__c_protocol__, ctypes.byref(anchor_ptr), None)
|
|
296
|
+
if anchor_ptr.value:
|
|
297
|
+
return anchor_ptr.value.decode("ascii")
|
|
298
|
+
return None
|
|
299
|
+
|
|
300
|
+
@property
|
|
301
|
+
def log_path(self) -> Optional[Path]:
|
|
302
|
+
"""
|
|
303
|
+
Get the current log file path.
|
|
304
|
+
|
|
305
|
+
:return: The full path to the log file currently being used,
|
|
306
|
+
or None if no monitor protocol is present.
|
|
307
|
+
"""
|
|
308
|
+
path_ptr = c_brp_lib.brp_get_log_path(self.__c_protocol__, None)
|
|
309
|
+
if path_ptr:
|
|
310
|
+
# path_ptr is a c_char_p, convert to string
|
|
311
|
+
path_str = ctypes.string_at(path_ptr).decode("utf-8")
|
|
312
|
+
return Path(path_str)
|
|
313
|
+
return None
|
|
314
|
+
|
|
315
|
+
@log_path.setter
|
|
316
|
+
def log_path(self, path: Union[str, Path]) -> None:
|
|
317
|
+
"""
|
|
318
|
+
Set the log file path.
|
|
319
|
+
|
|
320
|
+
:param path: The log file path. Can be a full file path
|
|
321
|
+
(e.g., "C:/logs/debug.html"), a directory (uses the default
|
|
322
|
+
filename schema {interface}-{instance}.html),
|
|
323
|
+
or None to restore the default behavior.
|
|
324
|
+
"""
|
|
325
|
+
if path is None:
|
|
326
|
+
path_cstr = None
|
|
327
|
+
else:
|
|
328
|
+
path_obj = Path(path)
|
|
329
|
+
if path_obj.is_dir():
|
|
330
|
+
path_str = str(path_obj) + os.sep
|
|
331
|
+
else:
|
|
332
|
+
path_str = str(path)
|
|
333
|
+
path_cstr = ctypes.create_string_buffer(path_str.encode("utf-8"))
|
|
334
|
+
c_brp_lib.brp_set_log_path(self.__c_protocol__, path_cstr)
|
|
335
|
+
|
|
336
|
+
@log_path.setter
|
|
337
|
+
def log_path(self, path: Optional[Union[str, Path]]) -> None:
|
|
338
|
+
"""
|
|
339
|
+
Set the log file path.
|
|
340
|
+
|
|
341
|
+
:param path: The log file path. Can be a full file path
|
|
342
|
+
(e.g., "C:/logs/debug.html"), a directory (uses the default
|
|
343
|
+
filename schema {interface}-{instance}.html),
|
|
344
|
+
or None to restore the default behavior.
|
|
345
|
+
"""
|
|
346
|
+
if path is None:
|
|
347
|
+
path_cstr = None
|
|
348
|
+
else:
|
|
349
|
+
path_obj = Path(path)
|
|
350
|
+
if path_obj.is_dir():
|
|
351
|
+
path_str = str(path_obj) + os.sep
|
|
352
|
+
else:
|
|
353
|
+
path_str = str(path)
|
|
354
|
+
path_cstr = ctypes.create_string_buffer(path_str.encode("utf-8"))
|
|
355
|
+
c_brp_lib.brp_set_log_path(self.__c_protocol__, path_cstr) # type: ignore[arg-type]
|
|
File without changes
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
pyproject.toml
|
|
2
|
+
src/brp_lib/__init__.py
|
|
3
|
+
src/brp_lib/brp_lib.win32.dll
|
|
4
|
+
src/brp_lib/brp_lib.win64.dll
|
|
5
|
+
src/brp_lib/dll_wrapper.py
|
|
6
|
+
src/brp_lib/err.py
|
|
7
|
+
src/brp_lib/protocols.py
|
|
8
|
+
src/brp_lib/py.typed
|
|
9
|
+
src/brp_lib.egg-info/PKG-INFO
|
|
10
|
+
src/brp_lib.egg-info/SOURCES.txt
|
|
11
|
+
src/brp_lib.egg-info/dependency_links.txt
|
|
12
|
+
src/brp_lib.egg-info/requires.txt
|
|
13
|
+
src/brp_lib.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
typing-extensions
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
brp_lib
|