package temperature // Kernel documentation: https://www.kernel.org/doc/html/latest/w1/slaves/w1_therm.html import ( "context" "errors" "io/ioutil" "os" "strconv" "strings" "sync" "time" "git.joco.dk/sng/fermentord/internal/metrics" "github.com/getsentry/sentry-go" "github.com/rs/zerolog/log" ) var ( // ErrReadSensor indicates that the sensor could not be read ErrReadSensor = errors.New("failed to read sensor") ) var ( // C receives the temperature readings C chan TemperatureReading // ConfigUpdates receives the list of sensors to read ConfigUpdates chan []string sensors []string ) func Initialize() { sensors = make([]string, 0) C = make(chan TemperatureReading, 30) ConfigUpdates = make(chan []string, 1) } func PollSensors(ctx context.Context, wg *sync.WaitGroup, readingInterval time.Duration) { defer wg.Done() if readingInterval < 750*time.Millisecond { log.Fatal(). Dur("interval", readingInterval). Msg("Reading interval must be at least 750 ms.") } ticker := time.NewTicker(readingInterval) defer ticker.Stop() for { select { case <-ticker.C: // Trigger a bulk read and wait 750ms for sensors to convert data. triggerBulkRead() time.Sleep(750 * time.Millisecond) readSensors() case c := <-ConfigUpdates: sensors = c case <-ctx.Done(): close(C) return } } } func readSensors() { for _, sensor := range sensors { start := time.Now() t, err := read(sensor) dur := time.Since(start).Seconds() if err != nil { sentry.CaptureException(err) log.Error(). Err(err). Str("sensor", sensor). Msg("Error reading temperature sensor.") continue } metrics.TemperatureSensorReadingDegreesCelcius. WithLabelValues("sensor", sensor). Set(float64(t)) metrics.TemperatureSensorReadingDurationSeconds. WithLabelValues("sensor", sensor). Observe(dur) C <- TemperatureReading{ Time: time.Now(), Sensor: sensor, MilliDegrees: t, } } } func triggerBulkRead() error { f, err := os.OpenFile("/sys/bus/w1/w1_bus_master/therm_bulk_read", os.O_APPEND, os.ModeAppend) if err != nil { return err } defer f.Close() _, err = f.WriteString("trigger\n") return err } // read returns the temperature of the specified sensor in millidegrees celcius. func read(sensor string) (int64, error) { path := "/sys/bus/w1/devices/" + sensor + "/w1_slave" data, err := ioutil.ReadFile(path) if err != nil { return 0.0, err } raw := string(data) if !strings.Contains(raw, " YES") { return 0.0, ErrReadSensor } i := strings.LastIndex(raw, "t=") if i == -1 { return 0.0, ErrReadSensor } c, err := strconv.ParseInt(raw[i+2:len(raw)-1], 10, 64) if err != nil { return 0.0, err } return c, nil }