Skip to content

Commit

Permalink
Refactoring. Added Lights support.
Browse files Browse the repository at this point in the history
  • Loading branch information
lordofscripts authored Jul 10, 2024
1 parent 4a6c0ee commit 2988cff
Showing 1 changed file with 84 additions and 160 deletions.
244 changes: 84 additions & 160 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ package main
// https://govee-public.s3.amazonaws.com/developer-docs/GoveeDeveloperAPIReference.pdf

import (
"flag"
"flag"
"fmt"
"os"
"path"
"strings"
"time"

"lordofscripts/govee"
"lordofscripts/govee/util"
Expand All @@ -24,163 +25,55 @@ const (
HOME_ENV = "HOME" // environment var. holding user home directory
MY_CONFIG = ".config/govee.json" // config file relative to HOME_ENV
API_KEY = ""
)

/* ----------------------------------------------------------------
* T y p e s
*-----------------------------------------------------------------*/
type ICommand interface {
execute() error
}

type GoveeCommand struct {
Client *veex.Client
Address string
Model string
}

/* ----------------------------------------------------------------
* T y p e s
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* OnCommand::GoveeCommand
*-----------------------------------------------------------------*/
type OnCommand struct {
GoveeCommand
}

func newCmdOn(clientPtr *veex.Client, address, model string) *OnCommand {
o := &OnCommand{
GoveeCommand: GoveeCommand{
Client: clientPtr,
Address: address,
Model: model,
},
}
return o
}

// implements ICommand for OnCommand
func (c *OnCommand) execute() error {
controlRequest, _ := c.Client.Device(c.Address, c.Model).TurnOn()
_, err := c.Client.Run(controlRequest)
return err
}

/* ----------------------------------------------------------------
* T y p e s
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* OffCommand::GoveeCommand
*-----------------------------------------------------------------*/
type OffCommand struct {
GoveeCommand
}

func newCmdOff(clientPtr *veex.Client, address, model string) *OffCommand {
o := &OffCommand{
GoveeCommand: GoveeCommand{
Client: clientPtr,
Address: address,
Model: model,
},
}
return o
}

// implements ICommand for OffCommand
func (c *OffCommand) execute() error {
controlRequest, _ := c.Client.Device(c.Address, c.Model).TurnOff()
_, err := c.Client.Run(controlRequest)
return err
}
RETVAL_CLI_MISSING int = 1
RETVAL_CLI_INVALID int = 2
RETVAL_CFG_ALIAS int = 3
RETVAL_CFG_MODEL int = 4
RETVAL_CFG_API int = 5
RETVAL_CMD_EXEC_ABORT int = 6
)

/* ----------------------------------------------------------------
* T y p e s
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* StateCommand::GoveeCommand
*-----------------------------------------------------------------*/

// the State commands obtains the online and powered state of a device
// along its h/w address and model-
type StateCommand struct {
GoveeCommand
}

func newCmdState(clientPtr *veex.Client, address, model string) *StateCommand {
o := &StateCommand{
GoveeCommand: GoveeCommand{
Client: clientPtr,
Address: address,
Model: model,
},
}
return o
}

// implements ICommand for StateCommand
func (c *StateCommand) execute() error {
stateRequest := c.Client.Device(c.Address, c.Model).State()
// {Code:200 Message:Success Data:{Device:D7:B6:60:74:F4:02:D5:A2 Model:H5083 Properties:[map[online:true] map[powerState:off]] Devices:[]}}
rsp, err := c.Client.Run(stateRequest) // govee.GoveeResponse
//fmt.Printf("%+v\n", rsp)
if rsp.Code != 200 {
err = fmt.Errorf("State request failed: %q", rsp.Message)
} else {
fmt.Println("\tMAC :", rsp.Data.Device)
fmt.Println("\tModel :", rsp.Data.Model)
fmt.Println("\tOnline :", rsp.Data.Properties[0]["online"])
fmt.Println("\tPowered:", rsp.Data.Properties[1]["powerState"])
}
return err
}
/* ----------------------------------------------------------------
* T y p e s
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* ListCommand::GoveeCommand
* F u n c t i o n s
*-----------------------------------------------------------------*/
type ListCommand struct {
GoveeCommand
}

func newCmdList(clientPtr *veex.Client) *ListCommand {
o := &ListCommand{
GoveeCommand: GoveeCommand{
Client: clientPtr,
Address: "",
Model: "",
},
}
return o
}

// implements ICommand for ListCommand
func (c *ListCommand) execute() error {
listRequest := c.Client.ListDevices()
if response, err := c.Client.Run(listRequest); err != nil {
return err
} else {
devices := response.Devices()
for i,d := range devices {
fmt.Printf("#%d\n\tDevice: %s\n\tModel : %s\n\tType : %s\n", i+1, d.Device, d.Model, d.DeviceName)
// look up our ~/.config/govee.json for entries matching MAC
func findByMAC(mac string) *govee.GoveeDevice {
cfgFilename := path.Join(os.Getenv(HOME_ENV), MY_CONFIG)
mac = strings.ToUpper(mac)
cfg := govee.Read(cfgFilename)
candidates := cfg.Devices.Where(govee.MAC, mac)
cnt := candidates.Count()
if cnt > 0 {
for _,v := range candidates {
if strings.ToUpper(v.MacAddress) == mac {
return &v
}
}
}

return nil
}

/* ----------------------------------------------------------------
* F u n c t i o n s
*-----------------------------------------------------------------*/
// print a message and terminate application execution
func die(retVal int, format string, v ...any) {
fmt.Printf(format + "\n", v...)
os.Exit(retVal)
}

func getHelp() {
fmt.Printf("Govee v%s by Lord of Scripts\n", govee.CURRENT_VERSION)
fmt.Printf("%s by Lord of Scripts\n", govee.Version)
fmt.Println("Usage:")
fmt.Println(" govee -list")
fmt.Println(" govee -mac {MAC_ADDRESS} -model {MODEL_NUMBER} -on")
fmt.Println(" govee -mac {MAC_ADDRESS} -model {MODEL_NUMBER} -off")
fmt.Println(" govee -mac {MAC_ADDRESS} -model {MODEL_NUMBER} -state")
fmt.Println(" govee -mac {MAC_ADDRESS} -model {MODEL_NUMBER} [device command]")
// these two need a config file with entries
fmt.Println(" govee -id {ALIAS} -on")
fmt.Println(" govee -id {ALIAS} -off")
fmt.Println(" govee -id {ALIAS} -state")
fmt.Println(" govee -id {ALIAS} [device command]")
fmt.Println("Options:")
flag.PrintDefaults()
fmt.Println("\t*** ***")
}
Expand All @@ -189,34 +82,53 @@ func getHelp() {
* M A I N
*-----------------------------------------------------------------*/
func main() {
fmt.Printf("\t\t../ GoveeLux v%s (c)2023 Lord of Scripts \\..\n", govee.CURRENT_VERSION)
const (
DEF_BRIGHT uint = 101
)
fmt.Printf("\t\t../ %s (c)2023-%d Lord of Scripts \\..\n", govee.Version, time.Now().Year())
fmt.Println("\t\t https://allmylinks.com/lordofscripts")

// declare real CLI options
var optHelp, optList, optOn, optOff, optState bool
var inDevice, inModel, inAlias string
// declare CLI options which have an explicit value
var inDevice, inModel, inAlias, inColor string
var inBright uint
flag.BoolVar(&optHelp, "help", false, "This help")
flag.BoolVar(&optList, "list", false, "List devices")
flag.BoolVar(&optOn, "on", false, "Turn ON")
flag.BoolVar(&optOff, "off", false, "Turn OFF")
flag.BoolVar(&optState, "state", false, "Request online/power state of device")
flag.BoolVar(&optOn, "on", false, "Turn ON [device command]")
flag.BoolVar(&optOff, "off", false, "Turn OFF [device command]")
flag.BoolVar(&optState, "state", false, "Request online/power state of device [device command]")
flag.StringVar(&inDevice, "mac", "", "Device MAC")
flag.StringVar(&inModel, "model", "", "Device Model")
flag.StringVar(&inAlias, "id", "", "Device alias (derive Model & MAC from this)")
flag.StringVar(&inColor, "color", "", "Hexadecimal RGB color code, i.e. ffa512")
flag.UintVar(&inBright, "brightness", DEF_BRIGHT, "Light brightness (0..100) [device command]")
flag.Parse()

// declare virtual options
var voptBright, voptColor bool
// virtual options based on values
if inBright < DEF_BRIGHT {
voptBright = true
}
if inColor != "" {
voptColor = true
}

// any command given or at least help?
if optHelp || !(optList || optOn || optOff || optState) {
if optHelp || !(optList || optOn || optOff || optState || voptBright || voptColor) {
getHelp()
if optHelp {
os.Exit(0)
}
os.Exit(1)

die(RETVAL_CLI_MISSING, "Missing CLI options")
}

// state && on & off are mutually exclusive
if !util.One(optOn, optOff, optState, optList) {
if !util.One(optOn, optOff, optState, optList, voptBright, voptColor) {
getHelp()
fmt.Println("Decide either -on OR -off OR -state OR -list")
os.Exit(1)

die(RETVAL_CLI_INVALID, "Decide either -on OR -off OR -state OR -list")
}

// Config
Expand All @@ -227,13 +139,11 @@ func main() {
candidates := cfg.Devices.Where(govee.ALIAS, inAlias)
cnt := candidates.Count()
if cnt == 0 {
fmt.Printf("Could not find alias %q in repository\n", inAlias)
os.Exit(2)
die(RETVAL_CFG_ALIAS, "Could not find alias %q in repository\n", inAlias)
}

if cnt > 1 {
fmt.Printf("Found %d entries. Alias not unique, please correct config\n", cnt)
os.Exit(2)
die(RETVAL_CFG_ALIAS, "Found %d entries. Alias not unique, please correct config\n", cnt)
}

fmt.Println("\tFound ", candidates[0], "\n\tAt :", candidates[0].Location)
Expand All @@ -244,17 +154,16 @@ func main() {
// with STATE, ON & OFF commands DEVICE & MODEL are required
if (optOn || optOff || optState) && ((len(inDevice) == 0) && (len(inModel) == 0)) {
getHelp()
fmt.Println("-dev MAC and -model MODEL options are required!")
os.Exit(1)

die(RETVAL_CFG_MODEL, "-dev MAC and -model MODEL options are required!")
} else {
inDevice = strings.ToUpper(inDevice)
inModel = strings.ToUpper(inModel)
}

key := govee.GetAPI(cfgFilename)
if len(key) == 0 {
fmt.Println("You forgot to set your GOVEE API Key!")
os.Exit(2)
die(RETVAL_CFG_API, "You forgot to set your GOVEE API Key!")
}

// TurnOff, TurnOn, SetBrightness, SetColor, SetColorTem
Expand All @@ -272,10 +181,25 @@ func main() {

case optState:
command = newCmdState(client, inDevice, inModel)

case voptBright:
command = newCmdBrightness(client, inDevice, inModel, inBright)

case voptColor:
if !strings.HasPrefix(inColor, "#") {
inColor = "#" + inColor
}
if color, err := util.ParseHexColorGovee(inColor); err == nil {
command = newCmdColor(client, inDevice, inModel, color)
} else {
die(RETVAL_CLI_INVALID, "Invalid hex RGB color code: %s", inColor)
}
}

if err := command.execute(); err != nil {
//fmt.Printf("Error type: %T\nMessage: %s\n%v\n", err, err, err)
fmt.Printf("\tError : %v\n", err)
fmt.Printf("\tCommand: %s\n\tError : %v\n", command.name(), err)
} else {
fmt.Printf("\tCommand: %s\n", command.name())
}
}
}

0 comments on commit 2988cff

Please sign in to comment.