Skip to content

Commit

Permalink
feat: allow only open or close fences (#26)
Browse files Browse the repository at this point in the history
Don't require both `open` and `close` geofences; allows users to only
define and utilize either `open` or `close` if they so wish.
  • Loading branch information
brchri authored Oct 16, 2023
1 parent 64b7e12 commit b9cd01a
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 14 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:

Expand Down
3 changes: 3 additions & 0 deletions config.example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 13 additions & 7 deletions internal/geo/geo.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,20 +164,26 @@ 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
}

// 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"
}
Expand All @@ -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"
}

Expand Down
21 changes: 15 additions & 6 deletions internal/util/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 ""
Expand All @@ -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)
Expand Down

0 comments on commit b9cd01a

Please sign in to comment.