package controllers import ( "sync" "time" "github.com/rs/zerolog/log" ) type ChamberState int const ( ChamberStateIdle ChamberState = iota ChamberStateCooling ChamberStateHeating ) type ChamberController struct { config *Config // Current state. chamberState ChamberState lastChamberStateChange time.Time C chan ChamberState mutex *sync.Mutex name string // Current temperature readings. ambientTemperature float64 chamberTemperature float64 wortTemperature float64 pid *PIDController initialized bool } func NewChamberController(name string, config *Config) *ChamberController { return &ChamberController{ C: make(chan ChamberState), name: name, config: config, pid: NewPIDController(config.PID.Kp, config.PID.Ki, config.PID.Kd), mutex: &sync.Mutex{}, } } func (p *ChamberController) Run() { for { offset := p.pid.Compute(p.wortTemperature, p.config.FermentationTemperature) chamberTargetTemp := p.config.FermentationTemperature + offset log.Debug().Float64("chamber_target_temp", chamberTargetTemp).Send() // TODO Merge hysteresis algorithm into this one. if !p.initialized { // heater off // cooler off continue } if p.wortTemperature < p.config.FermentationTemperature && p.chamberTemperature < chamberTargetTemp { // heater on } else if p.wortTemperature >= p.config.FermentationTemperature || p.chamberTemperature >= chamberTargetTemp { // heater off } else { // heater off } if p.wortTemperature > p.config.FermentationTemperature && p.chamberTemperature > chamberTargetTemp { // cooler on } else if p.wortTemperature <= p.config.FermentationTemperature || p.chamberTemperature <= chamberTargetTemp { // cooler off } else { // cooler off } } }