gate: Change opened/closed detection to slope calculation

Signed-off-by: Thomas Klaehn <thomas.klaehn@u-blox.com>
This commit is contained in:
Thomas Klaehn 2017-04-09 08:24:40 +02:00
parent ce4094a7b9
commit 40146343ea
3 changed files with 51 additions and 30 deletions

View File

@ -6,8 +6,10 @@ Created on Dec 19, 2016
import time import time
import gate import gate
import sys import sys
import logging
def main(): def main():
logging.basicConfig(filename='/var/log/gate_guard.log', level=logging.DEBUG)
gate_state = gate.Gate() gate_state = gate.Gate()
try: try:
while True: while True:

View File

@ -17,6 +17,9 @@ class DataBuffer(object):
self.__data.append(element) self.__data.append(element)
return True return True
def get(self):
return self.__data
def average(self): def average(self):
if len(self.__data) is 0: if len(self.__data) is 0:
return None return None

View File

@ -9,6 +9,9 @@ import data_buffer
import light_sensor import light_sensor
import engine import engine
import power_sensor import power_sensor
import scipy
import scipy.stats
import logging
STATE_INIT = "init" STATE_INIT = "init"
STATE_OPENED = "open" STATE_OPENED = "open"
@ -22,19 +25,19 @@ LIGHT_CONSECUTIVE_READS = 10
LIGHT_LX_THRESHOLD = {"open":10, "close":5} LIGHT_LX_THRESHOLD = {"open":10, "close":5}
MQTT_HOST = "proxy" MQTT_HOST = "proxy"
MQTT_TOPIC = "outdoor/chickenhouse/newgate" MQTT_TOPIC = "outdoor/chickenhouse/gate"
LIGHT_SENSOR_I2C_BUS = 1 LIGHT_SENSOR_I2C_BUS = 1
LIGHT_SENSOR_I2C_ADDRESS = 0x23 LIGHT_SENSOR_I2C_ADDRESS = 0x23
POWER_SENSOR_I2C_BUS = 1 POWER_SENSOR_I2C_BUS = 1
POWER_SENSOR_I2C_ADDRESS = 0x40 POWER_SENSOR_I2C_ADDRESS = 0x40
POWER_CONSECUTIVE_READS = 1000
MAX_ENGINE_POWER = {"up":330, "down":290} SLOPE_COUNT = 100
MAX_GATE_RUNTIME = {"open":300, "close":300} SLOPE_CNT_MIN = 10
MIN_GATE_RUNTIME = {"open":10, "close":10}
MAX_GATE_RUNTIME = {"open":60, "close":50}
MAX_POWER_SLOPE = {"up":10, "down":2}
class Gate(object): class Gate(object):
def __init__(self): def __init__(self):
@ -54,12 +57,12 @@ class Gate(object):
self.__engine = engine.Engine(gpio_1=13, gpio_2=19) self.__engine = engine.Engine(gpio_1=13, gpio_2=19)
self.__power_sensor = power_sensor.PowerSensor(POWER_SENSOR_I2C_BUS, \ self.__power_sensor = power_sensor.PowerSensor(POWER_SENSOR_I2C_BUS, \
POWER_SENSOR_I2C_ADDRESS) POWER_SENSOR_I2C_ADDRESS)
self.__power_data = data_buffer.DataBuffer(POWER_CONSECUTIVE_READS)
self.__gate_move_timeout = 0 self.__gate_move_timeout = 0
self.__light_read_timeout = 0 self.__light_read_timeout = 0
self.__error_count = 0 self.__error_count = 0
self.__runtime_open = 0
self.__runtime_close = 0 self.slope_power = data_buffer.DataBuffer(SLOPE_COUNT)
self.slope_time = data_buffer.DataBuffer(SLOPE_COUNT)
def poll(self): def poll(self):
current_time = time.time() current_time = time.time()
@ -75,6 +78,7 @@ class Gate(object):
def __is_transition(self): def __is_transition(self):
if self.__last_state != self.__next_state: if self.__last_state != self.__next_state:
logging.info('STATE: ' + self.__last_state + ' -> ' + self.__next_state)
return True return True
return False return False
@ -98,10 +102,13 @@ class Gate(object):
self.__engine.down() self.__engine.down()
time.sleep(5) time.sleep(5)
self.__engine.stop() self.__engine.stop()
slope, _, _, _, _ = scipy.stats.linregress(self.slope_time.get(),
self.slope_power.get())
self.__comserver.transmit(MQTT_TOPIC, str(time.time()) + \ self.__comserver.transmit(MQTT_TOPIC, str(time.time()) + \
" Opened " + \ " Opened " + \
str(self.__power_data.average()) + " mW") str(slope))
self.__power_data.clear() self.slope_power.clear()
self.slope_time.clear()
if (light_avg != None) and (light_avg <= LIGHT_LX_THRESHOLD["close"]): if (light_avg != None) and (light_avg <= LIGHT_LX_THRESHOLD["close"]):
next_state = STATE_CLOSING next_state = STATE_CLOSING
@ -115,10 +122,14 @@ class Gate(object):
self.__engine.up() self.__engine.up()
time.sleep(5) time.sleep(5)
self.__engine.stop() self.__engine.stop()
slope, _, _, _, _ = scipy.stats.linregress(self.slope_time.get(),
self.slope_power.get())
self.__comserver.transmit(MQTT_TOPIC, str(time.time()) + \ self.__comserver.transmit(MQTT_TOPIC, str(time.time()) + \
" Closed " + \ " Closed " + \
str(self.__power_data.average()) + " mW") str(slope))
self.__power_data.clear() self.__power_data.clear()
self.slope_power.clear()
self.slope_time.clear()
if (light_avg != None) and (light_avg > LIGHT_LX_THRESHOLD["open"]): if (light_avg != None) and (light_avg > LIGHT_LX_THRESHOLD["open"]):
next_state = STATE_OPENING next_state = STATE_OPENING
@ -129,43 +140,48 @@ class Gate(object):
def __opening_handler(self, light_avg): def __opening_handler(self, light_avg):
next_state = self.__next_state next_state = self.__next_state
if self.__is_transition(): if self.__is_transition():
self.__runtime_open = time.time() + MIN_GATE_RUNTIME["open"]
self.__engine.up() self.__engine.up()
self.__gate_move_timeout = time.time() + MAX_GATE_RUNTIME["open"] self.__gate_move_timeout = time.time() + MAX_GATE_RUNTIME["open"]
self.__comserver.transmit(MQTT_TOPIC, str(time.time()) + \ self.__comserver.transmit(MQTT_TOPIC, str(time.time()) + \
" Opening " + str(light_avg) + " lx") " Opening " + str(light_avg) + " lx")
tm = time.time()
if time.time() > self.__gate_move_timeout: if tm > self.__gate_move_timeout:
next_state = STATE_ERROR next_state = STATE_ERROR
else: else:
self.__power_data.push(self.__power_sensor.power_mw()) pwr = self.__power_sensor.power_mw()
current_avg = self.__power_data.average() self.slope_power.push(pwr)
if current_avg != None: self.slope_time.push(tm)
if current_avg > MAX_ENGINE_POWER["up"]: logging.debug('up: ' + str(tm) + ' ' + str(pwr))
if time.time() > self.__runtime_open: 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 next_state = STATE_OPENED
self.__update_state(next_state) self.__update_state(next_state)
def __closing_handler(self, light_avg): def __closing_handler(self, light_avg):
next_state = self.__next_state next_state = self.__next_state
if self.__is_transition(): if self.__is_transition():
self.__runtime_close = time.time() + MIN_GATE_RUNTIME["close"]
self.__engine.down() self.__engine.down()
self.__gate_move_timeout = time.time() + MAX_GATE_RUNTIME["close"] self.__gate_move_timeout = time.time() + MAX_GATE_RUNTIME["close"]
self.__comserver.transmit(MQTT_TOPIC, str(time.time()) + \ self.__comserver.transmit(MQTT_TOPIC, str(time.time()) + \
" Closing " + str(light_avg) + " lx") " Closing " + str(light_avg) + " lx")
if time.time() > self.__gate_move_timeout: tm = time.time()
if tm > self.__gate_move_timeout:
next_state = STATE_ERROR next_state = STATE_ERROR
else: else:
self.__power_data.push(self.__power_sensor.power_mw()) pwr = self.__power_sensor.power_mw()
current_avg = self.__power_data.average() self.slope_power.push(pwr)
if current_avg != None: self.slope_time.push(pwr)
if current_avg > MAX_ENGINE_POWER["down"]: logging.debug('dw: ' + str(tm) + ' ' + str(pwr))
if time.time() > self.__runtime_close: slope = 0
next_state = STATE_CLOSED 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) self.__update_state(next_state)