mirror of
https://github.com/tobiasehlert/teslamateapi.git
synced 2026-02-27 09:54:18 +08:00
initial commit of /cars
updated README
This commit is contained in:
@@ -13,3 +13,4 @@
|
|||||||
|
|
||||||
# Dependency directories (remove the comment below to include it)
|
# Dependency directories (remove the comment below to include it)
|
||||||
# vendor/
|
# vendor/
|
||||||
|
.env
|
||||||
|
|||||||
@@ -1,2 +1,19 @@
|
|||||||
# teslamateapi
|
# TeslaMateApi
|
||||||
TeslaMate RESTful API plugin to get data collected by self-hosted data logger TeslaMate
|
|
||||||
|
TeslaMate RESTful API plugin to get data collected by self-hosted data logger TeslaMate.
|
||||||
|
|
||||||
|
- Written in **[Golang](https://golang.org/)**
|
||||||
|
- Data is collected from TeslaMate **Postgres** database and local **MQTT** Broker
|
||||||
|
|
||||||
|
## How to use
|
||||||
|
|
||||||
|
*Information will be updated..*
|
||||||
|
|
||||||
|
## API documentation
|
||||||
|
|
||||||
|
*Information will be updated..*
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
- Authors: Tobias Lindberg – [List of contributors](https://github.com/tobiasehlert/teslamateapi/graphs/contributors)
|
||||||
|
- Distributed under MIT License
|
||||||
|
|||||||
@@ -0,0 +1,170 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
_ "github.com/lib/pq"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TeslaMateAPICars func
|
||||||
|
func TeslaMateAPICars() (string, bool) {
|
||||||
|
|
||||||
|
// creating structs for /cars
|
||||||
|
// CarDetails struct - child of Cars
|
||||||
|
type CarDetails struct {
|
||||||
|
EID int64 `json:"eid"` // bigint
|
||||||
|
VID int64 `json:"vid"` // bigint
|
||||||
|
Vin string `json:"vin"` // text
|
||||||
|
Model string `json:"model"` // character varying(255)
|
||||||
|
TrimBadging string `json:"trim_badging"` // text
|
||||||
|
Efficiency float64 `json:"efficiency"` // double precision
|
||||||
|
}
|
||||||
|
// CarExterior struct - child of Cars
|
||||||
|
type CarExterior struct {
|
||||||
|
ExteriorColor string `json:"exterior_color"` // text
|
||||||
|
SpoilerType string `json:"spoiler_type"` // text
|
||||||
|
WheelType string `json:"wheel_type"` // text
|
||||||
|
}
|
||||||
|
// CarSettings struct - child of Cars
|
||||||
|
type CarSettings struct {
|
||||||
|
SuspendMin int `json:"suspend_min"` // int
|
||||||
|
SuspendAfterIdleMin int `json:"suspend_after_idle_min"` // int
|
||||||
|
ReqNotUnlocked bool `json:"req_not_unlocked"` // bool
|
||||||
|
FreeSupercharging bool `json:"free_supercharging"` // bool
|
||||||
|
UseStreamingAPI bool `json:"use_streaming_api"` // bool
|
||||||
|
}
|
||||||
|
// TeslaMateDetails struct - child of Cars
|
||||||
|
type TeslaMateDetails struct {
|
||||||
|
InsertedAt string `json:"inserted_at"` // timestamp(0) without time zone
|
||||||
|
UpdatedAt string `json:"updated_at"` // timestamp(0) without time zone
|
||||||
|
}
|
||||||
|
// TeslaMateStats struct - child of Cars
|
||||||
|
type TeslaMateStats struct {
|
||||||
|
TotalCharges int `json:"total_charges"` // int
|
||||||
|
TotalDrives int `json:"total_drives"` // int
|
||||||
|
TotalUpdates int `json:"total_updates"` // int
|
||||||
|
}
|
||||||
|
// Cars struct - child of Data
|
||||||
|
type Cars struct {
|
||||||
|
ID int `json:"id"` // smallint
|
||||||
|
Name string `json:"name"` // text
|
||||||
|
CarDetails CarDetails `json:"car_details"` // struct
|
||||||
|
CarExterior CarExterior `json:"car_exterior"` // struct
|
||||||
|
CarSettings CarSettings `json:"car_settings"` // struct
|
||||||
|
TeslaMateDetails TeslaMateDetails `json:"teslamate_details"` // struct
|
||||||
|
TeslaMateStats TeslaMateStats `json:"teslamate_stats"` // struct
|
||||||
|
}
|
||||||
|
// Information struct - child of JSONData
|
||||||
|
type Data struct {
|
||||||
|
Cars []Cars `json:"cars"`
|
||||||
|
}
|
||||||
|
// JSONData struct - main
|
||||||
|
type JSONData struct {
|
||||||
|
Data Data `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// creating required vars
|
||||||
|
var CarsData []Cars
|
||||||
|
var ValidResponse bool // default is false
|
||||||
|
|
||||||
|
// getting data from database
|
||||||
|
query := `
|
||||||
|
SELECT
|
||||||
|
cars.id,
|
||||||
|
eid,
|
||||||
|
vid,
|
||||||
|
model,
|
||||||
|
efficiency,
|
||||||
|
inserted_at,
|
||||||
|
updated_at,
|
||||||
|
vin,
|
||||||
|
name,
|
||||||
|
trim_badging,
|
||||||
|
exterior_color,
|
||||||
|
spoiler_type,
|
||||||
|
wheel_type,
|
||||||
|
suspend_min,
|
||||||
|
suspend_after_idle_min,
|
||||||
|
req_not_unlocked,
|
||||||
|
free_supercharging,
|
||||||
|
use_streaming_api,
|
||||||
|
(SELECT COUNT(*) FROM charging_processes WHERE car_id=cars.id) as total_charges,
|
||||||
|
(SELECT COUNT(*) FROM drives WHERE car_id=cars.id) as total_drives,
|
||||||
|
(SELECT COUNT(*) FROM updates WHERE car_id=cars.id) as total_charges
|
||||||
|
FROM cars
|
||||||
|
LEFT JOIN car_settings ON cars.id = car_settings.id
|
||||||
|
ORDER BY id;`
|
||||||
|
rows, err := db.Query(query)
|
||||||
|
|
||||||
|
// checking for errors in query
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// defer closing rows
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
// looping through all results
|
||||||
|
for rows.Next() {
|
||||||
|
|
||||||
|
// creating car object based on struct
|
||||||
|
car := Cars{}
|
||||||
|
|
||||||
|
// scanning row and putting values into the car
|
||||||
|
err = rows.Scan(
|
||||||
|
&car.ID,
|
||||||
|
&car.CarDetails.EID,
|
||||||
|
&car.CarDetails.VID,
|
||||||
|
&car.CarDetails.Model,
|
||||||
|
&car.CarDetails.Efficiency,
|
||||||
|
&car.TeslaMateDetails.InsertedAt,
|
||||||
|
&car.TeslaMateDetails.UpdatedAt,
|
||||||
|
&car.CarDetails.Vin,
|
||||||
|
&car.Name,
|
||||||
|
&car.CarDetails.TrimBadging,
|
||||||
|
&car.CarExterior.ExteriorColor,
|
||||||
|
&car.CarExterior.SpoilerType,
|
||||||
|
&car.CarExterior.WheelType,
|
||||||
|
&car.CarSettings.SuspendMin,
|
||||||
|
&car.CarSettings.SuspendAfterIdleMin,
|
||||||
|
&car.CarSettings.ReqNotUnlocked,
|
||||||
|
&car.CarSettings.FreeSupercharging,
|
||||||
|
&car.CarSettings.UseStreamingAPI,
|
||||||
|
&car.TeslaMateStats.TotalCharges,
|
||||||
|
&car.TeslaMateStats.TotalDrives,
|
||||||
|
&car.TeslaMateStats.TotalUpdates,
|
||||||
|
)
|
||||||
|
|
||||||
|
// checking for errors after scanning
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// appending car to CarsData
|
||||||
|
CarsData = append(CarsData, car)
|
||||||
|
ValidResponse = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// checking for errors in the rows result
|
||||||
|
err = rows.Err()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// build the data-blob
|
||||||
|
jsonData := JSONData{
|
||||||
|
Data{
|
||||||
|
Cars: CarsData,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// print readable output to log
|
||||||
|
log.Println("data for /cars created:")
|
||||||
|
|
||||||
|
js, _ := json.Marshal(jsonData)
|
||||||
|
fmt.Printf("%s\n", js)
|
||||||
|
return string(js), ValidResponse
|
||||||
|
}
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
_ "github.com/lib/pq"
|
||||||
|
)
|
||||||
|
|
||||||
|
// defining db var
|
||||||
|
var db *sql.DB
|
||||||
|
|
||||||
|
// main function
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
// init of API with connection to database
|
||||||
|
initAPI()
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
// kicking off Gin in value r
|
||||||
|
r := gin.Default()
|
||||||
|
|
||||||
|
// /cars endpoint
|
||||||
|
r.GET("/cars", func(c *gin.Context) {
|
||||||
|
// TeslaMateAPICars to get cars
|
||||||
|
result, ValidResponse := TeslaMateAPICars()
|
||||||
|
|
||||||
|
c.Header("Content-Type", "application/json")
|
||||||
|
if ValidResponse {
|
||||||
|
c.String(http.StatusOK, result)
|
||||||
|
} else {
|
||||||
|
c.String(http.StatusNotFound, result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// /ping endpoint
|
||||||
|
r.GET("/ping", func(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"message": "pong",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// run this! :)
|
||||||
|
r.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
// initAPI func
|
||||||
|
func initAPI() {
|
||||||
|
|
||||||
|
// declare error var for use insite initAPI
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// creating connection string towards postgres
|
||||||
|
dbhost := getEnv("TM_DB_HOST", "database")
|
||||||
|
dbport := getEnvAsInt("TM_DB_PORT", 5432)
|
||||||
|
dbuser := getEnv("TM_DB_USER", "teslamate")
|
||||||
|
dbpass := getEnv("TM_DB_PASS", "secret")
|
||||||
|
dbname := getEnv("TM_DB_NAME", "teslamate")
|
||||||
|
psqlInfo := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", dbhost, dbport, dbuser, dbpass, dbname)
|
||||||
|
|
||||||
|
// opening connection to postgres
|
||||||
|
db, err = sql.Open("postgres", psqlInfo)
|
||||||
|
if err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// doing ping to database to test connection
|
||||||
|
err = db.Ping()
|
||||||
|
if err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// showing database successfully connected
|
||||||
|
log.Println("successfully connected to postgres.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// getEnv func - read an environment or return a default value
|
||||||
|
func getEnv(key string, defaultVal string) string {
|
||||||
|
if value, exists := os.LookupEnv(key); exists {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return defaultVal
|
||||||
|
}
|
||||||
|
|
||||||
|
// getEnvAsInt func - read an environment variable into integer or return a default value
|
||||||
|
func getEnvAsInt(name string, defaultVal int) int {
|
||||||
|
valueStr := getEnv(name, "")
|
||||||
|
if value, err := strconv.Atoi(valueStr); err == nil {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return defaultVal
|
||||||
|
}
|
||||||
|
|
||||||
|
// getEnvAsBool func - read an environment variable into a bool or return default value
|
||||||
|
func getEnvAsBool(name string, defaultVal bool) bool {
|
||||||
|
valStr := getEnv(name, "")
|
||||||
|
if val, err := strconv.ParseBool(valStr); err == nil {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
return defaultVal
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertStringToBool func
|
||||||
|
func convertStringToBool(data string) bool {
|
||||||
|
if value, err := strconv.ParseBool(data); err == nil {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
// else..
|
||||||
|
log.Println("Error: convertStringToBool could not return value correct.. returning false")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertStringToFloat func
|
||||||
|
func convertStringToFloat(data string) float64 {
|
||||||
|
if value, err := strconv.ParseFloat(data, 64); err == nil {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
// else..
|
||||||
|
log.Println("Error: convertStringToFloat could not return value correct.. returning 0.0")
|
||||||
|
return 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertStringToInteger func
|
||||||
|
func convertStringToInteger(data string) int {
|
||||||
|
if value, err := strconv.Atoi(data); err == nil {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
// else..
|
||||||
|
log.Println("Error: convertStringToInteger could not return value correct.. returning 0")
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// kilometersToMiles func
|
||||||
|
func kilometersToMiles(km float64) float64 {
|
||||||
|
return (km * 0.62137119223733)
|
||||||
|
}
|
||||||
|
|
||||||
|
// milesToKilometers func
|
||||||
|
func milesToKilometers(mi float64) float64 {
|
||||||
|
return (mi * 1.609344)
|
||||||
|
}
|
||||||
|
|
||||||
|
// celsiusToFahrenheit func
|
||||||
|
func celsiusToFahrenheit(c float64) float64 {
|
||||||
|
return (c*9/5 + 32)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fahrenheitToCelsius func
|
||||||
|
func fahrenheitToCelsius(f float64) float64 {
|
||||||
|
return ((f - 32) * 5 / 9)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user