Esempio n. 1
0
def heating(temperature, thermostat_temp):
    # red = [255, 0, 0]
    # black = [0, 0, 0]

    pin = 16
    led = LED(rpi, pin)
    if thermostat_temp <= temperature:
        # sense.led_2(black)
        led.turn_off()
        return "OFF"
    else:
        # sense.led_2(red)
        led.turn_on()
        return "ON"
Esempio n. 2
0
def led_on_demo():
    pin_number = 18  # edit pin number
    led = LED(rpi, pin_number)
    led.turn_on()
    sleep(2)
    led.turn_off()
class System:
    # __init__ is the class constructor and is also where you must define your instance variables
    def __init__(self, pin: str = '123456'):
        # system starts disarmed, the '_' in front on the name indicates that this variable is supposed to be private
        # python does not have any other way to differentiate between public and private. This same annotation is also
        # used for private functions
        # The 'self' indicates that this variable is an instance variable much like 'this' is used in other languages.
        self.is_armed = False
        self.is_sensing = False  # is sensing is used to tell the system start looking for intruders
        # When arming a system, the system needs to give a delay in order to for the home owner to leave the house
        # without tripping the alarm system
        self._arm_time_delay = 5  # 1 * 60
        # setting to a default pin
        self._pin = pin
        self._user_pin_entry = ""
        self._user_first_key_entry_time = 0
        self._invalid_entry_count = 0

        # When user incorrectly enters in the pass code 4 times the system gets locked for 5min
        self.system_locked = False
        self._lock_time = 0
        self._lockout_duration = 10  # * 60  # currently set to 5 min but might consider less for testing
        self._pin_entry_max_timeout = 10  # Unit in seconds
        self._max_invalid_entry_before_system_lock = 4
        self.alarm_active = False

        # variable that tracks if a user modified the current state of the system
        # System needs to be running
        self._running = False
        # list to keep track of worker threads
        # DO NOT Create or start threads here!! Do it in the run method.
        self._threads = []

        # Setup logging for this module.
        self._logger = logging.getLogger('AlarmSystem')
        ch = logging.StreamHandler()
        ch.setLevel(logging.DEBUG)
        formatter = logging.Formatter(
            '%(asctime)s - %(name)s - %(threadName)s - %(levelname)s - %(message)s'
        )
        ch.setFormatter(formatter)
        self._logger.addHandler(ch)
        self._logger.setLevel(logging.DEBUG)
        # setting up tcp socket to receive
        self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        try:
            self._socket.bind(('127.0.0.1', 9090))
        except socket.error as e:
            self._logger.fatal('{}'.format(e))
            exit(1)

        # Create the sub system items that the main system will monitor and control
        self.keypad = Keypad()
        self.led = LED()
        self.event_queue = queue.Queue()
        # self.pir_sensor = PirSensor(self.event_queue)  # Turned off for now due to interference
        self.ultra_sonic_distance_sensor = UltraSonicDistanceSensor(
            self.event_queue)
        self.calendar = Calendar()
        self.watson = Watson()
        self.notifications = Notifications()

        # The web UI
        self._web_client = ui.create_app()
        self._web_process = Process(target=self._web_client.run,
                                    args=('0.0.0.0', 1234))

    def run(self):
        """
        System will create and start the helper threads here to ensure synchronization
        """
        if not self._running:
            self._running = True
            sensor_t = Thread(target=self._sensor_thread,
                              args=(),
                              name="Sensor_Thread")
            self._threads.append(sensor_t)
            sensor_t.start()

            calendar_t = Thread(target=self._calendar_thread,
                                args=(),
                                name="Calendar_Thread")
            self._threads.append(calendar_t)
            calendar_t.start()

            k_thread = Thread(target=self.keypad.capture_keypress,
                              args=(),
                              name="Keypad_Thread")
            self._threads.append(k_thread)
            k_thread.start()

            alarm_t = Thread(target=self._alarm_thread,
                             args=(),
                             name="Alarm_Thread")
            self._threads.append(alarm_t)
            alarm_t.start()

            ultra_sonic_thread = Thread(
                target=self.ultra_sonic_distance_sensor.monitor_distance,
                args=(),
                name="Ultra_Sonic_Thread")
            self._threads.append(ultra_sonic_thread)
            ultra_sonic_thread.start()

            self._web_process.start()
            self._main_thread()
            self._logger.info('web client has started')

    def _process_keypress_event(self, keypress_event: str):
        """
        The process of keypress event to deactivate or activate a system

        :param keypress_event:
        :return:
        """
        # When the last entry has been greater than x seconds, just clear the past data because of the timeout req
        if time(
        ) - self._user_first_key_entry_time > self._pin_entry_max_timeout:
            self.reset_user_entry()

        if not self.is_armed:
            # start the timer for the first keypress and reset the user entry
            if keypress_event == "#":
                self.reset_user_entry()
                self._logger.debug('# entered, passcode reset')
            else:
                self._user_pin_entry += keypress_event
                self._logger.debug('current pass code:' + self._user_pin_entry)
                # Check for success, we will only check for valid entry when the sizes are the same
                if len(self._user_pin_entry) == len(self._pin):
                    self._arm(self._user_pin_entry)
        else:
            self._user_pin_entry += keypress_event
            self._logger.debug('current pass code:' + self._user_pin_entry)
            if len(self._user_pin_entry) == len(self._pin):
                self._disarm(self._user_pin_entry)

    def reset_user_entry(self):
        self._user_pin_entry = ""
        self._user_first_key_entry_time = time()

    def _alarm_thread(self):
        """
        This method is intended to handle the periodic processing when the alarm needs to go off. Sounding an alarm and led
        This is indented to be run in a thread and only run when alarm is active. The Pictures and video should be handled
        outside this thread when the alarm gets activated
        :return:
        """
        # Threads should reference this run flag for loops so the system can close and join them when stopped.
        while self._running:
            if self.alarm_active:
                while self.alarm_active and self._running:
                    self.led.turn_on(color=LEDColor.RED, debug=False)
                    sleep(.1)
                    self.led.turn_off(color=LEDColor.RED, debug=False)
                    sleep(.1)
            else:
                # Added to not eat up the processing. This give a max of .5 delay when the alarm can be started
                sleep(.5)

    def _process_pir_event(self, pir_event: PIREvent):
        """
        The process of a PIR event that can signal an alarm if the system is armed

        For the alarm it will be latched. A latched alarm means that once it has been activated someone has to
        manually disable it using the pin or some kind of confirmation.

        :param pir_event:
        :return:
        """

        if pir_event.event_type == PirEventType.falling:
            self._logger.debug('Falling event occurred')
            self.watson.send_movement_falling()
        elif pir_event.event_type == PirEventType.rising:
            if self.is_sensing:
                # First event that has occurred when armed, activate alarm thread
                if not self.alarm_active:
                    self.alarm_active = True
                    self._logger.info('Alarm has been activated')
                    self.watson.send_alarm_activated()
                    camera.take_photo()
                    camera.take_video()
                    sleep(1)
                    # Get the latest image taken and send that in the message
                    list_of_files = glob.glob("/home/pi/motion/camera1" +
                                              '/*-snapshot.jpg')
                    latest_file = max(list_of_files, key=os.path.getctime)
                    self.notifications.send_alert_message(latest_file)
                    camera.take_video()
            self._logger.debug('Rising event occurred')
            self.watson.send_movement_rising()

    def _sensor_thread(self):
        """
        Thread for checking the sensor inputs.
        """
        self._logger.debug('starting sensor thread')
        while self._running:
            """
            Keypress Event check
            In the even of the queue being empty, the exception queue.Empty will be thrown. Thus a Try catch will be
            needed to handle the normal case when no event is in the queue
            """
            try:
                """
                Monitor if the system is locked. When locked all keypress are ignored. After the 5min timer is up then
                the time is reset and the system is unlocked
                """
                keypress_event = self.keypad.keypress_queue.get_nowait()
                if not self.system_locked:
                    self._process_keypress_event(keypress_event)
                else:
                    # Once the lockout has expired, reset the invalid entry count and led status
                    if time() - self._lock_time > self._lockout_duration:
                        self.system_locked = False
                        self._invalid_entry_count = 0
                        self.led.turn_off(LEDColor.RED)

            except queue.Empty:
                pass
            """
            PIR Event check
            In the even of the queue being empty, the exception queue.Empty will be thrown. Thus a Try catch will be
            needed to handle the normal case when no event is in the queue
            """
            try:
                pir_event = self.event_queue.get_nowait()
                self._process_pir_event(pir_event)
            except queue.Empty:
                pass
            sleep(.2)
            # TODO - should we consider a delay in this tread to not eat up the process?

    def _main_thread(self):
        """
        Main thread that checks for inputs from user devices and WebGUI
        :return:
        """
        self._logger.debug('starting main thread')
        try:
            while self._running:
                # accept connections
                self._socket.listen(5)
                connection = self._socket.accept()
                if connection is not None:
                    # self._logger.info("Received connection")
                    # create new thread an pass it the connection
                    t = Thread(target=self._connection_thread,
                               args=(connection[0], ))
                    self._threads.append(t)
                    t.start()
        except socket.error as e:
            self._logger.error('{}'.format(e))
        except KeyboardInterrupt as e:
            self._logger.info('{}'.format(e))
        finally:
            self._running = False
            self._join_threads()

    def _connection_thread(self, connection: socket.socket):
        """
        Process a connection from the system client.
        :param connection: The socket connection to utilize
        """
        try:
            data = json.loads(bytes(connection.recv(1024)).decode('utf-8'))
            if data is not None and isinstance(data, dict) and 'func' in data:
                func = data['func']
                if func == 'arm_disarm' and 'pin' in data and isinstance(
                        data['pin'], str):
                    if self.is_armed:
                        result = self._disarm(data['pin'])
                        connection.send(
                            json.dumps({
                                'result': result
                            }).encode('utf-8'))
                    else:
                        result = self._arm(data['pin'])
                        connection.send(
                            json.dumps({
                                'result': result
                            }).encode('utf-8'))
                elif func == 'set_pin' and 'current_pin' in data and isinstance(data['current_pin'], str) \
                        and 'new_pin' in data and isinstance(data['new_pin'], str):
                    result = self._set_pin(data['current_pin'],
                                           data['new_pin'])
                    connection.send(
                        json.dumps({
                            'result': result
                        }).encode('utf-8'))
                elif func == 'take_photo':
                    camera.take_photo()
                    connection.send(
                        json.dumps({
                            'result': True
                        }).encode('utf-8'))
                elif func == 'take_video':
                    camera.take_video()
                    connection.send(
                        json.dumps({
                            'result': True
                        }).encode('utf-8'))
                elif func == 'status':
                    connection.send(
                        json.dumps({
                            'armed': self.is_armed,
                            'led_color': self.led.color.name,
                            'led_enabled': self.led.enabled,
                            "is_sensing": self.is_sensing
                        }).encode('utf-8'))
        except socket.error as e:
            self._logger.error('{}'.format(e))
        except json.JSONDecodeError as e:
            self._logger.error('{}'.format(e))
        finally:
            connection.close()

    def _calendar_thread(self):
        """
        Thread that checks the google calendar once a second and takes action as appropriate.
        """
        while self._running:
            res = self.calendar.check_calendar()
            if res[0]:
                if res[1] and not self.is_armed:
                    self._arm(self._pin)
                elif not res[1] and self.is_armed:
                    self._disarm(self._pin)
            sleep(1)

    def _join_threads(self):
        """
        Joins all active threads before the program exits
        """
        self._logger.debug('joining threads...')
        for t in self._threads:
            t.join()
        self._logger.debug('threads joined')
        self._logger.debug('stopping UI process')
        self._web_process.close()
        self._logger.debug('stopped UI process')

    def _set_arm_after_delay(self):
        # Its possible we could have the system disarmed right way so we need to ignore this request to start sensing
        if self.is_armed:
            self._logger.info("System has been set to armed")
            self.is_sensing = True
            self.watson.send_armed()

    def _turn_off_led_after_system_unlocked(self):
        self.system_locked = False
        self._invalid_entry_count = 0
        self.led.turn_off(color=LEDColor.RED)

    def _arm(self, pin: str):
        # to create function documentation in pycharm simple type '"' three times and hit enter.
        """
        Arms the system if the system is not armed and the pin is correct.
        Assumption: The pin entry is the same length as the given pin. Upon a failed check the user entry is reset
        :param pin: the system pin
        """
        if not self.is_armed and self._check_pin(pin):
            self._logger.info(
                'Passcode entered correctly. System will be armed in ' +
                str(self._arm_time_delay) + " seconds")
            self.reset_user_entry()
            self._invalid_entry_count = 0
            self.led.flash_led(color=LEDColor.GREEN, flash_count=5)
            self.led.turn_on(color=LEDColor.BLUE)
            self.is_armed = True
            Timer(self._arm_time_delay, self._set_arm_after_delay).start()
            self.notifications.send_armed_message()
            return True
        else:
            return self.invalid_pin_entry()

    def invalid_pin_entry(self):
        self._invalid_entry_count += 1
        if self._invalid_entry_count > self._max_invalid_entry_before_system_lock:
            self.led.flash_led(color=LEDColor.YELLOW, flash_count=5)
            self.led.turn_on(color=LEDColor.RED)
            self._lock_time = time()
            self.system_locked = True
            self.notifications.send_locked_message()
            self._logger.info('System is locked')
            Timer(self._lockout_duration,
                  self._turn_off_led_after_system_unlocked).start()
        else:
            turn_alarm_back_on = False
            # Handing the case where the alarm is active and we need to notify the user they entered in an incorrect pin
            # This case will disable the alarm while the led will flash yellow, then will turn the alarm back on
            if self.alarm_active:
                self.alarm_active = False
                turn_alarm_back_on = True
                sleep(.5)
            self.led.flash_led(color=LEDColor.YELLOW, flash_count=2)
            if turn_alarm_back_on:
                self.alarm_active = True

            self.reset_user_entry()
            self._logger.info('Failed to enter the pin correctly')
        return False

    def _disarm(self, pin: str):
        """
        Disarms the system if the system is armed and the pin is correct.
        :param pin: the system pin
        :return:
        """
        if self.is_armed:
            if self._check_pin(pin):
                # When the alarm is running, deactivate alarm
                if self.alarm_active:
                    self.alarm_active = False
                    self._logger.info('Alarm turned off')
                    self.watson.send_alarm_deactivated()
                self.reset_user_entry()
                self._logger.info('System is now disarmed')
                self.is_armed = False
                self.is_sensing = False
                self.watson.send_diarmed()
                self.led.clear_led()
                self.led.flash_led(color=LEDColor.GREEN, flash_count=5)
                self._invalid_entry_count = 0
                self.notifications.send_disarmed_message()
                return True
            else:
                return self.invalid_pin_entry()

        self._logger.info('Failed to disarmed system')
        return False

    def _set_pin(self, current_pin: str, new_pin: str):
        """
        Sets a new pin for the system if the current_pin proved matches the system pin.
        :param current_pin: the current system pin
        :param new_pin: the new pin value
        :return if setting the pin was successful
        :rtype bool
        """
        if self._check_pin(current_pin):
            # check to see if the new pin is a length of 6 and if it only contains numbers
            if len(new_pin) == 6 and new_pin.isnumeric():
                self._pin = new_pin
                self._logger.info('System pin set')
                return True
        self._logger.info('Failed to set system pin')
        return False

    def _check_pin(self, pin: str):
        """
        Checks to see if the pin provided matches the system pin
        :param pin: the given pin to check
        :return: returns True if given pin matches system pin
        """
        if self._pin == pin:
            return True
        return False

    def is_armed(self):
        """
        Returns the armed status of the system.
        :return: armed
        """
        return self.is_armed
Esempio n. 4
0
def led_on_demo():
    pin_number = 18  # edit pin number
    led = LED(rpi, pin_number)
    led.turn_on()
    sleep(2)
    led.turn_off()