Mercurial > projects > tpbatctl
view 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 source
#!/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