Started working on regulator software to hold temperature efficiently
This commit is contained in:
198
regulator.py
Normal file
198
regulator.py
Normal file
@@ -0,0 +1,198 @@
|
||||
import time
|
||||
import threading
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
# local packages
|
||||
import source
|
||||
from logger import logger
|
||||
|
||||
'''
|
||||
We want to also track the time heating and cooling
|
||||
time delta pr scale unit.
|
||||
|
||||
|
||||
When we pool:
|
||||
Temp too high, if relay is:
|
||||
- on --> keep off
|
||||
- off --> turn off
|
||||
Temp too low, if relay is:
|
||||
- on --> keep on
|
||||
- off --> turn on
|
||||
|
||||
Relay is on, temp is:
|
||||
- low --> keep on
|
||||
- high --> turn off
|
||||
relay is off, temp is:
|
||||
- low --> keep off
|
||||
- high --> turn on
|
||||
'''
|
||||
|
||||
class BrewRegulator():
|
||||
def __init__(self, temperatureSensor, coldRelay, hotRelay, temperatureGoal, degreesAllowedToDrift, interval=60, cooldown=600):
|
||||
self.interval = interval
|
||||
self.cooldown = cooldown
|
||||
self.nextStateChangeAfter = datetime.now()
|
||||
|
||||
self.isGoalMet = False
|
||||
self.state = 'heating'
|
||||
self.cooling = coldRelay
|
||||
self.heating = hotRelay
|
||||
self.temperatureSensor = temperatureSensor
|
||||
self.temperatureGoal = temperatureGoal
|
||||
self.degreesAllowedToDrift = degreesAllowedToDrift
|
||||
|
||||
self.secondsToDriftSingleDegree = 600
|
||||
|
||||
self.thread = threading.Thread(target=self.captureOnIntervalForever, args=())
|
||||
self.thread.daemon = True
|
||||
|
||||
def start(self):
|
||||
self.thread.start()
|
||||
|
||||
'''
|
||||
states: heating | cooling | idle
|
||||
regulation technique: correction | goal based
|
||||
|
||||
We get a new goal: 18 deg
|
||||
|
||||
Blackbox state() --> Read sensor and decide
|
||||
If blackboxState is not current state:
|
||||
update regulation tech.
|
||||
|
||||
|
||||
Get cooldown time based on delta and histogram for temperature interval
|
||||
'''
|
||||
|
||||
@property
|
||||
def withinDeviationLimit(self):
|
||||
return abs(self.temperatureGoal - self.currentTemp) < self.degreesAllowedToDrift
|
||||
|
||||
def checkGoal(self):
|
||||
if self.state == 'cooling' and self.currentTemp <= self.temperatureGoal:
|
||||
return True
|
||||
elif self.state == 'heating' and self.currentTemp >= self.temperatureGoal:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@property
|
||||
def shouldCool(self):
|
||||
return self.currentTemp > self.temperatureGoal
|
||||
|
||||
@property
|
||||
def shouldHeat(self):
|
||||
return self.currentTemp < self.temperatureGoal
|
||||
|
||||
def sleepRelativToOffset(self):
|
||||
diff = abs(self.temperatureGoal - self.currentTemp)
|
||||
temperatureRelativeTimeout = self.interval * diff
|
||||
print('sleeping: {}, diff: {}'.format(temperatureRelativeTimeout, diff))
|
||||
time.sleep(temperatureRelativeTimeout)
|
||||
|
||||
def chaseTemperature(self):
|
||||
isGoalMet = False
|
||||
if self.shouldCool:
|
||||
self.setCooling()
|
||||
elif self.shouldHeat:
|
||||
self.setHeating()
|
||||
|
||||
while not isGoalMet:
|
||||
self.readAndPrint()
|
||||
|
||||
if not self.checkGoal():
|
||||
print("Chasing goal, but sleeping")
|
||||
time.sleep(5)
|
||||
else:
|
||||
print("Temperature met, turning all off and returning")
|
||||
isGoalMet = True
|
||||
if self.state == 'cooling':
|
||||
self.cooling.set(False)
|
||||
elif self.state == 'heating':
|
||||
self.heating.set(False)
|
||||
|
||||
def temperatureLossFunction(self):
|
||||
return self.degreesAllowedToDrift * self.secondsToDriftSingleDegree
|
||||
|
||||
def sustainTemperature(self):
|
||||
print('Sustaining temperature')
|
||||
self.readAndPrint()
|
||||
|
||||
if self.currentTemp < self.temperatureGoal - self.degreesAllowedToDrift:
|
||||
self.cooling.set(True)
|
||||
coolingProperty = 30
|
||||
print('Cooling turned on! Turning off in {} seconds'.format(coolingProperty))
|
||||
time.sleep(coolingProperty)
|
||||
self.cooling.set(False)
|
||||
print('Cooling turned off')
|
||||
|
||||
elif self.currentTemp > self.temperatureGoal + self.degreesAllowedToDrift:
|
||||
self.heating.set(True)
|
||||
heatingProperty = 120
|
||||
print('Heating turned on! Turning off in {} seconds'.format(heatingProperty))
|
||||
time.sleep(heatingProperty)
|
||||
self.heating.set(False)
|
||||
print('Heating turned off')
|
||||
|
||||
estimatedTimeout = self.temperatureLossFunction()
|
||||
print('Allowed drift {}, estimated timeout: {}'.format(self.degreesAllowedToDrift, estimatedTimeout))
|
||||
time.sleep(estimatedTimeout)
|
||||
|
||||
|
||||
def setCooling(self):
|
||||
print('should cool')
|
||||
self.state = 'cooling'
|
||||
self.heating.set(False)
|
||||
self.cooling.set(True)
|
||||
|
||||
def setHeating(self):
|
||||
print('should heat')
|
||||
self.state = 'heating'
|
||||
self.heating.set(True)
|
||||
self.cooling.set(False)
|
||||
|
||||
def readAndPrint(self):
|
||||
self.currentTemp = self.temperatureSensor.temp
|
||||
print('current temp: {}, goal: {}'.format(self.currentTemp, self.temperatureGoal))
|
||||
print('cold state:', self.cooling.state)
|
||||
print('hot state:', self.heating.state)
|
||||
|
||||
def poolSensorAndSetRelay(self):
|
||||
self.readAndPrint()
|
||||
if not self.withinDeviationLimit:
|
||||
self.chaseTemperature()
|
||||
else:
|
||||
self.sustainTemperature()
|
||||
|
||||
def captureOnIntervalForever(self):
|
||||
try:
|
||||
while True:
|
||||
self.poolSensorAndSetRelay()
|
||||
|
||||
print('sleeping {}'.format(self.interval))
|
||||
print('- - - - -')
|
||||
time.sleep(self.interval)
|
||||
except Error as error:
|
||||
logger.error('Regulator crashed!', es={
|
||||
'error': str(error),
|
||||
'exception': error.__class__.__name__
|
||||
})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import source
|
||||
import source.loader as loader
|
||||
from source.brewSensor import BrewSensor
|
||||
from source.brewRelay import BrewRelay
|
||||
|
||||
externalPeripherals = loader.load('brew.yaml')
|
||||
sensors = externalPeripherals['sensors']
|
||||
relays = externalPeripherals['relays']
|
||||
|
||||
insideSensor = BrewSensor.getSensorByItsLocation(sensors, 'inside')
|
||||
|
||||
coldRelay = BrewRelay.getRelayByWhatItControls(relays, 'cooling')
|
||||
hotRelay = BrewRelay.getRelayByWhatItControls(relays, 'heating')
|
||||
|
||||
regulator = BrewRegulator(insideSensor, coldRelay, hotRelay, 18, 0.5, 10, 60)
|
||||
regulator.captureOnIntervalForever()
|
||||
|
||||
Reference in New Issue
Block a user