First draft for a computer side python implementation
This commit is contained in:
parent
3e4abc5db3
commit
699424ed7c
|
@ -3,3 +3,6 @@
|
||||||
_autosave-*
|
_autosave-*
|
||||||
*.swp
|
*.swp
|
||||||
*.dxf~
|
*.dxf~
|
||||||
|
*.pyc
|
||||||
|
|
||||||
|
software/python/virtenv
|
||||||
|
|
|
@ -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).
|
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.
|
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.
|
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,
|
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.
|
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
|
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.
|
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))`,
|
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.
|
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,
|
Device receiving on the bus are not necessarily able to detect collisions,
|
||||||
unless they show up as timing error at their UART.
|
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.
|
Therefore receiving devices should rely only on the length and the CRC sum of frame to detect collisions.
|
||||||
|
|
|
@ -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()
|
Loading…
Reference in New Issue