From c77b24de2f2d11950f603230513750277d639d99 Mon Sep 17 00:00:00 2001 From: "e.diakonov" Date: Wed, 2 Sep 2026 18:23:08 +0300 Subject: [PATCH] (fix) (self) keepalive ping system on server for prevent hub to offline --- Common/OpenShockApplication.cs | 4 ++++ Common/OpenShockMiddlewareHelper.cs | 4 +++- Common/OpenShockServiceHelper.cs | 23 +++++++++++++------ .../Controllers/HubV2Controller.cs | 17 ++++++++++++-- 4 files changed, 38 insertions(+), 10 deletions(-) diff --git a/Common/OpenShockApplication.cs b/Common/OpenShockApplication.cs index 822b9ed5..8607ad45 100644 --- a/Common/OpenShockApplication.cs +++ b/Common/OpenShockApplication.cs @@ -46,6 +46,10 @@ public static WebApplicationBuilder CreateDefaultBuilder(string[] args builder.WebHost.ConfigureKestrel(serverOptions => { serverOptions.Limits.RequestHeadersTimeout = TimeSpan.FromMilliseconds(3000); + // Hub / live-control sockets are mostly idle on the receive side between + // pongs. The default 240 B/s body rate would abort them. + serverOptions.Limits.MinRequestBodyDataRate = null; + serverOptions.Limits.MinResponseDataRate = null; }); builder.Host.UseSerilog((context, _, config) => config.ReadFrom.Configuration(context.Configuration)); diff --git a/Common/OpenShockMiddlewareHelper.cs b/Common/OpenShockMiddlewareHelper.cs index fef56cbc..19855da5 100644 --- a/Common/OpenShockMiddlewareHelper.cs +++ b/Common/OpenShockMiddlewareHelper.cs @@ -99,7 +99,9 @@ exception is null app.UseWebSockets(new WebSocketOptions { - KeepAliveInterval = TimeSpan.FromMinutes(1) + // RFC 6455 ping frames. Firmware 1.6.0-rc.1 ignores these for its 90s + // application timer, but they keep NAT / reverse-proxy idle timers alive. + KeepAliveInterval = TimeSpan.FromSeconds(15) }); app.UseRouting(); app.UseAuthentication(); diff --git a/Common/OpenShockServiceHelper.cs b/Common/OpenShockServiceHelper.cs index 43ad1dcd..07ca941d 100644 --- a/Common/OpenShockServiceHelper.cs +++ b/Common/OpenShockServiceHelper.cs @@ -362,13 +362,22 @@ await context.HttpContext.Response.WriteAsync("Too Many Requests. Please try aga public static IServiceCollection AddOpenShockSignalR(this IServiceCollection services, ConfigurationOptions redisConfig) { - services.AddSignalR() - .AddOpenShockStackExchangeRedis(options => { options.Configuration = redisConfig; }) - .AddJsonProtocol(options => - { - options.PayloadSerializerOptions.PropertyNameCaseInsensitive = true; - options.PayloadSerializerOptions.Converters.Add(new SemVersionJsonConverter()); - }); + services.AddSignalR(options => + { + // Browser UserHub only. Hub firmware uses a raw WebSocket at /2/ws/hub + // and application-level FlatBuffer Ping — these options do not apply there. + options.KeepAliveInterval = TimeSpan.FromSeconds(15); + options.ClientTimeoutInterval = TimeSpan.FromSeconds(120); + }) + .AddOpenShockStackExchangeRedis(options => + { + options.Configuration = redisConfig; + }) + .AddJsonProtocol(options => + { + options.PayloadSerializerOptions.PropertyNameCaseInsensitive = true; + options.PayloadSerializerOptions.Converters.Add(new SemVersionJsonConverter()); + }); return services; } diff --git a/LiveControlGateway/Controllers/HubV2Controller.cs b/LiveControlGateway/Controllers/HubV2Controller.cs index a7dc7d4d..b3ec8f63 100644 --- a/LiveControlGateway/Controllers/HubV2Controller.cs +++ b/LiveControlGateway/Controllers/HubV2Controller.cs @@ -16,7 +16,8 @@ using Serilog; namespace OpenShock.LiveControlGateway.Controllers; -//TODO: Implement new keep alive ping pong mechanism +// Hub keep-alive is application-level FlatBuffer Ping/Pong (see SendInitialData), +// not RFC 6455 ping frames and not SignalR. /// /// Communication with the hubs aka ESP-32 microcontrollers /// @@ -49,7 +50,18 @@ ILogger logger : base(HubToGatewayMessage.Serializer, GatewayToHubMessage.Serializer, hubLifetimeManager, serviceProvider, options, logger) { _userHubContext = userHubContext; - _pingTimer = new Timer(PingTimerElapsed, null, Duration.DevicePingInitialDelay, Duration.DevicePingPeriod); + // Do not start until the socket is accepted — see SendInitialData. + _pingTimer = new Timer(PingTimerElapsed, null, Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan); + } + + /// + protected override Task SendInitialData() + { + // Firmware 1.6.0-rc.1 starts a 90s timer on the first application Ping and + // disconnects if another Ping does not arrive in time. RFC 6455 ping frames + // do not reset that timer. Kick the first Ping as soon as the socket is up. + _pingTimer.Change(TimeSpan.Zero, Duration.DevicePingPeriod); + return Task.CompletedTask; } private async void PingTimerElapsed(object? state) @@ -57,6 +69,7 @@ private async void PingTimerElapsed(object? state) try { _pingTimestamp = Stopwatch.GetTimestamp(); + Logger.LogDebug("Sending ping to hub [{HubId}]", CurrentHubId); await QueueMessage(new GatewayToHubMessage { Payload = new GatewayToHubMessagePayload(new Ping