From 0cbaa989a9bd802209b9ae9cc8d127f18aab3190 Mon Sep 17 00:00:00 2001 From: Sebastian Kinne Date: Mon, 5 Oct 2015 13:55:56 -0700 Subject: [PATCH] Update rflib to support YARD Stick One --- rflib/__init__.py | 129 ++- rflib/bits.py | 163 ++- rflib/cc1111client.py | 2380 ---------------------------------------- rflib/ccspecan.py | 4 + rflib/chipcon_nic.py | 1985 ++++++++++++++++++++++++++++++--- rflib/chipcon_usb.py | 965 ++++++++++++++++ rflib/intelhex.py | 1131 +++++++++++++++++++ rflib/rflib_version.py | 1 + 8 files changed, 4197 insertions(+), 2561 deletions(-) create mode 100644 rflib/chipcon_usb.py create mode 100644 rflib/intelhex.py create mode 100644 rflib/rflib_version.py diff --git a/rflib/__init__.py b/rflib/__init__.py index 5189d9f..77f6cb3 100755 --- a/rflib/__init__.py +++ b/rflib/__init__.py @@ -1,9 +1,12 @@ -#!/usr/bin/env ipython +#!/usr/bin/env ipython -i --no-banner from chipcon_nic import * +import rflib.bits as rfbits RFCAT_START_SPECAN = 0x40 RFCAT_STOP_SPECAN = 0x41 +MAX_FREQ = 936e6 + class RfCat(FHSSNIC): def RFdump(self, msg="Receiving", maxnum=100, timeoutms=1000): try: @@ -20,10 +23,10 @@ class RfCat(FHSSNIC): self.RFdump("Clearing") self.lowball(lowball) self.setMdmDRate(drate) - print "Scanning range: " + print "Scanning range: " while not keystop(): try: - print + print "(press Enter to quit)" for freq in xrange(int(basefreq), int(basefreq+(inc*count)), int(inc)): print "Scanning for frequency %d..." % freq self.setFreq(freq) @@ -40,14 +43,13 @@ class RfCat(FHSSNIC): freq, delta = self._doSpecAn(basefreq, inc, count) import rflib.ccspecan as rfspecan - if not hasattr(self, "_qt_app") or self._qt_app is None: - self._qt_app = rfspecan.QtGui.QApplication([]) + rfspecan.ensureQapp() fhigh = freq + (delta*(count+1)) window = rfspecan.Window(self, freq, fhigh, delta, 0) window.show() - self._qt_app.exec_() + rfspecan._qt_app.exec_() def _doSpecAn(self, basefreq, inc, count): ''' @@ -55,6 +57,9 @@ class RfCat(FHSSNIC): ''' if count>255: raise Exception("sorry, only 255 samples per pass... (count)") + if (count * inc) + basefreq > MAX_FREQ: + raise Exception("Sorry, %1.3f + (%1.3f * %1.3f) is higher than %1.3f" % + (basefreq, count, inc)) self.getRadioConfig() self._specan_backup_radiocfg = self.radiocfg @@ -79,7 +84,9 @@ class RfCat(FHSSNIC): def rf_configure(*args, **k2args): pass - def rf_redirection(self, fdtup): + def rf_redirection(self, fdtup, use_rawinput=False, printable=False): + buf = '' + if len(fdtup)>1: fd0i, fd0o = fdtup else: @@ -90,30 +97,82 @@ class RfCat(FHSSNIC): if hasattr(fd0i, 'recv'): fdsock = True - while True: - x,y,z = select.select([fd0i ], [], [], .1) - #if self._pause: - # continue + try: + while True: + #if self._pause: + # continue - if fd0i in x: - if fdsock: - data = fd0i.recv(self.max_packet_size) - else: - data = fd0i.read(self.max_packet_size) + try: + x,y,z = select.select([fd0i ], [], [], .1) + if fd0i in x: + # FIXME: make this aware of VLEN/FLEN and the proper length + if fdsock: + data = fd0i.recv(self.max_packet_size) + else: + data = fd0i.read(self.max_packet_size) - if not len(data): # terminated socket - break + if not len(data): # terminated socket + break - self.RFxmit(data) + buf += data + pktlen, vlen = self.getPktLEN() + if vlen: + pktlen = ord(buf[0]) - try: - data = self.RFrecv(0) - if fdsock: - fd0o.sendall(data) - else: - fd0o.write(data) - except ChipconUsbTimeoutException: - pass + #FIXME: probably want to take in a length struct here and then only send when we have that many bytes... + data = buf[:pktlen] + if use_rawinput: + data = eval('"%s"'%data) + + if len(buf) >= pktlen: + self.RFxmit(data) + + except ChipconUsbTimeoutException: + pass + + try: + data, time = self.RFrecv(1) + + if printable: + data = "\n"+str(time)+": "+repr(data) + else: + data = struct.pack("H", ">I", ">I", ">Q", ">Q", ">Q", ">Q"] +sizes = [ 0, 1, 2, 4, 4, 8, 8, 8, 8] +masks = [ (1<<(8*i))-1 for i in xrange(9) ] + +def wtfo(string): + outstr = [] + bitlen = len(outstr) * 8 + for x in range(8): + outstr.append(shiftString(string, x)) + + string = strBitReverse(string) + for x in range(8): + outstr.append(shiftString(string, x)) + + return outstr + +def strBitReverse(string): + # FIXME: this is really dependent upon python's number system. large strings will not convert well. + # FIXME: break up array of 8-bit numbers and bit-swap in the array + num = 0 + bits = len(string)*8 + # convert to MSB number + for x in range(len(string)): + ch = string[x] + #num |= (ord(ch)<<(8*x)) # this is LSB + num <<= 8 + num |= ord(ch) + + print (hex(num)) + rnum = bitReverse(num, bits) + print (hex(rnum)) + + # convert back from MSB number to string + out = [] + for x in range(len(string)): + out.append(chr(rnum&0xff)) + rnum >>= 8 + out.reverse() + print(''.join(out).encode('hex')) + return ''.join(out) + +def strXorMSB(string, xorval, size): + ''' + lsb + pads end of string with 00 + ''' + out = [] + strlen = len(string) + string += "\x00" * sizes[size] + + for idx in range(0, strlen, size): + tempstr = string[idx:idx+sizes[size]] + temp, = struct.unpack( fmtsMSB[size], tempstr ) + temp ^= xorval + temp &= masks[size] + tempstr = struct.pack( fmtsMSB[size], temp )[-size:] + out.append(tempstr) + return ''.join(out) + + + + +def bitReverse(num, bitcnt): + newnum = 0 + for idx in range(bitcnt): + newnum <<= 1 + newnum |= num&1 + num >>= 1 + return newnum + def shiftString(string, bits): carry = 0 news = [] @@ -10,15 +81,66 @@ def shiftString(string, bits): news.append("%c"%newc) return "".join(news) -def findDword(byts, inverted=False): +def getNextByte_feedbackRegister7bitsMSB(): + ''' + this returns a byte of a 7-bit feedback register stemming off bits 4 and 7 + the register is 7 bits long, but we return a more usable 8bits (ie. + ''' + global fbRegister + + retval = 0 + for x in range(8): #MSB, + retval <<= 1 + retval |= (fbRegister >> 6) # start with bit 7 + nb = ( ( fbRegister>>3) ^ (fbRegister>>6)) &1 + fbRegister = ( ( fbRegister << 1 ) | nb ) & 0x7f # do shifting + #print "retval: %x fbRegister: %x bit7: %x nb: %x" % (retval, fbRegister, (fbRegister>>6), nb) + + return retval + +def getNextByte_feedbackRegister7bitsLSB(): + ''' + this returns a byte of a 7-bit feedback register stemming off bits 4 and 7 + the register is 7 bits long, but we return a more usable 8bits (ie. + ''' + global fbRegister + + retval = 0 + for x in range(8): #MSB, + retval >>= 1 + retval |= ((fbRegister << 1)&0x80) # start with bit 7 + + nb = ( ( fbRegister>>3) ^ (fbRegister>>6)) &1 + fbRegister = ( ( fbRegister << 1 ) | nb ) & 0x7f # do shifting + #print "retval: %x fbRegister: %x bit7: %x nb: %x" % (retval, fbRegister, (fbRegister>>6), nb) + + return retval + + +def whitenData(data, seed=0xffff, getNextByte=getNextByte_feedbackRegister7bitsMSB): + global fbRegister + fbRegister = seed + + carry = 0 + news = [] + for x in xrange(len(data)-1): + newc = ((ord(data[x]) ^ getNextByte() ) & 0xff) + news.append("%c"%newc) + return "".join(news) + +def findSyncWord(byts, sensitivity=4, minpreamble=2): + ''' + seek SyncWords from a raw bitstream. + assumes we capture at least two (more likely 3 or more) preamble bytes + ''' possDwords = [] # find the preamble (if any) - while True: + while True: # keep searching through string until we don't find any more preamble bits to pick on sbyts = byts - pidx = byts.find("\xaa\xaa") + pidx = byts.find("\xaa"*minpreamble) if pidx == -1: - pidx = byts.find("\x55\x55") - byts = shiftString(byts,1) + pidx = byts.find("\x55"*minpreamble) + byts = shiftString(byts, 1) if pidx == -1: return possDwords @@ -57,7 +179,8 @@ def findDword(byts, inverted=False): # bits1 >>= 2 #print "bits: %x" % (bits1) - for frontbits in xrange((0,1)[inverted], 17, 2): + bitcount = min( 2 * sensitivity, 17 ) + for frontbits in xrange( bitcount ): # with so many bit-inverted systems, let's not assume we know anything about the bit-arrangement. \x55\x55 could be a perfectly reasonable preamble. poss = (bits1 >> frontbits) & 0xffff if not poss in possDwords: possDwords.append(poss) @@ -65,7 +188,7 @@ def findDword(byts, inverted=False): return possDwords -def findDwordDoubled(byts): +def findSyncWordDoubled(byts): possDwords = [] # find the preamble (if any) bitoff = 0 @@ -345,3 +468,29 @@ def reprBitArray(bitAry, width=194): bots = "".join(bot) return "\n".join([tops, mids, bots]) +def invertBits(data): + output = [] + ldata = len(data) + off = 0 + + if ldata&1: + output.append( chr( ord( data[0] ) ^ 0xff) ) + off = 1 + + if ldata&2: + output.append( struct.pack( ">sys.stderr,(dev) - do = dev.open() - iSN = do.getDescriptor(1,0,50)[16] - devnum = dev.devnum - dongles.append((devnum, dev, do)) - - dongles.sort() - if len(dongles) == 0: - raise(Exception("No Dongle Found. Please insert a RFCAT dongle.")) - - self.rsema = threading.Lock() - self.xsema = threading.Lock() - - # claim that interface! - do = dongles[self.idx][2] - - try: - do.claimInterface(0) - except Exception,e: - if console or self._debug: print >>sys.stderr,("Error claiming usb interface:" + repr(e)) - - - - self.devnum, self._d, self._do = dongles[self.idx] - self._usbmaxi, self._usbmaxo = (EP5IN_MAX_PACKET_SIZE, EP5OUT_MAX_PACKET_SIZE) - self._usbcfg = self._d.configurations[0] - self._usbintf = self._usbcfg.interfaces[0][0] - self._usbeps = self._usbintf.endpoints - for ep in self._usbeps: - if ep.address & 0x80: - self._usbmaxi = ep.maxPacketSize - else: - self._usbmaxo = ep.maxPacketSize - - self._threadGo = True - - def resetup(self, console=True, copyDongle=None): - self._do=None - #self._threadGo = True - if self._debug: print >>sys.stderr,("waiting (resetup) %x" % self.idx) - while (self._do==None): - try: - self.setup(console, copyDongle) - if copyDongle is None: - self._clear_buffers(False) - - except Exception, e: - #if console: sys.stderr.write('.') - print >>sys.stderr,("Error in resetup():" + repr(e)) - if console or self._debug: print >>sys.stderr,("Error in resetup():" + repr(e)) - time.sleep(1) - - - - ######## BASE FOUNDATIONAL "HIDDEN" CALLS ######## - def _sendEP0(self, request=0, buf=None, value=0x200, index=0, timeout=1000): - if buf == None: - buf = 'HELLO THERE' - #return self._do.controlMsg(USB_BM_REQTYPE_TGT_EP|USB_BM_REQTYPE_TYPE_VENDOR|USB_BM_REQTYPE_DIR_OUT, request, "\x00\x00\x00\x00\x00\x00\x00\x00"+buf, value, index, timeout), buf - return self._do.controlMsg(USB_BM_REQTYPE_TGT_EP|USB_BM_REQTYPE_TYPE_VENDOR|USB_BM_REQTYPE_DIR_OUT, request, buf, value, index, timeout), buf - - def _recvEP0(self, request=0, length=64, value=0, index=0, timeout=100): - retary = ["%c"%x for x in self._do.controlMsg(USB_BM_REQTYPE_TGT_EP|USB_BM_REQTYPE_TYPE_VENDOR|USB_BM_REQTYPE_DIR_IN, request, length, value, index, timeout)] - if len(retary): - return ''.join(retary) - return "" - - def _sendEP5(self, buf=None, timeout=1000): - global direct - if (buf==None): - buf = "\xff\x82\x07\x00ABCDEFG" - if direct: - self._do.bulkWrite(5, buf, timeout) - return - - while (len(buf)>0): - drain = buf[:self._usbmaxo] - buf = buf[self._usbmaxo:] - - if self._debug: print >>sys.stderr,"XMIT:"+repr(drain) - try: - self._do.bulkWrite(5, drain, timeout) - except Exception, e: - if self._debug: print >>sys.stderr,"requeuing on error '%s' (%s)" % (repr(drain), e) - self.xsema.acquire() - msg = self.xmit_queue.insert(0, drain) - self.xsema.release() - if self._debug: print >>sys.stderr, repr(self.xmit_queue) - ''' - drain = buf[:self._usbmaxo] - buf = buf[self._usbmaxo:] - if len(buf): - if self._debug: print >>sys.stderr,"requeuing '%s'" % repr(buf) - self.xsema.acquire() - msg = self.xmit_queue.insert(0, buf) - self.xsema.release() - if self._debug: print >>sys.stderr, repr(self.xmit_queue) - if self._debug: print >>sys.stderr,"XMIT:"+repr(drain) - try: - self._do.bulkWrite(5, drain, timeout) - except Exception, e: - if self._debug: print >>sys.stderr,"requeuing on error '%s' (%s)" % (repr(drain), e) - self.xsema.acquire() - msg = self.xmit_queue.insert(0, drain) - self.xsema.release() - if self._debug: print >>sys.stderr, repr(self.xmit_queue) - - --- - while (len(buf)>0): - drain = buf[:self._usbmaxo] - buf = buf[self._usbmaxo:] - - if self._debug: print >>sys.stderr,"XMIT:"+repr(drain) - self._do.bulkWrite(5, drain, timeout) - time.sleep(1) - --- - if (len(buf) > self._usbmaxo): - drain = buf[:self._usbmaxo] - buf = buf[self._usbmaxo:] - self.xsema.acquire() - msg = self.xmit_queue.insert(0, buf) - self.xsema.release() - else: - drain = buf[:] - if self._debug: print >>sys.stderr,"XMIT:"+repr(drain) - self._do.bulkWrite(5, drain, timeout) - --- - while (len(buf)>0): - if (len(buf) > self._usbmaxo): - drain = buf[:self._usbmaxo] - buf = buf[self._usbmaxo:] - else: - drain = buf[:] - if self._debug: print >>sys.stderr,"XMIT:"+repr(drain) - self._do.bulkWrite(5, drain, timeout) - time.sleep(1) - ''' - - def _recvEP5(self, timeout=100): - retary = ["%c"%x for x in self._do.bulkRead(0x85, 500, timeout)] - if self._debug: print >>sys.stderr,"RECV:"+repr(retary) - if len(retary): - return ''.join(retary) - #return retary - return '' - - def _clear_buffers(self, clear_recv_mbox=False): - threadGo = self._threadGo - self._threadGo = False - if self._debug: - print >>sys.stderr,("_clear_buffers()") - if clear_recv_mbox: - for key in self.recv_mbox.keys(): - self.trash.extend(self.recvAll(key)) - self.trash.append((time.time(),self.recv_queue)) - self.recv_queue = '' - # self.xmit_queue = [] # do we want to keep this? - self._threadGo = threadGo - - - ######## TRANSMIT/RECEIVE THREADING ######## - def runEP5(self): - msg = '' - self.threadcounter = 0 - - while True: - if (self._do is None or not self._threadGo): - time.sleep(.04) - continue - - self.threadcounter = (self.threadcounter + 1) & 0xffffffff - - #### transmit stuff. if any exists in the xmit_queue - msgsent = False - msgrecv = False - try: - if len(self.xmit_queue): - self.xsema.acquire() - msg = self.xmit_queue.pop(0) - self.xsema.release() - self._sendEP5(msg) - msgsent = True - else: - if self._debug>3: sys.stderr.write("NoMsgToSend ") - #except IndexError: - #if self._debug==3: sys.stderr.write("NoMsgToSend ") - #pass - except: - sys.excepthook(*sys.exc_info()) - - - #### handle debug application - try: - q = None - b = self.recv_mbox.get(APP_DEBUG, None) - if (b != None): - for cmd in b.keys(): - q = b[cmd] - if len(q): - buf,timestamp = q.pop(0) - #cmd = ord(buf[1]) - if self._debug > 1: print >>sys.stderr,("buf length: %x\t\t cmd: %x\t\t(%s)"%(len(buf), cmd, repr(buf))) - if (cmd == DEBUG_CMD_STRING): - if (len(buf) < 4): - if (len(q)): - buf2 = q.pop(0) - buf += buf2 - q.insert(0,buf) - if self._debug: sys.stderr.write('*') - else: - length, = struct.unpack("1: print >>sys.stderr,("len=%d"%length) - if (len(buf) < 4+length): - if (len(q)): - buf2 = q.pop(0) - buf += buf2 - q.insert(0,buf) - if self._debug: sys.stderr.write('&') - else: - printbuf = buf[4:4+length] - requeuebuf = buf[4+length:] - if len(requeuebuf): - if self._debug>1: print >>sys.stderr,(" - DEBUG..requeuing %s"%repr(requeuebuf)) - q.insert(0,requeuebuf) - print >>sys.stderr,("DEBUG: (%.3f) %s" % (timestamp, repr(printbuf))) - elif (cmd == DEBUG_CMD_HEX): - #print >>sys.stderr, repr(buf) - print >>sys.stderr, "DEBUG: (%.3f) %x"%(timestamp, struct.unpack("B", buf[4:5])[0]) - elif (cmd == DEBUG_CMD_HEX16): - #print >>sys.stderr, repr(buf) - print >>sys.stderr, "DEBUG: (%.3f) %x"%(timestamp, struct.unpack(">sys.stderr, repr(buf) - print >>sys.stderr, "DEBUG: (%.3f) %x"%(timestamp, struct.unpack(">sys.stderr, "DEBUG: (%.3f) %d"%(timestamp, struct.unpack(">sys.stderr,('DEBUG COMMAND UNKNOWN: %x (buf=%s)'%(cmd,repr(buf))) - - except: - sys.excepthook(*sys.exc_info()) - - #### receive stuff. - try: - #### first we populate the queue - msg = self._recvEP5(timeout=self.ep5timeout) - if len(msg) > 0: - self.recv_queue += msg - msgrecv = True - except usb.USBError, e: - #sys.stderr.write(repr(self.recv_queue)) - #sys.stderr.write(repr(e)) - errstr = repr(e) - if self._debug>4: print >>sys.stderr,repr(sys.exc_info()) - if ('No error' in errstr): - pass - elif ('Operation timed out' in errstr): - pass - else: - if ('could not release intf' in errstr): - pass - elif ('No such device' in errstr): - self._threadGo = False - self.resetup(False) - elif ('Input/output error' in errstr): # USBerror 5 - self._threadGo = False - self.resetup(False) - - else: - if self._debug: print "Error in runEP5() (receiving): %s" % errstr - if self._debug>2: sys.excepthook(*sys.exc_info()) - self._usberrorcnt += 1 - pass - except AttributeError,e: - if "'NoneType' object has no attribute 'bInterfaceNumber'" in str(e): - print "Error: dongle went away. USB bus problems?" - self._threadGo = False - self.resetup(False) - - except: - sys.excepthook(*sys.exc_info()) - - - #### parse, sort, and deliver the mail. - try: - # FIXME: is this robust? or just overcomplex? - if len(self.recv_queue): - idx = self.recv_queue.find('@') - if (idx==-1): - if self._debug > 3: - sys.stderr.write('@') - else: - if (idx>0): - if self._debug: print >>sys.stderr,("runEP5(): idx>0?") - self.trash.append(self.recv_queue[:idx]) - self.recv_queue = self.recv_queue[idx:] - - # recv_queue is vulnerable here, but it's ok because we only modify it earlier in this same thread - # DON'T CHANGE recv_queue from other threads! - msg = self.recv_queue - msglen = len(msg) - while (msglen>=5): # if not enough to parse length... we'll wait. - if not self._recv_time: # should be 0 to start and when done with a packet - self._recv_time = time.time() - app = ord(msg[1]) - cmd = ord(msg[2]) - length, = struct.unpack("1: print>>sys.stderr,("app=%x cmd=%x len=%x"%(app,cmd,length)) - - if (msglen >= length+5): - #### if the queue has enough characters to handle the next message... chop it and put it in the appropriate recv_mbox - msg = self.recv_queue[1:length+5] # drop the initial '@' and chop out the right number of chars - self.recv_queue = self.recv_queue[length+5:] # chop it out of the queue - - b = self.recv_mbox.get(app,None) - if self.rsema.acquire(): # THREAD SAFETY DANCE - #if self._debug: print ("rsema.UNlocked", "rsema.locked")[self.rsema.locked()],0 - try: - if (b == None): - b = {} - self.recv_mbox[app] = b - except: - sys.excepthook(*sys.exc_info()) - finally: - self.rsema.release() # THREAD SAFETY DANCE COMPLETE - #if self._debug: print ("rsema.UNlocked", "rsema.locked")[self.rsema.locked()],0 - - q = b.get(cmd) - if self.rsema.acquire(): # THREAD SAFETY DANCE - #if self._debug: print ("rsema.UNlocked", "rsema.locked")[self.rsema.locked()],1 - try: - if (q is None): - q = [] - b[cmd] = q - - q.append((msg, self._recv_time)) - self._recv_time = 0 # we've delivered the current message - except: - sys.excepthook(*sys.exc_info()) - finally: - self.rsema.release() # THREAD SAFETY DANCE COMPLETE - #if self._debug: print ("rsema.UNlocked", "rsema.locked")[self.rsema.locked()],1 - - else: - if self._debug>1: sys.stderr.write('=') - - msg = self.recv_queue - msglen = len(msg) - # end of while loop - #else: - # if self._debug: sys.stderr.write('.') - except: - sys.excepthook(*sys.exc_info()) - - - if not (msgsent or msgrecv or len(msg)) : - #time.sleep(.1) - self.ep5timeout = EP_TIMEOUT_IDLE - else: - self.ep5timeout = EP_TIMEOUT_ACTIVE - if self._debug > 5: sys.stderr.write(" %s:%s:%d .-P."%(msgsent,msgrecv,len(msg))) - - - - - - - - ######## APPLICATION API ######## - def recv(self, app, cmd=None, wait=USB_RX_WAIT): - for x in xrange(wait+1): - try: - b = self.recv_mbox.get(app) - if cmd is None: - keys = b.keys() - if len(keys): - cmd = b.keys()[-1] - if b is not None: - q = b.get(cmd) - #print >>sys.stderr,"debug(recv) q='%s'"%repr(q) - if q is not None and self.rsema.acquire(False): - #if self._debug: print ("rsema.UNlocked", "rsema.locked")[self.rsema.locked()],2 - try: - resp, rt = q.pop(0) - self.rsema.release() - #if self._debug: print ("rsema.UNlocked", "rsema.locked")[self.rsema.locked()],2 - return resp[4:], rt - except IndexError: - pass - #sys.excepthook(*sys.exc_info()) - except AttributeError: - sys.excepthook(*sys.exc_info()) - pass - self.rsema.release() - #if self._debug: print ("rsema.UNlocked", "rsema.locked")[self.rsema.locked()],2 - except: - sys.excepthook(*sys.exc_info()) - - time.sleep(.001) # only hits here if we don't have something in queue - - raise(ChipconUsbTimeoutException()) - - def recvAll(self, app, cmd=None): - retval = self.recv_mbox.get(app,None) - if retval is not None: - if cmd is not None: - b = retval - if self.rsema.acquire(): - #if self._debug: print ("rsema.UNlocked", "rsema.locked")[self.rsema.locked()],3 - try: - retval = b.get(cmd) - b[cmd]=[] - if len(retval): - retval = [ (d[4:],t) for d,t in retval ] - except: - sys.excepthook(*sys.exc_info()) - finally: - self.rsema.release() - #if self._debug: print ("rsema.UNlocked", "rsema.locked")[self.rsema.locked()],3 - else: - if self.rsema.acquire(): - #if self._debug: print ("rsema.UNlocked", "rsema.locked")[self.rsema.locked()],4 - try: - self.recv_mbox[app]={} - finally: - self.rsema.release() - #if self._debug: print ("rsema.UNlocked", "rsema.locked")[self.rsema.locked()],4 - return retval - - def send(self, app, cmd, buf, wait=USB_TX_WAIT): - msg = "%c%c%s%s"%(app,cmd, struct.pack(">sys.stderr, ("DONGLE RESPONDING: mode :%x, last error# %d"%(self.getDebugCodes())) - except: - pass - print >>sys.stderr,('recv_queue:\t\t (%d bytes) "%s"'%(len(self.recv_queue),repr(self.recv_queue)[:len(self.recv_queue)%39+20])) - print >>sys.stderr,('trash: \t\t (%d bytes) "%s"'%(len(self.trash),repr(self.trash)[:len(self.trash)%39+20])) - print >>sys.stderr,('recv_mbox \t\t (%d keys) "%s"'%(len(self.recv_mbox),repr(self.recv_mbox)[:len(repr(self.recv_mbox))%79])) - for x in self.recv_mbox.keys(): - print >>sys.stderr,(' recv_mbox %d\t (%d records) "%s"'%(x,len(self.recv_mbox[x]),repr(self.recv_mbox[x])[:len(repr(self.recv_mbox[x]))%79])) - """ - print self.reprRadioState() - print self.reprClientState() - - x,y,z = select.select([sys.stdin],[],[], delay) - if sys.stdin in x: - sys.stdin.read(1) - break - - def ping(self, count=10, buf="ABCDEFGHIJKLMNOPQRSTUVWXYZ", wait=1000): - good=0 - bad=0 - start = time.time() - for x in range(count): - istart = time.time() - - try: - r = self.send(APP_SYSTEM, SYS_CMD_PING, buf, wait) - r,rt = r - istop = time.time() - print "PING: %d bytes transmitted, received: %s (%f seconds)"%(len(buf), repr(r), istop-istart) - except ChipconUsbTimeoutException, e: - r = None - print "Ping Failed." - if r==None: - bad+=1 - else: - good+=1 - stop = time.time() - return (good,bad,stop-start) - - def bootloader(self): - ''' - switch to bootloader mode. based on Fergus Noble's CC-Bootloader (https://github.com/fnoble/CC-Bootloader) - this allows the firmware to be updated via USB instead of goodfet/ccdebugger - ''' - try: - r = self.send(APP_SYSTEM, SYS_CMD_BOOTLOADER, "") - except ChipconUsbTimeoutException: - pass - - def RESET(self): - try: - r = self.send(APP_SYSTEM, SYS_CMD_RESET, "RESET_NOW\x00") - except ChipconUsbTimeoutException: - pass - - def peek(self, addr, bytecount=1): - r, t = self.send(APP_SYSTEM, SYS_CMD_PEEK, struct.pack("> 16 - radiocfg.freq1 = (num>>8) & 0xff - radiocfg.freq0 = num & 0xff - - if (freq > FREQ_EDGE_900 and freq < FREQ_MID_900) or (freq > FREQ_EDGE_400 and freq < FREQ_MID_400) or (freq < FREQ_MID_300): - # select low VCO - radiocfg.fscal2 = 0x0A - else: - # select high VCO - radiocfg.fscal2 = 0x2A - - if applyConfig: - marcstate = radiocfg.marcstate - if marcstate != MARC_STATE_IDLE: - self.strobeModeIDLE() - self.poke(FREQ2, struct.pack("3B", self.radiocfg.freq2, self.radiocfg.freq1, self.radiocfg.freq0)) - self.poke(FSCAL2, struct.pack("B", self.radiocfg.fscal2)) - - self.strobeModeReturn(marcstate) - #if (radiocfg.marcstate == MARC_STATE_RX): - #self.strobeModeRX() - #elif (radiocfg.marcstate == MARC_STATE_TX): - #self.strobeModeTX() - - def getFreq(self, mhz=24, radiocfg=None): - freqmult = (0x10000 / 1000000.0) / mhz - if radiocfg==None: - self.getRadioConfig() - radiocfg = self.radiocfg - - num = (radiocfg.freq2<<16) + (radiocfg.freq1<<8) + radiocfg.freq0 - freq = num / freqmult - return freq, hex(num) - - # set 'standard' power - for more complex power shaping this will need to be done manually - def setPower(self, power=None, radiocfg=None, invert=False): - if radiocfg == None: - self.getRadioConfig() - radiocfg = self.radiocfg - - mod= self.getMdmModulation(radiocfg=radiocfg) - - # we may be only changing PA_POWER, not power levels - if power is not None: - if mod == MOD_ASK_OOK and not invert: - radiocfg.pa_table0= 0x00 - radiocfg.pa_table1= power - else: - radiocfg.pa_table0= power - radiocfg.pa_table1= 0x00 - self.setRFRegister(PA_TABLE0, radiocfg.pa_table0) - self.setRFRegister(PA_TABLE1, radiocfg.pa_table1) - - radiocfg.frend0 &= ~FREND0_PA_POWER - if mod == MOD_ASK_OOK: - radiocfg.frend0 |= 0x01 - - self.setRFRegister(FREND0, radiocfg.frend0) - - # max power settings are frequency dependent, so set frequency before calling - def setMaxPower(self, radiocfg=None, invert=False): - if radiocfg == None: - self.getRadioConfig() - radiocfg = self.radiocfg - - freq= self.getFreq(radiocfg=radiocfg)[0] - - if freq <= 400000000: - power= 0xC2 - elif freq <= 464000000: - power= 0xC0 - elif freq <= 900000000: - power= 0xC2 - else: - power= 0xC0 - - self.setPower(power, radiocfg=radiocfg, invert=invert) - - def setMdmModulation(self, mod, radiocfg=None, invert=False): - if radiocfg == None: - self.getRadioConfig() - radiocfg = self.radiocfg - - if (mod) & ~MDMCFG2_MOD_FORMAT: - raise(Exception("Please use constants MOD_FORMAT_* to specify modulation and ")) - - radiocfg.mdmcfg2 &= ~MDMCFG2_MOD_FORMAT - radiocfg.mdmcfg2 |= (mod) - - power= None - # ASK_OOK needs to flip power table - if mod == MOD_ASK_OOK and not invert: - if radiocfg.pa_table1 == 0x00 and radiocfg.pa_table0 != 0x00: - power= radiocfg.pa_table0 - else: - if radiocfg.pa_table0 == 0x00 and radiocfg.pa_table1 != 0x00: - power= radiocfg.pa_table1 - - self.setRFRegister(MDMCFG2, radiocfg.mdmcfg2) - self.setPower(power, radiocfg=radiocfg, invert=invert) - - def getMdmModulation(self, radiocfg=None): - if radiocfg == None: - self.getRadioConfig() - radiocfg = self.radiocfg - - mdmcfg2 = radiocfg.mdmcfg2 - mod = (mdmcfg2) & MDMCFG2_MOD_FORMAT - return mod - - def printRadioConfig(self, mhz=24, radiocfg=None): - print self.reprRadioConfig(mhz, radiocfg) - - def reprRadioConfig(self, mhz=24, radiocfg=None): - if radiocfg == None: - self.getRadioConfig() - radiocfg = self.radiocfg - output = [] - - output.append( "== Hardware ==") - output.append( self.reprHardwareConfig()) - output.append( "\n== Software ==") - output.append( self.reprSoftwareConfig()) - output.append( "\n== Frequency Configuration ==") - output.append( self.reprFreqConfig(mhz, radiocfg)) - output.append( "\n== Modem Configuration ==") - output.append( self.reprModemConfig(mhz, radiocfg)) - output.append( "\n== Packet Configuration ==") - output.append( self.reprPacketConfig(radiocfg)) - output.append( "\n== AES Crypto Configuration ==") - output.append( self.reprAESMode()) - output.append( "\n== Radio Test Signal Configuration ==") - output.append( self.reprRadioTestSignalConfig(radiocfg)) - output.append( "\n== Radio State ==") - output.append( self.reprRadioState(radiocfg)) - output.append("\n== Client State ==") - output.append( self.reprClientState()) - return "\n".join(output) - - def reprHardwareConfig(self): - output= [] - - hardware= self.getBuildInfo() - output.append("Dongle: %s" % hardware.split(' ')[0]) - try: - output.append("Firmware rev: %s" % hardware.split('r')[1]) - except: - output.append("Firmware rev: Not found! Update needed!") - # see if we have a bootloader by loooking for it's recognition semaphores - # in SFR I2SCLKF0 & I2SCLKF1 - if(self.peek(0xDF46,1) == '\xF0' and self.peek(0xDF47,1) == '\x0D'): - output.append("Bootloader: CC-Bootloader") - else: - output.append("Bootloader: Not installed") - return "\n".join(output) - - def reprSoftwareConfig(self): - output= [] - - output.append("rflib rev: %s" % RFLIB_VERSION) - return "\n".join(output) - - def reprMdmModulation(self, radiocfg=None): - mod = self.getMdmModulation(radiocfg) - return ("Modulation: %s" % MODULATIONS[mod]) - - def getMdmChanSpc(self, mhz=24, radiocfg=None): - if radiocfg==None: - self.getRadioConfig() - radiocfg = self.radiocfg - - chanspc_m = radiocfg.mdmcfg0 - chanspc_e = radiocfg.mdmcfg1 & 3 - chanspc = 1000000.0 * mhz/pow(2,18) * (256 + chanspc_m) * pow(2, chanspc_e) - #print "chanspc_e: %x chanspc_m: %x chanspc: %f hz" % (chanspc_e, chanspc_m, chanspc) - return (chanspc) - - def setMdmChanSpc(self, chanspc=None, chanspc_m=None, chanspc_e=None, mhz=24, radiocfg=None): - ''' - calculates the appropriate exponent and mantissa and updates the correct registers - chanspc is in kHz. if you prefer, you may set the chanspc_m and chanspc_e settings - directly. - - only use one or the other: - * chanspc - * chanspc_m and chanspc_e - ''' - if radiocfg==None: - self.getRadioConfig() - radiocfg = self.radiocfg - - if (chanspc != None): - for e in range(4): - m = int(((chanspc * pow(2,18) / (1000000.0 * mhz * pow(2,e)))-256) +.5) # rounded evenly - if m < 256: - chanspc_e = e - chanspc_m = m - break - if chanspc_e is None or chanspc_m is None: - raise(Exception("ChanSpc does not translate into acceptable parameters. Should you be changing this?")) - - #chanspc = 1000000.0 * mhz/pow(2,18) * (256 + chanspc_m) * pow(2, chanspc_e) - #print "chanspc_e: %x chanspc_m: %x chanspc: %f hz" % (chanspc_e, chanspc_m, chanspc) - - radiocfg.mdmcfg1 &= ~MDMCFG1_CHANSPC_E # clear out old exponent value - radiocfg.mdmcfg1 |= chanspc_e - radiocfg.mdmcfg0 = chanspc_m - self.setRFRegister(MDMCFG1, (radiocfg.mdmcfg1)) - self.setRFRegister(MDMCFG0, (radiocfg.mdmcfg0)) - - def makePktVLEN(self, maxlen=RF_MAX_TX_BLOCK, radiocfg=None): - if radiocfg==None: - self.getRadioConfig() - radiocfg = self.radiocfg - - if maxlen > RF_MAX_TX_BLOCK: - raise(Exception("Packet too large (%d bytes). Maximum variable length packet is %d bytes." % (maxlen, RF_MAX_TX_BLOCK))) - - radiocfg.pktctrl0 &= ~PKTCTRL0_LENGTH_CONFIG - radiocfg.pktctrl0 |= 1 - radiocfg.pktlen = maxlen - self.setRFRegister(PKTCTRL0, (radiocfg.pktctrl0)) - self.setRFRegister(PKTLEN, (radiocfg.pktlen)) - - - def makePktFLEN(self, flen=RF_MAX_TX_BLOCK, radiocfg=None): - if radiocfg==None: - self.getRadioConfig() - radiocfg = self.radiocfg - - if flen > EP5OUT_BUFFER_SIZE - 4: - raise(Exception("Packet too large (%d bytes). Maximum fixed length packet is %d bytes." % (flen, EP5OUT_BUFFER_SIZE - 6))) - - radiocfg.pktctrl0 &= ~PKTCTRL0_LENGTH_CONFIG - # if we're sending a large block, pktlen is dealt with by the firmware - # using 'infinite' mode - if flen > RF_MAX_TX_BLOCK: - radiocfg.pktlen = 0x00 - else: - radiocfg.pktlen = flen - self.setRFRegister(PKTCTRL0, (radiocfg.pktctrl0)) - self.setRFRegister(PKTLEN, (radiocfg.pktlen)) - - def setEnablePktCRC(self, enable=True, radiocfg=None): - if radiocfg==None: - self.getRadioConfig() - radiocfg = self.radiocfg - - crcE = (0,1)[enable]<<2 - crcM = ~(1<<2) - radiocfg.pktctrl0 &= crcM - radiocfg.pktctrl0 |= crcE - self.setRFRegister(PKTCTRL0, (radiocfg.pktctrl0)) - - def getEnablePktCRC(self, radiocfg=None): - if radiocfg==None: - self.getRadioConfig() - radiocfg = self.radiocfg - - return (radiocfg.pktctrl0 >>2) & 0x1 - - def setEnablePktDataWhitening(self, enable=True, radiocfg=None): - if radiocfg==None: - self.getRadioConfig() - radiocfg = self.radiocfg - - dwEnable = (0,1)[enable]<<6 - radiocfg.pktctrl0 &= ~PKTCTRL0_WHITE_DATA - radiocfg.pktctrl0 |= dwEnable - self.setRFRegister(PKTCTRL0, (radiocfg.pktctrl0)) - - def getEnablePktDataWhitening(self, radiocfg=None): - if radiocfg==None: - self.getRadioConfig() - radiocfg = self.radiocfg - - return (radiocfg.pktctrl0 >>6) & 0x1 - - def setPktPQT(self, num=3, radiocfg=None): - if radiocfg==None: - self.getRadioConfig() - radiocfg = self.radiocfg - - num &= 7 - num <<= 5 - numM = ~(7<<5) - radiocfg.pktctrl1 &= numM - radiocfg.pktctrl1 |= num - self.setRFRegister(PKTCTRL1, (radiocfg.pktctrl1)) - - def getPktPQT(self, radiocfg=None): - if radiocfg==None: - self.getRadioConfig() - radiocfg = self.radiocfg - - return (radiocfg.pktctrl1 >> 5) & 7 - - def setEnablePktAppendStatus(self, enable=True, radiocfg=None): - ''' - enable append status bytes. two bytes will be appended to the payload of the packet, containing - RSSI and LQI values as well as CRC OK. - ''' - if radiocfg == None: - self.getRadioConfig() - radiocfg = self.radiocfg - - radiocfg.pktctrl1 &= ~PKTCTRL1_APPEND_STATUS - radiocfg.pktctrl1 |= (enable<<2) - self.setRFRegister(PKTCTRL1, radiocfg.pktctrl1) - - def getEnablePktAppendStatus(self, radiocfg=None): - ''' - return append status bytes setting. - ''' - if radiocfg == None: - self.getRadioConfig() - radiocfg = self.radiocfg - - pktctrl1 = radiocfg.pktctrl1 - append = (pktctrl1>>2) & 0x01 - return append - - def setEnableMdmManchester(self, enable=True, radiocfg=None): - if radiocfg == None: - self.getRadioConfig() - radiocfg = self.radiocfg - - radiocfg.mdmcfg2 &= ~MDMCFG2_MANCHESTER_EN - radiocfg.mdmcfg2 |= (enable<<3) - self.setRFRegister(MDMCFG2, radiocfg.mdmcfg2) - - def getEnableMdmManchester(self, radiocfg=None): - if radiocfg == None: - self.getRadioConfig() - radiocfg = self.radiocfg - - mdmcfg2 = radiocfg.mdmcfg2 - mchstr = (mdmcfg2>>3) & 0x01 - return mchstr - - def setEnableMdmFEC(self, enable=True, radiocfg=None): - if radiocfg==None: - self.getRadioConfig() - radiocfg = self.radiocfg - - fecEnable = (0,1)[enable]<<7 - radiocfg.mdmcfg1 &= ~MFMCFG1_FEC_EN - radiocfg.mdmcfg1 |= fecEnable - self.setRFRegister(MDMCFG1, (radiocfg.mdmcfg1)) - - def getEnableMdmFEC(self, radiocfg=None): - if radiocfg == None: - self.getRadioConfig() - radiocfg = self.radiocfg - - mdmcfg1 = radiocfg.mdmcfg1 - fecEnable = (mdmcfg1>>7) & 0x01 - return fecEnable - - def setEnableMdmDCFilter(self, enable=True, radiocfg=None): - if radiocfg==None: - self.getRadioConfig() - radiocfg = self.radiocfg - - dcfEnable = (0,1)[enable]<<7 - radiocfg.mdmcfg2 &= ~MDMCFG2_DEM_DCFILT_OFF - radiocfg.mdmcfg2 |= dcfEnable - self.setRFRegister(MDMCFG2, radiocfg.mdmcfg2) - - def getEnableMdmDCFilter(self, radiocfg=None): - if radiocfg==None: - self.getRadioConfig() - radiocfg = self.radiocfg - - dcfEnable = (radiocfg.mdmcfg2>>7) & 0x1 - return dcfEnable - - - def setFsIF(self, freq_if, mhz=24, radiocfg=None): - ''' - Note that the SmartRF Studio software - automatically calculates the optimum register - setting based on channel spacing and channel - filter bandwidth. (from cc1110f32.pdf) - ''' - if radiocfg==None: - self.getRadioConfig() - radiocfg = self.radiocfg - - ifBits = freq_if * pow(2,10) / (1000000.0 * mhz) - ifBits = int(ifBits + .5) # rounded evenly - - if ifBits >0x1f: - raise(Exception("FAIL: freq_if is too high? freqbits: %x (must be <0x1f)" % ifBits)) - radiocfg.fsctrl1 &= ~(0x1f) - radiocfg.fsctrl1 |= int(ifBits) - self.setRFRegister(FSCTRL1, (radiocfg.fsctrl1)) - - def getFsIF(self, mhz=24, radiocfg=None): - if radiocfg==None: - self.getRadioConfig() - radiocfg = self.radiocfg - - freq_if = (radiocfg.fsctrl1&0x1f) * (1000000.0 * mhz / pow(2,10)) - return freq_if - - - def setFsOffset(self, if_off, mhz=24, radiocfg=None): - ''' - Note that the SmartRF Studio software - automatically calculates the optimum register - setting based on channel spacing and channel - filter bandwidth. (from cc1110f32.pdf) - ''' - if radiocfg==None: - self.getRadioConfig() - radiocfg = self.radiocfg - - radiocfg.fsctrl0 = if_off - self.setRFRegister(FSCTRL0, (radiocfg.fsctrl0)) - - def getFsOffset(self, mhz=24, radiocfg=None): - if radiocfg==None: - self.getRadioConfig() - radiocfg = self.radiocfg - - freqoff = radiocfg.fsctrl0 - return freqoff - - def getChannel(self, radiocfg=None): - if radiocfg==None: - self.getRadioConfig() - radiocfg = self.radiocfg - - self.getRadioConfig() - return radiocfg.channr - - def setChannel(self, channr, radiocfg=None): - if radiocfg==None: - self.getRadioConfig() - radiocfg = self.radiocfg - - radiocfg.channr = channr - self.setRFRegister(CHANNR, (radiocfg.channr)) - - def setMdmChanBW(self, bw, mhz=24, radiocfg=None): - ''' - For best performance, the channel filter - bandwidth should be selected so that the - signal bandwidth occupies at most 80% of the - channel filter bandwidth. The channel centre - tolerance due to crystal accuracy should also - be subtracted from the signal bandwidth. The - following example illustrates this: - - With the channel filter bandwidth set to 500 - kHz, the signal should stay within 80% of 500 - kHz, which is 400 kHz. Assuming 915 MHz - frequency and +/-20 ppm frequency uncertainty - for both the transmitting device and the - receiving device, the total frequency - uncertainty is +/-40 ppm of 915 MHz, which is - +/-37 kHz. If the whole transmitted signal - bandwidth is to be received within 400 kHz, the - transmitted signal bandwidth should be - maximum 400 kHz - 2*37 kHz, which is 326 - kHz. - - DR:1.2kb Dev:5.1khz Mod:GFSK RXBW:63kHz sensitive fsctrl1:06 mdmcfg:e5 a3 13 23 11 dev:16 foc/bscfg:17/6c agctrl:03 40 91 frend:56 10 - DR:1.2kb Dev:5.1khz Mod:GFSK RXBW:63kHz lowpower fsctrl1:06 mdmcfg:e5 a3 93 23 11 dev:16 foc/bscfg:17/6c agctrl:03 40 91 frend:56 10 (DEM_DCFILT_OFF) - DR:2.4kb Dev:5.1khz Mod:GFSK RXBW:63kHz sensitive fsctrl1:06 mdmcfg:e6 a3 13 23 11 dev:16 foc/bscfg:17/6c agctrl:03 40 91 frend:56 10 - DR:2.4kb Dev:5.1khz Mod:GFSK RXBW:63kHz lowpower fsctrl1:06 mdmcfg:e6 a3 93 23 11 dev:16 foc/bscfg:17/6c agctrl:03 40 91 frend:56 10 (DEM_DCFILT_OFF) - DR:38.4kb Dev:20khz Mod:GFSK RXBW:94kHz sensitive fsctrl1:08 mdmcfg:ca a3 13 23 11 dev:36 foc/bscfg:16/6c agctrl:43 40 91 frend:56 10 (IF changes, Deviation) - DR:38.4kb Dev:20khz Mod:GFSK RXBW:94kHz lowpower fsctrl1:08 mdmcfg:ca a3 93 23 11 dev:36 foc/bscfg:16/6c agctrl:43 40 91 frend:56 10 (.. DEM_DCFILT_OFF) - - DR:250kb Dev:129khz Mod:GFSK RXBW:600kHz sensitive fsctrl1:0c mdmcfg:1d 55 13 23 11 dev:63 foc/bscfg:1d/1c agctrl:c7 00 b0 frend:b6 10 (IF_changes, Deviation) - - DR:500kb Mod:MSK RXBW:750kHz sensitive fsctrl1:0e mdmcfg:0e 55 73 43 11 dev:00 foc/bscfg:1d/1c agctrl:c7 00 b0 frend:b6 10 (IF_changes, Modulation of course, Deviation has different meaning with MSK) - ''' - if radiocfg==None: - self.getRadioConfig() - radiocfg = self.radiocfg - - chanbw_e = None - chanbw_m = None - for e in range(4): - m = int(((mhz*1000000.0 / (bw *pow(2,e) * 8.0 )) - 4) + .5) # rounded evenly - if m < 4: - chanbw_e = e - chanbw_m = m - break - if chanbw_e is None: - raise(Exception("ChanBW does not translate into acceptable parameters. Should you be changing this?")) - - bw = 1000.0*mhz / (8.0*(4+chanbw_m) * pow(2,chanbw_e)) - #print "chanbw_e: %x chanbw_m: %x chanbw: %f kHz" % (e, m, bw) - - radiocfg.mdmcfg4 &= ~(MDMCFG4_CHANBW_E | MDMCFG4_CHANBW_M) - radiocfg.mdmcfg4 |= ((chanbw_e<<6) | (chanbw_m<<4)) - self.setRFRegister(MDMCFG4, (radiocfg.mdmcfg4)) - - def getMdmChanBW(self, mhz=24, radiocfg=None): - if radiocfg==None: - self.getRadioConfig() - radiocfg = self.radiocfg - - chanbw_e = (radiocfg.mdmcfg4 >> 6) & 0x3 - chanbw_m = (radiocfg.mdmcfg4 >> 4) & 0x3 - bw = 1000000.0*mhz / (8.0*(4+chanbw_m) * pow(2,chanbw_e)) - #print "chanbw_e: %x chanbw_m: %x chanbw: %f hz" % (chanbw_e, chanbw_m, bw) - return bw - - def setMdmDRate(self, drate, mhz=24, radiocfg=None): - ''' - set the baud of data being modulated through the radio - ''' - if radiocfg==None: - self.getRadioConfig() - radiocfg = self.radiocfg - - drate_e = None - drate_m = None - for e in range(16): - m = int((drate * pow(2,28) / (pow(2,e)* (mhz*1000000.0))-256) + .5) # rounded evenly - if m < 256: - drate_e = e - drate_m = m - break - if drate_e is None: - raise(Exception("DRate does not translate into acceptable parameters. Should you be changing this?")) - - drate = 1000000.0 * mhz * (256+drate_m) * pow(2,drate_e) / pow(2,28) - if self._debug: print "drate_e: %x drate_m: %x drate: %f Hz" % (drate_e, drate_m, drate) - - radiocfg.mdmcfg3 = drate_m - radiocfg.mdmcfg4 &= ~MDMCFG4_DRATE_E - radiocfg.mdmcfg4 |= drate_e - self.setRFRegister(MDMCFG3, (radiocfg.mdmcfg3)) - self.setRFRegister(MDMCFG4, (radiocfg.mdmcfg4)) - - def getMdmDRate(self, mhz=24, radiocfg=None): - ''' - get the baud of data being modulated through the radio - ''' - if radiocfg==None: - self.getRadioConfig() - radiocfg = self.radiocfg - - drate_e = radiocfg.mdmcfg4 & 0xf - drate_m = radiocfg.mdmcfg3 - - drate = 1000000.0 * mhz * (256+drate_m) * pow(2,drate_e) / pow(2,28) - #print "drate_e: %x drate_m: %x drate: %f hz" % (drate_e, drate_m, drate) - return drate - - - def setMdmDeviatn(self, deviatn, mhz=24, radiocfg=None): - ''' - configure the deviation settings for the given modulation scheme - ''' - if radiocfg==None: - self.getRadioConfig() - radiocfg = self.radiocfg - - dev_e = None - dev_m = None - for e in range(8): - m = int((deviatn * pow(2,17) / (pow(2,e)* (mhz*1000000.0))-8) + .5) # rounded evenly - if m < 8: - dev_e = e - dev_m = m - break - if dev_e is None: - raise(Exception("Deviation does not translate into acceptable parameters. Should you be changing this?")) - - dev = 1000000.0 * mhz * (8+dev_m) * pow(2,dev_e) / pow(2,17) - #print "dev_e: %x dev_m: %x deviatn: %f Hz" % (e, m, dev) - - radiocfg.deviatn = (dev_e << 4) | dev_m - self.setRFRegister(DEVIATN, radiocfg.deviatn) - - def getMdmDeviatn(self, mhz=24, radiocfg=None): - if radiocfg==None: - self.getRadioConfig() - radiocfg = self.radiocfg - - dev_e = radiocfg.deviatn >> 4 - dev_m = radiocfg.deviatn & DEVIATN_DEVIATION_M - dev = 1000000.0 * mhz * (8+dev_m) * pow(2,dev_e) / pow(2,17) - return dev - - - def setMdmSyncWord(self, word, radiocfg=None): - if radiocfg==None: - self.getRadioConfig() - radiocfg = self.radiocfg - - radiocfg.sync1 = word >> 8 - radiocfg.sync0 = word & 0xff - self.setRFRegister(SYNC1, (radiocfg.sync1)) - self.setRFRegister(SYNC0, (radiocfg.sync0)) - - def getMdmSyncMode(self, radiocfg=None): - if radiocfg==None: - self.getRadioConfig() - radiocfg = self.radiocfg - - return radiocfg.mdmcfg2 & MDMCFG2_SYNC_MODE - - def setMdmSyncMode(self, syncmode=SYNCM_15_of_16, radiocfg=None): - if radiocfg==None: - self.getRadioConfig() - radiocfg = self.radiocfg - - radiocfg.mdmcfg2 &= ~MDMCFG2_SYNC_MODE - radiocfg.mdmcfg2 |= syncmode - self.setRFRegister(MDMCFG2, (radiocfg.mdmcfg2)) - - def getMdmNumPreamble(self, radiocfg=None): - ''' - get the minimum number of preamble bits to be transmitted. note this is a flag, not a count - so the return value must be interpeted - e.g. 0x30 == 0x03 << 4 == MFMCFG1_NUM_PREAMBLE_6 == 6 bytes - ''' - if radiocfg == None: - self.getRadioConfig() - radiocfg = self.radiocfg - - preamble= (radiocfg.mdmcfg1 & MFMCFG1_NUM_PREAMBLE) - return preamble - - def setMdmNumPreamble(self, preamble=MFMCFG1_NUM_PREAMBLE_4, radiocfg=None): - ''' - set the minimum number of preamble bits to be transmitted (default: MFMCFG1_NUM_PREAMBLE_4) - ''' - if radiocfg == None: - self.getRadioConfig() - radiocfg = self.radiocfg - - radiocfg.mdmcfg1 &= ~MFMCFG1_NUM_PREAMBLE - radiocfg.mdmcfg1 |= preamble - self.setRFRegister(MDMCFG1, (radiocfg.mdmcfg1)) - - def getBSLimit(self, radiocfg=None): - ''' - get the saturation point for the data rate offset compensation algorithm - ''' - if radiocfg==None: - self.getRadioConfig() - radiocfg = self.radiocfg - - return radiocfg.bscfg&BSCFG_BS_LIMIT - - def setBSLimit(self, bslimit, radiocfg=None): - ''' - set the saturation point for the data rate offset compensation algorithm - ''' - if radiocfg==None: - self.getRadioConfig() - radiocfg = self.radiocfg - - radiocfg.bscfg &= ~BSCFG_BS_LIMIT - radiocfg.bscfg |= bslimit - self.setRFRegister(BSCFG, (radiocfg.bscfg)) - - def calculateMdmDeviatn(self, mhz=24, radiocfg=None): - ''' calculates the optimal DEVIATN setting for the current freq/baud - * totally experimental * - from Smart RF Studio: - 1.2 kbaud 5.1khz dev - 2.4 kbaud 5.1khz dev - 38.4 kbaud 20khz dev - 250 kbaud 129khz dev - ''' - baud = self.getMdmDRate(mhz, radiocfg) - if baud <= 2400: - deviatn = 5100 - elif baud <= 38400: - deviatn = 20000 * ((baud-2400)/36000) - else: - deviatn = 129000 * ((baud-38400)/211600) - self.setMdmDeviatn(deviatn) - - def calculatePktChanBW(self, mhz=24, radiocfg=None): - ''' calculates the optimal ChanBW setting for the current freq/baud - * totally experimental * - from Smart RF Studio: - 1.2 kbaud BW: 63khz - 2.4 kbaud BW: 63khz - 38.4kbaud BW: 94khz - 250 kbaud BW: 600khz - ''' - freq, freqhex = self.getFreq() - center_freq = freq + 14000000 - freq_uncertainty = 20e-6 * freq # +-20ppm - freq_uncertainty *= 2 # both xmitter and receiver - #minbw = (2 * freq_uncertainty) + self.getMdmDRate() # uncertainty for both sender/receiver - minbw = (self.getMdmDRate() + freq_uncertainty) - - possibles = [ 53e3,63e3,75e3,93e3,107e3,125e3,150e3,188e3,214e3,250e3,300e3,375e3,428e3,500e3,600e3,750e3, ] - for bw in possibles: - #if (.8 * bw) > minbw: # can't occupy more the 80% of BW - if (bw) > minbw: - break - self.setMdmChanBW(bw, mhz, radiocfg) - - def calculateFsIF(self, mhz=24, radiocfg=None): - ''' calculates the optimal IF setting for the current freq/baud - * totally experimental * - 1.2 kbaud IF: 140khz - 2.4 kbaud IF: 140khz - 38.4kbaud IF: 164khz (140khz for "sensitive" version) - 250 kbaud IF: 281khz - 500 kbaud IF: 328khz - ''' - pass - def calculateFsOffset(self, mhz=24, radiocfg=None): - ''' calculates the optimal FreqOffset setting for the current freq/baud - * totally experimental * - ''' - - pass - - def reprModemConfig(self, mhz=24, radiocfg=None): - output = [] - if radiocfg==None: - self.getRadioConfig() - radiocfg = self.radiocfg - - reprMdmModulation = self.reprMdmModulation(radiocfg) - syncmode = self.getMdmSyncMode(radiocfg) - - output.append(reprMdmModulation) - - drate = self.getMdmDRate(mhz, radiocfg) - output.append("DRate: %f hz"%drate) - - bw = self.getMdmChanBW(mhz, radiocfg) - output.append("ChanBW: %f hz"%bw) - - output.append("DEVIATION: %f hz" % self.getMdmDeviatn(mhz, radiocfg)) - - output.append("Sync Mode: %s" % SYNCMODES[syncmode]) - - num_preamble = (radiocfg.mdmcfg1>>4)&7 - output.append("Min TX Preamble: %d bytes" % (NUM_PREAMBLE[num_preamble]) ) - - chanspc = self.getMdmChanSpc(mhz, radiocfg) - output.append("Chan Spacing: %f hz" % chanspc) - - bslimit = radiocfg.bscfg & BSCFG_BS_LIMIT - output.append("BSLimit: %s"%BSLIMITS[bslimit]) - - output.append("DC Filter: %s" % (("enabled", "disabled")[self.getEnableMdmDCFilter(radiocfg)])) - - mchstr = self.getEnableMdmManchester(radiocfg) - output.append("Manchester Encoding: %s" % (("disabled","enabled")[mchstr])) - - fec = self.getEnableMdmFEC(radiocfg) - output.append("Fwd Err Correct: %s" % (("disabled","enabled")[fec])) - - - return "\n".join(output) - - def getRSSI(self): - rssi = self.peek(RSSI) - return rssi - - def getLQI(self): - lqi = self.peek(LQI) - return lqi - - - def reprRadioTestSignalConfig(self, radiocfg=None): - if radiocfg==None: - self.getRadioConfig() - radiocfg = self.radiocfg - - output = [] - #output.append("GDO2_INV: %s" % ("do not Invert Output", "Invert output")[(radiocfg.iocfg2>>6)&1]) - #output.append("GDO2CFG: 0x%x" % (radiocfg.iocfg2&0x3f)) - #output.append("GDO_DS: %s" % (("minimum drive (>2.6vdd","Maximum drive (<2.6vdd)")[radiocfg.iocfg1>>7])) - #output.append("GDO1_INV: %s" % ("do not Invert Output", "Invert output")[(radiocfg.iocfg1>>6)&1]) - #output.append("GDO1CFG: 0x%x"%(radiocfg.iocfg1&0x3f)) - #output.append("GDO0_INV: %s" % ("do not Invert Output", "Invert output")[(radiocfg.iocfg0>>6)&1]) - #output.append("GDO0CFG: 0x%x"%(radiocfg.iocfg0&0x3f)) - output.append("TEST2: 0x%x"%radiocfg.test2) - output.append("TEST1: 0x%x"%radiocfg.test1) - output.append("TEST0: 0x%x"%(radiocfg.test0&0xfd)) - output.append("VCO_SEL_CAL_EN: 0x%x"%((radiocfg.test2>>1)&1)) - return "\n".join(output) - - - def reprFreqConfig(self, mhz=24, radiocfg=None): - if radiocfg==None: - self.getRadioConfig() - radiocfg = self.radiocfg - - output = [] - freq,num = self.getFreq(mhz, radiocfg) - output.append("Frequency: %f hz (%s)" % (freq,num)) - - output.append("Channel: %d" % radiocfg.channr) - - - freq_if = self.getFsIF(mhz, radiocfg) - freqoff = self.getFsOffset(mhz, radiocfg) - - output.append("Intermediate freq: %d hz" % freq_if) - output.append("Frequency Offset: %d +/-" % freqoff) - - return "\n".join(output) - - def reprAESMode(self): - output = [] - aesmode= ord(self.getAESmode()[0]) - - output.append("AES Mode: %s" % AESMODES[(aesmode & AES_CRYPTO_MODE)]) - if aesmode & AES_CRYPTO_IN_ENABLE: - output.append("Crypt RF Input: %s" % ("Decrypt", "Encrypt")[(aesmode & AES_CRYPTO_IN_TYPE)]) - else: - output.append("Crypt RF Input: off") - if aesmode & AES_CRYPTO_OUT_ENABLE: - output.append("Crypt RF Output: %s" % ("Decrypt", "Encrypt")[(aesmode & AES_CRYPTO_OUT_TYPE) >> 2]) - else: - output.append("Crypt RF Output: off") - - return "\n".join(output) - - def reprPacketConfig(self, radiocfg=None): - if radiocfg==None: - self.getRadioConfig() - radiocfg = self.radiocfg - - output = [] - output.append("Sync Word: 0x%.2X%.2X" % (radiocfg.sync1, radiocfg.sync0)) - output.append("Packet Length: %d" % radiocfg.pktlen) - length_config = radiocfg.pktctrl0&3 - output.append("Length Config: %s" % LENGTH_CONFIGS[length_config]) - - output.append("Configured Address: 0x%x" % radiocfg.addr) - - pqt = self.getPktPQT(radiocfg) - output.append("Preamble Quality Threshold: 4 * %d" % pqt) - - append = (radiocfg.pktctrl1>>2) & 1 - output.append("Append Status: %s" % ("No","Yes")[append]) - - adr_chk = radiocfg.pktctrl1&3 - output.append("Rcvd Packet Check: %s" % ADR_CHK_TYPES[adr_chk]) - - whitedata = self.getEnablePktDataWhitening(radiocfg) - output.append("Data Whitening: %s" % ("off", "ON (but only with cc2400_en==0)")[whitedata]) - - pkt_format = (radiocfg.pktctrl0>>5)&3 - output.append("Packet Format: %s" % PKT_FORMATS[pkt_format]) - - crc = self.getEnablePktCRC(radiocfg) - output.append("CRC: %s" % ("disabled", "ENABLED")[crc]) - - return "\n".join(output) - """ - SYNC1 = 0xa9; - SYNC0 = 0x47; - PKTLEN = 0xff; - PKTCTRL1 = 0x04; // APPEND_STATUS - PKTCTRL0 = 0x01; // VARIABLE LENGTH, no crc, no whitening - ADDR = 0x00; - CHANNR = 0x00; - FSCTRL1 = 0x0c; // IF - FSCTRL0 = 0x00; - FREQ2 = 0x25; - FREQ1 = 0x95; - FREQ0 = 0x55; - MDMCFG4 = 0x1d; // chan_bw and drate_e - MDMCFG3 = 0x55; // drate_m - MDMCFG2 = 0x13; // gfsk, 30/32+carrier sense sync - MDMCFG1 = 0x23; // 4-preamble-bytes, chanspc_e - MDMCFG0 = 0x11; // chanspc_m - DEVIATN = 0x63; - MCSM2 = 0x07; // RX_TIMEOUT - MCSM1 = 0x30; // CCA_MODE RSSI below threshold unless currently recvg pkt - MCSM0 = 0x18; // fsautosync when going from idle to rx/tx/fstxon - FOCCFG = 0x1d; - BSCFG = 0x1c; // bit sync config - AGCCTRL2 = 0xc7; - AGCCTRL1 = 0x00; - AGCCTRL0 = 0xb0; - FREND1 = 0xb6; - FREND0 = 0x10; - FSCAL3 = 0xea; - FSCAL2 = 0x2a; - FSCAL1 = 0x00; - FSCAL0 = 0x1f; - TEST2 = 0x88; - TEST1 = 0x31; - TEST0 = 0x09; - PA_TABLE0 = 0x83; -""" - - def reprRadioState(self, radiocfg=None): - output = [] - try: - if radiocfg==None: - self.getRadioConfig() - radiocfg = self.radiocfg - - output.append(" MARCSTATE: %s (%x)" % (self.getMARCSTATE(radiocfg))) - output.append(" DONGLE RESPONDING: mode :%x, last error# %d"%(self.getDebugCodes())) - except: - output.append(repr(sys.exc_info())) - output.append(" DONGLE *not* RESPONDING") - - return "\n".join(output) - - def reprClientState(self, width=120): - output = ["="*width] - output.append(' client thread cycles: %d' % self.threadcounter) - output.append(' client errored cycles: %d' % self._usberrorcnt) - output.append(' recv_queue: (%d bytes) %s'%(len(self.recv_queue),repr(self.recv_queue)[:width-42])) - output.append(' trash: (%d blobs) "%s"'%(len(self.trash),repr(self.trash)[:width-44])) - output.append(' recv_mbox (%d keys) "%s"'%(len(self.recv_mbox),repr([hex(x) for x in self.recv_mbox.keys()])[:width-44])) - for app in self.recv_mbox.keys(): - appbox = self.recv_mbox[app] - output.append(' app 0x%x (%d records)'%(app,len(appbox))) - for cmd in appbox.keys(): - output.append(' [0x%x] (%d frames) "%s"'%(cmd, len(appbox[cmd]), repr(appbox[cmd])[:width-36])) - output.append('') - return "\n".join(output) - - - ######## APPLICATION METHODS ######## - def setup900MHz(self): - self.getRadioConfig() - rc = self.radiocfg - rc.iocfg0 = 0x06 - rc.sync1 = 0x0b - rc.sync0 = 0x0b - rc.pktlen = 0xff - rc.pktctrl1 = 0xe5 - rc.pktctrl0 = 0x04 - rc.fsctrl1 = 0x12 - rc.fsctrl0 = 0x00 - rc.addr = 0x00 - rc.channr = 0x00 - rc.mdmcfg4 = 0x3e - rc.mdmcfg3 = 0x55 - rc.mdmcfg2 = 0x73 - rc.mdmcfg1 = 0x23 - rc.mdmcfg0 = 0x55 - rc.mcsm2 = 0x07 - rc.mcsm1 = 0x30 - rc.mcsm0 = 0x00 - rc.deviatn = 0x16 - rc.foccfg = 0x17 - rc.bscfg = 0x6c - rc.agcctrl2 |= AGCCTRL2_MAX_DVGA_GAIN - rc.agcctrl2 = 0x03 - rc.agcctrl1 = 0x40 - rc.agcctrl0 = 0x91 - rc.frend1 = 0x56 - rc.frend0 = 0x10 - rc.fscal3 = 0xEA - rc.fscal2 = 0x2A - rc.fscal1 = 0x00 - rc.fscal0 = 0x1F - rc.test2 = 0x88 - rc.test1 = 0x31 - rc.test0 = 0x09 - rc.pa_table0 = 0xc0 - self.setRadioConfig() - - def setup900MHzHopTrans(self): - self.getRadioConfig() - rc = self.radiocfg - rc.iocfg0 = 0x06 - rc.sync1 = 0x0b - rc.sync0 = 0x0b - rc.pktlen = 0xff - rc.pktctrl1 = 0x04 - rc.pktctrl0 = 0x05 - rc.addr = 0x00 - rc.channr = 0x00 - rc.fsctrl1 = 0x06 - rc.fsctrl0 = 0x00 - rc.mdmcfg4 = 0xee - rc.mdmcfg3 = 0x55 - rc.mdmcfg2 = 0x73 - rc.mdmcfg1 = 0x23 - rc.mdmcfg0 = 0x55 - rc.mcsm2 = 0x07 - rc.mcsm1 = 0x30 - rc.mcsm0 = 0x18 - rc.deviatn = 0x16 - rc.foccfg = 0x17 - rc.bscfg = 0x6c - rc.agcctrl2 = 0x03 - rc.agcctrl1 = 0x40 - rc.agcctrl0 = 0x91 - rc.frend1 = 0x56 - rc.frend0 = 0x10 - rc.fscal3 = 0xEA - rc.fscal2 = 0x2A - rc.fscal1 = 0x00 - rc.fscal0 = 0x1F - rc.test2 = 0x88 - rc.test1 = 0x31 - rc.test0 = 0x09 - rc.pa_table0 = 0xc0 - self.setRadioConfig() - - def setup900MHzContTrans(self): - self.getRadioConfig() - rc = self.radiocfg - rc.iocfg0 = 0x06 - rc.sync1 = 0x0b - rc.sync0 = 0x0b - rc.pktlen = 0xff - rc.pktctrl1 = 0x04 - rc.pktctrl0 = 0x05 - rc.addr = 0x00 - rc.channr = 0x00 - rc.fsctrl1 = 0x06 - rc.fsctrl0 = 0x00 - rc.freq2 = 0x26 - rc.freq1 = 0x55 - rc.freq0 = 0x55 - rc.mdmcfg4 = 0xee - rc.mdmcfg3 = 0x55 - rc.mdmcfg2 = 0x73 - rc.mdmcfg1 = 0x23 - rc.mdmcfg0 = 0x55 - rc.mcsm2 = 0x07 - rc.mcsm1 = 0x30 - rc.mcsm0 = 0x18 - rc.deviatn = 0x16 - rc.foccfg = 0x17 - rc.bscfg = 0x6c - rc.agcctrl2 = 0x03 - rc.agcctrl1 = 0x40 - rc.agcctrl0 = 0x91 - rc.frend1 = 0x56 - rc.frend0 = 0x10 - rc.fscal3 = 0xEA - rc.fscal2 = 0x2A - rc.fscal1 = 0x00 - rc.fscal0 = 0x1F - rc.test2 = 0x88 - rc.test1 = 0x31 - rc.test0 = 0x09 - rc.pa_table0 = 0xc0 - self.setRadioConfig() - - def setup_rfstudio_902PktTx(self): - self.getRadioConfig() - rc = self.radiocfg - rc.iocfg2 = 0x00 - rc.iocfg1 = 0x00 - rc.iocfg0 = 0x06 - rc.sync1 = 0x0b - rc.sync0 = 0x0b - rc.pktlen = 0xff - rc.pktctrl1 = 0x04 - rc.pktctrl0 = 0x05 - rc.addr = 0x00 - rc.channr = 0x00 - rc.fsctrl1 = 0x0c - rc.fsctrl0 = 0x00 - rc.freq2 = 0x25 - rc.freq1 = 0x95 - rc.freq0 = 0x55 - rc.mdmcfg4 = 0x1d - rc.mdmcfg3 = 0x55 - rc.mdmcfg2 = 0x13 - rc.mdmcfg1 = 0x23 - rc.mdmcfg0 = 0x11 - rc.mcsm2 = 0x07 - rc.mcsm1 = 0x30 - rc.mcsm0 = 0x18 - rc.deviatn = 0x63 - rc.foccfg = 0x1d - rc.bscfg = 0x1c - rc.agcctrl2 = 0xc7 - rc.agcctrl1 = 0x00 - rc.agcctrl0 = 0xb0 - rc.frend1 = 0xb6 - rc.frend0 = 0x10 - rc.fscal3 = 0xEA - rc.fscal2 = 0x2A - rc.fscal1 = 0x00 - rc.fscal0 = 0x1F - rc.test2 = 0x88 - rc.test1 = 0x31 - rc.test0 = 0x09 - rc.pa_table7 = 0x00 - rc.pa_table6 = 0x00 - rc.pa_table5 = 0x00 - rc.pa_table4 = 0x00 - rc.pa_table3 = 0x00 - rc.pa_table2 = 0x00 - rc.pa_table1 = 0x00 - #rc.pa_table0 = 0x8e - rc.pa_table0 = 0xc0 - self.setRadioConfig() - - def lowball(self, level=1, sync=0xaaaa, length=250, pqt=0, crc=False, fec=False, datawhite=False): - ''' - this configures the radio to the lowest possible level of filtering, potentially allowing complete radio noise to come through as data. very useful in some circumstances. - level == 0 changes the Sync Mode to SYNCM_NONE (wayyy more garbage) - level == 1 (default) sets the Sync Mode to SYNCM_CARRIER (requires a valid carrier detection for the data to be considered a packet) - level == 2 sets the Sync Mode to SYNCM_CARRIER_15_of_16 (requires a valid carrier detection and 15 of 16 bits of SYNC WORD match for the data to be considered a packet) - level == 3 sets the Sync Mode to SYNCM_CARRIER_16_of_16 (requires a valid carrier detection and 16 of 16 bits of SYNC WORD match for the data to be considered a packet) - ''' - if hasattr(self, '_last_radiocfg') and len(self._last_radiocfg): - print('not saving radio state. already have one saved. use lowballRestore() to restore the saved config and the next time you run lowball() the radio config will be saved.') - else: - self._last_radiocfg = self.getRadioConfig() - - self.makePktFLEN(length) - self.setEnablePktCRC(crc) - self.setEnableMdmFEC(fec) - self.setEnablePktDataWhitening(datawhite) - self.setMdmSyncWord(sync) - self.setPktPQT(pqt) - - if (level == 3): - self.setMdmSyncMode(SYNCM_CARRIER_16_of_16) - elif (level == 2): - self.setMdmSyncMode(SYNCM_15_of_16) - elif (level == 1): - self.setMdmSyncMode(SYNCM_CARRIER) - else: - self.setMdmSyncMode(SYNCM_NONE) - - - def lowballRestore(self): - if not hasattr(self, '_last_radiocfg'): - raise(Exception("lowballRestore requires that lowball have been executed first (it saves radio config state!)")) - self.setRadioConfig(self._last_radiocfg) - self._last_radiocfg = '' - - - def checkRepr(self, matchstr, checkval, maxdiff=0): - starry = self.reprRadioConfig().split('\n') - line,val = getValueFromReprString(starry, matchstr) - try: - f = checkval.__class__(val.split(" ")[0]) - if abs(f-checkval) <= maxdiff: - print " passed: reprRadioConfig test: %s %s" % (repr(val), checkval) - else: - print " *FAILED* reprRadioConfig test: %s %s %s" % (repr(line), repr(val), checkval) - - except ValueError, e: - print " ERROR checking repr: %s" % e - - -def unittest(self, mhz=24): - print "\nTesting USB ping()" - self.ping(3) - - print "\nTesting USB ep0Ping()" - self.ep0Ping() - - print "\nTesting USB enumeration" - print "getString(0,100): %s" % repr(self._do.getString(0,100)) - - print "\nTesting USB EP MAX_PACKET_SIZE handling (ep0Peek(0xf000, 100))" - print repr(self.ep0Peek(0xf000, 100)) - - print "\nTesting USB EP MAX_PACKET_SIZE handling (peek(0xf000, 300))" - print repr(self.peek(0xf000, 400)) - - print "\nTesting USB poke/peek" - data = "".join([chr(c) for c in xrange(120)]) - where = 0xf300 - self.poke(where, data) - ndata = self.peek(where, len(data)) - if ndata != data: - print " *FAILED*\n '%s'\n '%s'" % (data.encode("hex"), ndata.encode("hex")) - raise(Exception(" *FAILED*\n '%s'\n '%s'" % (data.encode("hex"), ndata.encode("hex")))) - else: - print " passed '%s'" % (ndata.encode("hex")) - - print "\nTesting getValueFromReprString()" - starry = self.reprRadioConfig().split('\n') - print repr(getValueFromReprString(starry, 'hz')) - - print "\nTesting reprRadioConfig()" - print self.reprRadioConfig() - - print "\nTesting Frequency Get/Setters" - # FREQ - freq0,freq0str = self.getFreq() - - testfreq = 902000000 - self.setFreq(testfreq) - freq,freqstr = self.getFreq() - if abs(testfreq - freq) < 1024: - print " passed: %d : %f (diff: %f)" % (testfreq, freq, testfreq-freq) - else: - print " *FAILED* %d : %f (diff: %f)" % (testfreq, freq, testfreq-freq) - - testfreq = 868000000 - self.setFreq(testfreq) - freq,freqstr = self.getFreq() - if abs(testfreq - freq) < 1024: - print " passed: %d : %f (diff: %f)" % (testfreq, freq, testfreq-freq) - else: - print " *FAILED* %d : %f (diff: %f)" % (testfreq, freq, testfreq-freq) - - testfreq = 433000000 - self.setFreq(testfreq) - freq,freqstr = self.getFreq() - if abs(testfreq - freq) < 1024: - print " passed: %d : %f (diff: %f)" % (testfreq, freq, testfreq-freq) - else: - print " *FAILED* %d : %f (diff: %f)" % (testfreq, freq, testfreq-freq) - - self.checkRepr("Frequency:", float(testfreq), 1024) - self.setFreq(freq0) - - # CHANNR - channr0 = self.getChannel() - for x in range(15): - self.setChannel(x) - channr = self.getChannel() - if channr != x: - print " *FAILED* get/setChannel(): %d : %d" % (x, channr) - else: - print " passed: get/setChannel(): %d : %d" % (x, channr) - self.checkRepr("Channel:", channr) - self.setChannel(channr0) - - # IF and FREQ_OFF - freq_if = self.getFsIF() - freqoff = self.getFsOffset() - for fif, foff in ((164062,1),(140625,2),(187500,3)): - self.setFsIF(fif) - self.setFsOffset(foff) - nfif = self.getFsIF() - nfoff = self.getFsOffset() - if abs(nfif - fif) > 5: - print " *FAILED* get/setFsIFandOffset(): %d : %f (diff: %f)" % (fif,nfif,nfif-fif) - else: - print " passed: get/setFsIFandOffset(): %d : %f (diff: %f)" % (fif,nfif,nfif-fif) - - if foff != nfoff: - print " *FAILED* get/setFsIFandOffset(): %d : %d (diff: %d)" % (foff,nfoff,nfoff-foff) - else: - print " passed: get/setFsIFandOffset(): %d : %d (diff: %d)" % (foff,nfoff,nfoff-foff) - self.checkRepr("Intermediate freq:", fif, 11720) - self.checkRepr("Frequency Offset:", foff) - - self.setFsIF(freq_if) - self.setFsOffset(freqoff) - - ### continuing with more simple tests. add completeness later? - # Modem tests - - mod = self.getMdmModulation(self.radiocfg) - self.setMdmModulation(mod, self.radiocfg) - modcheck = self.getMdmModulation(self.radiocfg) - if mod != modcheck: - print " *FAILED* get/setMdmModulation(): %d : %d " % (mod, modcheck) - else: - print " passed: get/setMdmModulation(): %d : %d " % (mod, modcheck) - - chanspc = self.getMdmChanSpc(mhz, self.radiocfg) - self.setMdmChanSpc(chanspc, mhz, self.radiocfg) - chanspc_check = self.getMdmChanSpc(mhz, self.radiocfg) - if chanspc != chanspc_check: - print " *FAILED* get/setMdmChanSpc(): %d : %d" % (chanspc, chanspc_check) - else: - print " passed: get/setMdmChanSpc(): %d : %d" % (chanspc, chanspc_check) - - chanbw = self.getMdmChanBW(mhz, self.radiocfg) - self.setMdmChanBW(chanbw, mhz, self.radiocfg) - chanbw_check = self.getMdmChanBW(mhz, self.radiocfg) - if chanbw != chanbw_check: - print " *FAILED* get/setMdmChanBW(): %d : %d" % (chanbw, chanbw_check) - else: - print " passed: get/setMdmChanBW(): %d : %d" % (chanbw, chanbw_check) - - drate = self.getMdmDRate(mhz, self.radiocfg) - self.setMdmDRate(drate, mhz, self.radiocfg) - drate_check = self.getMdmDRate(mhz, self.radiocfg) - if drate != drate_check: - print " *FAILED* get/setMdmDRate(): %d : %d" % (drate, drate_check) - else: - print " passed: get/setMdmDRate(): %d : %d" % (drate, drate_check) - - deviatn = self.getMdmDeviatn(mhz, self.radiocfg) - self.setMdmDeviatn(deviatn, mhz, self.radiocfg) - deviatn_check = self.getMdmDeviatn(mhz, self.radiocfg) - if deviatn != deviatn_check: - print " *FAILED* get/setMdmdeviatn(): %d : %d" % (deviatn, deviatn_check) - else: - print " passed: get/setMdmdeviatn(): %d : %d" % (deviatn, deviatn_check) - - syncm = self.getMdmSyncMode(self.radiocfg) - self.setMdmSyncMode(syncm, self.radiocfg) - syncm_check = self.getMdmSyncMode(self.radiocfg) - if syncm != syncm_check: - print " *FAILED* get/setMdmSyncMode(): %d : %d" % (syncm, syncm_check) - else: - print " passed: get/setMdmSyncMode(): %d : %d" % (syncm, syncm_check) - - mchstr = self.getEnableMdmManchester(self.radiocfg) - self.setEnableMdmManchester(mchstr, self.radiocfg) - mchstr_check = self.getEnableMdmManchester(self.radiocfg) - if mchstr != mchstr_check: - print " *FAILED* get/setMdmManchester(): %d : %d" % (mchstr, mchstr_check) - else: - print " passed: get/setMdmManchester(): %d : %d" % (mchstr, mchstr_check) - - fec = self.getEnableMdmFEC(self.radiocfg) - self.setEnableMdmFEC(fec, self.radiocfg) - fec_check = self.getEnableMdmFEC(self.radiocfg) - if fec != fec_check: - print " *FAILED* get/setEnableMdmFEC(): %d : %d" % (fec, fec_check) - else: - print " passed: get/setEnableMdmFEC(): %d : %d" % (fec, fec_check) - - dcf = self.getEnableMdmDCFilter(self.radiocfg) - self.setEnableMdmDCFilter(dcf, self.radiocfg) - dcf_check = self.getEnableMdmDCFilter(self.radiocfg) - if dcf != dcf_check: - print " *FAILED* get/setEnableMdmDCFilter(): %d : %d" % (dcf, dcf_check) - else: - print " passed: get/setEnableMdmDCFilter(): %d : %d" % (dcf, dcf_check) - - - # Pkt tests - pqt = self.getPktPQT(self.radiocfg) - self.setPktPQT(pqt, self.radiocfg) - pqt_check = self.getPktPQT(self.radiocfg) - if pqt != pqt_check: - print " *FAILED* get/setEnableMdmFEC(): %d : %d" % (pqt, pqt_check) - else: - print " passed: get/setEnableMdmFEC(): %d : %d" % (pqt, pqt_check) - -def getValueFromReprString(stringarray, line_text): - for string in stringarray: - if line_text in string: - idx = string.find(":") - val = string[idx+1:].strip() - return (string,val) - -def mkFreq(freq=902000000, mhz=24): - freqmult = (0x10000 / 1000000.0) / mhz - num = int(freq * freqmult) - freq2 = num >> 16 - freq1 = (num>>8) & 0xff - freq0 = num & 0xff - return (num, freq2,freq1,freq0) - - - -if __name__ == "__main__": - idx = 0 - if len(sys.argv) > 1: - idx = int(sys.argv.pop()) - d = USBDongle(idx=idx) - diff --git a/rflib/ccspecan.py b/rflib/ccspecan.py index d0b362b..db96a58 100755 --- a/rflib/ccspecan.py +++ b/rflib/ccspecan.py @@ -29,6 +29,10 @@ import cPickle as pickle from PySide import QtCore, QtGui from PySide.QtCore import Qt, QPointF, QLineF +def ensureQapp(): + global _qt_app + if not globals().get("_qt_app"): + _qt_app = QtGui.QApplication([]) APP_SPECAN = 0x43 diff --git a/rflib/chipcon_nic.py b/rflib/chipcon_nic.py index 10a2517..f5d29b4 100644 --- a/rflib/chipcon_nic.py +++ b/rflib/chipcon_nic.py @@ -1,26 +1,59 @@ #!/usr/bin/env ipython +import re import sys import usb import code import time import struct +import pickle import threading -import re #from chipcondefs import * -from cc1111client import * +from chipcon_usb import * + +# band limits in Hz +FREQ_MIN_300 = 281000000 +FREQ_MAX_300 = 361000000 +FREQ_MIN_400 = 378000000 +FREQ_MAX_400 = 481000000 +FREQ_MIN_900 = 749000000 +FREQ_MAX_900 = 962000000 + +# band transition points in Hz +FREQ_EDGE_400 = 369000000 +FREQ_EDGE_900 = 615000000 + +# VCO transition points in Hz +FREQ_MID_300 = 318000000 +FREQ_MID_400 = 424000000 +FREQ_MID_900 = 848000000 + +SYNCM_NONE = 0 +SYNCM_15_of_16 = 1 +SYNCM_16_of_16 = 2 +SYNCM_30_of_32 = 3 +SYNCM_CARRIER = 4 +SYNCM_CARRIER_15_of_16 = 5 +SYNCM_CARRIER_16_of_16 = 6 +SYNCM_CARRIER_30_of_32 = 7 + +RF_SUCCESS = 0 + +RF_MAX_TX_BLOCK = 255 +# RF_MAX_BLOCK must match BUFFER_SIZE definition in firmware/include/cc1111rf.h +RF_MAX_RX_BLOCK = 512 APP_NIC = 0x42 +APP_SPECAN = 0x43 + NIC_RECV = 0x1 NIC_XMIT = 0x2 NIC_SET_ID = 0x3 -NIC_RFMODE = 0x4 NIC_SET_RECV_LARGE = 0x5 NIC_SET_AES_MODE = 0x6 NIC_GET_AES_MODE = 0x7 NIC_SET_AES_IV = 0x8 NIC_SET_AES_KEY = 0x9 - FHSS_SET_CHANNELS = 0x10 FHSS_NEXT_CHANNEL = 0x11 FHSS_CHANGE_CHANNEL = 0x12 @@ -54,6 +87,109 @@ for key,val in globals().items(): FHSS_STATES[key] = val FHSS_STATES[val] = key +""" MODULATIONS +Note that MSK is only supported for data rates above 26 kBaud and GFSK, +ASK , and OOK is only supported for data rate up until 250 kBaud. MSK +cannot be used if Manchester encoding/decoding is enabled. +""" +MOD_2FSK = 0x00 +MOD_GFSK = 0x10 +MOD_ASK_OOK = 0x30 +MOD_MSK = 0x70 +MANCHESTER = 0x08 + +MODULATIONS = { + MOD_2FSK : "2FSK", + MOD_GFSK : "GFSK", + MOD_ASK_OOK : "ASK/OOK", + MOD_MSK : "MSK", + MOD_2FSK | MANCHESTER : "2FSK/Manchester encoding", + MOD_GFSK | MANCHESTER : "GFSK/Manchester encoding", + MOD_ASK_OOK | MANCHESTER : "ASK/OOK/Manchester encoding", + MOD_MSK | MANCHESTER : "MSK/Manchester encoding", + } + +SYNCMODES = { + SYNCM_NONE: "None", + SYNCM_15_of_16: "15 of 16 bits must match", + SYNCM_16_of_16: "16 of 16 bits must match", + SYNCM_30_of_32: "30 of 32 sync bits must match", + SYNCM_CARRIER: "Carrier Detect", + SYNCM_CARRIER_15_of_16: "Carrier Detect and 15 of 16 sync bits must match", + SYNCM_CARRIER_16_of_16: "Carrier Detect and 16 of 16 sync bits must match", + SYNCM_CARRIER_30_of_32: "Carrier Detect and 30 of 32 sync bits must match", + } + +BSLIMITS = { + BSCFG_BS_LIMIT_0: "No data rate offset compensation performed", + BSCFG_BS_LIMIT_3: "+/- 3.125% data rate offset", + BSCFG_BS_LIMIT_6: "+/- 6.25% data rate offset", + BSCFG_BS_LIMIT_12: "+/- 12.5% data rate offset", + } + +AESMODES = { + ENCCS_MODE_CBC: "CBC - Cipher Block Chaining", + ENCCS_MODE_CBCMAC: "CBC-MAC - Cipher Block Chaining Message Authentication Code", + ENCCS_MODE_CFB: "CFB - Cipher Feedback", + ENCCS_MODE_CTR: "CTR - Counter", + ENCCS_MODE_ECB: "ECB - Electronic Codebook", + ENCCS_MODE_OFB: "OFB - Output Feedback", + } + +NUM_PREAMBLE = [2, 3, 4, 6, 8, 12, 16, 24 ] + +ADR_CHK_TYPES = [ + "No address check", + "Address Check, No Broadcast", + "Address Check, 0x00 is broadcast", + "Address Check, 0x00 and 0xff are broadcast", + ] + + + +PKT_FORMATS = [ + "Normal mode", + "reserved...", + "Random TX mode", + "reserved", + ] + +LENGTH_CONFIGS = [ + "Fixed Packet Mode", + "Variable Packet Mode (len=first byte after sync word)", + "reserved", + "reserved", + ] +MARC_STATE_MAPPINGS = [ + (0, 'MARC_STATE_SLEEP', RFST_SIDLE), + (1, 'MARC_STATE_IDLE', RFST_SIDLE), + (3, 'MARC_STATE_VCOON_MC', RFST_SIDLE), + (4, 'MARC_STATE_REGON_MC', RFST_SIDLE), + (5, 'MARC_STATE_MANCAL', RFST_SCAL), + (6, 'MARC_STATE_VCOON', RFST_SIDLE), + (7, 'MARC_STATE_REGON', RFST_SIDLE), + (8, 'MARC_STATE_STARTCAL', RFST_SCAL), + (9, 'MARC_STATE_BWBOOST', RFST_SIDLE), + (10, 'MARC_STATE_FS_LOCK', RFST_SIDLE), + (11, 'MARC_STATE_IFADCON', RFST_SIDLE), + (12, 'MARC_STATE_ENDCAL', RFST_SCAL), + (13, 'MARC_STATE_RX', RFST_SRX), + (14, 'MARC_STATE_RX_END', RFST_SRX ), # FIXME: this should actually be the config setting in register + (15, 'MARC_STATE_RX_RST', RFST_SRX), + (16, 'MARC_STATE_TXRX_SWITCH', RFST_SIDLE), + (17, 'MARC_STATE_RX_OVERFLOW', RFST_SIDLE), + (18, 'MARC_STATE_FSTXON', RFST_SFSTXON), + (19, 'MARC_STATE_TX', RFST_STX), + (20, 'MARC_STATE_TX_END', RFST_STX), # FIXME: this should actually be the config setting in register + (21, 'MARC_STATE_RXTX_SWITCH', RFST_SIDLE), + (22, 'MARC_STATE_TX_UNDERFLOW', RFST_SIDLE) # FIXME: this should actually be the config setting in register +] + +MODES = {} +for num,name,rfst in MARC_STATE_MAPPINGS: + MODES[num] = name + MODES[name] = num + T2SETTINGS = {} T2SETTINGS_24MHz = { @@ -71,62 +207,958 @@ T2SETTINGS_26MHz = { TIP = (64,128,256,1024) -def calculateT2(ms, mhz=24): +def makeFriendlyAscii(instring): + out = [] + start = 0 + last = -1 + instrlen = len(instring) + + for cidx in xrange(instrlen): + if (0x20 < ord(instring[cidx]) < 0x7f): + if last < cidx-1: + out.append( "." * (cidx-1-last)) + start = cidx + last = cidx + else: + if last == cidx-1: + out.append( instring[ start:last+1 ] ) + + if last != cidx: + out.append( "." * (cidx-last) ) + else: # if start == 0: + out.append( instring[ start: ] ) + + return ''.join(out) + + + + +def calculateT2(tick_ms, mhz=24): + # each tick, not each cycle TICKSPD = [(mhz*1000000/pow(2,x)) for x in range(8)] - ms = 1.0*ms/1000 + tick_ms = 1.0*tick_ms/1000 candidates = [] for tickidx in xrange(8): for tipidx in range(4): for PR in xrange(256): T = 1.0 * PR * TIP[tipidx] / TICKSPD[tickidx] - if abs(T-ms) < .010: + if abs(T-tick_ms) < .010: candidates.append((T, tickidx, tipidx, PR)) diff = 1024 best = None for c in candidates: - if abs(c[0] - ms) < diff: + if abs(c[0] - tick_ms) < diff: best = c - diff = abs(c[0] - ms) + diff = abs(c[0] - tick_ms) return best #return ms, candidates, best -def invertBits(data): - output = [] - ldata = len(data) - off = 0 - if ldata&1: - output.append( chr( ord( data[0] ) ^ 0xff) ) - off = 1 - - if ldata&2: - output.append( struct.pack( "> 16 + radiocfg.freq1 = (num>>8) & 0xff + radiocfg.freq0 = num & 0xff + + if (freq > FREQ_EDGE_900 and freq < FREQ_MID_900) or (freq > FREQ_EDGE_400 and freq < FREQ_MID_400) or (freq < FREQ_MID_300): + # select low VCO + radiocfg.fscal2 = 0x0A + elif freq <1e9 and ((freq > FREQ_MID_900) or (freq > FREQ_MID_400) or (freq > FREQ_MID_300)): + # select high VCO + radiocfg.fscal2 = 0x2A + + if applyConfig: + marcstate = radiocfg.marcstate + if marcstate != MARC_STATE_IDLE: + self.strobeModeIDLE() + self.poke(FREQ2, struct.pack("3B", self.radiocfg.freq2, self.radiocfg.freq1, self.radiocfg.freq0)) + self.poke(FSCAL2, struct.pack("B", self.radiocfg.fscal2)) + + self.strobeModeReturn(marcstate) + #if (radiocfg.marcstate == MARC_STATE_RX): + #self.strobeModeRX() + #elif (radiocfg.marcstate == MARC_STATE_TX): + #self.strobeModeTX() + + def getFreq(self, mhz=24, radiocfg=None): + freqmult = (0x10000 / 1000000.0) / mhz + if radiocfg==None: + self.getRadioConfig() + radiocfg = self.radiocfg + + num = (radiocfg.freq2<<16) + (radiocfg.freq1<<8) + radiocfg.freq0 + freq = num / freqmult + return freq, hex(num) + + def getFreqEst(self, radiocfg=None): + if radiocfg==None: + self.getRadioConfig() + radiocfg = self.radiocfg + + return radiocfg.freqest + + # auto-adjust frequency offset based on internal estimate from last received packet (FSK/MSK modes only) + # note radio must be in IDLE mode when called for this to have any effect + # the TI design note for this is missing from TI's main website but can be found here: + # http://e2e.ti.com/cfs-file/__key/telligent-evolution-components-attachments/00-155-01-00-00-73-46-38/DN015_5F00_Permanent_5F00_Frequency_5F00_Offset_5F00_Compensation.pdf + def adjustFreqOffset(self, mhz=24, radiocfg=None): + if radiocfg==None: + self.getRadioConfig() + radiocfg = self.radiocfg + + self.freq_offset_accumulator += self.getFreqEst(radiocfg) + self.freq_offset_accumulator &= 0xff + self.setFsOffset(self.freq_offset_accumulator, mhz, radiocfg) + + # set 'standard' power - for more complex power shaping this will need to be done manually + def setPower(self, power=None, radiocfg=None, invert=False): + if radiocfg == None: + self.getRadioConfig() + radiocfg = self.radiocfg + + mod= self.getMdmModulation(radiocfg=radiocfg) + + # we may be only changing PA_POWER, not power levels + if power is not None: + if mod == MOD_ASK_OOK and not invert: + radiocfg.pa_table0= 0x00 + radiocfg.pa_table1= power + else: + radiocfg.pa_table0= power + radiocfg.pa_table1= 0x00 + self.setRFRegister(PA_TABLE0, radiocfg.pa_table0) + self.setRFRegister(PA_TABLE1, radiocfg.pa_table1) + + radiocfg.frend0 &= ~FREND0_PA_POWER + if mod == MOD_ASK_OOK: + radiocfg.frend0 |= 0x01 + + self.setRFRegister(FREND0, radiocfg.frend0) + + # max power settings are frequency dependent, so set frequency before calling + def setMaxPower(self, radiocfg=None, invert=False): + if radiocfg == None: + self.getRadioConfig() + radiocfg = self.radiocfg + + freq= self.getFreq(radiocfg=radiocfg)[0] + + if freq <= 400000000: + power= 0xC2 + elif freq <= 464000000: + power= 0xC0 + elif freq <= 900000000: + power= 0xC2 + else: + power= 0xC0 + + self.setPower(power, radiocfg=radiocfg, invert=invert) + + def setMdmModulation(self, mod, radiocfg=None, invert=False): + if radiocfg == None: + self.getRadioConfig() + radiocfg = self.radiocfg + + if (mod) & ~MDMCFG2_MOD_FORMAT: + raise(Exception("Please use constants MOD_FORMAT_* to specify modulation and ")) + + radiocfg.mdmcfg2 &= ~MDMCFG2_MOD_FORMAT + radiocfg.mdmcfg2 |= (mod) + + power= None + # ASK_OOK needs to flip power table + if mod == MOD_ASK_OOK and not invert: + if radiocfg.pa_table1 == 0x00 and radiocfg.pa_table0 != 0x00: + power= radiocfg.pa_table0 + else: + if radiocfg.pa_table0 == 0x00 and radiocfg.pa_table1 != 0x00: + power= radiocfg.pa_table1 + + self.setRFRegister(MDMCFG2, radiocfg.mdmcfg2) + self.setPower(power, radiocfg=radiocfg, invert=invert) + + def getMdmModulation(self, radiocfg=None): + if radiocfg == None: + self.getRadioConfig() + radiocfg = self.radiocfg + + mdmcfg2 = radiocfg.mdmcfg2 + mod = (mdmcfg2) & MDMCFG2_MOD_FORMAT + return mod + + def getMdmChanSpc(self, mhz=24, radiocfg=None): + if radiocfg==None: + self.getRadioConfig() + radiocfg = self.radiocfg + + chanspc_m = radiocfg.mdmcfg0 + chanspc_e = radiocfg.mdmcfg1 & 3 + chanspc = 1000000.0 * mhz/pow(2,18) * (256 + chanspc_m) * pow(2, chanspc_e) + #print "chanspc_e: %x chanspc_m: %x chanspc: %f hz" % (chanspc_e, chanspc_m, chanspc) + return (chanspc) + + def setMdmChanSpc(self, chanspc=None, chanspc_m=None, chanspc_e=None, mhz=24, radiocfg=None): + ''' + calculates the appropriate exponent and mantissa and updates the correct registers + chanspc is in kHz. if you prefer, you may set the chanspc_m and chanspc_e settings + directly. + + only use one or the other: + * chanspc + * chanspc_m and chanspc_e + ''' + if radiocfg==None: + self.getRadioConfig() + radiocfg = self.radiocfg + + if (chanspc != None): + for e in range(4): + m = int(((chanspc * pow(2,18) / (1000000.0 * mhz * pow(2,e)))-256) +.5) # rounded evenly + if m < 256: + chanspc_e = e + chanspc_m = m + break + if chanspc_e is None or chanspc_m is None: + raise(Exception("ChanSpc does not translate into acceptable parameters. Should you be changing this?")) + + #chanspc = 1000000.0 * mhz/pow(2,18) * (256 + chanspc_m) * pow(2, chanspc_e) + #print "chanspc_e: %x chanspc_m: %x chanspc: %f hz" % (chanspc_e, chanspc_m, chanspc) + + radiocfg.mdmcfg1 &= ~MDMCFG1_CHANSPC_E # clear out old exponent value + radiocfg.mdmcfg1 |= chanspc_e + radiocfg.mdmcfg0 = chanspc_m + self.setRFRegister(MDMCFG1, (radiocfg.mdmcfg1)) + self.setRFRegister(MDMCFG0, (radiocfg.mdmcfg0)) + + def makePktVLEN(self, maxlen=RF_MAX_TX_BLOCK, radiocfg=None): + if radiocfg==None: + self.getRadioConfig() + radiocfg = self.radiocfg + + if maxlen > RF_MAX_TX_BLOCK: + raise(Exception("Packet too large (%d bytes). Maximum variable length packet is %d bytes." % (maxlen, RF_MAX_TX_BLOCK))) + + radiocfg.pktctrl0 &= ~PKTCTRL0_LENGTH_CONFIG + radiocfg.pktctrl0 |= 1 + radiocfg.pktlen = maxlen + self.setRFRegister(PKTCTRL0, (radiocfg.pktctrl0)) + self.setRFRegister(PKTLEN, (radiocfg.pktlen)) + + + def makePktFLEN(self, flen=RF_MAX_TX_BLOCK, radiocfg=None): + if radiocfg==None: + self.getRadioConfig() + radiocfg = self.radiocfg + + if flen > EP5OUT_BUFFER_SIZE - 4: + raise(Exception("Packet too large (%d bytes). Maximum fixed length packet is %d bytes." % (flen, EP5OUT_BUFFER_SIZE - 6))) + + radiocfg.pktctrl0 &= ~PKTCTRL0_LENGTH_CONFIG + # if we're sending a large block, pktlen is dealt with by the firmware + # using 'infinite' mode + if flen > RF_MAX_TX_BLOCK: + radiocfg.pktlen = 0x00 + else: + radiocfg.pktlen = flen + self.setRFRegister(PKTCTRL0, (radiocfg.pktctrl0)) + self.setRFRegister(PKTLEN, (radiocfg.pktlen)) + + def getPktLEN(self): + ''' + returns (pktlen, pktctrl0) + ''' + return (self.radiocfg.pktlen, self.radiocfg.pktctrl0 & PKTCTRL0_LENGTH_CONFIG) + + def setEnablePktCRC(self, enable=True, radiocfg=None): + if radiocfg==None: + self.getRadioConfig() + radiocfg = self.radiocfg + + crcE = (0,1)[enable]<<2 + crcM = ~(1<<2) + radiocfg.pktctrl0 &= crcM + radiocfg.pktctrl0 |= crcE + self.setRFRegister(PKTCTRL0, (radiocfg.pktctrl0)) + + def getEnablePktCRC(self, radiocfg=None): + if radiocfg==None: + self.getRadioConfig() + radiocfg = self.radiocfg + + return (radiocfg.pktctrl0 >>2) & 0x1 + + def setEnablePktDataWhitening(self, enable=True, radiocfg=None): + if radiocfg==None: + self.getRadioConfig() + radiocfg = self.radiocfg + + dwEnable = (0,1)[enable]<<6 + radiocfg.pktctrl0 &= ~PKTCTRL0_WHITE_DATA + radiocfg.pktctrl0 |= dwEnable + self.setRFRegister(PKTCTRL0, (radiocfg.pktctrl0)) + + def getEnablePktDataWhitening(self, radiocfg=None): + if radiocfg==None: + self.getRadioConfig() + radiocfg = self.radiocfg + + return (radiocfg.pktctrl0 >>6) & 0x1 + + def setPktPQT(self, num=3, radiocfg=None): + if radiocfg==None: + self.getRadioConfig() + radiocfg = self.radiocfg + + num &= 7 + num <<= 5 + numM = ~(7<<5) + radiocfg.pktctrl1 &= numM + radiocfg.pktctrl1 |= num + self.setRFRegister(PKTCTRL1, (radiocfg.pktctrl1)) + + def getPktPQT(self, radiocfg=None): + if radiocfg==None: + self.getRadioConfig() + radiocfg = self.radiocfg + + return (radiocfg.pktctrl1 >> 5) & 7 + + def setEnablePktAppendStatus(self, enable=True, radiocfg=None): + ''' + enable append status bytes. two bytes will be appended to the payload of the packet, containing + RSSI and LQI values as well as CRC OK. + ''' + if radiocfg == None: + self.getRadioConfig() + radiocfg = self.radiocfg + + radiocfg.pktctrl1 &= ~PKTCTRL1_APPEND_STATUS + radiocfg.pktctrl1 |= (enable<<2) + self.setRFRegister(PKTCTRL1, radiocfg.pktctrl1) + + def getEnablePktAppendStatus(self, radiocfg=None): + ''' + return append status bytes setting. + ''' + if radiocfg == None: + self.getRadioConfig() + radiocfg = self.radiocfg + + pktctrl1 = radiocfg.pktctrl1 + append = (pktctrl1>>2) & 0x01 + return append + + def setEnableMdmManchester(self, enable=True, radiocfg=None): + if radiocfg == None: + self.getRadioConfig() + radiocfg = self.radiocfg + + radiocfg.mdmcfg2 &= ~MDMCFG2_MANCHESTER_EN + radiocfg.mdmcfg2 |= (enable<<3) + self.setRFRegister(MDMCFG2, radiocfg.mdmcfg2) + + def getEnableMdmManchester(self, radiocfg=None): + if radiocfg == None: + self.getRadioConfig() + radiocfg = self.radiocfg + + mdmcfg2 = radiocfg.mdmcfg2 + mchstr = (mdmcfg2>>3) & 0x01 + return mchstr + + def setEnableMdmFEC(self, enable=True, radiocfg=None): + if radiocfg==None: + self.getRadioConfig() + radiocfg = self.radiocfg + + fecEnable = (0,1)[enable]<<7 + radiocfg.mdmcfg1 &= ~MFMCFG1_FEC_EN + radiocfg.mdmcfg1 |= fecEnable + self.setRFRegister(MDMCFG1, (radiocfg.mdmcfg1)) + + def getEnableMdmFEC(self, radiocfg=None): + if radiocfg == None: + self.getRadioConfig() + radiocfg = self.radiocfg + + mdmcfg1 = radiocfg.mdmcfg1 + fecEnable = (mdmcfg1>>7) & 0x01 + return fecEnable + + def setEnableMdmDCFilter(self, enable=True, radiocfg=None): + if radiocfg==None: + self.getRadioConfig() + radiocfg = self.radiocfg + + dcfEnable = (0,1)[enable]<<7 + radiocfg.mdmcfg2 &= ~MDMCFG2_DEM_DCFILT_OFF + radiocfg.mdmcfg2 |= dcfEnable + self.setRFRegister(MDMCFG2, radiocfg.mdmcfg2) + + def getEnableMdmDCFilter(self, radiocfg=None): + if radiocfg==None: + self.getRadioConfig() + radiocfg = self.radiocfg + + dcfEnable = (radiocfg.mdmcfg2>>7) & 0x1 + return dcfEnable + + + def setFsIF(self, freq_if, mhz=24, radiocfg=None): + ''' + Note that the SmartRF Studio software + automatically calculates the optimum register + setting based on channel spacing and channel + filter bandwidth. (from cc1110f32.pdf) + ''' + if radiocfg==None: + self.getRadioConfig() + radiocfg = self.radiocfg + + ifBits = freq_if * pow(2,10) / (1000000.0 * mhz) + ifBits = int(ifBits + .5) # rounded evenly + + if ifBits >0x1f: + raise(Exception("FAIL: freq_if is too high? freqbits: %x (must be <0x1f)" % ifBits)) + radiocfg.fsctrl1 &= ~(0x1f) + radiocfg.fsctrl1 |= int(ifBits) + self.setRFRegister(FSCTRL1, (radiocfg.fsctrl1)) + + def getFsIF(self, mhz=24, radiocfg=None): + if radiocfg==None: + self.getRadioConfig() + radiocfg = self.radiocfg + + freq_if = (radiocfg.fsctrl1&0x1f) * (1000000.0 * mhz / pow(2,10)) + return freq_if + + + def setFsOffset(self, if_off, mhz=24, radiocfg=None): + ''' + Note that the SmartRF Studio software + automatically calculates the optimum register + setting based on channel spacing and channel + filter bandwidth. (from cc1110f32.pdf) + ''' + if radiocfg==None: + self.getRadioConfig() + radiocfg = self.radiocfg + + radiocfg.fsctrl0 = if_off + self.setRFRegister(FSCTRL0, (radiocfg.fsctrl0)) + + def getFsOffset(self, mhz=24, radiocfg=None): + if radiocfg==None: + self.getRadioConfig() + radiocfg = self.radiocfg + + freqoff = radiocfg.fsctrl0 + return freqoff + + def getChannel(self, radiocfg=None): + if radiocfg==None: + self.getRadioConfig() + radiocfg = self.radiocfg + + self.getRadioConfig() + return radiocfg.channr + + def setChannel(self, channr, radiocfg=None): + if radiocfg==None: + self.getRadioConfig() + radiocfg = self.radiocfg + + radiocfg.channr = channr + self.setRFRegister(CHANNR, (radiocfg.channr)) + + def setMdmChanBW(self, bw, mhz=24, radiocfg=None): + ''' + For best performance, the channel filter + bandwidth should be selected so that the + signal bandwidth occupies at most 80% of the + channel filter bandwidth. The channel centre + tolerance due to crystal accuracy should also + be subtracted from the signal bandwidth. The + following example illustrates this: + + With the channel filter bandwidth set to 500 + kHz, the signal should stay within 80% of 500 + kHz, which is 400 kHz. Assuming 915 MHz + frequency and +/-20 ppm frequency uncertainty + for both the transmitting device and the + receiving device, the total frequency + uncertainty is +/-40 ppm of 915 MHz, which is + +/-37 kHz. If the whole transmitted signal + bandwidth is to be received within 400 kHz, the + transmitted signal bandwidth should be + maximum 400 kHz - 2*37 kHz, which is 326 + kHz. + + DR:1.2kb Dev:5.1khz Mod:GFSK RXBW:63kHz sensitive fsctrl1:06 mdmcfg:e5 a3 13 23 11 dev:16 foc/bscfg:17/6c agctrl:03 40 91 frend:56 10 + DR:1.2kb Dev:5.1khz Mod:GFSK RXBW:63kHz lowpower fsctrl1:06 mdmcfg:e5 a3 93 23 11 dev:16 foc/bscfg:17/6c agctrl:03 40 91 frend:56 10 (DEM_DCFILT_OFF) + DR:2.4kb Dev:5.1khz Mod:GFSK RXBW:63kHz sensitive fsctrl1:06 mdmcfg:e6 a3 13 23 11 dev:16 foc/bscfg:17/6c agctrl:03 40 91 frend:56 10 + DR:2.4kb Dev:5.1khz Mod:GFSK RXBW:63kHz lowpower fsctrl1:06 mdmcfg:e6 a3 93 23 11 dev:16 foc/bscfg:17/6c agctrl:03 40 91 frend:56 10 (DEM_DCFILT_OFF) + DR:38.4kb Dev:20khz Mod:GFSK RXBW:94kHz sensitive fsctrl1:08 mdmcfg:ca a3 13 23 11 dev:36 foc/bscfg:16/6c agctrl:43 40 91 frend:56 10 (IF changes, Deviation) + DR:38.4kb Dev:20khz Mod:GFSK RXBW:94kHz lowpower fsctrl1:08 mdmcfg:ca a3 93 23 11 dev:36 foc/bscfg:16/6c agctrl:43 40 91 frend:56 10 (.. DEM_DCFILT_OFF) + + DR:250kb Dev:129khz Mod:GFSK RXBW:600kHz sensitive fsctrl1:0c mdmcfg:1d 55 13 23 11 dev:63 foc/bscfg:1d/1c agctrl:c7 00 b0 frend:b6 10 (IF_changes, Deviation) + + DR:500kb Mod:MSK RXBW:750kHz sensitive fsctrl1:0e mdmcfg:0e 55 73 43 11 dev:00 foc/bscfg:1d/1c agctrl:c7 00 b0 frend:b6 10 (IF_changes, Modulation of course, Deviation has different meaning with MSK) + ''' + if radiocfg==None: + self.getRadioConfig() + radiocfg = self.radiocfg + + chanbw_e = None + chanbw_m = None + for e in range(4): + m = int(((mhz*1000000.0 / (bw *pow(2,e) * 8.0 )) - 4) + .5) # rounded evenly + if m < 4: + chanbw_e = e + chanbw_m = m + break + if chanbw_e is None: + raise(Exception("ChanBW does not translate into acceptable parameters. Should you be changing this?")) + + bw = 1000.0*mhz / (8.0*(4+chanbw_m) * pow(2,chanbw_e)) + #print "chanbw_e: %x chanbw_m: %x chanbw: %f kHz" % (e, m, bw) + + radiocfg.mdmcfg4 &= ~(MDMCFG4_CHANBW_E | MDMCFG4_CHANBW_M) + radiocfg.mdmcfg4 |= ((chanbw_e<<6) | (chanbw_m<<4)) + self.setRFRegister(MDMCFG4, (radiocfg.mdmcfg4)) + + def getMdmChanBW(self, mhz=24, radiocfg=None): + if radiocfg==None: + self.getRadioConfig() + radiocfg = self.radiocfg + + chanbw_e = (radiocfg.mdmcfg4 >> 6) & 0x3 + chanbw_m = (radiocfg.mdmcfg4 >> 4) & 0x3 + bw = 1000000.0*mhz / (8.0*(4+chanbw_m) * pow(2,chanbw_e)) + #print "chanbw_e: %x chanbw_m: %x chanbw: %f hz" % (chanbw_e, chanbw_m, bw) + return bw + + def setMdmDRate(self, drate, mhz=24, radiocfg=None): + ''' + set the baud of data being modulated through the radio + ''' + if radiocfg==None: + self.getRadioConfig() + radiocfg = self.radiocfg + + drate_e = None + drate_m = None + for e in range(16): + m = int((drate * pow(2,28) / (pow(2,e)* (mhz*1000000.0))-256) + .5) # rounded evenly + if m < 256: + drate_e = e + drate_m = m + break + if drate_e is None: + raise(Exception("DRate does not translate into acceptable parameters. Should you be changing this?")) + + drate = 1000000.0 * mhz * (256+drate_m) * pow(2,drate_e) / pow(2,28) + if self._debug: print "drate_e: %x drate_m: %x drate: %f Hz" % (drate_e, drate_m, drate) + + radiocfg.mdmcfg3 = drate_m + radiocfg.mdmcfg4 &= ~MDMCFG4_DRATE_E + radiocfg.mdmcfg4 |= drate_e + self.setRFRegister(MDMCFG3, (radiocfg.mdmcfg3)) + self.setRFRegister(MDMCFG4, (radiocfg.mdmcfg4)) + + def getMdmDRate(self, mhz=24, radiocfg=None): + ''' + get the baud of data being modulated through the radio + ''' + if radiocfg==None: + self.getRadioConfig() + radiocfg = self.radiocfg + + drate_e = radiocfg.mdmcfg4 & 0xf + drate_m = radiocfg.mdmcfg3 + + drate = 1000000.0 * mhz * (256+drate_m) * pow(2,drate_e) / pow(2,28) + #print "drate_e: %x drate_m: %x drate: %f hz" % (drate_e, drate_m, drate) + return drate + + + def setMdmDeviatn(self, deviatn, mhz=24, radiocfg=None): + ''' + configure the deviation settings for the given modulation scheme + ''' + if radiocfg==None: + self.getRadioConfig() + radiocfg = self.radiocfg + + dev_e = None + dev_m = None + for e in range(8): + m = int((deviatn * pow(2,17) / (pow(2,e)* (mhz*1000000.0))-8) + .5) # rounded evenly + if m < 8: + dev_e = e + dev_m = m + break + if dev_e is None: + raise(Exception("Deviation does not translate into acceptable parameters. Should you be changing this?")) + + dev = 1000000.0 * mhz * (8+dev_m) * pow(2,dev_e) / pow(2,17) + #print "dev_e: %x dev_m: %x deviatn: %f Hz" % (e, m, dev) + + radiocfg.deviatn = (dev_e << 4) | dev_m + self.setRFRegister(DEVIATN, radiocfg.deviatn) + + def getMdmDeviatn(self, mhz=24, radiocfg=None): + if radiocfg==None: + self.getRadioConfig() + radiocfg = self.radiocfg + + dev_e = radiocfg.deviatn >> 4 + dev_m = radiocfg.deviatn & DEVIATN_DEVIATION_M + dev = 1000000.0 * mhz * (8+dev_m) * pow(2,dev_e) / pow(2,17) + return dev + + def getMdmSyncWord(self, radiocfg=None): + if radiocfg==None: + self.getRadioConfig() + radiocfg = self.radiocfg + + return (radiocfg.sync1 << 8) + radiocfg.sync0 + + def setMdmSyncWord(self, word, radiocfg=None): + if radiocfg==None: + self.getRadioConfig() + radiocfg = self.radiocfg + + radiocfg.sync1 = word >> 8 + radiocfg.sync0 = word & 0xff + self.setRFRegister(SYNC1, (radiocfg.sync1)) + self.setRFRegister(SYNC0, (radiocfg.sync0)) + + def getMdmSyncMode(self, radiocfg=None): + if radiocfg==None: + self.getRadioConfig() + radiocfg = self.radiocfg + + return radiocfg.mdmcfg2 & MDMCFG2_SYNC_MODE + + def setMdmSyncMode(self, syncmode=SYNCM_15_of_16, radiocfg=None): + if radiocfg==None: + self.getRadioConfig() + radiocfg = self.radiocfg + + radiocfg.mdmcfg2 &= ~MDMCFG2_SYNC_MODE + radiocfg.mdmcfg2 |= syncmode + self.setRFRegister(MDMCFG2, (radiocfg.mdmcfg2)) + + def getMdmNumPreamble(self, radiocfg=None): + ''' + get the minimum number of preamble bits to be transmitted. note this is a flag, not a count + so the return value must be interpeted - e.g. 0x30 == 0x03 << 4 == MFMCFG1_NUM_PREAMBLE_6 == 6 bytes + ''' + if radiocfg == None: + self.getRadioConfig() + radiocfg = self.radiocfg + + preamble= (radiocfg.mdmcfg1 & MFMCFG1_NUM_PREAMBLE) + return preamble + + def setMdmNumPreamble(self, preamble=MFMCFG1_NUM_PREAMBLE_4, radiocfg=None): + ''' + set the minimum number of preamble bits to be transmitted (default: MFMCFG1_NUM_PREAMBLE_4) + ''' + if radiocfg == None: + self.getRadioConfig() + radiocfg = self.radiocfg + + radiocfg.mdmcfg1 &= ~MFMCFG1_NUM_PREAMBLE + radiocfg.mdmcfg1 |= preamble + self.setRFRegister(MDMCFG1, (radiocfg.mdmcfg1)) + + def getBSLimit(self, radiocfg=None): + ''' + get the saturation point for the data rate offset compensation algorithm + ''' + if radiocfg==None: + self.getRadioConfig() + radiocfg = self.radiocfg + + return radiocfg.bscfg&BSCFG_BS_LIMIT + + def setBSLimit(self, bslimit, radiocfg=None): + ''' + set the saturation point for the data rate offset compensation algorithm + ''' + if radiocfg==None: + self.getRadioConfig() + radiocfg = self.radiocfg + + radiocfg.bscfg &= ~BSCFG_BS_LIMIT + radiocfg.bscfg |= bslimit + self.setRFRegister(BSCFG, (radiocfg.bscfg)) + + def calculateMdmDeviatn(self, mhz=24, radiocfg=None): + ''' calculates the optimal DEVIATN setting for the current freq/baud + * totally experimental * + from Smart RF Studio: + 1.2 kbaud 5.1khz dev + 2.4 kbaud 5.1khz dev + 38.4 kbaud 20khz dev + 250 kbaud 129khz dev + ''' + baud = self.getMdmDRate(mhz, radiocfg) + if baud <= 2400: + deviatn = 5100 + elif baud <= 38400: + deviatn = 20000 * ((baud-2400)/36000) + else: + deviatn = 129000 * ((baud-38400)/211600) + self.setMdmDeviatn(deviatn) + + def calculatePktChanBW(self, mhz=24, radiocfg=None): + ''' calculates the optimal ChanBW setting for the current freq/baud + * totally experimental * + from Smart RF Studio: + 1.2 kbaud BW: 63khz + 2.4 kbaud BW: 63khz + 38.4kbaud BW: 94khz + 250 kbaud BW: 600khz + ''' + freq, freqhex = self.getFreq() + center_freq = freq + 14000000 + freq_uncertainty = 20e-6 * freq # +-20ppm + freq_uncertainty *= 2 # both xmitter and receiver + #minbw = (2 * freq_uncertainty) + self.getMdmDRate() # uncertainty for both sender/receiver + minbw = (self.getMdmDRate() + freq_uncertainty) + + possibles = [ 53e3,63e3,75e3,93e3,107e3,125e3,150e3,188e3,214e3,250e3,300e3,375e3,428e3,500e3,600e3,750e3, ] + for bw in possibles: + #if (.8 * bw) > minbw: # can't occupy more the 80% of BW + if (bw) > minbw: + break + self.setMdmChanBW(bw, mhz, radiocfg) + + def calculateFsIF(self, mhz=24, radiocfg=None): + ''' calculates the optimal IF setting for the current freq/baud + * totally experimental * + 1.2 kbaud IF: 140khz + 2.4 kbaud IF: 140khz + 38.4kbaud IF: 164khz (140khz for "sensitive" version) + 250 kbaud IF: 281khz + 500 kbaud IF: 328khz + ''' + pass + def calculateFsOffset(self, mhz=24, radiocfg=None): + ''' calculates the optimal FreqOffset setting for the current freq/baud + * totally experimental * + ''' + + pass + + def getRSSI(self): + rssi = self.peek(RSSI) + return rssi + + def getLQI(self): + lqi = self.peek(LQI) + return lqi + + def setAESmode(self, aesmode=AES_CRYPTO_DEFAULT): ''' set AES crypto co-processor mode. @@ -193,8 +1225,21 @@ class FHSSNIC(USBDongle): ''' return self.send(APP_NIC, NIC_SET_AES_KEY, key) + def setPktAddr(self, addr): + return self.poke(ADDR, chr(addr)) + + def getPktAddr(self): + return self.peek(ADDR) + + def setEnDeCoder(self, endec=None): + self.endec = endec + + ##### RADIO XMIT/RECV and UTILITY FUNCTIONS ##### # set repeat & offset to optionally repeat tx of a section of the data block. repeat of 65535 means 'forever' def RFxmit(self, data, repeat=0, offset=0): + # encode, if necessary + if self.endec is not None: + data = self.endec.encode(data) # calculate wait time waitlen = len(data) waitlen += repeat * (len(data) - offset) @@ -207,16 +1252,23 @@ class FHSSNIC(USBDongle): if blocksize > EP5OUT_BUFFER_SIZE: raise(Exception("Blocksize too large. Maximum %d") % EP5OUT_BUFFER_SIZE) self.send(APP_NIC, NIC_SET_RECV_LARGE, "%s" % struct.pack(" to quit, and your radio config will be set back to its original configuration. + + lowball - lowball level of choosing (see help on lowball) + debug - sets _debug to this setting if not None. + length - arbitrary length of bytes we want to see per pseudopacket. (should be enough to identify interesting packets, but not too long) + IdentSyncWord - look for preamble in each packet and determine possible sync-words in use + SyncWordMatchList - attempt to find *these* sync words (provide a list) + Search - byte string to search through each received packet for (real bytes, not hex repr) + RegExpSearch - regular expression to search through received bytes (not the hex repr that is printed) + + if IdentSyncWord == True (or SyncWordMatchList != None), returns a dict of unique possible SyncWords identified along with the number of times seen. + ''' + retval = {} + oldebug = self._debug + + if SyncWordMatchList != None: + IdentSyncWord = True + + if IdentSyncWord: + if lowball <= 1: + print "Entering Discover mode and searching for possible SyncWords..." + if SyncWordMatchList != None: + print " seeking one of: %s" % repr([hex(x) for x in SyncWordMatchList]) + + else: + print "-- lowball too high -- ignoring request to IdentSyncWord" + print "Entering Discover mode..." + IdentSyncWord = False + + self.lowball(level=lowball, length=length) + if debug is not None: + self._debug = debug + + if Search is not None: + print "Search:",repr(Search) + + if RegExpSearch is not None: + print "RegExpSearch:",repr(RegExpSearch) + + print "(press Enter to quit)" + while not keystop(): + + try: + y, t = self.RFrecv() + yhex = y.encode('hex') + + print "(%5.3f) Received: %s" % (t, yhex) + if RegExpSearch is not None: + ynext = y + for loop in range(8): + if (re.Search(RegExpSearch, ynext) is not None): + print " REG EXP SEARCH SUCCESS:",RegExpSearch + ynext = bits.shiftString(ynext, 1) + + if Search is not None: + ynext = y + for loop in range(8): + if (Search in ynext): + print " SEARCH SUCCESS:",Search + ynext = bits.shiftString(ynext, 1) + + if IdentSyncWord: + #if lowball == 1: + # y = '\xaa\xaa' + y + + poss = bits.findSyncWord(y, ISWsensitivity, ISWminpreamble) + if len(poss): + print " possible Sync Dwords: %s" % repr([hex(x) for x in poss]) + for dw in poss: + lst = retval.get(dw, 0) + lst += 1 + retval[dw] = lst + + if SyncWordMatchList is not None: + for x in poss: + if x in SyncWordMatchList: + print "MATCH WITH KNOWN SYNC WORD:" + hex(x) + + except ChipconUsbTimeoutException: + pass + except KeyboardInterrupt: + print "Please press to stop" + + sys.stdin.read(1) + self._debug = oldebug + self.lowballRestore() + print "Exiting Discover mode..." + + if len(retval) == 0: + return + + printSyncWords(retval) + return retval + + + def lowball(self, level=1, sync=0xaaaa, length=250, pqt=0, crc=False, fec=False, datawhite=False): + ''' + this configures the radio to the lowest possible level of filtering, potentially allowing complete radio noise to come through as data. very useful in some circumstances. + level == 0 changes the Sync Mode to SYNCM_NONE (wayyy more garbage) + level == 1 (default) sets the Sync Mode to SYNCM_CARRIER (requires a valid carrier detection for the data to be considered a packet) + level == 2 sets the Sync Mode to SYNCM_CARRIER_15_of_16 (requires a valid carrier detection and 15 of 16 bits of SYNC WORD match for the data to be considered a packet) + level == 3 sets the Sync Mode to SYNCM_CARRIER_16_of_16 (requires a valid carrier detection and 16 of 16 bits of SYNC WORD match for the data to be considered a packet) + ''' + if hasattr(self, '_last_radiocfg') and len(self._last_radiocfg): + print('not saving radio state. already have one saved. use lowballRestore() to restore the saved config and the next time you run lowball() the radio config will be saved.') + else: + self._last_radiocfg = self.getRadioConfig() + + self.makePktFLEN(length) + self.setEnablePktCRC(crc) + self.setEnableMdmFEC(fec) + self.setEnablePktDataWhitening(datawhite) + self.setMdmSyncWord(sync) + self.setPktPQT(pqt) + + if (level == 3): + self.setMdmSyncMode(SYNCM_CARRIER_16_of_16) + elif (level == 2): + self.setMdmSyncMode(SYNCM_15_of_16) + elif (level == 1): + self.setMdmSyncMode(SYNCM_CARRIER) + else: + self.setMdmSyncMode(SYNCM_NONE) + + + def lowballRestore(self): + if not hasattr(self, '_last_radiocfg'): + raise(Exception("lowballRestore requires that lowball have been executed first (it saves radio config state!)")) + self.setRadioConfig(self._last_radiocfg) + self._last_radiocfg = '' + + ##### REPR FUNCTIONS ##### + def printRadioConfig(self, mhz=24, radiocfg=None): + print self.reprRadioConfig(mhz, radiocfg) + + def reprRadioConfig(self, mhz=24, radiocfg=None): + if radiocfg == None: + self.getRadioConfig() + radiocfg = self.radiocfg + output = [] + + output.append( "== Hardware ==") + output.append( self.reprHardwareConfig()) + output.append( "\n== Software ==") + output.append( self.reprSoftwareConfig()) + output.append( "\n== Frequency Configuration ==") + output.append( self.reprFreqConfig(mhz, radiocfg)) + output.append( "\n== Modem Configuration ==") + output.append( self.reprModemConfig(mhz, radiocfg)) + output.append( "\n== Packet Configuration ==") + output.append( self.reprPacketConfig(radiocfg)) + output.append( "\n== AES Crypto Configuration ==") + output.append( self.reprAESMode()) + output.append( "\n== Radio Test Signal Configuration ==") + output.append( self.reprRadioTestSignalConfig(radiocfg)) + output.append( "\n== Radio State ==") + output.append( self.reprRadioState(radiocfg)) + output.append("\n== Client State ==") + output.append( self.reprClientState()) + return "\n".join(output) + + def reprMdmModulation(self, radiocfg=None): + mod = self.getMdmModulation(radiocfg) + return ("Modulation: %s" % MODULATIONS[mod]) + + def reprRadioTestSignalConfig(self, radiocfg=None): + if radiocfg==None: + self.getRadioConfig() + radiocfg = self.radiocfg + + output = [] + #output.append("GDO2_INV: %s" % ("do not Invert Output", "Invert output")[(radiocfg.iocfg2>>6)&1]) + #output.append("GDO2CFG: 0x%x" % (radiocfg.iocfg2&0x3f)) + #output.append("GDO_DS: %s" % (("minimum drive (>2.6vdd","Maximum drive (<2.6vdd)")[radiocfg.iocfg1>>7])) + #output.append("GDO1_INV: %s" % ("do not Invert Output", "Invert output")[(radiocfg.iocfg1>>6)&1]) + #output.append("GDO1CFG: 0x%x"%(radiocfg.iocfg1&0x3f)) + #output.append("GDO0_INV: %s" % ("do not Invert Output", "Invert output")[(radiocfg.iocfg0>>6)&1]) + #output.append("GDO0CFG: 0x%x"%(radiocfg.iocfg0&0x3f)) + output.append("TEST2: 0x%x"%radiocfg.test2) + output.append("TEST1: 0x%x"%radiocfg.test1) + output.append("TEST0: 0x%x"%(radiocfg.test0&0xfd)) + output.append("VCO_SEL_CAL_EN: 0x%x"%((radiocfg.test2>>1)&1)) + return "\n".join(output) + + + def reprFreqConfig(self, mhz=24, radiocfg=None): + if radiocfg==None: + self.getRadioConfig() + radiocfg = self.radiocfg + + output = [] + freq,num = self.getFreq(mhz, radiocfg) + output.append("Frequency: %f hz (%s)" % (freq,num)) + + output.append("Channel: %d" % radiocfg.channr) + + + freq_if = self.getFsIF(mhz, radiocfg) + freqoff = self.getFsOffset(mhz, radiocfg) + freqest = self.getFreqEst(radiocfg) + + output.append("Intermediate freq: %d hz" % freq_if) + output.append("Frequency Offset: %d +/-" % freqoff) + output.append("Est. Freq Offset: %d" % freqest) + + return "\n".join(output) + + def reprAESMode(self): + output = [] + aesmode= ord(self.getAESmode()[0]) + + output.append("AES Mode: %s" % AESMODES[(aesmode & AES_CRYPTO_MODE)]) + if aesmode & AES_CRYPTO_IN_ENABLE: + output.append("Crypt RF Input: %s" % ("Decrypt", "Encrypt")[(aesmode & AES_CRYPTO_IN_TYPE)]) + else: + output.append("Crypt RF Input: off") + if aesmode & AES_CRYPTO_OUT_ENABLE: + output.append("Crypt RF Output: %s" % ("Decrypt", "Encrypt")[(aesmode & AES_CRYPTO_OUT_TYPE) >> 2]) + else: + output.append("Crypt RF Output: off") + + return "\n".join(output) + + def reprPacketConfig(self, radiocfg=None): + if radiocfg==None: + self.getRadioConfig() + radiocfg = self.radiocfg + + output = [] + output.append("Sync Word: 0x%.2X%.2X" % (radiocfg.sync1, radiocfg.sync0)) + output.append("Packet Length: %d" % radiocfg.pktlen) + length_config = radiocfg.pktctrl0&3 + output.append("Length Config: %s" % LENGTH_CONFIGS[length_config]) + + output.append("Configured Address: 0x%x" % radiocfg.addr) + + pqt = self.getPktPQT(radiocfg) + output.append("Preamble Quality Threshold: 4 * %d" % pqt) + + append = (radiocfg.pktctrl1>>2) & 1 + output.append("Append Status: %s" % ("No","Yes")[append]) + + adr_chk = radiocfg.pktctrl1&3 + output.append("Rcvd Packet Check: %s" % ADR_CHK_TYPES[adr_chk]) + + whitedata = self.getEnablePktDataWhitening(radiocfg) + output.append("Data Whitening: %s" % ("off", "ON (but only with cc2400_en==0)")[whitedata]) + + pkt_format = (radiocfg.pktctrl0>>5)&3 + output.append("Packet Format: %s" % PKT_FORMATS[pkt_format]) + + crc = self.getEnablePktCRC(radiocfg) + output.append("CRC: %s" % ("disabled", "ENABLED")[crc]) + + return "\n".join(output) + + def printRadioState(self, radiocfg=None): + print self.reprRadioState(radiocfg) + + def reprRadioState(self, radiocfg=None): + output = [] + try: + if radiocfg==None: + self.getRadioConfig() + radiocfg = self.radiocfg + + output.append(" MARCSTATE: %s (%x)" % (self.getMARCSTATE(radiocfg))) + output.append(" DONGLE RESPONDING: mode :%x, last error# %d"%(self.getDebugCodes())) + except: + output.append(repr(sys.exc_info())) + output.append(" DONGLE *not* RESPONDING") + + return "\n".join(output) + + def reprModemConfig(self, mhz=24, radiocfg=None): + output = [] + if radiocfg==None: + self.getRadioConfig() + radiocfg = self.radiocfg + + reprMdmModulation = self.reprMdmModulation(radiocfg) + syncmode = self.getMdmSyncMode(radiocfg) + + output.append(reprMdmModulation) + + drate = self.getMdmDRate(mhz, radiocfg) + output.append("DRate: %f hz"%drate) + + bw = self.getMdmChanBW(mhz, radiocfg) + output.append("ChanBW: %f hz"%bw) + + output.append("DEVIATION: %f hz" % self.getMdmDeviatn(mhz, radiocfg)) + + output.append("Sync Mode: %s" % SYNCMODES[syncmode]) + + num_preamble = (radiocfg.mdmcfg1>>4)&7 + output.append("Min TX Preamble: %d bytes" % (NUM_PREAMBLE[num_preamble]) ) + + chanspc = self.getMdmChanSpc(mhz, radiocfg) + output.append("Chan Spacing: %f hz" % chanspc) + + bslimit = radiocfg.bscfg & BSCFG_BS_LIMIT + output.append("BSLimit: %s"%BSLIMITS[bslimit]) + + output.append("DC Filter: %s" % (("enabled", "disabled")[self.getEnableMdmDCFilter(radiocfg)])) + + mchstr = self.getEnableMdmManchester(radiocfg) + output.append("Manchester Encoding: %s" % (("disabled","enabled")[mchstr])) + + fec = self.getEnableMdmFEC(radiocfg) + output.append("Fwd Err Correct: %s" % (("disabled","enabled")[fec])) + + + return "\n".join(output) + + + def checkRepr(self, matchstr, checkval, maxdiff=0): + starry = self.reprRadioConfig().split('\n') + line,val = getValueFromReprString(starry, matchstr) + try: + f = checkval.__class__(val.split(" ")[0]) + if abs(f-checkval) <= maxdiff: + print " passed: reprRadioConfig test: %s %s" % (repr(val), checkval) + else: + print " *FAILED* reprRadioConfig test: %s %s %s" % (repr(line), repr(val), checkval) + + except ValueError, e: + print " ERROR checking repr: %s" % e + + def testTX(self, data="XYZABCDEFGHIJKL"): + while (sys.stdin not in select.select([sys.stdin],[],[],0)[0]): + time.sleep(.4) + print "transmitting %s" % repr(data) + self.RFxmit(data) + sys.stdin.read(1) + + ######## APPLICATION METHODS - more for demonstration than anything ######## + def setup24330MHz(self): + #self.setRadioConfig('0c4eff000800000b0065600068b583231145073f14166c4340915610a90a0011593f3f8831090000000000000000c02e0006000000000000000000000000'.decode('hex')) + self.getRadioConfig() + rc = self.radiocfg + rc.iocfg0 = 0x06 + rc.sync1 = 0x0c + rc.sync0 = 0x4e + rc.pktlen = 0xff + rc.pktctrl1 = 0x00 + rc.pktctrl0 = 0x08 + rc.fsctrl1 = 0x0b + rc.fsctrl0 = 0x00 + rc.addr = 0x00 + rc.channr = 0x00 + rc.mdmcfg4 = 0x68 + rc.mdmcfg3 = 0xb5 + rc.mdmcfg2 = 0x83 + rc.mdmcfg1 = 0x23 + rc.mdmcfg0 = 0x11 + rc.mcsm2 = 0x07 + rc.mcsm1 = 0x3f + rc.mcsm0 = 0x14 + rc.deviatn = 0x45 + rc.foccfg = 0x16 + rc.bscfg = 0x6c + #rc.agcctrl2 |= AGCCTRL2_MAX_DVGA_GAIN + rc.agcctrl2 = 0x43 + rc.agcctrl1 = 0x40 + rc.agcctrl0 = 0x91 + rc.frend1 = 0x56 + rc.frend0 = 0x10 + rc.fscal3 = 0xad + rc.fscal2 = 0x0A + rc.fscal1 = 0x00 + rc.fscal0 = 0x11 + rc.test2 = 0x88 + rc.test1 = 0x31 + rc.test0 = 0x09 + rc.pa_table0 = 0xc0 + self.setRadioConfig() + + def setup900MHz(self): + self.getRadioConfig() + rc = self.radiocfg + rc.iocfg0 = 0x06 + rc.sync1 = 0x0b + rc.sync0 = 0x0b + rc.pktlen = 0xff + rc.pktctrl1 = 0xe5 + rc.pktctrl0 = 0x04 + rc.fsctrl1 = 0x12 + rc.fsctrl0 = 0x00 + rc.addr = 0x00 + rc.channr = 0x00 + rc.mdmcfg4 = 0x3e + rc.mdmcfg3 = 0x55 + rc.mdmcfg2 = 0x73 + rc.mdmcfg1 = 0x23 + rc.mdmcfg0 = 0x55 + rc.mcsm2 = 0x07 + rc.mcsm1 = 0x30 + rc.mcsm0 = 0x00 + rc.deviatn = 0x16 + rc.foccfg = 0x17 + rc.bscfg = 0x6c + rc.agcctrl2 |= AGCCTRL2_MAX_DVGA_GAIN + rc.agcctrl2 = 0x03 + rc.agcctrl1 = 0x40 + rc.agcctrl0 = 0x91 + rc.frend1 = 0x56 + rc.frend0 = 0x10 + rc.fscal3 = 0xEA + rc.fscal2 = 0x2A + rc.fscal1 = 0x00 + rc.fscal0 = 0x1F + rc.test2 = 0x88 + rc.test1 = 0x31 + rc.test0 = 0x09 + rc.pa_table0 = 0xc0 + self.setRadioConfig() + + def setup900MHzHopTrans(self): + self.getRadioConfig() + rc = self.radiocfg + rc.iocfg0 = 0x06 + rc.sync1 = 0x0b + rc.sync0 = 0x0b + rc.pktlen = 0xff + rc.pktctrl1 = 0x04 + rc.pktctrl0 = 0x05 + rc.addr = 0x00 + rc.channr = 0x00 + rc.fsctrl1 = 0x06 + rc.fsctrl0 = 0x00 + rc.mdmcfg4 = 0xee + rc.mdmcfg3 = 0x55 + rc.mdmcfg2 = 0x73 + rc.mdmcfg1 = 0x23 + rc.mdmcfg0 = 0x55 + rc.mcsm2 = 0x07 + rc.mcsm1 = 0x30 + rc.mcsm0 = 0x18 + rc.deviatn = 0x16 + rc.foccfg = 0x17 + rc.bscfg = 0x6c + rc.agcctrl2 = 0x03 + rc.agcctrl1 = 0x40 + rc.agcctrl0 = 0x91 + rc.frend1 = 0x56 + rc.frend0 = 0x10 + rc.fscal3 = 0xEA + rc.fscal2 = 0x2A + rc.fscal1 = 0x00 + rc.fscal0 = 0x1F + rc.test2 = 0x88 + rc.test1 = 0x31 + rc.test0 = 0x09 + rc.pa_table0 = 0xc0 + self.setRadioConfig() + + def setup900MHzContTrans(self): + self.getRadioConfig() + rc = self.radiocfg + rc.iocfg0 = 0x06 + rc.sync1 = 0x0b + rc.sync0 = 0x0b + rc.pktlen = 0xff + rc.pktctrl1 = 0x04 + rc.pktctrl0 = 0x05 + rc.addr = 0x00 + rc.channr = 0x00 + rc.fsctrl1 = 0x06 + rc.fsctrl0 = 0x00 + rc.freq2 = 0x26 + rc.freq1 = 0x55 + rc.freq0 = 0x55 + rc.mdmcfg4 = 0xee + rc.mdmcfg3 = 0x55 + rc.mdmcfg2 = 0x73 + rc.mdmcfg1 = 0x23 + rc.mdmcfg0 = 0x55 + rc.mcsm2 = 0x07 + rc.mcsm1 = 0x30 + rc.mcsm0 = 0x18 + rc.deviatn = 0x16 + rc.foccfg = 0x17 + rc.bscfg = 0x6c + rc.agcctrl2 = 0x03 + rc.agcctrl1 = 0x40 + rc.agcctrl0 = 0x91 + rc.frend1 = 0x56 + rc.frend0 = 0x10 + rc.fscal3 = 0xEA + rc.fscal2 = 0x2A + rc.fscal1 = 0x00 + rc.fscal0 = 0x1F + rc.test2 = 0x88 + rc.test1 = 0x31 + rc.test0 = 0x09 + rc.pa_table0 = 0xc0 + self.setRadioConfig() + + def setup_rfstudio_902PktTx(self): + self.getRadioConfig() + rc = self.radiocfg + rc.iocfg2 = 0x00 + rc.iocfg1 = 0x00 + rc.iocfg0 = 0x06 + rc.sync1 = 0x0b + rc.sync0 = 0x0b + rc.pktlen = 0xff + rc.pktctrl1 = 0x04 + rc.pktctrl0 = 0x05 + rc.addr = 0x00 + rc.channr = 0x00 + rc.fsctrl1 = 0x0c + rc.fsctrl0 = 0x00 + rc.freq2 = 0x25 + rc.freq1 = 0x95 + rc.freq0 = 0x55 + rc.mdmcfg4 = 0x1d + rc.mdmcfg3 = 0x55 + rc.mdmcfg2 = 0x13 + rc.mdmcfg1 = 0x23 + rc.mdmcfg0 = 0x11 + rc.mcsm2 = 0x07 + rc.mcsm1 = 0x30 + rc.mcsm0 = 0x18 + rc.deviatn = 0x63 + rc.foccfg = 0x1d + rc.bscfg = 0x1c + rc.agcctrl2 = 0xc7 + rc.agcctrl1 = 0x00 + rc.agcctrl0 = 0xb0 + rc.frend1 = 0xb6 + rc.frend0 = 0x10 + rc.fscal3 = 0xEA + rc.fscal2 = 0x2A + rc.fscal1 = 0x00 + rc.fscal0 = 0x1F + rc.test2 = 0x88 + rc.test1 = 0x31 + rc.test0 = 0x09 + rc.pa_table7 = 0x00 + rc.pa_table6 = 0x00 + rc.pa_table5 = 0x00 + rc.pa_table4 = 0x00 + rc.pa_table3 = 0x00 + rc.pa_table2 = 0x00 + rc.pa_table1 = 0x00 + #rc.pa_table0 = 0x8e + rc.pa_table0 = 0xc0 + self.setRadioConfig() + +class FHSSNIC(NICxx11): + ''' + advanced NIC implementation for CCxx11 chips, including Frequency Hopping + ''' def FHSSxmit(self, data): - self.send(APP_NIC, FHSS_XMIT, "%c%s" % (len(data)+1, data)) + return self.send(APP_NIC, FHSS_XMIT, "%c%s" % (len(data), data)) def changeChannel(self, chan): return self.send(APP_NIC, FHSS_CHANGE_CHANNEL, "%c" % (chan)) @@ -268,8 +1881,12 @@ class FHSSNIC(USBDongle): def stopHopping(self): return self.send(APP_NIC, FHSS_STOP_HOPPING, '') - def setMACperiod(self, ms, mhz=24): - val = calculateT2(ms, mhz) + def setMACperiod(self, dwell_ms, mhz=24): + macdata = self.getMACdata() + cycles_per_channel = macdata[1] + ticks_per_cycle = 256 + tick_ms = dwell_ms / (ticks_per_cycle * cycles_per_channel) + val = calculateT2(tick_ms, mhz) T, tickidx, tipidx, PR = val print "Setting MAC period to %f secs (%x %x %x)" % (val) t2ctl = (ord(self.peek(X_T2CTL)) & 0xfc) | (tipidx) @@ -286,7 +1903,7 @@ class FHSSNIC(USBDongle): def getMACdata(self): datastr, timestamp = self.send(APP_NIC, FHSS_GET_MAC_DATA, '') print (repr(datastr)) - data = struct.unpack(" to quit, and your radio config will be set back to its original configuration. - - lowball - lowball level of choosing (see help on lowball) - debug - sets _debug to this setting if not None. - length - arbitrary length of bytes we want to see per pseudopacket. (should be enough to identify interesting packets, but not too long) - IdentSyncWord - look for preamble in each packet and determine possible sync-words in use - SyncWordMatchList - attempt to find *these* sync words (provide a list) - Search - byte string to search through each received packet for (real bytes, not hex repr) - RegExpSearch - regular expression to search through received bytes (not the hex repr that is printed) - - ''' - oldebug = self._debug - if IdentSyncWord and lowball <= 1: - print "Entering Discover mode and searching for possible SyncWords..." - if SyncWordMatchList is not None: - print " seeking one of: %s" % repr([hex(x) for x in SyncWordMatchList]) - - else: - if IdentSyncWord and lowball > 1: - print "-- lowball too high -- ignoring request to IdentSyncWord" - IdentSyncWord = False - - print "Entering Discover mode..." - - self.lowball(level=lowball, length=length) - if debug is not None: - self._debug = debug - - if Search is not None: - print "Search:",repr(Search) - - if RegExpSearch is not None: - print "RegExpSearch:",repr(RegExpSearch) - - while not keystop(): - - try: - y, t = self.RFrecv() - yhex = y.encode('hex') - - print "(%5.3f) Received: %s" % (t, yhex) - if RegExpSearch is not None: - ynext = y - for loop in range(8): - if (re.Search(RegExpSearch, ynext) is not None): - print " REG EXP SEARCH SUCCESS:",RegExpSearch - ynext = bits.shiftString(ynext, 1) - - if Search is not None: - ynext = y - for loop in range(8): - if (Search in ynext): - print " SEARCH SUCCESS:",Search - ynext = bits.shiftString(ynext, 1) - - if IdentSyncWord: - if lowball == 1: - y = '\xaa\xaa' + y - - poss = bits.findDword(y) - if len(poss): - print " possible Sync Dwords: %s" % repr([hex(x) for x in poss]) - - if SyncWordMatchList is not None: - for x in poss: - if x in SyncWordMatchList: - print "MATCH WITH KNOWN SYNC WORD:" + hex(x) - - except ChipconUsbTimeoutException: - pass - except KeyboardInterrupt: - print "Please press to stop" - - sys.stdin.read(1) - self._debug = oldebug - self.lowballRestore() - print "Exiting Discover mode..." - - def testTX(self, data="XYZABCDEFGHIJKL"): - while (sys.stdin not in select.select([sys.stdin],[],[],0)[0]): - time.sleep(.4) - print "transmitting %s" % repr(data) - self.RFxmit(data) - sys.stdin.read(1) - - def unittest(dongle): - import cc1111client - cc1111client.unittest(dongle) + import chipcon_usb + chipcon_usb.unittest(dongle) + print "\nTesting getValueFromReprString()" + starry = dongle.reprRadioConfig().split('\n') + print repr(getValueFromReprString(starry, 'hz')) + + print "\nTesting reprRadioConfig()" + print dongle.reprRadioConfig() + + print "\nTesting Frequency Get/Setters" + # FREQ + freq0,freq0str = dongle.getFreq() + + testfreq = 902000000 + dongle.setFreq(testfreq) + freq,freqstr = dongle.getFreq() + if abs(testfreq - freq) < 1024: + print " passed: %d : %f (diff: %f)" % (testfreq, freq, testfreq-freq) + else: + print " *FAILED* %d : %f (diff: %f)" % (testfreq, freq, testfreq-freq) + + testfreq = 868000000 + dongle.setFreq(testfreq) + freq,freqstr = dongle.getFreq() + if abs(testfreq - freq) < 1024: + print " passed: %d : %f (diff: %f)" % (testfreq, freq, testfreq-freq) + else: + print " *FAILED* %d : %f (diff: %f)" % (testfreq, freq, testfreq-freq) + + testfreq = 433000000 + dongle.setFreq(testfreq) + freq,freqstr = dongle.getFreq() + if abs(testfreq - freq) < 1024: + print " passed: %d : %f (diff: %f)" % (testfreq, freq, testfreq-freq) + else: + print " *FAILED* %d : %f (diff: %f)" % (testfreq, freq, testfreq-freq) + + dongle.checkRepr("Frequency:", float(testfreq), 1024) + dongle.setFreq(freq0) + + # CHANNR + channr0 = dongle.getChannel() + for x in range(15): + dongle.setChannel(x) + channr = dongle.getChannel() + if channr != x: + print " *FAILED* get/setChannel(): %d : %d" % (x, channr) + else: + print " passed: get/setChannel(): %d : %d" % (x, channr) + dongle.checkRepr("Channel:", channr) + dongle.setChannel(channr0) + + # IF and FREQ_OFF + freq_if = dongle.getFsIF() + freqoff = dongle.getFsOffset() + for fif, foff in ((164062,1),(140625,2),(187500,3)): + dongle.setFsIF(fif) + dongle.setFsOffset(foff) + nfif = dongle.getFsIF() + nfoff = dongle.getFsOffset() + if abs(nfif - fif) > 5: + print " *FAILED* get/setFsIFandOffset(): %d : %f (diff: %f)" % (fif,nfif,nfif-fif) + else: + print " passed: get/setFsIFandOffset(): %d : %f (diff: %f)" % (fif,nfif,nfif-fif) + + if foff != nfoff: + print " *FAILED* get/setFsIFandOffset(): %d : %d (diff: %d)" % (foff,nfoff,nfoff-foff) + else: + print " passed: get/setFsIFandOffset(): %d : %d (diff: %d)" % (foff,nfoff,nfoff-foff) + dongle.checkRepr("Intermediate freq:", fif, 11720) + dongle.checkRepr("Frequency Offset:", foff) + + dongle.setFsIF(freq_if) + dongle.setFsOffset(freqoff) + + ### continuing with more simple tests. add completeness later? + # Modem tests + + mod = dongle.getMdmModulation(dongle.radiocfg) + dongle.setMdmModulation(mod, dongle.radiocfg) + modcheck = dongle.getMdmModulation(dongle.radiocfg) + if mod != modcheck: + print " *FAILED* get/setMdmModulation(): %d : %d " % (mod, modcheck) + else: + print " passed: get/setMdmModulation(): %d : %d " % (mod, modcheck) + + chanspc = dongle.getMdmChanSpc(dongle.mhz, dongle.radiocfg) + dongle.setMdmChanSpc(chanspc, dongle.mhz, dongle.radiocfg) + chanspc_check = dongle.getMdmChanSpc(dongle.mhz, dongle.radiocfg) + if chanspc != chanspc_check: + print " *FAILED* get/setMdmChanSpc(): %d : %d" % (chanspc, chanspc_check) + else: + print " passed: get/setMdmChanSpc(): %d : %d" % (chanspc, chanspc_check) + + chanbw = dongle.getMdmChanBW(dongle.mhz, dongle.radiocfg) + dongle.setMdmChanBW(chanbw, dongle.mhz, dongle.radiocfg) + chanbw_check = dongle.getMdmChanBW(dongle.mhz, dongle.radiocfg) + if chanbw != chanbw_check: + print " *FAILED* get/setMdmChanBW(): %d : %d" % (chanbw, chanbw_check) + else: + print " passed: get/setMdmChanBW(): %d : %d" % (chanbw, chanbw_check) + + drate = dongle.getMdmDRate(dongle.mhz, dongle.radiocfg) + dongle.setMdmDRate(drate, dongle.mhz, dongle.radiocfg) + drate_check = dongle.getMdmDRate(dongle.mhz, dongle.radiocfg) + if drate != drate_check: + print " *FAILED* get/setMdmDRate(): %d : %d" % (drate, drate_check) + else: + print " passed: get/setMdmDRate(): %d : %d" % (drate, drate_check) + + deviatn = dongle.getMdmDeviatn(dongle.mhz, dongle.radiocfg) + dongle.setMdmDeviatn(deviatn, dongle.mhz, dongle.radiocfg) + deviatn_check = dongle.getMdmDeviatn(dongle.mhz, dongle.radiocfg) + if deviatn != deviatn_check: + print " *FAILED* get/setMdmdeviatn(): %d : %d" % (deviatn, deviatn_check) + else: + print " passed: get/setMdmdeviatn(): %d : %d" % (deviatn, deviatn_check) + + syncm = dongle.getMdmSyncMode(dongle.radiocfg) + dongle.setMdmSyncMode(syncm, dongle.radiocfg) + syncm_check = dongle.getMdmSyncMode(dongle.radiocfg) + if syncm != syncm_check: + print " *FAILED* get/setMdmSyncMode(): %d : %d" % (syncm, syncm_check) + else: + print " passed: get/setMdmSyncMode(): %d : %d" % (syncm, syncm_check) + + mchstr = dongle.getEnableMdmManchester(dongle.radiocfg) + dongle.setEnableMdmManchester(mchstr, dongle.radiocfg) + mchstr_check = dongle.getEnableMdmManchester(dongle.radiocfg) + if mchstr != mchstr_check: + print " *FAILED* get/setMdmManchester(): %d : %d" % (mchstr, mchstr_check) + else: + print " passed: get/setMdmManchester(): %d : %d" % (mchstr, mchstr_check) + + fec = dongle.getEnableMdmFEC(dongle.radiocfg) + dongle.setEnableMdmFEC(fec, dongle.radiocfg) + fec_check = dongle.getEnableMdmFEC(dongle.radiocfg) + if fec != fec_check: + print " *FAILED* get/setEnableMdmFEC(): %d : %d" % (fec, fec_check) + else: + print " passed: get/setEnableMdmFEC(): %d : %d" % (fec, fec_check) + + dcf = dongle.getEnableMdmDCFilter(dongle.radiocfg) + dongle.setEnableMdmDCFilter(dcf, dongle.radiocfg) + dcf_check = dongle.getEnableMdmDCFilter(dongle.radiocfg) + if dcf != dcf_check: + print " *FAILED* get/setEnableMdmDCFilter(): %d : %d" % (dcf, dcf_check) + else: + print " passed: get/setEnableMdmDCFilter(): %d : %d" % (dcf, dcf_check) + + + # Pkt tests + pqt = dongle.getPktPQT(dongle.radiocfg) + dongle.setPktPQT(pqt, dongle.radiocfg) + pqt_check = dongle.getPktPQT(dongle.radiocfg) + if pqt != pqt_check: + print " *FAILED* get/setEnableMdmFEC(): %d : %d" % (pqt, pqt_check) + else: + print " passed: get/setEnableMdmFEC(): %d : %d" % (pqt, pqt_check) + + # FHSS tests print "\nTesting FHSS State set/get" fhssstate = dongle.getFHSSstate() print repr(fhssstate) @@ -447,9 +2129,26 @@ def unittest(dongle): print repr(dongle.setFHSSstate(fhssstate[1] )) print repr(dongle.getFHSSstate()) +def getValueFromReprString(stringarray, line_text): + for string in stringarray: + if line_text in string: + idx = string.find(":") + val = string[idx+1:].strip() + return (string,val) + +def mkFreq(freq=902000000, mhz=24): + freqmult = (0x10000 / 1000000.0) / mhz + num = int(freq * freqmult) + freq2 = num >> 16 + freq1 = (num>>8) & 0xff + freq0 = num & 0xff + return (num, freq2,freq1,freq0) + + + if __name__ == "__main__": idx = 0 if len(sys.argv) > 1: idx = int(sys.argv.pop()) - d = FHSSNIC(idx=idx) - + d = FHSSNIC(idx=idx, debug=False) + unittest(d) diff --git a/rflib/chipcon_usb.py b/rflib/chipcon_usb.py new file mode 100644 index 0000000..6850ec7 --- /dev/null +++ b/rflib/chipcon_usb.py @@ -0,0 +1,965 @@ +#!/usr/bin/env ipython +import sys, threading, time, struct, select +import usb + +import bits +from chipcondefs import * +from rflib_version import * + +DEFAULT_USB_TIMEOUT = 1000 + +EP_TIMEOUT_IDLE = 400 +EP_TIMEOUT_ACTIVE = 10 + +USB_MAX_BLOCK_SIZE = 512 +USB_RX_WAIT = 1000 +USB_TX_WAIT = 10000 + +USB_BM_REQTYPE_TGTMASK =0x1f +USB_BM_REQTYPE_TGT_DEV =0x00 +USB_BM_REQTYPE_TGT_INTF =0x01 +USB_BM_REQTYPE_TGT_EP =0x02 + +USB_BM_REQTYPE_TYPEMASK =0x60 +USB_BM_REQTYPE_TYPE_STD =0x00 +USB_BM_REQTYPE_TYPE_CLASS =0x20 +USB_BM_REQTYPE_TYPE_VENDOR =0x40 +USB_BM_REQTYPE_TYPE_RESERVED =0x60 + +USB_BM_REQTYPE_DIRMASK =0x80 +USB_BM_REQTYPE_DIR_OUT =0x00 +USB_BM_REQTYPE_DIR_IN =0x80 + +USB_GET_STATUS =0x00 +USB_CLEAR_FEATURE =0x01 +USB_SET_FEATURE =0x03 +USB_SET_ADDRESS =0x05 +USB_GET_DESCRIPTOR =0x06 +USB_SET_DESCRIPTOR =0x07 +USB_GET_CONFIGURATION =0x08 +USB_SET_CONFIGURATION =0x09 +USB_GET_INTERFACE =0x0a +USB_SET_INTERFACE =0x11 +USB_SYNCH_FRAME =0x12 + +APP_GENERIC = 0x01 +APP_DEBUG = 0xfe +APP_SYSTEM = 0xff + + +SYS_CMD_PEEK = 0x80 +SYS_CMD_POKE = 0x81 +SYS_CMD_PING = 0x82 +SYS_CMD_STATUS = 0x83 +SYS_CMD_POKE_REG = 0x84 +SYS_CMD_GET_CLOCK = 0x85 +SYS_CMD_BUILDTYPE = 0x86 +SYS_CMD_BOOTLOADER = 0x87 +SYS_CMD_RFMODE = 0x88 +SYS_CMD_PARTNUM = 0x8e +SYS_CMD_RESET = 0x8f + +EP0_CMD_GET_DEBUG_CODES = 0x00 +EP0_CMD_GET_ADDRESS = 0x01 +EP0_CMD_POKEX = 0x01 +EP0_CMD_PEEKX = 0x02 +EP0_CMD_PING0 = 0x03 +EP0_CMD_PING1 = 0x04 +EP0_CMD_RESET = 0xfe + + +DEBUG_CMD_STRING = 0xf0 +DEBUG_CMD_HEX = 0xf1 +DEBUG_CMD_HEX16 = 0xf2 +DEBUG_CMD_HEX32 = 0xf3 +DEBUG_CMD_INT = 0xf4 + +EP5OUT_MAX_PACKET_SIZE = 64 +EP5IN_MAX_PACKET_SIZE = 64 +# EP5OUT_BUFFER_SIZE must match firmware/include/chipcon_usb.h definition +EP5OUT_BUFFER_SIZE = 516 + +LC_USB_INITUSB = 0x2 +LC_MAIN_RFIF = 0xd +LC_USB_DATA_RESET_RESUME = 0xa +LC_USB_RESET = 0xb +LC_USB_EP5OUT = 0xc +LC_RF_VECTOR = 0x10 +LC_RFTXRX_VECTOR = 0x11 + +LCE_USB_EP5_TX_WHILE_INBUF_WRITTEN = 0x1 +LCE_USB_EP0_SENT_STALL = 0x4 +LCE_USB_EP5_OUT_WHILE_OUTBUF_WRITTEN = 0x5 +LCE_USB_EP5_LEN_TOO_BIG = 0x6 +LCE_USB_EP5_GOT_CRAP = 0x7 +LCE_USB_EP5_STALL = 0x8 +LCE_USB_DATA_LEFTOVER_FLAGS = 0x9 +LCE_RF_RXOVF = 0x10 +LCE_RF_TXUNF = 0x11 + +LCS = {} +LCES = {} +lcls = locals() +for lcl in lcls.keys(): + if lcl.startswith("LCE_"): + LCES[lcl] = lcls[lcl] + LCES[lcls[lcl]] = lcl + if lcl.startswith("LC_"): + LCS[lcl] = lcls[lcl] + LCS[lcls[lcl]] = lcl + +CHIPS = { + 0x91: "CC2511", + 0x81: "CC2510", + 0x11: "CC1111", + 0x01: "CC1110", + } + + +def keystop(delay=0): + return len(select.select([sys.stdin],[],[],delay)[0]) + +def getRfCatDevices(): + ''' + returns a list of USB device objects for any rfcats that are plugged in + NOTE: if any rfcats are in bootloader mode, this will cause python to Exit + ''' + rfcats = [] + for bus in usb.busses(): + for dev in bus.devices: + # OpenMoko assigned or Legacy TI + if (dev.idVendor == 0x0451 and dev.idProduct == 0x4715) or (dev.idVendor == 0x1d50 and (dev.idProduct == 0x6047 or dev.idProduct == 0x6048 or dev.idProduct == 0x605b)): + rfcats.append(dev) + + elif (dev.idVendor == 0x1d50 and (dev.idProduct == 0x6049 or dev.idProduct == 0x604a)): + print "Already in Bootloader Mode... exiting" + exit(0) + + return rfcats + +class ChipconUsbTimeoutException(Exception): + def __str__(self): + return "Timeout waiting for USB response." + +direct=False + +class USBDongle: + ######## INITIALIZATION ######## + def __init__(self, idx=0, debug=False, copyDongle=None, RfMode=RFST_SRX): + self.rsema = None + self.xsema = None + self._bootloader = False + self._init_on_reconnect = True + self._do = None + self.idx = idx + self.cleanup() + self._debug = debug + self._quiet = False + self._threadGo = threading.Event() + self._recv_time = 0 + self.radiocfg = RadioConfig() + self._rfmode = RfMode + self._radio_configured = False + + self.ctrl_thread = threading.Thread(target=self.run_ctrl) + self.ctrl_thread.setDaemon(True) + self.ctrl_thread.start() + + self.recv_thread = threading.Thread(target=self.runEP5_recv) + self.recv_thread.setDaemon(True) + self.recv_thread.start() + + self.send_thread = threading.Thread(target=self.runEP5_send) + self.send_thread.setDaemon(True) + self.send_thread.start() + + self.resetup(copyDongle=copyDongle) + self.max_packet_size = USB_MAX_BLOCK_SIZE + + def cleanup(self): + self._usberrorcnt = 0; + self.recv_queue = '' + self.recv_mbox = {} + self.recv_event = threading.Event() + self.xmit_event = threading.Event() + self.reset_event = threading.Event() + self.xmit_queue = [] + self.xmit_event.clear() + self.reset_event.clear() + self.trash = [] + + def setRFparameters(self): + pass + + def run_ctrl(self): + ''' + we wait for reset events and run resetup + ''' + while True: + self.reset_event.wait() + self.resetup(False) + self.reset_event.clear() + time.sleep(4) + + def setup(self, console=True, copyDongle=None): + global dongles + + if copyDongle is not None: + self.devnum = copyDongle.devnum + self._d = copyDongle._d + self._do = copyDongle._do + self._usbmaxi = copyDongle._usbmaxi + self._usbmaxo = copyDongle._usbmaxo + self._usbcfg = copyDongle._usbcfg + self._usbintf = copyDongle._usbintf + self._usbeps = copyDongle._usbeps + self._threadGo.set() + self.ep5timeout = EP_TIMEOUT_ACTIVE + copyDongle._threadGo.clear() # we're taking over from here. + self.rsema = copyDongle.rsema + self.xsema = copyDongle.xsema + return + + dongles = [] + self.ep5timeout = EP_TIMEOUT_ACTIVE + + for dev in getRfCatDevices(): + if self._debug: print >>sys.stderr,(dev) + do = dev.open() + iSN = do.getDescriptor(1,0,50)[16] + devnum = dev.devnum + dongles.append((devnum, dev, do)) + + dongles.sort() + if len(dongles) == 0: + raise(Exception("No Dongle Found. Please insert a RFCAT dongle.")) + + self.rsema = threading.Lock() + self.xsema = threading.Lock() + + # claim that interface! + do = dongles[self.idx][2] + + try: + do.claimInterface(0) + except Exception,e: + if console or self._debug: print >>sys.stderr,("Error claiming usb interface:" + repr(e)) + + + + self.devnum, self._d, self._do = dongles[self.idx] + self._usbmaxi, self._usbmaxo = (EP5IN_MAX_PACKET_SIZE, EP5OUT_MAX_PACKET_SIZE) + self._usbcfg = self._d.configurations[0] + self._usbintf = self._usbcfg.interfaces[0][0] + self._usbeps = self._usbintf.endpoints + for ep in self._usbeps: + if ep.address & 0x80: + self._usbmaxi = ep.maxPacketSize + else: + self._usbmaxo = ep.maxPacketSize + + self._threadGo.set() + + self.getRadioConfig() + chip = self.getPartNum() + chipstr = CHIPS.get(chip) + + self.chipnum = chip + self.chipstr = chipstr + + if chip == None: + print "Older firmware, consider upgrading." + else: + self.chipstr = "unrecognized dongle: %s" % chip + + if self._init_on_reconnect: + if self._radio_configured: + self._clear_buffers() + self.setRadioConfig() + else: + self.setRFparameters() + self._radio_configured = True + + def resetup(self, console=True, copyDongle=None): + self._do=None + if self._bootloader: + return + #self._threadGo = True + if self._debug: print >>sys.stderr,("waiting (resetup) %x" % self.idx) + while (self._do==None): + try: + self.setup(console, copyDongle) + if copyDongle is None: + self._clear_buffers(False) + self.ping(3, wait=10, silent=True) + self.setRfMode(self._rfmode) + + except Exception, e: + #if console: sys.stderr.write('.') + if not self._quiet: + print >>sys.stderr,("Error in resetup():" + repr(e)) + #if console or self._debug: print >>sys.stderr,("Error in resetup():" + repr(e)) + time.sleep(1) + + + + ######## BASE FOUNDATIONAL "HIDDEN" CALLS ######## + def _sendEP0(self, request=0, buf=None, value=0x200, index=0, timeout=DEFAULT_USB_TIMEOUT): + if buf == None: + buf = 'HELLO THERE' + #return self._do.controlMsg(USB_BM_REQTYPE_TGT_EP|USB_BM_REQTYPE_TYPE_VENDOR|USB_BM_REQTYPE_DIR_OUT, request, "\x00\x00\x00\x00\x00\x00\x00\x00"+buf, value, index, timeout), buf + return self._do.controlMsg(USB_BM_REQTYPE_TGT_EP|USB_BM_REQTYPE_TYPE_VENDOR|USB_BM_REQTYPE_DIR_OUT, request, buf, value, index, timeout), buf + + def _recvEP0(self, request=0, length=64, value=0, index=0, timeout=100): + retary = ["%c"%x for x in self._do.controlMsg(USB_BM_REQTYPE_TGT_EP|USB_BM_REQTYPE_TYPE_VENDOR|USB_BM_REQTYPE_DIR_IN, request, length, value, index, timeout)] + if len(retary): + return ''.join(retary) + return "" + + def _sendEP5(self, buf=None, timeout=DEFAULT_USB_TIMEOUT): + global direct + if (buf==None): + buf = "\xff\x82\x07\x00ABCDEFG" + if direct: + self._do.bulkWrite(5, buf, timeout) + return + + while (len(buf)>0): + drain = buf[:self._usbmaxo] + buf = buf[self._usbmaxo:] + + if self._debug: print >>sys.stderr,"XMIT:"+repr(drain) + try: + self._do.bulkWrite(5, drain, timeout) + except Exception, e: + if self._debug: print >>sys.stderr,"requeuing on error '%s' (%s)" % (repr(drain), e) + self.xsema.acquire() + msg = self.xmit_queue.insert(0, drain) + self.xmit_event.set() + self.xsema.release() + if self._debug: print >>sys.stderr, repr(self.xmit_queue) + ''' + drain = buf[:self._usbmaxo] + buf = buf[self._usbmaxo:] + if len(buf): + if self._debug: print >>sys.stderr,"requeuing '%s'" % repr(buf) + self.xsema.acquire() + msg = self.xmit_queue.insert(0, buf) + self.xsema.release() + if self._debug: print >>sys.stderr, repr(self.xmit_queue) + if self._debug: print >>sys.stderr,"XMIT:"+repr(drain) + try: + self._do.bulkWrite(5, drain, timeout) + except Exception, e: + if self._debug: print >>sys.stderr,"requeuing on error '%s' (%s)" % (repr(drain), e) + self.xsema.acquire() + msg = self.xmit_queue.insert(0, drain) + self.xsema.release() + if self._debug: print >>sys.stderr, repr(self.xmit_queue) + + --- + while (len(buf)>0): + drain = buf[:self._usbmaxo] + buf = buf[self._usbmaxo:] + + if self._debug: print >>sys.stderr,"XMIT:"+repr(drain) + self._do.bulkWrite(5, drain, timeout) + time.sleep(1) + --- + if (len(buf) > self._usbmaxo): + drain = buf[:self._usbmaxo] + buf = buf[self._usbmaxo:] + self.xsema.acquire() + msg = self.xmit_queue.insert(0, buf) + self.xsema.release() + else: + drain = buf[:] + if self._debug: print >>sys.stderr,"XMIT:"+repr(drain) + self._do.bulkWrite(5, drain, timeout) + --- + while (len(buf)>0): + if (len(buf) > self._usbmaxo): + drain = buf[:self._usbmaxo] + buf = buf[self._usbmaxo:] + else: + drain = buf[:] + if self._debug: print >>sys.stderr,"XMIT:"+repr(drain) + self._do.bulkWrite(5, drain, timeout) + time.sleep(1) + ''' + + def _recvEP5(self, timeout=100): + retary = ["%c"%x for x in self._do.bulkRead(0x85, 500, timeout)] + if self._debug: print >>sys.stderr,"RECV:"+repr(retary) + if len(retary): + return ''.join(retary) + return '' + + def _clear_buffers(self, clear_recv_mbox=False): + threadGoSet = self._threadGo.isSet() + self._threadGo.clear() + if self._debug: + print >>sys.stderr,("_clear_buffers()") + if clear_recv_mbox: + for key in self.recv_mbox.keys(): + self.trash.extend(self.recvAll(key)) + elif self.recv_mbox.get(APP_SYSTEM) != None: + self.trash.extend(self.recvAll(APP_SYSTEM)) + self.trash.append((time.time(),self.recv_queue)) + self.recv_queue = '' + # self.xmit_queue = [] # do we want to keep this? + if threadGoSet: self._threadGo.set() + + + ######## TRANSMIT/RECEIVE THREADING ######## + def runEP5_send(self): + msg = '' + self.send_threadcounter = 0 + + while True: + self._threadGo.wait() + self.send_threadcounter = (self.send_threadcounter + 1) & 0xffffffff + + #### transmit stuff. if any exists in the xmit_queue + self.xmit_event.wait() # event driven xmit + msgsent = False + try: + if len(self.xmit_queue): + self.xsema.acquire() + + msg = self.xmit_queue.pop(0) + if not len(self.xmit_queue): # if there was only one message + self.xmit_event.clear() # clear the queue, within the lock + + self.xsema.release() + + self._sendEP5(msg) + msgsent = True + + else: + if self._debug>3: sys.stderr.write("NoMsgToSend ") + except: + sys.excepthook(*sys.exc_info()) + + def runEP5_recv(self): + msg = '' + self.recv_threadcounter = 0 + + while True: + self._threadGo.wait() + if self._debug>3: sys.stderr.write(".") + + self.recv_threadcounter = (self.recv_threadcounter + 1) & 0xffffffff + msgrecv = False + + #### handle debug application + try: + q = None + b = self.recv_mbox.get(APP_DEBUG, None) + if (b != None): + for cmd in b.keys(): + q = b[cmd] + if len(q): + buf,timestamp = q.pop(0) + if self._debug > 1: print >>sys.stderr,("recvthread: buf length: %x\t\t cmd: %x\t\t(%s)"%(len(buf), cmd, repr(buf))) + + if (cmd == DEBUG_CMD_STRING): + if (len(buf) < 4): + if (len(q)): + buf2 = q.pop(0) + buf += buf2 + q.insert(0,buf) + if self._debug: sys.stderr.write('*') + else: + length, = struct.unpack("1: print >>sys.stderr,("len=%d"%length) + if (len(buf) < 4+length): + if (len(q)): + buf2 = q.pop(0) + buf += buf2 + q.insert(0,buf) + if self._debug: sys.stderr.write('&') + else: + printbuf = buf[4:4+length] + requeuebuf = buf[4+length:] + if len(requeuebuf): + if self._debug>1: print >>sys.stderr,(" - DEBUG..requeuing %s"%repr(requeuebuf)) + q.insert(0,requeuebuf) + print >>sys.stderr,("DEBUG: (%.3f) %s" % (timestamp, repr(printbuf))) + elif (cmd == DEBUG_CMD_HEX): + #print >>sys.stderr, repr(buf) + print >>sys.stderr, "DEBUG: (%.3f) %x"%(timestamp, struct.unpack("B", buf[4:5])[0]) + elif (cmd == DEBUG_CMD_HEX16): + #print >>sys.stderr, repr(buf) + print >>sys.stderr, "DEBUG: (%.3f) %x"%(timestamp, struct.unpack(">sys.stderr, repr(buf) + print >>sys.stderr, "DEBUG: (%.3f) %x"%(timestamp, struct.unpack(">sys.stderr, "DEBUG: (%.3f) %d"%(timestamp, struct.unpack(">sys.stderr,('DEBUG COMMAND UNKNOWN: %x (buf=%s)'%(cmd,repr(buf))) + + except: + sys.excepthook(*sys.exc_info()) + + #### receive stuff. + if self._debug>2: print >> sys.stderr, "recvthread: Doing receiving...",self.ep5timeout + try: + #### first we populate the queue + msg = self._recvEP5(timeout=self.ep5timeout) + if len(msg) > 0: + self.recv_queue += msg + msgrecv = True + except usb.USBError, e: + #sys.stderr.write(repr(self.recv_queue)) + #sys.stderr.write(repr(e)) + errstr = repr(e) + if self._debug>4: print >>sys.stderr,repr(sys.exc_info()) + if ('No error' in errstr): + pass + elif ('Connection timed out' in errstr): + pass + elif ('Operation timed out' in errstr): + pass + else: + if ('could not release intf' in errstr): + if self._debug: print "skipping" + pass + elif ('No such device' in errstr): + self._threadGo.clear() + #self.resetup(False) ## THIS IS A PROBLEM. + self.reset_event.set() + print "===== RESETUP set from recv thread" + elif ('Input/output error' in errstr): # USBerror 5 + self._threadGo.clear() + #self.resetup(False) ## THIS IS A PROBLEM. + self.reset_event.set() + print "===== RESETUP set from recv thread" + + else: + if self._debug: print "Error in runEP5() (receiving): %s" % errstr + if self._debug>2: sys.excepthook(*sys.exc_info()) + self._usberrorcnt += 1 + pass + except AttributeError,e: + if "'NoneType' object has no attribute 'bInterfaceNumber'" in str(e): + print "Error: dongle went away. USB bus problems?" + self._threadGo.clear() + #self.resetup(False) + self.reset_event.set() + + except: + sys.excepthook(*sys.exc_info()) + + if self._debug>2: print >> sys.stderr, "recvthread: Sorting mail..." + #### parse, sort, and deliver the mail. + try: + # FIXME: is this robust? or just overcomplex? + if len(self.recv_queue): + idx = self.recv_queue.find('@') + if (idx==-1): + if self._debug > 3: + sys.stderr.write('@') + else: + if (idx>0): + if self._debug: print >>sys.stderr,("runEP5(): idx>0?") + self.trash.append(self.recv_queue[:idx]) + self.recv_queue = self.recv_queue[idx:] + + # recv_queue is vulnerable here, but it's ok because we only modify it earlier in this same thread + # DON'T CHANGE recv_queue from other threads! + msg = self.recv_queue + msglen = len(msg) + #if self._debug > 2: print >> sys.stderr, "Sorting msg", len(msg), msg.encode("hex") + while (msglen>=5): # if not enough to parse length... we'll wait. + if not self._recv_time: # should be 0 to start and when done with a packet + self._recv_time = time.time() + app = ord(msg[1]) + cmd = ord(msg[2]) + length, = struct.unpack("1: print>>sys.stderr,("recvthread: app=%x cmd=%x len=%x"%(app,cmd,length)) + + if (msglen >= length+5): + #### if the queue has enough characters to handle the next message... chop it and put it in the appropriate recv_mbox + msg = self.recv_queue[1:length+5] # drop the initial '@' and chop out the right number of chars + self.recv_queue = self.recv_queue[length+5:] # chop it out of the queue + + b = self.recv_mbox.get(app,None) + + if self.rsema.acquire(): # THREAD SAFETY DANCE + try: + if (b == None): + b = {} + self.recv_mbox[app] = b + except: + sys.excepthook(*sys.exc_info()) + finally: + self.rsema.release() # THREAD SAFETY DANCE COMPLETE + + q = b.get(cmd) + + if self.rsema.acquire(): # THREAD SAFETY DANCE + try: + if (q is None): + q = [] + b[cmd] = q + + q.append((msg, self._recv_time)) + + # notify receivers that a new msg is available + self.recv_event.set() + self._recv_time = 0 # we've delivered the current message + + except: + sys.excepthook(*sys.exc_info()) + finally: + self.rsema.release() # THREAD SAFETY DANCE COMPLETE + + else: + if self._debug>1: sys.stderr.write('=') + + msg = self.recv_queue + msglen = len(msg) + # end of while loop + + except: + sys.excepthook(*sys.exc_info()) + + if self._debug>2: print >> sys.stderr, "readthread: Loop finished" + if not (msgrecv or len(msg)) : + #time.sleep(.1) + self.ep5timeout = EP_TIMEOUT_IDLE + else: + self.ep5timeout = EP_TIMEOUT_ACTIVE + if self._debug > 5: sys.stderr.write(" %s:%d .-P."%(msgrecv,len(msg))) + + + + ######## APPLICATION API ######## + def recv(self, app, cmd=None, wait=USB_RX_WAIT): + ''' + high-level USB EP5 receive. + checks the mbox for app "app" and command "cmd" and returns the next one in the queue + if any of this does not exist yet, wait for a RECV event until "wait" times out. + RECV events are generated by the low-level recv thread "runEP5_recv()" + ''' + startTime = time.time() + self.recv_event.clear() # an event is only interesting if we've already failed to find our message + + while (time.time() - startTime)*1000 < wait: + try: + b = self.recv_mbox.get(app) + if b: + if self._debug: print>>sys.stderr, "Recv msg",app,b,cmd + if cmd is None: + keys = b.keys() + if len(keys): + cmd = b.keys()[-1] # just grab one. no guarantees on the order + + if b is not None and cmd is not None: + q = b.get(cmd) + if self._debug: print >>sys.stderr,"debug(recv) q='%s'"%repr(q) + + if q is not None and self.rsema.acquire(False): + if self._debug>3: print ("rsema.UNlocked", "rsema.locked")[self.rsema.locked()],2 + try: + resp, rt = q.pop(0) + + self.rsema.release() + if self._debug>3: print ("rsema.UNlocked", "rsema.locked")[self.rsema.locked()],2 + + # bring it on home... this is the way out. + return resp[4:], rt + + except IndexError: + pass + + except AttributeError: + sys.excepthook(*sys.exc_info()) + pass + + self.rsema.release() + + self.recv_event.wait((wait - (time.time() - startTime)*1000)/1000) # wait on recv event, with timeout of remaining time + self.recv_event.clear() # clear event, if it's set + + except KeyboardInterrupt: + sys.excepthook(*sys.exc_info()) + break + except: + sys.excepthook(*sys.exc_info()) + + time.sleep(0.001) + + raise(ChipconUsbTimeoutException()) + + def recvAll(self, app, cmd=None): + retval = self.recv_mbox.get(app,None) + if retval is not None: + if cmd is not None: + b = retval + if self.rsema.acquire(): + #if self._debug: print ("rsema.UNlocked", "rsema.locked")[self.rsema.locked()],3 + try: + retval = b.get(cmd) + b[cmd]=[] + if len(retval): + retval = [ (d[4:],t) for d,t in retval ] + except: + sys.excepthook(*sys.exc_info()) + finally: + self.rsema.release() + #if self._debug: print ("rsema.UNlocked", "rsema.locked")[self.rsema.locked()],3 + else: + if self.rsema.acquire(): + #if self._debug: print ("rsema.UNlocked", "rsema.locked")[self.rsema.locked()],4 + try: + self.recv_mbox[app]={} + finally: + self.rsema.release() + #if self._debug: print ("rsema.UNlocked", "rsema.locked")[self.rsema.locked()],4 + return retval + + def send(self, app, cmd, buf, wait=USB_TX_WAIT): + msg = "%c%c%s%s"%(app,cmd, struct.pack(">sys.stderr, ("DONGLE RESPONDING: mode :%x, last error# %d"%(self.getDebugCodes())) + except: + pass + print >>sys.stderr,('recv_queue:\t\t (%d bytes) "%s"'%(len(self.recv_queue),repr(self.recv_queue)[:len(self.recv_queue)%39+20])) + print >>sys.stderr,('trash: \t\t (%d bytes) "%s"'%(len(self.trash),repr(self.trash)[:len(self.trash)%39+20])) + print >>sys.stderr,('recv_mbox \t\t (%d keys) "%s"'%(len(self.recv_mbox),repr(self.recv_mbox)[:len(repr(self.recv_mbox))%79])) + for x in self.recv_mbox.keys(): + print >>sys.stderr,(' recv_mbox %d\t (%d records) "%s"'%(x,len(self.recv_mbox[x]),repr(self.recv_mbox[x])[:len(repr(self.recv_mbox[x]))%79])) + """ + print self.reprRadioState() + print self.reprClientState() + + x,y,z = select.select([sys.stdin],[],[], delay) + if sys.stdin in x: + sys.stdin.read(1) + break + + def getPartNum(self): + try: + r = self.send(APP_SYSTEM, SYS_CMD_PARTNUM, "", 10000) + r,rt = r + except ChipconUsbTimeoutException, e: + r = None + print "SETUP Failed.",e + + return ord(r) + + + def ping(self, count=10, buf="ABCDEFGHIJKLMNOPQRSTUVWXYZ", wait=DEFAULT_USB_TIMEOUT, silent=False): + good=0 + bad=0 + start = time.time() + for x in range(count): + istart = time.time() + + try: + r = self.send(APP_SYSTEM, SYS_CMD_PING, buf, wait) + r,rt = r + istop = time.time() + if not silent: + print "PING: %d bytes transmitted, received: %s (%f seconds)"%(len(buf), repr(r), istop-istart) + except ChipconUsbTimeoutException, e: + r = None + if not silent: + print "Ping Failed.",e + if r==None: + bad+=1 + else: + good+=1 + stop = time.time() + return (good,bad,stop-start) + + def bootloader(self): + ''' + switch to bootloader mode. based on Fergus Noble's CC-Bootloader (https://github.com/fnoble/CC-Bootloader) + this allows the firmware to be updated via USB instead of goodfet/ccdebugger + ''' + try: + self._bootloader = True + r = self.send(APP_SYSTEM, SYS_CMD_BOOTLOADER, "", wait=1) + except ChipconUsbTimeoutException: + pass + + def RESET(self): + try: + r = self.send(APP_SYSTEM, SYS_CMD_RESET, "RESET_NOW\x00") + except ChipconUsbTimeoutException: + pass + + def peek(self, addr, bytecount=1): + r, t = self.send(APP_SYSTEM, SYS_CMD_PEEK, struct.pack(" 1: + idx = int(sys.argv.pop()) + d = USBDongle(idx=idx, debug=False) + + diff --git a/rflib/intelhex.py b/rflib/intelhex.py new file mode 100644 index 0000000..f34f8ab --- /dev/null +++ b/rflib/intelhex.py @@ -0,0 +1,1131 @@ +# Copyright (c) 2005-2009, Alexander Belchenko +# All rights reserved. +# +# Redistribution and use in source and binary forms, +# with or without modification, are permitted provided +# that the following conditions are met: +# +# * Redistributions of source code must retain +# the above copyright notice, this list of conditions +# and the following disclaimer. +# * Redistributions in binary form must reproduce +# the above copyright notice, this list of conditions +# and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of the author nor the names +# of its contributors may be used to endorse +# or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, +# BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +# AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, +# OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +'''Intel HEX file format reader and converter. + +@author Alexander Belchenko (bialix AT ukr net) +@version 1.1 +''' + + +__docformat__ = "javadoc" + + +from array import array +from binascii import hexlify, unhexlify +from bisect import bisect_right +import os + + +class IntelHex(object): + ''' Intel HEX file reader. ''' + + def __init__(self, source=None): + ''' Constructor. If source specified, object will be initialized + with the contents of source. Otherwise the object will be empty. + + @param source source for initialization + (file name of HEX file, file object, addr dict or + other IntelHex object) + ''' + #public members + self.padding = 0x0FF + # Start Address + self.start_addr = None + + # private members + self._buf = {} + self._offset = 0 + + if source is not None: + if isinstance(source, basestring) or getattr(source, "read", None): + # load hex file + self.loadhex(source) + elif isinstance(source, dict): + self.fromdict(source) + elif isinstance(source, IntelHex): + self.padding = source.padding + if source.start_addr: + self.start_addr = source.start_addr.copy() + self._buf = source._buf.copy() + else: + raise ValueError("source: bad initializer type") + + def _decode_record(self, s, line=0): + '''Decode one record of HEX file. + + @param s line with HEX record. + @param line line number (for error messages). + + @raise EndOfFile if EOF record encountered. + ''' + s = s.rstrip('\r\n') + if not s: + return # empty line + + if s[0] == ':': + try: + bin = array('B', unhexlify(s[1:])) + except TypeError: + # this might be raised by unhexlify when odd hexascii digits + raise HexRecordError(line=line) + length = len(bin) + if length < 5: + raise HexRecordError(line=line) + else: + raise HexRecordError(line=line) + + record_length = bin[0] + if length != (5 + record_length): + raise RecordLengthError(line=line) + + addr = bin[1]*256 + bin[2] + + record_type = bin[3] + if not (0 <= record_type <= 5): + raise RecordTypeError(line=line) + + crc = sum(bin) + crc &= 0x0FF + if crc != 0: + raise RecordChecksumError(line=line) + + if record_type == 0: + # data record + addr += self._offset + for i in xrange(4, 4+record_length): + if not self._buf.get(addr, None) is None: + raise AddressOverlapError(address=addr, line=line) + self._buf[addr] = bin[i] + addr += 1 # FIXME: addr should be wrapped + # BUT after 02 record (at 64K boundary) + # and after 04 record (at 4G boundary) + + elif record_type == 1: + # end of file record + if record_length != 0: + raise EOFRecordError(line=line) + raise _EndOfFile + + elif record_type == 2: + # Extended 8086 Segment Record + if record_length != 2 or addr != 0: + raise ExtendedSegmentAddressRecordError(line=line) + self._offset = (bin[4]*256 + bin[5]) * 16 + + elif record_type == 4: + # Extended Linear Address Record + if record_length != 2 or addr != 0: + raise ExtendedLinearAddressRecordError(line=line) + self._offset = (bin[4]*256 + bin[5]) * 65536 + + elif record_type == 3: + # Start Segment Address Record + if record_length != 4 or addr != 0: + raise StartSegmentAddressRecordError(line=line) + if self.start_addr: + raise DuplicateStartAddressRecordError(line=line) + self.start_addr = {'CS': bin[4]*256 + bin[5], + 'IP': bin[6]*256 + bin[7], + } + + elif record_type == 5: + # Start Linear Address Record + if record_length != 4 or addr != 0: + raise StartLinearAddressRecordError(line=line) + if self.start_addr: + raise DuplicateStartAddressRecordError(line=line) + self.start_addr = {'EIP': (bin[4]*16777216 + + bin[5]*65536 + + bin[6]*256 + + bin[7]), + } + + def loadhex(self, fobj): + """Load hex file into internal buffer. This is not necessary + if object was initialized with source set. This will overwrite + addresses if object was already initialized. + + @param fobj file name or file-like object + """ + if getattr(fobj, "read", None) is None: + fobj = file(fobj, "r") + fclose = fobj.close + else: + fclose = None + + self._offset = 0 + line = 0 + + try: + decode = self._decode_record + try: + for s in fobj: + line += 1 + decode(s, line) + except _EndOfFile: + pass + finally: + if fclose: + fclose() + + def loadbin(self, fobj, offset=0): + """Load bin file into internal buffer. Not needed if source set in + constructor. This will overwrite addresses without warning + if object was already initialized. + + @param fobj file name or file-like object + @param offset starting address offset + """ + fread = getattr(fobj, "read", None) + if fread is None: + f = file(fobj, "rb") + fread = f.read + fclose = f.close + else: + fclose = None + + try: + for b in array('B', fread()): + self._buf[offset] = b + offset += 1 + finally: + if fclose: + fclose() + + def loadfile(self, fobj, format): + """Load data file into internal buffer. Preferred wrapper over + loadbin or loadhex. + + @param fobj file name or file-like object + @param format file format ("hex" or "bin") + """ + if format == "hex": + self.loadhex(fobj) + elif format == "bin": + self.loadbin(fobj) + else: + raise ValueError('format should be either "hex" or "bin";' + ' got %r instead' % format) + + # alias (to be consistent with method tofile) + fromfile = loadfile + + def fromdict(self, dikt): + """Load data from dictionary. Dictionary should contain int keys + representing addresses. Values should be the data to be stored in + those addresses in unsigned char form (i.e. not strings). + The dictionary may contain the key, ``start_addr`` + to indicate the starting address of the data as described in README. + + The contents of the dict will be merged with this object and will + overwrite any conflicts. This function is not necessary if the + object was initialized with source specified. + """ + s = dikt.copy() + start_addr = s.get('start_addr') + if s.has_key('start_addr'): + del s['start_addr'] + for k in s.keys(): + if type(k) not in (int, long) or k < 0: + raise ValueError('Source dictionary should have only int keys') + self._buf.update(s) + if start_addr is not None: + self.start_addr = start_addr + + def _get_start_end(self, start=None, end=None): + """Return default values for start and end if they are None + """ + if start is None: + start = min(self._buf.keys()) + if end is None: + end = max(self._buf.keys()) + if start > end: + start, end = end, start + return start, end + + def tobinarray(self, start=None, end=None, pad=None): + ''' Convert this object to binary form as array. If start and end + unspecified, they will be inferred from the data. + @param start start address of output bytes. + @param end end address of output bytes. + @param pad fill empty spaces with this value + (if None used self.padding). + @return array of unsigned char data. + ''' + if pad is None: + pad = self.padding + + bin = array('B') + + if self._buf == {}: + return bin + + start, end = self._get_start_end(start, end) + + for i in xrange(start, end+1): + bin.append(self._buf.get(i, pad)) + + return bin + + def tobinstr(self, start=None, end=None, pad=0xFF): + ''' Convert to binary form and return as a string. + @param start start address of output bytes. + @param end end address of output bytes. + @param pad fill empty spaces with this value + (if None used self.padding). + @return string of binary data. + ''' + return self.tobinarray(start, end, pad).tostring() + + def tobinfile(self, fobj, start=None, end=None, pad=0xFF): + '''Convert to binary and write to file. + + @param fobj file name or file object for writing output bytes. + @param start start address of output bytes. + @param end end address of output bytes. + @param pad fill empty spaces with this value + (if None used self.padding). + ''' + if getattr(fobj, "write", None) is None: + fobj = file(fobj, "wb") + close_fd = True + else: + close_fd = False + + fobj.write(self.tobinstr(start, end, pad)) + + if close_fd: + fobj.close() + + def todict(self): + '''Convert to python dictionary. + + @return dict suitable for initializing another IntelHex object. + ''' + r = {} + r.update(self._buf) + if self.start_addr: + r['start_addr'] = self.start_addr + return r + + def addresses(self): + '''Returns all used addresses in sorted order. + @return list of occupied data addresses in sorted order. + ''' + aa = self._buf.keys() + aa.sort() + return aa + + def minaddr(self): + '''Get minimal address of HEX content. + @return minimal address or None if no data + ''' + aa = self._buf.keys() + if aa == []: + return None + else: + return min(aa) + + def maxaddr(self): + '''Get maximal address of HEX content. + @return maximal address or None if no data + ''' + aa = self._buf.keys() + if aa == []: + return None + else: + return max(aa) + + def __getitem__(self, addr): + ''' Get requested byte from address. + @param addr address of byte. + @return byte if address exists in HEX file, or self.padding + if no data found. + ''' + t = type(addr) + if t in (int, long): + if addr < 0: + raise TypeError('Address should be >= 0.') + return self._buf.get(addr, self.padding) + elif t == slice: + addresses = self._buf.keys() + ih = IntelHex() + if addresses: + addresses.sort() + start = addr.start or addresses[0] + stop = addr.stop or (addresses[-1]+1) + step = addr.step or 1 + for i in xrange(start, stop, step): + x = self._buf.get(i) + if x is not None: + ih[i] = x + return ih + else: + raise TypeError('Address has unsupported type: %s' % t) + + def __setitem__(self, addr, byte): + """Set byte at address.""" + t = type(addr) + if t in (int, long): + if addr < 0: + raise TypeError('Address should be >= 0.') + self._buf[addr] = byte + elif t == slice: + addresses = self._buf.keys() + if not isinstance(byte, (list, tuple)): + raise ValueError('Slice operation expects sequence of bytes') + start = addr.start + stop = addr.stop + step = addr.step or 1 + if None not in (start, stop): + ra = range(start, stop, step) + if len(ra) != len(byte): + raise ValueError('Length of bytes sequence does not match ' + 'address range') + elif (start, stop) == (None, None): + raise TypeError('Unsupported address range') + elif start is None: + start = stop - len(byte) + elif stop is None: + stop = start + len(byte) + if start < 0: + raise TypeError('start address cannot be negative') + if stop < 0: + raise TypeError('stop address cannot be negative') + j = 0 + for i in xrange(start, stop, step): + self._buf[i] = byte[j] + j += 1 + else: + raise TypeError('Address has unsupported type: %s' % t) + + def __delitem__(self, addr): + """Delete byte at address.""" + t = type(addr) + if t in (int, long): + if addr < 0: + raise TypeError('Address should be >= 0.') + del self._buf[addr] + elif t == slice: + addresses = self._buf.keys() + if addresses: + addresses.sort() + start = addr.start or addresses[0] + stop = addr.stop or (addresses[-1]+1) + step = addr.step or 1 + for i in xrange(start, stop, step): + x = self._buf.get(i) + if x is not None: + del self._buf[i] + else: + raise TypeError('Address has unsupported type: %s' % t) + + def __len__(self): + """Return count of bytes with real values.""" + return len(self._buf.keys()) + + def write_hex_file(self, f, write_start_addr=True): + """Write data to file f in HEX format. + + @param f filename or file-like object for writing + @param write_start_addr enable or disable writing start address + record to file (enabled by default). + If there is no start address in obj, nothing + will be written regardless of this setting. + """ + fwrite = getattr(f, "write", None) + if fwrite: + fobj = f + fclose = None + else: + fobj = file(f, 'w') + fwrite = fobj.write + fclose = fobj.close + + # Translation table for uppercasing hex ascii string. + # timeit shows that using hexstr.translate(table) + # is faster than hexstr.upper(): + # 0.452ms vs. 0.652ms (translate vs. upper) + table = ''.join(chr(i).upper() for i in range(256)) + + # start address record if any + if self.start_addr and write_start_addr: + keys = self.start_addr.keys() + keys.sort() + bin = array('B', '\0'*9) + if keys == ['CS','IP']: + # Start Segment Address Record + bin[0] = 4 # reclen + bin[1] = 0 # offset msb + bin[2] = 0 # offset lsb + bin[3] = 3 # rectyp + cs = self.start_addr['CS'] + bin[4] = (cs >> 8) & 0x0FF + bin[5] = cs & 0x0FF + ip = self.start_addr['IP'] + bin[6] = (ip >> 8) & 0x0FF + bin[7] = ip & 0x0FF + bin[8] = (-sum(bin)) & 0x0FF # chksum + fwrite(':' + hexlify(bin.tostring()).translate(table) + '\n') + elif keys == ['EIP']: + # Start Linear Address Record + bin[0] = 4 # reclen + bin[1] = 0 # offset msb + bin[2] = 0 # offset lsb + bin[3] = 5 # rectyp + eip = self.start_addr['EIP'] + bin[4] = (eip >> 24) & 0x0FF + bin[5] = (eip >> 16) & 0x0FF + bin[6] = (eip >> 8) & 0x0FF + bin[7] = eip & 0x0FF + bin[8] = (-sum(bin)) & 0x0FF # chksum + fwrite(':' + hexlify(bin.tostring()).translate(table) + '\n') + else: + if fclose: + fclose() + raise InvalidStartAddressValueError(start_addr=self.start_addr) + + # data + addresses = self._buf.keys() + addresses.sort() + addr_len = len(addresses) + if addr_len: + minaddr = addresses[0] + maxaddr = addresses[-1] + + if maxaddr > 65535: + need_offset_record = True + else: + need_offset_record = False + high_ofs = 0 + + cur_addr = minaddr + cur_ix = 0 + + while cur_addr <= maxaddr: + if need_offset_record: + bin = array('B', '\0'*7) + bin[0] = 2 # reclen + bin[1] = 0 # offset msb + bin[2] = 0 # offset lsb + bin[3] = 4 # rectyp + high_ofs = int(cur_addr/65536) + bytes = divmod(high_ofs, 256) + bin[4] = bytes[0] # msb of high_ofs + bin[5] = bytes[1] # lsb of high_ofs + bin[6] = (-sum(bin)) & 0x0FF # chksum + fwrite(':' + hexlify(bin.tostring()).translate(table) + '\n') + + while True: + # produce one record + low_addr = cur_addr & 0x0FFFF + # chain_len off by 1 + chain_len = min(15, 65535-low_addr, maxaddr-cur_addr) + + # search continuous chain + stop_addr = cur_addr + chain_len + if chain_len: + ix = bisect_right(addresses, stop_addr, + cur_ix, + min(cur_ix+chain_len+1, addr_len)) + chain_len = ix - cur_ix # real chain_len + # there could be small holes in the chain + # but we will catch them by try-except later + # so for big continuous files we will work + # at maximum possible speed + else: + chain_len = 1 # real chain_len + + bin = array('B', '\0'*(5+chain_len)) + bytes = divmod(low_addr, 256) + bin[1] = bytes[0] # msb of low_addr + bin[2] = bytes[1] # lsb of low_addr + bin[3] = 0 # rectype + try: # if there is small holes we'll catch them + for i in range(chain_len): + bin[4+i] = self._buf[cur_addr+i] + except KeyError: + # we catch a hole so we should shrink the chain + chain_len = i + bin = bin[:5+i] + bin[0] = chain_len + bin[4+chain_len] = (-sum(bin)) & 0x0FF # chksum + fwrite(':' + hexlify(bin.tostring()).translate(table) + '\n') + + # adjust cur_addr/cur_ix + cur_ix += chain_len + if cur_ix < addr_len: + cur_addr = addresses[cur_ix] + else: + cur_addr = maxaddr + 1 + break + high_addr = int(cur_addr/65536) + if high_addr > high_ofs: + break + + # end-of-file record + fwrite(":00000001FF\n") + if fclose: + fclose() + + def tofile(self, fobj, format): + """Write data to hex or bin file. Preferred method over tobin or tohex. + + @param fobj file name or file-like object + @param format file format ("hex" or "bin") + """ + if format == 'hex': + self.write_hex_file(fobj) + elif format == 'bin': + self.tobinfile(fobj) + else: + raise ValueError('format should be either "hex" or "bin";' + ' got %r instead' % format) + + def gets(self, addr, length): + """Get string of bytes from given address. If any entries are blank + from addr through addr+length, a NotEnoughDataError exception will + be raised. Padding is not used.""" + a = array('B', '\0'*length) + try: + for i in xrange(length): + a[i] = self._buf[addr+i] + except KeyError: + raise NotEnoughDataError(address=addr, length=length) + return a.tostring() + + def puts(self, addr, s): + """Put string of bytes at given address. Will overwrite any previous + entries. + """ + a = array('B', s) + for i in xrange(len(s)): + self._buf[addr+i] = a[i] + + def getsz(self, addr): + """Get zero-terminated string from given address. Will raise + NotEnoughDataError exception if a hole is encountered before a 0. + """ + i = 0 + try: + while True: + if self._buf[addr+i] == 0: + break + i += 1 + except KeyError: + raise NotEnoughDataError(msg=('Bad access at 0x%X: ' + 'not enough data to read zero-terminated string') % addr) + return self.gets(addr, i) + + def putsz(self, addr, s): + """Put string in object at addr and append terminating zero at end.""" + self.puts(addr, s) + self._buf[addr+len(s)] = 0 + + def dump(self, tofile=None): + """Dump object content to specified file or to stdout if None. Format + is a hexdump with some header information at the beginning, addresses + on the left, and data on right. + + @param tofile file-like object to dump to + """ + + if tofile is None: + import sys + tofile = sys.stdout + # start addr possibly + if self.start_addr is not None: + cs = self.start_addr.get('CS') + ip = self.start_addr.get('IP') + eip = self.start_addr.get('EIP') + if eip is not None and cs is None and ip is None: + tofile.write('EIP = 0x%08X\n' % eip) + elif eip is None and cs is not None and ip is not None: + tofile.write('CS = 0x%04X, IP = 0x%04X\n' % (cs, ip)) + else: + tofile.write('start_addr = %r\n' % start_addr) + # actual data + addresses = self._buf.keys() + if addresses: + addresses.sort() + minaddr = addresses[0] + maxaddr = addresses[-1] + startaddr = int(minaddr/16)*16 + endaddr = int(maxaddr/16+1)*16 + maxdigits = max(len(str(endaddr)), 4) + templa = '%%0%dX' % maxdigits + range16 = range(16) + for i in xrange(startaddr, endaddr, 16): + tofile.write(templa % i) + tofile.write(' ') + s = [] + for j in range16: + x = self._buf.get(i+j) + if x is not None: + tofile.write(' %02X' % x) + if 32 <= x < 128: + s.append(chr(x)) + else: + s.append('.') + else: + tofile.write(' --') + s.append(' ') + tofile.write(' |' + ''.join(s) + '|\n') + + def merge(this, other, overlap='error'): + """Merge content of other IntelHex object to this object. + @param other other IntelHex object. + @param overlap action on overlap of data or starting addr: + - error: raising OverlapError; + - ignore: ignore other data and keep this data + in overlapping region; + - replace: replace this data with other data + in overlapping region. + + @raise TypeError if other is not instance of IntelHex + @raise ValueError if other is the same object as this + @raise ValueError if overlap argument has incorrect value + @raise AddressOverlapError on overlapped data + """ + # check args + if not isinstance(other, IntelHex): + raise TypeError('other should be IntelHex object') + if other is this: + raise ValueError("Can't merge itself") + if overlap not in ('error', 'ignore', 'replace'): + raise ValueError("overlap argument should be either " + "'error', 'ignore' or 'replace'") + # merge data + this_buf = this._buf + other_buf = other._buf + for i in other_buf: + if i in this_buf: + if overlap == 'error': + raise AddressOverlapError( + 'Data overlapped at address 0x%X' % i) + elif overlap == 'ignore': + continue + this_buf[i] = other_buf[i] + # merge start_addr + if this.start_addr != other.start_addr: + if this.start_addr is None: # set start addr from other + this.start_addr = other.start_addr + elif other.start_addr is None: # keep this start addr + pass + else: # conflict + if overlap == 'error': + raise AddressOverlapError( + 'Starting addresses are different') + elif overlap == 'replace': + this.start_addr = other.start_addr +#/IntelHex + + +class IntelHex16bit(IntelHex): + """Access to data as 16-bit words.""" + + def __init__(self, source): + """Construct class from HEX file + or from instance of ordinary IntelHex class. If IntelHex object + is passed as source, the original IntelHex object should not be used + again because this class will alter it. This class leaves padding + alone unless it was precisely 0xFF. In that instance it is sign + extended to 0xFFFF. + + @param source file name of HEX file or file object + or instance of ordinary IntelHex class. + Will also accept dictionary from todict method. + """ + if isinstance(source, IntelHex): + # from ihex8 + self.padding = source.padding + # private members + self._buf = source._buf + self._offset = source._offset + else: + IntelHex.__init__(self, source) + + if self.padding == 0x0FF: + self.padding = 0x0FFFF + + def __getitem__(self, addr16): + """Get 16-bit word from address. + Raise error if only one byte from the pair is set. + We assume a Little Endian interpretation of the hex file. + + @param addr16 address of word (addr8 = 2 * addr16). + @return word if bytes exists in HEX file, or self.padding + if no data found. + """ + addr1 = addr16 * 2 + addr2 = addr1 + 1 + byte1 = self._buf.get(addr1, None) + byte2 = self._buf.get(addr2, None) + + if byte1 != None and byte2 != None: + return byte1 | (byte2 << 8) # low endian + + if byte1 == None and byte2 == None: + return self.padding + + raise BadAccess16bit(address=addr16) + + def __setitem__(self, addr16, word): + """Sets the address at addr16 to word assuming Little Endian mode. + """ + addr_byte = addr16 * 2 + bytes = divmod(word, 256) + self._buf[addr_byte] = bytes[1] + self._buf[addr_byte+1] = bytes[0] + + def minaddr(self): + '''Get minimal address of HEX content in 16-bit mode. + + @return minimal address used in this object + ''' + aa = self._buf.keys() + if aa == []: + return 0 + else: + return min(aa)/2 + + def maxaddr(self): + '''Get maximal address of HEX content in 16-bit mode. + + @return maximal address used in this object + ''' + aa = self._buf.keys() + if aa == []: + return 0 + else: + return max(aa)/2 + +#/class IntelHex16bit + + +def hex2bin(fin, fout, start=None, end=None, size=None, pad=0xFF): + """Hex-to-Bin convertor engine. + @return 0 if all OK + + @param fin input hex file (filename or file-like object) + @param fout output bin file (filename or file-like object) + @param start start of address range (optional) + @param end end of address range (optional) + @param size size of resulting file (in bytes) (optional) + @param pad padding byte (optional) + """ + try: + h = IntelHex(fin) + except HexReaderError, e: + print "ERROR: bad HEX file: %s" % str(e) + return 1 + + # start, end, size + if size != None and size != 0: + if end == None: + if start == None: + start = h.minaddr() + end = start + size - 1 + else: + if (end+1) >= size: + start = end + 1 - size + else: + start = 0 + + try: + h.tobinfile(fout, start, end, pad) + except IOError, e: + print "ERROR: Could not write to file: %s: %s" % (fout, str(e)) + return 1 + + return 0 +#/def hex2bin + + +def bin2hex(fin, fout, offset=0): + """Simple bin-to-hex convertor. + @return 0 if all OK + + @param fin input bin file (filename or file-like object) + @param fout output hex file (filename or file-like object) + @param offset starting address offset for loading bin + """ + h = IntelHex() + try: + h.loadbin(fin, offset) + except IOError, e: + print 'ERROR: unable to load bin file:', str(e) + return 1 + + try: + h.tofile(fout, format='hex') + except IOError, e: + print "ERROR: Could not write to file: %s: %s" % (fout, str(e)) + return 1 + + return 0 +#/def bin2hex + + +class Record(object): + """Helper methods to build valid ihex records.""" + + def _from_bytes(bytes): + """Takes a list of bytes, computes the checksum, and outputs the entire + record as a string. bytes should be the hex record without the colon + or final checksum. + + @param bytes list of byte values so far to pack into record. + @return String representation of one HEX record + """ + assert len(bytes) >= 4 + # calculate checksum + s = (-sum(bytes)) & 0x0FF + bin = array('B', bytes + [s]) + return ':' + hexlify(bin.tostring()).upper() + _from_bytes = staticmethod(_from_bytes) + + def data(offset, bytes): + """Return Data record. This constructs the full record, including + the length information, the record type (0x00), the + checksum, and the offset. + + @param offset load offset of first byte. + @param bytes list of byte values to pack into record. + + @return String representation of one HEX record + """ + assert 0 <= offset < 65536 + assert 0 < len(bytes) < 256 + b = [len(bytes), (offset>>8)&0x0FF, offset&0x0FF, 0x00] + bytes + return Record._from_bytes(b) + data = staticmethod(data) + + def eof(): + """Return End of File record as a string. + @return String representation of Intel Hex EOF record + """ + return ':00000001FF' + eof = staticmethod(eof) + + def extended_segment_address(usba): + """Return Extended Segment Address Record. + @param usba Upper Segment Base Address. + + @return String representation of Intel Hex USBA record. + """ + b = [2, 0, 0, 0x02, (usba>>8)&0x0FF, usba&0x0FF] + return Record._from_bytes(b) + extended_segment_address = staticmethod(extended_segment_address) + + def start_segment_address(cs, ip): + """Return Start Segment Address Record. + @param cs 16-bit value for CS register. + @param ip 16-bit value for IP register. + + @return String representation of Intel Hex SSA record. + """ + b = [4, 0, 0, 0x03, (cs>>8)&0x0FF, cs&0x0FF, + (ip>>8)&0x0FF, ip&0x0FF] + return Record._from_bytes(b) + start_segment_address = staticmethod(start_segment_address) + + def extended_linear_address(ulba): + """Return Extended Linear Address Record. + @param ulba Upper Linear Base Address. + + @return String representation of Intel Hex ELA record. + """ + b = [2, 0, 0, 0x04, (ulba>>8)&0x0FF, ulba&0x0FF] + return Record._from_bytes(b) + extended_linear_address = staticmethod(extended_linear_address) + + def start_linear_address(eip): + """Return Start Linear Address Record. + @param eip 32-bit linear address for the EIP register. + + @return String representation of Intel Hex SLA record. + """ + b = [4, 0, 0, 0x05, (eip>>24)&0x0FF, (eip>>16)&0x0FF, + (eip>>8)&0x0FF, eip&0x0FF] + return Record._from_bytes(b) + start_linear_address = staticmethod(start_linear_address) + + +class _BadFileNotation(Exception): + """Special error class to use with _get_file_and_addr_range.""" + pass + +def _get_file_and_addr_range(s, _support_drive_letter=None): + """Special method for hexmerge.py script to split file notation + into 3 parts: (filename, start, end) + + @raise _BadFileNotation when string cannot be safely split. + """ + if _support_drive_letter is None: + _support_drive_letter = (os.name == 'nt') + drive = '' + if _support_drive_letter: + if s[1:2] == ':' and s[0].upper() in ''.join([chr(i) for i in range(ord('A'), ord('Z')+1)]): + drive = s[:2] + s = s[2:] + parts = s.split(':') + n = len(parts) + if n == 1: + fname = parts[0] + fstart = None + fend = None + elif n != 3: + raise _BadFileNotation + else: + fname = parts[0] + def ascii_hex_to_int(ascii): + if ascii is not None: + try: + return int(ascii, 16) + except ValueError: + raise _BadFileNotation + return ascii + fstart = ascii_hex_to_int(parts[1] or None) + fend = ascii_hex_to_int(parts[2] or None) + return drive+fname, fstart, fend + + +## +# IntelHex Errors Hierarchy: +# +# IntelHexError - basic error +# HexReaderError - general hex reader error +# AddressOverlapError - data for the same address overlap +# HexRecordError - hex record decoder base error +# RecordLengthError - record has invalid length +# RecordTypeError - record has invalid type (RECTYP) +# RecordChecksumError - record checksum mismatch +# EOFRecordError - invalid EOF record (type 01) +# ExtendedAddressRecordError - extended address record base error +# ExtendedSegmentAddressRecordError - invalid extended segment address record (type 02) +# ExtendedLinearAddressRecordError - invalid extended linear address record (type 04) +# StartAddressRecordError - start address record base error +# StartSegmentAddressRecordError - invalid start segment address record (type 03) +# StartLinearAddressRecordError - invalid start linear address record (type 05) +# DuplicateStartAddressRecordError - start address record appears twice +# InvalidStartAddressValueError - invalid value of start addr record +# _EndOfFile - it's not real error, used internally by hex reader as signal that EOF record found +# BadAccess16bit - not enough data to read 16 bit value (deprecated, see NotEnoughDataError) +# NotEnoughDataError - not enough data to read N contiguous bytes + +class IntelHexError(Exception): + '''Base Exception class for IntelHex module''' + + _fmt = 'IntelHex base error' #: format string + + def __init__(self, msg=None, **kw): + """Initialize the Exception with the given message. + """ + self.msg = msg + for key, value in kw.items(): + setattr(self, key, value) + + def __str__(self): + """Return the message in this Exception.""" + if self.msg: + return self.msg + try: + return self._fmt % self.__dict__ + except (NameError, ValueError, KeyError), e: + return 'Unprintable exception %s: %s' \ + % (self.__class__.__name__, str(e)) + +class _EndOfFile(IntelHexError): + """Used for internal needs only.""" + _fmt = 'EOF record reached -- signal to stop read file' + +class HexReaderError(IntelHexError): + _fmt = 'Hex reader base error' + +class AddressOverlapError(HexReaderError): + _fmt = 'Hex file has data overlap at address 0x%(address)X on line %(line)d' + +# class NotAHexFileError was removed in trunk.revno.54 because it's not used + + +class HexRecordError(HexReaderError): + _fmt = 'Hex file contains invalid record at line %(line)d' + + +class RecordLengthError(HexRecordError): + _fmt = 'Record at line %(line)d has invalid length' + +class RecordTypeError(HexRecordError): + _fmt = 'Record at line %(line)d has invalid record type' + +class RecordChecksumError(HexRecordError): + _fmt = 'Record at line %(line)d has invalid checksum' + +class EOFRecordError(HexRecordError): + _fmt = 'File has invalid End-of-File record' + + +class ExtendedAddressRecordError(HexRecordError): + _fmt = 'Base class for extended address exceptions' + +class ExtendedSegmentAddressRecordError(ExtendedAddressRecordError): + _fmt = 'Invalid Extended Segment Address Record at line %(line)d' + +class ExtendedLinearAddressRecordError(ExtendedAddressRecordError): + _fmt = 'Invalid Extended Linear Address Record at line %(line)d' + + +class StartAddressRecordError(HexRecordError): + _fmt = 'Base class for start address exceptions' + +class StartSegmentAddressRecordError(StartAddressRecordError): + _fmt = 'Invalid Start Segment Address Record at line %(line)d' + +class StartLinearAddressRecordError(StartAddressRecordError): + _fmt = 'Invalid Start Linear Address Record at line %(line)d' + +class DuplicateStartAddressRecordError(StartAddressRecordError): + _fmt = 'Start Address Record appears twice at line %(line)d' + +class InvalidStartAddressValueError(StartAddressRecordError): + _fmt = 'Invalid start address value: %(start_addr)s' + + +class NotEnoughDataError(IntelHexError): + _fmt = ('Bad access at 0x%(address)X: ' + 'not enough data to read %(length)d contiguous bytes') + +class BadAccess16bit(NotEnoughDataError): + _fmt = 'Bad access at 0x%(address)X: not enough data to read 16 bit value' diff --git a/rflib/rflib_version.py b/rflib/rflib_version.py new file mode 100644 index 0000000..3a222fc --- /dev/null +++ b/rflib/rflib_version.py @@ -0,0 +1 @@ +RFLIB_VERSION=323 \ No newline at end of file