]> git.vanrenterghem.biz Git - elgato-keylight-script.git/blob - keylights.sh
Add examples for pretty json, avahi
[elgato-keylight-script.git] / keylights.sh
1 #!/bin/bash
2 set -Eeuo pipefail
4 trap destroy SIGINT SIGTERM ERR EXIT
6 # Settings
7 temp_file="/tmp/elgatokeylights"
8 script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd -P)
9 icon="${script_dir}/assets/elgato.png"
11 # Declarations
12 declare -i silent=0
13 declare -i pretty=0
14 declare action="usage"
15 declare target=""
16 declare format="json"
17 declare -A lights
18 declare lights_json
19 declare flat_json
20 declare call='curl --silent --show-error --location --header "Accept: application/json" --request'
21 declare devices="/elgato/lights"
22 declare accessory_info="/elgato/accessory-info"
23 declare settings="/elgato/lights/settings"
25 if [ ! -r "${icon}" ]; then icon=sunny; fi
27 notify() {
28     if [ $silent -eq 0 ]; then
29         notify-send -i "$icon" "Key Light Controller" "$1"
30     fi
31 }
33 die() {
34     echo >&2 -e "${1-}"
35     exit "${2-1}"
36 }
38 destroy() {
39     code=$?
40     rm "$temp_file" 2>/dev/null
42     exit ${code}
43 }
45 usage() {
46     cat <<EOF
47 Usage: $(basename "${BASH_SOURCE[0]}") [-h] [-f <value>] [-l] [-p] [-s] [-t <value>][-v] [--<option>] [--<option> <value>] <action>
49 Elgato Lights controller. Works for Key Light and Key Light Air.
51 Available actions:
52     list        List available lights
53     status      Get state of lights
54     on          Turn all lights on
55     off         Turn all lights off
56     temperature  Set temperature level (260-470)
57     brightness  Set brightness level (0-100)
58     increase    Increases brightness by 10
59     decrease    Decreases brightness by 10
61 Available formats:
62     json        Renders output as JSON (default)
63     flat        Renders output as flattened JSON with .(dot) notation JSON (default)
64     html        Renders output as basic html table
65     csv         Renders output as csv
66     table       Renders output as a printed table
67     pair        Renders output as flattened key=value pairs
70 Available options:
72 -h, --help      Print this help and exit
73 -f  --format    Set output format
74 -p, --pretty    Pretty print console output
75 -s, --silent    Supress notifications
76 -t, --target    Only perform action on devices where value matches filter
77 -v, --verbose   Print script debug info
78 EOF
79     exit
80 }
82 parse_params() {
83     # default values of variables set from params
85     while :; do
86         case "${1-}" in
87         -h | --help) usage ;;
88         -f | --format)
89             format="${2-}"
90             shift
91             ;;
92         -p | --pretty) pretty=1 ;;
93         -v | --verbose) set -x ;;
94         -s | --silent) silent=1 ;;
95         -t | --target)
96             target="${2-}"
97             shift
98             ;;
99         -?*) die "Unknown option: $1" ;;
100         *) break ;;
101         esac
102         shift
103     done
105     args=("$@")
107     # check required params and arguments
108     declare -A actions=([help]=1 [list]=1 [status]=1 [on]=1 [off]=1)
109     [[ ${#args[@]} -ne 1 ]] && die "Incorrect argument count"
111     #[[ ($silent -eq 1) && ($pretty -eq 1) ]] && die "Cannot use silent and pretty options simultaneously"
113     [[ -n "${actions[${args[0]}]}" ]] && action="${args[0]}"
115     return 0
118 dependencies() {
119     for var in "$@"; do
120         if ! command -v $var &>/dev/null; then
121             die "Dependency $var was not found, please install and try again"
122         fi
123     done
126 default_light_properties() {
127     # Default values for json type enforcement
128     device="N/A"
129     hostname="N/A"
130     manufacturer="N/A"
131     ipv4="N/A"
132     ipv6="N/A"
133     port=0
134     mac="N/A"
135     sku="N/A"
136     cfg="{}"
137     url="{}"
138     info="{}"
139     light="{}"
143 produce_json() {
144     declare json
145     t=$(eval echo "'.[] | select($target)'")
147     for l in "${!lights[@]}"; do
148         json+="${lights[$l]},"
149     done
151     lights_json=$(echo "[${json%,}]" | jq -c "$t")
154 output() {
155     data=${1-}
156     type=${2-"$format"}
158     # Mange user requested output format
159     case $format in
160     json | flat) print_json "$data" ;;
161     table) print_json "$data" ;;
162     csv) print_csv "$data" ;;
163     pair) print_pair "$data" ;;
164     html) print_html "$data" ;;
165     -?*) die "Unknown output format (-f/--format): $type" ;;
166     esac
169 print_json() {
170     # TODO: Evaluate adding jq filtering as filter argument
171     query=""
173     # Deconstruct json and assemble in flattened with .(dot) notation
174     if [[ $format == "flat" ]]; then
175         query='. as $in | reduce leaf_paths as $path ({}; . + { ($path | map(tostring) | join(".")): $in | getpath($path) })'
176     else
177         query="'.'"
178     fi
180     # Manage pretty printing
181     if [[ $pretty -eq 1 ]]; then
182         echo "${1-}" | jq "$query"
183     else
184         echo "${1-}" | jq -c -M "$query"
185     fi
187     exit 0
190 print_table() {
191     bold=$(tput bold)
192     normal=$(tput sgr0)
193     message='
194     
196     die "To be implemented"
199 set_state() {
200     new_state=$1
201     die "To be implemented"
204 find_lights() {
205     # Scan the network for Elgato devices
206     avahi-browse -d local _elg._tcp --resolve -t | grep -v "^\+" >"$temp_file"
208     declare device
209     declare hostname
210     declare manufacturer
211     declare ipv4
212     declare ipv6
213     declare -i port
214     declare mac
215     declare sku
216     declare cfg
217     declare url
218     declare info
219     declare light
220     default_light_properties
222     cat "$temp_file" >tmp
223     while read -r line; do
225         # Gather information about the light
226         if [[ ($line == =*) && ($line =~ IPv[46][[:space:]](.+)[[:space:]]_elg) ]]; then
227             device=$(eval echo "${BASH_REMATCH[1]}") # eval to strip whitespace
228         elif [[ $line =~ hostname.+\[(.+)\] ]]; then
229             hostname=${BASH_REMATCH[1]}
230         elif [[ $line =~ address.+\[(.+)\] ]]; then
231             ip=${BASH_REMATCH[1]}
232             [[ $ip =~ fe80 ]] && ipv6="$ip" || ipv4="$ip"
233             ip=""
234         elif [[ $line =~ port.+\[(.+)\] ]]; then
235             port=${BASH_REMATCH[1]}
236         elif [[ $line =~ txt.+\[(.+)\] ]]; then
237             txt=$(eval echo "${BASH_REMATCH[1]}") # eval to strip single and double quotes
239             if [[ $txt =~ mf=([^[[:space:]]*]*) ]]; then manufacturer=${BASH_REMATCH[1]}; fi
240             if [[ $txt =~ id=([^[[:space:]]*]*) ]]; then mac=${BASH_REMATCH[1]}; fi
241             if [[ $txt =~ md=.+[[:space:]]([^[[:space:]]*]*)[[:space:]]id= ]]; then sku=${BASH_REMATCH[1]}; fi
243             # Get information from the light
244             url="http://$ipv4:$port"
246             declare protocol="--ipv4"
247             if [[ $ipv4 == "N/A" ]]; then
248                 # Workaround: Ignoring ipv6 as Elgato miss-announces addressing and is not accepting requests
249                 # properly for v6. Will not change to filter only on ipv4 from avahi, as that can cause us to only end
250                 # up with an ipv6 address even though it was announced as ipv4, which in turn means we cannot communicate.
251                 continue
252                 # Remove above and uncomment below if a future update fixes ipv6 announcement and requests
253                 #protocol="--ipv6"
254                 #url="http://[$ip]:$port"
255             fi
257             cfg=$(eval "${call} GET $protocol ${url}${settings}") >/dev/null
258             info=$(eval "${call} GET $protocol ${url}${accessory_info}") >/dev/null
259             light=$(eval "${call} GET $protocol ${url}${devices}") >/dev/null
261             # Store the light as json
262             lights["$device"]=$(jq -n \
263                 --arg dev "$device" \
264                 --arg hn "$hostname" \
265                 --arg ipv4 "$ipv4" \
266                 --arg ipv6 "$ipv6" \
267                 --argjson port "$port" \
268                 --arg mf "$manufacturer" \
269                 --arg mac "$mac" \
270                 --arg sku "$sku" \
271                 --arg url "$url" \
272                 --argjson light "$light" \
273                 --argjson cfg "$cfg" \
274                 --argjson info "$info" \
275                 '{device: $dev, manufacturer: $mf, hostname: $hn, url: $url, ipv4: $ipv4, ipv6: $ipv6, 
276                     port: $port, mac: $mac, sku: $sku, light: $light, settings: $cfg, info: $info}')
278             # Reset for next light as we are processing the last avahi line
279             default_light_properties
281         fi
282     done <"$temp_file"
285 # Manage user parameters
286 parse_params "$@"
288 # Make sure dependencies are installed
289 dependencies avahi-browse curl notify-send jq
291 find_lights
293 # Fail if we cannot find lights
294 [[ ${#lights[@]} -eq 0 ]] && die "No lights found" 2
296 produce_json
298 # Dispatch actions
299 case $action in
300 usage) usage ;;
301 list) output "${lights_json}" ;;
302 status) status ;;
303 on) set_state 1 ;;
304 off) set_state 0 ;;
305 -?*) die "Unknown action" ;;
306 esac