adding endpoints for charges and drives

This commit is contained in:
Tobias Lindberg
2021-02-13 10:50:09 +01:00
parent e0a52fb6d5
commit 40f27cd902
5 changed files with 1215 additions and 0 deletions
+198
View File
@@ -0,0 +1,198 @@
package main
import (
"encoding/json"
"fmt"
"log"
_ "github.com/lib/pq"
)
// TeslaMateAPICarsCharges func
func TeslaMateAPICarsCharges(CarID int, ResultPage int, ResultShow int) (string, bool) {
// creating structs for /cars/<CarID>/charges
// Car struct - child of Data
type Car struct {
CarID int `json:"car_id"` // smallint
CarName string `json:"car_name"` // text
}
// BatteryDetails struct - child of Charges
type BatteryDetails struct {
StartBatteryLevel int `json:"start_battery_level"` // int
EndBatteryLevel int `json:"end_battery_level"` // int
}
// PreferredRange struct - child of Charges
type PreferredRange struct {
StartRange float64 `json:"start_range"` // float64
EndRange float64 `json:"end_range"` // float64
}
// Charges struct - child of Data
type Charges struct {
ChargeID int `json:"charge_id"` // int
StartDate string `json:"start_date"` // string
EndDate string `json:"end_date"` // string
Address string `json:"address"` // string
ChargeEnergyAdded float64 `json:"charge_energy_added"` // float64
ChargeEnergyUsed float64 `json:"charge_energy_used"` // float64
Cost float64 `json:"cost"` // float64
DurationMin int `json:"duration_min"` // int
DurationStr string `json:"duration_str"` // string
BatteryDetails BatteryDetails `json:"battery_details"` // BatteryDetails
RangeIdeal PreferredRange `json:"range_ideal"` // PreferredRange
RangeRated PreferredRange `json:"range_rated"` // PreferredRange
OutsideTempAvg float64 `json:"outside_temp_avg"` // float64
}
// TeslaMateUnits struct - child of Data
type TeslaMateUnits struct {
UnitsLength string `json:"unit_of_length"` // string
UnitsTemperature string `json:"unit_of_temperature"` // string
}
// Data struct - child of JSONData
type Data struct {
Car Car `json:"car"`
Charges []Charges `json:"charges"`
TeslaMateUnits TeslaMateUnits `json:"units"`
}
// JSONData struct - main
type JSONData struct {
Data Data `json:"data"`
}
// creating required vars
var ChargesData []Charges
var UnitsLength, UnitsTemperature, CarName string
var ValidResponse bool // default is false
// calculate offset based on page (page 0 is not possible, since first page is minimum 1)
if ResultPage > 0 {
ResultPage--
} else {
ResultPage = 0
}
ResultPage = (ResultPage * ResultShow)
// getting data from database
query := `
SELECT
charging_processes.id AS charge_id,
start_date,
end_date,
COALESCE(geofence.name, CONCAT_WS(', ', COALESCE(address.name, nullif(CONCAT_WS(' ', address.road, address.house_number), '')), address.city)) AS address,
COALESCE(charge_energy_added, 0) AS charge_energy_added,
COALESCE(charge_energy_used, 0) AS charge_energy_used,
COALESCE(cost, 0) AS cost,
start_ideal_range_km AS start_ideal_range,
end_ideal_range_km AS end_ideal_range,
start_rated_range_km AS start_rated_range,
end_rated_range_km AS end_rated_range,
start_battery_level,
end_battery_level,
duration_min,
TO_CHAR((duration_min * INTERVAL '1 minute'), 'HH24:MI') as duration_str,
outside_temp_avg,
(SELECT unit_of_length FROM settings LIMIT 1) as unit_of_length,
(SELECT unit_of_temperature FROM settings LIMIT 1) as unit_of_temperature,
cars.name
FROM charging_processes
LEFT JOIN cars ON car_id = cars.id
LEFT JOIN addresses address ON address_id = address.id
LEFT JOIN positions position ON position_id = position.id
LEFT JOIN geofences geofence ON geofence_id = geofence.id
WHERE charging_processes.car_id=$1
ORDER BY start_date DESC
LIMIT $2 OFFSET $3;`
rows, err := db.Query(query, CarID, ResultShow, ResultPage)
// 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 charge object based on struct
charge := Charges{}
// scanning row and putting values into the charge
err = rows.Scan(
&charge.ChargeID,
&charge.StartDate,
&charge.EndDate,
&charge.Address,
&charge.ChargeEnergyAdded,
&charge.ChargeEnergyUsed,
&charge.Cost,
&charge.RangeIdeal.StartRange,
&charge.RangeIdeal.EndRange,
&charge.RangeRated.StartRange,
&charge.RangeRated.EndRange,
&charge.BatteryDetails.StartBatteryLevel,
&charge.BatteryDetails.EndBatteryLevel,
&charge.DurationMin,
&charge.DurationStr,
&charge.OutsideTempAvg,
&UnitsLength,
&UnitsTemperature,
&CarName,
)
// converting values based of settings UnitsLength
if UnitsLength == "mi" {
charge.RangeIdeal.StartRange = kilometersToMiles(charge.RangeIdeal.StartRange)
charge.RangeIdeal.EndRange = kilometersToMiles(charge.RangeIdeal.EndRange)
charge.RangeRated.StartRange = kilometersToMiles(charge.RangeRated.StartRange)
charge.RangeRated.EndRange = kilometersToMiles(charge.RangeRated.EndRange)
}
// converting values based of settings UnitsTemperature
if UnitsTemperature == "F" {
charge.OutsideTempAvg = celsiusToFahrenheit(charge.OutsideTempAvg)
}
// adjusting to timezone differences from UTC to be userspecific
charge.StartDate = getTimeInTimeZone(charge.StartDate)
charge.EndDate = getTimeInTimeZone(charge.EndDate)
// checking for errors after scanning
if err != nil {
log.Fatal(err)
}
// appending charge to ChargesData
ChargesData = append(ChargesData, charge)
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{
Car: Car{
CarID: CarID,
CarName: CarName,
},
Charges: ChargesData,
TeslaMateUnits: TeslaMateUnits{
UnitsLength: UnitsLength,
UnitsTemperature: UnitsTemperature,
},
},
}
// print readable output to log
log.Printf("data for /cars/%d/charges created:", CarID)
js, _ := json.Marshal(jsonData)
fmt.Printf("%s\n", js)
return string(js), ValidResponse
}
+320
View File
@@ -0,0 +1,320 @@
package main
import (
"encoding/json"
"fmt"
"log"
_ "github.com/lib/pq"
)
// TeslaMateAPICarsChargesDetails func
func TeslaMateAPICarsChargesDetails(CarID int, ChargeID int) (string, bool) {
// creating structs for /cars/<CarID>/charges/<ChargeID>
// Car struct - child of Data
type Car struct {
CarID int `json:"car_id"` // smallint
CarName string `json:"car_name"` // text
}
// BatteryDetails struct - child of Charges
type BatteryDetails struct {
StartBatteryLevel int `json:"start_battery_level"` // int
EndBatteryLevel int `json:"end_battery_level"` // int
}
// PreferredRange struct - child of Charges
type PreferredRange struct {
StartRange float64 `json:"start_range"` // float64
EndRange float64 `json:"end_range"` // float64
}
// ChargerDetails struct - child of ChargeDetails
type ChargerDetails struct {
ChargerActualCurrent int `json:"charger_actual_current"` // int
ChargerPhases int `json:"charger_phases"` // int
ChargerPilotCurrent int `json:"charger_pilot_current"` // int
ChargerPower int `json:"charger_power"` // int
ChargerVoltage int `json:"charger_voltage"` // int
}
// FastChargerInfo struct - child of ChargeDetails
type FastChargerInfo struct {
FastChargerPresent bool `json:"fast_charger_present"` // bool
FastChargerBrand string `json:"fast_charger_brand"` // string
FastChargerType string `json:"fast_charger_type"` // string
}
// BatteryInfo struct - child of ChargeDetails
type BatteryInfo struct {
IdealBatteryRange float64 `json:"ideal_battery_range"` // float64
RatedBatteryRange float64 `json:"rated_battery_range"` // float64
BatteryHeater bool `json:"battery_heater"` // bool
BatteryHeaterOn bool `json:"battery_heater_on"` // bool
BatteryHeaterNoPower bool `json:"battery_heater_no_power"` // bool
}
// ChargeDetails struct - child of Charge
type ChargeDetails struct {
DetailID int `json:"detail_id"` // integer
Date string `json:"date"` // string
BatteryLevel int `json:"battery_level"` // int
UsableBatteryLevel int `json:"usable_battery_level"` // int
ChargeEnergyAdded float64 `json:"charge_energy_added"` // float64
NotEnoughPowerToHeat bool `json:"not_enough_power_to_heat"` // bool
ChargerDetails ChargerDetails `json:"charger_details"` // struct
BatteryInfo BatteryInfo `json:"battery_info"` // struct
ConnChargeCable string `json:"conn_charge_cable"` // string
FastChargerInfo FastChargerInfo `json:"fast_charger_info"` // struct
OutsideTemp float64 `json:"outside_temp"` // float64
}
// Charge struct - child of Data
type Charge struct {
ChargeID int `json:"charge_id"` // int
StartDate string `json:"start_date"` // string
EndDate string `json:"end_date"` // string
Address string `json:"address"` // string
ChargeEnergyAdded float64 `json:"charge_energy_added"` // float64
ChargeEnergyUsed float64 `json:"charge_energy_used"` // float64
Cost float64 `json:"cost"` // float64
DurationMin int `json:"duration_min"` // int
DurationStr string `json:"duration_str"` // string
BatteryDetails BatteryDetails `json:"battery_details"` // BatteryDetails
RangeIdeal PreferredRange `json:"range_ideal"` // PreferredRange
RangeRated PreferredRange `json:"range_rated"` // PreferredRange
OutsideTempAvg float64 `json:"outside_temp_avg"` // float64
ChargeDetails []ChargeDetails `json:"charge_details"` // struct
}
// TeslaMateUnits struct - child of Data
type TeslaMateUnits struct {
UnitsLength string `json:"unit_of_length"` // string
UnitsTemperature string `json:"unit_of_temperature"` // string
}
// Data struct - child of JSONData
type Data struct {
Car Car `json:"car"`
Charge Charge `json:"charge"`
TeslaMateUnits TeslaMateUnits `json:"units"`
}
// JSONData struct - main
type JSONData struct {
Data Data `json:"data"`
}
// creating required vars
var ChargeData Charge
var ChargeDetailsData []ChargeDetails
var UnitsLength, UnitsTemperature, CarName string
var ValidResponse bool // default is false
// getting data from database
query := `
SELECT
charging_processes.id AS charge_id,
start_date,
end_date,
COALESCE(geofence.name, CONCAT_WS(', ', COALESCE(address.name, nullif(CONCAT_WS(' ', address.road, address.house_number), '')), address.city)) AS address,
COALESCE(charging_processes.charge_energy_added, 0) AS charge_energy_added,
COALESCE(charge_energy_used, 0) AS charge_energy_used,
COALESCE(cost, 0) AS cost,
start_ideal_range_km AS start_ideal_range,
end_ideal_range_km AS end_ideal_range,
start_rated_range_km AS start_rated_range,
end_rated_range_km AS end_rated_range,
start_battery_level,
end_battery_level,
duration_min,
TO_CHAR((duration_min * INTERVAL '1 minute'), 'HH24:MI') as duration_str,
outside_temp_avg,
(SELECT unit_of_length FROM settings LIMIT 1) as unit_of_length,
(SELECT unit_of_temperature FROM settings LIMIT 1) as unit_of_temperature,
cars.name
FROM charging_processes
LEFT JOIN cars ON car_id = cars.id
LEFT JOIN addresses address ON address_id = address.id
LEFT JOIN positions position ON position_id = position.id
LEFT JOIN geofences geofence ON geofence_id = geofence.id
LEFT JOIN charges ON charging_processes.id = charges.id
WHERE charging_processes.car_id=$1 AND charging_processes.id=$2
ORDER BY start_date DESC;`
rows, err := db.Query(query, CarID, ChargeID)
// 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 charge object based on struct
charge := Charge{}
// scanning row and putting values into the charge
err = rows.Scan(
&charge.ChargeID,
&charge.StartDate,
&charge.EndDate,
&charge.Address,
&charge.ChargeEnergyAdded,
&charge.ChargeEnergyUsed,
&charge.Cost,
&charge.RangeIdeal.StartRange,
&charge.RangeIdeal.EndRange,
&charge.RangeRated.StartRange,
&charge.RangeRated.EndRange,
&charge.BatteryDetails.StartBatteryLevel,
&charge.BatteryDetails.EndBatteryLevel,
&charge.DurationMin,
&charge.DurationStr,
&charge.OutsideTempAvg,
&UnitsLength,
&UnitsTemperature,
&CarName,
)
// converting values based of settings UnitsLength
if UnitsLength == "mi" {
charge.RangeIdeal.StartRange = kilometersToMiles(charge.RangeIdeal.StartRange)
charge.RangeIdeal.EndRange = kilometersToMiles(charge.RangeIdeal.EndRange)
charge.RangeRated.StartRange = kilometersToMiles(charge.RangeRated.StartRange)
charge.RangeRated.EndRange = kilometersToMiles(charge.RangeRated.EndRange)
}
// converting values based of settings UnitsTemperature
if UnitsTemperature == "F" {
charge.OutsideTempAvg = celsiusToFahrenheit(charge.OutsideTempAvg)
}
// adjusting to timezone differences from UTC to be userspecific
charge.StartDate = getTimeInTimeZone(charge.StartDate)
charge.EndDate = getTimeInTimeZone(charge.EndDate)
// checking for errors after scanning
if err != nil {
log.Fatal(err)
}
// appending charge to ChargeData
ChargeData = charge
ValidResponse = true
// getting detailed charge data from database
query = `
SELECT
id AS detail_id,
date,
battery_level,
usable_battery_level,
charge_energy_added,
not_enough_power_to_heat,
COALESCE(charger_actual_current, 0) as charger_actual_current,
COALESCE(charger_phases, 0) AS charger_phases,
COALESCE(charger_pilot_current, 0) as charger_pilot_current,
COALESCE(charger_power, 0) as charger_power,
COALESCE(charger_voltage, 0) as charger_voltage,
ideal_battery_range_km AS ideal_battery_range,
rated_battery_range_km AS rated_battery_range,
battery_heater,
battery_heater_on,
battery_heater_no_power,
conn_charge_cable,
fast_charger_present,
fast_charger_brand,
fast_charger_type,
outside_temp
FROM charges
WHERE charging_process_id=$1
ORDER BY id ASC;`
rows, err = db.Query(query, ChargeID)
// 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 chargedetails object based on struct
chargedetails := ChargeDetails{}
// scanning row and putting values into the drive
err = rows.Scan(
&chargedetails.DetailID,
&chargedetails.Date,
&chargedetails.BatteryLevel,
&chargedetails.UsableBatteryLevel,
&chargedetails.ChargeEnergyAdded,
&chargedetails.NotEnoughPowerToHeat,
&chargedetails.ChargerDetails.ChargerActualCurrent,
&chargedetails.ChargerDetails.ChargerPhases,
&chargedetails.ChargerDetails.ChargerPilotCurrent,
&chargedetails.ChargerDetails.ChargerPower,
&chargedetails.ChargerDetails.ChargerVoltage,
&chargedetails.BatteryInfo.IdealBatteryRange,
&chargedetails.BatteryInfo.RatedBatteryRange,
&chargedetails.BatteryInfo.BatteryHeater,
&chargedetails.BatteryInfo.BatteryHeaterOn,
&chargedetails.BatteryInfo.BatteryHeaterNoPower,
&chargedetails.ConnChargeCable,
&chargedetails.FastChargerInfo.FastChargerPresent,
&chargedetails.FastChargerInfo.FastChargerBrand,
&chargedetails.FastChargerInfo.FastChargerType,
&chargedetails.OutsideTemp,
)
// converting values based of settings UnitsLength
if UnitsLength == "mi" {
chargedetails.BatteryInfo.IdealBatteryRange = kilometersToMiles(chargedetails.BatteryInfo.IdealBatteryRange)
chargedetails.BatteryInfo.RatedBatteryRange = kilometersToMiles(chargedetails.BatteryInfo.RatedBatteryRange)
}
// converting values based of settings UnitsTemperature
if UnitsTemperature == "F" {
chargedetails.OutsideTemp = celsiusToFahrenheit(chargedetails.OutsideTemp)
}
// adjusting to timezone differences from UTC to be userspecific
chargedetails.Date = getTimeInTimeZone(chargedetails.Date)
// checking for errors after scanning
if err != nil {
log.Fatal(err)
}
// appending drive to ChargeData
ChargeDetailsData = append(ChargeDetailsData, chargedetails)
ChargeData.ChargeDetails = ChargeDetailsData
}
}
// checking for errors in the rows result
err = rows.Err()
if err != nil {
log.Fatal(err)
}
//
// build the data-blob
jsonData := JSONData{
Data{
Car: Car{
CarID: CarID,
CarName: CarName,
},
Charge: ChargeData,
TeslaMateUnits: TeslaMateUnits{
UnitsLength: UnitsLength,
UnitsTemperature: UnitsTemperature,
},
},
}
// print readable output to log
log.Printf("data for /cars/%d/charges/%d created:", CarID, ChargeID)
js, _ := json.Marshal(jsonData)
fmt.Printf("%s\n", js)
return string(js), ValidResponse
}
+246
View File
@@ -0,0 +1,246 @@
package main
import (
"encoding/json"
"fmt"
"log"
_ "github.com/lib/pq"
)
// TeslaMateAPICarsDrives func
func TeslaMateAPICarsDrives(CarID int, ResultPage int, ResultShow int) (string, bool) {
// creating structs for /cars/<CarID>/drives
// Car struct - child of Data
type Car struct {
CarID int `json:"car_id"` // smallint
CarName string `json:"car_name"` // text
}
// OdometerDetails struct - child of Drives
type OdometerDetails struct {
OdometerStart float64 `json:"odometer_start"` // float64
OdometerEnd float64 `json:"odometer_end"` // float64
OdometerDistance float64 `json:"odometer_distance"` // float64
}
// BatteryDetails struct - child of Drives
type BatteryDetails struct {
StartUsableBatteryLevel int `json:"start_usable_battery_level"` // int
StartBatteryLevel int `json:"start_battery_level"` // int
EndUsableBatteryLevel int `json:"end_usable_battery_level"` // int
EndBatteryLevel int `json:"end_battery_level"` // int
ReducedRange bool `json:"reduced_range"` // bool
IsSufficientlyPrecise bool `json:"is_sufficiently_precise"` // bool
}
// PreferredRange struct - child of Drives
type PreferredRange struct {
StartRange float64 `json:"start_range"` // float64
EndRange float64 `json:"end_range"` // float64
RangeDiff float64 `json:"range_diff"` // float64
}
// Drives struct - child of Data
type Drives struct {
DriveID int `json:"drive_id"` // int
StartDate string `json:"start_date"` // string
EndDate string `json:"end_date"` // string
StartAddress string `json:"start_address"` // string
EndAddress string `json:"end_address"` // string
OdometerDetails OdometerDetails `json:"odometer_details"` // OdometerDetails
DurationMin int `json:"duration_min"` // int
DurationStr string `json:"duration_str"` // string
SpeedMax int `json:"speed_max"` // int
SpeedAvg float64 `json:"speed_avg"` // float64
PowerMax int `json:"power_max"` // int
PowerMin int `json:"power_min"` // int
BatteryDetails BatteryDetails `json:"battery_details"` // BatteryDetails
RangeIdeal PreferredRange `json:"range_ideal"` // PreferredRange
RangeRated PreferredRange `json:"range_rated"` // PreferredRange
OutsideTempAvg float64 `json:"outside_temp_avg"` // float64
InsideTempAvg float64 `json:"inside_temp_avg"` // float64
}
// TeslaMateUnits struct - child of Data
type TeslaMateUnits struct {
UnitsLength string `json:"unit_of_length"` // string
UnitsTemperature string `json:"unit_of_temperature"` // string
}
// Data struct - child of JSONData
type Data struct {
Car Car `json:"car"`
Drives []Drives `json:"drives"`
TeslaMateUnits TeslaMateUnits `json:"units"`
}
// JSONData struct - main
type JSONData struct {
Data Data `json:"data"`
}
// creating required vars
var DrivesData []Drives
var UnitsLength, UnitsTemperature, CarName string
var ValidResponse bool // default is false
// calculate offset based on page (page 0 is not possible, since first page is minimum 1)
if ResultPage > 0 {
ResultPage--
} else {
ResultPage = 0
}
ResultPage = (ResultPage * ResultShow)
// getting data from database
query := `
SELECT
drives.id AS drive_id,
start_date,
end_date,
COALESCE(start_geofence.name, CONCAT_WS(', ', COALESCE(start_address.name, nullif(CONCAT_WS(' ', start_address.road, start_address.house_number), '')), start_address.city)) AS start_address,
COALESCE(end_geofence.name, CONCAT_WS(', ', COALESCE(end_address.name, nullif(CONCAT_WS(' ', end_address.road, end_address.house_number), '')), end_address.city)) AS end_address,
start_km,
end_km,
distance,
duration_min,
TO_CHAR((duration_min * INTERVAL '1 minute'), 'HH24:MI') as duration_str,
speed_max,
COALESCE(distance / NULLIF(duration_min, 0) * 60, 0) AS speed_avg,
power_max,
power_min,
COALESCE(start_position.usable_battery_level, start_position.battery_level) as start_usable_battery_level,
start_position.battery_level as start_battery_level,
COALESCE(end_position.usable_battery_level, end_position.battery_level) as end_usable_battery_level,
end_position.battery_level as end_battery_level,
case when ( start_position.battery_level != start_position.usable_battery_level OR end_position.battery_level != end_position.usable_battery_level ) = true then true else false end as reduced_range,
duration_min > 1 AND distance > 1 AND ( start_position.usable_battery_level IS NULL OR end_position.usable_battery_level IS NULL OR ( end_position.battery_level - end_position.usable_battery_level ) = 0 ) as is_sufficiently_precise,
start_ideal_range_km,
end_ideal_range_km,
COALESCE( NULLIF ( GREATEST ( start_ideal_range_km - end_ideal_range_km, 0 ), 0 ),0 ) as range_diff_ideal_km,
start_rated_range_km,
end_rated_range_km,
COALESCE( NULLIF ( GREATEST ( start_rated_range_km - end_rated_range_km, 0 ), 0 ),0 ) as range_diff_rated_km,
outside_temp_avg,
inside_temp_avg,
(SELECT unit_of_length FROM settings LIMIT 1) as unit_of_length,
(SELECT unit_of_temperature FROM settings LIMIT 1) as unit_of_temperature,
cars.name
FROM drives
LEFT JOIN cars ON car_id = cars.id
LEFT JOIN addresses start_address ON start_address_id = start_address.id
LEFT JOIN addresses end_address ON end_address_id = end_address.id
LEFT JOIN positions start_position ON start_position_id = start_position.id
LEFT JOIN positions end_position ON end_position_id = end_position.id
LEFT JOIN geofences start_geofence ON start_geofence_id = start_geofence.id
LEFT JOIN geofences end_geofence ON end_geofence_id = end_geofence.id
WHERE drives.car_id=$1 AND end_date IS NOT NULL
ORDER BY start_date DESC
LIMIT $2 OFFSET $3;`
rows, err := db.Query(query, CarID, ResultShow, ResultPage)
// 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 drive object based on struct
drive := Drives{}
// scanning row and putting values into the drive
err = rows.Scan(
&drive.DriveID,
&drive.StartDate,
&drive.EndDate,
&drive.StartAddress,
&drive.EndAddress,
&drive.OdometerDetails.OdometerStart,
&drive.OdometerDetails.OdometerEnd,
&drive.OdometerDetails.OdometerDistance,
&drive.DurationMin,
&drive.DurationStr,
&drive.SpeedMax,
&drive.SpeedAvg,
&drive.PowerMax,
&drive.PowerMin,
&drive.BatteryDetails.StartUsableBatteryLevel,
&drive.BatteryDetails.StartBatteryLevel,
&drive.BatteryDetails.EndUsableBatteryLevel,
&drive.BatteryDetails.EndBatteryLevel,
&drive.BatteryDetails.ReducedRange,
&drive.BatteryDetails.IsSufficientlyPrecise,
&drive.RangeIdeal.StartRange,
&drive.RangeIdeal.EndRange,
&drive.RangeIdeal.RangeDiff,
&drive.RangeRated.StartRange,
&drive.RangeRated.EndRange,
&drive.RangeRated.RangeDiff,
&drive.OutsideTempAvg,
&drive.InsideTempAvg,
&UnitsLength,
&UnitsTemperature,
&CarName,
)
// converting values based of settings UnitsLength
if UnitsLength == "mi" {
drive.OdometerDetails.OdometerStart = kilometersToMiles(drive.OdometerDetails.OdometerStart)
drive.OdometerDetails.OdometerEnd = kilometersToMiles(drive.OdometerDetails.OdometerEnd)
drive.OdometerDetails.OdometerDistance = kilometersToMiles(drive.OdometerDetails.OdometerDistance)
drive.RangeIdeal.StartRange = kilometersToMiles(drive.RangeIdeal.StartRange)
drive.RangeIdeal.EndRange = kilometersToMiles(drive.RangeIdeal.EndRange)
drive.RangeIdeal.RangeDiff = kilometersToMiles(drive.RangeIdeal.RangeDiff)
drive.RangeRated.StartRange = kilometersToMiles(drive.RangeRated.StartRange)
drive.RangeRated.EndRange = kilometersToMiles(drive.RangeRated.EndRange)
drive.RangeRated.RangeDiff = kilometersToMiles(drive.RangeRated.RangeDiff)
}
// converting values based of settings UnitsTemperature
if UnitsTemperature == "F" {
drive.OutsideTempAvg = celsiusToFahrenheit(drive.OutsideTempAvg)
drive.InsideTempAvg = celsiusToFahrenheit(drive.InsideTempAvg)
}
// adjusting to timezone differences from UTC to be userspecific
drive.StartDate = getTimeInTimeZone(drive.StartDate)
drive.EndDate = getTimeInTimeZone(drive.EndDate)
// checking for errors after scanning
if err != nil {
log.Fatal(err)
}
// appending drive to DrivesData
DrivesData = append(DrivesData, drive)
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{
Car: Car{
CarID: CarID,
CarName: CarName,
},
Drives: DrivesData,
TeslaMateUnits: TeslaMateUnits{
UnitsLength: UnitsLength,
UnitsTemperature: UnitsTemperature,
},
},
}
// print readable output to log
log.Printf("data for /cars/%d/drives created:", CarID)
js, _ := json.Marshal(jsonData)
fmt.Printf("%s\n", js)
return string(js), ValidResponse
}
+383
View File
@@ -0,0 +1,383 @@
package main
import (
"encoding/json"
"fmt"
"log"
_ "github.com/lib/pq"
)
// TeslaMateAPICarsDrivesDetails func
func TeslaMateAPICarsDrivesDetails(CarID int, DriveID int) (string, bool) {
// creating structs for /cars/<CarID>/drives/<DriveID>
// Car struct - child of Data
type Car struct {
CarID int `json:"car_id"` // smallint
CarName string `json:"car_name"` // text
}
// OdometerDetails struct - child of Drives
type OdometerDetails struct {
OdometerStart float64 `json:"odometer_start"` // float64
OdometerEnd float64 `json:"odometer_end"` // float64
OdometerDistance float64 `json:"odometer_distance"` // float64
}
// BatteryDetails struct - child of Drives
type BatteryDetails struct {
StartUsableBatteryLevel int `json:"start_usable_battery_level"` // int
StartBatteryLevel int `json:"start_battery_level"` // int
EndUsableBatteryLevel int `json:"end_usable_battery_level"` // int
EndBatteryLevel int `json:"end_battery_level"` // int
ReducedRange bool `json:"reduced_range"` // bool
IsSufficientlyPrecise bool `json:"is_sufficiently_precise"` // bool
}
// PreferredRange struct - child of Drives
type PreferredRange struct {
StartRange float64 `json:"start_range"` // float64
EndRange float64 `json:"end_range"` // float64
RangeDiff float64 `json:"range_diff"` // float64
}
// ClimateInfo struct - child of DriveDetails
type ClimateInfo struct {
InsideTemp NullFloat64 `json:"inside_temp"` // numeric(4,1)
OutsideTemp NullFloat64 `json:"outside_temp"` // numeric(4,1)
IsClimateOn NullBool `json:"is_climate_on"` // boolean
FanStatus NullInt64 `json:"fan_status"` // integer
DriverTempSetting NullFloat64 `json:"driver_temp_setting"` // numeric(4,1)
PassengerTempSetting NullFloat64 `json:"passenger_temp_setting"` // numeric(4,1)
IsRearDefrosterOn NullBool `json:"is_rear_defroster_on"` // boolean
IsFrontDefrosterOn NullBool `json:"is_front_defroster_on"` // boolean
}
// BatteryInfo struct - child of DriveDetails
type BatteryInfo struct {
EstBatteryRange NullFloat64 `json:"est_battery_range"` // numeric(6,2)
IdealBatteryRange NullFloat64 `json:"ideal_battery_range"` // numeric(6,2)
RatedBatteryRange NullFloat64 `json:"rated_battery_range"` // numeric(6,2)
BatteryHeater NullBool `json:"battery_heater"` // boolean
BatteryHeaterOn NullBool `json:"battery_heater_on"` // boolean
BatteryHeaterNoPower NullBool `json:"battery_heater_no_power"` // boolean
}
// DriveDetails struct - child of Drive
type DriveDetails struct {
DetailID int `json:"detail_id"` // integer
Date string `json:"date"` // timestamp without time zone
Latitude float64 `json:"latitude"` // numeric(8,6)
Longitude float64 `json:"longitude"` // numeric(9,6)
Speed int `json:"speed"` // smallint
Power int `json:"power"` // smallint
Odometer float64 `json:"odometer"` // double precision
BatteryLevel int `json:"battery_level"` // smallint
UsableBatteryLevel NullInt64 `json:"usable_battery_level"` // smallint
Elevation NullInt64 `json:"elevation"` // smallint
ClimateInfo ClimateInfo `json:"climate_info"` // struct
BatteryInfo BatteryInfo `json:"battery_info"` // struct
}
// Drive struct - child of Data
type Drive struct {
DriveID int `json:"drive_id"` // int
StartDate string `json:"start_date"` // string
EndDate string `json:"end_date"` // string
StartAddress string `json:"start_address"` // string
EndAddress string `json:"end_address"` // string
OdometerDetails OdometerDetails `json:"odometer_details"` // OdometerDetails
DurationMin int `json:"duration_min"` // int
DurationStr string `json:"duration_str"` // string
SpeedMax int `json:"speed_max"` // int
SpeedAvg float64 `json:"speed_avg"` // float64
PowerMax int `json:"power_max"` // int
PowerMin int `json:"power_min"` // int
BatteryDetails BatteryDetails `json:"battery_details"` // BatteryDetails
RangeIdeal PreferredRange `json:"range_ideal"` // PreferredRange
RangeRated PreferredRange `json:"range_rated"` // PreferredRange
OutsideTempAvg float64 `json:"outside_temp_avg"` // float64
InsideTempAvg float64 `json:"inside_temp_avg"` // float64
DriveDetails []DriveDetails `json:"drive_details"` // struct
}
// TeslaMateUnits struct - child of Data
type TeslaMateUnits struct {
UnitsLength string `json:"unit_of_length"` // string
UnitsTemperature string `json:"unit_of_temperature"` // string
}
// Data struct - child of JSONData
type Data struct {
Car Car `json:"car"`
Drive Drive `json:"drive"`
TeslaMateUnits TeslaMateUnits `json:"units"`
}
// JSONData struct - main
type JSONData struct {
Data Data `json:"data"`
}
// creating required vars
var DriveData Drive
var DriveDetailsData []DriveDetails
var UnitsLength, UnitsTemperature, CarName string
var ValidResponse bool // default is false
// getting data from database
query := `
SELECT
drives.id AS drive_id,
start_date,
end_date,
COALESCE(start_geofence.name, CONCAT_WS(', ', COALESCE(start_address.name, nullif(CONCAT_WS(' ', start_address.road, start_address.house_number), '')), start_address.city)) AS start_address,
COALESCE(end_geofence.name, CONCAT_WS(', ', COALESCE(end_address.name, nullif(CONCAT_WS(' ', end_address.road, end_address.house_number), '')), end_address.city)) AS end_address,
start_km,
end_km,
distance,
duration_min,
TO_CHAR((duration_min * INTERVAL '1 minute'), 'HH24:MI') as duration_str,
speed_max,
COALESCE(distance / NULLIF(duration_min, 0) * 60, 0) AS speed_avg,
power_max,
power_min,
COALESCE(start_position.usable_battery_level, start_position.battery_level) as start_usable_battery_level,
start_position.battery_level as start_battery_level,
COALESCE(end_position.usable_battery_level, end_position.battery_level) as end_usable_battery_level,
end_position.battery_level as end_battery_level,
case when ( start_position.battery_level != start_position.usable_battery_level OR end_position.battery_level != end_position.usable_battery_level ) = true then true else false end as reduced_range,
duration_min > 1 AND distance > 1 AND ( start_position.usable_battery_level IS NULL OR end_position.usable_battery_level IS NULL OR ( end_position.battery_level - end_position.usable_battery_level ) = 0 ) as is_sufficiently_precise,
start_ideal_range_km,
end_ideal_range_km,
COALESCE( NULLIF ( GREATEST ( start_ideal_range_km - end_ideal_range_km, 0 ), 0 ),0 ) as range_diff_ideal_km,
start_rated_range_km,
end_rated_range_km,
COALESCE( NULLIF ( GREATEST ( start_rated_range_km - end_rated_range_km, 0 ), 0 ),0 ) as range_diff_rated_km,
outside_temp_avg,
inside_temp_avg,
(SELECT unit_of_length FROM settings LIMIT 1) as unit_of_length,
(SELECT unit_of_temperature FROM settings LIMIT 1) as unit_of_temperature,
cars.name
FROM drives
LEFT JOIN cars ON car_id = cars.id
LEFT JOIN addresses start_address ON start_address_id = start_address.id
LEFT JOIN addresses end_address ON end_address_id = end_address.id
LEFT JOIN positions start_position ON start_position_id = start_position.id
LEFT JOIN positions end_position ON end_position_id = end_position.id
LEFT JOIN geofences start_geofence ON start_geofence_id = start_geofence.id
LEFT JOIN geofences end_geofence ON end_geofence_id = end_geofence.id
WHERE drives.car_id=$1 AND end_date IS NOT NULL AND drives.id = $2;`
rows, err := db.Query(query, CarID, DriveID)
// 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 drive object based on struct
drive := Drive{}
// scanning row and putting values into the drive
err = rows.Scan(
&drive.DriveID,
&drive.StartDate,
&drive.EndDate,
&drive.StartAddress,
&drive.EndAddress,
&drive.OdometerDetails.OdometerStart,
&drive.OdometerDetails.OdometerEnd,
&drive.OdometerDetails.OdometerDistance,
&drive.DurationMin,
&drive.DurationStr,
&drive.SpeedMax,
&drive.SpeedAvg,
&drive.PowerMax,
&drive.PowerMin,
&drive.BatteryDetails.StartUsableBatteryLevel,
&drive.BatteryDetails.StartBatteryLevel,
&drive.BatteryDetails.EndUsableBatteryLevel,
&drive.BatteryDetails.EndBatteryLevel,
&drive.BatteryDetails.ReducedRange,
&drive.BatteryDetails.IsSufficientlyPrecise,
&drive.RangeIdeal.StartRange,
&drive.RangeIdeal.EndRange,
&drive.RangeIdeal.RangeDiff,
&drive.RangeRated.StartRange,
&drive.RangeRated.EndRange,
&drive.RangeRated.RangeDiff,
&drive.OutsideTempAvg,
&drive.InsideTempAvg,
&UnitsLength,
&UnitsTemperature,
&CarName,
)
// converting values based of settings UnitsLength
if UnitsLength == "mi" {
drive.OdometerDetails.OdometerStart = kilometersToMiles(drive.OdometerDetails.OdometerStart)
drive.OdometerDetails.OdometerEnd = kilometersToMiles(drive.OdometerDetails.OdometerEnd)
drive.OdometerDetails.OdometerDistance = kilometersToMiles(drive.OdometerDetails.OdometerDistance)
drive.SpeedMax = int(kilometersToMiles(float64(drive.SpeedMax)))
drive.SpeedAvg = kilometersToMiles(drive.SpeedAvg)
drive.RangeIdeal.StartRange = kilometersToMiles(drive.RangeIdeal.StartRange)
drive.RangeIdeal.EndRange = kilometersToMiles(drive.RangeIdeal.EndRange)
drive.RangeIdeal.RangeDiff = kilometersToMiles(drive.RangeIdeal.RangeDiff)
drive.RangeRated.StartRange = kilometersToMiles(drive.RangeRated.StartRange)
drive.RangeRated.EndRange = kilometersToMiles(drive.RangeRated.EndRange)
drive.RangeRated.RangeDiff = kilometersToMiles(drive.RangeRated.RangeDiff)
}
// converting values based of settings UnitsTemperature
if UnitsTemperature == "F" {
drive.OutsideTempAvg = celsiusToFahrenheit(drive.OutsideTempAvg)
drive.InsideTempAvg = celsiusToFahrenheit(drive.InsideTempAvg)
}
// adjusting to timezone differences from UTC to be userspecific
drive.StartDate = getTimeInTimeZone(drive.StartDate)
drive.EndDate = getTimeInTimeZone(drive.EndDate)
// checking for errors after scanning
if err != nil {
log.Fatal(err)
}
// appending drive to DriveData
DriveData = drive
ValidResponse = true
// getting detailed drive data from database
query = `
SELECT
id AS detail_id,
date,
latitude,
longitude,
COALESCE(speed, 0) AS speed,
power,
odometer,
battery_level,
usable_battery_level,
elevation,
inside_temp,
outside_temp,
is_climate_on,
fan_status,
driver_temp_setting,
passenger_temp_setting,
is_rear_defroster_on,
is_front_defroster_on,
est_battery_range_km,
ideal_battery_range_km,
rated_battery_range_km,
battery_heater,
battery_heater_on,
battery_heater_no_power
FROM positions
WHERE drive_id = $1
ORDER BY id ASC;`
rows, err = db.Query(query, DriveID)
// 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 drivedetails object based on struct
drivedetails := DriveDetails{}
// scanning row and putting values into the drive
err = rows.Scan(
&drivedetails.DetailID,
&drivedetails.Date,
&drivedetails.Latitude,
&drivedetails.Longitude,
&drivedetails.Speed,
&drivedetails.Power,
&drivedetails.Odometer,
&drivedetails.BatteryLevel,
&drivedetails.UsableBatteryLevel,
&drivedetails.Elevation,
&drivedetails.ClimateInfo.InsideTemp,
&drivedetails.ClimateInfo.OutsideTemp,
&drivedetails.ClimateInfo.IsClimateOn,
&drivedetails.ClimateInfo.FanStatus,
&drivedetails.ClimateInfo.DriverTempSetting,
&drivedetails.ClimateInfo.PassengerTempSetting,
&drivedetails.ClimateInfo.IsRearDefrosterOn,
&drivedetails.ClimateInfo.IsFrontDefrosterOn,
&drivedetails.BatteryInfo.EstBatteryRange,
&drivedetails.BatteryInfo.IdealBatteryRange,
&drivedetails.BatteryInfo.RatedBatteryRange,
&drivedetails.BatteryInfo.BatteryHeater,
&drivedetails.BatteryInfo.BatteryHeaterOn,
&drivedetails.BatteryInfo.BatteryHeaterNoPower,
)
// converting values based of settings UnitsLength
if UnitsLength == "mi" {
drivedetails.Odometer = kilometersToMiles(drivedetails.Odometer)
drivedetails.Speed = int(kilometersToMiles(float64(drivedetails.Speed)))
drivedetails.BatteryInfo.EstBatteryRange = kilometersToMilesNilSupport(drivedetails.BatteryInfo.EstBatteryRange)
drivedetails.BatteryInfo.IdealBatteryRange = kilometersToMilesNilSupport(drivedetails.BatteryInfo.IdealBatteryRange)
drivedetails.BatteryInfo.RatedBatteryRange = kilometersToMilesNilSupport(drivedetails.BatteryInfo.RatedBatteryRange)
}
// converting values based of settings UnitsTemperature
if UnitsTemperature == "F" {
drivedetails.ClimateInfo.InsideTemp = celsiusToFahrenheitNilSupport(drivedetails.ClimateInfo.InsideTemp)
drivedetails.ClimateInfo.OutsideTemp = celsiusToFahrenheitNilSupport(drivedetails.ClimateInfo.OutsideTemp)
drivedetails.ClimateInfo.DriverTempSetting = celsiusToFahrenheitNilSupport(drivedetails.ClimateInfo.DriverTempSetting)
drivedetails.ClimateInfo.PassengerTempSetting = celsiusToFahrenheitNilSupport(drivedetails.ClimateInfo.PassengerTempSetting)
}
// adjusting to timezone differences from UTC to be userspecific
drivedetails.Date = getTimeInTimeZone(drivedetails.Date)
// checking for errors after scanning
if err != nil {
log.Fatal(err)
}
// appending drive to DriveData
DriveDetailsData = append(DriveDetailsData, drivedetails)
DriveData.DriveDetails = DriveDetailsData
}
// checking for errors in the rows result
err = rows.Err()
if err != nil {
log.Fatal(err)
}
}
// checking for errors in the rows result
err = rows.Err()
if err != nil {
log.Fatal(err)
}
//
// build the data-blob
jsonData := JSONData{
Data{
Car: Car{
CarID: CarID,
CarName: CarName,
},
Drive: DriveData,
TeslaMateUnits: TeslaMateUnits{
UnitsLength: UnitsLength,
UnitsTemperature: UnitsTemperature,
},
},
}
// print readable output to log
log.Printf("data for /cars/%d/drives/%d created:", CarID, DriveID)
js, _ := json.Marshal(jsonData)
fmt.Printf("%s\n", js)
return string(js), ValidResponse
}
+68
View File
@@ -56,6 +56,74 @@ func main() {
}
})
// /cars/<CarID>/charges endpoint
r.GET("/cars/:CarID/charges", func(c *gin.Context) {
// getting CarID param from URL
CarID := convertStringToInteger(c.Param("CarID"))
// query options to modify query when collecting data
ResultPage := convertStringToInteger(c.DefaultQuery("page", "1"))
ResultShow := convertStringToInteger(c.DefaultQuery("show", "100"))
// TeslaMateAPICarsCharges to get data
result, ValidResponse := TeslaMateAPICarsCharges(CarID, ResultPage, ResultShow)
c.Header("Content-Type", "application/json")
if ValidResponse {
c.String(http.StatusOK, result)
} else {
c.String(http.StatusNotFound, result)
}
})
// /cars/<CarID>/charges/<ChargeID> endpoint
r.GET("/cars/:CarID/charges/:ChargeID", func(c *gin.Context) {
// getting CarID and ChargeID param from URL
CarID := convertStringToInteger(c.Param("CarID"))
ChargeID := convertStringToInteger(c.Param("ChargeID"))
// TeslaMateAPICarsChargesDetails to get data
result, ValidResponse := TeslaMateAPICarsChargesDetails(CarID, ChargeID)
c.Header("Content-Type", "application/json")
if ValidResponse {
c.String(http.StatusOK, result)
} else {
c.String(http.StatusNotFound, result)
}
})
// /cars/<CarID>/drives endpoint
r.GET("/cars/:CarID/drives", func(c *gin.Context) {
// getting CarID param from URL
CarID := convertStringToInteger(c.Param("CarID"))
// query options to modify query when collecting data
ResultPage := convertStringToInteger(c.DefaultQuery("page", "1"))
ResultShow := convertStringToInteger(c.DefaultQuery("show", "100"))
// TeslaMateAPICarsDrives to get data
result, ValidResponse := TeslaMateAPICarsDrives(CarID, ResultPage, ResultShow)
c.Header("Content-Type", "application/json")
if ValidResponse {
c.String(http.StatusOK, result)
} else {
c.String(http.StatusNotFound, result)
}
})
// /cars/<CarID>/drives/<DriveID> endpoint
r.GET("/cars/:CarID/drives/:DriveID", func(c *gin.Context) {
// getting CarID and DriveID param from URL
CarID := convertStringToInteger(c.Param("CarID"))
DriveID := convertStringToInteger(c.Param("DriveID"))
// TeslaMateAPICarsDrivesDetails to get data
result, ValidResponse := TeslaMateAPICarsDrivesDetails(CarID, DriveID)
c.Header("Content-Type", "application/json")
if ValidResponse {
c.String(http.StatusOK, result)
} else {
c.String(http.StatusNotFound, result)
}
})
// /cars/<CarID>/status endpoint
r.GET("/cars/:CarID/status", func(c *gin.Context) {
// getting mqtt flag