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