Exemplo n.º 1
0
    def __init__(self, arguments, thread_manager, location_callback,
                 receive_callback, valid_callback):
        """
        Initialize the RF sensor.

        The `arguments` parameter is used to load settings for a specific RF
        sensor type. The sensor has a `thread_manager`, which is a `Thread_Manager`
        object for registering its own thread loop. Additionally, it requires
        certian callbacks. The `location_callback` is called whenever the sensor
        needs to know its own location for the "rssi_broadcast" and the
        "rssi_ground_station" private packets. The `receive_callback` is called
        whenever non-private packets are received and has the `Packet` object
        as an argument. Finally, the `valid_callback` is called shortly after
        the `location_callback` is called. It may be given a boolean argument
        indicating whether another RF sensor has a valid location, but only when
        creating the "rssi_ground_station" private packet. This is used by the
        callback to determine if measurements at a certain location are finished.

        Classes that inherit this base class may extend this method.
        """

        super(RF_Sensor, self).__init__("rf_sensor", thread_manager)

        # Make sure that the provided callbacks are callable.
        for callback in [location_callback, receive_callback, valid_callback]:
            if not hasattr(callback, "__call__"):
                raise TypeError("Provided RF sensor callback is not callable")

        # Load settings for a specific RF sensor type.
        if isinstance(arguments, Arguments):
            self._settings = arguments.get_settings(self.type)
        else:
            raise ValueError("'arguments' must be an instance of Arguments")

        # Initialize common member variables.
        self._id = self._settings.get("rf_sensor_id")
        self._number_of_sensors = self._settings.get("number_of_sensors")
        self._address = None
        self._connection = None
        self._buffer = None
        self._scheduler = TDMA_Scheduler(self._id, arguments)
        self._packets = []
        self._queue = Queue.Queue()

        self._joined = False
        self._activated = False
        self._started = False

        self._loop_delay = self._settings.get("loop_delay")

        self._location_callback = location_callback
        self._receive_callback = receive_callback
        self._valid_callback = valid_callback
Exemplo n.º 2
0
    def __init__(self, arguments, thread_manager, location_callback,
                 receive_callback, valid_callback):
        """
        Initialize the RF sensor.

        The `arguments` parameter is used to load settings for a specific RF
        sensor type. The sensor has a `thread_manager`, which is a `Thread_Manager`
        object for registering its own thread loop. Additionally, it requires
        certian callbacks. The `location_callback` is called whenever the sensor
        needs to know its own location for the "rssi_broadcast" and the
        "rssi_ground_station" private packets. The `receive_callback` is called
        whenever non-private packets are received and has the `Packet` object
        as an argument. Finally, the `valid_callback` is called shortly after
        the `location_callback` is called. It may be given a boolean argument
        indicating whether another RF sensor has a valid location, but only when
        creating the "rssi_ground_station" private packet. This is used by the
        callback to determine if measurements at a certain location are finished.

        Classes that inherit this base class may extend this method.
        """

        super(RF_Sensor, self).__init__("rf_sensor", thread_manager)

        # Make sure that the provided callbacks are callable.
        for callback in [location_callback, receive_callback, valid_callback]:
            if not hasattr(callback, "__call__"):
                raise TypeError("Provided RF sensor callback is not callable")

        # Load settings for a specific RF sensor type.
        if isinstance(arguments, Arguments):
            self._settings = arguments.get_settings(self.type)
        else:
            raise ValueError("'arguments' must be an instance of Arguments")

        # Initialize common member variables.
        self._id = self._settings.get("rf_sensor_id")
        self._number_of_sensors = self._settings.get("number_of_sensors")
        self._address = None
        self._connection = None
        self._buffer = None
        self._scheduler = TDMA_Scheduler(self._id, arguments)
        self._packets = []
        self._queue = Queue.Queue()

        self._joined = False
        self._activated = False
        self._started = False

        self._loop_delay = self._settings.get("loop_delay")

        self._location_callback = location_callback
        self._receive_callback = receive_callback
        self._valid_callback = valid_callback
Exemplo n.º 3
0
class RF_Sensor(Threadable):
    """
    Base class for all RF sensors.

    This class is responsible for setting up the basic characteristics of an
    RF sensor and contains common code for the simulated and physical
    specializations.
    """
    def __init__(self, arguments, thread_manager, location_callback,
                 receive_callback, valid_callback):
        """
        Initialize the RF sensor.

        The `arguments` parameter is used to load settings for a specific RF
        sensor type. The sensor has a `thread_manager`, which is a `Thread_Manager`
        object for registering its own thread loop. Additionally, it requires
        certian callbacks. The `location_callback` is called whenever the sensor
        needs to know its own location for the "rssi_broadcast" and the
        "rssi_ground_station" private packets. The `receive_callback` is called
        whenever non-private packets are received and has the `Packet` object
        as an argument. Finally, the `valid_callback` is called shortly after
        the `location_callback` is called. It may be given a boolean argument
        indicating whether another RF sensor has a valid location, but only when
        creating the "rssi_ground_station" private packet. This is used by the
        callback to determine if measurements at a certain location are finished.

        Classes that inherit this base class may extend this method.
        """

        super(RF_Sensor, self).__init__("rf_sensor", thread_manager)

        # Make sure that the provided callbacks are callable.
        for callback in [location_callback, receive_callback, valid_callback]:
            if not hasattr(callback, "__call__"):
                raise TypeError("Provided RF sensor callback is not callable")

        # Load settings for a specific RF sensor type.
        if isinstance(arguments, Arguments):
            self._settings = arguments.get_settings(self.type)
        else:
            raise ValueError("'arguments' must be an instance of Arguments")

        # Initialize common member variables.
        self._id = self._settings.get("rf_sensor_id")
        self._number_of_sensors = self._settings.get("number_of_sensors")
        self._address = None
        self._connection = None
        self._buffer = None
        self._scheduler = TDMA_Scheduler(self._id, arguments)
        self._packets = []
        self._queue = Queue.Queue()

        self._joined = False
        self._activated = False
        self._started = False

        self._loop_delay = self._settings.get("loop_delay")

        self._location_callback = location_callback
        self._receive_callback = receive_callback
        self._valid_callback = valid_callback

    @property
    def id(self):
        """
        Get the ID of the RF sensor.
        """

        return self._id

    @property
    def number_of_sensors(self):
        """
        Get the number of sensors in the network.
        """

        return self._number_of_sensors

    @property
    def buffer(self):
        """
        Get the buffer of the RF sensor.
        """

        return self._buffer

    @buffer.setter
    def buffer(self, buffer):
        """
        Set the buffer.

        The `buffer` argument must be a `Buffer` object.
        """

        if not isinstance(buffer, Buffer):
            raise ValueError("The `buffer` argument must be a `Buffer` object")

        self._buffer = buffer

    @property
    def type(self):
        raise NotImplementedError(
            "Subclasses must implement the `type` property")

    @property
    def identity(self):
        """
        Get the identity of the RF sensor, consisting of its ID, address and
        network join status.

        Classes that inherit this base class may extend this property.
        """

        return {
            "id": self._id,
            "address": self._address,
            "joined": self._joined
        }

    def activate(self):
        """
        Activate the sensor to start sending and receiving packets.

        Classes that inherit this base class may extend this method.
        """

        super(RF_Sensor, self).activate()

        if not self._activated:
            self._activated = True

            if self._connection is None:
                self._setup()

            thread.start_new_thread(self._loop, ())

    def deactivate(self):
        """
        Deactivate the sensor to stop sending and receiving packets.

        Classes that inherit this base class may extend this method.
        """

        super(RF_Sensor, self).deactivate()

        if self._activated:
            self._activated = False

        if self._connection is not None:
            # Close the connection and clean up so that the thread might get
            # the signal faster and we can correctly reactivate later on.
            self._connection.close()
            self._connection = None

    def start(self):
        """
        Start the signal strength measurements (and stop sending custom packets).

        Classes that inherit this base class may extend this method.
        """

        self._scheduler.update()
        self._packets = []
        self._started = True

    def stop(self):
        """
        Stop the signal strength measurements (and start sending custom packets).
        """

        self._started = False

        # Reset the scheduler timestamp so that it updates correctly in case we
        # restart the sensor measurements.
        self._scheduler.timestamp = 0

    def enqueue(self, packet, to=None):
        """
        Enqueue a custom `packet` to send `to` another RF sensor in the network.
        """

        if not isinstance(packet, Packet):
            raise TypeError("Only `Packet` objects can be enqueued")

        if packet.is_private():
            raise ValueError("Private packets cannot be enqueued")

        if to is None:
            # No destination ID has been provided, so we broadcast the packet to
            # all sensors in the network except for ourself and the ground station.
            for to_id in xrange(1, self._number_of_sensors + 1):
                if to_id == self._id:
                    continue

                self._queue.put({"packet": copy.deepcopy(packet), "to": to_id})
        else:
            self._queue.put({"packet": packet, "to": to})

    def discover(self, callback, required_sensors=None):
        """
        Discover RF sensors in the network. The `callback` callable function is
        called when an RF sensor reports its identity. The `required_sensors`
        set indicates which sensors should be discovered; if it is not
        provided, then all RF sensors are discovered. Note that discovery may
        fail due to interference or disabled sensors.

        Classes that inherit this base class must extend this method.
        """

        if not hasattr(callback, "__call__"):
            raise TypeError("Provided discovery callback is not callable")

        if isinstance(required_sensors, set):
            if not required_sensors.issubset(
                    range(1, self._number_of_sensors + 1)):
                raise ValueError(
                    "Provided required sensors may only contain vehicle sensors"
                )
        elif required_sensors is not None:
            raise TypeError("Provided required sensors must be a `set`")

    def _setup(self):
        raise NotImplementedError("Subclasses must implement `_setup()`")

    def _loop(self):
        """
        Execute the sensor loop. This runs in a separate thread.
        """

        try:
            while self._activated:
                self._loop_body()
        except DisabledException:
            return
        except:
            super(RF_Sensor, self).interrupt()

    def _loop_body(self):
        """
        Body of the sensor loop.

        This is extracted into a separate method to make testing easier, as well
        as for keeping the `_loop` implementation in the base class.

        Classes that inherit this base class must extend this method.
        """

        # If the sensor has been activated, we only send enqueued custom packets.
        # If the sensor has been started, we stop sending custom packets and
        # start performing signal strength measurements.
        if not self._started:
            self._send_custom_packets()
        elif self._id > 0 and time.time() >= self._scheduler.timestamp:
            self._send()
            self._scheduler.update()

        time.sleep(self._loop_delay)

    def _send(self):
        """
        Send a broadcast packet to each other sensor in the network and
        send collected packets to the ground station.

        Classes that inherit this base class may extend this method.
        """

        # Create and send the RSSI broadcast packets.
        packet = self._create_rssi_broadcast_packet()
        for to_id in xrange(1, self._number_of_sensors + 1):
            if to_id == self._id:
                continue

            self._send_tx_frame(packet, to_id)

        # Send collected packets to the ground station.
        for packet in self._packets:
            self._send_tx_frame(packet, 0)

        self._packets = []

    def _send_custom_packets(self):
        """
        Send custom packets to their destinations.
        """

        while not self._queue.empty():
            item = self._queue.get()
            self._send_tx_frame(item["packet"], item["to"])

    def _send_tx_frame(self, packet, to=None):
        """
        Send a TX frame with `packet` as payload `to` another sensor.

        Classes that inherit this base class must extend this method.
        """

        if self._connection is None:
            raise DisabledException

        if not isinstance(packet, Packet):
            raise TypeError("Only `Packet` objects can be sent")

        if to is None:
            raise TypeError(
                "Invalid destination '{}' has been provided".format(to))

    def _receive(self, packet=None):
        raise NotImplementedError(
            "Subclasses must implement `_receive(packet=None)`")

    def _create_rssi_broadcast_packet(self):
        """
        Create a `Packet` object according to the "rssi_broadcast" specification.
        The resulting packet is complete.
        """

        location, waypoint_index = self._location_callback()

        packet = Packet()
        packet.set("specification", "rssi_broadcast")
        packet.set("latitude", location[0])
        packet.set("longitude", location[1])
        packet.set("valid", self._valid_callback())
        packet.set("waypoint_index", waypoint_index)
        packet.set("sensor_id", self._id)
        packet.set("timestamp", time.time())

        return packet

    def _create_rssi_ground_station_packet(self, rssi_broadcast_packet):
        """
        Create a `Packet` object according to the "rssi_ground_station"
        specification using data from an `rssi_broadcast_packet`. The
        `rssi_broadcast_packet` must be created according to the
        "rssi_broadcast" specification.

        The resulting packet is only missing RSSI information.

        The packet can be sent to the ground station as an indication of the
        signal strength between the RF sensor that sent the `rssi_broadcast_packet`
        and the current RF sensor.
        """

        from_valid = rssi_broadcast_packet.get("valid")
        from_id = rssi_broadcast_packet.get("sensor_id")
        from_waypoint_index = rssi_broadcast_packet.get("waypoint_index")

        location = self._location_callback()[0]
        location_valid = self._valid_callback(other_valid=from_valid,
                                              other_id=from_id,
                                              other_index=from_waypoint_index)

        packet = Packet()
        packet.set("specification", "rssi_ground_station")
        packet.set("sensor_id", self._id)
        packet.set("from_latitude", rssi_broadcast_packet.get("latitude"))
        packet.set("from_longitude", rssi_broadcast_packet.get("longitude"))
        packet.set("from_valid", from_valid)
        packet.set("to_latitude", location[0])
        packet.set("to_longitude", location[1])
        packet.set("to_valid", location_valid)

        return packet
Exemplo n.º 4
0
class RF_Sensor(Threadable):
    """
    Base class for all RF sensors.

    This class is responsible for setting up the basic characteristics of an
    RF sensor and contains common code for the simulated and physical
    specializations.
    """

    def __init__(self, arguments, thread_manager, location_callback,
                 receive_callback, valid_callback):
        """
        Initialize the RF sensor.

        The `arguments` parameter is used to load settings for a specific RF
        sensor type. The sensor has a `thread_manager`, which is a `Thread_Manager`
        object for registering its own thread loop. Additionally, it requires
        certian callbacks. The `location_callback` is called whenever the sensor
        needs to know its own location for the "rssi_broadcast" and the
        "rssi_ground_station" private packets. The `receive_callback` is called
        whenever non-private packets are received and has the `Packet` object
        as an argument. Finally, the `valid_callback` is called shortly after
        the `location_callback` is called. It may be given a boolean argument
        indicating whether another RF sensor has a valid location, but only when
        creating the "rssi_ground_station" private packet. This is used by the
        callback to determine if measurements at a certain location are finished.

        Classes that inherit this base class may extend this method.
        """

        super(RF_Sensor, self).__init__("rf_sensor", thread_manager)

        # Make sure that the provided callbacks are callable.
        for callback in [location_callback, receive_callback, valid_callback]:
            if not hasattr(callback, "__call__"):
                raise TypeError("Provided RF sensor callback is not callable")

        # Load settings for a specific RF sensor type.
        if isinstance(arguments, Arguments):
            self._settings = arguments.get_settings(self.type)
        else:
            raise ValueError("'arguments' must be an instance of Arguments")

        # Initialize common member variables.
        self._id = self._settings.get("rf_sensor_id")
        self._number_of_sensors = self._settings.get("number_of_sensors")
        self._address = None
        self._connection = None
        self._buffer = None
        self._scheduler = TDMA_Scheduler(self._id, arguments)
        self._packets = []
        self._queue = Queue.Queue()

        self._joined = False
        self._activated = False
        self._started = False

        self._loop_delay = self._settings.get("loop_delay")

        self._location_callback = location_callback
        self._receive_callback = receive_callback
        self._valid_callback = valid_callback

    @property
    def id(self):
        """
        Get the ID of the RF sensor.
        """

        return self._id

    @property
    def number_of_sensors(self):
        """
        Get the number of sensors in the network.
        """

        return self._number_of_sensors

    @property
    def buffer(self):
        """
        Get the buffer of the RF sensor.
        """

        return self._buffer

    @buffer.setter
    def buffer(self, buffer):
        """
        Set the buffer.

        The `buffer` argument must be a `Buffer` object.
        """

        if not isinstance(buffer, Buffer):
            raise ValueError("The `buffer` argument must be a `Buffer` object")

        self._buffer = buffer

    @property
    def type(self):
        raise NotImplementedError("Subclasses must implement the `type` property")

    @property
    def identity(self):
        """
        Get the identity of the RF sensor, consisting of its ID, address and
        network join status.

        Classes that inherit this base class may extend this property.
        """

        return {
            "id": self._id,
            "address": self._address,
            "joined": self._joined
        }

    def activate(self):
        """
        Activate the sensor to start sending and receiving packets.

        Classes that inherit this base class may extend this method.
        """

        super(RF_Sensor, self).activate()

        if not self._activated:
            self._activated = True

            if self._connection is None:
                self._setup()

            thread.start_new_thread(self._loop, ())

    def deactivate(self):
        """
        Deactivate the sensor to stop sending and receiving packets.

        Classes that inherit this base class may extend this method.
        """

        super(RF_Sensor, self).deactivate()

        if self._activated:
            self._activated = False

        if self._connection is not None:
            # Close the connection and clean up so that the thread might get 
            # the signal faster and we can correctly reactivate later on.
            self._connection.close()
            self._connection = None

    def start(self):
        """
        Start the signal strength measurements (and stop sending custom packets).

        Classes that inherit this base class may extend this method.
        """

        self._scheduler.update()
        self._packets = []
        self._started = True

    def stop(self):
        """
        Stop the signal strength measurements (and start sending custom packets).
        """

        self._started = False

        # Reset the scheduler timestamp so that it updates correctly in case we 
        # restart the sensor measurements.
        self._scheduler.timestamp = 0

    def enqueue(self, packet, to=None):
        """
        Enqueue a custom `packet` to send `to` another RF sensor in the network.
        """

        if not isinstance(packet, Packet):
            raise TypeError("Only `Packet` objects can be enqueued")

        if packet.is_private():
            raise ValueError("Private packets cannot be enqueued")

        if to is None:
            # No destination ID has been provided, so we broadcast the packet to
            # all sensors in the network except for ourself and the ground station.
            for to_id in xrange(1, self._number_of_sensors + 1):
                if to_id == self._id:
                    continue

                self._queue.put({
                    "packet": copy.deepcopy(packet),
                    "to": to_id
                })
        else:
            self._queue.put({
                "packet": packet,
                "to": to
            })

    def discover(self, callback, required_sensors=None):
        """
        Discover RF sensors in the network. The `callback` callable function is
        called when an RF sensor reports its identity. The `required_sensors`
        set indicates which sensors should be discovered; if it is not
        provided, then all RF sensors are discovered. Note that discovery may
        fail due to interference or disabled sensors.

        Classes that inherit this base class must extend this method.
        """

        if not hasattr(callback, "__call__"):
            raise TypeError("Provided discovery callback is not callable")

        if isinstance(required_sensors, set):
            if not required_sensors.issubset(range(1, self._number_of_sensors + 1)):
                raise ValueError("Provided required sensors may only contain vehicle sensors")
        elif required_sensors is not None:
            raise TypeError("Provided required sensors must be a `set`")

    def _setup(self):
        raise NotImplementedError("Subclasses must implement `_setup()`")

    def _loop(self):
        """
        Execute the sensor loop. This runs in a separate thread.
        """

        try:
            while self._activated:
                self._loop_body()
        except DisabledException:
            return
        except:
            super(RF_Sensor, self).interrupt()

    def _loop_body(self):
        """
        Body of the sensor loop.

        This is extracted into a separate method to make testing easier, as well
        as for keeping the `_loop` implementation in the base class.

        Classes that inherit this base class must extend this method.
        """

        # If the sensor has been activated, we only send enqueued custom packets.
        # If the sensor has been started, we stop sending custom packets and
        # start performing signal strength measurements.
        if not self._started:
            self._send_custom_packets()
        elif self._id > 0 and time.time() >= self._scheduler.timestamp:
            self._send()
            self._scheduler.update()

        time.sleep(self._loop_delay)

    def _send(self):
        """
        Send a broadcast packet to each other sensor in the network and
        send collected packets to the ground station.

        Classes that inherit this base class may extend this method.
        """

        # Create and send the RSSI broadcast packets.
        packet = self._create_rssi_broadcast_packet()
        for to_id in xrange(1, self._number_of_sensors + 1):
            if to_id == self._id:
                continue

            self._send_tx_frame(packet, to_id)

        # Send collected packets to the ground station.
        for packet in self._packets:
            self._send_tx_frame(packet, 0)

        self._packets = []

    def _send_custom_packets(self):
        """
        Send custom packets to their destinations.
        """

        while not self._queue.empty():
            item = self._queue.get()
            self._send_tx_frame(item["packet"], item["to"])

    def _send_tx_frame(self, packet, to=None):
        """
        Send a TX frame with `packet` as payload `to` another sensor.

        Classes that inherit this base class must extend this method.
        """

        if self._connection is None:
            raise DisabledException

        if not isinstance(packet, Packet):
            raise TypeError("Only `Packet` objects can be sent")

        if to is None:
            raise TypeError("Invalid destination '{}' has been provided".format(to))

    def _receive(self, packet=None):
        raise NotImplementedError("Subclasses must implement `_receive(packet=None)`")

    def _create_rssi_broadcast_packet(self):
        """
        Create a `Packet` object according to the "rssi_broadcast" specification.
        The resulting packet is complete.
        """

        location, waypoint_index = self._location_callback()

        packet = Packet()
        packet.set("specification", "rssi_broadcast")
        packet.set("latitude", location[0])
        packet.set("longitude", location[1])
        packet.set("valid", self._valid_callback())
        packet.set("waypoint_index", waypoint_index)
        packet.set("sensor_id", self._id)
        packet.set("timestamp", time.time())

        return packet

    def _create_rssi_ground_station_packet(self, rssi_broadcast_packet):
        """
        Create a `Packet` object according to the "rssi_ground_station"
        specification using data from an `rssi_broadcast_packet`. The
        `rssi_broadcast_packet` must be created according to the
        "rssi_broadcast" specification.

        The resulting packet is only missing RSSI information.

        The packet can be sent to the ground station as an indication of the
        signal strength between the RF sensor that sent the `rssi_broadcast_packet`
        and the current RF sensor.
        """

        from_valid = rssi_broadcast_packet.get("valid")
        from_id = rssi_broadcast_packet.get("sensor_id")
        from_waypoint_index = rssi_broadcast_packet.get("waypoint_index")

        location = self._location_callback()[0]
        location_valid = self._valid_callback(other_valid=from_valid,
                                              other_id=from_id,
                                              other_index=from_waypoint_index)

        packet = Packet()
        packet.set("specification", "rssi_ground_station")
        packet.set("sensor_id", self._id)
        packet.set("from_latitude", rssi_broadcast_packet.get("latitude"))
        packet.set("from_longitude", rssi_broadcast_packet.get("longitude"))
        packet.set("from_valid", from_valid)
        packet.set("to_latitude", location[0])
        packet.set("to_longitude", location[1])
        packet.set("to_valid", location_valid)

        return packet