Beispiel #1
0
    def __init__(self, dev, revision=DEFAULT_REVISION):
        '''
        Instantiates the KillerBee class for the ApiMote platform running GoodFET firmware.
        @type dev:   String
        @param dev:  Serial device identifier (ex /dev/ttyUSB0)
        @type revision: Integer
        @param revision: The revision number for the ApiMote, which is used by 
            the called GoodFET libraries to properly communicate with
            and configure the hardware.
        @return: None
        @rtype: None
        '''
        self._channel = None
        self._page = 0
        self.handle = None
        self.dev = dev

        self.__revision_num = revision
        # Set enviroment variables for GoodFET code to use
        os.environ["platform"] = "apimote%d".format(self.__revision_num)
        os.environ["board"] = "apimote%d".format(self.__revision_num)
        self.handle = GoodFETCCSPI()
        self.handle.serInit(port=self.dev)
        self.handle.setup()
        # TODO can we verify here the revision number that was sent is correct?

        self.__stream_open = False
        self.capabilities = KBCapabilities()
        self.__set_capabilities()
Beispiel #2
0
    def __init__(self, dev, bus, variant):
        #TODO deprecate bus param, and dev becomes a usb.core.Device object, not a string in pyUSB 1.x use
        """
        Instantiates the KillerBee class for Zigduino running GoodFET firmware.
        @type dev:   String
        @param dev:  PyUSB device
        @return: None
        @rtype: None
        """
        if variant == CC253x.VARIANT_CC2530:
            self._data_ep = CC253x.USB_CC2530_DATA_EP
        else:
            self._data_ep = CC253x.USB_CC2531_DATA_EP
        self._channel = None
        self._page = 0
        self.dev = dev

        self.__stream_open = False
        self.capabilities = KBCapabilities()
        self.__set_capabilities()

        # Set default configuration
        self.dev.set_configuration()

        # get name from USB descriptor
        self.name = usb.util.get_string(self.dev, self.dev.iProduct)

        # Get wMaxPacketSize from the data endpoint
        for cfg in self.dev:
            for intf in cfg:
                for ep in intf:
                    if ep.bEndpointAddress == self._data_ep:
                        self._maxPacketSize = ep.wMaxPacketSize
Beispiel #3
0
    def __init__(self, dev, bus):
        #TODO deprecate bus param, and dev becomes a usb.core.Device object, not a string in pyUSB 1.x use
        '''
        Instantiates the KillerBee class for the RZUSBSTICK hardware.

        @type dev:   TODO
        @param dev:  USB device identifier
        @type bus:   TODO
        @param bus:  Identifies the USB bus the device is on
        @return: None
        @rtype: None
        '''
        self._channel = None
        self.handle = None
        self.dev = dev
        self.__bus = bus

        if self.dev != None:
            self.__handle_open()
        else:
            raise Exception('No interface found')

        # Tracking the command operating mode (None or AirCapture Mode)
        self.__cmdmode = RZ_CMD_MODE_NONE

        # Tracking if the RZ_CMD_OPEN_STREAM parameter is set for packet reception
        self.__stream_open = False

        # Capabilities list
        self.capabilities = KBCapabilities()
        self.__set_capabilities()
    def __init__(self, dev=DEFAULT_IP, recvport=DEFAULT_UDP, recvip=DEFAULT_GW):
        '''
        Instantiates the KillerBee class for the Sewio Sniffer.
        @type dev:   String
        @param dev:  IP address (ex 10.10.10.2)
        @type recvport: Integer
        @param recvport: UDP port to listen for sniffed packets on.
        @type recvip: String
        @param recvip: IP address of the host, where the sniffer will send sniffed packets to.
        @return: None
        @rtype: None
        '''
        self._channel = None
        self._modulation = 0 #unknown, will be set by change channel currently
        self.handle = None
        self.dev = dev
        
        #TODO The receive port and receive IP address are currently not 
        # obtained from or verified against the Sewio sniffer, nor are they
        # used to change the settings on the sniffer.
        self.udp_recv_port = recvport
        self.udp_recv_ip   = recvip

        self.__revision_num = getFirmwareVersion(self.dev)
        if self.__revision_num not in TESTED_FW_VERS:
            print("Warning: Firmware revision {0} reported by the sniffer is not currently supported. Errors may occur and dev_sewio.py may need updating.".format(self.__revision_num))

        self.handle = socket(AF_INET, SOCK_DGRAM)
        self.handle.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
        self.handle.bind((self.udp_recv_ip, self.udp_recv_port))

        self.__stream_open = False
        self.capabilities = KBCapabilities()
        self.__set_capabilities()
Beispiel #5
0
    def __init__(self, dev, revision=DEFAULT_REVISION):
        '''
        Instantiates the KillerBee class for the ApiMote platform running GoodFET firmware.
        @type dev:   String
        @param dev:  Serial device identifier (ex /dev/ttyUSB0)
        @type revision: Integer
        @param revision: The revision number for the ApiMote, which is used by 
            the called GoodFET libraries to properly communicate with
            and configure the hardware.
        @return: None
        @rtype: None
        '''
        self._channel = None
        self.handle = None
        self.dev = dev

        self.__revision_num = revision
        # Set enviroment variables for GoodFET code to use
        os.environ["platform"] = "apimote%d".format(self.__revision_num)
        os.environ["board"] = "apimote%d".format(self.__revision_num)
        self.handle = GoodFETCCSPI()
        self.handle.serInit(port=self.dev)
        self.handle.setup()
        # TODO can we verify here the revision number that was sent is correct?

        self.__stream_open = False
        self.capabilities = KBCapabilities()
        self.__set_capabilities()
    def __init__(self, dev, bus):
        #TODO deprecate bus param, and dev becomes a usb.core.Device object, not a string in pyUSB 1.x use
        '''
        Instantiates the KillerBee class for the RZUSBSTICK hardware.

        @type dev:   TODO
        @param dev:  USB device identifier
        @type bus:   TODO
        @param bus:  Identifies the USB bus the device is on
        @return: None
        @rtype: None
        '''
        self._channel = None
        self.handle = None
        self.dev = dev
        self.__bus = bus

        if self.dev != None:
            self.__handle_open()
        else:
            raise Exception('No interface found')

        # Tracking the command operating mode (None or AirCapture Mode)
        self.__cmdmode = RZ_CMD_MODE_NONE

        # Tracking if the RZ_CMD_OPEN_STREAM parameter is set for packet reception
        self.__stream_open = False

        # Capabilities list
        self.capabilities = KBCapabilities()
        self.__set_capabilities()
Beispiel #7
0
 def __init__(self, serialpath):
     '''
     Instantiates the KillerBee class for our sketch running on ChibiArduino on Freakduino hardware.
     @param serialpath:  /dev/ttyUSB* type serial port identifier
     @return: None
     @rtype: None
     '''
     self._channel = None
     self.handle = None
     self.dev = serialpath
     self.date = None
     self.lon, self.lat, self.alt = (None, None, None)
     self.handle = serial.Serial(port=self.dev, baudrate=57600, \
                                 timeout=1, bytesize=8, parity='N', stopbits=1, xonxoff=0)
     self.capabilities = KBCapabilities()
     self.__set_capabilities()
Beispiel #8
0
    def __init__(self, dev, bus):
        """
        Instantiates the KillerBee class for the RZUSBSTICK hardware.

        @type dev:   String
        @param dev:  USB device identifier
        @type bus:   String
        @param bus:  Identifies the USB bus the device is on
        @return: None
        @rtype: None
        """
        self._channel = None
        self.handle = None
        self.dev = dev
        self.__bus = bus

        if self.dev != None:
            self.__handle_open()
        else:
            raise Exception("No interface found")

        # Tracking the command operating mode (None or AirCapture Mode)
        self.__cmdmode = RZ_CMD_MODE_NONE

        # Tracking if the RZ_CMD_OPEN_STREAM parameter is set for packet reception
        self.__stream_open = False

        # Capabilities list
        self.capabilities = KBCapabilities()
        self.__set_capabilities()
Beispiel #9
0
 def __init__(self, serialpath):
     '''
     Instantiates the KillerBee class for Silabs BEEHIVE
     @param serialpath:  /dev/ttyACM* type serial port identifier
     @return: None
     @rtype: None
     '''
     self._channel = None
     self._page = 0
     self.handle = None
     self.dev = serialpath
     self.date = None
     self.lon, self.lat, self.alt = (None, None, None)
     self.__stream_open = False
     self.handle = serial.Serial(port=self.dev, baudrate=115200, \
                                 timeout=.02, bytesize=8, parity='N', stopbits=1, xonxoff=0)
     self.capabilities = KBCapabilities()
     self.__set_capabilities()
Beispiel #10
0
    def __init__(self, dev):
        '''
	    Instantiates the KillerBee class for Zigduino running GoodFET firmware.
	    @type dev:   String
	    @param dev:  Serial device identifier (ex /dev/ttyUSB0)
	    @return: None
	    @rtype: None
	    '''
        self._channel = None
        self.handle = None
        self.dev = dev
        self.handle = GoodFETatmel128rfa1()
        self.handle.serInit(port=self.dev)
        self.handle.setup()

        self.__stream_open = False
        self.capabilities = KBCapabilities()
        self.__set_capabilities()
Beispiel #11
0
    def __init__(self, dev):
        '''
        Instantiates the KillerBee class for our TelosB/TmoteSky running GoodFET firmware.
        @type dev:   String
        @param dev:  Serial device identifier (ex /dev/ttyUSB0)
        @return: None
        @rtype: None
        '''
        self._channel = None
        self.handle = None
        self.dev = dev

        os.environ["board"] = "telosb" #set enviroment variable for GoodFET code to use
        self.handle = GoodFETCCSPI()
        self.handle.serInit(port=self.dev)
        self.handle.setup()

        self.__stream_open = False
        self.capabilities = KBCapabilities()
        self.__set_capabilities()
Beispiel #12
0
    def __init__(self,
                 dev=DEFAULT_IP,
                 recvport=DEFAULT_UDP,
                 recvip=DEFAULT_GW):
        '''
        Instantiates the KillerBee class for the Sewio Sniffer.
        @type dev:   String
        @param dev:  IP address (ex 10.10.10.2)
        @type recvport: Integer
        @param recvport: UDP port to listen for sniffed packets on.
        @type recvip: String
        @param recvip: IP address of the host, where the sniffer will send sniffed packets to.
        @return: None
        @rtype: None
        '''
        self._channel = None
        self._modulation = 0  #unknown, will be set by change channel currently
        self.handle = None
        self.dev = dev

        #TODO The receive port and receive IP address are currently not
        # obtained from or verified against the Sewio sniffer, nor are they
        # used to change the settings on the sniffer.
        self.udp_recv_port = recvport
        self.udp_recv_ip = recvip

        self.__revision_num = getFirmwareVersion(self.dev)
        if self.__revision_num not in TESTED_FW_VERS:
            print(
                "Warning: Firmware revision {0} reported by the sniffer is not currently supported. Errors may occur and dev_sewio.py may need updating."
                .format(self.__revision_num))

        self.handle = socket.socket(AF_INET, SOCK_DGRAM)
        self.handle.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
        self.handle.bind((self.udp_recv_ip, self.udp_recv_port))

        self.__stream_open = False
        self.capabilities = KBCapabilities()
        self.__set_capabilities()

        self.sniffer = OpenSniffer('10.10.10.2')
Beispiel #13
0
 def __init__(self, serialpath):
     '''
     Instantiates the KillerBee class for our sketch running on ChibiArduino on Freakduino hardware.
     @param serialpath:  /dev/ttyUSB* type serial port identifier
     @return: None
     @rtype: None
     '''
     self._channel = None
     self.handle = None
     self.dev = serialpath
     self.date = None
     self.lon, self.lat, self.alt = (None, None, None)
     self.handle = serial.Serial(port=self.dev, baudrate=57600, \
                                 timeout=1, bytesize=8, parity='N', stopbits=1, xonxoff=0)
     self.capabilities = KBCapabilities()
     self.__set_capabilities()
    def __init__(self, dev):
	    '''
	    Instantiates the KillerBee class for Zigduino running GoodFET firmware.
	    @type dev:   String
	    @param dev:  Serial device identifier (ex /dev/ttyUSB0)
	    @return: None
	    @rtype: None
	    '''
	    self._channel = None
	    self.handle = None
	    self.dev = dev
	    self.handle = GoodFETatmel128rfa1()
	    self.handle.serInit(port=self.dev)
	    self.handle.setup()

	    self.__stream_open = False
	    self.capabilities = KBCapabilities()
	    self.__set_capabilities()
Beispiel #15
0
class SEWIO:
    def __init__(self,
                 dev=DEFAULT_IP,
                 recvport=DEFAULT_UDP,
                 recvip=DEFAULT_GW):
        '''
        Instantiates the KillerBee class for the Sewio Sniffer.
        @type dev:   String
        @param dev:  IP address (ex 10.10.10.2)
        @type recvport: Integer
        @param recvport: UDP port to listen for sniffed packets on.
        @type recvip: String
        @param recvip: IP address of the host, where the sniffer will send sniffed packets to.
        @return: None
        @rtype: None
        '''
        self._channel = None
        self._modulation = 0  #unknown, will be set by change channel currently
        self.handle = None
        self.dev = dev

        #TODO The receive port and receive IP address are currently not
        # obtained from or verified against the Sewio sniffer, nor are they
        # used to change the settings on the sniffer.
        self.udp_recv_port = recvport
        self.udp_recv_ip = recvip

        self.__revision_num = getFirmwareVersion(self.dev)
        if self.__revision_num not in TESTED_FW_VERS:
            print(
                "Warning: Firmware revision {0} reported by the sniffer is not currently supported. Errors may occur and dev_sewio.py may need updating."
                .format(self.__revision_num))

        self.handle = socket(AF_INET, SOCK_DGRAM)
        self.handle.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
        self.handle.bind((self.udp_recv_ip, self.udp_recv_port))

        self.__stream_open = False
        self.capabilities = KBCapabilities()
        self.__set_capabilities()

    def close(self):
        '''Actually close the receiving UDP socket.'''
        self.sniffer_off()  # turn sniffer off if it's currently running
        self.handle.close()  # socket.close()
        self.handle = None

    def check_capability(self, capab):
        return self.capabilities.check(capab)

    def get_capabilities(self):
        return self.capabilities.getlist()

    def __set_capabilities(self):
        '''
        Sets the capability information appropriate for the client and firmware version.
        @rtype: None
        @return: None
        '''
        self.capabilities.setcapab(KBCapabilities.SNIFF, True)
        self.capabilities.setcapab(KBCapabilities.SETCHAN, True)
        self.capabilities.setcapab(KBCapabilities.FREQ_2400, True)
        self.capabilities.setcapab(KBCapabilities.FREQ_900, True)
        return

    # KillerBee expects the driver to implement this function
    def get_dev_info(self):
        '''
        Returns device information in a list identifying the device.
        @rtype: List
        @return: List of 3 strings identifying device.
        '''
        return [
            self.dev, "Sewio Open-Sniffer v{0}".format(self.__revision_num),
            getMacAddr(self.dev)
        ]

    def __make_rest_call(self, path, fetch=True):
        '''
        Wrapper to the sniffer's RESTful services.
        Reports URL/HTTP errors as KBInterfaceErrors.
        @rtype: If fetch==True, returns a String of the page. Otherwise, it
            returns True if an HTTP 200 code was received.
        '''
        try:
            html = urllib2.urlopen("http://{0}/{1}".format(self.dev, path))
            if fetch:
                return html.read()
            else:
                return (html.getcode() == 200)
        except Exception as e:
            raise KBInterfaceError(
                "Unable to preform a call to {0}/{1} (error: {2}).".format(
                    self.dev, path, e))

    def __sniffer_status(self):
        '''
        Because the firmware accepts only toggle commands for sniffer on/off,
        we need to check what state it's in before taking action. It's also
        useful to make sure our command worked.
        @rtype: Boolean
        '''
        html = self.__make_rest_call('')
        # Yup, we're going to have to steal the status out of a JavaScript variable
        res = re.search(r'<!--#pindex-->([A-Z]+),', html)
        if res is None:
            raise KBInterfaceError(
                "Unable to parse the sniffer's current status.")
        # RUNNING means it's sniffing, STOPPED means it's not.
        return (res.group(1) == "RUNNING")

    def __sync_status(self):
        '''
        This updates the standard self.__stream_open variable based on the 
        status as reported from asking the remote sniffer.
        '''
        self.__stream_open = self.__sniffer_status()

    def __sniffer_channel(self):
        '''
        Because the firmware accepts only toggle commands for sniffer on/off,
        we need to check what state it's in before taking action. It's also
        useful to make sure our command worked.
        @rtype: Boolean
        '''
        html = self.__make_rest_call('')
        # Yup, we're going to have to steal the channel number out of a JavaScript variable
        #  var values = removeSSItag('<!--#pindex-->RUNNING,00:1a:b6:00:0a:a4,10.10.10.2,0,High,0x0000,OFF,0,0').split(",");
        res = re.search(r'<!--#pindex-->[A-Z]+,[0-9a-f:]+,[0-9.]+,([0-9]+),',
                        html)
        if res is None:
            raise KBInterfaceError(
                "Unable to parse the sniffer's current channel.")
        return int(res.group(1))

    # KillerBee expects the driver to implement this function
    def sniffer_on(self, channel=None):
        '''
        Turns the sniffer on such that pnext() will start returning observed
        data.
        @type channel: Integer
        @param channel: Sets the channel, optional
        @rtype: None
        '''
        self.capabilities.require(KBCapabilities.SNIFF)

        # Because the Sewio just toggles, we have to only hit the page
        # if we need to go from off to on state.
        self.__sync_status()
        if self.__stream_open == False:
            if channel != None:
                self.set_channel(channel)

            if not self.__make_rest_call('status.cgi?p=2', fetch=False):
                raise KBInterfaceError(
                    "Error instructing sniffer to start capture.")

            #This makes sure the change actually happened
            self.__sync_status()
            if not self.__stream_open:
                raise KBInterfaceError("Sniffer did not turn on capture.")

    # KillerBee expects the driver to implement this function
    def sniffer_off(self):
        '''
        Turns the sniffer off.
        @rtype: None
        '''
        # Because the Sewio just toggles, we have to only hit the page
        # if we need to go from on to off state.
        self.__sync_status()
        if self.__stream_open == True:
            if not self.__make_rest_call('status.cgi?p=2', fetch=False):
                raise KBInterfaceError(
                    "Error instructing sniffer to stop capture.")

            #This makes sure the change actually happened
            self.__sync_status()
            if self.__stream_open:
                raise KBInterfaceError("Sniffer did not turn off capture.")

    @staticmethod
    def __get_default_modulation(channel):
        '''
        Return the Sewio-specific integer representing the modulation which
        should be choosen to be IEEE 802.15.4 complinating for a given channel 
        number.
        Captured values from sniffing Sewio web interface, unsure why these
        are done as such.
        Available modulations are listed at:
        http://www.sewio.net/open-sniffer/develop/http-rest-interface/
        @rtype: Integer, or None if unable to determine modulation
        '''
        if channel >= 11 or channel <= 26: return '0'  #O-QPSK 250 kb/s 2.4GHz
        elif channel >= 1 or channel <= 10: return 'c'  #O-QPSK 250 kb/s 915MHz
        elif channel >= 128 or channel <= 131:
            return '1c'  #O-QPSK 250 kb/s 760MHz
        elif channel == 0:
            return '0'  #O-QPSK 100 kb/s 868MHz
        else:
            return None  #Error status

    # KillerBee expects the driver to implement this function
    def set_channel(self, channel):
        '''
        Sets the radio interface to the specified channel, and the matching modulation setting.
        @type channel: Integer
        @param channel: Sets the channel, optional
        @rtype: None
        '''
        self.capabilities.require(KBCapabilities.SETCHAN)

        if self.capabilities.is_valid_channel(channel):
            # We only need to update our channel if it doesn't match the currently reported one.
            curChannel = self.__sniffer_channel()
            if channel != curChannel:
                self.modulation = self.__get_default_modulation(channel)
                print("Setting to channel {0}, modulation {1}.".format(
                    channel, self.modulation))
                # Examples captured in fw v0.5 sniffing:
                #   channel 6, 250 compliant: http://10.10.10.2/settings.cgi?chn=6&modul=c&rxsens=0
                #   channel 12, 250 compliant: http://10.10.10.2/settings.cgi?chn=12&modul=0&rxsens=0
                #   chinese 0, 780 MHz, 250 compliant: http://10.10.10.2/settings.cgi?chn=128&modul=1c&rxsens=0
                #   chinese 3, 786 MHz, 250 compliant: http://10.10.10.2/settings.cgi?chn=131&modul=1c&rxsens=0
                #rxsens 0 is normal, 3 is high sensitivity to receive at
                self.__make_rest_call(
                    "settings.cgi?chn={0}&modul={1}&rxsens=3".format(
                        channel, self.modulation),
                    fetch=False)
                self._channel = self.__sniffer_channel()
            else:
                self._channel = curChannel
        else:
            raise Exception(
                'Invalid channel number ({0}) was provided'.format(channel))

    # KillerBee expects the driver to implement this function
    def inject(self, packet, channel=None, count=1, delay=0):
        '''
        Not implemented.
        '''
        self.capabilities.require(KBCapabilities.INJECT)

    @staticmethod
    def __parse_zep_v2(data):
        '''
        Parse the packet from the ZigBee encapsulation protocol version 2/3 and 
        return the fields desired for usage by pnext().
        There is support here for some oddities specific to the Sewio 
        implementation of ZEP and the packet, such as CC24xx format FCS 
        headers being expected.
        
        The ZEP protocol parsing is mainly based on Wireshark source at:
        http://anonsvn.wireshark.org/wireshark/trunk/epan/dissectors/packet-zep.c
        * ZEP v2 Header will have the following format (if type=1/Data):
        *  |Preamble|Version| Type |Channel ID|Device ID|CRC/LQI Mode|LQI Val|NTP Timestamp|Sequence#|Reserved|Length|
        *  |2 bytes |1 byte |1 byte|  1 byte  | 2 bytes |   1 byte   |1 byte |   8 bytes   | 4 bytes |10 bytes|1 byte|
        * ZEP v2 Header will have the following format (if type=2/Ack):
        *  |Preamble|Version| Type |Sequence#|
        *  |2 bytes |1 byte |1 byte| 4 bytes |
        #define ZEP_PREAMBLE        "EX"
        #define ZEP_V2_HEADER_LEN   32
        #define ZEP_V2_ACK_LEN      8
        #define ZEP_V2_TYPE_DATA    1
        #define ZEP_V2_TYPE_ACK     2
        #define ZEP_LENGTH_MASK     0x7F
        '''
        # Unpack constant part of ZEPv2
        (preamble, version, zeptype) = unpack('<HBB', data[:4])
        if preamble != 22597 or version < 2:  # 'EX'==22597, and v3 is compat with v2 (I think??)
            raise Exception(
                "Can not parse provided data as ZEP due to incorrect preamble or unsupported version."
            )
        if zeptype == 1:  #data
            (ch, devid, crcmode, lqival, ntpsec, ntpnsec, seqnum,
             length) = unpack(">BHBBIII10xB", data[4:32])
            #print "Data ZEP:", ch, devid, crcmode, lqival, ntpsec, ntpnsec, seqnum, length
            #We could convert the NTP timestamp received to system time, but the
            # Sewio firmware uses "relative timestamping" where it begins at 0 each time
            # the sniffer is started. Thus, it isn't that useful to us, so we just add the
            # time the packet is received at the host instead.
            #print "\tConverted time:", ntp_to_system_time(ntpsec, ntpnsec)
            recdtime = datetime.utcnow()
            #The LQI comes in ZEP, but the RSSI comes in the first byte of the FCS,
            # if the FCS was correct. If the byte is 0xb1, Wireshark appears to do 0xb1-256 = -79 dBm.
            # It appears that if CRC/LQI Mode field == 1, then checksum was bad, so the RSSI isn't
            # available, as the CRC is left in the packet. If it == 0, then the first byte of FCS is the RSSI.
            # From Wireshark:
            #define IEEE802154_CC24xx_CRC_OK            0x8000
            #define IEEE802154_CC24xx_RSSI              0x00FF
            frame = data[32:]
            # A length vs len(frame) check is not used here but is an
            #  additional way to verify that all is good (length == len(frame)).
            if crcmode == 0:
                validcrc = ((ord(data[-1]) & 0x80) == 0x80)
                rssi = ord(data[-2])
                # We have to trust the sniffer that the FCS was OK, so we compute
                #  what a good FCS should be and patch it back into the packet.
                frame = frame[:-2] + makeFCS(frame[:-2])
            else:
                validcrc = False
                rssi = None
            return (frame, ch, validcrc, rssi, lqival, recdtime)
        elif zeptype == 2:  #ack
            frame = data[8:]
            (seqnum) = unpack(">I", data[4:8])
            recdtime = datetime.utcnow()
            validcrc = (frame[-2:] == makeFCS(frame[:-2]))
            return (frame, None, validcrc, None, None, recdtime)
        return None

    # KillerBee expects the driver to implement this function
    def pnext(self, timeout=100):
        '''
        Returns a dictionary containing packet data, else None.
        @type timeout: Integer
        @param timeout: Timeout to wait for packet reception in usec
        @rtype: List
        @return: Returns None is timeout expires and no packet received.  When a packet is received, a dictionary is returned with the keys bytes (string of packet bytes), validcrc (boolean if a vaid CRC), rssi (unscaled RSSI), and location (may be set to None). For backwards compatibility, keys for 0,1,2 are provided such that it can be treated as if a list is returned, in the form [ String: packet contents | Bool: Valid CRC | Int: Unscaled RSSI ]
        '''
        if self.__stream_open == False:
            self.sniffer_on()  #start sniffing

        # Use socket timeouts to implement the timeout
        self.handle.settimeout(timeout / 1000000.0)  # it takes seconds

        frame = None
        donetime = datetime.utcnow() + timedelta(microseconds=timeout)
        while True:
            try:
                data, addr = self.handle.recvfrom(1024)
            except error_timeout:
                return None
            # Ensure it's data coming from the right place, for now we just
            #  check the sending IP address. Ex: addr = ('10.10.10.2', 17754)
            if addr[0] != self.dev:
                continue
            # Dissect the UDP packet
            (frame, ch, validcrc, rssi, lqival,
             recdtime) = self.__parse_zep_v2(data)
            print "Valid CRC", validcrc, "LQI", lqival, "RSSI", rssi
            if frame == None or (ch is not None and ch != self._channel):
                #TODO this maybe should be an error condition, instead of ignored?
                print("ZEP parsing issue (bytes length={0}, channel={1}).".
                      format(len(frame) if frame is not None else None, ch))
                continue
            break

        if frame is None:
            return None

        #Return in a nicer dictionary format, so we don't have to reference by number indicies.
        #Note that 0,1,2 indicies inserted twice for backwards compatibility.
        result = {
            0: frame,
            1: validcrc,
            2: rssi,
            'bytes': frame,
            'validcrc': validcrc,
            'rssi': rssi,
            'dbm': None,
            'location': None,
            'datetime': recdtime
        }
        if rssi is not None:
            # Per note from Sewino team regarding encoding of RSSI value as 2's complement dBm values
            if rssi > 127: result['dbm'] = rssi - 256
            else: result['dbm'] = rssi
        return result

    def jammer_on(self, channel=None):
        '''
        Not yet implemented.
        @type channel: Integer
        @param channel: Sets the channel, optional
        @rtype: None
        '''
        self.capabilities.require(KBCapabilities.PHYJAM)

    def jammer_off(self, channel=None):
        '''
        Not yet implemented.
        @return: None
        @rtype: None
        '''
        self.capabilities.require(KBCapabilities.PHYJAM)
Beispiel #16
0
class RZUSBSTICK:
    def __init__(self, dev, bus):
        #TODO deprecate bus param, and dev becomes a usb.core.Device object, not a string in pyUSB 1.x use
        '''
        Instantiates the KillerBee class for the RZUSBSTICK hardware.

        @type dev:   TODO
        @param dev:  USB device identifier
        @type bus:   TODO
        @param bus:  Identifies the USB bus the device is on
        @return: None
        @rtype: None
        '''
        self._channel = None
        self.handle = None
        self.dev = dev
        self.__bus = bus

        if self.dev != None:
            self.__handle_open()
        else:
            raise Exception('No interface found')

        # Tracking the command operating mode (None or AirCapture Mode)
        self.__cmdmode = RZ_CMD_MODE_NONE

        # Tracking if the RZ_CMD_OPEN_STREAM parameter is set for packet reception
        self.__stream_open = False

        # Capabilities list
        self.capabilities = KBCapabilities()
        self.__set_capabilities()

    def __del__(self):
        try:
            self.close()
        except:
            pass

    def __handle_open(self):
        '''
        Opens the device identified as self.dev, populating self.handle.
        An RZUSBSTICK has a hierarchy of:
        \_ Config value: 1
          \_ Interface number 0, with alternate setting 0
             |- Endpoint 132 for responses
             |- Endpoint 2   for control
             |- Endpoint 129 for packets
        '''
        if USBVER == 0:
            self.__handle_open_v0x()
        else:  #pyUSB 1.x
            self.__dev_setup_v1x()

    def __dev_setup_v1x(self):
        # See http://pyusb.sourceforge.net/docs/1.0/tutorial.html for reference
        try:
            self.dev.set_configuration()
            self.dev.reset()
        except usb.core.USBError:
            raise Exception(
                "Unable to open device. " +
                "Ensure the device is free and plugged-in. You may need sudo.")

        #self.dev.set_configuration(1) # could also provide no config number
        #self.dev.set_interface_altsetting(interface = 0, alternate_setting = 0)
        #TODO alternative setup code:
        #self.dev.set_configuration()
        #cfg = self.dev.get_active_configuration()
        #interface_number = cfg[(0,0)].bInterfaceNumber
        #alternate_settting = usb.control.get_interface(interface_number)
        #intf = usb.util.find_descriptor(cfg, bInterfaceNumber=interface_number, bAlternateSetting=alternate_setting)
        #self.handle = usb.util.find_descriptor(intf, custom_match=lambda e: (e.bEndpointAddress == TODO_TARGET_ENDPOINT))

    def __handle_open_v0x(self):
        try:
            config = self.dev.configurations[0]
            intf = config.interfaces[0]
            alt = intf[0]
            self.handle = self.dev.open()
            self.handle.reset()
            self.handle.setConfiguration(config)
            self.handle.claimInterface(alt)
            self.handle.setAltInterface(alt)
        except:
            raise Exception(
                "Unable to open device. " +
                "Ensure the device is free and plugged-in. You may need sudo.")

    def close(self):
        '''
        Closes the device handle.  To be re-used, class should be re-instantiated.
        @return: None
        @rtype: None
        '''
        if USBVER == 0:
            self.handle.releaseInterface()
        else:
            usb.util.release_interface(self.dev, 0)

    # KillerBee implements these, maybe it shouldn't and instead leave it to the driver as needed.
    def check_capability(self, capab):
        return self.capabilities.check(capab)

    def get_capabilities(self):
        return self.capabilities.getlist()

    def __set_capabilities(self):
        '''
        Sets the capability information for RZUSB devices.
        @rtype: None
        @return: None
        '''
        # Examine the product string for this device, setting the capability information appropriately.
        prod = self.get_dev_info()[
            1]  # returns a list, with second element being product string

        if prod == "RZUSBSTICK":
            self.capabilities.setcapab(KBCapabilities.FREQ_2400, True)
            self.capabilities.setcapab(KBCapabilities.SNIFF, True)
            self.capabilities.setcapab(KBCapabilities.SETCHAN, True)
            self.capabilities.setcapab(KBCapabilities.BOOT, True)
        elif prod == "KILLERB001":
            self.capabilities.setcapab(KBCapabilities.FREQ_2400, True)
            self.capabilities.setcapab(KBCapabilities.SNIFF, True)
            self.capabilities.setcapab(KBCapabilities.SETCHAN, True)
            self.capabilities.setcapab(KBCapabilities.INJECT, True)
            self.capabilities.setcapab(KBCapabilities.PHYJAM, True)
        elif prod == "KILLERB006":
            self.capabilities.setcapab(KBCapabilities.FREQ_2400, True)
            self.capabilities.setcapab(KBCapabilities.SNIFF, True)
            self.capabilities.setcapab(KBCapabilities.SETCHAN, True)
            self.capabilities.setcapab(KBCapabilities.INJECT, True)
            self.capabilities.setcapab(KBCapabilities.PHYJAM, True)
            self.capabilities.setcapab(KBCapabilities.BOOT, True)
        elif prod == "KILLERB00T":
            self.capabilities.setcapab(KBCapabilities.BOOT, True)
        else:
            pass

    def get_dev_info(self):
        '''
        Returns device information in a list identifying the device identifier,
        product string and serial number in a list of strings.
        @rtype: List
        @return: List of 3 strings identifying device.
        '''
        if USBVER == 0:
            return [
                ''.join([self.__bus.dirname, ":", self.dev.filename]),
                self.dev.open().getString(self.dev.iProduct, 50),
                self.dev.open().getString(self.dev.iSerialNumber, 12)
            ]
        elif USBVER == 1:
            return ["{0}:{1}".format(self.dev.bus, self.dev.address),         \
                    usb.util.get_string(self.dev, self.dev.iProduct),     \
                    usb.util.get_string(self.dev, self.dev.iSerialNumber) ]

    def __usb_read(self):
        '''
        Read data from the USB device opened as self.handle.
        
        @rtype: String
        @param data: The data received from the USB endpoint
        '''
        response = None
        if USBVER == 0:  # TODO: UNTESTED!!!!
            try:
                response = self.handle.bulkRead(RZ_USB_RESPONSE_EP, 1)[0]
            except usb.USBError, e:
                if e.args != ('No error', ):  # http://bugs.debian.org/476796
                    raise e
        else:  #pyUSB 1.x
Beispiel #17
0
class ZIGDUINO:
    def __init__(self, dev):
        '''
	    Instantiates the KillerBee class for Zigduino running GoodFET firmware.
	    @type dev:   String
	    @param dev:  Serial device identifier (ex /dev/ttyUSB0)
	    @return: None
	    @rtype: None
	    '''
        self._channel = None
        self.handle = None
        self.dev = dev
        self.handle = GoodFETatmel128rfa1()
        self.handle.serInit(port=self.dev)
        self.handle.setup()

        self.__stream_open = False
        self.capabilities = KBCapabilities()
        self.__set_capabilities()

    def close(self):
        self.handle.serClose()
        self.handle = None

    def check_capability(self, capab):
        return self.capabilities.check(capab)

    def get_capabilities(self):
        return self.capabilities.getlist()

    def __set_capabilities(self):
        '''
        Sets the capability information appropriate for GoodFETAVR client and firmware.
        @rtype: None
        @return: None
        '''
        self.capabilities.setcapab(KBCapabilities.FREQ_2400, True)
        self.capabilities.setcapab(KBCapabilities.SNIFF, True)
        self.capabilities.setcapab(KBCapabilities.SETCHAN, True)
        self.capabilities.setcapab(KBCapabilities.INJECT, True)
        #self.capabilities.setcapab(KBCapabilities.PHYJAM_REFLEX, True)
        self.capabilities.setcapab(KBCapabilities.SET_SYNC, True)

    # KillerBee expects the driver to implement this function
    def get_dev_info(self):
        '''
	    Returns device information in a list identifying the device.
	    @rtype: List
	    @return: List of 3 strings identifying device.
	    '''
        return [self.dev, "Zigduino", ""]

    # KillerBee expects the driver to implement this function
    def sniffer_on(self, channel=None):
        '''
	    Turns the sniffer on such that pnext() will start returning observed
	    data.  Will set the command mode to Air Capture if it is not already
	    set.
	    @type channel: Integer
	    @param channel: Sets the channel, optional
	    @rtype: None
	    '''
        self.capabilities.require(KBCapabilities.SNIFF)

        if channel != None:
            self.set_channel(channel)

        #print "Sniffer started (listening as %010x on %i MHz)" % (self.handle.RF_getsmac(), self.handle.RF_getfreq()/10**6);

        self.__stream_open = True

    # KillerBee expects the driver to implement this function
    def sniffer_off(self):
        '''
	    Turns the sniffer off, freeing the hardware for other functions.  It is
	    not necessary to call this function before closing the interface with
	    close().
	    @rtype: None
	    '''
        #TODO actually have firmware stop sending us packets!
        self.__stream_open = False

    # KillerBee expects the driver to implement this function
    def set_channel(self, channel):
        '''
	    Sets the radio interface to the specifid channel (limited to 2.4 GHz channels 11-26)
	    @type channel: Integer
	    @param channel: Sets the channel, optional
	    @rtype: None
	    '''
        self.capabilities.require(KBCapabilities.SETCHAN)

        if channel >= 11 or channel <= 26:
            self._channel = channel
            self.handle.RF_setchannel(channel)
        else:
            raise Exception('Invalid channel')

    # KillerBee expects the driver to implement this function
    def inject(self, packet, channel=None, count=1, delay=0):
        '''
        Injects the specified packet contents.
        @type packet: String
        @param packet: Packet contents to transmit, without FCS.
        @type channel: Integer
        @param channel: Sets the channel, optional
        @type count: Integer
        @param count: Transmits a specified number of frames, def=1
        @type delay: Float
        @param delay: Delay between each frame, def=1
        @rtype: None
        '''
        self.capabilities.require(KBCapabilities.INJECT)

        if len(packet) < 1:
            raise Exception('Empty packet')
        if len(packet) > 125:  # 127 - 2 to accommodate FCS
            raise Exception('Packet too long')

        if channel != None:
            self.set_channel(channel)
        self.handle.RF_autocrc(1)  #let the radio add the CRC
        for pnum in range(0, count):
            gfready = [
                ord(x) for x in packet
            ]  #convert packet string to GoodFET expected integer format
            gfready.insert(0,
                           len(gfready) +
                           2)  #add a length that leaves room for CRC
            self.handle.RF_txpacket(gfready)
            time.sleep(delay)

    # KillerBee expects the driver to implement this function
    def pnext(self, timeout=100):
        '''
        Returns a dictionary containing packet data, else None.
        @type timeout: Integer
        @param timeout: Timeout to wait for packet reception in usec
        @rtype: List
        @return: Returns None is timeout expires and no packet received.  When a packet is received, a dictionary is returned with the keys bytes (string of packet bytes), validcrc (boolean if a vaid CRC), rssi (unscaled RSSI), and location (may be set to None). For backwards compatibility, keys for 0,1,2 are provided such that it can be treated as if a list is returned, in the form [ String: packet contents | Bool: Valid CRC | Int: Unscaled RSSI ]
        '''
        if self.__stream_open == False:
            self.sniffer_on()  #start sniffing

        packet = None
        start = datetime.utcnow()
        while (packet is None and
               (start + timedelta(microseconds=timeout) > datetime.utcnow())):
            packet = self.handle.RF_rxpacket()
        if packet is None:
            return None
        rssi = self.handle.RF_getrssi()  #TODO calibrate
        frame = packet
        if frame[-2:] == makeFCS(frame[:-2]): validcrc = True
        else: validcrc = False
        #Return in a nicer dictionary format, so we don't have to reference by number indicies.
        #Note that 0,1,2 indicies inserted twice for backwards compatibility.
        result = {
            0: frame,
            1: validcrc,
            2: rssi,
            'bytes': frame,
            'validcrc': validcrc,
            'rssi': rssi,
            'location': None
        }
        result[
            'dbm'] = rssi - 45  #TODO tune specifically to the Tmote platform (does ext antenna need to different?)
        result['datetime'] = datetime.utcnow()
        return result

    def jammer_on(self, channel=None):
        '''
	    Not yet implemented.
	    @type channel: Integer
	    @param channel: Sets the channel, optional
	    @rtype: None
	    '''
        raise Exception('Not yet implemented')

    def set_sync(self, sync=0xA7):
        '''Set the register controlling the 802.15.4 PHY sync byte.'''
        self.capabilities.require(KBCapabilities.SET_SYNC)
        if (sync >> 8) > 0:
            raise Exception("Sync word (%x) must be 1 byte." % sync)
        if (sync & 0x0F) == 0:
            raise Exception(
                "Least-significant nybble in sync (%x) cannot be 0." % sync)
        return self.handle.poke(ATMEL_REG_SYNC, sync)

    def jammer_off(self, channel=None):
        '''
	    Not yet implemented.
	    @return: None
	    @rtype: None
	    '''
        #TODO implement
        raise Exception('Not yet implemented')
Beispiel #18
0
class CC253x:
    USB_DIR_OUT        = 0x40
    USB_DIR_IN         = 0xC0
    USB_POWER_ON       = 0xC5
    USB_POWER_STATUS   = 0xC6
    USB_XFER_START     = 0xD0
    USB_XFER_STOP      = 0xD1
    USB_XFER_CHAN      = 0xD2
    USB_CC2530_DATA_EP = 0x82
    USB_CC2531_DATA_EP = 0x83

    VARIANT_CC2530 = 0
    VARIANT_CC2531 = 1

    def __init__(self, dev, bus, variant):
        #TODO deprecate bus param, and dev becomes a usb.core.Device object, not a string in pyUSB 1.x use
        """
        Instantiates the KillerBee class for Zigduino running GoodFET firmware.
        @type dev:   String
        @param dev:  PyUSB device
        @return: None
        @rtype: None
        """
        if variant == CC253x.VARIANT_CC2530:
            self._data_ep = CC253x.USB_CC2530_DATA_EP
        else:
            self._data_ep = CC253x.USB_CC2531_DATA_EP
        self._channel = None
        self._page = 0
        self.dev = dev

        self.__stream_open = False
        self.capabilities = KBCapabilities()
        self.__set_capabilities()

        # Set default configuration
        self.dev.set_configuration()

        # get name from USB descriptor
        self.name = usb.util.get_string(self.dev, self.dev.iProduct)

        # Get wMaxPacketSize from the data endpoint
        for cfg in self.dev:
            for intf in cfg:
                for ep in intf:
                    if ep.bEndpointAddress == self._data_ep:
                        self._maxPacketSize = ep.wMaxPacketSize


    def close(self):
        if self.__stream_open == True:
            self.sniffer_off()
        pass

    def check_capability(self, capab):
        return self.capabilities.check(capab)

    def get_capabilities(self):
        return self.capabilities.getlist()

    def __set_capabilities(self):
        """
        Sets the capability information appropriate for CC253x.
        @rtype: None
        @return: None
        """
        self.capabilities.setcapab(KBCapabilities.FREQ_2400, True)
        self.capabilities.setcapab(KBCapabilities.SNIFF, True)
        self.capabilities.setcapab(KBCapabilities.SETCHAN, True)

    # KillerBee expects the driver to implement this function
    def get_dev_info(self):
        """
        Returns device information in a list identifying the device.
        @rtype: List
        @return: List of 3 strings identifying device.
        """
        # TODO Determine if there is a way to get a unique ID from the device
        return [self.name, "CC253x", ""]

    # KillerBee expects the driver to implement this function
    def sniffer_on(self, channel=None, page=0):
        """
        Turns the sniffer on such that pnext() will start returning observed data.
        Will set the command mode to Air Capture if it is not already set.
        @type channel: Integer
        @param channel: Sets the channel, optional
        @type page: Integer
        @param page: Sets the subghz page, not supported on this device
        @rtype: None
        """
        self.capabilities.require(KBCapabilities.SNIFF)

        if channel != None:
            self.set_channel(channel, page)

        # Enable power in 802.15.4 radio
        self.dev.ctrl_transfer(CC253x.USB_DIR_OUT, CC253x.USB_POWER_ON, wIndex = 4)
        while True:
            # check if powered up
            power_status = self.dev.ctrl_transfer(CC253x.USB_DIR_IN, CC253x.USB_POWER_STATUS, data_or_wLength = 1)
            if power_status[0] == 4:
                break
            time.sleep(0.1)

        # Set the channel in the hardware now the radio is powered up
        self._do_set_channel()

        # Start capture
        self.dev.ctrl_transfer(CC253x.USB_DIR_OUT, CC253x.USB_XFER_START)
        self.__stream_open = True

    # KillerBee expects the driver to implement this function
    def sniffer_off(self):
        """
        Turns the sniffer off, freeing the hardware for other functions.  It is
        not necessary to call this function before closing the interface with
        close().
        @rtype: None
        """
        if self.__stream_open == True:
            # TODO Here, and in other places, add error handling for ctrl_transfer failure
            self.dev.ctrl_transfer(CC253x.USB_DIR_OUT, CC253x.USB_XFER_STOP)
            self.__stream_open = False

    def _do_set_channel(self):
        # Internal function to unconditionally set the channel in the hardware
        # For some CC253x dongles, this can only be done when the radio is powered up.
        self.dev.ctrl_transfer(CC253x.USB_DIR_OUT, CC253x.USB_XFER_CHAN, wIndex = 0, data_or_wLength = [self._channel])
        self.dev.ctrl_transfer(CC253x.USB_DIR_OUT, CC253x.USB_XFER_CHAN, wIndex = 1, data_or_wLength = [0x00])

    # KillerBee expects the driver to implement this function
    def set_channel(self, channel, page=0):
        """
        Sets the radio interface to the specifid channel (limited to 2.4 GHz channels 11-26)
        @type channel: Integer
        @param channel: Sets the channel, optional
        @type page: Integer
        @param page: Sets the subghz page, not supported on this device
        @rtype: None
        """
        self.capabilities.require(KBCapabilities.SETCHAN)

        if channel >= 11 or channel <= 26:
            self._channel = channel
            if self.__stream_open == True:
                self.dev.ctrl_transfer(CC253x.USB_DIR_OUT, CC253x.USB_XFER_STOP)
                self._do_set_channel()
                self.dev.ctrl_transfer(CC253x.USB_DIR_OUT, CC253x.USB_XFER_START)
        else:
            raise Exception('Invalid channel')
        if page:
            raise Exception('SubGHz not supported')

    # KillerBee expects the driver to implement this function
    def inject(self, packet, channel=None, count=1, delay=0, page=0):
        """
        Injects the specified packet contents.
        @type packet: String
        @param packet: Packet contents to transmit, without FCS.
        @type channel: Integer
        @param channel: Sets the channel, optional
        @type page: Integer
        @param page: Sets the subghz page, not supported on this device
        @type count: Integer
        @param count: Transmits a specified number of frames, def=1
        @type delay: Float
        @param delay: Delay between each frame, def=1
        @rtype: None
        """
        raise Exception('Not yet implemented')

    # KillerBee expects the driver to implement this function
    def pnext(self, timeout=100):
        """
        Returns a dictionary containing packet data, else None.
        @type timeout: Integer
        @param timeout: Timeout to wait for packet reception in usec
        @rtype: List
        @return: Returns None is timeout expires and no packet received.  When a packet is received, a dictionary is returned with the keys bytes (string of packet bytes), validcrc (boolean if a vaid CRC), rssi (unscaled RSSI), and location (may be set to None). For backwards compatibility, keys for 0,1,2 are provided such that it can be treated as if a list is returned, in the form [ String: packet contents | Bool: Valid CRC | Int: Unscaled RSSI ]
        """
        if self.__stream_open == False:
            self.sniffer_on() #start sniffing

        ret = None
        framedata = []
        explen = 0 # expected remaining packet length
        while True:
            pdata = None
            try:

                pdata = self.dev.read(self._data_ep, self._maxPacketSize, timeout=timeout)
            except usb.core.USBError as e:
                if e.errno != 110: #Operation timed out
                    print("Error args: {}".format(e.args))
                    raise e
                    #TODO error handling enhancements for USB 1.0
                else:
                    return None

            # Accumulate in 'framedata' until we have an entire frame
            for byteval in pdata:
                framedata.append(struct.pack("B", byteval))

            if len(pdata) < 64:

                if len(pdata) < 2:
                    #print "ERROR: Very short frame"
                    return None

                framelen = ord(framedata[1])
                if len(framedata) - 3 != framelen:
                    #print "ERROR: Bad frame length: expected {0}, got {1}".format(framelen, len(framedata))
                    return None

                if framedata[0] != '\x00':
                    #print "Not a capture frame:", framedata
                    return None

                payloadlen = ord(framedata[7]) # Includes TI format FCS
                payload = framedata[8:]

                if len(payload) != payloadlen:
                    # TODO: Log "ERROR: Bad payload length"
                    return None

                # See TI Smart RF User Guide for usage of 'CC24XX' format FCS fields
                # in last two bytes of framedata. Note that we remove these before return of the frame.

                # RSSI is signed value, offset by 73 (see CC2530 data sheet for offset)
                rssi = struct.unpack("b", framedata[-2])[0] - 73

                fcsx = ord(framedata[-1])
                # validcrc is the bit 7 in fcsx
                validcrc  = (fcsx & 0x80) == 0x80
                # correlation value is bits 0-6 in fcsx
                correlation = fcsx & 0x7f

                ret = {1:validcrc, 2:rssi,
                        'validcrc':validcrc, 'rssi':rssi, 'lqi':correlation,
                        'dbm':rssi,'datetime':datetime.utcnow()}

                # Convert the framedata to a string for the return value, and replace the TI FCS with a real FCS
                # if the radio told us that the FCS had passed validation.
                if validcrc:
                    ret[0] = ''.join(payload[:-2]) + makeFCS(payload[:-2])
                else:
                    ret[0] = ''.join(payload)
                ret['bytes'] = ret[0]
                return ret


    def jammer_on(self, channel=None, page=0):
        """
        Not yet implemented.
        @type channel: Integer
        @param channel: Sets the channel, optional
        @type page: Integer
        @param page: Sets the subghz page, not supported on this device
        @rtype: None
        """
        raise Exception('Not yet implemented')

    def set_sync(self, sync=0xA7):
        """
        Set the register controlling the 802.15.4 PHY sync byte.
        """
        raise Exception('Not yet implemented')

    def jammer_off(self, channel=None, page=0):
        """
        Not yet implemented.
        @return: None
        @rtype: None
        """
        raise Exception('Not yet implemented')
Beispiel #19
0
class APIMOTE:
    def __init__(self, dev, revision=DEFAULT_REVISION):
        '''
        Instantiates the KillerBee class for the ApiMote platform running GoodFET firmware.
        @type dev:   String
        @param dev:  Serial device identifier (ex /dev/ttyUSB0)
        @type revision: Integer
        @param revision: The revision number for the ApiMote, which is used by 
            the called GoodFET libraries to properly communicate with
            and configure the hardware.
        @return: None
        @rtype: None
        '''
        self._channel = None
        self._page = 0
        self.handle = None
        self.dev = dev

        self.__revision_num = revision
        # Set enviroment variables for GoodFET code to use
        os.environ["platform"] = "apimote%d".format(self.__revision_num)
        os.environ["board"] = "apimote%d".format(self.__revision_num)
        self.handle = GoodFETCCSPI()
        self.handle.serInit(port=self.dev)
        self.handle.setup()
        # TODO can we verify here the revision number that was sent is correct?

        self.__stream_open = False
        self.capabilities = KBCapabilities()
        self.__set_capabilities()

    def close(self):
        self.handle.serClose()
        self.handle = None

    def check_capability(self, capab):
        return self.capabilities.check(capab)
    def get_capabilities(self):
        return self.capabilities.getlist()
    def __set_capabilities(self):
        '''
        Sets the capability information appropriate for GoodFETCCSPI client and firmware.
        @rtype: None
        @return: None
        '''
        self.capabilities.setcapab(KBCapabilities.FREQ_2400, True)
        self.capabilities.setcapab(KBCapabilities.SNIFF, True)
        self.capabilities.setcapab(KBCapabilities.SETCHAN, True)
        self.capabilities.setcapab(KBCapabilities.INJECT, True)
        self.capabilities.setcapab(KBCapabilities.PHYJAM_REFLEX, True)
        self.capabilities.setcapab(KBCapabilities.SET_SYNC, True)
        return

    # KillerBee expects the driver to implement this function
    def get_dev_info(self):
        '''
        Returns device information in a list identifying the device.
        @rtype: List
        @return: List of 3 strings identifying device.
        '''
        return [self.dev, "GoodFET Apimote v%d".format(self.__revision_num), ""]

    # KillerBee expects the driver to implement this function
    def sniffer_on(self, channel=None, page=0):
        '''
        Turns the sniffer on such that pnext() will start returning observed
        data.  Will set the command mode to Air Capture if it is not already
        set.
        @type channel: Integer
        @param channel: Sets the channel, optional
        @type page: Integer
        @param page: Sets the subghz page, not supported on this device
        @rtype: None
        '''
        self.capabilities.require(KBCapabilities.SNIFF)

        self.handle.RF_promiscuity(1)
        self.handle.RF_autocrc(0)

        if channel != None:
            self.set_channel(channel, page)
        
        self.handle.CC_RFST_RX()
        #print "Sniffer started (listening as %010x on %i MHz)" % (self.handle.RF_getsmac(), self.handle.RF_getfreq()/10**6);

        self.__stream_open = True

    # KillerBee expects the driver to implement this function
    def sniffer_off(self):
        '''
        Turns the sniffer off, freeing the hardware for other functions.  It is
        not necessary to call this function before closing the interface with
        close().
        @rtype: None
        '''
        #TODO actually have firmware stop sending us packets!
        self.__stream_open = False

    # KillerBee expects the driver to implement this function
    def set_channel(self, channel, page=0):
        '''
        Sets the radio interface to the specifid channel (limited to 2.4 GHz channels 11-26)
        @type channel: Integer
        @param channel: Sets the channel, optional
        @rtype: None
        '''
        self.capabilities.require(KBCapabilities.SETCHAN)

        if channel >= 11 or channel <= 26:
            self._channel = channel
            self.handle.RF_setchan(channel)
        else:
            raise Exception('Invalid channel')
        if page:
            raise Exception('SubGHz not supported')

    # KillerBee expects the driver to implement this function
    def inject(self, packet, channel=None, count=1, delay=0, page=0):
        '''
        Injects the specified packet contents.
        @type packet: String
        @param packet: Packet contents to transmit, without FCS.
        @type channel: Integer
        @param channel: Sets the channel, optional
        @type page: Integer
        @param page: Sets the subghz page, not supported on this device
        @type count: Integer
        @param count: Transmits a specified number of frames, def=1
        @type delay: Float
        @param delay: Delay between each frame, def=1
        @rtype: None
        '''
        self.capabilities.require(KBCapabilities.INJECT)

        if len(packet) < 1:
            raise Exception('Empty packet')
        if len(packet) > 125:                   # 127 - 2 to accommodate FCS
            raise Exception('Packet too long')

        if channel != None:
            self.set_channel(channel, page)

        self.handle.RF_autocrc(1)               #let radio add the CRC
        for pnum in range(0, count):
            gfready = [ord(x) for x in packet]  #convert packet string to GoodFET expected integer format
            gfready.insert(0, len(gfready)+2)   #add a length that leaves room for CRC
            self.handle.RF_txpacket(gfready)
            time.sleep(1)

    # KillerBee expects the driver to implement this function
    def pnext(self, timeout=100):
        '''
        Returns a dictionary containing packet data, else None.
        @type timeout: Integer
        @param timeout: Timeout to wait for packet reception in usec
        @rtype: List
        @return: Returns None is timeout expires and no packet received.  When a packet is received, a dictionary is returned with the keys bytes (string of packet bytes), validcrc (boolean if a vaid CRC), rssi (unscaled RSSI), and location (may be set to None). For backwards compatibility, keys for 0,1,2 are provided such that it can be treated as if a list is returned, in the form [ String: packet contents | Bool: Valid CRC | Int: Unscaled RSSI ]
        '''
        if self.__stream_open == False:
            self.sniffer_on()

        packet = None
        start = datetime.utcnow()

        while (packet is None and (start + timedelta(microseconds=timeout) > datetime.utcnow())):
            packet = self.handle.RF_rxpacket()
            rssi = self.handle.RF_getrssi() #TODO calibrate

        if packet is None:
            return None

        frame = packet[1:]
        validcrc = False
        if frame[-2:] == makeFCS(frame[:-2]):
            validcrc = True
        #Return in a nicer dictionary format, so we don't have to reference by number indicies.
        #Note that 0,1,2 indicies inserted twice for backwards compatibility.
        result = {0:frame, 1:validcrc, 2:rssi, 'bytes':frame, 'validcrc':validcrc, 'rssi':rssi, 'location':None}
        result['dbm'] = rssi - 45 #TODO tune specifically to the Apimote platform (does ext antenna need to different?)
        result['datetime'] = datetime.utcnow()
        return result
 
    def ping(self, da, panid, sa, channel=None, page=0):
        '''
        Not yet implemented.
        @return: None
        @rtype: None
        '''
        raise Exception('Not yet implemented')

    def jammer_on(self, channel=None, page=0):
        '''
        Not yet implemented.
        @type channel: Integer
        @param channel: Sets the channel, optional
        @type page: Integer
        @param page: Sets the subghz page, not supported on this device
        @rtype: None
        '''
        self.capabilities.require(KBCapabilities.PHYJAM_REFLEX)

        self.handle.RF_promiscuity(1)
        self.handle.RF_autocrc(0)
        if channel != None:
            self.set_channel(channel, page)
        self.handle.CC_RFST_RX()
        self.handle.RF_carrier() #constant carrier wave jamming
        #self.handle.RF_reflexjam() #reflexive jamming (advanced)

    def set_sync(self, sync=0xA70F):
        '''Set the register controlling the 802.15.4 PHY sync byte.'''
        self.capabilities.require(KBCapabilities.SET_SYNC)
        if (sync >> 16) > 0:
            raise Exception("Sync word (%x) must be 2-bytes or less." % sync)
        return self.handle.poke(CC2420_REG_SYNC, sync)

    def jammer_off(self, channel=None, page=0):
        '''
        Not yet implemented.
        @return: None
        @rtype: None
        '''
        #TODO implement
        raise Exception('Not yet implemented')
class RZUSBSTICK:
    def __init__(self, dev, bus):
        #TODO deprecate bus param, and dev becomes a usb.core.Device object, not a string in pyUSB 1.x use
        '''
        Instantiates the KillerBee class for the RZUSBSTICK hardware.

        @type dev:   TODO
        @param dev:  USB device identifier
        @type bus:   TODO
        @param bus:  Identifies the USB bus the device is on
        @return: None
        @rtype: None
        '''
        self._channel = None
        self.handle = None
        self.dev = dev
        self.__bus = bus

        if self.dev != None:
            self.__handle_open()
        else:
            raise Exception('No interface found')

        # Tracking the command operating mode (None or AirCapture Mode)
        self.__cmdmode = RZ_CMD_MODE_NONE

        # Tracking if the RZ_CMD_OPEN_STREAM parameter is set for packet reception
        self.__stream_open = False

        # Capabilities list
        self.capabilities = KBCapabilities()
        self.__set_capabilities()

    def __del__(self):
        try:
            self.close()
        except:
            pass

    def __handle_open(self):
        '''
        Opens the device identified as self.dev, populating self.handle.
        An RZUSBSTICK has a hierarchy of:
        \_ Config value: 1
          \_ Interface number 0, with alternate setting 0
             |- Endpoint 132 for responses
             |- Endpoint 2   for control
             |- Endpoint 129 for packets
        '''
        if USBVER == 0:
            self.__handle_open_v0x()
        else: #pyUSB 1.x
            self.__dev_setup_v1x()

    def __dev_setup_v1x(self):
            # See http://pyusb.sourceforge.net/docs/1.0/tutorial.html for reference
            try:
                self.dev.set_configuration()
            except usb.core.USBError:
                raise Exception("Unable to open device. " +
                            "Ensure the device is free and plugged-in. You may need sudo.")

            #self.dev.reset()
            #self.dev.set_configuration(1) # could also provide no config number
            #self.dev.set_interface_altsetting(interface = 0, alternate_setting = 0)
            #TODO alternative setup code:
            #self.dev.set_configuration()
            #cfg = self.dev.get_active_configuration()
            #interface_number = cfg[(0,0)].bInterfaceNumber
            #alternate_settting = usb.control.get_interface(interface_number)
            #intf = usb.util.find_descriptor(cfg, bInterfaceNumber=interface_number, bAlternateSetting=alternate_setting)
            #self.handle = usb.util.find_descriptor(intf, custom_match=lambda e: (e.bEndpointAddress == TODO_TARGET_ENDPOINT))

    def __handle_open_v0x(self):
        try:
            config = self.dev.configurations[0]
            intf = config.interfaces[0]
            alt = intf[0]
            self.handle = self.dev.open()
            self.handle.reset()
            self.handle.setConfiguration(config)
            self.handle.claimInterface(alt)
            self.handle.setAltInterface(alt)
        except:
            raise Exception("Unable to open device. " +
                            "Ensure the device is free and plugged-in. You may need sudo.")

    def close(self):
        '''
        Closes the device handle.  To be re-used, class should be re-instantiated.
        @return: None
        @rtype: None
        '''
        if USBVER == 0:
            self.handle.releaseInterface()
        else:
            usb.util.release_interface(self.dev, 0)

    # KillerBee implements these, maybe it shouldn't and instead leave it to the driver as needed.
    def check_capability(self, capab):
        return self.capabilities.check(capab)
    def get_capabilities(self):
        return self.capabilities.getlist()

    def __set_capabilities(self):
        '''
        Sets the capability information for RZUSB devices.
        @rtype: None
        @return: None
        '''
        # Examine the product string for this device, setting the capability information appropriately.
        prod = self.get_dev_info()[1] # returns a list, with second element being product string

        if prod == "RZUSBSTICK":
            self.capabilities.setcapab(KBCapabilities.FREQ_2400, True)
            self.capabilities.setcapab(KBCapabilities.SNIFF, True)
            self.capabilities.setcapab(KBCapabilities.SETCHAN, True)
        elif prod == "KILLERB001":
            self.capabilities.setcapab(KBCapabilities.FREQ_2400, True)
            self.capabilities.setcapab(KBCapabilities.SNIFF, True)
            self.capabilities.setcapab(KBCapabilities.SETCHAN, True)
            self.capabilities.setcapab(KBCapabilities.INJECT, True)
        else:
            pass

    def get_dev_info(self):
        '''
        Returns device information in a list identifying the device identifier,
        product string and serial number in a list of strings.
        @rtype: List
        @return: List of 3 strings identifying device.
        '''
        if USBVER == 0:
            return [''.join([self.__bus.dirname, ":", self.dev.filename]), self.dev.open().getString(self.dev.iProduct, 50), self.dev.open().getString(self.dev.iSerialNumber, 12)]
        elif USBVER == 1:
            return ["{0}:{1}".format(self.dev.bus, self.dev.address),         \
                    usb.util.get_string(self.dev, self.dev.iProduct),     \
                    usb.util.get_string(self.dev, self.dev.iSerialNumber) ]

    def __usb_write(self, endpoint, data):
        '''
        Write data to the USB device opened as self.handle.
        
        @type endpoint: Integer
        @param endpoint: The USB endpoint to write to
        @type data: Mixed
        @param data: The data to send to the USB endpoint
        '''
        if USBVER == 0:
            try:
                self.handle.bulkWrite(endpoint, data)
                # Returns a tuple, first value is an int as the RZ_RESP_* code
                response = self.handle.bulkRead(RZ_USB_RESPONSE_EP, 1)[0]
            except usb.USBError, e:
                if e.args != ('No error',): # http://bugs.debian.org/476796
                    raise e
            time.sleep(0.0005)
            if response != RZ_RESP_SUCCESS:
                if response in RESPONSE_MAP:
                    raise Exception("Error: %s" % RESPONSE_MAP[response])
                else:
                    raise Exception("Unknown USB write error: 0x%02x" % response)
        else: #pyUSB 1.x
class SEWIO:
    def __init__(self, dev=DEFAULT_IP, recvport=DEFAULT_UDP, recvip=DEFAULT_GW):
        '''
        Instantiates the KillerBee class for the Sewio Sniffer.
        @type dev:   String
        @param dev:  IP address (ex 10.10.10.2)
        @type recvport: Integer
        @param recvport: UDP port to listen for sniffed packets on.
        @type recvip: String
        @param recvip: IP address of the host, where the sniffer will send sniffed packets to.
        @return: None
        @rtype: None
        '''
        self._channel = None
        self._modulation = 0 #unknown, will be set by change channel currently
        self.handle = None
        self.dev = dev
        
        #TODO The receive port and receive IP address are currently not 
        # obtained from or verified against the Sewio sniffer, nor are they
        # used to change the settings on the sniffer.
        self.udp_recv_port = recvport
        self.udp_recv_ip   = recvip

        self.__revision_num = getFirmwareVersion(self.dev)
        if self.__revision_num not in TESTED_FW_VERS:
            print("Warning: Firmware revision {0} reported by the sniffer is not currently supported. Errors may occur and dev_sewio.py may need updating.".format(self.__revision_num))

        self.handle = socket(AF_INET, SOCK_DGRAM)
        self.handle.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
        self.handle.bind((self.udp_recv_ip, self.udp_recv_port))

        self.__stream_open = False
        self.capabilities = KBCapabilities()
        self.__set_capabilities()
        
    def close(self):
        '''Actually close the receiving UDP socket.'''
        self.sniffer_off()  # turn sniffer off if it's currently running
        self.handle.close() # socket.close()
        self.handle = None

    def check_capability(self, capab):
        return self.capabilities.check(capab)
    def get_capabilities(self):
        return self.capabilities.getlist()
    def __set_capabilities(self):
        '''
        Sets the capability information appropriate for the client and firmware version.
        @rtype: None
        @return: None
        '''
        self.capabilities.setcapab(KBCapabilities.SNIFF, True)
        self.capabilities.setcapab(KBCapabilities.SETCHAN, True)
        self.capabilities.setcapab(KBCapabilities.FREQ_2400, True)
        self.capabilities.setcapab(KBCapabilities.FREQ_900, True)

        self.capabilities.setcapab(KBCapabilities.INJECT, True) #Added by Soteria

        return

    # KillerBee expects the driver to implement this function
    def get_dev_info(self):
	'''
        Returns device information in a list identifying the device.
        @rtype: List
        @return: List of 3 strings identifying device.
        '''
        return [self.dev, "Sewio Open-Sniffer v{0}".format(self.__revision_num), getMacAddr(self.dev)]

    def __make_rest_call(self, path, fetch=True):
        '''
        Wrapper to the sniffer's RESTful services.
        Reports URL/HTTP errors as KBInterfaceErrors.
        @rtype: If fetch==True, returns a String of the page. Otherwise, it
            returns True if an HTTP 200 code was received.
        '''
        try:
            html = urllib2.urlopen("http://{0}/{1}".format(self.dev, path))
            if fetch:
                return html.read()
            else:
                return (html.getcode() == 200)
        except Exception as e:
            raise KBInterfaceError("Unable to preform a call to {0}/{1} (error: {2}).".format(self.dev, path, e))

    def __sniffer_status(self):
        '''
        Because the firmware accepts only toggle commands for sniffer on/off,
        we need to check what state it's in before taking action. It's also
        useful to make sure our command worked.
        @rtype: Boolean
        '''
        html = self.__make_rest_call('')
        # Yup, we're going to have to steal the status out of a JavaScript variable
        res = re.search(r'<!--#pindex-->([A-Z]+),', html)
        if res is None:
            raise KBInterfaceError("Unable to parse the sniffer's current status.")
        # RUNNING means it's sniffing, STOPPED means it's not.
        return (res.group(1) == "RUNNING")

    def __sync_status(self):
        '''
        This updates the standard self.__stream_open variable based on the 
        status as reported from asking the remote sniffer.
        '''
        self.__stream_open = self.__sniffer_status()

    def __sniffer_channel(self):
        '''
        Because the firmware accepts only toggle commands for sniffer on/off,
        we need to check what state it's in before taking action. It's also
        useful to make sure our command worked.
        @rtype: Boolean
        '''
        html = self.__make_rest_call('')
        # Yup, we're going to have to steal the channel number out of a JavaScript variable
        #  var values = removeSSItag('<!--#pindex-->RUNNING,00:1a:b6:00:0a:a4,10.10.10.2,0,High,0x0000,OFF,0,0').split(",");
        res = re.search(r'<!--#pindex-->[A-Z]+,[0-9a-f:]+,[0-9.]+,([0-9]+),', html)
        if res is None:
            raise KBInterfaceError("Unable to parse the sniffer's current channel.")
        return int(res.group(1))

    # KillerBee expects the driver to implement this function
    def sniffer_on(self, channel=None):
        '''
        Turns the sniffer on such that pnext() will start returning observed
        data.
        @type channel: Integer
        @param channel: Sets the channel, optional
        @rtype: None
        '''
        self.capabilities.require(KBCapabilities.SNIFF)

        # Because the Sewio just toggles, we have to only hit the page 
        # if we need to go from off to on state.
        self.__sync_status()
        if self.__stream_open == False:
            if channel != None:
                self.set_channel(channel)
            
            if not self.__make_rest_call('status.cgi?p=2', fetch=False):
                raise KBInterfaceError("Error instructing sniffer to start capture.")

            #This makes sure the change actually happened
            self.__sync_status()
            if not self.__stream_open:
                raise KBInterfaceError("Sniffer did not turn on capture.")
                
    # KillerBee expects the driver to implement this function
    def sniffer_off(self):
        '''
        Turns the sniffer off.
        @rtype: None
        '''
        # Because the Sewio just toggles, we have to only hit the page 
        # if we need to go from on to off state.
        self.__sync_status()
        if self.__stream_open == True:
            if not self.__make_rest_call('status.cgi?p=2', fetch=False):
                raise KBInterfaceError("Error instructing sniffer to stop capture.")
            
            #This makes sure the change actually happened
            self.__sync_status()
            if self.__stream_open:
                raise KBInterfaceError("Sniffer did not turn off capture.")
    
    @staticmethod
    def __get_default_modulation(channel):
        '''
        Return the Sewio-specific integer representing the modulation which
        should be choosen to be IEEE 802.15.4 complinating for a given channel 
        number.
        Captured values from sniffing Sewio web interface, unsure why these
        are done as such.
        Available modulations are listed at:
        http://www.sewio.net/open-sniffer/develop/http-rest-interface/
        @rtype: Integer, or None if unable to determine modulation
        '''
        if channel >= 11 or channel <= 26: return '0'   #O-QPSK 250 kb/s 2.4GHz
        elif channel >= 1 or channel <= 10: return 'c'  #O-QPSK 250 kb/s 915MHz
        elif channel >= 128 or channel <= 131: return '1c' #O-QPSK 250 kb/s 760MHz
        elif channel == 0: return '0'                   #O-QPSK 100 kb/s 868MHz
        else: return None                               #Error status

    # KillerBee expects the driver to implement this function
    def set_channel(self, channel):
        '''
        Sets the radio interface to the specified channel, and the matching modulation setting.
        @type channel: Integer
        @param channel: Sets the channel, optional
        @rtype: None
        '''
        self.capabilities.require(KBCapabilities.SETCHAN)

        if self.capabilities.is_valid_channel(channel):
            # We only need to update our channel if it doesn't match the currently reported one.
            curChannel = self.__sniffer_channel()
            if channel != curChannel:
                self.modulation = self.__get_default_modulation(channel)
                print("Setting to channel {0}, modulation {1}.".format(channel, self.modulation))
                # Examples captured in fw v0.5 sniffing:
                #   channel 6, 250 compliant: http://10.10.10.2/settings.cgi?chn=6&modul=c&rxsens=0
                #   channel 12, 250 compliant: http://10.10.10.2/settings.cgi?chn=12&modul=0&rxsens=0
                #   chinese 0, 780 MHz, 250 compliant: http://10.10.10.2/settings.cgi?chn=128&modul=1c&rxsens=0
                #   chinese 3, 786 MHz, 250 compliant: http://10.10.10.2/settings.cgi?chn=131&modul=1c&rxsens=0
                #rxsens 0 is normal, 3 is high sensitivity to receive at
                self.__make_rest_call("settings.cgi?chn={0}&modul={1}&rxsens=3".format(channel, self.modulation), fetch=False)
                self._channel = self.__sniffer_channel()
            else:
                self._channel = curChannel
        else:
            raise Exception('Invalid channel number ({0}) was provided'.format(channel))

    # KillerBee expects the driver to implement this function
    def inject(self, packet, channel=None, count=1, delay=0):
        '''
        Implemented by soteria :-)
        '''

        self.capabilities.require(KBCapabilities.INJECT)

        if len(packet) < 1:
            raise Exception('Empty packet')
        if len(packet) > 125:                   # 127 - 2 to accommodate FCS
            raise Exception('Packet too long')

        if channel != None:
            self.set_channel(channel)

        self.modulation = self.__get_default_modulation(channel)

        # Transmitted power level.
        # Default value: 0 (3.0 dBm)
        txlevel = 0
        # Sniff after injection
        RxEnable = 1
        RepeatNumber = 1
        timeSpace = 1
        autoCorrect = 1


        correctPacket = packet.encode("hex")

        # import PrintHelper
        # correctPacket += PrintHelper.STATIC_DATA.LOW_DIM_VALUE

        packetLength = len(correctPacket)/ 2
        print "Packet length = " + str(packetLength)


        # Turn on sniffer (so we can continue to sniff after the injection)
        self.sniffer_on(channel=channel)
        for pnum in range(0, count):
            #Inject the packet
            formatedcall = "inject.cgi?chn={0}&modul={1}&txlevel={2}&rxen={3}&nrepeat={4}&tspace={5}&autocrc={6}&spayload={7}&len={8}".format(channel,self.modulation,txlevel,RxEnable,RepeatNumber,timeSpace,autoCorrect,correctPacket,packetLength)
            print formatedcall
            self.__make_rest_call(formatedcall, fetch=True)
            time.sleep(delay)


    @staticmethod
    def __parse_zep_v2(data):
        '''
        Parse the packet from the ZigBee encapsulation protocol version 2/3 and 
        return the fields desired for usage by pnext().
        There is support here for some oddities specific to the Sewio 
        implementation of ZEP and the packet, such as CC24xx format FCS 
        headers being expected.
        
        The ZEP protocol parsing is mainly based on Wireshark source at:
        http://anonsvn.wireshark.org/wireshark/trunk/epan/dissectors/packet-zep.c
        * ZEP v2 Header will have the following format (if type=1/Data):
        *  |Preamble|Version| Type |Channel ID|Device ID|CRC/LQI Mode|LQI Val|NTP Timestamp|Sequence#|Reserved|Length|
        *  |2 bytes |1 byte |1 byte|  1 byte  | 2 bytes |   1 byte   |1 byte |   8 bytes   | 4 bytes |10 bytes|1 byte|
        * ZEP v2 Header will have the following format (if type=2/Ack):
        *  |Preamble|Version| Type |Sequence#|
        *  |2 bytes |1 byte |1 byte| 4 bytes |
        #define ZEP_PREAMBLE        "EX"
        #define ZEP_V2_HEADER_LEN   32
        #define ZEP_V2_ACK_LEN      8
        #define ZEP_V2_TYPE_DATA    1
        #define ZEP_V2_TYPE_ACK     2
        #define ZEP_LENGTH_MASK     0x7F
        '''
        # Unpack constant part of ZEPv2
        (preamble, version, zeptype) = unpack('<HBB', data[:4])
        if preamble != 22597 or version < 2: # 'EX'==22597, and v3 is compat with v2 (I think??)
            raise Exception("Can not parse provided data as ZEP due to incorrect preamble or unsupported version.")
        if zeptype == 1: #data
            (ch, devid, crcmode, lqival, ntpsec, ntpnsec, seqnum, length) = unpack(">BHBBIII10xB", data[4:32])
            #print "Data ZEP:", ch, devid, crcmode, lqival, ntpsec, ntpnsec, seqnum, length
            #We could convert the NTP timestamp received to system time, but the
            # Sewio firmware uses "relative timestamping" where it begins at 0 each time
            # the sniffer is started. Thus, it isn't that useful to us, so we just add the
            # time the packet is received at the host instead.
            #print "\tConverted time:", ntp_to_system_time(ntpsec, ntpnsec)
            recdtime = datetime.utcnow()
            #The LQI comes in ZEP, but the RSSI comes in the first byte of the FCS,
            # if the FCS was correct. If the byte is 0xb1, Wireshark appears to do 0xb1-256 = -79 dBm.
            # It appears that if CRC/LQI Mode field == 1, then checksum was bad, so the RSSI isn't
            # available, as the CRC is left in the packet. If it == 0, then the first byte of FCS is the RSSI.
            # From Wireshark:
            #define IEEE802154_CC24xx_CRC_OK            0x8000
            #define IEEE802154_CC24xx_RSSI              0x00FF
            frame = data[32:]
            # A length vs len(frame) check is not used here but is an 
            #  additional way to verify that all is good (length == len(frame)).
            if crcmode == 0:
                validcrc = ((ord(data[-1]) & 0x80) == 0x80)
                rssi = ord(data[-2])
                # We have to trust the sniffer that the FCS was OK, so we compute
                #  what a good FCS should be and patch it back into the packet.
                frame = frame[:-2] + makeFCS(frame[:-2])
            else:
                validcrc = False
                rssi = None
            return (frame, ch, validcrc, rssi, lqival, recdtime)
        elif zeptype == 2: #ack
            frame = data[8:]
            (seqnum) = unpack(">I", data[4:8])
            recdtime = datetime.utcnow()
            validcrc = (frame[-2:] == makeFCS(frame[:-2]))
            return (frame, None, validcrc, None, None, recdtime)
        return None

    # KillerBee expects the driver to implement this function
    def pnext(self, timeout=100):
        '''
        Returns a dictionary containing packet data, else None.
        @type timeout: Integer
        @param timeout: Timeout to wait for packet reception in usec
        @rtype: List
        @return: Returns None is timeout expires and no packet received.  When a packet is received, a dictionary is returned with the keys bytes (string of packet bytes), validcrc (boolean if a vaid CRC), rssi (unscaled RSSI), and location (may be set to None). For backwards compatibility, keys for 0,1,2 are provided such that it can be treated as if a list is returned, in the form [ String: packet contents | Bool: Valid CRC | Int: Unscaled RSSI ]
        '''
        if self.__stream_open == False:
            self.sniffer_on() #start sniffing

        # Use socket timeouts to implement the timeout
        self.handle.settimeout(timeout / 1000000.0) # it takes seconds

        frame = None
        donetime = datetime.utcnow() + timedelta(microseconds=timeout)
        while True:
            try:
                data, addr = self.handle.recvfrom(1024)
            except error_timeout:
                return None
            # Ensure it's data coming from the right place, for now we just
            #  check the sending IP address. Ex: addr = ('10.10.10.2', 17754)
            if addr[0] != self.dev:
                continue
            # Dissect the UDP packet
            (frame, ch, validcrc, rssi, lqival, recdtime) = self.__parse_zep_v2(data)
            print "Valid CRC", validcrc, "LQI", lqival, "RSSI", rssi
            if frame == None or (ch is not None and ch != self._channel):
                #TODO this maybe should be an error condition, instead of ignored?
                print("ZEP parsing issue (bytes length={0}, channel={1}).".format(len(frame) if frame is not None else None, ch))
                continue
            break

        if frame is None:
            return None

        #Return in a nicer dictionary format, so we don't have to reference by number indicies.
        #Note that 0,1,2 indicies inserted twice for backwards compatibility.
        result = {0:frame, 1:validcrc, 2:rssi, 'bytes':frame, 'validcrc':validcrc, 'rssi':rssi, 'dbm':None, 'location':None, 'datetime':recdtime}
        if rssi is not None:
            # Per note from Sewino team regarding encoding of RSSI value as 2's complement dBm values
            if rssi > 127:  result['dbm'] = rssi - 256
            else:           result['dbm'] = rssi
        return result

    def jammer_on(self, channel=None):
        '''
        Not yet implemented.
        @type channel: Integer
        @param channel: Sets the channel, optional
        @rtype: None
        '''
        self.capabilities.require(KBCapabilities.PHYJAM)

    def jammer_off(self, channel=None):
        '''
        Not yet implemented.
        @return: None
        @rtype: None
        '''
        self.capabilities.require(KBCapabilities.PHYJAM)
Beispiel #22
0
class APIMOTE:
    def __init__(self, dev, revision=DEFAULT_REVISION):
        '''
        Instantiates the KillerBee class for the ApiMote platform running GoodFET firmware.
        @type dev:   String
        @param dev:  Serial device identifier (ex /dev/ttyUSB0)
        @type revision: Integer
        @param revision: The revision number for the ApiMote, which is used by 
            the called GoodFET libraries to properly communicate with
            and configure the hardware.
        @return: None
        @rtype: None
        '''
        self._channel = None
        self.handle = None
        self.dev = dev

        self.__revision_num = revision
        # Set enviroment variables for GoodFET code to use
        os.environ["platform"] = "apimote%d".format(self.__revision_num)
        os.environ["board"] = "apimote%d".format(self.__revision_num)
        self.handle = GoodFETCCSPI()
        self.handle.serInit(port=self.dev)
        self.handle.setup()
        # TODO can we verify here the revision number that was sent is correct?

        self.__stream_open = False
        self.capabilities = KBCapabilities()
        self.__set_capabilities()

    def close(self):
        self.handle.serClose()
        self.handle = None

    def check_capability(self, capab):
        return self.capabilities.check(capab)
    def get_capabilities(self):
        return self.capabilities.getlist()
    def __set_capabilities(self):
        '''
        Sets the capability information appropriate for GoodFETCCSPI client and firmware.
        @rtype: None
        @return: None
        '''
        self.capabilities.setcapab(KBCapabilities.FREQ_2400, True)
        self.capabilities.setcapab(KBCapabilities.SNIFF, True)
        self.capabilities.setcapab(KBCapabilities.SETCHAN, True)
        self.capabilities.setcapab(KBCapabilities.INJECT, True)
        self.capabilities.setcapab(KBCapabilities.PHYJAM_REFLEX, True)
        self.capabilities.setcapab(KBCapabilities.SET_SYNC, True)
        return

    # KillerBee expects the driver to implement this function
    def get_dev_info(self):
	'''
        Returns device information in a list identifying the device.
        @rtype: List
        @return: List of 3 strings identifying device.
        '''
        return [self.dev, "GoodFET Apimote v%d".format(self.__revision_num), ""]

    # KillerBee expects the driver to implement this function
    def sniffer_on(self, channel=None):
        '''
        Turns the sniffer on such that pnext() will start returning observed
        data.  Will set the command mode to Air Capture if it is not already
        set.
        @type channel: Integer
        @param channel: Sets the channel, optional
        @rtype: None
        '''
        self.capabilities.require(KBCapabilities.SNIFF)

        self.handle.RF_promiscuity(1);
        self.handle.RF_autocrc(0);

        if channel != None:
            self.set_channel(channel)
        
        self.handle.CC_RFST_RX();
        #print "Sniffer started (listening as %010x on %i MHz)" % (self.handle.RF_getsmac(), self.handle.RF_getfreq()/10**6);

        self.__stream_open = True

    # KillerBee expects the driver to implement this function
    def sniffer_off(self):
        '''
        Turns the sniffer off, freeing the hardware for other functions.  It is
        not necessary to call this function before closing the interface with
        close().
        @rtype: None
        '''
        #TODO actually have firmware stop sending us packets!
        self.__stream_open = False

    # KillerBee expects the driver to implement this function
    def set_channel(self, channel):
        '''
        Sets the radio interface to the specifid channel (limited to 2.4 GHz channels 11-26)
        @type channel: Integer
        @param channel: Sets the channel, optional
        @rtype: None
        '''
        self.capabilities.require(KBCapabilities.SETCHAN)

        if channel >= 11 or channel <= 26:
            self._channel = channel
            self.handle.RF_setchan(channel)
        else:
            raise Exception('Invalid channel')

    # KillerBee expects the driver to implement this function
    def inject(self, packet, channel=None, count=1, delay=0):
        '''
        Injects the specified packet contents.
        @type packet: String
        @param packet: Packet contents to transmit, without FCS.
        @type channel: Integer
        @param channel: Sets the channel, optional
        @type count: Integer
        @param count: Transmits a specified number of frames, def=1
        @type delay: Float
        @param delay: Delay between each frame, def=1
        @rtype: None
        '''
        self.capabilities.require(KBCapabilities.INJECT)

        if len(packet) < 1:
            raise Exception('Empty packet')
        if len(packet) > 125:                   # 127 - 2 to accommodate FCS
            raise Exception('Packet too long')

        if channel != None:
            self.set_channel(channel)

        self.handle.RF_autocrc(1)               #let radio add the CRC
        for pnum in range(0, count):
            gfready = [ord(x) for x in packet]  #convert packet string to GoodFET expected integer format
            gfready.insert(0, len(gfready)+2)   #add a length that leaves room for CRC
            self.handle.RF_txpacket(gfready)
            time.sleep(1)

    # KillerBee expects the driver to implement this function
    def pnext(self, timeout=100):
        '''
        Returns a dictionary containing packet data, else None.
        @type timeout: Integer
        @param timeout: Timeout to wait for packet reception in usec
        @rtype: List
        @return: Returns None is timeout expires and no packet received.  When a packet is received, a dictionary is returned with the keys bytes (string of packet bytes), validcrc (boolean if a vaid CRC), rssi (unscaled RSSI), and location (may be set to None). For backwards compatibility, keys for 0,1,2 are provided such that it can be treated as if a list is returned, in the form [ String: packet contents | Bool: Valid CRC | Int: Unscaled RSSI ]
        '''
        if self.__stream_open == False:
            self.sniffer_on() #start sniffing

        packet = None;
        start = datetime.utcnow()

        while (packet is None and (start + timedelta(microseconds=timeout) > datetime.utcnow())):
            packet = self.handle.RF_rxpacket()
            rssi = self.handle.RF_getrssi() #TODO calibrate

        if packet is None:
            return None

        frame = packet[1:]
        if frame[-2:] == makeFCS(frame[:-2]): validcrc = True
        else: validcrc = False
        #Return in a nicer dictionary format, so we don't have to reference by number indicies.
        #Note that 0,1,2 indicies inserted twice for backwards compatibility.
        result = {0:frame, 1:validcrc, 2:rssi, 'bytes':frame, 'validcrc':validcrc, 'rssi':rssi, 'location':None}
        result['dbm'] = rssi - 45 #TODO tune specifically to the Apimote platform (does ext antenna need to different?)
        result['datetime'] = datetime.utcnow()
        return result
 
    def ping(self, da, panid, sa, channel=None):
        '''
        Not yet implemented.
        @return: None
        @rtype: None
        '''
        raise Exception('Not yet implemented')

    def jammer_on(self, channel=None):
        '''
        Not yet implemented.
        @type channel: Integer
        @param channel: Sets the channel, optional
        @rtype: None
        '''
        self.capabilities.require(KBCapabilities.PHYJAM_REFLEX)

        self.handle.RF_promiscuity(1)
        self.handle.RF_autocrc(0)
        if channel != None:
            self.set_channel(channel)
        self.handle.CC_RFST_RX()
        self.handle.RF_carrier() #constant carrier wave jamming
        #self.handle.RF_reflexjam() #reflexive jamming (advanced)

    def set_sync(self, sync=0xA70F):
        '''Set the register controlling the 802.15.4 PHY sync byte.'''
        self.capabilities.require(KBCapabilities.SET_SYNC)
        if (sync >> 16) > 0:
            raise Exception("Sync word (%x) must be 2-bytes or less." % sync)
        return self.handle.poke(CC2420_REG_SYNC, sync)

    def jammer_off(self, channel=None):
        '''
        Not yet implemented.
        @return: None
        @rtype: None
        '''
        #TODO implement
        raise Exception('Not yet implemented')
Beispiel #23
0
class FREAKDUINO:
    def __init__(self, serialpath):
        '''
        Instantiates the KillerBee class for our sketch running on ChibiArduino on Freakduino hardware.
        @param serialpath:  /dev/ttyUSB* type serial port identifier
        @return: None
        @rtype: None
        '''
        self._channel = None
        self.handle = None
        self.dev = serialpath
        self.date = None
        self.lon, self.lat, self.alt = (None, None, None)
        self.handle = serial.Serial(port=self.dev, baudrate=57600, \
                                    timeout=1, bytesize=8, parity='N', stopbits=1, xonxoff=0)
        self.capabilities = KBCapabilities()
        self.__set_capabilities()

    def close(self):
        '''
        Closes the serial port. After closing, must reinitialize class again before use.
        @return: None
        @rtype: None
        '''
        self.handle.close()
        self.handle = None

    # KillerBee implements these, maybe it shouldn't and instead leave it to the driver as needed.
    # TODO deprecate these functions in favor of self.capabilities class functions
    def check_capability(self, capab):
        return self.capabilities.check(capab)
    def get_capabilities(self):
        return self.capabilities.getlist()

    def __set_capabilities(self):
        '''
        Sets the capability information for Freakdruino device based on the currently loaded sketch.
        @rtype: None
        @return: None
        '''
        #TODO have it set based on what the sketch says it can support
        self.capabilities.setcapab(KBCapabilities.SNIFF, True)
        self.capabilities.setcapab(KBCapabilities.SETCHAN, True)
        return

    def get_dev_info(self):
        '''
        Returns device information in a list identifying the device.
        @rtype: List
        @return: List of 3 strings identifying device.
        '''
        return [self.dev, "Dartmouth Freakduino", ""]

    def __send_cmd(self, cmdstr, arg=None):
        self.handle.flush()
        time.sleep(1.5) #this delay seems crucial

        self.handle.write("S")
        self.handle.write(cmdstr)
        if arg != None: self.handle.write(arg)
        self.handle.write('\r')

        #print "Sent S%s" % (cmdstr)
        self.handle.flush()

    # Deprecated due to unreliability
    def __serial_cmd(self, cmdstr, arg=None):
        '''
        Sends a command over the self.conn serial connection.
        Ex: If provided cmdstr = "C!N" it will send "SC!N", telling the device to turn on sniffing ("N"),
        and it expects to receive a confirmation back "&C!N" to confirm success.
        '''
        print "Flushing out of buffer:", self.handle.inWaiting()
        self.handle.flushInput()
        if len(cmdstr) > 3:
            raise Exception("Command string is less than minimum length (S%s)." % cmdstr)
        self.__send_cmd(cmdstr, arg)

        # TODO ugly and unreliable:
        # This should just wait for a & and then parse things after it,
        # however it seems sometimes you have to resend the command or something.
        print "Line:", self.handle.readline(eol='&')
        counter = 0
        char = self.handle.read()
        while (char != '&'):
            print self.handle.inWaiting(), "Waiting...", char
            time.sleep(0.01)
            if (counter > 8):
                self.__send_cmd(cmdstr, arg)
                counter = 0
                print "Resend Response Line:", self.handle.readline(eol='&')
            else: counter += 1
            char = self.handle.read()
        response = ''
        for i in range(3):
            response += self.handle.read()

        if response == cmdstr[:3]:
            print "Got a response:", response, "matches", cmdstr
            return True
        else:
            print "Invalid response:", response, cmdstr[:3]
            return False

    # Send the command for the Dartmouth-mod Freakduino to dump data logged in EEPROM
    def eeprom_dump(self):
        self.__send_cmd("C!D")

    # KillerBee expects the driver to implement this function
    def sniffer_on(self, channel=None):
        '''
        Turns the sniffer on such that pnext() will start returning observed
        data.  Will set the command mode to Air Capture if it is not already
        set.
        @type channel: Integer
        @param channel: Sets the channel, optional
        @rtype: None
        '''
        self.capabilities.require(KBCapabilities.SNIFF)

        if channel != None:
            self.set_channel(channel)

        #TODO implement mode change to start sniffer sending packets to us
        self.__send_cmd("C!N")
        self.mode = MODE_SNIFF
        self.__stream_open = True

    # KillerBee expects the driver to implement this function
    def sniffer_off(self):
        '''
        Turns the sniffer off, freeing the hardware for other functions.  It is
        not necessary to call this function before closing the interface with
        close().
        @rtype: None
        '''
        self.__send_cmd("C!F")
        self.mode = MODE_NONE
        self.__stream_open = False

    # KillerBee expects the driver to implement this function
    def set_channel(self, channel):
        '''
        Sets the radio interface to the specifid channel (limited to 2.4 GHz channels 11-26)
        @type channel: Integer
        @param channel: Sets the channel, optional
        @rtype: None
        '''
        self.capabilities.require(KBCapabilities.SETCHAN)

        if channel >= 10 or channel <= 26:
            self._channel = channel
            #TODO actually check that it responds correctly to the request
            self.__send_cmd("C!C %d" % channel)
        else:
            raise Exception('Invalid channel')

    # KillerBee expects the driver to implement this function
    def inject(self, packet, channel=None, count=1, delay=0):
        '''
        Injects the specified packet contents.
        @type packet: String
        @param packet: Packet contents to transmit, without FCS.
        @type channel: Integer
        @param channel: Sets the channel, optional
        @type count: Integer
        @param count: Transmits a specified number of frames, def=1
        @type delay: Float
        @param delay: Delay between each frame, def=1
        @rtype: None
        '''
        self.capabilities.require(KBCapabilities.INJECT)

        if len(packet) < 1:
            raise Exception('Empty packet')
        if len(packet) > 125:                   # 127 - 2 to accommodate FCS
            raise Exception('Packet too long')

        if channel != None:
            self.set_channel(channel)

        # Append two bytes to be replaced with FCS by firmware.
        packet = ''.join([packet, "\x00\x00"])

        for pnum in range(0, count):
            raise Exception('Not yet implemented')
            # Format for packet is opcode CMD_INJECT_FRAME, one-byte length,  packet data
            #TODO RZ_USB_COMMAND_EP, struct.pack("B", RZ_CMD_INJECT_FRAME) + struct.pack("B", len(packet)) + packet)
            time.sleep(delay)

    # KillerBee expects the driver to implement this function
    #TODO I suspect that if you don't call this often enough while getting frames, the serial buffer may overflow.
    def pnext(self, timeout=100):
        '''
        Returns packet data as a string, else None.
        @type timeout: Integer
        @param timeout: Timeout to wait for packet reception in usec
        @rtype: List
        @return: Returns None is timeout expires and no packet received.  When a packet is received,
                 a list is returned, in the form [ String: packet contents | Bool: Valid CRC | Int: Unscaled RSSI ]
        '''
        if self.__stream_open == False:
            self.sniffer_on() #start sniffing
        return self.pnext_rec(timeout)

    # Bulk of pnext implementation, but does not ensure the sniffer is on first, thus usable for EEPROM reading
    def pnext_rec(self, timeout=100):
        pdata = ''
        ldata = ''

        self.handle.timeout=timeout         # Allow pySerial to handle timeout
        startChar = self.handle.read()
        if startChar == None: return None   # Sense timeout case and return

        # Listens for serial message of general format R!<data>;
        if startChar == "R":                # Get packet data
            if self.handle.read() == "!":
                x = self.handle.read()
                while (x != ";"):
                    pdata += x
                    x = self.handle.read()
        if startChar == "L":                # Get location data
            if self.handle.read() == "!":
                x = self.handle.read()
                while (x != ";"):
                    ldata += x
                    x = self.handle.read()
        if startChar == "[":                # Sense when done reading from EEPROM
            if self.handle.read(40) == "{[ DONE READING BACK ALL LOGGED DATA ]}]":
                raise StopIteration("All Data Read")

        # If location received, update our local variables:
        if ldata != None and ldata != '':
            self.processLocationUpdate(ldata)

        if pdata == None or pdata == '':
            return None

        # Parse received data as <rssi>!<time>!<packtlen>!<frame>
        data = pdata.split("!", 3)
        try:
            rssi = ord(data[0])
            frame = data[3]
            if frame[-2:] == makeFCS(frame[:-2]): validcrc = True
            else: validcrc = False
        except:
            print "Error parsing stream received from device:", pdata, data
            return None
        #Return in a nicer dictionary format, so we don't have to reference by number indicies.
        #Note that 0,1,2 indicies inserted twice for backwards compatibility.
        result = {0:frame, 1:validcrc, 2:rssi, 'bytes':frame, 'validcrc':validcrc, 'rssi':rssi}
        result['dbm'] = None #TODO calculate dBm antenna signal based on RSSI formula
        result['datetime'] = self.getCaptureDateTime(data)
        result['location'] = (self.lon, self.lat, self.alt)
        return result

    def getCaptureDateTime(self, data):
        try:
            timestr = "%08d" % (struct.unpack('L', data[1])[0]) #in format hhmmsscc
            time = dttime(int(timestr[:2]), int(timestr[2:4]), int(timestr[4:6]), int(timestr[6:]))
        except:
            print "Issue with time format:", timestr, data
            time = None
        if self.date == None: self.date = date.today()
        if time == None or time == dttime.min: time = (datetime.now()).time()
        #TODO address timezones by going to UTC everywhere
        return datetime.combine(self.date, time)

    def processLocationUpdate(self, ldata):
        '''
        Take a location string passed from the device and update the driver's internal state of last received location.
        Format of ldata: longlatialtidate
        '''
        self.lon = struct.unpack('l', ldata[0:4])[0]
        self.lat = struct.unpack('l', ldata[4:8])[0]
        self.alt = struct.unpack('l', ldata[8:12])[0]
        date = str(struct.unpack('L', ldata[12:16])[0])
        self.date = datetime.date(date[-2:], date[-4:-2], date[:-4])
        #TODO parse data formats (lon=-7228745 lat=4370648 alt=3800 age=63 date=70111 time=312530)
        print self.lon, self.lat, self.alt, self.date

    def ping(self, da, panid, sa, channel=None):
        '''
        Not yet implemented.
        @return: None
        @rtype: None
        '''
        raise Exception('Not yet implemented')

    def jammer_on(self, channel=None):
        '''
        Not yet implemented.
        @type channel: Integer
        @param channel: Sets the channel, optional
        @rtype: None
        '''
        self.capabilities.require(KBCapabilities.PHYJAM)

        if self.__cmdmode != RZ_CMD_MODE_AC:
            self._set_mode(RZ_CMD_MODE_AC)

        if channel != None:
            self.set_channel(channel)

        #TODO implement
        raise Exception('Not yet implemented')

    def jammer_off(self, channel=None):
        '''
        Not yet implemented.
        @return: None
        @rtype: None
        '''
        #TODO implement
        raise Exception('Not yet implemented')
Beispiel #24
0
class SL_BEEHIVE:
    def __init__(self, serialpath):
        '''
        Instantiates the KillerBee class for Silabs BEEHIVE
        @param serialpath:  /dev/ttyACM* type serial port identifier
        @return: None
        @rtype: None
        '''
        self._channel = None
        self._page = 0
        self.handle = None
        self.dev = serialpath
        self.date = None
        self.lon, self.lat, self.alt = (None, None, None)
        self.__stream_open = False
        self.handle = serial.Serial(port=self.dev, baudrate=115200, \
                                    timeout=.02, bytesize=8, parity='N', stopbits=1, xonxoff=0)
        self.capabilities = KBCapabilities()
        self.__set_capabilities()

    def close(self):
        '''
        Closes the serial port. After closing, must reinitialize class again before use.
        @return: None
        @rtype: None
        '''
        self.handle.close()
        self.handle = None

    # KillerBee implements these, maybe it shouldn't and instead leave it to the driver as needed.
    # TODO deprecate these functions in favor of self.capabilities class functions
    def check_capability(self, capab):
        return self.capabilities.check(capab)
    def get_capabilities(self):
        return self.capabilities.getlist()

    def __set_capabilities(self):
        '''
        Sets the capability information for Silabs Node Test
        @rtype: None
        @return: None
        '''
        #NOTE frequencies will actually depend on radio daughterboard on the Silabs dev kit
        self.capabilities.setcapab(KBCapabilities.FREQ_2400, True)
        self.capabilities.setcapab(KBCapabilities.FREQ_915, True)
        self.capabilities.setcapab(KBCapabilities.FREQ_863, True)
        self.capabilities.setcapab(KBCapabilities.FREQ_868, True)
        self.capabilities.setcapab(KBCapabilities.FREQ_870, True)
        self.capabilities.setcapab(KBCapabilities.SNIFF, True)
        self.capabilities.setcapab(KBCapabilities.INJECT, True)
        self.capabilities.setcapab(KBCapabilities.SETCHAN, True)
        return

    def get_dev_info(self):
        '''
        Returns device information in a list identifying the device.
        @rtype: List
        @return: List of 3 strings identifying device.
        '''
        return [self.dev, "BeeHive SG", ""]

    def __send_cmd(self, cmdstr, arg=None, confirm= True, send_return= True, extra_delay= 0, initial_read= 3):
        # read any cruft
        #time.sleep(0.1)
        for x in range(initial_read):
            self.handle.readline()

        # some commands require us to be in idle, so do it always
        self.handle.write("rx 0\r")
        time.sleep(0.02)
        for x in range(3):
            self.handle.readline()

        if arg != None:
            cmdstr += ' ' + arg
        self.handle.write(cmdstr)
        if send_return:
            self.handle.write('\r')
        #time.sleep(0.1)
        #print "Sent %s" % (cmdstr)
        time.sleep(extra_delay)
        if confirm:
            ret= False
            for x in range(100):
                d= self.handle.readline().strip()
                #print 'got', d
                if d[-1:] == '>':
                    ret= True
                    break
        else:
            ret= True

        if not ret:
            raise Exception('Could not send command %s' % cmdstr)

    def __dissect_pkt(self, packet):
        '''
        internal routine to deconstruct serial text received from device
        packet will be in the format: "{{(rxPacket)}{len:11}{timeUs:994524212}{crc:Pass}{rssi:-44}{lqi:210}{phy:0}{isAck:False}{syncWordId:0}{antenna:0}{payload: 0x0a 0x03 0x08 0xba 0xff 0xff }"
        '''
        try:
            data = packet.replace('}','').split(':')[10].split()
        except:
            return None, None, None
        if not data:
            return None, None, None
        # payload is in the form e.g. "0x03 0x08 0xA3 0xFF 0xFF 0xFF 0xFF 0x07" so we need to convert to a string
        out = ''
        # first byte is length
        if len(data) != int(data[0], 16) + 1:
            return None, None, None
        for x in data[1:]:
            try:
                out += chr(int(x, 16))
            except:
                return None, None, None
        try:
            crc = packet.replace('}','').split(':')[4].split('{')[0] == 'Pass'
            rssi = packet.replace('}','').split(':')[4].split('{')[0]
        except:
            return None, None, None
        return rssi, out, crc

    # KillerBee expects the driver to implement this function
    def sniffer_on(self, channel=None, page=0):
        '''
        Turns the sniffer on such that pnext() will start returning observed
        data.  Will set the command mode to Air Capture if it is not already
        set.
        @type channel: Integer
        @param channel: Sets the channel, optional
        @rtype: None
        '''
        self.capabilities.require(KBCapabilities.SNIFF)

        if channel != None or page:
            self.set_channel(channel, page)

        if not self.__stream_open:
            # make sure we get all packets
            self.__init_radio()
            # start sniffer
            self.__send_cmd("rx", "1", confirm= False)
            for x in range(5):
                d = self.handle.readline()
                #print 'got', d
                if 'Rx:Enabled' in d:
                    self.mode = MODE_SNIFF
                    self.__stream_open = True

        if not self.__stream_open:
             raise Exception('Could not start sniffer')

    # KillerBee expects the driver to implement this function
    def sniffer_off(self):
        '''
        Turns the sniffer off, freeing the hardware for other functions.  It is
        not necessary to call this function before closing the interface with
        close().
        @rtype: None
        '''
        # reset timeout as sniffer has made it long
        self.handle.timeout= 0.2
        self.__send_cmd("rx", "0", confirm= False, initial_read= 0)
        for x in range(3):
            d= self.handle.readline().strip()
            if "Rx:Disabled" in d:
                self.mode = MODE_NONE
                self.__stream_open = False
                self.handle.readline()
        if self.__stream_open:
            raise Exception('Could not stop sniffer')

    def __init_radio(self):
        self.__send_cmd("reset", extra_delay = 0.6)
        self.__send_cmd("rx", "0")
        self.set_channel(self._channel, self._page)
        self.__send_cmd("BeeHiveMode", "1")
        self.__send_cmd("BeeHiveOptions", "1 1 1 1")

    # KillerBee expects the driver to implement this function
    def set_channel(self, channel, page=0):
        '''
        Sets the radio interface to the specifid channel (limited to 2.4 GHz channels 11-26)
        @type channel: Integer
        @param channel: Sets the channel, optional
        @type page: Integer
        @param page: Sets the subghz page
        @rtype: None
        '''
        self.capabilities.require(KBCapabilities.SETCHAN)

        #subghz channels must be calculated to include page (3 LSB page + 5 LSB channel)
        # e.g. page 28 channel 15 == '100' + '01111' = '10001111' = 0x8F
        self._channel = channel
        self._page = page
        if page:
            channel = ((page << 5) & 0xff) + (channel & 0x1f)
            if page == 31:
                self.__send_cmd("915MHz", extra_delay= 0.3)
            else:
                self.__send_cmd("863MHz", extra_delay= 0.3)
        else:
            self.__send_cmd("2p4GHz")
        #TODO actually check that it responds correctly to the request
        self.__send_cmd("setchannel", "%d" % channel)

    # KillerBee expects the driver to implement this function
    # note that BEEHIVE never generates FCS - you must provide it as part of the packet!
    def inject(self, packet, channel=None, count=1, delay=0, page=0):
        '''
        Injects the specified packet contents.
        @type packet: String
        @param packet: Packet contents to transmit, with FCS.
        @type channel: Integer
        @param channel: Sets the channel, optional
        @type count: Integer
        @param count: Transmits a specified number of frames, def=1
        @type delay: Float
        @param delay: Delay between each frame, def=1
        @rtype: None
        '''
        self.capabilities.require(KBCapabilities.INJECT)

        if len(packet) < 1:
            raise Exception('Empty packet')
        if len(packet) > 127:
            raise Exception('Packet too long')

        if channel != None or page:
            self.set_channel(channel, page)

        # 
        self.__send_cmd("setTxLength", "%d" % len(packet))
        maxp = 118
        # we can send max 256 bytes over the wire so we must split large packets due to hex doubling the size
        if len(packet) > maxp:
            tosend = maxp
        else:
            tosend = len(packet) 
        self.__send_cmd("setTxPayload", "00 %02x%s" % ((len(packet)), packet[:tosend].encode('hex')))
        if len(packet) > maxp:
            self.__send_cmd("setTxPayload", "%d %s" % (tosend + 1, packet[tosend:].encode('hex')))
        #print 'sending', len(packet), 'bytes:', packet.encode('hex')
        for pnum in range(0, count):
            self.__send_cmd("tx", "1", confirm= False)
            time.sleep(delay)

    # KillerBee expects the driver to implement this function
    #TODO I suspect that if you don't call this often enough while getting frames, the serial buffer may overflow.
    def pnext(self, timeout=1):
        '''
        Returns packet data as a string, else None.
        @type timeout: Integer
        @param timeout: Timeout to wait for packet reception in usec
        @rtype: List
        @return: Returns None is timeout expires and no packet received.  When a packet is received,
                 a list is returned, in the form [ String: packet contents | Bool: Valid CRC | Int: Unscaled RSSI ]
        '''
        if not self.__stream_open:
            self.sniffer_on() #start sniffing

        self.handle.timeout=timeout         # Allow pySerial to handle timeout
        packet = self.handle.readline().strip()
        #print 'reading'
        if packet == '':
            return None   # Sense timeout case and return

        #print packet
        rssi, frame, validcrc = self.__dissect_pkt(packet)
        if not frame:
            print "Error parsing stream received from device:", packet

        # Parse received data as <rssi>!<time>!<packtlen>!<frame>
        try:
            rssi = int(rssi)
        except:
            print "Error parsing stream received from device:", packet
            return None
        #Return in a nicer dictionary format, so we don't have to reference by number indicies.
        #Note that 0,1,2 indicies inserted twice for backwards compatibility.
        result = {0:frame, 1:validcrc, 2:rssi, 'bytes':frame, 'validcrc':validcrc, 'rssi':rssi}
        result['dbm'] = None #TODO calculate dBm antenna signal based on RSSI formula
        result['datetime'] = datetime.utcnow() # TODO - see what time field in sniff is actually telling us
        result['location'] = (self.lon, self.lat, self.alt)
        return result

    def ping(self, da, panid, sa, channel=None, page=0):
        '''
        Not yet implemented.
        @return: None
        @rtype: None
        '''
        raise Exception('Not yet implemented')

    def jammer_on(self, channel=None, page=0):
        '''
        Not yet implemented.
        @type channel: Integer
        @param channel: Sets the channel, optional
        @rtype: None
        '''
        self.capabilities.require(KBCapabilities.PHYJAM)

        if channel != None or page:
            self.set_channel(channel, page)

        #TODO implement
        raise Exception('Not yet implemented')

    def jammer_off(self, channel=None, page=0):
        '''
        Not yet implemented.
        @return: None
        @rtype: None
        '''
        #TODO implement
        raise Exception('Not yet implemented')
Beispiel #25
0
class SL_NODETEST:
    def __init__(self, serialpath):
        '''
        Instantiates the KillerBee class for Silabs Node Test
        @param serialpath:  /dev/ttyACM* type serial port identifier
        @return: None
        @rtype: None
        '''
        self._channel = None
        self._page = 0
        self.handle = None
        self.dev = serialpath
        self.date = None
        self.lon, self.lat, self.alt = (None, None, None)
        self.__stream_open = False
        self.handle = serial.Serial(port=self.dev, baudrate=115200, \
                                    timeout=.1, bytesize=8, parity='N', stopbits=1, xonxoff=0)
        self.capabilities = KBCapabilities()
        self.__set_capabilities()

    def close(self):
        '''
        Closes the serial port. After closing, must reinitialize class again before use.
        @return: None
        @rtype: None
        '''
        self.handle.close()
        self.handle = None

    # KillerBee implements these, maybe it shouldn't and instead leave it to the driver as needed.
    # TODO deprecate these functions in favor of self.capabilities class functions
    def check_capability(self, capab):
        return self.capabilities.check(capab)

    def get_capabilities(self):
        return self.capabilities.getlist()

    def __set_capabilities(self):
        '''
        Sets the capability information for Silabs Node Test
        @rtype: None
        @return: None
        '''
        #NOTE frequencies will actually depend on radio daughterboard on the Silabs dev kit
        self.capabilities.setcapab(KBCapabilities.FREQ_2400, True)
        self.capabilities.setcapab(KBCapabilities.FREQ_915, True)
        self.capabilities.setcapab(KBCapabilities.FREQ_863, True)
        self.capabilities.setcapab(KBCapabilities.FREQ_868, True)
        self.capabilities.setcapab(KBCapabilities.FREQ_870, True)
        self.capabilities.setcapab(KBCapabilities.SNIFF, True)
        self.capabilities.setcapab(KBCapabilities.SETCHAN, True)
        return

    def get_dev_info(self):
        '''
        Returns device information in a list identifying the device.
        @rtype: List
        @return: List of 3 strings identifying device.
        '''
        return [self.dev, "Silabs Node Test", ""]

    def __send_cmd(self, cmdstr, arg=None, confirm=True, send_return=True):
        self.handle.flush()
        #time.sleep(1.5) #this delay seems crucial

        if arg != None:
            cmdstr += ' ' + arg
        self.handle.write(cmdstr)
        if send_return:
            self.handle.write('\r')
        #print "Sent %s" % (cmdstr)
        if confirm:
            ret = False
            for x in range(10):
                d = self.handle.readline().strip()
                #print 'got', d
                if d == '>':
                    ret = True
                    break
        else:
            ret = True

        if not ret:
            raise Exception('Could not send command %s' % cmdstr)

    def __dissect_pkt(self, packet):
        '''
        internal routine to deconstruct serial text received from device
        packet will be in the format: "{{num}   {oflo}  {seq}   {per}  {err} {lqi}  {rssi}{ed}   {gain}       {status} {time}       {fp}{length}{payload}}"
        @type packet: String
        @param: packet: The raw packet to dissect
        @rtype: List
        @return: Returns None if packet is not in correct format.  When a packet is correct,
                 a list is returned, in the form [ String: Frame | Bool: Valid CRC | Int: Unscaled RSSI ]
        '''
        data = packet[1:].replace('{', ' ').replace('}', ' ').split()
        # should be 12 fields + payload length + payload
        if not data or not len(data[13:]) == int(data[12], 16):
            print "Error parsing stream received from device (payload size error):", packet
            return None
        # payload is in the form e.g. "0x03 0x08 0xA3 0xFF 0xFF 0xFF 0xFF 0x07" so we need to convert to a string
        frame = ''
        for x in data[13:]:
            try:
                frame += chr(int(x, 16))
            except:
                print "Error parsing stream received from device (invalid payload):", packet
                return None

        # Parse other useful fields
        try:
            rssi = int(data[6])
            # sniffer doesn't give us the CRC so we must add it, but must be correct or we would not have received it
            validcrc = True
            frame += makeFCS(frame)
        except:
            print "Error parsing stream received from device (invalid rssi or FCS build error):", packet
            return None
        return [frame, validcrc, rssi]

    # KillerBee expects the driver to implement this function
    def sniffer_on(self, channel=None, page=0):
        '''
        Turns the sniffer on such that pnext() will start returning observed
        data.  Will set the command mode to Air Capture if it is not already
        set.
        @type channel: Integer
        @param channel: Sets the channel, optional
        @rtype: None
        '''
        self.capabilities.require(KBCapabilities.SNIFF)

        if channel != None or page:
            self.set_channel(channel, page)

        if not self.__stream_open:
            # make sure we get hex dump of RX packet
            self.__send_cmd("showPayload", "1")
            # start sniffer
            self.__send_cmd("rx", confirm=False)
            for x in range(10):
                d = self.handle.readline()
                #print 'got', d
                if '{payload}' in d:
                    self.mode = MODE_SNIFF
                    self.__stream_open = True
                    break

        if not self.__stream_open:
            raise Exception('Could not start sniffer')

    # KillerBee expects the driver to implement this function
    def sniffer_off(self):
        '''
        Turns the sniffer off, freeing the hardware for other functions.  It is
        not necessary to call this function before closing the interface with
        close().
        @rtype: None
        '''
        self.__send_cmd("e", send_return=False, confirm=False)
        for x in range(5):
            d = self.handle.readline().strip()
            #print 'got', d
            if "test end" in d:
                self.mode = MODE_NONE
                self.__stream_open = False
                break
        if self.__stream_open:
            raise Exception('Could not stop sniffer')

    # KillerBee expects the driver to implement this function
    def set_channel(self, channel, page=0):
        '''
        Sets the radio interface to the specifid channel (limited to 2.4 GHz channels 11-26)
        @type channel: Integer
        @param channel: Sets the channel, optional
        @type page: Integer
        @param page: Sets the subghz page
        @rtype: None
        '''
        self.capabilities.require(KBCapabilities.SETCHAN)

        #subghz channels must be calculated to include page (3 LSB page + 5 LSB channel)
        # e.g. page 28 channel 15 == '100' + '01111' = '10001111' = 0x8F
        self._channel = channel
        if page:
            channel = ((page << 5) & 0xff) + (channel & 0x1f)
        self._page = page
        #TODO actually check that it responds correctly to the request
        self.__send_cmd("setchannel", "%02x" % channel)

    # KillerBee expects the driver to implement this function
    def inject(self, packet, channel=None, count=1, delay=0, page=0):
        '''
        Injects the specified packet contents.
        @type packet: String
        @param packet: Packet contents to transmit, without FCS.
        @type channel: Integer
        @param channel: Sets the channel, optional
        @type count: Integer
        @param count: Transmits a specified number of frames, def=1
        @type delay: Float
        @param delay: Delay between each frame, def=1
        @rtype: None
        '''
        self.capabilities.require(KBCapabilities.INJECT)

        if len(packet) < 1:
            raise Exception('Empty packet')
        if len(packet) > 125:  # 127 - 2 to accommodate FCS
            raise Exception('Packet too long')

        if channel != None or page:
            self.set_channel(channel, page)

        # Append two bytes to be replaced with FCS by firmware.
        packet = ''.join([packet, "\x00\x00"])

        raise Exception('Not yet implemented')

    # KillerBee expects the driver to implement this function
    #TODO I suspect that if you don't call this often enough while getting frames, the serial buffer may overflow.
    def pnext(self, timeout=100):
        '''
        Returns packet data as a string, else None.
        @type timeout: Integer
        @param timeout: Timeout to wait for packet reception in usec
        @rtype: List
        @return: Returns None if timeout expires and no packet received or packet is corrupt.  When a packet is received,
                 a list is returned, in the form [ String: packet contents | Bool: Valid CRC | Int: Unscaled RSSI ]
        '''
        if not self.__stream_open:
            self.sniffer_on()  #start sniffing

        self.handle.timeout = timeout  # Allow pySerial to handle timeout
        packet = self.handle.readline().strip()
        if packet == '':
            return None  # Sense timeout case and return

        data = self.__dissect_pkt(packet)
        if not data:
            return None
        frame = data[0]
        validcrc = data[1]
        rssi = data[2]
        #Return in a nicer dictionary format, so we don't have to reference by number indicies.
        #Note that 0,1,2 indicies inserted twice for backwards compatibility.
        result = {
            0: frame,
            1: validcrc,
            2: rssi,
            'bytes': frame,
            'validcrc': validcrc,
            'rssi': rssi
        }
        result[
            'dbm'] = None  #TODO calculate dBm antenna signal based on RSSI formula
        result['datetime'] = datetime.utcnow(
        )  # TODO - see what time field in sniff is actually telling us
        result['location'] = (self.lon, self.lat, self.alt)
        return result

    def ping(self, da, panid, sa, channel=None, page=0):
        '''
        Not yet implemented.
        @return: None
        @rtype: None
        '''
        raise Exception('Not yet implemented')

    def jammer_on(self, channel=None, page=0):
        '''
        Not yet implemented.
        @type channel: Integer
        @param channel: Sets the channel, optional
        @rtype: None
        '''
        self.capabilities.require(KBCapabilities.PHYJAM)

        if channel != None or page:
            self.set_channel(channel, page)

        #TODO implement
        raise Exception('Not yet implemented')

    def jammer_off(self, channel=None, page=0):
        '''
        Not yet implemented.
        @return: None
        @rtype: None
        '''
        #TODO implement
        raise Exception('Not yet implemented')
Beispiel #26
0
class RZUSBSTICK:
    def __init__(self, dev, bus):
        """
        Instantiates the KillerBee class for the RZUSBSTICK hardware.

        @type dev:   String
        @param dev:  USB device identifier
        @type bus:   String
        @param bus:  Identifies the USB bus the device is on
        @return: None
        @rtype: None
        """
        self._channel = None
        self.handle = None
        self.dev = dev
        self.__bus = bus

        if self.dev != None:
            self.__handle_open()
        else:
            raise Exception("No interface found")

        # Tracking the command operating mode (None or AirCapture Mode)
        self.__cmdmode = RZ_CMD_MODE_NONE

        # Tracking if the RZ_CMD_OPEN_STREAM parameter is set for packet reception
        self.__stream_open = False

        # Capabilities list
        self.capabilities = KBCapabilities()
        self.__set_capabilities()

    def __handle_open(self):
        """
        Opens the device identified as self.dev, populating self.handle
        """
        try:
            config = self.dev.configurations[0]
            intf = config.interfaces[0]
            alt = intf[0]
            self.handle = self.dev.open()
            self.handle.reset()
            self.handle.setConfiguration(config)
            self.handle.claimInterface(alt)
            self.handle.setAltInterface(alt)
        except:
            raise Exception("Unable to open device. " + "Ensure the device is free and plugged-in. You may need sudo.")

    def close(self):
        """
        Closes the device handle.  To be re-used, class should be re-instantiated.
        @return: None
        @rtype: None
        """
        self.handle.releaseInterface()

    # KillerBee implements these, maybe it shouldn't and instead leave it to the driver as needed.
    def check_capability(self, capab):
        return self.capabilities.check(capab)

    def get_capabilities(self):
        return self.capabilities.getlist()

    def __set_capabilities(self):
        """
        Sets the capability information for RZUSB devices.
        @rtype: None
        @return: None
        """
        # Examine the product string for this device, setting the capability information appropriately.
        prod = self.dev.open().getString(self.dev.iProduct, 50)

        if prod == "RZUSBSTICK":
            self.capabilities.setcapab(KBCapabilities.SNIFF, True)
            self.capabilities.setcapab(KBCapabilities.SETCHAN, True)
            return
        elif prod == "KILLERB001":
            self.capabilities.setcapab(KBCapabilities.SNIFF, True)
            self.capabilities.setcapab(KBCapabilities.SETCHAN, True)
            self.capabilities.setcapab(KBCapabilities.INJECT, True)
            return
        else:
            return

    def get_dev_info(self):
        """
        Returns device information in a list identifying the device identifier,
        product string and serial number in a list of strings.
        @rtype: List
        @return: List of 3 strings identifying device.
        """
        return [
            "".join([self.__bus.dirname, ":", self.dev.filename]),
            self.dev.open().getString(self.dev.iProduct, 50),
            self.dev.open().getString(self.dev.iSerialNumber, 12),
        ]

    def __usb_write(self, endpoint, data):
        """
        Write data to the USB device opened as self.handle.
        
        @type endpoint: Integer
        @param endpoint: The USB endpoint to write to
        @type data: Mixed
        @param data: The data to send to the USB endpoint
        """
        try:
            self.handle.bulkWrite(endpoint, data)
            # Returns a tuple, first value is an int as the RZ_RESP_* code
            response = self.handle.bulkRead(RZ_USB_RESPONSE_EP, 1)[0]
        except usb.USBError, e:
            if e.args != ("No error",):  # http://bugs.debian.org/476796
                raise e
        time.sleep(0.0005)
        if response != RZ_RESP_SUCCESS:
            if response in RESPONSE_MAP:
                raise Exception("Error: %s" % RESPONSE_MAP[response])
            else:
                raise Exception("Unknown USB write error: 0x%02x" % response)
Beispiel #27
0
class FREAKDUINO:
    def __init__(self, serialpath):
        '''
        Instantiates the KillerBee class for our sketch running on ChibiArduino on Freakduino hardware.
        @param serialpath:  /dev/ttyUSB* type serial port identifier
        @return: None
        @rtype: None
        '''
        self._channel = None
        self._page = 0
        self.handle = None
        self.dev = serialpath
        self.date = None
        self.lon, self.lat, self.alt = (None, None, None)
        self.handle = serial.Serial(port=self.dev, baudrate=57600, \
                                    timeout=1, bytesize=8, parity='N', stopbits=1, xonxoff=0)
        self.capabilities = KBCapabilities()
        self.__set_capabilities()

    def close(self):
        '''
        Closes the serial port. After closing, must reinitialize class again before use.
        @return: None
        @rtype: None
        '''
        self.handle.close()
        self.handle = None

    # KillerBee implements these, maybe it shouldn't and instead leave it to the driver as needed.
    # TODO deprecate these functions in favor of self.capabilities class functions
    def check_capability(self, capab):
        return self.capabilities.check(capab)
    def get_capabilities(self):
        return self.capabilities.getlist()

    def __set_capabilities(self):
        '''
        Sets the capability information for Freakdruino device based on the currently loaded sketch.
        @rtype: None
        @return: None
        '''
        #TODO have it set based on what the sketch says it can support
        self.capabilities.setcapab(KBCapabilities.FREQ_2400, True)
        self.capabilities.setcapab(KBCapabilities.SNIFF, True)
        self.capabilities.setcapab(KBCapabilities.SETCHAN, True)
        return

    def get_dev_info(self):
        '''
        Returns device information in a list identifying the device.
        @rtype: List
        @return: List of 3 strings identifying device.
        '''
        return [self.dev, "Dartmouth Freakduino", ""]

    def __send_cmd(self, cmdstr, arg=None):
        self.handle.flush()
        time.sleep(1.5) #this delay seems crucial

        self.handle.write("S")
        self.handle.write(cmdstr)
        if arg != None: self.handle.write(arg)
        self.handle.write('\r')

        #print "Sent S%s" % (cmdstr)
        self.handle.flush()

    # Deprecated due to unreliability
    def __serial_cmd(self, cmdstr, arg=None):
        '''
        Sends a command over the self.conn serial connection.
        Ex: If provided cmdstr = "C!N" it will send "SC!N", telling the device to turn on sniffing ("N"),
        and it expects to receive a confirmation back "&C!N" to confirm success.
        '''
        print "Flushing out of buffer:", self.handle.inWaiting()
        self.handle.flushInput()
        if len(cmdstr) > 3:
            raise Exception("Command string is less than minimum length (S%s)." % cmdstr)
        self.__send_cmd(cmdstr, arg)

        # TODO ugly and unreliable:
        # This should just wait for a & and then parse things after it,
        # however it seems sometimes you have to resend the command or something.
        print "Line:", self.handle.readline(eol='&')
        counter = 0
        char = self.handle.read()
        while (char != '&'):
            print self.handle.inWaiting(), "Waiting...", char
            time.sleep(0.01)
            if (counter > 8):
                self.__send_cmd(cmdstr, arg)
                counter = 0
                print "Resend Response Line:", self.handle.readline(eol='&')
            else: counter += 1
            char = self.handle.read()
        response = ''
        for i in range(3):
            response += self.handle.read()

        if response == cmdstr[:3]:
            print "Got a response:", response, "matches", cmdstr
            return True
        else:
            print "Invalid response:", response, cmdstr[:3]
            return False

    # Send the command for the Dartmouth-mod Freakduino to dump data logged in EEPROM
    def eeprom_dump(self):
        self.__send_cmd("C!D")

    # KillerBee expects the driver to implement this function
    def sniffer_on(self, channel=None, page=0):
        '''
        Turns the sniffer on such that pnext() will start returning observed
        data.  Will set the command mode to Air Capture if it is not already
        set.
        @type channel: Integer
        @param channel: Sets the channel, optional
        @type page: Integer
        @param page: Sets the subghz page, not supported on this device
        @rtype: None
        '''
        self.capabilities.require(KBCapabilities.SNIFF)

        if channel != None:
            self.set_channel(channel, page)

        #TODO implement mode change to start sniffer sending packets to us
        self.__send_cmd("C!N")
        self.mode = MODE_SNIFF
        self.__stream_open = True

    # KillerBee expects the driver to implement this function
    def sniffer_off(self):
        '''
        Turns the sniffer off, freeing the hardware for other functions.  It is
        not necessary to call this function before closing the interface with
        close().
        @rtype: None
        '''
        self.__send_cmd("C!F")
        self.mode = MODE_NONE
        self.__stream_open = False

    # KillerBee expects the driver to implement this function
    def set_channel(self, channel, page=0):
        '''
        Sets the radio interface to the specifid channel (limited to 2.4 GHz channels 11-26)
        @type channel: Integer
        @param channel: Sets the channel, optional
        @type page: Integer
        @param page: Sets the subghz page, not supported on this device
        @rtype: None
        '''
        self.capabilities.require(KBCapabilities.SETCHAN)

        if channel >= 10 or channel <= 26:
            self._channel = channel
            #TODO actually check that it responds correctly to the request
            self.__send_cmd("C!C %d" % channel)
        else:
            raise Exception('Invalid channel')
        if page:
            raise Exception('SubGHz not supported')

    # KillerBee expects the driver to implement this function
    def inject(self, packet, channel=None, count=1, delay=0, page=0):
        '''
        Injects the specified packet contents.
        @type packet: String
        @param packet: Packet contents to transmit, without FCS.
        @type channel: Integer
        @param channel: Sets the channel, optional
        @type page: Integer
        @param page: Sets the subghz page, not supported on this device
        @type count: Integer
        @param count: Transmits a specified number of frames, def=1
        @type delay: Float
        @param delay: Delay between each frame, def=1
        @rtype: None
        '''
        self.capabilities.require(KBCapabilities.INJECT)

        if len(packet) < 1:
            raise Exception('Empty packet')
        if len(packet) > 125:                   # 127 - 2 to accommodate FCS
            raise Exception('Packet too long')

        if channel != None:
            self.set_channel(channel, page)

        # Append two bytes to be replaced with FCS by firmware.
        packet = ''.join([packet, "\x00\x00"])

        for pnum in range(0, count):
            raise Exception('Not yet implemented')
            # Format for packet is opcode CMD_INJECT_FRAME, one-byte length,  packet data
            #TODO RZ_USB_COMMAND_EP, struct.pack("B", RZ_CMD_INJECT_FRAME) + struct.pack("B", len(packet)) + packet)
            time.sleep(delay)

    # KillerBee expects the driver to implement this function
    #TODO I suspect that if you don't call this often enough while getting frames, the serial buffer may overflow.
    def pnext(self, timeout=100):
        '''
        Returns packet data as a string, else None.
        @type timeout: Integer
        @param timeout: Timeout to wait for packet reception in usec
        @rtype: List
        @return: Returns None is timeout expires and no packet received.  When a packet is received,
                 a list is returned, in the form [ String: packet contents | Bool: Valid CRC | Int: Unscaled RSSI ]
        '''
        if self.__stream_open == False:
            self.sniffer_on() #start sniffing
        return self.pnext_rec(timeout)

    # Bulk of pnext implementation, but does not ensure the sniffer is on first, thus usable for EEPROM reading
    def pnext_rec(self, timeout=100):
        pdata = ''
        ldata = ''

        self.handle.timeout=timeout         # Allow pySerial to handle timeout
        startChar = self.handle.read()
        if startChar == None: return None   # Sense timeout case and return

        # Listens for serial message of general format R!<data>;
        if startChar == "R":                # Get packet data
            if self.handle.read() == "!":
                x = self.handle.read()
                while (x != ";"):
                    pdata += x
                    x = self.handle.read()
        if startChar == "L":                # Get location data
            if self.handle.read() == "!":
                x = self.handle.read()
                while (x != ";"):
                    ldata += x
                    x = self.handle.read()
        if startChar == "[":                # Sense when done reading from EEPROM
            if self.handle.read(40) == "{[ DONE READING BACK ALL LOGGED DATA ]}]":
                raise StopIteration("All Data Read")

        # If location received, update our local variables:
        if ldata != None and ldata != '':
            self.processLocationUpdate(ldata)

        if pdata == None or pdata == '':
            return None

        # Parse received data as <rssi>!<time>!<packtlen>!<frame>
        data = pdata.split("!", 3)
        try:
            rssi = ord(data[0])
            frame = data[3]
            if frame[-2:] == makeFCS(frame[:-2]): validcrc = True
            else: validcrc = False
        except:
            print "Error parsing stream received from device:", pdata, data
            return None
        #Return in a nicer dictionary format, so we don't have to reference by number indicies.
        #Note that 0,1,2 indicies inserted twice for backwards compatibility.
        result = {0:frame, 1:validcrc, 2:rssi, 'bytes':frame, 'validcrc':validcrc, 'rssi':rssi}
        result['dbm'] = None #TODO calculate dBm antenna signal based on RSSI formula
        result['datetime'] = self.getCaptureDateTime(data)
        result['location'] = (self.lon, self.lat, self.alt)
        return result

    def getCaptureDateTime(self, data):
        try:
            timestr = "%08d" % (struct.unpack('L', data[1])[0]) #in format hhmmsscc
            time = dttime(int(timestr[:2]), int(timestr[2:4]), int(timestr[4:6]), int(timestr[6:]))
        except:
            print "Issue with time format:", timestr, data
            time = None
        if self.date == None: self.date = date.utcnow().date()
        if time == None or time == dttime.min: time = (datetime.utcnow()).time()
        #TODO address timezones by going to UTC everywhere
        return datetime.combine(self.date, time)

    def processLocationUpdate(self, ldata):
        '''
        Take a location string passed from the device and update the driver's internal state of last received location.
        Format of ldata: longlatialtidate
        '''
        self.lon = struct.unpack('l', ldata[0:4])[0]
        self.lat = struct.unpack('l', ldata[4:8])[0]
        self.alt = struct.unpack('l', ldata[8:12])[0]
        date = str(struct.unpack('L', ldata[12:16])[0])
        self.date = datetime.date(date[-2:], date[-4:-2], date[:-4])
        #TODO parse data formats (lon=-7228745 lat=4370648 alt=3800 age=63 date=70111 time=312530)
        print self.lon, self.lat, self.alt, self.date

    def ping(self, da, panid, sa, channel=None, page=0):
        '''
        Not yet implemented.
        @return: None
        @rtype: None
        '''
        raise Exception('Not yet implemented')

    def jammer_on(self, channel=None, page=0):
        '''
        Not yet implemented.
        @type channel: Integer
        @param channel: Sets the channel, optional
        @type page: Integer
        @param page: Sets the subghz page, not supported on this device
        @rtype: None
        '''
        self.capabilities.require(KBCapabilities.PHYJAM)

        if self.__cmdmode != RZ_CMD_MODE_AC:
            self._set_mode(RZ_CMD_MODE_AC)

        if channel != None:
            self.set_channel(channel, page)

        #TODO implement
        raise Exception('Not yet implemented')

    def jammer_off(self, channel=None, page=0):
        '''
        Not yet implemented.
        @return: None
        @rtype: None
        '''
        #TODO implement
        raise Exception('Not yet implemented')