Skip to content

Commit bb82478

Browse files
committed
add utility function create_filter
1 parent 2e24af0 commit bb82478

6 files changed

Lines changed: 171 additions & 5 deletions

File tree

can/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
from .io import CSVWriter, CSVReader
3232
from .io import SqliteWriter, SqliteReader
3333

34-
from .util import set_logging_level
34+
from .util import set_logging_level, create_filter
3535

3636
from .message import Message
3737
from .bus import BusABC, BusState

can/bus.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,7 @@ def set_filters(
344344
filters to ``None``.
345345
346346
:param filters:
347-
A iterable of dictionaries each containing a "can_id",
347+
An iterable of dictionaries each containing a "can_id",
348348
a "can_mask", and an optional "extended" key.
349349
350350
>>> [{"can_id": 0x11, "can_mask": 0x21, "extended": False}]
@@ -354,6 +354,9 @@ def set_filters(
354354
If ``extended`` is set as well, it only matches messages where
355355
``<received_is_extended> == extended``. Else it matches every
356356
messages based only on the arbitration ID and mask.
357+
358+
The utility function :func:`~can.create_filter` is provided
359+
to generate suitable filters.
357360
"""
358361
self._filters = filters or None
359362
self._apply_filters(self._filters)

can/typechecking.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,13 @@
88

99
import typing_extensions
1010

11-
CanFilter = typing_extensions.TypedDict("CanFilter", {"can_id": int, "can_mask": int})
11+
CanFilter = typing_extensions.TypedDict(
12+
"CanFilter",
13+
{"can_id": int, "can_mask": int, "extended": typing_extensions.Literal[False]},
14+
)
1215
CanFilterExtended = typing_extensions.TypedDict(
13-
"CanFilterExtended", {"can_id": int, "can_mask": int, "extended": bool}
16+
"CanFilterExtended",
17+
{"can_id": int, "can_mask": int, "extended": typing_extensions.Literal[True]},
1418
)
1519
CanFilters = typing.Sequence[typing.Union[CanFilter, CanFilterExtended]]
1620

can/util.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,49 @@ def time_perfcounter_correlation() -> Tuple[float, float]:
371371
return t1, performance_counter
372372

373373

374+
def create_filter(
375+
accepted_std_ids: Iterable[int], accepted_ext_ids: Iterable[int]
376+
) -> typechecking.CanFilters:
377+
"""Calculate CAN filter settings for given arbitration IDs.
378+
379+
:param accepted_std_ids:
380+
An iterable of standard arbitration IDs (11bit) to be accepted.
381+
:param accepted_ext_ids:
382+
An iterable of extended arbitration IDs (29bit) to be accepted.
383+
:returns:
384+
CAN filters to be used with :meth:`~can.BusABC.set_filters`.
385+
"""
386+
std_code = 0x7FF
387+
std_mask = 0x7FF
388+
_std_mem = 0x7FF
389+
390+
ext_code = 0x1FFFFFFF
391+
ext_mask = 0x1FFFFFFF
392+
_ext_mem = 0x1FFFFFFF
393+
394+
for can_id in accepted_std_ids:
395+
if not (0 <= can_id <= 0x7FF):
396+
raise ValueError(f"Invalid CAN ID {can_id}")
397+
398+
if std_code != 0x7FF:
399+
_std_mem = ~(std_code ^ can_id) & 0x7FF
400+
std_code &= can_id
401+
std_mask &= _std_mem
402+
403+
for can_id in accepted_ext_ids:
404+
if not (0 <= can_id <= 0x1FFFFFFF):
405+
raise ValueError(f"Invalid Extended CAN ID {can_id}")
406+
if ext_code != 0x1FFFFFFF:
407+
_ext_mem = ~(ext_code ^ can_id) & 0x1FFFFFFF
408+
ext_code &= can_id
409+
ext_mask &= _ext_mem
410+
411+
return [
412+
{"can_id": std_code, "can_mask": std_mask, "extended": False},
413+
{"can_id": ext_code, "can_mask": ext_mask, "extended": True},
414+
]
415+
416+
374417
if __name__ == "__main__":
375418
print("Searching for configuration named:")
376419
print("\n".join(CONFIG_FILES))

doc/api.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ Utilities
2727

2828
.. autofunction:: can.detect_available_configs
2929

30+
.. autofunction:: can.create_filter
31+
3032

3133
.. _notifier:
3234

test/test_util.py

Lines changed: 115 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import unittest
22
import warnings
33

4-
from can.util import _rename_kwargs
4+
import pytest
5+
6+
from can.util import _rename_kwargs, create_filter
57

68

79
class RenameKwargsTest(unittest.TestCase):
@@ -47,3 +49,115 @@ def test_with_new_and_alias_present(self):
4749
aliases = {"old_a": "a", "old_b": "b", "z": None}
4850
with self.assertRaises(TypeError):
4951
self._test(kwargs, aliases)
52+
53+
54+
def test_create_filter():
55+
# test exceptions
56+
with pytest.raises(ValueError):
57+
create_filter(
58+
accepted_std_ids=[-1],
59+
accepted_ext_ids=[100],
60+
)
61+
with pytest.raises(ValueError):
62+
create_filter(
63+
accepted_std_ids=[100],
64+
accepted_ext_ids=[-1],
65+
)
66+
with pytest.raises(ValueError):
67+
create_filter(
68+
accepted_std_ids=[0x7FF + 1],
69+
accepted_ext_ids=[100],
70+
)
71+
with pytest.raises(ValueError):
72+
create_filter(
73+
accepted_std_ids=[100],
74+
accepted_ext_ids=[0x1FFFFFFF + 1],
75+
)
76+
77+
accepted_std_range = range(200, 205)
78+
accepted_ext_range = range(105_000, 105_005)
79+
80+
can_filters = create_filter(
81+
accepted_std_ids=accepted_std_range,
82+
accepted_ext_ids=accepted_ext_range,
83+
)
84+
std_code = can_filters[0]["can_id"]
85+
std_mask = can_filters[0]["can_mask"]
86+
ext_code = can_filters[1]["can_id"]
87+
ext_mask = can_filters[1]["can_mask"]
88+
89+
"""
90+
Standard ID proof:
91+
b00011001000 | id: 200
92+
b00011001001 | id: 201
93+
b00011001010 | id: 202
94+
b00011001011 | id: 203
95+
b00011001100 | id: 204
96+
-------------------------
97+
b00011001000 | code: 200
98+
b11111111000 | mask: 2040
99+
"""
100+
assert std_code == 200
101+
assert std_mask == 2040
102+
103+
"""
104+
Extended ID proof:
105+
b00000000000011001101000101000 | id: 105000
106+
b00000000000011001101000101001 | id: 105001
107+
b00000000000011001101000101010 | id: 105002
108+
b00000000000011001101000101011 | id: 105003
109+
b00000000000011001101000101100 | id: 105004
110+
-------------------------
111+
b00000000000011001101000101000 | code: 105000
112+
b11111111111111111111111111000 | mask: 536870904
113+
"""
114+
assert ext_code == 105000
115+
assert ext_mask == 536870904
116+
117+
# test that standard IDs in given range are accepted
118+
for can_id in accepted_std_range:
119+
assert 0 == (can_id ^ std_code) & std_mask
120+
121+
# test that extended IDs in given range are accepted
122+
for can_id in accepted_ext_range:
123+
assert 0 == (can_id ^ ext_code) & ext_mask
124+
125+
# test non-consecutive IDs
126+
can_filters = create_filter(
127+
accepted_std_ids=[333, 444],
128+
accepted_ext_ids=[33333, 44444],
129+
)
130+
std_code = can_filters[0]["can_id"]
131+
std_mask = can_filters[0]["can_mask"]
132+
ext_code = can_filters[1]["can_id"]
133+
ext_mask = can_filters[1]["can_mask"]
134+
135+
"""
136+
Standard ID proof (non-consecutive):
137+
b00101001101 | id: 333
138+
b00110111100 | id: 444
139+
-------------------------
140+
b00100001100 | code: 268
141+
b11100001110 | mask: 1806
142+
"""
143+
assert std_code == 268
144+
assert std_mask == 1806
145+
146+
"""
147+
Extended ID proof (non-consecutive):
148+
b00000000000001000001000110101 | id: 33333
149+
b00000000000001010110110011100 | id: 44444
150+
-------------------------
151+
b00000000000001000000000010100 | code: 32788
152+
b11111111111111101000001010110 | mask: 536858710
153+
"""
154+
assert ext_code == 32788
155+
assert ext_mask == 536858710
156+
157+
# test that standard IDs in given sequence are accepted
158+
for can_id in [333, 444]:
159+
assert 0 == (can_id ^ std_code) & std_mask
160+
161+
# test that extended IDs in given sequence are accepted
162+
for can_id in [33333, 44444]:
163+
assert 0 == (can_id ^ ext_code) & ext_mask

0 commit comments

Comments
 (0)