From bbe6f114c518fbe655c60596567cfa5a2df006a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Sat, 7 Dec 2024 16:25:37 +0100 Subject: [PATCH 1/2] feat(FleetTelemetryWebSocketService): can handle error messages --- .../DtoFleetTelemetryErrorMessage.cs | 8 +++ .../DtoTscFleetTelemetryMessage.cs | 2 +- .../FleetTelemetryWebSocketService.cs | 63 ++++++++++++++++++- .../Shared/Dtos/CarBasicConfiguration.cs | 2 +- 4 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 TeslaSolarCharger/Server/Dtos/FleetTelemetry/DtoFleetTelemetryErrorMessage.cs rename TeslaSolarCharger/Server/Dtos/{ => FleetTelemetry}/DtoTscFleetTelemetryMessage.cs (88%) diff --git a/TeslaSolarCharger/Server/Dtos/FleetTelemetry/DtoFleetTelemetryErrorMessage.cs b/TeslaSolarCharger/Server/Dtos/FleetTelemetry/DtoFleetTelemetryErrorMessage.cs new file mode 100644 index 000000000..3e2dc2740 --- /dev/null +++ b/TeslaSolarCharger/Server/Dtos/FleetTelemetry/DtoFleetTelemetryErrorMessage.cs @@ -0,0 +1,8 @@ +namespace TeslaSolarCharger.Server.Dtos.FleetTelemetry; + +public class DtoFleetTelemetryErrorMessage +{ + public List MissingKeyVins { get; set; } = new List(); + public List UnsupportedHardwareVins { get; set; } = new List(); + public List UnsupportedFirmwareVins { get; set; } = new List(); +} diff --git a/TeslaSolarCharger/Server/Dtos/DtoTscFleetTelemetryMessage.cs b/TeslaSolarCharger/Server/Dtos/FleetTelemetry/DtoTscFleetTelemetryMessage.cs similarity index 88% rename from TeslaSolarCharger/Server/Dtos/DtoTscFleetTelemetryMessage.cs rename to TeslaSolarCharger/Server/Dtos/FleetTelemetry/DtoTscFleetTelemetryMessage.cs index 95cc6d67e..7e2ad0fce 100644 --- a/TeslaSolarCharger/Server/Dtos/DtoTscFleetTelemetryMessage.cs +++ b/TeslaSolarCharger/Server/Dtos/FleetTelemetry/DtoTscFleetTelemetryMessage.cs @@ -1,6 +1,6 @@ using TeslaSolarCharger.Shared.Enums; -namespace TeslaSolarCharger.Server.Dtos; +namespace TeslaSolarCharger.Server.Dtos.FleetTelemetry; public class DtoTscFleetTelemetryMessage { diff --git a/TeslaSolarCharger/Server/Services/FleetTelemetryWebSocketService.cs b/TeslaSolarCharger/Server/Services/FleetTelemetryWebSocketService.cs index fc33ba1d9..c25d361a4 100644 --- a/TeslaSolarCharger/Server/Services/FleetTelemetryWebSocketService.cs +++ b/TeslaSolarCharger/Server/Services/FleetTelemetryWebSocketService.cs @@ -6,6 +6,7 @@ using TeslaSolarCharger.Model.Entities.TeslaSolarCharger; using TeslaSolarCharger.Model.EntityFramework; using TeslaSolarCharger.Server.Dtos; +using TeslaSolarCharger.Server.Dtos.FleetTelemetry; using TeslaSolarCharger.Server.Helper; using TeslaSolarCharger.Server.Services.Contracts; using TeslaSolarCharger.Shared.Contracts; @@ -39,7 +40,9 @@ public async Task ReconnectWebSocketsForEnabledCars() var scope = serviceProvider.CreateScope(); var context = scope.ServiceProvider.GetRequiredService(); var cars = await context.Cars - .Where(c => c.UseFleetTelemetry && (c.ShouldBeManaged == true)) + .Where(c => c.UseFleetTelemetry + && (c.ShouldBeManaged == true) + && (c.TeslaFleetApiState != TeslaCarFleetApiState.NotWorking)) .Select(c => new { c.Vin, c.UseFleetTelemetryForLocationData, }) .ToListAsync(); var bytesToSend = Encoding.UTF8.GetBytes("Heartbeat"); @@ -198,7 +201,11 @@ private async Task ReceiveMessages(DtoFleetTelemetryWebSocketClients client, str var message = DeserializeFleetTelemetryMessage(jsonMessage); if (message == default) { - logger.LogWarning("Could not deserialize non heartbeat message {string}", jsonMessage); + var couldHandleErrorMessage = await HandleErrorMessage(jsonMessage); + if (!couldHandleErrorMessage) + { + logger.LogWarning("Could not deserialize non heartbeat message {string}", jsonMessage); + } continue; } @@ -294,6 +301,58 @@ private async Task ReceiveMessages(DtoFleetTelemetryWebSocketClients client, str } } + private async Task HandleErrorMessage(string jsonMessage) + { + logger.LogTrace("{method}({jsonMessage}", nameof(HandleErrorMessage), jsonMessage); + var message = JsonConvert.DeserializeObject(jsonMessage); + if(message == default) + { + return false; + } + foreach (var vin in message.MissingKeyVins) + { + var scope = serviceProvider.CreateScope(); + var context = scope.ServiceProvider.GetRequiredService(); + var car = context.Cars.FirstOrDefault(c => c.Vin == vin); + if (car == default) + { + continue; + } + logger.LogInformation("Set Fleet API state for car {vin} to not working", vin); + car.TeslaFleetApiState = TeslaCarFleetApiState.NotWorking; + await context.SaveChangesAsync(); + } + + foreach (var vin in message.UnsupportedFirmwareVins) + { + logger.LogInformation("Disable Fleet Telemetry for car {vin} as firmware is not supported", vin); + await DisableFleetTelemetryForCar(vin).ConfigureAwait(false); + } + + foreach (var vin in message.UnsupportedHardwareVins) + { + logger.LogInformation("Disable Fleet Telemetry for car {vin} as hardware is not supported", vin); + await DisableFleetTelemetryForCar(vin).ConfigureAwait(false); + } + + return true; + } + + private async Task DisableFleetTelemetryForCar(string vin) + { + logger.LogTrace("{method}({vin})", nameof(DisableFleetTelemetryForCar), vin); + var scope = serviceProvider.CreateScope(); + var context = scope.ServiceProvider.GetRequiredService(); + var car = context.Cars.FirstOrDefault(c => c.Vin == vin); + if (car == default) + { + return; + } + car.UseFleetTelemetry = false; + car.UseFleetTelemetryForLocationData = false; + await context.SaveChangesAsync(); + } + internal DtoTscFleetTelemetryMessage? DeserializeFleetTelemetryMessage(string jsonMessage) { var settings = new JsonSerializerSettings diff --git a/TeslaSolarCharger/Shared/Dtos/CarBasicConfiguration.cs b/TeslaSolarCharger/Shared/Dtos/CarBasicConfiguration.cs index e1ae90296..2d546f9a8 100644 --- a/TeslaSolarCharger/Shared/Dtos/CarBasicConfiguration.cs +++ b/TeslaSolarCharger/Shared/Dtos/CarBasicConfiguration.cs @@ -43,7 +43,7 @@ public CarBasicConfiguration(int id, string? name) public bool UseBle { get; set; } [HelperText("Needed to send commands via BLE to the car. An example value would be `http://raspible:7210/`")] public string? BleApiBaseUrl { get; set; } - [HelperText("Only supported on cars with Software 2024.38.2+. Not supported on Pre 2021 Model S/X. If enabled, some data will be transferred via Fleet Telemetry. This improves the delay in the TSC detection of plugin and out of the car, as well as changes in the charging speed. Note: All data transferred via Fleet Telemetry passes my server.")] + [HelperText("Only supported on cars with Software 2024.38.2+. Not supported on Pre 2021 Model S/X. If enabled, some data will be transferred via Fleet Telemetry. This improves the delay in the TSC detection of plugin and out of the car, as well as changes in the charging speed. Note: All data transferred via Fleet Telemetry passes my server. If your car does not support fleet telemetry, this option will be disabled automatically within two minutes.")] public bool UseFleetTelemetry { get; set; } [HelperText("This further improves the detection if the car is at home. Enabling this results in additionally streaming the field Location over my server. If you do not mind that your car location data passes my server, do not disable this option.")] From 60030cf820cf2b672bdb74b7c963add41ad6fcce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Sat, 7 Dec 2024 17:17:53 +0100 Subject: [PATCH 2/2] feat(FleetTelemetryWebSocketService): can receive error messages --- .../FleetTelemetry/FleetTelemetryMessageBase.cs | 8 ++++++++ .../Server/Enums/FleetTelemetryMessageType.cs | 7 +++++++ .../Services/FleetTelemetryWebSocketService.cs | 16 ++++++++++++---- 3 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 TeslaSolarCharger/Server/Dtos/FleetTelemetry/FleetTelemetryMessageBase.cs create mode 100644 TeslaSolarCharger/Server/Enums/FleetTelemetryMessageType.cs diff --git a/TeslaSolarCharger/Server/Dtos/FleetTelemetry/FleetTelemetryMessageBase.cs b/TeslaSolarCharger/Server/Dtos/FleetTelemetry/FleetTelemetryMessageBase.cs new file mode 100644 index 000000000..ea4942a7f --- /dev/null +++ b/TeslaSolarCharger/Server/Dtos/FleetTelemetry/FleetTelemetryMessageBase.cs @@ -0,0 +1,8 @@ +using TeslaSolarCharger.Server.Enums; + +namespace TeslaSolarCharger.Server.Dtos.FleetTelemetry; + +public class FleetTelemetryMessageBase +{ + public FleetTelemetryMessageType MessageType { get; set; } +} diff --git a/TeslaSolarCharger/Server/Enums/FleetTelemetryMessageType.cs b/TeslaSolarCharger/Server/Enums/FleetTelemetryMessageType.cs new file mode 100644 index 000000000..674ecfcce --- /dev/null +++ b/TeslaSolarCharger/Server/Enums/FleetTelemetryMessageType.cs @@ -0,0 +1,7 @@ +namespace TeslaSolarCharger.Server.Enums; + +public enum FleetTelemetryMessageType +{ + Telemetry, + Error, +} diff --git a/TeslaSolarCharger/Server/Services/FleetTelemetryWebSocketService.cs b/TeslaSolarCharger/Server/Services/FleetTelemetryWebSocketService.cs index c25d361a4..83a2786c9 100644 --- a/TeslaSolarCharger/Server/Services/FleetTelemetryWebSocketService.cs +++ b/TeslaSolarCharger/Server/Services/FleetTelemetryWebSocketService.cs @@ -1,5 +1,6 @@ using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using System.Globalization; using System.Net.WebSockets; using System.Text; @@ -7,6 +8,7 @@ using TeslaSolarCharger.Model.EntityFramework; using TeslaSolarCharger.Server.Dtos; using TeslaSolarCharger.Server.Dtos.FleetTelemetry; +using TeslaSolarCharger.Server.Enums; using TeslaSolarCharger.Server.Helper; using TeslaSolarCharger.Server.Services.Contracts; using TeslaSolarCharger.Shared.Contracts; @@ -197,9 +199,10 @@ private async Task ReceiveMessages(DtoFleetTelemetryWebSocketClients client, str client.LastReceivedHeartbeat = dateTimeProvider.UtcNow(); continue; } - logger.LogTrace("Received non heartbeate message."); - var message = DeserializeFleetTelemetryMessage(jsonMessage); - if (message == default) + logger.LogTrace("Received non heartbeat message."); + var jObject = JObject.Parse(jsonMessage); + var messageType = jObject[nameof(FleetTelemetryMessageBase.MessageType)]?.ToObject(); + if (messageType == FleetTelemetryMessageType.Error) { var couldHandleErrorMessage = await HandleErrorMessage(jsonMessage); if (!couldHandleErrorMessage) @@ -208,7 +211,12 @@ private async Task ReceiveMessages(DtoFleetTelemetryWebSocketClients client, str } continue; } - + var message = DeserializeFleetTelemetryMessage(jsonMessage); + if (message == default) + { + logger.LogWarning("Could not deserialize non heartbeat message {string}", jsonMessage); + continue; + } if (configurationWrapper.LogLocationData() || (message.Type != CarValueType.Latitude && message.Type != CarValueType.Longitude)) {