Add Bluetooth power status
[khatus.git] / bin / khatus_loop
1 #! /bin/bash
2
3 set -e
4
5 produce_bluetooth_power() {
6 echo -e 'show \n quit' \
7 | bluetoothctl \
8 | awk '
9 /^Controller / {
10 controller = $2;
11 controllers[++ctrl_count] = controller;
12 }
13 /^\t[A-Z][A-Za-z]+:/ {
14 key = $1;
15 sub(":$", "", key);
16 val = $2;
17 for (i=3; i<=NF; i++) {
18 val = val " " $i};
19 data[controller, key] = val;
20 }
21 END {
22 # Using the 1st seen controller. Should we select specific instead?
23 power_status = data[controllers[1], "Powered"];
24 if (ctrl_count > 0) {
25 if (power_status == "no") {
26 power_status = "off"
27 } else if (power_status == "yes") {
28 power_status = "on"
29 } else {
30 printf("Unexpected bluetooth power status: %s\n", power_status)\
31 > "/dev/stderr";
32 power_status = "ERROR"
33 }
34 } else {
35 power_status = "off" # TODO: Perhaps use differentiated marker?
36 }
37 printf("%s", power_status);
38 }'
39 }
40
41 produce_screen_brightness() {
42 screen_brightness_device_path="$1"
43 echo "\
44 $(cat $screen_brightness_device_path/max_brightness) \
45 $(cat $screen_brightness_device_path/brightness)\
46 "
47 }
48
49 produce_volume() {
50 pactl list sinks \
51 | awk '
52 /^\tMute:/ {
53 printf("%s,", $0);
54 }
55 /^\tVolume:/ {
56 for (i=2; i<=NF; i++) printf(" %s", $i);
57 }' \
58 | awk -v RS=',' '
59 /^[ \t]*Mute:/ {mute = $2}
60 /^[ \t]*front-left:/ {left = $4}
61 /^[ \t]*front-right:/ {right = $4}
62 END {
63 if (mute == "yes") {
64 printf("x")
65 } else {
66 printf("%s %s", left, right)
67 }
68 }
69 '
70 }
71
72 produce_mpd_state() {
73 echo 'status' \
74 | nc 127.0.0.1 6600 \
75 | awk '
76 {
77 status[$1] = $2
78 }
79
80 /^time: +[0-9]+:[0-9]+$/ {
81 split($2, time, ":")
82 seconds_current = time[1]
83 seconds_total = time[2]
84
85 hours = int(seconds_current / 60 / 60);
86 secs_beyond_hours = seconds_current - (hours * 60 * 60);
87 mins = int(secs_beyond_hours / 60);
88 secs = secs_beyond_hours - (mins * 60);
89 if (hours > 0) {
90 current_time = sprintf("%d:%.2d:%.2d", hours, mins, secs)
91 } else {
92 current_time = sprintf("%.2d:%.2d", mins, secs)
93 }
94
95 if (seconds_total > 0) {
96 time_percentage = (seconds_current / seconds_total) * 100
97 current_percentage = sprintf("%d%%", time_percentage)
98 } else {
99 current_percentage = "~"
100 }
101 }
102
103 END {
104 state = status["state:"]
105
106 if (state == "play") {
107 symbol = "▶"
108 } else if (state == "pause") {
109 symbol = "❚❚"
110 } else if (state == "stop") {
111 symbol = "⬛"
112 } else {
113 symbol = "--"
114 }
115
116 printf(\
117 "%s %s %s",
118 status["state:"], current_time, current_percentage\
119 )
120 }
121 '
122 }
123
124 produce_mpd_song() {
125 echo 'currentsong' \
126 | nc 127.0.0.1 6600 \
127 | awk '
128 /^OK/ {
129 next
130 }
131
132 {
133 key = $1
134 sub("^" key " +", "")
135 val = $0
136 data[key] = val
137 }
138
139 END {
140 name = data["Name:"]
141 title = data["Title:"]
142 file = data["file:"]
143
144 if (name) {
145 out = name
146 } else if (title) {
147 out = title
148 } else if (file) {
149 last = split(file, parts, "/")
150 out = parts[last]
151 } else {
152 out = ""
153 }
154 print out
155 }
156 '
157 }
158
159 produce_weather() {
160 weather_station_id="$1"
161 metar -d "$weather_station_id" 2>&1 \
162 | awk '
163 /METAR pattern not found in NOAA data/ {
164 failures++
165 }
166
167 /^Temperature/ {
168 celsius = $3;
169 fahrenheit = (celsius * (9 / 5)) + 32;
170 temperature = fahrenheit
171 }
172
173 END {
174 if (failures > 0) {
175 temperature = "--"
176 }
177 print temperature "°F"
178 }'
179 }
180
181 produce_datetime() {
182 date +'%a %b %d %H:%M:%S'
183 }
184
185 consume() {
186 pipe="$1"
187 tail -f "$pipe" \
188 | stdbuf -o L awk \
189 -v opt_debug=0 \
190 -v opt_mpd_song_max_chars=10 \
191 '
192 /^in:BLUETOOTH_POWER/\
193 {
194 split_msg_parts()
195 db["bluetooth_power"] = msg_body
196 }
197
198 /^in:SCREEN_BRIGHTNESS/\
199 {
200 split_msg_parts()
201 set_screen_brightness()
202 }
203
204 /^in:VOLUME/\
205 {
206 split_msg_parts()
207 db["volume"] = msg_body
208 }
209
210 /^in:MPD_STATE/\
211 {
212 split_msg_parts()
213 db["mpd_state"] = $1
214 db["mpd_curr_song_time"] = $2
215 db["mpd_curr_song_percent"] = $3
216 }
217
218 /^in:MPD_SONG/\
219 {
220 split_msg_parts()
221 db["mpd_curr_song_name"] = msg_body
222 }
223
224 /^in:WEATHER/\
225 {
226 split_msg_parts()
227 db["weather_temperature"] = msg_body
228 }
229
230 /^in:DATE_TIME/\
231 {
232 split_msg_parts()
233 db["datetime"] = msg_body
234 }
235
236 /^out:BAR/\
237 {
238 split_msg_parts()
239 print make_bar()
240 }
241
242
243 function set_screen_brightness( max, cur) {
244 max = $1
245 cur = $2
246 db["screen_brightness"] = (cur / max) * 100
247 }
248
249 function split_msg_parts() {
250 msg_head = $1
251 sub("^" msg_head " +", "")
252 msg_body = $0
253 debug(msg_head, msg_body)
254 }
255
256 function make_bar( position, bar, sep, i, j) {
257 position[++i] = sprintf("B=%s", db["bluetooth_power"])
258 position[++i] = sprintf("*%d%%", db["screen_brightness"])
259 position[++i] = sprintf("(%s)", db["volume"])
260 position[++i] = make_status_mpd()
261 position[++i] = db["weather_temperature"]
262 position[++i] = db["datetime"]
263 bar = ""
264 sep = ""
265 for (j = 1; j <= i; j++) {
266 bar = bar sep position[j]
267 sep = " "
268 }
269 return bar
270 }
271
272 function make_status_mpd( state, status) {
273 state = db["mpd_state"]
274
275 if (state == "play") {
276 status = make_status_mpd_state_known("▶")
277 } else if (state == "pause") {
278 status = make_status_mpd_state_known("❚❚")
279 } else if (state == "stop") {
280 status = make_status_mpd_state_known("⬛")
281 } else {
282 status = make_status_mpd_state_unknown("--")
283 }
284
285 return sprintf("[%s]", status)
286 }
287
288 function make_status_mpd_state_known(symbol) {
289 return sprintf(\
290 "%s %s %s %s",
291 symbol,
292 db["mpd_curr_song_time"],
293 db["mpd_curr_song_percent"],
294 substr(db["mpd_curr_song_name"], 1, opt_mpd_song_max_chars)\
295 )
296 }
297
298 function make_status_mpd_state_unknown(symbol) {
299 return sprintf("%s", symbol)
300 }
301
302 function debug(location, msg) {
303 if (opt_debug) {
304 print_error(location, msg)
305 }
306 }
307
308 function print_error(location, msg) {
309 print(location " ==> " msg) > "/dev/stderr"
310 }
311 '
312 }
313
314 produce_bar_req() {
315 echo ''
316 }
317
318 spawn() {
319 cmd="$1"
320 pipe="$2"
321 msg_head="$3"
322 interval="$4"
323 while true; do
324 echo "${msg_head} $($cmd)" > "$pipe"
325 sleep "$interval"
326 done &
327 }
328
329 main() {
330 # Defaults
331 dir_data="$HOME/.khatus"
332 weather_station_id='KJFK'
333 screen_brightness_device_name='acpi_video0'
334
335 # User-overrides
336 OPTS=$(
337 getopt \
338 -o '' \
339 -l data-dir:,weather-station:screen-device: \
340 -- "$@"
341 )
342 eval set -- "$OPTS"
343 while true
344 do
345 case "$1" in
346 --data-dir)
347 dir_data="$2"
348 shift 2
349 ;;
350 --weather-station)
351 weather_station_id="$2"
352 shift 2
353 ;;
354 --screen-device)
355 screen_brightness_device_name="$2"
356 shift 2
357 ;;
358 --)
359 shift
360 break
361 ;;
362 esac
363 done
364
365 pipe="$dir_data/khatus_data_pipe"
366 screen_brightness_device_path='/sys/class/backlight'
367 screen_brightness_device_path+="/$screen_brightness_device_name"
368
369 ( echo "Khatus starting with the following parameters:"
370 ( echo " dir_data|= $dir_data"
371 echo " pipe|= $pipe"
372 echo " screen_brightness_device_name|=$screen_brightness_device_name"
373 echo " screen_brightness_device_path|=$screen_brightness_device_path"
374 echo " weather_station_id|= $weather_station_id"
375 ) | column -ts\|
376 echo ''
377 ) >&2
378
379 mkdir -p "$dir_data"
380 rm -f "$pipe"
381 mkfifo "$pipe"
382
383 cmd_produce_screen_brightness='produce_screen_brightness'
384 cmd_produce_screen_brightness+=" $screen_brightness_device_path"
385 cmd_produce_weather="produce_weather $weather_station_id"
386
387 # TODO: Redirect each worker's stderr to a dedicated log file
388 spawn produce_datetime "$pipe" 'in:DATE_TIME' 1
389 spawn "$cmd_produce_screen_brightness" "$pipe" 'in:SCREEN_BRIGHTNESS' 1
390 spawn "$cmd_produce_weather" "$pipe" 'in:WEATHER' $(( 30 * 60 ))
391 spawn produce_mpd_state "$pipe" 'in:MPD_STATE' 1
392 spawn produce_mpd_song "$pipe" 'in:MPD_SONG' 1
393 spawn produce_volume "$pipe" 'in:VOLUME' 1
394 spawn produce_bluetooth_power "$pipe" 'in:BLUETOOTH_POWER' 5
395 spawn produce_bar_req "$pipe" 'out:BAR' 1
396 consume "$pipe"
397 }
398
399 main $@
This page took 0.118328 seconds and 4 git commands to generate.