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:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -13,3 +13,4 @@
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
.env
|
||||
|
||||
21
README.md
21
README.md
@@ -1,2 +1,19 @@
|
||||
# teslamateapi
|
||||
TeslaMate RESTful API plugin to get data collected by self-hosted data logger TeslaMate
|
||||
# TeslaMateApi
|
||||
|
||||
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
|
||||
|
||||
170
src/TeslaMateAPICars.go
Normal file
170
src/TeslaMateAPICars.go
Normal file
@@ -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
|
||||
}
|
||||
156
src/webserver.go
Normal file
156
src/webserver.go
Normal file
@@ -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