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