package main import ( "flag" "fmt" "os" "strconv" "strings" "time" "github.com/bogosj/tesla" "github.com/therecipe/qt/widgets" ) var ( // Info/Statuses batteryLevel *widgets.QLabel batteryRange *widgets.QLabel chargingState *widgets.QLabel minutesToFull *widgets.QLabel fastChargerInd *widgets.QLabel batteryHeaterInd *widgets.QLabel chargeDoorOpenInd *widgets.QLabel insideTemp *widgets.QLabel outsideTemp *widgets.QLabel climateUnitLabel *widgets.QLabel // Controls tempSetting *widgets.QLineEdit climateOn *widgets.QCheckBox lockedDoors *widgets.QCheckBox sentryMode *widgets.QCheckBox startStopCharge *widgets.QCheckBox honk *widgets.QPushButton flashLights *widgets.QPushButton trunk *widgets.QPushButton frunk *widgets.QPushButton vehicle *tesla.Vehicle vehicleState *tesla.VehicleState chargeStats *tesla.ChargeState climateState *tesla.ClimateState guiSettings *tesla.GuiSettings window *widgets.QMainWindow mainApp *widgets.QApplication vehicleSearch string refresh bool popup = false ) func init() { flag.StringVar(&vehicleSearch, "v", "", "Vehicle Identifier") flag.BoolVar(&refresh, "r", false, "Auto-refresh (every minute)") flag.Parse() } func main() { mainApp = widgets.NewQApplication(len(os.Args), os.Args) window = widgets.NewQMainWindow(nil, 0) c := getTeslaClient() vehicles, err := c.Vehicles() if err != nil { showDialogue(false, "Unable to get vehicles.\n%+v", err) return } if len(vehicles) == 0 { showDialogue(false, "No vehicles to show.") return } else if len(vehicles) > 1 && vehicleSearch == "" { showDialogue(false, "Unable to determine vehicle.") } // Setup all UI Elements window.SetWindowTitle("Loading, please wait!") batteryLevel = widgets.NewQLabel(nil, 0) batteryRange = widgets.NewQLabel(nil, 0) chargingState = widgets.NewQLabel(nil, 0) minutesToFull = widgets.NewQLabel(nil, 0) fastChargerInd = widgets.NewQLabel(nil, 0) batteryHeaterInd = widgets.NewQLabel(nil, 0) chargeDoorOpenInd = widgets.NewQLabel(nil, 0) insideTemp = widgets.NewQLabel(nil, 0) outsideTemp = widgets.NewQLabel(nil, 0) currentChargeLabel := widgets.NewQLabel(nil, 0) currentRangeLabel := widgets.NewQLabel(nil, 0) insideTempLabel := widgets.NewQLabel(nil, 0) outsideTempLabel := widgets.NewQLabel(nil, 0) climateEnabledLabel := widgets.NewQLabel(nil, 0) climateSettingLabel := widgets.NewQLabel(nil, 0) doorLockLabel := widgets.NewQLabel(nil, 0) sentryModeLabel := widgets.NewQLabel(nil, 0) chargingStateLabel := widgets.NewQLabel(nil, 0) climateUnitLabel = widgets.NewQLabel(nil, 0) tempSetting = widgets.NewQLineEdit(nil) climateOn = widgets.NewQCheckBox(nil) lockedDoors = widgets.NewQCheckBox(nil) sentryMode = widgets.NewQCheckBox(nil) startStopCharge = widgets.NewQCheckBox(nil) honk = widgets.NewQPushButton(nil) flashLights = widgets.NewQPushButton(nil) trunk = widgets.NewQPushButton(nil) frunk = widgets.NewQPushButton(nil) statusLayout := widgets.NewQFormLayout(nil) chargeHbox := widgets.NewQHBoxLayout() tempHbox := widgets.NewQHBoxLayout() climateHbox := widgets.NewQHBoxLayout() securityHbox := widgets.NewQHBoxLayout() actionHbox := widgets.NewQHBoxLayout() centralWidget := widgets.NewQWidget(window, 0) // Set Values for everything go setValues() if refresh { go func() { time.Sleep(1 * time.Minute) setValues() }() } // Some adjustments lockedDoors.SetCheckable(false) climateOn.SetCheckable(false) tempSetting.SetReadOnly(true) batteryLevel.SetFixedWidth(30) insideTemp.SetFixedWidth(25) outsideTemp.SetFixedWidth(25) tempSetting.SetFixedWidth(25) currentChargeLabel.SetText("Current Charge:") currentRangeLabel.SetText("Current Range:") insideTempLabel.SetText("Inside Temp:") outsideTempLabel.SetText("Outside Temp:") climateEnabledLabel.SetText("Climate On:") climateSettingLabel.SetText("Climate Setting:") doorLockLabel.SetText("Lock Doors:") sentryModeLabel.SetText("Sentry Mode:") chargingStateLabel.SetText("Charging:") honk.SetText("Honk") flashLights.SetText("Flash") trunk.SetText("Trunk") frunk.SetText("Frunk") // Connect Controls honk.ConnectClicked(honkHorn) flashLights.ConnectClicked(flash) trunk.ConnectClicked(openTrunk) frunk.ConnectClicked(openFrunk) lockedDoors.ConnectStateChanged(lockDoors) sentryMode.ConnectStateChanged(sentryModeEnable) startStopCharge.ConnectStateChanged(enableCharging) climateOn.ConnectStateChanged(enableClimate) // Setup Layout for first row, Current Charge chargeHbox.AddWidget(batteryLevel, 0, 0) chargeHbox.AddItem(widgets.NewQSpacerItem(5, 2, widgets.QSizePolicy__Expanding, widgets.QSizePolicy__Expanding)) chargeHbox.AddWidget(currentRangeLabel, 0, 0) chargeHbox.AddWidget(batteryRange, 0, 0) // Charging State has its own section and is handled differently based on if it is present or not if chargeStats != nil && chargeStats.ChargingState != "Disconnected" { statusLayout.AddRow3("Minutes to Full:", minutesToFull) if chargeStats.FastChargerPresent { statusLayout.AddRow3("Fast Charger:", fastChargerInd) } if chargeStats.BatteryHeaterOn { statusLayout.AddRow3("Battey Heater:", batteryHeaterInd) } statusLayout.AddRow3(" ", nil) } // Temperature section (NOT CLIMATE CONTROL) tempHbox.AddWidget(insideTemp, 0, 0) tempHbox.AddItem(widgets.NewQSpacerItem(10, 10, widgets.QSizePolicy__Fixed, widgets.QSizePolicy__Fixed)) tempHbox.AddWidget(outsideTempLabel, 0, 0) tempHbox.AddWidget(outsideTemp, 0, 0) // Climate Control Section climateHbox.AddWidget(climateOn, 0, 0) climateHbox.AddItem(widgets.NewQSpacerItem(10, 10, widgets.QSizePolicy__Fixed, widgets.QSizePolicy__Fixed)) climateHbox.AddWidget(climateSettingLabel, 0, 0) climateHbox.AddWidget(tempSetting, 0, 0) climateHbox.AddWidget(climateUnitLabel, 0, 0) // Security Section (Lock/Unlock doors & start/stop charge. Also enable Sentry Mode) securityHbox.AddWidget(lockedDoors, 0, 0) securityHbox.AddItem(widgets.NewQSpacerItem(10, 10, widgets.QSizePolicy__Fixed, widgets.QSizePolicy__Fixed)) securityHbox.AddWidget(sentryModeLabel, 0, 0) securityHbox.AddWidget(sentryMode, 0, 0) if chargeStats != nil && chargeStats.ChargingState != "Disconnected" { securityHbox.AddItem(widgets.NewQSpacerItem(10, 10, widgets.QSizePolicy__Fixed, widgets.QSizePolicy__Fixed)) securityHbox.AddWidget(chargingStateLabel, 0, 0) securityHbox.AddWidget(startStopCharge, 0, 0) } // Action Buttons actionHbox.AddWidget(honk, 0, 0) actionHbox.AddItem(widgets.NewQSpacerItem(2, 2, widgets.QSizePolicy__Fixed, widgets.QSizePolicy__Fixed)) actionHbox.AddWidget(flashLights, 0, 0) actionHbox.AddItem(widgets.NewQSpacerItem(2, 2, widgets.QSizePolicy__Fixed, widgets.QSizePolicy__Fixed)) actionHbox.AddWidget(trunk, 0, 0) actionHbox.AddItem(widgets.NewQSpacerItem(2, 2, widgets.QSizePolicy__Fixed, widgets.QSizePolicy__Fixed)) actionHbox.AddWidget(frunk, 0, 0) actionHbox.AddItem(widgets.NewQSpacerItem(2, 2, widgets.QSizePolicy__Fixed, widgets.QSizePolicy__Fixed)) // Put all Sections Together, note ChargingState (top) is already handled statusLayout.AddRow2(currentChargeLabel, chargeHbox) statusLayout.AddRow3("Charging State:", chargingState) statusLayout.AddRow3("Charge Port:", chargeDoorOpenInd) statusLayout.AddRow3(" ", nil) statusLayout.AddRow2(insideTempLabel, tempHbox) statusLayout.AddRow2(climateEnabledLabel, climateHbox) statusLayout.AddRow3(" ", nil) statusLayout.AddRow2(doorLockLabel, securityHbox) statusLayout.AddRow6(actionHbox) // Finish setting up the window, and let her go centralWidget.SetLayout(statusLayout) window.SetCentralWidget(centralWidget) if !popup { window.Show() } widgets.QApplication_Exec() } func setValues() { vehicle = getVehicle(vehicleSearch) var err error if vehicle == nil { showDialogue(false, "Unable to get vehicle") return } vehicleState, err = vehicle.VehicleState() if err != nil { showDialogue(false, "Unable to get Vehicle State") } chargeStats, err = vehicle.ChargeState() if err != nil { showDialogue(false, "Unable to get Vehicle Charge State") } climateState, err = vehicle.ClimateState() if err != nil { showDialogue(false, "Unable to get Vehicle Climate") } guiSettings, err = vehicle.GuiSettings() if err != nil { showDialogue(false, "Unable to get Gui Settings") } window.SetWindowTitle(fmt.Sprintf("%+v: %+v", vehicle.DisplayName, vehicle.Vin)) tempSettingVal := climateState.DriverTempSetting insideTempVal := climateState.InsideTemp outsideTempVal := climateState.OutsideTemp if guiSettings.GuiTemperatureUnits == "F" { tempSettingVal = (climateState.DriverTempSetting * 1.8) + 32 insideTempVal = (climateState.InsideTemp * 1.8) + 32 outsideTempVal = (climateState.OutsideTemp * 1.8) + 32 } batteryLevel.SetText(fmt.Sprintf("%+v%%", chargeStats.BatteryLevel)) batteryRange.SetText(fmt.Sprintf("%.2f%+v", chargeStats.BatteryRange, strings.Replace(guiSettings.GuiDistanceUnits, "/hr", "", -1))) batteryRange.SetFixedWidth(10 * len(batteryRange.Text())) chargingState.SetText(chargeStats.ChargingState) minutesToFull.SetText(fmt.Sprintf("%+v", chargeStats.MinutesToFullCharge)) fastChargerInd.SetText(chargeStats.FastChargerBrand) if chargeStats.BatteryHeaterOn { batteryHeaterInd.SetText("On") } if chargeStats.ChargePortDoorOpen { chargeDoorOpenInd.SetText("Open") } else { chargeDoorOpenInd.SetText("Closed") } insideTemp.SetText(fmt.Sprintf("%.0f %+v", insideTempVal, guiSettings.GuiTemperatureUnits)) outsideTemp.SetText(fmt.Sprintf("%.0f %+v", outsideTempVal, guiSettings.GuiTemperatureUnits)) climateOn.SetChecked(climateState.IsClimateOn) tempSetting.SetText(fmt.Sprintf("%.0f", tempSettingVal)) climateUnitLabel.SetText(guiSettings.GuiTemperatureUnits) lockedDoors.SetChecked(vehicleState.Locked) sentryMode.SetChecked(vehicleState.SentryMode) sentryMode.SetCheckable(!vehicleState.SentryMode) lockedDoors.SetCheckable(true) climateOn.SetCheckable(true) tempSetting.SetReadOnly(false) startStopCharge.SetChecked(chargeStats.ChargingState == "Charging") if chargeStats.ChargingState == "Disconnected" { startStopCharge.SetCheckable(false) } } func enableClimate(i int) { temp, err := strconv.ParseFloat(tempSetting.Text(), 64) if err != nil { showDialogue(true, "Unable to parse temp setting\n%+v", err) } if guiSettings.GuiTemperatureUnits == "F" { temp = (temp - 32) * 5 / 9 } if i == 0 { vehicle.StopAirConditioning() } else { vehicle.SetTemperature(temp, temp) vehicle.StartAirConditioning() } go setValues() } func lockDoors(i int) { if i == 0 { vehicle.UnlockDoors() } else { vehicle.LockDoors() } go setValues() } func sentryModeEnable(i int) { vehicle.EnableSentry() go setValues() } func enableCharging(i int) { if i == 0 { vehicle.StopCharging() } else { vehicle.StartCharging() } go setValues() } func honkHorn(c bool) { err := vehicle.HonkHorn() if err != nil { showDialogue(true, "There was an error honking the horn\n%+v", err) fmt.Printf("%+v\n", err) } go setValues() } func flash(c bool) { err := vehicle.FlashLights() if err != nil { showDialogue(true, "There was an error flashing the lights\n%+v", err) fmt.Printf("%+v\n", err) } go setValues() } func openTrunk(c bool) { err := vehicle.OpenTrunk("rear") if err != nil { showDialogue(true, "There was an error opening your trunk\n%+v", err) fmt.Printf("%+v\n", err) } go setValues() } func openFrunk(c bool) { err := vehicle.OpenTrunk("front") if err != nil { showDialogue(true, "There was an error opening your frunk\n%+v", err) fmt.Printf("%+v\n", err) } go setValues() } func showDialogue(recover bool, msg string, a ...interface{}) { popup = true if !recover { window.Close() } dialogue := widgets.NewQDialog(nil, 0) centralWidget := widgets.NewQWidget(dialogue, 0) actionHBox := widgets.NewQHBoxLayout() formLayout := widgets.NewQFormLayout(nil) contBtn := widgets.NewQPushButton(nil) quitBtn := widgets.NewQPushButton(nil) message := widgets.NewQLabel(nil, 0) dialogue.SetWindowTitle("TeslaGo Alert") dialogue.SetMinimumWidth(255) dialogue.SetMinimumHeight(50 + (20 * (1 + strings.Count(msg, "\n")))) contBtn.SetText("Continue") quitBtn.SetText("Quit") message.SetText(fmt.Sprintf(msg, a...)) message.SetWordWrap(true) contBtn.ConnectClicked(func(checked bool) { window.Show() popup = false dialogue.Close() go setValues() }) quitBtn.ConnectClicked(func(checked bool) { mainApp.Quit() }) if recover { actionHBox.AddWidget(contBtn, 0, 0) } actionHBox.AddWidget(quitBtn, 0, 0) formLayout.AddRow5(message) formLayout.AddRow6(actionHBox) centralWidget.SetLayout(formLayout) dialogue.Show() }