Skip to content

Commit e909c51

Browse files
pierreluctgchristiansandberg
authored andcommitted
Add support for ICS neoVI interface
Adding support for ICS (INTREPID CONTROL SYSTEMS) neoVI interface via pyneovi
1 parent d9a0b17 commit e909c51

File tree

4 files changed

+267
-1
lines changed

4 files changed

+267
-1
lines changed

can/interfaces/interface.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
VALID_INTERFACES = set(['kvaser', 'serial', 'pcan', 'socketcan_native',
66
'socketcan_ctypes', 'socketcan', 'usb2can', 'ixxat',
7-
'nican', 'remote', 'virtual'])
7+
'nican', 'remote', 'virtual', 'neovi'])
88

99

1010
class Bus(object):
@@ -75,6 +75,9 @@ def __new__(cls, other, channel=None, *args, **kwargs):
7575
elif interface == 'virtual':
7676
from can.interfaces.virtual import VirtualBus
7777
cls = VirtualBus
78+
elif interface == 'neovi':
79+
from can.interfaces.neovi_api import NeoVIBus
80+
cls = NeoVIBus
7881
else:
7982
raise NotImplementedError("CAN interface '{}' not supported".format(interface))
8083

can/interfaces/neovi_api/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from can.interfaces.neovi_api.neovi_api import NeoVIBus

can/interfaces/neovi_api/neovi_api.py

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
"""
2+
pyneovi interface module.
3+
4+
pyneovi is a Python wrapper around the API provided by Intrepid Control Systems
5+
for communicating with their NeoVI range of devices.
6+
7+
Implementation references:
8+
* http://pyneovi.readthedocs.io/en/latest/
9+
* https://bitbucket.org/Kemp_J/pyneovi
10+
"""
11+
12+
import logging
13+
14+
import sys
15+
16+
logger = logging.getLogger(__name__)
17+
18+
try:
19+
from queue import Queue, Empty
20+
except ImportError:
21+
from Queue import Queue, Empty
22+
23+
if sys.platform == "win32":
24+
try:
25+
from neovi import neodevice
26+
from neovi import neovi
27+
from neovi.structures import icsSpyMessage
28+
except ImportError as e:
29+
logger.warning("Cannot load pyneovi: %s", e)
30+
else:
31+
# Will not work on other systems, but have it importable anyway for
32+
# tests/sphinx
33+
logger.warning("pyneovi library does not work on %s platform", sys.platform)
34+
35+
from can import Message
36+
from can.bus import BusABC
37+
38+
39+
SPY_STATUS_XTD_FRAME = 0x04
40+
SPY_STATUS_REMOTE_FRAME = 0x08
41+
42+
43+
# For the neoVI hardware, TimeHardware2 is more significant than TimeHardware.
44+
# The resolution of TimeHardware is 1.6us and and TimeHardware2 is 104.8576 ms.
45+
# To calculate the time of the message in seconds use the following formula:
46+
# "Timestamp (sec) = TimeHardware2* 0.1048576 + TimeHardware * 0.0000016".
47+
NEOVI_TIMEHARDWARE_SCALING = 0.0000016
48+
NEOVI_TIMEHARDWARE2_SCALING = 0.1048576
49+
50+
# For the neoVI PRO or ValueCAN hardware, TimeHardware2 is more significant than
51+
# TimeHardware. The resolution of TimeHardware is 1.0us and and TimeHardware2 is
52+
# 65.536 ms. To calculate the time of the message in seconds use the following
53+
# formula: "Timestamp (sec) = TimeHardware2* 0.065536 + TimeHardware * 0.000001"
54+
VALUECAN_TIMEHARDWARE_SCALING = 0.000001
55+
VALUECAN_TIMEHARDWARE2_SCALING = 0.065536
56+
57+
58+
def neo_device_name(device_type):
59+
names = {
60+
neovi.NEODEVICE_BLUE: 'neoVI BLUE',
61+
neovi.NEODEVICE_DW_VCAN: 'ValueCAN',
62+
neovi.NEODEVICE_FIRE: 'neoVI FIRE',
63+
neovi.NEODEVICE_VCAN3: 'ValueCAN3',
64+
neovi.NEODEVICE_YELLOW: 'neoVI YELLOW',
65+
neovi.NEODEVICE_RED: 'neoVI RED',
66+
neovi.NEODEVICE_ECU: 'neoECU',
67+
# neovi.NEODEVICE_IEVB: ''
68+
}
69+
return names.get(device_type, 'Unknown neoVI')
70+
71+
72+
class NeoVIBus(BusABC):
73+
"""
74+
The CAN Bus implemented for the pyneovi interface.
75+
"""
76+
77+
def __init__(self, channel=None, can_filters=None, **config):
78+
"""
79+
80+
:param int channel:
81+
The Channel id to create this bus with.
82+
:param list can_filters:
83+
A list of dictionaries each containing a "can_id" and a "can_mask".
84+
85+
>>> [{"can_id": 0x11, "can_mask": 0x21}]
86+
87+
"""
88+
type_filter = config.get('type_filter', neovi.NEODEVICE_ALL)
89+
neodevice.init_api()
90+
self.device = neodevice.find_devices(type_filter)[0]
91+
self.device.open()
92+
self.channel_info = '%s %s on channel %s' % (
93+
neo_device_name(self.device.get_type()),
94+
self.device.device.SerialNumber,
95+
channel
96+
)
97+
98+
if self.device.get_type() in [neovi.NEODEVICE_DW_VCAN]:
99+
self._time_scaling = (VALUECAN_TIMEHARDWARE_SCALING,
100+
VALUECAN_TIMEHARDWARE2_SCALING)
101+
else:
102+
self._time_scaling = (NEOVI_TIMEHARDWARE_SCALING,
103+
NEOVI_TIMEHARDWARE2_SCALING)
104+
105+
self.sw_filters = None
106+
self.set_filters(can_filters)
107+
self.rx_buffer = Queue()
108+
109+
self.network = int(channel) if channel is not None else None
110+
self.device.subscribe_to(self._rx_buffer, network=self.network)
111+
112+
def __del__(self):
113+
self.shutdown()
114+
115+
def shutdown(self):
116+
self.device.pump_messages = False
117+
if self.device.msg_queue_thread is not None:
118+
self.device.msg_queue_thread.join()
119+
120+
def _rx_buffer(self, msg, user_data):
121+
self.rx_buffer.put_nowait(msg)
122+
123+
def _is_filter_match(self, arb_id):
124+
"""
125+
If SW filtering is used, checks if the `arb_id` matches any of
126+
the filters setup.
127+
128+
:param int arb_id:
129+
CAN ID to check against.
130+
131+
:return:
132+
True if `arb_id` matches any filters
133+
(or if SW filtering is not used).
134+
"""
135+
if not self.sw_filters:
136+
# Filtering done on HW or driver level or no filtering
137+
return True
138+
for can_filter in self.sw_filters:
139+
if not (arb_id ^ can_filter['can_id']) & can_filter['can_mask']:
140+
return True
141+
142+
logging.info("%s not matching" % arb_id)
143+
return False
144+
145+
def _ics_msg_to_message(self, ics_msg):
146+
return Message(
147+
timestamp=(
148+
self._time_scaling[1] * ics_msg.TimeHardware2 +
149+
self._time_scaling[0] * ics_msg.TimeHardware
150+
),
151+
arbitration_id=ics_msg.ArbIDOrHeader,
152+
data=ics_msg.Data,
153+
dlc=ics_msg.NumberBytesData,
154+
extended_id=bool(ics_msg.StatusBitField &
155+
SPY_STATUS_XTD_FRAME),
156+
is_remote_frame=bool(ics_msg.StatusBitField &
157+
SPY_STATUS_REMOTE_FRAME),
158+
)
159+
160+
def recv(self, timeout=None):
161+
try:
162+
ics_msg = self.rx_buffer.get(block=True, timeout=timeout)
163+
except Empty:
164+
pass
165+
else:
166+
if ics_msg.NetworkID == self.network and \
167+
self._is_filter_match(ics_msg.ArbIDOrHeader):
168+
return self._ics_msg_to_message(ics_msg)
169+
170+
def send(self, msg):
171+
data = tuple(msg.data)
172+
flags = SPY_STATUS_XTD_FRAME if msg.is_extended_id else 0
173+
if msg.is_remote_frame:
174+
flags |= SPY_STATUS_REMOTE_FRAME
175+
176+
ics_msg = icsSpyMessage()
177+
ics_msg.ArbIDOrHeader = msg.arbitration_id
178+
ics_msg.NumberBytesData = len(data)
179+
ics_msg.Data = data
180+
ics_msg.StatusBitField = flags
181+
ics_msg.StatusBitField2 = 0
182+
ics_msg.DescriptionID = self.device.tx_id
183+
self.device.tx_id += 1
184+
self.device.tx_raw_message(ics_msg, self.network)
185+
186+
def set_filters(self, can_filters=None):
187+
"""Apply filtering to all messages received by this Bus.
188+
189+
Calling without passing any filters will reset the applied filters.
190+
191+
:param list can_filters:
192+
A list of dictionaries each containing a "can_id" and a "can_mask".
193+
194+
>>> [{"can_id": 0x11, "can_mask": 0x21}]
195+
196+
A filter matches, when
197+
``<received_can_id> & can_mask == can_id & can_mask``
198+
199+
"""
200+
self.sw_filters = can_filters
201+
202+
if self.sw_filters is None:
203+
logger.info("Filtering has been disabled")
204+
else:
205+
for can_filter in can_filters:
206+
can_id = can_filter["can_id"]
207+
can_mask = can_filter["can_mask"]
208+
logger.info("Filtering on ID 0x%X, mask 0x%X", can_id, can_mask)

doc/interfaces/neovi.rst

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
neoVI Interface
2+
===============
3+
4+
.. warning::
5+
6+
This ``neoVI`` documentation is a work in progress. Feedback and revisions
7+
are most welcome!
8+
9+
10+
Interface to `Intrepid Control Systems <https://www.intrepidcs.com/>`__ neoVI
11+
API range of devices via `pyneovi <http://kempj.co.uk/projects/pyneovi/>`__
12+
wrapper on Windows.
13+
14+
.. note::
15+
16+
This interface is not supported on Linux, however on Linux neoVI devices
17+
are supported via :doc:`socketcan` with ICS `Kernel-mode SocketCAN module
18+
for Intrepid devices
19+
<https://github.com/intrepidcs/intrepid-socketcan-kernel-module>`__ and
20+
`icsscand <https://github.com/intrepidcs/icsscand>`__
21+
22+
23+
Installation
24+
------------
25+
This neoVI interface requires the installation of the ICS neoVI DLL and pyneovi
26+
package.
27+
28+
- Download and install the Intrepid Product Drivers
29+
`Intrepid Product Drivers <https://cdn.intrepidcs.net/updates/files/ICSDrivers.zip>`__
30+
31+
- Install pyneovi using pip and the pyneovi bitbucket repo:
32+
.. code-block:: bash
33+
34+
pip install https://bitbucket.org/Kemp_J/pyneovi/get/default.zip
35+
36+
37+
Configuration
38+
-------------
39+
40+
An example `can.ini` file for windows 7:
41+
42+
::
43+
44+
[default]
45+
interface = neovi
46+
channel = 1
47+
48+
49+
Bus
50+
---
51+
52+
.. autoclass:: can.interfaces.neovi_api.NeoVIBus
53+
54+

0 commit comments

Comments
 (0)