1
0
Fork 0
mirror of https://github.com/shouptech/tempgopher.git synced 2026-02-03 16:49:42 +00:00

Complete refactoring of thermostat process

This commit is contained in:
Emma 2018-10-01 19:56:14 -06:00
parent fae1914fe1
commit aea54ff156
3 changed files with 149 additions and 90 deletions

43
main.go
View file

@ -1,11 +1,7 @@
package main package main
import ( import (
"log"
"os"
"os/signal"
"sync" "sync"
"syscall"
"github.com/alexflint/go-arg" "github.com/alexflint/go-arg"
"github.com/stianeikeland/go-rpio" "github.com/stianeikeland/go-rpio"
@ -22,46 +18,19 @@ func main() {
p.Fail("ACTION must be run") p.Fail("ACTION must be run")
} }
config, err := LoadConfig(args.ConfigFile) // Create a channel for receiving of state
if err != nil {
log.Fatal(err)
}
// Prep for GPIO access
err = rpio.Open()
if err != nil {
log.Fatal(err)
}
// run is tracking whether or not the thermostats should run
run := true
// done is used to signal the web frontend to stop
done := make(chan bool)
// Catch SIGTERM and SIGINT
sig := make(chan os.Signal)
signal.Notify(sig, os.Interrupt, syscall.SIGTERM)
signal.Notify(sig, os.Interrupt, syscall.SIGINT)
go func() {
<-sig
run = false
done <- true
}()
sc := make(chan State) sc := make(chan State)
// Launch the thermostat go routines // Use to track running routines
var wg sync.WaitGroup var wg sync.WaitGroup
for _, sensor := range config.Sensors {
// Launch the thermostat go routines
wg.Add(1) wg.Add(1)
go RunThermostat(sensor, sc, &run, &wg) go RunThermostat(args.ConfigFile, sc, &wg)
}
// Launch the web frontend // Launch the web frontend
wg.Add(1) wg.Add(1)
RunWeb(sc, done, &wg) RunWeb(sc, &wg)
// Wait for all threads to stop // Wait for all threads to stop
wg.Wait() wg.Wait()

View file

@ -3,7 +3,10 @@ package main
import ( import (
"errors" "errors"
"log" "log"
"os"
"os/signal"
"sync" "sync"
"syscall"
"time" "time"
"github.com/stianeikeland/go-rpio" "github.com/stianeikeland/go-rpio"
@ -16,6 +19,7 @@ type State struct {
Temp float64 `json:"temp"` Temp float64 `json:"temp"`
Cooling bool `json:"cooling"` Cooling bool `json:"cooling"`
Heating bool `json:"heating"` Heating bool `json:"heating"`
When time.Time `json:"reading"`
Changed time.Time `json:"changed"` Changed time.Time `json:"changed"`
} }
@ -51,71 +55,151 @@ func PinSwitch(pin rpio.Pin, on bool, invert bool) {
} }
} }
// RunThermostat monitors the temperature of the supplied sensor and does its best to keep it at the desired state. // ProcessSensor uses the current temperature and last state to determine if changes need to be made to switches.
func RunThermostat(sensor Sensor, sc chan<- State, run *bool, wg *sync.WaitGroup) { func ProcessSensor(sensor Sensor, state State) (State, error) {
var s State // Read the current temperature
s.Alias = sensor.Alias temp, err := ReadTemperature(sensor.ID)
s.Changed = time.Now()
cpin := rpio.Pin(sensor.CoolGPIO)
cpin.Output()
hpin := rpio.Pin(sensor.HeatGPIO)
hpin.Output()
PinSwitch(cpin, false, sensor.CoolInvert)
PinSwitch(hpin, false, sensor.HeatInvert)
for *run {
t, err := ReadTemperature(sensor.ID)
if err != nil { if err != nil {
log.Panicln(err) log.Panicln(err)
} }
min := time.Since(s.Changed).Minutes() // Initialize the pins
cpin := rpio.Pin(sensor.CoolGPIO)
cpin.Output()
hpin := rpio.Pin(sensor.HeatGPIO)
hpin.Output()
// Calculate duration
duration := time.Since(state.Changed).Minutes()
switch { switch {
case t > sensor.HighTemp && t < sensor.HighTemp: case temp > sensor.HighTemp && temp < sensor.HighTemp:
log.Println("Invalid state! Temperature is too high AND too low!") log.Println("Invalid state! Temperature is too high AND too low!")
case t > sensor.HighTemp && s.Heating: case temp > sensor.HighTemp && state.Heating:
PinSwitch(hpin, false, sensor.HeatInvert) PinSwitch(hpin, false, sensor.HeatInvert)
s.Heating = false state.Heating = false
s.Changed = time.Now() state.Changed = time.Now()
case t > sensor.HighTemp && s.Cooling: case temp > sensor.HighTemp && state.Cooling:
break break
case t > sensor.HighTemp && min > sensor.CoolMinutes: case temp > sensor.HighTemp && duration > sensor.CoolMinutes:
PinSwitch(cpin, true, sensor.CoolInvert) PinSwitch(cpin, true, sensor.CoolInvert)
s.Cooling = true state.Cooling = true
s.Changed = time.Now() state.Changed = time.Now()
case t < sensor.LowTemp && s.Cooling: case temp < sensor.LowTemp && state.Cooling:
PinSwitch(cpin, false, sensor.CoolInvert) PinSwitch(cpin, false, sensor.CoolInvert)
s.Cooling = false state.Cooling = false
s.Changed = time.Now() state.Changed = time.Now()
case t < sensor.LowTemp && s.Heating: case temp < sensor.LowTemp && state.Heating:
break break
case t < sensor.LowTemp && min > sensor.HeatMinutes: case temp < sensor.LowTemp && duration > sensor.HeatMinutes:
PinSwitch(hpin, true, sensor.HeatInvert) PinSwitch(hpin, true, sensor.HeatInvert)
s.Heating = true state.Heating = true
s.Changed = time.Now() state.Changed = time.Now()
default: default:
break break
} }
s.Temp = t state.Temp = temp
if sensor.Verbose { if sensor.Verbose {
log.Printf("%s Temp: %.2f, Cooling: %t, Heating: %t, Duration: %.1f", sensor.Alias, s.Temp, s.Cooling, s.Heating, min) log.Printf("%s Temp: %.2f, Cooling: %t, Heating: %t, Duration: %.1f", sensor.Alias, state.Temp, state.Cooling, state.Heating, duration)
} }
return state, nil
}
// TurnOffSensor turns off all switches for an individual sensor
func TurnOffSensor(sensor Sensor) {
cpin := rpio.Pin(sensor.CoolGPIO)
cpin.Output()
PinSwitch(cpin, false, sensor.CoolInvert)
hpin := rpio.Pin(sensor.HeatGPIO)
hpin.Output()
PinSwitch(hpin, false, sensor.HeatInvert)
}
// TurnOffSensors turns off all sensors defined in the config
func TurnOffSensors(config Config) {
for _, sensor := range config.Sensors {
TurnOffSensor(sensor)
}
}
// RunThermostat monitors the temperature of the supplied sensor and does its best to keep it at the desired state.
func RunThermostat(path string, sc chan<- State, wg *sync.WaitGroup) {
defer wg.Done()
// Load Config
config, err := LoadConfig(path)
if err != nil {
log.Panicln(err)
}
// Prep for GPIO access
err = rpio.Open()
if err != nil {
log.Panicln(err)
}
defer TurnOffSensors(*config)
defer rpio.Close()
// Track if thermostats should run
run := true
// Start with everything off
TurnOffSensors(*config)
// Listen for SIGHUP to reload config
hup := make(chan os.Signal)
signal.Notify(hup, os.Interrupt, syscall.SIGHUP)
go func() {
for {
<-hup
config, err = LoadConfig(path)
if err != nil {
log.Panicln(err)
}
}
}()
// Listen for SIGTERM & SIGINT to quit
sig := make(chan os.Signal)
signal.Notify(sig, os.Interrupt, syscall.SIGTERM)
signal.Notify(sig, os.Interrupt, syscall.SIGINT)
go func() {
<-sig
run = false
}()
states := make(map[string]State)
// For each sensor, run through the thermostat logic
for run {
for _, v := range config.Sensors {
// Create an initial state if there's not one already
if _, ok := states[v.ID]; !ok {
state := State{
Alias: v.Alias,
When: time.Now(),
Changed: time.Now(),
}
states[v.ID] = state
}
// Process the sensor
states[v.ID], err = ProcessSensor(v, states[v.ID])
if err != nil {
log.Panicln(err)
}
// Write the returned state to the channel (don't block if nothing is available to listen)
select { select {
case sc <- s: case sc <- states[v.ID]:
break break
default: default:
break break
} }
} }
}
log.Printf("%s Shutting down thermostat", sensor.Alias) log.Println("Shutting down thermostat")
PinSwitch(cpin, false, sensor.CoolInvert)
PinSwitch(hpin, false, sensor.HeatInvert)
wg.Done()
} }

10
web.go
View file

@ -4,7 +4,10 @@ import (
"context" "context"
"log" "log"
"net/http" "net/http"
"os"
"os/signal"
"sync" "sync"
"syscall"
"time" "time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -44,7 +47,7 @@ func SetupRouter() *gin.Engine {
} }
// RunWeb launches a web server. sc is used to update the states from the Thermostats. // RunWeb launches a web server. sc is used to update the states from the Thermostats.
func RunWeb(sc <-chan State, done <-chan bool, wg *sync.WaitGroup) { func RunWeb(sc <-chan State, wg *sync.WaitGroup) {
// Update sensor states when a new state comes back from the thermostat. // Update sensor states when a new state comes back from the thermostat.
states = make(map[string]State) states = make(map[string]State)
go func() { go func() {
@ -68,7 +71,10 @@ func RunWeb(sc <-chan State, done <-chan bool, wg *sync.WaitGroup) {
} }
}() }()
// Wait for the done signal // Listen for SIGTERM & SIGINT
done := make(chan os.Signal)
signal.Notify(done, os.Interrupt, syscall.SIGTERM)
signal.Notify(done, os.Interrupt, syscall.SIGINT)
<-done <-done
log.Println("Shutdown Server ...") log.Println("Shutdown Server ...")