def __init__(self, ready, logger, timer_id):
        threading.Thread.__init__(self)

        self.thread_startup_timer = timeit.default_timer()
        self.thread_shutdown_timer = 0
        self.ready = ready
        self.logger = logger
        self.timer_id = timer_id
        self.control = DaemonControl()

        with session_scope(MYCODO_DB_PATH) as new_session:
            timer = new_session.query(Timer).filter(
                Timer.id == self.timer_id).first()
            self.name = timer.name
            self.relay_id = timer.relay_id
            self.state = timer.state
            self.time = timer.time_on
            self.duration_on = timer.duration_on
            self.duration_off = timer.duration_off

        # Time of day split into hour and minute
        if self.time:
            time_split = self.time.split(":")
            self.hour = time_split[0]
            self.minute = time_split[1]
        else:
            self.hour = None
            self.minute = None

        self.duration_timer = time.time()
        self.date_timer_not_executed = True
        self.running = False
Example #2
0
    def __init__(self, ready, logger, timer_id):
        threading.Thread.__init__(self)

        self.thread_startup_timer = timeit.default_timer()
        self.thread_shutdown_timer = 0
        self.ready = ready
        self.logger = logger
        self.timer_id = timer_id
        self.control = DaemonControl()

        with session_scope(MYCODO_DB_PATH) as new_session:
            timer = new_session.query(Timer).filter(
                Timer.id == self.timer_id).first()
            self.name = timer.name
            self.relay_id = timer.relay_id
            self.state = timer.state
            self.time = timer.time_on
            self.duration_on = timer.duration_on
            self.duration_off = timer.duration_off

        # Time of day split into hour and minute
        if self.time:
            time_split = self.time.split(":")
            self.hour = time_split[0]
            self.minute = time_split[1]
        else:
            self.hour = None
            self.minute = None

        self.duration_timer = time.time()
        self.date_timer_not_executed = True
        self.running = False
Example #3
0
    def startup_stats(self):
        """Initial statistics collection and transmssion at startup"""
        with session_scope(MYCODO_DB_PATH) as new_session:
            misc = new_session.query(Misc).first()
            self.opt_out_statistics = misc.stats_opt_out
        if not self.opt_out_statistics:
            self.logger.info("[Daemon] Anonymous statistics enabled")
        else:
            self.logger.info("[Daemon] Anonymous statistics disabled")

        try:
            # if statistics file doesn't exist, create it
            if not os.path.isfile(STATS_CSV):
                self.logger.debug("[Daemon] Statistics file doesn't "
                                  "exist, creating {}".format(STATS_CSV))
                daemonutils.recreate_stat_file()

            daemon_startup_time = timeit.default_timer()-self.startup_timer
            self.logger.info("[Daemon] Mycodo v{} started in {} seconds".format(
                MYCODO_VERSION, daemon_startup_time))
            daemonutils.add_update_stat(
                self.logger, 'daemon_startup_seconds', daemon_startup_time)
        except Exception as msg:
            self.logger.warning(
                "[Daemon] Statistics initilization Error: {}".format(msg))
Example #4
0
    def __init__(self, ready, pid_id):
        threading.Thread.__init__(self)

        self.logger = logging.getLogger("mycodo.pid_{id}".format(id=pid_id))

        self.running = False
        self.thread_startup_timer = timeit.default_timer()
        self.thread_shutdown_timer = 0
        self.ready = ready
        self.pid_id = pid_id
        self.control = DaemonControl()

        self.initialize_values()

        self.control_variable = 0
        self.Derivator = 0
        self.Integrator = 0
        self.error = 0.0
        self.P_value = None
        self.I_value = None
        self.D_value = None
        self.set_point = 0
        self.lower_seconds_on = 0
        self.raise_seconds_on = 0
        self.last_measurement_success = False
        self.timer = t.time() + self.measure_interval

        # Check if a method is set for this PID
        if self.method_id:
            method = db_retrieve_table(MYCODO_DB_PATH, Method)
            method = method.filter(Method.method_id == self.method_id)
            method = method.filter(Method.method_order == 0).first()
            self.method_type = method.method_type
            self.method_start_time = method.start_time

            if self.method_type == 'Duration':
                if self.method_start_time == 'Ended':
                    # Method has ended and hasn't been instructed to begin again
                    pass
                elif self.method_start_time == 'Ready' or self.method_start_time is None:
                    # Method has been instructed to begin
                    with session_scope(MYCODO_DB_PATH) as db_session:
                        mod_method = db_session.query(Method)
                        mod_method = mod_method.filter(
                            Method.method_id == self.method_id)
                        mod_method = mod_method.filter(
                            Method.method_order == 0).first()
                        mod_method.start_time = datetime.datetime.now()
                        self.method_start_time = mod_method.start_time
                        db_session.commit()
                else:
                    # Method neither instructed to begin or not to
                    # Likely there was a daemon restart ot power failure
                    # Resume method with saved start_time
                    self.method_start_time = datetime.datetime.strptime(
                        str(self.method_start_time), '%Y-%m-%d %H:%M:%S.%f')
                    self.logger.warning(
                        "Resuming method {id} started at {time}".format(
                            id=self.method_id, time=self.method_start_time))
Example #5
0
    def __init__(self, ready, logger, pid_id):
        threading.Thread.__init__(self)

        self.thread_startup_timer = timeit.default_timer()
        self.thread_shutdown_timer = 0
        self.ready = ready
        self.logger = logger
        self.pid_id = pid_id
        self.control = DaemonControl()

        with session_scope(MYCODO_DB_PATH) as new_session:
            pid = new_session.query(PID).filter(PID.id == self.pid_id).first()
            self.sensor_id = pid.sensor_id
            self.measure_type = pid.measure_type
            self.direction = pid.direction
            self.raise_relay_id = pid.raise_relay_id
            self.raise_min_duration = pid.raise_min_duration
            self.raise_max_duration = pid.raise_max_duration
            self.lower_relay_id = pid.lower_relay_id
            self.lower_min_duration = pid.lower_min_duration
            self.lower_max_duration = pid.lower_max_duration
            self.Kp = pid.p
            self.Ki = pid.i
            self.Kd = pid.d
            self.measure_interval = pid.period
            self.default_set_point = pid.setpoint
            self.set_point = pid.setpoint

        with session_scope(MYCODO_DB_PATH) as new_session:
            self.pidsetpoints = new_session.query(PIDSetpoints)
            self.pidsetpoints = self.pidsetpoints.filter(PIDSetpoints.pid_id == self.pid_id)
            self.pidsetpoints = self.pidsetpoints.order_by(PIDSetpoints.start_time.asc())
            new_session.expunge_all()
            new_session.close()

        self.Derivator = 0
        self.Integrator = 0
        self.Integrator_max = 500
        self.Integrator_min = -500
        self.error = 0.0
        self.P_value = None
        self.I_value = None
        self.D_value = None
        self.raise_seconds_on = 0
        self.timer = t.time() + self.measure_interval
Example #6
0
    def stop_controller(self, ended_normally=True, deactivate_pid=False):
        self.thread_shutdown_timer = timeit.default_timer()
        self.running = False
        # Unset method start time
        if self.method_id and ended_normally:
            with session_scope(MYCODO_DB_PATH) as db_session:
                mod_pid = db_session.query(PID).filter(
                    PID.id == self.pid_id).first()
                mod_pid.method_start_time = 'Ended'
                mod_pid.method_end_time = None
                db_session.commit()

        if deactivate_pid:
            with session_scope(MYCODO_DB_PATH) as db_session:
                mod_pid = db_session.query(PID).filter(
                    PID.id == self.pid_id).first()
                mod_pid.is_activated = False
                db_session.commit()
Example #7
0
 def stop_controller(self):
     self.thread_shutdown_timer = timeit.default_timer()
     self.running = False
     # Unset method start time
     if self.method_id:
         with session_scope(MYCODO_DB_PATH) as db_session:
             mod_method = db_session.query(Method).filter(
                 Method.id == self.method_id).first()
             mod_method.method_start_time = 'Ended'
             db_session.commit()
Example #8
0
 def relay_state(self, relay_id):
     with session_scope(MYCODO_DB_PATH) as new_session:
         relay = new_session.query(Relay).filter(Relay.id == relay_id).first()
         gpio_state = ''
         GPIO.setmode(GPIO.BCM)
         if GPIO.input(relay.pin) == relay.trigger:
             gpio_state = 'On'
         else:
             gpio_state = 'Off'
     return gpio_state
Example #9
0
 def relay_state(self, relay_id):
     with session_scope(MYCODO_DB_PATH) as new_session:
         relay = new_session.query(Relay).filter(
             Relay.id == relay_id).first()
         gpio_state = ''
         GPIO.setmode(GPIO.BCM)
         if GPIO.input(relay.pin) == relay.trigger:
             gpio_state = 'On'
         else:
             gpio_state = 'Off'
     return gpio_state
Example #10
0
 def stopController(self):
     self.thread_shutdown_timer = timeit.default_timer()
     self.running = False
     # Unset method start time
     if self.method_id:
         with session_scope(MYCODO_DB_PATH) as db_session:
             mod_method = db_session.query(Method)
             mod_method = mod_method.filter(Method.method_id == self.method_id)
             mod_method = mod_method.filter(Method.method_order == 0).first()
             mod_method.start_time = 'Ended'
             db_session.commit()
Example #11
0
    def start_all_controllers(self):
        """Start all activated controllers"""
        # Obtain database configuration options
        with session_scope(MYCODO_DB_PATH) as new_session:
            lcd = new_session.query(LCD).all()
            log = new_session.query(Log).all()
            pid = new_session.query(PID).all()
            sensor = new_session.query(Sensor).all()
            timer = new_session.query(Timer).all()
            new_session.expunge_all()
            new_session.close()

        # Start thread to control relays turning on and off
        self.logger.debug("[Daemon] Starting relay controller")
        self.controller['Relay'] = RelayController(self.logger)
        self.controller['Relay'].start()

        # Start threads to modulate relays at predefined periods
        self.logger.debug("[Daemon] Starting all activated timer controllers")
        for each_timer in timer:
            if each_timer.activated:
                self.activateController('Timer', each_timer.id)
        self.logger.info("[Daemon] All activated timer controllers started")

        # Start threads to read sensors and create influxdb entries
        self.logger.debug("[Daemon] Starting all activated sensor controllers")
        for each_sensor in sensor:
            if each_sensor.activated:
                self.activateController('Sensor', each_sensor.id)
        self.logger.info("[Daemon] All activated sensor controllers started")

        # Start threads to read influxdb entries and write to a log file
        self.logger.debug("[Daemon] Starting all activated log controllers")
        for each_log in log:
            if each_log.activated:
                self.activateController('Log', each_log.id)
        self.logger.info("[Daemon] All activated log controllers started")

        # start threads to manipulate relays with discrete PID control
        self.logger.debug("[Daemon] Starting all activated PID controllers")
        for each_pid in pid:
            if each_pid.activated:
                self.activateController('PID', each_pid.id)
        self.logger.info("[Daemon] All activated PID controllers started")

        # start threads to output to LCDs
        self.logger.debug("[Daemon] Starting all activated LCD controllers")
        for each_lcd in lcd:
            if each_lcd.activated:
                self.activateController('LCD', each_lcd.id)
        self.logger.info("[Daemon] All activated LCD controllers started")
Example #12
0
    def start_all_controllers(self):
        """
        Start all activated controllers

        See the files named controller_[name].py for details of what each
        controller does.
        """
        # Obtain database configuration options
        with session_scope(MYCODO_DB_PATH) as new_session:
            lcd = new_session.query(LCD).all()
            log = new_session.query(Log).all()
            pid = new_session.query(PID).all()
            sensor = new_session.query(Sensor).all()
            timer = new_session.query(Timer).all()
            new_session.expunge_all()
            new_session.close()

        self.logger.debug("[Daemon] Starting relay controller")
        self.controller['Relay'] = RelayController(self.logger)
        self.controller['Relay'].start()

        self.logger.debug("[Daemon] Starting all activated timer controllers")
        for each_timer in timer:
            if each_timer.activated:
                self.activateController('Timer', each_timer.id)
        self.logger.info("[Daemon] All activated timer controllers started")

        self.logger.debug("[Daemon] Starting all activated sensor controllers")
        for each_sensor in sensor:
            if each_sensor.activated:
                self.activateController('Sensor', each_sensor.id)
        self.logger.info("[Daemon] All activated sensor controllers started")

        self.logger.debug("[Daemon] Starting all activated log controllers")
        for each_log in log:
            if each_log.activated:
                self.activateController('Log', each_log.id)
        self.logger.info("[Daemon] All activated log controllers started")

        self.logger.debug("[Daemon] Starting all activated PID controllers")
        for each_pid in pid:
            if each_pid.activated:
                self.activateController('PID', each_pid.id)
        self.logger.info("[Daemon] All activated PID controllers started")

        self.logger.debug("[Daemon] Starting all activated LCD controllers")
        for each_lcd in lcd:
            if each_lcd.activated:
                self.activateController('LCD', each_lcd.id)
        self.logger.info("[Daemon] All activated LCD controllers started")
Example #13
0
    def start_all_controllers(self):
        """
        Start all activated controllers

        See the files named controller_[name].py for details of what each
        controller does.
        """
        # Obtain database configuration options
        with session_scope(MYCODO_DB_PATH) as new_session:
            lcd = new_session.query(LCD).all()
            log = new_session.query(Log).all()
            pid = new_session.query(PID).all()
            sensor = new_session.query(Sensor).all()
            timer = new_session.query(Timer).all()
            new_session.expunge_all()
            new_session.close()

        self.logger.debug("[Daemon] Starting relay controller")
        self.controller['Relay'] = RelayController(self.logger)
        self.controller['Relay'].start()

        self.logger.debug("[Daemon] Starting all activated timer controllers")
        for each_timer in timer:
            if each_timer.activated:
                self.activateController('Timer', each_timer.id)
        self.logger.info("[Daemon] All activated timer controllers started")

        self.logger.debug("[Daemon] Starting all activated sensor controllers")
        for each_sensor in sensor:
            if each_sensor.activated:
                self.activateController('Sensor', each_sensor.id)
        self.logger.info("[Daemon] All activated sensor controllers started")

        self.logger.debug("[Daemon] Starting all activated log controllers")
        for each_log in log:
            if each_log.activated:
                self.activateController('Log', each_log.id)
        self.logger.info("[Daemon] All activated log controllers started")

        self.logger.debug("[Daemon] Starting all activated PID controllers")
        for each_pid in pid:
            if each_pid.activated:
                self.activateController('PID', each_pid.id)
        self.logger.info("[Daemon] All activated PID controllers started")

        self.logger.debug("[Daemon] Starting all activated LCD controllers")
        for each_lcd in lcd:
            if each_lcd.activated:
                self.activateController('LCD', each_lcd.id)
        self.logger.info("[Daemon] All activated LCD controllers started")
    def __init__(self, logger):
        threading.Thread.__init__(self)

        self.thread_startup_timer = timeit.default_timer()
        self.thread_shutdown_timer = 0
        self.logger = logger
        self.control = DaemonControl()

        self.relay_id = {}
        self.relay_name = {}
        self.relay_pin = {}
        self.relay_amps = {}
        self.relay_trigger = {}
        self.relay_start_state = {}
        self.relay_on_until = {}
        self.relay_last_duration = {}
        self.relay_on_duration = {}

        self.logger.debug("[Relay] Initializing relays")
        try:
            # Setup GPIO (BCM numbering) and initialize all relays in database
            GPIO.setmode(GPIO.BCM)
            GPIO.setwarnings(False)

            with session_scope(MYCODO_DB_PATH) as new_session:
                smtp = new_session.query(SMTP).first()
                self.smtp_max_count = smtp.hourly_max
                self.smtp_wait_time = time.time() + 3600
                self.smtp_timer = time.time()
                self.email_count = 0
                self.allowed_to_send_notice = True

                relays = new_session.query(Relay).all()

                self.all_relays_initialize(relays)
            # Turn all relays off
            self.all_relays_off()
            # Turn relays on that are set to be on at start
            self.all_relays_on()
            self.logger.info("[Relay] Finished initializing relays")

        except Exception as except_msg:
            self.logger.exception("[Relay] Problem initializing "
                                  "relays: {}", except_msg)

        self.running = False
    def __init__(self, logger):
        threading.Thread.__init__(self)

        self.thread_startup_timer = timeit.default_timer()
        self.thread_shutdown_timer = 0
        self.logger = logger
        self.control = DaemonControl()

        self.relay_id = {}
        self.relay_name = {}
        self.relay_pin = {}
        self.relay_amps = {}
        self.relay_trigger = {}
        self.relay_start_state = {}
        self.relay_on_until = {}
        self.relay_last_duration = {}
        self.relay_on_duration = {}

        self.logger.debug("[Relay] Initializing Relays")
        try:
            # Setup GPIO (BCM numbering) and initialize all relays in database
            GPIO.setmode(GPIO.BCM)
            GPIO.setwarnings(False)

            with session_scope(MYCODO_DB_PATH) as new_session:
                smtp = new_session.query(SMTP).first()
                self.smtp_max_count = smtp.hourly_max
                self.smtp_wait_time = time.time() + 3600
                self.smtp_timer = time.time()
                self.email_count = 0
                self.allowed_to_send_notice = True

                relays = new_session.query(Relay).all()

                self.all_relays_initialize(relays)
            # Turn all relays off
            self.all_relays_off()
            # Turn relays on that are set to be on at start
            self.all_relays_on()
            self.logger.info("[Relay] Relays Initialized")

        except Exception as except_msg:
            self.logger.exception("[Relay] Problem initializing "
                                  "relays: {}", except_msg)

        self.running = False
Example #16
0
    def __init__(self, ready, logger, log_id):
        threading.Thread.__init__(self)

        self.thread_startup_timer = timeit.default_timer()
        self.thread_shutdown_timer = 0
        self.ready = ready
        self.logger = logger
        self.log_id = log_id

        with session_scope(MYCODO_DB_PATH) as new_session:
            log = new_session.query(Log).filter(Log.id == self.log_id).first()
            self.name = log.name
            self.sensor_id = log.sensor_id
            self.measure_type = log.measure_type
            self.period = log.period

        self.last_timestamp = 0
        self.timer = time.time() + self.period
        self.running = False
        self.lastUpdate = None
Example #17
0
    def add_mod_relay(self, relay_id, do_setup_pin=False):
        """
        Add or modify local dictionary of relay settings form SQL database

        When a relay is added or modified while the relay controller is
        running, these local variables need to also be modified to
        maintain consistency between the SQL database and running controller.

        :return: 0 for success, 1 for fail, with success for fail message
        :rtype: int, str

        :param relay_id: Unique ID for each relay
        :type relay_id: str
        :param do_setup_pin: If True, initialize GPIO (when adding new relay)
        :type do_setup_pin: bool
        """
        try:
            with session_scope(MYCODO_DB_PATH) as new_session:
                relay = new_session.query(Relay).filter(
                    Relay.id == relay_id).first()
                self.relay_id[relay_id] = relay.id
                self.relay_name[relay_id] = relay.name
                self.relay_pin[relay_id] = relay.pin
                self.relay_amps[relay_id] = relay.amps
                self.relay_trigger[relay_id] = relay.trigger
                self.relay_start_state[relay_id] = relay.start_state
                self.relay_on_until[relay_id] = datetime.datetime.now()
                self.relay_time_turned_on[relay_id] = None
                self.relay_last_duration[relay_id] = 0
                self.relay_on_duration[relay_id] = False
                message = "[Relay] Relay {} ({}) ".format(
                    self.relay_id[relay_id], self.relay_name[relay_id])
                if do_setup_pin:
                    self.setup_pin(relay.pin)
                    message += "initiliazed"
                else:
                    message += "added"
                self.logger.debug(message)
            return 0, "success"
        except Exception as msg:
            return 1, "Add_Mod_Relay Error: ID {}: {}".format(relay_id, msg)
    def add_mod_relay(self, relay_id, do_setup_pin=False):
        """
        Add or modify local dictionary of relay settings form SQL database

        When a relay is added or modified while the relay controller is
        running, these local variables need to also be modified to
        maintain consistency between the SQL database and running controller.

        :return: 0 for success, 1 for fail, with success for fail message
        :rtype: int, str

        :param relay_id: Unique ID for each relay
        :type relay_id: str
        :param do_setup_pin: If True, initialize GPIO (when adding new relay)
        :type do_setup_pin: bool
        """
        try:
            with session_scope(MYCODO_DB_PATH) as new_session:
                relay = new_session.query(Relay).filter(
                    Relay.id == relay_id).first()
                self.relay_id[relay_id] = relay.id
                self.relay_name[relay_id] = relay.name
                self.relay_pin[relay_id] = relay.pin
                self.relay_amps[relay_id] = relay.amps
                self.relay_trigger[relay_id] = relay.trigger
                self.relay_start_state[relay_id] = relay.start_state
                self.relay_on_until[relay_id] = datetime.datetime.now()
                self.relay_last_duration[relay_id] = 0
                self.relay_on_duration[relay_id] = False
                message = "[Relay] Relay {} ({}) ".format(
                    self.relay_id[relay_id], self.relay_name[relay_id])
                if not do_setup_pin:
                    message += "added"
                else:
                    self.setup_pin(relay.pin)
                    message += "initiliazed"
                self.logger.debug(message)
            return 0, "success"
        except Exception as msg:
            return 1, "Error: {}".format(msg)
Example #19
0
    def __init__(self, new_logger):
        threading.Thread.__init__(self)

        self.startup_timer = timeit.default_timer()
        self.thread_shutdown_timer = None
        self.logger = new_logger
        self.daemon_run = True
        self.terminated = False
        self.controller = {}
        self.controller['LCD'] = {}
        self.controller['Log'] = {}
        self.controller['PID'] = {}
        self.controller['Relay'] = None
        self.controller['Sensor'] = {}
        self.controller['Timer'] = {}
        self.timer_ram_use = time.time()
        self.timer_stats = time.time()+120
        with session_scope(MYCODO_DB_PATH) as new_session:
            misc = new_session.query(Misc).first()
            self.opt_out_statistics = misc.stats_opt_out
        if self.opt_out_statistics:
            self.logger.info("[Daemon] Anonymous statistics disabled")
        else:
            self.logger.info("[Daemon] Anonymous statistics enabled")
Example #20
0
    def __init__(self, new_logger):
        threading.Thread.__init__(self)

        self.startup_timer = timeit.default_timer()
        self.thread_shutdown_timer = None
        self.logger = new_logger
        self.daemon_run = True
        self.terminated = False
        self.controller = {}
        self.controller['LCD'] = {}
        self.controller['Log'] = {}
        self.controller['PID'] = {}
        self.controller['Relay'] = None
        self.controller['Sensor'] = {}
        self.controller['Timer'] = {}
        self.timer_ram_use = time.time()
        self.timer_stats = time.time() + 120
        with session_scope(MYCODO_DB_PATH) as new_session:
            misc = new_session.query(Misc).first()
            self.opt_out_statistics = misc.stats_opt_out
        if self.opt_out_statistics:
            self.logger.info("[Daemon] Anonymous statistics disabled")
        else:
            self.logger.info("[Daemon] Anonymous statistics enabled")
Example #21
0
def db_retrieve_table(database, table, entry=None, device_id=None):
    """
    Return SQL database query object with optional filtering

    If entry='first', only the first table entry is returned.
    If entry='all', all table entries are returned.
    If device_id is set, the first entry with that device ID is returned.
    Otherwise, the table object is returned.
    """
    with session_scope(database) as new_session:
        if device_id:
            return_table = new_session.query(table).filter(
                table.id == device_id)
        else:
            return_table = new_session.query(table)

        if entry == 'first' or device_id:
            return_table = return_table.first()
        elif entry == 'all':
            return_table = return_table.all()

        new_session.expunge_all()
        new_session.close()
    return return_table
Example #22
0
    def get_lcd_strings(self):
        """
        Retrieve measurements and/or timestamps and create strings for LCDs
        If no data is retrieveable, create string "NO DATA RETURNED".
        """
        # loop to acquire all measurements required to be displayed on the LCD
        for i in range(1, self.lcd_y_lines+1):
            if self.lcd_line[i]['id']:
                # Get latest measurement (from within the past minute) from influxdb
                # FROM '/.*/' returns any measurement (for grabbing time of last measurement)
                last_measurement_success = False
                try:
                    if self.lcd_line[i]['measurement'] == 'relay_state':
                        self.lcd_line[i]['measurement_value'] = self.relay_state(self.lcd_line[i]['id'])
                        last_measurement_success = True
                    else:
                        if self.lcd_line[i]['measurement'] == 'time':
                            last_measurement = read_last_influxdb(
                                INFLUXDB_HOST,
                                INFLUXDB_PORT,
                                INFLUXDB_USER,
                                INFLUXDB_PASSWORD,
                                INFLUXDB_DATABASE,
                                self.lcd_line[i]['id'],
                                '/.*/',
                                duration_min=5).raw
                        else:
                            last_measurement = read_last_influxdb(
                                INFLUXDB_HOST,
                                INFLUXDB_PORT,
                                INFLUXDB_USER,
                                INFLUXDB_PASSWORD,
                                INFLUXDB_DATABASE,
                                self.lcd_line[i]['id'],
                                self.lcd_line[i]['measurement'],
                                duration_min=5).raw
                        if last_measurement:
                            number = len(last_measurement['series'][0]['values'])
                            self.lcd_line[i]['time'] = last_measurement['series'][0]['values'][number-1][0]
                            self.lcd_line[i]['measurement_value'] = last_measurement['series'][0]['values'][number-1][1]
                            utc_dt = datetime.datetime.strptime(self.lcd_line[i]['time'].split(".")[0], '%Y-%m-%dT%H:%M:%S')
                            utc_timestamp = calendar.timegm(utc_dt.timetuple())
                            local_timestamp = str(datetime.datetime.fromtimestamp(utc_timestamp))
                            self.logger.debug("[LCD {}] Latest {}: {} @ {}".format(
                                self.lcd_id, self.lcd_line[i]['measurement'],
                                self.lcd_line[i]['measurement_value'], local_timestamp))
                            last_measurement_success = True
                        else:
                            self.lcd_line[i]['time'] = None
                            self.lcd_line[i]['measurement_value'] = None
                            self.logger.debug("[LCD {}] No data returned "
                                "from influxdb".format(self.lcd_id))
                except Exception as except_msg:
                    self.logger.debug("[LCD {}] Failed to read "
                        "measurement from the influxdb database: "
                        "{}".format(self.lcd_id, except_msg))

                try:
                    if last_measurement_success:
                        # Determine if the LCD output will have a value unit
                        measurement = ''
                        if self.lcd_line[i]['measurement'] == 'setpoint':
                            with session_scope(MYCODO_DB_PATH) as new_session:
                                pid = new_session.query(PID).filter(
                                    PID.id == self.lcd_line[i]['id']).first()
                                new_session.expunge_all()
                                new_session.close()
                                measurement = pid.measure_type
                        elif self.lcd_line[i]['measurement'] in ['temperature',
                                                                 'temperature_die',
                                                                 'temperature_object',
                                                                 'humidity',
                                                                 'co2',
                                                                 'lux',
                                                                 'pressure',
                                                                 'altitude']:
                            measurement = self.lcd_line[i]['measurement']
                        elif self.lcd_line[i]['measurement'] == 'duration_sec':
                            measurement = 'duration_sec'
                        
                        # Produce the line that will be displayed on the LCD
                        number_characters = self.lcd_x_characters
                        if self.lcd_line[i]['measurement'] == 'time':
                            # Convert UTC timestamp to local timezone
                            utc_dt = datetime.datetime.strptime(self.lcd_line[i]['time'].split(".")[0], '%Y-%m-%dT%H:%M:%S')
                            utc_timestamp = calendar.timegm(utc_dt.timetuple())
                            self.lcd_string_line[i] = str(datetime.datetime.fromtimestamp(utc_timestamp))
                        elif measurement:
                            value_length = len(str(self.lcd_line[i]['measurement_value']))
                            unit_length = len(self.measurement_unit['metric'][measurement])
                            name_length = number_characters - value_length - unit_length - 2
                            name_cropped = self.lcd_line[i]['name'].ljust(name_length)[:name_length]
                            self.lcd_string_line[i] = '{} {} {}'.format(
                                name_cropped,
                                self.lcd_line[i]['measurement_value'],
                                self.measurement_unit['metric'][measurement])
                        else:
                            value_length = len(str(self.lcd_line[i]['measurement_value']))
                            name_length = number_characters - value_length - 1
                            name_cropped = self.lcd_line[i]['name'][:name_length]
                            self.lcd_string_line[i] = '{} {}'.format(
                                name_cropped,
                                self.lcd_line[i]['measurement_value'])
                    else:
                        self.lcd_string_line[i] = 'NO DATA < 5 MIN'
                except Exception as except_msg:
                    self.logger.exception("[LCD {}] Error ({}): {}".format(
                        self.lcd_id, except_msg))
            else:
                self.lcd_string_line[i] = ''
    def checkConditionals(self, cond_id):
        """
        Check if any sensor conditional statements are activated and
        execute their actions if the conditional is true.

        For example, if measured temperature is above 30C, notify [email protected]

        :rtype: None

        :param each_cond: Object of SQL table entries for a specific column
        :type each_cond: sqlalchemy object
        """
        last_measurement = self.getLastMeasurement(self.cond_measurement_type[cond_id])
        if last_measurement:
            message = "[Sensor Conditional {}] Last measurement {} is ".format(
                    cond_id, last_measurement)

            if ((self.cond_direction[cond_id] == 'above' and
                    last_measurement > self.cond_setpoint[cond_id]) or
                (self.cond_direction[cond_id] == 'below' and
                    last_measurement < self.cond_setpoint[cond_id])):

                if (self.cond_relay_id[cond_id] and
                        self.cond_relay_state[cond_id] in ['on', 'off']):
                    message += " {} {}, turning relay {} {}".format(
                            self.cond_direction[cond_id],
                            self.cond_setpoint[cond_id],
                            self.cond_relay_id[cond_id],
                            self.cond_relay_state[cond_id])
                    if (self.cond_relay_state[cond_id] == 'on' and
                            self.cond_relay_on_duration[cond_id]):
                        message += " for {} seconds".format(self.cond_relay_on_duration[cond_id])
                    message += ". "
                    relay_on_off = threading.Thread(
                        target=self.control.relay_on_off,
                        args=(self.cond_relay_id[cond_id],
                              self.cond_relay_state[cond_id],
                              self.cond_relay_on_duration[cond_id],))
                    relay_on_off.start()

                if self.cond_execute_command[cond_id]:
                    message += "Execute '{}'. ".format(
                            self.cond_execute_command[cond_id])
                    # execute command here

                if self.cond_email_notify[cond_id]:
                    if (self.email_count >= self.smtp_max_count and
                            time.time() < self.smtp_wait_timer[cond_id]):
                         self.allowed_to_send_notice = False
                    else:
                        if time.time() > self.smtp_wait_timer[cond_id]:
                            self.email_count = 0
                            self.smtp_wait_timer[cond_id] = time.time()+3600
                        self.allowed_to_send_notice = True
                    self.email_count += 1

                    if self.allowed_to_send_notice:
                        message += "Notify {}.".format(
                                self.cond_email_notify[cond_id])
                        with session_scope(MYCODO_DB_PATH) as new_session:
                            smtp = new_session.query(SMTP).first()
                            email(self.logger, smtp.host, smtp.ssl, smtp.port,
                                  smtp.user, smtp.passw, smtp.email_from,
                                  self.cond_email_notify[cond_id], message)
                    else:
                        self.logger.debug("[Sensor Conditional {}] "
                                          "{:.0f} seconds left to be "
                                          "allowed to email again.".format(
                                          cond_id,
                                          (self.smtp_wait_timer[cond_id]-time.time())))

                if self.cond_flash_lcd[cond_id]:
                    start_flashing = threading.Thread(
                        target=self.control.flash_lcd,
                        args=(self.cond_flash_lcd[cond_id],
                              1,))
                    start_flashing.start()

            self.logger.debug(message)
        else:
            self.logger.debug("[Sensor Conditional {}] Last measurement "
                              "not found".format(cond_id))
Example #24
0
def send_stats(logger, host, port, user, password, dbname):
    """
    Send anonymous usage statistics

    Example use:
        current_stat = return_stat_file_dict()
        add_update_stat(logger, 'stat', current_stat['stat'] + 5)
    """
    try:
        client = InfluxDBClient(host, port, user, password, dbname)
        # Prepare stats before sending
        with session_scope(MYCODO_DB_PATH) as new_session:
            relays = new_session.query(Relay)
            add_update_stat(logger, 'num_relays', get_count(relays))

            sensors = new_session.query(Sensor)
            add_update_stat(logger, 'num_sensors', get_count(sensors))
            add_update_stat(logger, 'num_sensors_active', get_count(sensors.filter(
                Sensor.activated == True)))

            pids = new_session.query(PID)
            add_update_stat(logger, 'num_pids', get_count(pids))
            add_update_stat(logger, 'num_pids_active', get_count(pids.filter(
                PID.activated == True)))

            lcds = new_session.query(LCD)
            add_update_stat(logger, 'num_lcds', get_count(lcds))
            add_update_stat(logger, 'num_lcds_active', get_count(lcds.filter(
                LCD.activated == True)))

            logs = new_session.query(Log)
            add_update_stat(logger, 'num_logs', get_count(logs))
            add_update_stat(logger, 'num_logs_active', get_count(logs.filter(
                Log.activated == True)))
            
            timers = new_session.query(Timer)
            add_update_stat(logger, 'num_timers', get_count(timers))
            add_update_stat(logger, 'num_timers_active', get_count(timers.filter(
                Timer.activated == True)))

        add_update_stat(logger, 'country', geocoder.ip('me').country)
        add_update_stat(logger, 'ram_use_mb', resource.getrusage(
            resource.RUSAGE_SELF).ru_maxrss / float(1000))
        
        user_count = 0
        admin_count = 0
        with session_scope(USER_DB_PATH) as db_session:
            users = db_session.query(Users).all()
            for each_user in users:
                user_count += 1
                if each_user.user_restriction == 'admin':
                    admin_count += 1
        add_update_stat(logger, 'num_users_admin', admin_count)
        add_update_stat(logger, 'num_users_guest', user_count-admin_count)

        add_update_stat(logger, 'Mycodo_revision', MYCODO_VERSION)

        # Combine stats into list of dictionaries to be pushed to influxdb
        new_stats_dict = return_stat_file_dict()
        formatted_stat_dict = []
        for each_key, each_value in new_stats_dict.iteritems():
            if each_key != 'stat':  # Do not send header row
                formatted_stat_dict = add_stat_dict(formatted_stat_dict,
                                                    new_stats_dict['id'],
                                                    each_key,
                                                    each_value)

        # Send stats to influxdb
        client.write_points(formatted_stat_dict)
        logger.debug("[Daemon] Sent anonymous usage statistics")
        return 0
    except Exception as except_msg:
        logger.warning('[Daemon] Could not send anonymous usage statictics: '
            '{}'.format(except_msg))
        return 1
Example #25
0
    def checkConditionals(self, relay_id, on_duration):
        with session_scope(MYCODO_DB_PATH) as new_session:
            conditionals = new_session.query(RelayConditional)
            new_session.expunge_all()
            new_session.close()

        conditionals = conditionals.filter(RelayConditional.if_relay_id == relay_id)
        conditionals = conditionals.filter(RelayConditional.activated == True)

        if self.is_on(relay_id):
            conditionals = conditionals.filter(RelayConditional.if_action == 'on')
            conditionals = conditionals.filter(RelayConditional.if_duration == on_duration)
        else:
            conditionals = conditionals.filter(RelayConditional.if_action == 'off')

        for each_conditional in conditionals.all():
            message = None
            if (each_conditional.do_relay_id or
                    each_conditional.execute_command or
                    each_conditional.email_notify):
                now = time.time()
                timestamp = datetime.datetime.fromtimestamp(now).strftime('%Y-%m-%d %H-%M-%S')
                message = "{}\n[Relay Conditional {}] {}\n".format(
                    timestamp, each_conditional.id, each_conditional.name)
                message += "If relay {} ({}) turns {}, Then:\n".format(
                    each_conditional.if_relay_id,
                    self.relay_name[each_conditional.if_relay_id],
                    each_conditional.if_action)

            if each_conditional.do_relay_id:
                message += "Turn relay {} ({}) {}".format(
                        each_conditional.do_relay_id,
                        self.relay_name[each_conditional.do_relay_id],
                        each_conditional.do_action)

                if each_conditional.do_duration == 0:
                    self.relay_on_off(each_conditional.do_relay_id,
                                      each_conditional.do_action)
                else:
                    message += " for {} seconds".format(each_conditional.do_duration)
                    self.relay_on_off(each_conditional.do_relay_id,
                                      each_conditional.do_action,
                                      each_conditional.do_duration)
                message += ".\n"

            if each_conditional.execute_command:
                # Execute command as user mycodo
                message += "Execute: '{}'. ".format(
                    each_conditional.execute_command)
                cmd_out, cmd_err, cmd_status = cmd_output(
                    self.cond_execute_command[cond_id])
                message += "Status: {}. ".format(cmd_status)

            if each_conditional.email_notify:
                if (self.email_count >= self.smtp_max_count and
                        time.time() < self.smtp_wait_time):
                     self.allowed_to_send_notice = False
                else:
                    if time.time() > self.smtp_wait_time:
                        self.email_count = 0
                        self.smtp_wait_time = time.time() + 3600
                    self.allowed_to_send_notice = True
                self.email_count += 1

                if self.allowed_to_send_notice:
                    message += "Notify {}.".format(
                        each_conditional.email_notify)

                    with session_scope(MYCODO_DB_PATH) as new_session:
                        smtp = new_session.query(SMTP).first()
                        send_email(self.logger, smtp.host, smtp.ssl, smtp.port,
                              smtp.user, smtp.passw, smtp.email_from,
                              each_conditional.email_notify, message)
                else:
                    self.logger.debug("[Relay Conditional {}] True: "
                                      "{:.0f} seconds left to be "
                                      "allowed to email again.".format(
                                      each_cond.id,
                                      (self.smtp_wait_time-time.time())))

            if each_conditional.flash_lcd:
                start_flashing = threading.Thread(
                    target=self.control.flash_lcd,
                    args=(each_conditional.flash_lcd,
                          1,))
                start_flashing.start()

            if (each_conditional.do_relay_id or
                    each_conditional.execute_command or
                    each_conditional.email_notify):
                self.logger.debug("{}".format(message))
Example #26
0
    def __init__(self, ready, logger, lcd_id):
        threading.Thread.__init__(self)

        self.thread_startup_timer = timeit.default_timer()
        self.thread_shutdown_timer = 0
        self.ready = ready
        self.logger = logger
        self.flash_lcd_on = False
        self.lcd_is_on = False

        try:
            with session_scope(MYCODO_DB_PATH) as new_session:
                lcd = new_session.query(LCD).filter(LCD.id == lcd_id).first()
                self.lcd_id = lcd_id
                self.lcd_name = lcd.name
                self.lcd_pin = lcd.pin
                self.lcd_period = lcd.period
                self.lcd_x_characters = lcd.x_characters
                self.lcd_y_lines = lcd.y_lines

                if lcd.multiplexer_address:
                    self.multiplexer_address_string = lcd.multiplexer_address
                    self.multiplexer_address = int(
                        str(lcd.multiplexer_address), 16)
                    self.multiplexer_channel = lcd.multiplexer_channel
                    self.multiplexer = TCA9548A(self.multiplexer_address)
                else:
                    self.multiplexer = None

                self.lcd_line = {}
                for i in range(1, 5):
                    self.lcd_line[i] = {}

                list_sensors = [
                    'sensor_time', 'temperature', 'humidity', 'co2',
                    'pressure', 'altitude', 'temperature_die',
                    'temperature_object', 'lux'
                ]

                list_PIDs = ['setpoint', 'pid_time']

                list_relays = ['duration_sec', 'relay_time', 'relay_state']

                if self.lcd_y_lines in [2, 4]:
                    self.lcd_line[1]['id'] = lcd.line_1_sensor_id
                    self.lcd_line[1]['measurement'] = lcd.line_1_measurement
                    if lcd.line_1_sensor_id:
                        if lcd.line_1_measurement in list_sensors:
                            table = Sensor
                        elif lcd.line_1_measurement in list_PIDs:
                            table = PID
                        elif lcd.line_1_measurement in list_relays:
                            table = Relay
                        sensor_line_1 = new_session.query(table).filter(
                            table.id == lcd.line_1_sensor_id).first()
                        self.lcd_line[1]['name'] = sensor_line_1.name
                        if 'time' in lcd.line_1_measurement:
                            self.lcd_line[1]['measurement'] = 'time'

                    self.lcd_line[2]['id'] = lcd.line_2_sensor_id
                    self.lcd_line[2]['measurement'] = lcd.line_2_measurement
                    if lcd.line_2_sensor_id:
                        if lcd.line_2_measurement in list_sensors:
                            table = Sensor
                        elif lcd.line_2_measurement in list_PIDs:
                            table = PID
                        elif lcd.line_2_measurement in list_relays:
                            table = Relay
                        sensor_line_2 = new_session.query(table).filter(
                            table.id == lcd.line_2_sensor_id).first()
                        self.lcd_line[2]['name'] = sensor_line_2.name
                        if 'time' in lcd.line_2_measurement:
                            self.lcd_line[2]['measurement'] = 'time'

                if self.lcd_y_lines == 4:
                    self.lcd_line[3]['id'] = lcd.line_3_sensor_id
                    self.lcd_line[3]['measurement'] = lcd.line_3_measurement
                    if lcd.line_3_sensor_id:
                        if lcd.line_3_measurement in list_sensors:
                            table = Sensor
                        elif lcd.line_3_measurement in list_PIDs:
                            table = PID
                        elif lcd.line_3_measurement in list_relays:
                            table = Relay
                        sensor_line_3 = new_session.query(table).filter(
                            table.id == lcd.line_3_sensor_id).first()
                        self.lcd_line[3]['name'] = sensor_line_3.name
                        if 'time' in lcd.line_3_measurement:
                            self.lcd_line[3]['measurement'] = 'time'

                    self.lcd_line[4]['id'] = lcd.line_4_sensor_id
                    self.lcd_line[4]['measurement'] = lcd.line_4_measurement
                    if lcd.line_4_sensor_id:
                        if lcd.line_4_measurement in list_sensors:
                            table = Sensor
                        elif lcd.line_4_measurement in list_PIDs:
                            table = PID
                        elif lcd.line_4_measurement in list_relays:
                            table = Relay
                        sensor_line_4 = new_session.query(table).filter(
                            table.id == lcd.line_4_sensor_id).first()
                        self.lcd_line[4]['name'] = sensor_line_4.name
                        if 'time' in lcd.line_4_measurement:
                            self.lcd_line[4]['measurement'] = 'time'

            self.measurement_unit = {}
            self.measurement_unit['metric'] = {
                "temperature": "C",
                "humidity": "%",
                "co2": "ppmv",
                "pressure": "Pa",
                "altitude": "m",
                "duration_sec": "s",
                "temperature_die": "C",
                "temperature_object": "C",
                "lux": "lux",
            }
            self.measurement_unit['standard'] = {
                "temperature": "F",
                "humidity": "%",
                "co2": "ppmv",
                "pressure": "atm",
                "altitude": "ft",
                "duration_sec": "s",
                "temperature_die": "F",
                "temperature_object": "F",
                "lux": "lux",
            }

            self.timer = time.time() + self.lcd_period
            self.backlight_timer = time.time()

            self.lcd_string_line = {}
            for i in range(1, self.lcd_y_lines + 1):
                self.lcd_string_line[i] = ''

            self.LCD_WIDTH = self.lcd_x_characters  # Maximum characters per line

            self.LCD_LINE = {}
            self.LCD_LINE[1] = 0x80  # LCD RAM address for the 1st line
            self.LCD_LINE[2] = 0xC0  # LCD RAM address for the 2nd line
            self.LCD_LINE[3] = 0x94  # LCD RAM address for the 3rd line
            self.LCD_LINE[4] = 0xD4  # LCD RAM address for the 4th line

            self.LCD_CHR = 1  # Mode - Sending data
            self.LCD_CMD = 0  # Mode - SenLCDding command

            self.LCD_BACKLIGHT = 0x08  # On
            self.LCD_BACKLIGHT_OFF = 0x00  # Off

            self.ENABLE = 0b00000100  # Enable bit

            # Timing constants
            self.E_PULSE = 0.0005
            self.E_DELAY = 0.0005

            # Setup I2C bus
            try:
                if GPIO.RPI_REVISION == 2 or GPIO.RPI_REVISION == 3:
                    I2C_bus_number = 1
                else:
                    I2C_bus_number = 0
                self.bus = smbus.SMBus(I2C_bus_number)
            except Exception as except_msg:
                self.logger.exception(
                    "Could not initialize I2C bus: {}".format(except_msg))

            self.I2C_ADDR = int(self.lcd_pin, 16)
            self.lcd_init()
            self.lcd_string_write('Mycodo {}'.format(MYCODO_VERSION),
                                  self.LCD_LINE[1])
            self.lcd_string_write('Start {}'.format(self.lcd_name),
                                  self.LCD_LINE[2])
        except Exception as except_msg:
            self.logger.exception("[LCD {}] Error: {}".format(
                self.lcd_id, except_msg))
    def __init__(self, ready, logger, sensor_id):
        threading.Thread.__init__(self)

        list_devices_i2c = [
            'ADS1x15', 'AM2315', 'ATLAS_PT1000', 'BMP', 'HTU21D', 'MCP342x',
            'SHT2x', 'TMP006', 'TSL2561'
        ]

        self.thread_startup_timer = timeit.default_timer()
        self.thread_shutdown_timer = 0
        self.ready = ready
        self.logger = logger
        self.lock = {}
        self.sensor_id = sensor_id
        self.control = DaemonControl()
        self.pause_loop = False
        self.verify_pause_loop = True
        self.setup_sensor_conditionals()

        with session_scope(MYCODO_DB_PATH) as new_session:
            sensor = new_session.query(Sensor)
            sensor = sensor.filter(Sensor.id == self.sensor_id).first()
            self.i2c_bus = sensor.i2c_bus
            self.location = sensor.location
            self.device_type = sensor.device
            self.sensor_type = sensor.device_type
            self.period = sensor.period
            self.multiplexer_address_raw = sensor.multiplexer_address
            self.multiplexer_bus = sensor.multiplexer_bus
            self.multiplexer_channel = sensor.multiplexer_channel
            self.adc_channel = sensor.adc_channel
            self.adc_gain = sensor.adc_gain
            self.adc_resolution = sensor.adc_resolution
            self.adc_measure = sensor.adc_measure
            self.adc_measure_units = sensor.adc_measure_units
            self.adc_volts_min = sensor.adc_volts_min
            self.adc_volts_max = sensor.adc_volts_max
            self.adc_units_min = sensor.adc_units_min
            self.adc_units_max = sensor.adc_units_max
            self.sht_clock_pin = sensor.sht_clock_pin
            self.sht_voltage = sensor.sht_voltage

            # Edge detection
            self.switch_edge = sensor.switch_edge
            self.switch_bouncetime = sensor.switch_bouncetime
            self.switch_reset_period = sensor.switch_reset_period

            # Relay that will activate prior to sensor read
            self.pre_relay_id = sensor.pre_relay_id
            self.pre_relay_duration = sensor.pre_relay_duration
            self.pre_relay_setup = False
            self.next_measurement = time.time()
            self.get_new_measurement = False
            self.measurement_acquired = False
            self.pre_relay_activated = False
            self.pre_relay_timer = time.time()
            relay = new_session.query(Relay).all()
            for each_relay in relay:  # Check if relay ID actually exists
                if each_relay.id == self.pre_relay_id and self.pre_relay_duration:
                    self.pre_relay_setup = True

            smtp = new_session.query(SMTP).first()
            self.smtp_max_count = smtp.hourly_max
            self.email_count = 0
            self.allowed_to_send_notice = True

        # Convert string I2C address to base-16 int
        if self.device_type in list_devices_i2c:
            self.i2c_address = int(str(self.location), 16)

        # Set up multiplexer if enabled
        if self.device_type in list_devices_i2c and self.multiplexer_address_raw:
            self.multiplexer_address_string = self.multiplexer_address_raw
            self.multiplexer_address = int(str(self.multiplexer_address_raw),
                                           16)
            self.multiplexer_lock_file = "/var/lock/mycodo_multiplexer_0x{:02X}.pid".format(
                self.multiplexer_address)
            self.multiplexer = TCA9548A(self.multiplexer_bus,
                                        self.multiplexer_address)
        else:
            self.multiplexer = None

        if self.device_type in ['ADS1x15', 'MCP342x'] and self.location:
            self.adc_lock_file = "/var/lock/mycodo_adc_bus{}_0x{:02X}.pid".format(
                self.i2c_bus, self.i2c_address)

        # Set up edge detection of a GPIO pin
        if self.device_type == 'EDGE':
            if self.switch_edge == 'rising':
                self.switch_edge_gpio = GPIO.RISING
            elif self.switch_edge == 'falling':
                self.switch_edge_gpio = GPIO.FALLING
            else:
                self.switch_edge_gpio = GPIO.BOTH

        # Set up analog-to-digital converter
        elif self.device_type == 'ADS1x15':
            self.adc = ADS1x15_read(self.i2c_address, self.i2c_bus,
                                    self.adc_channel, self.adc_gain)
        elif self.device_type == 'MCP342x':
            self.adc = MCP342x_read(self.i2c_address, self.i2c_bus,
                                    self.adc_channel, self.adc_gain,
                                    self.adc_resolution)
        else:
            self.adc = None

        self.device_recognized = True

        # Set up sensor
        if self.device_type in ['EDGE', 'ADS1x15', 'MCP342x']:
            self.measure_sensor = None
        elif self.device_type == 'RPiCPULoad':
            self.measure_sensor = RaspberryPiCPULoad()
        elif self.device_type == 'RPi':
            self.measure_sensor = RaspberryPiCPUTemp()
        elif self.device_type == 'DS18B20':
            self.measure_sensor = DS18B20(self.location)
        elif self.device_type == 'DHT11':
            self.measure_sensor = DHT11(pigpio.pi(), int(self.location))
        elif self.device_type in ['DHT22', 'AM2302']:
            self.measure_sensor = DHT22(pigpio.pi(), int(self.location))
        elif self.device_type == 'HTU21D':
            self.measure_sensor = HTU21D_read(self.i2c_bus)
        elif self.device_type == 'AM2315':
            self.measure_sensor = AM2315_read(self.i2c_bus)
        elif self.device_type == 'ATLAS_PT1000':
            self.measure_sensor = Atlas_PT1000(self.i2c_address, self.i2c_bus)
        elif self.device_type == 'K30':
            self.measure_sensor = K30()
        elif self.device_type == 'BMP':
            self.measure_sensor = BMP(self.i2c_bus)
        elif self.device_type == 'SHT1x_7x':
            self.measure_sensor = SHT1x_7x_read(self.location,
                                                self.sht_clock_pin,
                                                self.sht_voltage)
        elif self.device_type == 'SHT2x':
            self.measure_sensor = SHT2x_read(self.i2c_address, self.i2c_bus)
        elif self.device_type == 'TMP006':
            self.measure_sensor = TMP006_read(self.i2c_address, self.i2c_bus)
        elif self.device_type == 'TSL2561':
            self.measure_sensor = TSL2561_read(self.i2c_address, self.i2c_bus)
        else:
            self.device_recognized = False
            self.logger.debug("[Sensor {}] Device '{}' not "
                              "recognized:".format(self.sensor_id,
                                                   self.device_type))
            raise Exception("{} is not a valid device type.".format(
                self.device_type))

        self.edge_reset_timer = time.time()
        self.sensor_timer = time.time()
        self.running = False
        self.lastUpdate = None
    def checkConditionals(self, cond_id):
        """
        Check if any sensor conditional statements are activated and
        execute their actions if the conditional is true.

        For example, if measured temperature is above 30C, notify [email protected]

        :rtype: None

        :param each_cond: Object of SQL table entries for a specific column
        :type each_cond: sqlalchemy object
        """
        attachment_file = False
        attachment_type = False

        last_measurement = self.getLastMeasurement(
            self.cond_measurement_type[cond_id])
        if (last_measurement
                and ((self.cond_direction[cond_id] == 'above'
                      and last_measurement > self.cond_setpoint[cond_id]) or
                     (self.cond_direction[cond_id] == 'below'
                      and last_measurement < self.cond_setpoint[cond_id]))):

            now = time.time()
            timestamp = datetime.datetime.fromtimestamp(now).strftime(
                '%Y-%m-%d %H-%M-%S')
            message = "{}\n[Sensor Conditional {}] {}\n{} {} ".format(
                timestamp, cond_id, self.cond_name[cond_id],
                self.cond_measurement_type[cond_id], last_measurement)
            if self.cond_direction[cond_id] == 'above':
                message += ">"
            elif self.cond_direction[cond_id] == 'below':
                message += "<"
            message += " {} setpoint.".format(self.cond_setpoint[cond_id])

            if (self.cond_relay_id[cond_id]
                    and self.cond_relay_state[cond_id] in ['on', 'off']):
                message += "\nTurning relay {} {}".format(
                    self.cond_relay_id[cond_id],
                    self.cond_relay_state[cond_id])
                if (self.cond_relay_state[cond_id] == 'on'
                        and self.cond_relay_on_duration[cond_id]):
                    message += " for {} seconds".format(
                        self.cond_relay_on_duration[cond_id])
                message += ". "
                relay_on_off = threading.Thread(
                    target=self.control.relay_on_off,
                    args=(
                        self.cond_relay_id[cond_id],
                        self.cond_relay_state[cond_id],
                        self.cond_relay_on_duration[cond_id],
                    ))
                relay_on_off.start()

            # Execute command in shell
            if self.cond_execute_command[cond_id]:
                message += "\nExecute '{}'. ".format(
                    self.cond_execute_command[cond_id])
                cmd_out, cmd_err, cmd_status = cmd_output(
                    self.cond_execute_command[cond_id])
                message += "Status: {}. ".format(cmd_status)

            if self.cond_camera_record[cond_id] in ['photo', 'photoemail']:
                attachment_file = camera_record(INSTALL_DIRECTORY, 'photo')
            elif self.cond_camera_record[cond_id] in ['video', 'videoemail']:
                attachment_file = camera_record(INSTALL_DIRECTORY,
                                                'video',
                                                duration_sec=5)

            if self.cond_email_notify[cond_id]:
                if (self.email_count >= self.smtp_max_count
                        and time.time() < self.smtp_wait_timer[cond_id]):
                    self.allowed_to_send_notice = False
                else:
                    if time.time() > self.smtp_wait_timer[cond_id]:
                        self.email_count = 0
                        self.smtp_wait_timer[cond_id] = time.time() + 3600
                    self.allowed_to_send_notice = True
                self.email_count += 1

                # If the emails per hour limit has not been exceeded
                if self.allowed_to_send_notice:
                    message += "\nNotify {}.".format(
                        self.cond_email_notify[cond_id])
                    # attachment_type != False indicates to
                    # attach a photo or video
                    if self.cond_camera_record[cond_id] == 'photoemail':
                        message += "\nPhoto attached."
                        attachment_type = 'still'
                    elif self.cond_camera_record[cond_id] == 'videoemail':
                        message += "\nVideo attached."
                        attachment_type = 'video'
                    with session_scope(MYCODO_DB_PATH) as new_session:
                        smtp = new_session.query(SMTP).first()
                        send_email(self.logger, smtp.host, smtp.ssl, smtp.port,
                                   smtp.user, smtp.passw, smtp.email_from,
                                   self.cond_email_notify[cond_id], message,
                                   attachment_file, attachment_type)
                else:
                    self.logger.debug(
                        "[Sensor Conditional {}] "
                        "{:.0f} seconds left to be "
                        "allowed to email again.".format(
                            cond_id,
                            (self.smtp_wait_timer[cond_id] - time.time())))

            if self.cond_flash_lcd[cond_id]:
                start_flashing = threading.Thread(
                    target=self.control.flash_lcd,
                    args=(
                        self.cond_flash_lcd[cond_id],
                        1,
                    ))
                start_flashing.start()

            self.logger.debug(message)
        else:
            self.logger.debug("[Sensor Conditional {}] Last measurement "
                              "not found".format(cond_id))
Example #29
0
    def __init__(self, ready, logger, pid_id):
        threading.Thread.__init__(self)

        self.thread_startup_timer = timeit.default_timer()
        self.thread_shutdown_timer = 0
        self.ready = ready
        self.logger = logger
        self.pid_id = pid_id
        self.control = DaemonControl()

        with session_scope(MYCODO_DB_PATH) as new_session:
            pid = new_session.query(PID).filter(PID.id == self.pid_id).first()
            self.sensor_id = pid.sensor_id
            self.measure_type = pid.measure_type
            self.method_id = pid.method_id
            self.direction = pid.direction
            self.raise_relay_id = pid.raise_relay_id
            self.raise_min_duration = pid.raise_min_duration
            self.raise_max_duration = pid.raise_max_duration
            self.lower_relay_id = pid.lower_relay_id
            self.lower_min_duration = pid.lower_min_duration
            self.lower_max_duration = pid.lower_max_duration
            self.Kp = pid.p
            self.Ki = pid.i
            self.Kd = pid.d
            self.Integrator_min = pid.integrator_min
            self.Integrator_max = pid.integrator_max
            self.measure_interval = pid.period
            self.default_set_point = pid.setpoint
            self.set_point = pid.setpoint
            sensor = new_session.query(Sensor).filter(Sensor.id == self.sensor_id).first()
            self.sensor_duration = sensor.period

        self.Derivator = 0
        self.Integrator = 0
        self.error = 0.0
        self.P_value = None
        self.I_value = None
        self.D_value = None
        self.raise_seconds_on = 0
        self.timer = t.time()+self.measure_interval

        # Check if a method is set for this PID
        if self.method_id:
            with session_scope(MYCODO_DB_PATH) as new_session:
                method = new_session.query(Method)
                method = method.filter(Method.method_id == self.method_id)
                method = method.filter(Method.method_order == 0).first()
                self.method_type = method.method_type
                self.method_start_time = method.start_time

            if self.method_type == 'Duration':
                if self.method_start_time == 'Ended':
                    # Method has ended and hasn't been instructed to begin again
                    pass
                elif self.method_start_time == 'Ready' or self.method_start_time == None:
                    # Method has been instructed to begin
                    with session_scope(MYCODO_DB_PATH) as db_session:
                        mod_method = db_session.query(Method)
                        mod_method = mod_method.filter(Method.method_id == self.method_id)
                        mod_method = mod_method.filter(Method.method_order == 0).first()
                        mod_method.start_time = datetime.datetime.now()
                        self.method_start_time = mod_method.start_time
                        db_session.commit()
                else:
                    # Method neither instructed to begin or not to
                    # Likely there was a daemon restart ot power failure
                    # Resume method with saved start_time
                    self.method_start_time = datetime.datetime.strptime(
                        self.method_start_time, '%Y-%m-%d %H:%M:%S.%f')
                    self.logger.warning("[PID {}] Resuming method {} started at {}".format(
                        self.pid_id, self.method_id, self.method_start_time))
Example #30
0
    def manipulate_relays(self):
        """
        Activate a relay based on PID output (control variable) and whether
        the manipulation directive is to raise, lower, or both.

        :rtype: None
        """
        # If there was a measurement able to be retrieved from
        # influxdb database that was entered within the past minute
        if self.last_measurement_success:

            # Update setpoint if a method is selected
            if self.method_id != '':
                self.calculate_method_setpoint(self.method_id)

            self.addSetpointInfluxdb(self.pid_id, self.set_point)

            # Update PID and get control variable
            self.control_variable = self.update(self.last_measurement)

            #
            # PID control variable positive to raise environmental condition
            #
            if self.direction in ['raise', 'both'] and self.raise_relay_id:
                if self.control_variable > 0:
                    # Ensure the relay on duration doesn't exceed the set maximum
                    if (self.raise_max_duration and
                            self.control_variable > self.raise_max_duration):
                        self.raise_seconds_on = self.raise_max_duration
                    else:
                        self.raise_seconds_on = float("{0:.2f}".format(
                            self.control_variable))

                    # Turn off lower_relay if active, because we're now raising
                    if self.lower_relay_id:
                        with session_scope(MYCODO_DB_PATH) as new_session:
                            relay = new_session.query(Relay).filter(
                                Relay.id == self.lower_relay_id).first()
                            if relay.is_on():
                                self.control.relay_off(self.lower_relay_id)

                    if self.raise_seconds_on > self.raise_min_duration:
                        # Activate raise_relay for a duration
                        self.logger.debug("[PID {}] Setpoint: {} "
                                          "Output: {} to relay {}".format(
                                              self.pid_id, self.set_point,
                                              self.control_variable,
                                              self.raise_relay_id))
                        self.control.relay_on(self.raise_relay_id,
                                              self.raise_seconds_on)
                else:
                    self.control.relay_off(self.raise_relay_id)

            #
            # PID control variable negative to lower environmental condition
            #
            if self.direction in ['lower', 'both'] and self.lower_relay_id:
                if self.control_variable < 0:
                    # Ensure the relay on duration doesn't exceed the set maximum
                    if (self.lower_max_duration and abs(self.control_variable)
                            > self.lower_max_duration):
                        self.lower_seconds_on = self.lower_max_duration
                    else:
                        self.lower_seconds_on = abs(
                            float("{0:.2f}".format(self.control_variable)))

                    # Turn off raise_relay if active, because we're now lowering
                    if self.raise_relay_id:
                        with session_scope(MYCODO_DB_PATH) as new_session:
                            relay = new_session.query(Relay).filter(
                                Relay.id == self.raise_relay_id).first()
                            if relay.is_on():
                                self.control.relay_off(self.raise_relay_id)

                    if self.lower_seconds_on > self.lower_min_duration:
                        # Activate lower_relay for a duration
                        self.logger.debug("[PID {}] Setpoint: {} "
                                          "Output: {} to relay {}".format(
                                              self.pid_id, self.set_point,
                                              self.control_variable,
                                              self.lower_relay_id))
                        self.control.relay_on(self.lower_relay_id,
                                              self.lower_seconds_on)
                else:
                    self.control.relay_off(self.lower_relay_id)

        else:
            if self.direction in ['raise', 'both'] and self.raise_relay_id:
                self.control.relay_off(self.raise_relay_id)
            if self.direction in ['lower', 'both'] and self.lower_relay_id:
                self.control.relay_off(self.lower_relay_id)
Example #31
0
    def calculate_method_setpoint(self, method_id):
        with session_scope(MYCODO_DB_PATH) as new_session:
            method = new_session.query(Method)
            new_session.expunge_all()
            new_session.close()

        method_key = method.filter(Method.method_id == method_id)
        method_key = method_key.filter(Method.method_order == 0).first()

        method = method.filter(Method.method_id == method_id)
        method = method.filter(Method.relay_id == None)
        method = method.filter(Method.method_order > 0)
        method = method.order_by(Method.method_order.asc()).all()

        now = datetime.datetime.now()

        # Calculate where the current time/date is within the time/date method
        if method_key.method_type == 'Date':
            for each_method in method:
                start_time = datetime.datetime.strptime(each_method.start_time, '%Y-%m-%d %H:%M:%S')
                end_time = end_time = datetime.datetime.strptime(each_method.end_time, '%Y-%m-%d %H:%M:%S')
                if start_time < now < end_time:
                    start_setpoint = each_method.start_setpoint
                    if each_method.end_setpoint:
                        end_setpoint = each_method.end_setpoint
                    else:
                        end_setpoint = each_method.start_setpoint

                    setpoint_diff = abs(end_setpoint-start_setpoint)
                    total_seconds = (end_time-start_time).total_seconds()
                    part_seconds = (now-start_time).total_seconds()
                    percent_total = part_seconds/total_seconds

                    if start_setpoint < end_setpoint:
                        new_setpoint = start_setpoint+(setpoint_diff*percent_total)
                    else:
                        new_setpoint = start_setpoint-(setpoint_diff*percent_total)

                    self.logger.debug("[Method] Start: {} End: {}".format(
                        start_time, end_time))
                    self.logger.debug("[Method] Start: {} End: {}".format(
                        start_setpoint, end_setpoint))
                    self.logger.debug("[Method] Total: {} Part total: {} ({}%)".format(
                        total_seconds, part_seconds, percent_total))
                    self.logger.debug("[Method] New Setpoint: {}".format(
                        new_setpoint))
                    self.set_point = new_setpoint
                    return 0

        # Calculate where the current Hour:Minute:Seconds is within the Daily method
        elif method_key.method_type == 'Daily':
            daily_now = datetime.datetime.now().strftime('%H:%M:%S')
            daily_now = datetime.datetime.strptime(str(daily_now), '%H:%M:%S')
            for each_method in method:
                start_time = datetime.datetime.strptime(each_method.start_time, '%H:%M:%S')
                end_time = end_time = datetime.datetime.strptime(each_method.end_time, '%H:%M:%S')
                if start_time < daily_now < end_time:
                    start_setpoint = each_method.start_setpoint
                    if each_method.end_setpoint:
                        end_setpoint = each_method.end_setpoint
                    else:
                        end_setpoint = each_method.start_setpoint

                    setpoint_diff = abs(end_setpoint-start_setpoint)
                    total_seconds = (end_time-start_time).total_seconds()
                    part_seconds = (daily_now-start_time).total_seconds()
                    percent_total = part_seconds/total_seconds

                    if start_setpoint < end_setpoint:
                        new_setpoint = start_setpoint+(setpoint_diff*percent_total)
                    else:
                        new_setpoint = start_setpoint-(setpoint_diff*percent_total)

                    self.logger.debug("[Method] Start: {} End: {}".format(
                        start_time.strftime('%H:%M:%S'), end_time.strftime('%H:%M:%S')))
                    self.logger.debug("[Method] Start: {} End: {}".format(
                        start_setpoint, end_setpoint))
                    self.logger.debug("[Method] Total: {} Part total: {} ({}%)".format(
                        total_seconds, part_seconds, percent_total))
                    self.logger.debug("[Method] New Setpoint: {}".format(
                        new_setpoint))
                    self.set_point = new_setpoint
                    return 0

        # Calculate sine y-axis value from the x-axis (seconds of the day)
        elif method_key.method_type == 'DailySine':
            new_setpoint = sine_wave_y_out(method_key.amplitude,
                                           method_key.frequency,
                                           method_key.shift_angle,
                                           method_key.shift_y)
            self.set_point = new_setpoint
            return 0

        # Calculate Bezier curve y-axis value from the x-axis (seconds of the day)
        elif method_key.method_type == 'DailyBezier':
            new_setpoint = bezier_curve_y_out(method_key.shift_angle,
                                              (method_key.x0, method_key.y0),
                                              (method_key.x1, method_key.y1),
                                              (method_key.x2, method_key.y2),
                                              (method_key.x3, method_key.y3))
            self.set_point = new_setpoint
            return 0

        # Calculate the duration in the method based on self.method_start_time
        elif method_key.method_type == 'Duration' and self.method_start_time != 'Ended':
            seconds_from_start = (now-self.method_start_time).total_seconds()
            total_sec = 0
            previous_total_sec = 0
            for each_method in method:
                total_sec += each_method.duration_sec
                if previous_total_sec <= seconds_from_start < total_sec:
                    row_start_time = float(self.method_start_time.strftime('%s'))+previous_total_sec
                    row_since_start_sec = (now-(self.method_start_time+datetime.timedelta(0, previous_total_sec))).total_seconds()
                    percent_row = row_since_start_sec/each_method.duration_sec

                    start_setpoint = each_method.start_setpoint
                    if each_method.end_setpoint:
                        end_setpoint = each_method.end_setpoint
                    else:
                        end_setpoint = each_method.start_setpoint
                    setpoint_diff = abs(end_setpoint-start_setpoint)
                    if start_setpoint < end_setpoint:
                        new_setpoint = start_setpoint+(setpoint_diff*percent_row)
                    else:
                        new_setpoint = start_setpoint-(setpoint_diff*percent_row)
                    
                    self.logger.debug("[Method] Start: {} Seconds Since: {}".format(
                        self.method_start_time, seconds_from_start))
                    self.logger.debug("[Method] Start time of row: {}".format(
                        datetime.datetime.fromtimestamp(row_start_time)))
                    self.logger.debug("[Method] Sec since start of row: {}".format(
                        row_since_start_sec))
                    self.logger.debug("[Method] Percent of row: {}".format(
                        percent_row))
                    self.logger.debug("[Method] New Setpoint: {}".format(
                        new_setpoint))
                    self.set_point = new_setpoint
                    return 0
                previous_total_sec = total_sec

            # Duration method has ended, reset start_time locally and in DB
            if self.method_start_time:
                with session_scope(MYCODO_DB_PATH) as db_session:
                    mod_method = db_session.query(Method).filter(
                        Method.method_id == self.method_id)
                    mod_method = mod_method.filter(Method.method_order == 0).first()
                    mod_method.start_time = 'Ended'
                    db_session.commit()
                self.method_start_time = 'Ended'

        # Setpoint not needing to be calculated, use default setpoint
        self.set_point = self.default_set_point
Example #32
0
    def run(self):
        self.start_all_controllers()
        self.startup_stats()

        try:
            # loop until daemon is instructed to shut down
            while self.daemon_run:
                now = time.time()

                # If timelapse active, take photo at predefined periods
                if (os.path.isfile(FILE_TIMELAPSE_PARAM) and
                        os.path.isfile(LOCK_FILE_TIMELAPSE)):
                    # Read user-defined timelapse parameters
                    with open(FILE_TIMELAPSE_PARAM, mode='r') as infile:
                        reader = csv.reader(infile)
                        dict_timelapse_param = OrderedDict((row[0], row[1]) for row in reader)
                    if now > float(dict_timelapse_param['end_time']):
                        try:
                            os.remove(FILE_TIMELAPSE_PARAM)
                            os.remove(LOCK_FILE_TIMELAPSE)
                        except:
                            pass
                    elif now > float(dict_timelapse_param['next_capture']):
                        # Ensure next capture is greater than now (in case of power failure/reboot)
                        next_capture = float(dict_timelapse_param['next_capture'])
                        capture_number = int(dict_timelapse_param['capture_number'])
                        while next_capture < now:
                            # Update last capture and image number to latest before capture
                            next_capture += float(dict_timelapse_param['interval'])
                            capture_number += 1
                        add_update_csv(logger,
                                                   FILE_TIMELAPSE_PARAM,
                                                   'next_capture',
                                                   next_capture)
                        add_update_csv(logger,
                                                   FILE_TIMELAPSE_PARAM,
                                                   'capture_number',
                                                   capture_number)
                        # Capture image
                        with session_scope(MYCODO_DB_PATH) as new_session:
                            camera = new_session.query(CameraStill).first()
                            camera_record(
                                INSTALL_DIRECTORY,
                                'timelapse',
                                camera,
                                start_time=dict_timelapse_param['start_time'],
                                capture_number=capture_number)

                elif (os.path.isfile(FILE_TIMELAPSE_PARAM) or
                        os.path.isfile(LOCK_FILE_TIMELAPSE)):
                    try:
                        os.remove(FILE_TIMELAPSE_PARAM)
                        os.remove(LOCK_FILE_TIMELAPSE)
                    except:
                        pass

                # Log ram usage every 24 hours
                if now > self.timer_ram_use:
                    self.timer_ram_use = now+86400
                    ram = resource.getrusage(
                        resource.RUSAGE_SELF).ru_maxrss / float(1000)
                    self.logger.info("[Daemon] {} MB ram in use".format(ram))

                # collect and send anonymous statistics
                if (not self.opt_out_statistics and
                        now > self.timer_stats):
                    self.send_stats()

                time.sleep(0.25)
            GPIO.cleanup()
        except Exception as except_msg:
            self.logger.exception("Unexpected error: {}: {}".format(
                sys.exc_info()[0], except_msg))
            raise

        # If the daemon errors or finishes, shut it down
        finally:
            self.logger.debug("[Daemon] Stopping all running controllers")
            self.stop_all_controllers()
            

        self.logger.info("[Daemon] Mycodo terminated in {:.3f} seconds".format(
            timeit.default_timer()-self.thread_shutdown_timer))
        self.terminated = True

        # Wait for the client to receive the response before it disconnects
        time.sleep(0.25)
Example #33
0
    def run(self):
        self.start_all_controllers()
        self.startup_stats()
        try:
            # loop until daemon is instructed to shut down
            while self.daemon_run:
                now = time.time()

                # If timelapse active, take photo at predefined periods
                if (os.path.isfile(FILE_TIMELAPSE_PARAM)
                        and os.path.isfile(LOCK_FILE_TIMELAPSE)):
                    # Read user-defined timelapse parameters
                    with open(FILE_TIMELAPSE_PARAM, mode='r') as infile:
                        reader = csv.reader(infile)
                        dict_timelapse_param = OrderedDict(
                            (row[0], row[1]) for row in reader)
                    if now > float(dict_timelapse_param['end_time']):
                        try:
                            os.remove(FILE_TIMELAPSE_PARAM)
                            os.remove(LOCK_FILE_TIMELAPSE)
                        except:
                            pass
                    elif now > float(dict_timelapse_param['next_capture']):
                        # Ensure next capture is greater than now (in case of power failure/reboot)
                        next_capture = float(
                            dict_timelapse_param['next_capture'])
                        capture_number = int(
                            dict_timelapse_param['capture_number'])
                        while next_capture < now:
                            # Update last capture and image number to latest before capture
                            next_capture += float(
                                dict_timelapse_param['interval'])
                            capture_number += 1
                        add_update_csv(logger, FILE_TIMELAPSE_PARAM,
                                       'next_capture', next_capture)
                        add_update_csv(logger, FILE_TIMELAPSE_PARAM,
                                       'capture_number', capture_number)
                        # Capture image
                        with session_scope(MYCODO_DB_PATH) as new_session:
                            camera = new_session.query(Method)
                            camera_record(
                                INSTALL_DIRECTORY,
                                'timelapse',
                                camera,
                                start_time=dict_timelapse_param['start_time'],
                                capture_number=capture_number)

                elif (os.path.isfile(FILE_TIMELAPSE_PARAM)
                      or os.path.isfile(LOCK_FILE_TIMELAPSE)):
                    try:
                        os.remove(FILE_TIMELAPSE_PARAM)
                        os.remove(LOCK_FILE_TIMELAPSE)
                    except:
                        pass

                # Log ram usage every 24 hours
                if now > self.timer_ram_use:
                    self.timer_ram_use = now + 86400
                    ram = resource.getrusage(
                        resource.RUSAGE_SELF).ru_maxrss / float(1000)
                    self.logger.info("[Daemon] {} MB ram in use".format(ram))

                # collect and send anonymous statistics
                if (not self.opt_out_statistics and now > self.timer_stats):
                    self.send_stats()

                time.sleep(0.25)
        except Exception as except_msg:
            self.logger.exception("Unexpected error: {}: {}".format(
                sys.exc_info()[0], except_msg))
            raise

        # If the daemon errors or finishes, shut it down
        finally:
            self.logger.debug("[Daemon] Stopping all running controllers")
            self.stop_all_controllers()

        self.logger.info("[Daemon] Mycodo terminated in {:.3f} seconds".format(
            timeit.default_timer() - self.thread_shutdown_timer))
        self.terminated = True

        # Wait for the client to receive the response before it disconnects
        time.sleep(0.25)
Example #34
0
    def __init__(self, ready, timer_id):
        threading.Thread.__init__(self)

        self.logger = logging.getLogger(
            "mycodo.timer_{id}".format(id=timer_id))

        self.thread_startup_timer = timeit.default_timer()
        self.thread_shutdown_timer = 0
        self.ready = ready
        self.timer_id = timer_id
        self.control = DaemonControl()

        timer = db_retrieve_table_daemon(Timer, device_id=self.timer_id)
        self.timer_type = timer.timer_type
        self.output_unique_id = timer.relay_id
        self.method_id = timer.method_id
        self.method_period = timer.method_period
        self.state = timer.state
        self.time_start = timer.time_start
        self.time_end = timer.time_end
        self.duration_on = timer.duration_on
        self.duration_off = timer.duration_off

        self.output_id = db_retrieve_table_daemon(
            Output, unique_id=self.output_unique_id).id

        # Time of day split into hour and minute
        if self.time_start:
            time_split = self.time_start.split(":")
            self.start_hour = time_split[0]
            self.start_minute = time_split[1]
        else:
            self.start_hour = None
            self.start_minute = None

        if self.time_end:
            time_split = self.time_end.split(":")
            self.end_hour = time_split[0]
            self.end_minute = time_split[1]
        else:
            self.end_hour = None
            self.end_minute = None

        self.duration_timer = time.time()
        self.pwm_method_timer = time.time()
        self.date_timer_not_executed = True
        self.running = False

        if self.method_id:
            method = db_retrieve_table_daemon(Method, device_id=self.method_id)
            method_data = db_retrieve_table_daemon(MethodData)
            method_data = method_data.filter(
                MethodData.method_id == self.method_id)
            method_data_repeat = method_data.filter(
                MethodData.duration_sec == 0).first()
            self.method_type = method.method_type
            self.method_start_act = timer.method_start_time
            self.method_start_time = None
            self.method_end_time = None

            if self.method_type == 'Duration':
                if self.method_start_act == 'Ended':
                    self.stop_controller(ended_normally=False,
                                         deactivate_timer=True)
                    self.logger.warning(
                        "Method has ended. "
                        "Activate the Timer controller to start it again.")
                elif self.method_start_act == 'Ready' or self.method_start_act is None:
                    # Method has been instructed to begin
                    now = datetime.datetime.now()
                    self.method_start_time = now
                    if method_data_repeat and method_data_repeat.duration_end:
                        self.method_end_time = now + datetime.timedelta(
                            seconds=float(method_data_repeat.duration_end))

                    with session_scope(MYCODO_DB_PATH) as db_session:
                        mod_timer = db_session.query(Timer)
                        mod_timer = mod_timer.filter(
                            Timer.id == self.timer_id).first()
                        mod_timer.method_start_time = self.method_start_time
                        mod_timer.method_end_time = self.method_end_time
                        db_session.commit()
            else:
                # Method neither instructed to begin or not to
                # Likely there was a daemon restart ot power failure
                # Resume method with saved start_time
                self.method_start_time = datetime.datetime.strptime(
                    str(timer.method_start_time), '%Y-%m-%d %H:%M:%S.%f')
                if method_data_repeat and method_data_repeat.duration_end:
                    self.method_end_time = datetime.datetime.strptime(
                        str(timer.method_end_time), '%Y-%m-%d %H:%M:%S.%f')
                    if self.method_end_time > datetime.datetime.now():
                        self.logger.warning(
                            "Resuming method {id}: started {start}, "
                            "ends {end}".format(id=self.method_id,
                                                start=self.method_start_time,
                                                end=self.method_end_time))
                    else:
                        self.method_start_act = 'Ended'
                else:
                    self.method_start_act = 'Ended'
Example #35
0
    def calculate_method_setpoint(self, method_id):
        method = db_retrieve_table(MYCODO_DB_PATH, Method)

        method_key = method.filter(Method.method_id == method_id)
        method_key = method_key.filter(Method.method_order == 0).first()

        method = method.filter(Method.method_id == method_id)
        method = method.filter(Method.relay_id == None)
        method = method.filter(Method.method_order > 0)
        method = method.order_by(Method.method_order.asc()).all()

        now = datetime.datetime.now()

        # Calculate where the current time/date is within the time/date method
        if method_key.method_type == 'Date':
            for each_method in method:
                start_time = datetime.datetime.strptime(
                    each_method.start_time, '%Y-%m-%d %H:%M:%S')
                end_time = datetime.datetime.strptime(each_method.end_time,
                                                      '%Y-%m-%d %H:%M:%S')
                if start_time < now < end_time:
                    start_setpoint = each_method.start_setpoint
                    if each_method.end_setpoint:
                        end_setpoint = each_method.end_setpoint
                    else:
                        end_setpoint = each_method.start_setpoint

                    setpoint_diff = abs(end_setpoint - start_setpoint)
                    total_seconds = (end_time - start_time).total_seconds()
                    part_seconds = (now - start_time).total_seconds()
                    percent_total = part_seconds / total_seconds

                    if start_setpoint < end_setpoint:
                        new_setpoint = start_setpoint + (setpoint_diff *
                                                         percent_total)
                    else:
                        new_setpoint = start_setpoint - (setpoint_diff *
                                                         percent_total)

                    self.logger.debug("[Method] Start: {} End: {}".format(
                        start_time, end_time))
                    self.logger.debug("[Method] Start: {} End: {}".format(
                        start_setpoint, end_setpoint))
                    self.logger.debug(
                        "[Method] Total: {} Part total: {} ({}%)".format(
                            total_seconds, part_seconds, percent_total))
                    self.logger.debug(
                        "[Method] New Setpoint: {}".format(new_setpoint))
                    self.set_point = new_setpoint
                    return 0

        # Calculate where the current Hour:Minute:Seconds is within the Daily method
        elif method_key.method_type == 'Daily':
            daily_now = datetime.datetime.now().strftime('%H:%M:%S')
            daily_now = datetime.datetime.strptime(str(daily_now), '%H:%M:%S')
            for each_method in method:
                start_time = datetime.datetime.strptime(
                    each_method.start_time, '%H:%M:%S')
                end_time = datetime.datetime.strptime(each_method.end_time,
                                                      '%H:%M:%S')
                if start_time < daily_now < end_time:
                    start_setpoint = each_method.start_setpoint
                    if each_method.end_setpoint:
                        end_setpoint = each_method.end_setpoint
                    else:
                        end_setpoint = each_method.start_setpoint

                    setpoint_diff = abs(end_setpoint - start_setpoint)
                    total_seconds = (end_time - start_time).total_seconds()
                    part_seconds = (daily_now - start_time).total_seconds()
                    percent_total = part_seconds / total_seconds

                    if start_setpoint < end_setpoint:
                        new_setpoint = start_setpoint + (setpoint_diff *
                                                         percent_total)
                    else:
                        new_setpoint = start_setpoint - (setpoint_diff *
                                                         percent_total)

                    self.logger.debug("[Method] Start: {} End: {}".format(
                        start_time.strftime('%H:%M:%S'),
                        end_time.strftime('%H:%M:%S')))
                    self.logger.debug("[Method] Start: {} End: {}".format(
                        start_setpoint, end_setpoint))
                    self.logger.debug(
                        "[Method] Total: {} Part total: {} ({}%)".format(
                            total_seconds, part_seconds, percent_total))
                    self.logger.debug(
                        "[Method] New Setpoint: {}".format(new_setpoint))
                    self.set_point = new_setpoint
                    return 0

        # Calculate sine y-axis value from the x-axis (seconds of the day)
        elif method_key.method_type == 'DailySine':
            new_setpoint = sine_wave_y_out(method_key.amplitude,
                                           method_key.frequency,
                                           method_key.shift_angle,
                                           method_key.shift_y)
            self.set_point = new_setpoint
            return 0

        # Calculate Bezier curve y-axis value from the x-axis (seconds of the day)
        elif method_key.method_type == 'DailyBezier':
            new_setpoint = bezier_curve_y_out(method_key.shift_angle,
                                              (method_key.x0, method_key.y0),
                                              (method_key.x1, method_key.y1),
                                              (method_key.x2, method_key.y2),
                                              (method_key.x3, method_key.y3))
            self.set_point = new_setpoint
            return 0

        # Calculate the duration in the method based on self.method_start_time
        elif method_key.method_type == 'Duration' and self.method_start_time != 'Ended':
            seconds_from_start = (now - self.method_start_time).total_seconds()
            total_sec = 0
            previous_total_sec = 0
            for each_method in method:
                total_sec += each_method.duration_sec
                if previous_total_sec <= seconds_from_start < total_sec:
                    row_start_time = float(
                        self.method_start_time.strftime(
                            '%s')) + previous_total_sec
                    row_since_start_sec = (
                        now - (self.method_start_time + datetime.timedelta(
                            0, previous_total_sec))).total_seconds()
                    percent_row = row_since_start_sec / each_method.duration_sec

                    start_setpoint = each_method.start_setpoint
                    if each_method.end_setpoint:
                        end_setpoint = each_method.end_setpoint
                    else:
                        end_setpoint = each_method.start_setpoint
                    setpoint_diff = abs(end_setpoint - start_setpoint)
                    if start_setpoint < end_setpoint:
                        new_setpoint = start_setpoint + (setpoint_diff *
                                                         percent_row)
                    else:
                        new_setpoint = start_setpoint - (setpoint_diff *
                                                         percent_row)

                    self.logger.debug(
                        "[Method] Start: {} Seconds Since: {}".format(
                            self.method_start_time, seconds_from_start))
                    self.logger.debug("[Method] Start time of row: {}".format(
                        datetime.datetime.fromtimestamp(row_start_time)))
                    self.logger.debug(
                        "[Method] Sec since start of row: {}".format(
                            row_since_start_sec))
                    self.logger.debug(
                        "[Method] Percent of row: {}".format(percent_row))
                    self.logger.debug(
                        "[Method] New Setpoint: {}".format(new_setpoint))
                    self.set_point = new_setpoint
                    return 0
                previous_total_sec = total_sec

            # Duration method has ended, reset start_time locally and in DB
            if self.method_start_time:
                with session_scope(MYCODO_DB_PATH) as db_session:
                    mod_method = db_session.query(Method).filter(
                        Method.method_id == self.method_id)
                    mod_method = mod_method.filter(
                        Method.method_order == 0).first()
                    mod_method.start_time = 'Ended'
                    db_session.commit()
                self.method_start_time = 'Ended'

        # Setpoint not needing to be calculated, use default setpoint
        self.set_point = self.default_set_point
Example #36
0
def calculate_method_setpoint(method_id, table, controller, Method, MethodData,
                              logger):
    """
    Calculates the setpoint from a method
    :param method_id: ID of Method to be used
    :param table: Table of the controller using this function
    :param controller: The controller using this function
    :param logger: The logger to use
    :return: 0 (success) or 1 (error) and a setpoint value
    """
    method = db_retrieve_table_daemon(Method)

    method_key = method.filter(Method.id == method_id).first()

    method_data = db_retrieve_table_daemon(MethodData)
    method_data = method_data.filter(MethodData.method_id == method_id)

    method_data_all = method_data.filter(MethodData.relay_id == None).all()
    method_data_first = method_data.filter(MethodData.relay_id == None).first()

    now = datetime.datetime.now()

    # Calculate where the current time/date is within the time/date method
    if method_key.method_type == 'Date':
        for each_method in method_data_all:
            start_time = datetime.datetime.strptime(each_method.time_start,
                                                    '%Y-%m-%d %H:%M:%S')
            end_time = datetime.datetime.strptime(each_method.time_end,
                                                  '%Y-%m-%d %H:%M:%S')
            if start_time < now < end_time:
                setpoint_start = each_method.setpoint_start
                if each_method.setpoint_end:
                    setpoint_end = each_method.setpoint_end
                else:
                    setpoint_end = each_method.setpoint_start

                setpoint_diff = abs(setpoint_end - setpoint_start)
                total_seconds = (end_time - start_time).total_seconds()
                part_seconds = (now - start_time).total_seconds()
                percent_total = part_seconds / total_seconds

                if setpoint_start < setpoint_end:
                    new_setpoint = setpoint_start + (setpoint_diff *
                                                     percent_total)
                else:
                    new_setpoint = setpoint_start - (setpoint_diff *
                                                     percent_total)

                logger.debug("[Method] Start: {start} End: {end}".format(
                    start=start_time, end=end_time))
                logger.debug("[Method] Start: {start} End: {end}".format(
                    start=setpoint_start, end=setpoint_end))
                logger.debug(
                    "[Method] Total: {tot} Part total: {par} ({per}%)".format(
                        tot=total_seconds, par=part_seconds,
                        per=percent_total))
                logger.debug(
                    "[Method] New Setpoint: {sp}".format(sp=new_setpoint))
                return new_setpoint, False

    # Calculate where the current Hour:Minute:Seconds is within the Daily method
    elif method_key.method_type == 'Daily':
        daily_now = datetime.datetime.now().strftime('%H:%M:%S')
        daily_now = datetime.datetime.strptime(str(daily_now), '%H:%M:%S')
        for each_method in method_data_all:
            start_time = datetime.datetime.strptime(each_method.time_start,
                                                    '%H:%M:%S')
            end_time = datetime.datetime.strptime(each_method.time_end,
                                                  '%H:%M:%S')
            if start_time < daily_now < end_time:
                setpoint_start = each_method.setpoint_start
                if each_method.setpoint_end:
                    setpoint_end = each_method.setpoint_end
                else:
                    setpoint_end = each_method.setpoint_start

                setpoint_diff = abs(setpoint_end - setpoint_start)
                total_seconds = (end_time - start_time).total_seconds()
                part_seconds = (daily_now - start_time).total_seconds()
                percent_total = part_seconds / total_seconds

                if setpoint_start < setpoint_end:
                    new_setpoint = setpoint_start + (setpoint_diff *
                                                     percent_total)
                else:
                    new_setpoint = setpoint_start - (setpoint_diff *
                                                     percent_total)

                logger.debug("[Method] Start: {start} End: {end}".format(
                    start=start_time.strftime('%H:%M:%S'),
                    end=end_time.strftime('%H:%M:%S')))
                logger.debug("[Method] Start: {start} End: {end}".format(
                    start=setpoint_start, end=setpoint_end))
                logger.debug(
                    "[Method] Total: {tot} Part total: {par} ({per}%)".format(
                        tot=total_seconds, par=part_seconds,
                        per=percent_total))
                logger.debug(
                    "[Method] New Setpoint: {sp}".format(sp=new_setpoint))
                return new_setpoint, False

    # Calculate sine y-axis value from the x-axis (seconds of the day)
    elif method_key.method_type == 'DailySine':
        new_setpoint = sine_wave_y_out(method_data_first.amplitude,
                                       method_data_first.frequency,
                                       method_data_first.shift_angle,
                                       method_data_first.shift_y)
        return new_setpoint, False

    # Calculate Bezier curve y-axis value from the x-axis (seconds of the day)
    elif method_key.method_type == 'DailyBezier':
        new_setpoint = bezier_curve_y_out(
            method_data_first.shift_angle,
            (method_data_first.x0, method_data_first.y0),
            (method_data_first.x1, method_data_first.y1),
            (method_data_first.x2, method_data_first.y2),
            (method_data_first.x3, method_data_first.y3))
        return new_setpoint, False

    # Calculate the duration in the method based on self.method_start_time
    elif method_key.method_type == 'Duration':
        start_time = datetime.datetime.strptime(
            str(controller.method_start_time), '%Y-%m-%d %H:%M:%S.%f')
        ended = False

        # Check if method_end_time is not None
        if controller.method_end_time:
            # Convert time string to datetime object
            end_time = datetime.datetime.strptime(
                str(controller.method_end_time), '%Y-%m-%d %H:%M:%S.%f')
            if now > start_time:
                ended = True

        seconds_from_start = (now - start_time).total_seconds()
        total_sec = 0
        previous_total_sec = 0
        previous_end = None
        method_restart = False

        for each_method in method_data_all:
            # If duration_sec is 0, method has instruction to restart
            if each_method.duration_sec == 0:
                method_restart = True
            else:
                previous_end = each_method.setpoint_end

            total_sec += each_method.duration_sec
            if previous_total_sec <= seconds_from_start < total_sec:
                row_start_time = float(
                    start_time.strftime('%s')) + previous_total_sec
                row_since_start_sec = (now - (start_time + datetime.timedelta(
                    0, previous_total_sec))).total_seconds()
                percent_row = row_since_start_sec / each_method.duration_sec

                setpoint_start = each_method.setpoint_start
                if each_method.setpoint_end:
                    setpoint_end = each_method.setpoint_end
                else:
                    setpoint_end = each_method.setpoint_start
                setpoint_diff = abs(setpoint_end - setpoint_start)
                if setpoint_start < setpoint_end:
                    new_setpoint = setpoint_start + (setpoint_diff *
                                                     percent_row)
                else:
                    new_setpoint = setpoint_start - (setpoint_diff *
                                                     percent_row)

                logger.debug(
                    "[Method] Start: {start} Seconds Since: {sec}".format(
                        start=start_time, sec=seconds_from_start))
                logger.debug("[Method] Start time of row: {start}".format(
                    start=datetime.datetime.fromtimestamp(row_start_time)))
                logger.debug("[Method] Sec since start of row: {sec}".format(
                    sec=row_since_start_sec))
                logger.debug(
                    "[Method] Percent of row: {per}".format(per=percent_row))
                logger.debug(
                    "[Method] New Setpoint: {sp}".format(sp=new_setpoint))
                return new_setpoint, False
            previous_total_sec = total_sec

        if controller.method_start_time:
            if method_restart:
                if end_time and now > end_time:
                    ended = True
                else:
                    # Method has been instructed to restart
                    controller.method_start_time = datetime.datetime.now()
                    with session_scope(MYCODO_DB_PATH) as db_session:
                        mod_method = db_session.query(table)
                        mod_method = mod_method.filter(
                            table.method_id == method_id).first()
                        mod_method.method_start_time = controller.method_start_time
                        db_session.commit()
                        return previous_end, False
            else:
                ended = True

            if ended:
                # Duration method has ended, reset method_start_time locally and in DB
                with session_scope(MYCODO_DB_PATH) as db_session:
                    mod_method = db_session.query(table).filter(
                        table.method_id == method_id).first()
                    mod_method.method_start_time = 'Ended'
                    mod_method.method_end_time = None
                    db_session.commit()
                return None, True

    # Setpoint not needing to be calculated, use default setpoint
    return None, False
Example #37
0
    def __init__(self, ready, logger, lcd_id):
        threading.Thread.__init__(self)

        self.thread_startup_timer = timeit.default_timer()
        self.thread_shutdown_timer = 0
        self.ready = ready
        self.logger = logger
        self.flash_lcd_on = False
        self.lcd_is_on = False

        try:
            with session_scope(MYCODO_DB_PATH) as new_session:
                lcd = new_session.query(LCD).filter(LCD.id == lcd_id).first()
                self.lcd_id = lcd_id
                self.lcd_name = lcd.name
                self.lcd_pin = lcd.pin
                self.lcd_period = lcd.period
                self.lcd_x_characters = lcd.x_characters
                self.lcd_y_lines = lcd.y_lines

                if lcd.multiplexer_address:
                    self.multiplexer_address_string = lcd.multiplexer_address
                    self.multiplexer_address = int(str(lcd.multiplexer_address), 16)
                    self.multiplexer_channel = lcd.multiplexer_channel
                    self.multiplexer = TCA9548A(self.multiplexer_address)
                else:
                    self.multiplexer = None

                self.lcd_line = {}
                for i in range(1, 5):
                    self.lcd_line[i] = {}

                list_sensors = ['sensor_time', 'temperature',
                                'humidity', 'co2', 'pressure',
                                'altitude', 'temperature_die',
                                'temperature_object', 'lux']

                list_PIDs = ['setpoint', 'pid_time']

                list_relays = ['duration_sec', 'relay_time', 'relay_state']

                if self.lcd_y_lines == 2:
                    self.lcd_line[1]['id'] = lcd.line_1_sensor_id
                    self.lcd_line[1]['measurement'] = lcd.line_1_measurement
                    if lcd.line_1_sensor_id:
                        if lcd.line_1_measurement in list_sensors:
                            table = Sensor
                        elif lcd.line_1_measurement in list_PIDs:
                            table = PID
                        elif lcd.line_1_measurement in list_relays:
                            table = Relay
                        sensor_line_1 = new_session.query(table).filter(
                            table.id == lcd.line_1_sensor_id).first()
                        self.lcd_line[1]['name'] = sensor_line_1.name
                        if 'time' in lcd.line_1_measurement:
                            self.lcd_line[1]['measurement'] = 'time'

                    self.lcd_line[2]['id'] = lcd.line_2_sensor_id
                    self.lcd_line[2]['measurement'] = lcd.line_2_measurement
                    if lcd.line_2_sensor_id:
                        if lcd.line_2_measurement in list_sensors:
                            table = Sensor
                        elif lcd.line_2_measurement in list_PIDs:
                            table = PID
                        elif lcd.line_2_measurement in list_relays:
                            table = Relay
                        sensor_line_2 = new_session.query(table).filter(
                            table.id == lcd.line_2_sensor_id).first()
                        self.lcd_line[2]['name'] = sensor_line_2.name
                        if 'time' in lcd.line_2_measurement:
                            self.lcd_line[2]['measurement'] = 'time'

                elif self.lcd_y_lines == 4:
                    self.lcd_line[3]['id'] = lcd.line_3_sensor_id
                    self.lcd_line[3]['measurement'] = lcd.line_3_measurement
                    if lcd.line_3_sensor_id:
                        if lcd.line_3_measurement in list_sensors:
                            table = Sensor
                        elif lcd.line_3_measurement in list_PIDs:
                            table = PID
                        elif lcd.line_3_measurement in list_relays:
                            table = Relay
                        sensor_line_3 = new_session.query(table).filter(
                            table.id == lcd.line_3_sensor_id).first()
                        self.lcd_line[3]['name'] = sensor_line_3.name
                        if 'time' in lcd.line_3_measurement:
                            self.lcd_line[3]['measurement'] = 'time'

                    self.lcd_line[4]['id'] = lcd.line_4_sensor_id
                    self.lcd_line[4]['measurement'] = lcd.line_4_measurement
                    if lcd.line_4_sensor_id:
                        if lcd.line_4_measurement in list_sensors:
                            table = Sensor
                        elif lcd.line_4_measurement in list_PIDs:
                            table = PID
                        elif lcd.line_4_measurement in list_relays:
                            table = Relay
                        sensor_line_4 = new_session.query(table).filter(
                            table.id == lcd.line_4_sensor_id).first()
                        self.lcd_line[4]['name'] = sensor_line_4.name
                        if 'time' in lcd.line_4_measurement:
                            self.lcd_line[4]['measurement'] = 'time'

            self.measurement_unit = {}
            self.measurement_unit['metric'] = {
                "temperature": "C",
                "humidity": "%",
                "co2": "ppmv",
                "pressure": "Pa",
                "altitude": "m",
                "duration_sec": "s",
                "temperature_die": "C",
                "temperature_object": "C",
                "lux": "lux", 
            }
            self.measurement_unit['standard'] = {
                "temperature": "F",
                "humidity": "%",
                "co2": "ppmv",
                "pressure": "atm",
                "altitude": "ft",
                "duration_sec": "s",
                "temperature_die": "F",
                "temperature_object": "F",
                "lux": "lux", 
            }

            self.timer = time.time() + self.lcd_period
            self.backlight_timer = time.time()

            self.lcd_string_line = {}
            for i in range(1, self.lcd_y_lines+1):
                self.lcd_string_line[i] = ''

            self.LCD_WIDTH = self.lcd_x_characters # Maximum characters per line

            self.LCD_LINE = {}
            self.LCD_LINE[1] = 0x80 # LCD RAM address for the 1st line
            self.LCD_LINE[2] = 0xC0 # LCD RAM address for the 2nd line
            self.LCD_LINE[3] = 0x94 # LCD RAM address for the 3rd line
            self.LCD_LINE[4] = 0xD4 # LCD RAM address for the 4th line

            self.LCD_CHR = 1 # Mode - Sending data
            self.LCD_CMD = 0 # Mode - SenLCDding command

            self.LCD_BACKLIGHT = 0x08  # On
            self.LCD_BACKLIGHT_OFF = 0x00  # Off

            self.ENABLE = 0b00000100 # Enable bit

            # Timing constants
            self.E_PULSE = 0.0005
            self.E_DELAY = 0.0005

            # Setup I2C bus
            try:
                if GPIO.RPI_REVISION == 2 or GPIO.RPI_REVISION == 3:
                    I2C_bus_number = 1
                else:
                    I2C_bus_number = 0
                self.bus = smbus.SMBus(I2C_bus_number)
            except Exception as except_msg:
                self.logger.exception("Could not initialize I2C bus: {}".format(
                    except_msg))

            self.I2C_ADDR = int(self.lcd_pin, 16)
            self.lcd_init()
            self.lcd_string_write('Mycodo 3.6.0', self.LCD_LINE[1]) 
            self.lcd_string_write('Start {}'.format(
                self.lcd_name), self.LCD_LINE[2])
        except Exception as except_msg:
            self.logger.exception("[LCD {}] Error: {}".format(
                self.lcd_id, except_msg))
Example #38
0
    def __init__(self, ready, logger, pid_id):
        threading.Thread.__init__(self)

        self.thread_startup_timer = timeit.default_timer()
        self.thread_shutdown_timer = 0
        self.ready = ready
        self.logger = logger
        self.pid_id = pid_id
        self.control = DaemonControl()

        with session_scope(MYCODO_DB_PATH) as new_session:
            pid = new_session.query(PID).filter(PID.id == self.pid_id).first()
            self.sensor_id = pid.sensor_id
            self.measure_type = pid.measure_type
            self.method_id = pid.method_id
            self.direction = pid.direction
            self.raise_relay_id = pid.raise_relay_id
            self.raise_min_duration = pid.raise_min_duration
            self.raise_max_duration = pid.raise_max_duration
            self.lower_relay_id = pid.lower_relay_id
            self.lower_min_duration = pid.lower_min_duration
            self.lower_max_duration = pid.lower_max_duration
            self.Kp = pid.p
            self.Ki = pid.i
            self.Kd = pid.d
            self.Integrator_min = pid.integrator_min
            self.Integrator_max = pid.integrator_max
            self.measure_interval = pid.period
            self.default_set_point = pid.setpoint
            self.set_point = pid.setpoint
            sensor = new_session.query(Sensor).filter(
                Sensor.id == self.sensor_id).first()
            self.sensor_duration = sensor.period

        self.Derivator = 0
        self.Integrator = 0
        self.error = 0.0
        self.P_value = None
        self.I_value = None
        self.D_value = None
        self.raise_seconds_on = 0
        self.timer = t.time() + self.measure_interval

        # Check if a method is set for this PID
        if self.method_id:
            with session_scope(MYCODO_DB_PATH) as new_session:
                method = new_session.query(Method)
                method = method.filter(Method.method_id == self.method_id)
                method = method.filter(Method.method_order == 0).first()
                self.method_type = method.method_type
                self.method_start_time = method.start_time

            if self.method_type == 'Duration':
                if self.method_start_time == 'Ended':
                    # Method has ended and hasn't been instructed to begin again
                    pass
                elif self.method_start_time == 'Ready' or self.method_start_time == None:
                    # Method has been instructed to begin
                    with session_scope(MYCODO_DB_PATH) as db_session:
                        mod_method = db_session.query(Method)
                        mod_method = mod_method.filter(
                            Method.method_id == self.method_id)
                        mod_method = mod_method.filter(
                            Method.method_order == 0).first()
                        mod_method.start_time = datetime.datetime.now()
                        self.method_start_time = mod_method.start_time
                        db_session.commit()
                else:
                    # Method neither instructed to begin or not to
                    # Likely there was a daemon restart ot power failure
                    # Resume method with saved start_time
                    self.method_start_time = datetime.datetime.strptime(
                        self.method_start_time, '%Y-%m-%d %H:%M:%S.%f')
                    self.logger.warning(
                        "[PID {}] Resuming method {} started at {}".format(
                            self.pid_id, self.method_id,
                            self.method_start_time))
    def setup_sensor_conditionals(self, cond_mod='setup', cond_id=None):
        # Signal to pause the main loop and wait for verification
        self.pause_loop = True
        while not self.verify_pause_loop:
            time.sleep(0.1)

        if cond_mod == 'del':
            self.cond_id.pop(cond_id, None)
            self.cond_activated.pop(cond_id, None)
            self.cond_period.pop(cond_id, None)
            self.cond_name.pop(cond_id, None)
            self.cond_measurement_type.pop(cond_id, None)
            self.cond_edge_detected.pop(cond_id, None)
            self.cond_direction.pop(cond_id, None)
            self.cond_setpoint.pop(cond_id, None)
            self.cond_relay_id.pop(cond_id, None)
            self.cond_relay_state.pop(cond_id, None)
            self.cond_relay_on_duration.pop(cond_id, None)
            self.cond_execute_command.pop(cond_id, None)
            self.cond_email_notify.pop(cond_id, None)
            self.cond_flash_lcd.pop(cond_id, None)
            self.cond_camera_record.pop(cond_id, None)
            self.cond_timer.pop(cond_id, None)
            self.smtp_wait_timer.pop(cond_id, None)
            self.logger.debug("[Sensor Conditional {}] Deleted Conditional "
                              "from Sensor {}".format(cond_id, self.sensor_id))
        else:
            with session_scope(MYCODO_DB_PATH) as new_session:
                if cond_mod == 'setup':
                    self.cond_id = {}
                    self.cond_name = {}
                    self.cond_activated = {}
                    self.cond_period = {}
                    self.cond_measurement_type = {}
                    self.cond_edge_detected = {}
                    self.cond_direction = {}
                    self.cond_setpoint = {}
                    self.cond_relay_id = {}
                    self.cond_relay_state = {}
                    self.cond_relay_on_duration = {}
                    self.cond_execute_command = {}
                    self.cond_email_notify = {}
                    self.cond_flash_lcd = {}
                    self.cond_camera_record = {}
                    self.cond_timer = {}
                    self.smtp_wait_timer = {}
                    self.sensor_conditional = new_session.query(
                        SensorConditional).filter(
                            SensorConditional.sensor_id == self.sensor_id)
                    self.sensor_conditional = self.sensor_conditional.filter(
                        SensorConditional.activated == 1)
                elif cond_mod == 'add':
                    self.sensor_conditional = new_session.query(
                        SensorConditional).filter(
                            SensorConditional.sensor_id == self.sensor_id)
                    self.sensor_conditional = self.sensor_conditional.filter(
                        SensorConditional.activated == 1)
                    self.sensor_conditional = self.sensor_conditional.filter(
                        SensorConditional.id == cond_id)
                    self.logger.debug("[Sensor Conditional {}] Added "
                                      "Conditional to Sensor {}".format(
                                          cond_id, self.sensor_id))
                elif cond_mod == 'mod':
                    self.sensor_conditional = new_session.query(
                        SensorConditional).filter(
                            SensorConditional.sensor_id == self.sensor_id)
                    self.sensor_conditional = self.sensor_conditional.filter(
                        SensorConditional.id == cond_id)
                    self.logger.debug("[Sensor Conditional {}] Modified "
                                      "Conditional from Sensor {}".format(
                                          cond_id, self.sensor_id))
                else:
                    return 1

                for each_cond in self.sensor_conditional.all():
                    if cond_mod == 'setup':
                        self.logger.debug("[Sensor Conditional {}] Activated "
                                          "Conditional from Sensor {}".format(
                                              each_cond.id, self.sensor_id))
                    self.cond_id[each_cond.id] = each_cond.id
                    self.cond_name[each_cond.id] = each_cond.name
                    self.cond_activated[each_cond.id] = each_cond.activated
                    self.cond_period[each_cond.id] = each_cond.period
                    self.cond_measurement_type[
                        each_cond.id] = each_cond.measurement_type
                    self.cond_edge_detected[
                        each_cond.id] = each_cond.edge_detected
                    self.cond_direction[each_cond.id] = each_cond.direction
                    self.cond_setpoint[each_cond.id] = each_cond.setpoint
                    self.cond_relay_id[each_cond.id] = each_cond.relay_id
                    self.cond_relay_state[each_cond.id] = each_cond.relay_state
                    self.cond_relay_on_duration[
                        each_cond.id] = each_cond.relay_on_duration
                    self.cond_execute_command[
                        each_cond.id] = each_cond.execute_command
                    self.cond_email_notify[
                        each_cond.id] = each_cond.email_notify
                    self.cond_flash_lcd[each_cond.id] = each_cond.email_notify
                    self.cond_camera_record[
                        each_cond.id] = each_cond.camera_record
                    self.cond_timer[each_cond.id] = time.time(
                    ) + self.cond_period[each_cond.id]
                    self.smtp_wait_timer[each_cond.id] = time.time() + 3600

        self.pause_loop = False
        self.verify_pause_loop = False
Example #40
0
    def __init__(self, ready, pid_id):
        threading.Thread.__init__(self)

        self.logger = logging.getLogger("mycodo.pid_{id}".format(id=pid_id))

        self.running = False
        self.thread_startup_timer = timeit.default_timer()
        self.thread_shutdown_timer = 0
        self.ready = ready
        self.pid_id = pid_id
        self.pid_unique_id = db_retrieve_table_daemon(
            PID, device_id=self.pid_id).unique_id
        self.control = DaemonControl()

        self.control_variable = 0.0
        self.derivator = 0.0
        self.integrator = 0.0
        self.error = 0.0
        self.P_value = None
        self.I_value = None
        self.D_value = None
        self.set_point = 0.0
        self.lower_seconds_on = 0.0
        self.raise_seconds_on = 0.0
        self.lower_duty_cycle = 0.0
        self.raise_duty_cycle = 0.0
        self.last_time = None
        self.last_measurement = None
        self.last_measurement_success = False

        self.is_activated = None
        self.is_held = None
        self.is_paused = None
        self.measurement = None
        self.method_id = None
        self.direction = None
        self.raise_output_id = None
        self.raise_min_duration = None
        self.raise_max_duration = None
        self.raise_min_off_duration = None
        self.lower_output_id = None
        self.lower_min_duration = None
        self.lower_max_duration = None
        self.lower_min_off_duration = None
        self.Kp = None
        self.Ki = None
        self.Kd = None
        self.integrator_min = None
        self.integrator_max = None
        self.period = None
        self.max_measure_age = None
        self.default_set_point = None
        self.set_point = None

        self.input_unique_id = None
        self.input_duration = None

        self.raise_output_type = None
        self.lower_output_type = None

        self.initialize_values()

        self.timer = t.time() + self.period

        # Check if a method is set for this PID
        self.method_start_act = None
        if self.method_id:
            method = db_retrieve_table_daemon(Method, device_id=self.method_id)
            method_data = db_retrieve_table_daemon(MethodData)
            method_data = method_data.filter(
                MethodData.method_id == self.method_id)
            method_data_repeat = method_data.filter(
                MethodData.duration_sec == 0).first()
            pid = db_retrieve_table_daemon(PID, device_id=self.pid_id)
            self.method_type = method.method_type
            self.method_start_act = pid.method_start_time
            self.method_start_time = None
            self.method_end_time = None

            if self.method_type == 'Duration':
                if self.method_start_act == 'Ended':
                    # Method has ended and hasn't been instructed to begin again
                    pass
                elif self.method_start_act == 'Ready' or self.method_start_act is None:
                    # Method has been instructed to begin
                    now = datetime.datetime.now()
                    self.method_start_time = now
                    if method_data_repeat and method_data_repeat.duration_end:
                        self.method_end_time = now + datetime.timedelta(
                            seconds=float(method_data_repeat.duration_end))

                    with session_scope(MYCODO_DB_PATH) as db_session:
                        mod_pid = db_session.query(PID)
                        mod_pid = mod_pid.filter(PID.id == self.pid_id).first()
                        mod_pid.method_start_time = self.method_start_time
                        mod_pid.method_end_time = self.method_end_time
                        db_session.commit()
                else:
                    # Method neither instructed to begin or not to
                    # Likely there was a daemon restart ot power failure
                    # Resume method with saved start_time
                    self.method_start_time = datetime.datetime.strptime(
                        str(pid.method_start_time), '%Y-%m-%d %H:%M:%S.%f')
                    if method_data_repeat and method_data_repeat.duration_end:
                        self.method_end_time = datetime.datetime.strptime(
                            str(pid.method_end_time), '%Y-%m-%d %H:%M:%S.%f')
                        if self.method_end_time > datetime.datetime.now():
                            self.logger.warning(
                                "Resuming method {id}: started {start}, "
                                "ends {end}".format(
                                    id=self.method_id,
                                    start=self.method_start_time,
                                    end=self.method_end_time))
                        else:
                            self.method_start_act = 'Ended'
                    else:
                        self.method_start_act = 'Ended'
    def relay_on_off(self, relay_id, state,
                     duration=0.0, trigger_conditionals=True):
        """
        Turn a relay on or off
        The GPIO may be either HIGH or LOW to activate a relay. This trigger
        state will be referenced to determine if the GPIO needs to be high or
        low to turn the relay on or off.

        Conditionals will be checked for each action requested of a relay, and
        if true, those conditional actions will be executed. For example:
            'If relay 1 turns on, turn relay 3 off'

        :param relay_id: Unique ID for relay
        :type relay_id: str
        :param state: What state is desired? 'on' or 'off'
        :type state: str
        :param duration: If state is 'on', a duration can be set to turn the relay off after
        :type duration: float
        :param trigger_conditionals: Whether to trigger condionals to act or not
        :type trigger_conditionals: bool
        """
        # Check if relay exists
        if relay_id not in self.relay_id:
            self.logger.warning("[Relay] Cannot turn {} Relay with ID {}. It "
                                "doesn't exist".format(state, relay_id))
            return 1
        if state == 'on':
            if not self.relay_pin[relay_id]:
                self.logger.warning("[Relay] Cannot turn a relay "
                                    "{} ({}) on with a pin of "
                                    "0.".format(self.relay_id[relay_id],
                                                self.relay_name[relay_id]))
                return 1

            current_amps = self.current_amp_load()
            if current_amps+self.relay_amps[relay_id] > MAX_AMPS:
                self.logger.warning("[Relay] Cannot turn relay {} "
                                    "({}) On. If this relay turns on, "
                                    "there will be {} amps being drawn, "
                                    "which exceeds the maximum set draw of {}"
                                    " amps.".format(self.relay_id[relay_id],
                                                    self.relay_name[relay_id],
                                                    current_amps,
                                                    MAX_AMPS))
                return 1

            else:
                if duration:
                    time_now = datetime.datetime.now()
                    if self.is_on(relay_id) and self.relay_on_duration[relay_id]:
                        if self.relay_on_until[relay_id] > time_now:
                            remaining_time = (self.relay_on_until[relay_id]-time_now).seconds
                        else:
                            remaining_time = 0
                        time_on = self.relay_last_duration[relay_id] - remaining_time
                        self.logger.debug("[Relay] Relay {} ({}) is already "
                                            "on for a duration of {:.1f} seconds (with "
                                            "{:.1f} seconds remaining). Recording the "
                                            "amount of time the relay has been on ({:.1f} "
                                            "sec) and updating the on duration to {:.1f} "
                                            "seconds.".format(self.relay_id[relay_id],
                                                              self.relay_name[relay_id],
                                                              self.relay_last_duration[relay_id],
                                                              remaining_time,
                                                              time_on,
                                                              duration))
                        if time_on > 0:
                            write_db = threading.Thread(
                                target=write_influxdb,
                                args=(self.logger, INFLUXDB_HOST,
                                      INFLUXDB_PORT, INFLUXDB_USER,
                                      INFLUXDB_PASSWORD, INFLUXDB_DATABASE,
                                      'relay', relay_id, 'duration_sec',
                                      float(time_on),))
                            write_db.start()

                        self.relay_on_until[relay_id] = time_now+datetime.timedelta(seconds=duration)
                        self.relay_last_duration[relay_id] = duration
                        return 0
                    elif self.is_on(relay_id) and not self.relay_on_duration:
                        self.relay_on_duration[relay_id] = True
                        self.relay_on_until[relay_id] = time_now+datetime.timedelta(seconds=duration)
                        self.relay_last_duration[relay_id] = duration

                        self.logger.debug("[Relay] Relay {} ({}) is currently"
                                          " on without a duration. Turning "
                                          "into a duration  of {:.1f} "
                                          "seconds.".format(self.relay_id[relay_id],
                                                            self.relay_name[relay_id],
                                                            duration))
                        return 0
                    else:
                        self.relay_on_until[relay_id] = time_now+datetime.timedelta(seconds=duration)
                        self.relay_on_duration[relay_id] = True
                        self.relay_last_duration[relay_id] = duration
                        self.logger.debug("[Relay] Relay {} ({}) on for {:.1f} "
                                          "seconds.".format(self.relay_id[relay_id],
                                                             self.relay_name[relay_id],
                                                             duration))
                        GPIO.output(self.relay_pin[relay_id], self.relay_trigger[relay_id])

                else:
                    if self.is_on(relay_id):
                        self.logger.warning("[Relay] Relay {} ({}) is already on.".format(
                                self.relay_id[relay_id],
                                self.relay_name[relay_id]))
                        return 1
                    else:
                        GPIO.output(self.relay_pin[relay_id],
                                    self.relay_trigger[relay_id])

        else:
            if self._is_setup() and self.relay_pin[relay_id]:  # if pin not 0
                self.relay_on_duration[relay_id] = False
                self.relay_on_until[relay_id] = datetime.datetime.now()
                GPIO.output(self.relay_pin[relay_id], not self.relay_trigger[relay_id])
                self.logger.debug("[Relay] Relay {} ({}) turned off.".format(
                        self.relay_id[relay_id],
                        self.relay_name[relay_id]))

        if trigger_conditionals:
            with session_scope(MYCODO_DB_PATH) as new_session:
                conditionals = new_session.query(RelayConditional)
                new_session.expunge_all()
                new_session.close()

            conditionals = conditionals.filter(RelayConditional.if_relay_id == relay_id)
            conditionals = conditionals.filter(RelayConditional.activated == True)

            if self.is_on(relay_id):
                conditionals = conditionals.filter(RelayConditional.if_action == 'on')
                conditionals = conditionals.filter(RelayConditional.if_duration == 0)
            else:
                conditionals = conditionals.filter(RelayConditional.if_action == 'off')

            for each_conditional in conditionals.all():
                message = None
                if (each_conditional.do_relay_id or
                        each_conditional.execute_command or
                        each_conditional.email_notify):
                    message = "[Relay Conditional {}] " \
                              "If relay {} ({}) turns " \
                              "{}: ".format(each_conditional.id,
                                            each_conditional.if_relay_id,
                                            self.relay_name[each_conditional.if_relay_id],
                                            each_conditional.if_action)

                if each_conditional.do_relay_id:
                    message += "Turn relay {} ({}) {}".format(
                            each_conditional.do_relay_id,
                            self.relay_name[each_conditional.do_relay_id],
                            each_conditional.do_action)

                    if each_conditional.do_duration == 0:
                        self.relay_on_off(each_conditional.do_relay_id,
                                          each_conditional.do_action)
                    else:
                        message += " for {} seconds".format(each_conditional.do_duration)
                        self.relay_on_off(each_conditional.do_relay_id,
                                          each_conditional.do_action,
                                          each_conditional.do_duration)
                    message += ". "


                if each_conditional.execute_command:
                    #################################
                    #        DANGEROUS CODE         #
                    #################################
                    # This code is not secure at all#
                    # and could cause serious       #
                    # damage to your software and   #
                    # hardware.                     #
                    #################################

                    # TODO: SECURITY: FIX THIS This runs arbitrary as ROOT
                    #       Make sure this works (currently untested)

                    message += "Execute '{}'. ".format(each_conditional.execute_command)
                    pass
                    # p = subprocess.Popen(each_conditional.execute_command, shell=True,
                    #                      stdout=subprocess.PIPE, stderr=subprocess.PIPE)
                    # output, errors = p.communicate()
                    # self.logger.debug("[Relay Conditional {} ({})] "
                    #                   "Execute command: {} "
                    #                   "Command output: {} "
                    #                   "Command errors: {}".format(each_conditional.id,
                    #                                               each_conditional.name,
                    #                                               each_conditional.execute_command,
                    #                                               output, errors))

                if each_conditional.email_notify:
                    if (self.email_count >= self.smtp_max_count and
                            time.time() < self.smtp_wait_time):
                         self.allowed_to_send_notice = False
                    else:
                        if time.time() > self.smtp_wait_time:
                            self.email_count = 0
                            self.smtp_wait_time = time.time() + 3600
                        self.allowed_to_send_notice = True
                    self.email_count += 1

                    if self.allowed_to_send_notice:
                        message += "Notify {}.".format(
                            each_conditional.id,
                            each_conditional.email_notify)

                        with session_scope(MYCODO_DB_PATH) as new_session:
                            smtp = new_session.query(SMTP).first()
                            email(self.logger, smtp.host, smtp.ssl, smtp.port,
                                  smtp.user, smtp.passw, smtp.email_from,
                                  each_conditional.email_notify, message)
                    else:
                        self.logger.debug("[Relay Conditional {}] True: "
                                          "{:.0f} seconds left to be "
                                          "allowed to email again.".format(
                                          each_cond.id,
                                          (self.smtp_wait_time-time.time())))

                if each_conditional.flash_lcd:
                    start_flashing = threading.Thread(
                        target=self.control.flash_lcd,
                        args=(each_conditional.flash_lcd,
                              1,))
                    start_flashing.start()

                if (each_conditional.do_relay_id or
                        each_conditional.execute_command or
                        each_conditional.email_notify):
                    self.logger.debug("{}".format(message))
Example #42
0
def send_stats():
    """
    Send anonymous usage statistics

    Example use:
        current_stat = return_stat_file_dict(csv_file)
        add_update_csv(csv_file, 'stat', current_stat['stat'] + 5)
    """
    try:
        client = InfluxDBClient(STATS_HOST, STATS_PORT, STATS_USER,
                                STATS_PASSWORD, STATS_DATABASE)
        # Prepare stats before sending
        with session_scope(MYCODO_DB_PATH) as new_session:
            alembic = new_session.query(AlembicVersion).first()
            add_update_csv(STATS_CSV, 'alembic_version', alembic.version_num)

            relays = new_session.query(Relay)
            add_update_csv(STATS_CSV, 'num_relays', get_count(relays))

            sensors = new_session.query(Sensor)
            add_update_csv(STATS_CSV, 'num_sensors', get_count(sensors))
            add_update_csv(STATS_CSV, 'num_sensors_active',
                           get_count(sensors.filter(Sensor.activated == True)))

            pids = new_session.query(PID)
            add_update_csv(STATS_CSV, 'num_pids', get_count(pids))
            add_update_csv(STATS_CSV, 'num_pids_active',
                           get_count(pids.filter(PID.activated == True)))

            lcds = new_session.query(LCD)
            add_update_csv(STATS_CSV, 'num_lcds', get_count(lcds))
            add_update_csv(STATS_CSV, 'num_lcds_active',
                           get_count(lcds.filter(LCD.activated == True)))

            methods = new_session.query(Method)
            add_update_csv(STATS_CSV, 'num_methods',
                           get_count(methods.filter(Method.method_order == 0)))
            add_update_csv(STATS_CSV, 'num_methods_in_pid',
                           get_count(pids.filter(PID.method_id != '')))

            timers = new_session.query(Timer)
            add_update_csv(STATS_CSV, 'num_timers', get_count(timers))
            add_update_csv(STATS_CSV, 'num_timers_active',
                           get_count(timers.filter(Timer.activated == True)))

        add_update_csv(STATS_CSV, 'country', geocoder.ip('me').country)
        add_update_csv(
            STATS_CSV, 'ram_use_mb',
            resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / float(1000))

        user_count = 0
        admin_count = 0
        with session_scope(USER_DB_PATH) as db_session:
            try:
                users = db_session.query(Users).all()
                for each_user in users:
                    user_count += 1
                    if each_user.user_restriction == 'admin':
                        admin_count += 1
            except Exception:
                pass
        add_update_csv(STATS_CSV, 'num_users_admin', admin_count)
        add_update_csv(STATS_CSV, 'num_users_guest', user_count - admin_count)

        add_update_csv(STATS_CSV, 'Mycodo_revision', MYCODO_VERSION)

        # Combine stats into list of dictionaries
        new_stats_dict = return_stat_file_dict(STATS_CSV)
        formatted_stat_dict = []
        for each_key, each_value in new_stats_dict.iteritems():
            if each_key != 'stat':  # Do not send header row
                formatted_stat_dict = add_stat_dict(formatted_stat_dict,
                                                    new_stats_dict['id'],
                                                    each_key, each_value)

        # Send stats to secure, remote influxdb server
        client.write_points(formatted_stat_dict)
        logger.debug("Sent anonymous usage statistics")
        return 0
    except requests.ConnectionError:
        logger.error("Could not send anonymous usage statistics: Connection "
                     "timed out (expected if there's no internet)")
    except Exception as except_msg:
        logger.exception(
            "Could not send anonymous usage statistics: {err}".format(
                err=except_msg))
    return 1
Example #43
0
    def setup_sensor_conditionals(self, cond_mod='setup', cond_id=None):
        # Signal to pause the main loop and wait for verification
        self.pause_loop = True
        while not self.verify_pause_loop:
            time.sleep(0.1)

        if cond_mod == 'del':
            self.cond_id.pop(cond_id, None)
            self.cond_activated.pop(cond_id, None)
            self.cond_period.pop(cond_id, None)
            self.cond_name.pop(cond_id, None)
            self.cond_measurement_type.pop(cond_id, None)
            self.cond_edge_detected.pop(cond_id, None)
            self.cond_direction.pop(cond_id, None)
            self.cond_setpoint.pop(cond_id, None)
            self.cond_relay_id.pop(cond_id, None)
            self.cond_relay_state.pop(cond_id, None)
            self.cond_relay_on_duration.pop(cond_id, None)
            self.cond_execute_command.pop(cond_id, None)
            self.cond_email_notify.pop(cond_id, None)
            self.cond_flash_lcd.pop(cond_id, None)
            self.cond_camera_record.pop(cond_id, None)
            self.cond_timer.pop(cond_id, None)
            self.smtp_wait_timer.pop(cond_id, None)
            self.logger.debug("[Sensor Conditional {}] Deleted Conditional "
                              "from Sensor {}".format(cond_id, self.sensor_id))
        else:
            with session_scope(MYCODO_DB_PATH) as new_session:
                if cond_mod == 'setup':
                    self.cond_id = {}
                    self.cond_name = {}
                    self.cond_activated = {}
                    self.cond_period = {}
                    self.cond_measurement_type = {}
                    self.cond_edge_detected = {}
                    self.cond_direction = {}
                    self.cond_setpoint = {}
                    self.cond_relay_id = {}
                    self.cond_relay_state = {}
                    self.cond_relay_on_duration = {}
                    self.cond_execute_command = {}
                    self.cond_email_notify = {}
                    self.cond_flash_lcd = {}
                    self.cond_camera_record = {}
                    self.cond_timer = {}
                    self.smtp_wait_timer = {}
                    self.sensor_conditional = new_session.query(
                        SensorConditional).filter(
                            SensorConditional.sensor_id == self.sensor_id)
                    self.sensor_conditional = self.sensor_conditional.filter(
                        SensorConditional.activated == 1)
                elif cond_mod == 'add':
                    self.sensor_conditional = new_session.query(
                        SensorConditional).filter(
                            SensorConditional.sensor_id == self.sensor_id)
                    self.sensor_conditional = self.sensor_conditional.filter(
                        SensorConditional.activated == 1)
                    self.sensor_conditional = self.sensor_conditional.filter(
                        SensorConditional.id == cond_id)
                    self.logger.debug("[Sensor Conditional {}] Added "
                                      "Conditional to Sensor {}".format(
                                            cond_id, self.sensor_id))
                elif cond_mod == 'mod':
                    self.sensor_conditional = new_session.query(
                        SensorConditional).filter(
                            SensorConditional.sensor_id == self.sensor_id)
                    self.sensor_conditional = self.sensor_conditional.filter(
                        SensorConditional.id == cond_id)
                    self.logger.debug("[Sensor Conditional {}] Modified "
                                      "Conditional from Sensor {}".format(
                                            cond_id, self.sensor_id))
                else:
                    return 1

                for each_cond in self.sensor_conditional.all():
                    if cond_mod == 'setup':
                        self.logger.debug("[Sensor Conditional {}] Activated "
                                          "Conditional from Sensor {}".format(
                                                each_cond.id, self.sensor_id))
                    self.cond_id[each_cond.id] = each_cond.id
                    self.cond_name[each_cond.id] = each_cond.name
                    self.cond_activated[each_cond.id] = each_cond.activated
                    self.cond_period[each_cond.id] = each_cond.period
                    self.cond_measurement_type[each_cond.id] = each_cond.measurement_type
                    self.cond_edge_detected[each_cond.id] = each_cond.edge_detected
                    self.cond_direction[each_cond.id] = each_cond.direction
                    self.cond_setpoint[each_cond.id] = each_cond.setpoint
                    self.cond_relay_id[each_cond.id] = each_cond.relay_id
                    self.cond_relay_state[each_cond.id] = each_cond.relay_state
                    self.cond_relay_on_duration[each_cond.id] = each_cond.relay_on_duration
                    self.cond_execute_command[each_cond.id] = each_cond.execute_command
                    self.cond_email_notify[each_cond.id] = each_cond.email_notify
                    self.cond_flash_lcd[each_cond.id] = each_cond.email_notify
                    self.cond_camera_record[each_cond.id] = each_cond.camera_record
                    self.cond_timer[each_cond.id] = time.time()+self.cond_period[each_cond.id]
                    self.smtp_wait_timer[each_cond.id] = time.time()+3600

        self.pause_loop = False
        self.verify_pause_loop = False
Example #44
0
    def activateController(self, cont_type, cont_id):
        """
        Activate currently-inactive controller

        :return: 0 for success, 1 for fail, with success or error message
        :rtype: int, str

        :param cont_type: Which controller type is to be activated?
        :type cont_type: str
        :param cont_id: Unique ID for controller
        :type cont_id: str
        """
        if cont_id in self.controller[cont_type]:
            if self.controller[cont_type][cont_id].isRunning():
                self.logger.warning("[Daemon] Cannot activate {} controller "
                                    "with ID {}, it's already "
                                    "active.".format(cont_type,
                                                     cont_id))
                return 1, "Cannot activate {} controller with ID {}: "\
                    "Already active.".format(cont_type,
                                             cont_id)
        try:
            controller_manage = {}
            ready = threading.Event()
            if cont_type == 'LCD':
                controller_manage['type'] = LCD
                controller_manage['function'] = LCDController
            elif cont_type == 'Log':
                controller_manage['type'] = Log
                controller_manage['function'] = LogController
            elif cont_type == 'PID':
                controller_manage['type'] = PID
                controller_manage['function'] = PIDController
            elif cont_type == 'Sensor':
                controller_manage['type'] = Sensor
                controller_manage['function'] = SensorController
            elif cont_type == 'Timer':
                controller_manage['type'] = Timer
                controller_manage['function'] = TimerController
            else:
                return 1, "{} controller with ID {} not found.".format(
                    cont_type, cont_id)

            # Check if the controller ID actually exists and start it
            with session_scope(MYCODO_DB_PATH) as new_session:
                if new_session.query(controller_manage['type']).filter(
                        controller_manage['type'].id == cont_id).first():
                    self.controller[cont_type][cont_id] = controller_manage['function'](
                        ready, self.logger, cont_id)
                    self.controller[cont_type][cont_id].start()
                    ready.wait()  # wait for thread to return ready
                    return 0, "{} controller with ID {} activated.".format(
                        cont_type, cont_id)
                else:
                    return 1, "{} controller with ID {} not found.".format(
                        cont_type, cont_id)
        except Exception as except_msg:
            self.logger.exception("[Daemon] Could not activate {} controller "
                                  "with ID {}: {}".format(cont_type, cont_id,
                                                          except_msg))
            return 1, "Could not activate {} controller with ID "\
                "{}: {}".format(cont_type, cont_id, except_msg)
Example #45
0
    def activateController(self, cont_type, cont_id):
        """
        Activate currently-inactive controller

        :return: 0 for success, 1 for fail, with success or error message
        :rtype: int, str

        :param cont_type: Which controller type is to be activated?
        :type cont_type: str
        :param cont_id: Unique ID for controller
        :type cont_id: str
        """
        if cont_id in self.controller[cont_type]:
            if self.controller[cont_type][cont_id].isRunning():
                self.logger.warning("[Daemon] Cannot activate {} controller "
                                    "with ID {}, it's already "
                                    "active.".format(cont_type, cont_id))
                return 1, "Cannot activate {} controller with ID {}: "\
                    "Already active.".format(cont_type,
                                             cont_id)
        try:
            controller_manage = {}
            ready = threading.Event()
            if cont_type == 'LCD':
                controller_manage['type'] = LCD
                controller_manage['function'] = LCDController
            elif cont_type == 'Log':
                controller_manage['type'] = Log
                controller_manage['function'] = LogController
            elif cont_type == 'PID':
                controller_manage['type'] = PID
                controller_manage['function'] = PIDController
            elif cont_type == 'Sensor':
                controller_manage['type'] = Sensor
                controller_manage['function'] = SensorController
            elif cont_type == 'Timer':
                controller_manage['type'] = Timer
                controller_manage['function'] = TimerController
            else:
                return 1, "{} controller with ID {} not found.".format(
                    cont_type, cont_id)

            # Check if the controller ID actually exists and start it
            with session_scope(MYCODO_DB_PATH) as new_session:
                if new_session.query(controller_manage['type']).filter(
                        controller_manage['type'].id == cont_id).first():
                    self.controller[cont_type][cont_id] = controller_manage[
                        'function'](ready, self.logger, cont_id)
                    self.controller[cont_type][cont_id].start()
                    ready.wait()  # wait for thread to return ready
                    return 0, "{} controller with ID {} activated.".format(
                        cont_type, cont_id)
                else:
                    return 1, "{} controller with ID {} not found.".format(
                        cont_type, cont_id)
        except Exception as except_msg:
            self.logger.exception("[Daemon] Could not activate {} controller "
                                  "with ID {}: {}".format(
                                      cont_type, cont_id, except_msg))
            return 1, "Could not activate {} controller with ID "\
                "{}: {}".format(cont_type, cont_id, except_msg)
Example #46
0
    def get_lcd_strings(self):
        """
        Retrieve measurements and/or timestamps and create strings for LCDs
        If no data is retrieveable, create string "NO DATA RETURNED".
        """
        # loop to acquire all measurements required to be displayed on the LCD
        for i in range(1, self.lcd_y_lines + 1):
            if self.lcd_line[i]['id']:
                # Get latest measurement (from within the past minute) from influxdb
                # FROM '/.*/' returns any measurement (for grabbing time of last measurement)
                last_measurement_success = False
                try:
                    if self.lcd_line[i]['measurement'] == 'relay_state':
                        self.lcd_line[i][
                            'measurement_value'] = self.relay_state(
                                self.lcd_line[i]['id'])
                        last_measurement_success = True
                    else:
                        if self.lcd_line[i]['measurement'] == 'time':
                            last_measurement = read_last_influxdb(
                                INFLUXDB_HOST, INFLUXDB_PORT, INFLUXDB_USER,
                                INFLUXDB_PASSWORD, INFLUXDB_DATABASE,
                                self.lcd_line[i]['id'], '/.*/').raw
                        else:
                            last_measurement = read_last_influxdb(
                                INFLUXDB_HOST, INFLUXDB_PORT, INFLUXDB_USER,
                                INFLUXDB_PASSWORD, INFLUXDB_DATABASE,
                                self.lcd_line[i]['id'],
                                self.lcd_line[i]['measurement']).raw
                        if last_measurement:
                            number = len(
                                last_measurement['series'][0]['values'])
                            self.lcd_line[i]['time'] = last_measurement[
                                'series'][0]['values'][number - 1][0]
                            self.lcd_line[i][
                                'measurement_value'] = last_measurement[
                                    'series'][0]['values'][number - 1][1]
                            utc_dt = datetime.datetime.strptime(
                                self.lcd_line[i]['time'].split(".")[0],
                                '%Y-%m-%dT%H:%M:%S')
                            utc_timestamp = calendar.timegm(utc_dt.timetuple())
                            local_timestamp = str(
                                datetime.datetime.fromtimestamp(utc_timestamp))
                            self.logger.debug(
                                "[LCD {}] Latest {}: {} @ {}".format(
                                    self.lcd_id,
                                    self.lcd_line[i]['measurement'],
                                    self.lcd_line[i]['measurement_value'],
                                    local_timestamp))
                            last_measurement_success = True
                        else:
                            self.lcd_line[i]['time'] = None
                            self.lcd_line[i]['measurement_value'] = None
                            self.logger.debug("[LCD {}] No data returned "
                                              "from influxdb".format(
                                                  self.lcd_id))
                except Exception as except_msg:
                    self.logger.debug(
                        "[LCD {}] Failed to read "
                        "measurement from the influxdb database: "
                        "{}".format(self.lcd_id, except_msg))

                try:
                    if last_measurement_success:
                        # Determine if the LCD output will have a value unit
                        measurement = ''
                        if self.lcd_line[i]['measurement'] == 'setpoint':
                            with session_scope(MYCODO_DB_PATH) as new_session:
                                pid = new_session.query(PID).filter(
                                    PID.id == self.lcd_line[i]['id']).first()
                                new_session.expunge_all()
                                new_session.close()
                                measurement = pid.measure_type
                        elif self.lcd_line[i]['measurement'] in [
                                'temperature', 'temperature_die',
                                'temperature_object', 'humidity', 'co2', 'lux',
                                'pressure', 'altitude'
                        ]:
                            measurement = self.lcd_line[i]['measurement']
                        elif self.lcd_line[i]['measurement'] == 'duration_sec':
                            measurement = 'duration_sec'

                        # Produce the line that will be displayed on the LCD
                        number_characters = self.lcd_x_characters
                        if self.lcd_line[i]['measurement'] == 'time':
                            # Convert UTC timestamp to local timezone
                            utc_dt = datetime.datetime.strptime(
                                self.lcd_line[i]['time'].split(".")[0],
                                '%Y-%m-%dT%H:%M:%S')
                            utc_timestamp = calendar.timegm(utc_dt.timetuple())
                            self.lcd_string_line[i] = str(
                                datetime.datetime.fromtimestamp(utc_timestamp))
                        elif measurement:
                            value_length = len(
                                str(self.lcd_line[i]['measurement_value']))
                            unit_length = len(
                                self.measurement_unit['metric'][measurement])
                            name_length = number_characters - value_length - unit_length - 2
                            name_cropped = self.lcd_line[i]['name'].ljust(
                                name_length)[:name_length]
                            self.lcd_string_line[i] = '{} {} {}'.format(
                                name_cropped,
                                self.lcd_line[i]['measurement_value'],
                                self.measurement_unit['metric'][measurement])
                        else:
                            value_length = len(
                                str(self.lcd_line[i]['measurement_value']))
                            name_length = number_characters - value_length - 1
                            name_cropped = self.lcd_line[i][
                                'name'][:name_length]
                            self.lcd_string_line[i] = '{} {}'.format(
                                name_cropped,
                                self.lcd_line[i]['measurement_value'])
                    else:
                        self.lcd_string_line[i] = 'NO DATA < 5 MIN'
                except Exception as except_msg:
                    self.logger.exception("[LCD {}] Error ({}): {}".format(
                        self.lcd_id, except_msg))
            else:
                self.lcd_string_line[i] = ''
Example #47
0
    def checkConditionals(self, cond_id):
        """
        Check if any sensor conditional statements are activated and
        execute their actions if the conditional is true.

        For example, if measured temperature is above 30C, notify [email protected]

        :rtype: None

        :param each_cond: Object of SQL table entries for a specific column
        :type each_cond: sqlalchemy object
        """
        attachment_file = False
        attachment_type = False

        last_measurement = self.getLastMeasurement(self.cond_measurement_type[cond_id])
        if (last_measurement and
                ((self.cond_direction[cond_id] == 'above' and
                    last_measurement > self.cond_setpoint[cond_id]) or
                (self.cond_direction[cond_id] == 'below' and
                    last_measurement < self.cond_setpoint[cond_id]))):

            now = time.time()
            timestamp = datetime.datetime.fromtimestamp(now).strftime('%Y-%m-%d %H-%M-%S')
            message = "{}\n[Sensor Conditional {}] {}\n{} {} ".format(
                    timestamp, cond_id,
                    self.cond_name[cond_id],
                    self.cond_measurement_type[cond_id],
                    last_measurement)
            if self.cond_direction[cond_id] == 'above':
                message += ">"
            elif self.cond_direction[cond_id] == 'below':
                message += "<"
            message += " {} setpoint.".format(self.cond_setpoint[cond_id])

            if (self.cond_relay_id[cond_id] and
                    self.cond_relay_state[cond_id] in ['on', 'off']):
                message += "\nTurning relay {} {}".format(
                        self.cond_relay_id[cond_id],
                        self.cond_relay_state[cond_id])
                if (self.cond_relay_state[cond_id] == 'on' and
                        self.cond_relay_on_duration[cond_id]):
                    message += " for {} seconds".format(self.cond_relay_on_duration[cond_id])
                message += ". "
                relay_on_off = threading.Thread(
                    target=self.control.relay_on_off,
                    args=(self.cond_relay_id[cond_id],
                          self.cond_relay_state[cond_id],
                          self.cond_relay_on_duration[cond_id],))
                relay_on_off.start()

            # Execute command in shell
            if self.cond_execute_command[cond_id]:
                message += "\nExecute '{}'. ".format(
                        self.cond_execute_command[cond_id])
                cmd_out, cmd_err, cmd_status = cmd_output(self.cond_execute_command[cond_id])
                message += "Status: {}. ".format(cmd_status)

            if self.cond_camera_record[cond_id] in ['photo', 'photoemail']:
                attachment_file = camera_record('photo')
            elif self.cond_camera_record[cond_id] in ['video', 'videoemail']:
                attachment_file = camera_record('video', 5)

            if self.cond_email_notify[cond_id]:
                if (self.email_count >= self.smtp_max_count and
                        time.time() < self.smtp_wait_timer[cond_id]):
                     self.allowed_to_send_notice = False
                else:
                    if time.time() > self.smtp_wait_timer[cond_id]:
                        self.email_count = 0
                        self.smtp_wait_timer[cond_id] = time.time()+3600
                    self.allowed_to_send_notice = True
                self.email_count += 1

                # If the emails per hour limit has not been exceeded
                if self.allowed_to_send_notice:
                    message += "\nNotify {}.".format(
                            self.cond_email_notify[cond_id])
                    # attachment_type != False indicates to
                    # attach a photo or video
                    if self.cond_camera_record[cond_id] == 'photoemail':
                        message += "\nPhoto attached."
                        attachment_type = 'still'
                    elif self.cond_camera_record[cond_id] == 'videoemail':
                        message += "\nVideo attached."
                        attachment_type = 'video'
                    with session_scope(MYCODO_DB_PATH) as new_session:
                        smtp = new_session.query(SMTP).first()
                        email(self.logger, smtp.host, smtp.ssl, smtp.port,
                              smtp.user, smtp.passw, smtp.email_from,
                              self.cond_email_notify[cond_id], message,
                              attachment_file, attachment_type)
                else:
                    self.logger.debug("[Sensor Conditional {}] "
                                      "{:.0f} seconds left to be "
                                      "allowed to email again.".format(
                                      cond_id,
                                      (self.smtp_wait_timer[cond_id]-time.time())))

            if self.cond_flash_lcd[cond_id]:
                start_flashing = threading.Thread(
                    target=self.control.flash_lcd,
                    args=(self.cond_flash_lcd[cond_id],
                          1,))
                start_flashing.start()

            self.logger.debug(message)
        else:
            self.logger.debug("[Sensor Conditional {}] Last measurement "
                              "not found".format(cond_id))
Example #48
0
    def manipulate_relays(self):
        """
        Activate a relay based on PID output (control variable) and whether
        the manipulation directive is to raise, lower, or both.

        :rtype: None
        """
        # If there was a measurement able to be retrieved from
        # influxdb database that was entered within the past minute
        if self.last_measurement_success:

            # Update setpoint if a method is selected
            if self.method_id != '':
                self.calculate_method_setpoint(self.method_id)

            self.addSetpointInfluxdb(self.pid_id, self.set_point)

            # Update PID and get control variable
            self.control_variable = self.update(self.last_measurement)

            #
            # PID control variable positive to raise environmental condition
            #
            if self.direction in ['raise', 'both'] and self.raise_relay_id:
                if self.control_variable > 0:
                    # Ensure the relay on duration doesn't exceed the set maximum
                    if (self.raise_max_duration and
                            self.control_variable > self.raise_max_duration):
                        self.raise_seconds_on = self.raise_max_duration
                    else:
                        self.raise_seconds_on = float("{0:.2f}".format(self.control_variable))

                    # Turn off lower_relay if active, because we're now raising
                    if self.lower_relay_id:
                        with session_scope(MYCODO_DB_PATH) as new_session:
                            relay = new_session.query(Relay).filter(
                                Relay.id == self.lower_relay_id).first()
                            if relay.is_on():
                                self.control.relay_off(self.lower_relay_id)

                    if self.raise_seconds_on > self.raise_min_duration:
                        # Activate raise_relay for a duration
                        self.logger.debug("[PID {}] Setpoint: {} "
                            "Output: {} to relay {}".format(
                                self.pid_id,
                                self.set_point,
                                self.control_variable,
                                self.raise_relay_id))
                        self.control.relay_on(self.raise_relay_id,
                                         self.raise_seconds_on)
                else:
                    self.control.relay_off(self.raise_relay_id)

            #
            # PID control variable negative to lower environmental condition
            #
            if self.direction in ['lower', 'both'] and self.lower_relay_id:
                if self.control_variable < 0:
                    # Ensure the relay on duration doesn't exceed the set maximum
                    if (self.lower_max_duration and
                            abs(self.control_variable) > self.lower_max_duration):
                        self.lower_seconds_on = self.lower_max_duration
                    else:
                        self.lower_seconds_on = abs(float("{0:.2f}".format(self.control_variable)))

                    # Turn off raise_relay if active, because we're now lowering
                    if self.raise_relay_id:
                        with session_scope(MYCODO_DB_PATH) as new_session:
                            relay = new_session.query(Relay).filter(
                                Relay.id == self.raise_relay_id).first()
                            if relay.is_on():
                                self.control.relay_off(self.raise_relay_id)

                    if self.lower_seconds_on > self.lower_min_duration:
                        # Activate lower_relay for a duration
                        self.logger.debug("[PID {}] Setpoint: {} "
                            "Output: {} to relay {}".format(
                                self.pid_id,
                                self.set_point,
                                self.control_variable,
                                self.lower_relay_id))
                        self.control.relay_on(self.lower_relay_id,
                                         self.lower_seconds_on)
                else:
                    self.control.relay_off(self.lower_relay_id)

        else:
            if self.direction in ['raise', 'both'] and self.raise_relay_id:
                self.control.relay_off(self.raise_relay_id)
            if self.direction in ['lower', 'both'] and self.lower_relay_id:
                self.control.relay_off(self.lower_relay_id)
Example #49
0
    def __init__(self, ready, logger, sensor_id):
        threading.Thread.__init__(self)

        self.thread_startup_timer = timeit.default_timer()
        self.thread_shutdown_timer = 0
        self.ready = ready
        self.logger = logger
        self.lock = {}
        self.sensor_id = sensor_id
        self.control = DaemonControl()

        self.pause_loop = False
        self.verify_pause_loop = True
        self.setup_sensor_conditionals()

        # Obtain database configuration options
        with session_scope(MYCODO_DB_PATH) as new_session:
            sensor = new_session.query(Sensor).filter(
                Sensor.id == self.sensor_id).first()
            self.location = sensor.location
            self.device_type = sensor.device
            self.sensor_type = sensor.device_type
            self.period = sensor.period
            self.multiplexer_address_raw = sensor.multiplexer_address
            self.multiplexer_channel = sensor.multiplexer_channel
            self.adc_channel = sensor.adc_channel
            self.adc_gain = sensor.adc_gain
            self.adc_resolution = sensor.adc_resolution
            self.adc_measure = sensor.adc_measure
            self.adc_measure_units = sensor.adc_measure_units
            self.adc_volts_min = sensor.adc_volts_min
            self.adc_volts_max = sensor.adc_volts_max
            self.adc_units_min = sensor.adc_units_min
            self.adc_units_max = sensor.adc_units_max
            self.sht_clock_pin = sensor.sht_clock_pin
            self.sht_voltage = sensor.sht_voltage

            if self.device_type == 'EDGE':
                if sensor.switch_edge == 'rising':
                    self.switch_edge_gpio = GPIO.RISING
                elif sensor.switch_edge == 'falling':
                    self.switch_edge_gpio = GPIO.FALLING
                else:
                    self.switch_edge_gpio = GPIO.BOTH
                self.switch_edge = sensor.switch_edge
                self.switch_bouncetime = sensor.switch_bouncetime
                self.switch_reset_period = sensor.switch_reset_period

            smtp = new_session.query(SMTP).first()
            self.smtp_max_count = smtp.hourly_max
            self.email_count = 0
            self.allowed_to_send_notice = True

            # Relay that will activate prior to sensor read
            self.pre_relay_id = sensor.pre_relay_id
            self.pre_relay_duration = sensor.pre_relay_duration
            self.pre_relay_setup = False
            self.next_measurement = time.time()
            self.get_new_measurement = False
            self.measurement_acquired = False
            self.pre_relay_activated = False
            self.pre_relay_timer = time.time()
            relay = new_session.query(Relay).all()
            # Check if relay ID actually exists
            for each_relay in relay:
                if each_relay.id == self.pre_relay_id and self.pre_relay_duration:
                    self.pre_relay_setup = True

        if self.device_type in ['AM2315', 'BMP'] and self.multiplexer_address_raw:
            self.multiplexer_address_string = self.multiplexer_address_raw
            self.multiplexer_address = int(str(self.multiplexer_address_raw), 16)
            self.multiplexer_lock_file = "/var/lock/mycodo_multiplexer_0x{:02X}.pid".format(self.multiplexer_address)
            self.multiplexer = TCA9548A(self.multiplexer_address)
        else:
            self.multiplexer = None

        if self.device_type in ['ADS1x15','MCP342x'] and self.location:
            self.adc_lock_file = "/var/lock/mycodo_adc_0x{:02X}.pid".format(int(str(self.location), 16))
            
        else:
            self.adc = None

        self.device_recognized = True

        # Processes
        if self.device_type == 'RPiCPULoad':
            self.measure_sensor = RaspberryPiCPULoad()

        # Environmental Sensors
        elif self.device_type == 'RPi':
            self.measure_sensor = RaspberryPiCPUTemp()
        elif self.device_type == 'DS18B20':
            self.measure_sensor = DS18B20(self.location)
        elif self.device_type == 'DHT11':
            self.measure_sensor = DHT11(pigpio.pi(), int(self.location))
        elif self.device_type in ['DHT22', 'AM2302']:
            self.measure_sensor = DHT22(pigpio.pi(), int(self.location))
        elif self.device_type == 'AM2315':
            self.measure_sensor = AM2315_read()
        elif self.device_type == 'K30':
            self.measure_sensor = K30()
        elif self.device_type == 'BMP':
            self.measure_sensor = BMP()
        elif self.device_type == 'SHT1x_7x':
            self.measure_sensor = SHT1x_7x_read(self.location, self.sht_clock_pin, self.sht_voltage)
        elif self.device_type == 'SHT2x':
            self.measure_sensor = SHT2x_read(int(str(self.location), 16))
        elif self.device_type == 'TMP006':
            self.measure_sensor = TMP006_read(self.location)
        elif self.device_type == 'TSL2561':
            self.measure_sensor = TSL2561_read(self.location)

        # Devices
        elif self.device_type == 'ADS1x15':
            self.adc = ADS1x15_read(self.logger, int(str(self.location), 16), self.adc_channel, self.adc_gain)
        elif self.device_type == 'MCP342x':
            self.adc = MCP342x_read(self.logger, int(str(self.location), 16), self.adc_channel, self.adc_gain, self.adc_resolution)

        # Other
        elif self.device_type in ['EDGE', 'ADS1x15', 'MCP342x']:
            self.measure_sensor = None
        else:
            self.device_recognized = False
            self.logger.debug("[Sensor {}] Device '{}' not "
                              "recognized:".format(self.sensor_id,
                                                   self.device_type))
            raise Exception("{} is not a valid device type.".format(
                self.device_type))

        self.edge_reset_timer = time.time()
        self.sensor_timer = time.time()
        self.running = False
        self.lastUpdate = None
    def checkConditionals(self, relay_id, on_duration):
        with session_scope(MYCODO_DB_PATH) as new_session:
            conditionals = new_session.query(RelayConditional)
            new_session.expunge_all()
            new_session.close()

        conditionals = conditionals.filter(
            RelayConditional.if_relay_id == relay_id)
        conditionals = conditionals.filter(RelayConditional.activated == True)

        if self.is_on(relay_id):
            conditionals = conditionals.filter(
                RelayConditional.if_action == 'on')
            conditionals = conditionals.filter(
                RelayConditional.if_duration == on_duration)
        else:
            conditionals = conditionals.filter(
                RelayConditional.if_action == 'off')

        for each_conditional in conditionals.all():
            message = None
            if (each_conditional.do_relay_id
                    or each_conditional.execute_command
                    or each_conditional.email_notify):
                now = time.time()
                timestamp = datetime.datetime.fromtimestamp(now).strftime(
                    '%Y-%m-%d %H-%M-%S')
                message = "{}\n[Relay Conditional {}] {}\n".format(
                    timestamp, each_conditional.id, each_conditional.name)
                message += "If relay {} ({}) turns {}, Then:\n".format(
                    each_conditional.if_relay_id,
                    self.relay_name[each_conditional.if_relay_id],
                    each_conditional.if_action)

            if each_conditional.do_relay_id:
                message += "Turn relay {} ({}) {}".format(
                    each_conditional.do_relay_id,
                    self.relay_name[each_conditional.do_relay_id],
                    each_conditional.do_action)

                if each_conditional.do_duration == 0:
                    self.relay_on_off(each_conditional.do_relay_id,
                                      each_conditional.do_action)
                else:
                    message += " for {} seconds".format(
                        each_conditional.do_duration)
                    self.relay_on_off(each_conditional.do_relay_id,
                                      each_conditional.do_action,
                                      each_conditional.do_duration)
                message += ".\n"

            if each_conditional.execute_command:
                # Execute command as user mycodo
                message += "Execute: '{}'. ".format(
                    each_conditional.execute_command)
                cmd_out, cmd_err, cmd_status = cmd_output(
                    self.cond_execute_command[cond_id])
                message += "Status: {}. ".format(cmd_status)

            if each_conditional.email_notify:
                if (self.email_count >= self.smtp_max_count
                        and time.time() < self.smtp_wait_time):
                    self.allowed_to_send_notice = False
                else:
                    if time.time() > self.smtp_wait_time:
                        self.email_count = 0
                        self.smtp_wait_time = time.time() + 3600
                    self.allowed_to_send_notice = True
                self.email_count += 1

                if self.allowed_to_send_notice:
                    message += "Notify {}.".format(
                        each_conditional.email_notify)

                    with session_scope(MYCODO_DB_PATH) as new_session:
                        smtp = new_session.query(SMTP).first()
                        send_email(self.logger, smtp.host, smtp.ssl, smtp.port,
                                   smtp.user, smtp.passw, smtp.email_from,
                                   each_conditional.email_notify, message)
                else:
                    self.logger.debug("[Relay Conditional {}] True: "
                                      "{:.0f} seconds left to be "
                                      "allowed to email again.".format(
                                          each_cond.id,
                                          (self.smtp_wait_time - time.time())))

            if each_conditional.flash_lcd:
                start_flashing = threading.Thread(
                    target=self.control.flash_lcd,
                    args=(
                        each_conditional.flash_lcd,
                        1,
                    ))
                start_flashing.start()

            if (each_conditional.do_relay_id
                    or each_conditional.execute_command
                    or each_conditional.email_notify):
                self.logger.debug("{}".format(message))