diff --git a/README.md b/README.md index 45c57ed..83870fe 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,9 @@ Basically the same environment variables for the database, mqqt and timezone nee **Optional** environment variables +- **TESLAMATE_SSL** boolean *(default: false)* +- **TESLAMATE_HOST** string *(default: teslamate)* +- **TESLAMATE_PORT** string *(default: 4000)* - **API_TOKEN** string *(default: )* - **DATABASE_PORT** integer *(default: 5432)* - **DATABASE_TIMEOUT** integer *(default: 60000)* @@ -117,6 +120,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)* @@ -150,6 +154,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` @@ -158,7 +164,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_TeslaMateAPICarsLogging.go b/src/v1_TeslaMateAPICarsLogging.go new file mode 100644 index 0000000..27b7e9a --- /dev/null +++ b/src/v1_TeslaMateAPICarsLogging.go @@ -0,0 +1,116 @@ +package main + +import ( + "encoding/json" + "io/ioutil" + "log" + "net/http" + "strings" + + "github.com/gin-gonic/gin" + _ "github.com/lib/pq" +) + +// TeslaMateAPICarsLoggingV1 func +func TeslaMateAPICarsLoggingV1(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] TeslaMateAPICarsLoggingV1 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] TeslaMateAPICarsLoggingV1 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] TeslaMateAPICarsLoggingV1 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] TeslaMateAPICarsLoggingV1 command received:", command) + + if !checkArrayContainsString(allowList, command) { + log.Print("[warning] TeslaMateAPICarsLoggingV1 command: " + command + " not allowed") + c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"}) + return + } + + client := &http.Client{} + putURL := "" + if getEnvAsBool("TESLAMATE_SSL", false) { + putURL = "https://" + } else { + putURL = "http://" + } + putURL = putURL + getEnv("TESLAMATE_HOST", "teslamate") + ":" + getEnv("TESLAMATE_PORT", "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] TeslaMateAPICarsLoggingV1 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] TeslaMateAPICarsLoggingV1 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] TeslaMateAPICarsLoggingV1 " + 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] TeslaMateAPICarsLoggingV1 " + c.Request.RequestURI + " executed successful.") + } else { + log.Println("[error] TeslaMateAPICarsLoggingV1 " + c.Request.RequestURI + " error in execution!") + } + c.JSON(resp.StatusCode, jsonData) +} diff --git a/src/webserver.go b/src/webserver.go index dd8fe2b..b27a269 100644 --- a/src/webserver.go +++ b/src/webserver.go @@ -100,6 +100,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", TeslaMateAPICarsLoggingV1) + v1.PUT("/cars/:CarID/logging/:Command", TeslaMateAPICarsLoggingV1) + // v1 /api/v1/cars/:CarID/status endpoints v1.GET("/cars/:CarID/status", statusCache.TeslaMateAPICarsStatusV1)