diff --git a/app.go b/app.go index bd20a4f..7ac2132 100644 --- a/app.go +++ b/app.go @@ -6,6 +6,7 @@ import ( "fmt" "log/slog" "net/url" + "strings" "time" "github.com/golang-module/carbon" @@ -85,16 +86,47 @@ type NewAppRequest struct { 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 you can use to register schedules and listeners. */ func NewApp(request NewAppRequest) (*App, error) { - if (request.URL == "" && request.IpAddress == "") || request.HAAuthToken == "" || request.HomeZoneEntityId == "" { - slog.Error("URL, HAAuthToken, and HomeZoneEntityId are all required arguments in NewAppRequest") + if (request.URL == "" && request.IpAddress == "") || request.HAAuthToken == "" { + slog.Error("URL and HAAuthToken are required arguments in NewAppRequest") return nil, ErrInvalidArgs } + // Set default home zone if not provided + if request.HomeZoneEntityId == "" { + request.HomeZoneEntityId = "zone.home" + } + baseURL := &url.URL{} if request.URL != "" { @@ -133,6 +165,11 @@ func NewApp(request NewAppRequest) (*App, error) { return nil, err } + // Validate home zone + if err := validateHomeZone(state, request.HomeZoneEntityId); err != nil { + return nil, err + } + return &App{ conn: conn, wsWriter: wsWriter, diff --git a/cmd/generate/main.go b/cmd/generate/main.go index 6573adc..1a75723 100644 --- a/cmd/generate/main.go +++ b/cmd/generate/main.go @@ -16,7 +16,7 @@ import ( type Config struct { URL string `yaml:"url"` 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 { @@ -64,8 +64,38 @@ func toCamelCase(s string) 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 func generate(config Config) error { + if config.HomeZoneEntityId == "" { + config.HomeZoneEntityId = "zone.home" + } + app, err := ga.NewApp(ga.NewAppRequest{ URL: config.URL, HAAuthToken: config.HAAuthToken, @@ -76,6 +106,11 @@ func generate(config Config) error { } 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() if err != nil { return fmt.Errorf("failed to list entities: %w", err) @@ -178,8 +213,8 @@ func main() { config.HAAuthToken = os.Getenv("HA_AUTH_TOKEN") } - if config.URL == "" || config.HAAuthToken == "" || config.HomeZoneEntityId == "" { - fmt.Println("Error: url, ha_auth_token and home_zone_entity_id are required in config") + if config.URL == "" || config.HAAuthToken == "" { + fmt.Println("Error: url and ha_auth_token are required in config") os.Exit(1) } diff --git a/example/example.go b/example/example.go index 7d68e18..f05b79d 100644 --- a/example/example.go +++ b/example/example.go @@ -6,7 +6,7 @@ import ( "os" "time" - "example/entities" // Import generated entities + // "example/entities" // Optional import generated entities ga "saml.dev/gome-assistant" ) diff --git a/example/gen.yaml b/example/gen.yaml index 729c0e9..a15dad2 100644 --- a/example/gen.yaml +++ b/example/gen.yaml @@ -1,3 +1,3 @@ url: "http://192.168.4.67:8123" # Replace with your Home Assistant URL -ha_auth_token: "" # Your auth token -home_zone_entity_id: "zone.home" +ha_auth_token: "" # Your auth token or set HA_AUTH_TOKEN env var +home_zone_entity_id: "zone.home" # Optional: defaults to zone.home diff --git a/state.go b/state.go index 3d7430c..5280607 100644 --- a/state.go +++ b/state.go @@ -2,8 +2,8 @@ package gomeassistant import ( "encoding/json" - "errors" "fmt" + "strings" "time" "github.com/golang-module/carbon" @@ -37,34 +37,38 @@ type EntityState struct { func newState(c *http.HttpClient, homeZoneEntityId string) (*StateImpl, error) { 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 { - 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 } -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) { resp, err := s.httpClient.GetState(entityId) if err != nil {