-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds support for controlling gdo's that are controlled by home assistant, by proxying the command through home assistant.
- Loading branch information
Showing
8 changed files
with
286 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
# This is an example config file with all available options and explanations for circular geofence and homeassistant opener types. | ||
|
||
## 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: | ||
teslamate_mqtt_settings: # settings for teslamate's mqtt broker | ||
connection: | ||
host: localhost # dns, container name, or IP of teslamate's mqtt host | ||
port: 1883 | ||
client_id: tesla-geogdo # optional, arbitrary client name for MQTT connection; must not be the same as any other MQTT client name, will use random uuid if omitted | ||
user: mqtt_user # optional, only define if your mqtt broker requires authentication, can also be passed as env var MQTT_USER | ||
pass: mqtt_pass # optional, only define if your mqtt broker requires authentication, can also be passed as env var MQTT_PASS | ||
use_tls: false # optional, instructs app to connect to mqtt broker using tls (defaults to false) | ||
skip_tls_verify: false # optional, if use_tls = true, this option indicates whether the client should skip certificate validation on the mqtt broker | ||
cooldown: 5 # minutes to wait after operating garage before allowing another garage operation (set to 0 or omit to disable) | ||
|
||
garage_doors: | ||
- # main garage example | ||
geofence: # circular geofence with a center point, open and close distances (radii) | ||
type: circular | ||
settings: | ||
center: | ||
lat: 46.19290425661381 | ||
lng: -123.79965087116439 | ||
close_distance: .013 # distance in kilometers car must travel away from garage location to close garage door | ||
open_distance: .04 # distance in kilometers car must be in range of garage location while traveling closer to it to open garage door | ||
opener: | ||
type: homeassistant # type of garage door opener to use | ||
settings: | ||
connection: # connection settings for home assistant | ||
host: homeassistant.local # dns, container name, or IP of home assistant | ||
port: 8123 | ||
api_key: long_api_key # api key for home assistant; generate in user profile | ||
use_tls: false # optional, instructs app to connect to home assistant using tls (defaults to false) | ||
skip_tls_verify: false # optional, if use_tls = true, this option indicates whether the client should skip certificate validation on home assistant | ||
entity_id: cover.main_door # id for the garage door entity in home assistant, can be found by adding '/config/entities' to the base url in home assistant | ||
enable_status_checks: true # set to true if gdo supports garage states (e.g. garage is closed) | ||
cars: # list of cars that use this garage door | ||
- teslamate_car_id: 1 # id used for the first vehicle in TeslaMate's MQTT broker | ||
- teslamate_car_id: 2 # id used for the second vehicle in TeslaMate's MQTT broker |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
package homeassistant | ||
|
||
import ( | ||
"encoding/json" | ||
"os" | ||
"strings" | ||
|
||
httpGdo "github.com/brchri/tesla-geogdo/internal/gdo/http" | ||
"github.com/brchri/tesla-geogdo/internal/util" | ||
logger "github.com/sirupsen/logrus" | ||
"gopkg.in/yaml.v3" | ||
) | ||
|
||
// stubbed struct to extract api key and entity id from the yaml to pass into what is expected by httpGdo | ||
type HomeAssistant struct { | ||
Settings struct { | ||
Connection struct { | ||
ApiKey string `yaml:"api_key"` | ||
} `yaml:"connection"` | ||
EntityId string `yaml:"entity_id"` | ||
EnableStatusChecks bool `yaml:"enable_status_checks"` | ||
} `yaml:"settings"` | ||
} | ||
|
||
func init() { | ||
logger.SetFormatter(&util.CustomFormatter{}) | ||
logger.SetOutput(os.Stdout) | ||
if val, ok := os.LookupEnv("DEBUG"); ok && strings.ToLower(val) == "true" { | ||
logger.SetLevel(logger.DebugLevel) | ||
} | ||
} | ||
|
||
// this is just a wrapper for the http package with some predefined settings for homeassistant | ||
func Initialize(config map[string]interface{}) (httpGdo.HttpGdo, error) { | ||
h, err := NewHomeAssistantGdo(config) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return h, nil | ||
} | ||
|
||
func NewHomeAssistantGdo(config map[string]interface{}) (httpGdo.HttpGdo, error) { | ||
var hassGdo *HomeAssistant | ||
// marshall map[string]interface into yaml, then unmarshal to object based on yaml def in struct | ||
yamlData, err := yaml.Marshal(config) | ||
if err != nil { | ||
logger.Fatal("Failed to marhsal garage doors yaml object") | ||
} | ||
err = yaml.Unmarshal(yamlData, &hassGdo) | ||
if err != nil { | ||
logger.Fatal("Failed to unmarshal garage doors yaml object") | ||
} | ||
|
||
// add homeassistant-specific http settings to the config object | ||
if httpSettings, ok := config["settings"].(map[string]interface{}); ok { | ||
httpSettings["commands"] = []map[string]interface{}{ | ||
{ | ||
"name": "open", | ||
"endpoint": "/api/services/cover/open_cover", | ||
"http_method": "post", | ||
"body": `{"entity_id": "` + hassGdo.Settings.EntityId + `"}`, | ||
"required_start_state": "closed", | ||
"required_finish_state": "open", | ||
"headers": []string{ | ||
"Authorization: Bearer " + hassGdo.Settings.Connection.ApiKey, | ||
"Content-Type: application/json", | ||
}, | ||
}, | ||
{ | ||
"name": "close", | ||
"endpoint": "/api/services/cover/close_cover", | ||
"http_method": "post", | ||
"body": `{"entity_id": "` + hassGdo.Settings.EntityId + `"}`, | ||
"required_start_state": "open", | ||
"required_finish_state": "closed", | ||
"headers": []string{ | ||
"Authorization: Bearer " + hassGdo.Settings.Connection.ApiKey, | ||
"Content-Type: application/json", | ||
}, | ||
}, | ||
} | ||
|
||
if hassGdo.Settings.EnableStatusChecks { | ||
httpSettings["status"] = map[string]interface{}{ | ||
"endpoint": "/api/states/" + hassGdo.Settings.EntityId, | ||
"headers": []string{ | ||
"Authorization: Bearer " + hassGdo.Settings.Connection.ApiKey, | ||
"Content-Type: application/json", | ||
}, | ||
} | ||
} | ||
} | ||
|
||
// create new httpGdo object with updated config | ||
h, err := httpGdo.NewHttpGdo(config) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// set callback function for httpGdo object to parse returned garage status | ||
h.SetExtractStatusCallbackFunction(ExtractStatusCallback) | ||
|
||
return h, nil | ||
} | ||
|
||
// define a callback function for the httpGdo package to extract the garage status from the returned json | ||
// all that's needed is the json value for the `state` key | ||
func ExtractStatusCallback(status string) (string, error) { | ||
type statusResponse struct { | ||
State string `json:"state"` | ||
} | ||
|
||
var s statusResponse | ||
err := json.Unmarshal([]byte(status), &s) | ||
if err != nil { | ||
logger.Debugf("Unable to parse") | ||
} | ||
|
||
return s.State, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
package homeassistant | ||
|
||
import ( | ||
"path/filepath" | ||
"testing" | ||
|
||
"github.com/brchri/tesla-geogdo/internal/util" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
var sampleYaml = map[string]interface{}{ | ||
"settings": map[string]interface{}{ | ||
"connection": map[string]interface{}{ | ||
"host": "localhost", | ||
"port": 80, | ||
"api_key": "somelongtoken", | ||
"use_tls": false, | ||
"skip_tls_verify": false, | ||
}, | ||
"entity_id": "cover.main_cover", | ||
"enable_status_checks": true, | ||
}, | ||
} | ||
|
||
// Since homeassistant is just a wrapper for httpGdo with some predefined configs, | ||
// just need to ensure NewRatgdo doesn't throw any errors when returning | ||
// an httpGdo object | ||
func Test_NewHomeAssistantGdo(t *testing.T) { | ||
// test with sample config defined above | ||
_, err := NewHomeAssistantGdo(sampleYaml) | ||
assert.Equal(t, nil, err) | ||
|
||
// test with sample config extracted from example config.yml file | ||
util.LoadConfig(filepath.Join("..", "..", "..", "examples", "config.circular.homeassistant.yml")) | ||
door := *util.Config.GarageDoors[0] | ||
var openerConfig interface{} | ||
for k, v := range door { | ||
if k == "opener" { | ||
openerConfig = v | ||
} | ||
} | ||
if openerConfig == nil { | ||
t.Error("unable to parse config from garage door") | ||
return | ||
} | ||
config, ok := openerConfig.(map[string]interface{}) | ||
if !ok { | ||
t.Error("unable to parse config from garage door") | ||
return | ||
} | ||
_, err = NewHomeAssistantGdo(config) | ||
assert.Equal(t, nil, err) | ||
} |
Oops, something went wrong.