using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; namespace RobotNet10.RobotApp.Xloc; /// /// Polls XLOC diagnostics and calls when /// state transitions to READY (3) with an active map. Does not depend on the web UI. /// public sealed class XlocAutoLocalizationHostedService : BackgroundService { private readonly XlocIntegrationService _xloc; private readonly ILogger _logger; private readonly XlocIntegrationConfiguration _config = new(); private byte? _previousXlocState; private bool _hasAutoStartedOnce; public XlocAutoLocalizationHostedService( XlocIntegrationService xloc, IConfiguration configuration, ILogger logger) { _xloc = xloc; _logger = logger; var section = configuration.GetSection("Xloc:Integration"); if (section.Exists()) section.Bind(_config); } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { if (!_config.Enabled) { _logger.LogInformation("XlocAutoLocalizationHostedService skipped: Xloc:Integration:Enabled is false"); return; } if (!_config.AutoStartLocalizationOnReady) { _logger.LogInformation("XlocAutoLocalizationHostedService skipped: AutoStartLocalizationOnReady is false"); return; } var pollMs = Math.Clamp(_config.AutoStartLocalizationPollIntervalMs, 100, 10_000); var cooldownMs = Math.Max(0, _config.AutoStartLocalizationCooldownAfterStopMs); _logger.LogInformation( "XlocAutoLocalizationHostedService running (poll {PollMs}ms, stop cooldown {CooldownMs}ms)", pollMs, cooldownMs); while (!stoppingToken.IsCancellationRequested) { try { await Task.Delay(pollMs, stoppingToken).ConfigureAwait(false); var diag = _xloc.GetDiagnostics(); if (diag == null) continue; var s = diag.XlocState; var transitionedToReady = s == 3 && (!_previousXlocState.HasValue || _previousXlocState.Value != 3); _previousXlocState = s; if (!transitionedToReady || _hasAutoStartedOnce) continue; if (string.IsNullOrWhiteSpace(diag.CurrentActiveMap)) { _logger.LogDebug("Auto-start localization skipped: no active map in diagnostics"); continue; } if (_xloc.LastLocalizationStopUtc.HasValue) { var elapsedMs = (DateTime.UtcNow - _xloc.LastLocalizationStopUtc.Value).TotalMilliseconds; if (elapsedMs < cooldownMs) { _logger.LogDebug( "Auto-start localization skipped: cooldown after stop ({ElapsedMs:F0}ms < {CooldownMs}ms)", elapsedMs, cooldownMs); continue; } } _hasAutoStartedOnce = true; _logger.LogInformation( "XLOC READY with active map '{Map}'. Auto-starting localization...", diag.CurrentActiveMap); var ok = _xloc.StartLocalization(); if (!ok) _logger.LogWarning("Auto-start localization failed (StartLocalization returned false)"); } catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested) { break; } catch (Exception ex) { _logger.LogError(ex, "Error in XlocAutoLocalizationHostedService loop"); } } } }