Exemple #1
0
    def setUp(self):
        self.clock = Clock()

        el_id, _type, name, mod_id, reg_id = 0, 1, '', 0, 0
        self.output_element = OutputElement(el_id, _type, name, mod_id, reg_id)

        ef_id, value, _time = 0, 1, 1
        self.effect = Effect(ef_id, self.output_element, value, _time)
Exemple #2
0
class TestEffect(unittest.TestCase):

    def setUp(self):
        self.clock = Clock()

        el_id, _type, name, mod_id, reg_id = 0, 1, '', 0, 0
        self.output_element = OutputElement(el_id, _type, name, mod_id, reg_id)

        ef_id, value, _time = 0, 1, 1
        self.effect = Effect(ef_id, self.output_element, value, _time)

    def test_start(self):
        # when cause time is 1000ms
        self.effect.start(1000)

        # then effects cause time should be set to 1000ms and out_el value should be saved
        self.assertEqual(self.effect.cause_time, 1000)
        self.assertEqual(self.effect.prev_value, self.output_element.value)

    def test_run_cause_is_before_effect_time(self):
        # given effect time 1ms after cause
        self.effect.time = 1

        # when
        self.effect.start(self.clock.get_millis())
        time.sleep(0.001)

        # then
        self.assertTrue(self.effect.run())  # effect should happen
        self.assertEqual(self.output_element.desired_value, self.effect.value)

    def test_run_more_times(self):
        # given
        self.effect.time = 0
        # when
        self.effect.start(self.clock.get_millis())
        time.sleep(0.001)

        # then Effect should happen only once
        self.assertTrue(self.effect.run())  # effect should happen
        self.assertFalse(self.effect.run())  # effect should not happen
        self.assertFalse(self.effect.run())  # effect should not happen

    def test_revert_set(self):

        # when normal effect flow
        self.effect.start(self.clock.get_millis())
        time.sleep(0.001)
        self.effect.run()
        self.effect.revert()

        # then output_element desired value should be reverted
        self.assertEqual(self.output_element.desired_value, self.effect.prev_value)
Exemple #3
0
class TestEffect(unittest.TestCase):
    def setUp(self):
        self.clock = Clock()

        el_id, _type, name, mod_id, reg_id = 0, 1, '', 0, 0
        self.output_element = OutputElement(el_id, _type, name, mod_id, reg_id)

        ef_id, value, _time = 0, 1, 1
        self.effect = Effect(ef_id, self.output_element, value, _time)

    def test_start(self):
        # when cause time is 1000ms
        self.effect.start(1000)

        # then effects cause time should be set to 1000ms and out_el value should be saved
        self.assertEqual(self.effect.cause_time, 1000)
        self.assertEqual(self.effect.prev_value, self.output_element.value)

    def test_run_cause_is_before_effect_time(self):
        # given effect time 1ms after cause
        self.effect.time = 1

        # when
        self.effect.start(self.clock.get_millis())
        time.sleep(0.001)

        # then
        self.assertTrue(self.effect.run())  # effect should happen
        self.assertEqual(self.output_element.desired_value, self.effect.value)

    def test_run_more_times(self):
        # given
        self.effect.time = 0
        # when
        self.effect.start(self.clock.get_millis())
        time.sleep(0.001)

        # then Effect should happen only once
        self.assertTrue(self.effect.run())  # effect should happen
        self.assertFalse(self.effect.run())  # effect should not happen
        self.assertFalse(self.effect.run())  # effect should not happen

    def test_revert_set(self):

        # when normal effect flow
        self.effect.start(self.clock.get_millis())
        time.sleep(0.001)
        self.effect.run()
        self.effect.revert()

        # then output_element desired value should be reverted
        self.assertEqual(self.output_element.desired_value,
                         self.effect.prev_value)
Exemple #4
0
    def setUp(self):
        self.clock = Clock()

        el_id, _type, name, mod_id, reg_id = 0, 1, '', 0, 0
        self.output_element = OutputElement(el_id, _type, name, mod_id, reg_id)

        ef_id, value, _time = 0, 1, 1
        self.effect = Effect(ef_id, self.output_element, value, _time)
Exemple #5
0
def system_loader():
    """Loads all necessary objects for system operation from database
    Configures system and returns system named tuple"""

    system = namedtuple("System",
                        ["clock",
                         "elements",
                         "modules",
                         "dependancies",
                         "regulations"])

    system.elements = namedtuple("elements", ['all', 'input', 'output'])
    system.modules = namedtuple("modules", ['all', 'input', 'output'])
    system.modules.input = namedtuple("input_modules", ['input_board', 'ambient_board'])
    system.modules.output = namedtuple("input_modules", ['output_board', 'led_light_board'])

    system.clock = Clock()  # instantiate global clock. Reference is stored in clock.py

    db = create_db_object()
    db.load_objects_from_table(InputElement)
    db.load_objects_from_table(OutputElement)
    db.load_objects_from_table(Blind)

    db.load_objects_from_table(OutputBoard)
    db.load_objects_from_table(LedLightBoard)
    db.load_objects_from_table(AmbientBoard)
    db.load_objects_from_table(InputBoard)

    db.load_objects_from_table(Dependancy)
    db.load_objects_from_table(Regulation)

    for element in Element.items.values():
        module = Module.items[element.module_id]
        module.elements[element.reg_id] = element  # pass elements to modules registers

    # pair blinds so two motors of the same blinds never turn on both. It would cause shortcut!
    for blind in Blind.items.values():
        other_blind_id = blind.other_blind
        blind.other_blind = Blind.items[other_blind_id]

    for dep in Dependancy.items.values():
        dep._parse_cause(all_elements=Element.items)

    for reg in Regulation.items.values():
        InputElement.items[reg.feed_el_id].subscribe(reg)

    system.elements.all = Element.items
    system.elements.input = InputElement.items
    system.elements.output = OutputElement.items
    system.modules.input = InputModule.items
    system.modules.output = OutputModule.items
    system.dependancies = Dependancy.items
    system.regulations = Regulation.items

    return system
Exemple #6
0
class Effect:

    clock = Clock()

    def __init__(self, _id, out_el, value, _time):
        self.id = _id
        self.output_element = out_el
        self.value = value
        self.prev_value = None
        self.time = _time
        self.priority = 5

        self.done = True  # Should the effect be started. if not done it should be started
        self.cause_time = None  # Time when cause was True

    def start(self, milis):
        """Api for notifing effect that couse changed to True and at what time"""
        self.cause_time = milis
        self.done = False
        self.prev_value = self.output_element.value

    def run(self, ):
        """Sets desired value of element if it was not set yet and if the time is right"""
        if not self.done:
            if self.clock.get_millis() - self.cause_time >= self.time:
                self.done = self.output_element.set_desired_value(
                    self.value, self.priority, set_flag=True)
                return True
        return False

    def revert(self):
        """Reverts effect. Sets output_element previous value"""
        if self.done:  # efect can be reverted only once when it is done
            self.output_element.desired_value = self.prev_value

    def __repr__(self, ):
        return "El id: {} done: {}".format(self.el_id, self.done)
Exemple #7
0
class Module(BaseComponent):
    """Base class for all modules.
    It implements prototype of command that decorates all read and write functions"""

    table_name = 'modules'

    types = {Mt.led_light, Mt.output, Mt.ambient,
             Mt.input}  # Needed for loading objects from database
    ID = 0
    start_timeout = 10  # timeout in ms
    clock = Clock()
    items = {}

    def __init__(self, *args):

        super().__init__(args[0], Mt(args[1]), args[2])
        Module.items[self.id] = self
        self.ports = {
        }  # Module's physical ports. Dictionary stores elements during configuration so not to connect elment twice to same port
        self.elements = {
        }  # Module's elements. Keys are registers in which elements values are going to be stored
        self.modbus = ModbusNetwork(
        )  # Reference to modbus. Modbus is a singleton.

        self.available = True  # Flag indicating if there is communication with module
        self.last_timeout = 0
        self.timeout = Module.start_timeout
        self.max_timeout = 2
        self.correct_trans_num = 0
        self.transmission_num = 0
        self.courupted_trans_num = 0

    def is_available(self, ):
        """Checks if module is available.
        If it is not but timeout expired it makes module
        available so there would be communication trial"""
        if self.available:
            self.timeout = Module.start_timeout
            return True

        else:
            current_time = self.clock.get_millis()
            if current_time - self.last_timeout >= self.timeout:
                self.last_timeout = current_time
                self.available = True
                return True
        return False

    @staticmethod
    def command(func):
        """Decorator for all modbus commands. It counts correct and corupted transmisions.
            It sets timeout if the transmission was corrupted """
        @wraps(func)
        def func_wrapper(self, ):
            self.transmission_num += 1
            result = func(self)

            if result:  # if there is response from module
                self.correct_trans_num += 1
                return result
            else:
                self.available = False
                self.courupted_trans_num += 1
                if self.timeout <= self.max_timeout:
                    self.timeout *= 2  # Increase timeout
                # TODO notification about module failures
                return result

        return func_wrapper

    def check_port_range(self, port):
        if port > self.num_of_ports - 1 or port < 0:
            raise AddElementError('Port: ' + str(port) + ' out of range')

    def check_port_usage(self, port):
        try:
            self.ports[port]
            raise AddElementError('Port: ' + str(port) + ' is in use')
        except KeyError:
            pass

    def check_element_type(self, element):
        if element.type not in self.accepted_elements:
            raise AddElementError('Element: ' + element.type.name +
                                  ' is not valid for ' + self.type.name)

    def check_if_element_connected(self, element):
        if element.module_id and element.module_id != self.id and element.type != Et.blind:  # roleta moze byc podlaczona 2 razy do jednego modulu - gora i dol
            raise AddElementError('Element: ' + str(element.id) +
                                  ' already connected to ' +
                                  str(element.module_id))

    def add_element(self, port, element):
        self.check_element_type(element)
        self.check_port_range(port)
        self.check_port_usage(port)
        self.check_if_element_connected(element)

        self.ports[port] = element
        element.reg_id = port
        element.module_id = self.id
        self.elements[element.id] = element
Exemple #8
0
class Dependancy:

    table_name = 'dependancy'

    column_headers_and_types = [['id', 'integer primary key'],
                                ['name', 'text'], ['dep_str', 'text']]

    cond_start = '['  # flag of condition start
    cond_stop = ']'
    effect_start_t = '{'  # flag of effect start
    effect_stop_t = '}'
    cond_marker = '!'  # flag used by cause parser to mark conditions places.
    cause_effect_sep = 'then'  # separates cause and effects
    time_marker = 't'
    day_marker = 'd'
    element_marker = 'e'

    day_dict = {
        'mon': 0,
        'tue': 1,
        'wed': 2,
        'thu': 3,
        'fri': 4,
        'sat': 5,
        'sun': 6,
    }
    clock = Clock()
    items = {}

    def __init__(self, _id, name, dep_str):
        self.id = _id
        Dependancy.items[self.id] = self

        self.name = name
        self.dep_str = dep_str
        self.conditions = []  # Conditions which make the cause
        self.effects = [
        ]  # Effects which will happen after condition changes from False to True

        self.num_of_conds = 0
        self.num_of_effect = 0

        self.prev_cause_result = False

        self.cause_str, self.effects_str = dep_str.split(
            Dependancy.cause_effect_sep)
        self.cause_template = ''  # evaluated conditions are applied to it. Finnally template goes to eval()

    def run(self, ):
        """Evaluates cause. If it is true and prev result is false it notifies all efects.
       When the cause changes from True to false it restores effects to initial state """

        cause_result = self._evaluate_cause()

        if cause_result and not self.prev_cause_result:  # if the cause changes from False to True
            for effect in self.effects:
                effect.start(self.clock.get_millis())

        # if the cause changes from True to False effects should be undone
        if not cause_result and self.prev_cause_result:
            for effect in self.effects:
                effect.revert()

        self.prev_cause_result = cause_result

        for effect in self.effects:
            effect.run()  # perform effect

    def _evaluate_cause(self):
        eval_cause_string = ''
        condition_num = 0
        for s in self.cause_template:  # Evaluate all conditions and put their results into eval strin
            if s == Dependancy.cond_marker:
                eval_cause_string += self.conditions[condition_num].evaluate()
                condition_num += 1
            else:
                eval_cause_string += s

        return eval(eval_cause_string)

    def _parse_cause(self, all_elements=dict()):
        """Parses cause string"""

        for condition in self._find_condition():

            element, op, comp_value = self._parse_condition(condition)

            if element[0] == Dependancy.element_marker:
                element_id = int(element[1:])

                if element_id not in all_elements:
                    raise DependancyConfigError('Element does not exists: ' +
                                                str(element_id))
                comp_value = int(comp_value)
                subscribe = all_elements[element_id].subscribe

            if element[0] == Dependancy.day_marker:
                comp_value = comp_value.split(',')
                comp_value = [Dependancy.day_dict[day] for day in comp_value]
                subscribe = self.clock.subscribe_for_weekday

            if element[0] == Dependancy.time_marker:
                comp_value = comp_value.split(':')
                comp_value = [int(val) for val in comp_value]
                subscribe = self.clock.subscribe_for_minute

            condition = Condition(self.num_of_conds, op, comp_value)
            self.num_of_conds += 1
            subscribe(condition)
            self.conditions.append(condition)

    def _find_condition(self):
        """Yields condition and updates cause template"""
        condition = ''

        is_condition = False
        for s_pos, s in enumerate(self.cause_str):

            if s == Dependancy.cond_start:
                is_condition = True
                self.cause_template += Dependancy.cond_marker

            if is_condition:
                condition += s
            else:
                self.cause_template += s

            if s == Dependancy.cond_stop:
                yield condition[
                    1:
                    -1]  # First and last char are flags of begining and end of condition
                is_condition = False
                condition = ''

    @staticmethod
    def _parse_condition(condition):
        """Creates condition objects based on condition string"""

        op_pos = 0
        op = None  # operator
        for s_pos, s in enumerate(condition):
            if s in Condition.operator_dict.keys():
                op_pos = s_pos
                op = s
                break

        element = condition[:op_pos]
        comp_value = condition[op_pos + 1:]

        if op not in Condition.operator_dict.keys():
            raise DependancyConfigError(
                "Condition has wrong operator: {}".format(op))

        return element, op, comp_value

    def _parse_effects(self, output_elements=None):
        """Creates effect objects based on effect string"""

        effects_array = self.effects_str.strip().rstrip(';').split(';')
        for effect_str in effects_array:

            element_id, set_value, _time = self._parse_effect(effect_str)

            if element_id not in output_elements.keys():
                raise DependancyConfigError('Output element: ' +
                                            str(element_id) +
                                            ' not in output elements')

            effect = Effect(self.num_of_effect, output_elements[element_id],
                            set_value, _time)
            self.num_of_effect += 1
            self.effects.append(effect)

    @staticmethod
    def _parse_effect(effect_str):

        effect_str = effect_str.strip()
        op_pos = 0
        time_pos = None
        _time = ''
        is_time = False
        for s_pos, s in enumerate(effect_str):
            if s == '=':
                op_pos = s_pos

            if s == Dependancy.effect_start_t:
                time_pos = s_pos
                is_time = True

            if is_time:
                _time += s

            if s == Dependancy.effect_stop_t:
                is_time = False

        try:
            element_id = int(effect_str[1:op_pos])
            set_value = int(effect_str[op_pos + 1:time_pos])
            _time = int(
                effect_str[time_pos + 1:-1]
            ) * 1000  # First and last char are flags of begining and end of time
        except:
            raise DependancyConfigError(
                'Effect parsing error. Effect string: {}'.format(effect_str))

        if set_value < 0:
            raise DependancyConfigError(
                'Set value cant be negative. Effect string: {}'.format(
                    effect_str))

        if _time < 0:
            raise DependancyConfigError(
                'Time cant be negative. Effect string: {}'.format(effect_str))

        return element_id, set_value, _time

    def __str__(self, ):
        return "".join([
            "ID: ",
            str(self.id), "\ttype: ", "\tname: ", self.name, '\tdep_str: ',
            self.dep_str
        ])

    def __repr__(self):
        return "".join(["ID: ", str(self.id), " - ", self.name])
Exemple #9
0
class LogicManager(threading.Thread):

    clock = Clock()

    def __init__(
            self,
            args=(),
    ):
        threading.Thread.__init__(self, group=None, target=None, name='LOGIC')
        self._comunication_out_buffer = args[0]
        self._comunication_in_buffer = args[1]
        system = args[2]
        self.elements = system.elements.all
        self.output_elements = system.elements.output
        self.output_modules = system.modules.output
        self.regulations = system.regulations
        self.dependancies = system.dependancies

        self._client_priority = 5
        self.logger = logging.getLogger('LOGIC')
        self.tasks = queue.Queue()

    def set_desired_value(self, _type, _id, value, _msg):
        """Sets elements desired values"""
        if _type == 'e':
            set_flag = False
            if value > 0:
                set_flag = True
            self.output_elements[_id].set_desired_value(
                value, self._client_priority,
                set_flag)  # Client has low priority.
            self.logger.debug('Set desired value el: %s val: %s', _id, value)
        elif _type == 'r':
            self.regulations[_id].set_point = value
            self._comunication_in_buffer.put(
                _msg)  # Ack that regulation was set
            # self.logger.debug(self.output_elements.str())

    def process_input_communication(self, ):
        """Checks if there are any commands from client. """
        def parse_msg(msg):
            try:
                msg = msg.split(',')
                _type = msg[0][0]
                _id = int(msg[0][1:])
                value = int(msg[1])
            except:
                return None
            return _type, _id, value

        while not self._comunication_out_buffer.empty():
            msg = self._comunication_out_buffer.get()
            self.logger.debug(msg)
            _type, _id, value = parse_msg(msg)
            if not msg:
                yield None
            yield _type, _id, value, msg

    def _check_elements_values_and_notify(self, ):
        """Check elements new value flags which are set by modbus.
        If there are new values notify interested components and put message to communication thread"""
        for element in self.elements.values():
            if element.new_val_flag:
                self.logger.debug(element)
                element.notify_objects(
                )  # Notifies objects which are interested
                element.new_val_flag = False
                if element.type in (Et.pir, Et.rs, Et.switch, Et.heater,
                                    Et.blind):
                    msg = 'e' + str(element.id) + ',' + str(
                        element.value) + ',' + 's'
                else:
                    msg = 'e' + str(element.id) + ',' + str(element.value)
                yield msg

    def _run_relations(self, ):
        """Runs dependancies and regulations"""

        for dep in self.dependancies.values():
            dep.run()

        for reg in self.regulations.values():
            reg.run()

    def _generate_new_tasks(self, ):
        """Generates queue with modules which have elements with changed value"""
        modules_to_notify = set()
        for out_element in self.output_elements.values():
            if out_element.value != out_element.desired_value:
                modules_to_notify.add(
                    self.output_modules[out_element.module_id])

        while modules_to_notify:
            self.tasks.put(modules_to_notify.pop())

    def run(self, ):
        """Main logic loop"""
        self.logger.info('Thread {} start'.format(self.name))
        while True:
            self.clock.evaluate_time()

            for msg in self.process_input_communication():
                self.set_desired_value(*msg)

            for ack_msg in self._check_elements_values_and_notify():
                self._comunication_in_buffer.put(ack_msg)

            self._run_relations()
            self._generate_new_tasks()
            time.sleep(0.1)
Exemple #10
0
class TestClock(unittest.TestCase):
    def setUp(self):
        self.clock = Clock()
        self.clock.restart()

    def test_get_seconds(self):
        self.assertAlmostEqual(self.clock.get_seconds(), 0, 3)

    def test_get_milis(self):
        time.sleep(0.001)
        self.assertAlmostEqual(self.clock.get_millis(), 1, 0)

    def test_subscribe(self):

        notifiables = [Notifiable() for _ in range(3)]

        for notifiable in notifiables:
            self.clock.subscribe_for_weekday(notifiable)
            self.clock.subscribe_for_minute(notifiable)

        for notifiable in notifiables:
            self.assertIn(notifiable, self.clock.objects_to_notify_weekday)
            self.assertIn(notifiable, self.clock.objects_to_notify_time)

    def test_evaluate_time(self):

        self.clock.evaluate_time()
        self.assertEqual(self.clock.now.minute, self.clock.minute)
        self.assertEqual(self.clock.now.weekday(), self.clock.weekday)

    def test_notification(self):

        notifiable_minute = Notifiable()
        notifiable_weekday = Notifiable()

        self.clock.subscribe_for_minute(notifiable_minute)
        self.clock.subscribe_for_weekday(notifiable_weekday)

        self.clock.now = datetime.datetime.now()
        self.clock.weekday = 6

        self.clock.notify_minute()
        self.clock.notify_weekday()

        self.assertEqual(notifiable_minute.val, self.clock.now)
        self.assertEqual(notifiable_weekday.val, self.clock.weekday)
Exemple #11
0
 def setUp(self):
     self.clock = Clock()
     self.clock.restart()
Exemple #12
0
    def setUp(self):
        self.clock = Clock()
        dep_id, name = 0, ''
        dep_str = '[e1=2] and [e2=3] and [d=mon] and [t=5:30] then e3=20{0}; e3=0{200}; e4=1{0}'

        self.dep = Dependancy(dep_id, name, dep_str)
Exemple #13
0
class TestClock(unittest.TestCase):

    def setUp(self):
        self.clock = Clock()
        self.clock.restart()

    def test_get_seconds(self):
        self.assertAlmostEqual(self.clock.get_seconds(), 0, 3)

    def test_get_milis(self):
        time.sleep(0.001)
        self.assertAlmostEqual(self.clock.get_millis(), 1, 0)

    def test_subscribe(self):

        notifiables = [Notifiable() for _ in range(3)]

        for notifiable in notifiables:
            self.clock.subscribe_for_weekday(notifiable)
            self.clock.subscribe_for_minute(notifiable)

        for notifiable in notifiables:
            self.assertIn(notifiable, self.clock.objects_to_notify_weekday)
            self.assertIn(notifiable, self.clock.objects_to_notify_time)

    def test_evaluate_time(self):

        self.clock.evaluate_time()
        self.assertEqual(self.clock.now.minute, self.clock.minute)
        self.assertEqual(self.clock.now.weekday(), self.clock.weekday)

    def test_notification(self):

        notifiable_minute = Notifiable()
        notifiable_weekday = Notifiable()

        self.clock.subscribe_for_minute(notifiable_minute)
        self.clock.subscribe_for_weekday(notifiable_weekday)

        self.clock.now = datetime.datetime.now()
        self.clock.weekday = 6

        self.clock.notify_minute()
        self.clock.notify_weekday()

        self.assertEqual(notifiable_minute.val, self.clock.now)
        self.assertEqual(notifiable_weekday.val, self.clock.weekday)
Exemple #14
0
 def setUp(self):
     self.clock = Clock()
     self.clock.restart()