Exemplo n.º 1
0
    def __init__(self, port="5550"):
        self.__bind_addr = "tcp://*:%s" % port
        app.logger.info("Bind address: " + self.__bind_addr)

        self.__relay_lock = threading.Lock()

        self.__db = GarageDb(app.instance_path, app.resource_path)

        # Get initial reed state and subscribe to events
        GPIO.setup(app.config['REED_PIN'], GPIO.IN)
        GPIO.add_event_detect(app.config['REED_PIN'],
                              GPIO.BOTH,
                              callback=self.door_opened_or_closed)
        self.__door_state = None  # 1 for open, 0 for closed, None for uninitialized
        self.door_opened_or_closed(app.config['REED_PIN'])  # force update

        # Set up warning timer if there's a setting
        if app.config['DOOR_OPEN_WARNING_TIME']:
            app.logger.info('Starting schedule to check door at {0}...'.format(
                app.config['DOOR_OPEN_WARNING_TIME']))
            schedule.every().day.at(app.config['DOOR_OPEN_WARNING_TIME']).do(
                self.check_door_open_for_warning)
            t = Thread(target=self.run_schedule)
            t.start()
        else:
            app.logger.info('No schedule to run.')
Exemplo n.º 2
0
def get_db() -> GarageDb:
    """
    Creates a new database connection if there is none yet for the
    current application context.
    """
    if not hasattr(g, 'sqlite_db'):
        g.sqlite_db = GarageDb(app.instance_path, resource_path)
    return g.sqlite_db
Exemplo n.º 3
0
    def __init__(self, port="5550"):
        self.__bind_addr = "tcp://*:%s" % port
        app.logger.info("Bind address: " + self.__bind_addr)

        self.__relay_lock = threading.Lock()

        self.__db = GarageDb(app.instance_path, app.resource_path)

        # Get initial reed state and subscribe to events
        GPIO.setup(app.config['REED_PIN'], GPIO.IN)
        GPIO.add_event_detect(app.config['REED_PIN'], GPIO.BOTH, callback=self.door_opened_or_closed)
        self.__door_state = None                            # 1 for open, 0 for closed, None for uninitialized
        self.door_opened_or_closed(app.config['REED_PIN'])  # force update

        # Set up warning timer if there's a setting
        if app.config['DOOR_OPEN_WARNING_TIME']:
            app.logger.info('Starting schedule to check door at {0}...'.format(app.config['DOOR_OPEN_WARNING_TIME']))
            schedule.every().day.at(app.config['DOOR_OPEN_WARNING_TIME']).do(self.check_door_open_for_warning)
            t = Thread(target=self.run_schedule)
            t.start()
        else:
            app.logger.info('No schedule to run.')
Exemplo n.º 4
0
class GaragePiController:
    def __init__(self, port="5550"):
        self.__bind_addr = "tcp://*:%s" % port
        app.logger.info("Bind address: " + self.__bind_addr)

        self.__relay_lock = threading.Lock()

        self.__db = GarageDb(app.instance_path, app.resource_path)

        # Get initial reed state and subscribe to events
        GPIO.setup(app.config['REED_PIN'], GPIO.IN)
        GPIO.add_event_detect(app.config['REED_PIN'], GPIO.BOTH, callback=self.door_opened_or_closed)
        self.__door_state = None                            # 1 for open, 0 for closed, None for uninitialized
        self.door_opened_or_closed(app.config['REED_PIN'])  # force update

        # Set up warning timer if there's a setting
        if app.config['DOOR_OPEN_WARNING_TIME']:
            app.logger.info('Starting schedule to check door at {0}...'.format(app.config['DOOR_OPEN_WARNING_TIME']))
            schedule.every().day.at(app.config['DOOR_OPEN_WARNING_TIME']).do(self.check_door_open_for_warning)
            t = Thread(target=self.run_schedule)
            t.start()
        else:
            app.logger.info('No schedule to run.')

    def start(self):
        context = zmq.Context()
        socket = context.socket(zmq.ROUTER)
        socket.bind(self.__bind_addr)
        socket.setsockopt(zmq.SNDTIMEO, 1000)

        app.logger.info("Entering listen loop... ")

        try:
            while True:
                msg = socket.recv_multipart()
                app.logger.debug("Received msg: {0}".format(msg))

                if len(msg) != 3:
                    error_msg = 'invalid message received: %s' % msg
                    app.logger.error(error_msg)
                    reply = [msg[0], error_msg]
                    socket.send_multipart(reply)
                    continue

                # Break out incoming message
                id = msg[0]
                operation = bytes.decode(msg[1]) if type(msg[1]) is bytes else msg[1]
                contents = json.loads(bytes.decode(msg[2]) if type(msg[2]) is bytes else msg[2])

                # Initialize the reply. Must always send back the id with ROUTER
                reply = [id]

                if operation == 'echo':
                    # Just echo back the original contents serialized back to a string
                    reply.append(self.__get_json_bytes(contents))
                elif operation == 'get_status':
                    # Get status and return
                    reply.append(self.get_status().to_json_bytes())
                elif operation == 'trigger_relay':
                    # Trigger relay
                    self.trigger_relay(contents['user_agent'], contents['login'])
                    reply.append(b'{}')
                else:
                    app.logger.error('unknown request')

                socket.send_multipart(reply)

        finally:
            app.logger.info('Closing down socket')
            socket.setsockopt(zmq.LINGER, 500)
            socket.close()

    def __get_json_bytes(self, contents) -> bytes:
        json_str = json.dumps(contents)
        return str.encode(json_str)

    def __add_to_history(self, event: str, description: str, user_agent='SERVER', login='******'):
        self.__db.record_event(user_agent, login, event, description)

    def door_opened_or_closed(self, pin_changed: int):
        """
        Callback for monitoring the reed switch's GPIO pin.

        :param pin_changed: pin number for the pin that changed
        :return:
        """

        new_state = GPIO.input(pin_changed)
        old_state = self.__door_state
        if (new_state == old_state): return

        self.__door_state = new_state
        new_state_text = "OPEN" if new_state else "CLOSED"

        if (old_state is not None):
            self.__add_to_history('SensorTrip', 'Door state changed to {0}.'.format(new_state_text))
        else:
            self.__add_to_history('StartupSensorRead', 'Door state initialized to {0}.'.format(new_state_text))

        # Check for IFTTT events that need to be fired
        if (old_state is not None):
            if self.__door_state:
                change = 'opened'
                specific_event = app.opened_event
            else:
                change = 'closed'
                specific_event = app.closed_event

            if app.changed_event is not None: app.changed_event.trigger(change)
            if specific_event is not None: specific_event.trigger()

        app.logger.info("door {0} (pin {1} is {2})".format("OPENED" if new_state else "CLOSED", pin_changed, new_state))

    def get_status(self) -> Struct:
        """
        Gets the current system status
        :return: A Struct populated with system state info
        """
        data = Struct(is_open=self.__door_state)
        data.status_text = "OPEN" if data.is_open else "CLOSED"
        data.cpu_temp_c = self.get_cpu_temperature()
        data.cpu_temp_f = data.cpu_temp_c * 9.0 / 5.0 + 32
        data.gpu_temp_c = self.get_gpu_temperature()
        data.gpu_temp_f = data.gpu_temp_c * 9.0 / 5.0 + 32
        return data

    def get_cpu_temperature(self) -> float:
        res = os.popen('cat /sys/class/thermal/thermal_zone0/temp').readline()
        app.logger.debug('Checked CPU temp and got: %r' % res)
        return float(res) / 1000.0

    def get_gpu_temperature(self) -> float:
        res = os.popen('vcgencmd measure_temp').readline()
        app.logger.debug('Checked GPU temp and got: %r' % res)
        return float(res.replace("temp=","").replace("'C\n",""))

    def trigger_relay(self, user_agent: str, login: str):
        """ Triggers the relay for a short period. """
        app.logger.debug('Triggering relay for {0} ({1})'.format(login, user_agent))
        self.__db.record_event(user_agent if user_agent else 'UNKNOWN',
                               login if login else 'UNKNOWN',
                               'SwitchActivated',
                               'Door switch activated when in {0} state.'.format(self.get_status().status_text))

        with self.__relay_lock:
            # Relay triggers on low so just setting as output will trigger
            # and closing will switch back.
            GPIO.setup(app.config['RELAY_PIN'], GPIO.OUT)
            time.sleep(0.5)
            GPIO.setup(app.config['RELAY_PIN'], GPIO.IN)

    def check_door_open_for_warning(self):
        if self.__door_state and app.warning_event is not None:
            app.warning_event.trigger('open')

    def run_schedule(self):
        while 1:
            schedule.run_pending()
            time.sleep(1)
Exemplo n.º 5
0
class GaragePiController:
    def __init__(self, port="5550", use_proxy=False, host=None):
        self.__use_proxy = use_proxy
        if use_proxy and host is not None:
            self.__bind_addr = "tcp://{0}:{1}".format(host, port)
        else:
            self.__bind_addr = "tcp://*:%s" % port
        app.logger.info("Bind address: {}".format(self.__bind_addr))

        self.__relay_lock = threading.Lock()

        self.__db = GarageDb(app.instance_path, app.resource_path)

        # Get initial reed state and subscribe to events
        GPIO.setup(app.config['REED_PIN'], GPIO.IN)
        GPIO.add_event_detect(app.config['REED_PIN'],
                              GPIO.BOTH,
                              callback=self.door_opened_or_closed)
        self.__door_state = None  # 1 for open, 0 for closed, None for uninitialized
        self.door_opened_or_closed(app.config['REED_PIN'])  # force update

        # Set up warning timer if there's a setting
        if app.config['DOOR_OPEN_WARNING_TIME']:
            app.logger.info('Starting schedule to check door at {0}...'.format(
                app.config['DOOR_OPEN_WARNING_TIME']))
            schedule.every().day.at(app.config['DOOR_OPEN_WARNING_TIME']).do(
                self.check_door_open_for_warning)
            t = Thread(target=self.run_schedule)
            t.start()
        else:
            app.logger.info('No schedule to run.')

    def start(self):
        context = zmq.Context()
        socket = context.socket(zmq.ROUTER)
        if self.__use_proxy:
            # Connect if we are using the proxy
            socket.connect(self.__bind_addr)
        else:
            # Bind if we are working locally
            socket.bind(self.__bind_addr)
        socket.setsockopt(zmq.SNDTIMEO, 1000)

        app.logger.info("Entering listen loop... ")

        try:
            while True:
                msg = socket.recv_multipart()
                app.logger.debug("Received a msg: {0}".format(msg))

                if app.config['USE_PROXY']:
                    # When using the proxy, allow for two socket IDs
                    messageSize = 4
                    offset = 1
                    reply = msg[0:2]
                else:
                    # Only a single socket ID when local
                    messageSize = 3
                    offset = 0
                    reply = [msg[0]]

                if len(msg) != messageSize:
                    error_msg = 'invalid message received: length %d, %s' % (
                        len(msg), msg)
                    app.logger.error(error_msg)
                    reply.extend(error_msg)
                    socket.send_multipart(reply)
                    continue

                # Break out incoming message
                operation = bytes.decode(msg[1 + offset]) if type(
                    msg[1 + offset]) is bytes else msg[1 + offset]
                contents = json.loads(
                    bytes.decode(msg[2 + offset])
                    if type(msg[2 + offset]) is bytes else msg[2 + offset])

                if operation == 'echo':
                    # Just echo back the original contents serialized back to a string
                    reply.append(self.__get_json_bytes(contents))
                elif operation == 'get_status':
                    # Get status and return
                    reply.append(self.get_status().to_json_bytes())
                elif operation == 'trigger_relay':
                    # Trigger relay
                    self.trigger_relay(contents['user_agent'],
                                       contents['login'])
                    reply.append(b'{}')
                elif operation == 'get_history':
                    # Get operation history
                    history = self.get_history()
                    app.logger.info(
                        'Returning history. Record count: {0}'.format(
                            len(history)))
                    reply.extend(history)
                else:
                    app.logger.error('unknown request')

                app.logger.debug("Replying: {0}".format(reply))
                socket.send_multipart(reply)

        finally:
            app.logger.info('Closing down socket')
            socket.setsockopt(zmq.LINGER, 500)
            socket.close()

    def __get_json_bytes(self, contents) -> bytes:
        json_str = json.dumps(contents)
        return str.encode(json_str)

    def __add_to_history(self,
                         event: str,
                         description: str,
                         user_agent='SERVER',
                         login='******'):
        self.__db.record_event(user_agent, login, event, description)

    def door_opened_or_closed(self, pin_changed: int):
        """
        Callback for monitoring the reed switch's GPIO pin.

        :param pin_changed: pin number for the pin that changed
        :return:
        """

        new_state = GPIO.input(pin_changed)
        old_state = self.__door_state
        if (new_state == old_state): return

        self.__door_state = new_state
        new_state_text = "OPEN" if new_state else "CLOSED"

        if (old_state is not None):
            self.__add_to_history(
                'SensorTrip',
                'Door state changed to {0}.'.format(new_state_text))
        else:
            self.__add_to_history(
                'StartupSensorRead',
                'Door state initialized to {0}.'.format(new_state_text))

        # Check for IFTTT events that need to be fired
        if (old_state is not None):
            if self.__door_state:
                change = 'opened'
                specific_event = app.opened_event
            else:
                change = 'closed'
                specific_event = app.closed_event

            if app.changed_event is not None: app.changed_event.trigger(change)
            if specific_event is not None: specific_event.trigger()

        app.logger.info("door {0} (pin {1} is {2})".format(
            "OPENED" if new_state else "CLOSED", pin_changed, new_state))

    def get_status(self) -> Struct:
        """
        Gets the current system status
        :return: A Struct populated with system state info
        """
        data = Struct(is_open=self.__door_state)
        data.status_text = "OPEN" if data.is_open else "CLOSED"
        data.cpu_temp_c = self.get_cpu_temperature()
        data.cpu_temp_f = data.cpu_temp_c * 9.0 / 5.0 + 32
        data.gpu_temp_c = self.get_gpu_temperature()
        data.gpu_temp_f = data.gpu_temp_c * 9.0 / 5.0 + 32
        return data

    def get_cpu_temperature(self) -> float:
        res = os.popen('cat /sys/class/thermal/thermal_zone0/temp').readline()
        app.logger.debug('Checked CPU temp and got: %r' % res)
        return float(res) / 1000.0

    def get_gpu_temperature(self) -> float:
        res = os.popen('vcgencmd measure_temp').readline()
        app.logger.debug('Checked GPU temp and got: %r' % res)
        return float(res.replace("temp=", "").replace("'C\n", ""))

    def trigger_relay(self, user_agent: str, login: str):
        """ Triggers the relay for a short period. """
        app.logger.debug('Triggering relay for {0} ({1})'.format(
            login, user_agent))
        self.__db.record_event(
            user_agent if user_agent else 'UNKNOWN',
            login if login else 'UNKNOWN', 'SwitchActivated',
            'Door switch activated when in {0} state.'.format(
                self.get_status().status_text))

        with self.__relay_lock:
            # Relay triggers on low so just setting as output will trigger
            # and closing will switch back.
            GPIO.setup(app.config['RELAY_PIN'], GPIO.OUT)
            time.sleep(0.5)
            GPIO.setup(app.config['RELAY_PIN'], GPIO.IN)

    def check_door_open_for_warning(self):
        if self.__door_state and app.warning_event is not None:
            app.warning_event.trigger('open')

    def run_schedule(self):
        while 1:
            schedule.run_pending()
            time.sleep(1)

    def get_history(self):
        """ Get the history entries for delivery to the remote server.

        Get the history entries, formatted as Structs in order to send to a
        frontend server on a different machine.

        Returns:
            An list of json strings (as bytes), with one history entry per item.
            For example:
                [
                    b'{
                        "description": "Door state changed to OPEN.",
                        "event": "SwitchActivated",
                        "timestamp": "2017-03-21 18:04:12"}',
                    b'{
                        "description": "Door state changed to CLOSED.",
                        "event": "SwitchActivated",
                        "timestamp": "2017-03-21 18:03:39"}',
                    b'{
                        "description": "Door state changed to OPEN.",
                        "event": "SensorTrip",
                        "timestamp": "2017-03-21 18:03:39"}
                ]
        """
        entries = self.__db.read_history()
        history = []
        for entry in entries:
            data = Struct(timestamp=entry['timestamp'],
                          event=entry['event'],
                          description=entry['description'])
            history.append(data.to_json_bytes())
        return history