home zone changed to optional

This commit is contained in:
Sam Lewis
2025-06-01 17:50:03 -04:00
parent 79811571ca
commit 25076130d8
5 changed files with 108 additions and 32 deletions

41
app.go
View File

@@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"log/slog" "log/slog"
"net/url" "net/url"
"strings"
"time" "time"
"github.com/golang-module/carbon" "github.com/golang-module/carbon"
@@ -85,16 +86,47 @@ type NewAppRequest struct {
Secure bool Secure bool
} }
// validateHomeZone verifies that the home zone entity exists and has latitude/longitude
func validateHomeZone(state State, entityID string) error {
entity, err := state.Get(entityID)
if err != nil {
return fmt.Errorf("home zone entity '%s' not found: %w", entityID, err)
}
// Ensure it's a zone entity
if !strings.HasPrefix(entityID, "zone.") {
return fmt.Errorf("entity '%s' is not a zone entity (must start with zone.)", entityID)
}
// Verify it has latitude and longitude
if entity.Attributes == nil {
return fmt.Errorf("home zone entity '%s' has no attributes", entityID)
}
if entity.Attributes["latitude"] == nil {
return fmt.Errorf("home zone entity '%s' missing latitude attribute", entityID)
}
if entity.Attributes["longitude"] == nil {
return fmt.Errorf("home zone entity '%s' missing longitude attribute", entityID)
}
return nil
}
/* /*
NewApp establishes the websocket connection and returns an object NewApp establishes the websocket connection and returns an object
you can use to register schedules and listeners. you can use to register schedules and listeners.
*/ */
func NewApp(request NewAppRequest) (*App, error) { func NewApp(request NewAppRequest) (*App, error) {
if (request.URL == "" && request.IpAddress == "") || request.HAAuthToken == "" || request.HomeZoneEntityId == "" { if (request.URL == "" && request.IpAddress == "") || request.HAAuthToken == "" {
slog.Error("URL, HAAuthToken, and HomeZoneEntityId are all required arguments in NewAppRequest") slog.Error("URL and HAAuthToken are required arguments in NewAppRequest")
return nil, ErrInvalidArgs return nil, ErrInvalidArgs
} }
// Set default home zone if not provided
if request.HomeZoneEntityId == "" {
request.HomeZoneEntityId = "zone.home"
}
baseURL := &url.URL{} baseURL := &url.URL{}
if request.URL != "" { if request.URL != "" {
@@ -133,6 +165,11 @@ func NewApp(request NewAppRequest) (*App, error) {
return nil, err return nil, err
} }
// Validate home zone
if err := validateHomeZone(state, request.HomeZoneEntityId); err != nil {
return nil, err
}
return &App{ return &App{
conn: conn, conn: conn,
wsWriter: wsWriter, wsWriter: wsWriter,

View File

@@ -16,7 +16,7 @@ import (
type Config struct { type Config struct {
URL string `yaml:"url"` URL string `yaml:"url"`
HAAuthToken string `yaml:"ha_auth_token"` HAAuthToken string `yaml:"ha_auth_token"`
HomeZoneEntityId string `yaml:"home_zone_entity_id"` HomeZoneEntityId string `yaml:"home_zone_entity_id,omitempty"` // Now optional
} }
type Domain struct { type Domain struct {
@@ -64,8 +64,38 @@ func toCamelCase(s string) string {
return result.String() return result.String()
} }
// validateHomeZone verifies that the home zone entity exists and is valid
func validateHomeZone(state ga.State, entityID string) error {
entity, err := state.Get(entityID)
if err != nil {
return fmt.Errorf("home zone entity '%s' not found: %w", entityID, err)
}
// Ensure it's a zone entity
if !strings.HasPrefix(entityID, "zone.") {
return fmt.Errorf("entity '%s' is not a zone entity (must start with zone.)", entityID)
}
// Verify it has latitude and longitude
if entity.Attributes == nil {
return fmt.Errorf("home zone entity '%s' has no attributes", entityID)
}
if entity.Attributes["latitude"] == nil {
return fmt.Errorf("home zone entity '%s' missing latitude attribute", entityID)
}
if entity.Attributes["longitude"] == nil {
return fmt.Errorf("home zone entity '%s' missing longitude attribute", entityID)
}
return nil
}
// generate creates the entities.go file with constants for all Home Assistant entities // generate creates the entities.go file with constants for all Home Assistant entities
func generate(config Config) error { func generate(config Config) error {
if config.HomeZoneEntityId == "" {
config.HomeZoneEntityId = "zone.home"
}
app, err := ga.NewApp(ga.NewAppRequest{ app, err := ga.NewApp(ga.NewAppRequest{
URL: config.URL, URL: config.URL,
HAAuthToken: config.HAAuthToken, HAAuthToken: config.HAAuthToken,
@@ -76,6 +106,11 @@ func generate(config Config) error {
} }
defer app.Cleanup() defer app.Cleanup()
// Validate that the home zone exists before proceeding
if err := validateHomeZone(app.GetState(), config.HomeZoneEntityId); err != nil {
return fmt.Errorf("invalid home zone: %w", err)
}
entities, err := app.GetState().ListEntities() entities, err := app.GetState().ListEntities()
if err != nil { if err != nil {
return fmt.Errorf("failed to list entities: %w", err) return fmt.Errorf("failed to list entities: %w", err)
@@ -178,8 +213,8 @@ func main() {
config.HAAuthToken = os.Getenv("HA_AUTH_TOKEN") config.HAAuthToken = os.Getenv("HA_AUTH_TOKEN")
} }
if config.URL == "" || config.HAAuthToken == "" || config.HomeZoneEntityId == "" { if config.URL == "" || config.HAAuthToken == "" {
fmt.Println("Error: url, ha_auth_token and home_zone_entity_id are required in config") fmt.Println("Error: url and ha_auth_token are required in config")
os.Exit(1) os.Exit(1)
} }

View File

@@ -6,7 +6,7 @@ import (
"os" "os"
"time" "time"
"example/entities" // Import generated entities // "example/entities" // Optional import generated entities
ga "saml.dev/gome-assistant" ga "saml.dev/gome-assistant"
) )

View File

@@ -1,3 +1,3 @@
url: "http://192.168.4.67:8123" # Replace with your Home Assistant URL url: "http://192.168.4.67:8123" # Replace with your Home Assistant URL
ha_auth_token: "<token>" # Your auth token ha_auth_token: "<token>" # Your auth token or set HA_AUTH_TOKEN env var
home_zone_entity_id: "zone.home" home_zone_entity_id: "zone.home" # Optional: defaults to zone.home

View File

@@ -2,8 +2,8 @@ package gomeassistant
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"strings"
"time" "time"
"github.com/golang-module/carbon" "github.com/golang-module/carbon"
@@ -37,34 +37,38 @@ type EntityState struct {
func newState(c *http.HttpClient, homeZoneEntityId string) (*StateImpl, error) { func newState(c *http.HttpClient, homeZoneEntityId string) (*StateImpl, error) {
state := &StateImpl{httpClient: c} state := &StateImpl{httpClient: c}
err := state.getLatLong(c, homeZoneEntityId)
// Ensure the zone exists and has required attributes
entity, err := state.Get(homeZoneEntityId)
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("home zone entity '%s' not found: %w", homeZoneEntityId, err)
} }
// Ensure it's a zone entity
if !strings.HasPrefix(homeZoneEntityId, "zone.") {
return nil, fmt.Errorf("entity '%s' is not a zone entity (must start with zone.)", homeZoneEntityId)
}
// Verify and extract latitude and longitude
if entity.Attributes == nil {
return nil, fmt.Errorf("home zone entity '%s' has no attributes", homeZoneEntityId)
}
if lat, ok := entity.Attributes["latitude"].(float64); ok {
state.latitude = lat
} else {
return nil, fmt.Errorf("home zone entity '%s' missing valid latitude attribute", homeZoneEntityId)
}
if long, ok := entity.Attributes["longitude"].(float64); ok {
state.longitude = long
} else {
return nil, fmt.Errorf("home zone entity '%s' missing valid longitude attribute", homeZoneEntityId)
}
return state, nil return state, nil
} }
func (s *StateImpl) getLatLong(c *http.HttpClient, homeZoneEntityId string) error {
resp, err := s.Get(homeZoneEntityId)
if err != nil {
return fmt.Errorf("couldn't get latitude/longitude from home assistant entity '%s'. Did you type it correctly? It should be a zone like 'zone.home'", homeZoneEntityId)
}
if resp.Attributes["latitude"] != nil {
s.latitude = resp.Attributes["latitude"].(float64)
} else {
return errors.New("server returned nil latitude")
}
if resp.Attributes["longitude"] != nil {
s.longitude = resp.Attributes["longitude"].(float64)
} else {
return errors.New("server returned nil longitude")
}
return nil
}
func (s *StateImpl) Get(entityId string) (EntityState, error) { func (s *StateImpl) Get(entityId string) (EntityState, error) {
resp, err := s.httpClient.GetState(entityId) resp, err := s.httpClient.GetState(entityId)
if err != nil { if err != nil {