4 trap destroy SIGINT SIGTERM ERR EXIT
7 temp_file="/tmp/elgatokeylights"
8 script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd -P)
9 icon="${script_dir}/assets/elgato.png"
14 declare action="usage"
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
28 if [ $silent -eq 0 ]; then
29 notify-send -i "$icon" "Key Light Controller" "$1"
40 rm "$temp_file" 2>/dev/null
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.
52 list List available lights
53 status Get state of lights
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
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
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
83 # default values of variables set from params
92 -p | --pretty) pretty=1 ;;
93 -v | --verbose) set -x ;;
94 -s | --silent) silent=1 ;;
99 -?*) die "Unknown option: $1" ;;
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]}"
120 if ! command -v $var &>/dev/null; then
121 die "Dependency $var was not found, please install and try again"
126 default_light_properties() {
127 # Default values for json type enforcement
145 t=$(eval echo "'.[] | select($target)'")
147 for l in "${!lights[@]}"; do
148 json+="${lights[$l]},"
151 lights_json=$(echo "[${json%,}]" | jq -c "$t")
158 # Mange user requested output format
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" ;;
170 # TODO: Evaluate adding jq filtering as filter argument
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) })'
180 # Manage pretty printing
181 if [[ $pretty -eq 1 ]]; then
182 echo "${1-}" | jq "$query"
184 echo "${1-}" | jq -c -M "$query"
196 die "To be implemented"
201 die "To be implemented"
205 # Scan the network for Elgato devices
206 avahi-browse -d local _elg._tcp --resolve -t | grep -v "^\+" >"$temp_file"
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"
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.
252 # Remove above and uncomment below if a future update fixes ipv6 announcement and requests
254 #url="http://[$ip]:$port"
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" \
267 --argjson port "$port" \
268 --arg mf "$manufacturer" \
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
285 # Manage user parameters
288 # Make sure dependencies are installed
289 dependencies avahi-browse curl notify-send jq
293 # Fail if we cannot find lights
294 [[ ${#lights[@]} -eq 0 ]] && die "No lights found" 2
301 list) output "${lights_json}" ;;
305 -?*) die "Unknown action" ;;