From 8ca22041615d8f902c592f02bd60878eda29bcc3 Mon Sep 17 00:00:00 2001
From: Kevin Lyda <kevin@lyda.ie>
Date: Fri, 2 Dec 2022 22:26:10 +0000
Subject: [PATCH] Add battery module.

---
 go.mod             |   2 +
 go.sum             |   7 +++
 modules/battery.go | 138 +++++++++++++++++++++++++++++++++++++++++++++
 modules/clicks.go  |   2 +-
 modules/date.go    |   9 ++-
 modules/error.go   |   7 ++-
 modules/modules.go |   5 +-
 modules/text.go    |   9 ++-
 8 files changed, 174 insertions(+), 5 deletions(-)
 create mode 100644 modules/battery.go

diff --git a/go.mod b/go.mod
index f919972..381786f 100644
--- a/go.mod
+++ b/go.mod
@@ -12,6 +12,7 @@ require (
 )
 
 require (
+	github.com/distatus/battery v0.10.0 // indirect
 	github.com/hashicorp/hcl v1.0.0 // indirect
 	github.com/inconshreveable/mousetrap v1.0.1 // indirect
 	github.com/magiconair/properties v1.8.6 // indirect
@@ -25,4 +26,5 @@ require (
 	github.com/subosito/gotenv v1.4.1 // indirect
 	golang.org/x/text v0.4.0 // indirect
 	gopkg.in/ini.v1 v1.67.0 // indirect
+	howett.net/plist v0.0.0-20181124034731-591f970eefbb // indirect
 )
diff --git a/go.sum b/go.sum
index ceb5a35..d003e20 100644
--- a/go.sum
+++ b/go.sum
@@ -50,6 +50,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/distatus/battery v0.10.0 h1:YbizvmV33mqqC1fPCAEaQGV3bBhfYOfM+2XmL+mvt5o=
+github.com/distatus/battery v0.10.0/go.mod h1:STnSvFLX//eEpkaN7qWRxCWxrWOcssTDgnG4yqq9BRE=
 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@@ -125,6 +127,7 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:
 github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
 github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
+github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
 github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
@@ -282,6 +285,7 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190912141932-bc967efca4b8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -466,6 +470,7 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
 gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
@@ -479,6 +484,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
 honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+howett.net/plist v0.0.0-20181124034731-591f970eefbb h1:jhnBjNi9UFpfpl8YZhA9CrOqpnJdvzuiHsl/dnxl11M=
+howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
 rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
 rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
diff --git a/modules/battery.go b/modules/battery.go
new file mode 100644
index 0000000..4ea57cd
--- /dev/null
+++ b/modules/battery.go
@@ -0,0 +1,138 @@
+// Package modules implements all the modules.
+// Copyright (C) 2022 Kevin Lyda <kevin@lyda.ie>
+package modules
+
+import (
+	"fmt"
+	"math"
+	"time"
+
+	"github.com/distatus/battery"
+)
+
+type battTS struct {
+	charge float64
+	point  time.Time
+}
+
+// BatteryMod module parameters for the battery module.
+type BatteryMod struct {
+	name       string
+	onclick    string
+	battStats  []battTS
+	Battery    int    `yaml:"battery"`
+	StatusEmp  string `yaml:"status-chr"`
+	StatusChr  string `yaml:"status-chr"`
+	StatusBat  string `yaml:"status-bat"`
+	StatusUnk  string `yaml:"status-unk"`
+	StatusFull string `yaml:"status-full"`
+	OnError    string `yaml:"on-error"`
+	ColorOK    string `yaml:"color-ok"`
+	Color20    string `yaml:"color-20"`
+	Color10    string `yaml:"color-10"`
+	ColorError string `yaml:"color-error"`
+}
+
+// NewBattery creates a BatteryMod instance.
+func NewBattery(m *Module) *BatteryMod {
+	b := &BatteryMod{
+		name:    m.Name,
+		onclick: m.OnClick,
+	}
+	if b.name == "" {
+		b.name = "battery"
+	}
+	return b
+}
+
+// SetDefaults sets defaults.
+func (b *BatteryMod) SetDefaults() {
+	if b.StatusEmp != "" {
+		b.StatusEmp = "---"
+	}
+	if b.StatusChr != "" {
+		b.StatusChr = "CHR"
+	}
+	if b.StatusBat != "" {
+		b.StatusBat = "BAT"
+	}
+	if b.StatusUnk != "" {
+		b.StatusUnk = "UNK"
+	}
+	if b.StatusFull != "" {
+		b.StatusFull = "FULL"
+	}
+	if b.OnError != "" {
+		b.OnError = "No BAT"
+	}
+	if b.ColorOK != "" {
+		b.ColorOK = "#11ff11"
+	}
+	if b.Color20 != "" {
+		b.Color20 = "#ffa500"
+	}
+	if b.Color10 != "" {
+		b.Color10 = "#ffff00"
+	}
+	if b.ColorError != "" {
+		b.ColorError = "#ff1111"
+	}
+}
+
+// Name returns the name setting for the module.
+func (b *BatteryMod) Name() string {
+	return b.name
+}
+
+// OnClick returns the on-click setting for the module.
+func (b *BatteryMod) OnClick() string {
+	return b.onclick
+}
+
+// Render renders the module.
+func (b *BatteryMod) Render() string {
+	// TODO: Alerting.
+
+	now := time.Now()
+	batt, err := battery.Get(b.Battery)
+	if err != nil {
+		return fmt.Sprintf(`{"name": "%s", "full_text": "%s", "color": "%s"}`, b.name, b.OnError, b.ColorError)
+	}
+	left := "(?)"
+	if len(b.battStats) >= 1 {
+		b.battStats = append(b.battStats, battTS{
+			charge: batt.Current,
+			point:  now,
+		})
+		delta := now.Sub(b.battStats[0].point)
+		if delta >= (60 * time.Second) {
+			discharge := math.Abs(batt.Current - b.battStats[0].charge)
+			timeLeft := time.Duration(batt.Current/discharge) * delta
+			left = fmt.Sprintf("%dh%dm", int64(timeLeft.Hours()), int64(timeLeft.Minutes()))
+			b.battStats = b.battStats[1:]
+		}
+	}
+
+	states := [...]string{
+		battery.Unknown:     b.StatusUnk,
+		battery.Empty:       b.StatusEmp,
+		battery.Full:        b.StatusFull,
+		battery.Charging:    b.StatusChr,
+		battery.Discharging: b.StatusBat,
+	}
+
+	color := b.ColorOK
+	percent := batt.Current / batt.Full
+	if percent < 20 {
+		if percent < 10 {
+			color = b.Color10
+		} else {
+			color = b.Color20
+		}
+	}
+
+	if b.Battery > 0 {
+		return fmt.Sprintf(`{"name": "%s", "full_text": "%s%d %.1f%% %s", "color": "%s"}`, b.name, states[batt.State], b.Battery, percent, left, color)
+	}
+	return fmt.Sprintf(`{"name": "%s", "full_text": "%s %.1f%% %s", "color": "%s"}`, b.name, states[batt.State], percent, left, color)
+}
diff --git a/modules/clicks.go b/modules/clicks.go
index 018c331..5921c99 100644
--- a/modules/clicks.go
+++ b/modules/clicks.go
@@ -1,4 +1,4 @@
-// Package module implements all the modules.
+// Package modules implements all the modules.
 // Copyright (C) 2022 Kevin Lyda <kevin@lyda.ie>
 package modules
 
diff --git a/modules/date.go b/modules/date.go
index a846897..f0c6698 100644
--- a/modules/date.go
+++ b/modules/date.go
@@ -1,4 +1,4 @@
-// Package module implements all the modules.
+// Package modules implements all the modules.
 // Copyright (C) 2022 Kevin Lyda <kevin@lyda.ie>
 package modules
 
@@ -29,6 +29,13 @@ func NewDate(m *Module) *DateMod {
 	return d
 }
 
+// SetDefaults sets defaults.
+func (d *DateMod) SetDefaults() {
+	if d.Format == "" {
+		d.Format = "06/01/02 15:04"
+	}
+}
+
 // Name returns the name setting for the module.
 func (d *DateMod) Name() string {
 	return d.name
diff --git a/modules/error.go b/modules/error.go
index 2213863..ad0eb28 100644
--- a/modules/error.go
+++ b/modules/error.go
@@ -1,4 +1,4 @@
-// Package module implements all the modules.
+// Package modules implements all the modules.
 // Copyright (C) 2022 Kevin Lyda <kevin@lyda.ie>
 package modules
 
@@ -25,6 +25,11 @@ func NewError(m *Module) *ErrorMod {
 	return e
 }
 
+// SetDefaults sets defaults.
+func (e *ErrorMod) SetDefaults() {
+	return
+}
+
 // Name returns the name setting for the module.
 func (e *ErrorMod) Name() string {
 	return e.name
diff --git a/modules/modules.go b/modules/modules.go
index e66bdcb..2322c63 100644
--- a/modules/modules.go
+++ b/modules/modules.go
@@ -1,4 +1,4 @@
-// Package module implements all the modules.
+// Package modules implements all the modules.
 // Copyright (C) 2022 Kevin Lyda <kevin@lyda.ie>
 package modules
 
@@ -11,6 +11,7 @@ import (
 
 // ParamsInterface is the interface for module paramaters.
 type ParamsInterface interface {
+	SetDefaults()
 	Name() string
 	OnClick() string
 	Render() string
@@ -48,6 +49,8 @@ func (m *Module) UnmarshalYAML(node *yaml.Node) error {
 		m.Params = NewDate(m)
 	case "text":
 		m.Params = NewText(m)
+	case "battery":
+		m.Params = NewBattery(m)
 	default:
 		return fmt.Errorf("module '%s' is unknown", params.Module)
 	}
diff --git a/modules/text.go b/modules/text.go
index e5e845e..4c3be95 100644
--- a/modules/text.go
+++ b/modules/text.go
@@ -1,4 +1,4 @@
-// Package module implements all the modules.
+// Package modules implements all the modules.
 // Copyright (C) 2022 Kevin Lyda <kevin@lyda.ie>
 package modules
 
@@ -26,6 +26,13 @@ func NewText(m *Module) *TextMod {
 	return t
 }
 
+// SetDefaults sets defaults.
+func (t *TextMod) SetDefaults() {
+	if t.Text == "" {
+		t.Text = "Hello world!"
+	}
+}
+
 // Name returns the name setting for the module.
 func (t *TextMod) Name() string {
 	return t.name
-- 
GitLab