diff tpbatctl.sh @ 0:fe9ad91c9f65 default tip

Initial revision
author Guido Berhoerster <guido+tpbatctl@berhoerster.name>
date Thu, 02 Aug 2018 16:30:59 +0200
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tpbatctl.sh	Thu Aug 02 16:30:59 2018 +0200
@@ -0,0 +1,438 @@
+#!/bin/sh
+#
+# tpbatctl - get or set properties of IBM/Lenovo ThinkPad batteries
+#
+# Copyright (C) 2018 Guido Berhoerster <guido+tpbatctl@berhoerster.name>
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+PATH=/usr/sbin:/usr/bin:/sbin:/bin
+LC_NUMERIC=C
+
+#
+# usage: is_bit_set value bit_pos
+# returns: 0 if true, 1 if false
+#
+is_bit_set () {
+    [ $(( $1 & (1 << $2) )) -eq $(( 1 << $2 )) ]
+}
+
+#
+# usage: is_num str
+# returns: 0 if true, 1 if false
+#
+is_num () {
+    case $1 in
+        *[!0123456789]*)
+            return 1
+    esac
+}
+
+#
+# usage: is_thinkpad
+# returns: 0 if true, 1 if false
+#
+is_thinkpad () {
+    case $(head -1 /sys/class/dmi/id/sys_vendor) in
+        [Ii][Bb][Mm]|[Ll][Ee][Nn][Oo][Vv][Oo])
+            ;;
+        *)
+            return 1
+    esac
+
+    case $(head -1 /sys/class/dmi/id/product_version) in
+        ThinkPad*)
+            ;;
+        *)
+            return 1
+    esac
+}
+
+#
+# usage: get_acpi_base_path
+# returns: 0 if successful
+#
+get_acpi_base_path () {
+    for sys_device in /sys/class/power_supply/*; do
+        if [ ! -d "${sys_device}" ]; then
+            printf 'failed to obtain ACPI base path, battery not found\n' >&2
+            return 1;
+        fi
+
+        # strip trailing underscores from path components and remove the last
+        # component
+        sed -n -e '1s|_*\.|.|g' -e '1s|\.[^.]*$||' -e '1p' \
+                "${sys_device}/device/path" || return 1
+        return 0
+    done
+}
+
+#
+# usage: acpi_call_exec acpi_method argument
+# returns: 0 if successful
+#
+acpi_call_exec () {
+    printf '%s %s\n' "$1" "$2" >/proc/acpi/call
+}
+
+#
+# usage: acpi_call_read_int
+# returns: 0 if successful
+#
+acpi_call_read_int () {
+    acpi_response="$(head -1 /proc/acpi/call)"
+    case $acpi_response in
+        0x*)
+            # strip non-hexadecimal trailing chars
+            acpi_response="${acpi_response#0x}"
+            acpi_response="0x${acpi_response%%[!0123456789abcdef]*}"
+            printf '%d' "${acpi_response}"
+            ;;
+        Error:*)
+            printf 'ACPI method call failed: %s\n' "${acpi_response}" >&2
+            return 1
+            ;;
+        *)
+            printf 'ACPI method call failed, got unexpected response: %s\n' \
+                    "${acpi_response}" >&2
+            return 1
+    esac
+}
+
+#
+# usage: probe_battery acpi_base_path
+# returns: 0 if successful
+#
+probe_battery () {
+    acpi_call_exec "${1}.HKEY.BCTG" "0x1" || return $?
+    result="$(acpi_call_read_int)" || return $?
+
+    if is_bit_set "${result}" 31; then
+        printf 'failed to probe battery' >&2
+        return 1
+    fi
+
+    # capabilities
+    printf '%d' $(( result >> 8 ))
+    return 0
+}
+
+#
+# usage: get_threshold acpi_method battery
+# returns: 0 if successful
+#
+get_threshold () {
+    acpi_call_exec "$1" "$(printf '0x%x' $2)"
+    result="$(acpi_call_read_int)" || return $?
+
+    if is_bit_set $result 31; then
+        return 1
+    fi
+
+    # threshold value
+    printf '%d' $(( result & ((1 << 7) - 1) ))
+    return 0
+}
+
+#
+# usage: set_threshold acpi_method battery threshold
+# returns: 0 if successful
+#
+set_threshold () {
+    acpi_call_exec "$1" "$(printf '0x%x' $(( $3 | ($2 << 8) )) )"
+    result="$(acpi_call_read_int)" || return $?
+
+    if is_bit_set $result 31; then
+        return 1
+    fi
+
+    return 0
+}
+
+#
+# usage: get_start_threshold acpi_base_path battery
+# returns: 0 if successful
+#
+get_start_threshold () {
+    if ! threshold="$(get_threshold "${1}.HKEY.BCTG" $2)"; then
+        printf 'failed to read the start threshold\n' >&2
+        return 1
+    fi
+    printf '%d' "${threshold}"
+    return 0
+}
+
+#
+# usage: get_stop_threshold acpi_base_path battery
+# returns: 0 if successful
+#
+get_stop_threshold () {
+    if ! threshold="$(get_threshold "${1}.HKEY.BCSG" $2)"; then
+        printf 'failed to read the stop threshold\n' >&2
+        return 1
+    fi
+    printf '%d' "${threshold}"
+    return 0
+}
+
+#
+# usage: set_start_threshold acpi_base_path battery threshold
+# returns: 0 if successful
+#
+set_start_threshold () {
+    if ! set_threshold "${1}.HKEY.BCCS" $2 $3; then
+        printf 'failed to set the stop threshold\n' >&2
+        return 1
+    fi
+}
+
+#
+# usage: set_stop_threshold acpi_base_path battery threshold
+# returns: 0 if successful
+#
+set_stop_threshold () {
+    if ! set_threshold "${1}.HKEY.BCSS" $2 $3; then
+        printf 'failed to set the stop threshold\n' >&2
+        return 1
+    fi
+}
+
+#
+# usage: usage
+# returns: nothing
+#
+usage () {
+    printf 'usage: %s [-g property]\n' "${0##*/}"
+    printf '    %s [-s property=value]\n' "${0##*/}"
+}
+
+if ! is_thinkpad; then
+    printf 'device not supported\n' >&2
+    exit 1
+fi
+
+if [ ! -f /proc/acpi/call ]; then
+    if ! modprobe acpi_call; then
+        printf 'failed to load acpi_call kernel module\n' >&2
+        exit 1
+    fi
+fi
+
+if [ ! -r /proc/acpi/call ] || [ ! -w /proc/acpi/call ]; then
+    printf 'insufficient privilege to access /proc/acpi/call\n' >&2
+    exit 1
+fi
+
+selected_batteries=
+get_mode=false
+set_mode=false
+get_start_threshold=false
+get_stop_threshold=false
+assigned_start_threshold=
+assigned_stop_threshold=
+
+while getopts 'g:hs:' opt; do
+    case $opt in
+        g)
+            if $set_mode; then
+                usage
+                exit 1
+            fi
+
+            get_mode=true
+
+            case $OPTARG in
+                start_threshold)
+                    get_start_threshold=true
+                    ;;
+                stop_threshold)
+                    get_stop_threshold=true
+                    ;;
+                *)
+                    printf 'unknown property: %s\n' "${OPTARG}"
+                    exit 1
+                    ;;
+            esac
+            ;;
+        h)
+            usage
+            exit 0
+            ;;
+        s)
+            if $get_mode; then
+                usage
+                exit 1
+            fi
+
+            set_mode=true
+
+            case $OPTARG in
+                start_threshold=*)
+                    assigned_start_threshold="${OPTARG#*=}"
+                    if ! is_num "${assigned_start_threshold}"; then
+                        printf 'invalid threshold: %s\n' \
+                                "${assigned_start_threshold}" >&2
+                        exit 1
+                    elif [ $assigned_start_threshold -gt 100 ]; then
+                        printf 'invalid threshold: %s\n' \
+                                "${assigned_start_threshold}" >&2
+                        exit 1
+                    elif [ $assigned_start_threshold -eq 100 ]; then
+                        assigned_start_threshold=0
+                    fi
+                    ;;
+                stop_threshold=*)
+                    assigned_stop_threshold="${OPTARG#*=}"
+                    if ! is_num "${assigned_stop_threshold}"; then
+                        printf 'invalid threshold: %s\n' \
+                                "${assigned_stop_threshold}" >&2
+                        exit 1
+                    elif [ $assigned_stop_threshold -gt 100 ]; then
+                        printf 'invalid threshold: %s\n' \
+                                "${assigned_stop_threshold}" >&2
+                        exit 1
+                    elif [ $assigned_stop_threshold -eq 100 ]; then
+                        assigned_stop_threshold=0
+                    fi
+                    ;;
+                *)
+                    printf 'unknown property: %s\n' "${OPTARG}"
+                    exit 1
+                    ;;
+            esac
+            ;;
+        *)
+            usage
+            exit 1
+    esac
+done
+# default to get mode
+if ! $get_mode && ! $set_mode; then
+    get_mode=true
+    get_start_threshold=true
+    get_stop_threshold=true
+fi
+
+shift $(( OPTIND - 1 ))
+
+if [ $# -gt 1 ]; then
+    usage
+    exit 1
+elif [ $# -eq 1 ]; then
+    if ! is_num "$1" || [ $1 -lt 1 ] || [ $1 -gt 2 ]; then
+        printf 'invalid battery number: %s\n' "$1"
+        exit 1
+    fi
+    selected_batteries=$1
+    if [ ! -d "/sys/class/power_supply/BAT$(( selected_batteries - 1))" ]; then
+        printf 'battery %d does not exist\n' $selected_batteries
+        exit 1
+    fi
+else
+    # no explicitly selected battery means all batteries
+    for battery in 1 2; do
+        if [ -d "/sys/class/power_supply/BAT$(( battery - 1))" ]; then
+            selected_batteries="${selected_batteries} ${battery}"
+        fi
+    done
+fi
+
+if ! acpi_base_path="$(get_acpi_base_path)"; then
+    printf 'failed to obtain ACPI base path\n' >&2
+    exit 1
+fi
+
+if ! capabilities=$(probe_battery "${acpi_base_path}"); then
+    printf 'failed to obtain battery capabilities\n' >&2
+    exit 1
+fi
+
+if $get_mode; then
+    for battery in $selected_batteries; do
+        printf 'Battery %d\n' $battery
+        if $get_start_threshold; then
+            start_threshold="$(get_start_threshold \
+                    $acpi_base_path $battery)" || exit $?
+            printf '    Charge Start Threshold: %d %%\n' $start_threshold
+        fi
+
+        if $get_stop_threshold; then
+            stop_threshold="$(get_stop_threshold $acpi_base_path $battery)" || \
+                    exit $?
+            printf '    Charge Stop Threshold:  %d %%\n' $stop_threshold
+        fi
+    done
+else
+    for battery in $selected_batteries; do
+        if ! is_bit_set $capabilities 0; then
+            printf 'setting thresholds is not supported\n' >&2
+            exit 1
+        fi
+
+        start_threshold="$(get_start_threshold $acpi_base_path $battery)" || \
+                exit $?
+        stop_threshold="$(get_stop_threshold $acpi_base_path $battery)" || \
+                exit $?
+
+        if [ -n "${assigned_start_threshold}" ] && \
+                [ -n "${assigned_stop_threshold}" ]; then
+            if [ $assigned_start_threshold -gt $assigned_stop_threshold ]; then
+                printf 'the start threshold must be smaller than the stop threshold\n' >&2
+                exit 1
+            fi
+
+            if [ $assigned_start_threshold -gt $stop_threshold ]; then
+                # the new start threshold is greather then the old stop
+                # threshold, thus change the stop threshold first so that the
+                # start threshold always remains smaller than the stop
+                # threshold
+                set_stop_threshold $acpi_base_path $battery \
+                        $assigned_stop_threshold || exit $?
+                set_start_threshold $acpi_base_path $battery \
+                        $assigned_start_threshold || exit $?
+            else
+                # otherwise the new stop threshold is smaller than or equal to
+                # the old start threshold, thus change the start threshold
+                # first
+                set_start_threshold $acpi_base_path $battery \
+                        $assigned_start_threshold || exit $?
+                set_stop_threshold $acpi_base_path $battery \
+                        $assigned_stop_threshold || exit $?
+            fi
+        elif [ -n "${assigned_start_threshold}" ]; then
+            if [ $assigned_start_threshold -gt $stop_threshold ]; then
+                printf 'the start threshold must be smaller than the stop threshold\n' >&2
+                exit 1
+            fi
+
+            set_start_threshold $acpi_base_path $battery \
+                    $assigned_start_threshold || exit $?
+        else
+            if [ $assigned_stop_threshold -lt $start_threshold ]; then
+                printf 'the start threshold must be smaller than the stop threshold\n' >&2
+                exit 1
+            fi
+
+            set_stop_threshold $acpi_base_path $battery \
+                    $assigned_stop_threshold || exit $?
+        fi
+    done
+fi