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
This commit is contained in:
Nathan Heaps
2025-10-25 13:23:29 -04:00
committed by GitHub
parent b9139a1f9b
commit d608cf0955
3 changed files with 49 additions and 21 deletions

View File

@@ -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