From 212b2475d49107de7c13cbf7046eed34698ad786 Mon Sep 17 00:00:00 2001 From: Fabian Sylvester Date: Mon, 14 Oct 2024 17:28:01 +0000 Subject: [PATCH] feat: add brighness feature (#51) * direct command `brightness` (Aliases: `b`, `br`, `bright`) * flag `-b|--brightness min|max|1-99` for `on` * skip G602 check due to false positive --- cmd/brightness.go | 74 +++++++++++++++++++++++++++++++++++++++++++++++ cmd/on.go | 26 +++++++++++++++-- cmd/root.go | 1 + pkg/rest/rest.go | 7 +++-- pkg/rest/turn.go | 31 +++++++++++++++----- 5 files changed, 128 insertions(+), 11 deletions(-) create mode 100644 cmd/brightness.go diff --git a/cmd/brightness.go b/cmd/brightness.go new file mode 100644 index 0000000..6bf5541 --- /dev/null +++ b/cmd/brightness.go @@ -0,0 +1,74 @@ +// Copyright 2024 Fabian `xx4h` Sylvester +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "fmt" + "slices" + + "github.com/rs/zerolog/log" + "github.com/spf13/cobra" + + "github.com/xx4h/hctl/pkg" + o "github.com/xx4h/hctl/pkg/output" + "github.com/xx4h/hctl/pkg/util" +) + +var ( + brightnessRange = util.MakeRangeString(1, 99) +) + +func newBrightnessCmd(h *pkg.Hctl) *cobra.Command { + cmd := &cobra.Command{ + Use: "brightness [min|max|1-99]", + Short: "Change brightness", + Aliases: []string{"b", "br", "bright"}, + Args: cobra.MatchAll(cobra.ExactArgs(2)), + ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) == 0 { + return compListStates(toComplete, args, []string{"turn_on", "turn_off"}, []string{"brightness"}, "", h) + } else if len(args) == 1 { + brightnessRange = append([]string{"min", "max"}, brightnessRange...) + return brightnessRange, cobra.ShellCompDirectiveNoFileComp + } + return nil, cobra.ShellCompDirectiveDefault + }, + PersistentPreRunE: func(_ *cobra.Command, args []string) error { + if err := validateBrightness(args[1]); err != nil { + return err + } + return nil + }, + Run: func(_ *cobra.Command, args []string) { + c := h.GetRest() + obj, state, sub, err := c.TurnLightOnBrightness(args[0], args[1]) + if err != nil { + o.PrintError(err) + } else { + o.PrintSuccessAction(obj, state) + } + log.Debug().Caller().Msgf("Result: %s(%s) to %s", obj, sub, state) + }, + } + + return cmd +} + +func validateBrightness(brightness string) error { + if !slices.Contains(brightnessRange, brightness) && brightness != "min" && brightness != "max" { + return fmt.Errorf("brightness needs to be 1-99, or min/max") + } + return nil +} diff --git a/cmd/on.go b/cmd/on.go index 597f48c..7536296 100644 --- a/cmd/on.go +++ b/cmd/on.go @@ -23,9 +23,10 @@ import ( ) func newOnCmd(h *pkg.Hctl) *cobra.Command { + var brightness string cmd := &cobra.Command{ - Use: "on", + Use: "on [-b|--brightness min|max|1-99]", Short: "Switch or turn on a light or switch", Args: cobra.MatchAll(cobra.ExactArgs(1)), ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { @@ -34,9 +35,21 @@ func newOnCmd(h *pkg.Hctl) *cobra.Command { } return compListStates(toComplete, args, []string{"turn_on"}, nil, "off", h) }, + PersistentPreRunE: func(_ *cobra.Command, _ []string) error { + if err := validateBrightness(brightness); err != nil { + return err + } + return nil + }, Run: func(_ *cobra.Command, args []string) { c := h.GetRest() - obj, state, sub, err := c.TurnOn(args[0]) + var obj, state, sub string + var err error + if brightness != "" { + obj, state, sub, err = c.TurnLightOnBrightness(args[0], brightness) + } else { + obj, state, sub, err = c.TurnOn(args[0]) + } if err != nil { o.PrintError(err) } else { @@ -46,5 +59,14 @@ func newOnCmd(h *pkg.Hctl) *cobra.Command { }, } + cmd.PersistentFlags().StringVarP(&brightness, "brightness", "b", "", "Set brightness") + err := cmd.RegisterFlagCompletionFunc("brightness", func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { + brightnessRange = append([]string{"min", "max"}, brightnessRange...) + return brightnessRange, cobra.ShellCompDirectiveNoFileComp + }) + if err != nil { + log.Error().Msgf("Could not register flag completion func for brightness: %+v", err) + } + return cmd } diff --git a/cmd/root.go b/cmd/root.go index 01a1b1d..24b33d4 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -62,6 +62,7 @@ func newRootCmd(h *pkg.Hctl, out io.Writer, _ []string) *cobra.Command { } cmd.AddCommand( + newBrightnessCmd(h), newCompletionCmd(), newConfigCmd(h, out), newInitCmd(h), diff --git a/pkg/rest/rest.go b/pkg/rest/rest.go index a269e32..3cab478 100644 --- a/pkg/rest/rest.go +++ b/pkg/rest/rest.go @@ -177,11 +177,14 @@ func (h *Hass) findEntity(name string, domain string, service string) (string, s } func (h *Hass) entityArgHandler(args []string, service string) (string, string, error) { + domain, name := splitDomainAndName(args[0]) if len(args) == 1 { - domain, name := splitDomainAndName(args[0]) return h.findEntity(name, domain, service) } else if len(args) == 2 { - return args[0], args[1], nil + if domain == "" { + return args[0], args[1], nil // #nosec G602 + } + return h.findEntity(name, domain, service) } return "", "", fmt.Errorf("entityArgHandler has to many entries in args: %d", len(args)) } diff --git a/pkg/rest/turn.go b/pkg/rest/turn.go index 7c453b7..9857894 100644 --- a/pkg/rest/turn.go +++ b/pkg/rest/turn.go @@ -14,15 +14,19 @@ package rest -import "fmt" +import ( + "fmt" +) -func (h *Hass) turn(state string, sub string, obj string) error { +func (h *Hass) turn(state, domain, device, brightness string) error { // if err := h.checkEntity(sub, fmt.Sprintf("turn_%s", state), obj); err != nil { // return err // } - - payload := map[string]any{"entity_id": fmt.Sprintf("%s.%s", sub, obj)} - res, err := h.api("POST", fmt.Sprintf("/services/%s/turn_%s", sub, state), payload) + payload := map[string]any{"entity_id": fmt.Sprintf("%s.%s", domain, device)} + if brightness != "" { + payload["brightness"] = brightness + } + res, err := h.api("POST", fmt.Sprintf("/services/%s/turn_%s", domain, state), payload) if err != nil { return err } @@ -39,7 +43,7 @@ func (h *Hass) TurnOff(args ...string) (string, string, string, error) { if err != nil { return "", "", "", err } - return obj, "off", sub, h.turn("off", sub, obj) + return obj, "off", sub, h.turn("off", sub, obj, "") } func (h *Hass) TurnOn(args ...string) (string, string, string, error) { @@ -47,7 +51,20 @@ func (h *Hass) TurnOn(args ...string) (string, string, string, error) { if err != nil { return "", "", "", err } - return obj, "on", sub, h.turn("on", sub, obj) + return obj, "on", sub, h.turn("on", sub, obj, "") +} + +func (h *Hass) TurnLightOnBrightness(device, brightness string) (string, string, string, error) { + domain, device, err := h.entityArgHandler([]string{device}, "turn_on") + if brightness == "min" { + brightness = "1" + } else if brightness == "max" { + brightness = "99" + } + if err != nil { + return "", "", "", err + } + return device, "on", domain, h.turn("on", domain, device, brightness) } func (h *Hass) TurnLightOff(obj string) (string, string, string, error) {