Skip to content

Commit af56a42

Browse files
authored
Merge pull request #23 from mutablelogic/v1
Changes for home assistant service calling
2 parents c02b5cc + 316c55a commit af56a42

File tree

9 files changed

+339
-237
lines changed

9 files changed

+339
-237
lines changed

cmd/api/cmd.go

Lines changed: 3 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ type Fn struct {
2323
Description string
2424
MinArgs uint
2525
MaxArgs uint
26+
Syntax string
2627
Call func(context.Context, *tablewriter.Writer, []string) error
2728
}
2829

@@ -39,51 +40,8 @@ func (c *Cmd) Get(name string) *Fn {
3940
}
4041

4142
func (fn *Fn) CheckArgs(args []string) error {
42-
// Check number of arguments
43-
if fn.MinArgs != 0 && uint(len(args)) < fn.MinArgs {
44-
return fmt.Errorf("not enough arguments for %q (expected >= %d)", fn.Name, fn.MinArgs)
45-
}
46-
if fn.MaxArgs != 0 && uint(len(args)) > fn.MaxArgs {
47-
return fmt.Errorf("too many arguments for %q (expected <= %d)", fn.Name, fn.MaxArgs)
43+
if (fn.MinArgs != 0 && uint(len(args)) < fn.MinArgs) || (fn.MaxArgs != 0 && uint(len(args)) > fn.MaxArgs) {
44+
return fmt.Errorf("syntax error: %s %s", fn.Name, fn.Syntax)
4845
}
4946
return nil
5047
}
51-
52-
/*
53-
if fn == nil {
54-
return nil, fmt.Errorf("unknown command %q", name)
55-
}
56-
57-
return c.getFn(name), nil
58-
// Get the command function
59-
var fn *Fn
60-
var nargs uint
61-
var out []string
62-
if len(args) == 0 {
63-
fn = c.getFn("")
64-
} else {
65-
fn = c.getFn(args[0])
66-
nargs = uint(len(args) - 1)
67-
out = args[1:]
68-
}
69-
if fn == nil {
70-
// No arguments and no default command
71-
return nil, nil, nil
72-
}
73-
74-
// Check number of arguments
75-
name := fn.Name
76-
if name == "" {
77-
name = c.Name
78-
}
79-
if fn.MinArgs != 0 && nargs < fn.MinArgs {
80-
return nil, nil, fmt.Errorf("not enough arguments for %q", name)
81-
}
82-
if fn.MaxArgs != 0 && nargs > fn.MaxArgs {
83-
return nil, nil, fmt.Errorf("too many arguments for %q", name)
84-
}
85-
86-
// Return the command
87-
return fn, out, nil
88-
}
89-
*/

cmd/api/flags.go

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ func (flags *Flags) Parse(args []string) (*Fn, []string, error) {
7979
// Parse command line
8080
err := flags.FlagSet.Parse(args)
8181

82-
// If there is a version argument, print the version and exit
82+
// Check for global commands
8383
if flags.NArg() == 1 {
8484
switch flags.Arg(0) {
8585
case "version":
@@ -97,17 +97,27 @@ func (flags *Flags) Parse(args []string) (*Fn, []string, error) {
9797
// If the name of the command is the same as the name of the application
9898
flags.cmd = cmd
9999
flags.root = cmd.Name
100-
flags.fn = ""
101-
flags.args = flags.Args()
100+
if len(flags.Args()) > 0 {
101+
flags.fn = flags.Arg(0)
102+
if len(flags.Args()) > 1 {
103+
flags.args = flags.Args()[1:]
104+
}
105+
}
102106
} else if flags.NArg() > 0 {
103107
if cmd := flags.getCommandSet(flags.Arg(0)); cmd != nil {
104108
flags.cmd = cmd
105109
flags.root = strings.Join([]string{flags.Name(), cmd.Name}, " ")
106110
flags.fn = flags.Arg(1)
107-
flags.args = flags.Args()[1:]
111+
if len(flags.Args()) > 1 {
112+
flags.args = flags.Args()[2:]
113+
}
108114
}
109115
}
110116

117+
if flags.GetBool("debug") {
118+
fmt.Fprintf(os.Stderr, "Function: %q Args: %q\n", flags.fn, flags.args)
119+
}
120+
111121
// Print usage
112122
if err != nil {
113123
if err != flag.ErrHelp {
@@ -117,7 +127,7 @@ func (flags *Flags) Parse(args []string) (*Fn, []string, error) {
117127
}
118128
return nil, nil, err
119129
} else if flags.cmd == nil {
120-
fmt.Fprintln(os.Stderr, "Unknown command, try -help")
130+
fmt.Fprintf(os.Stderr, "Unknown command, try \"%s -help\"\n", flags.Name())
121131
return nil, nil, ErrHelp
122132
}
123133

@@ -140,7 +150,7 @@ func (flags *Flags) Parse(args []string) (*Fn, []string, error) {
140150
// Set the function to call
141151
fn := flags.cmd.Get(flags.fn)
142152
if fn == nil {
143-
fmt.Fprintf(os.Stderr, "Unknown command %q, try -help\n", flags.fn)
153+
fmt.Fprintf(os.Stderr, "Unknown command, try \"%s -help\"\n", flags.Name())
144154
return nil, nil, ErrHelp
145155
}
146156

@@ -151,7 +161,7 @@ func (flags *Flags) Parse(args []string) (*Fn, []string, error) {
151161
}
152162

153163
// Return success
154-
return fn, args, nil
164+
return fn, flags.args, nil
155165
}
156166

157167
// Get returns the value of a flag, and returns true if the flag exists
@@ -252,7 +262,7 @@ func (flags *Flags) PrintCommandUsage(cmd *Cmd) {
252262
// Help for command sets
253263
fmt.Fprintln(w, "Commands:")
254264
for _, fn := range cmd.Fn {
255-
fmt.Fprintln(w, " ", flags.root, fn.Name)
265+
fmt.Fprintln(w, " ", flags.root, fn.Name, fn.Syntax)
256266
fmt.Fprintln(w, " ", fn.Description)
257267
fmt.Fprintln(w, "")
258268
}

cmd/api/homeassistant.go

Lines changed: 79 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ package main
22

33
import (
44
"context"
5-
"fmt"
6-
"slices"
75
"strings"
86
"time"
97

@@ -16,16 +14,19 @@ import (
1614
// TYPES
1715

1816
type haEntity struct {
19-
Id string `json:"entity_id"`
17+
Id string `json:"entity_id,width:40"`
2018
Name string `json:"name,omitempty"`
2119
Class string `json:"class,omitempty"`
20+
Domain string `json:"domain,omitempty"`
2221
State string `json:"state,omitempty"`
2322
Attributes map[string]interface{} `json:"attributes,omitempty,wrap"`
24-
UpdatedAt time.Time `json:"last_updated,omitempty"`
23+
UpdatedAt time.Time `json:"last_updated,omitempty,width:34"`
24+
ChangedAt time.Time `json:"last_changed,omitempty,width:34"`
2525
}
2626

27-
type haClass struct {
28-
Class string `json:"class,omitempty"`
27+
type haDomain struct {
28+
Name string `json:"domain"`
29+
Services string `json:"services,omitempty"`
2930
}
3031

3132
///////////////////////////////////////////////////////////////////////////////
@@ -49,8 +50,10 @@ func haRegister(flags *Flags) {
4950
Description: "Information from home assistant",
5051
Parse: haParse,
5152
Fn: []Fn{
52-
{Name: "classes", Call: haClasses, Description: "Return entity classes"},
53-
{Name: "states", Call: haStates, Description: "Return entity states"},
53+
{Name: "domains", Call: haDomains, Description: "Enumerate entity domains"},
54+
{Name: "states", Call: haStates, Description: "Show current entity states", MaxArgs: 1, Syntax: "(<name>)"},
55+
{Name: "services", Call: haServices, Description: "Show services for an entity", MinArgs: 1, MaxArgs: 1, Syntax: "<entity>"},
56+
{Name: "call", Call: haCall, Description: "Call a service for an entity", MinArgs: 2, MaxArgs: 2, Syntax: "<call> <entity>"},
5457
},
5558
})
5659
}
@@ -71,14 +74,25 @@ func haParse(flags *Flags, opts ...client.ClientOpt) error {
7174
// METHODS
7275

7376
func haStates(_ context.Context, w *tablewriter.Writer, args []string) error {
74-
if states, err := haGetStates(args); err != nil {
77+
var result []haEntity
78+
states, err := haGetStates(nil)
79+
if err != nil {
7580
return err
76-
} else {
77-
return w.Write(states)
7881
}
82+
83+
for _, state := range states {
84+
if len(args) == 1 {
85+
if !haMatchString(args[0], state.Name, state.Id) {
86+
continue
87+
}
88+
89+
}
90+
result = append(result, state)
91+
}
92+
return w.Write(result)
7993
}
8094

81-
func haClasses(_ context.Context, w *tablewriter.Writer, args []string) error {
95+
func haDomains(_ context.Context, w *tablewriter.Writer, args []string) error {
8296
states, err := haGetStates(nil)
8397
if err != nil {
8498
return err
@@ -89,17 +103,52 @@ func haClasses(_ context.Context, w *tablewriter.Writer, args []string) error {
89103
classes[state.Class] = true
90104
}
91105

92-
result := []haClass{}
106+
result := []haDomain{}
93107
for c := range classes {
94-
result = append(result, haClass{Class: c})
108+
result = append(result, haDomain{
109+
Name: c,
110+
})
95111
}
96112
return w.Write(result)
97113
}
98114

115+
func haServices(_ context.Context, w *tablewriter.Writer, args []string) error {
116+
service, err := haClient.State(args[0])
117+
if err != nil {
118+
return err
119+
}
120+
services, err := haClient.Services(service.Domain())
121+
if err != nil {
122+
return err
123+
}
124+
return w.Write(services)
125+
}
126+
127+
func haCall(_ context.Context, w *tablewriter.Writer, args []string) error {
128+
service := args[0]
129+
entity := args[1]
130+
states, err := haClient.Call(service, entity)
131+
if err != nil {
132+
return err
133+
}
134+
return w.Write(states)
135+
}
136+
99137
///////////////////////////////////////////////////////////////////////////////
100138
// PRIVATE METHODS
101139

102-
func haGetStates(classes []string) ([]haEntity, error) {
140+
func haMatchString(q string, values ...string) bool {
141+
q = strings.ToLower(q)
142+
for _, v := range values {
143+
v = strings.ToLower(v)
144+
if strings.Contains(v, q) {
145+
return true
146+
}
147+
}
148+
return false
149+
}
150+
151+
func haGetStates(domains []string) ([]haEntity, error) {
103152
var result []haEntity
104153

105154
// Get states from the remote service
@@ -112,37 +161,29 @@ func haGetStates(classes []string) ([]haEntity, error) {
112161
for _, state := range states {
113162
entity := haEntity{
114163
Id: state.Entity,
115-
State: state.State,
164+
Name: state.Name(),
165+
Domain: state.Domain(),
166+
Class: state.Class(),
167+
State: state.Value(),
116168
Attributes: state.Attributes,
117-
UpdatedAt: state.LastChanged,
169+
UpdatedAt: state.LastUpdated,
170+
ChangedAt: state.LastChanged,
118171
}
119172

120-
// Ignore entities without state
121-
if entity.State == "" || entity.State == "unknown" || entity.State == "unavailable" {
173+
// Ignore any fields where the state is empty
174+
if entity.State == "" {
122175
continue
123176
}
124177

125-
// Set entity type and name from entity id
126-
parts := strings.SplitN(entity.Id, ".", 2)
127-
if len(parts) >= 2 {
128-
entity.Class = strings.ToLower(parts[0])
129-
entity.Name = parts[1]
130-
}
131-
132-
// Set entity type from device class
133-
if t, exists := state.Attributes["device_class"]; exists {
134-
entity.Class = fmt.Sprint(t)
178+
// Add unit of measurement
179+
if unit := state.UnitOfMeasurement(); unit != "" {
180+
entity.State += " " + unit
135181
}
136182

137-
// Filter classes
138-
if len(classes) > 0 && !slices.Contains(classes, entity.Class) {
139-
continue
140-
}
141-
142-
// Set entity name from attributes
143-
if name, exists := state.Attributes["friendly_name"]; exists {
144-
entity.Name = fmt.Sprint(name)
145-
}
183+
// Filter domains
184+
//if len(domains) > 0 && !slices.Contains(domains, entity.Domain) {
185+
// continue
186+
//}
146187

147188
// Append results
148189
result = append(result, entity)

pkg/homeassistant/client_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,18 @@ func Test_client_001(t *testing.T) {
2222
// ENVIRONMENT
2323

2424
func GetApiKey(t *testing.T) string {
25-
key := os.Getenv("HA_API_KEY")
25+
key := os.Getenv("HA_TOKEN")
2626
if key == "" {
27-
t.Skip("HA_API_KEY not set")
27+
t.Skip("HA_TOKEN not set")
2828
t.SkipNow()
2929
}
3030
return key
3131
}
3232

3333
func GetEndPoint(t *testing.T) string {
34-
key := os.Getenv("HA_API_URL")
34+
key := os.Getenv("HA_ENDPOINT")
3535
if key == "" {
36-
t.Skip("HA_API_URL not set")
36+
t.Skip("HA_ENDPOINT not set")
3737
t.SkipNow()
3838
}
3939
return key

0 commit comments

Comments
 (0)