python-can-canpal 0.1.0rc1__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.

Potentially problematic release.


This version of python-can-canpal might be problematic. Click here for more details.

Files changed (38) hide show
  1. python_can_canpal-0.1.0rc1/LICENSE +25 -0
  2. python_can_canpal-0.1.0rc1/MANIFEST.in +10 -0
  3. python_can_canpal-0.1.0rc1/PKG-INFO +109 -0
  4. python_can_canpal-0.1.0rc1/README.md +88 -0
  5. python_can_canpal-0.1.0rc1/docs/INSTALL.md +101 -0
  6. python_can_canpal-0.1.0rc1/docs/RELEASE.md +39 -0
  7. python_can_canpal-0.1.0rc1/docs/TESTING.md +82 -0
  8. python_can_canpal-0.1.0rc1/docs/hardware-lab.example.json +47 -0
  9. python_can_canpal-0.1.0rc1/native/fake/abi_internal.cpp +113 -0
  10. python_can_canpal-0.1.0rc1/native/fake/abi_probe.cpp +109 -0
  11. python_can_canpal-0.1.0rc1/native/fake/fake.cpp +280 -0
  12. python_can_canpal-0.1.0rc1/native/fake/sdk_smoke.cpp +93 -0
  13. python_can_canpal-0.1.0rc1/native/fake/sdk_smoke.pro +14 -0
  14. python_can_canpal-0.1.0rc1/native/sdk-revision.txt +1 -0
  15. python_can_canpal-0.1.0rc1/pyproject.toml +37 -0
  16. python_can_canpal-0.1.0rc1/setup.cfg +4 -0
  17. python_can_canpal-0.1.0rc1/src/canpal/__init__.py +10 -0
  18. python_can_canpal-0.1.0rc1/src/canpal/bus.py +312 -0
  19. python_can_canpal-0.1.0rc1/src/canpal/codec.py +110 -0
  20. python_can_canpal-0.1.0rc1/src/canpal/discovery.py +66 -0
  21. python_can_canpal-0.1.0rc1/src/canpal/native.py +187 -0
  22. python_can_canpal-0.1.0rc1/src/python_can_canpal.egg-info/PKG-INFO +109 -0
  23. python_can_canpal-0.1.0rc1/src/python_can_canpal.egg-info/SOURCES.txt +36 -0
  24. python_can_canpal-0.1.0rc1/src/python_can_canpal.egg-info/dependency_links.txt +1 -0
  25. python_can_canpal-0.1.0rc1/src/python_can_canpal.egg-info/entry_points.txt +2 -0
  26. python_can_canpal-0.1.0rc1/src/python_can_canpal.egg-info/requires.txt +7 -0
  27. python_can_canpal-0.1.0rc1/src/python_can_canpal.egg-info/top_level.txt +1 -0
  28. python_can_canpal-0.1.0rc1/tests/conftest.py +99 -0
  29. python_can_canpal-0.1.0rc1/tests/test_bus.py +279 -0
  30. python_can_canpal-0.1.0rc1/tests/test_codec.py +91 -0
  31. python_can_canpal-0.1.0rc1/tests/test_ecosystem.py +154 -0
  32. python_can_canpal-0.1.0rc1/tests/test_hardware.py +372 -0
  33. python_can_canpal-0.1.0rc1/tests/test_release_check.py +167 -0
  34. python_can_canpal-0.1.0rc1/tools/build_fake.py +51 -0
  35. python_can_canpal-0.1.0rc1/tools/build_sdk.py +93 -0
  36. python_can_canpal-0.1.0rc1/tools/inspect_devices.py +62 -0
  37. python_can_canpal-0.1.0rc1/tools/release_check.py +163 -0
  38. python_can_canpal-0.1.0rc1/tools/sdk_source.py +21 -0
@@ -0,0 +1,25 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Python CANPAL contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ This license applies to the newly developed PythonCAN plugin, test harness,
24
+ and documentation. The separately supplied CANPAL SDK, its headers, firmware,
25
+ Qt, and other third-party components retain their respective licenses.
@@ -0,0 +1,10 @@
1
+ include LICENSE README.md pyproject.toml
2
+ include docs/INSTALL.md docs/TESTING.md docs/RELEASE.md docs/hardware-lab.example.json
3
+ recursive-include native/fake *.cpp *.pro
4
+ include native/sdk-revision.txt
5
+ include tools/sdk_source.py tools/build_fake.py tools/build_sdk.py tools/inspect_devices.py tools/release_check.py
6
+ recursive-include tests *.py
7
+ prune build
8
+ prune dist
9
+ prune artifacts
10
+ exclude PLAN.zh-CN.md
@@ -0,0 +1,109 @@
1
+ Metadata-Version: 2.4
2
+ Name: python-can-canpal
3
+ Version: 0.1.0rc1
4
+ Summary: CANPAL USB and TCP backend for python-can
5
+ License-Expression: MIT
6
+ Classifier: Development Status :: 4 - Beta
7
+ Classifier: Programming Language :: Python :: 3.12
8
+ Classifier: Operating System :: Microsoft :: Windows
9
+ Classifier: Operating System :: POSIX :: Linux
10
+ Classifier: Topic :: System :: Hardware :: Hardware Drivers
11
+ Requires-Python: <3.13,>=3.12
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: python-can==4.6.1
15
+ Requires-Dist: pyserial==3.5
16
+ Provides-Extra: test
17
+ Requires-Dist: pytest<10,>=8; extra == "test"
18
+ Requires-Dist: build<2,>=1; extra == "test"
19
+ Requires-Dist: twine<8,>=6; extra == "test"
20
+ Dynamic: license-file
21
+
22
+ # python-can-canpal
23
+
24
+ CANPAL USB and TCP client backend for [python-can](https://python-can.readthedocs.io/).
25
+
26
+ **0.1.0rc1 is an experimental preview. Full hardware release acceptance is incomplete.**
27
+ Use it for evaluation on an isolated test bench. It is not the qualified 0.1.0 release.
28
+
29
+ ## Install
30
+
31
+ Requires Python 3.12, python-can 4.6.1, pySerial 3.5, and a separately supplied
32
+ 64-bit CANPAL native SDK with the fixes described below.
33
+
34
+ ```text
35
+ python -m pip install python-can-canpal==0.1.0rc1
36
+ ```
37
+
38
+ The Python package does **not** include the CANPAL SDK, firmware, device drivers,
39
+ Qt/MinGW runtime, or fake binaries. Obtain compatible native components from your
40
+ CANPAL supplier before attempting hardware access. The MIT license applies to
41
+ the new Python plugin, tests and documentation; native components have separate licenses.
42
+
43
+ ## What has been verified
44
+
45
+ | Environment | Preview evidence |
46
+ | --- | --- |
47
+ | Windows 11 x64 | X5 classic CAN/RTR and X6 classic CAN/RTR/ISO FD over USB and explicit-IP WLAN TCP |
48
+ | Windows, multiple devices | Short concurrent checks: two USB devices, X5 USB + X6 WLAN, X5 WLAN + X6 USB |
49
+ | Ubuntu 24.04 x86_64 in Hyper-V | Software, ABI and real SDK regressions; X5/X6 hardware checks through temporary SSH TCP forwarding via a Windows WLAN connection |
50
+
51
+ Both devices had CAN1 wired to their own CAN2 on separate physical buses.
52
+ These checks do not use an independent reference CAN interface. Classic checks
53
+ covered standard/extended frames and RTR DLC 0–8 in both directions. X6 FD checks
54
+ covered lengths 0–64 and BRS off/on. The post-fix checks verified 5,280 frames in total.
55
+
56
+ **Not yet qualified:** independent-reference CAN/CAN FD interoperability, native
57
+ Linux USB or direct Linux WLAN, two simultaneous WLAN devices, controlled physical
58
+ port renumbering/disconnection, rated-load/endurance runs, and native resource
59
+ stability. Fake tests cover additional error paths but do not establish those hardware claims.
60
+ Formal 0.1.0 retains the complete release acceptance requirements.
61
+
62
+ ## Native SDK and firmware requirements
63
+
64
+ The tested SDK source revision is `e222ccf48f29c530fd09617df6e3a83a8095d25f`.
65
+ It includes command ACK matching, cooperative worker shutdown and a Qt serial
66
+ buffered-read fix. Matching exported function names alone does not establish compatibility.
67
+
68
+ Classic RTR also requires firmware that preserves the received DLC. The original
69
+ X5 firmware reporting `0x0332` and X6 firmware reporting `0x0127` cleared RTR DLC
70
+ to zero. Supplier-signed diagnostic builds fixed this on the two test devices.
71
+ Those builds still report the same version numbers: ask your supplier to identify
72
+ the actual fixed build. The plugin cannot recover DLC that firmware has discarded.
73
+
74
+ Set `CANPAL_LIBRARY` to the absolute DLL/SO path, or pass `library_path`.
75
+ On Windows, `CANPAL_DEPENDENCY_DIRS` may list Qt/MinGW runtime directories,
76
+ separated by semicolons. On Linux, install the matching Qt 5 runtime and grant
77
+ serial-port access through udev or the dialout group.
78
+
79
+ ## Connect by UID or IP
80
+
81
+ ```python
82
+ import can
83
+
84
+ with can.Bus(interface="canpal", ignore_config=True,
85
+ transport="usb", device_id="YOUR_24_HEX_DIGIT_UID",
86
+ channel=0, bitrate=500_000) as bus:
87
+ message = bus.recv(timeout=1)
88
+ ```
89
+
90
+ The device UID remains the selector when a COM/ttyACM number changes. After a
91
+ reconnection, close and recreate the Bus using the same UID; automatic reconnect
92
+ is not implemented. Physical port renumbering acceptance is still pending.
93
+
94
+ Use `transport="wlan", host="192.0.2.10", port=8000` for TCP direct connection.
95
+ The IP and port must match the actual device; no network discovery is attempted.
96
+ Keep `device_id` to verify identity at that endpoint. X6 CAN FD additionally uses
97
+ `fd=True, data_bitrate=2_000_000`.
98
+
99
+ Different physical devices use independent Bus objects. A device can have one
100
+ Bus and one selected CAN channel at a time. Use one Notifier per device for
101
+ independent fault handling. UDP, server mode, transmit echo and concurrent
102
+ channels on one device are not implemented.
103
+
104
+ A successful `send()` means the native queue accepted the frame, not that the
105
+ CAN bus acknowledged it. Use a single receiver per Bus, stop its Notifier before
106
+ shutdown, and handle python-can exceptions. Timestamp alignment is approximate
107
+ per device and does not provide precise synchronization between devices.
108
+
109
+ The source distribution includes Chinese installation, testing and release notes.
@@ -0,0 +1,88 @@
1
+ # python-can-canpal
2
+
3
+ CANPAL USB and TCP client backend for [python-can](https://python-can.readthedocs.io/).
4
+
5
+ **0.1.0rc1 is an experimental preview. Full hardware release acceptance is incomplete.**
6
+ Use it for evaluation on an isolated test bench. It is not the qualified 0.1.0 release.
7
+
8
+ ## Install
9
+
10
+ Requires Python 3.12, python-can 4.6.1, pySerial 3.5, and a separately supplied
11
+ 64-bit CANPAL native SDK with the fixes described below.
12
+
13
+ ```text
14
+ python -m pip install python-can-canpal==0.1.0rc1
15
+ ```
16
+
17
+ The Python package does **not** include the CANPAL SDK, firmware, device drivers,
18
+ Qt/MinGW runtime, or fake binaries. Obtain compatible native components from your
19
+ CANPAL supplier before attempting hardware access. The MIT license applies to
20
+ the new Python plugin, tests and documentation; native components have separate licenses.
21
+
22
+ ## What has been verified
23
+
24
+ | Environment | Preview evidence |
25
+ | --- | --- |
26
+ | Windows 11 x64 | X5 classic CAN/RTR and X6 classic CAN/RTR/ISO FD over USB and explicit-IP WLAN TCP |
27
+ | Windows, multiple devices | Short concurrent checks: two USB devices, X5 USB + X6 WLAN, X5 WLAN + X6 USB |
28
+ | Ubuntu 24.04 x86_64 in Hyper-V | Software, ABI and real SDK regressions; X5/X6 hardware checks through temporary SSH TCP forwarding via a Windows WLAN connection |
29
+
30
+ Both devices had CAN1 wired to their own CAN2 on separate physical buses.
31
+ These checks do not use an independent reference CAN interface. Classic checks
32
+ covered standard/extended frames and RTR DLC 0–8 in both directions. X6 FD checks
33
+ covered lengths 0–64 and BRS off/on. The post-fix checks verified 5,280 frames in total.
34
+
35
+ **Not yet qualified:** independent-reference CAN/CAN FD interoperability, native
36
+ Linux USB or direct Linux WLAN, two simultaneous WLAN devices, controlled physical
37
+ port renumbering/disconnection, rated-load/endurance runs, and native resource
38
+ stability. Fake tests cover additional error paths but do not establish those hardware claims.
39
+ Formal 0.1.0 retains the complete release acceptance requirements.
40
+
41
+ ## Native SDK and firmware requirements
42
+
43
+ The tested SDK source revision is `e222ccf48f29c530fd09617df6e3a83a8095d25f`.
44
+ It includes command ACK matching, cooperative worker shutdown and a Qt serial
45
+ buffered-read fix. Matching exported function names alone does not establish compatibility.
46
+
47
+ Classic RTR also requires firmware that preserves the received DLC. The original
48
+ X5 firmware reporting `0x0332` and X6 firmware reporting `0x0127` cleared RTR DLC
49
+ to zero. Supplier-signed diagnostic builds fixed this on the two test devices.
50
+ Those builds still report the same version numbers: ask your supplier to identify
51
+ the actual fixed build. The plugin cannot recover DLC that firmware has discarded.
52
+
53
+ Set `CANPAL_LIBRARY` to the absolute DLL/SO path, or pass `library_path`.
54
+ On Windows, `CANPAL_DEPENDENCY_DIRS` may list Qt/MinGW runtime directories,
55
+ separated by semicolons. On Linux, install the matching Qt 5 runtime and grant
56
+ serial-port access through udev or the dialout group.
57
+
58
+ ## Connect by UID or IP
59
+
60
+ ```python
61
+ import can
62
+
63
+ with can.Bus(interface="canpal", ignore_config=True,
64
+ transport="usb", device_id="YOUR_24_HEX_DIGIT_UID",
65
+ channel=0, bitrate=500_000) as bus:
66
+ message = bus.recv(timeout=1)
67
+ ```
68
+
69
+ The device UID remains the selector when a COM/ttyACM number changes. After a
70
+ reconnection, close and recreate the Bus using the same UID; automatic reconnect
71
+ is not implemented. Physical port renumbering acceptance is still pending.
72
+
73
+ Use `transport="wlan", host="192.0.2.10", port=8000` for TCP direct connection.
74
+ The IP and port must match the actual device; no network discovery is attempted.
75
+ Keep `device_id` to verify identity at that endpoint. X6 CAN FD additionally uses
76
+ `fd=True, data_bitrate=2_000_000`.
77
+
78
+ Different physical devices use independent Bus objects. A device can have one
79
+ Bus and one selected CAN channel at a time. Use one Notifier per device for
80
+ independent fault handling. UDP, server mode, transmit echo and concurrent
81
+ channels on one device are not implemented.
82
+
83
+ A successful `send()` means the native queue accepted the frame, not that the
84
+ CAN bus acknowledged it. Use a single receiver per Bus, stop its Notifier before
85
+ shutdown, and handle python-can exceptions. Timestamp alignment is approximate
86
+ per device and does not provide precise synchronization between devices.
87
+
88
+ The source distribution includes Chinese installation, testing and release notes.
@@ -0,0 +1,101 @@
1
+ # 安装与连接
2
+
3
+ 当前 0.1.0rc1 为实验性预览版。正式发布资格以 RELEASE.md 和验收报告为准,构建成功不表示硬件验收完成。
4
+
5
+ ## 环境
6
+
7
+ 首版目标是 Python 3.12、Windows 11 x64 和 Ubuntu 24.04 x86_64。
8
+ python-can 固定 4.6.1,pySerial 固定 3.5。WSL 可用于开发,但不能代替原生 Linux 硬件验收。
9
+
10
+ 安装 Python 包后,还需要单独安装兼容的 CANPAL 原生 SDK、设备驱动和 Qt/编译器运行库。
11
+ 开发基线为 SDK 提交 e222ccf48f29c530fd09617df6e3a83a8095d25f,包含命令 ACK 匹配、协作退出及 Qt 串口已缓冲数据读取修复。
12
+ 旧版 SDK 仅凭导出函数相同不能认定兼容;应使用供应方提供并经过本插件验收的修复版本。
13
+ 原生 SDK、固件和第三方库不包含在 PyPI 包内,也不适用本插件的 MIT 许可证。
14
+
15
+ RTR 接收还需要设备固件保留远程帧 DLC 的修复。已发现的原 X5 0x0332、X6 0x0127
16
+ 会把收到的 RTR DLC 清零;插件不能从接收记录中恢复丢失的值。两台测试设备已刷入
17
+ 修复构建,并通过 USB/WLAN 双向 RTR DLC 0–8 检查。诊断构建仍报告原版本号,
18
+ 因此只比较 SWVer 不足以判断已修复;应向设备供应方确认具体固件构建包含该修复。
19
+
20
+
21
+ ```text
22
+ python -m pip install python-can-canpal==0.1.0rc1
23
+ ```
24
+
25
+ 实际支持范围与未验收项见包首页 README。开发时从源码执行:
26
+
27
+ ```text
28
+ python -m pip install -e ".[test]"
29
+ ```
30
+
31
+ Windows 设置 CANPAL_LIBRARY 为 DLL 绝对路径;若依赖不在 DLL 同目录,
32
+ 可设置 CANPAL_DEPENDENCY_DIRS,多个目录使用分号分隔。
33
+ Linux 设置 CANPAL_LIBRARY 为 SO 绝对路径,通过系统链接器安装 Qt 依赖。
34
+ 也可在 can.Bus 中传 library_path。导入模块不会加载库或打开设备。
35
+
36
+ Linux 串口为 /dev/ttyACM数字;通过 udev 或 dialout 组授予当前用户权限,正常运行不要求 root。
37
+ 首版不接受任意 ttyUSB 路径。WSL 下调用 Windows python.exe 仍属于 Windows 测试。
38
+
39
+ ## USB 与 UID
40
+
41
+ ```python
42
+ import can
43
+
44
+ bus = can.Bus(interface="canpal", ignore_config=True,
45
+ transport="usb", device_id="0123456789abcdef01234567",
46
+ channel=0, bitrate=500_000)
47
+ try:
48
+ print(bus.recv(1))
49
+ finally:
50
+ bus.shutdown()
51
+ ```
52
+
53
+ UID 为实际设备的 12 字节标识,用 24 位十六进制表示。不要把 USB 描述符序列号直接当 UID。
54
+ 插件先枚举 CANPAL VID/PID 端口,再读取原生设备身份;不会发送网络广播。
55
+ 端口改号后关闭并重新创建 Bus,仍使用相同 UID。
56
+
57
+ 可以同时提供 usb_port="COM9" 作为优先探测提示。
58
+ 有 UID 时旧端口不匹配会继续寻找其他候选;仅传 usb_port 则不保证拔插后的设备身份。
59
+ COM1000 等完整编号会被解析,不截取末三位。
60
+
61
+ ## WLAN
62
+
63
+ ```python
64
+ bus = can.Bus(interface="canpal", ignore_config=True,
65
+ transport="wlan", host="192.0.2.10", port=8000,
66
+ channel=0, bitrate=500_000,
67
+ device_id="0123456789abcdef01234567")
68
+ ```
69
+
70
+ IP 与端口必须来自设备实际配置。只支持 PC 为 TCP 客户端、设备为 TCP 服务端的模式。
71
+ 可不传 device_id,但仍会读取真实 UID 并检查本进程是否已经占用。
72
+ IP 直连完全不依赖 UDP/广播发现;无自动重连、监听或 UDP 回退。
73
+
74
+ ## 多设备和 CAN FD
75
+
76
+ 为每台不同 UID 设备创建一个 Bus。每台只能选择一个 CAN 通道。
77
+ 不同设备可使用相同 channel=0 和相同 CAN ID;应用应在监听器或日志中带上来源 UID。
78
+ 每台使用独立 Notifier,避免应用自己把多台设备合并成共享故障处理单元。
79
+
80
+ X6 开启 FD 时提供 fd=True、data_bitrate=2_000_000。
81
+ fd=False 时仍使用 X6 的原生 FD API承载经典帧。
82
+ 完整位时序取自 SDK 的 80/100 MHz 表,由型号和固件版本选择;不支持的速率明确拒绝。
83
+ FD 的非标准物理长度(如 9 字节)会补零到下一合法长度(12),不修改调用者的 Message。
84
+
85
+ 同一个 Bus 只使用一个接收者:直接 recv 或一个 Notifier。
86
+ 需要多个消费者时在该 Notifier 上增加监听器,不要并发调用 recv。
87
+
88
+ ## 行为与排错
89
+
90
+ - send 成功表示原生队列接受,不表示线缆 ACK 或对端收到;Python 不自动重发。
91
+ - send 的 timeout 只约束进入原生调用之前的本地等待;不能取消在途 DLL 调用。
92
+ - recv(None) 等待至收到消息、关闭或离线;软件过滤使用 python-can 基类。
93
+ - 收发时的故障转换为标准 python-can 异常。关闭应用前先停止 Notifier。
94
+ - 设备配置会临时关闭硬件过滤、自动发送、桥接和 spy 功能;不调用保存设置,不写非易失存储。
95
+ - termination=None 保留读取到的终端电阻;True/False 显式设置,listen_only=True 禁止发送。
96
+ - 周期发送使用上游实现,停止后睡眠线程可能在当前周期结束时才退出;关闭后不会再次进入原生发送。
97
+ - 时间戳是每设备初始化采样后的相对换算,含连接采样误差,不提供多设备精确时间同步。
98
+ - 配置失败后关闭本次连接;不在不确定的命令流上继续发送配置或回滚命令。
99
+ - “找不到库”检查绝对路径、64 位架构和 Qt/运行库依赖;禁止用 fake 库处理真实设备。
100
+ - “未找到或不可访问”检查 UID、串口权限、其他进程占用和实际 USB 型号。
101
+ - 本进程 UID 去重不能协调其他应用;同一物理设备应由一个应用独占。
@@ -0,0 +1,39 @@
1
+ # PyPI 发布流程
2
+
3
+ 用户已指定公开 PyPI,包名 python-can-canpal,新插件代码使用 MIT。
4
+ 原生 SDK、头文件、固件与 Qt 不纳入 Python 发布包;其许可独立。
5
+
6
+ 用户已批准先公开发布明确限界的 0.1.0rc1 预览版。版本号本身不代表上传成功或正式验收完成。
7
+ 正式 0.1.0 必须完成既定双平台硬件矩阵;没有硬件证据时停止在候选产物阶段。
8
+
9
+ 1. 完成 Windows/Python 3.12 和 Linux/Python 3.12 无硬件回归、三方 ABI、真实 SDK 回归。
10
+ 2. 在 Windows 11 x64 与原生 Ubuntu 24.04 x86_64 运行完整硬件 release profile。
11
+ 3. 核对 26 组验收、资源计数、实际设备能力、失败记录及支持矩阵。未运行、skip、xfail 均未完成。
12
+ 4. 冻结最终代码、SDK 构建哈希、包版本;清洁环境从 sdist 重建 wheel 并安装验证。
13
+ 5. 运行 python -m twine check dist/*,审计包内文件,不包含 fake 二进制、私有端点、凭据或 SDK。
14
+ 6. 用 tools/release_check.py 检查最终产物和全部证据。任何缺项返回非零。
15
+ 7. 通过已有 PyPI 账号的安全凭据或可信发布完成上传;不要把 token 写进源码、日志或聊天。
16
+ 8. 从 PyPI JSON 核对版本和 SHA256,并在干净环境从 PyPI 安装该精确版本验证入口和依赖。
17
+
18
+ 发布授权已经给出,不再额外要求一次重复批准。凭据或硬件信息缺失时需要用户补充;
19
+ 未通过验收不能将候选包上传后称为正式完成。
20
+ 新建公网仓库、上传原生 SDK 或更改 SDK 许可不包含在此发布动作中。
21
+
22
+ 环境及原始报告位于本地 artifacts,默认不随 PyPI 包发布。内部 release-evidence.json
23
+ 必须列出最终产物 SHA256、四份无跳过的测试报告(两平台 unit/hardware)、两平台 SDK 回归,
24
+ 以及 26 项验收的证据文件。模板允许完整保留 pending 状态。
25
+
26
+ ## 0.1.0rc1 预览版例外
27
+
28
+ 本次例外来自用户明确选择“先发布明确限界的预览版 0.1.0rc1”。
29
+ 只允许该预发布版本:README/PyPI 必须列出已验证平台/组合、未验收项目及修复版 SDK/固件要求。
30
+ 两平台软件、ABI、真实 SDK 回归和干净安装必须通过;两台刷写后的 12 组补充实机报告必须通过且哈希匹配。
31
+ 包内禁止分发原生库、固件、凭据和内部验收资料。正式版检查器默认行为保持完整门槛。
32
+
33
+ ```text
34
+ python tools/release_check.py --profile preview --evidence artifacts/release-evidence.json --dist dist
35
+ ```
36
+
37
+ 预览版检查只对本次明确授权的 0.1.0rc1 生效;不将未运行的正式硬件测试标为通过。
38
+ 上传后须核对 PyPI 的两份分发文件哈希,并从公共索引在干净环境安装精确版本。
39
+ 正式版继续使用默认 release profile,仍需完整双平台矩阵、独立参考设备和额定/长时间验收。
@@ -0,0 +1,82 @@
1
+ # 自动化测试与真实验收
2
+
3
+ ## 本地回归
4
+
5
+ 从独立安装的、记录提交版本的 CANPALAPI 源码构建 fake:
6
+
7
+ ```text
8
+ python tools/build_fake.py --sdk /path/to/CANPALAPI --compiler /path/to/g++
9
+ python -m pip install -e ".[test]"
10
+ python -m pytest -m "not hardware" --junitxml=artifacts/unit.xml
11
+ ```
12
+
13
+ Windows 的 MinGW 线程运行库目录应加入 CANPAL_DEPENDENCY_DIRS。
14
+ 同一份 C++ fake 导出真实 ABI,模拟四个设备、独立句柄/队列、断线、调用失败及发送阻塞。
15
+ fake 不进入生产 wheel;测试只通过显式路径加载它。
16
+
17
+ 构建脚本生成 abi.json 与 abi-internal.json,分别来自公开头文件与 SDK 实际内部定义;
18
+ 两者必须一致,pytest 再与 ctypes 的所有字段逐一对照。
19
+ SDK 源码不复制进本项目,运行环境和头文件哈希进入内部验收记录。
20
+
21
+ ## 真实 SDK 回归
22
+
23
+ 执行以下命令可复现构建并保存原生 JUnit、二进制哈希及构建日志:
24
+
25
+ ```text
26
+ python tools/build_sdk.py --sdk /path/to/CANPALAPI --qmake /path/to/qmake --make /path/to/make
27
+ ```
28
+
29
+ Windows 使用 MinGW 配套 qmake/mingw32-make,Linux 使用系统 Qt 5 工具。
30
+ 两个构建脚本强制核对 native/sdk-revision.txt 中的 SDK 提交,并拒绝修改过的 SDK 源文件。
31
+ CI 在两平台依次运行上述构建、pytest 和包构建命令,SDK 源码须由其授权渠道提供。
32
+ 再次运行会将旧报告保存到 artifacts/history,保留失败与重跑记录。
33
+
34
+ native/fake/sdk_smoke.pro 是真实库回归工程,名称所在目录不表示它链接 fake。
35
+ qmake 参数 SDK_ROOT 指向 SDK 源码,SDK_BUILD 指向真实 DLL/SO 和导入库目录。
36
+ 测试包括命令/通道/条目不匹配的 ACK,以及两台设备对象同时存在时 200 次线程协作回收。
37
+ Linux 还使用真实 Qt 串口与伪终端:先缓冲 128 字节,再停止输入,分批读完,防止等待新数据时遗漏已缓冲数据。
38
+ 这项测试已验证修复前失败、修复后通过。以上回归不打开真实 CAN 设备,不能替代连接故障和硬件验收。
39
+
40
+ ## 硬件配置
41
+
42
+ 复制 hardware-lab.example.json 到不纳入版本管理的内部位置,并填写:
43
+
44
+ - 独立且已隔离的测试总线确认、真实 SDK 路径、至少两台设备 UID 和网络端点;
45
+ - 每台的独立参考 CAN 接口,不能用 canpal 或 virtual 作为参考;
46
+ - 两条隔离物理 CAN 网络及各自终端电阻/位时序;
47
+ - 控制 USB 断开/连接/改号与网络断开的命令数组;
48
+ - 阻断和恢复发现广播的命令数组。
49
+
50
+ 控制脚本由测试台提供,pytest 通过 subprocess 参数数组调用,不使用 shell 展开。
51
+ 命令须同步完成对应动作后返回;不支持的控制不能填写一个空操作来冒充。
52
+ 系统实际 COM/ttyACM 编号必须发生变化,否则不计入改号次数。
53
+
54
+ ```text
55
+ python -m pytest -m hardware --hardware-config /path/to/lab.json --junitxml=artifacts/hardware.xml
56
+ ```
57
+
58
+ profile=smoke 用于短联调,不能放行正式版;profile=release 启用每组合 30 分钟和混合连接两小时运行,
59
+ 以及 10 次改号、每拓扑 5 次断线、USB/TCP 各 20 次重新打开。
60
+ 实际平均发送率至少达到预设 500 fps 的 98%,不在失败后降低目标。
61
+ 硬件套件会验证数据序号、载荷、丢失/重复、跨设备隔离、监听和关闭清理。
62
+ 线缆矩阵逐向校验标准/扩展 ID 的零值和最大值、经典 CAN 的 0–8 字节及 RTR DLC、
63
+ FD 的 0–64 字节和 BRS 开关,并检查完整标志位、DLC、补零和原消息不变。
64
+ 参考接口发送 FD 时使用合法线缆长度;任意长度补零由 CANPAL 发送路径负责验证。
65
+ RTR 没有数据不代表 DLC 必须为零,接收 DLC 与请求不符必须判失败。
66
+ 没有配置时硬件用例明确 skip,完整验收仍未完成。
67
+
68
+ Windows 与原生 Linux 分别执行;WSL 的 release profile 被拒绝。
69
+ 还须保存原生句柄/线程/端口资源计数、广播阻断抓包、型号/固件矩阵及实际重试/时间戳行为记录。
70
+ 这些设备和系统级证据不能由 fake 或 Python 对象数量替代。
71
+
72
+ ## 与方案对应
73
+
74
+ 测试名称中的 T 编号对应 PLAN.zh-CN.md 的 26 组要求。
75
+ 一个用例可能覆盖多组要求,同一组也可能需要 fake、真实库和硬件三种证据。
76
+ 自动化套件成功不是整个需求组自动关闭:尚未实测的型号、固件行为、
77
+ 故障清理和资源观察必须留在发布验收记录中,不允许补填“通过”。
78
+
79
+ 发布前从最终 sdist 构建 wheel,并在干净环境安装、检查 can.interface 入口、运行消息与 fake 回归。
80
+ 最终 SDK 二进制、wheel、sdist、代码提交及报告必须匹配。
81
+
82
+ 0.1.0rc1 经用户明确授权采用独立预览版门槛,见 RELEASE.md。预览版补充检查不关闭上述正式硬件验收缺项。
@@ -0,0 +1,47 @@
1
+ {
2
+ "isolated_test_bench": false,
3
+ "profile": "smoke",
4
+ "library_path": "/absolute/path/to/real/CANPAL/library",
5
+ "devices": [
6
+ {
7
+ "device_id": "REPLACE_WITH_24_HEX_UID",
8
+ "channel": 0,
9
+ "fd": false,
10
+ "host": "192.0.2.1",
11
+ "port": 8001,
12
+ "reference": {
13
+ "interface": "pcan",
14
+ "channel": "PCAN_USBBUS1"
15
+ },
16
+ "controls": {
17
+ "disconnect_usb": [],
18
+ "connect_usb": [],
19
+ "remap_usb": [],
20
+ "disconnect_wlan": [],
21
+ "connect_wlan": []
22
+ }
23
+ },
24
+ {
25
+ "device_id": "REPLACE_WITH_24_HEX_UID",
26
+ "channel": 0,
27
+ "fd": true,
28
+ "host": "192.0.2.2",
29
+ "port": 8002,
30
+ "reference": {
31
+ "interface": "pcan",
32
+ "channel": "PCAN_USBBUS2"
33
+ },
34
+ "controls": {
35
+ "disconnect_usb": [],
36
+ "connect_usb": [],
37
+ "remap_usb": [],
38
+ "disconnect_wlan": [],
39
+ "connect_wlan": []
40
+ }
41
+ }
42
+ ],
43
+ "controls": {
44
+ "block_discovery": [],
45
+ "restore_discovery": []
46
+ }
47
+ }
@@ -0,0 +1,113 @@
1
+ #include <cstdint>
2
+ #include <list>
3
+ #include "canpal_driver2.h"
4
+ using YXConnection = YXConection;
5
+ using YXCANMsgTxControl = decltype(YXCANMsg::tx);
6
+ #include <cstddef>
7
+ #include <iostream>
8
+ int main() {
9
+ std::cout << "{";
10
+ std::cout << "\"YXConnection\":{\"size\":" << sizeof(YXConnection) << ",\"offsets\":{";
11
+ std::cout << "\"CommType\":" << offsetof(YXConnection, type);
12
+ std::cout << ",\"DestHost\":" << offsetof(YXConnection, dest_host);
13
+ std::cout << ",\"DestPort\":" << offsetof(YXConnection, dest_port);
14
+ std::cout << ",\"LocalHost\":" << offsetof(YXConnection, local_host);
15
+ std::cout << ",\"LocalPort\":" << offsetof(YXConnection, local_port);
16
+ std::cout << "}}";
17
+ std::cout << ",";
18
+ std::cout << "\"YXDeviceInfo\":{\"size\":" << sizeof(YXDeviceInfo) << ",\"offsets\":{";
19
+ std::cout << "\"DeviceName\":" << offsetof(YXDeviceInfo, name);
20
+ std::cout << ",\"DeviceModel\":" << offsetof(YXDeviceInfo, type);
21
+ std::cout << ",\"HWVer\":" << offsetof(YXDeviceInfo, hw_ver);
22
+ std::cout << ",\"SWVer\":" << offsetof(YXDeviceInfo, sw_ver);
23
+ std::cout << ",\"UniqueID\":" << offsetof(YXDeviceInfo, unique_id);
24
+ std::cout << "}}";
25
+ std::cout << ",";
26
+ std::cout << "\"YXCapacity\":{\"size\":" << sizeof(YXCapacity) << ",\"offsets\":{";
27
+ std::cout << "\"CAN_Classic_supported\":" << offsetof(YXCapacity, CAN_Classic_supported);
28
+ std::cout << ",\"CAN_FD_supported\":" << offsetof(YXCapacity, CAN_FD_supported);
29
+ std::cout << ",\"LIN_supported\":" << offsetof(YXCapacity, LIN_supported);
30
+ std::cout << ",\"KLine_supported\":" << offsetof(YXCapacity, KLIne_supported);
31
+ std::cout << ",\"ISO_TP_supported\":" << offsetof(YXCapacity, ISO_TP_supported);
32
+ std::cout << ",\"Wifi_supported\":" << offsetof(YXCapacity, Wifi_supported);
33
+ std::cout << ",\"Ethernet_supported\":" << offsetof(YXCapacity, Ethernet_supported);
34
+ std::cout << ",\"USB_supported\":" << offsetof(YXCapacity, USB_supported);
35
+ std::cout << ",\"CAN_channel_num\":" << offsetof(YXCapacity, CAN_channel_num);
36
+ std::cout << ",\"LIN_channel_num\":" << offsetof(YXCapacity, LIN_channel_num);
37
+ std::cout << ",\"KLine_channel_num\":" << offsetof(YXCapacity, KLine_channel_num);
38
+ std::cout << "}}";
39
+ std::cout << ",";
40
+ std::cout << "\"YXParameter\":{\"size\":" << sizeof(YXParameter) << ",\"offsets\":{";
41
+ std::cout << "\"ID\":" << offsetof(YXParameter, id);
42
+ std::cout << ",\"Data\":" << offsetof(YXParameter, data);
43
+ std::cout << ",\"Len\":" << offsetof(YXParameter, len);
44
+ std::cout << "}}";
45
+ std::cout << ",";
46
+ std::cout << "\"YXCANMsgTxControl\":{\"size\":" << sizeof(YXCANMsgTxControl) << ",\"offsets\":{";
47
+ std::cout << "\"TxHandle\":" << offsetof(YXCANMsgTxControl, handle);
48
+ std::cout << ",\"MaxTry\":" << offsetof(YXCANMsgTxControl, max_try);
49
+ std::cout << ",\"MinGap\":" << offsetof(YXCANMsgTxControl, min_gap);
50
+ std::cout << ",\"Result\":" << offsetof(YXCANMsgTxControl, result);
51
+ std::cout << "}}";
52
+ std::cout << ",";
53
+ std::cout << "\"YXCANMsg\":{\"size\":" << sizeof(YXCANMsg) << ",\"offsets\":{";
54
+ std::cout << "\"MsgType\":" << offsetof(YXCANMsg, type);
55
+ std::cout << ",\"MsgFlag\":" << offsetof(YXCANMsg, flag);
56
+ std::cout << ",\"Bus\":" << offsetof(YXCANMsg, bus);
57
+ std::cout << ",\"ID\":" << offsetof(YXCANMsg, id);
58
+ std::cout << ",\"DLC\":" << offsetof(YXCANMsg, dlc);
59
+ std::cout << ",\"Data\":" << offsetof(YXCANMsg, data);
60
+ std::cout << ",\"Timestamp\":" << offsetof(YXCANMsg, timestamp);
61
+ std::cout << ",\"TXCtrl\":" << offsetof(YXCANMsg, tx);
62
+ std::cout << "}}";
63
+ std::cout << ",";
64
+ std::cout << "\"YXCANFDMsg\":{\"size\":" << sizeof(YXCANFDMsg) << ",\"offsets\":{";
65
+ std::cout << "\"MsgType\":" << offsetof(YXCANFDMsg, type);
66
+ std::cout << ",\"MsgFlag\":" << offsetof(YXCANFDMsg, flag);
67
+ std::cout << ",\"Bus\":" << offsetof(YXCANFDMsg, bus);
68
+ std::cout << ",\"ID\":" << offsetof(YXCANFDMsg, id);
69
+ std::cout << ",\"DLC\":" << offsetof(YXCANFDMsg, dlc);
70
+ std::cout << ",\"Timestamp\":" << offsetof(YXCANFDMsg, timestamp);
71
+ std::cout << ",\"TXCtrl\":" << offsetof(YXCANFDMsg, tx);
72
+ std::cout << ",\"Data\":" << offsetof(YXCANFDMsg, data);
73
+ std::cout << "}}";
74
+ std::cout << ",";
75
+ std::cout << "\"YXCANController\":{\"size\":" << sizeof(YXCANController) << ",\"offsets\":{";
76
+ std::cout << "\"CANMode\":" << offsetof(YXCANController, mode);
77
+ std::cout << ",\"Baudrate\":" << offsetof(YXCANController, baud_rate);
78
+ std::cout << ",\"SJW\":" << offsetof(YXCANController, SJW);
79
+ std::cout << ",\"BS1\":" << offsetof(YXCANController, BS1);
80
+ std::cout << ",\"BS2\":" << offsetof(YXCANController, BS2);
81
+ std::cout << ",\"Option\":" << offsetof(YXCANController, option);
82
+ std::cout << "}}";
83
+ std::cout << ",";
84
+ std::cout << "\"YXCANFDController\":{\"size\":" << sizeof(YXCANFDController) << ",\"offsets\":{";
85
+ std::cout << "\"CANMode\":" << offsetof(YXCANFDController, mode);
86
+ std::cout << ",\"NomBaudrate\":" << offsetof(YXCANFDController, nom_baud_rate);
87
+ std::cout << ",\"NomSJW\":" << offsetof(YXCANFDController, nom_SJW);
88
+ std::cout << ",\"NomBS1\":" << offsetof(YXCANFDController, nom_BS1);
89
+ std::cout << ",\"NomBS2\":" << offsetof(YXCANFDController, nom_BS2);
90
+ std::cout << ",\"DataBaudrate\":" << offsetof(YXCANFDController, data_baud_rate);
91
+ std::cout << ",\"DataSJW\":" << offsetof(YXCANFDController, data_SJW);
92
+ std::cout << ",\"DataBS1\":" << offsetof(YXCANFDController, data_BS1);
93
+ std::cout << ",\"DataBS2\":" << offsetof(YXCANFDController, data_BS2);
94
+ std::cout << ",\"FrameMode\":" << offsetof(YXCANFDController, frame_mode);
95
+ std::cout << ",\"ResistorEnable\":" << offsetof(YXCANFDController, resistor_enable);
96
+ std::cout << ",\"ISOModeEnable\":" << offsetof(YXCANFDController, ISO_mode_enable);
97
+ std::cout << ",\"CANTPEnable\":" << offsetof(YXCANFDController, CAN_TP_enable);
98
+ std::cout << "}}";
99
+ std::cout << ",";
100
+ std::cout << "\"YXCANInitHW\":{\"size\":" << sizeof(YXCANInitHW) << ",\"offsets\":{";
101
+ std::cout << "\"Inited\":" << offsetof(YXCANInitHW, inited);
102
+ std::cout << ",\"Timestamp\":" << offsetof(YXCANInitHW, timestamp);
103
+ std::cout << "}}";
104
+ std::cout << ",";
105
+ std::cout << "\"YXCANFilter\":{\"size\":" << sizeof(YXCANFilter) << ",\"offsets\":{";
106
+ std::cout << "\"Index\":" << offsetof(YXCANFilter, index);
107
+ std::cout << ",\"Active\":" << offsetof(YXCANFilter, active);
108
+ std::cout << ",\"FilterType\":" << offsetof(YXCANFilter, type);
109
+ std::cout << ",\"ID\":" << offsetof(YXCANFilter, id);
110
+ std::cout << ",\"Mask\":" << offsetof(YXCANFilter, mask);
111
+ std::cout << "}}";
112
+ std::cout << "}\n";
113
+ }