initial commit of /cars

updated README
This commit is contained in:
Tobias Lindberg
2021-02-09 09:37:44 +01:00
parent ecb60f7a49
commit 31dcb4bbce
4 changed files with 346 additions and 2 deletions

1
.gitignore vendored
View File

@@ -13,3 +13,4 @@
# Dependency directories (remove the comment below to include it)
# vendor/
.env

View File

@@ -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
View 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
View 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)
}