cantcp 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cantcp/__init__.py +74 -0
- cantcp/crc8.py +39 -0
- cantcp/crc_cover.py +15 -0
- cantcp/decoder.py +138 -0
- cantcp/encoder.py +87 -0
- cantcp/errors.py +85 -0
- cantcp/flags.py +31 -0
- cantcp/frame.py +340 -0
- cantcp/logger.py +14 -0
- cantcp/parser.py +269 -0
- cantcp/policy.py +15 -0
- cantcp/py.typed +0 -0
- cantcp/stats.py +34 -0
- cantcp/type.py +22 -0
- cantcp/validate.py +29 -0
- cantcp-0.1.0.dist-info/METADATA +837 -0
- cantcp-0.1.0.dist-info/RECORD +19 -0
- cantcp-0.1.0.dist-info/WHEEL +4 -0
- cantcp-0.1.0.dist-info/licenses/LICENSE +21 -0
cantcp/__init__.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
r"""Python library for the cantcp protocol.
|
|
2
|
+
|
|
3
|
+
The package carries CAN frames only: stream framing of raw SocketCAN frames
|
|
4
|
+
in a byte stream, plus parsing, encoding and decoding of Linux SocketCAN
|
|
5
|
+
frames. Connection setup is plain TCP: handshakes, subscriptions, keepalives,
|
|
6
|
+
statistics and health checks are not part of the protocol.
|
|
7
|
+
|
|
8
|
+
Quick start:
|
|
9
|
+
|
|
10
|
+
>>> import io
|
|
11
|
+
>>> import cantcp
|
|
12
|
+
>>> stream = io.BytesIO()
|
|
13
|
+
>>> frame = cantcp.Frame(type=cantcp.Type.CLASSIC, id=0x123, data=bytes.fromhex("dead"))
|
|
14
|
+
>>> cantcp.Encoder(stream).encode_frame(frame)
|
|
15
|
+
>>> print(next(iter(cantcp.Decoder(io.BytesIO(stream.getvalue())))))
|
|
16
|
+
Frame{Type:CAN, ID:0x123, Flags:none, DLC:2, Data:dead}
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
22
|
+
|
|
23
|
+
from cantcp.crc_cover import CrcCover
|
|
24
|
+
from cantcp.decoder import Decoder
|
|
25
|
+
from cantcp.encoder import Encoder
|
|
26
|
+
from cantcp.errors import (
|
|
27
|
+
BadDLCError,
|
|
28
|
+
BadFlagsError,
|
|
29
|
+
BadIDError,
|
|
30
|
+
BadLenError,
|
|
31
|
+
BadTypeError,
|
|
32
|
+
CantcpError,
|
|
33
|
+
FrameLenError,
|
|
34
|
+
ReservedError,
|
|
35
|
+
ShortWriteError,
|
|
36
|
+
TruncatedError,
|
|
37
|
+
)
|
|
38
|
+
from cantcp.flags import Flag
|
|
39
|
+
from cantcp.frame import Frame
|
|
40
|
+
from cantcp.logger import TRACE
|
|
41
|
+
from cantcp.parser import Parser
|
|
42
|
+
from cantcp.policy import BadFramePolicy
|
|
43
|
+
from cantcp.stats import Stats
|
|
44
|
+
from cantcp.type import Type
|
|
45
|
+
from cantcp.validate import validate_raw
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
__version__ = version("cantcp")
|
|
49
|
+
except PackageNotFoundError: # pragma: no cover - the package is not installed
|
|
50
|
+
__version__ = "0.0.0"
|
|
51
|
+
|
|
52
|
+
__all__ = [
|
|
53
|
+
"TRACE",
|
|
54
|
+
"BadDLCError",
|
|
55
|
+
"BadFlagsError",
|
|
56
|
+
"BadFramePolicy",
|
|
57
|
+
"BadIDError",
|
|
58
|
+
"BadLenError",
|
|
59
|
+
"BadTypeError",
|
|
60
|
+
"CantcpError",
|
|
61
|
+
"CrcCover",
|
|
62
|
+
"Decoder",
|
|
63
|
+
"Encoder",
|
|
64
|
+
"Flag",
|
|
65
|
+
"Frame",
|
|
66
|
+
"FrameLenError",
|
|
67
|
+
"Parser",
|
|
68
|
+
"ReservedError",
|
|
69
|
+
"ShortWriteError",
|
|
70
|
+
"Stats",
|
|
71
|
+
"TruncatedError",
|
|
72
|
+
"Type",
|
|
73
|
+
"validate_raw",
|
|
74
|
+
]
|
cantcp/crc8.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Table-driven CRC-8 with a configurable polynomial.
|
|
2
|
+
|
|
3
|
+
The default polynomial 0x07 is CRC-8/SMBus: init 0x00, no input or output
|
|
4
|
+
reflection, no final XOR. The table is built per instance, nothing is shared
|
|
5
|
+
globally.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
_TABLE_SIZE = 256
|
|
11
|
+
_BITS_PER_BYTE = 8
|
|
12
|
+
_BYTE_MASK = 0xFF
|
|
13
|
+
_TOP_BIT = 0x80
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class _CRC8:
|
|
17
|
+
"""A CRC-8 calculator with its own lookup table."""
|
|
18
|
+
|
|
19
|
+
__slots__ = ("_table",)
|
|
20
|
+
|
|
21
|
+
def __init__(self, poly: int) -> None:
|
|
22
|
+
table = []
|
|
23
|
+
for index in range(_TABLE_SIZE):
|
|
24
|
+
value = index
|
|
25
|
+
for _ in range(_BITS_PER_BYTE):
|
|
26
|
+
if value & _TOP_BIT:
|
|
27
|
+
value = (value << 1) ^ poly
|
|
28
|
+
else:
|
|
29
|
+
value <<= 1
|
|
30
|
+
value &= _BYTE_MASK
|
|
31
|
+
table.append(value)
|
|
32
|
+
self._table = tuple(table)
|
|
33
|
+
|
|
34
|
+
def sum(self, data: bytes) -> int:
|
|
35
|
+
"""Return the CRC-8 of ``data``."""
|
|
36
|
+
value = 0
|
|
37
|
+
for byte in data:
|
|
38
|
+
value = self._table[value ^ byte]
|
|
39
|
+
return value
|
cantcp/crc_cover.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Which part of a packet the CRC-8 covers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from enum import Enum
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CrcCover(Enum):
|
|
9
|
+
"""The byte span covered by the CRC-8 of a packet."""
|
|
10
|
+
|
|
11
|
+
HEADER = "magic+type+frame"
|
|
12
|
+
"""Magic, packet type byte and the raw frame (the default)."""
|
|
13
|
+
|
|
14
|
+
FRAME = "frame"
|
|
15
|
+
"""The raw frame only."""
|
cantcp/decoder.py
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""The Decoder object: reading framed CAN frames from a binary stream."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from collections.abc import Iterator
|
|
7
|
+
from typing import BinaryIO
|
|
8
|
+
|
|
9
|
+
from cantcp.crc_cover import CrcCover
|
|
10
|
+
from cantcp.errors import CantcpError
|
|
11
|
+
from cantcp.frame import Frame
|
|
12
|
+
from cantcp.parser import _FD_PACKET_LEN, Parser
|
|
13
|
+
from cantcp.policy import BadFramePolicy
|
|
14
|
+
from cantcp.stats import Stats
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Decoder:
|
|
18
|
+
"""Read framed CAN packets from a binary stream.
|
|
19
|
+
|
|
20
|
+
[magic 2 bytes][type 1 byte][struct can_frame 16 | canfd_frame 72][CRC-8 1]
|
|
21
|
+
|
|
22
|
+
Garbage and packets rejected by the CRC are dropped, stream counters are
|
|
23
|
+
collected and every call returns one raw frame. A decoder is not safe for
|
|
24
|
+
concurrent use: use one decoder per stream.
|
|
25
|
+
|
|
26
|
+
>>> import io
|
|
27
|
+
>>> raw = bytes(16)
|
|
28
|
+
>>> decoder = Decoder(io.BytesIO(Parser().encode(raw)))
|
|
29
|
+
>>> decoder.decode() == raw
|
|
30
|
+
True
|
|
31
|
+
>>> decoder.decode() is None
|
|
32
|
+
True
|
|
33
|
+
|
|
34
|
+
Iteration yields parsed :class:`cantcp.Frame` objects:
|
|
35
|
+
|
|
36
|
+
>>> import io
|
|
37
|
+
>>> stream = io.BytesIO(Parser().encode(bytes(16)))
|
|
38
|
+
>>> print(next(iter(Decoder(stream))))
|
|
39
|
+
Frame{Type:CAN, ID:0x0, Flags:none, DLC:0, Data:}
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
__slots__ = ("_buffer", "_failure", "_parser", "_reader")
|
|
43
|
+
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
reader: BinaryIO,
|
|
47
|
+
*,
|
|
48
|
+
magic: bytes = b"\xc3\x3c",
|
|
49
|
+
crc_poly: int = 0x07,
|
|
50
|
+
crc_cover: CrcCover = CrcCover.HEADER,
|
|
51
|
+
bad_frame_policy: BadFramePolicy = BadFramePolicy.SKIP,
|
|
52
|
+
logger: logging.Logger | None = None,
|
|
53
|
+
log_level: int = logging.INFO,
|
|
54
|
+
) -> None:
|
|
55
|
+
"""Create a decoder reading from ``reader``.
|
|
56
|
+
|
|
57
|
+
Options configure the internal parser exactly like
|
|
58
|
+
:class:`cantcp.Parser`. The reader is owned by the caller: the decoder
|
|
59
|
+
never closes it.
|
|
60
|
+
"""
|
|
61
|
+
self._reader = reader
|
|
62
|
+
self._parser = Parser(
|
|
63
|
+
magic=magic,
|
|
64
|
+
crc_poly=crc_poly,
|
|
65
|
+
crc_cover=crc_cover,
|
|
66
|
+
bad_frame_policy=bad_frame_policy,
|
|
67
|
+
logger=logger,
|
|
68
|
+
log_level=log_level,
|
|
69
|
+
)
|
|
70
|
+
self._buffer = bytearray()
|
|
71
|
+
self._failure: CantcpError | None = None
|
|
72
|
+
|
|
73
|
+
def decode(self) -> bytes | None:
|
|
74
|
+
"""Return the next raw frame or ``None`` at the end of the stream.
|
|
75
|
+
|
|
76
|
+
The returned bytes are independent of the decoder buffer and stay
|
|
77
|
+
valid after the next call. A packet cut off in the middle raises
|
|
78
|
+
:class:`cantcp.TruncatedError`; errors of the underlying reader are
|
|
79
|
+
propagated unchanged. After a terminal error every call raises the
|
|
80
|
+
same error, like a spent scanner.
|
|
81
|
+
"""
|
|
82
|
+
return self._next_raw()
|
|
83
|
+
|
|
84
|
+
def decode_frame(self) -> Frame | None:
|
|
85
|
+
"""Return the next frame parsed into a :class:`cantcp.Frame`.
|
|
86
|
+
|
|
87
|
+
The parsing is as strict as :meth:`cantcp.Frame.from_raw`: a CAN FD
|
|
88
|
+
length the 4-bit DLC field cannot encode raises
|
|
89
|
+
:class:`cantcp.BadLenError` even though the splitter accepted the
|
|
90
|
+
packet. ``None`` reports the end of the stream.
|
|
91
|
+
"""
|
|
92
|
+
raw = self._next_raw()
|
|
93
|
+
if raw is None:
|
|
94
|
+
return None
|
|
95
|
+
return Frame.from_raw(raw)
|
|
96
|
+
|
|
97
|
+
def __iter__(self) -> Iterator[Frame]:
|
|
98
|
+
"""Iterate over the frames of the stream."""
|
|
99
|
+
return self
|
|
100
|
+
|
|
101
|
+
def __next__(self) -> Frame:
|
|
102
|
+
"""Return the next frame or raise :class:`StopIteration` at the end."""
|
|
103
|
+
frame = self.decode_frame()
|
|
104
|
+
if frame is None:
|
|
105
|
+
raise StopIteration
|
|
106
|
+
return frame
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def stats(self) -> Stats:
|
|
110
|
+
"""A copy of the stream counters."""
|
|
111
|
+
return self._parser.stats
|
|
112
|
+
|
|
113
|
+
def reset_stats(self) -> None:
|
|
114
|
+
"""Zero the stream counters."""
|
|
115
|
+
self._parser.reset_stats()
|
|
116
|
+
|
|
117
|
+
def _next_raw(self) -> bytes | None:
|
|
118
|
+
"""Return the next raw frame, reading from the stream as needed."""
|
|
119
|
+
if self._failure is not None:
|
|
120
|
+
raise self._failure
|
|
121
|
+
final = False
|
|
122
|
+
while True:
|
|
123
|
+
try:
|
|
124
|
+
advance, token = self._parser.split(bytes(self._buffer), final)
|
|
125
|
+
except CantcpError as exc:
|
|
126
|
+
self._failure = exc
|
|
127
|
+
raise
|
|
128
|
+
if advance:
|
|
129
|
+
del self._buffer[:advance]
|
|
130
|
+
if token is not None:
|
|
131
|
+
return token
|
|
132
|
+
if final:
|
|
133
|
+
return None
|
|
134
|
+
chunk = self._reader.read(_FD_PACKET_LEN)
|
|
135
|
+
if not chunk:
|
|
136
|
+
final = True
|
|
137
|
+
continue
|
|
138
|
+
self._buffer.extend(chunk)
|
cantcp/encoder.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""The Encoder object: writing framed CAN frames to a binary stream."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import BinaryIO
|
|
7
|
+
|
|
8
|
+
from cantcp.crc_cover import CrcCover
|
|
9
|
+
from cantcp.errors import ShortWriteError
|
|
10
|
+
from cantcp.frame import Frame
|
|
11
|
+
from cantcp.parser import Parser
|
|
12
|
+
from cantcp.policy import BadFramePolicy
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Encoder:
|
|
16
|
+
"""Write framed CAN packets to a binary stream.
|
|
17
|
+
|
|
18
|
+
[magic 2 bytes][type 1 byte][struct can_frame 16 | canfd_frame 72][CRC-8 1]
|
|
19
|
+
|
|
20
|
+
The encoder wraps :meth:`cantcp.Parser.encode`: the frame length selects
|
|
21
|
+
the packet type, the CRC-8 and its coverage follow the parser
|
|
22
|
+
configuration, so a packet written by an encoder is always accepted by a
|
|
23
|
+
decoder created with the same options. An encoder is not safe for
|
|
24
|
+
concurrent use: use one encoder per stream.
|
|
25
|
+
|
|
26
|
+
>>> import io
|
|
27
|
+
>>> stream = io.BytesIO()
|
|
28
|
+
>>> Encoder(stream).encode(bytes(16))
|
|
29
|
+
>>> stream.getvalue().hex()
|
|
30
|
+
'c33c010000000000000000000000000000000064'
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
__slots__ = ("_parser", "_writer")
|
|
34
|
+
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
writer: BinaryIO,
|
|
38
|
+
*,
|
|
39
|
+
magic: bytes = b"\xc3\x3c",
|
|
40
|
+
crc_poly: int = 0x07,
|
|
41
|
+
crc_cover: CrcCover = CrcCover.HEADER,
|
|
42
|
+
bad_frame_policy: BadFramePolicy = BadFramePolicy.SKIP,
|
|
43
|
+
logger: logging.Logger | None = None,
|
|
44
|
+
log_level: int = logging.INFO,
|
|
45
|
+
) -> None:
|
|
46
|
+
"""Create an encoder writing to ``writer``.
|
|
47
|
+
|
|
48
|
+
Options configure the internal parser exactly like
|
|
49
|
+
:class:`cantcp.Parser`; ``bad_frame_policy``, ``logger`` and
|
|
50
|
+
``log_level`` are accepted for symmetry with the reader side and do
|
|
51
|
+
not affect writing. The writer is owned by the caller: the encoder
|
|
52
|
+
never closes it.
|
|
53
|
+
"""
|
|
54
|
+
self._writer = writer
|
|
55
|
+
self._parser = Parser(
|
|
56
|
+
magic=magic,
|
|
57
|
+
crc_poly=crc_poly,
|
|
58
|
+
crc_cover=crc_cover,
|
|
59
|
+
bad_frame_policy=bad_frame_policy,
|
|
60
|
+
logger=logger,
|
|
61
|
+
log_level=log_level,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
def encode(self, frame: bytes) -> None:
|
|
65
|
+
"""Write one packet carrying the raw ``frame``.
|
|
66
|
+
|
|
67
|
+
Validation errors of :meth:`cantcp.Parser.encode`
|
|
68
|
+
(:class:`cantcp.FrameLenError`, :class:`cantcp.BadDLCError`,
|
|
69
|
+
:class:`cantcp.BadLenError`) leave the stream untouched. A write that
|
|
70
|
+
stored fewer bytes than requested raises
|
|
71
|
+
:class:`cantcp.ShortWriteError`; a write error is propagated unchanged
|
|
72
|
+
and leaves the stream in an unknown state.
|
|
73
|
+
"""
|
|
74
|
+
packet = self._parser.encode(frame)
|
|
75
|
+
written = self._writer.write(packet)
|
|
76
|
+
if written is None or written < len(packet):
|
|
77
|
+
raise ShortWriteError()
|
|
78
|
+
|
|
79
|
+
def encode_frame(self, frame: Frame) -> None:
|
|
80
|
+
"""Write one packet carrying ``frame``.
|
|
81
|
+
|
|
82
|
+
The frame is marshalled with :meth:`cantcp.Frame.to_raw`; its errors
|
|
83
|
+
(:class:`cantcp.BadTypeError`, :class:`cantcp.BadIDError`,
|
|
84
|
+
:class:`cantcp.BadFlagsError`, :class:`cantcp.BadDLCError`,
|
|
85
|
+
:class:`cantcp.BadLenError`) leave the stream untouched.
|
|
86
|
+
"""
|
|
87
|
+
self.encode(frame.to_raw())
|
cantcp/errors.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""Errors raised by the cantcp package.
|
|
2
|
+
|
|
3
|
+
Every protocol error is a subclass of :class:`CantcpError` with a static
|
|
4
|
+
message; the texts are identical to the Go implementation of the library, so
|
|
5
|
+
logs of both languages match. Catch a concrete class with ``except`` or the
|
|
6
|
+
base class to handle all of them:
|
|
7
|
+
|
|
8
|
+
from cantcp import BadDLCError, CantcpError
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
frame.to_raw()
|
|
12
|
+
except BadDLCError as exc:
|
|
13
|
+
print(exc) # cantcp: can_dlc > 8
|
|
14
|
+
except CantcpError:
|
|
15
|
+
... # any other protocol error
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class CantcpError(Exception):
|
|
22
|
+
"""Base class for all errors raised by the cantcp package."""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class FrameLenError(CantcpError):
|
|
26
|
+
"""A frame is neither 16 (can_frame) nor 72 (canfd_frame) bytes."""
|
|
27
|
+
|
|
28
|
+
def __init__(self) -> None:
|
|
29
|
+
super().__init__("cantcp: frame length is not 16 or 72 bytes")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class BadDLCError(CantcpError):
|
|
33
|
+
"""A classic can_frame whose can_dlc is greater than 8."""
|
|
34
|
+
|
|
35
|
+
def __init__(self) -> None:
|
|
36
|
+
super().__init__("cantcp: can_dlc > 8")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class BadLenError(CantcpError):
|
|
40
|
+
"""An invalid CAN FD length: greater than 64 or not encodable in DLC."""
|
|
41
|
+
|
|
42
|
+
def __init__(self) -> None:
|
|
43
|
+
super().__init__("cantcp: invalid canfd length")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class BadTypeError(CantcpError):
|
|
47
|
+
"""An unknown frame type: not classic CAN and not CAN FD."""
|
|
48
|
+
|
|
49
|
+
def __init__(self) -> None:
|
|
50
|
+
super().__init__("cantcp: unknown frame type")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class BadFlagsError(CantcpError):
|
|
54
|
+
"""Flags that are not valid for the frame type."""
|
|
55
|
+
|
|
56
|
+
def __init__(self) -> None:
|
|
57
|
+
super().__init__("cantcp: flags are not valid for the frame type")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class BadIDError(CantcpError):
|
|
61
|
+
"""An identifier that does not fit the addressing mode."""
|
|
62
|
+
|
|
63
|
+
def __init__(self) -> None:
|
|
64
|
+
super().__init__("cantcp: identifier does not fit the addressing mode")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class ReservedError(CantcpError):
|
|
68
|
+
"""Non-zero reserved or padding bytes of a frame."""
|
|
69
|
+
|
|
70
|
+
def __init__(self) -> None:
|
|
71
|
+
super().__init__("cantcp: reserved bytes are not zero")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class TruncatedError(CantcpError):
|
|
75
|
+
"""An incomplete packet at the end of the stream."""
|
|
76
|
+
|
|
77
|
+
def __init__(self) -> None:
|
|
78
|
+
super().__init__("cantcp: stream truncated in the middle of a packet")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class ShortWriteError(CantcpError):
|
|
82
|
+
"""A write wrote fewer bytes than requested."""
|
|
83
|
+
|
|
84
|
+
def __init__(self) -> None:
|
|
85
|
+
super().__init__("cantcp: short write")
|
cantcp/flags.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""The Flag bits of a Frame."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from enum import IntFlag
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Flag(IntFlag):
|
|
9
|
+
"""Flag bits of a Frame, combined with bitwise OR.
|
|
10
|
+
|
|
11
|
+
``EFF``, ``RTR`` and ``ERR`` come from the CAN identifier word; ``BRS``
|
|
12
|
+
and ``ESI`` come from the CAN FD flags byte. The constants are the
|
|
13
|
+
argument of :meth:`cantcp.Frame.set_flags`:
|
|
14
|
+
|
|
15
|
+
frame.set_flags(Flag.EFF | Flag.RTR)
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
EFF = 1 << 0
|
|
19
|
+
"""Extended (29-bit) identifier."""
|
|
20
|
+
|
|
21
|
+
RTR = 1 << 1
|
|
22
|
+
"""Remote transmission request (classic CAN only)."""
|
|
23
|
+
|
|
24
|
+
ERR = 1 << 2
|
|
25
|
+
"""Error frame (carries no addressing mode)."""
|
|
26
|
+
|
|
27
|
+
BRS = 1 << 3
|
|
28
|
+
"""CAN FD bit rate switch."""
|
|
29
|
+
|
|
30
|
+
ESI = 1 << 4
|
|
31
|
+
"""CAN FD error state indicator."""
|