-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
10 changed files
with
1,455 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
## Changelog for lordofscripts/govee | ||
|
||
--- | ||
### 1.0 | ||
##### Released on _Wed 1.Nov.2023_ by _lordofscripts_ | ||
### First Release! | ||
* Supports queries (-list and -state) and state changes (-on and -off) | ||
* While it seems like an overkill, it uses a _Command Pattern_ to process commands | ||
* Command-line interface application written in GO |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
# Lord of Scripts(tm)) Govee CLI | ||
|
||
This is a handy command-line interface application to list all Govee smart | ||
devices in your home network, as well as control them (On/Off). | ||
|
||
The more verbose way requires several **CLI** parameters such as device MAC & Model | ||
in order to control them. Alternatively, you can put all your Govee smart | ||
devices in a configuration file and assign them an easy-to-remember ID (alias). | ||
|
||
With this you can use the CLI to perform the actions you desire, or create | ||
desktop shortcuts that invoke the CLI command. | ||
|
||
I'm sure you will find this utility quite handy. Feel free to consider a small | ||
donation so that I can continue working on these utilities: | ||
|
||
[Buy LostInWriting a coffee!](https://www.buymeacoffee.com/lostinwriting) | ||
|
||
## Requirements | ||
|
||
- GO v1.20 | ||
- github.com/loxhill/go-vee | ||
- Request a Govee API key | ||
|
||
## Using Govee API | ||
This small Go app is conveniently named "govee" which is the name of the | ||
executable. | ||
|
||
### Configuration File | ||
|
||
The configuration file, if present, should be at `~/.config/govee.json` and it | ||
must be in JSON format. It would look like this: | ||
|
||
>{ | ||
> "version": "0.1", | ||
> "apiKey": "YOUR GOVEE API KEY HERE" | ||
> "devices": [ | ||
> { | ||
> "model": "H5083", | ||
> "mac": "DEVICE MAC ADDRESS", | ||
> "alias": "SmartPlug1", | ||
> "location": "Recamara #2" | ||
> } | ||
> ] | ||
>} | ||
The MAC address is a series of 8 hexadecimal pairs which is unique to each | ||
device and looks like this: `AA:BB:CC:DD:EE:FF:00:11` | ||
|
||
You will need to request a personal API key to Govee, they are quite quick. But | ||
do remember that it is only for **personal** use, not commercial use! If you | ||
use a configuration file, make sure to put your API key where indicated. | ||
Alternatively, you can put the API key in the `GOVEE_API` environment variable. | ||
|
||
### Obtaining a GOVEE API key | ||
|
||
The keys are for personal use only, if you keep that in mind and suits your needs, | ||
then all is okay. | ||
|
||
- Install the Govee Home app on your mobile. You probably already have since | ||
you have a Govee device. I am using Govee Home app v5.8.30. | ||
- Click on the Cog wheel (`Settings`) on the upper right) | ||
- In the `Settings` page, click on the `Apply for API Key` | ||
- Fill out the requested information (Name, reason, agreement) and send. | ||
|
||
You should receive the personal API key within a day.) | ||
|
||
### Usage | ||
|
||
> govee -list | ||
Uses your WiFi/Internet connection to query your local network and list ALL | ||
Govee smart devices. It will list them giving you their model number (for use | ||
with -model flag), MAC address (-mac flag) and official name. | ||
|
||
> govee -mac {MAC_ADDRESS} -model {MODEL_NUMBER} -on | ||
Turns on the device identified by the MAC address and Model number. | ||
|
||
> govee -mac {MAC_ADDRESS} -model {MODEL_NUMBER} -off | ||
Turns off the device identified by the MAC address and Model number. | ||
|
||
However, it is much easier to assign easy identifications to your device and | ||
even annotate it with their locations within your premises. For that, you need | ||
to use a *configuration file* as described above. Once you do that, you can | ||
easily turn your devices on or off using their alias, which you can remember | ||
without having to look them up. Here are the config-based versions of the | ||
previous two commands: | ||
|
||
> govee -id {ALIAS_NAME} -on | ||
Turns on the device identified by the MAC address and Model number. For the | ||
sample configuration above it would become `govee -id SmartPlug1 -on` | ||
|
||
> govee -id {ALIAS_NAME} -off | ||
Turns off the device identified by the MAC address and Model number. | ||
|
||
----- | ||
https://allmylinks.com/lordofscripts |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,281 @@ | ||
/* ----------------------------------------------------------------- | ||
* https://allmylinks.com/lordofscripts | ||
* Copyright (C)2023 Lord of Scripts(tm) | ||
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
* Utility program that uses GOVEE API to control (On/Off/List/State) | ||
* GOVEE smart devices in your network. | ||
*-----------------------------------------------------------------*/ | ||
package main | ||
// https://govee-public.s3.amazonaws.com/developer-docs/GoveeDeveloperAPIReference.pdf | ||
|
||
import ( | ||
"flag" | ||
"fmt" | ||
"os" | ||
"path" | ||
"strings" | ||
|
||
"lordofscripts/govee" | ||
"lordofscripts/govee/util" | ||
veex "github.com/loxhill/go-vee" | ||
) | ||
|
||
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 | ||
} | ||
|
||
/* ---------------------------------------------------------------- | ||
* 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 | ||
*-----------------------------------------------------------------*/ | ||
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) | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
/* ---------------------------------------------------------------- | ||
* F u n c t i o n s | ||
*-----------------------------------------------------------------*/ | ||
|
||
func getHelp() { | ||
fmt.Printf("Govee v%s by Lord of Scripts\n", govee.CURRENT_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") | ||
// 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") | ||
flag.PrintDefaults() | ||
fmt.Println("\t*** ***") | ||
} | ||
|
||
/* ---------------------------------------------------------------- | ||
* M A I N | ||
*-----------------------------------------------------------------*/ | ||
func main() { | ||
fmt.Printf("\t\t../ GoveeLux v%s (c)2023 Lord of Scripts \\..\n", govee.CURRENT_VERSION) | ||
fmt.Println("\t\t https://allmylinks.com/lordofscripts") | ||
|
||
var optHelp, optList, optOn, optOff, optState bool | ||
var inDevice, inModel, inAlias string | ||
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.StringVar(&inDevice, "mac", "", "Device MAC") | ||
flag.StringVar(&inModel, "model", "", "Device Model") | ||
flag.StringVar(&inAlias, "id", "", "Device alias (derive Model & MAC from this)") | ||
flag.Parse() | ||
|
||
// any command given or at least help? | ||
if optHelp || !(optList || optOn || optOff || optState) { | ||
getHelp() | ||
if optHelp { | ||
os.Exit(0) | ||
} | ||
os.Exit(1) | ||
} | ||
// state && on & off are mutually exclusive | ||
if !util.One(optOn, optOff, optState, optList) { | ||
getHelp() | ||
fmt.Println("Decide either -on OR -off OR -state OR -list") | ||
os.Exit(1) | ||
} | ||
|
||
// Config | ||
cfgFilename := path.Join(os.Getenv(HOME_ENV), MY_CONFIG) | ||
|
||
if len(inAlias) != 0 { | ||
cfg := govee.Read(cfgFilename) | ||
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) | ||
} | ||
|
||
if cnt > 1 { | ||
fmt.Printf("Found %d entries. Alias not unique, please correct config\n", cnt) | ||
os.Exit(2) | ||
} | ||
|
||
fmt.Println("\tFound ", candidates[0], "\n\tAt :", candidates[0].Location) | ||
inDevice = candidates[0].MacAddress | ||
inModel = candidates[0].Model | ||
} | ||
|
||
// 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) | ||
} 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) | ||
} | ||
|
||
// TurnOff, TurnOn, SetBrightness, SetColor, SetColorTem | ||
client := veex.New(key) | ||
var command ICommand | ||
switch { | ||
case optList: | ||
command = newCmdList(client) | ||
|
||
case optOn: | ||
command = newCmdOn(client, inDevice, inModel) | ||
|
||
case optOff: | ||
command = newCmdOff(client, inDevice, inModel) | ||
|
||
case optState: | ||
command = newCmdState(client, inDevice, inModel) | ||
} | ||
|
||
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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
module lordofscripts/govee | ||
|
||
go 1.21.0 | ||
|
||
require github.com/loxhill/go-vee v0.0.0-20230825161508-7c8c34d2ee28 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
github.com/loxhill/go-vee v0.0.0-20230825161508-7c8c34d2ee28 h1:oSED7BzozltkdBVaMIaQkK5d7/V9avQx/XK3HfV8Aw0= | ||
github.com/loxhill/go-vee v0.0.0-20230825161508-7c8c34d2ee28/go.mod h1:gLqZGKn+W6944XJfI9VastC/QRVTVs+Htd9R8aiE1dQ= |
Oops, something went wrong.