Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions pyxcp/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
TraitError,
Unicode,
Union,
validate,
)
from traitlets.config import Application, Configurable, Instance, default
from traitlets.config.loader import Config
Expand Down Expand Up @@ -747,9 +748,26 @@ class SxI(Configurable):
tail_format = Enum(
values=["NO_CHECKSUM", "CHECKSUM_BYTE", "CHECKSUM_WORD"], default_value="NO_CHECKSUM", help="XCPonSxI tail format."
).tag(config=True)
framing = Bool(False, help="Enable SCI framing mechanism (ESC chars).").tag(config=True)
esc_sync = Integer(0x01, min=0, max=255, help="SCI framing protocol character SYNC.").tag(config=True)
esc_esc = Integer(0x00, min=0, max=255, help="SCI framing protocol character ESC.").tag(config=True)
framing = Enum(
values=["NO_FRAMING", "FRAMING", "IMPROVED_FRAMING"],
default_value="NO_FRAMING",
help="""SCI framing mechanism:

NO_FRAMING: framing fully disabled.
FRAMING: a SYNC char is prepended to each frame
IMPROVED_FRAMING: same like FRAMING and additionally inside a frame following replacement rules are applied for improved frame detection: SYNC -> ESC+ESC_SYNC and ESC -> ESC+ESC_ESC
""",
).tag(config=True)

@validate("framing")
def _validate_framing(self, proposal):
framing = proposal["value"]
if framing != "NO_FRAMING" and self.mode != "ASYNCH_FULL_DUPLEX_MODE":
raise TraitError("Framing can only be used in asynchronous (SCI) full duplex mode.")
return framing

sync = Integer(0x01, min=0, max=255, help="SCI framing protocol character SYNC.").tag(config=True)
esc = Integer(0x00, min=0, max=255, help="SCI framing protocol character ESC.").tag(config=True)


class Usb(Configurable):
Expand Down
36 changes: 34 additions & 2 deletions pyxcp/transport/sxi.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import enum
import struct
from collections import deque
from dataclasses import dataclass
Expand All @@ -7,6 +8,7 @@

import pyxcp.types as types
from pyxcp.transport.base import BaseTransport
from pyxcp.utils import hexDump


@dataclass
Expand All @@ -16,6 +18,11 @@ class HeaderValues:
filler: int = 0


class ESC(enum.IntEnum):
ESC_ESC = 0x00
ESC_SYNC = 0x01


RECV_SIZE = 16384


Expand All @@ -34,8 +41,8 @@ def __init__(self, config=None, policy=None, transport_layer_interface: Optional
self.header_format = self.config.header_format
self.tail_format = self.config.tail_format
self.framing = self.config.framing
self.esc_sync = self.config.esc_sync
self.esc_esc = self.config.esc_esc
self.sync = self.config.sync
self.esc = self.config.esc
self.make_header()
self.comm_port: serial.Serial

Expand Down Expand Up @@ -87,6 +94,21 @@ def unpack_len_filler(args):
self.HEADER_SIZE = self.HEADER.size
self.unpacker = unpacker

def handle_framing(self, frame):

if self.framing == "IMPROVED_FRAMING":

# Replace SYNC by ESC+ESC_SYNC
frame = b"".join(bytes([self.esc]) + bytes([ESC.ESC_SYNC]) if byte == self.sync else bytes([byte]) for byte in frame)

# Replace ESC by ESC+ESC_ESC
frame = b"".join(bytes([self.esc]) + bytes([ESC.ESC_ESC]) if byte == self.esc else bytes([byte]) for byte in frame)

# Add sync char in front of each frame
frame = bytes([self.sync]) + frame

return frame

def connect(self) -> None:
self.logger.info(
f"XCPonSxI - serial comm_port {self.comm_port.portstr!r} openend [{self.baudrate}/{self.bytesize}-{self.parity}-{self.stopbits}]"
Expand Down Expand Up @@ -115,6 +137,12 @@ def listen(self) -> None:
continue

recv_timestamp = self.timestamp.value

if self.framing != "NO_FRAMING":
sync = self.comm_port.read(1) # first byte before a frame must always be the sync byte
if sync != bytes([self.sync]):
raise types.FrameStructureError("Frame Wrong sync byte received.")

header_values = self.unpacker(self.HEADER.unpack(self.comm_port.read(self.HEADER_SIZE)))
length, counter, _ = header_values.length, header_values.counter, header_values.filler

Expand All @@ -127,8 +155,12 @@ def listen(self) -> None:

def send(self, frame) -> None:
self.pre_send_timestamp = self.timestamp.value
if self.framing != "NO_FRAMING":
frame = self.handle_framing(frame)
self.comm_port.write(frame)
self.post_send_timestamp = self.timestamp.value
if self._debug:
self.logger.debug(f"XCPonSxI - Raw data -> {hexDump(frame)}")

def close_connection(self) -> None:
if hasattr(self, "comm_port") and self.comm_port.is_open and not self.has_user_supplied_interface:
Expand Down
6 changes: 6 additions & 0 deletions pyxcp/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ class FrameSizeError(Exception):
"""


class FrameStructureError(Exception):
"""
A frame with an invalid structure was received.
"""


class XcpResponseError(Exception):
"""
Raise an `exception` from an XCP error packet.
Expand Down