From 35d1aa796c158e373f723aff20403d703aea92d1 Mon Sep 17 00:00:00 2001 From: Thomas Klaehn Date: Sat, 25 Aug 2018 11:15:45 +0200 Subject: [PATCH] gate: add 2nd. gate Signed-off-by: Thomas Klaehn --- ._gitlab-ci.yml | 27 --- gate_guard.service => chickenhouse.service | 0 gate_guard/gate.py | 219 +++++++++++++++------ scripts/create_release_script.py | 64 ------ scripts/deploy_release.sh | 20 -- scripts/pylint_wrapper.py | 26 --- setup.py | 24 +++ sonar-project.properties | 11 -- 8 files changed, 185 insertions(+), 206 deletions(-) delete mode 100644 ._gitlab-ci.yml rename gate_guard.service => chickenhouse.service (100%) delete mode 100755 scripts/create_release_script.py delete mode 100755 scripts/deploy_release.sh delete mode 100755 scripts/pylint_wrapper.py create mode 100644 setup.py delete mode 100644 sonar-project.properties diff --git a/._gitlab-ci.yml b/._gitlab-ci.yml deleted file mode 100644 index 8dd7e0d..0000000 --- a/._gitlab-ci.yml +++ /dev/null @@ -1,27 +0,0 @@ -before_script: - - "echo $CI_BUILD_ID" - - "echo $CI_BUILD_REF_NAME" - -stages: - - test - - release - -tests: - stage: test - script: - - "python scripts/pylint_wrapper.py -s gate_guard -s tests" - - "nosetests --with-coverage --cover-package=gate_guard --cover-xml" - - "nosetests --with-xunit tests/unittests/" - - "sonar-runner" - -releases: - stage: release - script: - - "python scripts/create_release_script.py" - - "python setup.py sdist" - - "scripts/deploy_release.sh" - only: - - /^[0-9]{1,}.[0-9]{1,}.[0-9]{1,}$/ - except: - - branches - diff --git a/gate_guard.service b/chickenhouse.service similarity index 100% rename from gate_guard.service rename to chickenhouse.service diff --git a/gate_guard/gate.py b/gate_guard/gate.py index 9e0b509..22e6166 100644 --- a/gate_guard/gate.py +++ b/gate_guard/gate.py @@ -9,7 +9,6 @@ import socket import ssl import time import paho.mqtt.client as mqtt -import scipy.stats import gate_guard.data_buffer import gate_guard.light_sensor import gate_guard.engine @@ -17,11 +16,14 @@ import gate_guard.power_sensor STATE_INIT_1 = "init_1" STATE_INIT_2 = "init_2" - +STATE_INIT_3 = "init_3" +STATE_INIT_4 = "init_4" STATE_OPENED = "open" STATE_CLOSED = "close" -STATE_OPENING = "opening" -STATE_CLOSING = "closing" +STATE_OPENING_1 = "opening_1" +STATE_OPENING_2 = "opening_2" +STATE_CLOSING_1 = "closing_1" +STATE_CLOSING_2 = "closing_2" LIGHT_READ_DELAY_S = 30 LIGHT_CONSECUTIVE_READS = 10 @@ -42,33 +44,63 @@ POWER_CONSECUTIVE_READS = 10 SLOPE_COUNT = 10 SLOPE_CNT_MIN = 2 -MAX_POWER = 500.0 +MAX_POWER_1 = 450.0 +MAX_POWER_2 = 300.0 +ENGINE_1_PIN_1 = 13 +ENGINE_1_PIN_2 = 19 +ENGINE_2_PIN_1 = 5 +ENGINE_2_PIN_2 = 6 + +def check_to_open(light_avg): + '''Check if gate needs to be opened.''' + ret = False + current_date = datetime.datetime.now() + if (current_date.hour >= 8) and (light_avg > LIGHT_LX_THRESHOLD): + ret = True + return ret + +def check_to_close(light_avg): + '''Check if gate needs to be closed.''' + ret = False + current_date = datetime.datetime.now() + if (current_date.hour >= 16) and (light_avg <= LIGHT_LX_THRESHOLD): + ret = True + return ret + class Gate(object): + '''Main class of the chickenhouse gates.''' def __init__(self): self.__state_handler = {STATE_INIT_1:self.__init1_handler, \ STATE_INIT_2:self.__init2_handler, \ + STATE_INIT_3:self.__init3_handler, \ + STATE_INIT_4:self.__init4_handler, \ STATE_OPENED:self.__opened_handler, \ STATE_CLOSED:self.__closed_handler, \ - STATE_OPENING:self.__opening_handler, \ - STATE_CLOSING:self.__closing_handler} + STATE_OPENING_1:self.__opening_1_handler, \ + STATE_OPENING_2:self.__opening_2_handler, \ + STATE_CLOSING_1:self.__closing_1_handler, \ + STATE_CLOSING_2:self.__closing_2_handler} self.__next_state = STATE_INIT_1 self.__last_state = STATE_OPENED self.__light_sensor = gate_guard.light_sensor.LightSensor( LIGHT_SENSOR_I2C_BUS, LIGHT_SENSOR_I2C_ADDRESS) self.__light_data = gate_guard.data_buffer.DataBuffer( LIGHT_CONSECUTIVE_READS) - self.__engine = gate_guard.engine.Engine(gpio_1=13, gpio_2=19) + self.__engine_1 = gate_guard.engine.Engine(gpio_1=ENGINE_1_PIN_1, gpio_2=ENGINE_1_PIN_2) + self.__engine_2 = gate_guard.engine.Engine(gpio_1=ENGINE_2_PIN_1, gpio_2=ENGINE_2_PIN_2) self.__power_sensor = gate_guard.power_sensor.PowerSensor( POWER_SENSOR_I2C_BUS, POWER_SENSOR_I2C_ADDRESS) self.__power_data = gate_guard.data_buffer.DataBuffer(POWER_CONSECUTIVE_READS) self.__light_read_timeout = 0 - self.__down_run_time = 0 + self.__down_run_time_1 = 0 + self.__down_run_time_2 = 0 self.__gate_run_time = 0 self.__client = mqtt.Client() self.__client.tls_set(MQTT_CERTS) def poll(self): + '''Poll function of the state machine.''' current_time = time.time() if current_time >= self.__light_read_timeout: self.__light_read_timeout = current_time + LIGHT_READ_DELAY_S @@ -94,7 +126,7 @@ class Gate(object): def __init1_handler(self, _): next_state = self.__next_state if self.__is_transition(): - self.__engine.down() + self.__engine_1.down() # workaround for high power after starting engine time.sleep(1) msg = str(time.time()) + " Initialization" @@ -106,53 +138,74 @@ class Gate(object): except (ValueError, TypeError, socket.error, ssl.CertificateError): logging.info('unable to publish to mqtt') pwr = self.__power_sensor.power_mw() - logging.debug('pwr: ' + str(pwr) + ' mW') - if pwr > MAX_POWER: + msg = 'e1: {} mW'.format(pwr) + logging.debug(msg) + if pwr > MAX_POWER_1: next_state = STATE_INIT_2 self.__update_state(next_state) def __init2_handler(self, _): next_state = self.__next_state if self.__is_transition(): - self.__engine.up() - self.__down_run_time = time.time() + self.__engine_1.up() + self.__down_run_time_1 = time.time() pwr = self.__power_sensor.power_mw() - logging.debug('pwr: ' + str(pwr) + ' mW') + msg = 'e1: {} mW'.format(pwr) + logging.debug(msg) - if pwr > MAX_POWER: - self.__down_run_time = (time.time() - self.__down_run_time) / 2 - logging.info('calculated down time: ' + str(self.__down_run_time) + ' s') - if self.__down_run_time > 30: - next_state = STATE_CLOSING + if pwr > MAX_POWER_1: + self.__down_run_time_1 = (time.time() - self.__down_run_time_1) / 2 + msg = 'calculated down time for engine 1: {} s'.format(self.__down_run_time_1) + logging.info(msg) + if self.__down_run_time_1 > 30: + next_state = STATE_INIT_3 else: logging.info("That's not very relaistic. Another try...") next_state = STATE_INIT_1 self.__update_state(next_state) - def __check_to_open(self, light_avg): - ret = False - current_date = datetime.datetime.now() - try: - if (current_date.hour >= 8) and (light_avg > LIGHT_LX_THRESHOLD): - ret = True - except Exception as e: - logging.error("{}".format(e)) - return ret + def __init3_handler(self, _): + next_state = self.__next_state + if self.__is_transition(): + self.__engine_1.down() + time.sleep(1) + self.__engine_1.stop() + self.__engine_2.down() + # workaround for high power after starting engine + time.sleep(1) + pwr = self.__power_sensor.power_mw() + msg = 'e2: {} mW'.format(pwr) + logging.debug(msg) + if pwr > MAX_POWER_2: + next_state = STATE_INIT_4 + self.__update_state(next_state) - def __check_to_close(self, light_avg): - ret = False - if (light_avg != None) and (light_avg <= LIGHT_LX_THRESHOLD): - current_date = datetime.datetime.now() - if (current_date.hour >= 16) and (current_date.minute >= 0): - ret = True - return ret + def __init4_handler(self, _): + next_state = self.__next_state + if self.__is_transition(): + self.__engine_2.up() + self.__down_run_time_2 = time.time() + pwr = self.__power_sensor.power_mw() + msg = 'e2: {} mW'.format(pwr) + logging.debug(msg) + + if pwr > MAX_POWER_2: + self.__down_run_time_2 = (time.time() - self.__down_run_time_2) / 2 + msg = 'calculated down time for engine 2: {} s'.format(self.__down_run_time_2) + logging.info(msg) + if self.__down_run_time_2 > 30: + next_state = STATE_OPENED + else: + logging.info("That's not very relaistic. Another try...") + next_state = STATE_INIT_3 + self.__update_state(next_state) def __opened_handler(self, light_avg): next_state = self.__next_state if self.__is_transition(): - self.__engine.down() + self.__engine_2.down() time.sleep(1) - self.__engine.stop() + self.__engine_2.stop() msg = str(time.time()) + " Opened" try: self.__client.connect(MQTT_HOST, MQTT_PORT) @@ -161,14 +214,14 @@ class Gate(object): self.__client.loop_stop() except (ValueError, TypeError, socket.error, ssl.CertificateError): logging.info('unable to publish to mqtt') - if self.__check_to_close(light_avg) is True: - next_state = STATE_CLOSING + if check_to_close(light_avg) is True: + next_state = STATE_CLOSING_1 self.__update_state(next_state) def __closed_handler(self, light_avg): next_state = self.__next_state if self.__is_transition(): - self.__engine.stop() + self.__engine_2.stop() msg = str(time.time()) + " Closed" try: self.__client.connect(MQTT_HOST, MQTT_PORT) @@ -177,15 +230,15 @@ class Gate(object): self.__client.loop_stop() except (ValueError, TypeError, socket.error, ssl.CertificateError): logging.info('unable to publish to mqtt') - if self.__check_to_open(light_avg) is True: - next_state = STATE_OPENING + if check_to_open(light_avg) is True: + next_state = STATE_OPENING_1 self.__update_state(next_state) - def __opening_handler(self, light_avg): + def __opening_1_handler(self, light_avg): next_state = self.__next_state if self.__is_transition(): - self.__engine.up() - msg = str(time.time()) + " Opening " + str(light_avg) + " lx" + self.__engine_1.up() + msg = str(time.time()) + " Opening" + str(light_avg) + " lx" try: self.__client.connect(MQTT_HOST, MQTT_PORT) self.__client.loop_start() @@ -199,17 +252,41 @@ class Gate(object): time.sleep(1) pwr = self.__power_sensor.power_mw() - logging.debug('pwr - abs: ' + str(pwr) + ' mW\tavg: ' + str(self.__power_data.average()) + ' mW') - if pwr > MAX_POWER: - deviation = abs(time.time() - self.__gate_run_time - self.__down_run_time) - logging.info('runtime deviation: ' + str(deviation)) + msg = 'e1 - abs: {} mW\tavg: {} mW'.format(pwr, self.__power_data.average()) + logging.debug(msg) + if pwr > MAX_POWER_1: + deviation = abs(time.time() - self.__gate_run_time - self.__down_run_time_1) + msg = 'runtime deviation of engine 1: {} s'.format(deviation) + logging.info(msg) + next_state = STATE_OPENING_2 + self.__update_state(next_state) + + def __opening_2_handler(self, _): + next_state = self.__next_state + if self.__is_transition(): + self.__engine_1.down() + time.sleep(1) + self.__engine_1.stop() + self.__engine_2.up() + self.__gate_run_time = time.time() + + # workaround for high power after starting engine + time.sleep(1) + + pwr = self.__power_sensor.power_mw() + msg = 'e2 - abs: {} mW\tavg: {} mW'.format(pwr, self.__power_data.average()) + logging.debug(msg) + if pwr > MAX_POWER_2: + deviation = abs(time.time() - self.__gate_run_time - self.__down_run_time_2) + msg = 'runtime deviation: {}'.format(deviation) + logging.info(msg) next_state = STATE_OPENED self.__update_state(next_state) - def __closing_handler(self, light_avg): + def __closing_1_handler(self, light_avg): next_state = self.__next_state if self.__is_transition(): - self.__engine.down() + self.__engine_1.down() msg = str(time.time()) + " Closing " + str(light_avg) + " lx" try: self.__client.connect(MQTT_HOST, MQTT_PORT) @@ -224,13 +301,39 @@ class Gate(object): time.sleep(1) pwr = self.__power_sensor.power_mw() - logging.debug('pwr: ' + str(pwr) + ' mW') + msg = 'e1: {} mW'.format(pwr) + logging.debug(msg) opening_time = time.time() - self.__gate_run_time - if opening_time > self.__down_run_time: - logging.info("actual running time bigger than calculated (" + str(opening_time) + " vs. " + str(self.__down_run_time) + ").") - next_state = STATE_CLOSED - if pwr > MAX_POWER: - self.__engine.stop() + if opening_time > self.__down_run_time_1: + msg = "Run time of gate 1 is bigger than calculated ({} vs. {}).".format( + opening_time, self.__down_run_time_1) + logging.info(msg) + next_state = STATE_CLOSING_2 + if pwr > MAX_POWER_1: + self.__engine_1.stop() next_state = STATE_INIT_1 self.__update_state(next_state) + def __closing_2_handler(self, _): + next_state = self.__next_state + if self.__is_transition(): + self.__engine_1.stop() + self.__engine_2.down() + self.__gate_run_time = time.time() + + # workaround for high power after starting engine + time.sleep(1) + + pwr = self.__power_sensor.power_mw() + msg = 'e2: {} mW'.format(pwr) + logging.debug(msg) + opening_time = time.time() - self.__gate_run_time + if opening_time > self.__down_run_time_2: + msg = "Run time of gate 1 is bigger than calculated ({} vs. {}).".format( + opening_time, self.__down_run_time_2) + logging.info(msg) + next_state = STATE_CLOSED + if pwr > MAX_POWER_2: + self.__engine_2.stop() + next_state = STATE_INIT_1 + self.__update_state(next_state) diff --git a/scripts/create_release_script.py b/scripts/create_release_script.py deleted file mode 100755 index 6924ce9..0000000 --- a/scripts/create_release_script.py +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/env python -''' -Created on Mar 13, 2017 - -@author: tkl -''' -import os -import sys - -DAEMON_START_SCRIPT_SRC = 'gate_guard.service' -DAEMON_START_SCRIPT_DST = '/lib/systemd/system/' + DAEMON_START_SCRIPT_SRC - -def main(_): - project_version = '' - project_name = '' - project_namespace = '' - if os.environ.has_key('CI_BUILD_TAG'): - project_version = str(os.environ.get('CI_BUILD_TAG')).strip() -# else: -# return -1 - - if os.environ.has_key('CI_PROJECT_NAME'): - project_name = str(os.environ.get('CI_PROJECT_NAME')).strip() -# else: -# return -1 - - if os.environ.has_key('CI_PROJECT_NAMESPACE'): - project_namespace = str(os.environ.get('CI_PROJECT_NAMESPACE')).strip() -# else: -# return -1 - - setup_str = ' setup(name=\'' + project_name + '\', ' - setup_str += 'version=\'' + project_version + '\', ' - setup_str += 'author=\'tkl\', ' - setup_str += 'author_email=\'tkl@blackfinn.de\', ' - setup_str += 'url=\'https://files.blackfinn.de/' + project_namespace + \ - '/' + project_name + '\', ' - setup_str += 'packages=[\'gate_guard\']' - setup_str_sdist = setup_str + ', scripts=[\'' + DAEMON_START_SCRIPT_SRC + '\'])\n' - setup_str += ')\n' - - handle = open('setup.py', 'w') - handle.write('#!/usr/bin/env python\n') - handle.write('from distutils.core import setup\n') - handle.write('import shutil\n') - handle.write('import os\n') - handle.write('import stat\n') - handle.write('import sys\n\n') - handle.write('if sys.argv[1] == \'install\':\n') - handle.write(' shutil.copyfile(\'' + DAEMON_START_SCRIPT_SRC + \ - '\', \'' + DAEMON_START_SCRIPT_DST + '\')\n') - handle.write(' os.chmod(\'' + DAEMON_START_SCRIPT_DST + \ - '\', stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH)\n') - handle.write(setup_str) - handle.write('elif sys.argv[1] == \'sdist\':\n') - handle.write(setup_str_sdist) - handle.write('\n') - - handle.close() - - return 0 - -if __name__ == "__main__": - sys.exit(main(sys.argv[1:])) diff --git a/scripts/deploy_release.sh b/scripts/deploy_release.sh deleted file mode 100755 index 33c9737..0000000 --- a/scripts/deploy_release.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash -set -x - -if [ -z ${CI_BUILD_TAG+x} ]; then - echo "Tag name not found" - exit 1; -fi -if [ -z ${CI_PROJECT_NAME+x} ]; then - echo "Project name not found" - exit 1; -fi - -delim="-" -file_ext=".tar.gz" -file=$CI_PROJECT_NAME$delim$CI_BUILD_TAG$file_ext -current_dir=`pwd` -release_dir="/dist/" -release_file=$current_dir$release_dir$file - -smbclient //proxy/files 4738tax -U tkl -c "mkdir python ; cd python ; mkdir $CI_PROJECT_NAME ; cd $CI_PROJECT_NAME ; put $release_file $file" diff --git a/scripts/pylint_wrapper.py b/scripts/pylint_wrapper.py deleted file mode 100755 index 3ee413a..0000000 --- a/scripts/pylint_wrapper.py +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env python -''' -Created on Feb 11, 2017 - -@author: tkl -''' -import os -import sys -import getopt - -def main(argv): - options, _ = getopt.getopt(argv, "s:", ["source="]) - source_list = [] - for opt, args in options: - if opt in ("-s", "--source"): - source_list.append(args) - - source_str = "" - for source in source_list: - source_str += source + " " - - os.system("pylint " + source_str + " -r n --msg-template=\"{path}:{line}: [{msg_id}({symbol}), {obj}] {msg}\" > pylint.txt") - return 0 - -if __name__ == "__main__": - sys.exit(main(sys.argv[1:])) diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..3cb0911 --- /dev/null +++ b/setup.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python +from distutils.core import setup +import shutil +import os +import stat +import sys + +NAME = 'chickenhouse' +VERSION = '1.0.0' +AUTHOR = 'tkl' +EMAIL = 'tkl@blackfinn.de' +URL = 'https://git.blackfinn.de/python/chickenhouse' +PACKAGES = ['gate_guard'] +SCRIPTS = ['chickenhouse.service'] + +if sys.argv[1] == 'install': + shutil.copyfile('chickenhouse.service', '/lib/systemd/system/chickenhouse.service') + os.chmod('/lib/systemd/system/chickenhouse.service', + stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH) + setup(name=NAME, version=VERSION, author=AUTHOR, author_email=EMAIL, + url=URL, packages=PACKAGES) +elif sys.argv[1] == 'sdist': + setup(name=NAME, version=VERSION, author=AUTHOR, author_email=EMAIL, + url=URL, packages=PACKAGES, scripts=SCRIPTS) diff --git a/sonar-project.properties b/sonar-project.properties deleted file mode 100644 index 98c89fa..0000000 --- a/sonar-project.properties +++ /dev/null @@ -1,11 +0,0 @@ -sonar.projectKey=chickenhouse:python -sonar.projectName=chickenhouse:python -sonar.projectVersion=1.0 -sonar.host.url=http://sonarqube:9000 -sonar.sources=gate_guard -sonar.tests=tests/unittests -sonar.language=py -sonar.sourceEncoding=UTF-8 -sonar.python.xunit.reportPath=nosetests.xml -sonar.python.coverage.reportPath=coverage.xml -sonar.python.pylint.reportPath=pylint.txt