diff --git a/internal/linux/time.go b/internal/linux/time.go new file mode 100644 index 000000000..ca0affbc0 --- /dev/null +++ b/internal/linux/time.go @@ -0,0 +1,128 @@ +// Copyright (c) 2023 Joshua Rich +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +package linux + +import ( + "context" + "time" + + "github.com/iancoleman/strcase" + "github.com/joshuar/go-hass-agent/internal/hass" + "github.com/lthibault/jitterbug/v2" + "github.com/rs/zerolog/log" + "github.com/shirou/gopsutil/v3/host" +) + +//go:generate stringer -type=timeProp -output time_props.go -linecomment +const ( + boottime timeProp = iota + 1 // Last Reboot + uptime // Uptime +) + +type timeProp int + +type timeSensor struct { + prop timeProp + value string +} + +func (m *timeSensor) Name() string { + return m.prop.String() +} + +func (m *timeSensor) ID() string { + return strcase.ToSnake(m.prop.String()) +} + +func (m *timeSensor) Icon() string { + return "mdi:restart" +} + +func (m *timeSensor) SensorType() hass.SensorType { + return hass.TypeSensor +} + +func (m *timeSensor) DeviceClass() hass.SensorDeviceClass { + switch m.prop { + case uptime: + return hass.Duration + case boottime: + return hass.Timestamp + } + return 0 +} + +func (m *timeSensor) StateClass() hass.SensorStateClass { + return 0 +} + +func (m *timeSensor) State() interface{} { + return m.value +} + +func (m *timeSensor) Units() string { + return "" +} + +func (m *timeSensor) Category() string { + return "diagnostic" +} + +func (m *timeSensor) Attributes() interface{} { + return nil +} + +func TimeUpdater(ctx context.Context, status chan interface{}) { + update := func() { + status <- &timeSensor{ + prop: uptime, + value: getUptime(ctx), + } + + status <- &timeSensor{ + prop: boottime, + value: getBoottime(ctx), + } + } + + update() + ticker := jitterbug.New( + time.Minute*15, + &jitterbug.Norm{Stdev: time.Minute}, + ) + go func() { + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + update() + } + } + }() +} + +func getUptime(ctx context.Context) string { + u, err := host.UptimeWithContext(ctx) + if err != nil { + log.Debug().Caller().Err(err). + Msg("Failed to retrieve uptime.") + return "Unknown" + } + epoch := time.Unix(0, 0) + uptime := time.Unix(int64(u), 0) + return uptime.Sub(epoch).String() +} + +func getBoottime(ctx context.Context) string { + u, err := host.BootTimeWithContext(ctx) + if err != nil { + log.Debug().Caller().Err(err). + Msg("Failed to retrieve boottime.") + return "Unknown" + } + return time.Unix(int64(u), 0).Format(time.RFC3339) +} diff --git a/internal/linux/time_props.go b/internal/linux/time_props.go new file mode 100644 index 000000000..552e2b460 --- /dev/null +++ b/internal/linux/time_props.go @@ -0,0 +1,25 @@ +// Code generated by "stringer -type=timeProp -output time_props.go -linecomment"; DO NOT EDIT. + +package linux + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[boottime-1] + _ = x[uptime-2] +} + +const _timeProp_name = "Last RebootUptime" + +var _timeProp_index = [...]uint8{0, 11, 17} + +func (i timeProp) String() string { + i -= 1 + if i < 0 || i >= timeProp(len(_timeProp_index)-1) { + return "timeProp(" + strconv.FormatInt(int64(i+1), 10) + ")" + } + return _timeProp_name[_timeProp_index[i]:_timeProp_index[i+1]] +} diff --git a/internal/sensors/setup_linux.go b/internal/sensors/setup_linux.go index b27bf041f..7c66e5bf0 100644 --- a/internal/sensors/setup_linux.go +++ b/internal/sensors/setup_linux.go @@ -23,6 +23,7 @@ func setupSensors() *device.SensorInfo { sensorInfo.Add("Memory", linux.MemoryUpdater) sensorInfo.Add("LoadAvg", linux.LoadAvgUpdater) sensorInfo.Add("DiskUsage", linux.DiskUsageUpdater) + sensorInfo.Add("Time", linux.TimeUpdater) // Add each SensorUpdater function here... return sensorInfo }