fermentord/pkg/temperature/ds18b20.go

140 lines
2.7 KiB
Go
Raw Normal View History

2021-08-30 20:46:38 +00:00
package temperature
2021-11-16 05:19:24 +00:00
// Kernel documentation: https://www.kernel.org/doc/html/latest/w1/slaves/w1_therm.html
2021-08-30 20:46:38 +00:00
import (
"context"
"errors"
"io/ioutil"
2021-11-16 05:19:24 +00:00
"os"
2021-08-30 20:46:38 +00:00
"strconv"
"strings"
"sync"
"time"
"git.joco.dk/sng/fermentord/internal/metrics"
2021-11-16 05:19:24 +00:00
"github.com/getsentry/sentry-go"
2021-08-30 20:46:38 +00:00
"github.com/rs/zerolog/log"
)
var (
2021-11-16 05:19:24 +00:00
// ErrReadSensor indicates that the sensor could not be read
ErrReadSensor = errors.New("failed to read sensor")
)
2021-08-30 20:46:38 +00:00
2021-11-16 05:19:24 +00:00
var (
// C receives the temperature readings
C chan TemperatureReading
2021-08-30 20:46:38 +00:00
2021-11-16 05:19:24 +00:00
// ConfigUpdates receives the list of sensors to read
ConfigUpdates chan []string
sensors []string
2021-08-30 20:46:38 +00:00
)
2021-11-16 05:19:24 +00:00
func Initialize() {
2021-08-30 20:46:38 +00:00
sensors = make([]string, 0)
2021-11-16 05:19:24 +00:00
C = make(chan TemperatureReading, 30)
ConfigUpdates = make(chan []string, 1)
2021-08-30 20:46:38 +00:00
}
2021-11-16 05:19:24 +00:00
func PollSensors(ctx context.Context, wg *sync.WaitGroup, readingInterval time.Duration) {
2021-08-30 20:46:38 +00:00
defer wg.Done()
2021-11-16 05:19:24 +00:00
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()
2021-08-30 20:46:38 +00:00
for {
select {
case <-ticker.C:
2021-11-16 05:19:24 +00:00
// Trigger a bulk read and wait 750ms for sensors to convert data.
triggerBulkRead()
time.Sleep(750 * time.Millisecond)
2021-08-30 20:46:38 +00:00
readSensors()
2021-11-16 05:19:24 +00:00
case c := <-ConfigUpdates:
sensors = c
2021-08-30 20:46:38 +00:00
case <-ctx.Done():
2021-11-16 05:19:24 +00:00
close(C)
2021-08-30 20:46:38 +00:00
return
}
}
}
func readSensors() {
for _, sensor := range sensors {
start := time.Now()
t, err := read(sensor)
dur := time.Since(start).Seconds()
if err != nil {
2021-11-16 05:19:24 +00:00
sentry.CaptureException(err)
2021-08-30 20:46:38 +00:00
log.Error().
Err(err).
Str("sensor", sensor).
Msg("Error reading temperature sensor.")
2021-11-16 05:19:24 +00:00
continue
2021-08-30 20:46:38 +00:00
}
metrics.TemperatureSensorReadingDegreesCelcius.
WithLabelValues("sensor", sensor).
Set(float64(t))
metrics.TemperatureSensorReadingDurationSeconds.
WithLabelValues("sensor", sensor).
Observe(dur)
2021-11-16 05:19:24 +00:00
C <- TemperatureReading{
2021-09-30 18:36:41 +00:00
Time: time.Now(),
Sensor: sensor,
MilliDegrees: t,
2021-08-30 20:46:38 +00:00
}
}
}
2021-11-16 05:19:24 +00:00
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
}
2021-08-30 20:46:38 +00:00
// 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
}