chickenhouse/gate_guard/gate.py
Thomas Klaehn 16931c50ba gate: post open/close msg via telegram
Signed-off-by: Thomas Klaehn <thomas.klaehn@u-blox.com>
2018-08-27 11:27:50 +02:00

242 lines
9.2 KiB
Python

'''
Created on Dec 19, 2016
@author: klaehn
'''
import datetime
import logging
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
import gate_guard.power_sensor
STATE_INIT_1 = "init_1"
STATE_INIT_2 = "init_2"
STATE_OPENED = "open"
STATE_CLOSED = "close"
STATE_OPENING = "opening"
STATE_CLOSING = "closing"
LIGHT_READ_DELAY_S = 30
LIGHT_CONSECUTIVE_READS = 10
LIGHT_LX_THRESHOLD = {"open":1, "close":0}
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
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_POWER = 400.0
class Gate(object):
def __init__(self):
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}
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.__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.__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
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 + ' -> ' + \
self.__next_state)
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 __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(1)
self.__engine.stop()
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()
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()
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)
def __closing_handler(self, light_avg):
next_state = self.__next_state
if self.__is_transition():
self.__engine.down()
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()
# 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)