package lcd import ( "context" "fmt" "log" "sync" "time" "git.joco.dk/snr/fermentord/internal/controllers" "git.joco.dk/snr/fermentord/pkg/temperature" device "github.com/d2r2/go-hd44780" "github.com/d2r2/go-i2c" "github.com/getsentry/sentry-go" ) 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 lastUpdate time.Time hub *sentry.Hub isOpen bool } func NewLCD(bus int) (*LCD, error) { var err error p := &LCD{ chTemp: make(chan temperature.TemperatureReading, 10), chState: make(chan controllers.ChamberState, 10), chSetpoint: make(chan float64, 10), lastUpdate: time.Now(), hub: sentry.CurrentHub().Clone(), } p.bus, err = i2c.NewI2C(0x27, bus) 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) 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 } p.isOpen = true return p, nil } func (p *LCD) Close() error { if err := p.lcd.BacklightOff(); err != nil { p.hub.CaptureException(err) log.Print(err) } p.hub.Flush(10 * time.Second) return p.bus.Close() } func (p *LCD) SetTemperature(t temperature.TemperatureReading) { select { case p.chTemp <- t: break default: err := fmt.Errorf("channel overflow on display temperature channel") p.hub.CaptureException(err) log.Print(err) } } func (p *LCD) SetSetpointTemp(t float64) { select { case p.chSetpoint <- t: break default: err := fmt.Errorf("channel overflow on display setpoint temperature channel") p.hub.CaptureException(err) log.Print(err) } } func (p *LCD) SetState(state controllers.ChamberState) { select { case p.chState <- state: break default: err := fmt.Errorf("channel overflow on display state channel") p.hub.CaptureException(err) log.Print(err) } } 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 if err := p.update(); err != nil { p.hub.CaptureException(err) log.Print(err) } case state := <-p.chState: switch state { case controllers.ChamberStateIdle: p.state = "Id" case controllers.ChamberStateHeating: p.state = "He" case controllers.ChamberStateCooling: p.state = "Co" } if err := p.update(); err != nil { p.hub.CaptureException(err) log.Print(err) } case t := <-p.chSetpoint: p.setpoint = t if err := p.update(); err != nil { p.hub.CaptureException(err) log.Print(err) } case <-ctx.Done(): if err := p.lcd.ShowMessage("Fermentor", device.SHOW_LINE_1|device.SHOW_BLANK_PADDING); err != nil { p.hub.CaptureException(err) log.Print(err) } if err := p.lcd.ShowMessage("Shutting down", device.SHOW_LINE_2|device.SHOW_BLANK_PADDING); err != nil { p.hub.CaptureException(err) log.Print(err) } return } } } func (p *LCD) update() error { if !p.isOpen { return nil } if time.Since(p.lastUpdate) < 3*time.Second { return nil } 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 } p.lastUpdate = time.Now() return nil }