#!/usr/bin/env python
# GoodFET Chipcon RF Radio Client
#
-# (C) 2009 Travis Goodspeed <travis at radiantmachines.com>
+# (C) 2009, 2012 Travis Goodspeed <travis at radiantmachines.com>
#
# This code is being rewritten and refactored. You've been warned!
-import sys, time, string, cStringIO, struct, glob, serial, os;
+import sys, time, string, cStringIO, struct, glob, os;
from GoodFET import GoodFET;
class GoodFETCCSPI(GoodFET):
- CCSPIAPP=0x50;
- def CCSPIsetup(self):
+ CCSPIAPP=0x51;
+ CCversions={0x233d: "CC2420",
+ }
+ def setup(self):
"""Move the FET into the CCSPI application."""
self.writecmd(self.CCSPIAPP,0x10,0,self.data); #CCSPI/SETUP
- def CCSPItrans8(self,byte):
+ #Set up the radio for ZigBee
+ self.strobe(0x01); #SXOSCON
+ self.strobe(0x02); #SCAL
+ self.poke(0x11, 0x0AC2 & (~0x0800)); #MDMCTRL0, promiscuous
+ self.poke(0x12, 0x0500); #MDMCTRL1
+ self.poke(0x1C, 0x007F); #IOCFG0
+ self.poke(0x19, 0x01C4); #SECCTRL0, disabling crypto
+ #self.poke(0x19, 0x0204); #SECCTRL0, as seen elsewhere.
+ #self.RF_setsync();
+
+ def ident(self):
+ return self.peek(0x1E); #MANFIDL
+ def identstr(self):
+ manfidl=self.peek(0x1E);
+ #manfidh=self.peek(0x1f);
+ try:
+ return "%s" % (self.CCversions[manfidl]);
+ except:
+ return "Unknown0x%04x" % manfidl;
+ def trans8(self,byte):
"""Read and write 8 bits by CCSPI."""
data=self.CCSPItrans([byte]);
return ord(data[0]);
- def CCSPItrans(self,data):
+ def trans(self,data):
"""Exchange data by CCSPI."""
self.data=data;
self.writecmd(self.CCSPIAPP,0x00,len(data),data);
return self.data;
-
- def peek(self,reg,bytes=-1):
+ def strobe(self,reg=0x00):
+ """Strobes a strobe register, returning the status."""
+ data=[reg];
+ self.trans(data);
+ return ord(self.data[0]);
+ def CC_RFST_IDLE(self):
+ """Switch the radio to idle mode, clearing overflows and errors."""
+ self.strobe(0x06); #SRXOFF
+ def CC_RFST_TX(self):
+ """Switch the radio to TX mode."""
+ self.strobe(0x04); #0x05 for CCA
+ def CC_RFST_RX(self):
+ """Switch the radio to RX mode."""
+ self.strobe(0x03); #RX ON
+ def CC_RFST_CAL(self):
+ """Calibrate strobe the radio."""
+ self.strobe(0x02); #RX Calibrate
+ def CC_RFST(self,state=0x00):
+ self.strobe(state);
+ return;
+ def peek(self,reg,bytes=2):
"""Read a CCSPI Register. For long regs, result is flipped."""
- data=[reg,0,0,0,0,0];
+
+ #Reg is ORed with 0x40 by the GoodFET.
+ data=[reg,0,0];
#Automatically calibrate the len.
- if bytes==-1:
- bytes=1;
- if reg==0x0a or reg==0x0b or reg==0x10: bytes=5;
+ bytes=2;
self.writecmd(self.CCSPIAPP,0x02,len(data),data);
- toret=0;
- for i in range(0,bytes):
- toret=toret|(ord(self.data[i+1])<<(8*i));
+ toret=(
+ ord(self.data[2])+
+ (ord(self.data[1])<<8)
+ );
return toret;
- def poke(self,reg,val,bytes=-1):
+ def poke(self,reg,val,bytes=2):
"""Write a CCSPI Register."""
- data=[reg];
-
- #Automatically calibrate the len.
- if bytes==-1:
- bytes=1;
- if reg==0x0a or reg==0x0b or reg==0x10: bytes=5;
-
- for i in range(0,bytes):
- data=data+[(val>>(8*i))&0xFF];
+ data=[reg,(val>>8)&0xFF,val&0xFF];
self.writecmd(self.CCSPIAPP,0x03,len(data),data);
- if self.peek(reg,bytes)!=val and reg!=0x07:
- print "Warning, failed to set r%02x=%02x, got %02x." %(reg,
- val,
- self.peek(reg,bytes));
- return;
+ if self.peek(reg,bytes)!=val and reg!=0x18:
+ print "Warning, failed to set r%02x=0x%04x, got %02x." %(
+ reg,
+ val,
+ self.peek(reg,bytes));
+ return False;
+ return True;
def status(self):
"""Read the status byte."""
- status=self.peek(0x07);
- print "Status=%02x" % status;
+ statusbits={0x80: "?",
+ 0x40: "XOSC16M_STABLE",
+ 0x20: "TX_UNDERFLOW",
+ 0x10: "ENC_BUSY",
+ 0x08: "TX_ACTIVE",
+ 0x04: "LOCK",
+ 0x02: "RSSI_VALID",
+ 0x01: "?"};
+ status=self.strobe(0x00);
+ i=1;
+ str="";
+ while i<0x100:
+ if status&i:
+ str="%s %s" % (statusbits[i],str);
+ i*=2;
+ return str;
#Radio stuff begins here.
- def RF_setenc(self,code="GFSK"):
+ def RF_setenc(self,code="802.15.4"):
"""Set the encoding type."""
- if code!=GFSK:
- return "%s not supported by the CCSPI24L01. Try GFSK."
- return;
+ return code;
def RF_getenc(self):
"""Get the encoding type."""
- return "GFSK";
+ return "802.15.4";
def RF_getrate(self):
- rate=self.peek(0x06)&0x28;
- if rate==0x28:
- rate=250*10**3; #256kbps
- elif rate==0x08:
- rate=2*10**6; #2Mbps
- elif rate==0x00:
- rate=1*10**6; #1Mbps
- return rate;
- def RF_setrate(self,rate=2*10**6):
- r6=self.peek(0x06); #RF_SETUP register
- r6=r6&(~0x28); #Clear rate fields.
- if rate==2*10**6:
- r6=r6|0x08;
- elif rate==1*10**6:
- r6=r6;
- elif rate==250*10**3:
- r6=r6|0x20;
- print "Setting r6=%02x." % r6;
- self.poke(0x06,r6); #Write new setting.
- def RF_setfreq(self,frequency):
- """Set the frequency in Hz."""
+ return 0;
+ def RF_setrate(self,rate=0):
+ return 0;
+ def RF_getsync(self):
+ return self.peek(0x14);
+ def RF_setsync(self,sync=0xa70F):
+ """Set the SYNC preamble.
+ Use 0xA70F for 0xA7."""
+ self.poke(0x14,sync);
+ return;
+
+ def RF_setkey(self,key):
+ """Sets the first key for encryption to the given argument."""
+ print "ERROR: Forgot to set the key.";
- #On the CCSPI24L01+, register 0x05 is the offset in
- #MHz above 2400.
+ return;
+ def RF_setnonce(self,key):
+ """Sets the first key for encryption to the given argument."""
+ print "ERROR: Forgot to set the nonce.";
- chan=frequency/1000000-2400;
- self.poke(0x05,chan);
+ return;
+
+ def RF_setfreq(self,frequency):
+ """Set the frequency in Hz."""
+ mhz=frequency/1000000;
+ #fsctrl=0x8000; #
+ fsctrl=self.peek(0x18)&(~0x3FF);
+ fsctrl=fsctrl+int(mhz-2048)
+ self.poke(0x18,fsctrl);
+ #self.CC_RFST_IDLE();
+ self.strobe(0x02);#SCAL
+ time.sleep(0.01);
+ self.strobe(0x03);#SRXON
def RF_getfreq(self):
"""Get the frequency in Hz."""
-
- #On the CCSPI24L01+, register 0x05 is the offset in
- #MHz above 2400.
-
- return (2400+self.peek(0x05))*10**6
- self.poke(0x05,chan);
+ fsctrl=self.peek(0x18);
+ mhz=2048+(fsctrl&0x3ff)
+ return mhz*1000000;
+ def RF_setchan(self,channel):
+ """Set the ZigBee/802.15.4 channel number."""
+ if channel < 11 or channel > 26:
+ print "Only 802.15.4 channels 11 to 26 are currently supported.";
+ else:
+ self.RF_setfreq( ( (channel-11)*5 + 2405 ) * 1000000 );
def RF_getsmac(self):
"""Return the source MAC address."""
-
- #Register 0A is RX_ADDR_P0, five bytes.
- mac=self.peek(0x0A, 5);
- return mac;
+ return 0xdeadbeef;
def RF_setsmac(self,mac):
"""Set the source MAC address."""
-
- #Register 0A is RX_ADDR_P0, five bytes.
- self.poke(0x0A, mac, 5);
- return mac;
+ return 0xdeadbeef;
def RF_gettmac(self):
"""Return the target MAC address."""
-
- #Register 0x10 is TX_ADDR, five bytes.
- mac=self.peek(0x10, 5);
- return mac;
+ return 0xdeadbeef;
def RF_settmac(self,mac):
"""Set the target MAC address."""
-
- #Register 0x10 is TX_ADDR, five bytes.
- self.poke(0x10, mac, 5);
- return mac;
+ return 0xdeadbeef;
+ def RF_getrssi(self):
+ """Returns the received signal strength, with a weird offset."""
+ rssival=self.peek(0x13)&0xFF; #raw RSSI register
+ return rssival^0x80;
+ def peekram(self,adr,count):
+ """Peeks data from CC2420 RAM."""
+ data=[
+ adr&0xFF,adr>>8, # Address first.
+ count&0xFF,count>>8 # Then length.
+ ];
+ self.writecmd(self.CCSPIAPP,0x84,len(data),data);
+ return self.data;
+ def pokeram(self,adr,data):
+ """Pokes data into CC2420 RAM."""
+ data=[adr&0xFF, adr>>8]+data;
+ self.writecmd(self.CCSPIAPP,0x85,len(data),data);
+ return;
+
+ lastpacket=range(0,0xff);
def RF_rxpacket(self):
- """Get a packet from the radio. Returns None if none is waiting."""
- if self.peek(0x07) & 0x40:
- #Packet has arrived.
- self.writecmd(self.CCSPIAPP,0x80,0,None); #RX Packet
- data=self.data;
- self.poke(0x07,0x40);#clear bit.
- return data;
- elif self.peek(0x07)==0:
- self.writecmd(self.CCSPIAPP,0x82,0,None); #Flush
- self.poke(0x07,0x40);#clear bit.
+ """Get a packet from the radio. Returns None if none is
+ waiting."""
+
+ data="\0";
+ self.data=data;
+ self.writecmd(self.CCSPIAPP,0x80,len(data),data);
+ buffer=self.data;
+
+ self.lastpacket=buffer;
+ if(len(buffer)==0):
+ return None;
+
+ return buffer;
+ def RF_rxpacketrepeat(self):
+ """Gets packets from the radio, ignoring all future requests so as
+ not to waste time. Call RF_rxpacket() after this."""
+
+ self.writecmd(self.CCSPIAPP,0x91,0,None);
return None;
+
+ def RF_rxpacketdec(self):
+ """Get and decrypt a packet from the radio. Returns None if
+ none is waiting."""
+
+ data="\0";
+ self.data=data;
+ self.writecmd(self.CCSPIAPP,0x90,len(data),data);
+ buffer=self.data;
+
+ self.lastpacket=buffer;
+ if(len(buffer)==0):
+ return None;
+
+ return buffer;
+
+ def RF_txpacket(self,packet):
+ """Send a packet through the radio."""
+ self.writecmd(self.CCSPIAPP,0x81,len(packet),packet);
+ #time.sleep(1);
+ #self.strobe(0x09);
+ return;
+
+ def RF_reflexjam(self,duration=0):
+ """Place the device into reflexive jamming mode."""
+ data = [duration&0xff,
+ (duration>>8)&0xff];
+ self.writecmd(self.CCSPIAPP,0xA0,len(data),data);
+ return;
+
+ def RF_reflexjam_autoack(self):
+ """Place the device into reflexive jamming mode
+ and that also sends a forged ACK if needed."""
+ data = "";
+ self.writecmd(self.CCSPIAPP,0xA1,len(data),data);
+ print "Got:", data, "and", self.data
+ return;
+
+ def RF_modulated_spectrum(self):
+ """Hold a carrier wave on the present frequency."""
+ # print "Don't know how to hold a carrier.";
+ # 33.1 p.55:
+ # reset chip
+ # SXOSCON
+ # set MDMCTRL1.TX_MODE to 3 0x12 3:2
+ # STXON 0x04
+
+ mdmctrl1=self.peek(0x12);
+ #print "mdmctrl1 was %04x" % mdmctrl1;
+ mdmctrl1=mdmctrl1|0x00c0; #MDMCTRL1.TX_MODE = 3
+ self.poke(0x12, mdmctrl1); #MDMCTRL1
+
+ mdmctrl1=self.peek(0x12);
+ #print "mdmctrl1 is %04x" % mdmctrl1;
+
+ # http://e2e.ti.com/support/low_power_rf/f/155/t/15914.aspx?PageIndex=2
+ # suggests this
+ self.strobe(0x02); #STXCAL
+ #print "STXCAL status: %s" % self.status()
+
+ # is this necessary?
+ self.strobe(0x09); #SFLUSHTX
+ #print "SFLUSHTX status: %s" % self.status()
+
+ self.strobe(0x04); #STXON
+ #print "STXON status: %s" % self.status()
+
def RF_carrier(self):
"""Hold a carrier wave on the present frequency."""
- # Set CONT_WAVE, PLL_LOCK, and 0dBm in RF_SETUP
- self.poke(0x06,8+10+4+2);
-
+ # print "Don't know how to hold a carrier.";
+ # 33.1 p.54:
+ # reset chip
+ # SXOSCON
+ # set MDMCTRL1.TX_MODE to 2 or 3 0x12 3:2
+ # set DACTST to 0x1800 0x2E
+ # STXON 0x04
+
+ mdmctrl1=self.peek(0x12);
+ #print "mdmctrl1 was %04x" % mdmctrl1;
+ mdmctrl1=mdmctrl1|0x0080;
+ mdmctrl1=mdmctrl1&0x0080; #MDMCTRL1.TX_MODE = 2
+ self.poke(0x12, mdmctrl1); #MDMCTRL1
+
+ mdmctrl1=self.peek(0x12);
+ #print "mdmctrl1 is %04x" % mdmctrl1;
+
+ self.poke(0x2E, 0x1800); #DACTST
+ dactst=self.peek(0x2E);
+ #print "dactst is %04x" % dactst;
+
+ # see above for why this is here
+ self.strobe(0x02); #STXCAL
+ #print "STXCAL status: %s" % self.status()
+ self.strobe(0x09); #SFLUSHTX
+ #print "SFLUSHTX status: %s" % self.status()
+
+ self.strobe(0x04); #STXON
+ #print "STXON status: %s" % self.status()
+
+ def RF_promiscuity(self,promiscuous=1):
+ mdmctrl0=self.peek(0x11);
+ if promiscuous>0:
+ mdmctrl0=mdmctrl0&(~0x800);
+ else:
+ mdmctrl0=mdmctrl0|0x800;
+ self.poke(0x11,mdmctrl0);
+ return;
+ def RF_autocrc(self,autocrc=1):
+ mdmctrl0=self.peek(0x11);
+ if autocrc==0:
+ mdmctrl0=mdmctrl0&(~0x0020);
+ else:
+ mdmctrl0=mdmctrl0|0x0020;
+ self.poke(0x11,mdmctrl0);
+ return;
+ def RF_autoack(self,autoack=1):
+ mdmctrl0=self.peek(0x11);
+ if autoack==0:
+ mdmctrl0=mdmctrl0&(~0x0010);
+ else:
+ mdmctrl0=mdmctrl0|0x0010;
+ self.poke(0x11,mdmctrl0);
+ return;
packetlen=16;
def RF_setpacketlen(self,len=16):
"""Set the number of bytes in the expected payload."""
- self.poke(0x11,len);
+ #self.poke(0x11,len);
self.packetlen=len;
def RF_getpacketlen(self):
"""Set the number of bytes in the expected payload."""
- len=self.peek(0x11);
+ #len=self.peek(0x11);
self.packetlen=len;
return len;
maclen=5;
choice=choices[len];
self.poke(0x03,choice);
self.maclen=len;
+ def printpacket(self,packet,prefix="#"):
+ print self.packet2str(packet,prefix);
+ def packet2str(self,packet,prefix="#"):
+ s="";
+ i=0;
+ for foo in packet:
+ s="%s %02x" % (s,ord(foo));
+ return "%s%s" % (prefix,s);
+
+ def printdissect(self,packet):
+ try:
+ from scapy.all import Dot15d4
+ except ImportError:
+ print "To use packet disection, Scapy must be installed and have the Dot15d4 extension present."
+ print "try: hg clone http://hg.secdev.org/scapy-com";
+ print " sudo ./setup.py install";
+ self.printpacket(packet);
+ try:
+ scapyd = Dot15d4(packet[1:]);
+ scapyd.show();
+ except:
+ pass;