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:
parent
fae1914fe1
commit
aea54ff156
3 changed files with 149 additions and 90 deletions
45
main.go
45
main.go
|
|
@ -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 {
|
|
||||||
wg.Add(1)
|
// Launch the thermostat go routines
|
||||||
go RunThermostat(sensor, sc, &run, &wg)
|
wg.Add(1)
|
||||||
}
|
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()
|
||||||
|
|
|
||||||
184
thermostat.go
184
thermostat.go
|
|
@ -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()
|
if err != nil {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the pins
|
||||||
cpin := rpio.Pin(sensor.CoolGPIO)
|
cpin := rpio.Pin(sensor.CoolGPIO)
|
||||||
cpin.Output()
|
cpin.Output()
|
||||||
|
|
||||||
hpin := rpio.Pin(sensor.HeatGPIO)
|
hpin := rpio.Pin(sensor.HeatGPIO)
|
||||||
hpin.Output()
|
hpin.Output()
|
||||||
|
|
||||||
|
// Calculate duration
|
||||||
|
duration := time.Since(state.Changed).Minutes()
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case temp > sensor.HighTemp && temp < sensor.HighTemp:
|
||||||
|
log.Println("Invalid state! Temperature is too high AND too low!")
|
||||||
|
case temp > sensor.HighTemp && state.Heating:
|
||||||
|
PinSwitch(hpin, false, sensor.HeatInvert)
|
||||||
|
state.Heating = false
|
||||||
|
state.Changed = time.Now()
|
||||||
|
case temp > sensor.HighTemp && state.Cooling:
|
||||||
|
break
|
||||||
|
case temp > sensor.HighTemp && duration > sensor.CoolMinutes:
|
||||||
|
PinSwitch(cpin, true, sensor.CoolInvert)
|
||||||
|
state.Cooling = true
|
||||||
|
state.Changed = time.Now()
|
||||||
|
case temp < sensor.LowTemp && state.Cooling:
|
||||||
|
PinSwitch(cpin, false, sensor.CoolInvert)
|
||||||
|
state.Cooling = false
|
||||||
|
state.Changed = time.Now()
|
||||||
|
case temp < sensor.LowTemp && state.Heating:
|
||||||
|
break
|
||||||
|
case temp < sensor.LowTemp && duration > sensor.HeatMinutes:
|
||||||
|
PinSwitch(hpin, true, sensor.HeatInvert)
|
||||||
|
state.Heating = true
|
||||||
|
state.Changed = time.Now()
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
state.Temp = temp
|
||||||
|
if sensor.Verbose {
|
||||||
|
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)
|
PinSwitch(cpin, false, sensor.CoolInvert)
|
||||||
|
|
||||||
|
hpin := rpio.Pin(sensor.HeatGPIO)
|
||||||
|
hpin.Output()
|
||||||
PinSwitch(hpin, false, sensor.HeatInvert)
|
PinSwitch(hpin, false, sensor.HeatInvert)
|
||||||
|
}
|
||||||
|
|
||||||
for *run {
|
// TurnOffSensors turns off all sensors defined in the config
|
||||||
t, err := ReadTemperature(sensor.ID)
|
func TurnOffSensors(config Config) {
|
||||||
if err != nil {
|
for _, sensor := range config.Sensors {
|
||||||
log.Panicln(err)
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
min := time.Since(s.Changed).Minutes()
|
// 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
|
||||||
|
}()
|
||||||
|
|
||||||
switch {
|
states := make(map[string]State)
|
||||||
case t > sensor.HighTemp && t < sensor.HighTemp:
|
// For each sensor, run through the thermostat logic
|
||||||
log.Println("Invalid state! Temperature is too high AND too low!")
|
for run {
|
||||||
case t > sensor.HighTemp && s.Heating:
|
for _, v := range config.Sensors {
|
||||||
PinSwitch(hpin, false, sensor.HeatInvert)
|
// Create an initial state if there's not one already
|
||||||
s.Heating = false
|
if _, ok := states[v.ID]; !ok {
|
||||||
s.Changed = time.Now()
|
state := State{
|
||||||
case t > sensor.HighTemp && s.Cooling:
|
Alias: v.Alias,
|
||||||
break
|
When: time.Now(),
|
||||||
case t > sensor.HighTemp && min > sensor.CoolMinutes:
|
Changed: time.Now(),
|
||||||
PinSwitch(cpin, true, sensor.CoolInvert)
|
}
|
||||||
s.Cooling = true
|
states[v.ID] = state
|
||||||
s.Changed = time.Now()
|
}
|
||||||
case t < sensor.LowTemp && s.Cooling:
|
|
||||||
PinSwitch(cpin, false, sensor.CoolInvert)
|
|
||||||
s.Cooling = false
|
|
||||||
s.Changed = time.Now()
|
|
||||||
case t < sensor.LowTemp && s.Heating:
|
|
||||||
break
|
|
||||||
case t < sensor.LowTemp && min > sensor.HeatMinutes:
|
|
||||||
PinSwitch(hpin, true, sensor.HeatInvert)
|
|
||||||
s.Heating = true
|
|
||||||
s.Changed = time.Now()
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
s.Temp = t
|
// Process the sensor
|
||||||
if sensor.Verbose {
|
states[v.ID], err = ProcessSensor(v, states[v.ID])
|
||||||
log.Printf("%s Temp: %.2f, Cooling: %t, Heating: %t, Duration: %.1f", sensor.Alias, s.Temp, s.Cooling, s.Heating, min)
|
if err != nil {
|
||||||
}
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
|
||||||
select {
|
// Write the returned state to the channel (don't block if nothing is available to listen)
|
||||||
case sc <- s:
|
select {
|
||||||
break
|
case sc <- states[v.ID]:
|
||||||
default:
|
break
|
||||||
break
|
default:
|
||||||
|
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
10
web.go
|
|
@ -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 ...")
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue