Begin X4 prototype
authorSiraaj Khandkar <siraaj@khandkar.net>
Fri, 8 Feb 2019 22:36:58 +0000 (17:36 -0500)
committerSiraaj Khandkar <siraaj@khandkar.net>
Fri, 8 Feb 2019 22:36:58 +0000 (17:36 -0500)
README.md
x4/README.md [new file with mode: 0644]
x4/bin/khatus_x4_lib_common_sensor.sh [new file with mode: 0755]
x4/bin/khatus_x4_parse_mpd_status_currentsong [new file with mode: 0755]
x4/bin/khatus_x4_parse_upower [new file with mode: 0755]
x4/bin/khatus_x4_sensor_datetime [new file with mode: 0755]
x4/bin/khatus_x4_sensor_energy [new file with mode: 0755]
x4/bin/khatus_x4_sensor_mpd [new file with mode: 0755]
x4/sanity_check [new file with mode: 0755]

index fcc7dbb..74caea1 100644 (file)
--- 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 (file)
index 0000000..2015c05
--- /dev/null
@@ -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 (executable)
index 0000000..fae1356
--- /dev/null
@@ -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 (executable)
index 0000000..3716928
--- /dev/null
@@ -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 (executable)
index 0000000..f220e9b
--- /dev/null
@@ -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 (executable)
index 0000000..4665847
--- /dev/null
@@ -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 (executable)
index 0000000..a7ade3f
--- /dev/null
@@ -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 (executable)
index 0000000..cc29595
--- /dev/null
@@ -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 (executable)
index 0000000..fea986c
--- /dev/null
@@ -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
This page took 0.055957 seconds and 4 git commands to generate.