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