#!/usr/bin/python # David Baird # 2003.11.25 # New Mexico Tech # Electrical Engineering Dept. # Intelligent Systems and Robotics Group (ISRG) # These are helpful: # http://www.python.org/cgi-bin/moinmoin/ThreadProgramming # http://www.python.org/doc/current/lib/re-syntax.html from maxphy import MaxStreamPhy from string import zfill from sys import argv from time import sleep import binascii import threading import re import types def _ascii(s): '''Returns a list of integers for each character in s using ASCII.''' if len(s) == 1: return [int(eval('0x%s' % binascii.b2a_hex(s)))] return [_ascii(c)[0] for c in s] class MaxStreamMacRXThread(threading.Thread): def __init__(self, mac): threading.Thread.__init__(self) self.mac = mac self.stop = 0 def shutdown(self): self.stop = 1 def read(self): mac = self.mac tired = 0 c = mac.phy.read(1) while not c and not self.stop: sleep(.001*2**tired) tired += 1 if tired > 7: tired = 8 c = mac.phy.read(1) return c def run(self): # deserialize data from phy '''Grab characters and try to make sense of them... kinda like a realtime regular expression.''' mac = self.mac # Other setup: s = '' # drop tail queue/string while not self.stop: c = self.read() if c: # want to keep looping while there are still characters s += c #print 'mac:', s.strip() s = mac.deserialize(s) class MaxStreamMac: class Des: # for storing the deserialization state pass # This inserts the mac between the phy and sendup layers. sendup # is a callback used whenever the mac receives data def __init__(self, addy, phy, sendup, promisc=0): self.sendup = sendup self.phy = phy self.inuse = 0 self.timeout = .1 self.phy_busy = 0 self.mac_busy = 0 self.addy = addy assert(addy != 0) self.promisc = promisc self.deserializeInit() # Create a thread to listen to phy self.RX = MaxStreamMacRXThread(mac=self) self.RX.start() print 'mac ready' def __del__(self): self.shutdown() def shutdown(self): self.RX.shutdown() self.RX.join() def deserializeInit(self): self.des = self.Des() des = self.des des.l = 0 # Regular expresion setup: des.head = '_T_S_R_LLL_' des.tail = '_CCCC_' # only accept packets addressed to self, or broadcasted if self.promisc: des.head_re = re.compile('_[rcda]_[0-9]_[0-9]_[0-9a-f]{3}_') else: des.head_re = re.compile('_[rcda]_[0-9]_[0-9]_[0-9a-f]{3}_') #des.head_re = re.compile('_[rcda]_[0-9]_[0%d]_[0-9a-f]{3}_' % mac.addy) des.tail_re = re.compile('_[0-9a-f]{4}_') des.matching = 'head' def deserialize(self, s): '''This is called everytime a new character (or multiple new characters) are received. s is a string that represents queued data and it will be modified by this function.''' # convenient references (note they cannot be assigned to!) des = self.des l = des.l matching = des.matching head = des.head tail = des.tail head_re = des.head_re tail_re = des.tail_re # if a timeout is exceeded, then purge s while len(s) > len(head): if matching == 'head': m = head_re.match(s) while (not m) and len(s) > len(head): s = s[1:] # drop tail m = head_re.match(s) # now does it match? if m: matching = 'data' l = eval('0x%s' % s[7:10]) if matching == 'data' and (len(s) - len(head)) > l: matching = 'tail' if matching == 'tail' and (len(s) - len(head) - l) >= len(tail): # We have enough characters to match the tail, and so it # better match on the first try or we'll have to start over # from the head (and drop characters one by one) taildata = s[len(head) + l:] matching = 'head' # This would be true if we could control when characters are # put into s: #assert(len(taildata) == len(tail)) assert(len(taildata) >= len(tail)) m = tail_re.match(taildata) if m: # make sure the checksum is valid nontaildata = s[:len(head) + l] ck1 = eval('0x%s' % taildata[1:5]) ck2 = self.hash16(nontaildata) if ck1 == ck2: # yeah! we got a packet self.upDemux(s) s = '' else: print 'Bad cksum (%04x): %s' % (ck2, s.rstrip()) s = s[1:] # drop tail # don't return continue if not m: # darn print 'Almost matched packet: %s' % s.rstrip() # We can either discard the data altogether # or just the head and see if the packet really # follows later s = s[1:] # drop tail # don't return continue break des.matching = matching des.l = l return s # Can I make static member functions in Python? def hash16(self, str): # Somehow related to a djb2 hash. There is no particular reason # I chose this. I really want a CRC here, but don't know where # to find one quickly. hash = 5381 for c in _ascii(str): hash = (((hash << 5) + hash) ^ c) & 0xffff # ...or we can cram the above into one line: hash2 = reduce(lambda h,c: (((h<<5)+h)^c)&0xffff, [5381]+_ascii(str)) assert(hash == hash2) return hash def downPacketize(self, type, data, dest): t = len(type)==1 and t or \ {'rts': 'r', 'cts': 'c', 'data': 'd', 'ack': 'a'}[type.lower()] l = len(data) # Packets look like this: # _T_S_R_LLL_DATA_CCCC_ # 01234567890 # T = type # S = sender # R = receiver # LLL = length (of data) # DATA = data # CCCC = checksum p0 = '_%c_%x_%x_%03x_%s' % (t, self.addy, dest, l, data) p = p0 + '_%04x_' % self.hash16(p0) assert(len(p) < 256) # constraint for microcontroller implementation return p def upDemux(self, p): # called whenever a packet is received #print 'Got packet:', p.strip() t = p[1] s = int(p[3]) r = int(p[5]) l = int(eval('0x'+p[7:10])) data = p[11:11+l] if type(self.promisc) == types.FunctionType: self.promisc(s) if r not in [0, self.addy]: return if t == 'r': print >>self.phy, self.downPacketize('cts', '', s) elif t == 'c': # Is the sendDown function interested in this? # Is it from who sendDown expects? pass elif t == 's': stats = self.phy.sendCommands(['ATRS', 'ATER', 'ATGD']) stats = {'RS': stats[0], 'ER': stats[1], 'GD': stats[2]} if type(self.sendup) == types.FunctionType: # (mac, stats, frm, bcast, data) self.sendup(self, stats, s, r == 0, data) print >>self.phy, self.downPacketize('ack', '', s) elif t == 'd': stats = {} if type(self.sendup) == types.FunctionType: # (mac, stats, frm, bcast, data) self.sendup(self, stats, s, r == 0, data) print >>self.phy, self.downPacketize('ack', '', s) elif t == 'a': # Is the sendDown function interested in this? # Is it from who sendDown expects? pass def sendDown(self, dest, data): # * listen # * send data # * cross fingers and hope it works! # okay, fine, don't listen # if not (self.phy_busy or self.mac_busy): print >>self.phy, self.downPacketize('data', data, dest) def sendDown1(self, dest, data): # Gain access to the channel: # * Listen # * Send RTS # * Expect CTS # * Send data # * Expect ACK # If any expectation fails, restart retries = 0 while 1: timeout = self.timeout * 2**retries sleep(timeout) while(self.mac.busy): sleep(self.timeout) print >>self.phy, self.downPacketize('rts', '') if __name__ == '__main__': def sendUp(mac, stats, frm, bcast, data): print frm, data from maxphy import MaxStreamPhy phy = MaxStreamPhy(0, 9600) if len(argv) > 1: if argv[1] == 'tx': mac = MaxStreamMac(1, phy, None) while 1: try: print >>mac.phy, mac.downPacketize('data', 'foobar', 2) sleep(1) print >>mac.phy, mac.downPacketize('data', '', 2) sleep(1) except: mac.shutdown() raise 'some exception' elif argv[1] == 'rx': mac = MaxStreamMac(2, phy, sendUp) while 1: try: sleep(1) except: mac.shutdown() raise 'some exception' else: mac = MaxStreamMac(1, phy, None) while 1: try: print mac.downPacketize('data', 'monkey', 0) sleep(1) except: mac.shutdown() raise 'some exception'