Mercurial > projects > tpbatctl
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 |