python-can-cansub 2026.6.2__tar.gz → 2026.8.17__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.
Files changed (30) hide show
  1. {python_can_cansub-2026.6.2 → python_can_cansub-2026.8.17}/.gitignore +1 -0
  2. python_can_cansub-2026.8.17/CHANGELOG.md +43 -0
  3. python_can_cansub-2026.8.17/PKG-INFO +471 -0
  4. python_can_cansub-2026.8.17/README.md +449 -0
  5. {python_can_cansub-2026.6.2 → python_can_cansub-2026.8.17}/pyproject.toml +14 -4
  6. python_can_cansub-2026.8.17/src/python_can_cansub/__init__.py +14 -0
  7. python_can_cansub-2026.8.17/src/python_can_cansub/cansub.py +1346 -0
  8. python_can_cansub-2026.8.17/src/python_can_cansub/cansub_csv.py +125 -0
  9. {python_can_cansub-2026.6.2 → python_can_cansub-2026.8.17}/src/python_can_cansub/cansub_protocol.py +6 -3
  10. python_can_cansub-2026.8.17/tests/README.md +14 -0
  11. python_can_cansub-2026.8.17/tests/bus/test_open_close.py +167 -0
  12. python_can_cansub-2026.8.17/tests/cmdline/test_cmdline.py +146 -0
  13. python_can_cansub-2026.8.17/tests/conftest.py +79 -0
  14. python_can_cansub-2026.8.17/tests/detect/test_detect.py +26 -0
  15. python_can_cansub-2026.8.17/tests/errors/test_error_frames.py +26 -0
  16. python_can_cansub-2026.8.17/tests/filters/test_filters.py +64 -0
  17. python_can_cansub-2026.8.17/tests/io/test_csv_io.py +127 -0
  18. python_can_cansub-2026.8.17/tests/periodic/test_periodic.py +65 -0
  19. python_can_cansub-2026.8.17/tests/send_recv/test_send_recv.py +110 -0
  20. python_can_cansub-2026.6.2/CHANGELOG.md +0 -16
  21. python_can_cansub-2026.6.2/PKG-INFO +0 -340
  22. python_can_cansub-2026.6.2/README.md +0 -321
  23. python_can_cansub-2026.6.2/private_repo/README.md +0 -1
  24. python_can_cansub-2026.6.2/private_repo/README_BUILD.md +0 -6
  25. python_can_cansub-2026.6.2/private_repo/README_DEV.md +0 -5
  26. python_can_cansub-2026.6.2/private_repo/README_PYPI.md +0 -58
  27. python_can_cansub-2026.6.2/private_repo/README_REPO.md +0 -70
  28. python_can_cansub-2026.6.2/src/python_can_cansub/__init__.py +0 -4
  29. python_can_cansub-2026.6.2/src/python_can_cansub/cansub.py +0 -897
  30. {python_can_cansub-2026.6.2 → python_can_cansub-2026.8.17}/src/python_can_cansub/cansub_root_cert.crt +0 -0
@@ -6,6 +6,7 @@
6
6
 
7
7
  # Virtual environment
8
8
  .venv/
9
+ venv/
9
10
 
10
11
  # Generated by MacOS
11
12
  .DS_Store
@@ -0,0 +1,43 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## 2026.08.17 (API 04.00)
6
+
7
+ ### Added
8
+ - `max_device_time_skew`: device/host clock skew (s) tolerated before setting the device clock on open (`None`=never).
9
+ - `receive_own_messages`: receive the messages transmitted by the bus itself (default `False`).
10
+
11
+ ### Changed
12
+ - Supported device API version changed from `03.00` to `04.00`.
13
+ - The device hostname is now resolved only once on bus open, reducing connection time.
14
+ - `data_bitrate` is now optional. When omitted, transmissions of FD frames are rejected.
15
+ - Updated shutdown sequence (using `DELETE` on the websocket resource after `shutdown_timeout` timeout).
16
+
17
+ ### Removed
18
+ - `server_cert`: the server certificate is now always verified against the built-in CANsub root certificate.
19
+ When connecting via an IP address, certificate hostname verification is automatically disabled
20
+ (certificate chain verification remains active).
21
+
22
+ ## 2026.07.02 (API 03.00)
23
+
24
+ ### Added
25
+ - `server_cert`: path to the server certificate (`.crt` file).
26
+ - `client_cert`: tuple of paths to the client certificate and key (`.crt`, `.key`), used for TLS mutual authentication.
27
+ - Warning when devices with unsupported API versions are found.
28
+
29
+ ### Changed
30
+ - Supported device API version changed from `02.00` to `03.00`.
31
+
32
+ ## 2026.06.02 (API 02.00)
33
+
34
+ ### Added
35
+ - `CANSUB_ROOT_CERT`: path to the CANsub root certificate.
36
+
37
+ ### Changed
38
+ - Transmit sequences (on the device) are deleted on bus open.
39
+
40
+ ## 2026.06.01 (API 02.00)
41
+
42
+ ### Fixed
43
+ - `CyclicSendTask` with `duration=None` now working.
@@ -0,0 +1,471 @@
1
+ Metadata-Version: 2.5
2
+ Name: python-can-cansub
3
+ Version: 2026.8.17
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
+ Project-URL: Changelog, https://github.com/CSS-Electronics/python-can-cansub/blob/master/CHANGELOG.md
8
+ Author: CSS Electronics
9
+ Author-email: contact@csselectronics.com
10
+ License: MIT
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python :: 3
14
+ Requires-Python: >=3.12
15
+ Requires-Dist: python-can>=4.6.0
16
+ Requires-Dist: requests>=2.32.5
17
+ Requires-Dist: wsproto>=1.3.2
18
+ Requires-Dist: zeroconf>=0.131.0
19
+ Provides-Extra: test
20
+ Requires-Dist: pytest>=8.0; extra == 'test'
21
+ Description-Content-Type: text/markdown
22
+
23
+ # python-can-cansub
24
+
25
+ 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).
26
+
27
+ 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.
28
+
29
+ > **Tip:** This README is optimized for LLMs. When using an AI coding assistant with this package, provide this file as context for accurate results.
30
+
31
+ ## Compatibility
32
+
33
+ The python-can-cansub package and the CANsub device communicate over a versioned API. They are compatible when the package supports the API version used by the device firmware.
34
+
35
+ - Each **python-can-cansub** release supports one API version. The supported API version for each release is listed in the [python-can-cansub changelog](https://github.com/CSS-Electronics/python-can-cansub/blob/master/CHANGELOG.md).
36
+ - Each **CANsub firmware** release uses one API version. The API version for each firmware release is listed in the CANsub changelog (provided with the device documentation).
37
+
38
+ To check compatibility, look up the API version of the python-can-cansub release and of the device firmware release in their respective changelogs. If they match, they are compatible.
39
+
40
+ Device *auto-detection* (see *Configuration*) skips devices with an unsupported API version and emits a `UserWarning`. Opening a bus on such a device raises `can.exceptions.CanInitializationError`. In either case, update the package or the device firmware so their API versions align.
41
+
42
+ ## python-can API
43
+
44
+ ### Installation
45
+
46
+ ```bash
47
+ pip install python-can-cansub
48
+ ```
49
+
50
+ ### Import
51
+
52
+ When `python-can-cansub` is installed, the `cansub` interface is automatically registered with python-can. Import with:
53
+
54
+ ```python
55
+ import can
56
+ ```
57
+
58
+ ### Configuration
59
+
60
+ In python-can, a hardware *configuration* is defined by an `interface` and a `channel` (a single interface can have multiple channels).
61
+
62
+ The CANsub `interface` is always `"cansub"`. The `channel` is constructed from the device's unique hostname and the channel index.
63
+
64
+ | Connection | Hostname | python-can `channel` string |
65
+ |------------|-------------------------|-----------------------------------|
66
+ | USB | `[DEVICE-ID]-usb.local` | `[DEVICE-ID]-usb.local@[channel]` |
67
+ | Ethernet | `[DEVICE-ID]-eth.local` | `[DEVICE-ID]-eth.local@[channel]` |
68
+
69
+ The `[DEVICE-ID]` is printed on the device label. Channel indexing is **1-based** - the first channel is `1`.
70
+
71
+ A configuration is passed to `can.Bus` to open a bus.
72
+
73
+ #### Fixed
74
+
75
+ Example of a fixed configuration:
76
+
77
+ ```python
78
+ configs = [{"interface": "cansub", "channel": "aabbccdd-usb.local@1"},
79
+ {"interface": "cansub", "channel": "aabbccdd-usb.local@2"}]
80
+ ```
81
+
82
+ #### Auto-detect
83
+
84
+ Example of using `detect_available_configs` to automatically discover all connected CANsub devices and channels via mDNS:
85
+
86
+ ```python
87
+ configs = can.detect_available_configs(interfaces=["cansub"])
88
+ # e.g. [{"interface": "cansub", "channel": "aabbccdd-usb.local@1"},
89
+ # {"interface": "cansub", "channel": "aabbccdd-usb.local@2"},
90
+ # {"interface": "cansub", "channel": "11223344-eth.local@1"},
91
+ # {"interface": "cansub", "channel": "11223344-eth.local@2"}]
92
+ ```
93
+
94
+ In the above example, two CANsub devices are detected, each with two channels. One device is connected via USB and the other via Ethernet.
95
+
96
+ > **Note:** mDNS discovery relies on inbound UDP port 5353. If a host firewall blocks it, no devices are detected (a fixed configuration still works).
97
+
98
+ ### Opening a Bus
99
+
100
+ The python-can-cansub constructor implements the required arguments for can.Bus and adds custom arguments specific to python-can-cansub. See the python-can-cansub constructor docstring for additional information.
101
+
102
+ A bus is opened by passing a configuration to `can.Bus`:
103
+
104
+ ```python
105
+ with can.Bus(interface="cansub", channel="aabbccdd-usb.local@1", bitrate=250_000, data_bitrate=1_000_000) as bus:
106
+ pass
107
+ ```
108
+
109
+ `**config` unpacks a config dict directly into `can.Bus` keyword arguments - convenient with auto-detected configs, and for opening multiple buses:
110
+
111
+ ```python
112
+ with (can.Bus(**configs[0], bitrate=250_000, data_bitrate=1_000_000) as bus1,
113
+ can.Bus(**configs[1], bitrate=250_000, data_bitrate=1_000_000) as bus2):
114
+ pass
115
+ ```
116
+
117
+ ### Bit Timing
118
+
119
+ `bitrate` and `data_bitrate` configure the bus with a fixed sample point of 80%. For full control of the bit timing (sample point, SJW), pass a `can.BitTiming` (classic CAN) or `can.BitTimingFd` (CAN FD) as `timing` instead. The CANsub CAN clock is 80 MHz:
120
+
121
+ ```python
122
+ timing = can.BitTimingFd.from_sample_point(f_clock=80_000_000,
123
+ nom_bitrate=250_000, nom_sample_point=87.5,
124
+ data_bitrate=1_000_000, data_sample_point=87.5)
125
+
126
+ with can.Bus(**configs[0], timing=timing) as bus:
127
+ pass
128
+ ```
129
+
130
+ ### TLS / Certificates
131
+
132
+ The data connection to the device is secured by TLS, with the device certificate verified against the built-in CANsub root certificate. When connecting via an IP address (which carries no name to verify the certificate hostname against), hostname verification is automatically disabled - the certificate chain is still verified.
133
+
134
+ When TLS mutual authentication is enabled, `client_cert` can be used to provide a tuple of paths to the client certificate (`.crt` file) and its unencrypted private key (`.key` file):
135
+
136
+ ```python
137
+ with can.Bus(**configs[0], client_cert=("/path/to/client.crt", "/path/to/client.key"), bitrate=250_000, data_bitrate=1_000_000) as bus:
138
+ pass
139
+ ```
140
+
141
+ ### Error Frames
142
+
143
+ Error frame reporting is disabled by default; enable it by passing `error_frames=True` to `can.Bus`. Bus errors are then received as a `can.Message` with `is_error_frame` set. The error type is encoded in `arbitration_id`, which can be converted to a `CanSubErrorFrameType` enum:
144
+
145
+ ```python
146
+ from python_can_cansub import CanSubErrorFrameType
147
+
148
+ with can.Bus(**configs[0], bitrate=250_000, data_bitrate=1_000_000, error_frames=True) as bus:
149
+ msg = bus.recv(timeout=1.0)
150
+ if msg and msg.is_error_frame:
151
+ error_type = CanSubErrorFrameType(msg.arbitration_id)
152
+ print(f"Bus error: {error_type.name}") # e.g. "Bus error: ACK"
153
+ ```
154
+
155
+ ### Bus State
156
+
157
+ `bus.state` queries the current channel state: `can.BusState.ACTIVE` (error-active or error-warning), `can.BusState.PASSIVE` (error-passive), or `can.BusState.ERROR` (bus-off). Reading the state of a stopped channel (e.g. after a connection loss) or of a closed bus raises `can.exceptions.CanOperationError`:
158
+
159
+ ```python
160
+ with can.Bus(**configs[0], bitrate=250_000, data_bitrate=1_000_000) as bus:
161
+ print(bus.state) # e.g. "BusState.ACTIVE"
162
+ ```
163
+
164
+ ### Filters
165
+
166
+ 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)`.
167
+
168
+ ```python
169
+ filters = [
170
+ {"can_id": 0x123, "can_mask": 0x7FF, "extended": False}, # standard frames, exact ID match
171
+ {"can_id": 0x000, "can_mask": 0x000, "extended": True}, # all extended frames
172
+ ]
173
+
174
+ with can.Bus(**configs[0], bitrate=250_000, data_bitrate=1_000_000, can_filters=filters) as bus:
175
+ msg = bus.recv(timeout=1.0)
176
+ print(msg)
177
+ ```
178
+
179
+ > **Tip:** Applying hardware filters reduces the network load between the CANsub and the connected client.
180
+
181
+ ### Receive and Transmit
182
+
183
+ A bus receives the messages transmitted by the other nodes on the CAN bus. Messages transmitted by the bus itself are not received, unless the bus is opened with `receive_own_messages=True`.
184
+
185
+ ```python
186
+ with can.Bus(**configs[0], bitrate=250_000, data_bitrate=1_000_000) as bus:
187
+
188
+ # Transmit
189
+ msg_tx = can.Message(is_extended_id=False, arbitration_id=0x123, data=[0x01, 0x02, 0x03, 0x04])
190
+ bus.send(msg_tx)
191
+
192
+ # Receive with timeout
193
+ msg_rx = bus.recv(timeout=1.0)
194
+ print(msg_rx)
195
+ ```
196
+
197
+ #### CAN FD
198
+
199
+ CAN FD frames are transmitted by setting `is_fd` (and typically `bitrate_switch`, which switches to `data_bitrate` for the payload). FD payloads can be up to 64 bytes. Transmitting an FD frame requires the bus to be opened with a `data_bitrate` (or an FD bit timing); on a classic CAN bus, FD transmission raises `can.exceptions.CanOperationError`:
200
+
201
+ ```python
202
+ with can.Bus(**configs[0], bitrate=250_000, data_bitrate=1_000_000) as bus:
203
+ msg_fd = can.Message(is_extended_id=False, arbitration_id=0x123,
204
+ is_fd=True, bitrate_switch=True, data=bytes(range(64)))
205
+ bus.send(msg_fd)
206
+ ```
207
+
208
+ ### Error Handling
209
+
210
+ Operations on a failed bus raise `can.exceptions.CanOperationError`: `send()` and `recv()` raise it once the connection to the device is lost (detected within seconds, also on an idle bus). `send()` additionally raises `can.exceptions.CanTimeoutError` when the transmit queue stays full for the full timeout (back-pressure from a slow or blocked CAN bus). Failures to open a bus raise `can.exceptions.CanInitializationError`.
211
+
212
+ A failed bus does not recover, and the package does not reconnect automatically. To recover from a connection loss, close the failed bus and open a new one:
213
+
214
+ ```python
215
+ try:
216
+ msg = bus.recv(timeout=1.0)
217
+ except can.CanOperationError:
218
+ # Connection lost: the bus cannot recover - close it and open a new one
219
+ bus.shutdown()
220
+ bus = can.Bus(**configs[0], bitrate=250_000, data_bitrate=1_000_000)
221
+ ```
222
+
223
+ ### Closing a Bus
224
+
225
+ Closing a bus (leaving the `with` block, or calling `bus.shutdown()`) waits up to `shutdown_timeout` seconds for the queued messages to be transmitted before asking the device to discard them (see the *Opening a Bus*).
226
+
227
+ Messages received up to (and during) the close remain retrievable with `recv()` after the bus is closed - drain until `None`:
228
+
229
+ ```python
230
+ with can.Bus(**configs[0], bitrate=250_000, data_bitrate=1_000_000) as bus:
231
+ bus.send(can.Message(is_extended_id=False, arbitration_id=0x123, data=[0x01, 0x02, 0x03, 0x04]))
232
+
233
+ # The bus is closed (all queued messages transmitted)
234
+ ```
235
+
236
+ > **Tip:** When two buses are connected to the same physical CAN bus (e.g. a transmitter and a receiver), close the transmitting bus first - closing it waits for the queued messages to be transmitted, which requires the other (acknowledging) bus to still be open.
237
+
238
+ > **Tip:** A `can.Notifier` stops dispatching the moment it is stopped - received messages not yet dispatched are discarded (python-can behavior). Stop the notifier only once the traffic has settled, or read the messages with `recv()` instead.
239
+
240
+ ### Notifier and Listeners
241
+
242
+ `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.
243
+
244
+ python-can provides built-in listeners including `can.Printer` (print to stdout) and `can.Logger` (log to file). The example below prints to stdout and logs to a CSV file while the main program continues. Custom listeners can be implemented by subclassing `can.Listener`.
245
+
246
+ ```python
247
+ from time import sleep
248
+
249
+ with can.Bus(**configs[0], bitrate=250_000, data_bitrate=1_000_000) as bus:
250
+ with can.Notifier([bus], listeners=[can.Printer(), can.Logger("log.csv")]):
251
+
252
+ # Perform other tasks here while frames are received in the background
253
+ sleep(10)
254
+ ```
255
+
256
+ ### Broadcast Manager
257
+
258
+ Periodic transmission jobs can be started with `bus.send_periodic()`.
259
+
260
+ Periodic transmission is offloaded to the CANsub hardware where possible, providing much better transmission time accuracy than a host-scheduled transmission. A host-side background task is used only as a fallback when hardware transmission is not available.
261
+
262
+ > **Note:** python-can requires all messages in a periodic task to share the same arbitration ID (the payload can differ per frame).
263
+
264
+ ```python
265
+ from time import sleep
266
+
267
+ msgs = [
268
+ can.Message(is_extended_id=False, arbitration_id=0x123, data=[0x01, 0x02, 0x03, 0x04]),
269
+ can.Message(is_extended_id=False, arbitration_id=0x123, data=[0x05, 0x06, 0x07, 0x08]),
270
+ can.Message(is_extended_id=False, arbitration_id=0x123, data=[0x09, 0x0A, 0x0B, 0x0C]),
271
+ ]
272
+
273
+ with can.Bus(**configs[0], bitrate=250_000, data_bitrate=1_000_000) as bus:
274
+ # period: time between individual frames (sequence repeats every len(msgs) * period)
275
+ # duration: total transmission time in seconds (None = transmit indefinitely)
276
+ task = bus.send_periodic(msgs, period=0.1, duration=5.0)
277
+
278
+ # Perform other tasks here while frames are transmitted in the background
279
+ sleep(6)
280
+ ```
281
+
282
+ ### Replaying Files
283
+
284
+ `can.MessageSync` can be used to replay messages from a log file.
285
+
286
+ ```python
287
+ with can.Bus(**configs[0], bitrate=250_000, data_bitrate=1_000_000) as bus:
288
+ with can.LogReader("log.csv") as reader:
289
+ for msg in can.MessageSync(messages=reader):
290
+ bus.send(msg)
291
+ ```
292
+
293
+ ### CSV Logger
294
+
295
+ On import, this package overrides the default python-can `.csv` reader and writer with a format compatible with the *webCAN* browser tool provided with the device. This applies automatically wherever `.csv` files are read or written, including `can.Logger`, `can.LogReader`, and the command-line tools.
296
+
297
+ The writer (`CanSubCSVWriter`) and reader (`CanSubCSVReader`) can also be used directly:
298
+
299
+ ```python
300
+ from python_can_cansub import CanSubCSVWriter, CanSubCSVReader
301
+
302
+ # Write received messages to a webCAN-compatible CSV file
303
+ with can.Bus(**configs[0], bitrate=250_000, data_bitrate=1_000_000) as bus:
304
+ with CanSubCSVWriter("log.csv") as writer:
305
+ msg = bus.recv(timeout=1.0)
306
+ if msg:
307
+ writer.on_message_received(msg)
308
+
309
+ # Read messages back from the CSV file
310
+ with CanSubCSVReader("log.csv") as reader:
311
+ for msg in reader:
312
+ print(msg)
313
+ ```
314
+
315
+ ## python-can Tools
316
+
317
+ python-can includes several command-line tools. All tools accept `--interface` and `--channel` to select the bus, following the same configuration as the API.
318
+
319
+ The common argument pattern for the CANsub:
320
+
321
+ ```
322
+ --interface cansub --channel aabbccdd-usb.local@1 --bitrate 250000 --data-bitrate 1000000
323
+ ```
324
+
325
+ Bus arguments without a dedicated command-line flag are passed with `--bus-kwargs key=value ...`. For example, listen-only monitoring:
326
+
327
+ ```
328
+ --interface cansub --channel 192.168.1.10@1 --bitrate 250000 --data-bitrate 1000000 --bus-kwargs listen_only=True
329
+ ```
330
+
331
+ > **Note:** `--bus-kwargs` consumes all values that follow it; place positional arguments (e.g. the `can_player` log file) before it.
332
+
333
+ Note that the *filter* argument supported by some command-line tools matches both standard (11-bit) and extended (29-bit) CAN IDs.
334
+
335
+ ### can_logger
336
+
337
+ Log received frames to a file (format inferred from file extension):
338
+
339
+ ```bash
340
+ can_logger --interface cansub --channel aabbccdd-usb.local@1 --bitrate 250000 --data-bitrate 1000000 --file_name log.csv
341
+ ```
342
+
343
+ ### can_player
344
+
345
+ Play back a previously recorded log file:
346
+
347
+ ```bash
348
+ can_player --interface cansub --channel aabbccdd-usb.local@1 --bitrate 250000 --data-bitrate 1000000 log.csv
349
+ ```
350
+
351
+ ### can_viewer
352
+
353
+ Live terminal viewer showing received frames, updated counts, timestamps, and byte-level changes:
354
+
355
+ ```bash
356
+ can_viewer --interface cansub --channel aabbccdd-usb.local@1 --bitrate 250000 --data-bitrate 1000000
357
+ ```
358
+
359
+ On Windows, `can_viewer` requires `windows-curses` (`pip install windows-curses`).
360
+
361
+ ### can_bridge
362
+
363
+ Forward all frames received on one bus to another (e.g., to bridge two CANsub channels):
364
+
365
+ ```bash
366
+ can_bridge --bus1-interface cansub --bus1-channel aabbccdd-usb.local@1 --bus1-bitrate 250000 --bus1-data-bitrate 1000000 \
367
+ --bus2-interface cansub --bus2-channel aabbccdd-usb.local@2 --bus2-bitrate 250000 --bus2-data-bitrate 1000000
368
+ ```
369
+
370
+ ### can_logconvert
371
+
372
+ Convert a log file between formats; the format is inferred from the file extension:
373
+
374
+ ```bash
375
+ can_logconvert log.csv log.asc
376
+ ```
377
+
378
+ ## Related Packages
379
+
380
+ The following packages complement `python-can-cansub` and are included here as inspiration for working with CAN data in Python.
381
+
382
+ ### cantools
383
+
384
+ [cantools](https://github.com/cantools/cantools) is a Python package for encoding and decoding CAN messages. Encoding/decoding rules can be created directly in code, or loaded from DBC and other database file formats. It works directly with `can.Message` objects from python-can.
385
+
386
+ #### Installation
387
+
388
+ ```bash
389
+ pip install cantools
390
+ ```
391
+
392
+ #### Create database in code
393
+
394
+ A database can be constructed directly in Python without a database file:
395
+
396
+ ```python
397
+ import cantools
398
+ from cantools.database.conversion import LinearConversion
399
+
400
+ msg_def = cantools.database.can.Message(
401
+ frame_id=0x123,
402
+ name="Message1",
403
+ length=8,
404
+ signals=[
405
+ cantools.database.can.Signal(name="Signal1", start=0, length=16,
406
+ conversion=LinearConversion(scale=0.1, offset=0.0, is_float=False),
407
+ minimum=0.0, maximum=100.0),
408
+ cantools.database.can.Signal(name="Signal2", start=16, length=16,
409
+ conversion=LinearConversion(scale=0.1, offset=0.0, is_float=False),
410
+ minimum=0.0, maximum=100.0),
411
+ ]
412
+ )
413
+
414
+ db = cantools.database.Database(messages=[msg_def])
415
+ ```
416
+
417
+ #### Load database from DBC file
418
+
419
+ ```python
420
+ import cantools
421
+
422
+ db = cantools.database.load_file("database.dbc")
423
+ msg_def = db.get_message_by_name("Message1")
424
+ ```
425
+
426
+ #### Encode
427
+
428
+ Encode signal values into the byte payload of a `can.Message`:
429
+
430
+ ```python
431
+ data = msg_def.encode({"Signal1": 1.0, "Signal2": 42.5})
432
+ msg_tx = can.Message(arbitration_id=msg_def.frame_id,
433
+ is_extended_id=msg_def.is_extended_frame,
434
+ data=data)
435
+
436
+ with can.Bus(**configs[0], bitrate=250_000, data_bitrate=1_000_000) as bus:
437
+ bus.send(msg_tx)
438
+ ```
439
+
440
+ #### Decode
441
+
442
+ Decode the byte payload of a received `can.Message` back into signal values:
443
+
444
+ ```python
445
+ with can.Bus(**configs[0], bitrate=250_000, data_bitrate=1_000_000) as bus:
446
+ msg_rx = bus.recv(timeout=1.0)
447
+ if msg_rx:
448
+ signals = db.decode_message(msg_rx.arbitration_id, msg_rx.data)
449
+ print(signals) # e.g. {'Signal1': 1.0, 'Signal2': 42.5}
450
+ ```
451
+
452
+ ### asammdf
453
+
454
+ [asammdf](https://github.com/danielhrisca/asammdf) is a Python package for reading and writing MDF (Measurement Data Format) files.
455
+
456
+ 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`:
457
+
458
+ #### Installation
459
+
460
+ ```bash
461
+ pip install asammdf
462
+ ```
463
+
464
+ #### Playback of MDF log file
465
+
466
+ ```python
467
+ with can.Bus(**configs[0], bitrate=250_000, data_bitrate=1_000_000) as bus:
468
+ with can.LogReader("recording.mf4") as reader:
469
+ for msg in can.MessageSync(messages=reader):
470
+ bus.send(msg)
471
+ ```