Skip to content

Commit

Permalink
New Get and Set scene commands (#41)
Browse files Browse the repository at this point in the history
This PR introduces 2 new scenes' related commands: 
- the `openhue get scene` command that allows listing the available
scenes
- the `openhue set scene` command that allows activating a scene

### `openhue get scene`
```
Displays all the scenes with their main information, including the room they belong to.

Usage:
  openhue get scene [sceneId|sceneName] [flags]

Aliases:
  scene, scenes

Examples:

# List all scenes
openhue get scene

# List all scenes as JSON 
openhue get scene --json

# Filter scenes for a given room name
openhue get scenes --room "Living Room"

# Filter scenes for a given room ID
openhue get scenes -r 878a65d6-613b-4239-8b77-588b535bfb4a

# List multiple scenes using either the ID or the name of the scene
openhue get scenes "Palm Beach" Nebula 462e54d9-ec5d-4bf6-879d-ad34cb9a692e

Flags:
  -h, --help          help for scene
  -r, --room string   Filter scenes by room (name or ID)

Global Flags:
  -j, --json   Format output as JSON
```

### `openhue set scene`
```
This command allows the activate a scene using either a static or a dynamic palette.

Usage:
  openhue set scene [sceneId|sceneName] [flags]

Aliases:
  scene, scenes

Examples:

# Activate a scene
openhue set scene Soho

# Activate a scene by ID
openhue set scene 62af7df3-d390-4408-a7ac-4b6b8805531b

# Activate a scene in a specific room
openhue set scene Soho -r Studio

# Activate a scene with a dynamic palette
openhue set scene Soho -a dynamic

Flags:
  -a, --action string   action to perform on the scene. allowed: active (default), dynamic or static
  -h, --help            help for scene
  -r, --room string     room where the scene is located (in case multiple scenes with the same name exist)
```
  • Loading branch information
thibauult authored Feb 2, 2024
1 parent 7aadf74 commit f3137c0
Show file tree
Hide file tree
Showing 11 changed files with 266 additions and 90 deletions.
1 change: 1 addition & 0 deletions cmd/get/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ Retrieve information for any kind of resources exposed by your Hue Bridge: light
}

func RunGetAllResources(ctx *openhue.Context, typeFlag string, o *CmdGetOptions) {

resp, err := ctx.Api.GetResourcesWithResponse(context.Background())
cobra.CheckErr(err)
resources := *(*resp.JSON200).Data
Expand Down
5 changes: 2 additions & 3 deletions cmd/get/get_scene.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func NewCmdGetScene(ctx *openhue.Context, co *CmdGetOptions) *cobra.Command {
}

cmd := &cobra.Command{
Use: "scene [sceneId]",
Use: "scene [sceneId|sceneName]",
Aliases: []string{"scenes"},
Short: "Get scenes",
Long: `
Expand All @@ -40,8 +40,7 @@ openhue get scenes --room "Living Room"
openhue get scenes -r 878a65d6-613b-4239-8b77-588b535bfb4a
# List multiple scenes using either the ID or the name of the scene
openhue get scenes "Palm Beach" Nebula 462e54d9-ec5d-4bf6-879d-ad34cb9a692e
`,
openhue get scenes "Palm Beach" Nebula 462e54d9-ec5d-4bf6-879d-ad34cb9a692e`,
Args: cobra.MatchAll(cobra.RangeArgs(0, 100), cobra.OnlyValidArgs),
Run: func(cmd *cobra.Command, args []string) {
o.RunGetSceneCmd(ctx, args)
Expand Down
49 changes: 49 additions & 0 deletions cmd/get/get_scene_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package get

import (
"github.com/stretchr/testify/assert"
"openhue-cli/openhue"
"openhue-cli/openhue/gen"
"testing"
)

func TestGetAllScenes(t *testing.T) {
mockCtx, _ := openhue.NewTestContext(NewFakeHome())
cmd := NewCmdGetScene(mockCtx, &CmdGetOptions{Json: true})
err := cmd.Execute()
assert.Nil(t, err)
}

func NewFakeHome() *openhue.Home {
return &openhue.Home{
Resource: openhue.Resource{
Id: "home-01",
Name: "Home",
Type: openhue.HomeResourceType(gen.BridgeHomeGetTypeBridgeHome),
Parent: nil,
},
Rooms: []openhue.Room{
{
Resource: openhue.Resource{
Id: "room-01",
Name: "Room 1",
Type: openhue.HomeResourceType(gen.ResourceGetTypeRoom),
},
Devices: nil,
Scenes: []openhue.Scene{
{
Resource: openhue.Resource{
Id: "scene-01",
Name: "Soho",
},
HueData: &gen.SceneGet{},
},
},
GroupedLight: nil,
HueData: &gen.RoomGet{},
},
},
Devices: []openhue.Device{},
HueData: &gen.BridgeHomeGet{},
}
}
3 changes: 1 addition & 2 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,7 @@ func Execute(buildInfo *openhue.BuildInfo) {

// get the API Client
api := c.NewOpenHueClient()
ctx := openhue.NewContext(openhue.NewIOStreams(), buildInfo, api)
ctx.Config = &c
ctx := openhue.NewContext(openhue.NewIOStreams(), buildInfo, api, &c)

// create the root command
root := NewCmdOpenHue(ctx)
Expand Down
3 changes: 2 additions & 1 deletion cmd/set/set.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@ func NewCmdSet(ctx *openhue.Context) *cobra.Command {
Short: "Set specific features on resources",
GroupID: "hue",
Long: `
Set the values for a specific resource
Set the values for specific resources.
`,
}

cmd.AddCommand(NewCmdSetLight(ctx))
cmd.AddCommand(NewCmdSetRoom(ctx))
cmd.AddCommand(NewCmdSetScene(ctx))

return cmd
}
129 changes: 129 additions & 0 deletions cmd/set/set_scene.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package set

import (
"errors"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"openhue-cli/openhue"
"openhue-cli/openhue/gen"
)

const (
setSceneDocShort = "Activate a scene"
setSceneDocLong = `
This command allows the activate a scene using either a static or a dynamic palette.
`
setSceneDocExample = `
# Activate a scene
openhue set scene Soho
# Activate a scene by ID
openhue set scene 62af7df3-d390-4408-a7ac-4b6b8805531b
# Activate a scene in a specific room
openhue set scene Soho -r Studio
# Activate a scene with a dynamic palette
openhue set scene Soho -a dynamic`
)

type CmdSetSceneOptions struct {
action actionEnum
room string
}

// NewCmdSetScene returns initialized cobra.Command instance for the 'set scene' sub command
func NewCmdSetScene(ctx *openhue.Context) *cobra.Command {

o := &CmdSetSceneOptions{}

cmd := &cobra.Command{
Use: "scene [sceneId|sceneName]",
Aliases: []string{"scenes"},
Short: setSceneDocShort,
Long: setSceneDocLong,
Example: setSceneDocExample,
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
Run: func(cmd *cobra.Command, args []string) {

scenes := openhue.SearchScenes(ctx.Home, o.room, args)

if len(scenes) == 0 {
ctx.Io.ErrPrintf("Scene '%s' not found%s\n", args[0], o.roomMsg())
return
}

for _, scene := range scenes {
err := scene.Activate(o.action.toSceneRecallAction())
if err != nil {
ctx.Io.ErrPrintln(err.Error())
continue
}
ctx.Io.Printf("Scene '%s' activated%s\n", scene.Name, o.roomMsg())
}
},
}

cmd.Flags().StringVarP(&o.room, "room", "r", "", "room where the scene is located (in case multiple scenes with the same name exist)")

// action flag
cmd.Flags().VarP(&o.action, "action", "a", "action to perform on the scene. allowed: active (default), dynamic or static")
_ = cmd.RegisterFlagCompletionFunc("action", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return []string{
"active\tthe actions in the scene are executed on the target",
"static\t(default)",
"dynamic\tstarts dynamic scene with colors in the Palette object",
}, cobra.ShellCompDirectiveDefault
})

return cmd
}

type actionEnum string

const (
active actionEnum = "active"
static actionEnum = "static"
dynamic actionEnum = "dynamic"
)

// String is used both by fmt.Print and by Cobra in help text
func (e *actionEnum) String() string {
return string(*e)
}

// Set must have pointer receiver so it doesn't change the value of a copy
func (e *actionEnum) Set(v string) error {
switch v {
case "active", "static", "dynamic":
*e = actionEnum(v)
return nil
default:
return errors.New(`must be one of "active", "static", or "dynamic"`)
}
}

// Type is only used in help text
func (e *actionEnum) Type() string {
return "string"
}

func (e *actionEnum) toSceneRecallAction() gen.SceneRecallAction {

if len(string(*e)) == 0 {
log.Info("action flag not set, defaulting to 'active")
*e = "active"
}

if *e == dynamic {
return gen.SceneRecallActionDynamicPalette
}
return gen.SceneRecallAction(e.String())
}

func (o *CmdSetSceneOptions) roomMsg() string {
if o.room != "" {
return " in room '" + o.room + "'"
}
return ""
}
2 changes: 1 addition & 1 deletion cmd/version/version_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const (

func TestNewCmdVersion(t *testing.T) {

ctx, out := openhue.NewTestContextWithoutApi()
ctx, out := openhue.NewTestContext(nil)

cmd := NewCmdVersion(ctx)
err := cmd.Execute()
Expand Down
11 changes: 8 additions & 3 deletions openhue/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,21 @@ type Context struct {
}

// NewContext returns an initialized Context from a given gen.ClientWithResponses API with default IOStreams
func NewContext(io IOStreams, buildInfo *BuildInfo, api *gen.ClientWithResponses) *Context {
func NewContext(io IOStreams, buildInfo *BuildInfo, api *gen.ClientWithResponses, config *Config) *Context {
return &Context{
Io: io,
BuildInfo: buildInfo,
Api: api,
Config: config,
}
}

func NewTestContextWithoutApi() (*Context, *bytes.Buffer) {
// NewTestContext returns a Context for testing usage only and the out buffer to validate
// the command output.
// The Api field of the returned Context is not set.
func NewTestContext(home *Home) (*Context, *bytes.Buffer) {
streams, _, out, _ := NewTestIOStreams()
ctx := NewContext(streams, NewTestBuildInfo(), nil)
ctx := NewContext(streams, NewTestBuildInfo(), nil, nil)
ctx.Home = home
return ctx, out
}
4 changes: 2 additions & 2 deletions openhue/context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import (
)

func TestNewContext(t *testing.T) {
ctx := NewContext(NewTestIOStreamsDiscard(), NewTestBuildInfo(), &gen.ClientWithResponses{})
ctx := NewContext(NewTestIOStreamsDiscard(), NewTestBuildInfo(), &gen.ClientWithResponses{}, nil)
assert.NotNil(t, ctx, "Context should not be nil")
}

func TestNewTestContextWithoutApi(t *testing.T) {
ctx, out := NewTestContextWithoutApi()
ctx, out := NewTestContext(nil)
assert.NotNil(t, ctx, "Context should not be nil")
assert.NotNil(t, out, "Out buffer should not be nil")
}
Loading

0 comments on commit f3137c0

Please sign in to comment.