Package killerbee :: Module dev_freakduino
[hide private]
[frames] | no frames]

Source Code for Module killerbee.dev_freakduino

  1  ''' 
  2  Support from the Freakduino platform from Abika/Freaklabs. 
  3   
  4  This is not a maintained platfrom and functionality may be broken or lacking. 
  5  ''' 
  6   
  7  import usb 
  8  import serial 
  9  import time 
 10  import struct 
 11  from datetime import datetime, date 
 12  from datetime import time as dttime 
 13  from kbutils import KBCapabilities, makeFCS 
 14   
 15  MODE_NONE    = 0x01 
 16  MODE_SNIFF   = 0x02 
 17   
18 -class FREAKDUINO:
19 - def __init__(self, serialpath):
20 ''' 21 Instantiates the KillerBee class for our sketch running on ChibiArduino on Freakduino hardware. 22 @param serialpath: /dev/ttyUSB* type serial port identifier 23 @return: None 24 @rtype: None 25 ''' 26 self._channel = None 27 self.handle = None 28 self.dev = serialpath 29 self.date = None 30 self.lon, self.lat, self.alt = (None, None, None) 31 self.handle = serial.Serial(port=self.dev, baudrate=57600, \ 32 timeout=1, bytesize=8, parity='N', stopbits=1, xonxoff=0) 33 self.capabilities = KBCapabilities() 34 self.__set_capabilities()
35
36 - def close(self):
37 ''' 38 Closes the serial port. After closing, must reinitialize class again before use. 39 @return: None 40 @rtype: None 41 ''' 42 self.handle.close() 43 self.handle = None
44 45 # KillerBee implements these, maybe it shouldn't and instead leave it to the driver as needed. 46 # TODO deprecate these functions in favor of self.capabilities class functions
47 - def check_capability(self, capab):
48 return self.capabilities.check(capab)
49 - def get_capabilities(self):
50 return self.capabilities.getlist()
51
52 - def __set_capabilities(self):
53 ''' 54 Sets the capability information for Freakdruino device based on the currently loaded sketch. 55 @rtype: None 56 @return: None 57 ''' 58 #TODO have it set based on what the sketch says it can support 59 self.capabilities.setcapab(KBCapabilities.FREQ_2400, True) 60 self.capabilities.setcapab(KBCapabilities.SNIFF, True) 61 self.capabilities.setcapab(KBCapabilities.SETCHAN, True) 62 return
63
64 - def get_dev_info(self):
65 ''' 66 Returns device information in a list identifying the device. 67 @rtype: List 68 @return: List of 3 strings identifying device. 69 ''' 70 return [self.dev, "Dartmouth Freakduino", ""]
71
72 - def __send_cmd(self, cmdstr, arg=None):
73 self.handle.flush() 74 time.sleep(1.5) #this delay seems crucial 75 76 self.handle.write("S") 77 self.handle.write(cmdstr) 78 if arg != None: self.handle.write(arg) 79 self.handle.write('\r') 80 81 #print "Sent S%s" % (cmdstr) 82 self.handle.flush()
83 84 # Deprecated due to unreliability
85 - def __serial_cmd(self, cmdstr, arg=None):
86 ''' 87 Sends a command over the self.conn serial connection. 88 Ex: If provided cmdstr = "C!N" it will send "SC!N", telling the device to turn on sniffing ("N"), 89 and it expects to receive a confirmation back "&C!N" to confirm success. 90 ''' 91 print "Flushing out of buffer:", self.handle.inWaiting() 92 self.handle.flushInput() 93 if len(cmdstr) > 3: 94 raise Exception("Command string is less than minimum length (S%s)." % cmdstr) 95 self.__send_cmd(cmdstr, arg) 96 97 # TODO ugly and unreliable: 98 # This should just wait for a & and then parse things after it, 99 # however it seems sometimes you have to resend the command or something. 100 print "Line:", self.handle.readline(eol='&') 101 counter = 0 102 char = self.handle.read() 103 while (char != '&'): 104 print self.handle.inWaiting(), "Waiting...", char 105 time.sleep(0.01) 106 if (counter > 8): 107 self.__send_cmd(cmdstr, arg) 108 counter = 0 109 print "Resend Response Line:", self.handle.readline(eol='&') 110 else: counter += 1 111 char = self.handle.read() 112 response = '' 113 for i in range(3): 114 response += self.handle.read() 115 116 if response == cmdstr[:3]: 117 print "Got a response:", response, "matches", cmdstr 118 return True 119 else: 120 print "Invalid response:", response, cmdstr[:3] 121 return False
122 123 # Send the command for the Dartmouth-mod Freakduino to dump data logged in EEPROM
124 - def eeprom_dump(self):
125 self.__send_cmd("C!D")
126 127 # KillerBee expects the driver to implement this function
128 - def sniffer_on(self, channel=None):
129 ''' 130 Turns the sniffer on such that pnext() will start returning observed 131 data. Will set the command mode to Air Capture if it is not already 132 set. 133 @type channel: Integer 134 @param channel: Sets the channel, optional 135 @rtype: None 136 ''' 137 self.capabilities.require(KBCapabilities.SNIFF) 138 139 if channel != None: 140 self.set_channel(channel) 141 142 #TODO implement mode change to start sniffer sending packets to us 143 self.__send_cmd("C!N") 144 self.mode = MODE_SNIFF 145 self.__stream_open = True
146 147 # KillerBee expects the driver to implement this function
148 - def sniffer_off(self):
149 ''' 150 Turns the sniffer off, freeing the hardware for other functions. It is 151 not necessary to call this function before closing the interface with 152 close(). 153 @rtype: None 154 ''' 155 self.__send_cmd("C!F") 156 self.mode = MODE_NONE 157 self.__stream_open = False
158 159 # KillerBee expects the driver to implement this function
160 - def set_channel(self, channel):
161 ''' 162 Sets the radio interface to the specifid channel (limited to 2.4 GHz channels 11-26) 163 @type channel: Integer 164 @param channel: Sets the channel, optional 165 @rtype: None 166 ''' 167 self.capabilities.require(KBCapabilities.SETCHAN) 168 169 if channel >= 10 or channel <= 26: 170 self._channel = channel 171 #TODO actually check that it responds correctly to the request 172 self.__send_cmd("C!C %d" % channel) 173 else: 174 raise Exception('Invalid channel')
175 176 # KillerBee expects the driver to implement this function
177 - def inject(self, packet, channel=None, count=1, delay=0):
178 ''' 179 Injects the specified packet contents. 180 @type packet: String 181 @param packet: Packet contents to transmit, without FCS. 182 @type channel: Integer 183 @param channel: Sets the channel, optional 184 @type count: Integer 185 @param count: Transmits a specified number of frames, def=1 186 @type delay: Float 187 @param delay: Delay between each frame, def=1 188 @rtype: None 189 ''' 190 self.capabilities.require(KBCapabilities.INJECT) 191 192 if len(packet) < 1: 193 raise Exception('Empty packet') 194 if len(packet) > 125: # 127 - 2 to accommodate FCS 195 raise Exception('Packet too long') 196 197 if channel != None: 198 self.set_channel(channel) 199 200 # Append two bytes to be replaced with FCS by firmware. 201 packet = ''.join([packet, "\x00\x00"]) 202 203 for pnum in range(0, count): 204 raise Exception('Not yet implemented') 205 # Format for packet is opcode CMD_INJECT_FRAME, one-byte length, packet data 206 #TODO RZ_USB_COMMAND_EP, struct.pack("B", RZ_CMD_INJECT_FRAME) + struct.pack("B", len(packet)) + packet) 207 time.sleep(delay)
208 209 # KillerBee expects the driver to implement this function 210 #TODO I suspect that if you don't call this often enough while getting frames, the serial buffer may overflow.
211 - def pnext(self, timeout=100):
212 ''' 213 Returns packet data as a string, else None. 214 @type timeout: Integer 215 @param timeout: Timeout to wait for packet reception in usec 216 @rtype: List 217 @return: Returns None is timeout expires and no packet received. When a packet is received, 218 a list is returned, in the form [ String: packet contents | Bool: Valid CRC | Int: Unscaled RSSI ] 219 ''' 220 if self.__stream_open == False: 221 self.sniffer_on() #start sniffing 222 return self.pnext_rec(timeout)
223 224 # Bulk of pnext implementation, but does not ensure the sniffer is on first, thus usable for EEPROM reading
225 - def pnext_rec(self, timeout=100):
226 pdata = '' 227 ldata = '' 228 229 self.handle.timeout=timeout # Allow pySerial to handle timeout 230 startChar = self.handle.read() 231 if startChar == None: return None # Sense timeout case and return 232 233 # Listens for serial message of general format R!<data>; 234 if startChar == "R": # Get packet data 235 if self.handle.read() == "!": 236 x = self.handle.read() 237 while (x != ";"): 238 pdata += x 239 x = self.handle.read() 240 if startChar == "L": # Get location data 241 if self.handle.read() == "!": 242 x = self.handle.read() 243 while (x != ";"): 244 ldata += x 245 x = self.handle.read() 246 if startChar == "[": # Sense when done reading from EEPROM 247 if self.handle.read(40) == "{[ DONE READING BACK ALL LOGGED DATA ]}]": 248 raise StopIteration("All Data Read") 249 250 # If location received, update our local variables: 251 if ldata != None and ldata != '': 252 self.processLocationUpdate(ldata) 253 254 if pdata == None or pdata == '': 255 return None 256 257 # Parse received data as <rssi>!<time>!<packtlen>!<frame> 258 data = pdata.split("!", 3) 259 try: 260 rssi = ord(data[0]) 261 frame = data[3] 262 if frame[-2:] == makeFCS(frame[:-2]): validcrc = True 263 else: validcrc = False 264 except: 265 print "Error parsing stream received from device:", pdata, data 266 return None 267 #Return in a nicer dictionary format, so we don't have to reference by number indicies. 268 #Note that 0,1,2 indicies inserted twice for backwards compatibility. 269 result = {0:frame, 1:validcrc, 2:rssi, 'bytes':frame, 'validcrc':validcrc, 'rssi':rssi} 270 result['dbm'] = None #TODO calculate dBm antenna signal based on RSSI formula 271 result['datetime'] = self.getCaptureDateTime(data) 272 result['location'] = (self.lon, self.lat, self.alt) 273 return result
274
275 - def getCaptureDateTime(self, data):
276 try: 277 timestr = "%08d" % (struct.unpack('L', data[1])[0]) #in format hhmmsscc 278 time = dttime(int(timestr[:2]), int(timestr[2:4]), int(timestr[4:6]), int(timestr[6:])) 279 except: 280 print "Issue with time format:", timestr, data 281 time = None 282 if self.date == None: self.date = date.today() 283 if time == None or time == dttime.min: time = (datetime.now()).time() 284 #TODO address timezones by going to UTC everywhere 285 return datetime.combine(self.date, time)
286
287 - def processLocationUpdate(self, ldata):
288 ''' 289 Take a location string passed from the device and update the driver's internal state of last received location. 290 Format of ldata: longlatialtidate 291 ''' 292 self.lon = struct.unpack('l', ldata[0:4])[0] 293 self.lat = struct.unpack('l', ldata[4:8])[0] 294 self.alt = struct.unpack('l', ldata[8:12])[0] 295 date = str(struct.unpack('L', ldata[12:16])[0]) 296 self.date = datetime.date(date[-2:], date[-4:-2], date[:-4]) 297 #TODO parse data formats (lon=-7228745 lat=4370648 alt=3800 age=63 date=70111 time=312530) 298 print self.lon, self.lat, self.alt, self.date
299
300 - def ping(self, da, panid, sa, channel=None):
301 ''' 302 Not yet implemented. 303 @return: None 304 @rtype: None 305 ''' 306 raise Exception('Not yet implemented')
307
308 - def jammer_on(self, channel=None):
309 ''' 310 Not yet implemented. 311 @type channel: Integer 312 @param channel: Sets the channel, optional 313 @rtype: None 314 ''' 315 self.capabilities.require(KBCapabilities.PHYJAM) 316 317 if self.__cmdmode != RZ_CMD_MODE_AC: 318 self._set_mode(RZ_CMD_MODE_AC) 319 320 if channel != None: 321 self.set_channel(channel) 322 323 #TODO implement 324 raise Exception('Not yet implemented')
325
326 - def jammer_off(self, channel=None):
327 ''' 328 Not yet implemented. 329 @return: None 330 @rtype: None 331 ''' 332 #TODO implement 333 raise Exception('Not yet implemented')
334