From 3535cdc404a8e9375f9ea8755d49a4e16ebf0a8c Mon Sep 17 00:00:00 2001 From: Matt Brittan Date: Thu, 18 Mar 2021 16:31:32 +1300 Subject: [PATCH] Refactor MQTT so it connects once to broker and caches messages --- src/v1_TeslaMateAPICarsStatus.go | 543 +++++++++++++++---------------- src/webserver.go | 8 +- 2 files changed, 262 insertions(+), 289 deletions(-) diff --git a/src/v1_TeslaMateAPICarsStatus.go b/src/v1_TeslaMateAPICarsStatus.go index bc194d3..6ca1a64 100644 --- a/src/v1_TeslaMateAPICarsStatus.go +++ b/src/v1_TeslaMateAPICarsStatus.go @@ -2,10 +2,11 @@ package main import ( "encoding/json" + "errors" "fmt" "log" "net/http" - "path" + "sync" "time" mqtt "github.com/eclipse/paho.mqtt.golang" @@ -13,8 +14,8 @@ import ( _ "github.com/lib/pq" ) -// defining a lot of vars that are used my the MQTT MessageHandler -var ( +// statusInfo holds the status info for a car +type statusInfo struct { MQTTDataDisplayName string MQTTDataState string MQTTDataStateSince string @@ -61,127 +62,25 @@ var ( MQTTDataChargerVoltage int MQTTDataScheduledChargingStartTime string MQTTDataTimeToFullCharge float64 -) - -// define a function for the default message handler -var f mqtt.MessageHandler = func(client mqtt.Client, msg mqtt.Message) { - - // extracting the last part of topic - _, MqttTopic := path.Split(msg.Topic()) - - // running if-else statements to collect data and put into overall vars.. - if MqttTopic == "display_name" { - MQTTDataDisplayName = string(msg.Payload()) - } else if MqttTopic == "state" { - MQTTDataState = string(msg.Payload()) - } else if MqttTopic == "since" { - MQTTDataStateSince = string(msg.Payload()) - } else if MqttTopic == "healthy" { - MQTTDataHealthy = convertStringToBool(string(msg.Payload())) - } else if MqttTopic == "version" { - MQTTDataVersion = string(msg.Payload()) - } else if MqttTopic == "update_available" { - MQTTDataUpdateAvailable = convertStringToBool(string(msg.Payload())) - } else if MqttTopic == "update_version" { - MQTTDataUpdateVersion = string(msg.Payload()) - } else if MqttTopic == "model" { - MQTTDataModel = string(msg.Payload()) - } else if MqttTopic == "trim_badging" { - MQTTDataTrimBadging = string(msg.Payload()) - } else if MqttTopic == "exterior_color" { - MQTTDataExteriorColor = string(msg.Payload()) - } else if MqttTopic == "wheel_type" { - MQTTDataWheelType = string(msg.Payload()) - } else if MqttTopic == "spoiler_type" { - MQTTDataSpoilerType = string(msg.Payload()) - } else if MqttTopic == "geofence" { - MQTTDataGeofence = string(msg.Payload()) - } else if MqttTopic == "latitude" { - MQTTDataLatitude = convertStringToFloat(string(msg.Payload())) - } else if MqttTopic == "longitude" { - MQTTDataLongitude = convertStringToFloat(string(msg.Payload())) - } else if MqttTopic == "shift_state" { - MQTTDataShiftState = string(msg.Payload()) - } else if MqttTopic == "speed" { - MQTTDataSpeed = convertStringToInteger(string(msg.Payload())) - } else if MqttTopic == "heading" { - MQTTDataHeading = convertStringToInteger(string(msg.Payload())) - } else if MqttTopic == "elevation" { - MQTTDataElevation = convertStringToInteger(string(msg.Payload())) - } else if MqttTopic == "locked" { - MQTTDataLocked = convertStringToBool(string(msg.Payload())) - } else if MqttTopic == "sentry_mode" { - MQTTDataSentryMode = convertStringToBool(string(msg.Payload())) - } else if MqttTopic == "windows_open" { - MQTTDataWindowsOpen = convertStringToBool(string(msg.Payload())) - } else if MqttTopic == "doors_open" { - MQTTDataDoorsOpen = convertStringToBool(string(msg.Payload())) - } else if MqttTopic == "trunk_open" { - MQTTDataTrunkOpen = convertStringToBool(string(msg.Payload())) - } else if MqttTopic == "frunk_open" { - MQTTDataFrunkOpen = convertStringToBool(string(msg.Payload())) - } else if MqttTopic == "is_user_present" { - MQTTDataIsUserPresent = convertStringToBool(string(msg.Payload())) - } else if MqttTopic == "is_climate_on" { - MQTTDataIsClimateOn = convertStringToBool(string(msg.Payload())) - } else if MqttTopic == "inside_temp" { - MQTTDataInsideTemp = convertStringToFloat(string(msg.Payload())) - } else if MqttTopic == "outside_temp" { - MQTTDataOutsideTemp = convertStringToFloat(string(msg.Payload())) - } else if MqttTopic == "is_preconditioning" { - MQTTDataIsPreconditioning = convertStringToBool(string(msg.Payload())) - } else if MqttTopic == "odometer" { - MQTTDataOdometer = convertStringToFloat(string(msg.Payload())) - } else if MqttTopic == "est_battery_range_km" { - MQTTDataEstBatteryRange = convertStringToFloat(string(msg.Payload())) - } else if MqttTopic == "rated_battery_range_km" { - MQTTDataRatedBatteryRange = convertStringToFloat(string(msg.Payload())) - } else if MqttTopic == "ideal_battery_range_km" { - MQTTDataIdealBatteryRange = convertStringToFloat(string(msg.Payload())) - } else if MqttTopic == "battery_level" { - MQTTDataBatteryLevel = convertStringToInteger(string(msg.Payload())) - } else if MqttTopic == "usable_battery_level" { - MQTTDataUsableBatteryLevel = convertStringToInteger(string(msg.Payload())) - } else if MqttTopic == "plugged_in" { - MQTTDataPluggedIn = convertStringToBool(string(msg.Payload())) - } else if MqttTopic == "charge_energy_added" { - MQTTDataChargeEnergyAdded = convertStringToFloat(string(msg.Payload())) - } else if MqttTopic == "charge_limit_soc" { - MQTTDataChargeLimitSoc = convertStringToInteger(string(msg.Payload())) - } else if MqttTopic == "charge_port_door_open" { - MQTTDataChargePortDoorOpen = convertStringToBool(string(msg.Payload())) - } else if MqttTopic == "charger_actual_current" { - MQTTDataChargerActualCurrent = convertStringToFloat(string(msg.Payload())) - } else if MqttTopic == "charger_phases" { - MQTTDataChargerPhases = convertStringToInteger(string(msg.Payload())) - } else if MqttTopic == "charger_power" { - MQTTDataChargerPower = convertStringToFloat(string(msg.Payload())) - } else if MqttTopic == "charger_voltage" { - MQTTDataChargerVoltage = convertStringToInteger(string(msg.Payload())) - } else if MqttTopic == "scheduled_charging_start_time" { - MQTTDataScheduledChargingStartTime = string(msg.Payload()) - } else if MqttTopic == "time_to_full_charge" { - MQTTDataTimeToFullCharge = convertStringToFloat(string(msg.Payload())) - } else { - log.Printf("[warning] mqtt.MessageHandler issue.. extraction of data for %s not implemented!", MqttTopic) - } - } -// TeslaMateAPICarsStatusV1 func -func TeslaMateAPICarsStatusV1(c *gin.Context) { +type statusCache struct { + mqttDisabled bool + topicScan string // scan parameter (expect it to generate car ID then relevant parameter) + + cache map[int]*statusInfo + mu sync.Mutex +} + +func startMQTT() (*statusCache, error) { + var s statusCache // getting mqtt flag - mqttdisabled := getEnvAsBool("DISABLE_MQTT", false) - if mqttdisabled { - log.Println("[notice] TeslaMateAPICarsStatusV1 DISABLE_MQTT is set to true.. can not return status for car without mqtt!") - c.JSON(http.StatusNotImplemented, gin.H{"error": "mqtt disabled.. status not accessible!"}) - return + s.mqttDisabled = getEnvAsBool("DISABLE_MQTT", false) + if s.mqttDisabled { + return nil, errors.New("[notice] TeslaMateAPICarsStatusV1 DISABLE_MQTT is set to true.. can not return status for car without mqtt!") } - // getting CarID param from URL - CarID := convertStringToInteger(c.Param("CarID")) - // default values that get might get overwritten.. MQTTPort := 0 MQTTUserstring := "" @@ -199,7 +98,6 @@ func TeslaMateAPICarsStatusV1(c *gin.Context) { MQTTUser := getEnv("MQTT_USERNAME", "") MQTTPass := getEnv("MQTT_PASSWORD", "") // MQTTInvCert := getEnvAsBool("MQTT_TLS_ACCEPT_INVALID_CERTS", false) - MQTTNameSpace := getEnv("MQTT_NAMESPACE", "") // creating mqttURL to connect with // mqtt[s]://[username][:password]@host.domain[:port] @@ -215,11 +113,6 @@ func TeslaMateAPICarsStatusV1(c *gin.Context) { mqttURL := fmt.Sprintf("%s://%s%s:%d", MQTTProtocol, MQTTUserstring, MQTTHost, MQTTPort) - // adding MQTTNameSpace info - if len(MQTTNameSpace) > 0 { - MQTTNameSpace = ("/" + MQTTNameSpace) - } - /* // if some more logging is needed.. which we skip for now mqtt.DEBUG = log.New(os.Stdout, "", 0) @@ -229,16 +122,20 @@ func TeslaMateAPICarsStatusV1(c *gin.Context) { // create options for the MQTT client connection opts := mqtt.NewClientOptions().AddBroker(mqttURL) // setting generic MQTT settings in opts - opts.SetKeepAlive(2 * time.Second) // setting keepalive for client - opts.SetDefaultPublishHandler(f) // using f mqtt.MessageHandler function - opts.SetPingTimeout(1 * time.Second) // setting pingtimeout for client - opts.SetClientID("teslamateapi") // setting mqtt client id for TeslaMateApi - opts.SetCleanSession(true) // removal of all subscriptions on disconnect + opts.SetKeepAlive(2 * time.Second) // setting keepalive for client + opts.SetDefaultPublishHandler(s.newMessage) // using f mqtt.MessageHandler function + opts.SetPingTimeout(1 * time.Second) // setting pingtimeout for client + opts.SetClientID("teslamateapi") // setting mqtt client id for TeslaMateApi + opts.SetCleanSession(true) // removal of all subscriptions on disconnect + opts.SetOrderMatters(false) // don't care about order (removes need for callbacks to return immediately) + opts.SetAutoReconnect(true) // if connection drops automatically re-establish it + opts.AutoReconnect = true // creating MQTT connection with options m := mqtt.NewClient(opts) if token := m.Connect(); token.Wait() && token.Error() != nil { - log.Panic(token.Error()) + return nil, fmt.Errorf("failed to connect to MQTT: %w", token.Error()) + // Note : May want to use opts.ConnectRetry which will keep trying the connection } // showing mqtt successfully connected @@ -246,6 +143,163 @@ func TeslaMateAPICarsStatusV1(c *gin.Context) { log.Println("TeslaMateAPICarsStatus successfully connected to mqtt.") } + // adding MQTTNameSpace info + MQTTNameSpace := getEnv("MQTT_NAMESPACE", "") + if len(MQTTNameSpace) > 0 { + MQTTNameSpace = ("/" + MQTTNameSpace) + } + + // Subscribe - we will accept info on any car... + topic := fmt.Sprintf("teslamate%s/cars/#", MQTTNameSpace) + if token := m.Subscribe(topic, 0, s.newMessage); token.Wait() && token.Error() != nil { + log.Panic(token.Error()) // Note : May want to use opts.ConnectRetry which will keep trying the connection + } + s.topicScan = fmt.Sprintf("teslamate%s/cars/%%d/%%s", MQTTNameSpace) + + // Thats all - newMessage will be called when something new arrives + return &s, nil +} + +// newMessage - called by mqtt package when new message received +func (s *statusCache) newMessage(c mqtt.Client, msg mqtt.Message) { + // topic is in the format teslamateMQTT_NAMESPACE/cars/carID/display_name + var carID int + var MqttTopic string + _, err := fmt.Sscanf(msg.Topic(), s.topicScan, &carID, &MqttTopic) + if err != nil { + log.Printf("Unexpected topic format (%s) - ignoring message: %v", msg.Topic(), err) + return + } + + // extracting the last part of topic + s.mu.Lock() + defer s.mu.Unlock() + stat := s.cache[carID] + if stat == nil { + stat = &statusInfo{} + s.cache[carID] = stat + } + + // running if-else statements to collect data and put into overall vars.. + if MqttTopic == "display_name" { + stat.MQTTDataDisplayName = string(msg.Payload()) + } else if MqttTopic == "state" { + stat.MQTTDataState = string(msg.Payload()) + } else if MqttTopic == "since" { + stat.MQTTDataStateSince = string(msg.Payload()) + } else if MqttTopic == "healthy" { + stat.MQTTDataHealthy = convertStringToBool(string(msg.Payload())) + } else if MqttTopic == "version" { + stat.MQTTDataVersion = string(msg.Payload()) + } else if MqttTopic == "update_available" { + stat.MQTTDataUpdateAvailable = convertStringToBool(string(msg.Payload())) + } else if MqttTopic == "update_version" { + stat.MQTTDataUpdateVersion = string(msg.Payload()) + } else if MqttTopic == "model" { + stat.MQTTDataModel = string(msg.Payload()) + } else if MqttTopic == "trim_badging" { + stat.MQTTDataTrimBadging = string(msg.Payload()) + } else if MqttTopic == "exterior_color" { + stat.MQTTDataExteriorColor = string(msg.Payload()) + } else if MqttTopic == "wheel_type" { + stat.MQTTDataWheelType = string(msg.Payload()) + } else if MqttTopic == "spoiler_type" { + stat.MQTTDataSpoilerType = string(msg.Payload()) + } else if MqttTopic == "geofence" { + stat.MQTTDataGeofence = string(msg.Payload()) + } else if MqttTopic == "latitude" { + stat.MQTTDataLatitude = convertStringToFloat(string(msg.Payload())) + } else if MqttTopic == "longitude" { + stat.MQTTDataLongitude = convertStringToFloat(string(msg.Payload())) + } else if MqttTopic == "shift_state" { + stat.MQTTDataShiftState = string(msg.Payload()) + } else if MqttTopic == "speed" { + stat.MQTTDataSpeed = convertStringToInteger(string(msg.Payload())) + } else if MqttTopic == "heading" { + stat.MQTTDataHeading = convertStringToInteger(string(msg.Payload())) + } else if MqttTopic == "elevation" { + stat.MQTTDataElevation = convertStringToInteger(string(msg.Payload())) + } else if MqttTopic == "locked" { + stat.MQTTDataLocked = convertStringToBool(string(msg.Payload())) + } else if MqttTopic == "sentry_mode" { + stat.MQTTDataSentryMode = convertStringToBool(string(msg.Payload())) + } else if MqttTopic == "windows_open" { + stat.MQTTDataWindowsOpen = convertStringToBool(string(msg.Payload())) + } else if MqttTopic == "doors_open" { + stat.MQTTDataDoorsOpen = convertStringToBool(string(msg.Payload())) + } else if MqttTopic == "trunk_open" { + stat.MQTTDataTrunkOpen = convertStringToBool(string(msg.Payload())) + } else if MqttTopic == "frunk_open" { + stat.MQTTDataFrunkOpen = convertStringToBool(string(msg.Payload())) + } else if MqttTopic == "is_user_present" { + stat.MQTTDataIsUserPresent = convertStringToBool(string(msg.Payload())) + } else if MqttTopic == "is_climate_on" { + stat.MQTTDataIsClimateOn = convertStringToBool(string(msg.Payload())) + } else if MqttTopic == "inside_temp" { + stat.MQTTDataInsideTemp = convertStringToFloat(string(msg.Payload())) + } else if MqttTopic == "outside_temp" { + stat.MQTTDataOutsideTemp = convertStringToFloat(string(msg.Payload())) + } else if MqttTopic == "is_preconditioning" { + stat.MQTTDataIsPreconditioning = convertStringToBool(string(msg.Payload())) + } else if MqttTopic == "odometer" { + stat.MQTTDataOdometer = convertStringToFloat(string(msg.Payload())) + } else if MqttTopic == "est_battery_range_km" { + stat.MQTTDataEstBatteryRange = convertStringToFloat(string(msg.Payload())) + } else if MqttTopic == "rated_battery_range_km" { + stat.MQTTDataRatedBatteryRange = convertStringToFloat(string(msg.Payload())) + } else if MqttTopic == "ideal_battery_range_km" { + stat.MQTTDataIdealBatteryRange = convertStringToFloat(string(msg.Payload())) + } else if MqttTopic == "battery_level" { + stat.MQTTDataBatteryLevel = convertStringToInteger(string(msg.Payload())) + } else if MqttTopic == "usable_battery_level" { + stat.MQTTDataUsableBatteryLevel = convertStringToInteger(string(msg.Payload())) + } else if MqttTopic == "plugged_in" { + stat.MQTTDataPluggedIn = convertStringToBool(string(msg.Payload())) + } else if MqttTopic == "charge_energy_added" { + stat.MQTTDataChargeEnergyAdded = convertStringToFloat(string(msg.Payload())) + } else if MqttTopic == "charge_limit_soc" { + stat.MQTTDataChargeLimitSoc = convertStringToInteger(string(msg.Payload())) + } else if MqttTopic == "charge_port_door_open" { + stat.MQTTDataChargePortDoorOpen = convertStringToBool(string(msg.Payload())) + } else if MqttTopic == "charger_actual_current" { + stat.MQTTDataChargerActualCurrent = convertStringToFloat(string(msg.Payload())) + } else if MqttTopic == "charger_phases" { + stat.MQTTDataChargerPhases = convertStringToInteger(string(msg.Payload())) + } else if MqttTopic == "charger_power" { + stat.MQTTDataChargerPower = convertStringToFloat(string(msg.Payload())) + } else if MqttTopic == "charger_voltage" { + stat.MQTTDataChargerVoltage = convertStringToInteger(string(msg.Payload())) + } else if MqttTopic == "scheduled_charging_start_time" { + stat.MQTTDataScheduledChargingStartTime = string(msg.Payload()) + } else if MqttTopic == "time_to_full_charge" { + stat.MQTTDataTimeToFullCharge = convertStringToFloat(string(msg.Payload())) + } else { + log.Printf("[warning] mqtt.MessageHandler issue.. extraction of data for %s not implemented!", MqttTopic) + return + } +} + +// TeslaMateAPICarsStatusV1 func +func (s *statusCache) TeslaMateAPICarsStatusV1(c *gin.Context) { + if s.mqttDisabled { + log.Println("[notice] TeslaMateAPICarsStatusV1 DISABLE_MQTT is set to true.. can not return status for car without mqtt!") + c.JSON(http.StatusNotImplemented, gin.H{"error": "mqtt disabled.. status not accessible!"}) + return + } + + // getting CarID param from URL + carID := convertStringToInteger(c.Param("CarID")) + + // Now see what data we have on the car + s.mu.Lock() + stat := s.cache[carID] + s.mu.Unlock() + + if stat == nil { + c.JSON(http.StatusNoContent, gin.H{"error": "no info on this car ID"}) + return + } + // creating structs for /cars // BatteryDetails struct - child of MQTTInformation type BatteryDetails struct { @@ -359,7 +413,7 @@ func TeslaMateAPICarsStatusV1(c *gin.Context) { var UnitsLength, UnitsTemperature string var ValidResponse bool // default is false - // getting data from database + // getting data from database (assume that carID is unique!) query := ` SELECT id, @@ -369,171 +423,84 @@ func TeslaMateAPICarsStatusV1(c *gin.Context) { FROM cars WHERE id=$1 LIMIT 1;` - rows, err := db.Query(query, CarID) + err := db.QueryRow(query, carID).Scan(&CarData.CarID, + &CarData.CarName, + &UnitsLength, + &UnitsTemperature) - // checking for errors in query + // checking for errors in query (this will include no rows found) if err != nil { log.Fatal(err) } - // defer closing rows - defer rows.Close() + // setting data from MQTT into data fields to return + MQTTInformationData.DisplayName = stat.MQTTDataDisplayName + MQTTInformationData.State = stat.MQTTDataState + MQTTInformationData.StateSince = stat.MQTTDataStateSince + MQTTInformationData.CarStatus.Healthy = stat.MQTTDataHealthy + MQTTInformationData.CarVersions.Version = stat.MQTTDataVersion + MQTTInformationData.CarVersions.UpdateAvailable = stat.MQTTDataUpdateAvailable + MQTTInformationData.CarVersions.UpdateVersion = stat.MQTTDataUpdateVersion + MQTTInformationData.CarDetails.Model = stat.MQTTDataModel + MQTTInformationData.CarDetails.TrimBadging = stat.MQTTDataTrimBadging + MQTTInformationData.CarExterior.ExteriorColor = stat.MQTTDataExteriorColor + MQTTInformationData.CarExterior.WheelType = stat.MQTTDataWheelType + MQTTInformationData.CarExterior.SpoilerType = stat.MQTTDataSpoilerType + MQTTInformationData.CarGeodata.Geofence = stat.MQTTDataGeofence + MQTTInformationData.CarGeodata.Latitude = stat.MQTTDataLatitude + MQTTInformationData.CarGeodata.Longitude = stat.MQTTDataLongitude + MQTTInformationData.DrivingDetails.ShiftState = stat.MQTTDataShiftState + MQTTInformationData.DrivingDetails.Speed = stat.MQTTDataSpeed + MQTTInformationData.DrivingDetails.Heading = stat.MQTTDataHeading + MQTTInformationData.DrivingDetails.Elevation = stat.MQTTDataElevation + MQTTInformationData.CarStatus.Locked = stat.MQTTDataLocked + MQTTInformationData.CarStatus.SentryMode = stat.MQTTDataSentryMode + MQTTInformationData.CarStatus.WindowsOpen = stat.MQTTDataWindowsOpen + MQTTInformationData.CarStatus.DoorsOpen = stat.MQTTDataDoorsOpen + MQTTInformationData.CarStatus.TrunkOpen = stat.MQTTDataTrunkOpen + MQTTInformationData.CarStatus.FrunkOpen = stat.MQTTDataFrunkOpen + MQTTInformationData.CarStatus.IsUserPresent = stat.MQTTDataIsUserPresent + MQTTInformationData.ClimateDetails.IsClimateOn = stat.MQTTDataIsClimateOn + MQTTInformationData.ClimateDetails.InsideTemp = stat.MQTTDataInsideTemp + MQTTInformationData.ClimateDetails.OutsideTemp = stat.MQTTDataOutsideTemp + MQTTInformationData.ClimateDetails.IsPreconditioning = stat.MQTTDataIsPreconditioning + MQTTInformationData.Odometer = stat.MQTTDataOdometer + MQTTInformationData.BatteryDetails.EstBatteryRange = stat.MQTTDataEstBatteryRange + MQTTInformationData.BatteryDetails.RatedBatteryRange = stat.MQTTDataRatedBatteryRange + MQTTInformationData.BatteryDetails.IdealBatteryRange = stat.MQTTDataIdealBatteryRange + MQTTInformationData.BatteryDetails.BatteryLevel = stat.MQTTDataBatteryLevel + MQTTInformationData.BatteryDetails.UsableBatteryLevel = stat.MQTTDataUsableBatteryLevel + MQTTInformationData.ChargingDetails.PluggedIn = stat.MQTTDataPluggedIn + MQTTInformationData.ChargingDetails.ChargeEnergyAdded = stat.MQTTDataChargeEnergyAdded + MQTTInformationData.ChargingDetails.ChargeLimitSoc = stat.MQTTDataChargeLimitSoc + MQTTInformationData.ChargingDetails.ChargePortDoorOpen = stat.MQTTDataChargePortDoorOpen + MQTTInformationData.ChargingDetails.ChargerActualCurrent = stat.MQTTDataChargerActualCurrent + MQTTInformationData.ChargingDetails.ChargerPhases = stat.MQTTDataChargerPhases + MQTTInformationData.ChargingDetails.ChargerPower = stat.MQTTDataChargerPower + MQTTInformationData.ChargingDetails.ChargerVoltage = stat.MQTTDataChargerVoltage + MQTTInformationData.ChargingDetails.ScheduledChargingStartTime = stat.MQTTDataScheduledChargingStartTime + MQTTInformationData.ChargingDetails.TimeToFullCharge = stat.MQTTDataTimeToFullCharge - // looping through all results - for rows.Next() { - - // scanning row and putting values into the car - err = rows.Scan( - &CarData.CarID, - &CarData.CarName, - &UnitsLength, - &UnitsTemperature, - ) - - // checking for errors after scanning - if err != nil { - log.Fatal(err) - } - - if CarID != 0 && CarID == CarData.CarID || CarID == 0 { - - // creating list of all topics to subscribe on.. - MQTTAllTopics := map[string]byte{ - fmt.Sprintf("teslamate%s/cars/%d/display_name", MQTTNameSpace, CarID): 0, - fmt.Sprintf("teslamate%s/cars/%d/state", MQTTNameSpace, CarID): 0, - fmt.Sprintf("teslamate%s/cars/%d/since", MQTTNameSpace, CarID): 0, - fmt.Sprintf("teslamate%s/cars/%d/healthy", MQTTNameSpace, CarID): 0, - fmt.Sprintf("teslamate%s/cars/%d/version", MQTTNameSpace, CarID): 0, - fmt.Sprintf("teslamate%s/cars/%d/update_available", MQTTNameSpace, CarID): 0, - fmt.Sprintf("teslamate%s/cars/%d/update_version", MQTTNameSpace, CarID): 0, - fmt.Sprintf("teslamate%s/cars/%d/model", MQTTNameSpace, CarID): 0, - fmt.Sprintf("teslamate%s/cars/%d/trim_badging", MQTTNameSpace, CarID): 0, - fmt.Sprintf("teslamate%s/cars/%d/exterior_color", MQTTNameSpace, CarID): 0, - fmt.Sprintf("teslamate%s/cars/%d/wheel_type", MQTTNameSpace, CarID): 0, - fmt.Sprintf("teslamate%s/cars/%d/spoiler_type", MQTTNameSpace, CarID): 0, - fmt.Sprintf("teslamate%s/cars/%d/geofence", MQTTNameSpace, CarID): 0, - fmt.Sprintf("teslamate%s/cars/%d/latitude", MQTTNameSpace, CarID): 0, - fmt.Sprintf("teslamate%s/cars/%d/longitude", MQTTNameSpace, CarID): 0, - fmt.Sprintf("teslamate%s/cars/%d/shift_state", MQTTNameSpace, CarID): 0, - fmt.Sprintf("teslamate%s/cars/%d/speed", MQTTNameSpace, CarID): 0, - fmt.Sprintf("teslamate%s/cars/%d/heading", MQTTNameSpace, CarID): 0, - fmt.Sprintf("teslamate%s/cars/%d/elevation", MQTTNameSpace, CarID): 0, - fmt.Sprintf("teslamate%s/cars/%d/locked", MQTTNameSpace, CarID): 0, - fmt.Sprintf("teslamate%s/cars/%d/sentry_mode", MQTTNameSpace, CarID): 0, - fmt.Sprintf("teslamate%s/cars/%d/windows_open", MQTTNameSpace, CarID): 0, - fmt.Sprintf("teslamate%s/cars/%d/doors_open", MQTTNameSpace, CarID): 0, - fmt.Sprintf("teslamate%s/cars/%d/trunk_open", MQTTNameSpace, CarID): 0, - fmt.Sprintf("teslamate%s/cars/%d/frunk_open", MQTTNameSpace, CarID): 0, - fmt.Sprintf("teslamate%s/cars/%d/is_user_present", MQTTNameSpace, CarID): 0, - fmt.Sprintf("teslamate%s/cars/%d/is_climate_on", MQTTNameSpace, CarID): 0, - fmt.Sprintf("teslamate%s/cars/%d/inside_temp", MQTTNameSpace, CarID): 0, - fmt.Sprintf("teslamate%s/cars/%d/outside_temp", MQTTNameSpace, CarID): 0, - fmt.Sprintf("teslamate%s/cars/%d/is_preconditioning", MQTTNameSpace, CarID): 0, - fmt.Sprintf("teslamate%s/cars/%d/odometer", MQTTNameSpace, CarID): 0, - fmt.Sprintf("teslamate%s/cars/%d/est_battery_range_km", MQTTNameSpace, CarID): 0, - fmt.Sprintf("teslamate%s/cars/%d/rated_battery_range_km", MQTTNameSpace, CarID): 0, - fmt.Sprintf("teslamate%s/cars/%d/ideal_battery_range_km", MQTTNameSpace, CarID): 0, - fmt.Sprintf("teslamate%s/cars/%d/battery_level", MQTTNameSpace, CarID): 0, - fmt.Sprintf("teslamate%s/cars/%d/usable_battery_level", MQTTNameSpace, CarID): 0, - fmt.Sprintf("teslamate%s/cars/%d/plugged_in", MQTTNameSpace, CarID): 0, - fmt.Sprintf("teslamate%s/cars/%d/charge_energy_added", MQTTNameSpace, CarID): 0, - fmt.Sprintf("teslamate%s/cars/%d/charge_limit_soc", MQTTNameSpace, CarID): 0, - fmt.Sprintf("teslamate%s/cars/%d/charge_port_door_open", MQTTNameSpace, CarID): 0, - fmt.Sprintf("teslamate%s/cars/%d/charger_actual_current", MQTTNameSpace, CarID): 0, - fmt.Sprintf("teslamate%s/cars/%d/charger_phases", MQTTNameSpace, CarID): 0, - fmt.Sprintf("teslamate%s/cars/%d/charger_power", MQTTNameSpace, CarID): 0, - fmt.Sprintf("teslamate%s/cars/%d/charger_voltage", MQTTNameSpace, CarID): 0, - fmt.Sprintf("teslamate%s/cars/%d/scheduled_charging_start_time", MQTTNameSpace, CarID): 0, - fmt.Sprintf("teslamate%s/cars/%d/time_to_full_charge", MQTTNameSpace, CarID): 0, - } - - // run SubscribeMultiple on MQTTAllTopics map[string]byte - m.SubscribeMultiple(MQTTAllTopics, nil) - - // getting sleep tim before disconnecting - MQTTSleepTime := getEnvAsInt("MQTT_SLEEPTIME", 100) - // adding some short sleep before disconnecting (based on MQTTSleepTimer) - time.Sleep(time.Duration(MQTTSleepTime) * time.Millisecond) - - // disconnecting from MQTT - m.Disconnect(250) - - // setting data from MQTT into data fields to return - MQTTInformationData.DisplayName = MQTTDataDisplayName - MQTTInformationData.State = MQTTDataState - MQTTInformationData.StateSince = MQTTDataStateSince - MQTTInformationData.CarStatus.Healthy = MQTTDataHealthy - MQTTInformationData.CarVersions.Version = MQTTDataVersion - MQTTInformationData.CarVersions.UpdateAvailable = MQTTDataUpdateAvailable - MQTTInformationData.CarVersions.UpdateVersion = MQTTDataUpdateVersion - MQTTInformationData.CarDetails.Model = MQTTDataModel - MQTTInformationData.CarDetails.TrimBadging = MQTTDataTrimBadging - MQTTInformationData.CarExterior.ExteriorColor = MQTTDataExteriorColor - MQTTInformationData.CarExterior.WheelType = MQTTDataWheelType - MQTTInformationData.CarExterior.SpoilerType = MQTTDataSpoilerType - MQTTInformationData.CarGeodata.Geofence = MQTTDataGeofence - MQTTInformationData.CarGeodata.Latitude = MQTTDataLatitude - MQTTInformationData.CarGeodata.Longitude = MQTTDataLongitude - MQTTInformationData.DrivingDetails.ShiftState = MQTTDataShiftState - MQTTInformationData.DrivingDetails.Speed = MQTTDataSpeed - MQTTInformationData.DrivingDetails.Heading = MQTTDataHeading - MQTTInformationData.DrivingDetails.Elevation = MQTTDataElevation - MQTTInformationData.CarStatus.Locked = MQTTDataLocked - MQTTInformationData.CarStatus.SentryMode = MQTTDataSentryMode - MQTTInformationData.CarStatus.WindowsOpen = MQTTDataWindowsOpen - MQTTInformationData.CarStatus.DoorsOpen = MQTTDataDoorsOpen - MQTTInformationData.CarStatus.TrunkOpen = MQTTDataTrunkOpen - MQTTInformationData.CarStatus.FrunkOpen = MQTTDataFrunkOpen - MQTTInformationData.CarStatus.IsUserPresent = MQTTDataIsUserPresent - MQTTInformationData.ClimateDetails.IsClimateOn = MQTTDataIsClimateOn - MQTTInformationData.ClimateDetails.InsideTemp = MQTTDataInsideTemp - MQTTInformationData.ClimateDetails.OutsideTemp = MQTTDataOutsideTemp - MQTTInformationData.ClimateDetails.IsPreconditioning = MQTTDataIsPreconditioning - MQTTInformationData.Odometer = MQTTDataOdometer - MQTTInformationData.BatteryDetails.EstBatteryRange = MQTTDataEstBatteryRange - MQTTInformationData.BatteryDetails.RatedBatteryRange = MQTTDataRatedBatteryRange - MQTTInformationData.BatteryDetails.IdealBatteryRange = MQTTDataIdealBatteryRange - MQTTInformationData.BatteryDetails.BatteryLevel = MQTTDataBatteryLevel - MQTTInformationData.BatteryDetails.UsableBatteryLevel = MQTTDataUsableBatteryLevel - MQTTInformationData.ChargingDetails.PluggedIn = MQTTDataPluggedIn - MQTTInformationData.ChargingDetails.ChargeEnergyAdded = MQTTDataChargeEnergyAdded - MQTTInformationData.ChargingDetails.ChargeLimitSoc = MQTTDataChargeLimitSoc - MQTTInformationData.ChargingDetails.ChargePortDoorOpen = MQTTDataChargePortDoorOpen - MQTTInformationData.ChargingDetails.ChargerActualCurrent = MQTTDataChargerActualCurrent - MQTTInformationData.ChargingDetails.ChargerPhases = MQTTDataChargerPhases - MQTTInformationData.ChargingDetails.ChargerPower = MQTTDataChargerPower - MQTTInformationData.ChargingDetails.ChargerVoltage = MQTTDataChargerVoltage - MQTTInformationData.ChargingDetails.ScheduledChargingStartTime = MQTTDataScheduledChargingStartTime - MQTTInformationData.ChargingDetails.TimeToFullCharge = MQTTDataTimeToFullCharge - - // converting values based of settings UnitsLength - if UnitsLength == "mi" { - // drive.OdometerDetails.OdometerStart = kilometersToMiles(drive.OdometerDetails.OdometerStart) - MQTTInformationData.Odometer = kilometersToMiles(MQTTInformationData.Odometer) - MQTTInformationData.BatteryDetails.EstBatteryRange = kilometersToMiles(MQTTInformationData.BatteryDetails.EstBatteryRange) - MQTTInformationData.BatteryDetails.RatedBatteryRange = kilometersToMiles(MQTTInformationData.BatteryDetails.RatedBatteryRange) - MQTTInformationData.BatteryDetails.IdealBatteryRange = kilometersToMiles(MQTTInformationData.BatteryDetails.IdealBatteryRange) - } - // converting values based of settings UnitsTemperature - if UnitsTemperature == "F" { - MQTTInformationData.ClimateDetails.InsideTemp = celsiusToFahrenheit(MQTTInformationData.ClimateDetails.InsideTemp) - MQTTInformationData.ClimateDetails.OutsideTemp = celsiusToFahrenheit(MQTTInformationData.ClimateDetails.OutsideTemp) - } - - // adjusting to timezone differences from UTC to be userspecific - MQTTInformationData.StateSince = getTimeInTimeZone(MQTTInformationData.StateSince) - MQTTInformationData.ChargingDetails.ScheduledChargingStartTime = getTimeInTimeZone(MQTTInformationData.ChargingDetails.ScheduledChargingStartTime) - - // setting response as valid - ValidResponse = true - } + // converting values based of settings UnitsLength + if UnitsLength == "mi" { + // drive.OdometerDetails.OdometerStart = kilometersToMiles(drive.OdometerDetails.OdometerStart) + MQTTInformationData.Odometer = kilometersToMiles(MQTTInformationData.Odometer) + MQTTInformationData.BatteryDetails.EstBatteryRange = kilometersToMiles(MQTTInformationData.BatteryDetails.EstBatteryRange) + MQTTInformationData.BatteryDetails.RatedBatteryRange = kilometersToMiles(MQTTInformationData.BatteryDetails.RatedBatteryRange) + MQTTInformationData.BatteryDetails.IdealBatteryRange = kilometersToMiles(MQTTInformationData.BatteryDetails.IdealBatteryRange) + } + // converting values based of settings UnitsTemperature + if UnitsTemperature == "F" { + MQTTInformationData.ClimateDetails.InsideTemp = celsiusToFahrenheit(MQTTInformationData.ClimateDetails.InsideTemp) + MQTTInformationData.ClimateDetails.OutsideTemp = celsiusToFahrenheit(MQTTInformationData.ClimateDetails.OutsideTemp) } - // checking for errors in the rows result - err = rows.Err() - if err != nil { - log.Fatal(err) - } + // adjusting to timezone differences from UTC to be userspecific + MQTTInformationData.StateSince = getTimeInTimeZone(MQTTInformationData.StateSince) + MQTTInformationData.ChargingDetails.ScheduledChargingStartTime = getTimeInTimeZone(MQTTInformationData.ChargingDetails.ScheduledChargingStartTime) + + // setting response as valid + ValidResponse = true // // build the data-blob diff --git a/src/webserver.go b/src/webserver.go index ba09fa9..2fe2416 100644 --- a/src/webserver.go +++ b/src/webserver.go @@ -38,6 +38,12 @@ func main() { initDBconnection() defer db.Close() + // Connect to the MQTT broker + statusCache, err := startMQTT() + if err != nil { + log.Fatalf("MQTT connection failed: %s", err) + } + // kicking off Gin in value r r := gin.Default() @@ -68,7 +74,7 @@ func main() { v1.GET("/cars/:CarID/charges/:ChargeID", TeslaMateAPICarsChargesDetailsV1) v1.GET("/cars/:CarID/drives", TeslaMateAPICarsDrivesV1) v1.GET("/cars/:CarID/drives/:DriveID", TeslaMateAPICarsDrivesDetailsV1) - v1.GET("/cars/:CarID/status", TeslaMateAPICarsStatusV1) + v1.GET("/cars/:CarID/status", statusCache.TeslaMateAPICarsStatusV1) v1.GET("/cars/:CarID/updates", TeslaMateAPICarsUpdatesV1) v1.GET("/globalsettings", TeslaMateAPIGlobalsettingsV1) }