#!/bin/sh #set -x # # huetil: script for interacting with the philips hue light system # for Asuswrt-merlin routers and Raspberry Pi's # # Author: John Grana jgranaroc@gmail.com # a rewite of a scripts based on the hue.sh by Harald van der Laan # with a few other bits from open source # version : v1.2.20 # date : 1/7/2024 # # inplemented features: # - turn on/off a hue lightbulb or group # - change the saturation of the lightbulb or group # - change the brightness of the lightbulb or group # - change the hue of a lightbulb or group # - change the xy gamut of a lightbulb or group # - change the ct temperature of a lightbulb or group # - change the color of a light # - show lights, groups, scenes or all connected to the Hue Bridge # - save and restore attributes of lights or groups. # - display information about the hue bridge # - display a list of known colors by name (i.e. yellow) # - convert a color name to it's x and y values # - support "action" commands from the Hue API # # usage: huetil command {arguments} # # # Commands: # # set ... # # action can be: # on or off - turn a light or group on or off # bri - set the brightness level to value # sat - set the saturation level to value # hue - set the hue to value # ct - set color temperature to value # xy - set color based on x and y values (x:y) # rgb - set color based on RGB values # color - set the color based on a color name # # actions can be all on one line. # # lights or groups can be defined by either their ID or by name. # # show lights: huetil show # will display the name and ID # of the lights, groups or scenes # show colors: huetil colors # display the list of colors (from Gammut C) that huetil knows how to send # save/restore: huetil save|restore {filename} # save the present settings of a light or group or all; restore a saved setting. # if no optional filename is given, huetil saves the settings file in the config directory # # convert : huetil convert to x and y values # convert one of the known colors to their X and Y values # # info : huetil info {bridge}| n # display information about the Hue bridge with "bridge" argument # or attributes such as state, color, brightness of a light or group # help : huetil help # install : huetil install # uninstall : huetil uninstall # define based on OS setOS() { OSis=$(uname -o) case $OSis in ASUSWRT-Merlin) OS="merlin" ;; "GNU/Linux") OS="linux" ;; *) printf "Your OS reports %s, you need to install huetil on either an Asuswrt-Merlin based router or Raspberry Pi\\n" $OSis printf "Bailing...\\n\\n" exit 1 esac } # global variables SCRIPTNAME="huetil" SCRIPTVER="1.2.20" HUEHASH="/tmp/huetil.hash" cleanuphue() { rm -f $HUEHASH rm -f /tmp/pprinttmp rm -f /tmp/jsontmp rm -f /tmp/huetmp rm -f /tmp/lites exit } trap cleanuphue INT TERM EXIT # this needs to be done first thing - figure out what OS we are running on setOS if [ "$OS" = "merlin" ] ; then SCRIPTLOC="/jffs/scripts" SCRIPTDIR="/jffs/addons/$SCRIPTNAME" SCRIPTCONF="$SCRIPTDIR/huetil.conf" CURLAP="/usr/sbin/curl" else SCRIPTLOC="/usr/local/sbin/" SCRIPTDIR="/$HOME/.config/$SCRIPTNAME" SCRIPTCONF="$SCRIPTDIR/huetil.conf" CURLAP="/usr/bin/curl" fi HUERESPONSE="$SCRIPTDIR/hueresponse" Xval=0 Yval=0 debug=0 # functions # help/usage usage() { echo "" echo "huetil Version $SCRIPTVER" echo "" echo " usage: huetil {command arguments}" echo "" echo " set ..." echo " arg can be..." echo " : huetil set light|group n {color}" echo " sat : huetil set light|group n sat <0-255>" echo " bri : huetil set light|group n bri <0-255>" echo " hue : huetil set light|group n hue <0-65535>" echo " xy : huetil set light|group n xy <0.0-1.0>:<0.0-1.0>" echo " rgb : huetil set light|group n rgb <0-255> <0-255> <0-255>" echo " ct : huetil set light|group n ct <153-500>" echo " color temp can also be Cool, Daylight, Natural, Warm or Candle" echo " color : huetil set light|group n color " echo " trans : huetil set light|group n trans <1-300>" echo "" echo " A light or group (n) can be defined by either their ID or by name." echo "" echo " play " echo " arg can be..." echo " effect : huetil play light|group n effect colorloop|none" echo " breathe : huetil play light|group n breathe {long}" echo " bri-inc : huetil play light|group n bri-inc <-255-255>" echo " hue-inc : huetil play light|group n hue-inc <-65534-65535>" echo " sat-inc : huetil play light|group n sat-inc <-255-255>" echo " ct-inc : huetil play light|group n ct-inc <-65534-65535>" echo "" echo "getcolor : huetil getcolor light n" echo "info : huetil info {bridge}|light|group n" echo "scene : huetil scene scenename group" echo "" echo "save light/group settings : huetil save n {filename}" echo "restore light/group settings : huetil restore n {filename}" echo "show lights/groups/scenes : huetil show " echo "colors - list colors : huetil colors" echo "showxy : huetil showxy light|group n" echo "convert - color to xy : huetil convert " echo "" echo "help (this screen) : huetil help" echo "install : huetil install" echo "uninstall : huetil uninstall" echo "version : huetil version" echo "update : huetil update" echo "set bridge username/API : huetil sethueun" echo "===============================================================================================" exit 1 } # print if verbose is set hueprint() { if [ "$hueVerbose" -eq "1" ]; then echo "$1" fi } # make sure there is a huetil.conf file and a reasonable IP address and API for the bridge checkapi() { if [ -f "$SCRIPTCONF" ]; then . "$SCRIPTCONF" hueBaseUrl="http://${hueBridge}:${huePort}/api/${hueApiHash}" else echo "[-] huetil: No $SCRIPTCONF found" echo "[-] huetil: Please run huetil install and try again." exit 1 fi if [ "$hueBridge" = "" ]; then echo "[-] huetil: No Hue Bridge IP address. Please edit your hueBridge variable in $SCRIPTCONF" exit 1 fi if [ "$hueApiHash" = "" ]; then echo "[-] huetil: Failed to get IDs from API! Please edit your api hash variable in $SCRIPTCONF" exit 1 fi } checkhueresp() { if [ $(grep -c "error" $HUERESPONSE) -gt 0 ]; then echo -n "[-] huetil: Command failed: " echo $(awk 'BEGIN { FS = "\"" }; { print $12 }' $HUERESPONSE) exit 1 fi } # display the lights or groups or scenes (or all of them) defined on the Hue bridge # # displays the name and id showhuestuff() { if [ -z "$1" ]; then usage exit 1 fi case "$1" in lights) showhuelights show echo "" exit 0 ;; groups) showhuegroups show echo "" exit 0 ;; scenes) echo "Scenes:" echo "" showhuescenes echo "" exit 0 ;; all) showhuelights show echo -n "Next...(press Enter)" read a showhuegroups show echo -n "Next...(press Enter)" read a echo "Scenes:" showhuescenes ;; *) usage exit 1 ;; esac } # the scipts that gets the lights on the bridge - name and ID number showhuelights() { local pprinttmp=/tmp/pprinttmp local jsontmp=/tmp/jsontmp $CURLAP -S --max-time ${hueTimeOut} --silent -o "$SCRIPTDIR/lights.json" --request GET ${hueBaseUrl}/lights if [ ${?} -ne 0 ]; then echo "[-] huetil: Failed to send show command." exit 1 fi printf "Light\t\tID\\n" > $pprinttmp cat "$SCRIPTDIR/lights.json" | jq -r 'keys[] as $k | "\(.[$k] | .name)\t\t\($k)"' | sed 's/ /_/g' > "$jsontmp" cat "$jsontmp" | column -t > "$SCRIPTDIR/lights" if [ "$1" = "show" ]; then cat $jsontmp >> $pprinttmp cat $pprinttmp | column -t\t > $jsontmp len=$(cat $jsontmp | wc -L ) i=0 lonlin="" while [ $i -lt $len ]; do lonlin=$lonlin- i="$((i + 1))" done lonlin=$lonlin- awk -v v="$lonlin" 'NR==2{print v}1' $jsontmp rm $pprinttmp rm $jsontmp fi } # the scipts that gets the groups on the bridge - name and ID number showhuegroups() { local pprinttmp=/tmp/pprinttmp local jsontmp=/tmp/jsontmp $CURLAP -S --max-time ${hueTimeOut} --silent -o "$SCRIPTDIR/groups.json" --request GET ${hueBaseUrl}/groups if [ ${?} -ne 0 ]; then echo "[-] huetil: Failed to send show command." exit 1 fi printf "Group\tID\tLights\\n" > $pprinttmp cat "$SCRIPTDIR/groups.json" | jq -r 'keys[] as $k | "\(.[$k] | .name)\t\($k)\t\(.[$k] | .lights)"' | sed 's/ /_/g' | tr -d \" > $jsontmp cat "$jsontmp" | column -t > "$SCRIPTDIR/groups" if [ "$1" = "show" ]; then cat $jsontmp >> $pprinttmp cat $pprinttmp | column -t\t > $jsontmp len=$(cat $jsontmp | wc -L ) i=0 lonlin="" while [ $i -lt $len ]; do lonlin=$lonlin- i="$((i + 1))" done lonlin=$lonlin- awk -v v="$lonlin" 'NR==2{print v}1' $jsontmp rm $pprinttmp rm $jsontmp fi } # the scipts that gets the scenes on the bridge - name and group assigned to showhuescenes() { local pprinttmp=/tmp/pprinttmp local jsontmp=/tmp/jsontmp checkscenes if [ $(cat $SCRIPTDIR/scenes| wc -l ) -gt 1 ]; then jq -c '.[] | .name,.group,.lights' "$SCRIPTDIR/scenes.json" | sed 's/ /_/g' > $jsontmp echo "Scene GroupID Lights" > $pprinttmp while read -r line1 && scene="$line1"; do read line2 && group=$line2 read line3 && lights="$line3" if [ ! "$group" = "null" ]; then printf "%s\t%s\t%s\\n" "$scene" "$group" "$lights" >> $pprinttmp fi done < $jsontmp sed 's/"//g' $pprinttmp | column -t\t > $jsontmp len=$(cat $jsontmp | wc -L ) i=0 lonlin="" while [ $i -lt $len ]; do lonlin=$lonlin- i="$((i + 1))" done lonlin=$lonlin- awk -v v="$lonlin" 'NR==2{print v}1' $jsontmp rm $pprinttmp rm $jsontmp else echo "No scenes found" fi } # grab information about scenes for use later checkscenes() { somedevices "scenes" $CURLAP -S --max-time ${hueTimeOut} --silent -o "$SCRIPTDIR/scenes.json" --request GET ${hueBaseUrl}/scenes if [ ${?} -ne 0 ]; then echo "[-] huetil: Failed to send show command" exit 1 fi if [ -z "$SCRIPTDIR/scenes.json" ]; then echo "huetil: no scenes detected" exit fi if [ $(jq '.' $SCRIPTDIR/scenes.json | wc -l ) -gt 1 ]; then cat "$SCRIPTDIR/scenes.json" | jq -r 'keys[] as $k | "\($k)\t\(.[$k] | .name + "\t" + .group )"' | sed "s/ /_/g" | awk '{if ($3) print $2"\t"$3"\t"$1;}' | column -t > "$SCRIPTDIR/scenes" else echo "No scenes found" fi } # turn on a scene for a group # $1 = group # $2 = scene hueSceneOn() { checkscenes hueBaseUrl="http://${hueBridge}:${huePort}/api/${hueApiHash}" local hueGroup=${2} local hueScene=${1} if ! echo "$hueGroup" | grep -q "^[0-9]" then huenum=$(name2num "group" "$hueGroup") else huenum="$hueGroup" fi if [ ! $(grep -c "$1" "$SCRIPTDIR/scenes") -gt 0 ]; then echo "[-] huetil: ${hueScene} : is not a scene." exit fi hueSceneId=$(cat "$SCRIPTDIR/scenes" | grep "$huenum" | grep "$hueScene" | awk '{ print $3}') if [ -z "$hueSceneId" ]; then echo "[-] huetil: ${hueScene} : is not available for the group ${hueGroup}." exit 1 fi hueUrl="${hueBaseUrl}/groups/${huenum}/action" hueJsonData="{\"on\":true,\"scene\":\"$hueSceneId\"}" $CURLAP --max-time ${hueTimeOut} --silent --request PUT --data ${hueJsonData} ${hueUrl} > $HUERESPONSE if [ ${?} -ne 0 ]; then echo "[-] huetil: Failed to send scene command to ${hueGroup}/${hueScene}." exit 1 fi checkhueresp hueprint "[+] huetil: scene sent successfully to ${hueGroup}/${hueScene}." } # set the devices brightness level (between 0 and 255) hueSetBri() { if ! echo "$1" | grep -q "^[0-9]" then echo "[-] huetil: Brightness value: $1 is not a number." exit 1 fi if [ $1 -lt 0 ] || [ $1 -gt 255 ]; then echo "[-] huetil: Brightness value must be between 0 and 255." exit 1 fi jsonData="$jsonData"'"bri": '"$1," } # set the devices saturation level (between 0 and 255) hueSetSat() { if ! echo "$1" | grep -q "^[0-9]" then echo "[-] huetil: Saturation value: $1 is not a number." exit 1 fi if [ $1 -lt 0 ] || [ $1 -gt 255 ]; then echo "[-] huetil: Saturation value must be between 0 and 255." exit 1 fi jsonData="$jsonData"'"sat": '"$1," } # set the hue color of the device (between 0 and 65535) hueSetHue() { if ! echo "$1" | grep -q "^[0-9]" then echo "[-] huetil: Hue value: $1 is not a number." exit 1 fi if [ $1 -lt 0 ] || [ $1 -gt 65535 ]; then echo "[-] huetil: Hue value must be between 0 and 65535." exit 1 fi jsonData="$jsonData"'"hue": '"$1," } # set color temperature of the device - fairly narrow range hueSetCt() { if ! echo "$1" | grep -q "^[0-9]" then case "$1" in Cool|cool) colortemp=153 ;; Daylight|daylight) colortemp=181 ;; Natural|natural) colortemp=225 ;; Warm|warm) colortemp=370 ;; Candle|candle) colortemp=455 ;; *) echo "[-] huetil: $1 is not a number or valid color temp." exit 1 ;; esac else colortemp=$1 fi if [ $colortemp -lt 153 ] || [ $colortemp -gt 500 ]; then echo "[-] huetil: Ct value must be between 153 and 500." exit 1 fi jsonData="$jsonData"'"ct": '"$colortemp," } # set the color of the device based on chromacity coordinates (x and y) hueSetXY() { if [ "$debug" = "1" ]; then printf "xval: %s yval: %s\\n" $1 $2 read a fi if ! echo "$1" | grep -q "^[0-9]" then echo "[-] huetil: x $1 is not a number." exit 1 fi if [ $(echo "$1 != 0"| bc) -eq 0 ] || [ $(echo "$1 > 1.0"|bc) -gt 0 ] ; then echo "[-] huetil: x must be less than 1 and greater than 0" exit fi if ! echo "$2" | grep -q "^[0-9]" then echo "[-] huetil: y $1 is not a number." exit 1 fi if [ $(echo "$2 != 0" | bc) -eq 0 ] || [ $(echo "$2>1.0"|bc) -gt 0 ] ; then echo "[-] huetil: y must be less than 1 and greater than 0" exit fi jsonData="$jsonData"'"xy":['"$1,$2]," } setTtime() { if ! echo "$1" | grep -q "^[0-9]" then echo "[-] huetil: Transition time value: $1 is not a number." exit 1 fi if [ $1 -lt 0 ] || [ $1 -gt 300 ]; then echo "[-] huetil: Transition time must be between 0 and 300." exit 1 fi nTtime="$1" } # cycle through a range of hue values, time between changes based on .conf CycleDelay hueCycle() { local hueType=${1} local hueTypeNumber=${2} local hueState1=${3} local hueState2=${4} somedevices ${hueType} if ! echo "${hueTypeNumber}" | grep -q "^[0-9]" then echo "[-] huetil: ${hueType} number: ${hueTypeNumber} is not a number." exit 1 fi case ${hueType} in light) hueUrl="${hueBaseUrl}/lights/${hueTypeNumber}/state" ;; group) hueUrl="${hueBaseUrl}/groups/${hueTypeNumber}/action" ;; *) echo "[-] huetil: The cycle device mode is not light or group."; exit 1 ;; esac if ! echo "${hueState1}" | grep -q "^[0-9]" then echo "[-] huetil: Cycle value1: ${hueState1} is not a number." exit 1 fi if ! echo "${hueState2}" | grep -q "^[0-9]" then echo "[-] huetil: Cycle value2: ${hueState2} is not a number." exit 1 fi if [ ${hueState1} -lt 0 ] || [ ${hueState1} -gt 65535 ]; then echo "[-] huetil: Cycle value1 must be between 0 and 65535." exit 1 fi if [ ${hueState2} -lt 0 ] || [ ${hueState2} -gt 65535 ]; then echo "[-] huetil: Cycle value2 must be between 0 and 65535." exit 1 fi if [ ${hueState1} -ge ${hueState2} ]; then echo "[-] huetil: Cycle value1 must be smaller then cycle value2." exit 1 fi $CURLAP --max-time ${hueTimeOut} --silent --request PUT --data '{"on":true,"bri":254,"hue":54000,"sat":255}' ${hueUrl} &> /dev/null if [ ${?} -ne 0 ]; then echo "[-] huetil: Failed to send reset command to ${hueType}/${hueTypeNumber}." fi lcount=${hueState1} while [ "$lcount" -lt "$hueState2" ]; do $CURLAP --max-time ${hueTimeOut} --silent --request PUT --data '{"hue":'${lcount}',"transitiontime":'${Ttime}'}' ${hueUrl} &> /dev/null if [ ${?} -ne 0 ]; then echo "[-] huetil: Failed to send cycle command to ${hueType}/${hueTypeNumber}, Hue is: ${hueValue}." else hueprint "[ ] Hue: Cycle command successfully send to ${hueType}/${hueTypeNumber}, Hue is: ${hueValue}." fi if [ $hueCycleDelay -eq 0 ]; then usleep 250000 else sleep $hueCycleDelay fi lcount="$((lcount + hueCycleStep))" done } # given a .json formated file ($1), return the value of the key ($2) pullarg() { cat "$1" | sed /{/d | sed /}/d | sed 's/,//' | sed 's/"//g' | sed '/effect/d' | awk 'BEGIN { FS = ":" }; { print $1 $2 }' | grep -m 1 "$2" | awk '{ print $2 }' } # retrieve attributes like brightness, hue, color etc of all the lights and groups and store for future restoring # saved files are in SCRIPTCONF/saved hueSaveAll() { if [ $(huenumdevice light) -gt 0 ]; then echo "Saving all lights present state..." showhuelights cat /dev/null > /tmp/litelist while IFS='' read -r light; do lit=$(echo $light | awk '{print $2}') echo -n "$lit " >> /tmp/litelist done < "$SCRIPTDIR/lights" for i in $(cat /tmp/litelist); do hueattr "light" "$i" "save" done fi if [ $(huenumdevice group) -gt 0 ]; then echo "Saving all groups present state..." showhuegroups echo while read line; do lit=$(echo $line | awk '{print $2}') hueattr "group" $lit "save" done < "$SCRIPTDIR/groups" fi echo rm -f /tmp/lites echo "Done." } # routine to manipulate the various attributes of the device # hueattr() { hueType=${1} hueTypeNumber=${2} hueCmnd=${3} hueFileName=${4} # special case for the Hue Hub if [ "${hueTypeNumber}" = "info" ] && [ "${hueType}" = "bridge" ]; then gethueconfig exit fi if ! echo "$hueTypeNumber" | grep -q "^[0-9]" then huenum=$(name2num "$hueType" "$hueTypeNumber") hueTypeNumber=$huenum fi devexist $hueType $hueTypeNumber if [ "${hueCmnd}" = "restore" ]; then case ${hueType} in light) hueUrl="${hueBaseUrl}/lights/${hueTypeNumber}/state" ;; group) hueUrl="${hueBaseUrl}/groups/${hueTypeNumber}/action" ;; *) echo "[-] huetil: The hue type not a light or group."; exit 1 ;; esac else case ${hueType} in light) hueUrl="${hueBaseUrl}/lights/${hueTypeNumber}" ;; group) hueUrl="${hueBaseUrl}/groups/${hueTypeNumber}" ;; *) echo "[-] huetil: The hue type not a light or group."; exit 1 ;; esac fi if [ "$debug" -eq 1 ]; then echo ${hueUrl} read a fi case "${hueCmnd}" in color) if [ ! "${hueType}" = "light" ]; then echo "huetil: getcolor only works with lights" exit fi $CURLAP --max-time ${hueTimeOut} --silent --request GET ${hueUrl} | jq -r '.' > $HUERESPONSE if [ ${?} -ne 0 ]; then echo "[-] huetil: Failed to send command to bridge" exit 1 fi checkhueresp ;; info) $CURLAP --max-time ${hueTimeOut} --silent --request GET ${hueUrl} | jq -r '.' > $HUERESPONSE if [ ${?} -ne 0 ]; then echo "[-] huetil: Failed to send command to bridge" exit 1 fi checkhueresp ;; save) case $hueType in light) $CURLAP --max-time ${hueTimeOut} --silent --request GET ${hueUrl} | jq -r '.state' > $HUERESPONSE if [ ${?} -ne 0 ]; then echo "[-] huetil: Failed to send command to bridge" exit 1 fi checkhueresp ;; group) $CURLAP --max-time ${hueTimeOut} --silent --request GET ${hueUrl} | jq -r '.action' > $HUERESPONSE if [ ${?} -ne 0 ]; then echo "[-] huetil: Failed to send command to bridge" exit 1 fi checkhueresp ;; *) echo "huetil: save what? light or group or all" exit ;; esac for i in on colormode effect alert reachable do sed -i "/$i/d" $HUERESPONSE done if [ "$hueFileName" = "" ]; then savename="$SCRIPTDIR/saved/${hueType}_${hueTypeNumber}.state" else savename="$hueFileName" fi cat $HUERESPONSE | tr -d '\n' | sed 's/,}/}/g' > $savename ;; restore) if [ "$hueFileName" = "" ]; then savename="$SCRIPTDIR/saved/${hueType}_${hueTypeNumber}.state" else savename="$hueFileName" fi if [ ! -f "$savename" ]; then echo "[-] huetil: Saved state file $savename doesn't exist" echo "[-] huetil: Save it using: huetil save $hueType $hueTypeNumber" exit 1 fi if [ "${hueType}" = "light" ]; then hueUrlcheck="${hueBaseUrl}/lights/${hueTypeNumber}" if [ "$($CURLAP --max-time ${hueTimeOut} --silent --request GET ${hueUrlcheck} | jq -r '.state.on')" = "false" ]; then echo "[-] huetil: Restoreing light ${hueTypeNumber} requires the light to be on" echo "[-] huetil: Turn the light on (huetil light $hueTypeNumber turn on)" exit 1 fi fi hueJsonData="$(cat $savename)" $CURLAP --max-time ${hueTimeOut} --silent --request PUT --data "${hueJsonData}" "${hueUrl}" > $HUERESPONSE if [ ${?} -ne 0 ]; then echo "[-] huetil: Failed to send command to bridge." exit 1 fi checkhueresp exit 0 ;; esac if [ ! "$hueCmnd" = "save" ]; then if [ "$hueType" = "group" ]; then X=$(cat $HUERESPONSE | jq -M '.action.xy' | sed -n '2p' | tr -d ',') Y=$(cat $HUERESPONSE | jq -M '.action.xy' | sed -n '3p' ) else X=$(cat $HUERESPONSE | jq -M '.state.xy' | sed -n '2p' | tr -d ',') Y=$(cat $HUERESPONSE | jq -M '.state.xy' | sed -n '3p' ) fi fi if [ "${hueCmnd}" = "color" ]; then printf "X=$X Y=$Y " x="$X" y="$Y" z=$(pullarg $HUERESPONSE "bri") # set brightness to what the light/group is now xy2rgb rgb2hex prtrgb echo elif [ "${hueCmnd}" = "info" ]; then hname="$(jq -r '.name' $HUERESPONSE)" hbri=$(pullarg $HUERESPONSE "bri") hhue=$(pullarg $HUERESPONSE "hue") hsat=$(pullarg $HUERESPONSE "sat") hct=$(pullarg $HUERESPONSE "ct") cmode=$(pullarg $HUERESPONSE "colormode") if [ "$hueType" = "light" ]; then lstate=$(pullarg $HUERESPONSE "on") htype="Light" else lstate=$(pullarg $HUERESPONSE "all_on") htype="Group" fi if [ "$lstate" = "true" ]; then ison="on" else ison="off" fi prodid=$(pullarg $HUERESPONSE "productid") modelid=$(pullarg $HUERESPONSE "modelid") swversion=$(pullarg $HUERESPONSE "swversion") maxlumen=$(pullarg $HUERESPONSE "maxlumen") printf "\\n%s %s (%s) is %s. Brightness: %s Hue: %s Saturation: %s\\n" $htype $hueTypeNumber "$hname" $ison $hbri $hhue $hsat printf " Color Temp: %s Color Mode: %s X: %s Y: %s\\n" $hct $cmode $X $Y if [ "$hueType" = "group" ]; then hname="$(jq -r '.name' $HUERESPONSE | sed 's/ /_/g')" printf "\\nLights in Group $hname: " for i in $(grep -w "$hname\s" "$SCRIPTDIR/groups" | awk '{print $3}' | tr -d '\[' | tr -d '\]' | sed 's/,/ /g') do lit=$(num2name light $i) printf " $lit ($i)," done printf "\\b \\n\\n" else x="$X" y="$Y" z=$(pullarg $HUERESPONSE "bri") printf " RGB: " xy2rgb rgb2hex prtrgb echo productid=$(pullarg $HUERESPONSE "productid") modelid=$(pullarg $HUERESPONSE "modelid") swversion=$(pullarg $HUERESPONSE "swversion") maxlumen=$(pullarg $HUERESPONSE "maxlumen") printf "\\nProduct ID: %s Model: %s Max Lumens: %s SW Version: %s\\n\\n" $productid $modelid $maxlumen $swversion fi fi } # query and return for the number of defined lights, groups or scenses defined on the bridge huenumdevice() { case "$1" in light) hueUrl="${hueBaseUrl}/lights" ;; group) hueUrl="${hueBaseUrl}/groups" ;; scenes) hueUrl="${hueBaseUrl}/scenes" ;; *) echo "[-] huetil: The hue device mode is not lights, groups or scenes."; exit 1 ;; esac $CURLAP --max-time ${hueTimeOut} --silent --request GET ${hueUrl} | jq -C '.[].name' > $HUERESPONSE if [ ${?} -ne 0 ]; then echo "[-] huetil: Failed to send command to bridge." exit 1 fi checkhueresp numdev=$(wc -l $HUERESPONSE | awk '{print $1}') echo $numdev } # show all the lights, groups and scenes the bride knows about hueshowdevices() { for i in light group scenes; do echo $i numd=$(huenumdevice $i) printf "Number of %s: %s\\n" $i $numd done } # set variable numdev to the number of devics known by the bridge # exit if none defined somedevices() { numdev=$(huenumdevice "$1") if [ $numdev -gt 0 ]; then return else printf "Hue Bridge reports no $1...\\n" exit fi } # function for trying different scenarios. Decided to leave in for now tstmode() { hueType=${1} hueTypeNumber=${2} hueState=${3} case ${hueType} in lights) hueUrl="${hueBaseUrl}/lights" ;; groups) hueUrl="${hueBaseUrl}/groups" ;; scenes) hueUrl="${hueBaseUrl}/scenes" ;; *) echo "[-] huetil: The hue device mode is not lights, groups or scenes."; exit 1 ;; esac case ${hueState} in on) hueJsonData="{\"effect\":\"colorloop\"}" ;; off) hueJsonData="{\"effect\":\"none\"}" ;; restore) hueJsonData="$(cat $SCRIPTDIR/saved/${hueType}_${hueTypeNumber}.state)" ;; *) echo "[-] huetil: The hue device effect can only be turned on or off."; exit 1 ;; esac if [ "$debug" -eq 1 ]; then echo ${hueUrl} echo ${hueJsonData} echo ${hueColor} read a fi $CURLAP --max-time ${hueTimeOut} --silent --request GET ${hueUrl} | jq -C '.[]' > $HUERESPONSE if [ ${?} -ne 0 ]; then echo "[-] huetil: Failed to send command to bridge." exit 1 fi checkhueresp cat $HUERESPONSE } # hue_color # snippet from: # https://raw.githubusercontent.com/Josef-Friedrich/Hue-shell/master/base.sh # Convert color strings to hue values. # $1: COLOR_NAME # to show x y values: # $1: show $2 COLOR_NAME # internal $1: convert $2 COLOR_NAME # populate Xval and Yval # # Always uses Gamut C - most common # hue_color() { SHOWXY=0 if [ "$1" = "show" ]; then SHOWXY=1 lookcolor="$2" elif [ "$1" = "convert" ]; then SHOWXY=2 lookcolor="$2" else lookcolor="$1" fi # Gamut C (most typical) case "$lookcolor" in start-color-list) ;; alice-blue) COLOR='-x 0.3088 -y 0.3212' ;; antique-white) COLOR='-x 0.3548 -y 0.3489' ;; aqua) COLOR='-x 0.1607 -y 0.3423' ;; aquamarine) COLOR='-x 0.2138 -y 0.4051' ;; azure) COLOR='-x 0.3059 -y 0.3303' ;; beige) COLOR='-x 0.3402 -y 0.356' ;; bisque) COLOR='-x 0.3806 -y 0.3576' ;; black) COLOR='-x 0.153 -y 0.048' ;; blanched-almond) COLOR='-x 0.3695 -y 0.3584' ;; blue) COLOR='-x 0.153 -y 0.048' ;; blue-violet) COLOR='-x 0.251 -y 0.1056' ;; brown) COLOR='-x 0.6399 -y 0.3041' ;; burlywood) COLOR='-x 0.4236 -y 0.3811' ;; cadet-blue) COLOR='-x 0.2211 -y 0.3328' ;; chartreuse) COLOR='-x 0.2505 -y 0.6395' ;; chocolate) COLOR='-x 0.6009 -y 0.3684' ;; coral) COLOR='-x 0.5763 -y 0.3486' ;; cornflower) COLOR='-x 0.1905 -y 0.1945' ;; cornsilk) COLOR='-x 0.3511 -y 0.3574' ;; crimson) COLOR='-x 0.6508 -y 0.2881' ;; cyan) COLOR='-x 0.1607 -y 0.3423' ;; d65) COLOR='-x 0.3138 -y 0.3310' ;; dark-blue) COLOR='-x 0.153 -y 0.048' ;; dark-cyan) COLOR='-x 0.1607 -y 0.3423' ;; dark-goldenrod) COLOR='-x 0.5214 -y 0.4361' ;; dark-gray) COLOR='-x 0.3227 -y 0.329' ;; dark-green) COLOR='-x 0.17 -y 0.7' ;; dark-khaki) COLOR='-x 0.4004 -y 0.4331' ;; dark-magenta) COLOR='-x 0.3833 -y 0.1591' ;; dark-olive-green) COLOR='-x 0.3475 -y 0.5047' ;; dark-orange) COLOR='-x 0.5921 -y 0.3831' ;; dark-orchid) COLOR='-x 0.2986 -y 0.1341' ;; dark-red) COLOR='-x 0.692 -y 0.308' ;; dark-salmon) COLOR='-x 0.4837 -y 0.3479' ;; dark-sea-green) COLOR='-x 0.2924 -y 0.4134' ;; dark-slate-blue) COLOR='-x 0.2206 -y 0.1484' ;; dark-slate-gray) COLOR='-x 0.2239 -y 0.3368' ;; dark-turquoise) COLOR='-x 0.1605 -y 0.3366' ;; dark-violet) COLOR='-x 0.2824 -y 0.1104' ;; deep-pink) COLOR='-x 0.5445 -y 0.2369' ;; deep-sky-blue) COLOR='-x 0.158 -y 0.2379' ;; dim-gray) COLOR='-x 0.3227 -y 0.329' ;; dodger-blue) COLOR='-x 0.1559 -y 0.1599' ;; firebrick) COLOR='-x 0.6621 -y 0.3023' ;; floral-white) COLOR='-x 0.3361 -y 0.3388' ;; forest-green) COLOR='-x 0.1984 -y 0.6746' ;; fuchsia) COLOR='-x 0.3833 -y 0.1591' ;; gainsboro) COLOR='-x 0.3227 -y 0.329' ;; ghost-white) COLOR='-x 0.3174 -y 0.3207' ;; gold) COLOR='-x 0.4871 -y 0.4618' ;; goldenrod) COLOR='-x 0.5125 -y 0.4428' ;; gray) COLOR='-x 0.3227 -y 0.329' ;; grow-lightA) COLOR='-x 0.192 -y 0.213' ;; grow-lightB) COLOR='-x 0.639 -y 0.330' ;; web-gray) COLOR='-x 0.3227 -y 0.329' ;; green) COLOR='-x 0.17 -y 0.7' ;; web-green) COLOR='-x 0.17 -y 0.7' ;; green-yellow) COLOR='-x 0.3221 -y 0.5857' ;; honeydew) COLOR='-x 0.316 -y 0.3477' ;; hot-pink) COLOR='-x 0.4682 -y 0.2452' ;; indian-red) COLOR='-x 0.5488 -y 0.3112' ;; indigo) COLOR='-x 0.2428 -y 0.0913' ;; ivory) COLOR='-x 0.3334 -y 0.3455' ;; judd) COLOR='-x 0.3067 -y 0.3108' ;; khaki) COLOR='-x 0.4019 -y 0.4261' ;; lavender) COLOR='-x 0.3085 -y 0.3071' ;; lavender-blush) COLOR='-x 0.3369 -y 0.3225' ;; lawn-green) COLOR='-x 0.2485 -y 0.641' ;; lemon-chiffon) COLOR='-x 0.3608 -y 0.3756' ;; light-blue) COLOR='-x 0.2621 -y 0.3157' ;; light-coral) COLOR='-x 0.5075 -y 0.3145' ;; light-cyan) COLOR='-x 0.2901 -y 0.3316' ;; light-goldenrod) COLOR='-x 0.3504 -y 0.3717' ;; light-gray) COLOR='-x 0.3227 -y 0.329' ;; light-green) COLOR='-x 0.2648 -y 0.4901' ;; light-pink) COLOR='-x 0.4112 -y 0.3091' ;; light-salmon) COLOR='-x 0.5016 -y 0.3531' ;; light-sea-green) COLOR='-x 0.1611 -y 0.3593' ;; light-sky-blue) COLOR='-x 0.214 -y 0.2749' ;; light-slate-gray) COLOR='-x 0.2738 -y 0.297' ;; light-steel-blue) COLOR='-x 0.276 -y 0.2975' ;; light-yellow) COLOR='-x 0.3436 -y 0.3612' ;; lime) COLOR='-x 0.17 -y 0.7' ;; lime-green) COLOR='-x 0.1972 -y 0.6781' ;; linen) COLOR='-x 0.3411 -y 0.3387' ;; magenta) COLOR='-x 0.3833 -y 0.1591' ;; maroon) COLOR='-x 0.5383 -y 0.2566' ;; web-maroon) COLOR='-x 0.692 -y 0.308' ;; medium-aquamarine) COLOR='-x 0.215 -y 0.4014' ;; medium-blue) COLOR='-x 0.153 -y 0.048' ;; medium-orchid) COLOR='-x 0.3365 -y 0.1735' ;; medium-purple) COLOR='-x 0.263 -y 0.1773' ;; medium-sea-green) COLOR='-x 0.1979 -y 0.5005' ;; medium-slate-blue) COLOR='-x 0.2179 -y 0.1424' ;; medium-spring-green) COLOR='-x 0.1655 -y 0.5275' ;; medium-turquoise) COLOR='-x 0.176 -y 0.3496' ;; medium-violet-red) COLOR='-x 0.5047 -y 0.2177' ;; midnight-blue) COLOR='-x 0.1616 -y 0.0802' ;; mint-cream) COLOR='-x 0.315 -y 0.3363' ;; misty-rose) COLOR='-x 0.3581 -y 0.3284' ;; moccasin) COLOR='-x 0.3927 -y 0.3732' ;; navajo-white) COLOR='-x 0.4027 -y 0.3757' ;; navy-blue) COLOR='-x 0.153 -y 0.048' ;; old-lace) COLOR='-x 0.3421 -y 0.344' ;; olive) COLOR='-x 0.4334 -y 0.5022' ;; olive-drab) COLOR='-x 0.354 -y 0.5561' ;; orange) COLOR='-x 0.5569 -y 0.4095' ;; orange-red) COLOR='-x 0.6731 -y 0.3222' ;; orchid) COLOR='-x 0.3688 -y 0.2095' ;; pale-goldenrod) COLOR='-x 0.3751 -y 0.3983' ;; pale-green) COLOR='-x 0.2675 -y 0.4826' ;; pale-turquoise) COLOR='-x 0.2539 -y 0.3344' ;; pale-violet-red) COLOR='-x 0.4658 -y 0.2773' ;; papaya-whip) COLOR='-x 0.3591 -y 0.3536' ;; peach-puff) COLOR='-x 0.3953 -y 0.3564' ;; peru) COLOR='-x 0.5305 -y 0.3911' ;; pink) COLOR='-x 0.3944 -y 0.3093' ;; plum) COLOR='-x 0.3495 -y 0.2545' ;; powder-blue) COLOR='-x 0.262 -y 0.3269' ;; purple) COLOR='-x 0.2725 -y 0.1096' ;; web-purple) COLOR='-x 0.3833 -y 0.1591' ;; rebecca-purple) COLOR='-x 0.2703 -y 0.1398' ;; red) COLOR='-x 0.692 -y 0.308' ;; rosy-brown) COLOR='-x 0.4026 -y 0.3227' ;; royal-blue) COLOR='-x 0.1649 -y 0.1338' ;; saddle-brown) COLOR='-x 0.5993 -y 0.369' ;; salmon) COLOR='-x 0.5346 -y 0.3247' ;; sandy-brown) COLOR='-x 0.5104 -y 0.3826' ;; sea-green) COLOR='-x 0.1968 -y 0.5047' ;; seashell) COLOR='-x 0.3397 -y 0.3353' ;; sienna) COLOR='-x 0.5714 -y 0.3559' ;; silver) COLOR='-x 0.3227 -y 0.329' ;; sky-blue) COLOR='-x 0.2206 -y 0.2948' ;; slate-blue) COLOR='-x 0.2218 -y 0.1444' ;; slate-gray) COLOR='-x 0.2762 -y 0.3009' ;; snow) COLOR='-x 0.3292 -y 0.3285' ;; spring-green) COLOR='-x 0.1671 -y 0.5906' ;; steel-blue) COLOR='-x 0.183 -y 0.2325' ;; tan) COLOR='-x 0.4035 -y 0.3772' ;; teal) COLOR='-x 0.1607 -y 0.3423' ;; thistle) COLOR='-x 0.3342 -y 0.2971' ;; tomato) COLOR='-x 0.6112 -y 0.3261' ;; turquoise) COLOR='-x 0.1702 -y 0.3675' ;; violet) COLOR='-x 0.3644 -y 0.2133' ;; wheat) COLOR='-x 0.3852 -y 0.3737' ;; white) COLOR='-x 0.3227 -y 0.329' ;; white-smoke) COLOR='-x 0.3227 -y 0.329' ;; yellow) COLOR='-x 0.4334 -y 0.5022' ;; yellow-green) COLOR='-x 0.3517 -y 0.5618' ;; end-color-list) ;; *) COLOR='Error' ;; esac if [ "$SHOWXY" = "1" ]; then echo -n "$lookcolor (xy): " echo "$COLOR" elif [ "$COLOR" = "Error" ]; then echo "huetil: $lookcolor not in known list of colors" elif [ "$SHOWXY" = "2" ]; then Xval=$(echo "$COLOR" | awk '{print $2}') Yval=$(echo "$COLOR" | awk '{print $4}') else x=$(echo "$COLOR" | awk '{print $2}') y=$(echo "$COLOR" | awk '{print $4}') z=254 echo "$COLOR" | sed 's/-x //' | sed 's/-y //' xy2rgb echo fi } makeRGB() { if [ $(echo "$1 > 1" | bc -l) -eq 1 ]; then echo 255 elif [ $(echo "$1 < 0" | bc -l) -eq 1 ]; then echo 0 else printf "%.0f" $(echo "$1 * 255" | bc -l) fi } rgb2hex() { printf " Hex: #%02X%02X%02X " $r $g $b } prtrgb() { printf " ANSI Color: \033[48;2;$r;$g;$b;4m \e[0m" } # xy2rgb - convert X and Y (along with the lights bri value) to RGB xy2rgb() { Y=$(echo "$z / 255.0" | bc -l) # insure x and y are within the limited gamut # future work for now #Red: 0.675, 0.322 #Green: 0.4091, 0.518 #Blue: 0.167, 0.04 z=$(echo "1.0 - $x - $y" | bc -l) X=$(echo "$Y / $y * $x" | bc -l) Z=$(echo "$Y / $y * $z" | bc -l) # Wide gamut D65 conversion r=$(echo "$X * 1.612 - $Y * 0.203 - $Z * 0.302" | bc -l) g=$(echo "-$X * 0.509 + $Y * 1.412 + $Z * 0.066" | bc -l) b=$(echo "$X * 0.026 - $Y * 0.072 + $Z * 0.962" | bc -l) # Apply gamma correction r=$(awk -v r="$r" 'BEGIN { if (r <= 0.0031308) printf "%.10f", 12.92 * r; else printf "%.10f", (1.0 + 0.055) * (r ^ (1.0 / 2.4)) - 0.055 }') g=$(awk -v g="$g" 'BEGIN { if (g <= 0.0031308) printf "%.10f", 12.92 * g; else printf "%.10f", (1.0 + 0.055) * (g ^ (1.0 / 2.4)) - 0.055 }') b=$(awk -v b="$b" 'BEGIN { if (b <= 0.0031308) printf "%.10f", 12.92 * b; else printf "%.10f", (1.0 + 0.055) * (b ^ (1.0 / 2.4)) - 0.055 }') if [ "$debug" = "1" ]; then printf " R: %s G: %s B: %s \\n" $r $g $b read a fi # now normalize r=$(makeRGB $r) g=$(makeRGB $g) b=$(makeRGB $b) # now print/return values and color printf " R: %s G: %s B: %s " $r $g $b } rgb2xy() { # parameter validation if [ $r -lt 0 ] || [ $r -gt 255 ]; then echo "R value $r is not in range (0-255)" exit 1 fi if [ $g -lt 0 ] || [ $g -gt 255 ]; then echo "G value $g is not in range (0-255)" exit 1 fi if [ $b -lt 0 ] || [ $b -gt 255 ]; then echo "B value $b is not in range (0-255)" exit 1 fi # normalize red=$(echo "$r / 255.0" | bc -l) green=$(echo "$g / 255.0" | bc -l) blue=$(echo "$b / 255.0" | bc -l) # start conversion # Apply gamma correction r=$(awk -v red="$red" 'BEGIN{ if (red > 0.04045) printf "%.10f", ((red + 0.055) / 1.055)^2.4; else printf "%.10f", red / 12.92}') g=$(awk -v green="$green" 'BEGIN{ if (green > 0.04045) printf "%.10f", ((green + 0.055) / 1.055)^2.4; else printf "%.10f", green / 12.92}') b=$(awk -v blue="$blue" 'BEGIN{ if (blue > 0.04045) printf "%.10f", ((blue + 0.055) / 1.055)^2.4; else printf "%.10f", blue / 12.92}') if [ "$debug" = "1" ]; then printf "r: %s g: %s b: %s \\n" $r $g $b printf "g1: %s g: %s\\n" $g1 $g printf "b1: %s b: %s\\n" $b1 $b read a fi # Wide gamut D65 conversion X=$(echo "$r * 0.649926 + $g * 0.103455 + $b * 0.197109" | bc -l) Y=$(echo "$r * 0.234327 + $g * 0.743075 + $b * 0.022598" | bc -l) Z=$(echo "$r * 0.0000000 + $g * 0.053077 + $b * 1.035763" | bc -l) cx=$(echo "$X / ($X + $Y + $Z)" | bc -l) cy=$(echo "$Y / ($X + $Y + $Z)" | bc -l) # Check for NaN and set to 0.0 if needed if [ -z "$cx" ] || [ "$cx" != "$cx" ]; then cx=0.0 fi if [ -z "$cy" ] || [ "$cy" != "$cy" ]; then cy=0.0 fi brix=$(echo "$Y * 255" | bc -l) BriVal=$(printf "%.0f" $brix) # return calcultated XY Xval=$(printf "%.3f" $cx) Yval=$(printf "%.3f" $cy) if [ "$debug" = "1" ]; then echo "{ x: $cx, y: $cy, bri: $Y bri: $BriVal }" printf "%.3f %.3f" $Xval $Yval read a fi } getxy() { hueType=${1} hueTypeNumber=${2} if ! echo "$hueTypeNumber" | grep -q "^[0-9]" then huenum=$(name2num "$hueType" "$hueTypeNumber") hueTypeNumber=$huenum fi devexist $hueType $hueTypeNumber case ${hueType} in light) hueUrl="${hueBaseUrl}/lights/${hueTypeNumber}" ;; group) hueUrl="${hueBaseUrl}/groups/${hueTypeNumber}" ;; *) echo "[-] huetil: The hue type not a light or group."; exit 1 ;; esac $CURLAP --max-time ${hueTimeOut} --silent --request GET ${hueUrl} | jq -r '.' > $HUERESPONSE if [ ${?} -ne 0 ]; then echo "[-] huetil: Failed to send command to bridge" exit 1 fi checkhueresp if [ "$hueType" = "group" ]; then X=$(cat $HUERESPONSE | jq -M '.action.xy' | sed -n '2p' | tr -d ',') Y=$(cat $HUERESPONSE | jq -M '.action.xy' | sed -n '3p' ) else X=$(cat $HUERESPONSE | jq -M '.state.xy' | sed -n '2p' | tr -d ',') Y=$(cat $HUERESPONSE | jq -M '.state.xy' | sed -n '3p' ) fi printf " x: %s y: %s \\n" $X $Y } # get a mac address from ethernet getmac() { if [ "$OS" = "merlin" ]; then ifconfig | grep -m 1 HWaddr | awk '{print $5}' | tr -d : else ifconfig | grep -m 1 ether | awk '{print $2}' | tr -d : fi } # gethue_user - get a hue bridge username # reqires user to press the link button on top of the hue # very experimental - might not work in all cases. # gethue_user() { if [ -f "$SCRIPTCONF" ]; then . "$SCRIPTCONF" else echo "[-] huetil: No $SCRIPTCONF found" echo "[-] huetil: Please run huetil install and try again." exit 1 fi hueBaseUrl="http://${hueBridge}:${huePort}" hueUrl="${hueBaseUrl}/api" hashmac="$(getmac)" hueUname="huetil#$hashmac" hueJsonData='{"devicetype":"huetil#'$hashmac'"}' cat < $HUEHASH if [ ${?} -ne 0 ]; then echo "[-] huetil: Failed to send scene command to hue bridge." exit 1 fi if [ $(grep -c 'error' $HUEHASH) -gt 0 ]; then if [ $(grep -c 'button' $HUEHASH) -gt 0 ]; then echo echo "Hue Bridge reported that the Link button wasnt pressed" sleep 1 showhowun rm -f $HUEHASH exit 1 else echo "Hue Bridge responded with an unexpected error:" cat $HUEHASH showhowun rm -f $HUEHASH exit 1 fi else huekey=$(cat $HUEHASH | awk 'BEGIN { FS = "\"" } ; {print $6};') echo "Success hue token key is $huekey" echo "hue username is $hueUname" echo sleep 2 sed -i '/hueUname/d' $SCRIPTCONF sed -i '/hueApiHash/d' $SCRIPTCONF echo "#hueApiHash generated during install - shouldn't have to change!" >> $SCRIPTCONF echo "hueUname=\"$hueUname\"" >> $SCRIPTCONF echo "hueApiHash=\"$huekey\"" >> $SCRIPTCONF echo echo "Updated $SCRIPTCONF" echo sleep 2 fi else showhowun fi rm -f $HUEHASH } # get the hue bridge configuration info and display gethueconfig() { if [ -f "$SCRIPTCONF" ]; then . "$SCRIPTCONF" else echo "[-] huetil: No $SCRIPTCONF found" echo "[-] huetil: Please run huetil install and try again." exit 1 fi hueBaseUrl="http://${hueBridge}:${huePort}" hueUrl="${hueBaseUrl}/api/${hueApiHash}/config" $CURLAP --max-time ${hueTimeOut} --silent --request GET ${hueUrl} > $SCRIPTDIR/hueconfig if [ ${?} -ne 0 ]; then echo "[-] huetil: Failed to send command to bridge." exit 1 fi if [ -z "$SCRIPTDIR/hueconfig" ]; then echo "[-] huetil: Failed to get configuration from bridge." exit 1 fi jq -C '.' $SCRIPTDIR/hueconfig | more } showhowun() { cat < "$SCRIPTCONF" echo "# IP Address of hue bridge" >> "$SCRIPTCONF" echo -n "Scanning local network for a Hue Bridge..." if [ "$OS" = "merlin" ]; then hubip=$(arp -n | grep "00:17:88" | grep -oP '(?<=[(])[^)]*') else echo "This might take a little while..." hubip=$(sudo arp-scan -l --retry 4 --bandwidth 64K --ouifile=/usr/share/arp-scan/ieee-oui.txt --macfile=/etc/arp-scan/mac-vendor.txt | \ grep -m 1 "Philips Lighting" | awk '{print $1}') fi echo "done." if [ ! -z $hubip ]; then checkhueip $hubip if [ "$HueBridgeName" != "" ]; then printf "\\nFound a Hue Bridge at %s (%s)\\n" $hubip "$HueBridgeName" sleep 2 echo "hueBridge='${hubip}'" >> "$SCRIPTCONF" fi else echo "Couldnt find hue bridge on the network." echo "You will need to find it and add it to ${SCRIPTDIR}/huetil.conf under hueBridge" echo "hueBridge=''" >> "$SCRIPTCONF" fi echo "# IP Port number of bridge - usually 80" >> "$SCRIPTCONF" echo "huePort='80'" >> "$SCRIPTCONF" echo "# Print all messages (1) or just errors (0)" >> "$SCRIPTCONF" echo "hueVerbose='0'" >> "$SCRIPTCONF" >> "$SCRIPTCONF" echo "# How many steps between cycles in cycle command" >> "$SCRIPTCONF" echo "hueCycleStep='500'" >> "$SCRIPTCONF" echo "# Time is seconds to delay when cycling" >> "$SCRIPTCONF" echo "hueCycleDelay='1'" >> "$SCRIPTCONF" echo "# Transition time for actions - in 100mSec. overrides default 400mSec" >> "$SCRIPTCONF" echo "Ttime='1'" >> "$SCRIPTCONF" echo "# Amount of time in seconds to wait for Hue Bridge to respond to commands" >> "$SCRIPTCONF" echo "hueTimeOut='7'" >> "$SCRIPTCONF" echo "# ApiHash is required. " >> "$SCRIPTCONF" echo "# If this field is empty, create an account and get and api key from: " >> "$SCRIPTCONF" echo "# https://developers.meethue.com/login/ " >> "$SCRIPTCONF" echo "# then insert the key below " >> "$SCRIPTCONF" echo "hueApiHash=''" >> "$SCRIPTCONF" # attempt to see about a username/key if [ ! -z $hubip ]; then gethue_user else echo "No valid Hue Bridge found - no username/key generated" echo "Find the Hue Bridge local IP address and update $SCRIPTCONF" exit 1 fi printf "\\nHere is the huetil.conf file:\\n\\n" cat $SCRIPTCONF sleep 3 printf "\\n\\nWould you also like to download a small collection of example scripts that use huetil (Y/N)? " read a if [ "$a" = "y" ] || [ "$a" = "Y" ]; then printf "\\nThis will download some scripts into the $SCRIPTDIR/examples directory" getexamples fi printf "\\n Install Complete\\n" printf "Would you like huetil to scan and display the lights,groups and scenes on the Hue Bridge (Y/N)? " read a if [ "$a" = "y" ] || [ "$a" = "Y" ]; then checkapi showhuestuff all fi } # uninstall script - remove all the stuff we installed (except dependent apps like nmap) remove_hue() { printf "\\n Uninstall huetil and it's data/directories? [Y=Yes] ";read -r continue case "$continue" in Y|y) printf "\\n Uninstalling...\\n" hueUname=$(grep hueUname $SCRIPTCONF | sed 's/hueUname=//g') rm -rf "$SCRIPTDIR" if [ "$OS" = "merlin" ]; then rm -f /jffs/scripts/huetil if [ -L /opt/sbin/huetil ]; then rm -f /opt/sbin/huetil fi else rm -rf "$SCRIPTCONF" rm -f $HOME/huetil sudo rm -f "$SCRIPTLOC/$SCRIPTNAME" fi printf "\\nhuetil uninstalled\\n" printf "The application is still enabled on the Hue Bridge\\n" printf "Philips only allows the user, via web, to remove the application ID on the bridge\\n" printf "To do this, login to your Philips Hue web portal (https://account.meethue.com) and go to\\n" printf "My Philips Hue system and then select your bridge.\\n" printf "\\n Look for an application named $hueUname and click Remove app \\n\\n" ;; *) printf "\\nhuetil NOT uninstalled\\n" ;; esac } updatehuetil() { if [ -x "$SCRIPTLOC/$SCRIPTNAME" ] && [ -f "$SCRIPTCONF" ]; then rm -f "$SCRIPTDIR/huetil.version" $CURLAP --retry 3 --silent "https://raw.githubusercontent.com/JGrana01/huetil/master/huetil.version" -o "$SCRIPTDIR/huetil.version" if [ -z "$SCRIPTDIR/huetil.version" ]; then echo "huetil: Could not retrieve version number from github. Exiting." exit fi oldwas=$(grep -m 1 "SCRIPTVER=" "$SCRIPTLOC/$SCRIPTNAME" | sed 's/SCRIPTVER\=//g' ) newis=$(grep -m 1 "SCRIPTVER=" "$SCRIPTDIR/huetil.version" | sed 's/SCRIPTVER\=//g') if [ "$oldwas" = "$newis" ]; then echo "huetil is up to date" exit fi printf "New version ($newis) of huetil found.\\n" printf "\\nDownload and install the latest version of huetil (Y/N)? " read a if [ "$a" = "n" ] || [ "$a" = "N" ]; then exit else printf "\\nOk, downloading huetil\\n" $CURLAP --retry 3 --silent "https://raw.githubusercontent.com/JGrana01/huetil/master/huetil" -o "/tmp/huetil" && chmod 0755 /tmp/huetil if [ "$OS" = "merlin" ]; then cp /tmp/huetil $SCRIPTLOC/$SCRIPTNAME else sudo cp /tmp/huetil $SCRIPTLOC/$SCRIPTNAME fi rm /tmp/huetil printf "\\n\\nDone.\\n" printf "Old version was %s, new version us %s\\n" $oldwas $newis fi if [ -d $SCRIPTDIR/examples ]; then printf "Would you also like to re-download the example scripts in $SCRIPTDIR/examples(Y/N)? " printf "\\n (Note - this will overwrite all the example scripts so a backup tar file (examples.tar) will be created in that directory)" read a if [ "$a" = "y" ] || [ "$a" = "Y" ]; then tar cf examples.tar -C $SCRIPTDIR/examples . getexamples fi fi else printf "\\nNo $SCRIPTLOC/$SCRIPTNAME or $SCRIPTCONF found" printf "\\nDownload and install manually" fi } # given the name of a light or group (as shown by show lights|groups) return it ID name2num() { case $1 in light) huedev=lights ;; group) huedev=groups ;; *) echo "[-] huetil: not a light or group - $1" exit ;; esac numis=$(grep -w "$2\s" "$SCRIPTDIR/$huedev" | awk '{print $2}') if [ -z "$numis" ]; then echo "[-] huetil: $1 $2 not found on Hue Bridge" exit else echo $numis fi } # given the ID of a light or group (as shown by show lights|groups) return its name num2name() { case $1 in light) huedev=lights numis=$(grep -w "\s$2$" "$SCRIPTDIR/$huedev" | awk '{print $1}') ;; group) huedev=groups numis=$(grep -w "$2\s" "$SCRIPTDIR/$huedev" | awk '{print $1}') ;; *) echo "[-] huetil: not a light or group - $1" exit ;; esac if [ -z "$numis" ]; then echo "[-] huetil: $1 $2 not found on Hue Bridge" exit else echo $numis fi } # main parseing routine parsem() { shift while [ "$#" -gt 0 ]; do case $1 in on) jsonData='{"on":true,' ;; off) jsonData='{"on":false,' ;; bri) hueSetBri $2; shift ;; sat) hueSetSat $2; shift ;; hue) hueSetHue $2; shift ;; ct) hueSetCt $2; shift ;; xy) xval=$(echo $2 | awk 'BEGIN { FS = ":" }; { print $1 }' | sed 's/xy://') yval=$(echo $2 | awk 'BEGIN { FS = ":" }; { print $2 }') if [ "$debug" = "1" ]; then printf "X: %s Y: %s\\n" $xval $yval read a fi hueSetXY $xval $yval shift ;; rgb) r=$2 shift g=$2 shift b=$2 if [ "$debug" = "1" ]; then printf "r %s g %s b %s\\n" $r $g $b read a fi rgb2xy hueSetXY $Xval $Yval hueSetBri $BriVal shift ;; color) hue_color convert $2 hueSetXY "$Xval" "$Yval"; shift ;; trans) setTtime $2 shift ;; *) echo "Invalid argument $1" exit ;; esac shift done } # given a device type and ID, is it known by the bridge devexist() { case "$1" in light) showhuelights > /dev/null if [ -z $(awk '{print $2}' "$SCRIPTDIR/lights" | grep -x "$2") ]; then echo "Light $2 is not known to the hue bridge" exit else return fi ;; group) showhuegroups if [ -z $(awk '{print $2}' "$SCRIPTDIR/groups" | grep -x "$2") ]; then echo "Group $2 is not known to the hue bridge" exit else return fi ;; *) echo "huetil: $1 not a light or group..." exit ;; esac } # script that does the playful actions doeffects() { if [ "$1" = "light" ] || [ "$1" = "group" ]; then somedevices "$1" else echo "huetil: play command expects light or group" exit fi if ! echo "$2" | grep -q "^[0-9]" then huenum=$(name2num "$1" "$2") else huenum="$2" fi case "$1" in light) devexist $1 $huenum hueUrl="${hueBaseUrl}/lights/$huenum/state" jsonData='{"on":true,' ;; group) devexist $1 $huenum hueUrl="${hueBaseUrl}/groups/$huenum/action" jsonData='{"on":true,' ;; *) echo "Error cant set device $1 $2" exit ;; esac case "$3" in effect) if [ "$4" = "colorloop" ] || [ "$4" = "none" ]; then jsonData="$jsonData\"effect\":\"$4\"}" else echo "huetil: effect must be either colorloop or none" exit fi ;; breathe) if [ "$4" = "long" ]; then beffect=lselect elif [ -z "$4" ]; then beffect=select else echo "huetil: unknown breathe type $4" exit fi jsonData="$jsonData\"alert\":\"$beffect\"}" ;; bri-inc) if [ $4 -lt -255 ] || [ $4 -gt 255 ]; then echo "huetil: bri-inc ($4) must be between -255 and 255" exit else jsonData="{\"bri_inc\": $4}" fi ;; hue-inc) if [ $4 -lt -65534 ] || [ $4 -gt 65534 ]; then echo "huetil: hue-inc ($4) must be between -65534 and 65534" exit else jsonData="{\"hue_inc\": $4}" fi ;; sat-inc) if [ $4 -lt -255 ] || [ $4 -gt 255 ]; then echo "huetil: sat-inc ($4) must be between -255 and 255" exit else jsonData="{\"sat_inc\": $4}" fi ;; ct-inc) if [ $4 -lt -65534 ] || [ $4 -gt 65535 ]; then echo "huetil: bri-inc ($4) must be between -65534 and 65535" exit else jsonData="{\"ct_inc\": $4}" fi ;; *) echo "unknown effect $3" exit ;; esac $CURLAP --max-time ${hueTimeOut} --silent --request PUT --data "$jsonData" ${hueUrl} > $HUERESPONSE if [ ${?} -ne 0 ]; then echo "[-] huetil: Failed to send command to bridge." exit 1 fi checkhueresp } # script the takes the collected set command args and sends it to the bridge setstuff() { if [ "$2" = "light" ] || [ "$2" = "group" ]; then somedevices "$2" else echo "huetil: set command expects light or group" exit fi if ! echo "$3" | grep -q "^[0-9]" then huenum=$(name2num "$2" "$3") else huenum="$3" fi # always set transitioin to default before action nTtime=$Ttime case "$2" in light) devexist $2 $huenum hueUrl="${hueBaseUrl}/lights/$huenum/state" jsonData='{"on":true,' shift shift parsem "$@" ;; group) devexist $2 $huenum hueUrl="${hueBaseUrl}/groups/$huenum/action" jsonData='{"on":true,' shift shift parsem "$@" ;; *) echo "Error cant set device $2" exit ;; esac # work around bug with bridge when turning devices off. # adding transition time will cause light/group brightness to go to 1 # if [ $( echo $jsonData | grep \"on\":false) ]; then jsonData='{"on":false}' else jsonData="$jsonData"'"transitiontime":'"$nTtime}" fi debug=0 if [ "$debug" = "1" ] ; then printf "hueUrl: %s\\n" "$hueUrl" printf "jsonData: %s\\n" "$jsonData" read a fi $CURLAP --max-time ${hueTimeOut} --silent --request PUT --data "$jsonData" ${hueUrl} > $HUERESPONSE if [ ${?} -ne 0 ]; then echo "[-] huetil: Failed to send set command to $1/$2." exit 1 fi checkhueresp } # main script if [ ${#} -eq 0 ]; then usage exit 0 fi setOS if [ "$1" = "install" ]; then install_hue exit fi if [ "$1" = "uninstall" ]; then remove_hue exit fi checkapi if [ -z $(which jq) ]; then echo "[-] huetil: jq is not installed. This script needs $CURLAP to communicate with the hue api." echo "[-] huetil: Please run huetil install and try again." exit 1 fi if [ -f "$SCRIPTCONF" ]; then . "$SCRIPTCONF" hueBaseUrl="http://${hueBridge}:${huePort}/api/${hueApiHash}" else echo "[-] huetil: No $SCRIPTCONF found" echo "[-] huetil: Please run huetil install and try again." exit 1 fi hueDeviceAction=${1} hueDevice=${2} hueDeviceNumber=${3} hueDeviceActionValue1=${4} hueDeviceActionValue2=${5} case ${hueDeviceAction} in cycle) hueCycle ${hueDevice} ${hueDeviceNumber} ${hueDeviceActionValue1} ${hueDeviceActionValue2} ;; install) install_hue exit 0 ;; uninstall) remove_hue exit 0 ;; version) echo $SCRIPTVER exit 0 ;; show) showhuestuff $hueDevice exit 0 ;; colors) sed -n '/alice-blue)/,/yellow-green)/p;/yellow-green)/q' "$SCRIPTLOC/$SCRIPTNAME" | awk 'BEGIN { FS = ")" } ; { print $1 }' | tr -s '\t' ' ' | column -x printf "\\n\\nAvailable color temps (ct):\\n\\n" echo " Cool Daylight Natural Warm Candle" echo exit 0 ;; convert) hue_color $hueDevice exit 0 ;; scene) hueSceneOn $hueDevice $hueDeviceNumber ;; sethueun) gethue_user exit 0 ;; info) hueattr $hueDevice $hueDeviceNumber "info" exit 0 ;; getcolor) hueattr $hueDevice $hueDeviceNumber "color" exit 0 ;; showxy) getxy $hueDevice $hueDeviceNumber exit 0 ;; save) if [ "${hueDevice}" = "all" ]; then hueSaveAll exit 0 fi hueattr $hueDevice $hueDeviceNumber "save" $hueDeviceActionValue1 exit 0 ;; restore) hueattr $hueDevice $hueDeviceNumber "restore" $hueDeviceActionValue1 exit 0 ;; verbose) if [ "$hueVerbose" -eq "0" ]; then sed -i "s/hueVerbose='0'/hueVerbose='1'/" $SCRIPTCONF else sed -i "s/hueVerbose='1'/hueVerbose='0'/" $SCRIPTCONF fi exit 0 ;; hubconfig) gethueconfig exit 0 ;; update) updatehuetil exit 0 ;; help) usage exit 0 ;; # testmode - hidden command to expriment with differnt things testmode) x=.101 y=.324 z=254 # brightest we can get printf "x: %s y: %s z: %s\\n" $x $y $z xy2rgb echo rgb2xy printf "X: %s Y: %s\\n" $Xval $Yval exit 0 ;; set) setstuff "$@" ;; play) doeffects ${hueDevice} ${hueDeviceNumber} ${hueDeviceActionValue1} ${hueDeviceActionValue2} ;; *) echo "huetil: Unknown command ${hueDeviceAction}" usage ;; esac exit 0