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