diff --git a/README.md b/README.md index 9e2b5b0..07eface 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ This app uses the MQTT broker bundled with [TeslaMate](https://github.com/adrian ## How to use ### Docker -This app is provided as a docker image. You will need to download the [config.example.yml](config.example.yml) file (or the simplified [config.simple.example.yml](config.simple.example.yml)), edit it appropriately, and then mount it to the container at runtime. For example: +This app is provided as a docker image. You will need to download the [config.example.yml](config.example.yml) file (or the simplified [config.simple.example.yml](config.simple.example.yml)), edit it appropriately (***make sure to preserve the leading spaces, they are important***), and then mount it to the container at runtime. For example: ```bash # see docker compose example below for parameter explanations @@ -84,6 +84,8 @@ docker run --rm \ ### Geofence Types You can define 3 different types of geofences to trigger garage operations. You must configure *one and only one* geofence type for each garage door. Each geofence type has separate `open` and `close` configurations (though they can be set to the same values). This is useful for situations where you might want a smaller geofence that closes the door so you can visually confirm it's closing, but you want a larger geofence that opens the door so it will start sooner and be fully opened when you actually arrive. +Note you do not need to define both `open` and `close` for a geofence, you may only define one or the other if you don't wish to have Tesla-YouQ both open and close your garage. + #### Circular Geofence This is the simplist geofence to configure. You provide a latitude and longitude coordinate as the center point, and the distance from the center point to trigger the garage action (in kilometers). You can use a tool such as [FreeMapTools](https://www.freemaptools.com/radius-around-point.htm) to determine what the center latitude and longitude coordinates are, as well as how big your want your radius to be. An example of a garage door configured with this type of geofence would look like this: diff --git a/config.example.yml b/config.example.yml index 3d36471..0f6ba51 100644 --- a/config.example.yml +++ b/config.example.yml @@ -6,6 +6,9 @@ # Anytime a garage door is operated by this app, it will wait the configured 5 minutes "cooldown" before allowing # further operations on that specific garage door. +## NOTE ## +# Spacing is very important in this file, particularly the leading spacing (indentations). Failure to properly indent may cause config parsing to fail silently + global: mqtt_host: localhost # dns, container name, or IP of teslamate's mqtt host mqtt_port: 1883 diff --git a/internal/geo/geo.go b/internal/geo/geo.go index 9ac0d73..3ac46ec 100644 --- a/internal/geo/geo.go +++ b/internal/geo/geo.go @@ -164,9 +164,13 @@ func getDistanceChangeAction(config util.ConfigStruct, car *util.Car) (action st car.CurDistance = distance(car.CurrentLocation, car.GarageDoor.CircularGeofence.Center) // check if car has crossed a geofence and set an appropriate action - if prevDistance <= car.GarageDoor.CircularGeofence.CloseDistance && car.CurDistance > car.GarageDoor.CircularGeofence.CloseDistance { // car was within close geofence, but now beyond it (car left geofence) + if car.GarageDoor.CircularGeofence.CloseDistance > 0 && // is valid close distance defined + prevDistance <= car.GarageDoor.CircularGeofence.CloseDistance && + car.CurDistance > car.GarageDoor.CircularGeofence.CloseDistance { // car was within close geofence, but now beyond it (car left geofence) action = myq.ActionClose - } else if prevDistance >= car.GarageDoor.CircularGeofence.OpenDistance && car.CurDistance < car.GarageDoor.CircularGeofence.OpenDistance { // car was outside of open geofence, but is now within it (car entered geofence) + } else if car.GarageDoor.CircularGeofence.OpenDistance > 0 && // is valid open distance defined + prevDistance >= car.GarageDoor.CircularGeofence.OpenDistance && + car.CurDistance < car.GarageDoor.CircularGeofence.OpenDistance { // car was outside of open geofence, but is now within it (car entered geofence) action = myq.ActionOpen } return @@ -174,10 +178,12 @@ func getDistanceChangeAction(config util.ConfigStruct, car *util.Car) (action st // gets action based on if there was a relevant geofence event change func getGeoChangeEventAction(config util.ConfigStruct, car *util.Car) (action string) { - if car.PrevGeofence == car.GarageDoor.TeslamateGeofence.Close.From && + if car.GarageDoor.TeslamateGeofence.Close.IsTriggerDefined() && + car.PrevGeofence == car.GarageDoor.TeslamateGeofence.Close.From && car.CurGeofence == car.GarageDoor.TeslamateGeofence.Close.To { action = "close" - } else if car.PrevGeofence == car.GarageDoor.TeslamateGeofence.Open.From && + } else if car.GarageDoor.TeslamateGeofence.Open.IsTriggerDefined() && + car.PrevGeofence == car.GarageDoor.TeslamateGeofence.Open.From && car.CurGeofence == car.GarageDoor.TeslamateGeofence.Open.To { action = "open" } @@ -188,15 +194,15 @@ func getGeoChangeEventAction(config util.ConfigStruct, car *util.Car) (action st // uses ray-casting algorithm, assumes a simple geofence (no holes or border cross points) func getPolygonGeoChangeEventAction(config util.ConfigStruct, car *util.Car) (action string) { if !car.CurrentLocation.IsPointDefined() { - return "" // need valid lat and long to check geofence + return // need valid lat and long to check geofence } isInsideCloseGeo := isInsidePolygonGeo(car.CurrentLocation, car.GarageDoor.PolygonGeofence.Close) isInsideOpenGeo := isInsidePolygonGeo(car.CurrentLocation, car.GarageDoor.PolygonGeofence.Open) - if car.InsidePolyCloseGeo && !isInsideCloseGeo { // if we were inside the close geofence and now we're not, then close + if car.GarageDoor.PolygonGeofence.Close != nil && car.InsidePolyCloseGeo && !isInsideCloseGeo { // if we were inside the close geofence and now we're not, then close action = "close" - } else if !car.InsidePolyOpenGeo && isInsideOpenGeo { // if we were not inside the open geo and now we are, then open + } else if car.GarageDoor.PolygonGeofence.Open != nil && !car.InsidePolyOpenGeo && isInsideOpenGeo { // if we were not inside the open geo and now we are, then open action = "open" } diff --git a/internal/util/config.go b/internal/util/config.go index e3960a5..e7df2b0 100644 --- a/internal/util/config.go +++ b/internal/util/config.go @@ -147,15 +147,20 @@ func (f *CustomFormatter) Format(entry *logger.Entry) ([]byte, error) { // checks for valid geofence values for a garage door // preferred priority is polygon > circular > teslamate +// at least one open OR one close must be defined to identify a geofence type func (g GarageDoor) GetGeofenceType() string { - if g.PolygonGeofence != nil && len(g.PolygonGeofence.Open) > 0 && len(g.PolygonGeofence.Close) > 0 { + if g.PolygonGeofence != nil && + (len(g.PolygonGeofence.Open) > 0 || + len(g.PolygonGeofence.Close) > 0) { return PolygonGeofenceType - } else if g.CircularGeofence != nil && g.CircularGeofence.Center.IsPointDefined() && g.CircularGeofence.OpenDistance > 0 && g.CircularGeofence.CloseDistance > 0 { + } else if g.CircularGeofence != nil && + g.CircularGeofence.Center.IsPointDefined() && + (g.CircularGeofence.OpenDistance > 0 || + g.CircularGeofence.CloseDistance > 0) { return CircularGeofenceType - } else if g.TeslamateGeofence != nil && g.TeslamateGeofence.Close.From != "" && - g.TeslamateGeofence.Close.To != "" && - g.TeslamateGeofence.Open.From != "" && - g.TeslamateGeofence.Open.To != "" { + } else if g.TeslamateGeofence != nil && + (g.TeslamateGeofence.Close.IsTriggerDefined() || + g.TeslamateGeofence.Open.IsTriggerDefined()) { return TeslamateGeofenceType } else { return "" @@ -167,6 +172,10 @@ func (p Point) IsPointDefined() bool { return p.Lat != 0 && p.Lng != 0 } +func (t TeslamateGeofenceTrigger) IsTriggerDefined() bool { + return t.From != "" && t.To != "" +} + // load yaml config func LoadConfig(configFile string) { logger.Debugf("Attempting to read config file: %v", configFile)