Example #1
0
class Sensor:
    """
    Defines sensor logic.
    It reads the distance of object continuously and calls services on the server on different events to notify server.
    Events are :
    * OBJECT_WITHIN_THRESHOLD1 : Notify server about an object which just showed up in the not-very-close distance.
    * OBJECT_WITHIN_THRESHOLD2 : Notify server about an object which just showed up in the close distance.

    It is smart enough to not to send same event over and over again. Also, if there is a very close event for a very
    long time, it doesn't broadcast the event to the server.
    """

    def __init__(self):
        if MOCK_HARDWARE:
            from hardware_mock import HardwareMock

            self._hardware = HardwareMock()
        else:
            from hardware import Hardware

            self._hardware = Hardware()

        if MOCK_HEARTBEAT:
            self._heartbeat = HeartbeatMock()
        else:
            self._heartbeat = Heartbeat()

        if MOCK_DATA:
            self._sensor_service = SensorServiceMock()
            self._registration_service = RegistrationServiceMock()
        else:
            self._sensor_service = SensorService()
            self._registration_service = RegistrationService()

    @staticmethod
    def _current_time_in_millis():
        # see http://stackoverflow.com/questions/5998245/get-current-time-in-milliseconds-in-python
        # about getting the current time in millis
        return int(round(time.time() * 1000))

    def _start(self):
        log.info("Starting program...")

        # first of all, check all the required settings
        # noinspection PyBroadException
        try:
            self._sensor_info = self._registration_service.get_sensor_info_sync()
        except:
            log.exception("Unable to register! Exiting program")
            return

        if not self._sensor_info:
            log.critical("Unable to get sensor info. Exiting program!")

        # give the token to services
        self._sensor_service.set_token(self._sensor_info.token)
        self._heartbeat.set_token(self._sensor_info.token)

        # since server settings are all good, send a heartbeat about starting the sensor program
        self._heartbeat.sendSync(Constants.HEARTBEAT_STARTING)

        # initialize hardware.
        # noinspection PyBroadException
        try:
            log.info("Initializing hardware GPIO...")
            self._hardware.initialize_gpio()
        except:
            # do not care about clean up, since hardware does it itself
            log.exception("Unable to initialize hardware GPIO. Exiting program!")
            # send heartbeat die with message
            self._heartbeat.sendSync(Constants.HEARTBEAT_DIE, "Unable to initialize GPIO.", sys.exc_info())
            return

        # send the sync heartbeat afterwards to not to mix GPIO initialization exceptions with heartbeat exceptions
        self._heartbeat.sendSync(Constants.HEARTBEAT_GPIO_INITIALIZED)

        # start heartbeat here
        self._heartbeat.start_heartbeat_thread()

        previous_broadcasted_event_type = None
        previous_broadcasted_event_time = 0

        # start measuring
        while True:
            event_type = None
            try:
                distance = self._hardware.measure()
            except:
                # do not care about clean up, since hardware itself does it
                # update heartbeat status so that server knows there is something wrong with the measurement
                self._heartbeat.sendSync(Constants.HEARTBEAT_DIE, "Error during measure.", sys.exc_info())
                # since long time if that is the case
                log.exception("Error during measurement!")
                # re-raise and eventually exit the program
                raise

            if distance < self._sensor_info.threshold1:
                if distance >= self._sensor_info.threshold2:
                    # we have event type 1
                    event_type = Constants.EVENT_TYPE_OBJECT_WITHIN_THRESHOLD1
                    log.info("Object found between threshold1 and threshold2 : {} cm".format(distance))
                else:
                    # we might have event type 2. but need to check if object is too close
                    if distance <= Constants.TOO_CLOSE_DISTANCE_THRESHOLD:
                        # ignore
                        log.info("Object is too close : {}".format(distance))
                    else:
                        # we have event type 2
                        log.info("Object is withing threshold2 and it is not too close : {} cm".format(distance))
                        event_type = Constants.EVENT_TYPE_OBJECT_WITHIN_THRESHOLD2
            else:
                # ignore the object since it is too far away
                log.info("Object is too far away : {} cm".format(distance))
                pass

            # do not broadcast the event every time!
            # broadcast the event if it is new.
            # new means:
            # last broadcasted event is from a different type
            # OR
            # it has been N seconds since last broadcasted event
            send_broadcast = False

            if not event_type:
                send_broadcast = False
            else:
                if previous_broadcasted_event_type != event_type:
                    send_broadcast = True
                else:
                    elapsed_time_since_last_broadcast = Sensor._current_time_in_millis() - previous_broadcasted_event_time
                    if elapsed_time_since_last_broadcast > self._sensor_info.seconds_to_ignore_same_events * 1000:
                        send_broadcast = True
                    else:
                        log.info(
                            "Not broadcasting the event since same type is broadcasted very recently : " + event_type)

            # noinspection PyBroadException
            try:
                if send_broadcast:
                    log.info("Gonna broadcast event : " + event_type)
                    self._sensor_service.broadcast_event(event_type)
            except:
                log.exception("Error broadcasting event : " + event_type)
                # do nothing. continue with the next measurement

            # sleep some time before measuring again
            if send_broadcast:
                previous_broadcasted_event_type = event_type
                previous_broadcasted_event_time = Sensor._current_time_in_millis()
                # if we do broadcast, then sleep less since some time will be gone during the REST call
                time.sleep(Constants.SLEEP_TIME_BEFORE_NEXT_MEASUREMENT_AFTER_BROADCAST)
            else:
                # sleep more
                time.sleep(Constants.SLEEP_TIME_BEFORE_NEXT_MEASUREMENT_NO_BROADCAST)

    def _on_exit(self):
        # try to clean up anyway. it is safe to do that over and over again
        self._hardware.clean_up()
        # stop heartbeat thread so that we don't send heartbeats anymore
        self._heartbeat.stop_heartbeat_thread()
        # send heartbeat die
        self._heartbeat.sendSync(Constants.HEARTBEAT_DIE, "Exiting program")

    def start_program(self):
        # noinspection PyBroadException
        try:
            self._start()
        except:
            # catch all unhandled exceptions
            # that means, program wanted to terminate
            log.exception("Program didn't handle the exception. Probably it wanted termination.")
            self._on_exit()