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/Monitors/ZebraMonitor.cs
2026-04-12 17:22:31 +02:00

154 Zeilen
5.0 KiB
C#

using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Text.Json;
using PrinterMonitor.Configuration;
using PrinterMonitor.Interfaces;
using PrinterMonitor.Models;
namespace PrinterMonitor.Monitors;
/// <summary>
/// IPrinterMonitor-Implementierung für Zebra Drucker via TCP mit JSON-Nachrichten.
///
/// Protokoll (Zebra SGD über TCP, Standard-Port 9200):
/// Senden: {}{\"sensor.peeler\":null,\"head.latch\":null,\"media.status\":null}
/// Empfangen: {\"sensor.peeler\":\"clear\",\"head.latch\":\"ok\",\"media.status\":\"ok\"}
///
/// Werte-Mapping:
/// sensor.peeler: "clear" = LTS frei, "not clear" = LTS belegt
/// head.latch: "ok" = Klappe geschlossen, "open" = Klappe offen
/// media.status: "ok" = Etiketten vorhanden, "out" = Etiketten leer
/// </summary>
public class ZebraMonitor : IPrinterMonitor
{
private static readonly string Query =
"{}{\"sensor.peeler\":null,\"head.latch\":null,\"media.status\":null}";
// 256-Byte-Lesepuffer: reduziert System-Calls drastisch gegenüber 1-Byte-Buffer.
// Als Instanzfeld gecacht – keine Allokation pro Poll-Zyklus.
private readonly byte[] _readBuffer = new byte[256];
// StringBuilder als Instanzfeld: wird bei jedem Poll nur geleert, nie neu allokiert.
private readonly StringBuilder _responseBuilder = new(256);
private readonly PrinterConfig _config;
private TcpClient? _tcpClient;
private NetworkStream? _stream;
// Wird auf true gesetzt wenn ein Read/Write-Fehler oder EOF erkannt wird
private volatile bool _isDisconnected = true;
public string PrinterName => _config.Name;
public bool IsConnected => _tcpClient?.Connected == true && !_isDisconnected;
public ZebraMonitor(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);
_stream = client.GetStream();
_tcpClient = client;
}
catch
{
client.Dispose();
_isDisconnected = true;
throw;
}
}
public Task DisconnectAsync()
{
_stream?.Dispose();
_tcpClient?.Dispose();
_stream = null;
_tcpClient = null;
return Task.CompletedTask;
}
public async Task<SimplePrinterState> PollStateAsync(CancellationToken ct = default)
{
if (_isDisconnected || _tcpClient == null || _stream == null || !_tcpClient.Connected)
throw new IOException("Verbindung zum Zebra-Drucker getrennt");
// Anfrage senden
var queryBytes = Encoding.UTF8.GetBytes(Query);
await _stream.WriteAsync(queryBytes, ct);
await _stream.FlushAsync(ct);
// Antwort lesen bis vollständiges JSON-Objekt
var json = await ReadJsonResponseAsync(ct);
// JSON parsen
using var doc = JsonDocument.Parse(json);
var root = doc.RootElement;
var peeler = root.GetProperty("sensor.peeler").GetString() ?? "";
var latch = root.GetProperty("head.latch").GetString() ?? "";
var media = root.GetProperty("media.status").GetString() ?? "";
return new SimplePrinterState
{
LtsSensor = !peeler.Equals("clear", StringComparison.OrdinalIgnoreCase),
Druckerklappe = latch.Equals("open", StringComparison.OrdinalIgnoreCase),
KeineEtiketten = media.Equals("out", StringComparison.OrdinalIgnoreCase)
};
}
/// <summary>
/// Liest die JSON-Antwort vom Stream bis die schließende Klammer } erreicht ist.
/// Nutzt einen 256-Byte-Puffer statt byteweisem Lesen, um System-Calls zu minimieren.
/// Zebra sendet CRLF innerhalb des JSON — das ist gültiger JSON-Whitespace.
/// </summary>
private async Task<string> ReadJsonResponseAsync(CancellationToken ct)
{
_responseBuilder.Clear();
int braceDepth = 0;
bool started = false;
bool done = false;
while (!ct.IsCancellationRequested && !done)
{
var bytesRead = await _stream!.ReadAsync(_readBuffer, ct);
if (bytesRead == 0)
{
_isDisconnected = true;
throw new IOException("Verbindung vom Zebra-Drucker geschlossen");
}
for (int i = 0; i < bytesRead && !done; i++)
{
char c = (char)_readBuffer[i];
_responseBuilder.Append(c);
if (c == '{')
{
braceDepth++;
started = true;
}
else if (c == '}')
{
braceDepth--;
if (started && braceDepth == 0)
done = true;
}
}
}
return _responseBuilder.ToString();
}
public async ValueTask DisposeAsync()
{
await DisconnectAsync();
}
}