190 lines
5.1 KiB
Python
190 lines
5.1 KiB
Python
#! /bin/env python3
|
|
# encoding: utf-8
|
|
|
|
import time
|
|
import serial
|
|
|
|
from . import et
|
|
|
|
prefix = et.prefix
|
|
|
|
"""
|
|
===== OverTalk protocol spec =====
|
|
==== Transport layer ====
|
|
|
|
frame = [ 0x2a | destination (1) | source (1) | payload length (1) | payload (n) | checksum (1) ]
|
|
|
|
Any time an 0x2a is encountered the parser should flush its state and begin reading a new frame.
|
|
Bytes 0x2a, 0x2b, 0x11 and 0x13 are prefixed with 0x2b and inverted (XORed with 0xff) to avoid collisions.
|
|
|
|
Frames with destinations unknown to an OverShell are sent to the default interface, or dropped if
|
|
that's the interface they came from.
|
|
|
|
==== Command layer ====
|
|
Payload consists of one or more commands:
|
|
|
|
get_command = [ 0x00 | register (1) | target register (1) ]
|
|
set_command = [ 0x01 | register (1) | length (1) | value (n) ]
|
|
|
|
With a get_command the sender requests the receiver to read its own "register" and issue
|
|
a set_command that sets the sender's "target register" to that value.
|
|
|
|
A set_command does what is says on the box.
|
|
"""
|
|
|
|
class Transport:
|
|
"""
|
|
OverTalk Transport layer implementation.
|
|
|
|
Reads data from multiple interfaces and either router frames or receives them.
|
|
A received frame is then made available via a public attribute.
|
|
|
|
Interfaces are objects that implement methods read() (returns one byte of data as int), write(buffer, len),
|
|
and waiting property (contains number of bytes waiting).
|
|
"""
|
|
def __init__(self, my_id, interfaces, def_route):
|
|
"""
|
|
@param my_id OverTalk address of this Transport instance
|
|
@param interfaces a dict of interfaces keyed by their IDs
|
|
@param def_route ID of default interface for sending
|
|
"""
|
|
|
|
assert my_id not in interfaces
|
|
assert def_route in interfaces
|
|
|
|
self._print = et.Output("over.com.Transport", default_suffix="\n", timestamp=True)
|
|
|
|
self.my_id = my_id
|
|
self.def_route = def_route
|
|
self.interfaces = interfaces
|
|
|
|
self.destination_unknown = 0
|
|
self.incoming = None
|
|
|
|
self._print("<Cg>on-line<C/>")
|
|
|
|
def update(self):
|
|
self.incoming = None
|
|
|
|
for interface_id, interface in self.interfaces.items():
|
|
if interface.waiting:
|
|
interface.last_data_received = time.time()
|
|
byte = interface.read()
|
|
|
|
if byte == 0x2a:
|
|
interface.rxbuffer = []
|
|
interface.reading_escape = False
|
|
interface.reading_frame = True
|
|
|
|
if byte == 0x2b:
|
|
interface.reading_escape = True
|
|
continue
|
|
|
|
if interface.reading_escape:
|
|
byte ^= 0xff
|
|
interface.reading_escape = False
|
|
|
|
if interface.reading_frame:
|
|
interface.rxbuffer.append(byte)
|
|
|
|
if self.verify_frame(interface):
|
|
self.process_frame(interface_id, interface.rxbuffer)
|
|
interface.rxbuffer = []
|
|
|
|
def verify_frame(self, interface):
|
|
buffer_length = len(interface.rxbuffer)
|
|
|
|
if buffer_length >= 4:
|
|
# at this point we can read frame's declared size
|
|
if buffer_length >= interface.rxbuffer[3] + 5: # header (4) + payload + checksum (1)
|
|
# a frame has been read, huzzah!
|
|
interface.reading_frame = False
|
|
|
|
# checksum
|
|
if sum(interface.rxbuffer[:-1]) % 0x100 == interface.rxbuffer[-1]:
|
|
return True
|
|
|
|
else:
|
|
interface.malformed_frames += 1
|
|
self._print("broken frame received: <Cr>%s<C/>" %(interface.rxbuffer))
|
|
interface.rxbuffer = []
|
|
|
|
return False
|
|
|
|
def escape_frame(self, frame):
|
|
assert frame[0] == 0x2a
|
|
|
|
frame_escaped = [0x2a]
|
|
|
|
for byte in frame[1:]:
|
|
if byte in (0x2a, 0x2b, 0x11, 0x13):
|
|
frame_escaped.append(0x2b)
|
|
byte ^= 0xff
|
|
|
|
frame_escaped.append(byte)
|
|
|
|
return frame_escaped
|
|
|
|
def process_frame(self, source_interface_id, frame):
|
|
payload = frame[4:-1]
|
|
destination = frame[1]
|
|
|
|
if destination == self.my_id:
|
|
self.incoming = (frame[2], payload)
|
|
else:
|
|
if destination in self.interfaces:
|
|
self._print("routing frame to [%d]" %(destination))
|
|
self.interfaces[destination].write(self.escape_frame(frame))
|
|
else:
|
|
if source_interface_id == self.def_route:
|
|
self.destination_unknown += 1
|
|
self._print("unknown destination <Cr>%d<C/> for frame: <Cy>%s<C/>" %(destination,
|
|
repr(frame)), prefix.fail)
|
|
else:
|
|
self._print("routing frame to default route [%d]" %(self.def_route))
|
|
self.interfaces[self.def_route].write(self.escape_frame(frame))
|
|
|
|
def send_data(self, destination, data):
|
|
frame = [0x2a, destination, self.my_id, len(data)] + list(data)
|
|
frame.append(sum(frame) % 0x100)
|
|
frame = self.escape_frame(frame)
|
|
|
|
if destination in self.interfaces:
|
|
s = self.interfaces[destination]
|
|
else:
|
|
s = self.interfaces[self.def_route]
|
|
|
|
s.write(frame)
|
|
|
|
class TTL_Interface:
|
|
def __init__(self, interface="/dev/ttyUSB0", baudrate=57600):
|
|
try:
|
|
self.s = serial.Serial(interface, baudrate, timeout=1)
|
|
except serial.serialutil.SerialException:
|
|
self.s = None
|
|
|
|
self.rxbuffer = []
|
|
self.reading_escape = False
|
|
self.reading_frame = False
|
|
|
|
self.malformed_frames = 0
|
|
self.last_data_received = 0
|
|
|
|
@property
|
|
def waiting(self):
|
|
if self.s:
|
|
return self.s.inWaiting()
|
|
else:
|
|
return 0
|
|
|
|
def read(self):
|
|
if self.s:
|
|
return ord(self.s.read())
|
|
else:
|
|
return 0
|
|
|
|
def write(self, data):
|
|
print("Sending:", ''.join([hex(x)[2:].zfill(2) for x in data]))
|
|
|
|
if self.s:
|
|
self.s.write(bytes(data))
|