Skip to content

Instantly share code, notes, and snippets.

@zkarj735
Created July 21, 2024 08:42
Show Gist options
  • Save zkarj735/6bfdb0a6311d17c3a8b86e9a31df2e5d to your computer and use it in GitHub Desktop.
Save zkarj735/6bfdb0a6311d17c3a8b86e9a31df2e5d to your computer and use it in GitHub Desktop.
Philips Hue light control from the command line
#!/bin/bash
#
# ARG_OPTIONAL_SINGLE([switch],[],[Switch 'on' or 'off'])
# ARG_OPTIONAL_SINGLE([rgb],[],[RGB triplet (0-255)])
# ARG_OPTIONAL_SINGLE([temp],[],[Colour temperature (2000-6500)K])
# ARG_OPTIONAL_SINGLE([dim],[],[Dimming level (1-100)])
# ARG_POSITIONAL_INF([lights],[Lights to set],[1])
# ARG_HELP([Allows control of Philips Hue lights via Hue Bridge])
# ARGBASH_GO()
# needed because of Argbash --> m4_ignore([
### START OF CODE GENERATED BY Argbash v2.9.0 one line above ###
# Argbash is a bash code generator used to get arguments parsing right.
# Argbash is FREE SOFTWARE, see https://argbash.io for more info
# Generated online by https://argbash.io/generate
die()
{
local _ret="${2:-1}"
test "${_PRINT_HELP:-no}" = yes && print_help >&2
echo "$1" >&2
exit "${_ret}"
}
begins_with_short_option()
{
local first_option all_short_options='h'
first_option="${1:0:1}"
test "$all_short_options" = "${all_short_options/$first_option/}" && return 1 || return 0
}
# THE DEFAULTS INITIALIZATION - POSITIONALS
_positionals=()
_arg_lights=('' )
# THE DEFAULTS INITIALIZATION - OPTIONALS
_arg_switch=
_arg_rgb=
_arg_temp=
_arg_dim=
print_help()
{
printf '%s\n' "Allows control of Philips Hue lights via Hue Bridge"
printf 'Usage: %s [--switch <arg>] [--rgb <arg>] [--temp <arg>] [--dim <arg>] [-h|--help] <lights-1> [<lights-2>] ... [<lights-n>] ...\n' "$0"
printf '\t%s\n' "<lights>: Lights to set"
printf '\t%s\n' "--switch: Switch 'on' or 'off' (no default)"
printf '\t%s\n' "--rgb: RGB triplet (0-255) (no default)"
printf '\t%s\n' "--temp: Colour temperature (2000-6500)K (no default)"
printf '\t%s\n' "--dim: Dimming level (1-100) (no default)"
printf '\t%s\n' "-h, --help: Prints help"
}
parse_commandline()
{
_positionals_count=0
while test $# -gt 0
do
_key="$1"
case "$_key" in
--switch)
test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
_arg_switch="$2"
shift
;;
--switch=*)
_arg_switch="${_key##--switch=}"
;;
--rgb)
test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
_arg_rgb="$2"
shift
;;
--rgb=*)
_arg_rgb="${_key##--rgb=}"
;;
--temp)
test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
_arg_temp="$2"
shift
;;
--temp=*)
_arg_temp="${_key##--temp=}"
;;
--dim)
test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
_arg_dim="$2"
shift
;;
--dim=*)
_arg_dim="${_key##--dim=}"
;;
-h|--help)
print_help
exit 0
;;
-h*)
print_help
exit 0
;;
*)
_last_positional="$1"
_positionals+=("$_last_positional")
_positionals_count=$((_positionals_count + 1))
;;
esac
shift
done
}
handle_passed_args_count()
{
local _required_args_string="'lights'"
test "${_positionals_count}" -ge 1 || _PRINT_HELP=yes die "FATAL ERROR: Not enough positional arguments - we require at least 1 (namely: $_required_args_string), but got only ${_positionals_count}." 1
}
assign_positional_args()
{
local _positional_name _shift_for=$1
_positional_names="_arg_lights "
_our_args=$((${#_positionals[@]} - 1))
for ((ii = 0; ii < _our_args; ii++))
do
_positional_names="$_positional_names _arg_lights[$((ii + 1))]"
done
shift "$_shift_for"
for _positional_name in ${_positional_names}
do
test $# -gt 0 || break
eval "$_positional_name=\${1}" || die "Error during argument parsing, possibly an Argbash bug." 1
shift
done
}
parse_commandline "$@"
handle_passed_args_count
assign_positional_args 1 "${_positionals[@]}"
# OTHER STUFF GENERATED BY Argbash
### END OF CODE GENERATED BY Argbash (sortof) ### ])
# [ <-- needed because of Argbash
# ----- EDIT THESE ---------------------------
hak="Qn74cB7YlKursSzMYyPL4pr5oLWxayBqhKyjFD10"
hba="192.168.1.15"
# --------------------------------------------
burl="https://${hba}/clip/v2/resource/light"
function get_id() {
local filter='.data[] | select(.metadata.name == "'"$light"'") | .id'
local dev_id=$(curl --request GET -ks --header "hue-application-key: $hak" "${burl}" | jq -r "$filter")
echo "$dev_id"
}
function get_colour() {
r_srgb="$(echo "scale=4; ${rgb[0]} / 255" | bc)"
g_srgb="$(echo "scale=4; ${rgb[1]} / 255" | bc)"
b_srgb="$(echo "scale=4; ${rgb[2]} / 255" | bc)"
# Linearise RGB
if [ 1 -eq "$(echo "${r_srgb} < 0.04045" | bc)" ]; then
r_lin=$( echo "${r_srgb}" | awk '{print $1 / 12.92};')
else
r_lin=$( echo "${r_srgb}" | awk '{print (($1 + 0.055) / 1.055) ^ 2.4};' )
fi
if [ 1 -eq "$(echo "${g_srgb} < 0.04045" | bc)" ]; then
g_lin=$( echo "${g_srgb}" | awk '{print $1 / 12.92};')
else
g_lin=$( echo "${g_srgb}" | awk '{print (($1 + 0.055) / 1.055) ^ 2.4};' )
fi
if [ 1 -eq "$(echo "${b_srgb} < 0.04045" | bc)" ]; then
b_lin=$( echo "${b_srgb}" | awk '{print $1 / 12.92};')
else
b_lin=$( echo "${b_srgb}" | awk '{print (($1 + 0.055) / 1.055) ^ 2.4};' )
fi
# Calculate XYZ
x_cie=$( echo "${r_lin} ${g_lin} ${b_lin}" | awk '{print $1 * 0.4124 + $2 * 0.3576 + $3 * 0.1805};')
y_cie=$( echo "${r_lin} ${g_lin} ${b_lin}" | awk '{print $1 * 0.2126 + $2 * 0.7152 + $3 * 0.0722};')
z_cie=$( echo "${r_lin} ${g_lin} ${b_lin}" | awk '{print $1 * 0.0193 + $2 * 0.1192 + $3 * 0.9505};')
json="\"color\":{\"xy\":{\"x\":$x_cie,\"y\":$y_cie}}"
echo $json
}
# Fetch valid light IDs
ids=()
for light in "${_arg_lights[@]}"; do
light_id=$(get_id)
if [ "$light_id" != "" ]; then
ids+=("$light_id")
fi
done
# Validate parameters given
# No valid lights
if [ "${#ids[@]}" = "0" ]; then
echo "No valid lights given."
exit 1
fi
# Switch must be on or off
if [ "$_arg_switch" != "" ]; then
if [ "$_arg_switch" != "on" ] && [ "$_arg_switch" != "off" ]; then
echo "--switch must be 'on' or 'off'."
exit 1
fi
fi
# Validate RGB triplet
if [ "$_arg_rgb" != "" ]; then
IFS=',' read -ra rgb <<< "$_arg_rgb"
if [ "${#rgb[@]}" != "3" ]; then
echo "--rgb must supply 3 comma-separated values"
exit 1
fi
for c in "${rgb[@]}"; do
if ! [[ $c =~ ^[0-9]+$ ]]; then
echo "--rgb must supply 3 comma separated numbers, e.g. --rgb 100,255,0"
exit 1
fi
if [[ $c -lt 0 || $c -gt 255 ]]; then
echo "--rgb must supply 3 comma separated values in the range 0-255"
exit 1
fi
done
fi
# Temp must be in the range 2000-6500
if [ "$_arg_temp" != "" ]; then
if ! [[ $_arg_temp =~ ^[0-9]+$ ]]; then
echo "--temp must be an integer in the range 2000-6500"
exit 1
fi
if [[ $_arg_temp -lt 2000 ]] || [[ $_arg_temp -gt 6500 ]]; then
echo "--temp must be in the range 2000-6500"
exit 1
fi
fi
# Dimming must be in the range 0-100
if [ "$_arg_dim" != "" ]; then
if ! [[ $_arg_dim =~ ^[0-9]+$ ]]; then
echo "-- dim must be an interger in the range 0-100"
exit 1
fi
if [[ $_arg_dim -lt 0 ]] || [[ $_arg_dim -gt 100 ]]; then
echo "--dim must be in the range 0-100"
exit 1
fi
fi
# Build up JSON actions
# Switch on or off
if [ "$_arg_switch" != "" ]; then
json="${json}\"on\":{\"on\":"
if [ "$_arg_switch" = "on" ]; then
json="${json}true"
else
json="${json}false"
fi
json="${json}}"
fi
# RGB
if [ "$_arg_rgb" != "" ]; then
if [ "$json" != "" ]; then
json="${json},"
fi
json="${json}$(get_colour)"
fi
# Temperature
if [ "$_arg_temp" != "" ]; then
if [ "$json" != "" ]; then
json="${json},"
fi
mirek=$(echo "scale=0; 1000000 / ${_arg_temp}" | bc)
json="${json}\"color_temperature\":{\"mirek\":${mirek}}"
fi
# Dimming
if [ "$_arg_dim" != "" ]; then
if [ "$json" != "" ]; then
json="${json},"
fi
json="${json}\"dimming\":{\"brightness\":${_arg_dim}}"
fi
# Wrap the json in an object
json="{${json}}"
# Finally, iterate over the lights and action each
for id in "${ids[@]}"; do
url="${burl}/${id}"
curl --request PUT -ks --data "${json}" --header "hue-application-key: $hak" "${url}" > /dev/null
done
# ] <-- needed because of Argbash
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment