158 lines
3.1 KiB
Go
158 lines
3.1 KiB
Go
package temperature
|
|
|
|
// Kernel documentation: https://www.kernel.org/doc/html/latest/w1/slaves/w1_therm.html
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"git.joco.dk/sng/fermentord/internal/metrics"
|
|
"github.com/getsentry/sentry-go"
|
|
)
|
|
|
|
const (
|
|
sensorConversionTime = 750 * time.Millisecond
|
|
)
|
|
|
|
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) {
|
|
hub := sentry.CurrentHub().Clone()
|
|
defer hub.Flush(10 * time.Second)
|
|
defer wg.Done()
|
|
|
|
if readingInterval < sensorConversionTime {
|
|
log.Fatalf("Reading interval must be at least %v ms.", sensorConversionTime)
|
|
}
|
|
|
|
ticker := time.NewTicker(readingInterval)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
start := time.Now()
|
|
|
|
// Trigger a bulk read to start conversion on all sensors.
|
|
if err := triggerBulkRead(); err != nil {
|
|
hub.CaptureException(err)
|
|
log.Print(err)
|
|
break
|
|
}
|
|
|
|
// Ensure that we wait for sensors to convert data.
|
|
deltaSleep := sensorConversionTime - time.Since(start)
|
|
if deltaSleep > 0 {
|
|
// Wait for sensors to convert data.
|
|
time.Sleep(deltaSleep)
|
|
}
|
|
|
|
// Read all sensors.
|
|
readSensors()
|
|
|
|
case c := <-ConfigUpdates:
|
|
sensors = c
|
|
|
|
case <-ctx.Done():
|
|
close(C)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func readSensors() {
|
|
hub := sentry.CurrentHub().Clone()
|
|
defer hub.Flush(10 * time.Second)
|
|
|
|
for _, sensor := range sensors {
|
|
start := time.Now()
|
|
t, err := read(sensor)
|
|
dur := time.Since(start).Seconds()
|
|
|
|
if err != nil {
|
|
hub.CaptureException(err)
|
|
log.Printf("Error reading temperature sensor %v: %v", sensor, err)
|
|
continue
|
|
}
|
|
|
|
r := TemperatureReading{
|
|
Time: time.Now(),
|
|
Sensor: sensor,
|
|
MilliDegrees: t,
|
|
}
|
|
|
|
metrics.TemperatureSensorReadingDegreesCelcius.
|
|
WithLabelValues(sensor).
|
|
Set(r.Degrees())
|
|
|
|
metrics.TemperatureSensorReadingDurationSeconds.
|
|
WithLabelValues(sensor).
|
|
Observe(dur)
|
|
|
|
C <- r
|
|
}
|
|
}
|
|
|
|
func triggerBulkRead() error {
|
|
f, err := os.OpenFile("/sys/bus/w1/devices/w1_bus_master1/therm_bulk_read", os.O_WRONLY, 0644)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
|
|
_, err = f.WriteString("trigger")
|
|
return err
|
|
}
|
|
|
|
// read returns the temperature of the specified sensor in millidegrees celcius.
|
|
func read(sensor string) (int64, error) {
|
|
path := filepath.Join("/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
|
|
}
|