Files
archived-teslamateapi/src/v1_TeslaMateAPICarsDrives.go
2025-07-23 12:27:44 +02:00

263 lines
10 KiB
Go

package main
import (
"github.com/gin-gonic/gin"
_ "github.com/lib/pq"
)
// TeslaMateAPICarsDrivesV1 godoc
// @Summary TeslaMate drives endpoint
// @Description Get all drives for a specific car by CarID from TeslaMate
// @Description Since no DriveID is given, all drives will be returned.
// @Tags drives
// @Param CarID path int true "The CarID"
// @Param page query int false "Page number for pagination" default(1)
// @Param show query int false "Number of results to show per page" default(100)
// @Router /v1/cars/{CarID}/drives [get]
func TeslaMateAPICarsDrivesV1(c *gin.Context) {
// define error messages
var CarsDrivesError1 = "Unable to load drives."
// 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"))
// creating structs for /cars/<CarID>/drives
// Car struct - child of Data
type Car struct {
CarID int `json:"car_id"` // smallint
CarName NullString `json:"car_name"` // text (nullable)
}
// 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 (
CarName NullString
DrivesData []Drives
UnitsLength, UnitsTemperature string
)
// 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 {
TeslaMateAPIHandleErrorResponse(c, "TeslaMateAPICarsDrivesV1", CarsDrivesError1, err.Error())
return
}
// 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.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 {
TeslaMateAPIHandleErrorResponse(c, "TeslaMateAPICarsDrivesV1", CarsDrivesError1, err.Error())
return
}
// appending drive to DrivesData
DrivesData = append(DrivesData, drive)
}
// checking for errors in the rows result
err = rows.Err()
if err != nil {
TeslaMateAPIHandleErrorResponse(c, "TeslaMateAPICarsDrivesV1", CarsDrivesError1, err.Error())
return
}
//
// build the data-blob
jsonData := JSONData{
Data{
Car: Car{
CarID: CarID,
CarName: CarName,
},
Drives: DrivesData,
TeslaMateUnits: TeslaMateUnits{
UnitsLength: UnitsLength,
UnitsTemperature: UnitsTemperature,
},
},
}
// return jsonData
TeslaMateAPIHandleSuccessResponse(c, "TeslaMateAPICarsDrivesV1", jsonData)
}