Exemple #1
0
def export_influxdb(form):
    """
    Save the Mycodo InfluxDB database in the Enterprise-compatible format, zip
    archive it, and serve it to the user.
    """
    action = '{action} {controller}'.format(
        action=TRANSLATIONS['export']['title'],
        controller=TRANSLATIONS['measurement']['title'])
    error = []

    try:
        influx_backup_dir = os.path.join(INSTALL_DIRECTORY, 'influx_backup')

        # Delete influxdb directory if it exists
        if os.path.isdir(influx_backup_dir):
            shutil.rmtree(influx_backup_dir)

        # Create new directory (make sure it's empty)
        assure_path_exists(influx_backup_dir)

        cmd = "/usr/bin/influxd backup -database {db} -portable {path}".format(
            db=INFLUXDB_DATABASE, path=influx_backup_dir)
        _, _, status = cmd_output(cmd)

        influxd_version_out, _, _ = cmd_output('/usr/bin/influxd version')
        if influxd_version_out:
            influxd_version = influxd_version_out.decode('utf-8').split(' ')[1]
        else:
            influxd_version = None
            error.append("Could not determine Influxdb version")

        if not status and influxd_version:
            # Zip all files in the influx_backup directory
            data = io.BytesIO()
            with zipfile.ZipFile(data, mode='w') as z:
                for _, _, files in os.walk(influx_backup_dir):
                    for filename in files:
                        z.write(os.path.join(influx_backup_dir, filename),
                                filename)
            data.seek(0)

            # Delete influxdb directory if it exists
            if os.path.isdir(influx_backup_dir):
                shutil.rmtree(influx_backup_dir)

            # Send zip file to user
            return send_file(
                data,
                mimetype='application/zip',
                as_attachment=True,
                attachment_filename='Mycodo_{mv}_Influxdb_{iv}_{host}_{dt}.zip'
                .format(
                    mv=MYCODO_VERSION,
                    iv=influxd_version,
                    host=socket.gethostname().replace(' ', ''),
                    dt=datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S")))
    except Exception as err:
        error.append("Error: {}".format(err))

    flash_success_errors(error, action, url_for('routes_page.page_export'))
Exemple #2
0
def thread_import_settings(tmp_folder):
    # Upgrade database
    cmd = "{pth}/mycodo/scripts/mycodo_wrapper " \
          "upgrade_database".format(
        pth=INSTALL_DIRECTORY)
    _, _, _ = cmd_output(cmd)

    # Install/update dependencies (could take a while)
    cmd = "{pth}/mycodo/scripts/mycodo_wrapper update_dependencies" \
          " | ts '[%Y-%m-%d %H:%M:%S]' >> {log} 2>&1".format(
        pth=INSTALL_DIRECTORY,
        log=DEPENDENCY_LOG_FILE)
    _, _, _ = cmd_output(cmd)

    # Initialize
    cmd = "{pth}/mycodo/scripts/mycodo_wrapper " \
          "initialize".format(
        pth=INSTALL_DIRECTORY)
    _, _, _ = cmd_output(cmd)

    # Start Mycodo daemon (backend)
    cmd = "{pth}/mycodo/scripts/mycodo_wrapper " \
          "daemon_start".format(
        pth=INSTALL_DIRECTORY)
    _, _, _ = cmd_output(cmd)

    # Delete tmp directory if it exists
    if os.path.isdir(tmp_folder):
        shutil.rmtree(tmp_folder)
Exemple #3
0
    def output_switch(self,
                      state,
                      output_type=None,
                      amount=None,
                      output_channel=None):
        if not self.is_setup():
            self.logger.error('Output not set up')
            return

        if state == 'on':
            cmd_return, cmd_error, cmd_status = cmd_output(
                self.options_channels['on_command'][0],
                user=self.options_channels['linux_command_user'][0])
            self.output_states[0] = True
        elif state == 'off':
            cmd_return, cmd_error, cmd_status = cmd_output(
                self.options_channels['off_command'][0],
                user=self.options_channels['linux_command_user'][0])
            self.output_states[0] = False
        else:
            return

        self.logger.debug(
            "Output on/off {state} command returned: Status: {stat}, Output: '{ret}', Error: '{err}'"
            .format(state=state,
                    stat=cmd_status,
                    ret=cmd_return,
                    err=cmd_error))
Exemple #4
0
def export_influxdb(form):
    """
    Save the InfluxDB metastore and mycodo_db database to a zip file and serve
    it to the user
    """
    action = '{action} {controller}'.format(
        action=TRANSLATIONS['export']['title'],
        controller=TRANSLATIONS['measurement']['title'])
    error = []

    try:
        influx_backup_dir = os.path.join(INSTALL_DIRECTORY, 'influx_backup')

        # Delete influxdb directory if it exists
        if os.path.isdir(influx_backup_dir):
            shutil.rmtree(influx_backup_dir)

        # Create new directory (make sure it's empty)
        assure_path_exists(influx_backup_dir)

        cmd = "/usr/bin/influxd backup -database mycodo_db {path}".format(
            path=influx_backup_dir)
        _, _, status = cmd_output(cmd)

        influxd_version_out, _, _ = cmd_output(
            '/usr/bin/influxd version')
        if influxd_version_out:
            influxd_version = influxd_version_out.decode('utf-8').split(' ')[1]
        else:
            influxd_version = None
            error.append("Could not determine Influxdb version")

        if not status and influxd_version:
            # Zip all files in the influx_backup directory
            data = io.BytesIO()
            with zipfile.ZipFile(data, mode='w') as z:
                for _, _, files in os.walk(influx_backup_dir):
                    for filename in files:
                        z.write(os.path.join(influx_backup_dir, filename),
                                filename)
            data.seek(0)

            # Delete influxdb directory if it exists
            if os.path.isdir(influx_backup_dir):
                shutil.rmtree(influx_backup_dir)

            # Send zip file to user
            return send_file(
                data,
                mimetype='application/zip',
                as_attachment=True,
                attachment_filename='Mycodo_{mv}_Influxdb_{iv}_{host}_{dt}.zip'.format(
                     mv=MYCODO_VERSION, iv=influxd_version,
                     host=socket.gethostname().replace(' ', ''),
                     dt=datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S"))
            )
    except Exception as err:
        error.append("Error: {}".format(err))

    flash_success_errors(error, action, url_for('routes_page.page_export'))
Exemple #5
0
    def output_switch(self, output_id, state, duty_cycle=None):
        """Conduct the actual execution of GPIO state change, PWM, or command execution"""
        if self.output_type[output_id] == 'wired':
            if state == 'on':
                GPIO.output(self.output_pin[output_id],
                            self.output_trigger[output_id])
            elif state == 'off':
                GPIO.output(self.output_pin[output_id],
                            not self.output_trigger[output_id])

        elif self.output_type[output_id] == 'wireless_433MHz_pi_switch':
            if state == 'on':
                self.wireless_pi_switch[output_id].transmit(
                    int(self.output_on_command[output_id]))
            elif state == 'off':
                self.wireless_pi_switch[output_id].transmit(
                    int(self.output_off_command[output_id]))

        elif self.output_type[output_id] == 'command':
            if state == 'on' and self.output_on_command[output_id]:
                cmd_return, _, cmd_status = cmd_output(
                    self.output_on_command[output_id])
            elif state == 'off' and self.output_off_command[output_id]:
                cmd_return, _, cmd_status = cmd_output(
                    self.output_off_command[output_id])
            else:
                return
            self.logger.debug("Output {state} command returned: "
                              "{stat}: '{ret}'".format(state=state,
                                                       stat=cmd_status,
                                                       ret=cmd_return))

        elif self.output_type[output_id] == 'pwm':
            if state == 'on':
                if self.pwm_library[output_id] == 'pigpio_hardware':
                    self.pwm_output[output_id].hardware_PWM(
                        self.output_pin[output_id], self.pwm_hertz[output_id],
                        int(abs(duty_cycle) * 10000))
                elif self.pwm_library[output_id] == 'pigpio_any':
                    self.pwm_output[output_id].set_PWM_frequency(
                        self.output_pin[output_id], self.pwm_hertz[output_id])
                    calc_duty_cycle = int((abs(duty_cycle) / 100.0) * 255)
                    if calc_duty_cycle > 255:
                        calc_duty_cycle = 255
                    if calc_duty_cycle < 0:
                        calc_duty_cycle = 0
                    self.pwm_output[output_id].set_PWM_dutycycle(
                        self.output_pin[output_id], calc_duty_cycle)
                self.pwm_state[output_id] = abs(duty_cycle)
            elif state == 'off':
                if self.pwm_library[output_id] == 'pigpio_hardware':
                    self.pwm_output[output_id].hardware_PWM(
                        self.output_pin[output_id], self.pwm_hertz[output_id],
                        0)
                elif self.pwm_library[output_id] == 'pigpio_any':
                    self.pwm_output[output_id].set_PWM_frequency(
                        self.output_pin[output_id], self.pwm_hertz[output_id])
                    self.pwm_output[output_id].set_PWM_dutycycle(
                        self.output_pin[output_id], 0)
                self.pwm_state[output_id] = None
Exemple #6
0
    def get_measurement(self):
        """ Determine if the return value of the command is a number """
        self.return_dict = measurements_dict.copy()

        self.logger.debug("Command being executed: {}".format(self.command))

        timeout = 360
        if self.command_timeout:
            timeout = self.command_timeout

        out, err, status = cmd_output(
            self.command,
            timeout=timeout,
            user=self.execute_as_user,
            cwd=self.current_working_dir)

        self.logger.debug("Command returned: {}, Status: {}, Error: {}".format(out, err, status))

        if str_is_float(out):
            measurement_value = float(out)
        else:
            self.logger.debug(
                "The command returned a non-numerical value. "
                "Ensure only one numerical value is returned "
                "by the command. Value returned: '{}'".format(out))
            return

        for channel in self.channels_measurement:
            if self.is_enabled(channel):
                self.return_dict[channel]['unit'] = self.channels_measurement[channel].unit
                self.return_dict[channel]['measurement'] = self.channels_measurement[channel].measurement
                self.return_dict[channel]['value'] = measurement_value

        return self.return_dict
Exemple #7
0
    def output_switch(self, state, output_type=None, amount=None):
        measure_dict = copy.deepcopy(measurements_dict)

        if self.pwm_command:
            if state == 'on' and 0 <= amount <= 100:
                if self.pwm_invert_signal:
                    amount = 100.0 - abs(amount)
            elif state == 'off':
                if self.pwm_invert_signal:
                    amount = 100
                else:
                    amount = 0
            else:
                return

            self.pwm_state = amount

            cmd = self.pwm_command.replace('((duty_cycle))', str(amount))
            cmd_return, cmd_error, cmd_status = cmd_output(
                cmd, user=self.linux_command_user)

            measure_dict[0]['value'] = self.pwm_state
            add_measurements_influxdb(self.unique_id, measure_dict)

            self.logger.debug("Duty cycle set to {dc:.2f} %".format(dc=amount))
            self.logger.debug(
                "Output duty cycle {duty_cycle} command returned: "
                "Status: {stat}, "
                "Output: '{ret}', "
                "Error: '{err}'".format(duty_cycle=amount,
                                        stat=cmd_status,
                                        ret=cmd_return,
                                        err=cmd_error))
Exemple #8
0
def thread_import_influxdb(tmp_folder):
    # Restore the backup to new database mycodo_db_bak
    output_successes = []
    cmd = "{pth}/mycodo/scripts/mycodo_wrapper " \
          "influxdb_restore_mycodo_db {dir}".format(
        pth=INSTALL_DIRECTORY, dir=tmp_folder)
    out, _, _ = cmd_output(cmd)
    if out:
        output_successes.append(out.decode('utf-8'))

    # Copy all measurements from backup to current database
    mycodo_db_backup = 'mycodo_db_bak'
    client = InfluxDBClient(
        INFLUXDB_HOST,
        INFLUXDB_PORT,
        INFLUXDB_USER,
        INFLUXDB_PASSWORD,
        mycodo_db_backup,
        timeout=5)
    query_str = "SELECT * INTO {}..:MEASUREMENT FROM /.*/ GROUP BY *".format(
        INFLUXDB_DATABASE)
    client.query(query_str)

    # Delete backup database
    client.drop_database(mycodo_db_backup)

    # Delete tmp directory if it exists
    if os.path.isdir(tmp_folder):
        shutil.rmtree(tmp_folder)
def get_installed_dependencies():
    met_deps = []

    list_dependencies = [
        parse_function_information(),
        parse_input_information(),
        parse_output_information(), CAMERA_INFO, FUNCTION_ACTION_INFO,
        FUNCTION_INFO, LCD_INFO, METHOD_INFO, DEPENDENCIES_GENERAL
    ]

    for each_section in list_dependencies:
        for device_type in each_section:
            if 'dependencies_module' in each_section[device_type]:
                dep_mod = each_section[device_type]['dependencies_module']
                for (install_type, package, install_id) in dep_mod:
                    entry = '{0} {1}'.format(install_type, install_id)
                    if install_type in ['pip-pypi', 'pip-git']:
                        try:
                            module = importlib.util.find_spec(package)
                            if module is not None and entry not in met_deps:
                                met_deps.append(entry)
                        except Exception:
                            logger.error(
                                'Exception checking python dependency: '
                                '{dep}'.format(dep=package))
                    elif install_type == 'apt':
                        start = "dpkg-query -W -f='${Status}'"
                        end = '2>/dev/null | grep -c "ok installed"'
                        cmd = "{} {} {}".format(start, package, end)
                        _, _, status = cmd_output(cmd, user='******')
                        if not status and entry not in met_deps:
                            met_deps.append(entry)

    return met_deps
Exemple #10
0
def execute_at_modification(
        mod_input,
        request_form,
        custom_options_json_presave,
        custom_options_json_postsave):
    """
    Function to run when the Input is saved to evaluate the Python 3 code using pylint3
    :param mod_input: The WTForms object containing the form data submitted by the web GUI
    :param request_form: The custom_options form input data (if it exists):param mod_widget:
    :param custom_options_json_presave:
    :param custom_options_json_postsave:
    :return:
    :return: tuple of (all_passed, error, mod_input) variables
    """
    all_passed = True
    error = []

    input_python_code_run, file_run = generate_code(mod_input.unique_id, mod_input)

    if len(input_python_code_run.splitlines()) > 999:
        error.append("Too many lines in code. Reduce code to less than 1000 lines.")

    lines_code = ''
    for line_num, each_line in enumerate(input_python_code_run.splitlines(), 1):
        if len(str(line_num)) == 3:
            line_spacing = ''
        elif len(str(line_num)) == 2:
            line_spacing = ' '
        else:
            line_spacing = '  '
        lines_code += '{sp}{ln}: {line}\n'.format(
            sp=line_spacing,
            ln=line_num,
            line=each_line)

    cmd_test = 'export PYTHONPATH=$PYTHONPATH:/var/mycodo-root && ' \
               'pylint3 -d I,W0621,C0103,C0111,C0301,C0327,C0410,C0413 {path}'.format(
        path=file_run)
    cmd_out, _, cmd_status = cmd_output(cmd_test)

    message = Markup(
        '<pre>\n\n'
        'Full Python Code Input code:\n\n{code}\n\n'
        'Python Code Input code analysis:\n\n{report}'
        '</pre>'.format(
            code=lines_code, report=cmd_out.decode("utf-8")))
    if cmd_status:
        flash('Error(s) were found while evaluating your code. Review '
              'the error(s), below, and fix them before activating your '
              'Input.', 'error')
        flash(message, 'error')
    else:
        flash(
            "No errors were found while evaluating your code. However, "
            "this doesn't mean your code will perform as expected. "
            "Review your code for issues and test your Input "
            "before putting it into a production environment.", 'success')
        flash(message, 'success')

    return all_passed, error, mod_input
def get_installed_dependencies():
    met_deps = []

    dict_inputs = parse_input_information()

    list_dependencies = [
        dict_inputs,
        MATH_INFO,
        METHOD_INFO,
        OUTPUT_INFO
    ]
    for each_section in list_dependencies:
        for device_type in each_section:
            for each_device, each_dict in each_section[device_type].items():
                if each_device == 'dependencies_module':
                    for (install_type, package, install_id) in each_dict:
                        entry = '{0} {1}'.format(install_type, install_id)
                        if install_type in ['pip-pypi', 'pip-git']:
                            try:
                                module = importlib.util.find_spec(package)
                                if module is not None and entry not in met_deps:
                                    met_deps.append(entry)
                            except Exception:
                                logger.error(
                                    'Exception while checking python dependency: '
                                    '{dep}'.format(dep=package))
                        elif install_type == 'apt':
                            cmd = 'dpkg -l {}'.format(package)
                            _, _, stat = cmd_output(cmd)
                            if not stat and entry not in met_deps:
                                met_deps.append(entry)

    return met_deps
Exemple #12
0
def get_installed_apt_dependencies():
    met_deps = []

    list_dependencies = [
        parse_function_information(),
        parse_action_information(),
        parse_input_information(),
        parse_output_information(),
        parse_widget_information(),
        CAMERA_INFO,
        FUNCTION_INFO,
        METHOD_INFO,
        DEPENDENCIES_GENERAL
    ]

    for each_section in list_dependencies:
        for device_type in each_section:
            if 'dependencies_module' in each_section[device_type]:
                dep_mod = each_section[device_type]['dependencies_module']
                for (install_type, package, install_id) in dep_mod:
                    if install_type == 'apt':
                        start = "dpkg-query -W -f='${Status}'"
                        end = '2>/dev/null | grep -c "ok installed"'
                        cmd = "{} {} {}".format(start, package, end)
                        _, _, status = cmd_output(cmd, user='******')
                        if not status and install_id not in met_deps:
                            met_deps.append(install_id)

    return met_deps
Exemple #13
0
    def output_switch(self, state, output_type=None, amount=None):
        if state == 'on':
            cmd_return, cmd_error, cmd_status = cmd_output(
                self.on_command, user=self.linux_command_user)
            self.output_state = True
        elif state == 'off':
            cmd_return, cmd_error, cmd_status = cmd_output(
                self.off_command, user=self.linux_command_user)
            self.output_state = False
        else:
            return

        self.logger.debug(
            "Output on/off {state} command returned: Status: {stat}, Output: '{ret}', Error: '{err}'"
            .format(state=state,
                    stat=cmd_status,
                    ret=cmd_return,
                    err=cmd_error))
def test_python_code(python_code_run, filename):
    """
    Function to evaluate the Python 3 code using pylint3
    :param :
    :return: tuple of (all_passed, error, mod_input) variables
    """
    success = []
    error = []

    try:
        python_code_run, file_run = create_python_file(python_code_run,
                                                       filename)

        lines_code = ''
        for line_num, each_line in enumerate(python_code_run.splitlines(), 1):
            if len(str(line_num)) == 3:
                line_spacing = ''
            elif len(str(line_num)) == 2:
                line_spacing = ' '
            else:
                line_spacing = '  '
            lines_code += '{sp}{ln}: {line}\n'.format(sp=line_spacing,
                                                      ln=line_num,
                                                      line=each_line)

        cmd_test = 'mkdir -p /var/mycodo-root/.pylint.d && ' \
                   'export PYTHONPATH=$PYTHONPATH:/var/mycodo-root && ' \
                   'export PYLINTHOME=/var/mycodo-root/.pylint.d && ' \
                   'pylint3 -d I,W0621,C0103,C0111,C0301,C0327,C0410,C0413,R0201,R0903,W0201,W0612 {path}'.format(
                       path=file_run)
        cmd_out, _, cmd_status = cmd_output(cmd_test)

        message = Markup('<pre>\n\n'
                         'Full Python Code Input code:\n\n{code}\n\n'
                         'Python Code Input code analysis:\n\n{report}'
                         '</pre>'.format(code=lines_code.replace("<", "&lt"),
                                         report=cmd_out.decode("utf-8")))
    except Exception as err:
        cmd_status = None
        message = "Error running pylint: {}".format(err)
        error.append(message)

    if cmd_status and cmd_status != 30:
        error.append('Error(s) were found while evaluating your code. Review '
                     'the error(s), below, and fix them.')
        error.append("pylint returned with status: {}".format(
            cmd_status, 'error'))
        error.append(message)
    else:
        success.append(
            "No errors were found while evaluating your code. However, "
            "this doesn't mean your code will perform as expected. "
            "Review your code for issues and test before putting it "
            "into a production environment.")
        success.append(message)

    return success, error
Exemple #15
0
def save_conditional_code(error, cond_statement, unique_id, test=False):
    indented_code = textwrap.indent(
        cond_statement, ' ' * 8)

    cond_statement_run = pre_statement_run + indented_code
    cond_statement_run = cond_statement_replace(cond_statement_run)

    assure_path_exists(PATH_PYTHON_CODE_USER)
    file_run = '{}/conditional_{}.py'.format(
        PATH_PYTHON_CODE_USER, unique_id)
    with open(file_run, 'w') as fw:
        fw.write('{}\n'.format(cond_statement_run))
        fw.close()
    set_user_grp(file_run, 'mycodo', 'mycodo')

    if len(cond_statement_run.splitlines()) > 999:
        error.append("Too many lines in code. Reduce code to less than 1000 lines.")

    if test:
        lines_code = ''
        for line_num, each_line in enumerate(cond_statement_run.splitlines(), 1):
            if len(str(line_num)) == 3:
                line_spacing = ''
            elif len(str(line_num)) == 2:
                line_spacing = ' '
            else:
                line_spacing = '  '
            lines_code += '{sp}{ln}: {line}\n'.format(
                sp=line_spacing,
                ln=line_num,
                line=each_line)

        cmd_test = 'export PYTHONPATH=$PYTHONPATH:/var/mycodo-root && ' \
                   'pylint3 -d I,W0621,C0103,C0111,C0301,C0327,C0410,C0413 {path}'.format(
            path=file_run)
        cmd_out, _, cmd_status = cmd_output(cmd_test)

        message = Markup(
            '<pre>\n\n'
            'Full Conditional Statement code:\n\n{code}\n\n'
            'Conditional Statement code analysis:\n\n{report}'
            '</pre>'.format(
                code=lines_code, report=cmd_out.decode("utf-8")))
        if cmd_status:
            flash('Error(s) were found while evaluating your code. Review '
                  'the error(s), below, and fix them before activating your '
                  'Conditional.', 'error')
            flash(message, 'error')
        else:
            flash(
                "No errors were found while evaluating your code. However, "
                "this doesn't mean your code will perform as expected. "
                "Review your code for issues and test your Conditional "
                "before putting it into a production environment.", 'success')
            flash(message, 'success')

    return error
Exemple #16
0
 def get_measurement(self, display_id, i):
     try:
         if self.lcd_line[display_id][i]['measure'] == 'IP':
             str_IP_cmd = "ip addr | grep 'state UP' -A2 | tail -n1 | awk '{print $2}' | cut -f1  -d'/'"
             IP_out, _, _ = cmd_output(str_IP_cmd)
             self.lcd_line[display_id][i]['name'] = ''
             self.lcd_line[display_id][i]['unit'] = ''
             self.lcd_line[display_id][i]['measure_val'] = IP_out.rstrip(
             ).decode("utf-8")
             return True
         elif self.lcd_line[display_id][i]['measure'] == 'output_state':
             self.lcd_line[display_id][i][
                 'measure_val'] = self.output_state(
                     self.lcd_line[display_id][i]['id'])
             return True
         else:
             if self.lcd_line[display_id][i]['measure'] == 'time':
                 last_measurement = read_last_influxdb(
                     self.lcd_line[display_id][i]['id'],
                     '/.*/',
                     duration_sec=self.lcd_max_age[display_id][i])
             else:
                 last_measurement = read_last_influxdb(
                     self.lcd_line[display_id][i]['id'],
                     self.lcd_line[display_id][i]['measure'],
                     duration_sec=self.lcd_max_age[display_id][i])
             if last_measurement:
                 self.lcd_line[display_id][i]['time'] = last_measurement[0]
                 if self.lcd_decimal_places[display_id][i] == 0:
                     self.lcd_line[display_id][i]['measure_val'] = int(
                         last_measurement[1])
                 else:
                     self.lcd_line[display_id][i]['measure_val'] = round(
                         last_measurement[1],
                         self.lcd_decimal_places[display_id][i])
                 utc_dt = datetime.datetime.strptime(
                     self.lcd_line[display_id][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("Latest {}: {} @ {}".format(
                     self.lcd_line[display_id][i]['measure'],
                     self.lcd_line[display_id][i]['measure_val'],
                     local_timestamp))
                 return True
             else:
                 self.lcd_line[display_id][i]['time'] = None
                 self.lcd_line[display_id][i]['measure_val'] = None
                 self.logger.debug("No data returned from influxdb")
         return False
     except Exception as except_msg:
         self.logger.debug(
             "Failed to read measurement from the influxdb database: "
             "{err}".format(err=except_msg))
         return False
Exemple #17
0
def thread_import_settings(tmp_folder):
    logger.info("Finishing up settings import")

    try:
        # Upgrade database
        cmd = "{pth}/mycodo/scripts/mycodo_wrapper " \
              "upgrade_database".format(
            pth=INSTALL_DIRECTORY)
        _, _, _ = cmd_output(cmd)

        # Install/update dependencies (could take a while)
        cmd = "{pth}/mycodo/scripts/mycodo_wrapper update_dependencies" \
              " | ts '[%Y-%m-%d %H:%M:%S]' >> {log} 2>&1".format(
            pth=INSTALL_DIRECTORY,
            log=DEPENDENCY_LOG_FILE)
        _, _, _ = cmd_output(cmd)

        # Initialize
        cmd = "{pth}/mycodo/scripts/mycodo_wrapper " \
              "initialize".format(
            pth=INSTALL_DIRECTORY)
        _, _, _ = cmd_output(cmd)

        # Generate widget HTML
        generate_widget_html()

        if DOCKER_CONTAINER:
            subprocess.Popen('docker start mycodo_daemon 2>&1', shell=True)
        else:
            # Start Mycodo daemon (backend)
            cmd = "{pth}/mycodo/scripts/mycodo_wrapper " \
                  "daemon_start".format(
                pth=INSTALL_DIRECTORY)
            _, _, _ = cmd_output(cmd)

        # Delete tmp directory if it exists
        if os.path.isdir(tmp_folder):
            shutil.rmtree(tmp_folder)
    except:
        logger.exception("thread_import_settings()")

    logger.info("Settings import complete")
Exemple #18
0
def save_conditional_code(error,
                          cond_statement,
                          unique_id,
                          table_conditions_all,
                          table_actions_all,
                          test=False):
    lines_code = None
    cmd_status = None
    cmd_out = None

    try:
        indented_code = textwrap.indent(cond_statement, ' ' * 8)

        cond_statement_run = pre_statement_run + indented_code
        cond_statement_run = cond_statement_replace(cond_statement_run,
                                                    table_conditions_all,
                                                    table_actions_all)

        assure_path_exists(PATH_PYTHON_CODE_USER)
        file_run = '{}/conditional_{}.py'.format(PATH_PYTHON_CODE_USER,
                                                 unique_id)
        with open(file_run, 'w') as fw:
            fw.write('{}\n'.format(cond_statement_run))
            fw.close()
        set_user_grp(file_run, 'mycodo', 'mycodo')

        if len(cond_statement_run.splitlines()) > 999:
            error.append(
                "Too many lines in code. Reduce code to less than 1000 lines.")

        if test:
            lines_code = ''
            for line_num, each_line in enumerate(
                    cond_statement_run.splitlines(), 1):
                if len(str(line_num)) == 3:
                    line_spacing = ''
                elif len(str(line_num)) == 2:
                    line_spacing = ' '
                else:
                    line_spacing = '  '
                lines_code += '{sp}{ln}: {line}\n'.format(sp=line_spacing,
                                                          ln=line_num,
                                                          line=each_line)

            cmd_test = 'mkdir -p /var/mycodo-root/.pylint.d && ' \
                       'export PYTHONPATH=$PYTHONPATH:/var/mycodo-root && ' \
                       'export PYLINTHOME=/var/mycodo-root/.pylint.d && ' \
                       'pylint3 -d I,W0621,C0103,C0111,C0301,C0327,C0410,C0413,R0912,R0914,R0915 {path}'.format(
                           path=file_run)
            cmd_out, _, cmd_status = cmd_output(cmd_test)
    except Exception as err:
        error.append("Error saving/testing conditional code: {}".format(err))

    return error, lines_code, cmd_status, cmd_out
Exemple #19
0
    def get_measurement(self):
        """ Determine if the return value of the command is a number """
        self._measurement = None

        out, _, _ = cmd_output(self.command)
        if str_is_float(out):
            return float(out)
        else:
            logger.error("The command returned a non-numerical value. "
                         "Ensure only one numerical value is returned "
                         "by the command.")
            return None
def test_python_code(python_code_run, filename):
    """
    Function to evaluate the Python 3 code using pylint3
    :param :
    :return: tuple (info, warning, success, error)
    """
    info = []
    warning = []
    success = []
    error = []

    try:
        python_code_run, file_run = create_python_file(python_code_run,
                                                       filename)

        lines_code = ''
        for line_num, each_line in enumerate(python_code_run.splitlines(), 1):
            if len(str(line_num)) == 3:
                line_spacing = ''
            elif len(str(line_num)) == 2:
                line_spacing = ' '
            else:
                line_spacing = '  '
            lines_code += '{sp}{ln}: {line}\n'.format(sp=line_spacing,
                                                      ln=line_num,
                                                      line=each_line)

        cmd_test = 'mkdir -p /var/mycodo-root/.pylint.d && ' \
                   'export PYTHONPATH=$PYTHONPATH:/var/mycodo-root && ' \
                   'export PYLINTHOME=/var/mycodo-root/.pylint.d && ' \
                   'pylint3 -d I,W0621,C0103,C0111,C0301,C0327,C0410,C0413,R0201,R0903,W0201,W0612 {path}'.format(
                       path=file_run)
        cmd_out, _, cmd_status = cmd_output(cmd_test)

        message = Markup('<pre>\n\n'
                         'Full Python Code Input code:\n\n{code}\n\n'
                         'Python Code Input code analysis:\n\n{report}'
                         '</pre>'.format(code=lines_code.replace("<", "&lt"),
                                         report=cmd_out.decode("utf-8")))
    except Exception as err:
        cmd_status = None
        message = "Error running pylint: {}".format(err)
        error.append(message)

    if cmd_status:
        warning.append("pylint returned with status: {}".format(cmd_status))

    if message:
        info.append("Review your code for issues and test before putting it "
                    "into a production environment.")
        info.append(message)

    return info, warning, success, error
Exemple #21
0
    def backup_measurements(self):
        influxd_version_out, _, _ = cmd_output('/usr/bin/influxd version')
        if influxd_version_out:
            influxd_version = influxd_version_out.decode('utf-8').split(' ')[1]
        else:
            influxd_version = "UNKNOWN"
        filename = 'Mycodo_{mv}_Influxdb_{iv}_{host}_{dt}.zip'.format(
            mv=MYCODO_VERSION,
            iv=influxd_version,
            host=socket.gethostname().replace(' ', ''),
            dt=datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S"))
        path_save = os.path.join(PATH_MEASUREMENTS_BACKUP, filename)
        assure_path_exists(PATH_MEASUREMENTS_BACKUP)
        status, saved_path = create_measurements_export(save_path=path_save)
        if not status:
            self.logger.debug("Saved measurements file: "
                              "{}".format(saved_path))
        else:
            self.logger.debug("Could not create measurements file: "
                              "{}".format(saved_path))

        if self.backup_remove_measurements_archives:
            remove_files = "--remove-source-files "
        else:
            remove_files = ""
        rsync_cmd = "rsync {rem}-avz -e 'ssh -p {port}' {path_local} {user}@{host}:{remote_path}".format(
            rem=remove_files,
            port=self.ssh_port,
            path_local=PATH_MEASUREMENTS_BACKUP,
            user=self.remote_user,
            host=self.remote_host,
            remote_path=self.remote_backup_path)
        self.logger.debug("rsync command: {}".format(rsync_cmd))
        cmd_out, cmd_err, cmd_status = cmd_output(rsync_cmd,
                                                  timeout=self.rsync_timeout,
                                                  user=self.local_user)
        self.logger.debug(
            "rsync returned:\nOut: {}\nError: {}\nStatus: {}".format(
                cmd_out.decode(), cmd_err.decode(), cmd_status))
Exemple #22
0
def action_command(cond_action, message):
    # Replace string variables with actual values
    command_str = cond_action.do_action_string
    user = cond_action.do_output_state

    # TODO: Maybe get this working again with the new measurement system
    # # Replace measurement variables
    # if last_measurement:
    #     command_str = command_str.replace(
    #         "((measure_{var}))".format(
    #             var=device_measurement), str(last_measurement))
    # if device and device.period:
    #     command_str = command_str.replace(
    #         "((measure_period))", str(device.period))
    # if input_dev:
    #     command_str = command_str.replace(
    #         "((measure_location))", str(input_dev.location))
    # if input_dev and device_measurement == input_dev.measurements:
    #     command_str = command_str.replace(
    #         "((measure_linux_command))", str(last_measurement))
    #
    # # Replace output variables
    # if output:
    #     if output.pin:
    #         command_str = command_str.replace(
    #             "((output_pin))", str(output.pin))
    #     if output_state:
    #         command_str = command_str.replace(
    #             "((output_action))", str(output_state))
    #     if on_duration:
    #         command_str = command_str.replace(
    #             "((output_duration))", str(on_duration))
    #     if duty_cycle:
    #         command_str = command_str.replace(
    #             "((output_pwm))", str(duty_cycle))
    #
    # # Replace edge variables
    # if edge:
    #     command_str = command_str.replace(
    #         "((edge_state))", str(edge))

    message += " Execute '{com}' ".format(
        com=command_str)

    cmd_out, cmd_err, cmd_status = cmd_output(command_str, user=user)

    message += "(return out: {out}, err: {err}, status: {stat}).".format(
        out=cmd_out, err=cmd_err, stat=cmd_status)
    return message
def action_ir_send(cond_action, message):
    command = 'irsend SEND_ONCE {remote} {code}'.format(
        remote=cond_action.remote, code=cond_action.code)
    output, err, stat = cmd_output(command)

    # Send more than once
    if cond_action.send_times > 1:
        for _ in range(cond_action.send_times - 1):
            time.sleep(0.5)
            output, err, stat = cmd_output(command)

    message += " [{id}] Infrared Send " \
               "code '{code}', remote '{remote}', times: {times}:" \
               "\nOutput: {out}" \
               "\nError: {err}" \
               "\nStatus: {stat}'.".format(
        id=cond_action.id,
        code=cond_action.code,
        remote=cond_action.remote,
        times=cond_action.send_times,
        out=output,
        err=err,
        stat=stat)
    return message
Exemple #24
0
    def create_new_settings_backup(self, args_dict):
        filename = 'Mycodo_{mver}_Settings_{aver}_{host}_{dt}.zip'.format(
            mver=MYCODO_VERSION, aver=ALEMBIC_VERSION,
            host=socket.gethostname().replace(' ', ''),
            dt=datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S"))
        self.create_settings_backup(filename)

        rsync_cmd = "rsync -avz -e ssh {path_local} {user}@{host}:{remote_path}".format(
            path_local=PATH_SETTINGS_BACKUP,
            user=self.remote_user,
            host=self.remote_host,
            remote_path=self.remote_backup_path
        )
        self.logger.debug("rsync command: {}".format(rsync_cmd))
        cmd_out, cmd_err, cmd_status = cmd_output(
            rsync_cmd, timeout=self.rsync_timeout, user=self.local_user)
        self.logger.debug("rsync returned:\nOut: {}\nError: {}\nStatus: {}".format(
            cmd_out.decode(), cmd_err.decode(), cmd_status))
Exemple #25
0
def get_raspi_config_settings():
    settings = {}
    i2c_status, _, _ = cmd_output("raspi-config nonint get_i2c")
    settings['i2c_enabled'] = not bool(int(i2c_status))
    ssh_status, _, _ = cmd_output("raspi-config nonint get_ssh")
    settings['ssh_enabled'] = not bool(int(ssh_status))
    cam_status, _, _ = cmd_output("raspi-config nonint get_camera")
    settings['pi_camera_enabled'] = not bool(int(cam_status))
    one_wire_status, _, _ = cmd_output("raspi-config nonint get_onewire")
    settings['one_wire_enabled'] = not bool(int(one_wire_status))
    serial_status, _, _ = cmd_output("raspi-config nonint get_serial")
    settings['serial_enabled'] = not bool(int(serial_status))
    spi_status, _, _ = cmd_output("raspi-config nonint get_spi")
    settings['spi_enabled'] = not bool(int(spi_status))
    hostname_out, _, _ = cmd_output("raspi-config nonint get_hostname")
    settings['hostname'] = hostname_out.decode("utf-8")
    return settings
Exemple #26
0
def get_raspi_config_settings():
    settings = {}
    i2c_status, _, _ = cmd_output("raspi-config nonint get_i2c")
    settings['i2c_enabled'] = not bool(int(i2c_status))
    ssh_status, _, _ = cmd_output("raspi-config nonint get_ssh")
    settings['ssh_enabled'] = not bool(int(ssh_status))
    cam_status, _, _ = cmd_output("raspi-config nonint get_camera")
    settings['pi_camera_enabled'] = not bool(int(cam_status))
    one_wire_status, _, _ = cmd_output("raspi-config nonint get_onewire")
    settings['one_wire_enabled'] = not bool(int(one_wire_status))
    serial_status, _, _ = cmd_output("raspi-config nonint get_serial")
    settings['serial_enabled'] = not bool(int(serial_status))
    spi_status, _, _ = cmd_output("raspi-config nonint get_spi")
    settings['spi_enabled'] = not bool(int(spi_status))
    hostname_out, _, _ = cmd_output("raspi-config nonint get_hostname")
    settings['hostname'] = hostname_out.decode("utf-8")
    return settings
Exemple #27
0
    def run_action(self, message, dict_vars):
        try:
            command = dict_vars["value"]["command"]
        except:
            command = self.command

        try:
            user = dict_vars["value"]["user"]
        except:
            user = self.user

        message += f" Execute '{command}' as {user}."

        cmd_out, cmd_err, cmd_status = cmd_output(command, user=user)

        message += f" return out: {cmd_out}, err: {cmd_err}, status: {cmd_status}."

        self.logger.debug(f"Message: {message}")

        return message
Exemple #28
0
    def backup_camera(self):
        if self.backup_remove_camera_images:
            remove_files = "--remove-source-files "
        else:
            remove_files = ""
        rsync_cmd = "rsync {rem}-avz -e 'ssh -p {port}' {path_local} {user}@{host}:{remote_path}".format(
            rem=remove_files,
            port=self.ssh_port,
            path_local=PATH_CAMERAS,
            user=self.remote_user,
            host=self.remote_host,
            remote_path=self.remote_backup_path)

        self.logger.debug("rsync command: {}".format(rsync_cmd))
        cmd_out, cmd_err, cmd_status = cmd_output(rsync_cmd,
                                                  timeout=self.rsync_timeout,
                                                  user=self.local_user)
        self.logger.debug(
            "rsync returned:\nOut: {}\nError: {}\nStatus: {}".format(
                cmd_out.decode(), cmd_err.decode(), cmd_status))
Exemple #29
0
    def loop(self):
        if self.timer_loop > time.time():
            return

        while self.timer_loop < time.time():
            self.timer_loop += self.period

        if not self.is_setup:
            self.logger.error("Cannot run: Not all options are set")
            return

        if self.backup_settings:
            filename = 'Mycodo_{mver}_Settings_{aver}_{host}.zip'.format(
                    mver=MYCODO_VERSION, aver=ALEMBIC_VERSION,
                    host=socket.gethostname().replace(' ', ''))
            self.create_settings_backup(filename)

            rsync_cmd = "rsync -avz -e ssh {path_local} {user}@{host}:{remote_path}".format(
                path_local=PATH_SETTINGS_BACKUP,
                user=self.remote_user,
                host=self.remote_host,
                remote_path=self.remote_backup_path
            )
            self.logger.debug("rsync command: {}".format(rsync_cmd))
            cmd_out, cmd_err, cmd_status = cmd_output(
                rsync_cmd, timeout=self.rsync_timeout, user=self.local_user)
            self.logger.debug("rsync returned:\nOut: {}\nError: {}\nStatus: {}".format(
                cmd_out.decode(), cmd_err.decode(), cmd_status))

        if self.backup_cameras:
            rsync_cmd = "rsync -avz -e ssh {path_local} {user}@{host}:{remote_path}".format(
                path_local=PATH_CAMERAS,
                user=self.remote_user,
                host=self.remote_host,
                remote_path=self.remote_backup_path
            )
            self.logger.debug("rsync command: {}".format(rsync_cmd))
            cmd_out, cmd_err, cmd_status = cmd_output\
                (rsync_cmd, timeout=self.rsync_timeout, user=self.local_user)
            self.logger.debug("rsync returned:\nOut: {}\nError: {}\nStatus: {}".format(
                cmd_out.decode(), cmd_err.decode(), cmd_status))
Exemple #30
0
    def get_measurement(self):
        """ Determine if the return value of the command is a number """
        self.return_dict = measurements_dict.copy()

        out, _, _ = cmd_output(self.command)

        if str_is_float(out):
            list_measurements = [float(out)]
        else:
            self.logger.error("The command returned a non-numerical value. "
                              "Ensure only one numerical value is returned "
                              "by the command.")
            return

        for channel, meas in enumerate(self.device_measurements.all()):
            if meas.is_enabled:
                self.return_dict[channel]['unit'] = meas.unit
                self.return_dict[channel]['measurement'] = meas.measurement
                self.return_dict[channel]['value'] = list_measurements[channel]

        return self.return_dict
Exemple #31
0
    def get_measurement(self):
        """ Determine if the return value of the command is a number """
        return_dict = measurements_dict.copy()

        out, _, _ = cmd_output(self.command)

        if str_is_float(out):
            list_measurements = [float(out)]
        else:
            self.logger.error(
                "The command returned a non-numerical value. "
                "Ensure only one numerical value is returned "
                "by the command.")
            return

        for channel, meas in enumerate(self.device_measurements.all()):
            if meas.is_enabled:
                return_dict[channel]['unit'] = meas.unit
                return_dict[channel]['measurement'] = meas.measurement
                return_dict[channel]['value'] = list_measurements[channel]

        return return_dict
Exemple #32
0
def create_measurements_export(save_path=None):
    try:
        data = io.BytesIO()
        influx_backup_dir = os.path.join(INSTALL_DIRECTORY, 'influx_backup')

        # Delete influxdb directory if it exists
        if os.path.isdir(influx_backup_dir):
            shutil.rmtree(influx_backup_dir)

        # Create new directory (make sure it's empty)
        assure_path_exists(influx_backup_dir)

        cmd = "/usr/bin/influxd backup -database {db} -portable {path}".format(
            db=INFLUXDB_DATABASE, path=influx_backup_dir)
        _, _, status = cmd_output(cmd)

        if not status:
            # Zip all files in the influx_backup directory
            with zipfile.ZipFile(data, mode='w') as z:
                for _, _, files in os.walk(influx_backup_dir):
                    for filename in files:
                        z.write(os.path.join(influx_backup_dir, filename),
                                filename)
            data.seek(0)

            # Delete influxdb directory if it exists
            if os.path.isdir(influx_backup_dir):
                shutil.rmtree(influx_backup_dir)

            if save_path:
                with open(save_path, "wb") as f:
                    f.write(data.getbuffer())
                set_user_grp(save_path, 'mycodo', 'mycodo')
                return 0, save_path
            else:
                return 0, data
    except Exception as err:
        logger.error("Error: {}".format(err))
        return 1, err
Exemple #33
0
    def backup_settings(self):
        filename = 'Mycodo_{mver}_Settings_{aver}_{host}_{dt}.zip'.format(
            mver=MYCODO_VERSION,
            aver=ALEMBIC_VERSION,
            host=socket.gethostname().replace(' ', ''),
            dt=datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S"))
        path_save = os.path.join(PATH_SETTINGS_BACKUP, filename)
        assure_path_exists(PATH_SETTINGS_BACKUP)
        if os.path.exists(path_save):
            self.logger.debug("Skipping backup of settings: "
                              "File already exists: {}".format(path_save))
        else:
            status, saved_path = create_settings_export(save_path=path_save)
            if not status:
                self.logger.debug("Saved settings file: "
                                  "{}".format(saved_path))
            else:
                self.logger.debug("Could not create settings file: "
                                  "{}".format(saved_path))

        if self.backup_remove_settings_archives:
            remove_files = "--remove-source-files "
        else:
            remove_files = ""
        rsync_cmd = "rsync {rem}-avz -e 'ssh -p {port}' {path_local} {user}@{host}:{remote_path}".format(
            rem=remove_files,
            port=self.ssh_port,
            path_local=PATH_SETTINGS_BACKUP,
            user=self.remote_user,
            host=self.remote_host,
            remote_path=self.remote_backup_path)
        self.logger.debug("rsync command: {}".format(rsync_cmd))
        cmd_out, cmd_err, cmd_status = cmd_output(rsync_cmd,
                                                  timeout=self.rsync_timeout,
                                                  user=self.local_user)
        self.logger.debug(
            "rsync returned:\nOut: {}\nError: {}\nStatus: {}".format(
                cmd_out.decode(), cmd_err.decode(), cmd_status))
                        elif install_type == 'apt':
                            cmd = 'dpkg -l {}'.format(package)
                            _, _, stat = cmd_output(cmd)
                            if not stat and entry not in met_deps:
                                met_deps.append(entry)

    return met_deps


if __name__ == "__main__":
    installed_deps = get_installed_dependencies()
    for each_dep in installed_deps:
        if each_dep.split(' ')[0] == 'apt':
            update_cmd = '{home}/mycodo/scripts/dependencies.sh {dep}'.format(
                home=INSTALL_DIRECTORY, dep=each_dep)
            output, err, stat = cmd_output(update_cmd)
            print("{}".format(output))

    tmp_req_file = '{home}/install/requirements-generated.txt'.format(home=INSTALL_DIRECTORY)
    with open(tmp_req_file, "w") as f:
        for each_dep in installed_deps:
            if each_dep.split(' ')[0] == 'pip-pypi':
                f.write('{dep}\n'.format(dep=each_dep.split(' ')[1]))
            elif each_dep.split(' ')[0] == 'pip-git':
                f.write('-e {dep}\n'.format(dep=each_dep.split(' ')[1]))

    pip_req_update = '{home}/env/bin/pip install --upgrade -r {home}/install/requirements-generated.txt'.format(home=INSTALL_DIRECTORY)
    output, err, stat = cmd_output(pip_req_update)
    print("{}".format(output))
    os.remove(tmp_req_file)
Exemple #35
0
def import_influxdb(form):
    """
    Receive a zip file contatining influx metastore and database that was
    exported with export_influxdb(), then import the metastore and database
    in InfluxDB.
    """
    action = '{action} {controller}'.format(
        action=TRANSLATIONS['import']['title'],
        controller="Influxdb")
    error = []

    try:
        correct_format = 'Mycodo_MYCODOVERSION_Influxdb_INFLUXVERSION_HOST_DATETIME.zip'
        upload_folder = os.path.join(INSTALL_DIRECTORY, 'upload')
        tmp_folder = os.path.join(upload_folder, 'mycodo_influx_tmp')
        full_path = None

        if not form.influxdb_import_file.data:
            error.append('No file present')
        elif form.influxdb_import_file.data.filename == '':
            error.append('No file name')
        else:
            # Split the uploaded file into parts
            file_name = form.influxdb_import_file.data.filename
            name = file_name.rsplit('.', 1)[0]
            extension = file_name.rsplit('.', 1)[1].lower()
            name_split = name.split('_')

            # Split the correctly-formatted filename into parts
            correct_name = correct_format.rsplit('.', 1)[0]
            correct_name_1 = correct_name.split('_')[0]
            correct_name_2 = correct_name.split('_')[2]
            correct_extension = correct_format.rsplit('.', 1)[1].lower()

            # Compare the uploaded filename parts to the correct parts
            try:
                if name_split[0] != correct_name_1:
                    error.append(
                        "Invalid file name: {n}: {fn} != {cn}.".format(
                            n=file_name,
                            fn=name_split[0],
                            cn=correct_name_1))
                    error.append("Correct format is: {fmt}".format(
                        fmt=correct_format))
                elif name_split[2] != correct_name_2:
                    error.append(
                        "Invalid file name: {n}: {fn} != {cn}".format(
                            n=file_name,
                            fn=name_split[2],
                            cn=correct_name_2))
                    error.append("Correct format is: {fmt}".format(
                        fmt=correct_format))
                elif extension != correct_extension:
                    error.append("Extension not 'zip'")
            except Exception as err:
                error.append(
                    "Exception while verifying file name: "
                    "{err}".format(err=err))

        if not error:
            # Save file to upload directory
            filename = secure_filename(
                form.influxdb_import_file.data.filename)
            full_path = os.path.join(tmp_folder, filename)
            assure_path_exists(tmp_folder)
            assure_path_exists(tmp_folder)
            form.influxdb_import_file.data.save(
                os.path.join(tmp_folder, filename))

            # Check if contents of zip file are correct
            try:
                file_list = zipfile.ZipFile(full_path, 'r').namelist()
                if not any("meta." in s for s in file_list):
                    error.append(
                        "Metastore not found: No 'meta.*' files found "
                        "in archive")
                elif not any("mycodo_db.autogen." in s for s in file_list):
                    error.append(
                        "Databases not found: No 'mycodo_db.autogen.*' "
                        "files found in archive")
            except Exception as err:
                error.append("Exception while opening zip file: "
                             "{err}".format(err=err))

        if not error:
            # Unzip file
            try:
                zip_ref = zipfile.ZipFile(full_path, 'r')
                zip_ref.extractall(tmp_folder)
                zip_ref.close()
            except Exception as err:
                error.append("Exception while extracting zip file: "
                             "{err}".format(err=err))

        if not error:
            try:
                # Stop influxdb and Mycodo daemon (backend) from
                # running (influxdb must be stopped to restore database)
                cmd = "{pth}/mycodo/scripts/mycodo_wrapper " \
                      "daemon_stop".format(
                    pth=INSTALL_DIRECTORY)
                out, _, _ = cmd_output(cmd)

                cmd = "{pth}/mycodo/scripts/mycodo_wrapper " \
                      "influxdb_stop".format(
                    pth=INSTALL_DIRECTORY)
                out, _, _ = cmd_output(cmd)

                # Import the mestastore and database
                output_successes = []
                cmd = "{pth}/mycodo/scripts/mycodo_wrapper " \
                      "influxdb_restore_metastore {dir}".format(
                    pth=INSTALL_DIRECTORY, dir=tmp_folder)
                out, _, _ = cmd_output(cmd)
                if out:
                    output_successes.append(out.decode('utf-8'))

                cmd = "{pth}/mycodo/scripts/mycodo_wrapper " \
                      "influxdb_restore_database {dir}".format(
                    pth=INSTALL_DIRECTORY, dir=tmp_folder)
                out, _, _ = cmd_output(cmd)
                if out:
                    output_successes.append(out.decode('utf-8'))

                # Start influxdb and Mycodo daemon (backend)
                cmd = "{pth}/mycodo/scripts/mycodo_wrapper " \
                      "influxdb_start".format(
                    pth=INSTALL_DIRECTORY)
                out, _, _ = cmd_output(cmd)

                time.sleep(2)

                cmd = "{pth}/mycodo/scripts/mycodo_wrapper " \
                      "daemon_start".format(
                    pth=INSTALL_DIRECTORY)
                out, _, _ = cmd_output(cmd)

                # Delete tmp directory if it exists
                if os.path.isdir(tmp_folder):
                    shutil.rmtree(tmp_folder)

                if all(output_successes):  # Success!
                    output_successes.append(
                        "InfluxDB metastore and database successfully "
                        "imported")
                    return output_successes
            except Exception as err:
                error.append(
                    "Exception while importing metastore and database: "
                    "{err}".format(err=err))

    except Exception as err:
        error.append("Exception: {}".format(err))

    flash_success_errors(error, action, url_for('routes_page.page_export'))
Exemple #36
0
def import_settings(form):
    """
    Receive a zip file containing a Mycodo settings database that was
    exported with export_settings(), then back up the current Mycodo settings
    database and implement the one form the zip in its's place.
    """
    action = '{action} {controller}'.format(
        action=TRANSLATIONS['import']['title'],
        controller=TRANSLATIONS['settings']['title'])
    error = []

    try:
        correct_format = 'Mycodo_MYCODOVERSION_Settings_DBVERSION_HOST_DATETIME.zip'
        upload_folder = os.path.join(INSTALL_DIRECTORY, 'upload')
        tmp_folder = os.path.join(upload_folder, 'mycodo_db_tmp')
        mycodo_database_name = 'mycodo.db'
        full_path = None

        if not form.settings_import_file.data:
            error.append('No file present')
        elif form.settings_import_file.data.filename == '':
            error.append('No file name')
        else:
            # Split the uploaded file into parts
            file_name = form.settings_import_file.data.filename
            name = file_name.rsplit('.', 1)[0]
            extension = file_name.rsplit('.', 1)[1].lower()
            name_split = name.split('_')

            # Split the correctly-formatted filename into parts
            correct_name = correct_format.rsplit('.', 1)[0]
            correct_name_1 = correct_name.split('_')[0]
            correct_name_2 = correct_name.split('_')[2]
            correct_extension = correct_format.rsplit('.', 1)[1].lower()

            # Compare the uploaded filename parts to the correct parts
            try:
                if name_split[0] != correct_name_1:
                    error.append(
                        "Invalid file name: {n}: {fn} != {cn}.".format(
                            n=file_name,
                            fn=name_split[0],
                            cn=correct_name_1))
                    error.append("Correct format is: {fmt}".format(
                        fmt=correct_format))
                elif name_split[2] != correct_name_2:
                    error.append(
                        "Invalid file name: {n}: {fn} != {cn}".format(
                            n=file_name,
                            fn=name_split[2],
                            cn=correct_name_2))
                    error.append("Correct format is: {fmt}".format(
                        fmt=correct_format))
                elif extension != correct_extension:
                    error.append("Extension not 'zip'")
                elif name_split[1] != MYCODO_VERSION:
                    error.append("Invalid Mycodo version: {fv} != {mv}. "
                                 "This database can only be imported to "
                                 "Mycodo version {mver}".format(
                        fv=name_split[1],
                        mv=MYCODO_VERSION,
                        mver=name_split[1]))
                elif name_split[3] != ALEMBIC_VERSION:
                    error.append("Invalid database version: {fv} != {dv}."
                                 " This database can only be imported to"
                                 " Mycodo version {mver}".format(
                        fv=name_split[3],
                        dv=ALEMBIC_VERSION,
                        mver=name_split[1]))
            except Exception as err:
                error.append(
                    "Exception while verifying file name: {err}".format(err=err))

        if not error:
            # Save file to upload directory
            filename = secure_filename(
                form.settings_import_file.data.filename)
            full_path = os.path.join(tmp_folder, filename)
            assure_path_exists(upload_folder)
            assure_path_exists(tmp_folder)
            form.settings_import_file.data.save(
                os.path.join(tmp_folder, filename))

            # Check if contents of zip file are correct
            try:
                file_list = zipfile.ZipFile(full_path, 'r').namelist()
                if len(file_list) > 1:
                    error.append("Incorrect number of files in zip: "
                                 "{an} != 1".format(an=len(file_list)))
                elif file_list[0] != mycodo_database_name:
                    error.append("Incorrect file in zip: {af} != {cf}".format(
                        af=file_list[0], cf=mycodo_database_name))
            except Exception as err:
                error.append("Exception while opening zip file: "
                             "{err}".format(err=err))

        if not error:
            # Unzip file
            try:
                zip_ref = zipfile.ZipFile(full_path, 'r')
                zip_ref.extractall(tmp_folder)
                zip_ref.close()
            except Exception as err:
                error.append("Exception while extracting zip file: "
                             "{err}".format(err=err))

        if not error:
            try:
                # Stop Mycodo daemon (backend)
                cmd = "{pth}/mycodo/scripts/mycodo_wrapper " \
                      "daemon_stop".format(
                        pth=INSTALL_DIRECTORY)
                _, _, _ = cmd_output(cmd)

                # Backup current database and replace with extracted mycodo.db
                imported_database = os.path.join(
                    tmp_folder, mycodo_database_name)
                backup_name = (
                        SQL_DATABASE_MYCODO + '.backup_' +
                        datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S"))
                os.rename(SQL_DATABASE_MYCODO, backup_name)
                os.rename(imported_database, SQL_DATABASE_MYCODO)

                # Start Mycodo daemon (backend)
                cmd = "{pth}/mycodo/scripts/mycodo_wrapper " \
                      "daemon_start".format(
                        pth=INSTALL_DIRECTORY)
                _, _, _ = cmd_output(cmd)

                # Delete tmp directory if it exists
                if os.path.isdir(tmp_folder):
                    shutil.rmtree(tmp_folder)

                return backup_name  # Success!
            except Exception as err:
                error.append("Exception while replacing database: "
                             "{err}".format(err=err))

    except Exception as err:
        error.append("Exception: {}".format(err))

    flash_success_errors(error, action, url_for('routes_page.page_export'))
Exemple #37
0
def camera_record(record_type, unique_id, duration_sec=None, tmp_filename=None):
    """
    Record still image from cameras
    :param record_type:
    :param unique_id:
    :param duration_sec:
    :param tmp_filename:
    :return:
    """
    daemon_control = None
    settings = db_retrieve_table_daemon(Camera, unique_id=unique_id)
    timestamp = datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
    assure_path_exists(PATH_CAMERAS)
    camera_path = assure_path_exists(
        os.path.join(PATH_CAMERAS, '{uid}'.format(uid=settings.unique_id)))
    if record_type == 'photo':
        if settings.path_still != '':
            save_path = settings.path_still
        else:
            save_path = assure_path_exists(os.path.join(camera_path, 'still'))
        filename = 'Still-{cam_id}-{cam}-{ts}.jpg'.format(
            cam_id=settings.id,
            cam=settings.name,
            ts=timestamp).replace(" ", "_")
    elif record_type == 'timelapse':
        if settings.path_timelapse != '':
            save_path = settings.path_timelapse
        else:
            save_path = assure_path_exists(os.path.join(camera_path, 'timelapse'))
        start = datetime.datetime.fromtimestamp(
            settings.timelapse_start_time).strftime("%Y-%m-%d_%H-%M-%S")
        filename = 'Timelapse-{cam_id}-{cam}-{st}-img-{cn:05d}.jpg'.format(
            cam_id=settings.id,
            cam=settings.name,
            st=start,
            cn=settings.timelapse_capture_number).replace(" ", "_")
    elif record_type == 'video':
        if settings.path_video != '':
            save_path = settings.path_video
        else:
            save_path = assure_path_exists(os.path.join(camera_path, 'video'))
        filename = 'Video-{cam}-{ts}.h264'.format(
            cam=settings.name,
            ts=timestamp).replace(" ", "_")
    else:
        return

    assure_path_exists(save_path)

    if tmp_filename:
        filename = tmp_filename

    path_file = os.path.join(save_path, filename)

    # Turn on output, if configured
    if settings.output_id:
        daemon_control = DaemonControl()
        daemon_control.output_on(settings.output_id)

    # Pause while the output remains on for the specified duration.
    # Used for instance to allow fluorescent lights to fully turn on before
    # capturing an image.
    if settings.output_duration:
        time.sleep(settings.output_duration)

    if settings.library == 'picamera':
        # Try 5 times to access the pi camera (in case another process is accessing it)
        for _ in range(5):
            try:
                with picamera.PiCamera() as camera:
                    camera.resolution = (settings.width, settings.height)
                    camera.hflip = settings.hflip
                    camera.vflip = settings.vflip
                    camera.rotation = settings.rotation
                    camera.brightness = int(settings.brightness)
                    camera.contrast = int(settings.contrast)
                    camera.exposure_compensation = int(settings.exposure)
                    camera.saturation = int(settings.saturation)
                    camera.start_preview()
                    time.sleep(2)  # Camera warm-up time

                    if record_type in ['photo', 'timelapse']:
                        camera.capture(path_file, use_video_port=False)
                    elif record_type == 'video':
                        camera.start_recording(path_file, format='h264', quality=20)
                        camera.wait_recording(duration_sec)
                        camera.stop_recording()
                    else:
                        return
                    break
            except picamera.exc.PiCameraMMALError:
                logger.error("The camera is already open by picamera. Retrying 4 times.")
            time.sleep(1)

    elif settings.library == 'fswebcam':
        cmd = "/usr/bin/fswebcam --device {dev} --resolution {w}x{h} --set brightness={bt}% " \
              "--no-banner --save {file}".format(dev=settings.device,
                                                 w=settings.width,
                                                 h=settings.height,
                                                 bt=settings.brightness,
                                                 file=path_file)
        if settings.hflip:
            cmd += " --flip h"
        if settings.vflip:
            cmd += " --flip h"
        if settings.rotation:
            cmd += " --rotate {angle}".format(angle=settings.rotation)
        if settings.custom_options:
            cmd += " " + settings.custom_options

        out, err, status = cmd_output(cmd, stdout_pipe=False)
        # logger.error("TEST01: {}; {}; {}; {}".format(cmd, out, err, status))

    # Turn off output, if configured
    if settings.output_id and daemon_control:
        daemon_control.output_off(settings.output_id)

    try:
        set_user_grp(path_file, 'mycodo', 'mycodo')
        return save_path, filename
    except Exception as e:
        logger.exception(
            "Exception raised in 'camera_record' when setting user grp: "
            "{err}".format(err=e))
Exemple #38
0
    def get_measurement(self, display_id, i):
        try:
            if self.lcd_line[display_id][i]['measure'] == 'BLANK':
                self.lcd_line[display_id][i]['name'] = ''
                self.lcd_line[display_id][i]['unit'] = ''
                self.lcd_line[display_id][i]['measure_val'] = ''
                return True
            elif self.lcd_line[display_id][i]['measure'] == 'IP':
                str_ip_cmd = "ip addr | grep 'state UP' -A2 | tail -n1 | awk '{print $2}' | cut -f1  -d'/'"
                ip_out, _, _ = cmd_output(str_ip_cmd)
                self.lcd_line[display_id][i]['name'] = ''
                self.lcd_line[display_id][i]['unit'] = ''
                self.lcd_line[display_id][i]['measure_val'] = ip_out.rstrip().decode("utf-8")
                return True
            elif self.lcd_line[display_id][i]['measure'] == 'output_state':
                self.lcd_line[display_id][i]['measure_val'] = self.output_state(
                    self.lcd_line[display_id][i]['id'])
                return True
            else:
                if self.lcd_line[display_id][i]['measure'] == 'time':
                    last_measurement = read_last_influxdb(
                        self.lcd_line[display_id][i]['id'],
                        '/.*/',
                        None,
                        None,
                        duration_sec=self.lcd_max_age[display_id][i])
                else:
                    last_measurement = read_last_influxdb(
                        self.lcd_line[display_id][i]['id'],
                        self.lcd_line[display_id][i]['unit'],
                        self.lcd_line[display_id][i]['measure'],
                        self.lcd_line[display_id][i]['channel'],
                        duration_sec=self.lcd_max_age[display_id][i])

                if last_measurement:
                    self.lcd_line[display_id][i]['time'] = last_measurement[0]
                    if self.lcd_decimal_places[display_id][i] == 0:
                        self.lcd_line[display_id][i]['measure_val'] = int(last_measurement[1])
                    else:
                        self.lcd_line[display_id][i]['measure_val'] = round(
                            last_measurement[1], self.lcd_decimal_places[display_id][i])
                    utc_dt = datetime.datetime.strptime(
                        self.lcd_line[display_id][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("Latest {}: {} @ {}".format(
                        self.lcd_line[display_id][i]['measure'],
                        self.lcd_line[display_id][i]['measure_val'], local_timestamp))
                    return True

                else:
                    self.lcd_line[display_id][i]['time'] = None
                    self.lcd_line[display_id][i]['measure_val'] = None
                    self.logger.debug("No data returned from influxdb")
            return False
        except Exception as except_msg:
            self.logger.debug(
                "Failed to read measurement from the influxdb database: "
                "{err}".format(err=except_msg))
            return False
def trigger_action(
        cond_action_id,
        message='',
        note_tags=None,
        email_recipients=None,
        attachment_file=None,
        attachment_type=None,
        single_action=False):
    """
    Trigger individual action

    If single_action == False, message, note_tags, email_recipients,
    attachment_file, and attachment_type are returned and may be
    passed back to this function in order to append to those lists.

    :param cond_action_id: unique_id of action
    :param message: message string to append to that will be sent back
    :param note_tags: list of note tags to use if creating a note
    :param email_recipients: list of email addresses to notify if sending an email
    :param attachment_file: string location of a file to attach to an email
    :param attachment_type: string type of email attachment
    :param single_action: True if only one action is being triggered, False if only one of multiple actions
    :return: message or (message, note_tags, email_recipients, attachment_file, attachment_type)
    """
    cond_action = db_retrieve_table_daemon(Actions, unique_id=cond_action_id)
    if not cond_action:
        message += 'Error: Action with ID {} not found!'.format(cond_action_id)
        if single_action:
            return message
        else:
            return message, note_tags, email_recipients, attachment_file, attachment_type

    logger_actions = logging.getLogger("mycodo.trigger_action_{id}".format(
        id=cond_action.unique_id.split('-')[0]))

    message += "\n[Action {id}]:".format(
        id=cond_action.unique_id.split('-')[0], action_type=cond_action.action_type)

    try:
        control = DaemonControl()

        smtp_table = db_retrieve_table_daemon(
            SMTP, entry='first')
        smtp_max_count = smtp_table.hourly_max
        smtp_wait_timer = smtp_table.smtp_wait_timer
        email_count = smtp_table.email_count

        # Pause
        if cond_action.action_type == 'pause_actions':
            message += " [{id}] Pause actions for {sec} seconds.".format(
                id=cond_action.id,
                sec=cond_action.pause_duration)

            time.sleep(cond_action.pause_duration)

        # Actuate output (duration)
        if (cond_action.action_type == 'output' and
                cond_action.do_unique_id and
                cond_action.do_output_state in ['on', 'off']):
            this_output = db_retrieve_table_daemon(
                Output, unique_id=cond_action.do_unique_id, entry='first')
            message += " Turn output {unique_id} ({id}, {name}) {state}".format(
                unique_id=cond_action.do_unique_id,
                id=this_output.id,
                name=this_output.name,
                state=cond_action.do_output_state)
            if (cond_action.do_output_state == 'on' and
                    cond_action.do_output_duration):
                message += " for {sec} seconds".format(
                    sec=cond_action.do_output_duration)
            message += "."

            output_on_off = threading.Thread(
                target=control.output_on_off,
                args=(cond_action.do_unique_id,
                      cond_action.do_output_state,),
                kwargs={'duration': cond_action.do_output_duration})
            output_on_off.start()

        # Actuate output (PWM)
        if (cond_action.action_type == 'output_pwm' and
                cond_action.do_unique_id and
                0 <= cond_action.do_output_pwm <= 100):
            this_output = db_retrieve_table_daemon(
                Output, unique_id=cond_action.do_unique_id, entry='first')
            message += " Turn output {unique_id} ({id}, {name}) duty cycle to {duty_cycle}%.".format(
                unique_id=cond_action.do_unique_id,
                id=this_output.id,
                name=this_output.name,
                duty_cycle=cond_action.do_output_pwm)

            output_on = threading.Thread(
                target=control.output_on,
                args=(cond_action.do_unique_id,),
                kwargs={'duty_cycle': cond_action.do_output_pwm})
            output_on.start()

        # Execute command in shell
        if cond_action.action_type == 'command':

            # Replace string variables with actual values
            command_str = cond_action.do_action_string

            # TODO: Maybe get this working again with the new measurement system
            # # Replace measurement variables
            # if last_measurement:
            #     command_str = command_str.replace(
            #         "((measure_{var}))".format(
            #             var=device_measurement), str(last_measurement))
            # if device and device.period:
            #     command_str = command_str.replace(
            #         "((measure_period))", str(device.period))
            # if input_dev:
            #     command_str = command_str.replace(
            #         "((measure_location))", str(input_dev.location))
            # if input_dev and device_measurement == input_dev.measurements:
            #     command_str = command_str.replace(
            #         "((measure_linux_command))", str(last_measurement))
            #
            # # Replace output variables
            # if output:
            #     if output.pin:
            #         command_str = command_str.replace(
            #             "((output_pin))", str(output.pin))
            #     if output_state:
            #         command_str = command_str.replace(
            #             "((output_action))", str(output_state))
            #     if on_duration:
            #         command_str = command_str.replace(
            #             "((output_duration))", str(on_duration))
            #     if duty_cycle:
            #         command_str = command_str.replace(
            #             "((output_pwm))", str(duty_cycle))
            #
            # # Replace edge variables
            # if edge:
            #     command_str = command_str.replace(
            #         "((edge_state))", str(edge))

            message += " Execute '{com}' ".format(
                com=command_str)

            _, _, cmd_status = cmd_output(command_str)

            message += "(return status: {stat}).".format(stat=cmd_status)

        # Create Note
        if cond_action.action_type == 'create_note':
            tag_name = db_retrieve_table_daemon(
                NoteTags, unique_id=cond_action.do_action_string).name

            message += " Create note with tag '{}'.".format(tag_name)
            if single_action and cond_action.do_action_string:
                    list_tags = []
                    check_tag = db_retrieve_table_daemon(
                        NoteTags, unique_id=cond_action.do_action_string)
                    if check_tag:
                        list_tags.append(cond_action.do_action_string)

                    if list_tags:
                        with session_scope(MYCODO_DB_PATH) as db_session:
                            new_note = Notes()
                            new_note.name = 'Action'
                            new_note.tags = ','.join(list_tags)
                            new_note.note = message
                            db_session.add(new_note)
            else:
                note_tags.append(cond_action.do_action_string)

        # Capture photo
        # If emailing later, store location of photo as attachment_file
        if cond_action.action_type in ['photo', 'photo_email']:
            this_camera = db_retrieve_table_daemon(
                Camera, unique_id=cond_action.do_unique_id, entry='first')
            message += "  Capturing photo with camera {unique_id} ({id}, {name}).".format(
                unique_id=cond_action.do_unique_id,
                id=this_camera.id,
                name=this_camera.name)
            camera_still = db_retrieve_table_daemon(
                Camera, unique_id=cond_action.do_unique_id)
            attachment_path_file = camera_record('photo', camera_still.unique_id)
            attachment_file = os.path.join(attachment_path_file[0], attachment_path_file[1])

        # Capture video
        if cond_action.action_type in ['video', 'video_email']:
            this_camera = db_retrieve_table_daemon(
                Camera, unique_id=cond_action.do_unique_id, entry='first')
            message += "  Capturing video with camera {unique_id} ({id}, {name}).".format(
                unique_id=cond_action.do_unique_id,
                id=this_camera.id,
                name=this_camera.name)
            camera_stream = db_retrieve_table_daemon(
                Camera, unique_id=cond_action.do_unique_id)
            attachment_path_file = camera_record(
                'video', camera_stream.unique_id,
                duration_sec=cond_action.do_camera_duration)
            attachment_file = os.path.join(attachment_path_file[0], attachment_path_file[1])

        # Activate Controller
        if cond_action.action_type == 'activate_controller':
            (controller_type,
             controller_object,
             controller_entry) = which_controller(
                cond_action.do_unique_id)
            message += " Activate Controller {unique_id} ({id}, {name}).".format(
                unique_id=cond_action.do_unique_id,
                id=controller_entry.id,
                name=controller_entry.name)
            if controller_entry.is_activated:
                message += " Notice: Controller is already active!"
            else:
                with session_scope(MYCODO_DB_PATH) as new_session:
                    mod_cont = new_session.query(controller_object).filter(
                        controller_object.unique_id == cond_action.do_unique_id).first()
                    mod_cont.is_activated = True
                    new_session.commit()
                activate_controller = threading.Thread(
                    target=control.controller_activate,
                    args=(controller_type,
                          cond_action.do_unique_id,))
                activate_controller.start()

        # Deactivate Controller
        if cond_action.action_type == 'deactivate_controller':
            (controller_type,
             controller_object,
             controller_entry) = which_controller(
                cond_action.do_unique_id)
            message += " Deactivate Controller {unique_id} ({id}, {name}).".format(
                unique_id=cond_action.do_unique_id,
                id=controller_entry.id,
                name=controller_entry.name)
            if not controller_entry.is_activated:
                message += " Notice: Controller is already inactive!"
            else:
                with session_scope(MYCODO_DB_PATH) as new_session:
                    mod_cont = new_session.query(controller_object).filter(
                        controller_object.unique_id == cond_action.do_unique_id).first()
                    mod_cont.is_activated = False
                    new_session.commit()
                deactivate_controller = threading.Thread(
                    target=control.controller_deactivate,
                    args=(controller_type,
                          cond_action.do_unique_id,))
                deactivate_controller.start()

        # Resume PID controller
        if cond_action.action_type == 'resume_pid':
            pid = db_retrieve_table_daemon(
                PID, unique_id=cond_action.do_unique_id, entry='first')
            message += " Resume PID {unique_id} ({id}, {name}).".format(
                unique_id=cond_action.do_unique_id,
                id=pid.id,
                name=pid.name)
            if not pid.is_paused:
                message += " Notice: PID is not paused!"
            elif pid.is_activated:
                with session_scope(MYCODO_DB_PATH) as new_session:
                    mod_pid = new_session.query(PID).filter(
                        PID.unique_id == cond_action.do_unique_id).first()
                    mod_pid.is_paused = False
                    new_session.commit()
                resume_pid = threading.Thread(
                    target=control.pid_resume,
                    args=(cond_action.do_unique_id,))
                resume_pid.start()

        # Pause PID controller
        if cond_action.action_type == 'pause_pid':
            pid = db_retrieve_table_daemon(
                PID, unique_id=cond_action.do_unique_id, entry='first')
            message += " Pause PID {unique_id} ({id}, {name}).".format(
                unique_id=cond_action.do_unique_id,
                id=pid.id,
                name=pid.name)
            if pid.is_paused:
                message += " Notice: PID is already paused!"
            elif pid.is_activated:
                with session_scope(MYCODO_DB_PATH) as new_session:
                    mod_pid = new_session.query(PID).filter(
                        PID.unique_id == cond_action.do_unique_id).first()
                    mod_pid.is_paused = True
                    new_session.commit()
                pause_pid = threading.Thread(
                    target=control.pid_pause,
                    args=(cond_action.do_unique_id,))
                pause_pid.start()

        # Set PID Setpoint
        if cond_action.action_type == 'setpoint_pid':
            pid = db_retrieve_table_daemon(
                PID, unique_id=cond_action.do_unique_id, entry='first')
            message += " Set Setpoint of PID {unique_id} ({id}, {name}).".format(
                unique_id=cond_action.do_unique_id,
                id=pid.id,
                name=pid.name)
            if pid.is_activated:
                setpoint_pid = threading.Thread(
                    target=control.pid_set,
                    args=(pid.unique_id,
                          'setpoint',
                          float(cond_action.do_action_string),))
                setpoint_pid.start()
            else:
                with session_scope(MYCODO_DB_PATH) as new_session:
                    mod_pid = new_session.query(PID).filter(
                        PID.unique_id == cond_action.do_unique_id).first()
                    mod_pid.setpoint = cond_action.do_action_string
                    new_session.commit()

        # Set PID Method and start method from beginning
        if cond_action.action_type == 'method_pid':
            pid = db_retrieve_table_daemon(
                PID, unique_id=cond_action.do_unique_id, entry='first')
            message += " Set Method of PID {unique_id} ({id}, {name}).".format(
                unique_id=cond_action.do_unique_id,
                id=pid.id,
                name=pid.name)

            # Instruct method to start
            with session_scope(MYCODO_DB_PATH) as new_session:
                mod_pid = new_session.query(PID).filter(
                    PID.unique_id == cond_action.do_unique_id).first()
                mod_pid.method_start_time = 'Ready'
                new_session.commit()

            pid = db_retrieve_table_daemon(
                PID, unique_id=cond_action.do_unique_id, entry='first')
            if pid.is_activated:
                method_pid = threading.Thread(
                    target=control.pid_set,
                    args=(pid.unique_id,
                          'method',
                          cond_action.do_action_string,))
                method_pid.start()
            else:
                with session_scope(MYCODO_DB_PATH) as new_session:
                    mod_pid = new_session.query(PID).filter(
                        PID.unique_id == cond_action.do_unique_id).first()
                    mod_pid.method_id = cond_action.do_action_string
                    new_session.commit()

        # Email the Conditional message. Optionally capture a photo or
        # video and attach to the email.
        if cond_action.action_type in [
                'email', 'photo_email', 'video_email']:

            if (email_count >= smtp_max_count and
                    time.time() < smtp_wait_timer):
                allowed_to_send_notice = False
            else:
                if time.time() > smtp_wait_timer:
                    with session_scope(MYCODO_DB_PATH) as new_session:
                        mod_smtp = new_session.query(SMTP).first()
                        mod_smtp.email_count = 0
                        mod_smtp.smtp_wait_timer = time.time() + 3600
                        new_session.commit()
                allowed_to_send_notice = True

            with session_scope(MYCODO_DB_PATH) as new_session:
                mod_smtp = new_session.query(SMTP).first()
                mod_smtp.email_count += 1
                new_session.commit()

            # If the emails per hour limit has not been exceeded
            if allowed_to_send_notice:
                message += " Notify {email}.".format(
                    email=cond_action.do_action_string)
                # attachment_type != False indicates to
                # attach a photo or video
                if cond_action.action_type == 'photo_email':
                    message += " Photo attached to email."
                    attachment_type = 'still'
                elif cond_action.action_type == 'video_email':
                    message += " Video attached to email."
                    attachment_type = 'video'

                if single_action and cond_action.do_action_string:
                    smtp = db_retrieve_table_daemon(SMTP, entry='first')
                    send_email(smtp.host, smtp.ssl, smtp.port,
                               smtp.user, smtp.passw, smtp.email_from,
                               [cond_action.do_action_string], message,
                               attachment_file, attachment_type)
                else:
                    email_recipients.append(cond_action.do_action_string)
            else:
                logger_actions.error(
                    "Wait {sec:.0f} seconds to email again.".format(
                        sec=smtp_wait_timer - time.time()))

        if cond_action.action_type == 'flash_lcd_on':
            lcd = db_retrieve_table_daemon(
                LCD, unique_id=cond_action.do_unique_id)
            message += " LCD {unique_id} ({id}, {name}) Flash On.".format(
                unique_id=cond_action.do_unique_id,
                id=lcd.id,
                name=lcd.name)

            start_flashing = threading.Thread(
                target=control.lcd_flash,
                args=(cond_action.do_unique_id, True,))
            start_flashing.start()

        if cond_action.action_type == 'flash_lcd_off':
            lcd = db_retrieve_table_daemon(
                LCD, unique_id=cond_action.do_unique_id)
            message += " LCD {unique_id} ({id}, {name}) Flash Off.".format(
                unique_id=cond_action.do_unique_id,
                id=lcd.id,
                name=lcd.name)

            start_flashing = threading.Thread(
                target=control.lcd_flash,
                args=(cond_action.do_unique_id, False,))
            start_flashing.start()

        if cond_action.action_type == 'lcd_backlight_off':
            lcd = db_retrieve_table_daemon(
                LCD, unique_id=cond_action.do_unique_id)
            message += " LCD {unique_id} ({id}, {name}) Backlight Off.".format(
                unique_id=cond_action.do_unique_id,
                id=lcd.id,
                name=lcd.name)

            start_flashing = threading.Thread(
                target=control.lcd_backlight,
                args=(cond_action.do_unique_id, False,))
            start_flashing.start()

        if cond_action.action_type == 'lcd_backlight_on':
            lcd = db_retrieve_table_daemon(
                LCD, unique_id=cond_action.do_unique_id)
            message += " LCD {unique_id} ({id}, {name}) Backlight On.".format(
                unique_id=cond_action.do_unique_id,
                id=lcd.id,
                name=lcd.name)

            start_flashing = threading.Thread(
                target=control.lcd_backlight,
                args=(cond_action.do_unique_id, True,))
            start_flashing.start()
    except Exception:
        logger_actions.exception("Error triggering action:")
        message += " Error while executing action! See Daemon log for details."

    if single_action:
        return message
    else:
        return message, note_tags, email_recipients, attachment_file, attachment_type
def conditional_mod(form):
    """Modify a Conditional"""
    error = []
    action = '{action} {controller}'.format(
        action=TRANSLATIONS['modify']['title'],
        controller=TRANSLATIONS['conditional']['title'])

    try:
        pre_statement = """import os, random, sys
sys.path.append(os.path.abspath('/var/mycodo-root'))
from mycodo.mycodo_client import DaemonControl

control = DaemonControl()
message = ''

def measure(condition_id):
    # pylint: disable=unused-argument
    return random.choice([None, -100000, -10000, -1000, -100, -10, 0, 1, 10, 100, 1000, 10000, 100000])

def run_all_actions(message=message):
    # pylint: disable=unused-argument
    pass

def run_action(action_id, message=message):
    # pylint: disable=unused-argument
    pass

###########################
##### BEGIN USER CODE #####
###########################

"""

        cond_statement = (pre_statement +
                          form.conditional_statement.data)

        if len(cond_statement.splitlines()) > 999:
            error.append("Too many lines in code. Reduce code to less than 1000 lines.")

        lines_code = ''
        for line_num, each_line in enumerate(cond_statement.splitlines(), 1):
            if len(str(line_num)) == 3:
                line_spacing = ''
            elif len(str(line_num)) == 2:
                line_spacing = ' '
            else:
                line_spacing = '  '
            lines_code += '{sp}{ln}: {line}\n'.format(
                sp=line_spacing,
                ln=line_num,
                line=each_line)

        path_file = '/tmp/conditional_code_{}.py'.format(
            str(uuid.uuid4()).split('-')[0])
        with open(path_file, 'w') as out:
            out.write('{}\n'.format(cond_statement))

        cmd_test = 'export PYTHONPATH=$PYTHONPATH:/var/mycodo-root && ' \
                   'pylint3 -d I,W0621,C0103,C0111,C0301,C0327,C0410,C0413 {path}'.format(
            path=path_file)
        cmd_out, cmd_err, cmd_status = cmd_output(cmd_test)

        os.remove(path_file)

        message = Markup(
            '<pre>\n\n'
            'Full Conditional Statement code:\n\n{code}\n\n'
            'Conditional Statement code analysis:\n\n{report}'
            '</pre>'.format(
                code=lines_code, report=cmd_out.decode("utf-8")))
        if cmd_status:
            flash('Error(s) were found while evaluating your code. Review '
                  'the error(s), below, and fix them before activating your '
                  'Conditional.', 'error')
            flash(message, 'error')
        else:
            flash(
                "No errors were found while evaluating your code. However, "
                "this doesn't mean your code will perform as expected. "
                "Review your code for issues and test your Conditional "
                "before putting it into a production environment.", 'success')
            flash(message, 'success')

        cond_mod = Conditional.query.filter(
            Conditional.unique_id == form.function_id.data).first()
        cond_mod.name = form.name.data
        cond_mod.conditional_statement = form.conditional_statement.data
        cond_mod.period = form.period.data
        cond_mod.start_offset = form.start_offset.data
        cond_mod.refractory_period = form.refractory_period.data

        if not error:
            db.session.commit()

            if cond_mod.is_activated:
                control = DaemonControl()
                return_value = control.refresh_daemon_conditional_settings(
                    form.function_id.data)
                flash(gettext(
                    "Daemon response: %(resp)s",
                    resp=return_value), "success")

    except sqlalchemy.exc.OperationalError as except_msg:
        error.append(except_msg)
    except sqlalchemy.exc.IntegrityError as except_msg:
        error.append(except_msg)
    except Exception as except_msg:
        error.append(except_msg)

    flash_success_errors(error, action, url_for('routes_page.page_function'))