Skip to content

Commit

Permalink
The 'openhue set light' command now allows setting the color (#18)
Browse files Browse the repository at this point in the history
Examples: 
```shell
# Set color (in RGB) of a single light
openhue set light 15f51223-1e83-4e48-9158-0c20dbd5734e --on --rgb #3399FF

# Set color (in CIE space) of a single light
openhue set light 15f51223-1e83-4e48-9158-0c20dbd5734e --on -x 0.675 -y 0.322

# Set color (by name) of a single light
openhue set light 15f51223-1e83-4e48-9158-0c20dbd5734e --on --color powder_blue
```
  • Loading branch information
thibauult authored Nov 27, 2023
1 parent 14dbbdb commit 13389e0
Show file tree
Hide file tree
Showing 10 changed files with 488 additions and 22 deletions.
111 changes: 97 additions & 14 deletions cmd/set/set_light.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"github.com/spf13/cobra"
"golang.org/x/net/context"
"openhue-cli/openhue"
"openhue-cli/util/color"
)

const (
Expand All @@ -28,25 +29,47 @@ openhue set light 15f51223-1e83-4e48-9158-0c20dbd5734e --off
# Set brightness of a single light
openhue set light 15f51223-1e83-4e48-9158-0c20dbd5734e --on --brightness 42.65
# Set color (in RGB) of a single light
openhue set light 15f51223-1e83-4e48-9158-0c20dbd5734e --on --rgb #3399FF
# Set color (in CIE space) of a single light
openhue set light 15f51223-1e83-4e48-9158-0c20dbd5734e --on -x 0.675 -y 0.322
# Set color (by name) of a single light
openhue set light 15f51223-1e83-4e48-9158-0c20dbd5734e --on --color powder_blue
`
)

type LightOptions struct {
Status LightStatus
Brightness float32
Color color.XY
}

type LightFlags struct {
On bool
Off bool
Brightness float32
Rgb string
X float32
Y float32
ColorName string
}

func NewSetLightOptions() *LightOptions {
return &LightOptions{
Status: Undefined,
Brightness: -1,
Color: color.UndefinedColor,
}
}

// NewCmdSetLight returns initialized Command instance for the 'set light' sub command
func NewCmdSetLight(api *openhue.ClientWithResponses) *cobra.Command {

o := NewSetLightOptions()
f := LightFlags{}

cmd := &cobra.Command{
Use: "light [lightId]",
Expand All @@ -55,39 +78,90 @@ func NewCmdSetLight(api *openhue.ClientWithResponses) *cobra.Command {
Example: docExample,
Args: cobra.MatchAll(cobra.RangeArgs(1, 10), cobra.OnlyValidArgs),
Run: func(cmd *cobra.Command, args []string) {
cobra.CheckErr(o.PrepareCmdSetLight(cmd))
cobra.CheckErr(o.PrepareCmdSetLight(&f))
cobra.CheckErr(o.RunCmdSetLight(api, args))
},
}

f.InitFlags(cmd)

return cmd
}

func (f *LightFlags) InitFlags(cmd *cobra.Command) {
// on/off flags
cmd.Flags().Bool("on", false, "Turn on the lights")
cmd.Flags().Bool("off", false, "Turn off the lights")
cmd.Flags().BoolVar(&f.On, "on", false, "Turn on the lights")
cmd.Flags().BoolVar(&f.Off, "off", false, "Turn off the lights")
cmd.MarkFlagsMutuallyExclusive("on", "off")

// Brightness flag
cmd.Flags().Float32VarP(&o.Brightness, "brightness", "b", -1, "Set the brightness [min=0, max=100]")
cmd.Flags().Float32VarP(&f.Brightness, "brightness", "b", -1, "Set the brightness [min=0, max=100]")

return cmd
//
// Color flags
//

// rgb
cmd.Flags().StringVar(&f.Rgb, "rgb", "", "RGB hexadecimal value (example #CCE5FF)")

// xy
cmd.Flags().Float32VarP(&f.X, "cie-x", "x", -1, "X coordinate in the CIE color space (example 0.675)")
cmd.Flags().Float32VarP(&f.Y, "cie-y", "y", -1, "Y coordinate in the CIE color space (example 0.322)")
cmd.MarkFlagsRequiredTogether("cie-x", "cie-y")

// name
cmd.Flags().StringVarP(&f.ColorName, "color", "c", "", "Color name. Allowed: white, lime, green, blue, etc.")
_ = cmd.RegisterFlagCompletionFunc("color", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return color.GetSupportColorList(), cobra.ShellCompDirectiveDefault
})

// exclusions
cmd.MarkFlagsMutuallyExclusive("color", "rgb", "cie-x")
}

// PrepareCmdSetLight makes sure provided values for LightOptions are valid
func (o *LightOptions) PrepareCmdSetLight(cmd *cobra.Command) error {
func (o *LightOptions) PrepareCmdSetLight(flags *LightFlags) error {

on, _ := cmd.Flags().GetBool("on")
off, _ := cmd.Flags().GetBool("off")

if on {
if flags.On {
o.Status = On
}

if off {
if flags.Off {
o.Status = Off
}

// validate the --brightness flag
if o.Brightness > 100.0 || (o.Brightness != -1 && o.Brightness < 0) {
if flags.Brightness > 100.0 || (flags.Brightness != -1 && flags.Brightness < 0) {
return fmt.Errorf("--brightness flag must be greater than 0.0 and lower than 100.0, current value is %.2f", o.Brightness)
} else {
o.Brightness = flags.Brightness
}

// color in RGB
if flags.Rgb != "" {
rgb, err := color.NewRGBFomHex(flags.Rgb)
if err != nil {
return err
}
o.Color = *rgb.ToXY()
}

// color in CIE space
if flags.X >= 0 && flags.Y >= 0 {
o.Color = color.XY{
X: flags.X,
Y: flags.Y,
}
}

// color from enum
if flags.ColorName != "" {
c, err := color.FindColorByName(flags.ColorName)
cobra.CheckErr(err)
o.Color = color.XY{
X: c.X,
Y: c.Y,
}
}

return nil
Expand All @@ -110,8 +184,17 @@ func (o *LightOptions) RunCmdSetLight(api *openhue.ClientWithResponses, args []s
}
}

for _, id := range args {
_, err := api.UpdateLight(context.Background(), id, *request)
if o.Color != color.UndefinedColor {
request.Color = &openhue.Color{
Xy: &openhue.GamutPosition{
X: &o.Color.X,
Y: &o.Color.Y,
},
}
}

for _, lightId := range args {
_, err := api.UpdateLight(context.Background(), lightId, *request)
cobra.CheckErr(err)
}

Expand Down
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package main

//go:generate oapi-codegen --package=openhue -generate=client,types -o ./openhue/openhue.gen.go https://api.redocly.com/registry/bundle/openhue/openhue/v2/openapi.yaml?branch=main
//go:generate oapi-codegen --package=openhue -generate=client,types -o ./openhue/openhue.gen.go /Users/thibault.pensec/local/perso/openhue-api/build/openhue.yaml

import (
"openhue-cli/cmd"
Expand Down
15 changes: 8 additions & 7 deletions openhue/openhue.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

40 changes: 40 additions & 0 deletions testing/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package testing

import "math"

// AlmostEqual32 returns true if a and b are equal within a relative error of e.
// See http://floating-point-gui.de/errors/comparison/ for the details of the applied method.
func AlmostEqual32(a, b, e float32) bool {

MinNormal32 := math.Float32frombits(0x00800000)

if a == b {
return true
}
absA := Abs32(a)
absB := Abs32(b)
diff := Abs32(a - b)
if a == 0 || b == 0 || absA+absB < MinNormal32 {
return diff < e*MinNormal32
}
return diff/Min32(absA+absB, math.MaxFloat32) < e
}

// Abs32 works like math.Abs, but for float32.
func Abs32(x float32) float32 {
switch {
case x < 0:
return -x
case x == 0:
return 0 // return correctly abs(-0)
}
return x
}

// Min32 works like math.Min, but for float32.
func Min32(x, y float32) float32 {
if x < y {
return x
}
return y
}
31 changes: 31 additions & 0 deletions util/color/color.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package color

type RGB struct {
Red int
Green int
Blue int
}

type XY struct {
X float32
Y float32
Brightness float32
}

var UndefinedColor = XY{
X: -1.0,
Y: -1.0,
Brightness: -1.0,
}

// RGBHex Hexadecimal representation of an RGB color. Must start with '#'
type RGBHex string

// toXY converts a RGBHex value to its XY representation in CIE color space
func (c *RGBHex) toXY() *XY {
hex, err := NewRGBFomHex(string(*c))
if err != nil {
return nil
}
return hex.ToXY()
}
1 change: 1 addition & 0 deletions util/color/color_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package color
Loading

0 comments on commit 13389e0

Please sign in to comment.