Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow only open or close fences #26

Merged
merged 7 commits into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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