Add fan speed
[khatus.git] / bin / khatus_loop
CommitLineData
438d0d5f
SK
1#! /bin/bash
2
4d314e0f
SK
3set -e
4
b9bdec44
SK
5produce_fan() {
6 fan_path="$1"
7 cat "$fan_path"
8}
9
5dd1535a
SK
10produce_temperature() {
11 thermal_zone="$1"
12 cat "/sys/class/thermal/thermal_zone${thermal_zone}/temp"
13}
14
d3d7d712
SK
15produce_loadavg() {
16 cat /proc/loadavg
17}
18
cf698ca8
SK
19produce_disk_io() {
20 disk_io_device="$1"
21 awk '
22 {
23 r = $3
24 w = $7
25 print w, r
26 }
27 ' "/sys/block/$disk_io_device/stat"
28}
29
c0e8473e
SK
30produce_disk_space() {
31 disk_space_device="$1"
32 df --output=pcent "$disk_space_device" | awk 'NR == 2 {print $1}'
33}
34
2b221665
SK
35produce_net_addr_io() {
36 ip -s addr \
e70e90e1 37 | awk '
2b221665
SK
38 BEGIN {
39 bytes_per_unit = 1024 * 1024
40 }
41
42 /^[0-9]+:/ {
43 sub(":$", "", $1)
44 sub(":$", "", $2)
45 sequence = $1
46 interface = $2
47 interfaces[sequence] = interface
48 }
49
50 /^ +inet [0-9]/ {
51 sub("/[0-9]+", "", $2)
52 addr = $2
53 addrs[interface] = addr
54 }
55
56 /^ +RX: / {transfer_direction = "r"}
57 /^ +TX: / {transfer_direction = "w"}
58
59 /^ +[0-9]+ +[0-9]+ +[0-9]+ +[0-9]+ +[0-9]+ +[0-9]+ *$/ {
60 io[interface, transfer_direction] = $1;
61 }
62
63 END {
64 for (seq=1; seq<=sequence; seq++) {
65 interface = interfaces[seq]
66 label = substr(interface, 1, 1)
67 if (addrs[interface]) {
68 curr_read = io[interface, "r"]
69 curr_write = io[interface, "w"]
70 print(interface, addrs[interface], curr_write, curr_read)
71 } else {
72 print(interface)
73 }
74 }
75 }'
76}
77
0d116e88
SK
78produce_net_wifi_status() {
79 nmcli \
80 -f ACTIVE,SSID,SIGNAL \
81 -t \
82 d wifi \
83 | awk \
84 -F ':' \
85 '
86 BEGIN {wifi_status = "--"}
87 $1 == "yes" {wifi_status = $2 ":" $3 "%"}
88 END {print wifi_status}
89 '
90}
91
ad0b3a5b
SK
92produce_bluetooth_power() {
93 echo -e 'show \n quit' \
94 | bluetoothctl \
95 | awk '
96 /^Controller / {
97 controller = $2;
98 controllers[++ctrl_count] = controller;
99 }
100 /^\t[A-Z][A-Za-z]+:/ {
101 key = $1;
102 sub(":$", "", key);
103 val = $2;
104 for (i=3; i<=NF; i++) {
105 val = val " " $i};
106 data[controller, key] = val;
107 }
108 END {
109 # Using the 1st seen controller. Should we select specific instead?
110 power_status = data[controllers[1], "Powered"];
111 if (ctrl_count > 0) {
112 if (power_status == "no") {
113 power_status = "off"
114 } else if (power_status == "yes") {
115 power_status = "on"
116 } else {
117 printf("Unexpected bluetooth power status: %s\n", power_status)\
118 > "/dev/stderr";
119 power_status = "ERROR"
120 }
121 } else {
122 power_status = "off" # TODO: Perhaps use differentiated marker?
123 }
62aa4063 124 printf("%s\n", power_status);
ad0b3a5b
SK
125 }'
126}
127
bbb42518
SK
128produce_screen_brightness() {
129 screen_brightness_device_path="$1"
130 echo "\
131 $(cat $screen_brightness_device_path/max_brightness) \
132 $(cat $screen_brightness_device_path/brightness)\
133 "
134}
135
61e5a351
SK
136produce_volume() {
137 pactl list sinks \
138 | awk '
139 /^\tMute:/ {
140 printf("%s,", $0);
141 }
142 /^\tVolume:/ {
143 for (i=2; i<=NF; i++) printf(" %s", $i);
144 }' \
145 | awk -v RS=',' '
146 /^[ \t]*Mute:/ {mute = $2}
147 /^[ \t]*front-left:/ {left = $4}
148 /^[ \t]*front-right:/ {right = $4}
149 END {
150 if (mute == "yes") {
62aa4063 151 print("x")
61e5a351 152 } else {
62aa4063 153 print("%s %s\n", left, right)
61e5a351
SK
154 }
155 }
156 '
157}
158
365549a9
SK
159produce_mpd_state() {
160 echo 'status' \
161 | nc 127.0.0.1 6600 \
162 | awk '
163 {
164 status[$1] = $2
165 }
166
167 /^time: +[0-9]+:[0-9]+$/ {
168 split($2, time, ":")
169 seconds_current = time[1]
170 seconds_total = time[2]
171
172 hours = int(seconds_current / 60 / 60);
173 secs_beyond_hours = seconds_current - (hours * 60 * 60);
174 mins = int(secs_beyond_hours / 60);
175 secs = secs_beyond_hours - (mins * 60);
176 if (hours > 0) {
177 current_time = sprintf("%d:%.2d:%.2d", hours, mins, secs)
178 } else {
179 current_time = sprintf("%.2d:%.2d", mins, secs)
180 }
181
182 if (seconds_total > 0) {
183 time_percentage = (seconds_current / seconds_total) * 100
184 current_percentage = sprintf("%d%%", time_percentage)
185 } else {
186 current_percentage = "~"
187 }
188 }
189
190 END {
191 state = status["state:"]
192
193 if (state == "play") {
194 symbol = "▶"
195 } else if (state == "pause") {
196 symbol = "❚❚"
197 } else if (state == "stop") {
198 symbol = "⬛"
199 } else {
200 symbol = "--"
201 }
202
203 printf(\
62aa4063 204 "%s %s %s\n",
365549a9
SK
205 status["state:"], current_time, current_percentage\
206 )
207 }
208 '
209}
210
211produce_mpd_song() {
212 echo 'currentsong' \
213 | nc 127.0.0.1 6600 \
214 | awk '
215 /^OK/ {
216 next
217 }
218
219 {
220 key = $1
221 sub("^" key " +", "")
222 val = $0
223 data[key] = val
224 }
225
226 END {
227 name = data["Name:"]
228 title = data["Title:"]
229 file = data["file:"]
230
231 if (name) {
232 out = name
233 } else if (title) {
234 out = title
235 } else if (file) {
236 last = split(file, parts, "/")
237 out = parts[last]
238 } else {
239 out = ""
240 }
241 print out
242 }
243 '
244}
438d0d5f 245
6339a4f8 246produce_weather() {
4d314e0f
SK
247 weather_station_id="$1"
248 metar -d "$weather_station_id" 2>&1 \
438d0d5f
SK
249 | awk '
250 /METAR pattern not found in NOAA data/ {
251 failures++
252 }
253
254 /^Temperature/ {
255 celsius = $3;
256 fahrenheit = (celsius * (9 / 5)) + 32;
257 temperature = fahrenheit
258 }
259
260 END {
261 if (failures > 0) {
262 temperature = "--"
263 }
264 print temperature "°F"
265 }'
266}
267
6339a4f8 268produce_datetime() {
438d0d5f
SK
269 date +'%a %b %d %H:%M:%S'
270}
271
6339a4f8 272consume() {
438d0d5f 273 pipe="$1"
2b221665
SK
274 debug="$2"
275 prefixes_of_net_interfaces_to_show="$3"
438d0d5f
SK
276 tail -f "$pipe" \
277 | stdbuf -o L awk \
2b221665 278 -v opt_debug="$debug" \
365549a9 279 -v opt_mpd_song_max_chars=10 \
2b221665 280 -v opt_prefixes_of_net_interfaces_to_show="$prefixes_of_net_interfaces_to_show" \
438d0d5f 281 '
b9bdec44
SK
282 /^in:FAN +status:/\
283 {
284 split_msg_parts()
285 db["fan_status"] = $2
286 }
287
288 /^in:FAN +speed:/\
289 {
290 split_msg_parts()
291 db["fan_speed"] = $2
292 }
293
294 /^in:FAN +level:/\
295 {
296 split_msg_parts()
297 db["fan_level"] = $2
298 }
299
5dd1535a
SK
300 /^in:TEMPERATURE/\
301 {
302 split_msg_parts()
303 db["temperature"] = $1
304 }
305
d3d7d712
SK
306 /^in:LOAD_AVG/\
307 {
308 split_msg_parts()
309 set_load_avg()
310 }
311
cf698ca8
SK
312 /^in:DISK_IO/\
313 {
314 split_msg_parts()
315 set_disk_io()
316 }
317
c0e8473e
SK
318 /^in:DISK_SPACE/\
319 {
320 split_msg_parts()
321 db["disk_space_used"] = msg_body
322 }
323
2b221665
SK
324 /^in:NET_ADDR_IO/\
325 {
326 split_msg_parts()
327 set_net_addr_io()
328 }
329
0d116e88
SK
330 /^in:NET_WIFI_STATUS/\
331 {
332 split_msg_parts()
333 db["net_wifi_status"] = msg_body
334 }
335
ad0b3a5b
SK
336 /^in:BLUETOOTH_POWER/\
337 {
338 split_msg_parts()
339 db["bluetooth_power"] = msg_body
340 }
341
bbb42518
SK
342 /^in:SCREEN_BRIGHTNESS/\
343 {
344 split_msg_parts()
345 set_screen_brightness()
346 }
347
61e5a351
SK
348 /^in:VOLUME/\
349 {
350 split_msg_parts()
351 db["volume"] = msg_body
352 }
353
365549a9
SK
354 /^in:MPD_STATE/\
355 {
356 split_msg_parts()
357 db["mpd_state"] = $1
358 db["mpd_curr_song_time"] = $2
359 db["mpd_curr_song_percent"] = $3
360 }
361
362 /^in:MPD_SONG/\
438d0d5f 363 {
365549a9
SK
364 split_msg_parts()
365 db["mpd_curr_song_name"] = msg_body
438d0d5f
SK
366 }
367
365549a9 368 /^in:WEATHER/\
438d0d5f 369 {
365549a9
SK
370 split_msg_parts()
371 db["weather_temperature"] = msg_body
438d0d5f
SK
372 }
373
365549a9 374 /^in:DATE_TIME/\
438d0d5f 375 {
365549a9
SK
376 split_msg_parts()
377 db["datetime"] = msg_body
378 }
379
380 /^out:BAR/\
381 {
382 split_msg_parts()
438d0d5f
SK
383 print make_bar()
384 }
385
d3d7d712
SK
386 function set_load_avg( sched) {
387 split($4, sched, "/")
388 db["load_avg_1min"] = $1
389 db["load_avg_5min"] = $2
390 db["load_avg_15min"] = $3
391 db["kern_sched_queue_runnable"] = sched[1]
392 db["kern_sched_queue_total"] = sched[2]
393 db["kern_sched_latest_pid"] = $5
394 }
365549a9 395
cf698ca8
SK
396 function set_disk_io( curr_w, curr_r, prev_w, prev_r) {
397 curr_w = $1
398 curr_r = $2
399 prev_w = db["disk_io_curr_w"]
400 prev_r = db["disk_io_curr_r"]
401 db["disk_io_curr_w"] = curr_w
402 db["disk_io_curr_r"] = curr_r
403 db["disk_io_diff_w"] = curr_w - prev_w
404 db["disk_io_diff_r"] = curr_r - prev_r
405 }
406
2b221665
SK
407 function set_net_addr_io( \
408 interface, address, io_curr_w, io_curr_r, io_prev_w, io_prev_r\
409 ) {
410 interface = $1
411 address = $2
412 io_curr_w = $3
413 io_curr_r = $4
414 if (interface) {
415 if (address && io_curr_w && io_curr_r) {
416 # recalculate
417 io_prev_w = net_io_curr_w[interface]
418 io_prev_r = net_io_curr_r[interface]
419
420 net_addr[interface] = address
421 net_io_curr_w[interface] = io_curr_w
422 net_io_curr_r[interface] = io_curr_r
423 net_io_diff_w[interface] = io_curr_w - io_prev_w
424 net_io_diff_r[interface] = io_curr_r - io_prev_r
425 } else {
426 # clear
427 net_addr[interface] = ""
428 net_io_curr_w[interface] = 0
429 net_io_curr_r[interface] = 0
430 net_io_diff_w[interface] = 0
431 net_io_diff_r[interface] = 0
432 }
433 }
434 }
435
bbb42518
SK
436 function set_screen_brightness( max, cur) {
437 max = $1
438 cur = $2
439 db["screen_brightness"] = (cur / max) * 100
440 }
441
365549a9
SK
442 function split_msg_parts() {
443 msg_head = $1
444 sub("^" msg_head " +", "")
445 msg_body = $0
446 debug(msg_head, msg_body)
438d0d5f
SK
447 }
448
365549a9 449 function make_bar( position, bar, sep, i, j) {
d3d7d712 450 position[++i] = make_status_cpu()
cf698ca8 451 position[++i] = make_status_disk()
0d116e88 452 position[++i] = make_status_net()
ad0b3a5b 453 position[++i] = sprintf("B=%s", db["bluetooth_power"])
bbb42518 454 position[++i] = sprintf("*%d%%", db["screen_brightness"])
61e5a351 455 position[++i] = sprintf("(%s)", db["volume"])
365549a9 456 position[++i] = make_status_mpd()
438d0d5f
SK
457 position[++i] = db["weather_temperature"]
458 position[++i] = db["datetime"]
459 bar = ""
460 sep = ""
461 for (j = 1; j <= i; j++) {
462 bar = bar sep position[j]
463 sep = " "
464 }
465 return bar
466 }
365549a9 467
b9bdec44 468 function make_status_cpu( load, temp, fan) {
d3d7d712 469 load = db["load_avg_1min"]
5dd1535a 470 temp = db["temperature"] / 1000
b9bdec44
SK
471 fan = db["fan_speed"]
472 return sprintf("C=[%4.2f %d°C %4drpm]", load, temp, fan)
d3d7d712
SK
473 }
474
cf698ca8
SK
475 function make_status_disk( bytes_per_sector, bytes_per_mb, w, r) {
476 bytes_per_sector = 512
477 bytes_per_mb = 1024 * 1024
478 w = (db["disk_io_diff_w"] * bytes_per_sector) / bytes_per_mb
479 r = (db["disk_io_diff_r"] * bytes_per_sector) / bytes_per_mb
480 return \
481 sprintf("D=[%s %0.3f▲ %0.3f▼]", db["disk_space_used"], w, r)
482 }
483
2b221665
SK
484 function make_status_net( \
485 out,
486 number_of_interfaces_to_show,
487 n,
488 array_of_prefixes_of_interfaces_to_show,
489 prefix,
490 interface,
491 label,
492 count_printed,
493 sep,
494 io_stat,
495 dw, dr,
496 bytes_per_unit\
497 ) {
498 out = ""
499 number_of_interfaces_to_show = \
500 split(\
501 opt_prefixes_of_net_interfaces_to_show,\
502 array_of_prefixes_of_interfaces_to_show,\
503 ","\
504 )
505 for (n = 1; n <= number_of_interfaces_to_show; n++) {
506 prefix = array_of_prefixes_of_interfaces_to_show[n]
507 for (interface in net_addr) {
508 if (interface ~ ("^" prefix)) {
509 label = substr(interface, 1, 1)
510 if (net_addr[interface]) {
511 bytes_per_mb = 1024 * 1024 # TODO: option
512 dw = net_io_diff_w[interface] / bytes_per_mb
513 dr = net_io_diff_r[interface] / bytes_per_mb
514 io_stat = sprintf("%0.3f▲ %0.3f▼", dw, dr)
515 } else {
516 io_stat = "--"
517 }
518 if (interface ~ "^w") {
519 label = label ":" db["net_wifi_status"]
520 }
521 if (++count_printed > 1) {
522 sep = " "
523 } else {
524 sep = ""
525 }
526 out = out sep label ":" io_stat
527 }
528 }
529 }
530 return sprintf("N[%s]", out)
0d116e88
SK
531 }
532
365549a9
SK
533 function make_status_mpd( state, status) {
534 state = db["mpd_state"]
535
536 if (state == "play") {
537 status = make_status_mpd_state_known("▶")
538 } else if (state == "pause") {
539 status = make_status_mpd_state_known("❚❚")
540 } else if (state == "stop") {
541 status = make_status_mpd_state_known("⬛")
542 } else {
543 status = make_status_mpd_state_unknown("--")
544 }
545
546 return sprintf("[%s]", status)
547 }
548
549 function make_status_mpd_state_known(symbol) {
550 return sprintf(\
551 "%s %s %s %s",
552 symbol,
553 db["mpd_curr_song_time"],
554 db["mpd_curr_song_percent"],
555 substr(db["mpd_curr_song_name"], 1, opt_mpd_song_max_chars)\
556 )
557 }
558
559 function make_status_mpd_state_unknown(symbol) {
560 return sprintf("%s", symbol)
561 }
562
563 function debug(location, msg) {
564 if (opt_debug) {
565 print_error(location, msg)
566 }
567 }
568
569 function print_error(location, msg) {
570 print(location " ==> " msg) > "/dev/stderr"
571 }
438d0d5f
SK
572 '
573}
574
6339a4f8 575produce_bar_req() {
438d0d5f
SK
576 echo ''
577}
578
579spawn() {
580 cmd="$1"
581 pipe="$2"
365549a9 582 msg_head="$3"
438d0d5f
SK
583 interval="$4"
584 while true; do
2b221665
SK
585 $cmd | while read line; do
586 echo "${msg_head} $line" > "$pipe"
587 done
588 sleep "$interval"
438d0d5f
SK
589 done &
590}
591
592main() {
4d314e0f 593 # Defaults
2b221665 594 debug=0
4d314e0f
SK
595 dir_data="$HOME/.khatus"
596 weather_station_id='KJFK'
bbb42518 597 screen_brightness_device_name='acpi_video0'
2b221665 598 prefixes_of_net_interfaces_to_show='w' # comma-separated
c0e8473e 599 disk_space_device='/'
cf698ca8 600 disk_io_device='sda'
5dd1535a 601 thermal_zone=0
b9bdec44 602 fan_path='/proc/acpi/ibm/fan'
4d314e0f
SK
603
604 # User-overrides
2b221665
SK
605 long_options=''
606 long_options+='debug'
607 long_options+=',data-dir:'
608 long_options+=',weather-station:'
609 long_options+=',screen-device:'
610 long_options+=',prefixes_of_net_interfaces_to_show:'
c0e8473e 611 long_options+=',disk_space_device:'
5dd1535a 612 long_options+=',thermal_zone:'
b9bdec44 613 long_options+=',fan_path:'
4d314e0f
SK
614 OPTS=$(
615 getopt \
2b221665
SK
616 -o 'd' \
617 -l $long_options \
4d314e0f
SK
618 -- "$@"
619 )
620 eval set -- "$OPTS"
621 while true
622 do
623 case "$1" in
2b221665
SK
624 -d|--debug)
625 debug=1
626 shift
627 ;;
4d314e0f
SK
628 --data-dir)
629 dir_data="$2"
630 shift 2
631 ;;
632 --weather-station)
633 weather_station_id="$2"
634 shift 2
635 ;;
bbb42518
SK
636 --screen-device)
637 screen_brightness_device_name="$2"
638 shift 2
639 ;;
2b221665
SK
640 --prefixes_of_net_interfaces_to_show)
641 prefixes_of_net_interfaces_to_show="$2"
642 shift 2
643 ;;
c0e8473e
SK
644 --disk_space_device)
645 disk_space_device="$2"
646 shift 2
647 ;;
cf698ca8
SK
648 --disk_io_device)
649 disk_io_device="$2"
650 shift 2
651 ;;
5dd1535a
SK
652 --thermal_zone)
653 thermal_zone="$2"
654 shift 2
655 ;;
b9bdec44
SK
656 --fan_path)
657 fan_path="$2"
658 shift 2
659 ;;
4d314e0f
SK
660 --)
661 shift
662 break
663 ;;
664 esac
665 done
666
0c0ebbe1 667 pipe="$dir_data/khatus_data_pipe"
bbb42518
SK
668 screen_brightness_device_path='/sys/class/backlight'
669 screen_brightness_device_path+="/$screen_brightness_device_name"
0c0ebbe1 670
4d314e0f 671 ( echo "Khatus starting with the following parameters:"
2b221665
SK
672 ( echo " debug|= $debug"
673 echo " dir_data|= $dir_data"
0c0ebbe1 674 echo " pipe|= $pipe"
2b221665
SK
675 echo " screen_brightness_device_name|= $screen_brightness_device_name"
676 echo " screen_brightness_device_path|= $screen_brightness_device_path"
4d314e0f 677 echo " weather_station_id|= $weather_station_id"
c0e8473e
SK
678 echo " prefixes_of_net_interfaces_to_show|= $prefixes_of_net_interfaces_to_show"
679 echo " disk_space_device|= $disk_space_device"
cf698ca8 680 echo " disk_io_device|= $disk_io_device"
5dd1535a 681 echo " thermal_zone|= $thermal_zone"
b9bdec44 682 echo " fan_path|= $fan_path"
4d314e0f
SK
683 ) | column -ts\|
684 echo ''
685 ) >&2
686
4d314e0f 687 mkdir -p "$dir_data"
438d0d5f
SK
688 rm -f "$pipe"
689 mkfifo "$pipe"
690
bbb42518
SK
691 cmd_produce_screen_brightness='produce_screen_brightness'
692 cmd_produce_screen_brightness+=" $screen_brightness_device_path"
2b221665 693
0c0ebbe1
SK
694 cmd_produce_weather="produce_weather $weather_station_id"
695
c0e8473e
SK
696 cmd_produce_disk_space="produce_disk_space $disk_space_device"
697
cf698ca8
SK
698 cmd_produce_disk_io="produce_disk_io $disk_io_device"
699
5dd1535a
SK
700 cmd_produce_temperature="produce_temperature $thermal_zone"
701
b9bdec44
SK
702 cmd_produce_fan="produce_fan $fan_path"
703
4d314e0f 704 # TODO: Redirect each worker's stderr to a dedicated log file
0c0ebbe1 705 spawn produce_datetime "$pipe" 'in:DATE_TIME' 1
bbb42518 706 spawn "$cmd_produce_screen_brightness" "$pipe" 'in:SCREEN_BRIGHTNESS' 1
0c0ebbe1
SK
707 spawn "$cmd_produce_weather" "$pipe" 'in:WEATHER' $(( 30 * 60 ))
708 spawn produce_mpd_state "$pipe" 'in:MPD_STATE' 1
709 spawn produce_mpd_song "$pipe" 'in:MPD_SONG' 1
710 spawn produce_volume "$pipe" 'in:VOLUME' 1
ad0b3a5b 711 spawn produce_bluetooth_power "$pipe" 'in:BLUETOOTH_POWER' 5
0d116e88 712 spawn produce_net_wifi_status "$pipe" 'in:NET_WIFI_STATUS' 5
2b221665 713 spawn produce_net_addr_io "$pipe" 'in:NET_ADDR_IO' 1
c0e8473e 714 spawn "$cmd_produce_disk_space" "$pipe" 'in:DISK_SPACE' 1
cf698ca8 715 spawn "$cmd_produce_disk_io" "$pipe" 'in:DISK_IO' 1
d3d7d712 716 spawn produce_loadavg "$pipe" 'in:LOAD_AVG' 1
5dd1535a 717 spawn "$cmd_produce_temperature" "$pipe" 'in:TEMPERATURE' 1
b9bdec44 718 spawn "$cmd_produce_fan" "$pipe" 'in:FAN' 1
0c0ebbe1 719 spawn produce_bar_req "$pipe" 'out:BAR' 1
2b221665
SK
720
721 consume \
722 "$pipe" \
723 "$debug" \
724 "$prefixes_of_net_interfaces_to_show"
438d0d5f
SK
725}
726
727main $@
This page took 0.165778 seconds and 4 git commands to generate.