Merge branch 'uniform-interface'
authorSiraaj Khandkar <siraaj@khandkar.net>
Sun, 19 Aug 2018 16:26:22 +0000 (12:26 -0400)
committerSiraaj Khandkar <siraaj@khandkar.net>
Sun, 19 Aug 2018 16:26:22 +0000 (12:26 -0400)
34 files changed:
README.md
bin/khatus
bin/khatus_actuate_alert_to_notify_send
bin/khatus_actuate_status_bar_to_xsetroot_name
bin/khatus_bar [new file with mode: 0755]
bin/khatus_controller [deleted file]
bin/khatus_monitor_energy [new file with mode: 0755]
bin/khatus_monitor_errors [new file with mode: 0755]
bin/khatus_parse_bluetoothctl_show
bin/khatus_parse_df_pcent [new file with mode: 0755]
bin/khatus_parse_fan_file [new file with mode: 0755]
bin/khatus_parse_free [new file with mode: 0755]
bin/khatus_parse_ip_addr
bin/khatus_parse_iwconfig
bin/khatus_parse_loadavg_file [new file with mode: 0755]
bin/khatus_parse_metar_d_output
bin/khatus_parse_mpd_status [deleted file]
bin/khatus_parse_mpd_status_currentsong [new file with mode: 0755]
bin/khatus_parse_pactl_list_sinks
bin/khatus_parse_sys_block_stat [new file with mode: 0755]
bin/khatus_parse_upower
bin/khatus_sensor_datetime
bin/khatus_sensor_disk_io
bin/khatus_sensor_disk_space
bin/khatus_sensor_energy
bin/khatus_sensor_fan
bin/khatus_sensor_loadavg
bin/khatus_sensor_memory
bin/khatus_sensor_mpd [new file with mode: 0755]
bin/khatus_sensor_mpd_song [deleted file]
bin/khatus_sensor_mpd_state [deleted file]
bin/khatus_sensor_screen_brightness
bin/khatus_sensor_temperature
sanity_check [new file with mode: 0755]

index 605daaf..5381f90 100644 (file)
--- a/README.md
+++ b/README.md
@@ -2,15 +2,96 @@ khatus
 ======
 ![mascot](mascot.jpg)
 
-Experimental, system monitor and status (bar) reporter I use with
+Experimental system-monitor and status (bar) reporter I use with
 [dwm](https://dwm.suckless.org/) on GNU/Linux.
 
 ![screenshot](screenshot.jpg)
 
+Usage
+-----
+
+In my `~/.xinitrc` I have something like the following:
+
+```sh
+( $BIN/khatus \
+    --wifi_interface 'wlp3s0' \
+| stdbuf -o L tee \
+    >(stdbuf -o L "$BIN"/khatus_bar \
+        -v Opt_Mpd_Song_Max_Chars=10 \
+        -v Opt_Net_Interfaces_To_Show=wlp3s0 \
+        -v Opt_Pulseaudio_Sink=0 \
+    | "$BIN"/khatus_actuate_status_bar_to_xsetroot_name \
+    ) \
+    >(stdbuf -o L "$BIN"/khatus_monitor_energy \
+    | "$BIN"/khatus_actuate_alert_to_notify_send \
+    ) \
+    >(stdbuf -o L "$BIN"/khatus_monitor_errors \
+    | "$BIN"/khatus_actuate_alert_to_notify_send \
+    ) \
+) \
+2> >($BIN/twrap.sh >> $HOME/var/log/khatus/main.log) \
+1> /dev/null \
+&
+```
+(where `twrap` is a simple script which prefixes a timestamp to each line)
+
+The idea is to support appending any number of ad-hoc, experimental monitors by
+giving maximum flexibility for what to do with the sensor outputs, while
+maintaining some uniformity of msg formats (again, to ease ad-hoc combinations
+(e.g. Does the CPU get hotter when MPD is playing Wu-Tang?)).  `khatus_bar`,
+`khatus_monitor_energy` and `khatus_monitor_errors` are just some initial
+examples.
 
 Design
 ------
 
+### 2.0
+
+In an effort to simplify the components and their interfaces, I removed the
+concept of a global controller from the previous design (which, at least for
+now, is superfluous), so now it is essentially a pub-sub - parallel publishers
+(sensors) write to a pipe, which is then copied to any number of interested
+subscribers that can filter-out what they need and then do whatever they want
+with the data. Status bar is one such subscriber:
+
+`P1 > pipe&; P2 > pipe&; ... PN > pipe&; tail -f pipe | tee >(S1) >(S2) ... >(SN) > /dev/null
+
+The cool thing is that, because the pipe is always read (`tail -f ... > /dev/null`),
+the publishers are never blocked, so we get a live stream of event to which we
+can attach any number of interested subscribers (` ... tee ... `) and, because
+the pipe is named, if a subscriber needs to - it too can publish something to
+the pipe without being blocked.
+
+```
+parallel    +----------+  +----------+          +----------+
+stateless   | sensor_1 |  | sensor_2 |    ...   | sensor_n |
+collectors  +----------+  +----------+          +----------+
+                 |             |           |         |
+               data          data        data      data
+                 |             |           |         |
+                 V             V           V         V
+multiplexing     +-------------+-----------+---------+
+to a pipe                      |
+                               |
+                               V
+copying to       +-------------+-+---------+---------+
+subscribers      |               |         |         |
+                 V               V         V         V
+              +------------+         ...      +----------------+
+any number of | status bar |                  | energy monitor |
+parallel      +------------+                  +----------------+
+subscribers      |                                    |
+                 V                                    V
+              +----------------+              +-------------+
+              | xsetroot -name |              | notify-send |
+              +----------------+              +-------------+
+```
+
+### 1.0
+
+This was an improvement of having everything in one script, but the controller
+was still way too complicated for no good reason.
+
 ```
 parallel    +----------+  +----------+          +----------+
 stateless   | sensor_1 |  | sensor_2 |    ...   | sensor_n |
@@ -53,6 +134,20 @@ executors   +------------+ +------------+     +------------+
             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 ```
 
+### 0.x
+
+A single script, re-executed in a loop at some intervals, serially grabbing all
+the needed data and outputting a status bar string, then passed to `xsetroot -name`,
+while saving state in files (e.g. previous totals, to be converted to deltas).
+
+This actually worked surprisingly-OK, but had limitations:
+
+- I use an SSD and want to minimize disk writes
+- not flexible-enough to support my main goal - easy experimentation with
+  various ad-hoc monitors:
+    - I want to set different update intervals for different data sources
+    - I don't want long-running data collectors to block the main loop
+
 ### Actuator
 Actuator is anything that takes action upon controller messages. A few generic
 ones are included:
@@ -60,36 +155,8 @@ ones are included:
 - `khatus_actuate_alert_to_notify_send`
 - `khatus_actuate_status_bar_to_xsetroot_name`
 
-and, by default, are left disconnected from the controller's output, so if
-desired - it needs to be manually attached when starting `khatus`. For example,
-in my `.xinitrc` I have:
-
-```sh
-$BIN/khatus \
-2> >($BIN/twrap >> $HOME/var/log/khatus.log) \
-| tee \
-    >($BIN/khatus_actuate_status_bar_to_xsetroot_name) \
-    >(grep -v MpdNowPlaying | $BIN/khatus_actuate_alert_to_notify_send) \
-2> >($BIN/twrap >> $HOME/var/log/khatus-actuators.log) \
-&
-```
-(where `twrap` is a simple script which prefixes a timestamp to each line)
-
-The idea is to give maximum flexibility for what to do with the controller
-output, say, for instance:
-
-```sh
-$BIN/khatus \
-| tee \
-... \
->(grep '^REPORT' | actuate_report_to_email) \
->(grep '^ALERT' | grep mpd | actuate_alert_to_email) \
->(grep '^ALERT' | grep IntrusionAttempt | actuate_intruder_to_iptables_drop) \
->(grep '^ALERT' | grep NewDevice | actuate_alert_to_notify_send)
->(grep '^ALERT' | grep DiskError | actuate_call_mom)
-...
-```
-... and so on, for any other such fun you might imagine.
+and, by default, are left disconnected from the data feed, so if desired - need
+to be manually attached when starting `khatus`. See usage section.
 
 ### Errors
 Any errors encountered by any sensor are propagated as alerts by the
@@ -101,6 +168,7 @@ controller, which are in turn actualized as desktop notifications by the
 TODO
 ----
 
+- status bar templating language
 - retry/cache for sensors fetching flaky remote resources (such as weather)
 - throttling of broken sensors (constantly returns errors)
 - alert specification language
@@ -109,7 +177,6 @@ TODO
     - priority
     - snooze time (if already alerted, when to re-alert?)
     - text: subject/body
-- more-structured controller API: a sensor submits a list of k/v pairs
 
 Redesign notes
 --------------
index bbc7ab8..5fa635d 100755 (executable)
@@ -1,24 +1,18 @@
 #! /bin/bash
 
+MSG_FS='|'
+
 set -e
 
-consume() {
-    pipe="$1"
-    debug="$2"
-    bin="$3"
-    prefixes_of_net_interfaces_to_show="$4"
-    tail -f "$pipe" \
-    | stdbuf -o L "$bin"/khatus_controller \
-        -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"
+executable_name_of_cmd() {
+    basename "$(echo $1 | awk '{print $1; exit}')"
 }
 
 run_producer() {
     pipe="$1"
     bin="$2"
     cmd="$3"
-    msg_head="$4"
+    executable_name="$4"
     perf_log="$5"
 
     if [ ! "$perf_log" = '' ]
@@ -42,73 +36,82 @@ run_producer() {
     2> >(
         while read line
         do
-            echo "ERROR ${msg_head} $line" > "$pipe"
+            echo "ERROR${MSG_FS}${executable_name}${MSG_FS}$line" > "$pipe"
         done \
     ) \
     | while read line
         do
-            echo "OK ${msg_head} $line" > "$pipe"
+            echo "OK${MSG_FS}${executable_name}${MSG_FS}$line" > "$pipe"
         done
     cmd_exit_code=${PIPESTATUS[0]}
     if [ "$cmd_exit_code" -ne 0 ]
     then
-        echo "ERROR ${msg_head} NON_ZERO_EXIT_CODE $cmd_exit_code" > "$pipe"
+        echo
+            "ERROR${MSG_FS}${executable_name}${MSG_FS}NON_ZERO_EXIT_CODE${MSG_FS}$cmd_exit_code" \
+            > "$pipe"
     fi
 }
 
 fork_watcher() {
-    run_producer "$@" &
+    pipe="$1"
+    bin="$2"
+    cmd="$3"
+    executable_name=$(executable_name_of_cmd "$cmd")
+    run_producer "$pipe" "$bin" "$cmd" "$executable_name" &
 }
 
 fork_poller() {
     interval="$1"
     perf_log_dir="$2"
     shift 2
+    pipe="$1"
+    bin="$2"
+    cmd="$3"
+
+    executable_name=$(basename "$(echo $cmd | awk '{print $1; exit}')")
 
     if [ ! "$perf_log_dir" = '' ]
     then
         cmd="$3"
-        perf_log_file=$(basename "$(echo $cmd | awk '{print $1; exit}')").log
+        perf_log_file=${executable_name}.log
         mkdir -p "$perf_log_dir"
         perf_log_path="$perf_log_dir/$perf_log_file"
     fi
 
     while :
     do
-        run_producer "$@" "$perf_log_path"
+        run_producer "$pipe" "$bin" "$cmd" "$executable_name" "$perf_log_path"
         sleep "$interval"
     done &
 }
 
 main() {
     declare -A opts=(
-        ["--debug"]=0
         ["--dir_bin"]="$HOME/bin"
         ["--dir_perf_logs"]=''
         ["--file_pipe"]=$(mktemp)
         ["--weather_station_id"]='KJFK'
         ["--screen_brightness_device_name"]='acpi_video0'
-        ["--prefixes_of_net_interfaces_to_show"]='w'  # comma-separated
         ["--wifi_interface"]=''
         ["--disk_space_device"]='/'
         ["--disk_io_device"]='sda'
         ["--thermal_zone"]=0
         ["--fan_path"]='/proc/acpi/ibm/fan'
-        ["--interval_inp_datetime"]=1
-        ["--interval_inp_brightness"]=1
-        ["--interval_inp_weather"]=$(( 30 * 60))  # 30 minutes
-        ["--interval_inp_mpd_state"]=1
-        ["--interval_inp_mpd_song"]=1
-        ["--interval_inp_volume"]=1
-        ["--interval_inp_bluetooth"]=5
-        ["--interval_inp_net_wifi"]=5
-        ["--interval_inp_net_io"]=1
-        ["--interval_inp_disk_space"]=5
-        ["--interval_inp_disk_io"]=1
-        ["--interval_inp_loadavg"]=1
-        ["--interval_inp_temp"]=1
-        ["--interval_inp_fan"]=1
-        ["--interval_inp_mem"]=1
+        ["--pulseaudio_sink"]='0'
+        ["--interval_datetime"]=1
+        ["--interval_brightness"]=1
+        ["--interval_weather"]=$(( 30 * 60))  # 30 minutes
+        ["--interval_mpd"]=1
+        ["--interval_volume"]=1
+        ["--interval_bluetooth"]=1
+        ["--interval_net_wifi"]=1
+        ["--interval_net_io"]=1
+        ["--interval_disk_space"]=1
+        ["--interval_disk_io"]=1
+        ["--interval_loadavg"]=1
+        ["--interval_temp"]=1
+        ["--interval_fan"]=1
+        ["--interval_mem"]=1
     )
     while :
     do
@@ -121,11 +124,7 @@ main() {
             * )
                 if [ -v opts["$key"] ]
                 then
-                    if [ "$key" == "--debug" ]
-                    then
-                        opts["$key"]=1
-                        shift
-                    elif [ "$val" != "" ]
+                    if [ "$val" != "" ]
                     then
                         opts["$key"]="$val"
                         shift
@@ -175,38 +174,35 @@ main() {
     cmd_sens_screen_brightness+=" $screen_brightness_device_path"
 
     cmd_sens_weather="khatus_sensor_weather $bin ${opts['--weather_station_id']}"
-    cmd_sens_disk_space="khatus_sensor_disk_space ${opts['--disk_space_device']}"
-    cmd_sens_disk_io="khatus_sensor_disk_io ${opts['--disk_io_device']}"
+    cmd_sens_disk_space="khatus_sensor_disk_space $bin ${opts['--disk_space_device']}"
+    cmd_sens_disk_io="khatus_sensor_disk_io $bin ${opts['--disk_io_device']}"
     cmd_sens_temperature="khatus_sensor_temperature ${opts['--thermal_zone']}"
-    cmd_sens_fan="khatus_sensor_fan ${opts['--fan_path']}"
+    cmd_sens_fan="khatus_sensor_fan $bin ${opts['--fan_path']}"
     cmd_sens_bluetooth="khatus_sensor_bluetooth_power $bin"
-    cmd_sens_mpd_state="khatus_sensor_mpd_state $bin"
+    cmd_sens_mpd="khatus_sensor_mpd $bin"
     cmd_sens_net_addr_io="khatus_sensor_net_addr_io $bin"
     cmd_sens_volume="khatus_sensor_volume $bin"
     cmd_sens_wifi="khatus_sensor_net_wifi_status $bin ${opts['--wifi_interface']}"
-
-    fork_watcher                                               "$pipe" "$bin" "khatus_sensor_energy $bin"     'in:ENERGY'
-    fork_poller "${opts['--interval_inp_datetime']}"   "$perf" "$pipe" "$bin" khatus_sensor_datetime          'in:DATE_TIME'
-    fork_poller "${opts['--interval_inp_brightness']}" "$perf" "$pipe" "$bin" "$cmd_sens_screen_brightness"   'in:SCREEN_BRIGHTNESS'
-    fork_poller "${opts['--interval_inp_weather']}"    "$perf" "$pipe" "$bin" "$cmd_sens_weather"             'in:WEATHER'
-    fork_poller "${opts['--interval_inp_mpd_state']}"  "$perf" "$pipe" "$bin" "$cmd_sens_mpd_state"           'in:MPD_STATE'
-    fork_poller "${opts['--interval_inp_mpd_song']}"   "$perf" "$pipe" "$bin" khatus_sensor_mpd_song          'in:MPD_SONG'
-    fork_poller "${opts['--interval_inp_volume']}"     "$perf" "$pipe" "$bin" "$cmd_sens_volume"              'in:VOLUME'
-    fork_poller "${opts['--interval_inp_bluetooth']}"  "$perf" "$pipe" "$bin" "$cmd_sens_bluetooth"           'in:BLUETOOTH_POWER'
-    fork_poller "${opts['--interval_inp_net_wifi']}"   "$perf" "$pipe" "$bin" "$cmd_sens_wifi"                'in:NET_WIFI_STATUS'
-    fork_poller "${opts['--interval_inp_net_io']}"     "$perf" "$pipe" "$bin" "$cmd_sens_net_addr_io"         'in:NET_ADDR_IO'
-    fork_poller "${opts['--interval_inp_disk_space']}" "$perf" "$pipe" "$bin" "$cmd_sens_disk_space"          'in:DISK_SPACE'
-    fork_poller "${opts['--interval_inp_disk_io']}"    "$perf" "$pipe" "$bin" "$cmd_sens_disk_io"             'in:DISK_IO'
-    fork_poller "${opts['--interval_inp_loadavg']}"    "$perf" "$pipe" "$bin" khatus_sensor_loadavg           'in:LOAD_AVG'
-    fork_poller "${opts['--interval_inp_temp']}"       "$perf" "$pipe" "$bin" "$cmd_sens_temperature"         'in:TEMPERATURE'
-    fork_poller "${opts['--interval_inp_fan']}"        "$perf" "$pipe" "$bin" "$cmd_sens_fan"                 'in:FAN'
-    fork_poller "${opts['--interval_inp_mem']}"        "$perf" "$pipe" "$bin" khatus_sensor_memory            'in:MEMORY'
-
-    consume \
-      "$pipe" \
-      "${opts['--debug']}" \
-      "$bin" \
-      "${opts['--prefixes_of_net_interfaces_to_show']}"
+    cmd_sens_loadavg="khatus_sensor_loadavg $bin"
+    cmd_sens_memory="khatus_sensor_memory $bin"
+
+    fork_watcher                                           "$pipe" "$bin" "khatus_sensor_energy $bin"
+    fork_poller "${opts['--interval_datetime']}"   "$perf" "$pipe" "$bin" khatus_sensor_datetime
+    fork_poller "${opts['--interval_brightness']}" "$perf" "$pipe" "$bin" "$cmd_sens_screen_brightness"
+    fork_poller "${opts['--interval_weather']}"    "$perf" "$pipe" "$bin" "$cmd_sens_weather"
+    fork_poller "${opts['--interval_mpd']}"        "$perf" "$pipe" "$bin" "$cmd_sens_mpd"
+    fork_poller "${opts['--interval_volume']}"     "$perf" "$pipe" "$bin" "$cmd_sens_volume"
+    fork_poller "${opts['--interval_bluetooth']}"  "$perf" "$pipe" "$bin" "$cmd_sens_bluetooth"
+    fork_poller "${opts['--interval_net_wifi']}"   "$perf" "$pipe" "$bin" "$cmd_sens_wifi"
+    fork_poller "${opts['--interval_net_io']}"     "$perf" "$pipe" "$bin" "$cmd_sens_net_addr_io"
+    fork_poller "${opts['--interval_disk_space']}" "$perf" "$pipe" "$bin" "$cmd_sens_disk_space"
+    fork_poller "${opts['--interval_disk_io']}"    "$perf" "$pipe" "$bin" "$cmd_sens_disk_io"
+    fork_poller "${opts['--interval_loadavg']}"    "$perf" "$pipe" "$bin" "$cmd_sens_loadavg"
+    fork_poller "${opts['--interval_temp']}"       "$perf" "$pipe" "$bin" "$cmd_sens_temperature"
+    fork_poller "${opts['--interval_fan']}"        "$perf" "$pipe" "$bin" "$cmd_sens_fan"
+    fork_poller "${opts['--interval_mem']}"        "$perf" "$pipe" "$bin" "$cmd_sens_memory"
+
+    stdbuf -o L tail -f "$pipe"
 }
 
 main $@
index b1c695a..e383917 100755 (executable)
@@ -6,14 +6,23 @@ BEGIN {
     #   khatus_actuate_alert_to_notify_send -v display="$CORRECT_DISPLAY"
     #
     display = ":0"
+     FS = msg_fs ? msg_fs : "|"
+    OFS = msg_fs ? msg_fs : "|"
+    Kfs = key_fs ? key_fs : ":"
 }
 
-/^ALERT / {
+$1 == "OK" && \
+$3 == "alert" {
     src      = $2
-    priority = $3
-    subject  = $4
-    sub("^" $1 " +" $2 " +" $3 " +" $4 " +", "")
-    body     = $0
+    priority = $4
+    subject  = $5
+
+    # Not just using $6 for body - because body might contain a character
+    # identical to FS
+    len_line = length($0)
+    len_head = length($1 FS $2 FS $3 FS $4 FS $5 FS)
+    len_body = len_line - len_head
+    body = substr($0, len_head + 1, len_body)
 
     sep = body ? "\n" : ""
     body = body sep "--" src
index a7463e2..f41cbd1 100755 (executable)
@@ -1,8 +1,21 @@
 #! /usr/bin/awk -f
 
-/^STATUS_BAR / {
-    sub("^" $1 " +", "")
-    # TODO: Move padding back to controller, now that we no-longer use readline
-    system("xsetroot -name \" " $0 "\" ")
+BEGIN {
+     FS = msg_fs ? msg_fs : "|"
+    OFS = msg_fs ? msg_fs : "|"
+    Kfs = key_fs ? key_fs : ":"
+}
+
+$1 == "OK" && \
+$2 == "khatus_bar" && \
+$3 == "status_bar" {
+    # Not just using $4 for val - because val might contain a character
+    # identical to FS
+    len_line = length($0)
+    len_head = length($1 FS $2 FS $3 FS)
+    len_val  = len_line - len_head
+    val = substr($0, len_head + 1, len_val)
+
+    system("xsetroot -name \"" val "\"")
     next
 }
diff --git a/bin/khatus_bar b/bin/khatus_bar
new file mode 100755 (executable)
index 0000000..bd31283
--- /dev/null
@@ -0,0 +1,347 @@
+#! /usr/bin/awk -f
+
+# Naming convention:
+#     Variables:
+#         - global, builtin : ALLCAPS
+#         - global, public  : Camel_Snake_Man_Bear_Pig
+#         - global, private : _snake_case_prefixed_underscore
+#         - local           : snake_case
+#     Functions:
+#         - global, public  : snake_case
+
+BEGIN {
+     FS = msg_fs ? msg_fs : "|"
+    OFS = msg_fs ? msg_fs : "|"
+    Kfs = key_fs ? key_fs : ":"
+
+    _total_to_diff["khatus_sensor_net_addr_io", "bytes_read"     ] = 1
+    _total_to_diff["khatus_sensor_net_addr_io", "bytes_written"  ] = 1
+    _total_to_diff["khatus_sensor_disk_io"    , "sectors_read"   ] = 1
+    _total_to_diff["khatus_sensor_disk_io"    , "sectors_written"] = 1
+
+    # (x * y) / z = x * w
+    #   ==> w = y / z
+    # (x * bytes_per_sector) / bytes_per_mb = x * scaling_factor
+    #   ==> scaling_factor = bytes_per_sector / bytes_per_mb
+    _bytes_per_sector = 512
+    _bytes_per_mb     = 1024 * 1024
+    _scale["khatus_sensor_disk_io", "sectors_written"] = _bytes_per_sector / _bytes_per_mb
+    _scale["khatus_sensor_disk_io", "sectors_read"   ] = _bytes_per_sector / _bytes_per_mb
+    # (x / y) = x * z
+    #   ==> z = 1 / y
+    # x / bytes_per_mb = x * scaling_factor
+    #   ==> scaling_factor = 1 / bytes_per_mb
+    _scale["khatus_sensor_net_addr_io", "bytes_written"] = 1 / _bytes_per_mb
+    _scale["khatus_sensor_net_addr_io", "bytes_read"   ] = 1 / _bytes_per_mb
+}
+
+# -----------------------------------------------------------------------------
+# Input
+# -----------------------------------------------------------------------------
+$1 == "OK" {
+    cache_update()
+}
+
+$1 == "OK" && \
+$2 == "khatus_sensor_datetime" {
+    print_msg_ok("status_bar", make_status_bar())
+}
+
+# -----------------------------------------------------------------------------
+# Cache
+# -----------------------------------------------------------------------------
+
+function cache_update(    src, key, val, len_line, len_head, len_val, time) {
+    src = $2
+    key = $3
+    # Not just using $4 for val - because an unstructured value (like name of a
+    # song) might contain a character identical to FS
+    len_line = length($0)
+    len_head = length($1 FS $2 FS $3 FS)
+    len_val  = len_line - len_head
+    val = substr($0, len_head + 1, len_val)
+    val = cache_maybe_total_to_diff(src, key, val)
+    val = cache_maybe_scale(src, key, val)
+    _cache[src, key] = val
+    time = cache_get_time()
+    _cache_mtime[src, key] = time
+    if (time % 3600 == 0) {
+        cache_gc()
+    }
+}
+
+function cache_get(result, src, key, ttl,    time, age, is_expired) {
+    time = cache_get_time()
+    _cache_atime[src, key] = time
+    age = time - _cache_mtime[src, key]
+    result["is_expired"] = ttl && age > ttl  # ttl = 0 => forever
+    result["value"] = _cache[src, key]
+}
+
+function cache_res_fmt_or_def(result, format, default) {
+    return result["is_expired"] ? default : sprintf(format, result["value"])
+}
+
+function cache_get_fmt_def(src, key, ttl, format, default,    result) {
+    default = default ? default : "--"
+    cache_get(result, src, key, ttl)
+    return cache_res_fmt_or_def(result, format, default)
+}
+
+function cache_get_time(    src, key, time) {
+    src = "khatus_sensor_datetime"
+    key = "epoch"
+    time = _cache[src, key]
+    _cache_atime[src, key] = time
+    return time
+}
+
+function cache_gc(    src_and_key, unused_for) {
+    for (src_and_key in _cache) {
+        unused_for = cache_get_time() - _cache_atime[src_and_key]
+        if (unused_for > 3600) {
+            print_msg_info(\
+                "cache_gc", "Deleting unused src_and_key: " src_and_key \
+            )
+            delete _cache[src_and_key]
+        }
+    }
+}
+
+function cache_maybe_total_to_diff(src, key, val,    key_parts) {
+    split(key, key_parts, Kfs)
+    if (_total_to_diff[src, key_parts[1]]) {
+        _prev[src, key] = _curr[src, key]
+        _curr[src, key] = val
+        return (_curr[src, key] - _prev[src, key])
+    } else {
+        return val
+    }
+}
+
+function cache_maybe_scale(src, key, val,    key_parts) {
+    split(key, key_parts, Kfs)
+    if ((src SUBSEP key_parts[1]) in _scale) {
+        return val * _scale[src, key_parts[1]]
+    } else {
+        return val
+    }
+}
+
+# -----------------------------------------------------------------------------
+# Status bar
+# -----------------------------------------------------------------------------
+
+function make_status_bar(    position, bar, sep, i, j) {
+    position[++i] = ""
+    position[++i] = make_status_energy()
+    position[++i] = make_status_mem()
+    position[++i] = make_status_cpu()
+    position[++i] = make_status_disk()
+    position[++i] = make_status_net()
+    position[++i] = make_status_bluetooth()
+    position[++i] = make_status_screen_brightness()
+    position[++i] = make_status_volume()
+    position[++i] = make_status_mpd()
+    position[++i] = make_status_weather()
+    position[++i] = make_status_datetime()
+    position[++i] = ""
+    bar = ""
+    sep = ""
+    for (j = 1; j <= i; j++) {
+        bar = bar sep position[j]
+        sep = " "
+    }
+    return bar
+}
+
+function make_status_energy(    state, charge, direction_of_change) {
+    cache_get(state , "khatus_sensor_energy", "battery_state"     , 0)
+    cache_get(charge, "khatus_sensor_energy", "battery_percentage", 0)
+
+    if (state["value"] == "discharging") {
+        direction_of_change = "<"
+    } else if (state["value"] == "charging") {
+        direction_of_change = ">"
+    } else {
+        direction_of_change = "="
+    }
+
+    return sprintf("E%s%d%%", direction_of_change, charge["value"])
+}
+
+function make_status_mem(    total, used, percent, status) {
+    cache_get(total, "khatus_sensor_memory", "total", 5)
+    cache_get(used , "khatus_sensor_memory", "used" , 5)
+    # Checking total["value"] to avoid division by zero when data is missing
+    if (!total["is_expired"] && \
+        !used["is_expired"] && \
+        total["value"] \
+        ) {
+        percent = round((used["value"] / total["value"]) * 100)
+        status = sprintf("%d%%", percent)
+    } else {
+        status = "__"
+    }
+    return sprintf("M=%s", status)
+}
+
+function make_status_cpu(    l, t, f) {
+    l_src = "khatus_sensor_loadavg"
+    t_src = "khatus_sensor_temperature"
+    f_src = "khatus_sensor_fan"
+    l = cache_get_fmt_def(l_src, "load_avg_1min", 5, "%4.2f")
+    t = cache_get_fmt_def(t_src, "temp_c"       , 5, "%d"   )
+    f = cache_get_fmt_def(f_src, "speed"        , 5, "%4d"  )
+    return sprintf("C=[%s %s°C %srpm]", l, t, f)
+}
+
+function make_status_disk(    u, w, r, src_u, src_io) {
+    src_u  = "khatus_sensor_disk_space"
+    src_io = "khatus_sensor_disk_io"
+    u = cache_get_fmt_def(src_u , "disk_usage_percentage", 10, "%s")
+    w = cache_get_fmt_def(src_io, "sectors_written"      ,  5, "%0.3f")
+    r = cache_get_fmt_def(src_io, "sectors_read"         ,  5, "%0.3f")
+    return sprintf("D=[%s%% %s▲ %s▼]", u, w, r)
+}
+
+function make_status_net(    \
+    number_of_net_interfaces_to_show, \
+    net_interfaces_to_show, \
+    io, \
+    wi, \
+    i, \
+    interface, \
+    label, \
+    wifi, \
+    addr, \
+    w, \
+    r, \
+    io_stat, \
+    out, \
+    sep \
+) {
+    number_of_net_interfaces_to_show = \
+        split(Opt_Net_Interfaces_To_Show, net_interfaces_to_show, ",")
+    io = "khatus_sensor_net_addr_io"
+    wi = "khatus_sensor_net_wifi_status"
+    out = ""
+    sep = ""
+    for (i = number_of_net_interfaces_to_show; i > 0; i--) {
+        interface = net_interfaces_to_show[i]
+        label = substr(interface, 1, 1)
+        if (interface ~ "^w") {
+            wifi = cache_get_fmt_def(wi, "status" Kfs interface, 10, "%s")
+            label = label ":" wifi
+        }
+        addr = cache_get_fmt_def(io, "addr"          Kfs interface, 5, "%s", "")
+        w    = cache_get_fmt_def(io, "bytes_written" Kfs interface, 5, "%0.3f")
+        r    = cache_get_fmt_def(io, "bytes_read"    Kfs interface, 5, "%0.3f")
+        io_stat = addr ? sprintf("%s▲ %s▼", w, r) : "--"
+        out = out sep label ":" io_stat
+        sep = " "
+    }
+    return sprintf("N[%s]", out)
+}
+
+function make_status_bluetooth(    src, key) {
+    src = "khatus_sensor_bluetooth_power"
+    key = "power_status"
+    return sprintf("B=%s", cache_get_fmt_def(src, key, 10, "%s"))
+}
+
+function make_status_screen_brightness(    src, key) {
+    src = "khatus_sensor_screen_brightness"
+    key = "percentage"
+    return sprintf("*%s%%", cache_get_fmt_def(src, key, 5, "%d"))
+}
+
+function make_status_volume(    sink, mu, vl, vr, show) {
+    sink = Opt_Pulseaudio_Sink
+    cache_get(mu, "khatus_sensor_volume", "mute"      Kfs sink, 5)
+    cache_get(vl, "khatus_sensor_volume", "vol_left"  Kfs sink, 5)
+    cache_get(vr, "khatus_sensor_volume", "vol_right" Kfs sink, 5)
+    show = "--"
+    if (!mu["is_expired"] && !vl["is_expired"] && !vr["is_expired"]) {
+             if (mu["value"] == "yes") {show = "X"}
+        else if (mu["value"] == "no")  {show = vl["value"] " " vr["value"]}
+        else {
+            print_msg_error(\
+                "make_status_volume", \
+                "Unexpected value for 'mute' field: " mu["value"] \
+            )
+        }
+    }
+    return sprintf("(%s)", show)
+}
+
+function make_status_mpd(    state, status) {
+    cache_get(state, "khatus_sensor_mpd", "state", 5)
+    if (!state["is_expired"] && state["value"]) {
+        if (state["value"] == "play") {
+            status = make_status_mpd_state_known("▶")
+        } else if (state["value"] == "pause") {
+            status = make_status_mpd_state_known("❚❚")
+        } else if (state["value"] == "stop") {
+            status = make_status_mpd_state_known("⬛")
+        } else {
+            print_msg_error(\
+                "make_status_mpd", \
+                "Unexpected value for 'state' field: " state["value"] \
+            )
+            status = "--"
+        }
+    } else {
+        status = "--"
+    }
+
+    return sprintf("[%s]", status)
+}
+
+function make_status_mpd_state_known(symbol,    s, song, time, percentage) {
+    s = "khatus_sensor_mpd"
+    song    = cache_get_fmt_def(s, "song"                   , 5, "%s", "?")
+    time    = cache_get_fmt_def(s, "play_time_minimal_units", 5, "%s", "?")
+    percent = cache_get_fmt_def(s, "play_time_percentage"   , 5, "%s", "?")
+    song    = substr(song, 1, Opt_Mpd_Song_Max_Chars)
+    return sprintf("%s %s %s %s", symbol, time, percent, song)
+}
+
+function make_status_weather(    src, hour, t_f) {
+    src = "khatus_sensor_weather"
+    hour = 60 * 60
+    t_f = cache_get_fmt_def(src, "temperature_f", 3 * hour, "%d")
+    return sprintf("%s°F", t_f)
+}
+
+function make_status_datetime(    dt) {
+    return cache_get_fmt_def("khatus_sensor_datetime", "datetime", 5, "%s")
+}
+
+# -----------------------------------------------------------------------------
+# Output
+# -----------------------------------------------------------------------------
+
+function print_msg_ok(key, val) {
+    print_msg("OK", key, val, "/dev/stdout")
+}
+
+function print_msg_info(location, msg) {
+    print_msg("INFO", location, msg, "/dev/stderr")
+}
+
+function print_msg_error(location, msg) {
+    print_msg("ERROR", location, msg, "/dev/stderr")
+}
+
+function print_msg(status, key, val, channel) {
+    print(status, "khatus_bar", key, val) > channel
+}
+
+# -----------------------------------------------------------------------------
+# Numbers
+# -----------------------------------------------------------------------------
+
+function round(n) {
+    return int(n + 0.5)
+}
diff --git a/bin/khatus_controller b/bin/khatus_controller
deleted file mode 100755 (executable)
index 72136d0..0000000
+++ /dev/null
@@ -1,602 +0,0 @@
-#! /usr/bin/awk -f
-
-/^OK/ { debug("OK line", $0) }
-
-/^ERROR in:MPD.*NON_ZERO_EXIT_CODE/ {
-    for (mpd_key in db) {
-        if (mpd_key ~ "^mpd_") {
-            delete db[mpd_key]
-        }
-    }
-    next
-}
-
-/^ERROR/ {
-    debug("ERROR line", $0)
-    shift()
-    msg_head = $1
-    shift()
-    msg_body = $0
-    alert_trigger_hi(msg_head, "KhatusSensorError", msg_body)
-}
-
-/^OK in:ENERGY battery/\
-{
-    debug("ENERGY battery", $0)
-    sub("%$", "", $5)
-    db["energy_state_prev"] = db["energy_state_curr"]
-    db["energy_state_curr"] = $4
-    db["energy_percentage"] = ensure_numeric($5)
-    alert_check_energy_battery()
-}
-
-/^OK in:ENERGY line_power/\
-{
-    debug("ENERGY line_power", $0)
-    db["energy_line_power_prev"] = db["energy_line_power_curr"]
-    db["energy_line_power_curr"] = $4
-    alert_check_energy_line_power()
-}
-
-/^OK in:MEMORY/\
-{
-    shift()
-    shift()
-    db["memory_total"] = $1
-    db["memory_used"]  = $2
-}
-
-/^OK in:FAN +status:/\
-{
-    shift()
-    shift()
-    db["fan_status"] = $2
-}
-
-/^OK in:FAN +speed:/\
-{
-    shift()
-    shift()
-    db["fan_speed"] = $2
-}
-
-/^OK in:FAN +level:/\
-{
-    shift()
-    shift()
-    db["fan_level"] = $2
-}
-
-/^OK in:TEMPERATURE/\
-{
-    shift()
-    shift()
-    db["temperature"] = $1
-}
-
-/^OK in:LOAD_AVG/\
-{
-    shift()
-    shift()
-    set_load_avg()
-}
-
-/^OK in:DISK_IO/\
-{
-    shift()
-    shift()
-    set_disk_io()
-}
-
-/^OK in:DISK_SPACE/\
-{
-    shift()
-    shift()
-    db["disk_space_used"] = $0
-}
-
-/^OK in:NET_ADDR_IO/\
-{
-    shift()
-    shift()
-    set_net_addr_io()
-}
-
-/^OK in:NET_WIFI_STATUS/\
-{
-    shift()
-    shift()
-    set_net_wifi_status()
-}
-
-/^OK in:BLUETOOTH_POWER/\
-{
-    shift()
-    shift()
-    db["bluetooth_power"] = $0
-}
-
-/^OK in:SCREEN_BRIGHTNESS/\
-{
-    shift()
-    shift()
-    set_screen_brightness()
-}
-
-/^OK in:VOLUME/\
-{
-    shift()
-    set_volume()
-}
-
-/^OK in:MPD_SONG OK +MPD/ { delete db_mpd_song; next }
-/^OK in:MPD_SONG OK$/     { set_mpd_playing() ; next }
-/^OK in:MPD_SONG /        { set_mpd_song()    ; next }
-
-/^OK in:MPD_STATE /\
-{
-    shift()
-    shift()
-    db["mpd_status_state"]   = $1
-    db["mpd_status_time"]    = $2
-    db["mpd_status_percent"] = $3
-}
-
-/^OK in:WEATHER temperature/\
-{
-    shift()
-    shift()
-    shift()
-    db["weather_temperature"] = $0
-}
-
-/^OK in:WEATHER phenomenon/\
-{
-    shift()
-    shift()
-    shift()
-    alert_trigger_low("weather_phenomenon", "WeatherPhenomenon", $0)
-}
-
-/^OK in:DATE_TIME/\
-{
-    shift()
-    shift()
-    db["datetime"] = $0
-    output_msg_status_bar(make_status_bar())
-}
-
-function set_volume(    mute, left, right) {
-    # 0 RUNNING no 75% 75%
-    #msg_head = $1
-    #sink     = $2
-    #state    = $3
-    mute      = $4
-    left      = $5
-    right     = $6
-
-    if (mute == "no") {
-        db["volume"] = sprintf("%s %s", left, right)
-    } else if (mute == "yes") {
-        db["volume"] = "X"
-    } else {
-        error("set_volume", "Unexpected value for 'mute' field: " mute)
-    }
-}
-
-function set_mpd_song(    key, val) {
-    shift()
-    key = $2
-    shift()
-    shift()
-    val = $0
-    db_mpd_song[key] = val
-    debug("set_mpd_song", "", db_mpd_song)
-}
-
-function set_mpd_playing(    \
-    currently_playing, name, title, file, last, parts\
-) {
-    debug("set_mpd_playing", "", db_mpd_song)
-    name  = db_mpd_song["Name:"]
-    title = db_mpd_song["Title:"]
-    file  = db_mpd_song["file:"]
-
-    if (name) {
-        currently_playing = name
-    } else if (title) {
-        currently_playing = title
-    } else if (file) {
-        last = split(file, parts, "/")
-        currently_playing = parts[last]
-    } else {
-        currently_playing = ""
-    }
-    db["mpd_playing_prev"] = db["mpd_playing_curr"]
-    db["mpd_playing_curr"] = currently_playing
-
-    alert_check_mpd()
-}
-
-function alert_check_mpd(    curr, prev, name, body) {
-    prev = db["mpd_playing_prev"]
-    curr = db["mpd_playing_curr"]
-    if (curr && curr != prev) {
-        name = db_mpd_song["Name:"]
-        if (name) {
-            body = name
-        } else {
-            body = \
-                      db_mpd_song["Artist:"] \
-                " - " db_mpd_song["Album:"] \
-                " - " db_mpd_song["Title:"]
-        }
-        alert_trigger_low("alert_check_mpd", "MpdNowPlaying", body)
-    }
-}
-
-# TODO: Generalize alert spec lang
-#       - trigger threshold
-#       - above/bellow/equal to threshold value
-#       - priority
-#       - snooze time (if already alerted, when to re-alert?)
-#       - text: subject/body
-function alert_check_energy_battery(    \
-    from, dbg, state_curr, state_prev, remaining, subj, body\
-) {
-    from = "alert_check_energy_battery"
-
-    state_curr = db["energy_state_curr"]
-    state_prev = db["energy_state_prev"]
-    remaining  = db["energy_percentage"]
-
-    dbg["state_curr"] = state_curr
-    dbg["remaining"] = remaining
-    debug(from, "", dbg)
-
-    if (state_curr == "discharging") {
-        if (remaining < 5) {
-            subj = "Energy_CRITICALLY_Low"
-            body = sprintf("%d%% CHARGE NOW!!! GO GO GO!!!", remaining)
-            alert_trigger_hi(from, subj, body)
-        } else if (remaining < 10) {
-            subj = "Energy_Very_Low"
-            body = sprintf("%d%% Plug it in ASAP.", remaining)
-            alert_trigger_hi(from, subj, body)
-        } else if (remaining < 15) {
-            subj = "Energy_Low"
-            body = sprintf("%d%% Get the charger.", remaining)
-            alert_trigger_hi(from, subj, body)
-        } else if (remaining < 20) {
-            subj = "Energy_Low"
-            body = sprintf("%d%% Get the charger.", remaining)
-            alert_trigger_med(from, subj, body)
-        } else if (remaining < 50) {
-            if (!state__alerts__energy__notified_bellow_half) {
-                state__alerts__energy__notified_bellow_half = 1
-                subj = "Energy_Bellow_Half"
-                body = sprintf("%d%% Where is the charger?", remaining)
-                alert_trigger_med(from, subj, body)
-            }
-        }
-    } else {
-        # TODO: Reconsider the competing global-state organizing-conventions
-        state__alerts__energy__notified_bellow_half = 0
-    }
-}
-
-function alert_check_energy_line_power(    \
-    from, dbg, line_power_curr, line_power_prev, subj, body \
-) {
-    from = "alert_check_energy_line_power"
-
-    dbg["energy_line_power_prev"] = db["energy_line_power_prev"]
-    dbg["energy_line_power_curr"] = db["energy_line_power_curr"]
-    debug(from, "", dbg)
-
-    line_power_curr = db["energy_line_power_curr"]
-    line_power_prev = db["energy_line_power_prev"]
-
-    if (line_power_curr == "no" && line_power_prev != "no") {
-        alert_trigger_low(from, "PowerUnplugged", "")
-    }
-}
-
-function alert_trigger_low(from, subject, body) {
-    alert_trigger("low", from, subject, body)
-}
-
-function alert_trigger_med(from, subject, body) {
-    alert_trigger("med", from, subject, body)
-}
-
-function alert_trigger_hi(from, subject, body) {
-    alert_trigger("hi", from, subject, body)
-}
-
-function alert_trigger(priority, from, subject, body,    msg) {
-    # priority : "low" | "med" | "hi"
-    # subject  : no spaces
-    # body     : anything
-    msg = sprintf("khatus_%s %s %s %s", from, priority, subject, body)
-    output_msg_alert(msg)
-}
-
-function output_msg_alert(msg) {
-    # TODO: Should alerts go into a dedicated channel?
-    output_msg("ALERT", msg, "/dev/stdout")
-}
-
-function output_msg_status_bar(msg) {
-    output_msg("STATUS_BAR", msg, "/dev/stdout")
-}
-
-function output_msg(type, content, channel) {
-    print(type, content) > channel
-}
-
-function set_load_avg(    sched) {
-    split($4, sched, "/")
-    db["load_avg_1min"]             = $1
-    db["load_avg_5min"]             = $2
-    db["load_avg_15min"]            = $3
-    db["kern_sched_queue_runnable"] = sched[1]
-    db["kern_sched_queue_total"]    = sched[2]
-    db["kern_sched_latest_pid"]     = $5
-}
-
-function set_disk_io(    curr_w, curr_r, prev_w, prev_r) {
-    curr_w = $1
-    curr_r = $2
-    prev_w = db["disk_io_curr_w"]
-    prev_r = db["disk_io_curr_r"]
-    db["disk_io_curr_w"] = curr_w
-    db["disk_io_curr_r"] = curr_r
-    db["disk_io_diff_w"] = curr_w - prev_w
-    db["disk_io_diff_r"] = curr_r - prev_r
-}
-
-function set_net_wifi_status(    interface) {
-    interface = $1
-    shift()
-    db["net_wifi_status", interface] = $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
-}
-
-# TODO: Revise overuse of shift() where it is not really needed
-function shift() {
-    sub("^" $1 " +", "")
-}
-
-function make_status_bar(    position, bar, sep, i, j) {
-    position[++i] = make_status_energy()
-    position[++i] = make_status_mem()
-    position[++i] = make_status_cpu()
-    position[++i] = make_status_disk()
-    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 = ""
-    sep = ""
-    for (j = 1; j <= i; j++) {
-        bar = bar sep position[j]
-        sep = " "
-    }
-    return bar
-}
-
-function make_status_energy(    state, direction_of_change) {
-    state = db["energy_state_curr"]
-    if (state == "discharging") {
-        direction_of_change = "<"
-    } else if (state == "charging") {
-        direction_of_change = ">"
-    } else {
-        direction_of_change = "="
-    };
-    return sprintf("E%s%d%%", direction_of_change, db["energy_percentage"])
-}
-
-function make_status_mem(    total, used, percent, status) {
-    total = db["memory_total"]
-    used  = db["memory_used"]
-    # To avoid division by zero when data is missing
-    if (total && used) {
-        percent = round((used / total) * 100)
-        status = sprintf("%d%%", percent)
-    } else {
-        status = "__"
-    }
-    return sprintf("M=%s", status)
-}
-
-function make_status_cpu(    load, temp, fan) {
-    load = db["load_avg_1min"]
-    temp = db["temperature"] / 1000
-    fan  = db["fan_speed"]
-    return sprintf("C=[%4.2f %d°C %4drpm]", load, temp, fan)
-}
-
-function make_status_disk(    bytes_per_sector, bytes_per_mb, w, r) {
-    bytes_per_sector = 512
-    bytes_per_mb     = 1024 * 1024
-    w = (db["disk_io_diff_w"] * bytes_per_sector) / bytes_per_mb
-    r = (db["disk_io_diff_r"] * bytes_per_sector) / bytes_per_mb
-    return \
-        sprintf("D=[%s %0.3f▲ %0.3f▼]", db["disk_space_used"], w, r)
-}
-
-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", interface]
-                }
-                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_status_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_status_time"],
-        db["mpd_status_percent"],
-        substr(db["mpd_playing_curr"], 1, opt_mpd_song_max_chars)\
-    )
-}
-
-function make_status_mpd_state_unknown(symbol) {
-    return sprintf("%s", symbol)
-}
-
-function round(n) {
-    return int(n + 0.5)
-}
-
-function debug(location, msg, values,    sep, vals, key, payload) {
-    if (opt_debug) {
-        sep = ""
-        vals = ""
-        for (key in values) {
-            vals = sprintf("%s%s%s: %s", vals, sep, key, values[key])
-            sep = ", "
-        }
-        payload = \
-            sprintf("LOCATION[%s] MSG[%s] DATA[%s]", location, msg, vals)
-        output_msg("DEBUG", payload, "/dev/stderr")
-    }
-}
-
-function error(location, msg) {
-    # TODO: Reconsider classifying internal errors as alerts
-    #       Maybe better to keep the error class distinct and provide a
-    #       an optional transformation from error to alert
-    alert_trigger_hi(location, "KhatusControllerError", msg)
-}
-
-function ensure_numeric(n) {
-    return n + 0
-}
-#-------------------------------
-# Why do we need ensure_numeric?
-#-------------------------------
-# awk appears to be guessing the type of an inputted scalar based on usage, so
-# if we read-in a number, but did not use it in any numeric operations, but did
-# use as a string (even in just a format string!) - it will be treated as a
-# string and can lead to REALLY SURPRISING behavior in conditional statements,
-# where smaller number may compare as greater than the bigger ones, such as.
-#
-# Demo:
-#
-# $ awk 'BEGIN {x = "75"; y = "100"; sprintf("x: %d, y: %d\n", x, y); if (x > y) {print "75 > 100"} else if (x < y) {print "75 < 100"}}'
-# 75 < 100
-# $ awk 'BEGIN {x = "75"; y = "100"; sprintf("x: %s, y: %d\n", x, y); if (x > y) {print "75 > 100"} else if (x < y) {print "75 < 100"}}'
-# 75 > 100
-
-# However, once used as a number, seems to stay that way even after being
-# used as string:
-#
-# $ awk 'BEGIN {x = "75"; y = "100"; x + y; sprintf("x: %s, y: %d\n", x, y); if (x > y) {print "75 > 100"} else if (x < y) {print "75 < 100"}}'
-# 75 < 100
-# 
-# $ awk 'BEGIN {x = "75"; y = "100"; x + y; sprintf("x: %s, y: %d\n", x, y); z = x y; if (x > y) {print "75 > 100"} else if (x < y) {print "75 < 100"}}'
-# 75 < 100
-# 
-# $ awk 'BEGIN {x = "75"; y = "100"; x + y; z = x y; if (x > y) {print "75 > 100"} else if (x < y) {print "75 < 100"}}'
-# 75 < 100
-# $ awk 'BEGIN {x = "75"; y = "100"; z = x y; if (x > y) {print "75 > 100"} else if (x < y) {print "75 < 100"}}'
-# 75 > 100
diff --git a/bin/khatus_monitor_energy b/bin/khatus_monitor_energy
new file mode 100755 (executable)
index 0000000..24a4b74
--- /dev/null
@@ -0,0 +1,95 @@
+#! /usr/bin/awk -f
+
+BEGIN {
+     FS = msg_fs ? msg_fs : "|"
+    OFS = msg_fs ? msg_fs : "|"
+    Kfs = key_fs ? key_fs : ":"
+
+    # TODO: Read spec from a file
+    bat_alert_spec[100] = "low|Energy_Bellow_Full|Must have perfection!"
+    bat_alert_spec[50] = "low|Energy_Bellow_Half|Where is the charger?"
+    bat_alert_spec[20] = "med|Energy_Low|Get the charger."
+    bat_alert_spec[15] = "med|Energy_Low|Get the charger!"
+    bat_alert_spec[10] = "hi|Energy_Low|Plug it in, ASAP!"
+    bat_alert_spec[5]  = "hi|Energy_CRITICALLY_Low|CHARGE NOW!!! GO GO GO!!!"
+}
+
+$1 == "OK" && \
+$2 == "khatus_sensor_energy" && \
+$3 == "line_power" {
+    line_power_prev = line_power_curr
+    line_power_curr = $4
+    if (line_power_curr == "no" && line_power_prev != "no") {
+        alert("low", "PowerUnplugged", "")
+    }
+}
+
+$1 == "OK" && \
+$2 == "khatus_sensor_energy" && \
+$3 == "battery_state" {
+    battery_state_prev = battery_state_curr
+    battery_state_curr = $4
+}
+
+$1 == "OK" && \
+$2 == "khatus_sensor_energy" && \
+$3 == "battery_percentage" {
+    # TODO: Re-think the spec - can't rely on order of keys
+    battery_percentage = ensure_numeric($4)
+    if (battery_state_curr == "discharging") {
+        for (threshold in bat_alert_spec) {
+            threshold = ensure_numeric(threshold)
+            if (battery_percentage <= threshold && !alerted[threshold]) {
+                split(bat_alert_spec[threshold], msg, "|")
+                priority = msg[1]
+                subject = msg[2]
+                body = sprintf("%d%% %s", battery_percentage, msg[3])
+                alert(priority, subject, body)
+                alerted[threshold]++
+            }
+        }
+    } else {
+        delete alerted
+    }
+}
+
+function alert(priority, subject, body) {
+    # priority : "low" | "med" | "hi"
+    # subject  : no spaces
+    # body     : anything
+    print("OK", "khatus_monitor_energy", "alert", priority, subject, body)
+}
+
+function ensure_numeric(n) {
+    return n + 0
+}
+
+#-------------------------------
+# Why do we need ensure_numeric?
+#-------------------------------
+# awk appears to be guessing the type of an inputted scalar based on usage, so
+# if we read-in a number, but did not use it in any numeric operations, but did
+# use as a string (even in just a format string!) - it will be treated as a
+# string and can lead to REALLY SURPRISING behavior in conditional statements,
+# where smaller number may compare as greater than the bigger ones, such as.
+#
+# Demo:
+#
+# $ awk 'BEGIN {x = "75"; y = "100"; sprintf("x: %d, y: %d\n", x, y); if (x > y) {print "75 > 100"} else if (x < y) {print "75 < 100"}}'
+# 75 < 100
+# $ awk 'BEGIN {x = "75"; y = "100"; sprintf("x: %s, y: %d\n", x, y); if (x > y) {print "75 > 100"} else if (x < y) {print "75 < 100"}}'
+# 75 > 100
+
+# However, once used as a number, seems to stay that way even after being
+# used as string:
+#
+# $ awk 'BEGIN {x = "75"; y = "100"; x + y; sprintf("x: %s, y: %d\n", x, y); if (x > y) {print "75 > 100"} else if (x < y) {print "75 < 100"}}'
+# 75 < 100
+# 
+# $ awk 'BEGIN {x = "75"; y = "100"; x + y; sprintf("x: %s, y: %d\n", x, y); z = x y; if (x > y) {print "75 > 100"} else if (x < y) {print "75 < 100"}}'
+# 75 < 100
+# 
+# $ awk 'BEGIN {x = "75"; y = "100"; x + y; z = x y; if (x > y) {print "75 > 100"} else if (x < y) {print "75 < 100"}}'
+# 75 < 100
+# $ awk 'BEGIN {x = "75"; y = "100"; z = x y; if (x > y) {print "75 > 100"} else if (x < y) {print "75 < 100"}}'
+# 75 > 100
diff --git a/bin/khatus_monitor_errors b/bin/khatus_monitor_errors
new file mode 100755 (executable)
index 0000000..5f3f3b1
--- /dev/null
@@ -0,0 +1,25 @@
+#! /usr/bin/awk -f
+
+BEGIN {
+     FS = msg_fs ? msg_fs : "|"
+    OFS = msg_fs ? msg_fs : "|"
+    Kfs = key_fs ? key_fs : ":"
+}
+
+/^ERROR/ {
+    src = $2
+    # Not just using $6 for body - because body might contain a character
+    # identical to FS
+    len_line = length($0)
+    len_head = length($1 FS $2 FS)
+    len_body = len_line - len_head
+    body = substr($0, len_head + 1, len_body)
+    alert("hi", "Error_in_" src, body)
+}
+
+function alert(priority, subject, body) {
+    # priority : "low" | "med" | "hi"
+    # subject  : no spaces
+    # body     : anything
+    print("OK", "khatus_monitor_errors", "alert", priority, subject, body)
+}
index ed1e228..0d56853 100755 (executable)
@@ -1,5 +1,10 @@
 #! /usr/bin/awk -f
 
+BEGIN {
+    OFS = msg_fs ? msg_fs : "|"
+    Kfs = key_fs ? key_fs : ":"
+}
+
 /^Controller / {
     controller = $2
     controllers[++ctrl_count] = controller
@@ -28,7 +33,7 @@ END {
     } else {
         show = "n/a"
     }
-    print(show)
+    print("power_status", show)
 }
 
 function print_error(msg) {
diff --git a/bin/khatus_parse_df_pcent b/bin/khatus_parse_df_pcent
new file mode 100755 (executable)
index 0000000..7e4a534
--- /dev/null
@@ -0,0 +1,12 @@
+#! /usr/bin/awk -f
+
+BEGIN {
+    OFS = msg_fs ? msg_fs : "|"
+    Kfs = key_fs ? key_fs : ":"
+}
+
+NR == 2 {
+       percentage = $1
+       sub("%$", "", percentage)
+    print("disk_usage_percentage", percentage)
+}
diff --git a/bin/khatus_parse_fan_file b/bin/khatus_parse_fan_file
new file mode 100755 (executable)
index 0000000..120e150
--- /dev/null
@@ -0,0 +1,13 @@
+#! /usr/bin/awk -f
+
+BEGIN {
+    OFS = msg_fs ? msg_fs : "|"
+    Kfs = key_fs ? key_fs : ":"
+}
+
+{
+    key = $1
+    sub(":$", "", key)
+    val = $2
+    print(key, val)
+}
diff --git a/bin/khatus_parse_free b/bin/khatus_parse_free
new file mode 100755 (executable)
index 0000000..452f44d
--- /dev/null
@@ -0,0 +1,11 @@
+#! /usr/bin/awk -f
+
+BEGIN {
+    OFS = msg_fs ? msg_fs : "|"
+    Kfs = key_fs ? key_fs : ":"
+}
+
+$1 == "Mem:" {
+    print("total", $2)
+    print("used" , $3)
+}
index bc833b2..b1b2ecd 100755 (executable)
@@ -1,17 +1,22 @@
 #! /usr/bin/awk -f
 
+BEGIN {
+    OFS = msg_fs ? msg_fs : "|"
+    Kfs = key_fs ? key_fs : ":"
+}
+
 /^[0-9]+:/ {
     sub(":$", "", $1)
     sub(":$", "", $2)
     sequence = $1
     interface = $2
-    interfaces[sequence] = interface
+    Interfaces[sequence] = interface
 }
 
 /^ +inet [0-9]/ {
     sub("/[0-9]+", "", $2)
     addr = $2
-    addrs[interface] = addr
+    Addrs[interface] = addr
 }
 
 /^ +RX: / {transfer_direction = "r"}
 
 END {
     for (seq=1; seq<=sequence; seq++) {
-        interface = interfaces[seq]
+        interface = Interfaces[seq]
         label = substr(interface, 1, 1)
-        addr = addrs[interface]
+        addr = Addrs[interface]
         if (addr) {
-            curr_read  = io[interface, "r"]
-            curr_write = io[interface, "w"]
-            print(interface, addr, curr_write, curr_read)
+            bytes_read    = io[interface, "r"]
+            bytes_written = io[interface, "w"]
         } else {
-            print(interface)
+            bytes_read    = ""
+            bytes_written = ""
         }
+        output["addr"          Kfs interface] = addr
+        output["bytes_read"    Kfs interface] = bytes_read
+        output["bytes_written" Kfs interface] = bytes_written
+    }
+    for (key in output) {
+        print(key, output[key])
     }
 }
index e502731..a33b50f 100755 (executable)
 #
 # USAGE: khatus_parse_iwconfig -v requested_interface="$wifi_interface"
 
+BEGIN {
+    OFS = msg_fs ? msg_fs : "|"
+    Kfs = key_fs ? key_fs : ":"
+}
+
 /^[a-z0-9]+ +IEEE 802\.11 +ESSID:/ {
     interface = $1
     split($4, essid_parts, ":")
@@ -32,6 +37,6 @@
 
 END {
     i = requested_interface
-    status = link[i] ? sprintf("%s %s:%d%%", i, essid[i], link[i]) : i " --"
-    print(status)
+    status = link[i] ? sprintf("%s:%d%%", essid[i], link[i]) : "--:--%"
+    print("status" Kfs i, status)
 }
diff --git a/bin/khatus_parse_loadavg_file b/bin/khatus_parse_loadavg_file
new file mode 100755 (executable)
index 0000000..f7665fa
--- /dev/null
@@ -0,0 +1,17 @@
+#! /usr/bin/awk -f
+
+BEGIN {
+    OFS = msg_fs ? msg_fs : "|"
+    Kfs = key_fs ? key_fs : ":"
+}
+
+# 0.71 1.04 1.12 1/325 2409
+{
+    split($4, sched, "/")
+    print("load_avg_1min"             , $1)
+    print("load_avg_5min"             , $2)
+    print("load_avg_15min"            , $3)
+    print("kern_sched_queue_runnable" , sched[1])
+    print("kern_sched_queue_total"    , sched[2])
+    print("kern_sched_latest_pid"     , $5)
+}
index b228390..1dd2778 100755 (executable)
@@ -3,10 +3,9 @@
 # Qualifying the name as "_d_output" lest it be mistaken for parser of actual
 # metar format.
 
-function strip(s) {
-    sub("^ *", "", s)
-    sub(" *$", "", s)
-    return s
+BEGIN {
+    OFS = msg_fs ? msg_fs : "|"
+    Kfs = key_fs ? key_fs : ":"
 }
 
 /METAR pattern not found in NOAA data/ {
@@ -36,12 +35,19 @@ END {
         split(temp_string, temp_parts, " +")
         temp_celsius = temp_parts[1]
         temp_fahrenheit = (temp_celsius * (9 / 5)) + 32
-        print "temperature " temp_fahrenheit "°F"
+        print("temperature_c", temp_celsius)     # °C
+        print("temperature_f", temp_fahrenheit)  # °F
         for (i=first["Phenomena"]; i<=last["Phenomena"]; i++) {
             phenomenon = values[i]
             if (phenomenon) {
-                print "phenomenon " phenomenon
+                print("phenomenon" Kfs i, phenomenon)
             }
         }
     }
 }
+
+function strip(s) {
+    sub("^ *", "", s)
+    sub(" *$", "", s)
+    return s
+}
diff --git a/bin/khatus_parse_mpd_status b/bin/khatus_parse_mpd_status
deleted file mode 100755 (executable)
index 1088b4e..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-#! /usr/bin/awk -f
-
-# Msg BEGINs
-/^OK MPD / { delete status; next}
-
-# Msg ENDs
-/^OK$/ {
-    split(status["time"], 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 = "~"
-    }
-
-    printf("%s %s %s\n", status["state"], current_time, current_percentage)
-    next
-}
-
-# Msg content
-/^[a-z]+: / {
-    sub(":$", "", $1)
-    status[$1] = $2
-    next
-}
diff --git a/bin/khatus_parse_mpd_status_currentsong b/bin/khatus_parse_mpd_status_currentsong
new file mode 100755 (executable)
index 0000000..c045c0f
--- /dev/null
@@ -0,0 +1,88 @@
+#! /usr/bin/awk -f
+
+BEGIN {
+    OFS = msg_fs ? msg_fs : "|"
+    Kfs = key_fs ? key_fs : ":"
+}
+
+# Msg separator
+/^OK/ {msg_count++; next}
+
+# Msg content
+/^[a-zA-Z-]+: / {
+    key = $1
+    val = $0
+    sub(".*" key " *", "", val)
+    sub(":$", "", key)
+    key = tolower(key)
+    # Note that we expect a particular order of response messages (also
+    # reflected in the name of this script file): "status" THEN "currentsong"
+         if (msg_count == 1) {status[key]      = val}
+    else if (msg_count == 2) {currentsong[key] = val}
+    else {
+        printf("Unexpected msg_count in mpd response: %d\n", msg_count) \
+            > "/dev/stderr"
+        exit 1
+    }
+    next
+}
+
+END {
+    name  = currentsong["name"]
+    title = currentsong["title"]
+    file  = currentsong["file"]
+
+    if (name) {
+        song = name
+    } else if (title) {
+        song = title
+    } else if (file) {
+        last = split(file, parts, "/")
+        song = parts[last]
+    } else {
+        song = "?"
+    }
+
+    format_time(status["time"], time)
+    output["play_time_minimal_units"] = time["minimal_units"]
+    output["play_time_percentage"]    = time["percentage"]
+    output["state"]                   = status["state"]
+    output["song"]                    = song
+    for (key in output) {
+        print(key, output[key])
+    }
+}
+
+function format_time(time_str, time_arr,    \
+    \
+    time_str_parts,
+    seconds_current,
+    seconds_total,
+    hours,
+    secs_beyond_hours,
+    mins,
+    secs,
+    time_percentage \
+) {
+    split(time_str, time_str_parts, ":")
+    seconds_current = time_str_parts[1]
+    seconds_total   = time_str_parts[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) {
+        time_arr["minimal_units"] = sprintf("%d:%.2d:%.2d", hours, mins, secs)
+    } else {
+        time_arr["minimal_units"] = sprintf("%.2d:%.2d", mins, secs)
+    }
+
+    if (seconds_total > 0) {
+        time_percentage = (seconds_current / seconds_total) * 100
+        time_arr["percentage"] = sprintf("%d%%", time_percentage)
+    } else {
+        time_arr["percentage"] = "~"
+    }
+}
index 2de73e8..7ed10e6 100755 (executable)
@@ -1,5 +1,10 @@
 #! /usr/bin/awk -f
 
+BEGIN {
+    OFS = msg_fs ? msg_fs : "|"
+    Kfs = key_fs ? key_fs : ":"
+}
+
 /^Sink \#[0-9]+$/ {
     sub("^#", "", $2)
     sink = $2
@@ -34,9 +39,9 @@
 
 END {
     for (sink in state) {
-        printf(\
-            "%s %s %s %s %s\n",
-            sink, state[sink], mute[sink], vol_left[sink], vol_right[sink] \
-        )
+        print("state"     Kfs sink, state[sink])
+        print("mute"      Kfs sink, mute[sink])
+        print("vol_left"  Kfs sink, vol_left[sink])
+        print("vol_right" Kfs sink, vol_right[sink])
     }
 }
diff --git a/bin/khatus_parse_sys_block_stat b/bin/khatus_parse_sys_block_stat
new file mode 100755 (executable)
index 0000000..923e3ee
--- /dev/null
@@ -0,0 +1,11 @@
+#! /usr/bin/awk -f
+
+BEGIN {
+    OFS = msg_fs ? msg_fs : "|"
+    Kfs = key_fs ? key_fs : ":"
+}
+
+{
+    print("sectors_read"   , $3)
+    print("sectors_written", $7)
+}
index 6761508..af73e19 100755 (executable)
@@ -1,5 +1,10 @@
 #! /usr/bin/awk -f
 
+BEGIN {
+    OFS = msg_fs ? msg_fs : "|"
+    Kfs = key_fs ? key_fs : ":"
+}
+
 # When parsing 'upower --dump'
 /^Device:[ \t]+/ {
     device["path"] = $2
 
 /    percentage:/ && device["is_battery"] {
     device["battery_percentage"] = $2
+    sub("%$", "", device["battery_percentage"])
     next
 }
 
 /^$/ && device["is_battery"] {
-    printf("battery %s %s\n", device["battery_state"], device["battery_percentage"])
+    print("battery_state"     , device["battery_state"])
+    print("battery_percentage", device["battery_percentage"])
 }
 # END battery
 
@@ -45,7 +52,7 @@
 }
 
 /^$/ && device["is_line_power"] {
-    printf("line_power %s\n", device["line_power_online"])
+    print("line_power", device["line_power_online"])
 }
 # END line-power
 
index ee1da65..effcb1a 100755 (executable)
@@ -1,3 +1,19 @@
 #! /bin/sh
 
-date +'%a %b %d %H:%M:%S'
+set -e
+
+date +'%s %a %b %d %H:%M:%S' \
+| awk '
+    BEGIN {
+        OFS = msg_fs ? msg_fs : "|"
+        Kfs = key_fs ? key_fs : ":"
+    }
+
+       {
+               epoch = $1
+               datetime = $0
+               sub("^" epoch " +", "", datetime)
+               print("epoch"   , epoch)
+               print("datetime", datetime)
+       }
+       '
index 379bcb3..840aed3 100755 (executable)
@@ -2,13 +2,7 @@
 
 set -e
 
-disk_io_device="$1"
+dir_bin="$1"
+disk_io_device="$2"
 
-awk '
-    {
-        r = $3
-        w = $7
-        print w, r
-    }
-    ' \
-    "/sys/block/$disk_io_device/stat"
+"$dir_bin"/khatus_parse_sys_block_stat "/sys/block/$disk_io_device/stat"
index b0f00e6..218d050 100755 (executable)
@@ -2,6 +2,7 @@
 
 set -e
 
-disk_space_device="$1"
+dir_bin="$1"
+disk_space_device="$2"
 
-df --output=pcent "$disk_space_device" | awk 'NR == 2 {print $1}'
+df --output=pcent "$disk_space_device" | "$dir_bin"/khatus_parse_df_pcent
index 068ab81..53472c8 100755 (executable)
@@ -4,5 +4,5 @@ set -e
 
 dir_bin="$1"
 
-upower --dump           | stdbuf -o L "$dir_bin"/khatus_parse_upower
-upower --monitor-detail | stdbuf -o L "$dir_bin"/khatus_parse_upower
+stdbuf -o L upower --dump           | stdbuf -o L "$dir_bin"/khatus_parse_upower
+stdbuf -o L upower --monitor-detail | stdbuf -o L "$dir_bin"/khatus_parse_upower
index 12db7b6..40c580b 100755 (executable)
@@ -2,6 +2,7 @@
 
 set -e
 
-fan_path="$1"
+dir_bin="$1"
+fan_path="$2"
 
-cat "$fan_path"
+"$dir_bin"/khatus_parse_fan_file "$fan_path"
index 283d032..04f386f 100755 (executable)
@@ -2,4 +2,6 @@
 
 set -e
 
-cat /proc/loadavg
+dir_bin="$1"
+
+"$dir_bin"/khatus_parse_loadavg_file /proc/loadavg
index 0b79722..10dd058 100755 (executable)
@@ -2,4 +2,6 @@
 
 set -e
 
-free | awk '$1 == "Mem:" {print $2, $3}'
+dir_bin="$1"
+
+free | "$dir_bin"/khatus_parse_free
diff --git a/bin/khatus_sensor_mpd b/bin/khatus_sensor_mpd
new file mode 100755 (executable)
index 0000000..058cecd
--- /dev/null
@@ -0,0 +1,9 @@
+#! /bin/sh
+
+set -e
+
+dir_bin="$1"
+
+echo 'status\ncurrentsong' \
+| nc 127.0.0.1 6600 \
+| "$dir_bin"/khatus_parse_mpd_status_currentsong
diff --git a/bin/khatus_sensor_mpd_song b/bin/khatus_sensor_mpd_song
deleted file mode 100755 (executable)
index 0d119d1..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-#! /bin/sh
-
-set -e
-
-echo 'currentsong' | nc 127.0.0.1 6600
diff --git a/bin/khatus_sensor_mpd_state b/bin/khatus_sensor_mpd_state
deleted file mode 100755 (executable)
index 20cc49a..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-#! /bin/sh
-
-set -e
-
-dir_bin="$1"
-
-echo 'status' \
-| nc 127.0.0.1 6600 \
-| "$dir_bin"/khatus_parse_mpd_status
index 08a8dfc..9b075d3 100755 (executable)
@@ -4,7 +4,15 @@ set -e
 
 screen_brightness_device_path="$1"
 
-echo "\
-    $(cat $screen_brightness_device_path/max_brightness) \
-    $(cat $screen_brightness_device_path/brightness)\
-"
+awk '
+    BEGIN {
+        OFS = msg_fs ? msg_fs : "|"
+        Kfs = key_fs ? key_fs : ":"
+    }
+
+    FILENAME ~ "/max_brightness$" {max = $1; next}
+    FILENAME ~     "/brightness$" {cur = $1; next}
+    END                           {print("percentage", (cur / max) * 100)}
+' \
+"$screen_brightness_device_path/max_brightness" \
+"$screen_brightness_device_path/brightness"
index 6f9bd23..321c8d8 100755 (executable)
@@ -4,4 +4,12 @@ set -e
 
 thermal_zone="$1"
 
-cat "/sys/class/thermal/thermal_zone${thermal_zone}/temp"
+awk '
+    BEGIN {
+        OFS = msg_fs ? msg_fs : "|"
+        Kfs = key_fs ? key_fs : ":"
+    }
+
+    {print("temp_c", $1 / 1000)}
+' \
+"/sys/class/thermal/thermal_zone${thermal_zone}/temp"
diff --git a/sanity_check b/sanity_check
new file mode 100755 (executable)
index 0000000..79b86d5
--- /dev/null
@@ -0,0 +1,12 @@
+#! /bin/bash
+
+./bin/khatus --dir_bin ./bin --net_interfaces_to_show wlp3s0 --wifi_interface wlp3s0 \
+| stdbuf -o L tee \
+    >(stdbuf -o L ./bin/khatus_bar \
+        -v Opt_Mpd_Song_Max_Chars=10 \
+        -v Opt_Net_Interfaces_To_Show=wlp3s0 \
+        -v Opt_Pulseaudio_Sink=0 \
+    ) \
+    >(stdbuf -o L ./bin/khatus_monitor_energy) \
+    >(stdbuf -o L ./bin/khatus_monitor_errors) \
+> /dev/null
This page took 0.108302 seconds and 4 git commands to generate.