Example #1
0
    def _devices_to_process(self) -> list:
        # todo: area filtering
        to_check = self.condition.check_instance
        to_process = []
        disabled_list = []

        if isinstance(to_check, GaInputModel):
            if to_check.enabled == 1:
                for device in to_check.member_list:
                    if device.enabled == 1:
                        to_process.append(device)

                    else:
                        disabled_list.append(device)

        else:
            if to_check.enabled == 1:
                to_process.append(to_check)

            else:
                disabled_list.append(to_check)

        if len(disabled_list) > 0:
            device_log(f"Condition match \"{self.condition.name}\" has some disabled inputs: \"{disabled_list}\"", add=self.name, level=8)

        if len(to_process) == 0:
            device_log(f"Got no inputs to pull data from for condition match \"{self.condition.name}\"", add=self.name, level=4)
            raise ValueError(f"No data to process for match \"{self.condition.name}\"")

        return to_process
Example #2
0
    def _get_link_data(self, link: GaConditionLink) -> dict:
        """Will get the two results of its condition-link members.
        :param link: Condition link to process
        :type link: GaConditionLink
        :return: Member results
        :rtype: dict
        """
        device_log(f"Getting data for link \"{link.name}\"",
                   add=self.name,
                   level=9)

        result_dict = {}

        # process all conditions in link
        for order, condition in link.condition_match_dict.items():
            if condition.data is not None:
                # get its data from a previous link-result if it is set
                result_dict[order] = condition.data
            else:
                # else process the condition
                result_dict[order] = ConditionResult(
                    condition=condition, device=self.group.name).get()

        for order, condition in link.condition_group_dict.items():
            # if group -> run function recursively
            result_dict[order] = self._process_links(group=condition)

        device_log(
            f"Data of condition-link \"{link.name}\": \"{result_dict}\"",
            add=self.name,
            level=8)

        return result_dict
Example #3
0
    def _get_link_data_result(self, link: GaConditionLink,
                              result_dict: dict) -> bool:
        """Will get the result for a link while using its members results.
        :param link: Condition link to process
        :type link: GaConditionLink
        :param result_dict: Results of the links members
        :type result_dict: dict
        :return: Result of the link
        :rtype: bool
        """
        device_log(f"Getting result of condition-link \"{link.name}\"",
                   add=self.name,
                   level=9)

        try:
            result = self._get_result(
                link=link,
                result_dict=result_dict,
            )

            device_log(
                f"Result of condition-link \"{link.name}\": \"{result}\"",
                add=self.name,
                level=8)
            return result

        except (KeyError, ValueError) as error_msg:
            return self._error(error_exception=error_msg)
Example #4
0
    def _downlink(self, instance):
        dl = instance.downlink
        if self.manually or dl.enabled == 1:
            device_log(f"Device \"{instance.name}\" is connected via downlink \"{dl.name}\"", add=self.name, level=7)
            self.task_instance_list.append({'downlink': dl, 'device': instance})

        else:
            device_log(f"Device \"{dl.name}\" is disabled and will not be processed!", add=self.name, level=3)
Example #5
0
    def _model(self):
        if self.manually or self.instance.enabled == 1:
            for device_instance in self.instance.member_list:
                if device_instance.enabled == 1:
                    self._device(instance=device_instance)

        else:
            device_log(f"Device \"{self.instance.name}\" is disabled and will not be processed!", add=self.name, level=2)
 def _parse_error(self):
     device_log(
         f'Unable to parse {self.condition.check_instance.name} value provided for condition match \"{self.condition.name}\". '
         f'Please check the documentation for supported formats.',
         add=self.name,
         level=4)
     raise ValueError(
         f'Unsupported value for condition match \"{self.condition.name}\"')
Example #7
0
    def _device(self, instance):
        if self.manually or instance.enabled == 1:
            if instance.downlink is not None:
                self._downlink(instance=instance)

            else:
                self.task_instance_list.append({'device': instance})

        else:
            device_log(f"Device \"{instance.name}\" is disabled and will not be processed!", add=self.name, level=3)
Example #8
0
    def _reset_flags(self, group: GaConditionGroup) -> None:
        """Resets states of links and data of link members and resets them to the defaults.
        :param group: Condition to process for resetting
        :type group: GaConditionGroup
        :rtype: None
        """
        device_log(f"Resetting flags for condition \"{group.name}\"",
                   add=self.name,
                   level=9)

        for link in group.member_list:
            link.processed = False
            for condition in self._get_member_list(link):
                condition.data = None
Example #9
0
    def _fail_check(self):
        if not self.manually and self.instance.fail_sleep is not None:  # skip fail-sleep if executed manually
            if datetime.now() < self.instance.fail_sleep:
                device_log(
                    f"Skipping execution of device \"{self.instance.name}\" since it has reached the max error threshold",
                    add=self.name,
                    level=4)
                device_log(
                    f"Device \"{self.instance.name}\" will be skipped until \"{self.instance.fail_sleep.strftime('%Y-%m-%d %H:%M:%S:%f')}\"",
                    add=self.name,
                    level=6)
                return False

        return True
Example #10
0
    def _get_data(self) -> None:
        self.process_list = self._devices_to_process()
        self.data_method = self._get_data_prerequisites()
        self.data_type = self._get_data_type()

        if isinstance(self.condition.check_instance, GaInputModel):
            self._get_data_group()

        else:
            self.data_list = self._get_data_device(device=self.process_list[0])

        if len(self.data_list) == 0:
            device_log(f"No data received for condition match \"{self.condition.name}\" (id \"{self.condition.object_id}\")", add=self.name, level=5)
            raise ValueError(f"Got no data for condition match \"{self.condition.name}\"")
Example #11
0
    def _get_data_prerequisites(self):
        # should only run once since its the same for all devices processed
        period_type = self.condition.period
        device_log(f"Condition match \"{self.condition.name}\", period type \"{period_type}\", period \"{self.condition.period_data}\"", add=self.name, level=8)

        if period_type == 'time':
            data_method = self._get_data_by_time

        elif period_type == 'range':
            data_method = self._get_data_by_range

        else:
            device_log(f"Condition match \"{self.condition.name}\" has an unsupported period_type \"{period_type}\"", add=self.name, level=4)
            raise ValueError(f"Unsupported period type for condition match \"{self.condition.name}\"")

        return data_method
Example #12
0
def get(instance) -> bool:
    start_time = time()

    while time() < start_time + LOCK_MAX_WAIT:
        if not instance.locked:
            instance.locked = True
            return True
        else:
            sleep(LOCK_CHECK_INTERVAL)

    device_log(
        f"Gave up to get lock for device \"{instance.name}\" after \"{LOCK_MAX_WAIT}\" sec",
        add=instance.name,
        level=6)

    return False
Example #13
0
    def _datetime_compare(self, value: datetime, equal_format: str):
        """
        Will compare a given datetime to the current one.

        :param value: Datetime to match
        :param equal_format: Datetime format used for equality check
        :return: bool
        """
        now = datetime.now()
        operator = self.condition.operator
        result = False

        def _equal(_now, _data) -> bool:
            # will use custom time format since some make no sense for this comparison
            if now.strftime(equal_format) == value.strftime(equal_format):
                return True

            return False

        if operator == '=':
            result = _equal(now, value)

        elif operator == '!=':
            result = not _equal(now, value)

        elif operator == '>':
            if value > now:
                result = True

        elif operator == '<':
            if value < now:
                result = True

        else:
            device_log(
                f"Condition match \"{self.condition.name}\" has an unsupported operator \"{operator}\"",
                add=self.name,
                level=4)
            raise ValueError(
                f"Unsupported operator for condition \"{self.condition.name}\""
            )

        device_log(
            f"Condition match \"{self.condition.name}\" result for comparison \"{value} {operator} {now}\" = {result}",
            add=self.name,
            level=8)
        return result
Example #14
0
    def get(self) -> bool:
        if self.condition.check_instance.name == 'time':
            return self._time(raw_value=self.condition.value.copy())

        elif self.condition.check_instance.name == 'date':
            return self._date(raw_value=self.condition.value.copy())

        elif self.condition.check_instance.name.startswith('day_'):
            return self._day()

        device_log(
            f"Condition match \"{self.condition.name}\" has an unsupported special match set: \"{self.condition.check_instance.name}\"",
            add=self.name,
            level=4)
        raise ValueError(
            f"Unsupported special match type for condition \"{self.condition.name}\""
        )
Example #15
0
    def _run(self) -> bool:
        if self.manually:
            if type(self.instance) == GaOutputModel:
                output_list = self.instance.member_list

            elif type(self.instance) == GaOutputDevice:
                output_list = [self.instance]

            else:
                device_log(
                    f'Manual execution only allows OutputModels and OutputDevices to be supplied - got neither!',
                    add=self.name,
                    level=3)
                return False

            device_log(f"Output list to process: \"{output_list}\"",
                       add=self.name,
                       level=7)
            areas = None

        else:  # service will always supply a GaConditionGroup
            output_list = self.instance.output_object_list.copy()
            device_log(
                f"Output list of \"{self.instance.name}\": \"{output_list}\"",
                add=self.name,
                level=7)
            output_list.extend(self.instance.output_group_list)
            device_log(
                f"Output-group list of \"{self.instance.name}\": \"{self.instance.output_group_list}\"",
                add=self.name,
                level=7)
            areas = self.instance.area_group_list

        task_instance_list = []

        for output in output_list:
            task_instance_list.extend(
                Check(
                    instance=output,
                    model_obj=GaOutputModel,
                    device_obj=GaOutputDevice,
                    areas=areas,
                    manually=self.manually,
                ).get())

        results = []

        for task_dict in task_instance_list:
            results.append(self._process(task_dict=task_dict))

        if len(results) > 0:
            return all(results)

        return False
Example #16
0
    def _error_action(self, error) -> None:
        if error not in config.NONE_RESULTS:
            device_log(
                f"An error occurred while processing device \"{self.instance.name}\": \"{error}\"",
                add=self.name)
            self.instance.fail_count += 1

            if self.instance.fail_count > config.AGENT.device_fail_count:
                self.instance.fail_sleep = datetime.fromtimestamp(
                    datetime.now().timestamp() +
                    config.AGENT.device_fail_sleep)
                device_log(
                    f"Device \"{self.instance.name}\" has reached its fail threshold -> will skip execution "
                    f"until \"{self.instance.fail_sleep.strftime('%Y-%m-%d %H:%M:%S:%f')}\"",
                    add=self.name,
                    level=3)

        else:
            self.instance.fail_count = 0
Example #17
0
    def start(self) -> (str, None, bool):
        # output:
        #   None -> Error
        #   False -> Fail-Sleep
        #   True -> successful output
        #   str/data -> successful input

        if self._fail_check():
            if lock.get(instance=self.instance):
                result = self._execute()

                lock.remove(instance=self.instance)

                if isinstance(self.instance,
                              (GaInputDevice, GaConnectionDevice)):
                    if result not in config.NONE_RESULTS:
                        device_log(
                            f"Got result for device \"{self.instance.name}\": \"{result}\"",
                            add=self.name,
                            level=6)

                    else:
                        device_log(
                            f"Got no result for device \"{self.instance.name}\"!",
                            add=self.name,
                            level=3)

                    if self.instance.fail_count == 0:
                        try:
                            data = json_loads(result)
                            return data[self.INPUT_DATA_KEY]

                        except (KeyError, JSONDecodeError) as error:
                            device_log(
                                f"Unable decode received data; error: \"{error}\"",
                                add=self.name,
                                level=2)
                            return None

                else:
                    if result not in config.NONE_RESULTS:
                        # output devices do not need to return a useful result
                        device_log(
                            f"Got result for device \"{self.instance.name}\": \"{result}\"",
                            add=self.name,
                            level=6)

                    return result

        else:
            return False

        return None
Example #18
0
    def _reverse_condition(self, task_dict: dict) -> None:
        device = task_dict['device']
        device_log(
            f"Entering reverse-condition loop for output-device \"{device.name}\"",
            add=self.name,
            level=6)

        thread = Thread()
        thread_description = f"Conditional reversing for '{device.name}'"

        @thread.add_thread(
            sleep_time=int(1),
            thread_data=task_dict,
            once=True,
            description=thread_description,
        )
        def thread_task(data):
            tries = 0

            while not self._process(task_dict=data, reverse=True):
                if config.REVERSE_CONDITION_MAX_RETRIES is not None and tries >= config.REVERSE_CONDITION_MAX_RETRIES:
                    device_log(
                        f"Reversing of device \"{device.name}\" failed: reached maximum number of retries {tries}",
                        add=self.name,
                        level=3)
                    break

                device_log(f"Reversing of device \"{device.name}\" continues",
                           add=self.name,
                           level=8)
                sleep(config.REVERSE_CONDITION_INTERVAL)
                tries += 1

            if config.REVERSE_CONDITION_MAX_RETRIES is None or tries < config.REVERSE_CONDITION_MAX_RETRIES:
                device_log(f"Reversing of device \"{device.name}\" finished",
                           add=self.name,
                           level=6)

            self._exit()
            thread.stop_thread(description=device.name)

        thread.start_thread(description=thread_description)
Example #19
0
    def _get_data_type(self) -> (bool, float, int, str):
        data_type = self.condition.check_instance.datatype

        if data_type == 'bool':
            typ = bool

        elif data_type == 'float':
            typ = float

        elif data_type == 'int':
            typ = int

        elif data_type == 'str':
            typ = str

        else:
            device_log(f"Input device/model \"{self.condition.check_instance.name}\" has an unsupported data data_type set \"{data_type}\"", level=4)
            raise ValueError(f"Unsupported data type for input \"{self.condition.check_instance.name}\"")

        return typ
Example #20
0
    def start(self) -> bool:
        task_instance_list = Check(
            instance=self.instance,
            model_obj=GaInputModel,
            device_obj=GaInputDevice,
        ).get()

        results = []

        for task_dict in task_instance_list:
            device = task_dict['device']
            task_name = device.name
            task_id = device.object_id
            device_log(f"Processing device instance: \"{device.__dict__}\"", add=self.name, level=7)

            if 'downlink' in task_dict:
                data = Process(
                    instance=task_dict['downlink'],
                    nested_instance=device,
                    script_dir='connection',
                    manually=self.manually,
                ).start()

            else:
                data = Process(
                    instance=device,
                    script_dir='input',
                    manually=self.manually,
                ).start()

            if data is None:
                device_log(f"No data received for device \"{task_name}\"", add=self.name, level=3)
                results.append(False)

            elif data is False:
                device_log(f"Device \"{task_name}\" is in fail-sleep", add=self.name, level=4)
                results.append(False)

            else:
                self.database.put(command=self.SQL_DATA_COMMAND % (datetime.now(), data, task_id))
                device_log(f"Processing of input-device \"{task_name}\" succeeded", add=self.name, level=7)
                results.append(True)

        del self.database

        if len(results) > 0:
            return all(results)

        return False
Example #21
0
    def _reverse_timer(self, task_dict: dict) -> None:
        device = task_dict['device']
        device_log(
            f"Entering wait timer ({device.reverse_type_data} secs) for output-device \"{device.name}\"",
            add=self.name,
            level=6)

        thread = Thread()

        @thread.add_thread(
            sleep_time=int(device.reverse_type_data),
            thread_data=task_dict,
            once=True,
            description=f"Timed reversing for '{device.name}'",
        )
        def thread_task(data):
            self._process(task_dict=data, reverse=True)
            device_log(f"Reversing of device \"{device.name}\" finished",
                       add=self.name,
                       level=6)
            thread.stop_thread(description=device.name)

        thread.start()
Example #22
0
    def _reverse_check(self):
        if self.manually:
            # if a user manually executes an action we will not check if it is active => let the user overrule possible bugs..
            return True

        elif isinstance(
                self.instance, GaOutputDevice
        ) and self.instance.reverse == 1 and self.instance.active and not self.reverse:
            device_log(
                f"Reversible device \"{self.instance.name}\" is active and should not be reversed",
                add=self.name,
                level=5)
            return False

        elif isinstance(self.instance, GaConnectionDevice) and isinstance(self.nested_instance, GaOutputDevice) \
                and self.nested_instance.reverse == 1 and self.nested_instance.active and not self.reverse:
            device_log(
                f"Reversible device \"{self.nested_instance.name}\" is active and should not be reversed",
                add=self.name,
                level=5)
            return False

        return True
Example #23
0
    def _compare_day(self, value: int, compare: int) -> bool:
        """
        Simple comparison of current day against the match-day.

        :param value: Day to match
        :param compare: Current day
        :return: bool
        """
        operator = self.condition.operator
        result = False

        if operator == '=':
            if value == compare:
                result = True

        elif operator == '!=':
            if value != compare:
                result = True

        elif operator == '>':
            if value > compare:
                result = True

        elif operator == '<':
            if value < compare:
                result = True

        else:
            device_log(
                f"Condition match \"{self.condition.name}\" has an unsupported operator \"{operator}\"",
                add=self.name,
                level=4)
            raise ValueError(
                f"Unsupported operator for condition \"{self.condition.name}\""
            )

        return result
Example #24
0
    def _reverse_flags(self, error) -> (bool, None):
        if isinstance(self.instance, GaOutputDevice):
            if error is None:
                if self.instance.reverse == 1:
                    if self.reverse:
                        self.instance.active = False
                        device_log(
                            f"Reversible device \"{self.instance.name}\" was stopped",
                            add=self.name,
                            level=6)

                    else:
                        self.instance.active = True
                        device_log(
                            f"Reversible device \"{self.instance.name}\" entered the active state",
                            add=self.name,
                            level=6)

                return True

            else:
                return None

        return False
Example #25
0
    def _get_single_link_member_list(self, group: GaConditionGroup) -> list:
        """Will get a list of link members that are only linked at one non-processed link.
        We must always start processing at a 'edge' link. Else we might break the chain.
        :param group: Condition to check the link from
        :type group: GaConditionGroup
        :return: List of link members that are linked only once
        :rtype: list
        """
        device_log(
            f"Getting single link members for condition \"{group.name}\"",
            add=self.name,
            level=9)

        lm_list = []
        slm_list = []

        for link in group.member_list:
            if not link.processed:
                lm_list.extend(self._get_member_list(link))

        # check all non-processed link members for multiple occurrence
        counted = Counter(lm_list)

        for instance, count in counted.items():
            if count == 1:
                slm_list.append(instance)

        device_log(
            f"Condition \"{group.name}\" has the following single link members \"{slm_list}\"",
            add=self.name,
            level=8)

        if len(slm_list) == 0:
            raise ValueError('It looks like you have a configuration error.')

        return slm_list
Example #26
0
    def _process_links(self, group: GaConditionGroup) -> bool:
        """Will be called to process links inside of a condition.
        Is used recursively.
        :param group: The condition object
        :type group: GaConditionGroup
        :return: Will return the calculated result of the condition
        :rtype: bool
        """
        device_log(f"Processing links for condition \"{group.name}\"",
                   add=self.name,
                   level=8)
        group_member_count = len(group.member_list)
        last_result = None

        for process_nr in range(1, group_member_count + 1):
            link = self._get_link_to_process(group=group)
            result_dict = self._get_link_data(link=link)

            result = self._get_link_data_result(link=link,
                                                result_dict=result_dict)

            self._post_process(link=link,
                               result=result,
                               group=group,
                               process_nr=process_nr,
                               group_member_count=group_member_count)

            link.processed = True
            last_result = result

        device_log(f"Result of condition \"{group.name}\": \"{last_result}\" ",
                   add=self.name,
                   level=6)
        self._reset_flags(group)

        return last_result
Example #27
0
    def _post_process(self, link: GaConditionLink, result: bool,
                      group: GaConditionGroup, process_nr: int,
                      group_member_count: int) -> None:
        """Check which of the two members will be further processed.
        Updating the value of the link to further process to the links result.
        :param link: Link to check the members from
        :type link: GaConditionLink
        :param result: Result of the link
        :type result: bool
        :param group: Condition to which the link belongs
        :type group: GaConditionGroup
        :param process_nr: The links process number
        :type process_nr: int
        :param group_member_count: Number of links in the group
        :type group_member_count: int
        :rtype: None
        :raises: RuntimeError: There is no condition match to further process
        """
        device_log(
            f"Post-process of condition \"{group.name}\", link \"{link.name}\", result \"{result}\", process_nr \"{process_nr}\", group_member_count \"{group_member_count}\"",
            add=self.name,
            level=9)

        slm_list = self._get_single_link_member_list(group=group)
        nslm = False

        for condition in self._get_member_list(link):
            if nslm:
                # there can only be one member to further process
                break

            if condition not in slm_list:
                # if the condition will be further processed -> update its data to the link result
                device_log(
                    f"Updating data of condition \"{condition.name}\" to result \"{result}\"",
                    add=self.name,
                    level=9)

                condition.data = result
                nslm = True

        if process_nr != group_member_count and not nslm:
            # there is no condition that should be further processed and the link is not the last one
            device_log(
                f"Link \"{link.name}\" (id \"{link.object_id}\") has only single members \"{slm_list}\" and is not the last one to process",
                add=self.name,
                level=4)
            self._error(
                RuntimeError(
                    f"Link with id \"{link.object_id}\" is not the last to process but it does not have any link-neighbors."
                ))
Example #28
0
    def get(self) -> list:
        if isinstance(self.instance, self.model_obj):
            self._model()

        elif isinstance(self.instance, self.device_obj):
            self._device(instance=self.instance)

        else:
            device_log(f"Object \"{self.instance.name}\" matches neither provided objects", add=self.name, level=3)

        device_log(f"Object \"{self.instance.name}\" - unfiltered device list to process: \"{self.task_instance_list}\"", add=self.name, level=8)
        filtered_instance_list = area_filter(areas=self.areas, devices=self.task_instance_list)
        device_log(f"Object \"{self.instance.name}\" - filtered device list to process: \"{filtered_instance_list}\"", add=self.name, level=7)

        return filtered_instance_list
Example #29
0
    def _get_script_params(self) -> (None, dict):
        params_dict = {
            'script': self.instance.script,
            'bin': self.instance.script_bin,
            'arg': self.instance.script_arg
        }

        if params_dict['bin'] == 'python3':
            params_dict[
                'bin'] = f'{config.AGENT.path_home}{config.PATH_HOME_VENV}/python3'

        # reverse parameters
        if isinstance(self.instance,
                      GaOutputDevice) and self.instance.reverse == 1:
            if self.instance.active or self.manually:
                if self.reverse:
                    if self.instance.reverse_script is not None:
                        params_dict['script'] = self.instance.reverse_script

                    if self.instance.reverse_script_arg is not None:
                        params_dict['arg'] = self.instance.reverse_script_arg

                    if self.instance.reverse_script_bin is not None:
                        params_dict['bin'] = self.instance.reverse_script_bin

                else:
                    if not self.manually:
                        device_log(
                            f"Device \"{self.instance.name}\" is reversible and active, but should not be reversed",
                            add=self.name,
                            level=4)
                        return None

            else:
                device_log(
                    f"Device \"{self.instance.name}\" is either not reversible or not active",
                    add=self.name,
                    level=7)

        if params_dict[
                'bin'] in config.NONE_RESULTS:  # it should only be possible to be NoneType-None
            device_log(
                f"No binary provided to execute for device \"{self.instance.name}\"",
                add=self.name,
                level=2)
            return None

        return params_dict
Example #30
0
    def start(self) -> bool:
        if self.manually:  # if the action is triggered manually by the user we don't want to check the conditions
            _ = 'stopp' if self.action == 'stop' else self.action
            device_log(
                f"{_.capitalize()}ing \"{self.instance.name}\" manually",
                add=self.name,
                level=5)
            return self._run()

        elif GetGroupResult(group=self.instance).go():
            device_log(f"Conditions for \"{self.instance.name}\" were met",
                       add=self.name,
                       level=6)
            return self._run()

        else:
            device_log(f"Conditions for \"{self.instance.name}\" were not met",
                       add=self.name,
                       level=3)
            return False