From fc67850d3053117e8472d936568e238c34e458bc Mon Sep 17 00:00:00 2001 From: eleksir Date: Thu, 24 Apr 2025 19:49:50 +0300 Subject: [PATCH] Add sysfs method to get battery charge for linux --- cmd/i3status-go/default-config.json | 9 +++ internal/lib/batteries.go | 94 ++++++++++++++++++++++++++--- internal/lib/config.go | 27 ++++++--- 3 files changed, 115 insertions(+), 15 deletions(-) diff --git a/cmd/i3status-go/default-config.json b/cmd/i3status-go/default-config.json index c96ed88..f180393 100644 --- a/cmd/i3status-go/default-config.json +++ b/cmd/i3status-go/default-config.json @@ -518,6 +518,15 @@ "battery": { "enabled": false, + // Valid only on linux, where we have mounted sysfs and /sys/class/power_supply/BAT0/capacity available + "use_sysfs": false, + + // it is possible to set files here, if none set, /sys/class/power_supply/BAT0/capacity will be taken. + "sysfs_files": [ + "/sys/class/power_supply/BAT0/capacity", + "/sys/class/power_supply/HIDP0/capacity" + ], + // If omitted set to default color defined up here. "color": "#3e78fd", diff --git a/internal/lib/batteries.go b/internal/lib/batteries.go index fe6c9a1..c37f077 100644 --- a/internal/lib/batteries.go +++ b/internal/lib/batteries.go @@ -2,7 +2,12 @@ package lib import ( "fmt" + "log" "math" + "os" + "path/filepath" + "strconv" + "strings" "time" "github.com/distatus/battery" @@ -11,9 +16,6 @@ import ( // UpdateBatteryInfo updates info about battery charge. func (c *MyConfig) UpdateBatteryInfo() { var ( - ch int - status string - Batts string InitialDelay = 100 * time.Millisecond LoopIterationDelay = 5 * time.Second Delay = InitialDelay @@ -26,9 +28,83 @@ func (c *MyConfig) UpdateBatteryInfo() { ticker.Reset(Delay) } - batteries, _ := battery.GetAll() - // In theory, this module should give for each entry its separate err, but in practice it gives - // one single err for all entries, so we cannot detemine whist exatly entry errored. + var ( + batteries []*battery.Battery = []*battery.Battery{} + Batts string + ch int + status string + ) + + if c.Battery.UseSysfs { + for _, file := range c.Battery.SysfsFiles { + var batt battery.Battery + + _, err := os.Stat(file) + + // If file exists. + if !os.IsNotExist(err) { + var ( + myCharge string + myStatus string + ) + + b, err := os.ReadFile(file) + + if err != nil { + log.Printf("Unable to read file %s: %s", file, err) + continue + } + + myCharge = strings.Trim(string(b), "\n") + + statusFile := filepath.Dir(file) + "/status" + + // If file exists. + if !os.IsNotExist(err) { + b, err := os.ReadFile(statusFile) + + if err != nil { + log.Printf("Unable to read file %s: %s", statusFile, err) + continue + } + + myStatus = strings.Trim(string(b), "\n") + } else { + continue + } + + // Make stub battery class :) . + switch myStatus { + case "Charging": + batt.State.Raw = battery.Charging + case "Discharging": + batt.State.Raw = battery.Discharging + case "Empty": + batt.State.Raw = battery.Empty + case "Full": + batt.State.Raw = battery.Full + default: + batt.State.Raw = battery.Unknown + } + + ch, err := strconv.Atoi(myCharge) + + if err != nil { + log.Printf("Unable to convert string from file %s to integer", file) + continue + } + + batt.Current = float64(ch) + + batteries = append(batteries, &batt) + } + break + } + } else { + batteries, _ = battery.GetAll() + // In theory, this module should give for each entry its separate err, but in practice it gives + // one single err for all entries, so we cannot detemine whist exatly entry errored. + } var ( battsInfo string @@ -49,7 +125,11 @@ func (c *MyConfig) UpdateBatteryInfo() { // N.B. there can be case when battery is overcharged and shows >100%. It also can indicate that // calibration data is out of date and battery should be re-calibrated. - ch = int(math.Round((b.Full - (b.Full - b.Current)) * (100 / b.Full))) + if c.Battery.UseSysfs { + ch = int(b.Current) + } else { + ch = int(math.Round((b.Full - (b.Full - b.Current)) * (100 / b.Full))) + } switch { case ch <= 500 && ch >= 84: diff --git a/internal/lib/config.go b/internal/lib/config.go index 309c97f..0ae0851 100644 --- a/internal/lib/config.go +++ b/internal/lib/config.go @@ -134,14 +134,16 @@ type MyConfig struct { } `json:"clock,omitempty"` Battery struct { - Enabled bool `json:"enabled,omitempty"` - Color string `json:"color,omitempty"` - Background string `json:"background,omitempty"` - Font string `json:"font,omitempty"` - FontSize string `json:"font_size,omitempty"` - Symbol string `json:"symbol,omitempty"` - SymbolFont string `json:"symbol_font,omitempty"` - SymbolFontSize string `json:"symbol_font_size,omitempty"` + Enabled bool `json:"enabled,omitempty"` + UseSysfs bool `json:"use_sysfs,omitempty"` + SysfsFiles []string `json:"sysfs_files,omitempty"` + Color string `json:"color,omitempty"` + Background string `json:"background,omitempty"` + Font string `json:"font,omitempty"` + FontSize string `json:"font_size,omitempty"` + Symbol string `json:"symbol,omitempty"` + SymbolFont string `json:"symbol_font,omitempty"` + SymbolFontSize string `json:"symbol_font_size,omitempty"` ChargeColor struct { Full string `json:"full,omitempty"` @@ -778,6 +780,15 @@ func ReadConf(defaultConfig []byte) (*MyConfig, error) { } // sampleConfig.Battery.Enabled will be false if not set in config + // sampleConfig.Battery.UseSysfs will be false if not set in config + + if len(sampleConfig.Battery.SysfsFiles) == 0 { + sampleConfig.Battery.SysfsFiles = append( + sampleConfig.Battery.SysfsFiles, + "/sys/class/power_supply/BAT0/capacity", + ) + } + if sampleConfig.Battery.Color == "" { sampleConfig.Battery.Color = sampleConfig.Color }