#! /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("on-line") 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: %s" %(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 %d for frame: %s" %(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))