Add hardware support for door sensor and lights
This commit is contained in:
parent
37ffb95543
commit
3596204460
4 changed files with 146 additions and 6 deletions
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue