Add hardware support for door sensor and lights

This commit is contained in:
Søren Rasmussen 2022-03-12 20:51:15 +01:00
parent 37ffb95543
commit 3596204460
4 changed files with 146 additions and 6 deletions

View file

@ -2,6 +2,7 @@ package main
import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
@ -78,6 +79,35 @@ func mainLoop(ctx context.Context, wg *sync.WaitGroup, js nats.JetStream, config
ingest.AddReading(reading)
display.SetTemperature(reading)
case ev := <-gpio.C:
switch ev {
case hw.DoorClosed:
gpio.LightsOff()
gpio.StartFan()
ctrl.Resume()
case hw.DoorOpened:
ctrl.Pause()
gpio.LightsOn()
gpio.StopFan()
}
b, err := json.Marshal(map[string]interface{}{
"event": ev,
})
if err != nil {
hub.CaptureException(err)
log.Printf("Error marshaling JSON: %v", err)
break
}
// Publish to NATS
_, err = js.Publish(config.NATS.Subject.Event, b)
if err != nil {
hub.CaptureException(err)
log.Printf("Error publishing to NATS: %v", err)
}
case state := <-ctrl.C:
gpioSetState(state, gpio)
ingest.AddState(state)

View file

@ -13,8 +13,9 @@ type Configuration struct {
URL string `mapstructure:"url"`
Stream string `mapstructure:"stream"`
Subject struct {
Temp string `mapstructure:"temp"`
Event string `mapstructure:"event"`
State string `mapstructure:"state"`
Temp string `mapstructure:"temp"`
} `mapstructure:"subject"`
} `mapstructure:"nats"`
@ -55,6 +56,7 @@ func LoadConfiguration() *Configuration {
viper.SetDefault("http.port", 8000)
viper.SetDefault("nats.stream", "FERMENTOR")
viper.SetDefault("nats.subject.event", "FERMENTOR.event")
viper.SetDefault("nats.subject.state", "FERMENTOR.state")
viper.SetDefault("nats.subject.temp", "FERMENTOR.temp")
viper.SetDefault("nats.url", "nats.service.consul")

View file

@ -34,6 +34,7 @@ type ChamberController struct {
lastChamberStateChange time.Time
lastCoolerStateChange time.Time
C chan ChamberState
isPaused bool
name string
@ -42,7 +43,8 @@ type ChamberController struct {
chamberTemperature float64
wortTemperature float64
chTemp chan temperature.TemperatureReading
chTemp chan temperature.TemperatureReading
chPause chan bool
pid *PIDController
hub *sentry.Hub
@ -55,6 +57,7 @@ func NewChamberController(name string, config configuration.Configuration) *Cham
config: config,
pid: NewPIDController(config.PID.Kp, config.PID.Ki, config.PID.Kd),
chTemp: make(chan temperature.TemperatureReading),
chPause: make(chan bool, 1),
chamberState: ChamberStateIdle,
ConfigUpdates: make(chan configuration.Configuration, 1),
hub: sentry.CurrentHub().Clone(),
@ -78,11 +81,16 @@ func (p *ChamberController) Run(ctx context.Context, wg *sync.WaitGroup) {
p.chamberTemperature = t.Chamber
p.wortTemperature = t.Wort
case pause := <-p.chPause:
p.setPause(pause)
case c := <-p.ConfigUpdates:
p.config = c
case <-ctx.Done():
ticker.Stop()
close(p.chPause)
p.isPaused = false
p.setChamberState(ChamberStateIdle)
close(p.chTemp)
close(p.C)
@ -96,12 +104,37 @@ func (p *ChamberController) SetTemperature(t temperature.TemperatureReading) {
p.chTemp <- t
}
// Pause is a thread-safe way to pause the controller.
func (p *ChamberController) Pause() {
p.chPause <- true
}
// Resume is a thread-safe way to resume the controller.
func (p *ChamberController) Resume() {
p.chPause <- false
}
func (p *ChamberController) setPause(pause bool) {
if pause {
p.setChamberState(ChamberStateIdle)
p.isPaused = true
} else {
p.isPaused = false
state := p.computeChamberState()
p.setChamberState(state)
}
}
func (p *ChamberController) setChamberState(state ChamberState) {
if state == p.chamberState {
return
}
//log.Printf("State changed from %v to %v", p.chamberState, state)
if p.isPaused {
return
}
log.Printf("State changed from %v to %v", p.chamberState, state)
if p.chamberState == ChamberStateCooling || state == ChamberStateCooling {
p.lastCoolerStateChange = time.Now()

View file

@ -1,38 +1,80 @@
package hw
import (
"time"
"github.com/warthog618/gpiod"
)
const (
pinDoorOpen = 17
pinFanPower = 22
pinCoolerPower = 23
pinHeaterPower = 24
pinLightsPower = 25
off = 0
on = 1
)
type HWEvent int
const (
NoOp HWEvent = iota
DoorClosed
DoorOpened
)
type Gpio struct {
chip *gpiod.Chip
doorOpen *gpiod.Line
lightsPower *gpiod.Line
fanPower *gpiod.Line
coolerPower *gpiod.Line
heaterPower *gpiod.Line
C chan HWEvent
}
func NewGpio() (*Gpio, error) {
var err error
p := &Gpio{}
p := &Gpio{
C: make(chan HWEvent),
}
p.chip, err = gpiod.NewChip("gpiochip0", gpiod.WithConsumer("fermentord"))
if err != nil {
return nil, err
}
p.coolerPower, err = p.chip.RequestLine(pinCoolerPower, gpiod.AsOutput(1))
p.doorOpen, err = p.chip.RequestLine(
pinDoorOpen,
gpiod.AsInput,
gpiod.LineBiasPullUp,
gpiod.WithDebounce(200*time.Millisecond),
gpiod.WithBothEdges,
gpiod.WithEventHandler(p.onDoorStateChanged),
)
if err != nil {
return nil, err
}
p.heaterPower, err = p.chip.RequestLine(pinHeaterPower, gpiod.AsOutput(1))
p.lightsPower, err = p.chip.RequestLine(pinLightsPower, gpiod.AsOutput(0))
if err != nil {
return nil, err
}
p.fanPower, err = p.chip.RequestLine(pinFanPower, gpiod.AsOutput(0))
if err != nil {
return nil, err
}
p.coolerPower, err = p.chip.RequestLine(pinCoolerPower, gpiod.AsOutput(0))
if err != nil {
return nil, err
}
p.heaterPower, err = p.chip.RequestLine(pinHeaterPower, gpiod.AsOutput(0))
if err != nil {
return nil, err
}
@ -43,10 +85,17 @@ func NewGpio() (*Gpio, error) {
func (p *Gpio) Close() {
p.coolerPower.SetValue(off)
p.heaterPower.SetValue(off)
p.fanPower.SetValue(off)
p.lightsPower.SetValue(off)
p.heaterPower.Close()
p.coolerPower.Close()
p.fanPower.Close()
p.lightsPower.Close()
p.doorOpen.Close()
p.chip.Close()
close(p.C)
}
func (p *Gpio) StartCooler() {
@ -64,3 +113,29 @@ func (p *Gpio) StartHeater() {
func (p *Gpio) StopHeater() {
p.heaterPower.SetValue(off)
}
func (p *Gpio) StartFan() {
p.fanPower.SetValue(on)
}
func (p *Gpio) StopFan() {
p.fanPower.SetValue(off)
}
func (p *Gpio) LightsOn() {
p.lightsPower.SetValue(on)
}
func (p *Gpio) LightsOff() {
p.lightsPower.SetValue(off)
}
func (p *Gpio) onDoorStateChanged(le gpiod.LineEvent) {
switch le.Type {
case gpiod.LineEventFallingEdge:
p.C <- DoorOpened
case gpiod.LineEventRisingEdge:
p.C <- DoorClosed
}
}