Skip to content

Commit 0a0c7bd

Browse files
authored
Add NI-XNET CANFD interface (#968)
* Add initial NI-XNET support * Add NI-XNET doc * update authors information * Add queue len condition to read and use BRS flag to change message type
1 parent 966a5e4 commit 0a0c7bd

File tree

5 files changed

+280
-0
lines changed

5 files changed

+280
-0
lines changed

can/interfaces/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"seeedstudio": ("can.interfaces.seeedstudio", "SeeedBus"),
2828
"cantact": ("can.interfaces.cantact", "CantactBus"),
2929
"gs_usb": ("can.interfaces.gs_usb", "GsUsbBus"),
30+
"nixnet": ("can.interfaces.nixnet", "NiXNETcanBus"),
3031
}
3132

3233
BACKENDS.update(

can/interfaces/nixnet.py

+257
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
# coding: utf-8
2+
3+
"""
4+
NI-XNET interface module.
5+
6+
Implementation references:
7+
NI-XNET Hardware and Software Manual: https://www.ni.com/pdf/manuals/372840h.pdf
8+
NI-XNET Python implementation: https://github.com/ni/nixnet-python
9+
10+
Authors: Javier Rubio Giménez <[email protected]>, Jose A. Escobar <[email protected]>
11+
"""
12+
13+
import logging
14+
import sys
15+
import time
16+
import struct
17+
18+
from can import CanError, BusABC, Message
19+
20+
logger = logging.getLogger(__name__)
21+
22+
if sys.platform == "win32":
23+
try:
24+
from nixnet import session, types, constants, errors, system, database
25+
except ImportError:
26+
logger.error("Error, NIXNET python module cannot be loaded.")
27+
raise ImportError()
28+
else:
29+
logger.error("NI-XNET interface is only available on Windows systems")
30+
raise NotImplementedError("NiXNET is not supported on not Win32 platforms")
31+
32+
33+
class NiXNETcanBus(BusABC):
34+
"""
35+
The CAN Bus implemented for the NI-XNET interface.
36+
37+
"""
38+
39+
def __init__(
40+
self,
41+
channel,
42+
can_filters=None,
43+
bitrate=None,
44+
fd=False,
45+
fd_bitrate=None,
46+
brs=False,
47+
can_termination=False,
48+
log_errors=True,
49+
**kwargs
50+
):
51+
"""
52+
:param str channel:
53+
Name of the object to open (e.g. 'CAN0')
54+
55+
:param int bitrate:
56+
Bitrate in bits/s
57+
58+
:param list can_filters:
59+
See :meth:`can.BusABC.set_filters`.
60+
61+
:param bool log_errors:
62+
If True, communication errors will appear as CAN messages with
63+
``is_error_frame`` set to True and ``arbitration_id`` will identify
64+
the error (default True)
65+
66+
:raises can.interfaces.nixnet.NiXNETError:
67+
If starting communication fails
68+
69+
"""
70+
self._rx_queue = []
71+
self.channel = channel
72+
self.channel_info = "NI-XNET: " + channel
73+
74+
# Set database for the initialization
75+
if not fd:
76+
database_name = ":memory:"
77+
else:
78+
if not brs:
79+
database_name = ":can_fd:"
80+
else:
81+
database_name = ":can_fd_brs:"
82+
83+
try:
84+
85+
# We need two sessions for this application, one to send frames and another to receive them
86+
87+
self.__session_send = session.FrameOutStreamSession(
88+
channel, database_name=database_name
89+
)
90+
self.__session_receive = session.FrameInStreamSession(
91+
channel, database_name=database_name
92+
)
93+
94+
# We stop the sessions to allow reconfiguration, as by default they autostart at creation
95+
self.__session_send.stop()
96+
self.__session_receive.stop()
97+
98+
# See page 1017 of NI-XNET Hardware and Software Manual to set custom can configuration
99+
if bitrate:
100+
self.__session_send.intf.baud_rate = bitrate
101+
self.__session_receive.intf.baud_rate = bitrate
102+
103+
if fd_bitrate:
104+
# See page 951 of NI-XNET Hardware and Software Manual to set custom can configuration
105+
self.__session_send.intf.can_fd_baud_rate = fd_bitrate
106+
self.__session_receive.intf.can_fd_baud_rate = fd_bitrate
107+
108+
if can_termination:
109+
self.__session_send.intf.can_term = constants.CanTerm.ON
110+
self.__session_receive.intf.can_term = constants.CanTerm.ON
111+
112+
self.__session_receive.queue_size = 512
113+
# Once that all the parameters have been restarted, we start the sessions
114+
self.__session_send.start()
115+
self.__session_receive.start()
116+
117+
except errors.XnetError as err:
118+
raise NiXNETError(function="__init__", error_message=err.args[0]) from None
119+
120+
self._is_filtered = False
121+
super(NiXNETcanBus, self).__init__(
122+
channel=channel,
123+
can_filters=can_filters,
124+
bitrate=bitrate,
125+
log_errors=log_errors,
126+
**kwargs
127+
)
128+
129+
def _recv_internal(self, timeout):
130+
try:
131+
if len(self._rx_queue) == 0:
132+
fr = self.__session_receive.frames.read(4, timeout=0)
133+
for f in fr:
134+
self._rx_queue.append(f)
135+
can_frame = self._rx_queue.pop(0)
136+
137+
# Timestamp should be converted from raw frame format(100ns increment from(12:00 a.m. January 1 1601 Coordinated
138+
# Universal Time (UTC)) to epoch time(number of seconds from January 1, 1970 (midnight UTC/GMT))
139+
msg = Message(
140+
timestamp=can_frame.timestamp / 10000000.0 - 11644473600,
141+
channel=self.channel,
142+
is_remote_frame=can_frame.type == constants.FrameType.CAN_REMOTE,
143+
is_error_frame=can_frame.type == constants.FrameType.CAN_BUS_ERROR,
144+
is_fd=(
145+
can_frame.type == constants.FrameType.CANFD_DATA
146+
or can_frame.type == constants.FrameType.CANFDBRS_DATA
147+
),
148+
bitrate_switch=can_frame.type == constants.FrameType.CANFDBRS_DATA,
149+
is_extended_id=can_frame.identifier.extended,
150+
# Get identifier from CanIdentifier structure
151+
arbitration_id=can_frame.identifier.identifier,
152+
dlc=len(can_frame.payload),
153+
data=can_frame.payload,
154+
)
155+
156+
return msg, self._filters is None
157+
except Exception as e:
158+
# print('Error: ', e)
159+
return None, self._filters is None
160+
161+
def send(self, msg, timeout=None):
162+
"""
163+
Send a message using NI-XNET.
164+
165+
:param can.Message msg:
166+
Message to send
167+
168+
:param float timeout:
169+
Max time to wait for the device to be ready in seconds, None if time is infinite
170+
171+
:raises can.interfaces.nixnet.NiXNETError:
172+
If writing to transmit buffer fails.
173+
It does not wait for message to be ACKed currently.
174+
"""
175+
if timeout is None:
176+
timeout = constants.TIMEOUT_INFINITE
177+
178+
if msg.is_remote_frame:
179+
type_message = constants.FrameType.CAN_REMOTE
180+
elif msg.is_error_frame:
181+
type_message = constants.FrameType.CAN_BUS_ERROR
182+
elif msg.is_fd:
183+
if msg.bitrate_switch:
184+
type_message = constants.FrameType.CANFDBRS_DATA
185+
else:
186+
type_message = constants.FrameType.CANFD_DATA
187+
else:
188+
type_message = constants.FrameType.CAN_DATA
189+
190+
can_frame = types.CanFrame(
191+
types.CanIdentifier(msg.arbitration_id, msg.is_extended_id),
192+
type=type_message,
193+
payload=msg.data,
194+
)
195+
196+
try:
197+
self.__session_send.frames.write([can_frame], timeout)
198+
except errors.XnetError as err:
199+
raise NiXNETError(function="send", error_message=err.args[0]) from None
200+
201+
def reset(self):
202+
"""
203+
Resets network interface. Stops network interface, then resets the CAN
204+
chip to clear the CAN error counters (clear error passive state).
205+
Resetting includes clearing all entries from read and write queues.
206+
"""
207+
self.__session_send.flush()
208+
self.__session_receive.flush()
209+
210+
self.__session_send.stop()
211+
self.__session_receive.stop()
212+
213+
self.__session_send.start()
214+
self.__session_receive.start()
215+
216+
def shutdown(self):
217+
"""Close object."""
218+
self.__session_send.flush()
219+
self.__session_receive.flush()
220+
221+
self.__session_send.stop()
222+
self.__session_receive.stop()
223+
224+
self.__session_send.close()
225+
self.__session_receive.close()
226+
227+
@staticmethod
228+
def _detect_available_configs():
229+
configs = []
230+
nixnet_system = system.System()
231+
for can_intf in nixnet_system.intf_refs_can:
232+
logger.info("Channel index %d: %s", can_intf.port_num, str(can_intf))
233+
configs.append(
234+
{
235+
"interface": "nixnet",
236+
"channel": str(can_intf),
237+
"can_term_available": can_intf.can_term_cap
238+
== constants.CanTermCap.YES,
239+
}
240+
)
241+
nixnet_system.close()
242+
return configs
243+
244+
245+
# To-Do review error management, I don't like this implementation
246+
class NiXNETError(CanError):
247+
"""Error from NI-XNET driver."""
248+
249+
def __init__(self, function="", error_message=""):
250+
super(NiXNETError, self).__init__()
251+
#: Function that failed
252+
self.function = function
253+
#: Arguments passed to function
254+
self.error_message = error_message
255+
256+
def __str__(self):
257+
return "Function %s failed:\n%s" % (self.function, self.error_message)

doc/interfaces.rst

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ The available interfaces are:
1919
interfaces/udp_multicast
2020
interfaces/neovi
2121
interfaces/nican
22+
interfaces/nixnet
2223
interfaces/pcan
2324
interfaces/robotell
2425
interfaces/seeedstudio

doc/interfaces/nixnet.rst

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
NI-XNET
2+
=======
3+
4+
This interface adds support for NI-XNET CAN controllers by `National Instruments`_.
5+
6+
7+
.. warning::
8+
9+
NI-XNET only seems to support windows platforms.
10+
11+
12+
Bus
13+
---
14+
15+
.. autoclass:: can.interfaces.nican.NiXNETcanBus
16+
17+
.. autoexception:: can.interfaces.nican.NiXNETError
18+
19+
20+
.. _National Instruments: http://www.ni.com/can/

setup.py

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"neovi": ["filelock", "python-ics>=2.12"],
3232
"cantact": ["cantact>=0.0.7"],
3333
"gs_usb": ["gs_usb>=0.2.1"],
34+
"nixnet": ["nixnet>=0.3.1"],
3435
}
3536

3637
setup(

0 commit comments

Comments
 (0)