Skip to content

Commit

Permalink
Merge pull request #7 from mutablelogic/v1
Browse files Browse the repository at this point in the history
Added home assistant client
  • Loading branch information
djthorpe authored Mar 31, 2024
2 parents b9740e8 + 4e523b5 commit 3c52b9b
Show file tree
Hide file tree
Showing 8 changed files with 417 additions and 0 deletions.
39 changes: 39 additions & 0 deletions pkg/homeassistant/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
homeassistant implements an API client for Home Assistant API
https://developers.home-assistant.io/docs/api/rest/
*/
package homeassistant

import (
// Packages
"github.com/mutablelogic/go-client/pkg/client"
)

///////////////////////////////////////////////////////////////////////////////
// TYPES

type Client struct {
*client.Client
}

///////////////////////////////////////////////////////////////////////////////
// LIFECYCLE

func New(endPoint, apiKey string, opts ...client.ClientOpt) (*Client, error) {
// Add a final slash to the endpoint
if endPoint[len(endPoint)-1] != '/' {
endPoint += "/"
}

// Create client
client, err := client.New(append(opts, client.OptEndpoint(endPoint), client.OptReqToken(client.Token{
Scheme: client.Bearer,
Value: apiKey,
}))...)
if err != nil {
return nil, err
}

// Return the client
return &Client{client}, nil
}
40 changes: 40 additions & 0 deletions pkg/homeassistant/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package homeassistant_test

import (
"os"
"testing"

// Packages
opts "github.com/mutablelogic/go-client/pkg/client"
homeassistant "github.com/mutablelogic/go-client/pkg/homeassistant"
assert "github.com/stretchr/testify/assert"
)

func Test_client_001(t *testing.T) {
assert := assert.New(t)
client, err := homeassistant.New(GetEndPoint(t), GetApiKey(t), opts.OptTrace(os.Stderr, true))
assert.NoError(err)
assert.NotNil(client)
t.Log(client)
}

///////////////////////////////////////////////////////////////////////////////
// ENVIRONMENT

func GetApiKey(t *testing.T) string {
key := os.Getenv("HA_API_KEY")
if key == "" {
t.Skip("HA_API_KEY not set")
t.SkipNow()
}
return key
}

func GetEndPoint(t *testing.T) string {
key := os.Getenv("HA_API_URL")
if key == "" {
t.Skip("HA_API_URL not set")
t.SkipNow()
}
return key
}
26 changes: 26 additions & 0 deletions pkg/homeassistant/events.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package homeassistant

import "github.com/mutablelogic/go-client/pkg/client"

///////////////////////////////////////////////////////////////////////////////
// TYPES

type Event struct {
Event string `json:"event"`
Listeners uint `json:"listener_count"`
}

///////////////////////////////////////////////////////////////////////////////
// API CALLS

// Events returns all the events and number of listeners
func (c *Client) Events() ([]Event, error) {
var response []Event
payload := client.NewRequest(client.ContentTypeJson)
if err := c.Do(payload, &response, client.OptPath("events")); err != nil {
return nil, err
}

// Return success
return response, nil
}
24 changes: 24 additions & 0 deletions pkg/homeassistant/events_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package homeassistant_test

import (
"os"
"testing"

// Packages
opts "github.com/mutablelogic/go-client/pkg/client"
homeassistant "github.com/mutablelogic/go-client/pkg/homeassistant"
assert "github.com/stretchr/testify/assert"
)

func Test_events_001(t *testing.T) {
assert := assert.New(t)
client, err := homeassistant.New(GetEndPoint(t), GetApiKey(t), opts.OptTrace(os.Stderr, true))
assert.NoError(err)
assert.NotNil(client)

events, err := client.Events()
assert.NoError(err)
assert.NotNil(events)

t.Log(events)
}
24 changes: 24 additions & 0 deletions pkg/homeassistant/health.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package homeassistant

import "github.com/mutablelogic/go-client/pkg/client"

///////////////////////////////////////////////////////////////////////////////
// API CALLS

// ListModels returns all the models
func (c *Client) Health() (string, error) {
// Response schema
type responseHealth struct {
Message string `json:"message"`
}

// Return the response
var response responseHealth
payload := client.NewRequest(client.ContentTypeJson)
if err := c.Do(payload, &response); err != nil {
return "", err
}

// Return success
return response.Message, nil
}
24 changes: 24 additions & 0 deletions pkg/homeassistant/health_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package homeassistant_test

import (
"os"
"testing"

// Packages
opts "github.com/mutablelogic/go-client/pkg/client"
homeassistant "github.com/mutablelogic/go-client/pkg/homeassistant"
assert "github.com/stretchr/testify/assert"
)

func Test_health_001(t *testing.T) {
assert := assert.New(t)
client, err := homeassistant.New(GetEndPoint(t), GetApiKey(t), opts.OptTrace(os.Stderr, true))
assert.NoError(err)
assert.NotNil(client)

message, err := client.Health()
assert.NoError(err)
assert.NotEmpty(message)

t.Log(message)
}
177 changes: 177 additions & 0 deletions pkg/homeassistant/states.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
package homeassistant

import (
"encoding/json"
"strings"
"time"

"github.com/mutablelogic/go-client/pkg/client"
)

///////////////////////////////////////////////////////////////////////////////
// TYPES

type State struct {
Entity string `json:"entity_id"`
LastChanged time.Time `json:"last_changed"`
State string `json:"state"`
Attributes map[string]any `json:"attributes"`
}

type Sensor struct {
Type string `json:"type"`
Entity string `json:"entity_id"`
Name string `json:"friendly_name"`
Value string `json:"state,omitempty"`
Unit string `json:"unit_of_measurement,omitempty"`
Class string `json:"device_class,omitempty"`
}

///////////////////////////////////////////////////////////////////////////////
// API CALLS

// States returns all the entities and their state
func (c *Client) States() ([]State, error) {
// Return the response
var response []State
payload := client.NewRequest(client.ContentTypeJson)
if err := c.Do(payload, &response, client.OptPath("states")); err != nil {
return nil, err
}

// Return success
return response, nil
}

// Sensors returns all sensor entities and their state
func (c *Client) Sensors() ([]Sensor, error) {
// Return the response
var response []State
payload := client.NewRequest(client.ContentTypeJson)
if err := c.Do(payload, &response, client.OptPath("states")); err != nil {
return nil, err
}

// Filter out sensors
var sensors []Sensor
for _, state := range response {
if !strings.HasPrefix(state.Entity, "sensor.") && !strings.HasPrefix(state.Entity, "binary_sensor.") {
continue
}
sensors = append(sensors, Sensor{
Type: "sensor",
Entity: state.Entity,
Name: state.Name(),
Value: state.State,
Unit: state.UnitOfMeasurement(),
Class: state.DeviceClass(),
})
}

// Return success
return sensors, nil
}

// Actuators returns all button, switch and lock entities and their state
func (c *Client) Actuators() ([]Sensor, error) {
// Return the response
var response []State
payload := client.NewRequest(client.ContentTypeJson)
if err := c.Do(payload, &response, client.OptPath("states")); err != nil {
return nil, err
}

// Filter out buttons, locks, and switches
var sensors []Sensor
for _, state := range response {
if !strings.HasPrefix(state.Entity, "button.") && !strings.HasPrefix(state.Entity, "lock.") && !strings.HasPrefix(state.Entity, "switch.") {
continue
}
sensors = append(sensors, Sensor{
Type: "actuator",
Entity: state.Entity,
Name: state.Name(),
Value: state.State,
Class: state.DeviceClass(),
})
}

// Return success
return sensors, nil
}

// Lights returns all light entities and their state
func (c *Client) Lights() ([]Sensor, error) {
// Return the response
var response []State
payload := client.NewRequest(client.ContentTypeJson)
if err := c.Do(payload, &response, client.OptPath("states")); err != nil {
return nil, err
}

// Filter out sensors
var lights []Sensor
for _, state := range response {
if !strings.HasPrefix(state.Entity, "light.") {
continue
}
lights = append(lights, Sensor{
Type: "light",
Entity: state.Entity,
Name: state.Name(),
Value: state.State,
})
}

// Return success
return lights, nil
}

///////////////////////////////////////////////////////////////////////////////
// STRINGIFY

func (s State) String() string {
data, _ := json.MarshalIndent(s, "", " ")
return string(data)
}

func (s Sensor) String() string {
data, _ := json.MarshalIndent(s, "", " ")
return string(data)
}

///////////////////////////////////////////////////////////////////////////////
// METHODS

func (s State) Name() string {
name, ok := s.Attributes["friendly_name"]
if !ok {
return s.Entity
} else if name_, ok := name.(string); !ok {
return s.Entity
} else {
return name_
}
}

func (s State) DeviceClass() string {
class, ok := s.Attributes["device_class"]
if !ok {
return ""
} else if class_, ok := class.(string); !ok {
return ""
} else {
return class_
}
}

func (s State) UnitOfMeasurement() string {
unit, ok := s.Attributes["unit_of_measurement"]
if !ok {
return ""
} else if unit_, ok := unit.(string); !ok {
return ""
} else {
return unit_
}
}
Loading

0 comments on commit 3c52b9b

Please sign in to comment.