ABRP keeps getting live data while the car server is down: with an ABRP API key and user token set in the app (Live data tab, or abrp_api_key / abrp_token in the config file), the car sends the newest sample to ABRP itself at every upload. It tells the server the newest sample ABRP took (abrpSentUtc), and the server forwards only newer ones, so ABRP gets each sample once. Without credentials in the app the server forwards everything, as before. The credentials sit in encrypted storage, go in the request body, and neither they nor ABRP's answer are logged. Settings are grouped by what they are for: - Live data (was Upload): on/off, ABRP credentials, upload frequency. - Settings (replaces the Wi-Fi tab): full screen and voice key, usable capacity and manual SoH (were on Consumption), charging price (was on Charging), Wi-Fi. Charging and Live data get icons of their own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QLJfZQZeAKAwSqViTmLzki
114 lines
3.7 KiB
Go
114 lines
3.7 KiB
Go
package main
|
|
|
|
import "time"
|
|
|
|
// Totals are one day's (or one chunk's) consumption counters.
|
|
type Totals struct {
|
|
Km float64 `json:"km"`
|
|
Kwh float64 `json:"kwh"`
|
|
Hours float64 `json:"hours"`
|
|
SocDrop float64 `json:"socDrop"`
|
|
}
|
|
|
|
func (t *Totals) add(o Totals) {
|
|
t.Km += o.Km
|
|
t.Kwh += o.Kwh
|
|
t.Hours += o.Hours
|
|
t.SocDrop += o.SocDrop
|
|
}
|
|
|
|
// ConsumptionChunk is one 2-minute delta of a day's consumption, as the car sends it.
|
|
// Days are built from these when history is read.
|
|
type ConsumptionChunk struct {
|
|
InstallationID string
|
|
Date string // the car's local date, YYYY-MM-DD
|
|
RecordedAt time.Time
|
|
Day Totals
|
|
SohPercent *float64
|
|
}
|
|
|
|
// DailyConsumption is a day folded from its chunks.
|
|
type DailyConsumption struct {
|
|
InstallationID string `json:"installationId"`
|
|
Date string `json:"date"`
|
|
RecordedAt time.Time `json:"recordedAt"`
|
|
Day Totals `json:"day"`
|
|
SohPercent *float64 `json:"sohPercent,omitempty"`
|
|
// Lifetime is the running total up to and including this day; not part of the
|
|
// car's history response, which carries one lifetime for all days.
|
|
Lifetime Totals `json:"-"`
|
|
}
|
|
|
|
type CurvePoint struct {
|
|
Timestamp time.Time
|
|
PowerKw float64
|
|
SocPercent *float64
|
|
}
|
|
|
|
type ChargingSession struct {
|
|
InstallationID string
|
|
SessionID string
|
|
StartedAt time.Time
|
|
EndedAt time.Time
|
|
DurationSeconds float64
|
|
EnergyKwh float64
|
|
ChargingLossPercent float64
|
|
GridEnergyKwh float64
|
|
PricePerKwh float64
|
|
TotalCost float64
|
|
IsDemo bool
|
|
StartSocPercent *float64
|
|
EndSocPercent *float64
|
|
Curve []CurvePoint
|
|
}
|
|
|
|
// telemetryNumbers are the numeric live fields, named and unit-ed as ABRP's tlm/send
|
|
// expects them, with the range each one must fall in.
|
|
var telemetryNumbers = map[string][2]float64{
|
|
"soc": {0, 100}, // %
|
|
"speed": {0, 400}, // km/h
|
|
"power": {-1000, 1000}, // kW, positive leaving the battery
|
|
"est_battery_range": {0, 2000}, // km
|
|
"capacity": {0, 500}, // kWh
|
|
"soe": {0, 500}, // kWh
|
|
"soh": {0, 100}, // %
|
|
"kwh_charged": {0, 1000}, // kWh, this charge session
|
|
"odometer": {0, 10_000_000}, // km
|
|
"lat": {-90, 90}, // °
|
|
"lon": {-180, 180}, // °
|
|
"elevation": {-1000, 10_000}, // m
|
|
"heading": {0, 360}, // °
|
|
"batt_temp": {-60, 100}, // °C
|
|
"cabin_temp": {-60, 100}, // °C
|
|
"ext_temp": {-60, 70}, // °C
|
|
"hvac_setpoint": {0, 40}, // °C
|
|
"hvac_power": {0, 50}, // kW
|
|
"voltage": {0, 1500}, // V
|
|
"current": {-2000, 2000}, // A
|
|
"tire_pressure_fl": {0, 1000}, // kPa
|
|
"tire_pressure_fr": {0, 1000}, // kPa
|
|
"tire_pressure_rl": {0, 1000}, // kPa
|
|
"tire_pressure_rr": {0, 1000}, // kPa
|
|
}
|
|
|
|
// telemetryFlags are the boolean live fields; ABRP takes them as 0/1.
|
|
var telemetryFlags = []string{"is_charging", "is_dcfc", "is_parked"}
|
|
|
|
// TelemetrySample is one live sample. Only UTC is always present; an unread signal is
|
|
// absent from Numbers and Flags, never 0.
|
|
type TelemetrySample struct {
|
|
UTC int64 // epoch seconds
|
|
Numbers map[string]float64
|
|
Flags map[string]bool
|
|
}
|
|
|
|
type TelemetryBatch struct {
|
|
InstallationID string
|
|
Samples []TelemetrySample
|
|
// Dropped names the values and samples validation left out, e.g. "samples[3].speed".
|
|
Dropped []string
|
|
// AbrpSentUTC is the newest sample the car delivered to ABRP itself (0: none). The
|
|
// server forwards only samples newer than that, so ABRP gets each one once.
|
|
AbrpSentUTC int64
|
|
}
|