def initialize_variables(self):
        """ Define all settings """
        cond = db_retrieve_table_daemon(Conditional, unique_id=self.unique_id)
        self.is_activated = cond.is_activated
        self.conditional_statement = cond.conditional_statement
        self.period = cond.period
        self.start_offset = cond.start_offset
        self.log_level_debug = cond.log_level_debug
        self.message_include_code = cond.message_include_code

        self.set_log_level_debug(self.log_level_debug)

        now = time.time()
        self.timer_period = now + self.start_offset

        self.file_run = '{}/conditional_{}.py'.format(PATH_PYTHON_CODE_USER,
                                                      self.unique_id)

        # If the file to execute doesn't exist, generate it
        if not os.path.exists(self.file_run):
            save_conditional_code(
                [], self.conditional_statement, self.unique_id,
                db_retrieve_table_daemon(ConditionalConditions, entry='all'),
                db_retrieve_table_daemon(Actions, entry='all'))

        module_name = "mycodo.conditional.{}".format(
            os.path.basename(self.file_run).split('.')[0])
        spec = importlib.util.spec_from_file_location(module_name,
                                                      self.file_run)
        conditional_run = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(conditional_run)
        self.conditional_run = conditional_run.ConditionalRun(
            self.logger, self.unique_id, '')
    def initialize_variables(self):
        """Define all settings."""
        cond = db_retrieve_table_daemon(
            Conditional, unique_id=self.unique_id)
        self.is_activated = cond.is_activated
        self.conditional_statement = cond.conditional_statement
        self.conditional_status = cond.conditional_status
        self.period = cond.period
        self.start_offset = cond.start_offset
        self.pyro_timeout = cond.pyro_timeout
        self.log_level_debug = cond.log_level_debug
        self.message_include_code = cond.message_include_code

        self.sample_rate = db_retrieve_table_daemon(
            Misc, entry='first').sample_rate_controller_conditional

        self.set_log_level_debug(self.log_level_debug)

        now = time.time()
        self.timer_period = now + self.start_offset

        self.file_run = f'{PATH_PYTHON_CODE_USER}/conditional_{self.unique_id}.py'

        # If the file to execute doesn't exist, generate it
        if not os.path.exists(self.file_run):
            save_conditional_code(
                [],
                self.conditional_statement,
                self.conditional_status,
                self.unique_id,
                db_retrieve_table_daemon(ConditionalConditions, entry='all'),
                db_retrieve_table_daemon(Actions, entry='all'),
                timeout=self.pyro_timeout)

        module_name = f"mycodo.conditional.{os.path.basename(self.file_run).split('.')[0]}"
        spec = importlib.util.spec_from_file_location(
            module_name, self.file_run)
        conditional_run = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(conditional_run)
        self.conditional_run = conditional_run.ConditionalRun(
            self.logger, self.unique_id, '')

        self.logger.debug(
            f"Conditional Statement (pre-replacement):\n{self.conditional_statement}")

        with open(self.file_run, 'r') as file:
            self.logger.debug(
                f"Conditional Statement (post-replacement):\n{file.read()}")

        self.ready.set()
        self.running = True
def conditional_mod(form):
    """Modify a Conditional"""
    error = []
    action = '{action} {controller}'.format(
        action=TRANSLATIONS['modify']['title'],
        controller=TRANSLATIONS['conditional']['title'])

    try:
        error, lines_code, cmd_status, cmd_out = save_conditional_code(
            error,
            form.conditional_statement.data,
            form.function_id.data,
            ConditionalConditions.query.all(),
            Actions.query.all(),
            test=True)

        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")))

        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.log_level_debug = form.log_level_debug.data
        cond_mod.message_include_code = form.message_include_code.data
        cond_mod.start_offset = form.start_offset.data

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

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

        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'))
Exemple #4
0
                with session_scope(MYCODO_DB_PATH) as cond_sess:
                    for each_cond in cond_sess.query(Conditional).all():
                        error = []
                        each_cond.conditional_statement = each_cond.conditional_statement.replace(
                            'self.measure(', 'self.condition(')
                        each_cond.conditional_statement = each_cond.conditional_statement.replace(
                            'self.measure_dict(', 'self.condition_dict(')
                        if not error:
                            cond_sess.commit()
                        else:
                            for each_error in error:
                                print("Error: {}".format(each_error))

                        save_conditional_code(
                            [],
                            each_cond.conditional_statement,
                            each_cond.unique_is,
                            conditions,
                            actions)
            except Exception:
                msg = "ERROR: post-alembic revision {}: {}".format(
                    each_revision, traceback.format_exc())
                error.append(msg)
                print(msg)

        elif each_revision == '545744b31813':
            print("Executing post-alembic code for revision {}".format(
                each_revision))
            try:
                from mycodo.databases.models import Output

                with session_scope(MYCODO_DB_PATH) as output_sess:
Exemple #5
0
def conditional_mod(form):
    """Modify a Conditional."""
    messages = {
        "success": [],
        "info": [],
        "warning": [],
        "error": [],
        "page_refresh": True,
        "return_text": [],
        "name": None
    }

    try:
        if current_app.config['TESTING']:
            cmd_status = None
            pylint_message = ""
        else:
            messages[
                "error"], lines_code, cmd_status, cmd_out = save_conditional_code(
                    messages["error"],
                    form.conditional_statement.data,
                    form.conditional_status.data,
                    form.function_id.data,
                    ConditionalConditions.query.all(),
                    Actions.query.all(),
                    timeout=form.pyro_timeout.data,
                    test=True)

            pylint_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")))

        cond_mod = Conditional.query.filter(
            Conditional.unique_id == form.function_id.data).first()
        cond_mod.name = form.name.data
        messages["name"] = form.name.data
        cond_mod.conditional_statement = form.conditional_statement.data
        cond_mod.conditional_status = form.conditional_status.data
        cond_mod.period = form.period.data
        cond_mod.log_level_debug = form.log_level_debug.data
        cond_mod.message_include_code = form.message_include_code.data
        cond_mod.start_offset = form.start_offset.data
        cond_mod.pyro_timeout = form.pyro_timeout.data

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

        if pylint_message:
            messages["info"].append(
                "Review your code for issues and test before putting it "
                "into a production environment.")
            messages["return_text"].append(pylint_message)

        if not messages["error"]:
            db.session.commit()
            messages["success"].append('{action} {controller}'.format(
                action=TRANSLATIONS['modify']['title'],
                controller=TRANSLATIONS['conditional']['title']))

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

    except sqlalchemy.exc.OperationalError as except_msg:
        messages["error"].append(str(except_msg))
    except sqlalchemy.exc.IntegrityError as except_msg:
        messages["error"].append(str(except_msg))
    except Exception as except_msg:
        messages["error"].append(str(except_msg))

    return messages
Exemple #6
0
def function_add(form_add_func):
    action = '{action} {controller}'.format(
        action=TRANSLATIONS['add']['title'],
        controller=TRANSLATIONS['function']['title'])
    error = []

    function_name = form_add_func.function_type.data

    dict_controllers = parse_function_information()

    if current_app.config['TESTING']:
        dep_unmet = False
    else:
        dep_unmet, _ = return_dependencies(function_name)
        if dep_unmet:
            list_unmet_deps = []
            for each_dep in dep_unmet:
                list_unmet_deps.append(each_dep[0])
            error.append(
                "The {dev} device you're trying to add has unmet "
                "dependencies: {dep}".format(dev=function_name,
                                             dep=', '.join(list_unmet_deps)))

    new_func = None

    try:
        if function_name == 'conditional_conditional':
            new_func = Conditional()
            new_func.conditional_statement = '''
# Example code for learning how to use a Conditional. See the manual for more information.

self.logger.info("This INFO log entry will appear in the Daemon Log")
self.logger.error("This ERROR log entry will appear in the Daemon Log")

# Replace "asdf1234" with a Condition ID
measurement = self.condition("{asdf1234}")
self.logger.info("Check this measurement in the Daemon Log. The value is {val}".format(val=measurement))

if measurement is not None:  # If a measurement exists
    self.message += "This message appears in email alerts and notes.\\n"

    if measurement < 23:  # If the measurement is less than 23
        self.message += "Measurement is too Low! Measurement is {meas}\\n".format(meas=measurement)
        self.run_all_actions(message=self.message)  # Run all actions sequentially

    elif measurement > 27:  # Else If the measurement is greater than 27
        self.message += "Measurement is too High! Measurement is {meas}\\n".format(meas=measurement)
        # Replace "qwer5678" with an Action ID
        self.run_action("{qwer5678}", message=self.message)  # Run a single specific Action'''

            if not error:
                new_func.save()
                save_conditional_code(error,
                                      new_func.conditional_statement,
                                      new_func.unique_id,
                                      ConditionalConditions.query.all(),
                                      Actions.query.all(),
                                      test=False)

        elif function_name == 'pid_pid':
            new_func = PID().save()

            for each_channel, measure_info in PID_INFO['measure'].items():
                new_measurement = DeviceMeasurements()

                if 'name' in measure_info:
                    new_measurement.name = measure_info['name']
                if 'measurement_type' in measure_info:
                    new_measurement.measurement_type = measure_info[
                        'measurement_type']

                new_measurement.device_id = new_func.unique_id
                new_measurement.measurement = measure_info['measurement']
                new_measurement.unit = measure_info['unit']
                new_measurement.channel = each_channel
                if not error:
                    new_measurement.save()

        elif function_name in [
                'trigger_edge', 'trigger_output', 'trigger_output_pwm',
                'trigger_timer_daily_time_point',
                'trigger_timer_daily_time_span', 'trigger_timer_duration',
                'trigger_infrared_remote_input', 'trigger_run_pwm_method',
                'trigger_sunrise_sunset'
        ]:
            new_func = Trigger()
            new_func.name = '{}'.format(FUNCTION_INFO[function_name]['name'])
            new_func.trigger_type = function_name
            if not error:
                new_func.save()

        elif function_name in ['function_spacer', 'function_actions']:
            new_func = Function()
            if function_name == 'function_spacer':
                new_func.name = 'Spacer'
            new_func.function_type = function_name
            if not error:
                new_func.save()

        elif function_name in dict_controllers:
            # Custom Function Controller
            new_func = CustomController()
            new_func.device = function_name

            if 'function_name' in dict_controllers[function_name]:
                new_func.name = dict_controllers[function_name][
                    'function_name']
            else:
                new_func.name = 'Function Name'

            # TODO: Switch to JSON function
            list_options = []
            if 'custom_options' in dict_controllers[function_name]:
                for each_option in dict_controllers[function_name][
                        'custom_options']:
                    if 'id' not in each_option:
                        continue

                    if each_option['default_value'] is False:
                        default_value = ''
                    else:
                        default_value = each_option['default_value']
                    option = '{id},{value}'.format(id=each_option['id'],
                                                   value=default_value)
                    list_options.append(option)
            new_func.custom_options = ';'.join(list_options)
            if not error:
                new_func.save()

        elif function_name == '':
            error.append("Must select a function type")
        else:
            error.append("Unknown function type: '{}'".format(function_name))

        if not error:
            display_order = csv_to_list_of_str(
                DisplayOrder.query.first().function)
            DisplayOrder.query.first().function = add_display_order(
                display_order, new_func.unique_id)
            db.session.commit()

    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'))

    if dep_unmet:
        return 1
Exemple #7
0
def function_add(form_add_func):
    messages = {"success": [], "info": [], "warning": [], "error": []}
    new_function_id = None
    list_unmet_deps = []
    dep_name = None
    dep_message = ''

    function_name = form_add_func.function_type.data

    dict_controllers = parse_function_information()

    if not current_app.config['TESTING']:
        dep_unmet, _, dep_message = return_dependencies(function_name)
        if dep_unmet:
            for each_dep in dep_unmet:
                list_unmet_deps.append(each_dep[3])
            messages["error"].append(
                f"{function_name} has unmet dependencies. "
                "They must be installed before the Function can be added.")
            if function_name in dict_controllers:
                dep_name = dict_controllers[function_name]['function_name']
            else:
                messages["error"].append(
                    f"Function not found: {function_name}")

            return messages, dep_name, list_unmet_deps, dep_message, None

    new_func = None

    try:
        if function_name == 'conditional_conditional':
            new_func = Conditional()
            new_func.position_y = 999
            new_func.conditional_statement = '''
# Example code for learning how to use a Conditional. See the manual for more information.

self.logger.info("This INFO log entry will appear in the Daemon Log")
self.logger.error("This ERROR log entry will appear in the Daemon Log")

if not hasattr(self, "loop_count"):  # Initialize objects saved across executions
    self.loop_count = 1
else:
    self.loop_count += 1

# Replace "asdf1234" with a Condition ID
measurement = self.condition("{asdf1234}") 
self.logger.info(f"Check this measurement in the Daemon Log. The value is {measurement}")

if measurement is not None:  # If a measurement exists
    self.message += "This message appears in email alerts and notes.\\n"

    if measurement < 23:  # If the measurement is less than 23
        self.message += f"Measurement is too Low! Measurement is {measurement}\\n"
        self.run_all_actions(message=self.message)  # Run all actions sequentially

    elif measurement > 27:  # Else If the measurement is greater than 27
        self.message += f"Measurement is too High! Measurement is {measurement}\\n"
        # Replace "qwer5678" with an Action ID
        self.run_action("{qwer5678}", message=self.message)  # Run a single specific Action'''

            new_func.conditional_status = '''
# Example code to provide a return status for other controllers and widgets.
status_dict = {
    'string_status': f"This is the demo status of the conditional controller. "
                     f"The controller has looped {self.loop_count} times",
    'loop_count': self.loop_count,
    'error': []
}
return status_dict'''

            if not messages["error"]:
                new_func.save()
                new_function_id = new_func.unique_id
                if not current_app.config['TESTING']:
                    save_conditional_code(messages["error"],
                                          new_func.conditional_statement,
                                          new_func.conditional_status,
                                          new_func.unique_id,
                                          ConditionalConditions.query.all(),
                                          Actions.query.all(),
                                          test=False)

        elif function_name == 'pid_pid':
            new_func = PID()
            new_func.position_y = 999
            new_func.save()
            new_function_id = new_func.unique_id

            for each_channel, measure_info in PID_INFO['measure'].items():
                new_measurement = DeviceMeasurements()

                if 'name' in measure_info:
                    new_measurement.name = measure_info['name']
                if 'measurement_type' in measure_info:
                    new_measurement.measurement_type = measure_info[
                        'measurement_type']

                new_measurement.device_id = new_func.unique_id
                new_measurement.measurement = measure_info['measurement']
                new_measurement.unit = measure_info['unit']
                new_measurement.channel = each_channel
                if not messages["error"]:
                    new_measurement.save()

        elif function_name in [
                'trigger_edge', 'trigger_output', 'trigger_output_pwm',
                'trigger_timer_daily_time_point',
                'trigger_timer_daily_time_span', 'trigger_timer_duration',
                'trigger_run_pwm_method', 'trigger_sunrise_sunset'
        ]:
            new_func = Trigger()
            new_func.name = '{}'.format(FUNCTION_INFO[function_name]['name'])
            new_func.trigger_type = function_name
            new_func.position_y = 999

            if not messages["error"]:
                new_func.save()
                new_function_id = new_func.unique_id

        elif function_name == 'function_actions':
            new_func = Function()
            new_func.position_y = 999
            new_func.function_type = function_name
            if not messages["error"]:
                new_func.save()
                new_function_id = new_func.unique_id

        elif function_name in dict_controllers:
            # Custom Function Controller
            new_func = CustomController()
            new_func.device = function_name
            new_func.position_y = 999

            if 'function_name' in dict_controllers[function_name]:
                new_func.name = dict_controllers[function_name][
                    'function_name']
            else:
                new_func.name = 'Function Name'

            # Generate string to save from custom options
            messages["error"], custom_options = custom_options_return_json(
                messages["error"],
                dict_controllers,
                device=function_name,
                use_defaults=True)
            new_func.custom_options = custom_options

            new_func.unique_id = set_uuid()

            if ('execute_at_creation' in dict_controllers[new_func.device]
                    and not current_app.config['TESTING']):
                messages["error"], new_func = dict_controllers[
                    new_func.device]['execute_at_creation'](
                        messages["error"], new_func,
                        dict_controllers[new_func.device])

            if not messages["error"]:
                new_func.save()
                new_function_id = new_func.unique_id

        elif function_name == '':
            messages["error"].append("Must select a function type")
        else:
            messages["error"].append(
                f"Unknown function type: '{function_name}'")

        if not messages["error"]:
            if function_name in dict_controllers:
                #
                # Add measurements defined in the Function Module
                #

                if ('measurements_dict' in dict_controllers[function_name] and
                        dict_controllers[function_name]['measurements_dict']):
                    for each_channel in dict_controllers[function_name][
                            'measurements_dict']:
                        measure_info = dict_controllers[function_name][
                            'measurements_dict'][each_channel]
                        new_measurement = DeviceMeasurements()
                        new_measurement.device_id = new_func.unique_id
                        if 'name' in measure_info:
                            new_measurement.name = measure_info['name']
                        else:
                            new_measurement.name = ""
                        if 'measurement' in measure_info:
                            new_measurement.measurement = measure_info[
                                'measurement']
                        else:
                            new_measurement.measurement = ""
                        if 'unit' in measure_info:
                            new_measurement.unit = measure_info['unit']
                        else:
                            new_measurement.unit = ""
                        new_measurement.channel = each_channel
                        new_measurement.save()

                #
                # If there are a variable number of measurements
                #

                elif ('measurements_variable_amount'
                      in dict_controllers[function_name]
                      and dict_controllers[function_name]
                      ['measurements_variable_amount']):
                    # Add first default measurement with empty unit and measurement
                    new_measurement = DeviceMeasurements()
                    new_measurement.name = ""
                    new_measurement.device_id = new_func.unique_id
                    new_measurement.measurement = ""
                    new_measurement.unit = ""
                    new_measurement.channel = 0
                    new_measurement.save()

                #
                # Add channels defined in the Function Module
                #

                if 'channels_dict' in dict_controllers[function_name]:
                    for each_channel, channel_info in dict_controllers[
                            function_name]['channels_dict'].items():
                        new_channel = FunctionChannel()
                        new_channel.channel = each_channel
                        new_channel.function_id = new_func.unique_id

                        # Generate string to save from custom options
                        messages[
                            "error"], custom_options = custom_channel_options_return_json(
                                messages["error"],
                                dict_controllers,
                                None,
                                new_func.unique_id,
                                each_channel,
                                device=new_func.device,
                                use_defaults=True)
                        new_channel.custom_options = custom_options

                        new_channel.save()

            messages["success"].append(
                f"{TRANSLATIONS['add']['title']} {TRANSLATIONS['function']['title']}"
            )

    except sqlalchemy.exc.OperationalError as except_msg:
        messages["error"].append(str(except_msg))
    except sqlalchemy.exc.IntegrityError as except_msg:
        messages["error"].append(str(except_msg))
    except Exception as except_msg:
        logger.exception("Add Function")
        messages["error"].append(str(except_msg))

    return messages, dep_name, list_unmet_deps, dep_message, new_function_id
Exemple #8
0
def function_add(form_add_func):
    action = '{action} {controller}'.format(
        action=TRANSLATIONS['add']['title'],
        controller=TRANSLATIONS['function']['title'])
    error = []

    function_name = form_add_func.function_type.data

    dict_controllers = parse_function_information()

    if current_app.config['TESTING']:
        dep_unmet = False
    else:
        dep_unmet, _ = return_dependencies(function_name)
        if dep_unmet:
            list_unmet_deps = []
            for each_dep in dep_unmet:
                list_unmet_deps.append(each_dep[0])
            error.append(
                "The {dev} device you're trying to add has unmet "
                "dependencies: {dep}".format(
                    dev=function_name, dep=', '.join(list_unmet_deps)))

    new_func = None

    try:
        if function_name == 'conditional_conditional':
            new_func = Conditional()
            new_func.conditional_statement = '''
# Example code for learning how to use a Conditional. See the manual for more information.

self.logger.info("This INFO log entry will appear in the Daemon Log")
self.logger.error("This ERROR log entry will appear in the Daemon Log")

# Replace "asdf1234" with a Condition ID
measurement = self.condition("{asdf1234}")
self.logger.info("Check this measurement in the Daemon Log. The value is {val}".format(val=measurement))

if measurement is not None:  # If a measurement exists
    self.message += "This message appears in email alerts and notes.\\n"

    if measurement < 23:  # If the measurement is less than 23
        self.message += "Measurement is too Low! Measurement is {meas}\\n".format(meas=measurement)
        self.run_all_actions(message=self.message)  # Run all actions sequentially

    elif measurement > 27:  # Else If the measurement is greater than 27
        self.message += "Measurement is too High! Measurement is {meas}\\n".format(meas=measurement)
        # Replace "qwer5678" with an Action ID
        self.run_action("{qwer5678}", message=self.message)  # Run a single specific Action'''

            if not error:
                new_func.save()
                save_conditional_code(
                    error,
                    new_func.conditional_statement,
                    new_func.unique_id,
                    ConditionalConditions.query.all(),
                    Actions.query.all(),
                    test=False)

        elif function_name == 'pid_pid':
            new_func = PID().save()

            for each_channel, measure_info in PID_INFO['measure'].items():
                new_measurement = DeviceMeasurements()

                if 'name' in measure_info:
                    new_measurement.name = measure_info['name']
                if 'measurement_type' in measure_info:
                    new_measurement.measurement_type = measure_info['measurement_type']

                new_measurement.device_id = new_func.unique_id
                new_measurement.measurement = measure_info['measurement']
                new_measurement.unit = measure_info['unit']
                new_measurement.channel = each_channel
                if not error:
                    new_measurement.save()

        elif function_name in ['trigger_edge',
                               'trigger_output',
                               'trigger_output_pwm',
                               'trigger_timer_daily_time_point',
                               'trigger_timer_daily_time_span',
                               'trigger_timer_duration',
                               'trigger_run_pwm_method',
                               'trigger_sunrise_sunset']:
            new_func = Trigger()
            new_func.name = '{}'.format(FUNCTION_INFO[function_name]['name'])
            new_func.trigger_type = function_name
            if not error:
                new_func.save()

        elif function_name in ['function_spacer',
                               'function_actions']:
            new_func = Function()
            if function_name == 'function_spacer':
                new_func.name = 'Spacer'
            new_func.function_type = function_name
            if not error:
                new_func.save()

        elif function_name in dict_controllers:
            # Custom Function Controller
            new_func = CustomController()
            new_func.device = function_name

            if 'function_name' in dict_controllers[function_name]:
                new_func.name = dict_controllers[function_name]['function_name']
            else:
                new_func.name = 'Function Name'

            # Generate string to save from custom options
            error, custom_options = custom_options_return_json(
                error, dict_controllers, device=function_name, use_defaults=True)
            new_func.custom_options = custom_options

            new_func.unique_id = set_uuid()

            if ('execute_at_creation' in dict_controllers[new_func.device] and
                    not current_app.config['TESTING']):
                error, new_func = dict_controllers[new_func.device]['execute_at_creation'](
                    error, new_func, dict_controllers[new_func.device])

            if not error:
                new_func.save()

        elif function_name == '':
            error.append("Must select a function type")
        else:
            error.append("Unknown function type: '{}'".format(
                function_name))

        if not error:
            display_order = csv_to_list_of_str(
                DisplayOrder.query.first().function)
            DisplayOrder.query.first().function = add_display_order(
                display_order, new_func.unique_id)
            db.session.commit()

            if function_name in dict_controllers:
                #
                # Add measurements defined in the Function Module
                #

                if ('measurements_dict' in dict_controllers[function_name] and
                        dict_controllers[function_name]['measurements_dict']):
                    for each_channel in dict_controllers[function_name]['measurements_dict']:
                        measure_info = dict_controllers[function_name]['measurements_dict'][each_channel]
                        new_measurement = DeviceMeasurements()
                        new_measurement.device_id = new_func.unique_id
                        if 'name' in measure_info:
                            new_measurement.name = measure_info['name']
                        else:
                            new_measurement.name = ""
                        if 'measurement' in measure_info:
                            new_measurement.measurement = measure_info['measurement']
                        else:
                            new_measurement.measurement = ""
                        if 'unit' in measure_info:
                            new_measurement.unit = measure_info['unit']
                        else:
                            new_measurement.unit = ""
                        new_measurement.channel = each_channel
                        new_measurement.save()

                #
                # If there are a variable number of measurements
                #

                elif ('measurements_variable_amount' in dict_controllers[function_name] and
                        dict_controllers[function_name]['measurements_variable_amount']):
                    # Add first default measurement with empty unit and measurement
                    new_measurement = DeviceMeasurements()
                    new_measurement.name = ""
                    new_measurement.device_id = new_func.unique_id
                    new_measurement.measurement = ""
                    new_measurement.unit = ""
                    new_measurement.channel = 0
                    new_measurement.save()

                #
                # Add channels defined in the Function Module
                #

                if 'channels_dict' in dict_controllers[function_name]:
                    for each_channel, channel_info in dict_controllers[function_name]['channels_dict'].items():
                        new_channel = FunctionChannel()
                        new_channel.channel = each_channel
                        new_channel.function_id = new_func.unique_id

                        # Generate string to save from custom options
                        error, custom_options = custom_channel_options_return_json(
                            error, dict_controllers, None,
                            new_func.unique_id, each_channel,
                            device=new_func.device, use_defaults=True)
                        new_channel.custom_options = custom_options

                        new_channel.save()

            flash(gettext(
                "%(type)s Function with ID %(id)s (%(uuid)s) successfully added",
                type=new_func.name,
                id=new_func.id,
                uuid=new_func.unique_id),
                "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:
        logger.exception("Add Function")
        error.append(except_msg)

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

    if dep_unmet:
        return 1