Metadata-Version: 2.4
Name: dmcan_sdk
Version: 2.1.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` 的 **Python 封装**（纯 ctypes，无编译依赖）：设备枚举与打开、通道与位定时、CAN / CAN FD 收发、
周期与批量发送、真 echo、硬件过滤表、USB2UART 透传、热插拔与日志订阅。

## 兼容性

| Python 包 | 需要的 libdmcan |
| --- | --- |
| **2.1.x** | **2.1.0** |

- 构造 `DmCanContext` 时会校验后端库：主版本必须一致、次版本不低于 2.1；
  **不配套会立刻报错**（缺符号会点名是哪个），不会静默读到错位数据；
- **版本对齐规则**：包版本的 major.minor 跟随所绑定的 libdmcan（当前 `2.1`）；仅 Python 侧的修正抬
  PATCH 位（如 `2.1.0` → `2.1.1`），不改变所支持的 ABI。

## 安装

要求 **Python 3.9+**（3.9 / 3.12 / 3.13 均已实测）。

```bash
pip install dmcan_sdk
```

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 口，此时串口助手 / 别的程序打不开；
- 通道参数默认只改运行期，**掉电就回**：要保留得调 `save_param()`；
- `get_channel_stats()` 在固件首条周期上报（1 Hz）到达前返回 `None`，那是“还没有值”不是失败；
- 看门狗使能后由 SDK 按**超时的一半**周期喂狗；**老固件**上“关狗”命令可能被拒，
  此时会话结束后通道会被咬，重新 `enable_channel()`（或给设备断电）即可恢复；
- 通道开 / 关（`enable_channel` / `disable_channel`）要求固件回 **2 字节 `{操作结果, 状态}` 应答**：
  老固件回 1 字节时这两个接口会返回 false（库会打 ERROR 日志提示升级固件）。

## 从 1.x 升级到 2.x（接口不兼容）

| 旧写法（1.0.x） | 新写法（2.x） |
| --- | --- |
| 后端库 `libdm_device.dll` | **`libdmcan.dll`**（文件名变了） |
| `send_can(ch, id, dlen, payload, ...)` | `send_can(ch, id, payload, ...)`（长度由 payload 决定） |
| `usb_rx_frame` / `frame.head.dlc` | `dmcan_rx_frame` / `frame.dlen`（字节长度） |
| `device.print_version()` | `device.get_version()`（读固件信息并格式化） |
| `dmcan_channel_can_config` | `dmcan_ch_baud_conf` |
| 失败返回 `False` | 失败抛 `DmCanError` |

## 包内结构

```
dmcan/dmcan_def.py      ABI 类型与常量（结构体 / 枚举 / 回调原型）
dmcan/dmcan_device.py   设备级接口（通道、波特率、收发、订阅、诊断、UART）
dmcan/dmcan_context.py  上下文、后端库查找与加载、设备枚举、日志与热插拔
```

示例与其它语言的 SDK（C/C++、C#、Rust）见发布仓：<https://gitee.com/kit-miao/dm-device-sdk>

## 许可证

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