From 4411059d155436af0e80e5e6c3928ac8373093d6 Mon Sep 17 00:00:00 2001 From: Siraaj Khandkar Date: Fri, 8 Feb 2019 17:36:58 -0500 Subject: [PATCH] Begin X4 prototype --- README.md | 11 +- x4/README.md | 10 ++ x4/bin/khatus_x4_lib_common_sensor.sh | 122 ++++++++++++++++++ x4/bin/khatus_x4_parse_mpd_status_currentsong | 83 ++++++++++++ x4/bin/khatus_x4_parse_upower | 96 ++++++++++++++ x4/bin/khatus_x4_sensor_datetime | 11 ++ x4/bin/khatus_x4_sensor_energy | 15 +++ x4/bin/khatus_x4_sensor_mpd | 27 ++++ x4/sanity_check | 42 ++++++ 9 files changed, 412 insertions(+), 5 deletions(-) create mode 100644 x4/README.md create mode 100755 x4/bin/khatus_x4_lib_common_sensor.sh create mode 100755 x4/bin/khatus_x4_parse_mpd_status_currentsong create mode 100755 x4/bin/khatus_x4_parse_upower create mode 100755 x4/bin/khatus_x4_sensor_datetime create mode 100755 x4/bin/khatus_x4_sensor_energy create mode 100755 x4/bin/khatus_x4_sensor_mpd create mode 100755 x4/sanity_check diff --git a/README.md b/README.md index fcc7dbb..74caea1 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,9 @@ Experiments The approaches experimented-with so far (later versions do not _necessarily_ obsolete earlier ones, they're just different): -| Name | Status | Language | Tested-on | Description | -|--------|--------|-----------|-------------------------|-------------| -| __x1__ | Works | Bash, AWK | Ubuntu 16.04 | Single, synchronous script, saving state in text files | -| __x2__ | Works | Bash, AWK | Ubuntu 16.04, Debian 10 | Parallel processes: collectors, cache and reporters; passing messages over pipes | -| __x3__ | In dev | OCaml | Debian 10 | Re-write and refinement of __x2__ | +| Name | Status | Language | Tested-on | Description | +|--------|-----------|-----------|--------------|-------------| +| __x1__ | Archived | Bash, AWK | Ubuntu 16.04 | Single, synchronous script, saving state in text files | +| __x2__ | In-use | Bash, AWK | Debian 10 | Parallel processes: collectors, cache and reporters; passing messages over pipes | +| __x3__ | Scratched | OCaml | Debian 10 | Re-write and refinement of __x2__ | +| __x4__ | In-dev | Dash, AWK | Debian 10 | Sensors are completely decoupled daemons, cache is a file tree | diff --git a/x4/README.md b/x4/README.md new file mode 100644 index 0000000..2015c05 --- /dev/null +++ b/x4/README.md @@ -0,0 +1,10 @@ +X4 +== + +- caching directly on the file system +- sensors are isolated daemons + +Status +------ + +Began prototyping diff --git a/x4/bin/khatus_x4_lib_common_sensor.sh b/x4/bin/khatus_x4_lib_common_sensor.sh new file mode 100755 index 0000000..fae1356 --- /dev/null +++ b/x4/bin/khatus_x4_lib_common_sensor.sh @@ -0,0 +1,122 @@ +#! /bin/bash + +set -e + +# Defaults +prefix='/dev/shm/khatus' +host="$(hostname)" +sensor="$(basename $0)" +run_in='foreground' # foreground | background +run_as='poller' # poller | streamer +interval=1 # Only relevant if run_as poller, ignored otherwise. + +set_common_options() { + while : + do + case "$1" + in '') + break + ;; -d|--daemon) + run_in='background' + shift 1 + ;; -i|--interval) + case "$2" + in '') + printf "Option $1 requires and argument\n" >&2 + exit 1 + ;; *) + interval="$2" + shift 2 + esac + ;; *) + shift 1 + esac + done +} + +init_dirs() { + work_dir="${prefix}/${host}/${sensor}" + out_dir="${work_dir}/out" + err_file="${work_dir}/err" + pid_file="${work_dir}/pid" + + mkdir -p "$out_dir" +} + +streamer() { + sensor \ + | while read key val + do + printf "%s\n" "$val" > "${out_dir}/${key}" + done + >> "$err_file" +} + +poller() { + while : + do + streamer + sleep "$interval" + done +} + +pid_file_create_of_parent() { + printf "$$\n" > "$pid_file" +} + +pid_file_create_of_child() { + printf "$!\n" > "$pid_file" +} + +pid_file_test() { + if test -e "$pid_file" + then + printf "Error - $sensor already running (i.e. PID file exists at $pid_file)\n" 1>&2 + exit 1 + fi +} + +pid_file_remove() { + rm -f "$pid_file" +} + +run_in_foreground() { + # TODO: Why do INT and EXIT traps only work in combination? + trap true INT + trap exit TERM + trap pid_file_remove EXIT + $run_as +} + +run_in_background_2nd_fork() { + run_in_foreground & + pid_file_create_of_child +} + +run_in_background() { + run_in_background_2nd_fork & +} + +run() { + case "$run_as" + in 'poller' | 'streamer') + true + ;; *) + printf "Error - illegal value for \$run_as: $run_in\n" 1>&2 + exit 1 + esac + pid_file_test + case "$run_in" + in 'background') + run_in_background + ;; 'foreground') + pid_file_create_of_parent + run_in_foreground + ;; *) + printf "Error - illegal value for \$run_in: $run_in\n" 1>&2 + exit 1 + esac +} + +set_common_options $@ +init_dirs diff --git a/x4/bin/khatus_x4_parse_mpd_status_currentsong b/x4/bin/khatus_x4_parse_mpd_status_currentsong new file mode 100755 index 0000000..3716928 --- /dev/null +++ b/x4/bin/khatus_x4_parse_mpd_status_currentsong @@ -0,0 +1,83 @@ +#! /usr/bin/awk -f + +# 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"] = "~" + } +} diff --git a/x4/bin/khatus_x4_parse_upower b/x4/bin/khatus_x4_parse_upower new file mode 100755 index 0000000..f220e9b --- /dev/null +++ b/x4/bin/khatus_x4_parse_upower @@ -0,0 +1,96 @@ +#! /usr/bin/awk -f + +# When parsing 'upower --dump' +/^Device:[ \t]+/ { + device["path"] = $2 + next +} + +# When parsing 'upower --monitor-detail' +/^\[[0-9]+:[0-9]+:[0-9]+\.[0-9]+\][ \t]+device changed:[ \t]+/ { + device["path"] = $4 + next +} + +/ native-path:/ && device["path"] { + device["native_path"] = $2 + next +} + +# BEGIN battery +/ battery/ && device["path"] { + device["is_battery"] = 1 + next +} + +/ state:/ && device["is_battery"] { + device["battery_state"] = $2 + next +} + +/ energy:/ && device["is_battery"] { + device["energy"] = $2 + next +} + +/ energy-full:/ && device["is_battery"] { + device["energy_full"] = $2 + next +} + +/ percentage:/ && device["is_battery"] { + device["battery_percentage"] = $2 + sub("%$", "", device["battery_percentage"]) + next +} + +/^$/ && device["is_battery"] { + print("battery_state" , aggregate_battery_state()) + print("battery_percentage", aggregate_battery_percentage()) +} +# END battery + +# BEGIN line-power +/ line-power/ && device["path"] { + device["is_line_power"] = 1 + next +} + +/ online:/ && device["is_line_power"] { + device["line_power_online"] = $2 + next +} + +/^$/ && device["is_line_power"] { + print("line_power", device["line_power_online"]) +} +# END line-power + +/^$/ { + delete device + next +} + +function aggregate_battery_percentage( bat, curr, full) { + _battery_energy[device["native_path"]] = device["energy"] + _battery_energy_full[device["native_path"]] = device["energy_full"] + for (bat in _battery_energy) { + curr = curr + _battery_energy[bat] + full = full + _battery_energy_full[bat] + } + return ((curr / full) * 100) +} + +function aggregate_battery_state( curr, bat, new) { + _battery_state[device["native_path"]] = device["battery_state"] + curr = device["battery_state"] + for (bat in _battery_state) { + new = _battery_state[bat] + if (new == "discharging") { + curr = new + } else if (curr != "discharging" && new == "charging") { + curr = new + } + } + return curr +} diff --git a/x4/bin/khatus_x4_sensor_datetime b/x4/bin/khatus_x4_sensor_datetime new file mode 100755 index 0000000..4665847 --- /dev/null +++ b/x4/bin/khatus_x4_sensor_datetime @@ -0,0 +1,11 @@ +#! /bin/sh + +set -e + +. "$(dirname $(realpath $0))/khatus_x4_lib_common_sensor.sh" + +sensor() { + printf "datetime $(date +'%a %b %d %H:%M:%S')\n" +} + +run diff --git a/x4/bin/khatus_x4_sensor_energy b/x4/bin/khatus_x4_sensor_energy new file mode 100755 index 0000000..a7ade3f --- /dev/null +++ b/x4/bin/khatus_x4_sensor_energy @@ -0,0 +1,15 @@ +#! /bin/sh + +set -e + +bin_dir="$(dirname $(realpath $0))" + +. "$bin_dir/khatus_x4_lib_common_sensor.sh" + +sensor() { + stdbuf -o L upower --dump | stdbuf -o L "$bin_dir"/khatus_x4_parse_upower + stdbuf -o L upower --monitor-detail | stdbuf -o L "$bin_dir"/khatus_x4_parse_upower +} + +run_as='streamer' +run diff --git a/x4/bin/khatus_x4_sensor_mpd b/x4/bin/khatus_x4_sensor_mpd new file mode 100755 index 0000000..cc29595 --- /dev/null +++ b/x4/bin/khatus_x4_sensor_mpd @@ -0,0 +1,27 @@ +#! /bin/sh + +set -e + +bin_dir="$(dirname $(realpath $0))" + +. "$bin_dir/khatus_x4_lib_common_sensor.sh" + +sensor() { + # TODO: Convert mpd sensor to watcher from poller + # Since we can just open the connection and send periodic requests. + # + # close + # Closes the connection to MPD. MPD will try to send the remaining output + # buffer before it actually closes the connection, but that cannot be + # guaranteed. This command will not generate a response. + # + # Clients should not use this command; instead, they should just close the socket. + # + # https://www.musicpd.org/doc/html/protocol.html#connection-settings + # + echo 'status\ncurrentsong\nclose' \ + | nc 127.0.0.1 6600 \ + | "$bin_dir"/khatus_x4_parse_mpd_status_currentsong +} + +run diff --git a/x4/sanity_check b/x4/sanity_check new file mode 100755 index 0000000..fea986c --- /dev/null +++ b/x4/sanity_check @@ -0,0 +1,42 @@ +#! /bin/sh + +set -e + +. ./bin/khatus_x4_lib_common_sensor.sh + +dir="${prefix}/${host}" + +kill_sensor() { + if test -f "$1" + then + kill $(cat "$1") + fi +} + +read_sensor() { + if test -f "$1" + then + cat "$1" + else + printf '%s\n' '--' + fi +} + +kill_sensor ${dir}/khatus_x4_sensor_datetime/pid +kill_sensor ${dir}/khatus_x4_sensor_mpd/pid +kill_sensor ${dir}/khatus_x4_sensor_energy/pid + +./bin/khatus_x4_sensor_datetime -d +./bin/khatus_x4_sensor_mpd -d +./bin/khatus_x4_sensor_energy -d + +while : +do + battery_state="$(read_sensor ${dir}/khatus_x4_sensor_energy/out/battery_state)" + battery_percentage="$(read_sensor ${dir}/khatus_x4_sensor_energy/out/battery_percentage)" + datetime="$(read_sensor ${dir}/khatus_x4_sensor_datetime/out/datetime)" + mpd="$(read_sensor ${dir}/khatus_x4_sensor_mpd/out/state)" + printf \ + "E[${battery_state} ${battery_percentage}] [${mpd}] ${datetime}\n" + sleep 1 +done -- 2.20.1