From 24531edfcdb741c5cafc1ff797653e7ef355e90d Mon Sep 17 00:00:00 2001 From: Leland Sindt Date: Tue, 23 Mar 2021 20:50:57 -0500 Subject: [PATCH] add support for teslamate logging resume/suspend --- README.md | 6 +- src/CommandSupport.go | 7 ++ src/v1_TeslaMateAPICarsLoggingCommand.go | 110 +++++++++++++++++++++++ src/webserver.go | 4 + 4 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 src/v1_TeslaMateAPICarsLoggingCommand.go diff --git a/README.md b/README.md index 712a4f5..b3ab39c 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,7 @@ Basically the same environment variables for the database, mqqt and timezone nee - **DATABASE_NAME** string *(default: teslamate)* - **DATABASE_HOST** string *(default: database)* - **MQTT_HOST** string *(default: mosquitto)* +- **TESLAMATE_URL** string *(default: http://teslamate:4000)* - **TZ** string *(default: Europe/Berlin)* **Optional** environment variables @@ -118,6 +119,7 @@ Basically the same environment variables for the database, mqqt and timezone nee - **ENABLE_COMMANDS** boolean *(default: false)* - **COMMANDS_ALL** boolean *(default: false)* - **COMMANDS_ALLOWLIST** string *(default: allow_list.json)* +- **COMMANDS_LOGGING** boolean *(deafault: false)* - **COMMANDS_WAKE** boolean *(default: false)* - **COMMANDS_ALERT** boolean *(default: false)* - **COMMANDS_REMOTESTART** boolean *(default: false)* @@ -151,6 +153,8 @@ More detailed documentation of every endpoint will come.. - POST `/api/v1/cars/:CarID/command/:Command` - GET `/api/v1/cars/:CarID/drives` - GET `/api/v1/cars/:CarID/drives/:DriveID` +- PUT `/api/v1/cars/:CarID/logging/:Command` +- GET `/api/v1/cars/:CarID/logging` - GET `/api/v1/cars/:CarID/status` - GET `/api/v1/cars/:CarID/updates` - POST `/api/v1/cars/:CarID/wake_up` @@ -159,7 +163,7 @@ More detailed documentation of every endpoint will come.. ### Authentication -If you want to use command endpoints such as `/api/v1/cars/:CarID/command/:Command` and `/api/v1/cars/:CarID/wake_up`, you need to add authentication to your request. +If you want to use command or logging endpoints such as `/api/v1/cars/:CarID/command/:Command`, `/api/v1/cars/:CarID/wake_up`, or `/api/v1/cars/:CarID/logging/:Command` you need to add authentication to your request. You need to specify a token yourself (called **API_TOKEN**) in the environment variables file, to set it. The token has the requirement to be a minimum of 32 characters long. diff --git a/src/CommandSupport.go b/src/CommandSupport.go index 685af04..6a26b08 100644 --- a/src/CommandSupport.go +++ b/src/CommandSupport.go @@ -16,6 +16,13 @@ func initCommandAllowList() { // allow all commands available below allowAll := getEnvAsBool("COMMANDS_ALL", false) + // https://github.com/adriankumpf/teslamate/discussions/1433 + if getEnvAsBool("COMMANDS_LOGGING", false) || allowAll { + allowList = append(allowList, + "/logging/resume", + "/logging/suspend") + } + // https://tesla-api.timdorr.com/vehicle/commands/wake if getEnvAsBool("COMMANDS_WAKE", false) || allowAll { allowList = append(allowList, "/wake_up") diff --git a/src/v1_TeslaMateAPICarsLoggingCommand.go b/src/v1_TeslaMateAPICarsLoggingCommand.go new file mode 100644 index 0000000..ff40c5b --- /dev/null +++ b/src/v1_TeslaMateAPICarsLoggingCommand.go @@ -0,0 +1,110 @@ +package main + +import ( + "encoding/json" + "io/ioutil" + "log" + "net/http" + "strings" + + "github.com/gin-gonic/gin" + _ "github.com/lib/pq" +) + +// TeslaMateAPICarsLoggingCommandV1 func +func TeslaMateAPICarsLoggingCommandV1(c *gin.Context) { + + // creating required vars + var jsonData map[string]interface{} + var err error + + // check if commands are enabled.. if not we need to abort + if getEnvAsBool("ENABLE_COMMANDS", false) == false { + log.Println("[warning] TeslaMateAPICarsLoggingCommandV1 ENABLE_COMMANDS is not true.. returning 403 forbidden.") + c.JSON(http.StatusForbidden, gin.H{"error": "You are not allowed to access logging commands"}) + return + } + + // if request method is GET return list of commands + if c.Request.Method == http.MethodGet { + c.JSON(http.StatusOK, gin.H{"enabled_commands": allowList}) + return + } + + // authentication for the endpoint + validToken, errorMessage := validateAuthToken(c) + if !validToken { + c.JSON(http.StatusUnauthorized, gin.H{"error": errorMessage}) + return + } + + // getting CarID param from URL + ParamCarID := c.Param("CarID") + var CarID int + if ParamCarID != "" { + CarID = convertStringToInteger(ParamCarID) + } + + // validating that CarID is not zero + if CarID == 0 { + log.Println("[error] TeslaMateAPICarsLoggingCommandV1 CarID is invalid (zero)!") + c.JSON(http.StatusBadRequest, gin.H{"error": "CarID invalid"}) + return + } + + // getting request body to pass to Tesla + reqBody, err := ioutil.ReadAll(c.Request.Body) + if err != nil { + log.Println("[error] TeslaMateAPICarsLoggingCommandV1 error in first ioutil.ReadAll", err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "internal"}) + return + } + + // getting :Command + command := ("/logging/" + c.Param("Command")) + log.Println("[debug] TeslaMateAPICarsLoggingCommandV1 command received:", command) + + if !checkArrayContainsString(allowList, command) { + log.Print("[warning] TeslaMateAPICarsCommandV1 command: " + command + " not allowed") + c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"}) + return + } + + client := &http.Client{} + putURL := getEnv("TESLAMATE_URL", "http://teslamate:4000") + "/api/car/" + ParamCarID + command + req, _ := http.NewRequest(http.MethodPut, putURL, strings.NewReader(string(reqBody))) + req.Header.Set("User-Agent", "TeslaMateApi/"+apiVersion+" https://github.com/tobiasehlert/teslamateapi") + resp, err := client.Do(req) + + // check response error + if err != nil { + log.Println("[error] TeslaMateAPICarsLoggingCommandV1 error in http request to http://teslamate:", err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "internal"}) + return + } + + defer resp.Body.Close() + defer client.CloseIdleConnections() + + respBody, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.Println("[error] TeslaMateAPICarsLoggingCommandV1 error in second ioutil.ReadAll:", err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "internal"}) + return + } + json.Unmarshal([]byte(respBody), &jsonData) + + // print to log about request + if gin.IsDebugging() { + log.Println("[debug] TeslaMateAPICarsLoggingCommandV1 " + c.Request.RequestURI + " returned data:") + js, _ := json.Marshal(jsonData) + log.Printf("[debug] %s\n", js) + } + + if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusNoContent { + log.Println("[info] TeslaMateAPICarsLoggingCommandV1 " + c.Request.RequestURI + " executed successful.") + } else { + log.Println("[error] TeslaMateAPICarsLoggingCommandV1 " + c.Request.RequestURI + " error in execution!") + } + c.JSON(resp.StatusCode, jsonData) +} diff --git a/src/webserver.go b/src/webserver.go index bf6862e..43a19dd 100644 --- a/src/webserver.go +++ b/src/webserver.go @@ -94,6 +94,10 @@ func main() { v1.GET("/cars/:CarID/drives", TeslaMateAPICarsDrivesV1) v1.GET("/cars/:CarID/drives/:DriveID", TeslaMateAPICarsDrivesDetailsV1) + // v1 /api/v1/cars/:CarID/logging endpoints + v1.GET("/cars/:CarID/logging", TeslaMateAPICarsLoggingCommandV1) + v1.PUT("/cars/:CarID/logging/:Command", TeslaMateAPICarsLoggingCommandV1) + // v1 /api/v1/cars/:CarID/status endpoints v1.GET("/cars/:CarID/status", TeslaMateAPICarsStatusV1)