Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
fc81c9a59This file is pinned to the selected release. Download the original to open it in your design tool.
import serial
import time
import threading
import logging
logger = logging.getLogger()
class LINFrame:
"""LIN Frame class.
Attributes
----------
id: int
frame id
data: bytearray
frame data as byte array
"""
id = 0
data = bytearray()
class LUC:
def __def_frame_rx_handler(self, f):
pass
def __def_new_frame_rx_handler(self, f):
pass
def __init__(self, com, timeout=0.02, baudrate=9600):
self.lastframes = {}
self.currentframes = {}
self.ser = serial.Serial(com, timeout=timeout, baudrate=baudrate)
if self.ser is None:
raise NameError('SerialPortNotPresent')
self.ser.reset_output_buffer()
self.ser.reset_input_buffer()
self.__rx_byte_thread_handler = threading.Thread(target=self.__rxThread, args=())
self.__rx_frame_thread_handler = threading.Thread(target=self.__newFrameProcess, args=())
self.__uart_buffer = ''
self._stop_event = threading.Event()
# Guards serial writes; writes may come from the main thread and from
# rx handlers (which run in the frame-processing thread).
self.__serial_lock = threading.Lock()
# True once enable() has started the rx thread. While running, the rx
# thread owns all reads, so command methods must not call readline().
self.__running = False
# True once the port has been closed, so disable()/__del__ are idempotent.
self.__closed = False
self.frame_rx_handler = self.__def_frame_rx_handler
self.new_frame_rx_handler = self.__def_new_frame_rx_handler
def set_frame_rx_handler(self, rx_handler):
"""Sets handler for frame reception, will be called after any frame reception.
Parameters
----------
rx_handler : function handler, with LINFrame argument
"""
self.frame_rx_handler = rx_handler
def set_new_frame_rx_handler(self, rx_handler):
"""Sets handler for frame reception, will be called only when there is diffrent data on frame received.
Parameters
----------
rx_handler : function handler, with LINFrame argument
"""
self.new_frame_rx_handler = rx_handler
def flushData(self, data):
with self.__serial_lock:
self.ser.write(data)
logger.info(data)
def __command(self, data, expected):
"""Send a command and confirm its reply.
While the converter is running (after enable()) the rx thread owns all
serial reads, so the reply cannot be read here without racing it; in
that case the command is written and True is returned. Before enable()
the reply is read and compared against the expected response string.
"""
self.flushData(data)
if self.__running:
return True
return self.ser.readline().decode("utf-8") == expected
def requestFirmwareVersion(self):
self.flushData(b'v\r')
line = self.ser.readline().decode("utf-8")
return line.replace("v", "").replace("\r", "")
def openAsMaster(self):
"""Open LIN Converter in Master mode"""
return self.__command(b'O\r', '\r')
def lowSpeed(self):
"""LIN low speed"""
return self.__command(b'S2\r', '\r')
def highSpeed(self):
"""LIN high speed"""
return self.__command(b'S1\r', '\r')
def openAsSlave(self):
"""Open LIN Converter in slave mode"""
return self.__command(b'L\r', '\r')
def openAsMonitor(self):
"""Open LIN Converter in monitor mode"""
return self.__command(b'l\r', '\r')
def close(self):
"""Close LIN connection"""
return self.__command(b'C\r', '\r')
def addTransmitFrameToTable(self, id, data):
"""Add a transmit frame to the LIN Converter table.
In master/slave mode this registers the frame in the schedule table.
In monitor mode it transmits the frame immediately on the bus.
Parameters
----------
id : int
LIN frame id (0..0x3F)
data : bytes-like or int
Frame payload. Pass bytes (e.g. b'\\x22\\x33'); an int is
accepted and encoded as a big-endian byte string.
"""
return self.__command(self._format_transmit_line(id, data), 'z\r')
@staticmethod
def _format_transmit_line(id, data):
"""Build the 't' transmit command bytes for a frame.
Returns the SLCAN line, e.g. b't00122233\\r' for id=1, data=b'\\x22\\x33'.
"""
if isinstance(data, int):
data_str = format(data, 'x')
if len(data_str) % 2:
data_str = '0' + data_str
else:
data_str = bytes(data).hex()
data_len = len(data_str) // 2
line = ('t0' + format(id, '02x') + str(data_len) + data_str + '\r')
return line.encode()
def addReceptionFrameToTable(self, id, len):
"""Add a reception header to the LIN Converter table"""
line = ('r0' + format(id, '02x') + str(len) + '\r')
return self.__command(line.encode(), 'z\r')
def enable(self):
"""Enable frame sending"""
self.__running = True
self.flushData(b'r1ff0\r')
# r = (self.ser.readline().decode("utf-8") == 'z\r')
self.__rx_byte_thread_handler.start()
self.__rx_frame_thread_handler.start()
return 1
def disable(self):
"""Stop reception.
Idempotent: safe to call more than once and after deInitSerial().
"""
if self.__closed:
return False
if self.__rx_byte_thread_handler.is_alive():
self._stop_event.set()
self.__rx_byte_thread_handler.join()
self.__rx_frame_thread_handler.join()
# rx thread has stopped, so this method can safely read the reply again
self.__running = False
time.sleep(0.010)
# Discard any received frames still buffered so the reply read below is
# not contaminated by them.
self.ser.reset_input_buffer()
self.flushData(b'r2ff0\r')
return self.ser.readline().decode("utf-8") == 'z\r'
def __isStartingChar(self, c):
return (c == 't') or (c == 'r')
def __rxThread(self):
while True:
if self._stop_event.is_set():
break
while self.ser.in_waiting > 0:
cc = self.ser.read(self.ser.in_waiting).decode('ascii')
self.__uart_buffer += cc
time.sleep(0.001)
def __newFrameProcess(self):
while True:
if self._stop_event.is_set():
break
f = self.__waitForFrame()
if f is not None:
self.frame_rx_handler(f)
if (self.currentframes.get(f.id) is None) or (self.currentframes[f.id] != f.data):
self.currentframes[f.id] = f.data
self.new_frame_rx_handler(f)
def __waitForFrame(self):
while True:
if self._stop_event.is_set():
return None
if len(self.__uart_buffer) > 1000:
self.__uart_buffer = ''
start = self.__uart_buffer.find('t')
end = self.__uart_buffer.find('\r')
if start != -1 and end != -1:
if start > (end - 5):
self.__uart_buffer = self.__uart_buffer[start:]
else:
frame_string = self.__uart_buffer[start:end]
self.__uart_buffer = ''
lf = LINFrame()
try:
lf.id = int(frame_string[2:4], 16)
lf.data = bytes.fromhex(frame_string[5:])
return lf
except ValueError:
pass
# @TODO FIXME this should not occur but sometimes does ...
# JP: found this error when using default serial baudrate 9600. Changing it to 19200 fixed
# the issue for me. Added as optional argument to initialization.
# print ("!!id " + frame_string[2:4])
# print ("!!data " + frame_string[5:])
# print ("!!wf " + frame_string)
time.sleep(0.001)
def deInitSerial(self):
"""Close the serial port. Idempotent: safe to call more than once."""
if self.__closed:
return
self.__closed = True
try:
self.ser.flush()
except Exception:
pass
self.ser.close()
def isRunning(self):
"""Simple function that returns true if rx thread is alive. Useful for implementation of exception and
interruption (like KeyboardInterrupt) handling in scripts using LUC object.
"""
return self.__rx_byte_thread_handler.is_alive()
def __del__(self):
try:
self.disable()
except Exception:
pass
try:
self.deInitSerial()
except Exception:
pass