From d608cf0955d0f08a8b581130af2009f6345c6a17 Mon Sep 17 00:00:00 2001 From: Nathan Heaps <1282393+nsheaps@users.noreply.github.com> Date: Sat, 25 Oct 2025 13:23:29 -0400 Subject: [PATCH] fix: send battery stats even if some batteries are missing stats (#1287) Implement battery error reporting mechanism Added error reporting for battery information retrieval. Handles cases like bluetooth devices where `battery` finds it but it has incomplete information about that battery, which would have otherwise caused a null pointer error. Related: #1254 --- agent/battery/battery.go | 51 +++++++++++++++++++++++++++++++--------- agent/system.go | 5 ++-- go.sum | 14 +++++------ 3 files changed, 49 insertions(+), 21 deletions(-) diff --git a/agent/battery/battery.go b/agent/battery/battery.go index 8dbbc53b..bb5fb757 100644 --- a/agent/battery/battery.go +++ b/agent/battery/battery.go @@ -5,6 +5,8 @@ package battery import ( "errors" + "fmt" + "os" "log/slog" "github.com/distatus/battery" @@ -19,33 +21,60 @@ func HasReadableBattery() bool { return systemHasBattery } haveCheckedBattery = true - bat, err := battery.Get(0) - systemHasBattery = err == nil && bat != nil && bat.Design != 0 && bat.Full != 0 + batteries,err := battery.GetAll() + if err != nil { + // even if there's errors getting some batteries, the system + // definitely has a battery if the list is not empty. + // This list will include everything `battery` can find, + // including things like bluetooth devices. + fmt.Fprintln(os.Stderr, err) + } + systemHasBattery = len(batteries) > 0 if !systemHasBattery { slog.Debug("No battery found", "err", err) } return systemHasBattery } -// GetBatteryStats returns the current battery percent and charge state +// GetBatteryStats returns the current battery percent and charge state +// percent = (current charge of all batteries) / (sum of designed/full capacity of all batteries) func GetBatteryStats() (batteryPercent uint8, batteryState uint8, err error) { - if !systemHasBattery { + if !HasReadableBattery() { return batteryPercent, batteryState, errors.ErrUnsupported } batteries, err := battery.GetAll() - if err != nil || len(batteries) == 0 { - return batteryPercent, batteryState, err + // we'll handle errors later by skipping batteries with errors, rather + // than skipping everything because of the presence of some errors. + if len(batteries) == 0 { + return batteryPercent, batteryState, errors.New("no batteries") } + totalCapacity := float64(0) totalCharge := float64(0) - for _, bat := range batteries { - if bat.Design != 0 { - totalCapacity += bat.Design - } else { - totalCapacity += bat.Full + errs, partialErrs := err.(battery.Errors) + + for i, bat := range batteries { + if partialErrs && errs[i] != nil { + // if there were some errors, like missing data, skip it + continue } + if bat.Full == 0 { + // skip batteries with no capacity. Charge is unlikely to ever be zero, but + // we can't guarantee that, so don't skip based on charge. + continue + } + totalCapacity += bat.Full totalCharge += bat.Current } + + if totalCapacity == 0 { + // for macs there's sometimes a ghost battery with 0 capacity + // https://github.com/distatus/battery/issues/34 + // Instead of skipping over those batteries, we'll check for total 0 capacity + // and return an error. This also prevents a divide by zero. + return batteryPercent, batteryState, errors.New("no battery capacity") + } + batteryPercent = uint8(totalCharge / totalCapacity * 100) batteryState = uint8(batteries[0].State.Raw) return batteryPercent, batteryState, nil diff --git a/agent/system.go b/agent/system.go index ee8b87f3..dad48ed2 100644 --- a/agent/system.go +++ b/agent/system.go @@ -78,8 +78,9 @@ func (a *Agent) getSystemStats(cacheTimeMs uint16) system.Stats { var systemStats system.Stats // battery - if battery.HasReadableBattery() { - systemStats.Battery[0], systemStats.Battery[1], _ = battery.GetBatteryStats() + if batteryPercent, batteryState, err := battery.GetBatteryStats(); err == nil { + systemStats.Battery[0] = batteryPercent + systemStats.Battery[1] = batteryState } // cpu percent diff --git a/go.sum b/go.sum index 88a10571..d7ef6eec 100644 --- a/go.sum +++ b/go.sum @@ -169,20 +169,18 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM= howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= -modernc.org/cc/v4 v4.26.5 h1:xM3bX7Mve6G8K8b+T11ReenJOT+BmVqQj0FY5T4+5Y4= -modernc.org/cc/v4 v4.26.5/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= -modernc.org/ccgo/v4 v4.28.1 h1:wPKYn5EC/mYTqBO373jKjvX2n+3+aK7+sICCv4Fjy1A= -modernc.org/ccgo/v4 v4.28.1/go.mod h1:uD+4RnfrVgE6ec9NGguUNdhqzNIeeomeXf6CL0GTE5Q= -modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA= -modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= +modernc.org/cc/v4 v4.26.2 h1:991HMkLjJzYBIfha6ECZdjrIYz2/1ayr+FL8GN+CNzM= +modernc.org/cc/v4 v4.26.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= +modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU= +modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE= +modernc.org/fileutil v1.3.8 h1:qtzNm7ED75pd1C7WgAGcK4edm4fvhtBsEiI/0NQ54YM= +modernc.org/fileutil v1.3.8/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= modernc.org/libc v1.66.3 h1:cfCbjTUcdsKyyZZfEUKfoHcP3S0Wkvz3jgSzByEWVCQ= modernc.org/libc v1.66.3/go.mod h1:XD9zO8kt59cANKvHPXpx7yS2ELPheAey0vjIuZOhOU8= -modernc.org/libc v1.66.10 h1:yZkb3YeLx4oynyR+iUsXsybsX4Ubx7MQlSYEw4yj59A= -modernc.org/libc v1.66.10/go.mod h1:8vGSEwvoUoltr4dlywvHqjtAqHBaw0j1jI7iFBTAr2I= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=