Commit | Line | Data |
---|---|---|
438d0d5f SK |
1 | #! /bin/bash |
2 | ||
4d314e0f SK |
3 | set -e |
4 | ||
2b221665 SK |
5 | produce_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 |
50 | produce_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 |
64 | produce_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 |
100 | produce_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 |
108 | produce_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 |
131 | produce_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 | ||
183 | produce_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 | 218 | produce_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 | 240 | produce_datetime() { |
438d0d5f SK |
241 | date +'%a %b %d %H:%M:%S' |
242 | } | |
243 | ||
6339a4f8 | 244 | consume() { |
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 | 467 | produce_bar_req() { |
438d0d5f SK |
468 | echo '' |
469 | } | |
470 | ||
471 | spawn() { | |
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 | ||
484 | main() { | |
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 | ||
579 | main $@ |