Skip to content

Commit 2b1f6f6

Browse files
authored
Add Support for Remote and Error Frames to SerialBus (#1948)
1 parent 9fe17e4 commit 2b1f6f6

File tree

4 files changed

+71
-44
lines changed

4 files changed

+71
-44
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ jobs:
4141
- name: Setup SocketCAN
4242
if: ${{ matrix.os == 'ubuntu-latest' }}
4343
run: |
44+
sudo apt-get update
4445
sudo apt-get -y install linux-modules-extra-$(uname -r)
4546
sudo ./test/open_vcan.sh
4647
- name: Test with pytest via tox

can/interfaces/serial/serial_can.py

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,13 @@ def list_comports() -> list[Any]:
4242
return []
4343

4444

45+
CAN_ERR_FLAG = 0x20000000
46+
CAN_RTR_FLAG = 0x40000000
47+
CAN_EFF_FLAG = 0x80000000
48+
CAN_ID_MASK_EXT = 0x1FFFFFFF
49+
CAN_ID_MASK_STD = 0x7FF
50+
51+
4552
class SerialBus(BusABC):
4653
"""
4754
Enable basic can communication over a serial device.
@@ -116,9 +123,6 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None:
116123
:param msg:
117124
Message to send.
118125
119-
.. note:: Flags like ``extended_id``, ``is_remote_frame`` and
120-
``is_error_frame`` will be ignored.
121-
122126
.. note:: If the timestamp is a float value it will be converted
123127
to an integer.
124128
@@ -134,20 +138,25 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None:
134138
raise ValueError(f"Timestamp is out of range: {msg.timestamp}") from None
135139

136140
# Pack arbitration ID
137-
try:
138-
arbitration_id = msg.arbitration_id + (0 if msg.is_extended_id else 0x20000000)
139-
arbitration_id = struct.pack("<I", arbitration_id)
140-
except struct.error:
141-
raise ValueError(
142-
f"Arbitration ID is out of range: {msg.arbitration_id}"
143-
) from None
141+
if msg.is_extended_id:
142+
arbitration_id = msg.arbitration_id & CAN_ID_MASK_EXT
143+
arbitration_id |= CAN_EFF_FLAG
144+
else:
145+
arbitration_id = msg.arbitration_id & CAN_ID_MASK_STD
146+
147+
if msg.is_error_frame:
148+
arbitration_id |= CAN_ERR_FLAG
149+
if msg.is_remote_frame:
150+
arbitration_id |= CAN_RTR_FLAG
151+
152+
arbitration_id_bytes = struct.pack("<I", arbitration_id)
144153

145154
# Assemble message
146155
byte_msg = bytearray()
147156
byte_msg.append(0xAA)
148157
byte_msg += timestamp
149158
byte_msg.append(msg.dlc)
150-
byte_msg += arbitration_id
159+
byte_msg += arbitration_id_bytes
151160
byte_msg += msg.data
152161
byte_msg.append(0xBB)
153162

@@ -172,11 +181,6 @@ def _recv_internal(
172181
173182
:returns:
174183
Received message and :obj:`False` (because no filtering as taken place).
175-
176-
.. warning::
177-
Flags like ``is_extended_id``, ``is_remote_frame`` and ``is_error_frame``
178-
will not be set over this function, the flags in the return
179-
message are the default values.
180184
"""
181185
try:
182186
rx_byte = self._ser.read()
@@ -189,16 +193,14 @@ def _recv_internal(
189193

190194
s = self._ser.read(4)
191195
arbitration_id = struct.unpack("<I", s)[0]
192-
is_extended_id = False if arbitration_id & 0x20000000 else True
193-
arbitration_id -= 0 if is_extended_id else 0x20000000
194-
if is_extended_id and arbitration_id >= 0x20000000:
195-
raise ValueError(
196-
"received arbitration id may not exceed or equal 2^29 (0x20000000) if extended"
197-
)
198-
if not is_extended_id and arbitration_id >= 0x800:
199-
raise ValueError(
200-
"received arbitration id may not exceed or equal 2^11 (0x800) if not extended"
201-
)
196+
is_extended_id = bool(arbitration_id & CAN_EFF_FLAG)
197+
is_error_frame = bool(arbitration_id & CAN_ERR_FLAG)
198+
is_remote_frame = bool(arbitration_id & CAN_RTR_FLAG)
199+
200+
if is_extended_id:
201+
arbitration_id = arbitration_id & CAN_ID_MASK_EXT
202+
else:
203+
arbitration_id = arbitration_id & CAN_ID_MASK_STD
202204

203205
data = self._ser.read(dlc)
204206

@@ -212,6 +214,8 @@ def _recv_internal(
212214
dlc=dlc,
213215
data=data,
214216
is_extended_id=is_extended_id,
217+
is_error_frame=is_error_frame,
218+
is_remote_frame=is_remote_frame,
215219
)
216220
return msg, False
217221

doc/interfaces/serial.rst

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ six parts. The start and the stop byte for the frame, the timestamp, DLC,
3030
arbitration ID and the payload. The payload has a variable length of between
3131
0 and 8 bytes, the other parts are fixed. Both, the timestamp and the
3232
arbitration ID will be interpreted as 4 byte unsigned integers. The DLC is
33-
also an unsigned integer with a length of 1 byte. Non-extended (11-bit)
34-
identifiers are encoded by adding 0x20000000 to the 11-bit ID. For example, an
35-
11-bit CAN ID of 0x123 is encoded with an arbitration ID of 0x20000123.
33+
also an unsigned integer with a length of 1 byte. Extended (29-bit)
34+
identifiers are encoded by adding 0x80000000 to the ID. For example, a
35+
29-bit CAN ID of 0x123 is encoded with an arbitration ID of 0x80000123.
3636

3737
Serial frame format
3838
^^^^^^^^^^^^^^^^^^^
@@ -105,20 +105,20 @@ Examples of serial frames
105105
| 0xAA | 0x66 0x73 0x00 0x00 | 0x00 | 0x01 0x00 0x00 0x00 | 0xBB |
106106
+----------------+---------------------+------+---------------------+--------------+
107107

108-
.. rubric:: CAN message with 0 byte payload with an 11-bit CAN ID
108+
.. rubric:: Extended Frame CAN message with 0 byte payload with an 29-bit CAN ID
109109

110110
+----------------+---------+
111111
| CAN message |
112112
+----------------+---------+
113113
| Arbitration ID | Payload |
114114
+================+=========+
115-
| 0x20000001 (1) | None |
115+
| 0x80000001 (1) | None |
116116
+----------------+---------+
117117

118118
+----------------+---------------------+------+---------------------+--------------+
119119
| Serial frame |
120120
+----------------+---------------------+------+---------------------+--------------+
121121
| Start of frame | Timestamp | DLC | Arbitration ID | End of frame |
122122
+================+=====================+======+=====================+==============+
123-
| 0xAA | 0x66 0x73 0x00 0x00 | 0x00 | 0x01 0x00 0x00 0x20 | 0xBB |
123+
| 0xAA | 0x66 0x73 0x00 0x00 | 0x00 | 0x01 0x00 0x00 0x80 | 0xBB |
124124
+----------------+---------------------+------+---------------------+--------------+

test/serial_test.py

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -84,38 +84,38 @@ def test_rx_tx_data_none(self):
8484
msg_receive = self.bus.recv()
8585
self.assertMessageEqual(msg, msg_receive)
8686

87-
def test_rx_tx_min_id(self):
87+
def test_rx_tx_min_std_id(self):
8888
"""
89-
Tests the transfer with the lowest extended arbitration id
89+
Tests the transfer with the lowest standard arbitration id
9090
"""
91-
msg = can.Message(arbitration_id=0)
91+
msg = can.Message(arbitration_id=0, is_extended_id=False)
9292
self.bus.send(msg)
9393
msg_receive = self.bus.recv()
9494
self.assertMessageEqual(msg, msg_receive)
9595

96-
def test_rx_tx_max_id(self):
96+
def test_rx_tx_max_std_id(self):
9797
"""
98-
Tests the transfer with the highest extended arbitration id
98+
Tests the transfer with the highest standard arbitration id
9999
"""
100-
msg = can.Message(arbitration_id=536870911)
100+
msg = can.Message(arbitration_id=0x7FF, is_extended_id=False)
101101
self.bus.send(msg)
102102
msg_receive = self.bus.recv()
103103
self.assertMessageEqual(msg, msg_receive)
104104

105-
def test_rx_tx_min_nonext_id(self):
105+
def test_rx_tx_min_ext_id(self):
106106
"""
107-
Tests the transfer with the lowest non-extended arbitration id
107+
Tests the transfer with the lowest extended arbitration id
108108
"""
109-
msg = can.Message(arbitration_id=0x000, is_extended_id=False)
109+
msg = can.Message(arbitration_id=0x000, is_extended_id=True)
110110
self.bus.send(msg)
111111
msg_receive = self.bus.recv()
112112
self.assertMessageEqual(msg, msg_receive)
113113

114-
def test_rx_tx_max_nonext_id(self):
114+
def test_rx_tx_max_ext_id(self):
115115
"""
116-
Tests the transfer with the highest non-extended arbitration id
116+
Tests the transfer with the highest extended arbitration id
117117
"""
118-
msg = can.Message(arbitration_id=0x7FF, is_extended_id=False)
118+
msg = can.Message(arbitration_id=0x1FFFFFFF, is_extended_id=True)
119119
self.bus.send(msg)
120120
msg_receive = self.bus.recv()
121121
self.assertMessageEqual(msg, msg_receive)
@@ -155,6 +155,28 @@ def test_rx_tx_min_timestamp_error(self):
155155
msg = can.Message(timestamp=-1)
156156
self.assertRaises(ValueError, self.bus.send, msg)
157157

158+
def test_rx_tx_err_frame(self):
159+
"""
160+
Test the transfer of error frames.
161+
"""
162+
msg = can.Message(
163+
is_extended_id=False, is_error_frame=True, is_remote_frame=False
164+
)
165+
self.bus.send(msg)
166+
msg_receive = self.bus.recv()
167+
self.assertMessageEqual(msg, msg_receive)
168+
169+
def test_rx_tx_rtr_frame(self):
170+
"""
171+
Test the transfer of remote frames.
172+
"""
173+
msg = can.Message(
174+
is_extended_id=False, is_error_frame=False, is_remote_frame=True
175+
)
176+
self.bus.send(msg)
177+
msg_receive = self.bus.recv()
178+
self.assertMessageEqual(msg, msg_receive)
179+
158180
def test_when_no_fileno(self):
159181
"""
160182
Tests for the fileno method catching the missing pyserial implementeation on the Windows platform

0 commit comments

Comments
 (0)