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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -78,6 +79,35 @@ func mainLoop(ctx context.Context, wg *sync.WaitGroup, js nats.JetStream, config
|
||||||
ingest.AddReading(reading)
|
ingest.AddReading(reading)
|
||||||
display.SetTemperature(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:
|
case state := <-ctrl.C:
|
||||||
gpioSetState(state, gpio)
|
gpioSetState(state, gpio)
|
||||||
ingest.AddState(state)
|
ingest.AddState(state)
|
||||||
|
|
|
@ -13,8 +13,9 @@ type Configuration struct {
|
||||||
URL string `mapstructure:"url"`
|
URL string `mapstructure:"url"`
|
||||||
Stream string `mapstructure:"stream"`
|
Stream string `mapstructure:"stream"`
|
||||||
Subject struct {
|
Subject struct {
|
||||||
Temp string `mapstructure:"temp"`
|
Event string `mapstructure:"event"`
|
||||||
State string `mapstructure:"state"`
|
State string `mapstructure:"state"`
|
||||||
|
Temp string `mapstructure:"temp"`
|
||||||
} `mapstructure:"subject"`
|
} `mapstructure:"subject"`
|
||||||
} `mapstructure:"nats"`
|
} `mapstructure:"nats"`
|
||||||
|
|
||||||
|
@ -55,6 +56,7 @@ func LoadConfiguration() *Configuration {
|
||||||
|
|
||||||
viper.SetDefault("http.port", 8000)
|
viper.SetDefault("http.port", 8000)
|
||||||
viper.SetDefault("nats.stream", "FERMENTOR")
|
viper.SetDefault("nats.stream", "FERMENTOR")
|
||||||
|
viper.SetDefault("nats.subject.event", "FERMENTOR.event")
|
||||||
viper.SetDefault("nats.subject.state", "FERMENTOR.state")
|
viper.SetDefault("nats.subject.state", "FERMENTOR.state")
|
||||||
viper.SetDefault("nats.subject.temp", "FERMENTOR.temp")
|
viper.SetDefault("nats.subject.temp", "FERMENTOR.temp")
|
||||||
viper.SetDefault("nats.url", "nats.service.consul")
|
viper.SetDefault("nats.url", "nats.service.consul")
|
||||||
|
|
|
@ -34,6 +34,7 @@ type ChamberController struct {
|
||||||
lastChamberStateChange time.Time
|
lastChamberStateChange time.Time
|
||||||
lastCoolerStateChange time.Time
|
lastCoolerStateChange time.Time
|
||||||
C chan ChamberState
|
C chan ChamberState
|
||||||
|
isPaused bool
|
||||||
|
|
||||||
name string
|
name string
|
||||||
|
|
||||||
|
@ -43,6 +44,7 @@ type ChamberController struct {
|
||||||
wortTemperature float64
|
wortTemperature float64
|
||||||
|
|
||||||
chTemp chan temperature.TemperatureReading
|
chTemp chan temperature.TemperatureReading
|
||||||
|
chPause chan bool
|
||||||
|
|
||||||
pid *PIDController
|
pid *PIDController
|
||||||
hub *sentry.Hub
|
hub *sentry.Hub
|
||||||
|
@ -55,6 +57,7 @@ func NewChamberController(name string, config configuration.Configuration) *Cham
|
||||||
config: config,
|
config: config,
|
||||||
pid: NewPIDController(config.PID.Kp, config.PID.Ki, config.PID.Kd),
|
pid: NewPIDController(config.PID.Kp, config.PID.Ki, config.PID.Kd),
|
||||||
chTemp: make(chan temperature.TemperatureReading),
|
chTemp: make(chan temperature.TemperatureReading),
|
||||||
|
chPause: make(chan bool, 1),
|
||||||
chamberState: ChamberStateIdle,
|
chamberState: ChamberStateIdle,
|
||||||
ConfigUpdates: make(chan configuration.Configuration, 1),
|
ConfigUpdates: make(chan configuration.Configuration, 1),
|
||||||
hub: sentry.CurrentHub().Clone(),
|
hub: sentry.CurrentHub().Clone(),
|
||||||
|
@ -78,11 +81,16 @@ func (p *ChamberController) Run(ctx context.Context, wg *sync.WaitGroup) {
|
||||||
p.chamberTemperature = t.Chamber
|
p.chamberTemperature = t.Chamber
|
||||||
p.wortTemperature = t.Wort
|
p.wortTemperature = t.Wort
|
||||||
|
|
||||||
|
case pause := <-p.chPause:
|
||||||
|
p.setPause(pause)
|
||||||
|
|
||||||
case c := <-p.ConfigUpdates:
|
case c := <-p.ConfigUpdates:
|
||||||
p.config = c
|
p.config = c
|
||||||
|
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
ticker.Stop()
|
ticker.Stop()
|
||||||
|
close(p.chPause)
|
||||||
|
p.isPaused = false
|
||||||
p.setChamberState(ChamberStateIdle)
|
p.setChamberState(ChamberStateIdle)
|
||||||
close(p.chTemp)
|
close(p.chTemp)
|
||||||
close(p.C)
|
close(p.C)
|
||||||
|
@ -96,12 +104,37 @@ func (p *ChamberController) SetTemperature(t temperature.TemperatureReading) {
|
||||||
p.chTemp <- t
|
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) {
|
func (p *ChamberController) setChamberState(state ChamberState) {
|
||||||
if state == p.chamberState {
|
if state == p.chamberState {
|
||||||
return
|
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 {
|
if p.chamberState == ChamberStateCooling || state == ChamberStateCooling {
|
||||||
p.lastCoolerStateChange = time.Now()
|
p.lastCoolerStateChange = time.Now()
|
||||||
|
|
|
@ -1,38 +1,80 @@
|
||||||
package hw
|
package hw
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/warthog618/gpiod"
|
"github.com/warthog618/gpiod"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
pinDoorOpen = 17
|
||||||
|
pinFanPower = 22
|
||||||
pinCoolerPower = 23
|
pinCoolerPower = 23
|
||||||
pinHeaterPower = 24
|
pinHeaterPower = 24
|
||||||
|
pinLightsPower = 25
|
||||||
|
|
||||||
off = 0
|
off = 0
|
||||||
on = 1
|
on = 1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type HWEvent int
|
||||||
|
|
||||||
|
const (
|
||||||
|
NoOp HWEvent = iota
|
||||||
|
DoorClosed
|
||||||
|
DoorOpened
|
||||||
|
)
|
||||||
|
|
||||||
type Gpio struct {
|
type Gpio struct {
|
||||||
chip *gpiod.Chip
|
chip *gpiod.Chip
|
||||||
|
doorOpen *gpiod.Line
|
||||||
|
lightsPower *gpiod.Line
|
||||||
|
fanPower *gpiod.Line
|
||||||
coolerPower *gpiod.Line
|
coolerPower *gpiod.Line
|
||||||
heaterPower *gpiod.Line
|
heaterPower *gpiod.Line
|
||||||
|
|
||||||
|
C chan HWEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGpio() (*Gpio, error) {
|
func NewGpio() (*Gpio, error) {
|
||||||
var err error
|
var err error
|
||||||
p := &Gpio{}
|
p := &Gpio{
|
||||||
|
C: make(chan HWEvent),
|
||||||
|
}
|
||||||
|
|
||||||
p.chip, err = gpiod.NewChip("gpiochip0", gpiod.WithConsumer("fermentord"))
|
p.chip, err = gpiod.NewChip("gpiochip0", gpiod.WithConsumer("fermentord"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -43,10 +85,17 @@ func NewGpio() (*Gpio, error) {
|
||||||
func (p *Gpio) Close() {
|
func (p *Gpio) Close() {
|
||||||
p.coolerPower.SetValue(off)
|
p.coolerPower.SetValue(off)
|
||||||
p.heaterPower.SetValue(off)
|
p.heaterPower.SetValue(off)
|
||||||
|
p.fanPower.SetValue(off)
|
||||||
|
p.lightsPower.SetValue(off)
|
||||||
|
|
||||||
p.heaterPower.Close()
|
p.heaterPower.Close()
|
||||||
p.coolerPower.Close()
|
p.coolerPower.Close()
|
||||||
|
p.fanPower.Close()
|
||||||
|
p.lightsPower.Close()
|
||||||
|
p.doorOpen.Close()
|
||||||
p.chip.Close()
|
p.chip.Close()
|
||||||
|
|
||||||
|
close(p.C)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Gpio) StartCooler() {
|
func (p *Gpio) StartCooler() {
|
||||||
|
@ -64,3 +113,29 @@ func (p *Gpio) StartHeater() {
|
||||||
func (p *Gpio) StopHeater() {
|
func (p *Gpio) StopHeater() {
|
||||||
p.heaterPower.SetValue(off)
|
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