1+ /* -----------------------------------------------------------------
2+ * https://allmylinks.com/lordofscripts
3+ * Copyright (C)2023 Lord of Scripts(tm)
4+ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
5+ * Utility program that uses GOVEE API to control (On/Off/List/State)
6+ * GOVEE smart devices in your network.
7+ *-----------------------------------------------------------------*/
8+ package main
9+ // https://govee-public.s3.amazonaws.com/developer-docs/GoveeDeveloperAPIReference.pdf
10+
11+ import (
12+ "flag"
13+ "fmt"
14+ "os"
15+ "path"
16+ "strings"
17+
18+ "lordofscripts/govee"
19+ "lordofscripts/govee/util"
20+ veex "github.com/loxhill/go-vee"
21+ )
22+
23+ const (
24+ HOME_ENV = "HOME" // environment var. holding user home directory
25+ MY_CONFIG = ".config/govee.json" // config file relative to HOME_ENV
26+ API_KEY = ""
27+ )
28+
29+ /* ----------------------------------------------------------------
30+ * T y p e s
31+ *-----------------------------------------------------------------*/
32+ type ICommand interface {
33+ execute () error
34+ }
35+
36+ type GoveeCommand struct {
37+ Client * veex.Client
38+ Address string
39+ Model string
40+ }
41+
42+ /* ----------------------------------------------------------------
43+ * T y p e s
44+ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
45+ * OnCommand::GoveeCommand
46+ *-----------------------------------------------------------------*/
47+ type OnCommand struct {
48+ GoveeCommand
49+ }
50+
51+ func newCmdOn (clientPtr * veex.Client , address , model string ) * OnCommand {
52+ o := & OnCommand {
53+ GoveeCommand : GoveeCommand {
54+ Client : clientPtr ,
55+ Address : address ,
56+ Model : model ,
57+ },
58+ }
59+ return o
60+ }
61+
62+ // implements ICommand for OnCommand
63+ func (c * OnCommand ) execute () error {
64+ controlRequest , _ := c .Client .Device (c .Address , c .Model ).TurnOn ()
65+ _ , err := c .Client .Run (controlRequest )
66+ return err
67+ }
68+
69+ /* ----------------------------------------------------------------
70+ * T y p e s
71+ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
72+ * OffCommand::GoveeCommand
73+ *-----------------------------------------------------------------*/
74+ type OffCommand struct {
75+ GoveeCommand
76+ }
77+
78+ func newCmdOff (clientPtr * veex.Client , address , model string ) * OffCommand {
79+ o := & OffCommand {
80+ GoveeCommand : GoveeCommand {
81+ Client : clientPtr ,
82+ Address : address ,
83+ Model : model ,
84+ },
85+ }
86+ return o
87+ }
88+
89+ // implements ICommand for OffCommand
90+ func (c * OffCommand ) execute () error {
91+ controlRequest , _ := c .Client .Device (c .Address , c .Model ).TurnOff ()
92+ _ , err := c .Client .Run (controlRequest )
93+ return err
94+ }
95+
96+ /* ----------------------------------------------------------------
97+ * T y p e s
98+ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
99+ * StateCommand::GoveeCommand
100+ *-----------------------------------------------------------------*/
101+
102+ // the State commands obtains the online and powered state of a device
103+ // along its h/w address and model-
104+ type StateCommand struct {
105+ GoveeCommand
106+ }
107+
108+ func newCmdState (clientPtr * veex.Client , address , model string ) * StateCommand {
109+ o := & StateCommand {
110+ GoveeCommand : GoveeCommand {
111+ Client : clientPtr ,
112+ Address : address ,
113+ Model : model ,
114+ },
115+ }
116+ return o
117+ }
118+
119+ // implements ICommand for StateCommand
120+ func (c * StateCommand ) execute () error {
121+ stateRequest := c .Client .Device (c .Address , c .Model ).State ()
122+ // {Code:200 Message:Success Data:{Device:D7:B6:60:74:F4:02:D5:A2 Model:H5083 Properties:[map[online:true] map[powerState:off]] Devices:[]}}
123+ rsp , err := c .Client .Run (stateRequest ) // govee.GoveeResponse
124+ //fmt.Printf("%+v\n", rsp)
125+ if rsp .Code != 200 {
126+ err = fmt .Errorf ("State request failed: %q" , rsp .Message )
127+ } else {
128+ fmt .Println ("\t MAC :" , rsp .Data .Device )
129+ fmt .Println ("\t Model :" , rsp .Data .Model )
130+ fmt .Println ("\t Online :" , rsp .Data .Properties [0 ]["online" ])
131+ fmt .Println ("\t Powered:" , rsp .Data .Properties [1 ]["powerState" ])
132+ }
133+ return err
134+ }
135+ /* ----------------------------------------------------------------
136+ * T y p e s
137+ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
138+ * ListCommand::GoveeCommand
139+ *-----------------------------------------------------------------*/
140+ type ListCommand struct {
141+ GoveeCommand
142+ }
143+
144+ func newCmdList (clientPtr * veex.Client ) * ListCommand {
145+ o := & ListCommand {
146+ GoveeCommand : GoveeCommand {
147+ Client : clientPtr ,
148+ Address : "" ,
149+ Model : "" ,
150+ },
151+ }
152+ return o
153+ }
154+
155+ // implements ICommand for ListCommand
156+ func (c * ListCommand ) execute () error {
157+ listRequest := c .Client .ListDevices ()
158+ if response , err := c .Client .Run (listRequest ); err != nil {
159+ return err
160+ } else {
161+ devices := response .Devices ()
162+ for i ,d := range devices {
163+ fmt .Printf ("#%d\n \t Device: %s\n \t Model : %s\n \t Type : %s\n " , i + 1 , d .Device , d .Model , d .DeviceName )
164+ }
165+ }
166+ return nil
167+ }
168+
169+ /* ----------------------------------------------------------------
170+ * F u n c t i o n s
171+ *-----------------------------------------------------------------*/
172+
173+ func getHelp () {
174+ fmt .Printf ("Govee v%s by Lord of Scripts\n " , govee .CURRENT_VERSION )
175+ fmt .Println ("Usage:" )
176+ fmt .Println (" govee -list" )
177+ fmt .Println (" govee -mac {MAC_ADDRESS} -model {MODEL_NUMBER} -on" )
178+ fmt .Println (" govee -mac {MAC_ADDRESS} -model {MODEL_NUMBER} -off" )
179+ fmt .Println (" govee -mac {MAC_ADDRESS} -model {MODEL_NUMBER} -state" )
180+ // these two need a config file with entries
181+ fmt .Println (" govee -id {ALIAS} -on" )
182+ fmt .Println (" govee -id {ALIAS} -off" )
183+ fmt .Println (" govee -id {ALIAS} -state" )
184+ flag .PrintDefaults ()
185+ fmt .Println ("\t *** ***" )
186+ }
187+
188+ /* ----------------------------------------------------------------
189+ * M A I N
190+ *-----------------------------------------------------------------*/
191+ func main () {
192+ fmt .Printf ("\t \t ../ GoveeLux v%s (c)2023 Lord of Scripts \\ ..\n " , govee .CURRENT_VERSION )
193+ fmt .Println ("\t \t https://allmylinks.com/lordofscripts" )
194+
195+ var optHelp , optList , optOn , optOff , optState bool
196+ var inDevice , inModel , inAlias string
197+ flag .BoolVar (& optHelp , "help" , false , "This help" )
198+ flag .BoolVar (& optList , "list" , false , "List devices" )
199+ flag .BoolVar (& optOn , "on" , false , "Turn ON" )
200+ flag .BoolVar (& optOff , "off" , false , "Turn OFF" )
201+ flag .BoolVar (& optState , "state" , false , "Request online/power state of device" )
202+ flag .StringVar (& inDevice , "mac" , "" , "Device MAC" )
203+ flag .StringVar (& inModel , "model" , "" , "Device Model" )
204+ flag .StringVar (& inAlias , "id" , "" , "Device alias (derive Model & MAC from this)" )
205+ flag .Parse ()
206+
207+ // any command given or at least help?
208+ if optHelp || ! (optList || optOn || optOff || optState ) {
209+ getHelp ()
210+ if optHelp {
211+ os .Exit (0 )
212+ }
213+ os .Exit (1 )
214+ }
215+ // state && on & off are mutually exclusive
216+ if ! util .One (optOn , optOff , optState , optList ) {
217+ getHelp ()
218+ fmt .Println ("Decide either -on OR -off OR -state OR -list" )
219+ os .Exit (1 )
220+ }
221+
222+ // Config
223+ cfgFilename := path .Join (os .Getenv (HOME_ENV ), MY_CONFIG )
224+
225+ if len (inAlias ) != 0 {
226+ cfg := govee .Read (cfgFilename )
227+ candidates := cfg .Devices .Where (govee .ALIAS , inAlias )
228+ cnt := candidates .Count ()
229+ if cnt == 0 {
230+ fmt .Printf ("Could not find alias %q in repository\n " , inAlias )
231+ os .Exit (2 )
232+ }
233+
234+ if cnt > 1 {
235+ fmt .Printf ("Found %d entries. Alias not unique, please correct config\n " , cnt )
236+ os .Exit (2 )
237+ }
238+
239+ fmt .Println ("\t Found " , candidates [0 ], "\n \t At :" , candidates [0 ].Location )
240+ inDevice = candidates [0 ].MacAddress
241+ inModel = candidates [0 ].Model
242+ }
243+
244+ // with STATE, ON & OFF commands DEVICE & MODEL are required
245+ if (optOn || optOff || optState ) && ((len (inDevice ) == 0 ) && (len (inModel ) == 0 )) {
246+ getHelp ()
247+ fmt .Println ("-dev MAC and -model MODEL options are required!" )
248+ os .Exit (1 )
249+ } else {
250+ inDevice = strings .ToUpper (inDevice )
251+ inModel = strings .ToUpper (inModel )
252+ }
253+
254+ key := govee .GetAPI (cfgFilename )
255+ if len (key ) == 0 {
256+ fmt .Println ("You forgot to set your GOVEE API Key!" )
257+ os .Exit (2 )
258+ }
259+
260+ // TurnOff, TurnOn, SetBrightness, SetColor, SetColorTem
261+ client := veex .New (key )
262+ var command ICommand
263+ switch {
264+ case optList :
265+ command = newCmdList (client )
266+
267+ case optOn :
268+ command = newCmdOn (client , inDevice , inModel )
269+
270+ case optOff :
271+ command = newCmdOff (client , inDevice , inModel )
272+
273+ case optState :
274+ command = newCmdState (client , inDevice , inModel )
275+ }
276+
277+ if err := command .execute (); err != nil {
278+ //fmt.Printf("Error type: %T\nMessage: %s\n%v\n", err, err, err)
279+ fmt .Printf ("\t Error : %v\n " , err )
280+ }
281+ }
0 commit comments