8e6a2550f0
Signed-off-by: Thomas Klaehn <thomas.klaehn@u-blox.com>
196 lines
7.0 KiB
Python
196 lines
7.0 KiB
Python
'''
|
|
Created on Dec 19, 2016
|
|
|
|
@author: klaehn
|
|
'''
|
|
import time
|
|
import mqtt
|
|
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_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}
|
|
|
|
MQTT_HOST = "proxy"
|
|
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
|
|
|
|
SLOPE_COUNT = 10
|
|
SLOPE_CNT_MIN = 2
|
|
|
|
MAX_GATE_RUNTIME = {"open":70, "close":60}
|
|
MAX_POWER_SLOPE = {"up":40, "down":15}
|
|
|
|
class Gate(object):
|
|
def __init__(self):
|
|
self.__state_handler = {STATE_INIT:self.__init_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
|
|
|
|
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.__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)
|
|
|
|
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())
|
|
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 __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 __opened_handler(self, light_avg):
|
|
next_state = self.__next_state
|
|
if self.__is_transition():
|
|
self.__engine.down()
|
|
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()) + \
|
|
" Opened " + \
|
|
str(slope))
|
|
self.slope_power.clear()
|
|
self.slope_time.clear()
|
|
|
|
if (light_avg != None) and (light_avg <= LIGHT_LX_THRESHOLD["close"]):
|
|
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"]):
|
|
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"]:
|
|
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()
|
|
self.__gate_move_timeout = time.time() + MAX_GATE_RUNTIME["close"]
|
|
self.__comserver.transmit(MQTT_TOPIC, str(time.time()) + \
|
|
" Closing " + 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('dw: ' + str(tm) + ' ' + str(pwr) + ' ' + str(slope))
|
|
if slope > MAX_POWER_SLOPE["down"]:
|
|
next_state = STATE_CLOSED
|
|
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)
|