Metadata-Version: 2.4
Name: dmcan_sdk
Version: 2.0.1
Summary: DM USB CAN/CAN FD 设备 SDK 的 Python 封装（ctypes 绑定 libdmcan）
License-Expression: Apache-2.0
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# dmcan —— DM USB CAN/CAN FD 设备 Python SDK

`libdmcan`（C SDK）的 **ctypes 封装**：设备枚举与打开、通道与位定时、CAN / CAN FD 收发、
周期与批量发送、真 echo、硬件过滤表、USB2UART 透传、热插拔与日志订阅。

## 兼容性

| Python 包 | 对应的 libdmcan | 说明 |
| --- | --- | --- |
| **2.0.0** | **2.1.x** | 结构体按 2.1.0 逐字段对拍；`DmCanContext` 构造时校验"主版本相等 且 次版本 ≥ 2.1" |

不配套的库**在构造 `DmCanContext` 时就会报错**（缺符号会点名是哪个），不会静默读到错位数据。
2.0.1 → 2.1.0 的破坏性变化（固件信息接口合并、新增节点模式）见
仓库内 `docs/PY_2.0_MIGRATION.md`。

## 安装

要求 **Python 3.9+**：在 3.9.13 / 3.12.10 / 3.13.5 上都实跑过导入与离线 ABI 对拍（见 `tests/ABI_COMPAT_TEST_REPORT.md`）。暂未发布到 PyPI，本地安装 / 打包：

```bash
pip install .
python -m pip wheel . --no-deps -w dist     # 产出 dmcan_sdk-<版本>-py3-none-any.whl
```

wheel 是**纯 Python**的：后端动态库（`libdmcan`）**不在 PyPI 上**，需从对应的 SDK 交付包获取
（版本要和上表匹配），然后按下一节的方式让 SDK 找到它。

## 后端动态库（libdmcan）怎么找

按顺序找，命中第一个就用：

1. 环境变量 `DMCAN_LIBRARY_PATH` 指向的完整路径；
2. 包内 `dmcan/dlls/`（`libdmcan.dll` / `dmcan.dll` / `libdmcan.so` / `libdmcan.dylib`）；
3. 仓库根 `dlls/`；
4. 当前工作目录；
5. 交给系统搜索路径。

Windows 上 `libdmcan.dll` 还依赖 `libusb-1.0.dll`、`libstdc++-6.dll`、`libgcc_s_seh-1.dll`、
`libwinpthread-1.dll`，**要和它放在同一个目录**：SDK 会把该目录加进 DLL 搜索路径。

## 快速上手

```python
from dmcan import DmCanContext, dmcan_event_flag

with DmCanContext() as ctx:                  # 自动找库；不配套会在这里抛错
    if ctx.find_devices() == 0:
        raise SystemExit("没有找到设备")
    dev = ctx.get_device(0)
    print(dev.get_info())                    # 型号 / VID / PID / 序列号
    dev.open()                               # 注意：会独占设备的 UART COM 口（默认自动打开 UART）
    print(dev.get_version())                 # 例如 USB2CANFD_S V2.0.0 [efe47b5 2026-10-10 16:49:42]

    sub = dev.subscribe(dmcan_event_flag.RX_FRAME, lambda d, e: print(e.frame))
    dev.enable_channel(0, True)
    dev.send_can(0, 0x123, b"\x01\x02\x03\x04", canfd=True, brs=True)
    sub.cancel()
    dev.close()
```

几条容易踩的：

- **失败一律抛 `DmCanError`**（`exc.status` 是 `dmcan_status` 错误码），不再返回 `False`；
- `open()` 默认会**独占**设备的 UART COM 口，此时串口助手 / app 侧脚本打不开；
- 通道参数默认只改运行期，**掉电就回**：要保留得调 `save_param()`；
- `get_channel_stats()` 在固件首条周期上报（1 Hz）到达前返回 `None`，那是"还没有值"不是失败；
- 看门狗一旦使能，本固件上**无法用 API 关闭**（关狗命令被拒），会话结束后通道会被咬，
  重新 `enable_channel()`（或给设备断电）即可恢复。

## 目录结构

```
dmcan/          包本体：dmcan_def(ABI 类型) / dmcan_device(设备) / dmcan_context(上下文与库加载)
examples/       最小示例
tools/          abi_check.py(离线 ABI 对拍) / real_device_check.py(真机用例工具)
tests/          用例文档与真机报告（*_TEST.md / *_REPORT.md）
docs/           适配与迁移说明
```

## 许可证

本项目采用 **Apache License 2.0**：完整文本随发行包分发（wheel 里的 `dmcan_sdk-<版本>.dist-info/licenses/LICENSE`、sdist 根目录的 `LICENSE`）。

