python-can-hub 0.2.2__py3-none-win_amd64.whl → 0.2.4__py3-none-win_amd64.whl
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.
- canhub/_native.py +11 -0
- canhub/bus.py +94 -17
- canhub/libcanhub.dll +0 -0
- {python_can_hub-0.2.2.dist-info → python_can_hub-0.2.4.dist-info}/METADATA +44 -3
- python_can_hub-0.2.4.dist-info/RECORD +11 -0
- python_can_hub-0.2.2.dist-info/RECORD +0 -11
- {python_can_hub-0.2.2.dist-info → python_can_hub-0.2.4.dist-info}/WHEEL +0 -0
- {python_can_hub-0.2.2.dist-info → python_can_hub-0.2.4.dist-info}/entry_points.txt +0 -0
- {python_can_hub-0.2.2.dist-info → python_can_hub-0.2.4.dist-info}/top_level.txt +0 -0
canhub/_native.py
CHANGED
|
@@ -126,3 +126,14 @@ lib.canhub_send.argtypes = [c_void_p, POINTER(CanHubFrame)]
|
|
|
126
126
|
def last_error(session):
|
|
127
127
|
detail = lib.canhub_last_error(session)
|
|
128
128
|
return detail.decode("utf-8", errors="replace") if detail else "unknown error"
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def list_interfaces(session, timeout_ms, capacity=64):
|
|
132
|
+
while True:
|
|
133
|
+
buffer = (CanHubInterfaceInfo * capacity)()
|
|
134
|
+
count = lib.canhub_list(session, buffer, capacity, timeout_ms)
|
|
135
|
+
if count < 0:
|
|
136
|
+
raise OSError(last_error(session))
|
|
137
|
+
if count < capacity:
|
|
138
|
+
return [buffer[index] for index in range(count)]
|
|
139
|
+
capacity *= 2
|
canhub/bus.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"""python-can backend over libcanhub: can.Bus(interface="canhub", ...)."""
|
|
2
2
|
|
|
3
3
|
import ctypes
|
|
4
|
-
|
|
4
|
+
import os
|
|
5
|
+
from typing import List, Optional, Tuple
|
|
5
6
|
|
|
6
7
|
from can import BusABC, CanInitializationError, CanOperationError, Message
|
|
7
8
|
|
|
@@ -49,22 +50,9 @@ class CanHubBus(BusABC):
|
|
|
49
50
|
self._session = None
|
|
50
51
|
self._writable = False
|
|
51
52
|
|
|
52
|
-
|
|
53
|
-
hub_fingerprint
|
|
54
|
-
|
|
55
|
-
raise CanInitializationError(
|
|
56
|
-
"hub_fingerprint must be 64 hex characters (it may have been "
|
|
57
|
-
"mangled by python-can config value casting; pass it as a string)"
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
config = native.CanHubConnectConfig()
|
|
61
|
-
config.struct_size = ctypes.sizeof(config)
|
|
62
|
-
config.url = url.encode() if url else None
|
|
63
|
-
config.state_directory = state_dir.encode() if state_dir else None
|
|
64
|
-
config.certificate_path = identity_cert.encode() if identity_cert else None
|
|
65
|
-
config.key_path = identity_key.encode() if identity_key else None
|
|
66
|
-
config.hub_fingerprint = hub_fingerprint.encode() if hub_fingerprint else None
|
|
67
|
-
config.connect_timeout_ms = DEFAULT_TIMEOUT_MS
|
|
53
|
+
config = self._build_connect_config(
|
|
54
|
+
url, identity_cert, identity_key, hub_fingerprint, state_dir, DEFAULT_TIMEOUT_MS
|
|
55
|
+
)
|
|
68
56
|
|
|
69
57
|
self._session = native.lib.canhub_connect(ctypes.byref(config))
|
|
70
58
|
if not self._session:
|
|
@@ -74,6 +62,62 @@ class CanHubBus(BusABC):
|
|
|
74
62
|
self.channel_info = f"canhub {channel} via {url or 'unix socket'}"
|
|
75
63
|
super().__init__(channel=channel, **kwargs)
|
|
76
64
|
|
|
65
|
+
@classmethod
|
|
66
|
+
def list_interfaces(
|
|
67
|
+
cls,
|
|
68
|
+
url: Optional[str] = None,
|
|
69
|
+
identity_cert: Optional[str] = None,
|
|
70
|
+
identity_key: Optional[str] = None,
|
|
71
|
+
hub_fingerprint: Optional[str] = None,
|
|
72
|
+
state_dir: Optional[str] = None,
|
|
73
|
+
timeout: float = DEFAULT_TIMEOUT_MS / 1000,
|
|
74
|
+
) -> List[dict]:
|
|
75
|
+
"""List the interfaces a hub exports, ready to splat into ``can.Bus``.
|
|
76
|
+
|
|
77
|
+
Connects to ``url`` (or the local hub unix socket when ``None``), asks the
|
|
78
|
+
hub for its interface directory and disconnects. Each entry is a python-can
|
|
79
|
+
config dict whose keys ``can.Bus(**entry)`` consumes directly. Connection
|
|
80
|
+
parameters mirror :class:`CanHubBus`.
|
|
81
|
+
"""
|
|
82
|
+
timeout_ms = max(0, int(timeout * 1000))
|
|
83
|
+
config = cls._build_connect_config(
|
|
84
|
+
url, identity_cert, identity_key, hub_fingerprint, state_dir, timeout_ms
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
session = native.lib.canhub_connect(ctypes.byref(config))
|
|
88
|
+
if not session:
|
|
89
|
+
raise CanInitializationError(f"could not connect to {url or 'the local can-hub socket'}")
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
interfaces = native.list_interfaces(session, timeout_ms)
|
|
93
|
+
except OSError as error:
|
|
94
|
+
raise CanOperationError(str(error)) from error
|
|
95
|
+
finally:
|
|
96
|
+
native.lib.canhub_close(session)
|
|
97
|
+
|
|
98
|
+
connection = {
|
|
99
|
+
"url": url,
|
|
100
|
+
"identity_cert": identity_cert,
|
|
101
|
+
"identity_key": identity_key,
|
|
102
|
+
"hub_fingerprint": hub_fingerprint,
|
|
103
|
+
"state_dir": state_dir,
|
|
104
|
+
}
|
|
105
|
+
present = {key: value for key, value in connection.items() if value is not None}
|
|
106
|
+
return [cls._to_config(info, present) for info in interfaces]
|
|
107
|
+
|
|
108
|
+
@classmethod
|
|
109
|
+
def _detect_available_configs(cls) -> List[dict]:
|
|
110
|
+
try:
|
|
111
|
+
return cls.list_interfaces(
|
|
112
|
+
url=os.environ.get("CANHUB_URL"),
|
|
113
|
+
identity_cert=os.environ.get("CANHUB_IDENTITY_CERT"),
|
|
114
|
+
identity_key=os.environ.get("CANHUB_IDENTITY_KEY"),
|
|
115
|
+
hub_fingerprint=os.environ.get("CANHUB_HUB_FINGERPRINT"),
|
|
116
|
+
state_dir=os.environ.get("CANHUB_STATE_DIR"),
|
|
117
|
+
)
|
|
118
|
+
except Exception:
|
|
119
|
+
return []
|
|
120
|
+
|
|
77
121
|
def _recv_internal(self, timeout: Optional[float]) -> Tuple[Optional[Message], bool]:
|
|
78
122
|
frame = native.CanHubFrame()
|
|
79
123
|
timeout_ms = -1 if timeout is None else max(0, int(timeout * 1000))
|
|
@@ -151,6 +195,39 @@ class CanHubBus(BusABC):
|
|
|
151
195
|
self._session = None
|
|
152
196
|
raise CanInitializationError(f"could not open {channel}: {detail}")
|
|
153
197
|
|
|
198
|
+
@staticmethod
|
|
199
|
+
def _build_connect_config(
|
|
200
|
+
url: Optional[str],
|
|
201
|
+
identity_cert: Optional[str],
|
|
202
|
+
identity_key: Optional[str],
|
|
203
|
+
hub_fingerprint: Optional[str],
|
|
204
|
+
state_dir: Optional[str],
|
|
205
|
+
timeout_ms: int,
|
|
206
|
+
) -> "native.CanHubConnectConfig":
|
|
207
|
+
if hub_fingerprint is not None:
|
|
208
|
+
hub_fingerprint = str(hub_fingerprint)
|
|
209
|
+
if not _is_fingerprint(hub_fingerprint):
|
|
210
|
+
raise CanInitializationError(
|
|
211
|
+
"hub_fingerprint must be 64 hex characters (it may have been "
|
|
212
|
+
"mangled by python-can config value casting; pass it as a string)"
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
config = native.CanHubConnectConfig()
|
|
216
|
+
config.struct_size = ctypes.sizeof(config)
|
|
217
|
+
config.url = url.encode() if url else None
|
|
218
|
+
config.state_directory = state_dir.encode() if state_dir else None
|
|
219
|
+
config.certificate_path = identity_cert.encode() if identity_cert else None
|
|
220
|
+
config.key_path = identity_key.encode() if identity_key else None
|
|
221
|
+
config.hub_fingerprint = hub_fingerprint.encode() if hub_fingerprint else None
|
|
222
|
+
config.connect_timeout_ms = timeout_ms
|
|
223
|
+
return config
|
|
224
|
+
|
|
225
|
+
@staticmethod
|
|
226
|
+
def _to_config(info: native.CanHubInterfaceInfo, connection: dict) -> dict:
|
|
227
|
+
agent = info.agent.decode("utf-8", errors="replace")
|
|
228
|
+
name = info.interface_name.decode("utf-8", errors="replace")
|
|
229
|
+
return {"interface": "canhub", "channel": f"{agent}/{name}", **connection}
|
|
230
|
+
|
|
154
231
|
@staticmethod
|
|
155
232
|
def _to_message(frame: native.CanHubFrame) -> Message:
|
|
156
233
|
return Message(
|
canhub/libcanhub.dll
CHANGED
|
Binary file
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: python-can-hub
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: python-can backend for can-hub: remote CAN interfaces over unix/tcp/tls/quic
|
|
5
5
|
License: AGPL-3.0-only
|
|
6
6
|
Project-URL: Homepage, https://github.com/can-hub-io/can-hub
|
|
7
7
|
Requires-Python: >=3.9
|
|
8
8
|
Description-Content-Type: text/markdown
|
|
9
9
|
Requires-Dist: python-can >=4.0
|
|
10
|
+
Provides-Extra: test
|
|
11
|
+
Requires-Dist: pytest >=7 ; extra == 'test'
|
|
10
12
|
|
|
11
13
|
# python-can-hub
|
|
12
14
|
|
|
@@ -38,9 +40,47 @@ for message in bus:
|
|
|
38
40
|
Write access follows the hub client ACLs: if the ACL grants read-only, the
|
|
39
41
|
bus opens read-only and `send()` raises.
|
|
40
42
|
|
|
43
|
+
## Listing interfaces
|
|
44
|
+
|
|
45
|
+
`CanHubBus.list_interfaces()` asks a hub for the interfaces it exports. Each
|
|
46
|
+
entry is a python-can config dict you can splat straight into `can.Bus`:
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from canhub import CanHubBus
|
|
50
|
+
import can
|
|
51
|
+
|
|
52
|
+
for config in CanHubBus.list_interfaces(url="quic://hub.example.com:7227"):
|
|
53
|
+
print(config["channel"]) # e.g. "can-agent/can0"
|
|
54
|
+
bus = can.Bus(**CanHubBus.list_interfaces()[0]) # first interface, local hub
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
It accepts the same connection arguments as the bus (`url`, `identity_cert`,
|
|
58
|
+
`identity_key`, `hub_fingerprint`, `state_dir`); omit `url` to query the local
|
|
59
|
+
hub unix socket.
|
|
60
|
+
|
|
61
|
+
This also wires python-can's discovery: `can.detect_available_configs("canhub")`
|
|
62
|
+
returns the same dicts. With no explicit target it reads the connection from the
|
|
63
|
+
environment — `CANHUB_URL`, `CANHUB_STATE_DIR`, `CANHUB_IDENTITY_CERT`,
|
|
64
|
+
`CANHUB_IDENTITY_KEY`, `CANHUB_HUB_FINGERPRINT` — falling back to the local hub
|
|
65
|
+
unix socket. So `CANHUB_URL=quic://hub.example.com:7227` points discovery at a
|
|
66
|
+
remote host without any code.
|
|
67
|
+
|
|
41
68
|
The wheel bundles `libcanhub.so` with the TLS/QUIC stack linked in
|
|
42
69
|
statically; the only runtime dependency is glibc.
|
|
43
70
|
|
|
71
|
+
## Testing
|
|
72
|
+
|
|
73
|
+
The unit tests stub the native library, so they run without a build:
|
|
74
|
+
|
|
75
|
+
```sh
|
|
76
|
+
pip install -e python/.[test]
|
|
77
|
+
pytest python/tests
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The backend is also exercised end-to-end against real binaries (recv, send,
|
|
81
|
+
`list_interfaces`, and `can.detect_available_configs` discovery) by
|
|
82
|
+
`test/e2e/tests/python_can.robot`; run the whole bench with `make e2e`.
|
|
83
|
+
|
|
44
84
|
## Building from source
|
|
45
85
|
|
|
46
86
|
```sh
|
|
@@ -58,5 +98,6 @@ cross arches):
|
|
|
58
98
|
./scripts/build-python-wheel.sh armv7l
|
|
59
99
|
```
|
|
60
100
|
|
|
61
|
-
The release workflow builds
|
|
62
|
-
|
|
101
|
+
The release workflow builds these three on native runners (aarch64/armv7l on
|
|
102
|
+
the arm64 hosted runner, no QEMU) plus a cross-compiled win_amd64 wheel, and
|
|
103
|
+
publishes all four to PyPI on a `v*` tag.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
canhub/__init__.py,sha256=yC7SFSz4vUDFgve7gehAaOSqZ1Iv1Kbv1A4c5nj_XcM,485
|
|
2
|
+
canhub/__main__.py,sha256=WM0VMTSjzySENh7zkbTT4WpLnqyS_gsZBgJ4f05qNHQ,366
|
|
3
|
+
canhub/_native.py,sha256=Dnk0z-fWQ3UkaoxXt4N6Ml7OHJQtXKSGtU-46CH56qo,3672
|
|
4
|
+
canhub/bus.py,sha256=6qv9tXgIA7hHIqnBJMewOyGyFxS-48Of9OaaJ9Abhrw,10085
|
|
5
|
+
canhub/fingerprint.py,sha256=N38dP3_odFYn6821XViWG6aQKUPhxJHDY8xE8uW8zNo,711
|
|
6
|
+
canhub/libcanhub.dll,sha256=xPTHIVUv-1ti0sMR6lA_8iePdFKK5dA9gzTKpd1CKgo,6398464
|
|
7
|
+
python_can_hub-0.2.4.dist-info/METADATA,sha256=t0735LhHCZw2lPs1EI_zNTg971bs8ymKQx05G8JZLZw,3588
|
|
8
|
+
python_can_hub-0.2.4.dist-info/WHEEL,sha256=KTdQDMVZqs2eeRhOpF4kInPW2OgLgRWl7KhVZQueA2o,99
|
|
9
|
+
python_can_hub-0.2.4.dist-info/entry_points.txt,sha256=ZfPo9cCR4xH9BjYRCi_nOcw_48SN6vqboV0B2y8HcjU,46
|
|
10
|
+
python_can_hub-0.2.4.dist-info/top_level.txt,sha256=nPyUT2Mw-NsKrJhpH-4EUcBBqz7GOSwfxnGkFEILnKk,7
|
|
11
|
+
python_can_hub-0.2.4.dist-info/RECORD,,
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
canhub/__init__.py,sha256=yC7SFSz4vUDFgve7gehAaOSqZ1Iv1Kbv1A4c5nj_XcM,485
|
|
2
|
-
canhub/__main__.py,sha256=WM0VMTSjzySENh7zkbTT4WpLnqyS_gsZBgJ4f05qNHQ,366
|
|
3
|
-
canhub/_native.py,sha256=4MHqnxR86NfMN_4jrp5rg60MR1hfhD7p-zXozSoAEAg,3295
|
|
4
|
-
canhub/bus.py,sha256=dLwIGXdGplCyG_vh0Rb9DmM5kUjGCMyV_OlMU10q4x4,7060
|
|
5
|
-
canhub/fingerprint.py,sha256=N38dP3_odFYn6821XViWG6aQKUPhxJHDY8xE8uW8zNo,711
|
|
6
|
-
canhub/libcanhub.dll,sha256=BFcvsPyRdRgD2wiGAAlHZEDRXTqHQlHwjlTkDaxu19k,6396928
|
|
7
|
-
python_can_hub-0.2.2.dist-info/METADATA,sha256=MaxFh9oRfrcQ60f7_UfbYdbz6Ng3RarPS0Om8fDaWVs,1998
|
|
8
|
-
python_can_hub-0.2.2.dist-info/WHEEL,sha256=KTdQDMVZqs2eeRhOpF4kInPW2OgLgRWl7KhVZQueA2o,99
|
|
9
|
-
python_can_hub-0.2.2.dist-info/entry_points.txt,sha256=ZfPo9cCR4xH9BjYRCi_nOcw_48SN6vqboV0B2y8HcjU,46
|
|
10
|
-
python_can_hub-0.2.2.dist-info/top_level.txt,sha256=nPyUT2Mw-NsKrJhpH-4EUcBBqz7GOSwfxnGkFEILnKk,7
|
|
11
|
-
python_can_hub-0.2.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|