Skip to content

Instantly share code, notes, and snippets.

@Miguel-Dorta
Created October 22, 2023 01:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Miguel-Dorta/31be2264a50a151aa946acfc89595ff5 to your computer and use it in GitHub Desktop.
Save Miguel-Dorta/31be2264a50a151aa946acfc89595ff5 to your computer and use it in GitHub Desktop.
Simple script to check the battery in your server and notify you / turn it off
package main
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"golang.org/x/sys/unix"
"io"
"net/http"
"os"
"os/signal"
"time"
)
const (
checkBatteryIntervalSec = 60
telegramApiTimeoutSec = 10
telegramBotChatId = "12345678"
telegramBotToken = "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"
powerOffAtBatteryLevel = 20
)
type batteryStatus struct {
level int
isPlugged bool
}
func main() {
notifyChan := make(chan batteryStatus, 20)
go startChecker(notifyChan)
go startNotifier(notifyChan)
quit := make(chan os.Signal, 2)
signal.Notify(quit, unix.SIGINT, unix.SIGTERM)
<-quit
os.Exit(0)
}
func startChecker(notify chan<- batteryStatus) {
fAdpOnline, err := os.OpenFile("/sys/class/power_supply/ADP0/online", os.O_RDONLY, os.ModePerm)
if err != nil {
panic(err)
}
fBatCapacity, err := os.OpenFile("/sys/class/power_supply/BAT0/capacity", os.O_RDONLY, os.ModePerm)
if err != nil {
panic(err)
}
prevLvlRange := (readLevel(fBatCapacity) + 1) / 10
wasPlugged := readIsPlugged(fAdpOnline)
for {
time.Sleep(time.Second * checkBatteryIntervalSec)
batLvl := readLevel(fBatCapacity)
batLvlRange := (batLvl + 1) / 10
isPlugged := readIsPlugged(fAdpOnline)
if batLvl <= powerOffAtBatteryLevel {
_ = unix.Reboot(unix.LINUX_REBOOT_CMD_POWER_OFF)
os.Exit(0)
}
if isPlugged != wasPlugged || batLvlRange < prevLvlRange {
fmt.Printf("NOTIFY | Battery Level: % 3d %% | Is Plugged? %s\n",
batLvl, ternaryOp(isPlugged, "Yes", "No"))
notify <- batteryStatus{
level: batLvl,
isPlugged: isPlugged,
}
}
prevLvlRange = batLvlRange
wasPlugged = isPlugged
}
}
func startNotifier(queue <-chan batteryStatus) {
client := http.Client{Timeout: telegramApiTimeoutSec * time.Second}
hostname, err := os.Hostname()
if err != nil {
hostname = "<unknown>"
}
for {
status := <-queue
msg, err := json.Marshal(map[string]string{
"chat_id": telegramBotChatId,
"parse_mode": "HTML",
"text": composeNotificationText(status, hostname),
})
if err != nil {
panic(err)
}
for i := time.Millisecond; ; i *= 5 {
if i > 5*time.Minute {
i = 5 * time.Minute
}
time.Sleep(i)
resp, err := client.Post(
"https://api.telegram.org/bot"+telegramBotToken+"/sendMessage",
"application/json", bytes.NewReader(msg))
if err != nil {
_, _ = fmt.Fprintln(os.Stderr, "error sending notification:", err)
continue
}
body, err := io.ReadAll(resp.Body)
if err != nil {
panic(err)
}
fmt.Println(string(body))
_ = resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
_, _ = fmt.Fprintln(os.Stderr, "non-ok status returned by telegram api:", resp.Status)
continue
}
break
}
}
}
func readLevel(f *os.File) int {
buf := make([]byte, 3)
bytesRead, err := f.ReadAt(buf, 0)
if bytesRead < 1 {
err = errors.New("no data found in battery capacity file")
}
if err != nil && err != io.EOF {
panic(err)
}
return bytesToInt(buf[:bytesRead])
}
func readIsPlugged(f *os.File) bool {
buf := make([]byte, 1)
bytesRead, err := f.ReadAt(buf, 0)
if bytesRead < 1 {
err = errors.New("no data found in power adapter online file")
}
if err != nil {
panic(err)
}
return (buf[0] - '0') != 0
}
func composeNotificationText(status batteryStatus, hostname string) string {
return fmt.Sprintf("⚠️ Battery notification alert ⚠️\n\n"+
"%s <b>Current level:</b> %d %%\n"+
"%s <b>Is plugged?</b> %s\n"+
"🖥 <b>Server:</b> %s\n"+
"🕓 <b>Time:</b> %s",
ternaryOp(status.level > 50, "🔋", "\U0001FAAB"), status.level,
ternaryOp(status.isPlugged, "⚡️", "🔌"), ternaryOp(status.isPlugged, "Yes", "No"),
hostname,
time.Now().Format(time.DateTime))
}
func ternaryOp(cond bool, a, b any) any {
if cond {
return a
}
return b
}
func bytesToInt(data []byte) (i int) {
for _, b := range data {
if b < '0' || b > '9' {
return
}
i = (i * 10) + int(b-'0')
}
return
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment