dmcan-sdk 1.0.1__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.
- dmcan_sdk-1.0.1/PKG-INFO +7 -0
- dmcan_sdk-1.0.1/dmcan/__init__.py +10 -0
- dmcan_sdk-1.0.1/dmcan/dmcan_context.py +145 -0
- dmcan_sdk-1.0.1/dmcan/dmcan_def.py +97 -0
- dmcan_sdk-1.0.1/dmcan/dmcan_device.py +173 -0
- dmcan_sdk-1.0.1/dmcan_sdk.egg-info/PKG-INFO +7 -0
- dmcan_sdk-1.0.1/dmcan_sdk.egg-info/SOURCES.txt +9 -0
- dmcan_sdk-1.0.1/dmcan_sdk.egg-info/dependency_links.txt +1 -0
- dmcan_sdk-1.0.1/dmcan_sdk.egg-info/top_level.txt +1 -0
- dmcan_sdk-1.0.1/setup.cfg +4 -0
- dmcan_sdk-1.0.1/setup.py +9 -0
dmcan_sdk-1.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
from ctypes import *
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Optional, List
|
|
6
|
+
from dmcan.dmcan_def import dmcan_device_handle, dmcan_context
|
|
7
|
+
from dmcan.dmcan_device import DmCanDevice
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def find_backend_dll_path():
|
|
11
|
+
"""
|
|
12
|
+
查找后端 DLL 路径
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
str: DLL 的绝对路径,找不到返回 None
|
|
16
|
+
"""
|
|
17
|
+
# 获取平台对应的文件名
|
|
18
|
+
if sys.platform == "win32":
|
|
19
|
+
dll_name = "libdm_device.dll"
|
|
20
|
+
elif sys.platform == "darwin":
|
|
21
|
+
dll_name = "libdm_device.dylib"
|
|
22
|
+
elif sys.platform == "linux":
|
|
23
|
+
dll_name = "libdm_device.so"
|
|
24
|
+
else:
|
|
25
|
+
return None
|
|
26
|
+
|
|
27
|
+
# 获取项目根目录(假设当前文件在 core/ 或项目根目录)
|
|
28
|
+
current_file = Path(__file__).resolve()
|
|
29
|
+
|
|
30
|
+
# 尝试多个可能的位置
|
|
31
|
+
possible_paths = [
|
|
32
|
+
# 相对于当前文件
|
|
33
|
+
current_file.parent / "dlls" / dll_name,
|
|
34
|
+
current_file.parent.parent / "dlls" / dll_name,
|
|
35
|
+
# 相对于当前工作目录
|
|
36
|
+
Path.cwd() / "dlls" / dll_name,
|
|
37
|
+
Path.cwd() / dll_name,
|
|
38
|
+
# 系统路径(直接使用文件名)
|
|
39
|
+
Path(dll_name),
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
# 返回第一个存在的路径
|
|
43
|
+
for path in possible_paths:
|
|
44
|
+
if path.exists():
|
|
45
|
+
return str(path)
|
|
46
|
+
|
|
47
|
+
# 找不到,返回默认相对路径(让调用者处理错误)
|
|
48
|
+
return f"./dlls/{dll_name}"
|
|
49
|
+
|
|
50
|
+
class DmCanContext:
|
|
51
|
+
|
|
52
|
+
def __init__(self):
|
|
53
|
+
|
|
54
|
+
self.dll_path=find_backend_dll_path()
|
|
55
|
+
self.dll=CDLL(self.dll_path,winmode=0)
|
|
56
|
+
self._ctx=POINTER(dmcan_context)()
|
|
57
|
+
self._devices:List[dmcan_device_handle]=[]
|
|
58
|
+
|
|
59
|
+
self._init_funcs()
|
|
60
|
+
|
|
61
|
+
self.dll.dmcan_context_create(self._ctx)
|
|
62
|
+
|
|
63
|
+
def _init_funcs(self):
|
|
64
|
+
|
|
65
|
+
self.dll.dmcan_context_create.argtypes=[POINTER(POINTER(dmcan_context))]
|
|
66
|
+
self.dll.dmcan_context_create.restype=None
|
|
67
|
+
|
|
68
|
+
self.dll.dmcan_context_destroy.argtypes=[POINTER(dmcan_context)]
|
|
69
|
+
self.dll.dmcan_context_destroy.restype=None
|
|
70
|
+
|
|
71
|
+
self.dll.dmcan_print_version.argtypes=[POINTER(dmcan_context)]
|
|
72
|
+
self.dll.dmcan_print_version.restype=None
|
|
73
|
+
|
|
74
|
+
self.dll.dmcan_find_devices.argtypes=[POINTER(dmcan_context)]
|
|
75
|
+
self.dll.dmcan_find_devices.restype=c_int
|
|
76
|
+
|
|
77
|
+
self.dll.dmcan_find_devices_with_type.argtypes=[POINTER(dmcan_context),c_int]
|
|
78
|
+
self.dll.dmcan_find_devices_with_type.restype=c_int
|
|
79
|
+
|
|
80
|
+
self.dll.dmcan_show_all_devices.argtypes=[POINTER(dmcan_context)]
|
|
81
|
+
self.dll.dmcan_show_all_devices.restype=None
|
|
82
|
+
|
|
83
|
+
self.dll.dmcan_device_get.argtypes=[POINTER(dmcan_context),POINTER(POINTER(dmcan_device_handle)),c_int]
|
|
84
|
+
self.dll.dmcan_device_get.restype=c_bool
|
|
85
|
+
|
|
86
|
+
def destroy(self):
|
|
87
|
+
if self._ctx:
|
|
88
|
+
self.dll.dmcan_context_destroy(self._ctx)
|
|
89
|
+
self._ctx=None
|
|
90
|
+
|
|
91
|
+
def print_version(self):
|
|
92
|
+
if self._ctx:
|
|
93
|
+
self.dll.dmcan_print_version(self._ctx)
|
|
94
|
+
|
|
95
|
+
def find_devices(self, dmcan_device_type:Optional[int]=None) -> int:
|
|
96
|
+
if self._ctx is None:
|
|
97
|
+
return 0
|
|
98
|
+
|
|
99
|
+
device_cnt=0
|
|
100
|
+
if dmcan_device_type is None:
|
|
101
|
+
device_cnt= self.dll.dmcan_find_devices(self._ctx)
|
|
102
|
+
else:
|
|
103
|
+
device_cnt= self.dll.dmcan_find_devices_with_type(self._ctx, dmcan_device_type)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
self._devices.clear()
|
|
108
|
+
|
|
109
|
+
for i in range(device_cnt):
|
|
110
|
+
|
|
111
|
+
dev_handle = POINTER(dmcan_device_handle)()
|
|
112
|
+
|
|
113
|
+
ret=self.dll.dmcan_device_get(self._ctx,byref(dev_handle),i)
|
|
114
|
+
|
|
115
|
+
if not ret:
|
|
116
|
+
continue
|
|
117
|
+
|
|
118
|
+
if not dev_handle:
|
|
119
|
+
continue
|
|
120
|
+
device=DmCanDevice(self.dll,dev_handle,i)
|
|
121
|
+
self._devices.append(device)
|
|
122
|
+
|
|
123
|
+
return device_cnt
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def show_all_devices(self):
|
|
127
|
+
if self._ctx is None:
|
|
128
|
+
return
|
|
129
|
+
self.dll.dmcan_show_all_devices(self._ctx)
|
|
130
|
+
|
|
131
|
+
def get_device(self,index:int)->DmCanDevice:
|
|
132
|
+
return self._devices[index]
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def __enter__(self):
|
|
136
|
+
return self
|
|
137
|
+
|
|
138
|
+
def __exit__(self,exc_type,exc_value,traceback):
|
|
139
|
+
for device in self._devices:
|
|
140
|
+
device.close()
|
|
141
|
+
self.destroy()
|
|
142
|
+
|
|
143
|
+
def __del__(self):
|
|
144
|
+
if hasattr(self, '_ctx'):
|
|
145
|
+
self.destroy()
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import ctypes
|
|
2
|
+
from ctypes import *
|
|
3
|
+
from enum import Enum, IntEnum
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class dmcan_device_type(IntEnum):
|
|
8
|
+
USB2CANFD = 0
|
|
9
|
+
USB2CANFD_DUAL=1
|
|
10
|
+
LinkX4C=2
|
|
11
|
+
|
|
12
|
+
class dmcan_channel_can_info(Structure):
|
|
13
|
+
_pack_ = 1
|
|
14
|
+
_fields_ = [
|
|
15
|
+
|
|
16
|
+
("channel",c_uint8),
|
|
17
|
+
("canfd",c_bool),
|
|
18
|
+
("can_baudrate",c_uint32),
|
|
19
|
+
("canfd_baudrate",c_uint32),
|
|
20
|
+
("can_sp",c_float),
|
|
21
|
+
("canfd_sp",c_float)
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
class dmcan_channel_can_config(Structure):
|
|
25
|
+
_pack_ = 1
|
|
26
|
+
_fields_ = [
|
|
27
|
+
("channel",c_uint8),
|
|
28
|
+
("can_fd",c_uint8),
|
|
29
|
+
("can_seg1",c_uint8),
|
|
30
|
+
("can_seg2", c_uint8),
|
|
31
|
+
("can_sjw", c_uint8),
|
|
32
|
+
("can_prescaler", c_uint8),
|
|
33
|
+
("canfd_seg1", c_uint8),
|
|
34
|
+
("canfd_seg2", c_uint8),
|
|
35
|
+
("canfd_sjw", c_uint8),
|
|
36
|
+
("canfd_prescaler", c_uint8)
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
class usb_rx_frame_head(Structure):
|
|
40
|
+
_pack_ = 1
|
|
41
|
+
_fields_ = [
|
|
42
|
+
|
|
43
|
+
("can_id",c_uint32,29),
|
|
44
|
+
("esi",c_uint32,1),
|
|
45
|
+
("ext", c_uint32, 1),
|
|
46
|
+
("rtr", c_uint32, 1),
|
|
47
|
+
("timestamp", c_uint64),
|
|
48
|
+
("channel",c_uint8),
|
|
49
|
+
("canfd",c_uint8,1),
|
|
50
|
+
("dir",c_uint8,1),
|
|
51
|
+
("brs",c_uint8,1),
|
|
52
|
+
("ack",c_uint8,1),
|
|
53
|
+
("dlc",c_uint8,4),
|
|
54
|
+
("reserved",c_uint16),
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
def get_can_id_string(self) -> str:
|
|
58
|
+
"""获取格式化的 CAN ID 字符串"""
|
|
59
|
+
if self.ext:
|
|
60
|
+
return f"0x{self.can_id:08X}" # 扩展帧 29位
|
|
61
|
+
else:
|
|
62
|
+
return f"0x{self.can_id:03X}" # 标准帧 11位
|
|
63
|
+
def get_data_length(self):
|
|
64
|
+
"""根据 DLC 获取实际数据长度"""
|
|
65
|
+
dlc_map = {0:0, 1:1, 2:2, 3:3, 4:4, 5:5, 6:6, 7:7, 8:8,
|
|
66
|
+
9:12, 10:16, 11:20, 12:24, 13:32, 14:48, 15:64}
|
|
67
|
+
return dlc_map.get(self.dlc, 0)
|
|
68
|
+
|
|
69
|
+
class usb_rx_frame(Structure):
|
|
70
|
+
_pack_ = 1
|
|
71
|
+
_fields_ = [
|
|
72
|
+
("head",usb_rx_frame_head),
|
|
73
|
+
("payload",c_uint8*64),
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
def get_payload_bytes(self, length=None):
|
|
77
|
+
"""获取 payload 数据字节"""
|
|
78
|
+
if length is None:
|
|
79
|
+
length = self.head.get_data_length()
|
|
80
|
+
length = min(length, 64)
|
|
81
|
+
return bytes(self.payload[:length])
|
|
82
|
+
|
|
83
|
+
def get_payload_hex(self, length=None):
|
|
84
|
+
"""获取十六进制格式的 payload"""
|
|
85
|
+
data = self.get_payload_bytes(length)
|
|
86
|
+
return ' '.join(f'{b:02X}' for b in data)
|
|
87
|
+
|
|
88
|
+
class dmcan_context(Structure):
|
|
89
|
+
pass
|
|
90
|
+
|
|
91
|
+
class dmcan_device_handle(Structure):
|
|
92
|
+
pass
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
DEV_RECV_CALLBACK=CFUNCTYPE(None,POINTER(dmcan_device_handle),POINTER(usb_rx_frame))
|
|
96
|
+
DEV_SENT_CALLBACK=CFUNCTYPE(None,POINTER(dmcan_device_handle),POINTER(usb_rx_frame))
|
|
97
|
+
DEV_ERR_CALLBACK=CFUNCTYPE(None,POINTER(dmcan_device_handle),POINTER(usb_rx_frame))
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import ctypes
|
|
2
|
+
from collections.abc import Callable
|
|
3
|
+
from ctypes import *
|
|
4
|
+
from typing import Optional, Any
|
|
5
|
+
|
|
6
|
+
from dmcan.dmcan_def import dmcan_device_handle, dmcan_channel_can_config, dmcan_channel_can_info, DEV_RECV_CALLBACK, \
|
|
7
|
+
DEV_SENT_CALLBACK, DEV_ERR_CALLBACK, usb_rx_frame
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class DmCanDevice:
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def __init__(self, dll: ctypes.CDLL,handle:POINTER(dmcan_device_handle),index:int):
|
|
14
|
+
self.index = index
|
|
15
|
+
self.dll = dll
|
|
16
|
+
self._handle = handle
|
|
17
|
+
self._recv_callback:Optional[Callable]=None
|
|
18
|
+
self._sent_callback:Optional[Callable]=None
|
|
19
|
+
self._error_callback:Optional[Callable]=None
|
|
20
|
+
|
|
21
|
+
self._recv_cb_wrapper:Optional[Callable]=None
|
|
22
|
+
self._sent_cb_wrapper:Optional[Callable]=None
|
|
23
|
+
self._error_cb_wrapper:Optional[Callable]=None
|
|
24
|
+
|
|
25
|
+
self._init_funcs()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _init_funcs(self):
|
|
29
|
+
|
|
30
|
+
self.dll.dmcan_device_open.argtypes=[POINTER(dmcan_device_handle)]
|
|
31
|
+
self.dll.dmcan_device_open.restype=c_bool
|
|
32
|
+
|
|
33
|
+
self.dll.dmcan_device_close.argtypes=[POINTER(dmcan_device_handle)]
|
|
34
|
+
self.dll.dmcan_device_close.restype=None
|
|
35
|
+
|
|
36
|
+
self.dll.dmcan_device_get_version.argtypes = [POINTER(dmcan_device_handle),c_char_p, c_size_t]
|
|
37
|
+
self.dll.dmcan_device_get_version.restype = None
|
|
38
|
+
|
|
39
|
+
self.dll.dmcan_device_print_version.argtypes=[POINTER(dmcan_device_handle)]
|
|
40
|
+
self.dll.dmcan_device_print_version.restype=None
|
|
41
|
+
|
|
42
|
+
self.dll.dmcan_device_enable_channel.argtypes=[POINTER(dmcan_device_handle),c_uint8]
|
|
43
|
+
self.dll.dmcan_device_enable_channel.restype=None
|
|
44
|
+
|
|
45
|
+
self.dll.dmcan_device_disable_channel.argtypes=[POINTER(dmcan_device_handle),c_uint8]
|
|
46
|
+
self.dll.dmcan_device_disable_channel.restype=None
|
|
47
|
+
|
|
48
|
+
self.dll.dmcan_device_get_channel_baudrate.argtypes=[POINTER(dmcan_device_handle),c_uint8,POINTER(dmcan_channel_can_info)]
|
|
49
|
+
self.dll.dmcan_device_get_channel_baudrate.restype=c_bool
|
|
50
|
+
|
|
51
|
+
self.dll.dmcan_device_get_channel_baudrate_details.argtypes = [POINTER(dmcan_device_handle), c_uint8,POINTER(dmcan_channel_can_config)]
|
|
52
|
+
self.dll.dmcan_device_get_channel_baudrate_details.restype = c_bool
|
|
53
|
+
|
|
54
|
+
self.dll.dmcan_device_set_channel_baudrate.argtypes=[POINTER(dmcan_device_handle),c_uint8,dmcan_channel_can_info]
|
|
55
|
+
self.dll.dmcan_device_set_channel_baudrate.restype=c_bool
|
|
56
|
+
|
|
57
|
+
self.dll.dmcan_device_set_channel_baudrate_details.argtypes = [POINTER(dmcan_device_handle), c_uint8,dmcan_channel_can_config]
|
|
58
|
+
self.dll.dmcan_device_set_channel_baudrate_details.restype = c_bool
|
|
59
|
+
|
|
60
|
+
self.dll.dmcan_device_hook_recv_callback.argtypes=[POINTER(dmcan_device_handle),DEV_RECV_CALLBACK]
|
|
61
|
+
self.dll.dmcan_device_hook_recv_callback.restype=None
|
|
62
|
+
|
|
63
|
+
self.dll.dmcan_device_hook_sent_callback.argtypes = [POINTER(dmcan_device_handle), DEV_SENT_CALLBACK]
|
|
64
|
+
self.dll.dmcan_device_hook_sent_callback.restype = None
|
|
65
|
+
|
|
66
|
+
self.dll.dmcan_device_hook_err_callback.argtypes = [POINTER(dmcan_device_handle), DEV_ERR_CALLBACK]
|
|
67
|
+
self.dll.dmcan_device_hook_err_callback.restype = None
|
|
68
|
+
|
|
69
|
+
self.dll.dmcan_device_send_can.argtypes=[POINTER(dmcan_device_handle),c_uint8,c_uint32,c_bool,c_bool,c_bool,c_bool,c_uint8,POINTER(c_uint8)]
|
|
70
|
+
self.dll.dmcan_device_send_can.restype=c_bool
|
|
71
|
+
|
|
72
|
+
self.dll.dmcan_device_send_can_details.argtypes=[POINTER(dmcan_device_handle),c_uint8,c_uint32,c_uint16,c_uint32,c_int,c_uint32,c_bool,c_bool,c_bool,c_bool,c_bool,c_bool,c_uint8,POINTER(c_uint8)]
|
|
73
|
+
self.dll.dmcan_device_send_can_details.restype=c_bool
|
|
74
|
+
|
|
75
|
+
def open(self)->bool:
|
|
76
|
+
if not self._handle:
|
|
77
|
+
return False
|
|
78
|
+
return self.dll.dmcan_device_open(self._handle)
|
|
79
|
+
|
|
80
|
+
def close(self):
|
|
81
|
+
if not self._handle:
|
|
82
|
+
return None
|
|
83
|
+
return self.dll.dmcan_device_close(self._handle)
|
|
84
|
+
|
|
85
|
+
def get_version(self)->str:
|
|
86
|
+
if not self._handle:
|
|
87
|
+
return None
|
|
88
|
+
buf=create_string_buffer(256)
|
|
89
|
+
self.dll.dmcan_device_get_version(self._handle,buf,len(buf))
|
|
90
|
+
return buf.value.decode('utf-8')
|
|
91
|
+
|
|
92
|
+
def print_version(self):
|
|
93
|
+
if not self._handle:
|
|
94
|
+
return None
|
|
95
|
+
return self.dll.dmcan_device_print_version(self._handle)
|
|
96
|
+
|
|
97
|
+
def enable_channel(self,channel:int,enable:bool)->bool:
|
|
98
|
+
if not self._handle:
|
|
99
|
+
return False
|
|
100
|
+
if enable:
|
|
101
|
+
return self.dll.dmcan_device_enable_channel(self._handle,channel)
|
|
102
|
+
else:
|
|
103
|
+
return self.dll.dmcan_device_disable_channel(self._handle,channel)
|
|
104
|
+
|
|
105
|
+
def get_channel_baudrate(self,channel:int)->dmcan_channel_can_info:
|
|
106
|
+
if not self._handle:
|
|
107
|
+
return None
|
|
108
|
+
|
|
109
|
+
info=dmcan_channel_can_info()
|
|
110
|
+
if self.dll.dmcan_device_get_channel_baudrate(self._handle,channel,info):
|
|
111
|
+
return info
|
|
112
|
+
else:
|
|
113
|
+
return None
|
|
114
|
+
|
|
115
|
+
def set_channel_baudrate(self,channel:int,info:dmcan_channel_can_info)->bool:
|
|
116
|
+
if not self._handle:
|
|
117
|
+
return False
|
|
118
|
+
if info:
|
|
119
|
+
return self.dll.dmcan_device_set_channel_baudrate(self._handle,channel,info)
|
|
120
|
+
else:
|
|
121
|
+
return False
|
|
122
|
+
|
|
123
|
+
def send_can(self,channel:int,can_id:int,dlen:int,payload:bytes,canfd:bool=False,ext:bool=False,rtr:bool=False,brs:bool=False)->bool:
|
|
124
|
+
if rtr:
|
|
125
|
+
dlen = 0
|
|
126
|
+
|
|
127
|
+
if len(payload) < dlen:
|
|
128
|
+
raise ValueError(f"Payload length {len(payload)} < {dlen}")
|
|
129
|
+
|
|
130
|
+
# 转换 payload 为 c_uint8 数组
|
|
131
|
+
data = (c_uint8 * dlen)(*payload[:dlen])
|
|
132
|
+
|
|
133
|
+
return self.dll.dmcan_device_send_can(
|
|
134
|
+
self._handle, channel, can_id, canfd, ext, rtr, brs, dlen, data)
|
|
135
|
+
|
|
136
|
+
def hook_recv_callback(self, callback: Callable[[Any, usb_rx_frame], None]):
|
|
137
|
+
|
|
138
|
+
self._recv_callback = callback
|
|
139
|
+
|
|
140
|
+
@DEV_RECV_CALLBACK
|
|
141
|
+
def wrapper(handle_ptr, frame_ptr):
|
|
142
|
+
if frame_ptr:
|
|
143
|
+
frame = frame_ptr.contents
|
|
144
|
+
self._recv_callback(self, frame)
|
|
145
|
+
|
|
146
|
+
self._recv_cb_wrapper = wrapper
|
|
147
|
+
self.dll.dmcan_device_hook_recv_callback(self._handle, wrapper)
|
|
148
|
+
|
|
149
|
+
def hook_sent_callback(self, callback: Callable[[Any, usb_rx_frame], None]):
|
|
150
|
+
|
|
151
|
+
self._sent_callback = callback
|
|
152
|
+
|
|
153
|
+
@DEV_SENT_CALLBACK
|
|
154
|
+
def wrapper(handle_ptr, frame_ptr):
|
|
155
|
+
if frame_ptr:
|
|
156
|
+
frame = frame_ptr.contents
|
|
157
|
+
self._sent_callback(self, frame)
|
|
158
|
+
|
|
159
|
+
self._sent_cb_wrapper = wrapper
|
|
160
|
+
self.dll.dmcan_device_hook_sent_callback(self._handle, wrapper)
|
|
161
|
+
|
|
162
|
+
def hook_err_callback(self, callback: Callable[[Any, usb_rx_frame], None]):
|
|
163
|
+
|
|
164
|
+
self._error_callback = callback
|
|
165
|
+
|
|
166
|
+
@DEV_ERR_CALLBACK
|
|
167
|
+
def wrapper(handle_ptr, frame_ptr):
|
|
168
|
+
if frame_ptr:
|
|
169
|
+
frame = frame_ptr.contents
|
|
170
|
+
self._error_callback(self, frame)
|
|
171
|
+
|
|
172
|
+
self._error_cb_wrapper = wrapper
|
|
173
|
+
self.dll.dmcan_device_hook_err_callback(self._handle, wrapper)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
dmcan
|