Merge pull request #34 from gaussmeter/feature-command

add support for teslamate logging resume/suspend
This commit is contained in:
Tobias Lindberg
2021-05-03 13:12:09 +02:00
committed by GitHub
4 changed files with 126 additions and 1 deletions
+5 -1
View File
@@ -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.
+7
View File
@@ -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")
+110
View File
@@ -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)
}
+4
View File
@@ -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)