diff --git a/.gitignore b/.gitignore index ae323a4..5269b74 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,6 @@ _autosave-* *.swp *.dxf~ +*.pyc + +software/python/virtenv diff --git a/README.md b/README.md index 623f1ff..a71827e 100644 --- a/README.md +++ b/README.md @@ -57,9 +57,9 @@ Transport Layer --------------- A device is allowed to send data after the bus has been idle for at least *48 bit times* (aka. 5ms). After this period the sender is allowed to send a single frame. -The frame format is simply the first byte is the length of the entire frame in bytes (`= N + 3`), +The frame format is simple: The first byte is the length of the entire frame in bytes (`= N + 3`), followed by N bytes of payload. -The remaining 2 bytes of the are a 16bit CRC checksum over the frames payload. +The remaining 2 bytes are a 16bit CRC checksum over the frames payload. If a received frame has timed out or if its CRC sum does not match its payload, any receiving devices should assume a collision and drop the frame. A frame can be assumed as timed out if it was not fully received yet and no @@ -73,6 +73,7 @@ If the read byte does not match the written byte, a collision occurred. In this case the sender has to wait for random back off interval until attempting a retransmit. The back off interval is calculated as `(8 * bit_time) * (priority + rand(1,5))`, where `bit_time = 1 / 9600 s` and priority is the frames priority. +After the backoff interval the bus has to be idle for *48 bit times* before retransmit. Device receiving on the bus are not necessarily able to detect collisions, unless they show up as timing error at their UART. Therefore receiving devices should rely only on the length and the CRC sum of frame to detect collisions. diff --git a/software/python/sss7bus.py b/software/python/sss7bus.py new file mode 100644 index 0000000..766a950 --- /dev/null +++ b/software/python/sss7bus.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python2 + +import serial +import random +import time +from crcmod.predefined import PredefinedCrc + + +# Timings in bit times +TIMEOUT = 24 +CLEARCHANNEL = 48 + + +class SSS7Bus(object): + ERR_SUCCESS = 0 + ERR_TIMEOUT_ON_LENGTH = -1 + ERR_TIMEOUT_ON_PAYLOAD = -2 + ERR_CRC_ERRROR = -3 + + + def __init__(self, port, baudrate=9600): + self._bit_time = 1.0 / baudrate + + self._serial = serial.Serial(port, baudrate, timeout=self._bit_time * TIMEOUT) + + self._buffer = [] + + + def _debug(self, msg): + print "[SSS7Bus] %s" % msg + + + def _read_frame(self, clear_channel=False): + if clear_channel : + self._serial.timeout = self._bit_time * CLEARCHANNEL + self._debug("Checking if bus is idle for %f seconds" % (self._bit_time * CLEARCHANNEL)) + else: + self._debug("Trying to read a frame from the bus") + self._serial.timeout = timeout=self._bit_time * TIMEOUT + + length_byte = self._serial.read(1) + if len(length_byte) == 0: + if clear_channel: + self._debug("Bus was idle") + else: + self._debug("Timeout reading frame length") + return self.ERR_TIMEOUT_ON_LENGTH + + if clear_channel: + self._debug("Bus was not idle, trying to read frame") + + length = ord(length_byte) - 1 # we read the size length byte already + + self._serial.timeout = timeout=self._bit_time * TIMEOUT + + self._debug("Trying to read remaing %d bytes of frame" % length) + data = self._serial.read(length) + + # if read returns less then length, no new byte has been received for timeout seconds + if len(data) != length: + self._debug("Timeout reading frame payload") + return self.ERR_TIMEOUT_ON_PAYLOAD + + crc = data[-2:] + payload = data[:-2] + + crc16 = PredefinedCrc('crc16') + crc16.update(payload) + real_crc = crc16.digest() + + if real_crc != crc: + self._debug("Wrong crc for frame payload") + return self.ERR_CRC_ERRROR + + self._debug("Sucessfully read rest of frame") + self._buffer.insert(0,data) + + return self.ERR_SUCCESS + + + def _flush_input_buffer(self): + while self._serial.inWaiting() > 0: + self._debug("Flushing input buffer") + self._read_frame() + + def _can_send(self): + self._flush_input_buffer() + return self._read_frame(True) == self.ERR_TIMEOUT_ON_LENGTH + + + def _send_byte(self, byte): + self._serial.write(byte) + return byte == self._serial.read(1) + + + def _send_frame(self, frame): + while not self._can_send(): + pass + + for byte in frame: + result = self._send_byte(byte) + if not result: + self._debug("Sending frame failed with collision") + return False + + self._debug("Successfully send frame") + return True + + + def send_message(self, msg, priority=5): + crc16 = PredefinedCrc('crc16') + crc16.update(msg) + msg_crc = crc16.digest() + + frame = chr(len(msg) + 3) + msg + msg_crc + + result = self._send_frame(frame) + + while not result: + backoff = 8 * self._bit_time * (priority + random.randint(1,5)) + self._debug("Collision occured backing off %f" % backoff) + time.sleep(backoff) + result = self._send_frame(frame) + + + def read_message(self): + while len(self._buffer) == 0: + self._read_frame() + + return self._buffer.pop()