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'))
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)
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))
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'))
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
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
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))
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
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
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
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("<", "<"), 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
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
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
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")
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
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("<", "<"), 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
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))
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
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))
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
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
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))
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))
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
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
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
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)
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'))
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'))
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))
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'))