comparison 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
comparison
equal deleted inserted replaced
-1:000000000000 0:fe9ad91c9f65
1 #!/bin/sh
2 #
3 # tpbatctl - get or set properties of IBM/Lenovo ThinkPad batteries
4 #
5 # Copyright (C) 2018 Guido Berhoerster <guido+tpbatctl@berhoerster.name>
6 #
7 # Permission is hereby granted, free of charge, to any person obtaining
8 # a copy of this software and associated documentation files (the
9 # "Software"), to deal in the Software without restriction, including
10 # without limitation the rights to use, copy, modify, merge, publish,
11 # distribute, sublicense, and/or sell copies of the Software, and to
12 # permit persons to whom the Software is furnished to do so, subject to
13 # the following conditions:
14 #
15 # The above copyright notice and this permission notice shall be included
16 # in all copies or substantial portions of the Software.
17 #
18 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
21 # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
22 # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
23 # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
24 # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25 #
26
27 PATH=/usr/sbin:/usr/bin:/sbin:/bin
28 LC_NUMERIC=C
29
30 #
31 # usage: is_bit_set value bit_pos
32 # returns: 0 if true, 1 if false
33 #
34 is_bit_set () {
35 [ $(( $1 & (1 << $2) )) -eq $(( 1 << $2 )) ]
36 }
37
38 #
39 # usage: is_num str
40 # returns: 0 if true, 1 if false
41 #
42 is_num () {
43 case $1 in
44 *[!0123456789]*)
45 return 1
46 esac
47 }
48
49 #
50 # usage: is_thinkpad
51 # returns: 0 if true, 1 if false
52 #
53 is_thinkpad () {
54 case $(head -1 /sys/class/dmi/id/sys_vendor) in
55 [Ii][Bb][Mm]|[Ll][Ee][Nn][Oo][Vv][Oo])
56 ;;
57 *)
58 return 1
59 esac
60
61 case $(head -1 /sys/class/dmi/id/product_version) in
62 ThinkPad*)
63 ;;
64 *)
65 return 1
66 esac
67 }
68
69 #
70 # usage: get_acpi_base_path
71 # returns: 0 if successful
72 #
73 get_acpi_base_path () {
74 for sys_device in /sys/class/power_supply/*; do
75 if [ ! -d "${sys_device}" ]; then
76 printf 'failed to obtain ACPI base path, battery not found\n' >&2
77 return 1;
78 fi
79
80 # strip trailing underscores from path components and remove the last
81 # component
82 sed -n -e '1s|_*\.|.|g' -e '1s|\.[^.]*$||' -e '1p' \
83 "${sys_device}/device/path" || return 1
84 return 0
85 done
86 }
87
88 #
89 # usage: acpi_call_exec acpi_method argument
90 # returns: 0 if successful
91 #
92 acpi_call_exec () {
93 printf '%s %s\n' "$1" "$2" >/proc/acpi/call
94 }
95
96 #
97 # usage: acpi_call_read_int
98 # returns: 0 if successful
99 #
100 acpi_call_read_int () {
101 acpi_response="$(head -1 /proc/acpi/call)"
102 case $acpi_response in
103 0x*)
104 # strip non-hexadecimal trailing chars
105 acpi_response="${acpi_response#0x}"
106 acpi_response="0x${acpi_response%%[!0123456789abcdef]*}"
107 printf '%d' "${acpi_response}"
108 ;;
109 Error:*)
110 printf 'ACPI method call failed: %s\n' "${acpi_response}" >&2
111 return 1
112 ;;
113 *)
114 printf 'ACPI method call failed, got unexpected response: %s\n' \
115 "${acpi_response}" >&2
116 return 1
117 esac
118 }
119
120 #
121 # usage: probe_battery acpi_base_path
122 # returns: 0 if successful
123 #
124 probe_battery () {
125 acpi_call_exec "${1}.HKEY.BCTG" "0x1" || return $?
126 result="$(acpi_call_read_int)" || return $?
127
128 if is_bit_set "${result}" 31; then
129 printf 'failed to probe battery' >&2
130 return 1
131 fi
132
133 # capabilities
134 printf '%d' $(( result >> 8 ))
135 return 0
136 }
137
138 #
139 # usage: get_threshold acpi_method battery
140 # returns: 0 if successful
141 #
142 get_threshold () {
143 acpi_call_exec "$1" "$(printf '0x%x' $2)"
144 result="$(acpi_call_read_int)" || return $?
145
146 if is_bit_set $result 31; then
147 return 1
148 fi
149
150 # threshold value
151 printf '%d' $(( result & ((1 << 7) - 1) ))
152 return 0
153 }
154
155 #
156 # usage: set_threshold acpi_method battery threshold
157 # returns: 0 if successful
158 #
159 set_threshold () {
160 acpi_call_exec "$1" "$(printf '0x%x' $(( $3 | ($2 << 8) )) )"
161 result="$(acpi_call_read_int)" || return $?
162
163 if is_bit_set $result 31; then
164 return 1
165 fi
166
167 return 0
168 }
169
170 #
171 # usage: get_start_threshold acpi_base_path battery
172 # returns: 0 if successful
173 #
174 get_start_threshold () {
175 if ! threshold="$(get_threshold "${1}.HKEY.BCTG" $2)"; then
176 printf 'failed to read the start threshold\n' >&2
177 return 1
178 fi
179 printf '%d' "${threshold}"
180 return 0
181 }
182
183 #
184 # usage: get_stop_threshold acpi_base_path battery
185 # returns: 0 if successful
186 #
187 get_stop_threshold () {
188 if ! threshold="$(get_threshold "${1}.HKEY.BCSG" $2)"; then
189 printf 'failed to read the stop threshold\n' >&2
190 return 1
191 fi
192 printf '%d' "${threshold}"
193 return 0
194 }
195
196 #
197 # usage: set_start_threshold acpi_base_path battery threshold
198 # returns: 0 if successful
199 #
200 set_start_threshold () {
201 if ! set_threshold "${1}.HKEY.BCCS" $2 $3; then
202 printf 'failed to set the stop threshold\n' >&2
203 return 1
204 fi
205 }
206
207 #
208 # usage: set_stop_threshold acpi_base_path battery threshold
209 # returns: 0 if successful
210 #
211 set_stop_threshold () {
212 if ! set_threshold "${1}.HKEY.BCSS" $2 $3; then
213 printf 'failed to set the stop threshold\n' >&2
214 return 1
215 fi
216 }
217
218 #
219 # usage: usage
220 # returns: nothing
221 #
222 usage () {
223 printf 'usage: %s [-g property]\n' "${0##*/}"
224 printf ' %s [-s property=value]\n' "${0##*/}"
225 }
226
227 if ! is_thinkpad; then
228 printf 'device not supported\n' >&2
229 exit 1
230 fi
231
232 if [ ! -f /proc/acpi/call ]; then
233 if ! modprobe acpi_call; then
234 printf 'failed to load acpi_call kernel module\n' >&2
235 exit 1
236 fi
237 fi
238
239 if [ ! -r /proc/acpi/call ] || [ ! -w /proc/acpi/call ]; then
240 printf 'insufficient privilege to access /proc/acpi/call\n' >&2
241 exit 1
242 fi
243
244 selected_batteries=
245 get_mode=false
246 set_mode=false
247 get_start_threshold=false
248 get_stop_threshold=false
249 assigned_start_threshold=
250 assigned_stop_threshold=
251
252 while getopts 'g:hs:' opt; do
253 case $opt in
254 g)
255 if $set_mode; then
256 usage
257 exit 1
258 fi
259
260 get_mode=true
261
262 case $OPTARG in
263 start_threshold)
264 get_start_threshold=true
265 ;;
266 stop_threshold)
267 get_stop_threshold=true
268 ;;
269 *)
270 printf 'unknown property: %s\n' "${OPTARG}"
271 exit 1
272 ;;
273 esac
274 ;;
275 h)
276 usage
277 exit 0
278 ;;
279 s)
280 if $get_mode; then
281 usage
282 exit 1
283 fi
284
285 set_mode=true
286
287 case $OPTARG in
288 start_threshold=*)
289 assigned_start_threshold="${OPTARG#*=}"
290 if ! is_num "${assigned_start_threshold}"; then
291 printf 'invalid threshold: %s\n' \
292 "${assigned_start_threshold}" >&2
293 exit 1
294 elif [ $assigned_start_threshold -gt 100 ]; then
295 printf 'invalid threshold: %s\n' \
296 "${assigned_start_threshold}" >&2
297 exit 1
298 elif [ $assigned_start_threshold -eq 100 ]; then
299 assigned_start_threshold=0
300 fi
301 ;;
302 stop_threshold=*)
303 assigned_stop_threshold="${OPTARG#*=}"
304 if ! is_num "${assigned_stop_threshold}"; then
305 printf 'invalid threshold: %s\n' \
306 "${assigned_stop_threshold}" >&2
307 exit 1
308 elif [ $assigned_stop_threshold -gt 100 ]; then
309 printf 'invalid threshold: %s\n' \
310 "${assigned_stop_threshold}" >&2
311 exit 1
312 elif [ $assigned_stop_threshold -eq 100 ]; then
313 assigned_stop_threshold=0
314 fi
315 ;;
316 *)
317 printf 'unknown property: %s\n' "${OPTARG}"
318 exit 1
319 ;;
320 esac
321 ;;
322 *)
323 usage
324 exit 1
325 esac
326 done
327 # default to get mode
328 if ! $get_mode && ! $set_mode; then
329 get_mode=true
330 get_start_threshold=true
331 get_stop_threshold=true
332 fi
333
334 shift $(( OPTIND - 1 ))
335
336 if [ $# -gt 1 ]; then
337 usage
338 exit 1
339 elif [ $# -eq 1 ]; then
340 if ! is_num "$1" || [ $1 -lt 1 ] || [ $1 -gt 2 ]; then
341 printf 'invalid battery number: %s\n' "$1"
342 exit 1
343 fi
344 selected_batteries=$1
345 if [ ! -d "/sys/class/power_supply/BAT$(( selected_batteries - 1))" ]; then
346 printf 'battery %d does not exist\n' $selected_batteries
347 exit 1
348 fi
349 else
350 # no explicitly selected battery means all batteries
351 for battery in 1 2; do
352 if [ -d "/sys/class/power_supply/BAT$(( battery - 1))" ]; then
353 selected_batteries="${selected_batteries} ${battery}"
354 fi
355 done
356 fi
357
358 if ! acpi_base_path="$(get_acpi_base_path)"; then
359 printf 'failed to obtain ACPI base path\n' >&2
360 exit 1
361 fi
362
363 if ! capabilities=$(probe_battery "${acpi_base_path}"); then
364 printf 'failed to obtain battery capabilities\n' >&2
365 exit 1
366 fi
367
368 if $get_mode; then
369 for battery in $selected_batteries; do
370 printf 'Battery %d\n' $battery
371 if $get_start_threshold; then
372 start_threshold="$(get_start_threshold \
373 $acpi_base_path $battery)" || exit $?
374 printf ' Charge Start Threshold: %d %%\n' $start_threshold
375 fi
376
377 if $get_stop_threshold; then
378 stop_threshold="$(get_stop_threshold $acpi_base_path $battery)" || \
379 exit $?
380 printf ' Charge Stop Threshold: %d %%\n' $stop_threshold
381 fi
382 done
383 else
384 for battery in $selected_batteries; do
385 if ! is_bit_set $capabilities 0; then
386 printf 'setting thresholds is not supported\n' >&2
387 exit 1
388 fi
389
390 start_threshold="$(get_start_threshold $acpi_base_path $battery)" || \
391 exit $?
392 stop_threshold="$(get_stop_threshold $acpi_base_path $battery)" || \
393 exit $?
394
395 if [ -n "${assigned_start_threshold}" ] && \
396 [ -n "${assigned_stop_threshold}" ]; then
397 if [ $assigned_start_threshold -gt $assigned_stop_threshold ]; then
398 printf 'the start threshold must be smaller than the stop threshold\n' >&2
399 exit 1
400 fi
401
402 if [ $assigned_start_threshold -gt $stop_threshold ]; then
403 # the new start threshold is greather then the old stop
404 # threshold, thus change the stop threshold first so that the
405 # start threshold always remains smaller than the stop
406 # threshold
407 set_stop_threshold $acpi_base_path $battery \
408 $assigned_stop_threshold || exit $?
409 set_start_threshold $acpi_base_path $battery \
410 $assigned_start_threshold || exit $?
411 else
412 # otherwise the new stop threshold is smaller than or equal to
413 # the old start threshold, thus change the start threshold
414 # first
415 set_start_threshold $acpi_base_path $battery \
416 $assigned_start_threshold || exit $?
417 set_stop_threshold $acpi_base_path $battery \
418 $assigned_stop_threshold || exit $?
419 fi
420 elif [ -n "${assigned_start_threshold}" ]; then
421 if [ $assigned_start_threshold -gt $stop_threshold ]; then
422 printf 'the start threshold must be smaller than the stop threshold\n' >&2
423 exit 1
424 fi
425
426 set_start_threshold $acpi_base_path $battery \
427 $assigned_start_threshold || exit $?
428 else
429 if [ $assigned_stop_threshold -lt $start_threshold ]; then
430 printf 'the start threshold must be smaller than the stop threshold\n' >&2
431 exit 1
432 fi
433
434 set_stop_threshold $acpi_base_path $battery \
435 $assigned_stop_threshold || exit $?
436 fi
437 done
438 fi