Keep the colleague's microbu-esp32c5 tree in this repository
obu-firmware builds against vanetza-idf from microbu-esp32c5/external, but that tree was gitignored, so a clone of this repository could not build the firmware it ships. It is now committed here as ordinary files in its own folder, microbu-esp32c5/: the colleague's commit cf4b99f plus the V2X2MAP bridge's signature verification (--trust) used on the bench. Nothing is fetched from or pushed to the colleague's repository; this repository and its remotes carry everything. The folder's own .gitignore keeps build output, downloaded components and private key material out, as it did there; the committed file set is identical to that repository's tracked files. The ESP32-C5 is still flashed from obu-firmware/, which only takes vanetza-idf from microbu-esp32c5/, so the two stay separate folders. FLASHING.md says how to take a newer version of the colleague's tree (copy it over the folder, rebuild, test, commit).
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
"""Serial transport of the station-internal link (the app's Phase 03 framing).
|
||||
|
||||
``[AA][55][type][length LE][payload][crc16 LE]``, CRC-16/CCITT-FALSE over type+length+payload.
|
||||
Frame types: 0x10 link message, 0x11 test channel, 0x7F log line. Requires pyserial.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import enum
|
||||
import struct
|
||||
import threading
|
||||
from typing import Callable, List, Tuple
|
||||
|
||||
MAXIMUM_PAYLOAD = 1536
|
||||
|
||||
|
||||
class FrameType(enum.IntEnum):
|
||||
LINK = 0x10
|
||||
TEST = 0x11
|
||||
LOG = 0x7F
|
||||
|
||||
|
||||
def crc16_ccitt_false(data: bytes) -> int:
|
||||
crc = 0xFFFF
|
||||
for octet in data:
|
||||
crc ^= octet << 8
|
||||
for _ in range(8):
|
||||
crc = ((crc << 1) ^ 0x1021) & 0xFFFF if crc & 0x8000 else (crc << 1) & 0xFFFF
|
||||
return crc
|
||||
|
||||
|
||||
def encode_frame(frame_type: int, payload: bytes) -> bytes:
|
||||
if len(payload) > MAXIMUM_PAYLOAD:
|
||||
raise ValueError('frame payload exceeds %d octets' % MAXIMUM_PAYLOAD)
|
||||
head = struct.pack('<BH', int(frame_type), len(payload)) + payload
|
||||
return b'\xaa\x55' + head + struct.pack('<H', crc16_ccitt_false(head))
|
||||
|
||||
|
||||
class FrameDecoder:
|
||||
"""Byte-at-a-time state machine, the app's SerialFrameDecoder with the larger payload limit."""
|
||||
|
||||
def __init__(self):
|
||||
self.state = 'SYNC0'
|
||||
self.type = 0
|
||||
self.length = 0
|
||||
self.payload = bytearray()
|
||||
self.crc = 0
|
||||
self.crc_errors = 0
|
||||
self.frames = 0
|
||||
|
||||
def feed(self, data: bytes) -> List[Tuple[int, bytes]]:
|
||||
out = []
|
||||
for b in data:
|
||||
s = self.state
|
||||
if s == 'SYNC0':
|
||||
self.state = 'SYNC1' if b == 0xAA else 'SYNC0'
|
||||
elif s == 'SYNC1':
|
||||
self.state = 'TYPE' if b == 0x55 else ('SYNC1' if b == 0xAA else 'SYNC0')
|
||||
elif s == 'TYPE':
|
||||
self.type = b
|
||||
self.state = 'LEN_LO'
|
||||
elif s == 'LEN_LO':
|
||||
self.length = b
|
||||
self.state = 'LEN_HI'
|
||||
elif s == 'LEN_HI':
|
||||
self.length |= b << 8
|
||||
self.payload = bytearray()
|
||||
if self.length > MAXIMUM_PAYLOAD:
|
||||
self.state = 'SYNC0'
|
||||
else:
|
||||
self.state = 'CRC_LO' if self.length == 0 else 'PAYLOAD'
|
||||
elif s == 'PAYLOAD':
|
||||
self.payload.append(b)
|
||||
if len(self.payload) >= self.length:
|
||||
self.state = 'CRC_LO'
|
||||
elif s == 'CRC_LO':
|
||||
self.crc = b
|
||||
self.state = 'CRC_HI'
|
||||
elif s == 'CRC_HI':
|
||||
self.crc |= b << 8
|
||||
head = struct.pack('<BH', self.type, self.length) + bytes(self.payload)
|
||||
if crc16_ccitt_false(head) == self.crc:
|
||||
self.frames += 1
|
||||
out.append((self.type, bytes(self.payload)))
|
||||
else:
|
||||
self.crc_errors += 1
|
||||
self.state = 'SYNC0'
|
||||
return out
|
||||
|
||||
|
||||
class SerialTransport:
|
||||
"""Reader thread plus a locked writer over one pyserial port."""
|
||||
|
||||
def __init__(self, port: str, on_frame: Callable[[int, bytes], None], baudrate: int = 115200):
|
||||
import serial
|
||||
self.port = serial.Serial(port=None, baudrate=baudrate, timeout=0.05, write_timeout=2)
|
||||
# do not toggle DTR/RTS: the USB Serial/JTAG controller would reset the board
|
||||
self.port.dtr = False
|
||||
self.port.rts = False
|
||||
self.port.port = port
|
||||
self.port.open()
|
||||
self.on_frame = on_frame
|
||||
self.decoder = FrameDecoder()
|
||||
self.write_lock = threading.Lock()
|
||||
self.running = True
|
||||
self.thread = threading.Thread(target=self._reader, name='microbu-link-rx', daemon=True)
|
||||
self.thread.start()
|
||||
|
||||
def _reader(self):
|
||||
while self.running:
|
||||
try:
|
||||
data = self.port.read(4096)
|
||||
except Exception:
|
||||
if self.running:
|
||||
continue
|
||||
return
|
||||
if data:
|
||||
for frame_type, payload in self.decoder.feed(data):
|
||||
try:
|
||||
self.on_frame(frame_type, payload)
|
||||
except Exception as error: # a handler failure must not stop the reader
|
||||
print('frame handler error:', error)
|
||||
|
||||
def write(self, frame_type: int, payload: bytes):
|
||||
frame = encode_frame(frame_type, payload)
|
||||
with self.write_lock:
|
||||
if self.port.write(frame) != len(frame):
|
||||
raise IOError('incomplete serial write')
|
||||
|
||||
def close(self):
|
||||
self.running = False
|
||||
try:
|
||||
self.port.cancel_read()
|
||||
except Exception:
|
||||
pass
|
||||
self.thread.join(timeout=1)
|
||||
self.port.close()
|
||||
Reference in New Issue
Block a user