using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using RobotNet10.CANOpen; namespace RobotNet10.RobotApp.Drivers.Battery.Varta; public sealed class VartaChargerSimulatorService : IHostedService, IDisposable { private readonly ILogger _logger; private readonly ICanOpenManager _canOpenManager; private readonly VartaChargerSimulatorOptions _options; private readonly SemaphoreSlim _controlLock = new(1, 1); private Task? _runTask; private CancellationTokenSource? _runCts; private string _currentInterface = "can0"; private bool _disposed; public bool IsRunning => _runTask is { IsCompleted: false }; public string CurrentInterface => _currentInterface; public VartaChargerSimulatorService( IConfiguration configuration, ILogger logger, ICanOpenManager canOpenManager) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _canOpenManager = canOpenManager ?? throw new ArgumentNullException(nameof(canOpenManager)); _options = new VartaChargerSimulatorOptions(); configuration.GetSection("Varta:Charger59VSimulator").Bind(_options); } public async Task StartAsync(CancellationToken cancellationToken) { if (_options.Enabled) { await StartSimulatorAsync(_options.CanInterface, cancellationToken); return; } _logger.LogInformation("Varta Charger59V simulator is disabled at startup."); } public async Task StopAsync(CancellationToken cancellationToken) { await StopSimulatorAsync(cancellationToken); } public async Task StartSimulatorAsync(string? canInterface = null, CancellationToken cancellationToken = default) { ThrowIfDisposed(); await _controlLock.WaitAsync(cancellationToken); try { if (_runTask is { IsCompleted: false }) { return false; } _currentInterface = string.IsNullOrWhiteSpace(canInterface) ? "can0" : canInterface; _runCts = new CancellationTokenSource(); _runTask = RunSimulatorAsync(_currentInterface, _runCts.Token); return true; } finally { _controlLock.Release(); } } public async Task StopSimulatorAsync(CancellationToken cancellationToken = default) { ThrowIfDisposed(); Task? runTask; CancellationTokenSource? runCts; await _controlLock.WaitAsync(cancellationToken); try { if (_runTask is not { IsCompleted: false } || _runCts is null) { return false; } runTask = _runTask; runCts = _runCts; _runTask = null; _runCts = null; } finally { _controlLock.Release(); } runCts.Cancel(); try { await runTask.WaitAsync(cancellationToken); } catch (OperationCanceledException) { } finally { runCts.Dispose(); } return true; } private async Task RunSimulatorAsync(string canInterface, CancellationToken token) { _logger.LogInformation("Starting Varta Charger59V simulator on CAN interface {CanInterface}", canInterface); try { var simulator = new Charger59V(_canOpenManager, canInterface, token); await simulator.RunAsync(); } catch (OperationCanceledException) when (token.IsCancellationRequested) { _logger.LogInformation("Varta Charger59V simulator stopped."); } catch (Exception ex) { _logger.LogError(ex, "Varta Charger59V simulator crashed."); } } public void Dispose() { if (_disposed) { return; } _disposed = true; _runCts?.Cancel(); _runCts?.Dispose(); _controlLock.Dispose(); } private void ThrowIfDisposed() { if (_disposed) { throw new ObjectDisposedException(nameof(VartaChargerSimulatorService)); } } } public sealed class VartaChargerSimulatorOptions { public bool Enabled { get; set; } public string CanInterface { get; set; } = "can0"; } public sealed class VartaChargerSimulatorStartRequest { public string? CanInterface { get; set; } }