Skip to content

Commit 865cd27

Browse files
committed
Merged in kvaser-set-filters (pull request #33)
Improved filters for Kvaser. Hardware and software filtering options.
2 parents 26d2acb + 667540b commit 865cd27

File tree

3 files changed

+126
-25
lines changed

3 files changed

+126
-25
lines changed

can/interfaces/kvaser/canlib.py

Lines changed: 60 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -349,16 +349,6 @@ def __init__(self, channel, can_filters=None, **config):
349349
4)
350350
canSetBusParams(self._read_handle, bitrate, tseg1, tseg2, sjw, no_samp, 0)
351351

352-
if can_filters is not None and len(can_filters):
353-
log.info("The kvaser canlib backend is filtering messages")
354-
code, mask = 0, 0
355-
for can_filter in can_filters:
356-
code |= can_filter['can_id']
357-
mask |= can_filter['can_mask']
358-
log.info("Filtering on: {} {}".format(code, mask))
359-
canSetAcceptanceFilter(self._read_handle, code, mask, 0)
360-
canSetAcceptanceFilter(self._read_handle, code, mask, 1)
361-
362352
if self.single_handle:
363353
log.debug("We don't require separate handles to the bus")
364354
self._write_handle = self._read_handle
@@ -367,6 +357,9 @@ def __init__(self, channel, can_filters=None, **config):
367357
self._write_handle = canOpenChannel(channel, canstat.canOPEN_ACCEPT_VIRTUAL)
368358
canBusOn(self._read_handle)
369359

360+
self.sw_filters = []
361+
self.set_filters(can_filters)
362+
370363
can_driver_mode = canstat.canDRIVER_SILENT if driver_mode == DRIVER_MODE_SILENT else canstat.canDRIVER_NORMAL
371364
canSetBusOutputControl(self._write_handle, can_driver_mode)
372365
log.debug('Going bus on TX handle')
@@ -384,6 +377,41 @@ def __init__(self, channel, can_filters=None, **config):
384377

385378
super(KvaserBus, self).__init__()
386379

380+
def set_filters(self, can_filters=None):
381+
"""Apply filtering to all messages received by this Bus.
382+
383+
Calling without passing any filters will reset the applied filters.
384+
385+
Since Kvaser only supports setting one filter per handle, the filtering
386+
will be done in the :meth:`recv` if more than one filter is requested.
387+
388+
:param list can_filters:
389+
A list of dictionaries each containing a "can_id" and a "can_mask".
390+
391+
>>> [{"can_id": 0x11, "can_mask": 0x21}]
392+
393+
A filter matches, when ``<received_can_id> & can_mask == can_id & can_mask``
394+
"""
395+
can_id = 0
396+
can_mask = 0
397+
398+
if not can_filters:
399+
log.info('Filtering has been disabled')
400+
self.sw_filters = []
401+
elif len(can_filters) == 1:
402+
can_id = can_filters[0]['can_id']
403+
can_mask = can_filters[0]['can_mask']
404+
log.info('canlib is filtering on ID 0x%X, mask 0x%X', can_id, can_mask)
405+
self.sw_filters = []
406+
elif len(can_filters) > 1:
407+
log.info('Filtering is handled in Python')
408+
self.sw_filters = can_filters
409+
410+
# Set same filter for both handles as well as standard and extended IDs
411+
for handle in (self._read_handle, self._write_handle):
412+
for ext in (0, 1):
413+
canSetAcceptanceFilter(handle, can_id, can_mask, ext)
414+
387415
def flush_tx_buffer(self):
388416
"""
389417
Flushes the transmit buffer on the Kvaser
@@ -417,6 +445,26 @@ def __convert_timestamp(self, value):
417445
self.pc_time_offset += lag
418446
return timestamp
419447

448+
def _is_filter_match(self, arb_id):
449+
"""
450+
If SW filtering is used, checks if the `arb_id` matches any of
451+
the filters setup.
452+
453+
:param int arb_id:
454+
CAN ID to check against.
455+
456+
:return:
457+
True if `arb_id` matches any filters
458+
(or if SW filtering is not used).
459+
"""
460+
if not self.sw_filters:
461+
# Filtering done on HW or driver level or no filtering
462+
return True
463+
for can_filter in self.sw_filters:
464+
if not (arb_id ^ can_filter['can_id']) & can_filter['can_mask']:
465+
return True
466+
return False
467+
420468
def recv(self, timeout=None):
421469
"""
422470
Read a message from kvaser device.
@@ -441,6 +489,8 @@ def recv(self, timeout=None):
441489

442490
if status == canstat.canOK:
443491
log.debug('read complete -> status OK')
492+
if not self._is_filter_match(arb_id.value):
493+
return None
444494
data_array = data.raw
445495
flags = flags.value
446496
is_extended = bool(flags & canstat.canMSG_EXT)

doc/kvaser.rst

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,25 +16,20 @@ Internals
1616
The Kvaser :class:`~can.Bus` object with a physical CAN Bus can be operated in two
1717
modes; ``single_handle`` mode with one shared bus handle used for both reading and
1818
writing to the CAN bus, or with two separate bus handles.
19+
Two separate handles are needed if receiving and sending messages are done in
20+
different threads (see `Kvaser documentation
21+
<http://www.kvaser.com/canlib-webhelp/page_user_guide_threads_applications.html>`_).
1922

2023

2124
.. warning:: Any objects inheriting from `Bus`_ should *not* directly
2225
use the interface handle(/s).
2326

2427

25-
Threading
26-
~~~~~~~~~
28+
Message filtering
29+
~~~~~~~~~~~~~~~~~
2730

28-
To avoid contention for the bus handle in ``single_handle`` mode, access is protected for
29-
the **read** and **write** daemon threads with a ``writing_event`` *Event* and a
30-
``done_writing`` *Condition*.
31-
32-
The read thread acquires ``done_writing`` and while ``writing_event`` is set
33-
blocks waiting on the ``done_writing`` Condition. A 1ms blocking read is carried
34-
out before ``done_writing`` is released.
35-
36-
The write thread blocks for 5ms on a queue (to allow the thread to stop). If
37-
a message was received it *sets* the writing_event to tell the read thread
38-
that a message in waiting to be sent. The ``done_writing`` is acquired for
39-
the actual write, the writing_event is cleared and the ``done_writing`` event
40-
is notified to start the read thread again.
31+
The Kvaser driver and hardware only supports setting one filter per handle.
32+
If one filter is requested, this is will be handled by the Kvaser driver.
33+
If more than one filter is needed, these will be handled in Python code
34+
in the ``recv`` method. If a message does not match any of the filters,
35+
``recv()`` will return None.

test/test_kvaser.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ def setUp(self):
2626
canlib.canClose = Mock()
2727
canlib.canSetBusOutputControl = Mock()
2828
canlib.canGetChannelData = Mock()
29+
canlib.canSetAcceptanceFilter = Mock()
2930
canlib.canWriteWait = self.canWriteWait
3031
canlib.canReadWait = self.canReadWait
3132

@@ -49,6 +50,61 @@ def test_bus_shutdown(self):
4950
self.assertTrue(canlib.canBusOff.called)
5051
self.assertTrue(canlib.canClose.called)
5152

53+
def test_filter_setup(self):
54+
# No filter in constructor
55+
expected_args = [
56+
((0, 0, 0, 0),), # Disable filtering STD on read handle
57+
((0, 0, 0, 1),), # Disable filtering EXT on read handle
58+
((0, 0, 0, 0),), # Disable filtering STD on write handle
59+
((0, 0, 0, 1),), # Disable filtering EXT on write handle
60+
]
61+
self.assertEqual(canlib.canSetAcceptanceFilter.call_args_list,
62+
expected_args)
63+
64+
# One filter, will be handled by canlib
65+
canlib.canSetAcceptanceFilter.reset_mock()
66+
self.bus.set_filters([
67+
{'can_id': 0x8, 'can_mask': 0xff}
68+
])
69+
expected_args = [
70+
((0, 0x8, 0xff, 0),), # Enable filtering STD on read handle
71+
((0, 0x8, 0xff, 1),), # Enable filtering EXT on read handle
72+
((0, 0x8, 0xff, 0),), # Enable filtering STD on write handle
73+
((0, 0x8, 0xff, 1),), # Enable filtering EXT on write handle
74+
]
75+
self.assertEqual(canlib.canSetAcceptanceFilter.call_args_list,
76+
expected_args)
77+
78+
# Multiple filters, will be handled in Python
79+
canlib.canSetAcceptanceFilter.reset_mock()
80+
multiple_filters = [
81+
{'can_id': 0x8, 'can_mask': 0xff},
82+
{'can_id': 0x9, 'can_mask': 0xff}
83+
]
84+
self.bus.set_filters(multiple_filters)
85+
expected_args = [
86+
((0, 0, 0, 0),), # Disable filtering STD on read handle
87+
((0, 0, 0, 1),), # Disable filtering EXT on read handle
88+
((0, 0, 0, 0),), # Disable filtering STD on write handle
89+
((0, 0, 0, 1),), # Disable filtering EXT on write handle
90+
]
91+
self.assertEqual(canlib.canSetAcceptanceFilter.call_args_list,
92+
expected_args)
93+
self.assertEqual(self.bus.sw_filters, multiple_filters)
94+
95+
def test_sw_filtering(self):
96+
self.bus.set_filters([
97+
{'can_id': 0x8, 'can_mask': 0xff},
98+
{'can_id': 0x9, 'can_mask': 0xffff}
99+
])
100+
self.assertTrue(self.bus._is_filter_match(0x8))
101+
self.assertTrue(self.bus._is_filter_match(0x9))
102+
self.assertTrue(self.bus._is_filter_match(0x108))
103+
self.assertTrue(self.bus._is_filter_match(0x10009))
104+
self.assertFalse(self.bus._is_filter_match(0x10))
105+
self.assertFalse(self.bus._is_filter_match(0x0))
106+
self.assertFalse(self.bus._is_filter_match(0x109))
107+
52108
def test_send_extended(self):
53109
msg = can.Message(
54110
arbitration_id=0xc0ffee,

0 commit comments

Comments
 (0)