joshuar-go-hass-agent/internal/linux/system/time.go

116 lines
2.7 KiB
Go

// Copyright (c) 2024 Joshua Rich <joshua.rich@gmail.com>
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
//revive:disable:unused-receiver
package system
import (
"bufio"
"context"
"fmt"
"log/slog"
"os"
"strconv"
"time"
"github.com/joshuar/go-hass-agent/internal/hass/sensor"
"github.com/joshuar/go-hass-agent/internal/hass/sensor/types"
"github.com/joshuar/go-hass-agent/internal/linux"
"github.com/joshuar/go-hass-agent/internal/logging"
)
const (
uptimeInterval = 15 * time.Minute
uptimeJitter = time.Minute
timeWorkerID = "time_sensors"
)
type timeWorker struct {
boottime time.Time
logger *slog.Logger
boottimeSent bool
}
func (w *timeWorker) UpdateDelta(_ time.Duration) {}
func (w *timeWorker) Sensors(_ context.Context) ([]sensor.Details, error) {
var sensors []sensor.Details
// Send the uptime.
sensors = append(sensors, &linux.Sensor{
DisplayName: "Uptime",
UniqueID: "uptime",
Value: w.getUptime() / 60 / 60, //nolint:mnd
IsDiagnostic: true,
UnitsString: "h",
IconString: "mdi:restart",
DeviceClassValue: types.DeviceClassDuration,
StateClassValue: types.StateClassMeasurement,
})
// Send the boottime if we haven't already.
if !w.boottimeSent {
sensors = append(sensors, &linux.Sensor{
DisplayName: "Last Reboot",
UniqueID: "last_reboot",
Value: w.boottime.Format(time.RFC3339),
IsDiagnostic: true,
IconString: "mdi:restart",
DeviceClassValue: types.DeviceClassTimestamp,
})
w.boottimeSent = true
}
return sensors, nil
}
// getUptime retrieve the uptime of the device running Go Hass Agent, in
// seconds. If the uptime cannot be retrieved, it will return 0.
func (w *timeWorker) getUptime() float64 {
data, err := os.Open(linux.UptimeFile)
if err != nil {
w.logger.Debug("Unable to retrieve uptime.", slog.Any("error", err))
return 0
}
defer data.Close()
line := bufio.NewScanner(data)
line.Split(bufio.ScanWords)
if !line.Scan() {
w.logger.Debug("Could not parse uptime.")
return 0
}
uptimeValue, err := strconv.ParseFloat(line.Text(), 64)
if err != nil {
w.logger.Debug("Could not parse uptime.")
return 0
}
return uptimeValue
}
func NewTimeWorker(ctx context.Context) (*linux.PollingSensorWorker, error) {
worker := linux.NewPollingWorker(timeWorkerID, uptimeInterval, uptimeJitter)
boottime, found := linux.CtxGetBoottime(ctx)
if !found {
return worker, fmt.Errorf("%w: no boottime value", linux.ErrInvalidCtx)
}
worker.PollingType = &timeWorker{
boottime: boottime,
logger: logging.FromContext(ctx).With(slog.String("worker", timeWorkerID)),
}
return worker, nil
}