185 Zeilen
5.4 KiB
C#
185 Zeilen
5.4 KiB
C#
using System.IO;
|
|
using System.Net.Sockets;
|
|
using System.Text;
|
|
using PrinterMonitor.Configuration;
|
|
using PrinterMonitor.Interfaces;
|
|
using PrinterMonitor.Models;
|
|
|
|
namespace PrinterMonitor.Monitors;
|
|
|
|
/// <summary>
|
|
/// IPrinterMonitor-Implementierung für Honeywell PM45 Drucker.
|
|
///
|
|
/// Protokoll (ASCII über TCP, Standard-Port 9201):
|
|
/// Beim Verbindungsaufbau sendet der Drucker automatisch Statuszeilen:
|
|
/// LTS:[0|1];HEAD:[0|1];MEDIA:[0|1] + LF (0x0A)
|
|
///
|
|
/// Werte-Mapping:
|
|
/// LTS: 0 = Sensor frei, 1 = Etikett steht an
|
|
/// HEAD: 0 = Klappe geschlossen, 1 = Druckkopf offen
|
|
/// MEDIA: 0 = Etiketten vorhanden, 1 = Papier leer
|
|
///
|
|
/// Der Monitor verbindet sich als TCP-Client und liest eingehende Zeilen
|
|
/// im Hintergrund. PollStateAsync() gibt den zuletzt empfangenen Zustand zurück.
|
|
/// </summary>
|
|
public class HoneywellMonitor : IPrinterMonitor
|
|
{
|
|
private readonly PrinterConfig _config;
|
|
private TcpClient? _tcpClient;
|
|
private StreamReader? _reader;
|
|
private CancellationTokenSource? _readCts;
|
|
private Task? _readTask;
|
|
|
|
// Letzter empfangener Zustand – wird vom Lese-Thread aktualisiert
|
|
private volatile SimplePrinterState _lastState = new();
|
|
|
|
// Wird auf true gesetzt sobald der Lese-Thread ein EOF / Disconnect erkennt
|
|
private volatile bool _isDisconnected = true;
|
|
|
|
public string PrinterName => _config.Name;
|
|
public bool IsConnected => _tcpClient?.Connected == true && !_isDisconnected;
|
|
|
|
public HoneywellMonitor(PrinterConfig config)
|
|
{
|
|
_config = config;
|
|
}
|
|
|
|
public async Task ConnectAsync(CancellationToken ct = default)
|
|
{
|
|
_isDisconnected = false;
|
|
var client = new TcpClient();
|
|
try
|
|
{
|
|
await client.ConnectAsync(_config.Host, _config.Port, ct);
|
|
|
|
_reader = new StreamReader(
|
|
client.GetStream(), Encoding.ASCII, false, 256, leaveOpen: true);
|
|
|
|
_tcpClient = client;
|
|
_readCts = new CancellationTokenSource();
|
|
_readTask = ReadLoopAsync(_readCts.Token);
|
|
}
|
|
catch
|
|
{
|
|
client.Dispose();
|
|
_isDisconnected = true;
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public async Task DisconnectAsync()
|
|
{
|
|
_readCts?.Cancel();
|
|
|
|
if (_readTask != null)
|
|
{
|
|
try { await _readTask; } catch { }
|
|
_readTask = null;
|
|
}
|
|
|
|
_readCts?.Dispose();
|
|
_readCts = null;
|
|
_reader?.Dispose();
|
|
_reader = null;
|
|
_tcpClient?.Dispose();
|
|
_tcpClient = null;
|
|
}
|
|
|
|
public Task<SimplePrinterState> PollStateAsync(CancellationToken ct = default)
|
|
{
|
|
if (_isDisconnected || _tcpClient == null || !_tcpClient.Connected)
|
|
throw new IOException("Verbindung zum Honeywell-Drucker getrennt");
|
|
|
|
return Task.FromResult(_lastState);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Liest Statuszeilen vom Drucker im Hintergrund.
|
|
/// Format: LTS:0;HEAD:0;MEDIA:0
|
|
/// </summary>
|
|
private async Task ReadLoopAsync(CancellationToken ct)
|
|
{
|
|
try
|
|
{
|
|
while (!ct.IsCancellationRequested && _reader != null)
|
|
{
|
|
var line = await _reader.ReadLineAsync(ct);
|
|
if (line == null)
|
|
{
|
|
// Drucker hat die Verbindung geschlossen (EOF)
|
|
_isDisconnected = true;
|
|
break;
|
|
}
|
|
|
|
var parsed = ParseStatusLine(line);
|
|
if (parsed != null)
|
|
_lastState = parsed;
|
|
else
|
|
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] {PrinterName}: Ungültige Statuszeile: {line}");
|
|
}
|
|
}
|
|
catch (OperationCanceledException) { }
|
|
catch (IOException)
|
|
{
|
|
// Netzwerkfehler → als Disconnect markieren
|
|
_isDisconnected = true;
|
|
}
|
|
catch (ObjectDisposedException) { }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parst eine Honeywell-Statuszeile: LTS:0;HEAD:0;MEDIA:0
|
|
/// Gibt null zurück wenn das Format ungültig ist.
|
|
/// </summary>
|
|
private static SimplePrinterState? ParseStatusLine(string line)
|
|
{
|
|
// Beispiel: "LTS:0;HEAD:0;MEDIA:0"
|
|
line = line.Trim();
|
|
if (string.IsNullOrEmpty(line)) return null;
|
|
|
|
bool lts = false, head = false, media = false;
|
|
bool foundLts = false, foundHead = false, foundMedia = false;
|
|
|
|
var parts = line.Split(';');
|
|
foreach (var part in parts)
|
|
{
|
|
var kv = part.Split(':');
|
|
if (kv.Length != 2) continue;
|
|
|
|
var key = kv[0].Trim().ToUpperInvariant();
|
|
var val = kv[1].Trim();
|
|
|
|
switch (key)
|
|
{
|
|
case "LTS":
|
|
lts = val == "1";
|
|
foundLts = true;
|
|
break;
|
|
case "HEAD":
|
|
head = val == "1";
|
|
foundHead = true;
|
|
break;
|
|
case "MEDIA":
|
|
media = val == "1";
|
|
foundMedia = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!foundLts || !foundHead || !foundMedia)
|
|
return null;
|
|
|
|
return new SimplePrinterState
|
|
{
|
|
LtsSensor = lts,
|
|
Druckerklappe = head,
|
|
KeineEtiketten = media
|
|
};
|
|
}
|
|
|
|
public async ValueTask DisposeAsync()
|
|
{
|
|
await DisconnectAsync();
|
|
}
|
|
}
|