mirror of
https://github.com/OpenSolo/OpenSolo.git
synced 2025-04-30 14:44:31 +02:00
193 lines
6.0 KiB
Python
Executable File
193 lines
6.0 KiB
Python
Executable File
#!/usr/bin/env python
|
|
|
|
import datetime
|
|
import logging
|
|
import logging.config
|
|
import serial
|
|
|
|
configFileName = "/etc/sololink.conf"
|
|
|
|
logging.config.fileConfig(configFileName)
|
|
logger = logging.getLogger("slip")
|
|
|
|
|
|
|
|
class slip():
|
|
|
|
|
|
END = chr(0xc0) # indicates end of packet
|
|
ESC = chr(0xdb) # indicates byte stuffing
|
|
ESC_END = chr(0xdc) # ESC ESC_END means END data byte
|
|
ESC_ESC = chr(0xdd) # ESC ESC_ESC means ESC data byte
|
|
|
|
|
|
# This is really for detecting garbage input, and not passing huge junk to the
|
|
# stm32 message handlers. Intended to be much bigger than any real packet. If
|
|
# we get more than this much data without finding an END, we drop the packet
|
|
# and go back to looking for sync.
|
|
maxPktLen = 1024
|
|
|
|
|
|
def __init__(self, stream):
|
|
# I/O interface, provides s=read() and write(s)
|
|
self._stream = stream
|
|
self._inSync = False
|
|
# count of each reason we lost sync
|
|
self.desyncCounts = {}
|
|
self._desyncLogInterval = datetime.timedelta(seconds=1) # =None to never log
|
|
self._desyncLogLast = None
|
|
# receive counters
|
|
self.counts = { 'BYTE': 0, 'SYNC': 0, 'DROP': 0, 'ESC': 0 }
|
|
self._syncLogInterval = datetime.timedelta(seconds=1) # =None to never log
|
|
self._syncLogLast = None
|
|
|
|
|
|
# Lost sync. Functionally, we just want to set inSync False, but for analysis
|
|
# and debugging, we use a function so we can count things and log and such.
|
|
def desync(self, reason):
|
|
now = datetime.datetime.now()
|
|
self._inSync = False
|
|
# count various reasons for losing sync (debug)
|
|
if reason not in self.desyncCounts:
|
|
self.desyncCounts[reason] = 0
|
|
self.desyncCounts[reason] += 1
|
|
# periodically log (debug)
|
|
if (self._desyncLogInterval is not None) and \
|
|
((self._desyncLogLast is None) or \
|
|
((now - self._desyncLogLast) >= self._desyncLogInterval)):
|
|
logger.info("lost sync (desyncs = %s)", str(self.desyncCounts))
|
|
self._desyncLogLast = now
|
|
|
|
|
|
# Receive one SLIP-encoded packet. If there is a timeout
|
|
# waiting for the next character, any data received so far is dropped and
|
|
# (None, None) is returned. The timout is set when the port is opened.
|
|
def recv(self):
|
|
|
|
pktTime = None
|
|
pkt = []
|
|
|
|
if not self._inSync:
|
|
now = datetime.datetime.now()
|
|
self.counts['SYNC'] += 1
|
|
# periodically log
|
|
if (self._syncLogInterval is not None) and \
|
|
((self._syncLogLast is None) or \
|
|
((now - self._syncLogLast) >= self._syncLogInterval)):
|
|
logger.info("syncing, counts = %s", str(self.counts))
|
|
self._syncLogLast = now
|
|
# find the next END
|
|
while True:
|
|
b = self._stream.read()
|
|
if len(b) == 0:
|
|
return None, None # timeout
|
|
self.counts['BYTE'] += 1
|
|
if b == slip.END:
|
|
break
|
|
self.counts['DROP'] += 1
|
|
|
|
# already synced, or just got an END
|
|
self._inSync = True
|
|
|
|
# read packet
|
|
while True:
|
|
b = self._stream.read()
|
|
if len(b) == 0:
|
|
return None, None # timeout
|
|
self.counts['BYTE'] += 1
|
|
if len(pkt) == 0:
|
|
if b == slip.END:
|
|
# packet data has not started
|
|
continue
|
|
else:
|
|
# first byte in packet
|
|
# timestamp is that of the first byte in the packet
|
|
pktTime = datetime.datetime.now()
|
|
|
|
# If b is END at this point, it is the packet end. pkt[] must have
|
|
# at least one byte, and pktTime must have been set to get past the
|
|
# continue a few lines up
|
|
if b == slip.END:
|
|
return pktTime, pkt
|
|
|
|
if b == slip.ESC:
|
|
b = self._stream.read()
|
|
if len(b) == 0:
|
|
return None, None # timeout
|
|
self.counts['BYTE'] += 1
|
|
if b == slip.ESC_END:
|
|
pkt.append(slip.END)
|
|
elif b == slip.ESC_ESC:
|
|
pkt.append(slip.ESC)
|
|
else:
|
|
pkt.append(b)
|
|
self.counts['ESC'] += 1
|
|
else:
|
|
pkt.append(b)
|
|
|
|
# Sanity check
|
|
if len(pkt) > slip.maxPktLen:
|
|
# Something is wrong
|
|
slip.desync('TOO_LONG')
|
|
|
|
|
|
# Send one SLIP-encoded packet.
|
|
# 'pkt' is anything we can iterate over a byte at a time
|
|
# (e.g. string, list of single-char strings)
|
|
def send(self, pkt):
|
|
|
|
self._stream.write(slip.END) # may be optional
|
|
|
|
for c in pkt:
|
|
if c == slip.END:
|
|
self._stream.write(slip.ESC)
|
|
self._stream.write(slip.ESC_END)
|
|
elif c == slip.ESC:
|
|
self._stream.write(slip.ESC)
|
|
self._stream.write(slip.ESC_ESC)
|
|
else:
|
|
self._stream.write(c)
|
|
|
|
self._stream.write(slip.END)
|
|
|
|
|
|
|
|
# END class slip
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
# test
|
|
|
|
class dataSource():
|
|
def __init__(self, data):
|
|
self._data = data
|
|
self._index = 0
|
|
def read(self):
|
|
b = self._data[self._index]
|
|
self._index += 1
|
|
return b
|
|
|
|
# These tests just check the END processing.
|
|
# ESC processing has been running for a while; add tests if needed.
|
|
|
|
testData = ""
|
|
# junk discarded before first END, and the first END
|
|
testData += "abc" + slip.END
|
|
# first packet
|
|
testData += "1:123" + slip.END
|
|
# second packet, only the one END between first and second
|
|
testData += "2:45" + slip.END
|
|
# leading ENDs are okay
|
|
testData += slip.END + "3:6" + slip.END
|
|
# lots of ENDs are okay
|
|
testData += slip.END + slip.END + slip.END + "4:789" + slip.END
|
|
|
|
testSer = dataSource(testData)
|
|
|
|
while True:
|
|
try:
|
|
print recv(testSer)
|
|
except:
|
|
break
|