From 16931c50ba5bf6e71119a08c03474d851a4b1463 Mon Sep 17 00:00:00 2001 From: Thomas Klaehn Date: Tue, 11 Apr 2017 09:13:33 +0200 Subject: [PATCH] gate: post open/close msg via telegram Signed-off-by: Thomas Klaehn --- .gitlab-ci.yml => ._gitlab-ci.yml | 0 .project | 17 -- .pydevproject | 10 -- .pylintrc | 2 - gate_guard/__init__.py | 2 +- gate_guard/engine.py | 12 +- gate_guard/gate.py | 246 +++++++++++++++++----------- scripts/create_release_script.py | 3 +- tests/unittests/test_data_buffer.py | 4 +- 9 files changed, 155 insertions(+), 141 deletions(-) rename .gitlab-ci.yml => ._gitlab-ci.yml (100%) delete mode 100644 .project delete mode 100644 .pydevproject delete mode 100644 .pylintrc diff --git a/.gitlab-ci.yml b/._gitlab-ci.yml similarity index 100% rename from .gitlab-ci.yml rename to ._gitlab-ci.yml diff --git a/.project b/.project deleted file mode 100644 index 127e014..0000000 --- a/.project +++ /dev/null @@ -1,17 +0,0 @@ - - - chickenhouse - - - - - - org.python.pydev.PyDevBuilder - - - - - - org.python.pydev.pythonNature - - diff --git a/.pydevproject b/.pydevproject deleted file mode 100644 index 60cc573..0000000 --- a/.pydevproject +++ /dev/null @@ -1,10 +0,0 @@ - - - -/${PROJECT_DIR_NAME}/scripts -/${PROJECT_DIR_NAME}/tests -/${PROJECT_DIR_NAME}/gate_guard - -python 2.7 -Default - diff --git a/.pylintrc b/.pylintrc deleted file mode 100644 index 1b59ef2..0000000 --- a/.pylintrc +++ /dev/null @@ -1,2 +0,0 @@ -[TYPECHECK] -ignored-modules = numpy \ No newline at end of file diff --git a/gate_guard/__init__.py b/gate_guard/__init__.py index d292206..4e6221b 100644 --- a/gate_guard/__init__.py +++ b/gate_guard/__init__.py @@ -9,7 +9,7 @@ import logging import gate_guard.gate def main(): - logging.basicConfig(filename='/var/log/gate_guard.log', level=logging.DEBUG) + logging.basicConfig(filename='/var/log/gate_guard.log', level=logging.DEBUG, format='%(asctime)s %(message)s') gate_state = gate_guard.gate.Gate() try: while True: diff --git a/gate_guard/engine.py b/gate_guard/engine.py index 493fdf3..61cf2d2 100644 --- a/gate_guard/engine.py +++ b/gate_guard/engine.py @@ -17,13 +17,13 @@ class Engine(object): self.stop() def stop(self): - self.gpio_1.set(0) - self.gpio_2.set(0) + self.gpio_1.write(0) + self.gpio_2.write(0) def up(self): - self.gpio_1.set(1) - self.gpio_2.set(0) + self.gpio_1.write(1) + self.gpio_2.write(0) def down(self): - self.gpio_1.set(0) - self.gpio_2.set(1) + self.gpio_1.write(0) + self.gpio_2.write(1) diff --git a/gate_guard/gate.py b/gate_guard/gate.py index fc0ce70..b35bc21 100644 --- a/gate_guard/gate.py +++ b/gate_guard/gate.py @@ -3,28 +3,33 @@ Created on Dec 19, 2016 @author: klaehn ''' +import datetime +import logging +import socket +import ssl import time -import mqtt +import paho.mqtt.client as mqtt +import scipy.stats import gate_guard.data_buffer import gate_guard.light_sensor import gate_guard.engine import gate_guard.power_sensor -import scipy -import scipy.stats -import logging -STATE_INIT = "init" +STATE_INIT_1 = "init_1" +STATE_INIT_2 = "init_2" + STATE_OPENED = "open" STATE_CLOSED = "close" STATE_OPENING = "opening" STATE_CLOSING = "closing" -STATE_ERROR = "error" LIGHT_READ_DELAY_S = 30 LIGHT_CONSECUTIVE_READS = 10 -LIGHT_LX_THRESHOLD = {"open":10, "close":5} +LIGHT_LX_THRESHOLD = {"open":1, "close":0} -MQTT_HOST = "proxy" +MQTT_HOST = "mqtt.blackfinn.de" +MQTT_PORT = 8883 +MQTT_CERTS = "/etc/ssl/certs/DST_Root_CA_X3.pem" MQTT_TOPIC = "outdoor/chickenhouse/gate" LIGHT_SENSOR_I2C_BUS = 1 @@ -33,50 +38,52 @@ LIGHT_SENSOR_I2C_ADDRESS = 0x23 POWER_SENSOR_I2C_BUS = 1 POWER_SENSOR_I2C_ADDRESS = 0x40 +POWER_CONSECUTIVE_READS = 10 + SLOPE_COUNT = 10 SLOPE_CNT_MIN = 2 - -MAX_GATE_RUNTIME = {"open":70, "close":60} -MAX_POWER_SLOPE = {"up":40, "down":15} +MAX_POWER = 400.0 class Gate(object): def __init__(self): - self.__state_handler = {STATE_INIT:self.__init_handler, \ + self.__state_handler = {STATE_INIT_1:self.__init1_handler, \ + STATE_INIT_2:self.__init2_handler, \ STATE_OPENED:self.__opened_handler, \ STATE_CLOSED:self.__closed_handler, \ STATE_OPENING:self.__opening_handler, \ - STATE_CLOSING:self.__closing_handler, \ - STATE_ERROR:self.__error_handler} - self.__next_state = STATE_INIT - self.__last_state = STATE_ERROR - + STATE_CLOSING:self.__closing_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.__comserver = mqtt.Mqtt(MQTT_HOST) self.__engine = gate_guard.engine.Engine(gpio_1=13, gpio_2=19) self.__power_sensor = gate_guard.power_sensor.PowerSensor( POWER_SENSOR_I2C_BUS, POWER_SENSOR_I2C_ADDRESS) - self.__gate_move_timeout = 0 + self.__power_data = gate_guard.data_buffer.DataBuffer(POWER_CONSECUTIVE_READS) self.__light_read_timeout = 0 - self.__error_count = 0 - - self.slope_power = gate_guard.data_buffer.DataBuffer(SLOPE_COUNT) - self.slope_time = gate_guard.data_buffer.DataBuffer(SLOPE_COUNT) + self.__down_run_time = 0 + self.__gate_run_time = 0 + self.__client = mqtt.Client() + self.__client.tls_set(MQTT_CERTS) def poll(self): current_time = time.time() if current_time >= self.__light_read_timeout: self.__light_read_timeout = current_time + LIGHT_READ_DELAY_S - self.__light_data.push(self.__light_sensor.read()) + light_read = self.__light_sensor.read() + self.__light_data.push(light_read) + logging.info('light - abs: ' + str(light_read) + ', avg: ' + \ + str(self.__light_data.average())) + power_read = self.__power_sensor.power_mw() + self.__power_data.push(power_read) self.__state_handler[self.__next_state](self.__light_data.average()) def __update_state(self, new_state): self.__last_state = self.__next_state self.__next_state = new_state - def __is_transition(self): if self.__last_state != self.__next_state: logging.info('STATE: ' + self.__last_state + ' -> ' + \ @@ -84,80 +91,123 @@ class Gate(object): return True return False + def __init1_handler(self, _): + next_state = self.__next_state + if self.__is_transition(): + self.__engine.down() + # workaround for high power after starting engine + time.sleep(1) + msg = str(time.time()) + " Initialization" + try: + self.__client.connect(MQTT_HOST, MQTT_PORT) + self.__client.loop_start() + self.__client.publish(MQTT_TOPIC, msg, qos=2, retain=True) + self.__client.loop_stop() + except (ValueError, TypeError, socket.error, ssl.CertificateError): + logging.info('unable to publish to mqtt') + pwr = self.__power_sensor.power_mw() + logging.info('pwr: ' + str(pwr) + ' mW') + if pwr > MAX_POWER: + next_state = STATE_INIT_2 + self.__update_state(next_state) - def __init_handler(self, light_avg): - ''' - In init we don't know anything neither about gate state nor about - light. So first we try to reach STATE_OPENED. - ''' - #pylint: disable=unused-argument - self.__comserver.connect() - self.__comserver.transmit(MQTT_TOPIC, str(time.time()) + \ - " gate gard initiated") - self.__comserver.disconnect() - self.__update_state(STATE_OPENING) + def __init2_handler(self, _): + next_state = self.__next_state + if self.__is_transition(): + self.__engine.up() + self.__down_run_time = time.time() + pwr = self.__power_sensor.power_mw() + logging.info('pwr: ' + str(pwr) + ' mW') + 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 + 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 + if (light_avg != None) and (light_avg > LIGHT_LX_THRESHOLD["open"]): + ret = True + current_date = datetime.datetime.now() + if (current_date.hour <= 12) and (current_date.hour >= 8): + ret = True + return ret + + def __check_to_close(self, light_avg): + ret = False + if (light_avg != None) and (light_avg <= LIGHT_LX_THRESHOLD["close"]): + current_date = datetime.datetime.now() + if (current_date.hour >= 16) and (current_date.minute >= 0): + ret = True + return ret def __opened_handler(self, light_avg): next_state = self.__next_state if self.__is_transition(): self.__engine.down() - time.sleep(0.5) + time.sleep(1) self.__engine.stop() - slope, _, _, _, _ = scipy.stats.linregress(self.slope_time.get(), - self.slope_power.get()) - self.__comserver.transmit(MQTT_TOPIC, str(time.time()) + \ - " Opened " + \ - str(slope)) - self.slope_power.clear() - self.slope_time.clear() - - if (light_avg != None) and (light_avg <= LIGHT_LX_THRESHOLD["close"]): + msg = str(time.time()) + " Opened" + try: + self.__client.connect(MQTT_HOST, MQTT_PORT) + self.__client.loop_start() + self.__client.publish(MQTT_TOPIC, msg, qos=2, retain=True) + 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 - self.__update_state(next_state) - def __closed_handler(self, light_avg): next_state = self.__next_state if self.__is_transition(): self.__engine.up() time.sleep(0.5) self.__engine.stop() - slope, _, _, _, _ = scipy.stats.linregress(self.slope_time.get(), - self.slope_power.get()) - self.__comserver.transmit(MQTT_TOPIC, str(time.time()) + \ - " Closed " + \ - str(slope)) - self.slope_power.clear() - self.slope_time.clear() - - if (light_avg != None) and (light_avg > LIGHT_LX_THRESHOLD["open"]): + msg = str(time.time()) + " Closed" + try: + self.__client.connect(MQTT_HOST, MQTT_PORT) + self.__client.loop_start() + self.__client.publish(MQTT_TOPIC, msg, qos=2, retain=True) + 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 - self.__update_state(next_state) - def __opening_handler(self, light_avg): next_state = self.__next_state if self.__is_transition(): self.__engine.up() - self.__gate_move_timeout = time.time() + MAX_GATE_RUNTIME["open"] - self.__comserver.transmit(MQTT_TOPIC, str(time.time()) + \ - " Opening " + str(light_avg) + " lx") - tm = time.time() - if tm > self.__gate_move_timeout: - next_state = STATE_ERROR - else: - pwr = self.__power_sensor.power_mw() - self.slope_power.push(pwr) - self.slope_time.push(tm) - slope = 0 - if self.slope_power.length() >= SLOPE_CNT_MIN: - slope, _, _, _, _ = scipy.stats.linregress( - self.slope_time.get(), self.slope_power.get()) - logging.debug('up: ' + str(tm) + ' ' + str(pwr) + ' ' + str(slope)) - if slope > MAX_POWER_SLOPE["up"]: + msg = str(time.time()) + " Opening " + str(light_avg) + " lx" + try: + self.__client.connect(MQTT_HOST, MQTT_PORT) + self.__client.loop_start() + self.__client.publish(MQTT_TOPIC, msg, qos=2, retain=True) + self.__client.loop_stop() + except (ValueError, TypeError, socket.error, ssl.CertificateError): + logging.info('unable to publish to mqtt') + self.__gate_run_time = time.time() + + # workaround for high power after starting engine + time.sleep(1) + + pwr = self.__power_sensor.power_mw() + logging.info('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)) + if deviation > (self.__down_run_time / 10): + logging.info('Deviation too big. Re-initializing...') + next_state = STATE_INIT_1 + else: next_state = STATE_OPENED self.__update_state(next_state) @@ -165,31 +215,27 @@ class Gate(object): next_state = self.__next_state if self.__is_transition(): self.__engine.down() - self.__gate_move_timeout = time.time() + MAX_GATE_RUNTIME["close"] - self.__comserver.transmit(MQTT_TOPIC, str(time.time()) + \ - " Closing " + str(light_avg) + " lx") + msg = str(time.time()) + " Closing " + str(light_avg) + " lx" + try: + self.__client.connect(MQTT_HOST, MQTT_PORT) + self.__client.loop_start() + self.__client.publish(MQTT_TOPIC, msg, qos=2, retain=True) + self.__client.loop_stop() + except (ValueError, TypeError, socket.error, ssl.CertificateError): + logging.info('unable to publish to mqtt') + self.__gate_run_time = time.time() - tm = time.time() - if tm > self.__gate_move_timeout: - next_state = STATE_ERROR - else: - pwr = self.__power_sensor.power_mw() - self.slope_power.push(pwr) - self.slope_time.push(tm) - slope = 0 - if self.slope_power.length() >= SLOPE_CNT_MIN: - slope, _, _, _, _ = scipy.stats.linregress( - self.slope_time.get(), self.slope_power.get()) - logging.debug('dw: ' + str(tm) + ' ' + str(pwr) + ' ' + str(slope)) - if slope > MAX_POWER_SLOPE["down"]: - next_state = STATE_CLOSED + # workaround for high power after starting engine + time.sleep(1) + + pwr = self.__power_sensor.power_mw() + logging.info('pwr: ' + str(pwr) + ' mW') + 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() + next_state = STATE_INIT_1 self.__update_state(next_state) - - def __error_handler(self, light_avg): - #pylint: disable=unused-argument - if self.__is_transition(): - self.__engine.stop() - self.__comserver.transmit(MQTT_TOPIC, str(time.time()) + \ - " Error handler!!!") - self.__update_state(STATE_INIT) diff --git a/scripts/create_release_script.py b/scripts/create_release_script.py index 5f1d2c5..6924ce9 100755 --- a/scripts/create_release_script.py +++ b/scripts/create_release_script.py @@ -10,7 +10,7 @@ import sys DAEMON_START_SCRIPT_SRC = 'gate_guard.service' DAEMON_START_SCRIPT_DST = '/lib/systemd/system/' + DAEMON_START_SCRIPT_SRC -def main(argv): +def main(_): project_version = '' project_name = '' project_namespace = '' @@ -62,4 +62,3 @@ def main(argv): if __name__ == "__main__": sys.exit(main(sys.argv[1:])) - diff --git a/tests/unittests/test_data_buffer.py b/tests/unittests/test_data_buffer.py index e1a7333..558db24 100644 --- a/tests/unittests/test_data_buffer.py +++ b/tests/unittests/test_data_buffer.py @@ -10,7 +10,6 @@ import gate_guard.data_buffer class Test(unittest.TestCase): - def test_zero_space(self): dut = gate_guard.data_buffer.DataBuffer(0) result = dut.push(10) @@ -26,5 +25,4 @@ class Test(unittest.TestCase): str(buffer_size) + " res: " + str(result)) if __name__ == "__main__": - #import sys;sys.argv = ['', 'Test.testName'] - unittest.main() \ No newline at end of file + unittest.main()