Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions util/ema.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,7 @@ func (e *EMA) Update(value float64) float64 {
e.samples = 1
} else {
e.value = e.alpha*value + (1-e.alpha)*e.value
if e.samples < e.period {
e.samples++
}
e.samples++
}
return e.value
}
Expand All @@ -42,7 +40,7 @@ func (e *EMA) Value() float64 {
return e.value
}

// Ready returns true when enough samples have been collected.
// Ready returns true after at least one smoothing has occurred.
func (e *EMA) Ready() bool {
return e.samples >= e.period
return e.samples >= 2
}
15 changes: 5 additions & 10 deletions util/ema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,30 +66,25 @@ func TestEMA_Smoothing(t *testing.T) {
}

func TestEMA_Ready(t *testing.T) {
ema := NewEMA(3)
ema := NewEMA(5)

if ema.Ready() {
t.Error("Should not be ready before any updates")
}

ema.Update(10)
if ema.Ready() {
t.Error("Should not be ready after 1 update (need 3)")
t.Error("Should not be ready after 1 update (need 2 for first smoothing)")
}

ema.Update(20)
if ema.Ready() {
t.Error("Should not be ready after 2 updates (need 3)")
}

ema.Update(30)
if !ema.Ready() {
t.Error("Should be ready after 3 updates")
t.Error("Should be ready after 2 updates (smoothing has occurred)")
}

ema.Update(40)
ema.Update(30)
if !ema.Ready() {
t.Error("Should still be ready after 4 updates")
t.Error("Should still be ready after 3 updates")
}
}

Expand Down
73 changes: 61 additions & 12 deletions widgets/battery.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,52 @@ const (
BatteryModeRemainingTime = 1
)

type BatteryState int

const (
BatteryStateUnknown BatteryState = iota
BatteryStateCharging
BatteryStateDischarging
BatteryStateFull
BatteryStateNotCharging
)

func (s BatteryState) IsOnAC() bool {
return s == BatteryStateCharging || s == BatteryStateFull || s == BatteryStateNotCharging
}

// priority returns the precedence for aggregating multiple battery states.
// Higher value = higher priority when combining states.
func (s BatteryState) priority() int {
switch s {
case BatteryStateCharging:
return 5
case BatteryStateDischarging:
return 4
case BatteryStateNotCharging:
return 3
case BatteryStateFull:
return 2
default: // BatteryStateUnknown
return 1
}
}

func parseBatteryStatus(status string) BatteryState {
switch status {
case "Charging":
return BatteryStateCharging
case "Discharging":
return BatteryStateDischarging
case "Full":
return BatteryStateFull
case "Not charging":
return BatteryStateNotCharging
default:
return BatteryStateUnknown
}
}
Comment thread
neoden marked this conversation as resolved.

type BatteryInfo struct {
Path string
Status string
Expand All @@ -36,7 +82,7 @@ type Battery struct {
EnergyNow int
PowerNow int
Remaining time.Duration
IsCharging bool
State BatteryState
Mode int
}

Expand All @@ -59,7 +105,8 @@ func (b *Battery) Update() {
b.EnergyFull = 0
b.EnergyNow = 0
b.PowerNow = 0
Comment thread
neoden marked this conversation as resolved.
b.IsCharging = false
b.Remaining = 0
b.State = BatteryStateUnknown

// Find all batteries
matches, err := afero.Glob(b.fs, "/sys/class/power_supply/BAT*")
Expand Down Expand Up @@ -101,8 +148,10 @@ func (b *Battery) Update() {
b.EnergyNow += bat.EnergyNow
b.PowerNow += bat.PowerNow

if bat.Status == "Charging" {
b.IsCharging = true
// Determine state using priority (Charging > Discharging > NotCharging > Full > Unknown)
batState := parseBatteryStatus(bat.Status)
if batState.priority() > b.State.priority() {
b.State = batState
}
}

Expand All @@ -119,9 +168,9 @@ func (b *Battery) Update() {

// Calculate remaining time
if b.PowerNow > 0 {
if b.IsCharging {
if b.State == BatteryStateCharging {
b.Remaining = time.Duration((b.EnergyFull-b.EnergyNow)*1000/b.PowerNow) * time.Hour / 1000
} else {
} else if b.State == BatteryStateDischarging {
b.Remaining = time.Duration(b.EnergyNow*1000/b.PowerNow) * time.Hour / 1000
}
}
Expand All @@ -132,25 +181,25 @@ func (b *Battery) GetBlock() string {
return ""
}

if b.IsCharging && b.Percentage > b.cfg.HideChargingAbove {
if b.State.IsOnAC() && b.Percentage > b.cfg.HideChargingAbove {
return ""
}
if !b.IsCharging && b.Percentage > b.cfg.HideDischargingAbove {
if !b.State.IsOnAC() && b.Percentage > b.cfg.HideDischargingAbove {
return ""
}

symbols := [6]string{"\uf244", "\uf243", "\uf242", "\uf241", "\uf240", "\uf240"}
batteryLevelSymbols := [6]string{"\uf244", "\uf243", "\uf242", "\uf241", "\uf240", "\uf240"}
var symbol string
var text string

if b.IsCharging {
symbol = "\uf1e6"
if b.State == BatteryStateCharging {
symbol = "\uf1e6" // nf-fa-plug
} else {
idx := b.Percentage / 20
if idx > 5 {
idx = 5
}
symbol = symbols[idx]
symbol = batteryLevelSymbols[idx]
}

if b.Mode == BatteryModePercentage {
Expand Down
Loading