gate: add 2nd. gate

Signed-off-by: Thomas Klaehn <thomas.klaehn@u-blox.com>
This commit is contained in:
Thomas Klaehn 2018-08-25 11:15:45 +02:00
parent a5205cfbcb
commit 35d1aa796c
8 changed files with 185 additions and 206 deletions

View File

@ -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

View File

@ -9,7 +9,6 @@ import socket
import ssl import ssl
import time import time
import paho.mqtt.client as mqtt import paho.mqtt.client as mqtt
import scipy.stats
import gate_guard.data_buffer import gate_guard.data_buffer
import gate_guard.light_sensor import gate_guard.light_sensor
import gate_guard.engine import gate_guard.engine
@ -17,11 +16,14 @@ import gate_guard.power_sensor
STATE_INIT_1 = "init_1" STATE_INIT_1 = "init_1"
STATE_INIT_2 = "init_2" STATE_INIT_2 = "init_2"
STATE_INIT_3 = "init_3"
STATE_INIT_4 = "init_4"
STATE_OPENED = "open" STATE_OPENED = "open"
STATE_CLOSED = "close" STATE_CLOSED = "close"
STATE_OPENING = "opening" STATE_OPENING_1 = "opening_1"
STATE_CLOSING = "closing" STATE_OPENING_2 = "opening_2"
STATE_CLOSING_1 = "closing_1"
STATE_CLOSING_2 = "closing_2"
LIGHT_READ_DELAY_S = 30 LIGHT_READ_DELAY_S = 30
LIGHT_CONSECUTIVE_READS = 10 LIGHT_CONSECUTIVE_READS = 10
@ -42,33 +44,63 @@ POWER_CONSECUTIVE_READS = 10
SLOPE_COUNT = 10 SLOPE_COUNT = 10
SLOPE_CNT_MIN = 2 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): class Gate(object):
'''Main class of the chickenhouse gates.'''
def __init__(self): def __init__(self):
self.__state_handler = {STATE_INIT_1:self.__init1_handler, \ self.__state_handler = {STATE_INIT_1:self.__init1_handler, \
STATE_INIT_2:self.__init2_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_OPENED:self.__opened_handler, \
STATE_CLOSED:self.__closed_handler, \ STATE_CLOSED:self.__closed_handler, \
STATE_OPENING:self.__opening_handler, \ STATE_OPENING_1:self.__opening_1_handler, \
STATE_CLOSING:self.__closing_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.__next_state = STATE_INIT_1
self.__last_state = STATE_OPENED self.__last_state = STATE_OPENED
self.__light_sensor = gate_guard.light_sensor.LightSensor( self.__light_sensor = gate_guard.light_sensor.LightSensor(
LIGHT_SENSOR_I2C_BUS, LIGHT_SENSOR_I2C_ADDRESS) LIGHT_SENSOR_I2C_BUS, LIGHT_SENSOR_I2C_ADDRESS)
self.__light_data = gate_guard.data_buffer.DataBuffer( self.__light_data = gate_guard.data_buffer.DataBuffer(
LIGHT_CONSECUTIVE_READS) 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( self.__power_sensor = gate_guard.power_sensor.PowerSensor(
POWER_SENSOR_I2C_BUS, POWER_SENSOR_I2C_ADDRESS) POWER_SENSOR_I2C_BUS, POWER_SENSOR_I2C_ADDRESS)
self.__power_data = gate_guard.data_buffer.DataBuffer(POWER_CONSECUTIVE_READS) self.__power_data = gate_guard.data_buffer.DataBuffer(POWER_CONSECUTIVE_READS)
self.__light_read_timeout = 0 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.__gate_run_time = 0
self.__client = mqtt.Client() self.__client = mqtt.Client()
self.__client.tls_set(MQTT_CERTS) self.__client.tls_set(MQTT_CERTS)
def poll(self): def poll(self):
'''Poll function of the state machine.'''
current_time = time.time() current_time = time.time()
if current_time >= self.__light_read_timeout: if current_time >= self.__light_read_timeout:
self.__light_read_timeout = current_time + LIGHT_READ_DELAY_S self.__light_read_timeout = current_time + LIGHT_READ_DELAY_S
@ -94,7 +126,7 @@ class Gate(object):
def __init1_handler(self, _): def __init1_handler(self, _):
next_state = self.__next_state next_state = self.__next_state
if self.__is_transition(): if self.__is_transition():
self.__engine.down() self.__engine_1.down()
# workaround for high power after starting engine # workaround for high power after starting engine
time.sleep(1) time.sleep(1)
msg = str(time.time()) + " Initialization" msg = str(time.time()) + " Initialization"
@ -106,53 +138,74 @@ class Gate(object):
except (ValueError, TypeError, socket.error, ssl.CertificateError): except (ValueError, TypeError, socket.error, ssl.CertificateError):
logging.info('unable to publish to mqtt') logging.info('unable to publish to mqtt')
pwr = self.__power_sensor.power_mw() pwr = self.__power_sensor.power_mw()
logging.debug('pwr: ' + str(pwr) + ' mW') msg = 'e1: {} mW'.format(pwr)
if pwr > MAX_POWER: logging.debug(msg)
if pwr > MAX_POWER_1:
next_state = STATE_INIT_2 next_state = STATE_INIT_2
self.__update_state(next_state) self.__update_state(next_state)
def __init2_handler(self, _): def __init2_handler(self, _):
next_state = self.__next_state next_state = self.__next_state
if self.__is_transition(): if self.__is_transition():
self.__engine.up() self.__engine_1.up()
self.__down_run_time = time.time() self.__down_run_time_1 = time.time()
pwr = self.__power_sensor.power_mw() pwr = self.__power_sensor.power_mw()
logging.debug('pwr: ' + str(pwr) + ' mW') msg = 'e1: {} mW'.format(pwr)
logging.debug(msg)
if pwr > MAX_POWER: if pwr > MAX_POWER_1:
self.__down_run_time = (time.time() - self.__down_run_time) / 2 self.__down_run_time_1 = (time.time() - self.__down_run_time_1) / 2
logging.info('calculated down time: ' + str(self.__down_run_time) + ' s') msg = 'calculated down time for engine 1: {} s'.format(self.__down_run_time_1)
if self.__down_run_time > 30: logging.info(msg)
next_state = STATE_CLOSING if self.__down_run_time_1 > 30:
next_state = STATE_INIT_3
else: else:
logging.info("That's not very relaistic. Another try...") logging.info("That's not very relaistic. Another try...")
next_state = STATE_INIT_1 next_state = STATE_INIT_1
self.__update_state(next_state) self.__update_state(next_state)
def __check_to_open(self, light_avg): def __init3_handler(self, _):
ret = False next_state = self.__next_state
current_date = datetime.datetime.now() if self.__is_transition():
try: self.__engine_1.down()
if (current_date.hour >= 8) and (light_avg > LIGHT_LX_THRESHOLD): time.sleep(1)
ret = True self.__engine_1.stop()
except Exception as e: self.__engine_2.down()
logging.error("{}".format(e)) # workaround for high power after starting engine
return ret 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): def __init4_handler(self, _):
ret = False next_state = self.__next_state
if (light_avg != None) and (light_avg <= LIGHT_LX_THRESHOLD): if self.__is_transition():
current_date = datetime.datetime.now() self.__engine_2.up()
if (current_date.hour >= 16) and (current_date.minute >= 0): self.__down_run_time_2 = time.time()
ret = True pwr = self.__power_sensor.power_mw()
return ret 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): def __opened_handler(self, light_avg):
next_state = self.__next_state next_state = self.__next_state
if self.__is_transition(): if self.__is_transition():
self.__engine.down() self.__engine_2.down()
time.sleep(1) time.sleep(1)
self.__engine.stop() self.__engine_2.stop()
msg = str(time.time()) + " Opened" msg = str(time.time()) + " Opened"
try: try:
self.__client.connect(MQTT_HOST, MQTT_PORT) self.__client.connect(MQTT_HOST, MQTT_PORT)
@ -161,14 +214,14 @@ class Gate(object):
self.__client.loop_stop() self.__client.loop_stop()
except (ValueError, TypeError, socket.error, ssl.CertificateError): except (ValueError, TypeError, socket.error, ssl.CertificateError):
logging.info('unable to publish to mqtt') logging.info('unable to publish to mqtt')
if self.__check_to_close(light_avg) is True: if check_to_close(light_avg) is True:
next_state = STATE_CLOSING next_state = STATE_CLOSING_1
self.__update_state(next_state) self.__update_state(next_state)
def __closed_handler(self, light_avg): def __closed_handler(self, light_avg):
next_state = self.__next_state next_state = self.__next_state
if self.__is_transition(): if self.__is_transition():
self.__engine.stop() self.__engine_2.stop()
msg = str(time.time()) + " Closed" msg = str(time.time()) + " Closed"
try: try:
self.__client.connect(MQTT_HOST, MQTT_PORT) self.__client.connect(MQTT_HOST, MQTT_PORT)
@ -177,15 +230,15 @@ class Gate(object):
self.__client.loop_stop() self.__client.loop_stop()
except (ValueError, TypeError, socket.error, ssl.CertificateError): except (ValueError, TypeError, socket.error, ssl.CertificateError):
logging.info('unable to publish to mqtt') logging.info('unable to publish to mqtt')
if self.__check_to_open(light_avg) is True: if check_to_open(light_avg) is True:
next_state = STATE_OPENING next_state = STATE_OPENING_1
self.__update_state(next_state) self.__update_state(next_state)
def __opening_handler(self, light_avg): def __opening_1_handler(self, light_avg):
next_state = self.__next_state next_state = self.__next_state
if self.__is_transition(): if self.__is_transition():
self.__engine.up() self.__engine_1.up()
msg = str(time.time()) + " Opening " + str(light_avg) + " lx" msg = str(time.time()) + " Opening" + str(light_avg) + " lx"
try: try:
self.__client.connect(MQTT_HOST, MQTT_PORT) self.__client.connect(MQTT_HOST, MQTT_PORT)
self.__client.loop_start() self.__client.loop_start()
@ -199,17 +252,41 @@ class Gate(object):
time.sleep(1) time.sleep(1)
pwr = self.__power_sensor.power_mw() pwr = self.__power_sensor.power_mw()
logging.debug('pwr - abs: ' + str(pwr) + ' mW\tavg: ' + str(self.__power_data.average()) + ' mW') msg = 'e1 - abs: {} mW\tavg: {} mW'.format(pwr, self.__power_data.average())
if pwr > MAX_POWER: logging.debug(msg)
deviation = abs(time.time() - self.__gate_run_time - self.__down_run_time) if pwr > MAX_POWER_1:
logging.info('runtime deviation: ' + str(deviation)) 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 next_state = STATE_OPENED
self.__update_state(next_state) self.__update_state(next_state)
def __closing_handler(self, light_avg): def __closing_1_handler(self, light_avg):
next_state = self.__next_state next_state = self.__next_state
if self.__is_transition(): if self.__is_transition():
self.__engine.down() self.__engine_1.down()
msg = str(time.time()) + " Closing " + str(light_avg) + " lx" msg = str(time.time()) + " Closing " + str(light_avg) + " lx"
try: try:
self.__client.connect(MQTT_HOST, MQTT_PORT) self.__client.connect(MQTT_HOST, MQTT_PORT)
@ -224,13 +301,39 @@ class Gate(object):
time.sleep(1) time.sleep(1)
pwr = self.__power_sensor.power_mw() 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 opening_time = time.time() - self.__gate_run_time
if opening_time > self.__down_run_time: if opening_time > self.__down_run_time_1:
logging.info("actual running time bigger than calculated (" + str(opening_time) + " vs. " + str(self.__down_run_time) + ").") msg = "Run time of gate 1 is bigger than calculated ({} vs. {}).".format(
next_state = STATE_CLOSED opening_time, self.__down_run_time_1)
if pwr > MAX_POWER: logging.info(msg)
self.__engine.stop() next_state = STATE_CLOSING_2
if pwr > MAX_POWER_1:
self.__engine_1.stop()
next_state = STATE_INIT_1 next_state = STATE_INIT_1
self.__update_state(next_state) 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)

View File

@ -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:]))

View File

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

View File

@ -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:]))

24
setup.py Normal file
View File

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

View File

@ -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