4 trap destroy SIGINT SIGTERM ERR EXIT
7 script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd -P)
8 icon="${script_dir}/assets/elgato.png"
13 declare action="usage"
22 declare call='curl --silent --show-error --location --header "Accept: application/json" --request'
23 declare devices="/elgato/lights"
24 declare accessory_info="/elgato/accessory-info"
25 declare settings="/elgato/lights/settings"
27 if [ ! -r "${icon}" ]; then icon=sunny; fi
30 if [ $silent -eq 0 ]; then
31 notify-send -i "$icon" "Key Light Controller" "$1"
48 Usage: $(basename "${BASH_SOURCE[0]}") [-h] [-f <value>] [-l <value>] [-p] [-s] [-t <value>][-v] [--<option>] [--<option> <value>] <action>
50 Elgato Lights controller. Works for Key Light and Key Light Air.
53 list List available lights
54 status Get state of lights
56 off Turn all lights off
57 temperature Set temperature level (260-470)
58 brightness Set brightness level (0-100)
59 increase Increases brightness by 10
60 decrease Decreases brightness by 10
63 json Renders output as JSON (default)
64 simple Renders output as JSON array of single level objects with subarrays as .(dot) notation JSON
65 flat Renders output as fully flattened single level JSON with .(dot) notation JSON
66 html Renders output as basic html table
67 csv Renders output as csv
68 table Renders output as a printed table
69 pair Renders output as flattened key=value pairs
74 -h, --help Print this help and exit
75 -f, --format Set output format
76 -l, --limit <list> Limit top level output fields to the specified comma separated list
77 -p, --pretty Pretty print console output
78 -s, --silent Supress notifications
79 -t, --target <filter> Only perform action on devices where value matches filter
80 -v, --verbose Print script debug info
86 # default values of variables set from params
96 limit=$(eval echo "\| {${2-}} ")
99 -p | --pretty) pretty=1 ;;
100 -v | --verbose) set -x ;;
101 -s | --silent) silent=1 ;;
106 -?*) die "Unknown option: $1" ;;
114 # check required params and arguments
115 declare -A actions=([help]=1 [list]=1 [status]=1 [on]=1 [off]=1)
116 [[ ${#args[@]} -ne 1 ]] && die "Incorrect argument count"
118 #[[ ($silent -eq 1) && ($pretty -eq 1) ]] && die "Cannot use silent and pretty options simultaneously"
120 [[ -n "${actions[${args[0]}]}" ]] && action="${args[0]}"
127 if ! command -v $var &>/dev/null; then
128 die "Dependency $var was not found, please install and try again"
133 default_light_properties() {
134 # Default values for json type enforcement
151 t=$(eval echo "'[.[] $limit| select($target)]'")
152 f=$(eval echo "'[.[] | select($target)]'")
154 lights_json=$(echo "${lights[@]}" | jq -c -s "$t")
155 full_json=$(echo "${lights[@]}" | jq -c -s "$f")
156 simple_json=$(echo "${lights_json}" | jq -c '.[] | reduce ( tostream | select(length==2) | .[0] |= [join(".")] ) as [$p,$v] ({}; setpath($p; $v)) ')
157 simple_json=$(echo "${simple_json}" | jq -c -s '.') # slurp it to make it an array
158 flat_json=$(echo "${lights_json}" | jq -c -s '.[] | reduce ( tostream | select(length==2) | .[0] |= [join(".")] ) as [$p,$v] ({}; setpath($p; $v)) ')
164 # Mange user requested output format
166 json) print_json "$lights_json" ;;
167 simple) print_json "$simple_json" ;;
168 flat) print_json "$flat_json" ;;
169 table) print_structured '@tsv' ;;
170 csv) print_structured '@csv' ;;
171 pair) print_structured 'pairs' ;;
173 -?*) die "Unknown output format (-f/--format): $format" ;;
179 # Manage pretty printing
180 if [[ $pretty -eq 1 ]]; then
181 echo "${1-}" | jq '.'
183 echo "${1-}" | jq -c -M '.'
192 # Handle csv and table printing
193 query="(.[0] | keys_unsorted | map(ascii_upcase)), (.[] | [.[]])|${1-@csv}"
195 # Handle printing as key value pairs
196 if [[ ${1} == 'pairs' ]]; then
197 query='.[] | "--------------",(to_entries[] | [.key, "=", .value] | @tsv)'
200 # Manage pretty printing
201 if [[ $pp -eq 1 ]]; then
202 echo "${simple_json}" | jq --raw-output "$query" | column -t -s$'\t' | sed -e 's/"//g'
204 if [[ ${1} == 'pairs' ]]; then
205 echo "${simple_json}" | jq -r "$query" | sed -e 's/\t//g'
207 echo "${simple_json}" | jq -r "$query"
213 data=$(print_structured '@csv' 1)
220 if $print_header; then
221 echo "<tr><th>${d//,/<\/th><th>}</th></tr>"
225 echo "<tr><td>${d//,/</td><td>}</td></tr>"
234 die "To be implemented"
238 # Scan the network for Elgato devices
240 readarray -t avahi < <(avahi-browse -d local _elg._tcp --resolve -t -p | grep -v "^\+")
254 default_light_properties
256 for l in "${avahi[@]}"; do
257 IFS=';' read -ra data <<<"$l" # split line into array
259 # Gather information about the light
260 device=$(echo "${data[3]}" | sed -e 's/\\032/ /g') # fix avahi output
262 [[ ${data[7]} =~ fe80 ]] && ipv6=${data[7]} || ipv4=${data[7]}
264 txt=$(eval echo "${data[9]}") # eval to strip quotes
265 [[ $txt =~ mf=([^[[:space:]]*]*) ]] && manufacturer=${BASH_REMATCH[1]}
266 [[ $txt =~ id=([^[[:space:]]*]*) ]] && mac=${BASH_REMATCH[1]}
267 [[ $txt =~ md=.+[[:space:]]([^[[:space:]]*]*)[[:space:]]id= ]] && sku=${BASH_REMATCH[1]}
269 # Get information from the light
270 url="http://$ipv4:$port"
272 declare protocol="--ipv4"
273 if [[ $ipv4 == "N/A" ]]; then
274 # Workaround: Ignoring ipv6 as Elgato miss-announces addressing and is not accepting requests
275 # properly for v6. Will not change to filter only on ipv4 from avahi, as that can cause us to only end
276 # up with an ipv6 address even though it was announced as ipv4, which in turn means we cannot communicate.
278 # Remove above and uncomment below if a future update fixes ipv6 announcement and requests
280 #url="http://[$ip]:$port"
283 cfg=$(eval "${call} GET $protocol ${url}${settings}") >/dev/null
284 info=$(eval "${call} GET $protocol ${url}${accessory_info}") >/dev/null
285 light=$(eval "${call} GET $protocol ${url}${devices}") >/dev/null
288 --arg dev "$device" \
289 --arg hn "$hostname" \
292 --argjson port "$port" \
293 --arg mf "$manufacturer" \
297 --argjson cfg "$cfg" \
298 '{device: $dev, manufacturer: $mf, hostname: $hn, url: $url, ipv4: $ipv4, ipv6: $ipv6,
299 port: $port, mac: $mac, sku: $sku, settings: $cfg}')
301 # Store the light as json and merge info + light into base object
302 lights["$device"]=$(echo "$info $light $json" | jq -s '. | add')
304 # Reset for next light as we are processing the last avahi line
305 default_light_properties
310 # Manage user parameters
313 # Make sure dependencies are installed
314 dependencies avahi-browse curl notify-send jq
318 # Fail if we cannot find lights
319 [[ ${#lights[@]} -eq 0 ]] && die "No lights found"
330 -?*) die "Unknown action" ;;