From 7b8dabd68f5abf34252d298979c8849c42edbf29 Mon Sep 17 00:00:00 2001 From: Mattia Gheda Date: Fri, 11 Apr 2014 19:11:15 -0400 Subject: [PATCH] add battery widget (taken from https://github.com/coleifer/mastodon/blob/master/battery.go) --- i3status/power.go | 124 ++++++++++++++++++++++++++++++++++++++++----- i3status/utils.go | 53 +++++++++++++++++++ main.go | 2 +- test/power_test.go | 2 +- 4 files changed, 165 insertions(+), 16 deletions(-) create mode 100644 i3status/utils.go diff --git a/i3status/power.go b/i3status/power.go index bc5054c..efc5c5e 100644 --- a/i3status/power.go +++ b/i3status/power.go @@ -1,19 +1,26 @@ package i3status import ( - "bytes" + "errors" "fmt" - "os/exec" "strconv" + "strings" "time" ) const ( - Charging = "charging" - Discharging = "discharging" - Charged = "charged" + BATTERY_DISCHARGING = iota + BATTERY_CHARGING + BATTERY_FULL ) +type BatteryInfo struct { + PercentRemaining float64 + SecondsRemaining float64 + Consumption float64 + status int +} + type PowerWidget struct { BaseWidget } @@ -26,13 +33,25 @@ func NewPowerWidget() *PowerWidget { return &w } -func (w *PowerWidget) execCommand() string { - var out bytes.Buffer - cmd := exec.Command("acpi", "-b") - cmd.Stdout = &out - cmd.Run() - str, _ := out.ReadString('\n') - return str +func (w *PowerWidget) getStatus() (string, string) { + bi, _ := readBatteryInfo(0) + + bar := MakeBar(bi.PercentRemaining, 20) + remaining := HumanDuration(int64(bi.SecondsRemaining)) + prefix := "BAT" + color := "#ffffff" + if bi.IsCharging() { + prefix = "CHR" + } else if bi.IsFull() { + prefix = "FULL" + } else { + if bi.PercentRemaining < 10 { + color = "#ff0000" + } else if bi.PercentRemaining < 30 { + color = "#ffff00" + } + } + return fmt.Sprintf("%s %s %s %0.1f%%", prefix, remaining, bar, bi.PercentRemaining), color } func (w *PowerWidget) basicLoop() { @@ -41,8 +60,7 @@ func (w *PowerWidget) basicLoop() { msg.Color = "#ffffff" msg.Instance = strconv.Itoa(w.Instance) for { - str := w.execCommand() - msg.FullText = fmt.Sprintf("%s", str) + msg.FullText, msg.Color = w.getStatus() w.Output <- *msg time.Sleep(5000 * time.Millisecond) } @@ -52,3 +70,81 @@ func (w *PowerWidget) Start() { go w.basicLoop() go w.readLoop() } + +// stolen from https://github.com/coleifer/mastodon/blob/master/battery.go +func (batteryInfo *BatteryInfo) IsCharging() bool { + return batteryInfo.status == BATTERY_CHARGING +} + +func (batteryInfo *BatteryInfo) IsFull() bool { + return batteryInfo.status == BATTERY_FULL +} + +func readBatteryInfo(battery int) (*BatteryInfo, error) { + rawInfo := make(map[string]string) + batteryInfo := new(BatteryInfo) + + path := fmt.Sprintf("/sys/class/power_supply/BAT%d/uevent", battery) + if !FileExists(path) { + return batteryInfo, errors.New("Battery not found") + } + callback := func(line string) bool { + data := strings.Split(string(line), "=") + rawInfo[data[0]] = data[1] + return true + } + ReadLines(path, callback) + + var remaining, presentRate, voltage, fullDesign float64 + var wattAsUnit bool + batteryInfo.status = BATTERY_DISCHARGING + + if rawInfo["POWER_SUPPLY_STATUS"] == "Charging" { + batteryInfo.status = BATTERY_CHARGING + } else if rawInfo["POWER_SUPPLY_STATUS"] == "Full" { + batteryInfo.status = BATTERY_FULL + } + + /* Convert to float shorthand */ + pf := func(keys ...string) float64 { + for _, key := range keys { + if _, ok := rawInfo[key]; ok { + f, _ := strconv.ParseFloat(rawInfo[key], 64) + return f + } + } + return 0. + } + + /* Read values from file */ + remaining = pf("POWER_SUPPLY_ENERGY_NOW", "POWER_SUPPLY_CHARGE_NOW") + presentRate = pf("POWER_SUPPLY_CURRENT_NOW", "POWER_SUPPLY_POWER_NOW") + voltage = pf("POWER_SUPPLY_VOLTAGE_NOW") + fullDesign = pf("POWER_SUPPLY_CHARGE_FULL_DESIGN", "POWER_SUPPLY_ENERGY_FULL_DESIGN") + _, wattAsUnit = rawInfo["POWER_SUPPLY_ENERGY_NOW"] + + if !wattAsUnit { + presentRate = (voltage / 1000.0) * (presentRate / 1000.0) + remaining = (voltage / 1000.0) * (remaining / 1000.0) + fullDesign = (voltage / 1000.0) * (fullDesign / 1000.0) + } + + if fullDesign == 0 { + return batteryInfo, errors.New("Battery full design missing") + } + + batteryInfo.PercentRemaining = (remaining / fullDesign) * 100 + + var remainingTime float64 + if presentRate > 0 { + if batteryInfo.status == BATTERY_CHARGING { + remainingTime = (fullDesign - remaining) / presentRate + } else if batteryInfo.status == BATTERY_DISCHARGING { + remainingTime = remaining / presentRate + } + batteryInfo.SecondsRemaining = remainingTime * 3600 + } + batteryInfo.Consumption = presentRate / 1000000.0 + + return batteryInfo, nil +} diff --git a/i3status/utils.go b/i3status/utils.go new file mode 100644 index 0000000..9813410 --- /dev/null +++ b/i3status/utils.go @@ -0,0 +1,53 @@ +// this has been stolen form +// https://github.com/coleifer/mastodon/blob/master/utils.go +package i3status + +import ( + "bufio" + "bytes" + "fmt" + "io" + "os" +) + +func ReadLines(fileName string, callback func(string) bool) { + fin, err := os.Open(fileName) + if err != nil { + fmt.Fprintf(os.Stderr, "The file %s does not exist!\n", fileName) + return + } + defer fin.Close() + + reader := bufio.NewReader(fin) + for line, _, err := reader.ReadLine(); err != io.EOF; line, _, err = reader.ReadLine() { + if !callback(string(line)) { + break + } + } +} + +func FileExists(path string) bool { + _, err := os.Stat(path) + return err == nil +} + +func MakeBar(percent float64, bar_size int) string { + var bar bytes.Buffer + cutoff := int(percent * .01 * float64(bar_size)) + bar.WriteString("[") + for i := 0; i < bar_size; i += 1 { + if i <= cutoff { + bar.WriteString("#") + } else { + bar.WriteString(" ") + } + } + bar.WriteString("]") + return bar.String() +} + +func HumanDuration(n int64) string { + hours := n / 3600 + minutes := (n % 3600) / 60 + return fmt.Sprintf("%d:%02d", hours, minutes) +} diff --git a/main.go b/main.go index 7432558..7dbf078 100644 --- a/main.go +++ b/main.go @@ -32,7 +32,7 @@ func main() { b.Add(i3status.NewPowerWidget()) b.Add(i3status.NewOnOffWidget()) b.Add(i3status.NewI3statusWidget()) - b.Add(i3status.NewEchoWidget()) + //b.Add(i3status.NewEchoWidget()) for { m := <-b.Output diff --git a/test/power_test.go b/test/power_test.go index c1d7d74..7408dd8 100644 --- a/test/power_test.go +++ b/test/power_test.go @@ -30,7 +30,7 @@ func TestPowerWidgetHasMessage(t *testing.T) { w.Start() Convey("output message is available", func() { msg := <-c - So(msg.FullText, ShouldContainSubstring, "Battery") + So(msg.FullText, ShouldContainSubstring, "BAT") }) }) })