Clear MPD state on non-0 exit from MPD song sensor
[khatus.git] / bin / khatus_controller
CommitLineData
f37162a4
SK
1#! /usr/bin/awk -f
2
688fb20e 3/^OK/ { debug("OK line", $0) }
e16fe0ea 4
7a2b16ba
SK
5/^ERROR in:MPD.*NON_ZERO_EXIT_CODE/ {
6 for (mpd_key in db) {
7 if (mpd_key ~ "^mpd_") {
8 delete db[mpd_key]
9 }
10 }
11 next
12}
13
688fb20e 14/^ERROR/ {
1b82be54
SK
15 debug("ERROR line", $0)
16 shift()
17 msg_head = $1
18 shift()
19 msg_body = $0
20 alert_trigger_hi(msg_head, "KhatusSensorError", msg_body)
688fb20e
SK
21}
22
23/^OK in:ENERGY battery/\
f37162a4 24{
ab99b556 25 debug("ENERGY battery", $0)
688fb20e 26 sub("%$", "", $5)
7851644f 27 db["energy_state_prev"] = db["energy_state_curr"]
688fb20e
SK
28 db["energy_state_curr"] = $4
29 db["energy_percentage"] = ensure_numeric($5)
f03e88c6 30 alert_check_energy_battery()
ab99b556
SK
31}
32
688fb20e 33/^OK in:ENERGY line_power/\
ab99b556
SK
34{
35 debug("ENERGY line_power", $0)
36 db["energy_line_power_prev"] = db["energy_line_power_curr"]
688fb20e 37 db["energy_line_power_curr"] = $4
f03e88c6 38 alert_check_energy_line_power()
f37162a4
SK
39}
40
688fb20e 41/^OK in:MEMORY/\
f37162a4 42{
688fb20e 43 shift()
ab99b556 44 shift()
f37162a4
SK
45 db["memory_total"] = $1
46 db["memory_used"] = $2
47}
48
688fb20e 49/^OK in:FAN +status:/\
f37162a4 50{
688fb20e 51 shift()
ab99b556 52 shift()
f37162a4
SK
53 db["fan_status"] = $2
54}
55
688fb20e 56/^OK in:FAN +speed:/\
f37162a4 57{
688fb20e 58 shift()
ab99b556 59 shift()
f37162a4
SK
60 db["fan_speed"] = $2
61}
62
688fb20e 63/^OK in:FAN +level:/\
f37162a4 64{
688fb20e 65 shift()
ab99b556 66 shift()
f37162a4
SK
67 db["fan_level"] = $2
68}
69
688fb20e 70/^OK in:TEMPERATURE/\
f37162a4 71{
688fb20e 72 shift()
ab99b556 73 shift()
f37162a4
SK
74 db["temperature"] = $1
75}
76
688fb20e 77/^OK in:LOAD_AVG/\
f37162a4 78{
688fb20e 79 shift()
ab99b556 80 shift()
f37162a4
SK
81 set_load_avg()
82}
83
688fb20e 84/^OK in:DISK_IO/\
f37162a4 85{
688fb20e 86 shift()
ab99b556 87 shift()
f37162a4
SK
88 set_disk_io()
89}
90
688fb20e 91/^OK in:DISK_SPACE/\
f37162a4 92{
688fb20e 93 shift()
ab99b556 94 shift()
9419890b 95 db["disk_space_used"] = $0
f37162a4
SK
96}
97
688fb20e 98/^OK in:NET_ADDR_IO/\
f37162a4 99{
688fb20e 100 shift()
ab99b556 101 shift()
f37162a4
SK
102 set_net_addr_io()
103}
104
688fb20e 105/^OK in:NET_WIFI_STATUS/\
f37162a4 106{
688fb20e 107 shift()
ab99b556 108 shift()
b4612a8a 109 set_net_wifi_status()
f37162a4
SK
110}
111
688fb20e 112/^OK in:BLUETOOTH_POWER/\
f37162a4 113{
688fb20e 114 shift()
ab99b556 115 shift()
9419890b 116 db["bluetooth_power"] = $0
f37162a4
SK
117}
118
688fb20e 119/^OK in:SCREEN_BRIGHTNESS/\
f37162a4 120{
688fb20e 121 shift()
ab99b556 122 shift()
f37162a4
SK
123 set_screen_brightness()
124}
125
688fb20e 126/^OK in:VOLUME/\
f37162a4 127{
688fb20e 128 shift()
d06e7260 129 set_volume()
f37162a4
SK
130}
131
688fb20e
SK
132/^OK in:MPD_SONG OK +MPD/ { delete db_mpd_song; next }
133/^OK in:MPD_SONG OK$/ { set_mpd_playing() ; next }
134/^OK in:MPD_SONG / { set_mpd_song() ; next }
f5dfebd2 135
688fb20e 136/^OK in:MPD_STATE /\
a3bb1cff 137{
688fb20e 138 shift()
ab99b556 139 shift()
a3bb1cff
SK
140 db["mpd_status_state"] = $1
141 db["mpd_status_time"] = $2
142 db["mpd_status_percent"] = $3
f37162a4
SK
143}
144
654ea6e2 145/^OK in:WEATHER temperature/\
f37162a4 146{
654ea6e2 147 shift()
688fb20e 148 shift()
ab99b556 149 shift()
9419890b 150 db["weather_temperature"] = $0
f37162a4
SK
151}
152
3fc7fe2c 153/^OK in:WEATHER phenomenon/\
654ea6e2
SK
154{
155 shift()
156 shift()
157 shift()
3fc7fe2c 158 alert_trigger_low("weather_phenomenon", "WeatherPhenomenon", $0)
654ea6e2
SK
159}
160
688fb20e 161/^OK in:DATE_TIME/\
f37162a4 162{
688fb20e 163 shift()
ab99b556 164 shift()
9419890b 165 db["datetime"] = $0
cae87802
SK
166 output_msg_status_bar(make_status_bar())
167}
168
d06e7260
SK
169function set_volume( mute, left, right) {
170 # 0 RUNNING no 75% 75%
171 #msg_head = $1
172 #sink = $2
173 #state = $3
174 mute = $4
175 left = $5
176 right = $6
177
178 if (mute == "no") {
179 db["volume"] = sprintf("%s %s", left, right)
180 } else if (mute == "yes") {
181 db["volume"] = "X"
182 } else {
0684c786 183 error("set_volume", "Unexpected value for 'mute' field: " mute)
d06e7260
SK
184 }
185}
186
30b9a4e1 187function set_mpd_song( key, val) {
688fb20e 188 shift()
30b9a4e1 189 key = $2
ab99b556
SK
190 shift()
191 shift()
30b9a4e1
SK
192 val = $0
193 db_mpd_song[key] = val
bb1e4fee 194 debug("set_mpd_song", "", db_mpd_song)
30b9a4e1
SK
195}
196
f5dfebd2
SK
197function set_mpd_playing( \
198 currently_playing, name, title, file, last, parts\
199) {
bb1e4fee 200 debug("set_mpd_playing", "", db_mpd_song)
30b9a4e1
SK
201 name = db_mpd_song["Name:"]
202 title = db_mpd_song["Title:"]
203 file = db_mpd_song["file:"]
f5dfebd2
SK
204
205 if (name) {
206 currently_playing = name
207 } else if (title) {
208 currently_playing = title
209 } else if (file) {
210 last = split(file, parts, "/")
211 currently_playing = parts[last]
212 } else {
213 currently_playing = ""
214 }
215 db["mpd_playing_prev"] = db["mpd_playing_curr"]
216 db["mpd_playing_curr"] = currently_playing
217
218 alert_check_mpd()
219}
220
30b9a4e1 221function alert_check_mpd( curr, prev, name, body) {
f5dfebd2
SK
222 prev = db["mpd_playing_prev"]
223 curr = db["mpd_playing_curr"]
e8c1404e 224 if (curr && curr != prev) {
30b9a4e1
SK
225 name = db_mpd_song["Name:"]
226 if (name) {
227 body = name
228 } else {
229 body = \
230 db_mpd_song["Artist:"] \
231 " - " db_mpd_song["Album:"] \
232 " - " db_mpd_song["Title:"]
233 }
43e49903 234 alert_trigger_low("alert_check_mpd", "MpdNowPlaying", body)
f5dfebd2
SK
235 }
236}
237
cae87802
SK
238# TODO: Generalize alert spec lang
239# - trigger threshold
240# - above/bellow/equal to threshold value
241# - priority
242# - snooze time (if already alerted, when to re-alert?)
243# - text: subject/body
f03e88c6
SK
244function alert_check_energy_battery( \
245 from, dbg, state_curr, state_prev, remaining, subj, body\
7851644f 246) {
f03e88c6 247 from = "alert_check_energy_battery"
d4c26c51 248
7851644f
SK
249 state_curr = db["energy_state_curr"]
250 state_prev = db["energy_state_prev"]
251 remaining = db["energy_percentage"]
e16fe0ea 252
7851644f 253 dbg["state_curr"] = state_curr
e16fe0ea 254 dbg["remaining"] = remaining
bb1e4fee 255 debug(from, "", dbg)
e16fe0ea 256
ab99b556 257 if (state_curr == "discharging") {
cae87802
SK
258 if (remaining < 5) {
259 subj = "Energy_CRITICALLY_Low"
260 body = sprintf("%d%% CHARGE NOW!!! GO GO GO!!!", remaining)
d4c26c51 261 alert_trigger_hi(from, subj, body)
cae87802
SK
262 } else if (remaining < 10) {
263 subj = "Energy_Very_Low"
264 body = sprintf("%d%% Plug it in ASAP.", remaining)
d4c26c51 265 alert_trigger_hi(from, subj, body)
cae87802
SK
266 } else if (remaining < 15) {
267 subj = "Energy_Low"
268 body = sprintf("%d%% Get the charger.", remaining)
d4c26c51 269 alert_trigger_hi(from, subj, body)
361523eb
SK
270 } else if (remaining < 20) {
271 subj = "Energy_Low"
272 body = sprintf("%d%% Get the charger.", remaining)
273 alert_trigger_med(from, subj, body)
cae87802
SK
274 } else if (remaining < 50) {
275 if (!state__alerts__energy__notified_bellow_half) {
276 state__alerts__energy__notified_bellow_half = 1
277 subj = "Energy_Bellow_Half"
278 body = sprintf("%d%% Where is the charger?", remaining)
d4c26c51 279 alert_trigger_med(from, subj, body)
cae87802
SK
280 }
281 }
282 } else {
283 # TODO: Reconsider the competing global-state organizing-conventions
284 state__alerts__energy__notified_bellow_half = 0
285 }
286}
287
f03e88c6
SK
288function alert_check_energy_line_power( \
289 from, dbg, line_power_curr, line_power_prev, subj, body \
290) {
291 from = "alert_check_energy_line_power"
292
293 dbg["energy_line_power_prev"] = db["energy_line_power_prev"]
294 dbg["energy_line_power_curr"] = db["energy_line_power_curr"]
295 debug(from, "", dbg)
296
297 line_power_curr = db["energy_line_power_curr"]
298 line_power_prev = db["energy_line_power_prev"]
299
300 if (line_power_curr == "no" && line_power_prev != "no") {
301 alert_trigger_low(from, "PowerUnplugged", "")
302 }
303}
304
d4c26c51
SK
305function alert_trigger_low(from, subject, body) {
306 alert_trigger("low", from, subject, body)
cae87802
SK
307}
308
d4c26c51
SK
309function alert_trigger_med(from, subject, body) {
310 alert_trigger("med", from, subject, body)
cae87802
SK
311}
312
d4c26c51
SK
313function alert_trigger_hi(from, subject, body) {
314 alert_trigger("hi", from, subject, body)
cae87802
SK
315}
316
d4c26c51 317function alert_trigger(priority, from, subject, body, msg) {
cae87802
SK
318 # priority : "low" | "med" | "hi"
319 # subject : no spaces
320 # body : anything
d4c26c51 321 msg = sprintf("khatus_%s %s %s %s", from, priority, subject, body)
cae87802
SK
322 output_msg_alert(msg)
323}
324
325function output_msg_alert(msg) {
326 # TODO: Should alerts go into a dedicated channel?
327 output_msg("ALERT", msg, "/dev/stdout")
328}
329
330function output_msg_status_bar(msg) {
331 output_msg("STATUS_BAR", msg, "/dev/stdout")
332}
333
334function output_msg(type, content, channel) {
335 print(type, content) > channel
f37162a4
SK
336}
337
338function set_load_avg( sched) {
339 split($4, sched, "/")
340 db["load_avg_1min"] = $1
341 db["load_avg_5min"] = $2
342 db["load_avg_15min"] = $3
343 db["kern_sched_queue_runnable"] = sched[1]
344 db["kern_sched_queue_total"] = sched[2]
345 db["kern_sched_latest_pid"] = $5
346}
347
348function set_disk_io( curr_w, curr_r, prev_w, prev_r) {
349 curr_w = $1
350 curr_r = $2
351 prev_w = db["disk_io_curr_w"]
352 prev_r = db["disk_io_curr_r"]
353 db["disk_io_curr_w"] = curr_w
354 db["disk_io_curr_r"] = curr_r
355 db["disk_io_diff_w"] = curr_w - prev_w
356 db["disk_io_diff_r"] = curr_r - prev_r
357}
358
b4612a8a
SK
359function set_net_wifi_status( interface) {
360 interface = $1
361 shift()
362 db["net_wifi_status", interface] = $0
363}
364
f37162a4
SK
365function set_net_addr_io( \
366 interface, address, io_curr_w, io_curr_r, io_prev_w, io_prev_r\
367) {
368 interface = $1
369 address = $2
370 io_curr_w = $3
371 io_curr_r = $4
372 if (interface) {
373 if (address && io_curr_w && io_curr_r) {
374 # recalculate
375 io_prev_w = net_io_curr_w[interface]
376 io_prev_r = net_io_curr_r[interface]
377
378 net_addr[interface] = address
379 net_io_curr_w[interface] = io_curr_w
380 net_io_curr_r[interface] = io_curr_r
381 net_io_diff_w[interface] = io_curr_w - io_prev_w
382 net_io_diff_r[interface] = io_curr_r - io_prev_r
383 } else {
384 # clear
385 net_addr[interface] = ""
386 net_io_curr_w[interface] = 0
387 net_io_curr_r[interface] = 0
388 net_io_diff_w[interface] = 0
389 net_io_diff_r[interface] = 0
390 }
391 }
392}
393
394function set_screen_brightness( max, cur) {
395 max = $1
396 cur = $2
397 db["screen_brightness"] = (cur / max) * 100
398}
399
ab99b556
SK
400# TODO: Revise overuse of shift() where it is not really needed
401function shift() {
9419890b 402 sub("^" $1 " +", "")
f37162a4
SK
403}
404
cae87802 405function make_status_bar( position, bar, sep, i, j) {
f37162a4
SK
406 position[++i] = make_status_energy()
407 position[++i] = make_status_mem()
408 position[++i] = make_status_cpu()
409 position[++i] = make_status_disk()
410 position[++i] = make_status_net()
411 position[++i] = sprintf("B=%s", db["bluetooth_power"])
412 position[++i] = sprintf("*%d%%", db["screen_brightness"])
413 position[++i] = sprintf("(%s)", db["volume"])
414 position[++i] = make_status_mpd()
415 position[++i] = db["weather_temperature"]
416 position[++i] = db["datetime"]
417 bar = ""
418 sep = ""
419 for (j = 1; j <= i; j++) {
420 bar = bar sep position[j]
421 sep = " "
422 }
423 return bar
424}
425
426function make_status_energy( state, direction_of_change) {
7851644f 427 state = db["energy_state_curr"]
f37162a4
SK
428 if (state == "discharging") {
429 direction_of_change = "<"
430 } else if (state == "charging") {
431 direction_of_change = ">"
432 } else {
433 direction_of_change = "="
434 };
e16fe0ea 435 return sprintf("E%s%d%%", direction_of_change, db["energy_percentage"])
f37162a4
SK
436}
437
438function make_status_mem( total, used, percent, status) {
439 total = db["memory_total"]
440 used = db["memory_used"]
441 # To avoid division by zero when data is missing
442 if (total && used) {
443 percent = round((used / total) * 100)
444 status = sprintf("%d%%", percent)
445 } else {
446 status = "__"
447 }
448 return sprintf("M=%s", status)
449}
450
451function make_status_cpu( load, temp, fan) {
452 load = db["load_avg_1min"]
453 temp = db["temperature"] / 1000
454 fan = db["fan_speed"]
455 return sprintf("C=[%4.2f %d°C %4drpm]", load, temp, fan)
456}
457
458function make_status_disk( bytes_per_sector, bytes_per_mb, w, r) {
459 bytes_per_sector = 512
460 bytes_per_mb = 1024 * 1024
461 w = (db["disk_io_diff_w"] * bytes_per_sector) / bytes_per_mb
462 r = (db["disk_io_diff_r"] * bytes_per_sector) / bytes_per_mb
463 return \
464 sprintf("D=[%s %0.3f▲ %0.3f▼]", db["disk_space_used"], w, r)
465}
466
467function make_status_net( \
468 out,
469 number_of_interfaces_to_show,
470 n,
471 array_of_prefixes_of_interfaces_to_show,
472 prefix,
473 interface,
474 label,
475 count_printed,
476 sep,
477 io_stat,
478 dw, dr,
479 bytes_per_unit\
480) {
481 out = ""
482 number_of_interfaces_to_show = \
483 split(\
484 opt_prefixes_of_net_interfaces_to_show,\
485 array_of_prefixes_of_interfaces_to_show,\
486 ","\
487 )
488 for (n = 1; n <= number_of_interfaces_to_show; n++) {
489 prefix = array_of_prefixes_of_interfaces_to_show[n]
490 for (interface in net_addr) {
491 if (interface ~ ("^" prefix)) {
492 label = substr(interface, 1, 1)
493 if (net_addr[interface]) {
494 bytes_per_mb = 1024 * 1024 # TODO: option
495 dw = net_io_diff_w[interface] / bytes_per_mb
496 dr = net_io_diff_r[interface] / bytes_per_mb
497 io_stat = sprintf("%0.3f▲ %0.3f▼", dw, dr)
498 } else {
499 io_stat = "--"
500 }
501 if (interface ~ "^w") {
b4612a8a 502 label = label ":" db["net_wifi_status", interface]
f37162a4
SK
503 }
504 if (++count_printed > 1) {
505 sep = " "
506 } else {
507 sep = ""
508 }
509 out = out sep label ":" io_stat
510 }
511 }
512 }
513 return sprintf("N[%s]", out)
514}
515
516function make_status_mpd( state, status) {
a3bb1cff 517 state = db["mpd_status_state"]
f37162a4
SK
518
519 if (state == "play") {
520 status = make_status_mpd_state_known("▶")
521 } else if (state == "pause") {
522 status = make_status_mpd_state_known("❚❚")
523 } else if (state == "stop") {
524 status = make_status_mpd_state_known("⬛")
525 } else {
526 status = make_status_mpd_state_unknown("--")
527 }
528
529 return sprintf("[%s]", status)
530}
531
f5dfebd2 532function make_status_mpd_state_known(symbol) {
f37162a4
SK
533 return sprintf(\
534 "%s %s %s %s",
535 symbol,
a3bb1cff
SK
536 db["mpd_status_time"],
537 db["mpd_status_percent"],
f5dfebd2 538 substr(db["mpd_playing_curr"], 1, opt_mpd_song_max_chars)\
f37162a4
SK
539 )
540}
541
542function make_status_mpd_state_unknown(symbol) {
543 return sprintf("%s", symbol)
544}
545
546function round(n) {
547 return int(n + 0.5)
548}
549
bb1e4fee 550function debug(location, msg, values, sep, vals, key, payload) {
f37162a4 551 if (opt_debug) {
e16fe0ea
SK
552 sep = ""
553 vals = ""
554 for (key in values) {
555 vals = sprintf("%s%s%s: %s", vals, sep, key, values[key])
556 sep = ", "
557 }
ab99b556
SK
558 payload = \
559 sprintf("LOCATION[%s] MSG[%s] DATA[%s]", location, msg, vals)
bb1e4fee 560 output_msg("DEBUG", payload, "/dev/stderr")
f37162a4
SK
561 }
562}
e16fe0ea 563
0684c786
SK
564function error(location, msg) {
565 # TODO: Reconsider classifying internal errors as alerts
566 # Maybe better to keep the error class distinct and provide a
567 # an optional transformation from error to alert
568 alert_trigger_hi(location, "KhatusControllerError", msg)
7275500c
SK
569}
570
e16fe0ea
SK
571function ensure_numeric(n) {
572 return n + 0
573}
574#-------------------------------
575# Why do we need ensure_numeric?
576#-------------------------------
577# awk appears to be guessing the type of an inputted scalar based on usage, so
578# if we read-in a number, but did not use it in any numeric operations, but did
579# use as a string (even in just a format string!) - it will be treated as a
580# string and can lead to REALLY SURPRISING behavior in conditional statements,
581# where smaller number may compare as greater than the bigger ones, such as.
582#
583# Demo:
584#
585# $ 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"}}'
586# 75 < 100
587# $ 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"}}'
588# 75 > 100
589
590# However, once used as a number, seems to stay that way even after being
591# used as string:
592#
593# $ 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"}}'
594# 75 < 100
595#
596# $ 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"}}'
597# 75 < 100
598#
599# $ awk 'BEGIN {x = "75"; y = "100"; x + y; z = x y; if (x > y) {print "75 > 100"} else if (x < y) {print "75 < 100"}}'
600# 75 < 100
601# $ awk 'BEGIN {x = "75"; y = "100"; z = x y; if (x > y) {print "75 > 100"} else if (x < y) {print "75 < 100"}}'
602# 75 > 100
This page took 0.086393 seconds and 4 git commands to generate.