Skip to content

Commit af0d832

Browse files
authored
Merge pull request #1144 from felixn/etas
Add ETAS interface
2 parents cee2d78 + 5ff0f15 commit af0d832

File tree

7 files changed

+1078
-2
lines changed

7 files changed

+1078
-2
lines changed

CONTRIBUTORS.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,4 @@ Jan Goeteyn
2929
Lear Corporation
3030
Nick Black <[email protected]>
3131
Francisco Javier Burgos Macia
32+
Felix Nieuwenhuizen <[email protected]>

can/interfaces/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"gs_usb": ("can.interfaces.gs_usb", "GsUsbBus"),
2626
"nixnet": ("can.interfaces.nixnet", "NiXNETcanBus"),
2727
"neousys": ("can.interfaces.neousys", "NeousysBus"),
28+
"etas": ("can.interfaces.etas", "EtasBus"),
2829
"socketcand": ("can.interfaces.socketcand", "SocketCanDaemonBus"),
2930
}
3031

can/interfaces/etas/__init__.py

Lines changed: 316 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,316 @@
1+
import ctypes
2+
import time
3+
from typing import Dict, List, Optional, Tuple
4+
5+
import can
6+
from ...exceptions import CanInitializationError
7+
from .boa import *
8+
9+
10+
class EtasBus(can.BusABC):
11+
def __init__(
12+
self,
13+
channel: str,
14+
can_filters: Optional[can.typechecking.CanFilters] = None,
15+
receive_own_messages: bool = False,
16+
bitrate: int = 1000000,
17+
fd: bool = True,
18+
data_bitrate: int = 2000000,
19+
**kwargs: object,
20+
):
21+
self.receive_own_messages = receive_own_messages
22+
23+
nodeRange = CSI_NodeRange(CSI_NODE_MIN, CSI_NODE_MAX)
24+
self.tree = ctypes.POINTER(CSI_Tree)()
25+
CSI_CreateProtocolTree(ctypes.c_char_p(b""), nodeRange, ctypes.byref(self.tree))
26+
27+
oci_can_v = BOA_Version(1, 4, 0, 0)
28+
29+
self.ctrl = OCI_ControllerHandle()
30+
OCI_CreateCANControllerNoSearch(
31+
channel.encode(),
32+
ctypes.byref(oci_can_v),
33+
self.tree,
34+
ctypes.byref(self.ctrl),
35+
)
36+
37+
ctrlConf = OCI_CANConfiguration()
38+
ctrlConf.baudrate = bitrate
39+
ctrlConf.samplePoint = 80
40+
ctrlConf.samplesPerBit = OCI_CAN_THREE_SAMPLES_PER_BIT
41+
ctrlConf.BTL_Cycles = 10
42+
ctrlConf.SJW = 1
43+
ctrlConf.syncEdge = OCI_CAN_SINGLE_SYNC_EDGE
44+
ctrlConf.physicalMedia = OCI_CAN_MEDIA_HIGH_SPEED
45+
if receive_own_messages:
46+
ctrlConf.selfReceptionMode = OCI_SELF_RECEPTION_ON
47+
else:
48+
ctrlConf.selfReceptionMode = OCI_SELF_RECEPTION_OFF
49+
ctrlConf.busParticipationMode = OCI_BUSMODE_ACTIVE
50+
51+
if fd:
52+
ctrlConf.canFDEnabled = True
53+
ctrlConf.canFDConfig.dataBitRate = data_bitrate
54+
ctrlConf.canFDConfig.dataBTL_Cycles = 10
55+
ctrlConf.canFDConfig.dataSamplePoint = 80
56+
ctrlConf.canFDConfig.dataSJW = 1
57+
ctrlConf.canFDConfig.flags = 0
58+
ctrlConf.canFDConfig.canFdTxConfig = OCI_CANFDTX_USE_CAN_AND_CANFD_FRAMES
59+
ctrlConf.canFDConfig.canFdRxConfig.canRxMode = (
60+
OCI_CAN_RXMODE_CAN_FRAMES_USING_CAN_MESSAGE
61+
)
62+
ctrlConf.canFDConfig.canFdRxConfig.canFdRxMode = (
63+
OCI_CANFDRXMODE_CANFD_FRAMES_USING_CANFD_MESSAGE
64+
)
65+
66+
ctrlProp = OCI_CANControllerProperties()
67+
ctrlProp.mode = OCI_CONTROLLER_MODE_RUNNING
68+
69+
ec = OCI_OpenCANController(
70+
self.ctrl, ctypes.byref(ctrlConf), ctypes.byref(ctrlProp)
71+
)
72+
if ec != 0x0 and ec != 0x40004000: # accept BOA_WARN_PARAM_ADAPTED
73+
raise CanInitializationError(
74+
f"OCI_OpenCANController failed with error 0x{ec:X}"
75+
)
76+
77+
# RX
78+
79+
rxQConf = OCI_CANRxQueueConfiguration()
80+
rxQConf.onFrame.function = ctypes.cast(None, OCI_CANRxCallbackFunctionSingleMsg)
81+
rxQConf.onFrame.userData = None
82+
rxQConf.onEvent.function = ctypes.cast(None, OCI_CANRxCallbackFunctionSingleMsg)
83+
rxQConf.onEvent.userData = None
84+
if receive_own_messages:
85+
rxQConf.selfReceptionMode = OCI_SELF_RECEPTION_ON
86+
else:
87+
rxQConf.selfReceptionMode = OCI_SELF_RECEPTION_OFF
88+
self.rxQueue = OCI_QueueHandle()
89+
OCI_CreateCANRxQueue(
90+
self.ctrl, ctypes.byref(rxQConf), ctypes.byref(self.rxQueue)
91+
)
92+
93+
self._oci_filters = None
94+
self.filters = can_filters
95+
96+
# TX
97+
98+
txQConf = OCI_CANTxQueueConfiguration()
99+
txQConf.reserved = 0
100+
self.txQueue = OCI_QueueHandle()
101+
OCI_CreateCANTxQueue(
102+
self.ctrl, ctypes.byref(txQConf), ctypes.byref(self.txQueue)
103+
)
104+
105+
# Common
106+
107+
timerCapabilities = OCI_TimerCapabilities()
108+
OCI_GetTimerCapabilities(self.ctrl, ctypes.byref(timerCapabilities))
109+
self.tickFrequency = timerCapabilities.tickFrequency # clock ticks per second
110+
111+
# all timestamps are hardware timestamps relative to the CAN device powerup
112+
# calculate an offset to make them relative to epoch
113+
now = OCI_Time()
114+
OCI_GetTimerValue(self.ctrl, ctypes.byref(now))
115+
self.timeOffset = time.time() - (float(now.value) / self.tickFrequency)
116+
117+
self.channel_info = channel
118+
119+
def _recv_internal(
120+
self, timeout: Optional[float]
121+
) -> Tuple[Optional[can.Message], bool]:
122+
ociMsgs = (ctypes.POINTER(OCI_CANMessageEx) * 1)()
123+
ociMsg = OCI_CANMessageEx()
124+
ociMsgs[0] = ctypes.pointer(ociMsg)
125+
126+
count = ctypes.c_uint32()
127+
if timeout is not None: # wait for specified time
128+
t = OCI_Time(round(timeout * self.tickFrequency))
129+
else: # wait indefinitely
130+
t = OCI_NO_TIME
131+
OCI_ReadCANDataEx(
132+
self.rxQueue,
133+
t,
134+
ociMsgs,
135+
1,
136+
ctypes.byref(count),
137+
None,
138+
)
139+
140+
msg = None
141+
142+
if count.value != 0:
143+
if ociMsg.type == OCI_CANFDRX_MESSAGE.value:
144+
ociRxMsg = ociMsg.data.canFDRxMessage
145+
msg = can.Message(
146+
timestamp=float(ociRxMsg.timeStamp) / self.tickFrequency
147+
+ self.timeOffset,
148+
arbitration_id=ociRxMsg.frameID,
149+
is_extended_id=bool(ociRxMsg.flags & OCI_CAN_MSG_FLAG_EXTENDED),
150+
is_remote_frame=bool(
151+
ociRxMsg.flags & OCI_CAN_MSG_FLAG_REMOTE_FRAME
152+
),
153+
# is_error_frame=False,
154+
# channel=None,
155+
dlc=ociRxMsg.size,
156+
data=ociRxMsg.data[0 : ociRxMsg.size],
157+
is_fd=True,
158+
is_rx=not bool(ociRxMsg.flags & OCI_CAN_MSG_FLAG_SELFRECEPTION),
159+
bitrate_switch=bool(
160+
ociRxMsg.flags & OCI_CAN_MSG_FLAG_FD_DATA_BIT_RATE
161+
),
162+
# error_state_indicator=False,
163+
# check=False,
164+
)
165+
elif ociMsg.type == OCI_CAN_RX_MESSAGE.value:
166+
ociRxMsg = ociMsg.data.rxMessage
167+
msg = can.Message(
168+
timestamp=float(ociRxMsg.timeStamp) / self.tickFrequency
169+
+ self.timeOffset,
170+
arbitration_id=ociRxMsg.frameID,
171+
is_extended_id=bool(ociRxMsg.flags & OCI_CAN_MSG_FLAG_EXTENDED),
172+
is_remote_frame=bool(
173+
ociRxMsg.flags & OCI_CAN_MSG_FLAG_REMOTE_FRAME
174+
),
175+
# is_error_frame=False,
176+
# channel=None,
177+
dlc=ociRxMsg.dlc,
178+
data=ociRxMsg.data[0 : ociRxMsg.dlc],
179+
# is_fd=False,
180+
is_rx=not bool(ociRxMsg.flags & OCI_CAN_MSG_FLAG_SELFRECEPTION),
181+
# bitrate_switch=False,
182+
# error_state_indicator=False,
183+
# check=False,
184+
)
185+
186+
return (msg, True)
187+
188+
def send(self, msg: can.Message, timeout: Optional[float] = None) -> None:
189+
ociMsgs = (ctypes.POINTER(OCI_CANMessageEx) * 1)()
190+
ociMsg = OCI_CANMessageEx()
191+
ociMsgs[0] = ctypes.pointer(ociMsg)
192+
193+
if msg.is_fd:
194+
ociMsg.type = OCI_CANFDTX_MESSAGE
195+
ociTxMsg = ociMsg.data.canFDTxMessage
196+
ociTxMsg.size = msg.dlc
197+
else:
198+
ociMsg.type = OCI_CAN_TX_MESSAGE
199+
ociTxMsg = ociMsg.data.txMessage
200+
ociTxMsg.dlc = msg.dlc
201+
202+
# set fields common to CAN / CAN-FD
203+
ociTxMsg.frameID = msg.arbitration_id
204+
ociTxMsg.flags = 0
205+
if msg.is_extended_id:
206+
ociTxMsg.flags |= OCI_CAN_MSG_FLAG_EXTENDED
207+
if msg.is_remote_frame:
208+
ociTxMsg.flags |= OCI_CAN_MSG_FLAG_REMOTE_FRAME
209+
ociTxMsg.data = tuple(msg.data)
210+
211+
if msg.is_fd:
212+
ociTxMsg.flags |= OCI_CAN_MSG_FLAG_FD_DATA
213+
if msg.bitrate_switch:
214+
ociTxMsg.flags |= OCI_CAN_MSG_FLAG_FD_DATA_BIT_RATE
215+
216+
OCI_WriteCANDataEx(self.txQueue, OCI_NO_TIME, ociMsgs, 1, None)
217+
218+
def _apply_filters(self, filters: Optional[can.typechecking.CanFilters]) -> None:
219+
if self._oci_filters:
220+
OCI_RemoveCANFrameFilterEx(self.rxQueue, self._oci_filters, 1)
221+
222+
# "accept all" filter
223+
if filters is None:
224+
filters = [{"can_id": 0x0, "can_mask": 0x0}]
225+
226+
self._oci_filters = (ctypes.POINTER(OCI_CANRxFilterEx) * len(filters))()
227+
228+
for i, filter in enumerate(filters):
229+
f = OCI_CANRxFilterEx()
230+
f.frameIDValue = filter["can_id"]
231+
f.frameIDMask = filter["can_mask"]
232+
f.tag = 0
233+
f.flagsValue = 0
234+
if self.receive_own_messages:
235+
# mask out the SR bit, i.e. ignore the bit -> receive all
236+
f.flagsMask = 0
237+
else:
238+
# enable the SR bit in the mask. since the bit is 0 in flagsValue -> do not self-receive
239+
f.flagsMask = OCI_CAN_MSG_FLAG_SELFRECEPTION
240+
if filter.get("extended"):
241+
f.flagsValue |= OCI_CAN_MSG_FLAG_EXTENDED
242+
f.flagsMask |= OCI_CAN_MSG_FLAG_EXTENDED
243+
self._oci_filters[i].contents = f
244+
245+
OCI_AddCANFrameFilterEx(self.rxQueue, self._oci_filters, len(self._oci_filters))
246+
247+
def flush_tx_buffer(self) -> None:
248+
OCI_ResetQueue(self.txQueue)
249+
250+
def shutdown(self) -> None:
251+
# Cleanup TX
252+
if self.txQueue:
253+
OCI_DestroyCANTxQueue(self.txQueue)
254+
self.txQueue = None
255+
256+
# Cleanup RX
257+
if self.rxQueue:
258+
OCI_DestroyCANRxQueue(self.rxQueue)
259+
self.rxQueue = None
260+
261+
# Cleanup common
262+
if self.ctrl:
263+
OCI_CloseCANController(self.ctrl)
264+
OCI_DestroyCANController(self.ctrl)
265+
self.ctrl = None
266+
267+
if self.tree:
268+
CSI_DestroyProtocolTree(self.tree)
269+
self.tree = None
270+
271+
@property
272+
def state(self) -> can.BusState:
273+
status = OCI_CANControllerStatus()
274+
OCI_GetCANControllerStatus(self.ctrl, ctypes.byref(status))
275+
if status.stateCode & OCI_CAN_STATE_ACTIVE:
276+
return can.BusState.ACTIVE
277+
elif status.stateCode & OCI_CAN_STATE_PASSIVE:
278+
return can.BusState.PASSIVE
279+
280+
@state.setter
281+
def state(self, new_state: can.BusState) -> None:
282+
# disabled, OCI_AdaptCANConfiguration does not allow changing the bus mode
283+
# if new_state == can.BusState.ACTIVE:
284+
# self.ctrlConf.busParticipationMode = OCI_BUSMODE_ACTIVE
285+
# else:
286+
# self.ctrlConf.busParticipationMode = OCI_BUSMODE_PASSIVE
287+
# ec = OCI_AdaptCANConfiguration(self.ctrl, ctypes.byref(self.ctrlConf))
288+
# if ec != 0x0:
289+
# raise CanOperationError(f"OCI_AdaptCANConfiguration failed with error 0x{ec:X}")
290+
raise NotImplementedError("Setting state is not implemented.")
291+
292+
def _detect_available_configs() -> List[can.typechecking.AutoDetectedConfig]:
293+
nodeRange = CSI_NodeRange(CSI_NODE_MIN, CSI_NODE_MAX)
294+
tree = ctypes.POINTER(CSI_Tree)()
295+
CSI_CreateProtocolTree(ctypes.c_char_p(b""), nodeRange, ctypes.byref(tree))
296+
297+
nodes: Dict[str, str] = []
298+
299+
def _findNodes(tree, prefix):
300+
uri = f"{prefix}/{tree.contents.item.uriName.decode()}"
301+
if "CAN:" in uri:
302+
nodes.append({"interface": "etas", "channel": uri})
303+
elif tree.contents.child:
304+
_findNodes(
305+
tree.contents.child,
306+
f"{prefix}/{tree.contents.item.uriName.decode()}",
307+
)
308+
309+
if tree.contents.sibling:
310+
_findNodes(tree.contents.sibling, prefix)
311+
312+
_findNodes(tree, "ETAS:/")
313+
314+
CSI_DestroyProtocolTree(tree)
315+
316+
return nodes

0 commit comments

Comments
 (0)