#! /bin/bash
-MSG_TAG_SEP=': '
+set -e
+
+produce_net_addr_io() {
+ ip -s addr \
+ | awk \
+ -v prefixes_of_interfaces_to_show="$PREFIXES_OF_INTERFACES_TO_SHOW" \
+ '
+ BEGIN {
+ bytes_per_unit = 1024 * 1024
+ }
+
+ /^[0-9]+:/ {
+ sub(":$", "", $1)
+ sub(":$", "", $2)
+ sequence = $1
+ interface = $2
+ interfaces[sequence] = interface
+ }
+
+ /^ +inet [0-9]/ {
+ sub("/[0-9]+", "", $2)
+ addr = $2
+ addrs[interface] = addr
+ }
+
+ /^ +RX: / {transfer_direction = "r"}
+ /^ +TX: / {transfer_direction = "w"}
+
+ /^ +[0-9]+ +[0-9]+ +[0-9]+ +[0-9]+ +[0-9]+ +[0-9]+ *$/ {
+ io[interface, transfer_direction] = $1;
+ }
+
+ END {
+ for (seq=1; seq<=sequence; seq++) {
+ interface = interfaces[seq]
+ label = substr(interface, 1, 1)
+ if (addrs[interface]) {
+ curr_read = io[interface, "r"]
+ curr_write = io[interface, "w"]
+ print(interface, addrs[interface], curr_write, curr_read)
+ } else {
+ print(interface)
+ }
+ }
+ }'
+}
+
+produce_net_wifi_status() {
+ nmcli \
+ -f ACTIVE,SSID,SIGNAL \
+ -t \
+ d wifi \
+ | awk \
+ -F ':' \
+ '
+ BEGIN {wifi_status = "--"}
+ $1 == "yes" {wifi_status = $2 ":" $3 "%"}
+ END {print wifi_status}
+ '
+}
+
+produce_bluetooth_power() {
+ echo -e 'show \n quit' \
+ | bluetoothctl \
+ | awk '
+ /^Controller / {
+ controller = $2;
+ controllers[++ctrl_count] = controller;
+ }
+ /^\t[A-Z][A-Za-z]+:/ {
+ key = $1;
+ sub(":$", "", key);
+ val = $2;
+ for (i=3; i<=NF; i++) {
+ val = val " " $i};
+ data[controller, key] = val;
+ }
+ END {
+ # Using the 1st seen controller. Should we select specific instead?
+ power_status = data[controllers[1], "Powered"];
+ if (ctrl_count > 0) {
+ if (power_status == "no") {
+ power_status = "off"
+ } else if (power_status == "yes") {
+ power_status = "on"
+ } else {
+ printf("Unexpected bluetooth power status: %s\n", power_status)\
+ > "/dev/stderr";
+ power_status = "ERROR"
+ }
+ } else {
+ power_status = "off" # TODO: Perhaps use differentiated marker?
+ }
+ printf("%s\n", power_status);
+ }'
+}
+
+produce_screen_brightness() {
+ screen_brightness_device_path="$1"
+ echo "\
+ $(cat $screen_brightness_device_path/max_brightness) \
+ $(cat $screen_brightness_device_path/brightness)\
+ "
+}
+
+produce_volume() {
+ pactl list sinks \
+ | awk '
+ /^\tMute:/ {
+ printf("%s,", $0);
+ }
+ /^\tVolume:/ {
+ for (i=2; i<=NF; i++) printf(" %s", $i);
+ }' \
+ | awk -v RS=',' '
+ /^[ \t]*Mute:/ {mute = $2}
+ /^[ \t]*front-left:/ {left = $4}
+ /^[ \t]*front-right:/ {right = $4}
+ END {
+ if (mute == "yes") {
+ print("x")
+ } else {
+ print("%s %s\n", left, right)
+ }
+ }
+ '
+}
+
+produce_mpd_state() {
+ echo 'status' \
+ | nc 127.0.0.1 6600 \
+ | awk '
+ {
+ status[$1] = $2
+ }
+
+ /^time: +[0-9]+:[0-9]+$/ {
+ split($2, time, ":")
+ seconds_current = time[1]
+ seconds_total = time[2]
+
+ hours = int(seconds_current / 60 / 60);
+ secs_beyond_hours = seconds_current - (hours * 60 * 60);
+ mins = int(secs_beyond_hours / 60);
+ secs = secs_beyond_hours - (mins * 60);
+ if (hours > 0) {
+ current_time = sprintf("%d:%.2d:%.2d", hours, mins, secs)
+ } else {
+ current_time = sprintf("%.2d:%.2d", mins, secs)
+ }
+
+ if (seconds_total > 0) {
+ time_percentage = (seconds_current / seconds_total) * 100
+ current_percentage = sprintf("%d%%", time_percentage)
+ } else {
+ current_percentage = "~"
+ }
+ }
+
+ END {
+ state = status["state:"]
+
+ if (state == "play") {
+ symbol = "▶"
+ } else if (state == "pause") {
+ symbol = "❚❚"
+ } else if (state == "stop") {
+ symbol = "⬛"
+ } else {
+ symbol = "--"
+ }
+
+ printf(\
+ "%s %s %s\n",
+ status["state:"], current_time, current_percentage\
+ )
+ }
+ '
+}
+
+produce_mpd_song() {
+ echo 'currentsong' \
+ | nc 127.0.0.1 6600 \
+ | awk '
+ /^OK/ {
+ next
+ }
+
+ {
+ key = $1
+ sub("^" key " +", "")
+ val = $0
+ data[key] = val
+ }
+
+ END {
+ name = data["Name:"]
+ title = data["Title:"]
+ file = data["file:"]
+
+ if (name) {
+ out = name
+ } else if (title) {
+ out = title
+ } else if (file) {
+ last = split(file, parts, "/")
+ out = parts[last]
+ } else {
+ out = ""
+ }
+ print out
+ }
+ '
+}
produce_weather() {
- metar -d "$WEATHER_STATION_ID" 2>&1 \
+ weather_station_id="$1"
+ metar -d "$weather_station_id" 2>&1 \
| awk '
/METAR pattern not found in NOAA data/ {
failures++
consume() {
pipe="$1"
+ debug="$2"
+ prefixes_of_net_interfaces_to_show="$3"
tail -f "$pipe" \
| stdbuf -o L awk \
+ -v opt_debug="$debug" \
+ -v opt_mpd_song_max_chars=10 \
+ -v opt_prefixes_of_net_interfaces_to_show="$prefixes_of_net_interfaces_to_show" \
'
- /^in:WEATHER:/\
+ /^in:NET_ADDR_IO/\
+ {
+ split_msg_parts()
+ set_net_addr_io()
+ }
+
+ /^in:NET_WIFI_STATUS/\
+ {
+ split_msg_parts()
+ db["net_wifi_status"] = msg_body
+ }
+
+ /^in:BLUETOOTH_POWER/\
+ {
+ split_msg_parts()
+ db["bluetooth_power"] = msg_body
+ }
+
+ /^in:SCREEN_BRIGHTNESS/\
+ {
+ split_msg_parts()
+ set_screen_brightness()
+ }
+
+ /^in:VOLUME/\
+ {
+ split_msg_parts()
+ db["volume"] = msg_body
+ }
+
+ /^in:MPD_STATE/\
+ {
+ split_msg_parts()
+ db["mpd_state"] = $1
+ db["mpd_curr_song_time"] = $2
+ db["mpd_curr_song_percent"] = $3
+ }
+
+ /^in:MPD_SONG/\
+ {
+ split_msg_parts()
+ db["mpd_curr_song_name"] = msg_body
+ }
+
+ /^in:WEATHER/\
{
- db["weather_temperature"] = read_msg()
+ split_msg_parts()
+ db["weather_temperature"] = msg_body
}
- /^in:DATE_TIME:/\
+ /^in:DATE_TIME/\
{
- db["datetime"] = read_msg()
+ split_msg_parts()
+ db["datetime"] = msg_body
}
- /^out:BAR:/\
+ /^out:BAR/\
{
+ split_msg_parts()
print make_bar()
}
- function read_msg() {
- sub("^" $1 " +", "")
- return $0
+
+ function set_net_addr_io( \
+ interface, address, io_curr_w, io_curr_r, io_prev_w, io_prev_r\
+ ) {
+ interface = $1
+ address = $2
+ io_curr_w = $3
+ io_curr_r = $4
+ if (interface) {
+ if (address && io_curr_w && io_curr_r) {
+ # recalculate
+ io_prev_w = net_io_curr_w[interface]
+ io_prev_r = net_io_curr_r[interface]
+
+ net_addr[interface] = address
+ net_io_curr_w[interface] = io_curr_w
+ net_io_curr_r[interface] = io_curr_r
+ net_io_diff_w[interface] = io_curr_w - io_prev_w
+ net_io_diff_r[interface] = io_curr_r - io_prev_r
+ } else {
+ # clear
+ net_addr[interface] = ""
+ net_io_curr_w[interface] = 0
+ net_io_curr_r[interface] = 0
+ net_io_diff_w[interface] = 0
+ net_io_diff_r[interface] = 0
+ }
+ }
+ }
+
+ function set_screen_brightness( max, cur) {
+ max = $1
+ cur = $2
+ db["screen_brightness"] = (cur / max) * 100
+ }
+
+ function split_msg_parts() {
+ msg_head = $1
+ sub("^" msg_head " +", "")
+ msg_body = $0
+ debug(msg_head, msg_body)
}
- function make_bar( position, bar, i) {
+ function make_bar( position, bar, sep, i, j) {
+ position[++i] = make_status_net()
+ position[++i] = sprintf("B=%s", db["bluetooth_power"])
+ position[++i] = sprintf("*%d%%", db["screen_brightness"])
+ position[++i] = sprintf("(%s)", db["volume"])
+ position[++i] = make_status_mpd()
position[++i] = db["weather_temperature"]
position[++i] = db["datetime"]
bar = ""
}
return bar
}
+
+ function make_status_net( \
+ out,
+ number_of_interfaces_to_show,
+ n,
+ array_of_prefixes_of_interfaces_to_show,
+ prefix,
+ interface,
+ label,
+ count_printed,
+ sep,
+ io_stat,
+ dw, dr,
+ bytes_per_unit\
+ ) {
+ out = ""
+ number_of_interfaces_to_show = \
+ split(\
+ opt_prefixes_of_net_interfaces_to_show,\
+ array_of_prefixes_of_interfaces_to_show,\
+ ","\
+ )
+ for (n = 1; n <= number_of_interfaces_to_show; n++) {
+ prefix = array_of_prefixes_of_interfaces_to_show[n]
+ for (interface in net_addr) {
+ if (interface ~ ("^" prefix)) {
+ label = substr(interface, 1, 1)
+ if (net_addr[interface]) {
+ bytes_per_mb = 1024 * 1024 # TODO: option
+ dw = net_io_diff_w[interface] / bytes_per_mb
+ dr = net_io_diff_r[interface] / bytes_per_mb
+ io_stat = sprintf("%0.3f▲ %0.3f▼", dw, dr)
+ } else {
+ io_stat = "--"
+ }
+ if (interface ~ "^w") {
+ label = label ":" db["net_wifi_status"]
+ }
+ if (++count_printed > 1) {
+ sep = " "
+ } else {
+ sep = ""
+ }
+ out = out sep label ":" io_stat
+ }
+ }
+ }
+ return sprintf("N[%s]", out)
+ }
+
+ function make_status_mpd( state, status) {
+ state = db["mpd_state"]
+
+ if (state == "play") {
+ status = make_status_mpd_state_known("▶")
+ } else if (state == "pause") {
+ status = make_status_mpd_state_known("❚❚")
+ } else if (state == "stop") {
+ status = make_status_mpd_state_known("⬛")
+ } else {
+ status = make_status_mpd_state_unknown("--")
+ }
+
+ return sprintf("[%s]", status)
+ }
+
+ function make_status_mpd_state_known(symbol) {
+ return sprintf(\
+ "%s %s %s %s",
+ symbol,
+ db["mpd_curr_song_time"],
+ db["mpd_curr_song_percent"],
+ substr(db["mpd_curr_song_name"], 1, opt_mpd_song_max_chars)\
+ )
+ }
+
+ function make_status_mpd_state_unknown(symbol) {
+ return sprintf("%s", symbol)
+ }
+
+ function debug(location, msg) {
+ if (opt_debug) {
+ print_error(location, msg)
+ }
+ }
+
+ function print_error(location, msg) {
+ print(location " ==> " msg) > "/dev/stderr"
+ }
'
}
spawn() {
cmd="$1"
pipe="$2"
- tag="$3"
+ msg_head="$3"
interval="$4"
while true; do
- echo "${tag}${MSG_TAG_SEP}$($cmd)" > "$pipe"
- sleep "$interval"
+ $cmd | while read line; do
+ echo "${msg_head} $line" > "$pipe"
+ done
+ sleep "$interval"
done &
}
main() {
- dir_bin="$1"
- dir_data="$2"
- pipe="$dir_data/pipe"
+ # Defaults
+ debug=0
+ dir_data="$HOME/.khatus"
+ weather_station_id='KJFK'
+ screen_brightness_device_name='acpi_video0'
+ prefixes_of_net_interfaces_to_show='w' # comma-separated
+
+ # User-overrides
+ long_options=''
+ long_options+='debug'
+ long_options+=',data-dir:'
+ long_options+=',weather-station:'
+ long_options+=',screen-device:'
+ long_options+=',prefixes_of_net_interfaces_to_show:'
+ OPTS=$(
+ getopt \
+ -o 'd' \
+ -l $long_options \
+ -- "$@"
+ )
+ eval set -- "$OPTS"
+ while true
+ do
+ case "$1" in
+ -d|--debug)
+ debug=1
+ shift
+ ;;
+ --data-dir)
+ dir_data="$2"
+ shift 2
+ ;;
+ --weather-station)
+ weather_station_id="$2"
+ shift 2
+ ;;
+ --screen-device)
+ screen_brightness_device_name="$2"
+ shift 2
+ ;;
+ --prefixes_of_net_interfaces_to_show)
+ prefixes_of_net_interfaces_to_show="$2"
+ shift 2
+ ;;
+ --)
+ shift
+ break
+ ;;
+ esac
+ done
+
+ pipe="$dir_data/khatus_data_pipe"
+ screen_brightness_device_path='/sys/class/backlight'
+ screen_brightness_device_path+="/$screen_brightness_device_name"
- WEATHER_STATION_ID='KJFK'
+ ( echo "Khatus starting with the following parameters:"
+ ( echo " debug|= $debug"
+ echo " dir_data|= $dir_data"
+ echo " pipe|= $pipe"
+ echo " screen_brightness_device_name|= $screen_brightness_device_name"
+ echo " screen_brightness_device_path|= $screen_brightness_device_path"
+ echo " weather_station_id|= $weather_station_id"
+ echo " prefixes_of_net_interfaces_to_show|=$prefixes_of_net_interfaces_to_show"
+ ) | column -ts\|
+ echo ''
+ ) >&2
+ mkdir -p "$dir_data"
rm -f "$pipe"
mkfifo "$pipe"
- spawn produce_datetime "$pipe" 'in:DATE_TIME' 1
- spawn produce_weather "$pipe" 'in:WEATHER' $(( 30 * 60 ))
- spawn produce_bar_req "$pipe" 'out:BAR' 1
- consume "$pipe"
+ cmd_produce_screen_brightness='produce_screen_brightness'
+ cmd_produce_screen_brightness+=" $screen_brightness_device_path"
+
+ cmd_produce_weather="produce_weather $weather_station_id"
+
+ # TODO: Redirect each worker's stderr to a dedicated log file
+ spawn produce_datetime "$pipe" 'in:DATE_TIME' 1
+ spawn "$cmd_produce_screen_brightness" "$pipe" 'in:SCREEN_BRIGHTNESS' 1
+ spawn "$cmd_produce_weather" "$pipe" 'in:WEATHER' $(( 30 * 60 ))
+ spawn produce_mpd_state "$pipe" 'in:MPD_STATE' 1
+ spawn produce_mpd_song "$pipe" 'in:MPD_SONG' 1
+ spawn produce_volume "$pipe" 'in:VOLUME' 1
+ spawn produce_bluetooth_power "$pipe" 'in:BLUETOOTH_POWER' 5
+ spawn produce_net_wifi_status "$pipe" 'in:NET_WIFI_STATUS' 5
+ spawn produce_net_addr_io "$pipe" 'in:NET_ADDR_IO' 1
+ spawn produce_bar_req "$pipe" 'out:BAR' 1
+
+ consume \
+ "$pipe" \
+ "$debug" \
+ "$prefixes_of_net_interfaces_to_show"
}
main $@