diff --git a/TeslaSolarCharger/Client/Components/RightAlignedButtonComponent.razor b/TeslaSolarCharger/Client/Components/RightAlignedButtonComponent.razor
index 3811b9f90..8b545c754 100644
--- a/TeslaSolarCharger/Client/Components/RightAlignedButtonComponent.razor
+++ b/TeslaSolarCharger/Client/Components/RightAlignedButtonComponent.razor
@@ -22,7 +22,7 @@
[Parameter]
public string ButtonText { get; set; }
[Parameter]
- public string StartIcon { get; set; } = Icons.Material.Filled.Add;
+ public string StartIcon { get; set; }
[Parameter]
public EventCallback OnButtonClicked { get; set; }
diff --git a/TeslaSolarCharger/Client/Pages/CarSettings.razor b/TeslaSolarCharger/Client/Pages/CarSettings.razor
index 4cd9b5254..a376cf2aa 100644
--- a/TeslaSolarCharger/Client/Pages/CarSettings.razor
+++ b/TeslaSolarCharger/Client/Pages/CarSettings.razor
@@ -1,6 +1,8 @@
@page "/CarSettings"
@using TeslaSolarCharger.Shared.Dtos
@using TeslaSolarCharger.Client.Wrapper
+@using TeslaSolarCharger.Shared.Dtos.Ble
+@using Newtonsoft.Json
@inject HttpClient HttpClient
@inject ISnackbar Snackbar
@@ -27,17 +29,44 @@ else
+
+ BLE Pairing and test
- Note: Before TSC can update charge speed via BLE you must pair the car with TSC.
+ TSC can use BLE instead of the Fleet API. This is useful to come around rate limits.
- Ble Pair
+ @if (_pairingResults.TryGetValue(carBasicConfiguration.Vin, out var result))
+ {
+
+
+
+ }
+
+ Note: When clicking the pair button the car won't display any feedback. You have to place the card on the center console. Only after doing so, a message will pop up. If you don't see a message, the pairing failed. As the car does not send any feedback, just try a few times, if it still does not work reboot your BLE device.
+
+
+ Test BLE access
- @if (_pairingResults.ContainsKey(carBasicConfiguration.Vin))
- {
- @_pairingResults[carBasicConfiguration.Vin]
- }
+ Before you can test BLE access you must pair the car with TSC. This includes placing the card on your center console and confirming the new "phone key" on the car's screen.
+
+
+ After clicking the test button the front lights should flash.
+
+ @if (_bleTestResults.TryGetValue(carBasicConfiguration.Vin, out var bleResult))
+ {
+
+
+ @bleResult.Message
+
+
+ }
+
+
-
}
}
@@ -45,7 +74,9 @@ else
private List? _carBasicConfigurations;
private readonly List _savingCarIds = new();
- private Dictionary _pairingResults = new Dictionary();
+ private Dictionary _pairingResults = new();
+
+ private Dictionary _bleTestResults = new();
protected override async Task OnInitializedAsync()
{
@@ -70,7 +101,18 @@ else
private async Task PairCar(string vin)
{
var result = await HttpClient.GetStringAsync($"/api/Ble/PairKey?vin={vin}").ConfigureAwait(false);
- _pairingResults[vin] = result;
+ var resultJson = JsonConvert.DeserializeObject(result);
+ _pairingResults[vin] = resultJson?.Message ?? result;
+ }
+
+ private async Task TestBle(string vin)
+ {
+ var result = await HttpClient.GetFromJsonAsync($"/api/Ble/FlashLights?vin={vin}").ConfigureAwait(false) ?? new DtoBleResult { Success = false, Message = "Could not deserialize message from TSC." };
+ if(result.Success && string.IsNullOrWhiteSpace(result.Message))
+ {
+ result.Message = "Ble success seems to work. Please double check if lights were flashing.";
+ }
+ _bleTestResults[vin] = result;
}
}
\ No newline at end of file
diff --git a/TeslaSolarCharger/Server/Controllers/BleController.cs b/TeslaSolarCharger/Server/Controllers/BleController.cs
index fbefd78e8..ee494348f 100644
--- a/TeslaSolarCharger/Server/Controllers/BleController.cs
+++ b/TeslaSolarCharger/Server/Controllers/BleController.cs
@@ -1,5 +1,7 @@
using Microsoft.AspNetCore.Mvc;
+using TeslaSolarCharger.Server.Dtos.Ble;
using TeslaSolarCharger.Server.Services.Contracts;
+using TeslaSolarCharger.Shared.Dtos.Ble;
using TeslaSolarCharger.SharedBackend.Abstracts;
namespace TeslaSolarCharger.Server.Controllers;
@@ -7,7 +9,7 @@ namespace TeslaSolarCharger.Server.Controllers;
public class BleController (IBleService bleService) : ApiBaseController
{
[HttpGet]
- public Task PairKey(string vin) => bleService.PairKey(vin);
+ public Task PairKey(string vin) => bleService.PairKey(vin);
[HttpGet]
public Task StartCharging(string vin) => bleService.StartCharging(vin);
@@ -17,4 +19,7 @@ public class BleController (IBleService bleService) : ApiBaseController
[HttpGet]
public Task SetAmp(string vin, int amps) => bleService.SetAmp(vin, amps);
+
+ [HttpGet]
+ public Task FlashLights(string vin) => bleService.FlashLights(vin);
}
diff --git a/TeslaSolarCharger/Server/Services/Contracts/IBleService.cs b/TeslaSolarCharger/Server/Services/Contracts/IBleService.cs
index 71e7da197..9d457cae8 100644
--- a/TeslaSolarCharger/Server/Services/Contracts/IBleService.cs
+++ b/TeslaSolarCharger/Server/Services/Contracts/IBleService.cs
@@ -1,4 +1,6 @@
-using TeslaSolarCharger.Shared.Enums;
+using TeslaSolarCharger.Server.Dtos.Ble;
+using TeslaSolarCharger.Shared.Dtos.Ble;
+using TeslaSolarCharger.Shared.Enums;
namespace TeslaSolarCharger.Server.Services.Contracts;
@@ -7,5 +9,6 @@ public interface IBleService
Task StartCharging(string vin);
Task StopCharging(string vin);
Task SetAmp(string vin, int amps);
- Task PairKey(string vin);
+ Task FlashLights(string vin);
+ Task PairKey(string vin);
}
diff --git a/TeslaSolarCharger/Server/Services/TeslaBleService.cs b/TeslaSolarCharger/Server/Services/TeslaBleService.cs
index e5f282164..51db1a65c 100644
--- a/TeslaSolarCharger/Server/Services/TeslaBleService.cs
+++ b/TeslaSolarCharger/Server/Services/TeslaBleService.cs
@@ -1,9 +1,11 @@
using Newtonsoft.Json;
+using System.Net;
using System.Web;
using TeslaSolarCharger.Server.Dtos.Ble;
using TeslaSolarCharger.Server.Services.ApiServices.Contracts;
using TeslaSolarCharger.Server.Services.Contracts;
using TeslaSolarCharger.Shared.Contracts;
+using TeslaSolarCharger.Shared.Dtos.Ble;
using TeslaSolarCharger.Shared.Dtos.Contracts;
using TeslaSolarCharger.Shared.Enums;
@@ -52,28 +54,50 @@ public async Task SetAmp(string vin, int amps)
var result = await SendCommandToBle(request).ConfigureAwait(false);
}
- public async Task PairKey(string vin)
+ public async Task FlashLights(string vin)
+ {
+ var request = new DtoBleRequest
+ {
+ Vin = vin,
+ CommandName = "flash-lights",
+ };
+ var result = await SendCommandToBle(request).ConfigureAwait(false);
+ return result;
+ }
+
+ public async Task PairKey(string vin)
{
logger.LogTrace("{method}({vin})", nameof(PairKey), vin);
var bleBaseUrl = configurationWrapper.BleBaseUrl();
- if (!bleBaseUrl.EndsWith("/"))
+ if (string.IsNullOrWhiteSpace(bleBaseUrl))
{
- bleBaseUrl += "/";
+ return new DtoBleResult() { Message = "BLE Base Url is not set.", StatusCode = HttpStatusCode.BadRequest, Success = false, };
}
+
bleBaseUrl += "Pairing/PairCar";
var queryString = HttpUtility.ParseQueryString(string.Empty);
queryString.Add("vin", vin);
var url = $"{bleBaseUrl}?{queryString}";
logger.LogTrace("Ble Url: {bleUrl}", url);
using var client = new HttpClient();
- var response = await client.GetAsync(url).ConfigureAwait(false);
- var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
- if (!response.IsSuccessStatusCode)
+ try
+ {
+ var response = await client.GetAsync(url).ConfigureAwait(false);
+ var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
+ if (!response.IsSuccessStatusCode)
+ {
+ return new DtoBleResult() { Message = responseContent, StatusCode = response.StatusCode, Success = false, };
+ }
+
+ // Success is unknown as the response is not known
+ return new DtoBleResult() { Message = responseContent, StatusCode = response.StatusCode, Success = false };
+ }
+ catch (Exception ex)
{
- logger.LogError("Failed to send command to BLE. StatusCode: {statusCode} {responseContent}", response.StatusCode, responseContent);
- throw new InvalidOperationException();
+ logger.LogError(ex, "Failed to pair key.");
+ return new DtoBleResult() { Message = ex.Message, StatusCode = HttpStatusCode.InternalServerError, Success = false, };
}
- return responseContent;
+
}
public Task SetScheduledCharging(int carId, DateTimeOffset? chargingStartTime)
@@ -90,9 +114,14 @@ private async Task SendCommandToBle(DtoBleRequest request)
{
logger.LogTrace("{method}({@request})", nameof(SendCommandToBle), request);
var bleBaseUrl = configurationWrapper.BleBaseUrl();
- if (!bleBaseUrl.EndsWith("/"))
+ if (string.IsNullOrWhiteSpace(bleBaseUrl))
{
- bleBaseUrl += "/";
+ return new DtoBleResult()
+ {
+ Success = false,
+ Message = "BLE Base Url is not set.",
+ StatusCode = HttpStatusCode.BadRequest,
+ };
}
bleBaseUrl += "Command/ExecuteCommand";
var queryString = HttpUtility.ParseQueryString(string.Empty);
@@ -102,15 +131,24 @@ private async Task SendCommandToBle(DtoBleRequest request)
logger.LogTrace("Ble Url: {bleUrl}", url);
logger.LogTrace("Parameters: {@parameters}", request.Parameters);
using var client = new HttpClient();
- var response = await client.PostAsJsonAsync(url, request.Parameters).ConfigureAwait(false);
- var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
- if (!response.IsSuccessStatusCode)
+ try
+ {
+ var response = await client.PostAsJsonAsync(url, request.Parameters).ConfigureAwait(false);
+ var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
+ if (!response.IsSuccessStatusCode)
+ {
+ logger.LogError("Failed to send command to BLE. StatusCode: {statusCode} {responseContent}", response.StatusCode, responseContent);
+ throw new InvalidOperationException();
+ }
+ var result = JsonConvert.DeserializeObject(responseContent);
+ return result ?? throw new InvalidDataException($"Could not parse {responseContent} to {nameof(DtoBleResult)}");
+ }
+ catch (Exception ex)
{
- logger.LogError("Failed to send command to BLE. StatusCode: {statusCode} {responseContent}", response.StatusCode, responseContent);
- throw new InvalidOperationException();
+ logger.LogError(ex, "Failed to send ble command.");
+ return new DtoBleResult() { Message = ex.Message, StatusCode = HttpStatusCode.InternalServerError, Success = false, };
}
- var result = JsonConvert.DeserializeObject(responseContent);
- return result ?? throw new InvalidDataException($"Could not parse {responseContent} to {nameof(DtoBleResult)}");
+
}
private async Task WakeUpCarIfNeeded(int carId, CarStateEnum? carState)
diff --git a/TeslaSolarCharger/Server/appsettings.Development.json b/TeslaSolarCharger/Server/appsettings.Development.json
index 7fefcbd1d..772b3a3c3 100644
--- a/TeslaSolarCharger/Server/appsettings.Development.json
+++ b/TeslaSolarCharger/Server/appsettings.Development.json
@@ -38,6 +38,7 @@
"AllowCORS": true,
"DisplayApiRequestCounter": true,
"IgnoreSslErrors": true,
+ "BleBaseUrl": "http://raspible:7210/",
"GridPriceProvider": {
"EnergyProvider": "Tibber",
"Octopus": {
diff --git a/TeslaSolarCharger/Server/appsettings.json b/TeslaSolarCharger/Server/appsettings.json
index 0e6591648..194fa9555 100644
--- a/TeslaSolarCharger/Server/appsettings.json
+++ b/TeslaSolarCharger/Server/appsettings.json
@@ -69,7 +69,7 @@
"GetVehicleDataFromTesla": false,
"GetVehicleDataFromTeslaDebug": false,
"AwattarBaseUrl": "https://api.awattar.de/v1/marketdata",
- "BleBaseUrl": "http://raspible:7210/api",
+ "BleBaseUrl": null,
"GridPriceProvider": {
"EnergyProvider": "FixedPrice",
"Octopus": {
diff --git a/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs b/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs
index 8a1a9750c..1323491d3 100644
--- a/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs
+++ b/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs
@@ -99,5 +99,5 @@ public interface IConfigurationWrapper
bool GetVehicleDataFromTesla();
bool GetVehicleDataFromTeslaDebug();
int? MaxInverterAcPower();
- string BleBaseUrl();
+ string? BleBaseUrl();
}
diff --git a/TeslaSolarCharger/Server/Dtos/Ble/DtoBleResult.cs b/TeslaSolarCharger/Shared/Dtos/Ble/DtoBleResult.cs
similarity index 80%
rename from TeslaSolarCharger/Server/Dtos/Ble/DtoBleResult.cs
rename to TeslaSolarCharger/Shared/Dtos/Ble/DtoBleResult.cs
index c077f89e0..5e12b29ba 100644
--- a/TeslaSolarCharger/Server/Dtos/Ble/DtoBleResult.cs
+++ b/TeslaSolarCharger/Shared/Dtos/Ble/DtoBleResult.cs
@@ -1,6 +1,6 @@
using System.Net;
-namespace TeslaSolarCharger.Server.Dtos.Ble;
+namespace TeslaSolarCharger.Shared.Dtos.Ble;
public class DtoBleResult
{
diff --git a/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs b/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs
index a8c288cfd..0c0bd2c52 100644
--- a/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs
+++ b/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs
@@ -169,10 +169,21 @@ public string FleetApiClientId()
return value;
}
- public string BleBaseUrl()
+ public string? BleBaseUrl()
{
var environmentVariableName = "BleBaseUrl";
- var value = GetNotNullableConfigurationValue(environmentVariableName);
+ var value = configuration.GetValue(environmentVariableName);
+ if (!string.IsNullOrWhiteSpace(value))
+ {
+ if (!value.EndsWith("/"))
+ {
+ value += "/";
+ }
+ if (!value.EndsWith("/api/"))
+ {
+ value += "api/";
+ }
+ }
return value;
}