python-can-cansub 2026.5.22__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.
@@ -0,0 +1,28 @@
1
+ # Compiled Python bytecode
2
+ *.py[cod]
3
+
4
+ # JetBrains IDE
5
+ .idea/
6
+
7
+ # Virtual environment
8
+ .venv/
9
+
10
+ # Generated by MacOS
11
+ .DS_Store
12
+
13
+ # Generated by Windows
14
+ Thumbs.db
15
+
16
+ # Applications
17
+ *.app
18
+ *.exe
19
+ *.war
20
+
21
+ # Large media files
22
+ *.mp4
23
+ *.tiff
24
+ *.avi
25
+ *.flv
26
+ *.mov
27
+ *.wmv
28
+
@@ -0,0 +1,339 @@
1
+ Metadata-Version: 2.4
2
+ Name: python-can-cansub
3
+ Version: 2026.5.22
4
+ Summary: CANsub python-can interface
5
+ Project-URL: Homepage, https://csselectronics.com/
6
+ Project-URL: Source, https://github.com/CSS-Electronics/python-can-cansub
7
+ Author: CSS Electronics
8
+ Author-email: contact@csselectronics.com
9
+ License: MIT
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3
13
+ Requires-Python: >=3.12
14
+ Requires-Dist: python-can>=4.6.0
15
+ Requires-Dist: requests>=2.32.5
16
+ Requires-Dist: wsproto>=1.3.2
17
+ Requires-Dist: zeroconf>=0.131.0
18
+ Description-Content-Type: text/markdown
19
+
20
+ # python-can-cansub
21
+
22
+ A [python-can](https://python-can.readthedocs.io/) integration for the [CANsub](https://csselectronics.com/) CAN bus interface family by CSS Electronics. Source on [GitHub](https://github.com/CSS-Electronics/python-can-cansub).
23
+
24
+ This package registers the CANsub as a standard python-can interface, making it compatible with all python-can tools and workflows. It also adds a CSV logger compatible with the *webCAN* browser tool provided with the device.
25
+
26
+ > **Tip:** This README is optimized for LLMs. When using an AI coding assistant with this package, provide this file as context for accurate results.
27
+
28
+ ## python-can API
29
+
30
+ ### Installation
31
+
32
+ ```bash
33
+ pip install python-can-cansub
34
+ ```
35
+
36
+ ### Import
37
+
38
+ When `python-can-cansub` is installed, the `cansub` interface is automatically registered with python-can. Import with:
39
+
40
+ ```python
41
+ import can
42
+ ```
43
+
44
+ ### Configuration
45
+
46
+ Python-can defines a hardware *configuration* by an `interface` and a `channel` (a single interface can have multiple channels).
47
+
48
+ The CANsub `interface` is fixed `"cansub"`. The `channel` is constructed from the device hostname (unique) and channel index.
49
+
50
+ | Connection | Hostname | python-can `channel` string |
51
+ |------------|-------------------------|-----------------------------------|
52
+ | USB | `[DEVICE-ID]-usb.local` | `[DEVICE-ID]-usb.local@[channel]` |
53
+ | Ethernet | `[DEVICE-ID]-eth.local` | `[DEVICE-ID]-eth.local@[channel]` |
54
+
55
+ The device-ID is printed on the device label. Channel indexing is **1-based** - the first channel is `1`.
56
+
57
+ A configuration is passed to `can.Bus` to open a bus.
58
+
59
+ #### Fixed
60
+
61
+ Example of a fixed configuration:
62
+
63
+ ```python
64
+ configs = [{"interface": "cansub", "channel": "aabbccdd-usb.local@1"},
65
+ {"interface": "cansub", "channel": "aabbccdd-usb.local@2"}]
66
+ ```
67
+
68
+ #### Auto-detect
69
+
70
+ Example of using `detect_available_configs` to automatically discover (uses mDNS) all connected CANsub devices and channels:
71
+
72
+ ```python
73
+ configs = can.detect_available_configs(interfaces=["cansub"])
74
+ # e.g. [{"interface": "cansub", "channel": "aabbccdd-usb.local@1"},
75
+ # {"interface": "cansub", "channel": "aabbccdd-usb.local@2"}
76
+ # {"interface": "cansub", "channel": "11223344-eth.local@1"}
77
+ # {"interface": "cansub", "channel": "11223344-eth.local@2"}]
78
+ ```
79
+
80
+ In the above example two CANsub devices are detected, each with two channels. One device is connected via USB and the other via Ethernet.
81
+
82
+ ### Opening a Bus
83
+
84
+ #### Single bus - hardcoded
85
+
86
+ ```python
87
+ with can.Bus(interface="cansub", channel="aabbccdd-usb.local@1", bitrate=250_000, data_bitrate=1_000_000) as bus:
88
+ pass
89
+ ```
90
+
91
+ #### Single bus - from configs
92
+
93
+ ```python
94
+ with can.Bus(interface=configs[0]["interface"], channel=configs[0]["channel"], bitrate=250_000, data_bitrate=1_000_000) as bus:
95
+ pass
96
+ ```
97
+
98
+ #### Multiple buses - from configs
99
+
100
+ ```python
101
+ with (can.Bus(interface=configs[0]["interface"], channel=configs[0]["channel"], bitrate=250_000, data_bitrate=1_000_000) as bus1,
102
+ can.Bus(interface=configs[1]["interface"], channel=configs[1]["channel"], bitrate=250_000, data_bitrate=1_000_000) as bus2):
103
+ pass
104
+ ```
105
+
106
+ > **Tip:** `**config` unpacks a config dict directly into `can.Bus` keyword arguments:
107
+ >
108
+ > ```python
109
+ > with can.Bus(**configs[0], bitrate=250_000, data_bitrate=1_000_000) as bus:
110
+ > pass
111
+ > ```
112
+
113
+ ### Receive and Transmit
114
+
115
+ ```python
116
+ with can.Bus(**configs[0], bitrate=250_000, data_bitrate=1_000_000) as bus:
117
+
118
+ # Transmit
119
+ msg_tx = can.Message(is_extended_id=False, arbitration_id=0x123, data=[0x01, 0x02, 0x03, 0x04])
120
+ bus.send(msg_tx)
121
+
122
+ # Receive with timeout
123
+ msg_rx = bus.recv(timeout=1.0)
124
+ print(msg_rx)
125
+ ```
126
+
127
+ ### Filters
128
+
129
+ Apply hardware filters by passing `can_filters` to `can.Bus`. Each filter specifies a `can_id`, a `can_mask`, and whether to match standard (`extended=False`) or extended (`extended=True`) frames. A frame passes if `(frame_id & can_mask) == (can_id & can_mask)`.
130
+
131
+ ```python
132
+ filters = [
133
+ {"can_id": 0x123, "can_mask": 0x7FF, "extended": False}, # standard frames, exact ID match
134
+ {"can_id": 0x000, "can_mask": 0x000, "extended": True}, # all extended frames
135
+ ]
136
+
137
+ with can.Bus(**configs[0], bitrate=250_000, data_bitrate=1_000_000, can_filters=filters) as bus:
138
+ msg = bus.recv(timeout=1.0)
139
+ print(msg)
140
+ ```
141
+
142
+ > **Tip:** Applying hardware filters reduces the network load between the CANsub and the connected client.
143
+
144
+ ### Notifier and Listeners
145
+
146
+ `bus.recv()` blocks until a frame arrives. A `can.Notifier` runs a background thread that dispatches received frames to one or more *listeners*, allowing the main program to continue other work.
147
+
148
+ python-can provides built-in listeners including `can.Printer` (print to stdout) and `can.Logger` (log to file). The example below logs to a CSV file while the main program continues. Custom listeners can be implemented by subclassing `can.Listener`.
149
+
150
+ ```python
151
+ from time import sleep
152
+
153
+ print_listener = can.Printer()
154
+ csv_listener = can.Logger("log.csv")
155
+
156
+ with can.Bus(**configs[0], bitrate=250_000, data_bitrate=1_000_000) as bus:
157
+ with can.Notifier([bus], listeners=[print_listener, csv_listener]):
158
+
159
+ # Perform other tasks here while frames are received in the background
160
+ sleep(10)
161
+ ```
162
+
163
+ ### Broadcast Manager
164
+
165
+ Periodic transmission jobs can be started with `bus.send_periodic()`.
166
+
167
+ Most periodic transmission job types can be offloaded to the CANsub hardware, providing much better transmission time accuracy (compared to a host-scheduled transmission). A host-side background task is used only as a fallback when hardware transmission is not available.
168
+
169
+ ```python
170
+ from time import sleep
171
+
172
+ msgs = [
173
+ can.Message(is_extended_id=False, arbitration_id=0x123, data=[0x01, 0x02, 0x03, 0x04]),
174
+ can.Message(is_extended_id=False, arbitration_id=0x124, data=[0x05, 0x06, 0x07, 0x08]),
175
+ can.Message(is_extended_id=False, arbitration_id=0x125, data=[0x09, 0x0A, 0x0B, 0x0C]),
176
+ ]
177
+
178
+ with can.Bus(**configs[0], bitrate=250_000, data_bitrate=1_000_000) as bus:
179
+ # period: time between individual frames (sequence repeats every len(msgs) * period)
180
+ # duration: total transmission time in seconds (None = transmit indefinitely)
181
+ task = bus.send_periodic(msgs, period=0.1, duration=5.0)
182
+
183
+ # Perform other tasks here while frames are transmitted in the background
184
+ sleep(6)
185
+ ```
186
+
187
+ ### Replaying files
188
+
189
+ `can.MessageSync` can be used to replay messages from a log file.
190
+
191
+ ```python
192
+ with can.Bus(**configs[0], bitrate=250_000, data_bitrate=1_000_000) as bus:
193
+ with can.LogReader("log.csv") as reader:
194
+ for msg in can.MessageSync(messages=reader):
195
+ bus.send(msg)
196
+ ```
197
+
198
+ ## python-can tools
199
+
200
+ python-can includes several command line tools. All tools accept `--interface` and `--channel` to select the bus, following the same configuration as the API.
201
+
202
+ The common argument pattern for the CANsub:
203
+
204
+ ```
205
+ --interface cansub --channel aabbccdd-usb.local@1 --bitrate 250000 --data-bitrate 1000000
206
+ ```
207
+
208
+ ### can_logger
209
+
210
+ Log received frames to a file (CSV by default; format inferred from file extension):
211
+
212
+ ```bash
213
+ can_logger --interface cansub --channel aabbccdd-usb.local@1 --bitrate 250000 --data-bitrate 1000000 --output-file log.csv
214
+ ```
215
+
216
+ ### can_player
217
+
218
+ Play back a previously recorded log file:
219
+
220
+ ```bash
221
+ can_player --interface cansub --channel aabbccdd-usb.local@1 --bitrate 250000 --data-bitrate 1000000 log.csv
222
+ ```
223
+
224
+ ### can_viewer
225
+
226
+ Live terminal viewer showing received frames, updated counts, timestamps, and byte-level changes:
227
+
228
+ ```bash
229
+ can_viewer --interface cansub --channel aabbccdd-usb.local@1 --bitrate 250000 --data-bitrate 1000000
230
+ ```
231
+
232
+ ### can_bridge
233
+
234
+ Forward all frames received on one bus to another (e.g. bridge two CANsub channels):
235
+
236
+ ```bash
237
+ can_bridge --interface cansub --channel aabbccdd-usb.local@1 --bitrate 250000 --data-bitrate 1000000 \
238
+ --interface2 cansub --channel2 aabbccdd-usb.local@2 --bitrate2 250000 --data-bitrate2 1000000
239
+ ```
240
+
241
+ ### can_logconvert
242
+
243
+ Convert a log file between formats; the format is inferred from the file extension:
244
+
245
+ ```bash
246
+ can_logconvert log.csv log.asc
247
+ ```
248
+
249
+ ## Related Packages
250
+
251
+ The following packages complement `python-can-cansub` and are included here as inspiration for working with CAN data in Python.
252
+
253
+ ### cantools
254
+
255
+ [cantools](https://github.com/cantools/cantools) is a Python package for encoding and decoding CAN messages. Encoding/decoding rules can be created or loaded from DBC (and other) database files. It works directly with `can.Message` objects from python-can.
256
+
257
+ #### Installation
258
+
259
+ ```bash
260
+ pip install cantools
261
+ ```
262
+
263
+ #### Create database in code
264
+
265
+ A database can be constructed directly in Python without a database file:
266
+
267
+ ```python
268
+ import cantools
269
+
270
+ db = cantools.database.Database()
271
+
272
+ msg_def = cantools.database.can.Message(
273
+ frame_id=0x123,
274
+ name="Message1",
275
+ length=8,
276
+ signals=[
277
+ cantools.database.can.Signal(name="Signal1", start=0, length=16, scale=0.1, offset=0.0, minimum=0.0, maximum=100.0),
278
+ cantools.database.can.Signal(name="Signal2", start=16, length=16, scale=0.1, offset=0.0, minimum=0.0, maximum=100.0),
279
+ ]
280
+ )
281
+
282
+ db.add_message(msg_def)
283
+ ```
284
+
285
+ #### Load database from DBC file
286
+
287
+ ```python
288
+ import cantools
289
+
290
+ db = cantools.database.load_file("database.dbc")
291
+ msg_def = db.get_message_by_name("Message1")
292
+ ```
293
+
294
+ #### Encode
295
+
296
+ Encode signal values into the byte payload of a `can.Message`:
297
+
298
+ ```python
299
+ data = msg_def.encode({"Signal1": 1.0, "Signal2": 42.5})
300
+ msg_tx = can.Message(arbitration_id=msg_def.frame_id,
301
+ is_extended_id=msg_def.is_extended_frame,
302
+ data=data)
303
+
304
+ with can.Bus(**configs[0], bitrate=250_000, data_bitrate=1_000_000) as bus:
305
+ bus.send(msg_tx)
306
+ ```
307
+
308
+ #### Decode
309
+
310
+ Decode the byte payload of a received `can.Message` back into signal values:
311
+
312
+ ```python
313
+ with can.Bus(**configs[0], bitrate=250_000, data_bitrate=1_000_000) as bus:
314
+ msg_rx = bus.recv(timeout=1.0)
315
+ if msg_rx:
316
+ signals = db.decode_message(msg_rx.arbitration_id, msg_rx.data)
317
+ print(signals) # e.g. {'Signal1': 1.0, 'Signal2': 42.5}
318
+ ```
319
+
320
+ ### asammdf
321
+
322
+ [asammdf](https://github.com/danielhrisca/asammdf) is a Python package for reading and writing MDF (Measurement Data Format) files.
323
+
324
+ When `asammdf` is installed, python-can automatically gains support for reading MDF log files via `can.LogReader`, allowing MDF recordings to be played back directly using `can.MessageSync`:
325
+
326
+ #### Installation
327
+
328
+ ```bash
329
+ pip install asammdf
330
+ ```
331
+
332
+ #### Playback of MDF log file
333
+
334
+ ```python
335
+ with can.Bus(**configs[0], bitrate=250_000, data_bitrate=1_000_000) as bus:
336
+ with can.LogReader("recording.mf4") as reader:
337
+ for msg in can.MessageSync(messages=reader):
338
+ bus.send(msg)
339
+ ```
@@ -0,0 +1,320 @@
1
+ # python-can-cansub
2
+
3
+ A [python-can](https://python-can.readthedocs.io/) integration for the [CANsub](https://csselectronics.com/) CAN bus interface family by CSS Electronics. Source on [GitHub](https://github.com/CSS-Electronics/python-can-cansub).
4
+
5
+ This package registers the CANsub as a standard python-can interface, making it compatible with all python-can tools and workflows. It also adds a CSV logger compatible with the *webCAN* browser tool provided with the device.
6
+
7
+ > **Tip:** This README is optimized for LLMs. When using an AI coding assistant with this package, provide this file as context for accurate results.
8
+
9
+ ## python-can API
10
+
11
+ ### Installation
12
+
13
+ ```bash
14
+ pip install python-can-cansub
15
+ ```
16
+
17
+ ### Import
18
+
19
+ When `python-can-cansub` is installed, the `cansub` interface is automatically registered with python-can. Import with:
20
+
21
+ ```python
22
+ import can
23
+ ```
24
+
25
+ ### Configuration
26
+
27
+ Python-can defines a hardware *configuration* by an `interface` and a `channel` (a single interface can have multiple channels).
28
+
29
+ The CANsub `interface` is fixed `"cansub"`. The `channel` is constructed from the device hostname (unique) and channel index.
30
+
31
+ | Connection | Hostname | python-can `channel` string |
32
+ |------------|-------------------------|-----------------------------------|
33
+ | USB | `[DEVICE-ID]-usb.local` | `[DEVICE-ID]-usb.local@[channel]` |
34
+ | Ethernet | `[DEVICE-ID]-eth.local` | `[DEVICE-ID]-eth.local@[channel]` |
35
+
36
+ The device-ID is printed on the device label. Channel indexing is **1-based** - the first channel is `1`.
37
+
38
+ A configuration is passed to `can.Bus` to open a bus.
39
+
40
+ #### Fixed
41
+
42
+ Example of a fixed configuration:
43
+
44
+ ```python
45
+ configs = [{"interface": "cansub", "channel": "aabbccdd-usb.local@1"},
46
+ {"interface": "cansub", "channel": "aabbccdd-usb.local@2"}]
47
+ ```
48
+
49
+ #### Auto-detect
50
+
51
+ Example of using `detect_available_configs` to automatically discover (uses mDNS) all connected CANsub devices and channels:
52
+
53
+ ```python
54
+ configs = can.detect_available_configs(interfaces=["cansub"])
55
+ # e.g. [{"interface": "cansub", "channel": "aabbccdd-usb.local@1"},
56
+ # {"interface": "cansub", "channel": "aabbccdd-usb.local@2"}
57
+ # {"interface": "cansub", "channel": "11223344-eth.local@1"}
58
+ # {"interface": "cansub", "channel": "11223344-eth.local@2"}]
59
+ ```
60
+
61
+ In the above example two CANsub devices are detected, each with two channels. One device is connected via USB and the other via Ethernet.
62
+
63
+ ### Opening a Bus
64
+
65
+ #### Single bus - hardcoded
66
+
67
+ ```python
68
+ with can.Bus(interface="cansub", channel="aabbccdd-usb.local@1", bitrate=250_000, data_bitrate=1_000_000) as bus:
69
+ pass
70
+ ```
71
+
72
+ #### Single bus - from configs
73
+
74
+ ```python
75
+ with can.Bus(interface=configs[0]["interface"], channel=configs[0]["channel"], bitrate=250_000, data_bitrate=1_000_000) as bus:
76
+ pass
77
+ ```
78
+
79
+ #### Multiple buses - from configs
80
+
81
+ ```python
82
+ with (can.Bus(interface=configs[0]["interface"], channel=configs[0]["channel"], bitrate=250_000, data_bitrate=1_000_000) as bus1,
83
+ can.Bus(interface=configs[1]["interface"], channel=configs[1]["channel"], bitrate=250_000, data_bitrate=1_000_000) as bus2):
84
+ pass
85
+ ```
86
+
87
+ > **Tip:** `**config` unpacks a config dict directly into `can.Bus` keyword arguments:
88
+ >
89
+ > ```python
90
+ > with can.Bus(**configs[0], bitrate=250_000, data_bitrate=1_000_000) as bus:
91
+ > pass
92
+ > ```
93
+
94
+ ### Receive and Transmit
95
+
96
+ ```python
97
+ with can.Bus(**configs[0], bitrate=250_000, data_bitrate=1_000_000) as bus:
98
+
99
+ # Transmit
100
+ msg_tx = can.Message(is_extended_id=False, arbitration_id=0x123, data=[0x01, 0x02, 0x03, 0x04])
101
+ bus.send(msg_tx)
102
+
103
+ # Receive with timeout
104
+ msg_rx = bus.recv(timeout=1.0)
105
+ print(msg_rx)
106
+ ```
107
+
108
+ ### Filters
109
+
110
+ Apply hardware filters by passing `can_filters` to `can.Bus`. Each filter specifies a `can_id`, a `can_mask`, and whether to match standard (`extended=False`) or extended (`extended=True`) frames. A frame passes if `(frame_id & can_mask) == (can_id & can_mask)`.
111
+
112
+ ```python
113
+ filters = [
114
+ {"can_id": 0x123, "can_mask": 0x7FF, "extended": False}, # standard frames, exact ID match
115
+ {"can_id": 0x000, "can_mask": 0x000, "extended": True}, # all extended frames
116
+ ]
117
+
118
+ with can.Bus(**configs[0], bitrate=250_000, data_bitrate=1_000_000, can_filters=filters) as bus:
119
+ msg = bus.recv(timeout=1.0)
120
+ print(msg)
121
+ ```
122
+
123
+ > **Tip:** Applying hardware filters reduces the network load between the CANsub and the connected client.
124
+
125
+ ### Notifier and Listeners
126
+
127
+ `bus.recv()` blocks until a frame arrives. A `can.Notifier` runs a background thread that dispatches received frames to one or more *listeners*, allowing the main program to continue other work.
128
+
129
+ python-can provides built-in listeners including `can.Printer` (print to stdout) and `can.Logger` (log to file). The example below logs to a CSV file while the main program continues. Custom listeners can be implemented by subclassing `can.Listener`.
130
+
131
+ ```python
132
+ from time import sleep
133
+
134
+ print_listener = can.Printer()
135
+ csv_listener = can.Logger("log.csv")
136
+
137
+ with can.Bus(**configs[0], bitrate=250_000, data_bitrate=1_000_000) as bus:
138
+ with can.Notifier([bus], listeners=[print_listener, csv_listener]):
139
+
140
+ # Perform other tasks here while frames are received in the background
141
+ sleep(10)
142
+ ```
143
+
144
+ ### Broadcast Manager
145
+
146
+ Periodic transmission jobs can be started with `bus.send_periodic()`.
147
+
148
+ Most periodic transmission job types can be offloaded to the CANsub hardware, providing much better transmission time accuracy (compared to a host-scheduled transmission). A host-side background task is used only as a fallback when hardware transmission is not available.
149
+
150
+ ```python
151
+ from time import sleep
152
+
153
+ msgs = [
154
+ can.Message(is_extended_id=False, arbitration_id=0x123, data=[0x01, 0x02, 0x03, 0x04]),
155
+ can.Message(is_extended_id=False, arbitration_id=0x124, data=[0x05, 0x06, 0x07, 0x08]),
156
+ can.Message(is_extended_id=False, arbitration_id=0x125, data=[0x09, 0x0A, 0x0B, 0x0C]),
157
+ ]
158
+
159
+ with can.Bus(**configs[0], bitrate=250_000, data_bitrate=1_000_000) as bus:
160
+ # period: time between individual frames (sequence repeats every len(msgs) * period)
161
+ # duration: total transmission time in seconds (None = transmit indefinitely)
162
+ task = bus.send_periodic(msgs, period=0.1, duration=5.0)
163
+
164
+ # Perform other tasks here while frames are transmitted in the background
165
+ sleep(6)
166
+ ```
167
+
168
+ ### Replaying files
169
+
170
+ `can.MessageSync` can be used to replay messages from a log file.
171
+
172
+ ```python
173
+ with can.Bus(**configs[0], bitrate=250_000, data_bitrate=1_000_000) as bus:
174
+ with can.LogReader("log.csv") as reader:
175
+ for msg in can.MessageSync(messages=reader):
176
+ bus.send(msg)
177
+ ```
178
+
179
+ ## python-can tools
180
+
181
+ python-can includes several command line tools. All tools accept `--interface` and `--channel` to select the bus, following the same configuration as the API.
182
+
183
+ The common argument pattern for the CANsub:
184
+
185
+ ```
186
+ --interface cansub --channel aabbccdd-usb.local@1 --bitrate 250000 --data-bitrate 1000000
187
+ ```
188
+
189
+ ### can_logger
190
+
191
+ Log received frames to a file (CSV by default; format inferred from file extension):
192
+
193
+ ```bash
194
+ can_logger --interface cansub --channel aabbccdd-usb.local@1 --bitrate 250000 --data-bitrate 1000000 --output-file log.csv
195
+ ```
196
+
197
+ ### can_player
198
+
199
+ Play back a previously recorded log file:
200
+
201
+ ```bash
202
+ can_player --interface cansub --channel aabbccdd-usb.local@1 --bitrate 250000 --data-bitrate 1000000 log.csv
203
+ ```
204
+
205
+ ### can_viewer
206
+
207
+ Live terminal viewer showing received frames, updated counts, timestamps, and byte-level changes:
208
+
209
+ ```bash
210
+ can_viewer --interface cansub --channel aabbccdd-usb.local@1 --bitrate 250000 --data-bitrate 1000000
211
+ ```
212
+
213
+ ### can_bridge
214
+
215
+ Forward all frames received on one bus to another (e.g. bridge two CANsub channels):
216
+
217
+ ```bash
218
+ can_bridge --interface cansub --channel aabbccdd-usb.local@1 --bitrate 250000 --data-bitrate 1000000 \
219
+ --interface2 cansub --channel2 aabbccdd-usb.local@2 --bitrate2 250000 --data-bitrate2 1000000
220
+ ```
221
+
222
+ ### can_logconvert
223
+
224
+ Convert a log file between formats; the format is inferred from the file extension:
225
+
226
+ ```bash
227
+ can_logconvert log.csv log.asc
228
+ ```
229
+
230
+ ## Related Packages
231
+
232
+ The following packages complement `python-can-cansub` and are included here as inspiration for working with CAN data in Python.
233
+
234
+ ### cantools
235
+
236
+ [cantools](https://github.com/cantools/cantools) is a Python package for encoding and decoding CAN messages. Encoding/decoding rules can be created or loaded from DBC (and other) database files. It works directly with `can.Message` objects from python-can.
237
+
238
+ #### Installation
239
+
240
+ ```bash
241
+ pip install cantools
242
+ ```
243
+
244
+ #### Create database in code
245
+
246
+ A database can be constructed directly in Python without a database file:
247
+
248
+ ```python
249
+ import cantools
250
+
251
+ db = cantools.database.Database()
252
+
253
+ msg_def = cantools.database.can.Message(
254
+ frame_id=0x123,
255
+ name="Message1",
256
+ length=8,
257
+ signals=[
258
+ cantools.database.can.Signal(name="Signal1", start=0, length=16, scale=0.1, offset=0.0, minimum=0.0, maximum=100.0),
259
+ cantools.database.can.Signal(name="Signal2", start=16, length=16, scale=0.1, offset=0.0, minimum=0.0, maximum=100.0),
260
+ ]
261
+ )
262
+
263
+ db.add_message(msg_def)
264
+ ```
265
+
266
+ #### Load database from DBC file
267
+
268
+ ```python
269
+ import cantools
270
+
271
+ db = cantools.database.load_file("database.dbc")
272
+ msg_def = db.get_message_by_name("Message1")
273
+ ```
274
+
275
+ #### Encode
276
+
277
+ Encode signal values into the byte payload of a `can.Message`:
278
+
279
+ ```python
280
+ data = msg_def.encode({"Signal1": 1.0, "Signal2": 42.5})
281
+ msg_tx = can.Message(arbitration_id=msg_def.frame_id,
282
+ is_extended_id=msg_def.is_extended_frame,
283
+ data=data)
284
+
285
+ with can.Bus(**configs[0], bitrate=250_000, data_bitrate=1_000_000) as bus:
286
+ bus.send(msg_tx)
287
+ ```
288
+
289
+ #### Decode
290
+
291
+ Decode the byte payload of a received `can.Message` back into signal values:
292
+
293
+ ```python
294
+ with can.Bus(**configs[0], bitrate=250_000, data_bitrate=1_000_000) as bus:
295
+ msg_rx = bus.recv(timeout=1.0)
296
+ if msg_rx:
297
+ signals = db.decode_message(msg_rx.arbitration_id, msg_rx.data)
298
+ print(signals) # e.g. {'Signal1': 1.0, 'Signal2': 42.5}
299
+ ```
300
+
301
+ ### asammdf
302
+
303
+ [asammdf](https://github.com/danielhrisca/asammdf) is a Python package for reading and writing MDF (Measurement Data Format) files.
304
+
305
+ When `asammdf` is installed, python-can automatically gains support for reading MDF log files via `can.LogReader`, allowing MDF recordings to be played back directly using `can.MessageSync`:
306
+
307
+ #### Installation
308
+
309
+ ```bash
310
+ pip install asammdf
311
+ ```
312
+
313
+ #### Playback of MDF log file
314
+
315
+ ```python
316
+ with can.Bus(**configs[0], bitrate=250_000, data_bitrate=1_000_000) as bus:
317
+ with can.LogReader("recording.mf4") as reader:
318
+ for msg in can.MessageSync(messages=reader):
319
+ bus.send(msg)
320
+ ```
@@ -0,0 +1 @@
1
+ Avoid the contents of this folder in the public repo (no harm, just not needed).
@@ -0,0 +1,6 @@
1
+ Build steps:
2
+
3
+ - Update version in pyproject.toml
4
+ - Ensure "build" is installed, "pip install build"
5
+ - Build with "python -m build"
6
+ - Install with e.g.: "pip install dist/python_can_cansub-2026.03.06-py3-none-any.whl"
@@ -0,0 +1,5 @@
1
+ While developing, install the package in editable mode from the project root:
2
+
3
+ .venv/bin/pip install -e .
4
+
5
+ This makes pip create a direct link into src/ instead of copying files. Any changes you make to src/python_can_cansub/ are reflected immediately - no rebuild or reinstall needed.