226 lines
6.5 KiB
Go
226 lines
6.5 KiB
Go
package controllers
|
|
|
|
import (
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/rs/zerolog/log"
|
|
)
|
|
|
|
// Hysteresis will only run when the ambient temperature is above the
|
|
// fermentation temperature. The compressor will run until the wort
|
|
// is cooled to FermentationTemperature, the chamber is cooled to
|
|
// MinChamberTemperature or the compressor has run for MaxCoolerRuntimeSecs.
|
|
// The compressor will run for min. MinCoolerRuntimeSecs before switching off,
|
|
// regardless of temperatures. Once turned off, the compressor will not be
|
|
// turned on before MinCoolerCooldownSecs has expired, regardless of temperatures.
|
|
type Hysteresis struct {
|
|
config *Config
|
|
|
|
// Current state.
|
|
coolerState bool
|
|
lastCoolerStateChange time.Time
|
|
C chan bool
|
|
mutex *sync.Mutex
|
|
|
|
name string
|
|
|
|
// Current temperature readings.
|
|
ambientTemperature float64
|
|
chamberTemperature float64
|
|
wortTemperature float64
|
|
}
|
|
|
|
func NewHysteresis(name string) *Hysteresis {
|
|
return &Hysteresis{
|
|
C: make(chan bool),
|
|
name: name,
|
|
mutex: &sync.Mutex{},
|
|
}
|
|
}
|
|
|
|
func (h *Hysteresis) SetConfig(newConfig *Config) {
|
|
h.lock()
|
|
h.config = newConfig
|
|
h.unlock()
|
|
h.update()
|
|
}
|
|
|
|
func (h *Hysteresis) UpdateAmbientTemperature(value float64) {
|
|
h.lock()
|
|
h.ambientTemperature = value
|
|
h.unlock()
|
|
h.update()
|
|
}
|
|
|
|
func (h *Hysteresis) UpdateChamberTemperature(value float64) {
|
|
h.lock()
|
|
h.chamberTemperature = value
|
|
h.unlock()
|
|
h.update()
|
|
}
|
|
|
|
func (h *Hysteresis) UpdateWortTemperature(value float64) {
|
|
h.lock()
|
|
h.wortTemperature = value
|
|
h.unlock()
|
|
h.update()
|
|
}
|
|
|
|
func (h *Hysteresis) lock() {
|
|
h.mutex.Lock()
|
|
}
|
|
|
|
func (h *Hysteresis) unlock() {
|
|
h.mutex.Unlock()
|
|
}
|
|
|
|
func (h *Hysteresis) GetCoolerState() bool {
|
|
return h.coolerState
|
|
}
|
|
|
|
func (h *Hysteresis) setState(state bool) {
|
|
h.lock()
|
|
|
|
if state == h.coolerState {
|
|
h.unlock()
|
|
return
|
|
}
|
|
|
|
log.Debug().Bool("state", state).Msg("Setting state")
|
|
h.coolerState = state
|
|
h.lastCoolerStateChange = time.Now()
|
|
h.unlock()
|
|
|
|
h.C <- state
|
|
}
|
|
|
|
func (h *Hysteresis) update() {
|
|
// Compressor runtime has highest priority.
|
|
lastStateChangeAgeSecs := time.Since(h.lastCoolerStateChange).Seconds()
|
|
if h.coolerState {
|
|
// Keep the cooler running until min. runtime is reached.
|
|
if lastStateChangeAgeSecs < h.config.MinCoolerRuntimeSecs {
|
|
log.Debug().
|
|
Str("name", h.name).
|
|
Float64("runtime", lastStateChangeAgeSecs).
|
|
Float64("min_runtime", h.config.MinCoolerRuntimeSecs).
|
|
Bool("state", h.coolerState).
|
|
Msg("Cooler kept running as it's runtime threshold is not yet reached.")
|
|
return
|
|
}
|
|
|
|
// Stop the cooler if it's runtime exceeds max.
|
|
if lastStateChangeAgeSecs > h.config.MaxCoolerRuntimeSecs {
|
|
log.Info().
|
|
Str("name", h.name).
|
|
Float64("runtime", lastStateChangeAgeSecs).
|
|
Float64("max_runtime", h.config.MaxCoolerRuntimeSecs).
|
|
Bool("state", false).
|
|
Msg("Cooler stopped as it's runtime exceeds max. threshold.")
|
|
h.setState(false)
|
|
return
|
|
}
|
|
} else {
|
|
// Keep the cooler off until min. cooldown time is reached.
|
|
if lastStateChangeAgeSecs < h.config.MinCoolerCooldownSecs {
|
|
log.Debug().
|
|
Str("name", h.name).
|
|
Float64("runtime", lastStateChangeAgeSecs).
|
|
Float64("min_cooldown", h.config.MinCoolerCooldownSecs).
|
|
Bool("state", h.coolerState).
|
|
Msg("Cooler kept off as it's cooldown time threshold is not yet reached.")
|
|
return
|
|
}
|
|
}
|
|
|
|
// Do not allow the cooler to run when the ambient temperature is below
|
|
// the fermentation temperature.
|
|
if h.ambientTemperature < h.config.FermentationTemperature {
|
|
if h.coolerState {
|
|
log.Info().
|
|
Str("name", h.name).
|
|
Float64("ambient_temp", h.ambientTemperature).
|
|
Float64("fermentation_temp", h.config.FermentationTemperature).
|
|
Msg("Turn off cooler as ambient temperature is less than fermentation temperature.")
|
|
h.setState(false)
|
|
} else {
|
|
log.Debug().
|
|
Str("name", h.name).
|
|
Float64("ambient_temp", h.ambientTemperature).
|
|
Float64("fermentation_temp", h.config.FermentationTemperature).
|
|
Msg("Cooler kept off as ambient temperature is less than fermentation temperature.")
|
|
}
|
|
return
|
|
}
|
|
|
|
// Do not allow the cooler to run if the chamber temperature is below
|
|
// minimum.
|
|
if h.chamberTemperature <= h.config.MinChamberTemperature {
|
|
if h.coolerState {
|
|
log.Info().
|
|
Str("name", h.name).
|
|
Float64("chamber_temp", h.chamberTemperature).
|
|
Float64("min_chamber_temp", h.config.MinChamberTemperature).
|
|
Msg("Cooler turned off as chamber temperature dropped below threshold.")
|
|
h.setState(false)
|
|
} else {
|
|
log.Debug().
|
|
Str("name", h.name).
|
|
Float64("ambient_temp", h.ambientTemperature).
|
|
Float64("fermentation_temp", h.config.FermentationTemperature).
|
|
Msg("Cooler kept off as chamber temperature is below threshold.")
|
|
}
|
|
return
|
|
}
|
|
|
|
// Keep the cooler stopped until the wort delta temperature reaches it's threshold.
|
|
delta := h.wortTemperature - h.config.FermentationTemperature
|
|
if h.coolerState {
|
|
// Keep the cooler running until the wort reaches the desired temp.
|
|
if delta <= 0 {
|
|
// TODO Investigate how much the wort temp. drops below fermentation temp.
|
|
// TODO A threshold may be needed.
|
|
log.Info().
|
|
Str("name", h.name).
|
|
Float64("wort_temp", h.wortTemperature).
|
|
Float64("fermentation_temp", h.config.FermentationTemperature).
|
|
Float64("delta", delta).
|
|
Float64("max_wort_delta", h.config.MaxWortDelta).
|
|
Msg("Cooler stopped as wort is cooled down to fermentation temp.")
|
|
h.setState(false)
|
|
return
|
|
} else {
|
|
log.Debug().
|
|
Str("name", h.name).
|
|
Float64("wort_temp", h.wortTemperature).
|
|
Float64("fermentation_temp", h.config.FermentationTemperature).
|
|
Float64("delta", delta).
|
|
Float64("max_wort_delta", h.config.MaxWortDelta).
|
|
Msg("Cooler kept running as the wort has not yet reached fermentation temp.")
|
|
}
|
|
} else {
|
|
// Start the cooler when delta exceeds threshold.
|
|
if delta > h.config.MaxWortDelta {
|
|
log.Info().
|
|
Str("name", h.name).
|
|
Float64("wort_temp", h.wortTemperature).
|
|
Float64("fermentation_temp", h.config.FermentationTemperature).
|
|
Float64("delta", delta).
|
|
Float64("max_wort_delta", h.config.MaxWortDelta).
|
|
Msg("Cooler started as wort delta exceeds threshold.")
|
|
h.setState(true)
|
|
return
|
|
} else {
|
|
log.Debug().
|
|
Str("name", h.name).
|
|
Float64("wort_temp", h.wortTemperature).
|
|
Float64("fermentation_temp", h.config.FermentationTemperature).
|
|
Float64("delta", delta).
|
|
Float64("max_wort_delta", h.config.MaxWortDelta).
|
|
Msg("Cooler kept off as delta does not yet exceed threshold.")
|
|
// TODO If this is reached, then we can lower the delta as min. cooldown is reached.
|
|
// TODO This is relevant for PID.
|
|
}
|
|
}
|
|
}
|