agent: refactor new battery package (#1872)

This commit is contained in:
henrygd
2026-04-02 19:55:48 -04:00
committed by hank
parent 80b73c7faf
commit d656036d3b
7 changed files with 33 additions and 84 deletions

View File

@@ -1,14 +1,11 @@
//go:build !freebsd && !darwin && !linux && !windows
// Package battery provides functions to check if the system has a battery and to get the battery stats.
package battery
import "errors"
func HasReadableBattery() bool {
return false
}
func GetBatteryStats() (uint8, uint8, error) {
return 0, 0, errors.ErrUnsupported
}
const (
stateUnknown uint8 = iota
stateEmpty
stateFull
stateCharging
stateDischarging
stateIdle
)

View File

@@ -7,19 +7,11 @@ import (
"log/slog"
"math"
"os/exec"
"sync"
"howett.net/plist"
)
const (
stateUnknown uint8 = 0
stateEmpty uint8 = 1
stateFull uint8 = 2
stateCharging uint8 = 3
stateDischarging uint8 = 4
stateIdle uint8 = 5
)
type macBattery struct {
CurrentCapacity int `plist:"CurrentCapacity"`
MaxCapacity int `plist:"MaxCapacity"`
@@ -28,11 +20,6 @@ type macBattery struct {
ExternalConnected bool `plist:"ExternalConnected"`
}
var (
systemHasBattery = false
haveCheckedBattery = false
)
func readMacBatteries() ([]macBattery, error) {
out, err := exec.Command("ioreg", "-n", "AppleSmartBattery", "-r", "-a").Output()
if err != nil {
@@ -49,11 +36,8 @@ func readMacBatteries() ([]macBattery, error) {
}
// HasReadableBattery checks if the system has a battery and returns true if it does.
func HasReadableBattery() bool {
if haveCheckedBattery {
return systemHasBattery
}
haveCheckedBattery = true
var HasReadableBattery = sync.OnceValue(func() bool {
systemHasBattery := false
batteries, err := readMacBatteries()
for _, bat := range batteries {
if bat.MaxCapacity > 0 {
@@ -65,7 +49,7 @@ func HasReadableBattery() bool {
slog.Debug("No battery found", "err", err)
}
return systemHasBattery
}
})
// GetBatteryStats returns the current battery percent and charge state.
// Uses CurrentCapacity/MaxCapacity to match the value macOS displays.

View File

@@ -9,27 +9,14 @@ import (
"os"
"path/filepath"
"strconv"
"sync"
"github.com/henrygd/beszel/agent/utils"
)
const (
stateUnknown uint8 = 0
stateEmpty uint8 = 1
stateFull uint8 = 2
stateCharging uint8 = 3
stateDischarging uint8 = 4
stateIdle uint8 = 5
)
const sysfsPowerSupply = "/sys/class/power_supply"
var (
systemHasBattery = false
haveCheckedBattery = false
)
func getBatteryPaths() ([]string, error) {
var getBatteryPaths = sync.OnceValues(func() ([]string, error) {
entries, err := os.ReadDir(sysfsPowerSupply)
if err != nil {
return nil, err
@@ -42,7 +29,7 @@ func getBatteryPaths() ([]string, error) {
}
}
return paths, nil
}
})
func parseSysfsState(status string) uint8 {
switch status {
@@ -62,11 +49,8 @@ func parseSysfsState(status string) uint8 {
}
// HasReadableBattery checks if the system has a battery and returns true if it does.
func HasReadableBattery() bool {
if haveCheckedBattery {
return systemHasBattery
}
haveCheckedBattery = true
var HasReadableBattery = sync.OnceValue(func() bool {
systemHasBattery := false
paths, err := getBatteryPaths()
for _, path := range paths {
if _, ok := utils.ReadStringFileOK(filepath.Join(path, "capacity")); ok {
@@ -78,7 +62,7 @@ func HasReadableBattery() bool {
slog.Debug("No battery found", "err", err)
}
return systemHasBattery
}
})
// GetBatteryStats returns the current battery percent and charge state.
// Reads /sys/class/power_supply/*/capacity directly so the kernel-reported

View File

@@ -1,4 +1,4 @@
//go:build freebsd
//go:build !darwin && !linux && !windows
package battery

View File

@@ -6,21 +6,13 @@ import (
"errors"
"log/slog"
"math"
"sync"
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
const (
stateUnknown uint8 = 0
stateEmpty uint8 = 1
stateFull uint8 = 2
stateCharging uint8 = 3
stateDischarging uint8 = 4
stateIdle uint8 = 5
)
type batteryQueryInformation struct {
BatteryTag uint32
InformationLevel int32
@@ -77,15 +69,16 @@ var guidDeviceBattery = winGUID{
}
var (
setupapi = &windows.LazyDLL{Name: "setupapi.dll", System: true}
setupDiGetClassDevsW = setupapi.NewProc("SetupDiGetClassDevsW")
setupDiEnumDeviceInterfaces = setupapi.NewProc("SetupDiEnumDeviceInterfaces")
setupapi = &windows.LazyDLL{Name: "setupapi.dll", System: true}
setupDiGetClassDevsW = setupapi.NewProc("SetupDiGetClassDevsW")
setupDiEnumDeviceInterfaces = setupapi.NewProc("SetupDiEnumDeviceInterfaces")
setupDiGetDeviceInterfaceDetailW = setupapi.NewProc("SetupDiGetDeviceInterfaceDetailW")
setupDiDestroyDeviceInfoList = setupapi.NewProc("SetupDiDestroyDeviceInfoList")
setupDiDestroyDeviceInfoList = setupapi.NewProc("SetupDiDestroyDeviceInfoList")
)
func setupDiSetup(proc *windows.LazyProc, nargs, a1, a2, a3, a4, a5, a6 uintptr) (uintptr, error) {
r1, _, errno := syscall.Syscall6(proc.Addr(), nargs, a1, a2, a3, a4, a5, a6)
_ = nargs
r1, _, errno := syscall.SyscallN(proc.Addr(), a1, a2, a3, a4, a5, a6)
if windows.Handle(r1) == windows.InvalidHandle {
if errno != 0 {
return 0, error(errno)
@@ -96,7 +89,8 @@ func setupDiSetup(proc *windows.LazyProc, nargs, a1, a2, a3, a4, a5, a6 uintptr)
}
func setupDiCall(proc *windows.LazyProc, nargs, a1, a2, a3, a4, a5, a6 uintptr) syscall.Errno {
r1, _, errno := syscall.Syscall6(proc.Addr(), nargs, a1, a2, a3, a4, a5, a6)
_ = nargs
r1, _, errno := syscall.SyscallN(proc.Addr(), a1, a2, a3, a4, a5, a6)
if r1 == 0 {
if errno != 0 {
return errno
@@ -137,7 +131,7 @@ func winBatteryGet(idx int) (full, current uint32, state uint8, err error) {
if err != nil {
return 0, 0, stateUnknown, err
}
defer syscall.Syscall(setupDiDestroyDeviceInfoList.Addr(), 1, hdev, 0, 0)
defer syscall.SyscallN(setupDiDestroyDeviceInfoList.Addr(), hdev)
var did spDeviceInterfaceData
did.cbSize = uint32(unsafe.Sizeof(did))
@@ -256,17 +250,9 @@ func winBatteryGet(idx int) (full, current uint32, state uint8, err error) {
return bi.FullChargedCapacity, bs.Capacity, readWinBatteryState(bs.PowerState), nil
}
var (
systemHasBattery = false
haveCheckedBattery = false
)
// HasReadableBattery checks if the system has a battery and returns true if it does.
func HasReadableBattery() bool {
if haveCheckedBattery {
return systemHasBattery
}
haveCheckedBattery = true
var HasReadableBattery = sync.OnceValue(func() bool {
systemHasBattery := false
full, _, _, err := winBatteryGet(0)
if err == nil && full > 0 {
systemHasBattery = true
@@ -275,7 +261,7 @@ func HasReadableBattery() bool {
slog.Debug("No battery found", "err", err)
}
return systemHasBattery
}
})
// GetBatteryStats returns the current battery percent and charge state.
func GetBatteryStats() (batteryPercent uint8, batteryState uint8, err error) {

2
go.mod
View File

@@ -5,7 +5,7 @@ go 1.26.1
require (
github.com/blang/semver v3.5.1+incompatible
github.com/coreos/go-systemd/v22 v22.7.0
github.com/ebitengine/purego v0.10.0
github.com/ebitengine/purego v0.10.0
github.com/fxamacker/cbor/v2 v2.9.0
github.com/gliderlabs/ssh v0.3.8
github.com/google/uuid v1.6.0

2
go.sum
View File

@@ -17,8 +17,6 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/distatus/battery v0.11.0 h1:KJk89gz90Iq/wJtbjjM9yUzBXV+ASV/EG2WOOL7N8lc=
github.com/distatus/battery v0.11.0/go.mod h1:KmVkE8A8hpIX4T78QRdMktYpEp35QfOL8A8dwZBxq2k=
github.com/domodwyer/mailyak/v3 v3.6.2 h1:x3tGMsyFhTCaxp6ycgR0FE/bu5QiNp+hetUuCOBXMn8=
github.com/domodwyer/mailyak/v3 v3.6.2/go.mod h1:lOm/u9CyCVWHeaAmHIdF4RiKVxKUT/H5XX10lIKAL6c=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=