''' Created on Dec 19, 2016 @author: klaehn ''' import time import mqtt import data_buffer import light_sensor import engine import 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 = 100 SLOPE_CNT_MIN = 10 MAX_GATE_RUNTIME = {"open":60, "close":50} MAX_POWER_SLOPE = {"up":10, "down":2} 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 = light_sensor.LightSensor(LIGHT_SENSOR_I2C_BUS, \ LIGHT_SENSOR_I2C_ADDRESS) self.__light_data = data_buffer.DataBuffer(LIGHT_CONSECUTIVE_READS) self.__comserver = mqtt.Mqtt(MQTT_HOST) self.__engine = engine.Engine(gpio_1=13, gpio_2=19) self.__power_sensor = 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 = data_buffer.DataBuffer(SLOPE_COUNT) self.slope_time = 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(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(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.__power_data.clear() 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) logging.debug('up: ' + str(tm) + ' ' + str(pwr)) slope = 0 if self.slope_power.length() >= SLOPE_CNT_MIN: slope, _, _, _, _ = scipy.stats.linregress(self.slope_time.get(), self.slope_power.get()) 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(pwr) logging.debug('dw: ' + str(tm) + ' ' + str(pwr)) slope = 0 if self.slope_power.length() >= SLOPE_CNT_MIN: slope, _, _, _, _ = scipy.stats.linregress(self.slope_time.get(), self.slope_power.get()) if slope > MAX_POWER_SLOPE["down"]: next_state = STATE_OPENED 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)