Skip to content

Commit

Permalink
feat: add threshold persistence after hibernation
Browse files Browse the repository at this point in the history
Creates an additional systemd service that enables the charging
threshold to persist after the system goes into hibernation.

Also addresses parts of the following issues by adding checks for the
required systemd version,
    - #3
    - #5
  • Loading branch information
Tshaka Eric Lekholoane committed Mar 26, 2021
1 parent 06e4ed3 commit dab4854
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 22 deletions.
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ DESCRIPTION
-c, --capacity print the current battery level
-h, --help print this help document
-p, --persist persist the current charging threshold setting between
restarts (requires sudo permissions)
restarts (requires superuser permissions)
-r, --reset prevents the charging threshold from persisting between
restarts
-s, --status print the charging status
-s, --status print charging status
-t, --threshold print the current charging threshold limit
specify a value between 1 and 100 to set a new threshold
(the latter requires superuser permissions)
e.g. bat --threshold 80
```

Expand All @@ -31,7 +32,7 @@ This has only shown to work on ASUS laptops. For Dell systems, see [smbios-utils

## Installation

Precompiled binaries (Linux x86-64) are available from the [GitHub releases page](https://github.com/leveson/bat/releases), the latest of which can be downloaded from [here](https://github.com/leveson/bat/releases/download/0.4.1/bat).
Precompiled binaries (Linux x86-64) are available from the [GitHub releases page](https://github.com/leveson/bat/releases), the latest of which can be downloaded from [here](https://github.com/leveson/bat/releases/download/0.5/bat).

After downloading the binary, give it permission to execute on your system by running the following command. For example, assuming the binary is located in the user's Downloads folder:

Expand All @@ -57,7 +58,7 @@ $ bat --threshold

# Set a new charging threshold, say 80%.
# (requires superuser permissions).
$ bat --threshold 80
$ sudo bat --threshold 80

# Persist the current charging threshold setting between restarts
# (requires superuser permissions).
Expand All @@ -68,4 +69,4 @@ $ sudo bat --persist

Linux kernel version later than 5.4 which is the [earliest version to expose the battery charging threshold variable](https://github.com/torvalds/linux/commit/7973353e92ee1e7ca3b2eb361a4b7cb66c92abee).

To persist threshold settings between restarts, the application relies on [Bash](https://www.gnu.org/software/bash/) and [systemd](https://systemd.io/) which are bundled with most Linux distributions.
To persist the threshold setting between restarts, the application relies on [systemd](https://systemd.io/), particularly a version later than 244, and [Bash](https://www.gnu.org/software/bash/) which are bundled with most Linux distributions.
109 changes: 92 additions & 17 deletions bat.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,13 @@ DESCRIPTION
-c, --capacity print the current battery level
-h, --help print this help document
-p, --persist persist the current charging threshold setting between
restarts (requires sudo permissions)
restarts (requires superuser permissions)
-r, --reset prevents the charging threshold from persisting between
restarts
-s, --status print charging status
-t, --threshold print the current charging threshold limit
specify a value between 1 and 100 to set a new threshold
(the latter requires superuser permissions)
e.g. bat --threshold 80
REFERENCE
Expand All @@ -48,7 +49,7 @@ func hasRequiredKernelVer() bool {
if err != nil {
log.Fatal(err)
}
re := regexp.MustCompile(`[0-9]+\.[0-9]+`)
re := regexp.MustCompile(`\d+\.\d+`)
ver := string(re.Find(out))
maj, _ := strconv.Atoi(strings.Split(ver, ".")[0])
min, _ := strconv.Atoi(strings.Split(ver, ".")[1])
Expand All @@ -67,6 +68,22 @@ func hasRequiredKernelVer() bool {
return false
}

// hasRequiredSystemdVer returns true if the systemd version of the
// system in question is later than 244 and returns false otherwise.
func hasRequiredSystemdVer() bool {
cmd := exec.Command("systemctl", "--version")
out, err := cmd.Output()
if err != nil {
log.Fatal(err)
}
re := regexp.MustCompile(`\d+`)
ver, _ := strconv.Atoi(string(re.Find(out)))
if ver < 244 {
return false
}
return true
}

// page invokes the less pager on a specified string.
func page(out string) {
cmd := exec.Command("less")
Expand All @@ -79,12 +96,19 @@ func page(out string) {
}

// persist persists the prevailing battery charging threshold level
// between restarts by creating or updating a systemd service with the
// name `bat.service`.
// between restarts and hibernation by creating or updating the systemd
// services `bat-boot.service` and `bat-sleep.service`.
func persist() {
service := fmt.Sprintf(
if !hasRequiredSystemdVer() {
fmt.Println("Requires systemd version 244 or later.")
os.Exit(1)
}

// Write systemd service that will persist the threshold after
// restarts.
bootUnit := fmt.Sprintf(
`[Unit]
Description=Set the battery charging threshold
Description=Persist the battery charging threshold between restarts
After=multi-user.target
StartLimitBurst=0
Expand All @@ -97,27 +121,62 @@ ExecStart=/bin/bash -c 'echo %s > /sys/class/power_supply/BAT?/charge_control_en
WantedBy=multi-user.target
`,
scat("/sys/class/power_supply/BAT?/charge_control_end_threshold"))
f, err := os.Create("/etc/systemd/system/bat.service")
bootService, err := os.Create("/etc/systemd/system/bat-boot.service")
if err != nil {
if strings.HasSuffix(err.Error(), ": permission denied") {
fmt.Println("This command requires sudo permissions.")
os.Exit(1)
}
log.Fatal(err)
}
defer f.Close()
f.WriteString(service)
cmd := exec.Command("systemctl", "enable", "bat.service")
defer bootService.Close()
bootService.WriteString(bootUnit)

// Enable the service.
cmd := exec.Command("systemctl", "enable", "bat-boot.service")
err = cmd.Run()
if err != nil {
log.Fatal(err)
}

// Write systemd service that will persist the threshold after
// hibernation.
sleepUnit := fmt.Sprintf(
`[Unit]
Description=Persist the battery charging threshold after hibernation
Before=sleep.target
StartLimitBurst=0
[Service]
Type=oneshot
Restart=on-failure
ExecStart=/bin/bash -c 'echo %s > /sys/class/power_supply/BAT?/charge_control_end_threshold'
[Install]
WantedBy=sleep.target
`,
scat("/sys/class/power_supply/BAT?/charge_control_end_threshold"))
sleepService, err := os.Create("/etc/systemd/system/bat-sleep.service")
if err != nil {
log.Fatal(err)
}
defer sleepService.Close()
sleepService.WriteString(sleepUnit)

// Enable the service.
cmd = exec.Command("systemctl", "enable", "bat-sleep.service")
err = cmd.Run()
if err != nil {
log.Fatal(err)
}
}

// reset disables the systemd service that persists the charging
// threshold between restarts.
// threshold between restarts and hibernation.
func reset() {
err := os.Remove("/etc/systemd/system/bat.service")
// Delete service that persists the charging threshold between
// restarts.
err := os.Remove("/etc/systemd/system/bat-boot.service")
if err != nil {
switch {
case strings.HasSuffix(err.Error(), ": permission denied"):
Expand All @@ -129,15 +188,31 @@ func reset() {
log.Fatal(err)
}
}
cmd := exec.Command("systemctl", "disable", "bat.service")
cmd := exec.Command("systemctl", "disable", "bat-boot.service")
var stdErr bytes.Buffer
cmd.Stderr = &stdErr
err = cmd.Run()
if err != nil {
switch msg := strings.TrimSpace(stdErr.String()); {
case strings.HasSuffix(msg, ": Unit file bat.service does not exist."):
break
default:
msg := strings.TrimSpace(stdErr.String())
if !strings.HasSuffix(msg, " file bat-boot.service does not exist.") {
log.Fatal(err)
}
}

// Delete the service that persists the charging threshold after
// hibernation.
err = os.Remove("/etc/systemd/system/bat-sleep.service")
if err != nil {
if !strings.HasSuffix(err.Error(), ": no such file or directory") {
log.Fatal(err)
}
}
cmd = exec.Command("systemctl", "disable", "bat-sleep.service")
cmd.Stderr = &stdErr
err = cmd.Run()
if err != nil {
msg := strings.TrimSpace(stdErr.String())
if !strings.HasSuffix(msg, " file bat-sleep.service does not exist.") {
log.Fatal(err)
}
}
Expand Down

0 comments on commit dab4854

Please sign in to comment.