Du hast auf den Branch log-test-branch 2026-04-12 20:57:35 +00:00 gepusht
Neuer Pull-Request
Du hast auf den Branch anon-test2 2026-04-12 20:50:35 +00:00 gepusht
Neuer Pull-Request
Dateien
Soft-LTS/Services/PrinterService.cs
2026-04-12 17:39:45 +02:00

277 Zeilen
9.4 KiB
C#

using System.Collections.Concurrent;
using PrinterMonitor.Configuration;
using PrinterMonitor.Interfaces;
using PrinterMonitor.Models;
using PrinterMonitor.Monitors;
using PrinterMonitor.Tcp;
namespace PrinterMonitor.Services;
/// <summary>
/// Verwaltet alle konfigurierten Drucker:
/// - Erzeugt Monitor pro Drucker, pollt mit festem Intervall (300 ms)
/// - Aggregiert alle Druckerzustände per ODER-Verknüpfung
/// - Pusht bei Zustandsänderung sofort, sendet alle 500 ms einen Heartbeat
/// - Ein einziger TCP-Client verbindet sich auf localhost:TcpTargetPort (Default 12164)
/// </summary>
public class PrinterService : IAsyncDisposable
{
private const int PollingIntervalMs = 300;
private const int ReconnectDelayMs = 5000;
private const int HeartbeatIntervalMs = 500;
private readonly AppSettings _settings;
private readonly ConcurrentDictionary<string, IPrinterMonitor> _monitors = new(StringComparer.OrdinalIgnoreCase);
private readonly ConcurrentDictionary<string, PrinterStatus> _statusCache = new(StringComparer.OrdinalIgnoreCase);
private readonly ConcurrentDictionary<string, SimplePrinterState?> _lastStates = new(StringComparer.OrdinalIgnoreCase);
private TcpPushClient? _tcpClient;
private SimplePrinterState? _lastAggregatedState;
private readonly object _aggregationLock = new();
private CancellationTokenSource? _cts;
private readonly List<Task> _pollingTasks = new();
public PrinterService(AppSettings settings)
{
_settings = settings;
}
public async Task StartAsync()
{
_cts = new CancellationTokenSource();
_tcpClient = new TcpPushClient("localhost", _settings.TcpTargetPort);
_tcpClient.Start();
foreach (var config in _settings.Printers.Where(p => p.Enabled))
{
var monitor = CreateMonitor(config);
_monitors[config.Name] = monitor;
_statusCache[config.Name] = new PrinterStatus
{
PrinterName = config.Name,
PrinterType = config.Type,
IsOnline = false,
ErrorMessage = "Noch nicht abgefragt"
};
_lastStates[config.Name] = null;
_pollingTasks.Add(RunPollingLoop(config, monitor, _cts.Token));
}
_pollingTasks.Add(PeriodicBroadcastLoop(_cts.Token));
await Task.CompletedTask;
}
public async Task StopAsync()
{
_cts?.Cancel();
try { await Task.WhenAll(_pollingTasks); }
catch (Exception ex) { Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] Fehler beim Stoppen der Polling-Tasks: {ex.Message}"); }
_pollingTasks.Clear();
foreach (var monitor in _monitors.Values)
{
try { await monitor.DisconnectAsync(); }
catch (Exception ex) { Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] Fehler beim Trennen von {monitor.PrinterName}: {ex.Message}"); }
}
_monitors.Clear();
_lastStates.Clear();
_statusCache.Clear();
_lastAggregatedState = null;
if (_tcpClient != null)
{
try { await _tcpClient.DisposeAsync(); }
catch (Exception ex) { Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] Fehler beim Stoppen des TCP-Clients: {ex.Message}"); }
_tcpClient = null;
}
}
/// <summary>
/// Startet das Monitoring mit der aktuellen Konfiguration neu.
/// Wird nach dem Speichern der Einstellungen aufgerufen.
/// </summary>
public async Task RestartAsync()
{
await StopAsync();
await StartAsync();
}
public IReadOnlyList<string> GetPrinterNames()
=> _statusCache.Keys.ToList().AsReadOnly();
public PrinterStatus? GetStatus(string printerName)
{
_statusCache.TryGetValue(printerName, out var status);
return status;
}
/// <summary>
/// Setzt den Zustand eines Simulation-Druckers manuell.
/// </summary>
public void SetSimulationState(string printerName, bool ltsSensor, bool druckerklappe, bool keineEtiketten)
{
if (_monitors.TryGetValue(printerName, out var monitor) && monitor is SimulationMonitor sim)
{
sim.SetState(ltsSensor, druckerklappe, keineEtiketten);
}
}
private IPrinterMonitor CreateMonitor(PrinterConfig config)
{
// Defensiv: alten korrupten Wert "System.Windows.Controls.ComboBoxItem: X" bereinigen
var type = config.Type ?? "";
if (type.Contains(':'))
type = type.Split(':').Last().Trim();
return type.ToLowerInvariant() switch
{
"cabsquix" => new CabSquixMonitor(config),
"zebra" => new ZebraMonitor(config),
"honeywell" => new HoneywellMonitor(config),
"simulation" => new SimulationMonitor(config),
_ => throw new NotSupportedException($"Druckertyp '{config.Type}' wird nicht unterstützt.")
};
}
private async Task RunPollingLoop(PrinterConfig config, IPrinterMonitor monitor, CancellationToken ct)
{
var name = config.Name;
while (!ct.IsCancellationRequested)
{
if (!monitor.IsConnected)
{
try
{
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] {name}: Verbinde...");
await monitor.ConnectAsync(ct);
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] {name}: Verbunden.");
}
catch (Exception ex)
{
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] {name}: Verbindung fehlgeschlagen – {ex.Message}");
UpdateStatus(name, config.Type, online: false, error: $"Verbindungsfehler: {ex.Message}");
await SafeDelay(ReconnectDelayMs, ct);
continue;
}
}
try
{
var newState = await monitor.PollStateAsync(ct);
UpdateStatus(name, config.Type, online: true, state: newState);
_lastStates[name] = newState;
await BroadcastIfChangedAsync();
}
catch (Exception ex)
{
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] {name}: Polling-Fehler – {ex.Message}");
UpdateStatus(name, config.Type, online: false, error: ex.Message);
_lastStates[name] = null;
// Sofort aggregieren & senden damit der offline-State nicht im Puffer bleibt
await BroadcastIfChangedAsync();
try { await monitor.DisconnectAsync(); }
catch (Exception dex) { Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] {name}: Fehler beim Trennen – {dex.Message}"); }
await SafeDelay(ReconnectDelayMs, ct);
continue;
}
await SafeDelay(PollingIntervalMs, ct);
}
}
private async Task BroadcastIfChangedAsync()
{
string? payload;
lock (_aggregationLock)
{
SimplePrinterState? aggregated = null;
foreach (var s in _lastStates.Values)
{
if (s == null) continue;
if (aggregated == null)
{
aggregated = s;
continue;
}
aggregated = new SimplePrinterState
{
LtsSensor = aggregated.LtsSensor || s.LtsSensor,
Druckerklappe = aggregated.Druckerklappe || s.Druckerklappe,
KeineEtiketten = aggregated.KeineEtiketten || s.KeineEtiketten
};
}
if (aggregated == null)
{
_lastAggregatedState = null;
return;
}
if (_lastAggregatedState != null && aggregated.Equals(_lastAggregatedState))
return;
_lastAggregatedState = aggregated;
payload = aggregated.ToTcpString();
}
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] Aggregiert: Zustandsänderung -> {payload}");
if (_tcpClient != null)
await _tcpClient.SendStateAsync(payload);
}
private async Task PeriodicBroadcastLoop(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
await SafeDelay(HeartbeatIntervalMs, ct);
string? payload;
lock (_aggregationLock)
{
if (_lastAggregatedState == null) continue;
payload = _lastAggregatedState.ToTcpString();
}
if (_tcpClient != null)
await _tcpClient.SendStateAsync(payload);
}
}
private void UpdateStatus(string name, string type, bool online,
SimplePrinterState? state = null, string? error = null)
{
_statusCache[name] = new PrinterStatus
{
PrinterName = name,
PrinterType = type,
IsOnline = online,
LastUpdated = DateTime.Now,
ErrorMessage = error,
LtsSensor = state?.LtsSensor,
Druckerklappe = state?.Druckerklappe,
KeineEtiketten = state?.KeineEtiketten
};
}
private static async Task SafeDelay(int ms, CancellationToken ct)
{
try { await Task.Delay(ms, ct); }
catch (OperationCanceledException) { }
}
public async ValueTask DisposeAsync()
{
await StopAsync();
_cts?.Dispose();
}
}