Add display code
This commit is contained in:
parent
7adca39509
commit
37ffb95543
10 changed files with 271 additions and 89 deletions
|
@ -13,6 +13,7 @@ import (
|
||||||
"git.joco.dk/sng/fermentord/internal/controllers"
|
"git.joco.dk/sng/fermentord/internal/controllers"
|
||||||
"git.joco.dk/sng/fermentord/internal/dwingest"
|
"git.joco.dk/sng/fermentord/internal/dwingest"
|
||||||
"git.joco.dk/sng/fermentord/internal/hw"
|
"git.joco.dk/sng/fermentord/internal/hw"
|
||||||
|
"git.joco.dk/sng/fermentord/internal/lcd"
|
||||||
"git.joco.dk/sng/fermentord/internal/metrics"
|
"git.joco.dk/sng/fermentord/internal/metrics"
|
||||||
"git.joco.dk/sng/fermentord/pkg/daemon"
|
"git.joco.dk/sng/fermentord/pkg/daemon"
|
||||||
"git.joco.dk/sng/fermentord/pkg/temperature"
|
"git.joco.dk/sng/fermentord/pkg/temperature"
|
||||||
|
@ -29,6 +30,14 @@ func mainLoop(ctx context.Context, wg *sync.WaitGroup, js nats.JetStream, config
|
||||||
defer hub.Flush(10 * time.Second)
|
defer hub.Flush(10 * time.Second)
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
|
// Display
|
||||||
|
display, err := lcd.NewLCD()
|
||||||
|
if err != nil {
|
||||||
|
hub.CaptureException(err)
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer display.Close()
|
||||||
|
|
||||||
// Controller
|
// Controller
|
||||||
ctrl := controllers.NewChamberController("Chamber 1", *config)
|
ctrl := controllers.NewChamberController("Chamber 1", *config)
|
||||||
|
|
||||||
|
@ -36,11 +45,17 @@ func mainLoop(ctx context.Context, wg *sync.WaitGroup, js nats.JetStream, config
|
||||||
ingest := dwingest.NewDWIngest()
|
ingest := dwingest.NewDWIngest()
|
||||||
|
|
||||||
// Configuration reload
|
// Configuration reload
|
||||||
|
loadConfiguration := func() {
|
||||||
|
temperature.ConfigUpdate <- *config
|
||||||
|
ctrl.ConfigUpdates <- *config
|
||||||
|
display.SetSetpointTemp(config.FermentationTemperature)
|
||||||
|
}
|
||||||
|
|
||||||
viper.OnConfigChange(func(in fsnotify.Event) {
|
viper.OnConfigChange(func(in fsnotify.Event) {
|
||||||
controllers.ReloadConfiguration(config, ctrl)
|
loadConfiguration()
|
||||||
})
|
})
|
||||||
|
loadConfiguration()
|
||||||
viper.WatchConfig()
|
viper.WatchConfig()
|
||||||
controllers.ReloadConfiguration(config, ctrl)
|
|
||||||
|
|
||||||
gpio, err := hw.NewGpio()
|
gpio, err := hw.NewGpio()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -49,7 +64,8 @@ func mainLoop(ctx context.Context, wg *sync.WaitGroup, js nats.JetStream, config
|
||||||
}
|
}
|
||||||
defer gpio.Close()
|
defer gpio.Close()
|
||||||
|
|
||||||
wg.Add(4)
|
wg.Add(5)
|
||||||
|
go display.Run(ctx, wg)
|
||||||
go ctrl.Run(ctx, wg)
|
go ctrl.Run(ctx, wg)
|
||||||
go ingest.Run(ctx, wg, js, config)
|
go ingest.Run(ctx, wg, js, config)
|
||||||
go temperature.PollSensors(ctx, wg, 1*time.Second, config.Sensors.Weight)
|
go temperature.PollSensors(ctx, wg, 1*time.Second, config.Sensors.Weight)
|
||||||
|
@ -57,13 +73,15 @@ func mainLoop(ctx context.Context, wg *sync.WaitGroup, js nats.JetStream, config
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case reading := <-temperature.C:
|
case reading := <-temperature.Reading:
|
||||||
ctrl.SetTemperature(reading)
|
ctrl.SetTemperature(reading)
|
||||||
ingest.AddReading(reading)
|
ingest.AddReading(reading)
|
||||||
|
display.SetTemperature(reading)
|
||||||
|
|
||||||
case state := <-ctrl.C:
|
case state := <-ctrl.C:
|
||||||
gpioSetState(state, gpio)
|
gpioSetState(state, gpio)
|
||||||
ingest.AddState(state)
|
ingest.AddState(state)
|
||||||
|
display.SetState(state)
|
||||||
|
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
|
|
5
go.mod
5
go.mod
|
@ -4,6 +4,8 @@ go 1.18
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/JuulLabs-OSS/ble v0.0.0-20200716215611-d4fcc9d598bb
|
github.com/JuulLabs-OSS/ble v0.0.0-20200716215611-d4fcc9d598bb
|
||||||
|
github.com/d2r2/go-hd44780 v0.0.0-20181002113701-74cc28c83a3e
|
||||||
|
github.com/d2r2/go-i2c v0.0.0-20191123181816-73a8a799d6bc
|
||||||
github.com/fsnotify/fsnotify v1.5.4
|
github.com/fsnotify/fsnotify v1.5.4
|
||||||
github.com/getsentry/sentry-go v0.13.0
|
github.com/getsentry/sentry-go v0.13.0
|
||||||
github.com/nats-io/nats.go v1.15.0
|
github.com/nats-io/nats.go v1.15.0
|
||||||
|
@ -17,6 +19,8 @@ require (
|
||||||
github.com/JuulLabs-OSS/cbgo v0.0.1 // indirect
|
github.com/JuulLabs-OSS/cbgo v0.0.1 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||||
|
github.com/d2r2/go-logger v0.0.0-20210606094344-60e9d1233e22 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/golang/protobuf v1.5.2 // indirect
|
github.com/golang/protobuf v1.5.2 // indirect
|
||||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3 // indirect
|
github.com/konsorten/go-windows-terminal-sequences v1.0.3 // indirect
|
||||||
|
@ -30,6 +34,7 @@ require (
|
||||||
github.com/nats-io/nats-server/v2 v2.7.1 // indirect
|
github.com/nats-io/nats-server/v2 v2.7.1 // indirect
|
||||||
github.com/nats-io/nkeys v0.3.0 // indirect
|
github.com/nats-io/nkeys v0.3.0 // indirect
|
||||||
github.com/nats-io/nuid v1.0.1 // indirect
|
github.com/nats-io/nuid v1.0.1 // indirect
|
||||||
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 // indirect
|
||||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.0.1 // indirect
|
github.com/pelletier/go-toml/v2 v2.0.1 // indirect
|
||||||
github.com/prometheus/client_model v0.2.0 // indirect
|
github.com/prometheus/client_model v0.2.0 // indirect
|
||||||
|
|
8
go.sum
8
go.sum
|
@ -63,6 +63,12 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX
|
||||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
|
github.com/d2r2/go-hd44780 v0.0.0-20181002113701-74cc28c83a3e h1:3gLJWdofXjBoecDb9e+giWp77saiF6r2Mtu+edWCksY=
|
||||||
|
github.com/d2r2/go-hd44780 v0.0.0-20181002113701-74cc28c83a3e/go.mod h1:IruYZr0O1UbQs3rV5N2WPM8CpaT5rRgvPPzksu1+N6o=
|
||||||
|
github.com/d2r2/go-i2c v0.0.0-20191123181816-73a8a799d6bc h1:HLRSIWzUGMLCq4ldt0W1GLs3nnAxa5EGoP+9qHgh6j0=
|
||||||
|
github.com/d2r2/go-i2c v0.0.0-20191123181816-73a8a799d6bc/go.mod h1:AwxDPnsgIpy47jbGXZHA9Rv7pDkOJvQbezPuK1Y+nNk=
|
||||||
|
github.com/d2r2/go-logger v0.0.0-20210606094344-60e9d1233e22 h1:nO+SY4KOMsF/LsZ5EtbSKhiT3M6sv/igo2PEru/xEHI=
|
||||||
|
github.com/d2r2/go-logger v0.0.0-20210606094344-60e9d1233e22/go.mod h1:eSx+YfcVy5vCjRZBNIhpIpfCGFMQ6XSOSQkDk7+VCpg=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
@ -211,6 +217,8 @@ github.com/nats-io/nkeys v0.3.0 h1:cgM5tL53EvYRU+2YLXIK0G2mJtK12Ft9oeooSZMA2G8=
|
||||||
github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4=
|
github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4=
|
||||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||||
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
|
||||||
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||||
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||||
github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU=
|
github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU=
|
||||||
|
|
|
@ -2,7 +2,6 @@ package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"log"
|
"log"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
@ -74,8 +73,10 @@ func (p *ChamberController) Run(ctx context.Context, wg *sync.WaitGroup) {
|
||||||
state := p.computeChamberState()
|
state := p.computeChamberState()
|
||||||
p.setChamberState(state)
|
p.setChamberState(state)
|
||||||
|
|
||||||
case temp := <-p.chTemp:
|
case t := <-p.chTemp:
|
||||||
p.updateTemperature(temp)
|
p.ambientTemperature = t.Ambient
|
||||||
|
p.chamberTemperature = t.Chamber
|
||||||
|
p.wortTemperature = t.Wort
|
||||||
|
|
||||||
case c := <-p.ConfigUpdates:
|
case c := <-p.ConfigUpdates:
|
||||||
p.config = c
|
p.config = c
|
||||||
|
@ -95,23 +96,6 @@ func (p *ChamberController) SetTemperature(t temperature.TemperatureReading) {
|
||||||
p.chTemp <- t
|
p.chTemp <- t
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ChamberController) updateTemperature(t temperature.TemperatureReading) {
|
|
||||||
switch t.Sensor {
|
|
||||||
case p.config.Sensors.Ambient:
|
|
||||||
p.ambientTemperature = t.Degrees
|
|
||||||
|
|
||||||
case p.config.Sensors.Chamber:
|
|
||||||
p.chamberTemperature = t.Degrees
|
|
||||||
|
|
||||||
case p.config.Sensors.Wort:
|
|
||||||
p.wortTemperature = t.Degrees
|
|
||||||
|
|
||||||
default:
|
|
||||||
log.Printf("Unknown sensor: sensor=%v temp=%v", t.Sensor, t.Degrees)
|
|
||||||
p.hub.CaptureMessage(fmt.Sprintf("Unknown sensor: sensor=%v temp=%v", t.Sensor, t.Degrees))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ChamberController) setChamberState(state ChamberState) {
|
func (p *ChamberController) setChamberState(state ChamberState) {
|
||||||
if state == p.chamberState {
|
if state == p.chamberState {
|
||||||
return
|
return
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
package controllers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"git.joco.dk/sng/fermentord/internal/configuration"
|
|
||||||
"git.joco.dk/sng/fermentord/pkg/temperature"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ReloadConfiguration(config *configuration.Configuration, ctrl *ChamberController) {
|
|
||||||
log.Printf("Reloading configuration")
|
|
||||||
|
|
||||||
temperature.ConfigUpdates <- []string{
|
|
||||||
config.Sensors.Ambient,
|
|
||||||
config.Sensors.Chamber,
|
|
||||||
config.Sensors.Wort,
|
|
||||||
}
|
|
||||||
|
|
||||||
ctrl.ConfigUpdates <- *config
|
|
||||||
}
|
|
146
internal/lcd/hd44780.go
Normal file
146
internal/lcd/hd44780.go
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
package lcd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"git.joco.dk/sng/fermentord/internal/controllers"
|
||||||
|
"git.joco.dk/sng/fermentord/pkg/temperature"
|
||||||
|
device "github.com/d2r2/go-hd44780"
|
||||||
|
"github.com/d2r2/go-i2c"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LCD struct {
|
||||||
|
ambient, chamber, setpoint, wort float64
|
||||||
|
state string
|
||||||
|
|
||||||
|
bus *i2c.I2C
|
||||||
|
lcd *device.Lcd
|
||||||
|
l1, l2 string
|
||||||
|
chTemp chan temperature.TemperatureReading
|
||||||
|
chState chan controllers.ChamberState
|
||||||
|
chSetpoint chan float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLCD() (*LCD, error) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
p := &LCD{
|
||||||
|
chTemp: make(chan temperature.TemperatureReading, 10),
|
||||||
|
chState: make(chan controllers.ChamberState, 10),
|
||||||
|
chSetpoint: make(chan float64, 10),
|
||||||
|
}
|
||||||
|
|
||||||
|
p.bus, err = i2c.NewI2C(0x27, 2)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.lcd, err = device.NewLcd(p.bus, device.LCD_16x2)
|
||||||
|
if err != nil {
|
||||||
|
p.bus.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = p.lcd.BacklightOn()
|
||||||
|
if err != nil {
|
||||||
|
p.bus.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = p.lcd.ShowMessage("Fermentor", device.SHOW_LINE_1|device.SHOW_BLANK_PADDING)
|
||||||
|
if err != nil {
|
||||||
|
p.bus.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = p.lcd.ShowMessage("Initializing", device.SHOW_LINE_2|device.SHOW_BLANK_PADDING)
|
||||||
|
if err != nil {
|
||||||
|
p.bus.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return p, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *LCD) Close() error {
|
||||||
|
return p.bus.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *LCD) SetTemperature(t temperature.TemperatureReading) {
|
||||||
|
p.chTemp <- t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *LCD) SetSetpointTemp(t float64) {
|
||||||
|
p.chSetpoint <- t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *LCD) SetState(state controllers.ChamberState) {
|
||||||
|
p.chState <- state
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *LCD) Run(ctx context.Context, wg *sync.WaitGroup) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case t := <-p.chTemp:
|
||||||
|
p.ambient = t.Ambient
|
||||||
|
p.chamber = t.Chamber
|
||||||
|
p.wort = t.Wort
|
||||||
|
p.update()
|
||||||
|
|
||||||
|
case state := <-p.chState:
|
||||||
|
switch state {
|
||||||
|
case controllers.ChamberStateIdle:
|
||||||
|
p.state = "Id"
|
||||||
|
|
||||||
|
case controllers.ChamberStateHeating:
|
||||||
|
p.state = "He"
|
||||||
|
|
||||||
|
case controllers.ChamberStateCooling:
|
||||||
|
p.state = "Co"
|
||||||
|
}
|
||||||
|
p.update()
|
||||||
|
|
||||||
|
case t := <-p.chSetpoint:
|
||||||
|
p.setpoint = t
|
||||||
|
p.update()
|
||||||
|
|
||||||
|
case <-ctx.Done():
|
||||||
|
p.lcd.ShowMessage("Fermentor", device.SHOW_LINE_1|device.SHOW_BLANK_PADDING)
|
||||||
|
p.lcd.ShowMessage("Shutting down", device.SHOW_LINE_2|device.SHOW_BLANK_PADDING)
|
||||||
|
|
||||||
|
close(p.chSetpoint)
|
||||||
|
close(p.chState)
|
||||||
|
close(p.chTemp)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *LCD) update() error {
|
||||||
|
l1 := fmt.Sprintf("W:%4.1f C:%4.1f %s", p.wort, p.chamber, p.state)
|
||||||
|
l2 := fmt.Sprintf("S:%4.1f A:%4.1f", p.ambient, p.setpoint)
|
||||||
|
|
||||||
|
if l1 != p.l1 {
|
||||||
|
if err := p.lcd.ShowMessage(l1, device.SHOW_LINE_1|device.SHOW_BLANK_PADDING); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.l1 = l1
|
||||||
|
}
|
||||||
|
|
||||||
|
if l2 != p.l2 {
|
||||||
|
if err := p.lcd.ShowMessage(l2, device.SHOW_LINE_2|device.SHOW_BLANK_PADDING); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.l2 = l2
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -14,6 +14,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.joco.dk/sng/fermentord/internal/configuration"
|
||||||
"git.joco.dk/sng/fermentord/internal/metrics"
|
"git.joco.dk/sng/fermentord/internal/metrics"
|
||||||
"github.com/getsentry/sentry-go"
|
"github.com/getsentry/sentry-go"
|
||||||
)
|
)
|
||||||
|
@ -21,6 +22,8 @@ import (
|
||||||
type BulkReadBusState int
|
type BulkReadBusState int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
NaN = -999
|
||||||
|
|
||||||
BulkReadIdle BulkReadBusState = 0
|
BulkReadIdle BulkReadBusState = 0
|
||||||
BulkReadBusy BulkReadBusState = -1
|
BulkReadBusy BulkReadBusState = -1
|
||||||
BulkReadReady BulkReadBusState = 1
|
BulkReadReady BulkReadBusState = 1
|
||||||
|
@ -35,23 +38,26 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// C receives the temperature readings
|
// Reading receives the temperature readings
|
||||||
C chan TemperatureReading
|
Reading chan TemperatureReading
|
||||||
|
ConfigUpdate chan configuration.Configuration
|
||||||
|
|
||||||
// ConfigUpdates receives the list of sensors to read
|
|
||||||
ConfigUpdates chan []string
|
|
||||||
sensors []string
|
|
||||||
accErrTrigger int
|
accErrTrigger int
|
||||||
accErrSensor map[string]int
|
sensors []Sensor
|
||||||
filters map[string]*EWMAFilter
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
sensors = make([]string, 0)
|
Reading = make(chan TemperatureReading, 30)
|
||||||
filters = make(map[string]*EWMAFilter)
|
ConfigUpdate = make(chan configuration.Configuration)
|
||||||
C = make(chan TemperatureReading, 30)
|
sensors = make([]Sensor, 0)
|
||||||
ConfigUpdates = make(chan []string, 1)
|
}
|
||||||
accErrSensor = make(map[string]int)
|
|
||||||
|
func configure(config configuration.Configuration) {
|
||||||
|
sensors = []Sensor{
|
||||||
|
NewSensor("Ambient", config.Sensors.Ambient, config.Sensors.Weight),
|
||||||
|
NewSensor("Chamber", config.Sensors.Chamber, config.Sensors.Weight),
|
||||||
|
NewSensor("Wort", config.Sensors.Wort, config.Sensors.Weight),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func PollSensors(ctx context.Context, wg *sync.WaitGroup, readingInterval time.Duration, filterWeight float64) {
|
func PollSensors(ctx context.Context, wg *sync.WaitGroup, readingInterval time.Duration, filterWeight float64) {
|
||||||
|
@ -76,12 +82,6 @@ func PollSensors(ctx context.Context, wg *sync.WaitGroup, readingInterval time.D
|
||||||
log.Fatal("Thermal bulk read failed 60 times in a row -- terminating")
|
log.Fatal("Thermal bulk read failed 60 times in a row -- terminating")
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, sensor := range sensors {
|
|
||||||
if accErrSensor[sensor] > 60 {
|
|
||||||
log.Fatalf("Thermal sensor read of %v failed 60 times in a row -- terminating", sensor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trigger a bulk read to start conversion on all sensors.
|
// Trigger a bulk read to start conversion on all sensors.
|
||||||
if err := triggerBulkRead(); err != nil {
|
if err := triggerBulkRead(); err != nil {
|
||||||
accErrTrigger++
|
accErrTrigger++
|
||||||
|
@ -118,52 +118,57 @@ func PollSensors(ctx context.Context, wg *sync.WaitGroup, readingInterval time.D
|
||||||
readSensors(hub)
|
readSensors(hub)
|
||||||
}
|
}
|
||||||
|
|
||||||
case c := <-ConfigUpdates:
|
case c := <-ConfigUpdate:
|
||||||
sensors = c
|
configure(c)
|
||||||
|
|
||||||
for k := range filters {
|
|
||||||
delete(filters, k)
|
|
||||||
}
|
|
||||||
for _, x := range sensors {
|
|
||||||
filters[x] = NewEWMAFilter(filterWeight)
|
|
||||||
}
|
|
||||||
|
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
close(C)
|
close(Reading)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func readSensors(hub *sentry.Hub) {
|
func readSensors(hub *sentry.Hub) {
|
||||||
|
r := TemperatureReading{
|
||||||
|
Time: time.Now(),
|
||||||
|
Ambient: NaN,
|
||||||
|
Chamber: NaN,
|
||||||
|
Wort: NaN,
|
||||||
|
}
|
||||||
|
|
||||||
for _, sensor := range sensors {
|
for _, sensor := range sensors {
|
||||||
t, err := read(sensor)
|
t, err := read(sensor.Path)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
accErrSensor[sensor]++
|
sensor.Fail()
|
||||||
hub.CaptureException(err)
|
hub.CaptureException(err)
|
||||||
log.Printf("Error reading temperature sensor %v: %v", sensor, err)
|
log.Printf("Error reading temperature sensor %v: %v", sensor, err)
|
||||||
metrics.TemperatureSensorReadingStatus.WithLabelValues(sensor, "failed").Inc()
|
metrics.TemperatureSensorReadingStatus.WithLabelValues(sensor.Name, "failed").Inc()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
accErrSensor[sensor] = 0
|
tw := sensor.Update(t)
|
||||||
tw := filters[sensor].Update(float64(t) / 1000)
|
|
||||||
|
|
||||||
r := TemperatureReading{
|
switch sensor.Name {
|
||||||
Time: time.Now(),
|
case "Ambient":
|
||||||
Sensor: sensor,
|
r.Ambient = tw
|
||||||
Degrees: tw,
|
|
||||||
|
case "Chamber":
|
||||||
|
r.Chamber = tw
|
||||||
|
|
||||||
|
case "Wort":
|
||||||
|
r.Wort = tw
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO Move metrics out
|
||||||
metrics.TemperatureSensorReadingDegreesCelcius.
|
metrics.TemperatureSensorReadingDegreesCelcius.
|
||||||
WithLabelValues(sensor).
|
WithLabelValues(sensor.Name).
|
||||||
Set(tw)
|
Set(tw)
|
||||||
|
|
||||||
C <- r
|
metrics.TemperatureSensorReadingStatus.WithLabelValues(sensor.Name, "ok").Inc()
|
||||||
|
|
||||||
metrics.TemperatureSensorReadingStatus.WithLabelValues(sensor, "ok").Inc()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reading <- r
|
||||||
}
|
}
|
||||||
|
|
||||||
func triggerBulkRead() error {
|
func triggerBulkRead() error {
|
||||||
|
|
|
@ -8,8 +8,8 @@ type EWMAFilter struct {
|
||||||
isInitialized bool
|
isInitialized bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewEWMAFilter(weight float64) *EWMAFilter {
|
func NewEWMAFilter(weight float64) EWMAFilter {
|
||||||
return &EWMAFilter{
|
return EWMAFilter{
|
||||||
weight: weight,
|
weight: weight,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
35
pkg/temperature/sensor.go
Normal file
35
pkg/temperature/sensor.go
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
package temperature
|
||||||
|
|
||||||
|
import "log"
|
||||||
|
|
||||||
|
type Sensor struct {
|
||||||
|
Name string
|
||||||
|
Path string
|
||||||
|
Filter EWMAFilter
|
||||||
|
accErrors int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSensor(name, path string, weight float64) Sensor {
|
||||||
|
return Sensor{
|
||||||
|
Name: name,
|
||||||
|
Path: path,
|
||||||
|
Filter: NewEWMAFilter(weight),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Sensor) Update(value int64) float64 {
|
||||||
|
p.accErrors = 0
|
||||||
|
degrees := float64(value) / 1000
|
||||||
|
|
||||||
|
return p.Filter.Update(degrees)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Sensor) Fail() error {
|
||||||
|
p.accErrors++
|
||||||
|
|
||||||
|
if p.accErrors > 60 {
|
||||||
|
log.Fatalf("Thermal sensor read of %v failed 60 times in a row -- terminating", p.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -3,7 +3,8 @@ package temperature
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
type TemperatureReading struct {
|
type TemperatureReading struct {
|
||||||
Sensor string `json:"sensor"`
|
|
||||||
Time time.Time `json:"time"`
|
Time time.Time `json:"time"`
|
||||||
Degrees float64 `json:"degrees"`
|
Ambient float64 `json:"ambient"`
|
||||||
|
Chamber float64 `json:"chamber"`
|
||||||
|
Wort float64 `json:"wort"`
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue