From 7666ea64ace13b1e030bac4337f12620dbf5aec8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Thu, 28 Nov 2024 13:36:23 +0100 Subject: [PATCH] feat(FleetTelemetryWebSocketService): detect server heartbeat and use installation id on connection --- .../Dtos/DtoFleetTelemetryWebSocketClients.cs | 1 + .../FleetTelemetryWebSocketService.cs | 29 ++++++++++++------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/TeslaSolarCharger/Server/Dtos/DtoFleetTelemetryWebSocketClients.cs b/TeslaSolarCharger/Server/Dtos/DtoFleetTelemetryWebSocketClients.cs index 5269fac35..0274466d0 100644 --- a/TeslaSolarCharger/Server/Dtos/DtoFleetTelemetryWebSocketClients.cs +++ b/TeslaSolarCharger/Server/Dtos/DtoFleetTelemetryWebSocketClients.cs @@ -6,5 +6,6 @@ public class DtoFleetTelemetryWebSocketClients { public string Vin { get; set; } public ClientWebSocket WebSocketClient { get; set; } + public DateTime LastReceivedHeartbeat { get; set; } public CancellationToken CancellationToken { get; set; } } diff --git a/TeslaSolarCharger/Server/Services/FleetTelemetryWebSocketService.cs b/TeslaSolarCharger/Server/Services/FleetTelemetryWebSocketService.cs index 07c31cbb9..8a28987eb 100644 --- a/TeslaSolarCharger/Server/Services/FleetTelemetryWebSocketService.cs +++ b/TeslaSolarCharger/Server/Services/FleetTelemetryWebSocketService.cs @@ -20,7 +20,8 @@ public class FleetTelemetryWebSocketService( IConfigurationWrapper configurationWrapper, IDateTimeProvider dateTimeProvider, IServiceProvider serviceProvider, - ISettings settings) : IFleetTelemetryWebSocketService + ISettings settings, + ITscConfigurationService tscConfigurationService) : IFleetTelemetryWebSocketService { private readonly TimeSpan _heartbeatsendTimeout = TimeSpan.FromSeconds(5); @@ -52,7 +53,13 @@ public async Task ReconnectWebSocketsForEnabledCars() var existingClient = Clients.FirstOrDefault(c => c.Vin == car.Vin); if (existingClient != default) { - if (existingClient.WebSocketClient.State == WebSocketState.Open) + var currentTime = dateTimeProvider.UtcNow(); + //When intervall is changed, change it also in the server WebSocketConnectionHandlingService.SendHeartbeatsTask + var serverSideHeartbeatIntervall = TimeSpan.FromSeconds(54); + var additionalIntervallbuffer = TimeSpan.FromSeconds(30); + var maxLastHeartbeatAge = serverSideHeartbeatIntervall + additionalIntervallbuffer; + var earliestPossibleLastHeartbeat = currentTime - maxLastHeartbeatAge; + if ((existingClient.WebSocketClient.State == WebSocketState.Open) && (existingClient.LastReceivedHeartbeat > earliestPossibleLastHeartbeat)) { var segment = new ArraySegment(bytesToSend); try @@ -72,7 +79,7 @@ await existingClient.WebSocketClient.SendAsync(segment, WebSocketMessageType.Tex continue; } - logger.LogInformation("Websocket Client for car {vin} is not open. Disposing client", car.Vin); + logger.LogInformation("Websocket Client for car {vin} is not open or last heartbeat is too old. Disposing client", car.Vin); existingClient.WebSocketClient.Dispose(); Clients.Remove(existingClient); } @@ -114,9 +121,9 @@ private async Task ConnectToFleetTelemetryApi(string vin, bool useFleetTelemetry logger.LogError("Can not connect to WebSocket: No token found for car {vin}", vin); return; } - + var installationId = await tscConfigurationService.GetInstallationId().ConfigureAwait(false); var url = configurationWrapper.FleetTelemetryApiUrl() + - $"teslaToken={token.AccessToken}®ion={token.Region}&vin={vin}&forceReconfiguration=false&includeLocation={useFleetTelemetryForLocationData}"; + $"teslaToken={token.AccessToken}®ion={token.Region}&vin={vin}&forceReconfiguration=false&includeLocation={useFleetTelemetryForLocationData}&installationId={installationId}"; using var client = new ClientWebSocket(); try { @@ -127,6 +134,7 @@ private async Task ConnectToFleetTelemetryApi(string vin, bool useFleetTelemetry Vin = vin, WebSocketClient = client, CancellationToken = cancellation.Token, + LastReceivedHeartbeat = currentDate, }; Clients.Add(dtoClient); var carId = await context.Cars @@ -135,7 +143,7 @@ private async Task ConnectToFleetTelemetryApi(string vin, bool useFleetTelemetry .FirstOrDefaultAsync().ConfigureAwait(false); try { - await ReceiveMessages(client, dtoClient.CancellationToken, dtoClient.Vin, carId).ConfigureAwait(false); + await ReceiveMessages(dtoClient, dtoClient.Vin, carId).ConfigureAwait(false); } catch (Exception ex) { @@ -157,22 +165,22 @@ await client.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", } } - private async Task ReceiveMessages(ClientWebSocket webSocket, CancellationToken ctx, string vin, int carId) + private async Task ReceiveMessages(DtoFleetTelemetryWebSocketClients client, string vin, int carId) { logger.LogTrace("{method}(webSocket, ctx, {vin}, {carId})", nameof(ReceiveMessages), vin, carId); var buffer = new byte[1024 * 4]; // Buffer to store incoming data - while (webSocket.State == WebSocketState.Open) + while (client.WebSocketClient.State == WebSocketState.Open) { try { // Receive message from the WebSocket server logger.LogTrace("Waiting for new fleet telemetry message for car {vin}", vin); - var result = await webSocket.ReceiveAsync(new(buffer), ctx); + var result = await client.WebSocketClient.ReceiveAsync(new(buffer), client.CancellationToken); logger.LogTrace("Received new fleet telemetry message for car {vin}", vin); if (result.MessageType == WebSocketMessageType.Close) { // If the server closed the connection, close the WebSocket - await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, result.CloseStatusDescription, ctx); + await client.WebSocketClient.CloseAsync(WebSocketCloseStatus.NormalClosure, result.CloseStatusDescription, client.CancellationToken); logger.LogInformation("WebSocket connection closed by server."); } else @@ -182,6 +190,7 @@ private async Task ReceiveMessages(ClientWebSocket webSocket, CancellationToken if (jsonMessage == "Heartbeat") { logger.LogTrace("Received heartbeat: {message}", jsonMessage); + client.LastReceivedHeartbeat = dateTimeProvider.UtcNow(); continue; }