From 70bbec4ec7402a32d46d2b4e01be2d136255fb2f Mon Sep 17 00:00:00 2001 From: Dirk Alders Date: Sun, 26 Jan 2020 16:44:32 +0100 Subject: [PATCH] Initial leyk server implementation --- .gitignore | 2 + .gitmodules | 24 +++ geo | 1 + leyk.py | 17 ++ piface_function.py | 479 +++++++++++++++++++++++++++++++++++++++++++++ protocol | 1 + report | 1 + requirements.txt | 2 + socket_protocol | 1 + state_machine | 1 + stringtools | 1 + task | 1 + tcp_socket | 1 + 13 files changed, 532 insertions(+) create mode 100644 .gitmodules create mode 160000 geo create mode 100644 leyk.py create mode 100644 piface_function.py create mode 160000 protocol create mode 160000 report create mode 100644 requirements.txt create mode 160000 socket_protocol create mode 160000 state_machine create mode 160000 stringtools create mode 160000 task create mode 160000 tcp_socket diff --git a/.gitignore b/.gitignore index e61bca2..292800c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ __pycache__/ *.py[cod] *$py.class +config.py + # C extensions *.so diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..4b6fffe --- /dev/null +++ b/.gitmodules @@ -0,0 +1,24 @@ +[submodule "geo"] + path = geo + url = https://git.mount-mockery.de/pylib/geo.git +[submodule "report"] + path = report + url = https://git.mount-mockery.de/pylib/report.git +[submodule "socket_protocol"] + path = socket_protocol + url = https://git.mount-mockery.de/pylib/socket_protocol.git +[submodule "state_machine"] + path = state_machine + url = https://git.mount-mockery.de/pylib/state_machine.git +[submodule "stringtools"] + path = stringtools + url = https://git.mount-mockery.de/pylib/stringtools.git +[submodule "task"] + path = task + url = https://git.mount-mockery.de/pylib/task.git +[submodule "tcp_socket"] + path = tcp_socket + url = https://git.mount-mockery.de/pylib/tcp_socket.git +[submodule "protocol"] + path = protocol + url = https://git.mount-mockery.de/application/protocol_leyk.git diff --git a/geo b/geo new file mode 160000 index 0000000..f59b19e --- /dev/null +++ b/geo @@ -0,0 +1 @@ +Subproject commit f59b19ed1fd2a64735ca477fdb1fba24681e8879 diff --git a/leyk.py b/leyk.py new file mode 100644 index 0000000..7ee5d84 --- /dev/null +++ b/leyk.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- + +import config +import os +import report +import tcp_socket +import protocol +import time + + +if __name__ == '__main__': + report.appLoggingConfigure(os.path.dirname(__file__), 'logfile', config.loggers) + s = tcp_socket.tcp_server_stp(config.server_ip, port=config.server_port) + prot = protocol.my_server_protocol(s, secret=config.secret) + while True: + time.sleep(0.1) diff --git a/piface_function.py b/piface_function.py new file mode 100644 index 0000000..2813309 --- /dev/null +++ b/piface_function.py @@ -0,0 +1,479 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- + +try: + import pifacedigitalio +except ImportError: + pifacedigitalio = None +import geo +import logging +import state_machine +import task +import random +import time + +logger = logging.getLogger('APP') + +geo_position = geo.gps.coordinate(lat=49.976596, lon=9.1481443) + + +class pi_face(object): + LOG_PREFIX = 'PiFace:' + + PF_OUT_NAMES = ['Output 0', # 0 + 'Output 1', # 1 + 'Output 2', # 2 + 'Ploenlein', # 3 + 'Bakery', # 4 + 'Mill', # 5 + 'Reese House', # 6 + 'Bake House'] # 7 + + def __init__(self): + if pifacedigitalio is not None: + pifacedigitalio.init() + pi = pifacedigitalio.PiFaceDigital() + self.__pf_outputs__ = pi.output_pins + self.__pf_output_states__ = 8 * [False] + # + self._reload = 0 + self._task_10ms = task.periodic(0.01 - 0.007, self.task_10ms) + # + for i in range(0, 8): + self.set_output(i, self.__pf_output_states__[i]) + + def set_output(self, index, state, tries=5): + tries = min(max(1, tries), 5) + try_txt = ['1st', '2nd', '3rd', '4th', '5th'] + state = state is True + if pifacedigitalio is not None: + cnt = 0 + while (cnt < tries) and (self.__pf_outputs__[index].value != state): + self.__pf_outputs__[index].value = 1 * state + time.sleep(0.1) + cnt += 1 + if self.__pf_outputs__[index].value != state: + logger.warning('%s Control of output[%d] (%s) after %s try not successfull!', self.LOG_PREFIX, index, self.PF_OUT_NAMES[index], try_txt[cnt - 1]) + else: + logger.info('%s Set output "%s" to %s.', self.LOG_PREFIX, self.PF_OUT_NAMES[index], repr(state)) + self.__pf_output_states__[index] = self.__pf_outputs__[index].value == 1 + else: + logger.info('%s Set virtual output "%s" to %s.', self.LOG_PREFIX, self.PF_OUT_NAMES[index], repr(state)) + self.__pf_output_states__[index] = state + + def get_output(self, index): + return self.__pf_output_states__[index] + + def get_ploenlein(self): + return self.get_output(3) + + def set_bakery(self, state): + self.set_output(4, state) + + def get_bakery(self): + return self.get_output(4) + + def set_mill(self, state): + self.set_output(5, state) + + def get_mill(self): + return self.get_output(5) + + def set_ploenlein(self, state): + self.set_output(3, state) + + def set_reese_house(self, state): + self.set_output(6, state) + + def get_reese_house(self): + return self.get_output(6) + + def set_bake_house(self, state): + if state is True: + self.set_output(7, state) + self._task_10ms.run() + elif state is False: + self._task_10ms.stop() + self.join() + time.sleep(0.1) + self.set_output(7, state) + + def get_bake_house(self): + return not self._task_10ms._stopped + + def task_10ms(self, task_inst): + if self._reload <= 0: + if pifacedigitalio is not None: + self.__pf_outputs__[7].turn_on() + self._reload = random.choice(2 * [0] + 20 * [1] + 1 * [2]) + else: + if pifacedigitalio is not None: + self.__pf_outputs__[7].turn_off() + self._reload -= 1 + + def join(self): + self._task_10ms.join() + + def stop(self): + self._task_10ms.stop() + self._task_10ms.join() + for i in range(0, 8): + self.control_output(i, False) + pifacedigitalio.deinit() + + def __del__(self): + self.stop() + + +class state_machine_mode(state_machine.state_machine): + LOG_PREFIX = 'LeykMode:' + + STATE_AUTOMATIC = 'automatic' + STATE_MANUAL = 'manual' + + CONDITION_EXTERNAL_TRIGGER = 'external_trigger' + + TRANSITIONS = { + STATE_AUTOMATIC: ( + (CONDITION_EXTERNAL_TRIGGER, 1, STATE_MANUAL), + ), + STATE_MANUAL: ( + (CONDITION_EXTERNAL_TRIGGER, 1, STATE_AUTOMATIC), + ), + } + + def __init__(self, **kwargs): + state_machine.state_machine.__init__(self, self.STATE_AUTOMATIC, logging.INFO) + self.__reset_triggers__() + + def __reset_triggers__(self): + self.__to_automatic__ = False + self.__to_manual__ = False + + def external_trigger(self): + rv = False + if self.this_state() == self.STATE_AUTOMATIC: + rv = self.__to_manual__ + elif self.this_state() == self.STATE_MANUAL: + rv = self.__to_automatic__ + self.__reset_triggers__() + return rv + + def trigger_to_maual(self): + if self.this_state() == self.STATE_AUTOMATIC: + self.__to_manual__ = True + return True + return False + + def trigger_to_automatic(self): + if self.this_state() == self.STATE_MANUAL: + self.__to_automatic__ = True + return True + return False + + +class state_machine_day_state(state_machine.state_machine): + LOG_PREFIX = 'LeykState:' + + STATE_IDLE = 'idle' + STATE_WAKE = 'wake' + STATE_SUNRISE = 'sunrise' + STATE_SUNSET = 'sunset' + STATE_SLEEP = 'sleep' + + CONDITION_WAKE = 'condition_wake' + CONDITION_SUNRISE = 'condition_sunrise' + CONDITION_SUNSET = 'condition_sunset' + CONDITION_SLEEP = 'condition_sleep' + CONDITION_IDLE = 'condition_idle' + + TRANSITIONS = { + STATE_IDLE: ( + (CONDITION_WAKE, 1, STATE_WAKE), + (CONDITION_SUNRISE, 1, STATE_SUNRISE), + (CONDITION_SUNSET, 1, STATE_SUNSET), + (CONDITION_SLEEP, 1, STATE_SLEEP), + ), + STATE_WAKE: ( + (CONDITION_SUNRISE, 1, STATE_SUNRISE), + (CONDITION_IDLE, 1, STATE_IDLE), + ), + STATE_SUNRISE: ( + (CONDITION_SUNSET, 1, STATE_SUNSET), + (CONDITION_IDLE, 1, STATE_IDLE), + ), + STATE_SUNSET: ( + (CONDITION_SLEEP, 1, STATE_SLEEP), + (CONDITION_IDLE, 1, STATE_IDLE), + ), + STATE_SLEEP: ( + (CONDITION_WAKE, 1, STATE_WAKE), + (CONDITION_IDLE, 1, STATE_IDLE), + ), + } + + def __init__(self, **kwargs): + state_machine.state_machine.__init__(self, self.STATE_IDLE, logging.INFO, **kwargs) + + def __current_state_calc__(self): + def wake_time(): + tm = time.localtime() + tm = list(tm) + tm[3] = 6 # tm_hour + tm[4] = 0 # tm_min + tm[5] = 0 # tm_sec=0 + return time.mktime(time.struct_time(tm)) + + def sunrise_time(): + return time.mktime(geo.sun.sunrise(geo_position)) + 30 * 60 + + def sunset_time(): + return time.mktime(geo.sun.sunset(geo_position)) - 30 * 60 + + def sleep_time(): + tm = time.localtime() + tm = list(tm) + tm[3] = 21 # tm_hour + tm[4] = 15 # tm_min + tm[5] = 0 # tm_sec=0 + return time.mktime(time.struct_time(tm)) + + now = time.mktime(time.localtime()) + if now > sleep_time(): + return self.STATE_SLEEP + elif now > sunset_time(): + return self.STATE_SUNSET + elif now > sunrise_time(): + return self.STATE_SUNRISE + elif now > wake_time(): + return self.STATE_WAKE + else: + return self.STATE_IDLE + + def condition_wake(self): + if self.condition_idle(): + return False + return self.__current_state_calc__() == self.STATE_WAKE + + def condition_sunrise(self): + if self.condition_idle(): + return False + return self.__current_state_calc__() == self.STATE_SUNRISE + + def condition_sunset(self): + if self.condition_idle(): + return False + return self.__current_state_calc__() == self.STATE_SUNSET + + def condition_sleep(self): + if self.condition_idle(): + return False + return self.__current_state_calc__() == self.STATE_SLEEP + + def condition_idle(self): + return not self.sm_mode.this_state_is(self.sm_mode.STATE_AUTOMATIC) + + def report_sunset_sunrise(self): + state_machine.logger.debug('Sunrise: %s - Sunset: %s;', time.strftime("%H:%M", geo.sun.sunrise(geo_position)), time.strftime("%H:%M", geo.sun.sunset(geo_position)) +) + + +class leyk(object): + LOG_PREFIX = 'Leyk:' + + def __init__(self): + self.sm_mode = state_machine_mode() + self.sm_day_state = state_machine_day_state(sm_mode=self.sm_mode) + self.__pf__ = pi_face() + self._queue = task.threaded_queue() + self._queue.run() + self._task_1s = task.periodic(1, self.task_1s) + self._task_1s.run() + self.sm_mode.register_state_change_callback(self.sm_mode.STATE_MANUAL, None, self._queue.clean_queue) + self.sm_day_state.register_state_change_callback(self.sm_day_state.STATE_SLEEP, None, self.sm_day_state.report_sunset_sunrise) + self.sm_day_state.register_state_change_callback(self.sm_day_state.STATE_SLEEP, None, self.fill_sleep_queue) + self.sm_day_state.register_state_change_callback(self.sm_day_state.STATE_SUNRISE, None, self.sm_day_state.report_sunset_sunrise) + self.sm_day_state.register_state_change_callback(self.sm_day_state.STATE_SUNRISE, None, self.fill_sunrise_queue) + self.sm_day_state.register_state_change_callback(self.sm_day_state.STATE_SUNSET, None, self.sm_day_state.report_sunset_sunrise) + self.sm_day_state.register_state_change_callback(self.sm_day_state.STATE_SUNSET, None, self.fill_sunset_queue) + self.sm_day_state.register_state_change_callback(self.sm_day_state.STATE_WAKE, None, self.sm_day_state.report_sunset_sunrise) + self.sm_day_state.register_state_change_callback(self.sm_day_state.STATE_WAKE, None, self.fill_wake_queue) + + def set_mode(self, mode): + if mode == self.sm_mode.STATE_AUTOMATIC: + return self.sm_mode.trigger_to_automatic() + elif mode == self.sm_mode.STATE_MANUAL: + return self.sm_mode.trigger_to_maual() + else: + return False + + + def get_mode(self): + return self.sm_mode.this_state() + + def get_state(self): + return self.sm_day_state.this_state() + + def __queue_wrapper__(self, queue_inst, function, *args, **kwargs): + function(*args, **kwargs) + + def set_ploenlein(self, state): + if self.sm_mode.this_state_is(self.sm_mode.STATE_MANUAL): + self.__pf__.set_ploenlein(state) + return True + else: + return False + + def get_ploenlein(self): + return self.__pf__.get_ploenlein() + + def set_bakery(self, state): + if self.sm_mode.this_state_is(self.sm_mode.STATE_MANUAL): + self.__pf__.set_bakery(state) + return True + else: + return False + + def get_bakery(self): + return self.__pf__.get_bakery() + + def set_bake_house(self, state): + if self.sm_mode.this_state_is(self.sm_mode.STATE_MANUAL): + self.__pf__.set_bake_house(state) + return True + else: + return False + + def get_bake_house(self): + return self.__pf__.get_bake_house() + + def set_mill(self, state): + if self.sm_mode.this_state_is(self.sm_mode.STATE_MANUAL): + self.__pf__.set_mill(state) + return True + else: + return False + + def get_mill(self): + return self.__pf__.get_mill() + + def set_reese_house(self, state): + if self.sm_mode.this_state_is(self.sm_mode.STATE_MANUAL): + self.__pf__.set_reese_house(state) + return True + else: + return False + + def get_reese_house(self): + return self.__pf__.get_reese_house() + + def wake_time(self): + tm = time.localtime() + tm = list(tm) + tm[3] = 6 # tm_hour + tm[4] = 0 # tm_min + tm[5] = 0 # tm_sec=0 + return time.mktime(time.struct_time(tm)) + + def sunrise_time(self): + return time.mktime(geo.sun.sunrise(geo_position)) + 30 * 60 + + def sunset_time(self): + return time.mktime(geo.sun.sunset(geo_position)) - 30 * 60 + + def sleep_time(self): + tm = time.localtime() + tm = list(tm) + tm[3] = 21 # tm_hour + tm[4] = 15 # tm_min + tm[5] = 0 # tm_sec=0 + return time.mktime(time.struct_time(tm)) + + def identify_current_state(self): + now = time.mktime(time.localtime()) + if now > self.sleep_time(): + return self.ST_SLEEP + elif now > self.sunset_time(): + return self.ST_SUNSET + elif now > self.sunrise_time(): + return self.ST_SUNRISE + elif now > self.wake_time(): + return self.ST_WAKE + else: + return self.ST_SLEEP + + def wait(self, queue_inst, delay): + if delay > 0: + logger.debug('%s Wait for %d seconds initiated. %d elements left in queue.', self.LOG_PREFIX, delay, queue_inst.qsize()) + cnt = 0 + while cnt < delay * 5: + if queue_inst.qsize() == 0: + logger.debug('%s Quit wait for %d seconds. %d elements left in queue.', self.LOG_PREFIX, delay, queue_inst.qsize()) + break + time.sleep(0.2) + cnt += 1 + + def fill_wake_queue(self): + no_delay = self.sm_day_state.previous_state_was(self.sm_day_state.STATE_IDLE) + # WAKE + self._queue.enqueue(1, self.__queue_wrapper__, self.__pf__.set_bakery, True) + self._queue.enqueue(2, self.wait, 0 if no_delay else 5 * 60) + self._queue.enqueue(3, self.__queue_wrapper__, self.__pf__.set_bake_house, True) + self._queue.enqueue(4, self.wait, 0 if no_delay else 10 * 60) + self._queue.enqueue(5, self.__queue_wrapper__, self.__pf__.set_reese_house, True) + self._queue.enqueue(6, self.wait, 0 if no_delay else 7 * 60) + self._queue.enqueue(7, self.__queue_wrapper__, self.__pf__.set_ploenlein, True) + self._queue.enqueue(8, self.wait, 0 if no_delay else 8 * 60) + self._queue.enqueue(9, self.__queue_wrapper__, self.__pf__.set_mill, True) + + def fill_sunrise_queue(self): + no_delay = self.sm_day_state.previous_state_was(self.sm_day_state.STATE_IDLE) + # SUNRISE + self._queue.enqueue(1, self.__queue_wrapper__, self.__pf__.set_bake_house, True) + self._queue.enqueue(2, self.__queue_wrapper__, self.__pf__.set_ploenlein, False) + self._queue.enqueue(3, self.wait, 0 if no_delay else 5 * 60) + self._queue.enqueue(4, self.__queue_wrapper__, self.__pf__.set_mill, False) + self._queue.enqueue(5, self.wait, 0 if no_delay else 8 * 60) + self._queue.enqueue(6, self.__queue_wrapper__, self.__pf__.set_bakery, False) + self._queue.enqueue(7, self.wait, 0 if no_delay else 12 * 60) + self._queue.enqueue(8, self.__queue_wrapper__, self.__pf__.set_reese_house, False) + + def fill_sunset_queue(self): + no_delay = self.sm_day_state.previous_state_was(self.sm_day_state.STATE_IDLE) + # SUNSET + self._queue.enqueue(1, self.__queue_wrapper__, self.__pf__.set_bake_house, True) + self._queue.enqueue(2, self.__queue_wrapper__, self.__pf__.set_bakery, True) + self._queue.enqueue(3, self.wait, 0 if no_delay else 10 * 60) + self._queue.enqueue(4, self.__queue_wrapper__, self.__pf__.set_reese_house, True) + self._queue.enqueue(5, self.wait, 0 if no_delay else 8 * 60) + self._queue.enqueue(6, self.__queue_wrapper__, self.__pf__.set_mill, True) + self._queue.enqueue(7, self.wait, 0 if no_delay else 7 * 60) + self._queue.enqueue(8, self.__queue_wrapper__, self.__pf__.set_ploenlein, True) + + def fill_sleep_queue(self): + no_delay = self.sm_day_state.previous_state_was(self.sm_day_state.STATE_IDLE) + # SLEEP + self._queue.enqueue(1, self.__queue_wrapper__, self.__pf__.set_bake_house, False) + self._queue.enqueue(2, self.wait, 0 if no_delay else 5 * 60) + self._queue.enqueue(3, self.__queue_wrapper__, self.__pf__.set_bakery, False) + self._queue.enqueue(4, self.wait, 0 if no_delay else 9 * 60) + self._queue.enqueue(5, self.__queue_wrapper__, self.__pf__.set_ploenlein, False) + self._queue.enqueue(6, self.wait, 0 if no_delay else 9 * 60) + self._queue.enqueue(7, self.__queue_wrapper__, self.__pf__.set_mill, False) + self._queue.enqueue(8, self.wait, 0 if no_delay else 6 * 60) + self._queue.enqueue(9, self.__queue_wrapper__, self.__pf__.set_reese_house, False) + + def task_1s(self, task_inst): + self.sm_mode.work() + self.sm_day_state.work() + + def join(self): + self._task_1s.join() + self._queue.join() + + def stop(self): + self._task_1s.stop() + self._queue.stop() + + def __del__(self): + self.stop() diff --git a/protocol b/protocol new file mode 160000 index 0000000..6ac1b58 --- /dev/null +++ b/protocol @@ -0,0 +1 @@ +Subproject commit 6ac1b584fb53aeb58c1b9809fff306a56dd32400 diff --git a/report b/report new file mode 160000 index 0000000..98fea3a --- /dev/null +++ b/report @@ -0,0 +1 @@ +Subproject commit 98fea3a4d45ba906d08a8ebc90525fb3592f06be diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..5de72e5 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +pifacecommon +pifacedigitalio diff --git a/socket_protocol b/socket_protocol new file mode 160000 index 0000000..127ef51 --- /dev/null +++ b/socket_protocol @@ -0,0 +1 @@ +Subproject commit 127ef51719f2fdbc6699f7e0751f9d92f6773f0f diff --git a/state_machine b/state_machine new file mode 160000 index 0000000..ddf94e9 --- /dev/null +++ b/state_machine @@ -0,0 +1 @@ +Subproject commit ddf94e9881ae211dcbab9d6e1ba1ac3d7b8919c4 diff --git a/stringtools b/stringtools new file mode 160000 index 0000000..b86248c --- /dev/null +++ b/stringtools @@ -0,0 +1 @@ +Subproject commit b86248c1a3a3456e489e007857271106e1ecf090 diff --git a/task b/task new file mode 160000 index 0000000..d3fbc59 --- /dev/null +++ b/task @@ -0,0 +1 @@ +Subproject commit d3fbc5934051ab165723ca37e8f02596329f174a diff --git a/tcp_socket b/tcp_socket new file mode 160000 index 0000000..62f13e2 --- /dev/null +++ b/tcp_socket @@ -0,0 +1 @@ +Subproject commit 62f13e2d878b09b81d094c2f77dbd830d72be2c7