package dwingest import ( "context" "encoding/json" "fmt" "log" "sync" "time" "git.joco.dk/sng/fermentord/internal/configuration" "git.joco.dk/sng/fermentord/internal/controllers" "git.joco.dk/sng/fermentord/pkg/temperature" "git.joco.dk/sng/fermentord/pkg/tilt" "github.com/getsentry/sentry-go" "github.com/gofrs/uuid" "github.com/nats-io/nats.go" ) type DWIngest struct { ConfigUpdates chan configuration.Configuration chTemperatureReading chan temperature.TemperatureReading chState chan controllers.ChamberState chEvent chan string js nats.JetStream config configuration.Configuration hub *sentry.Hub } func NewDWIngest(js nats.JetStream, config configuration.Configuration) *DWIngest { return &DWIngest{ chTemperatureReading: make(chan temperature.TemperatureReading, 3600), chState: make(chan controllers.ChamberState, 100), chEvent: make(chan string, 100), hub: sentry.CurrentHub().Clone(), js: js, config: config, ConfigUpdates: make(chan configuration.Configuration, 1), } } func (p *DWIngest) AddReading(reading temperature.TemperatureReading) { select { case p.chTemperatureReading <- reading: break default: err := fmt.Errorf("channel overflow on dwingest temperature channel") p.hub.CaptureException(err) log.Print(err) } } func (p *DWIngest) AddState(state controllers.ChamberState) { select { case p.chState <- state: break default: err := fmt.Errorf("channel overflow on dwingest state channel") p.hub.CaptureException(err) log.Print(err) } } func (p *DWIngest) AddEvent(event string) { select { case p.chEvent <- event: break default: err := fmt.Errorf("channel overflow on dwingest event channel") p.hub.CaptureException(err) log.Print(err) } } func (p *DWIngest) Run(ctx context.Context, wg *sync.WaitGroup) { defer wg.Done() defer p.hub.Flush(10 * time.Second) for { select { case reading := <-p.chTemperatureReading: p.publishTemperatureReading(reading) case state := <-p.chState: p.publishState(state) case ev := <-p.chEvent: p.publishEvent(ev) // TODO Refactor case t := <-tilt.C: p.publishTilt(t) case config := <-p.ConfigUpdates: p.config = config case <-ctx.Done(): return } } } func (p *DWIngest) publish(subject string, reading any) error { b, err := json.Marshal(reading) if err != nil { p.hub.CaptureException(err) log.Printf("Error marshaling JSON: %v", err) return err } msgID, err := uuid.NewV7(uuid.MillisecondPrecision) if err != nil { p.hub.CaptureException(err) log.Print(err) return err } msg := nats.NewMsg(subject) msg.Header.Add("Nats-Msg-Id", msgID.String()) msg.Data = b _, err = p.js.PublishMsg(msg, nats.AckWait(30*time.Second)) if err != nil { p.hub.CaptureException(err) log.Print(err) return err } return nil } func (p *DWIngest) publishTilt(t tilt.Tilt) error { ev := Tilt{ Time: time.Now().UTC(), BrewUUID: p.config.Brew.UUID, Color: string(t.Color()), Gravity: t.Gravity(), Temperature: t.Celsius(), } return p.publish(p.config.NATS.Subject.Tilt, ev) } func (p *DWIngest) publishTemperatureReading(reading temperature.TemperatureReading) error { reading.BrewUUID = p.config.Brew.UUID return p.publish(p.config.NATS.Subject.Temp, reading) } func (p *DWIngest) publishState(state controllers.ChamberState) error { st := State{ Time: time.Now().UTC(), BrewUUID: p.config.Brew.UUID, State: controllers.ChamberStateMap[state], } return p.publish(p.config.NATS.Subject.State, st) } func (p *DWIngest) publishEvent(event string) error { ev := Event{ Time: time.Now().UTC(), BrewUUID: p.config.Brew.UUID, Event: event, } return p.publish(p.config.NATS.Subject.Event, ev) }