Initial commit

This commit is contained in:
2026-07-03 16:31:37 +07:00
commit 899c7c637d
1939 changed files with 641750 additions and 0 deletions

View File

@@ -0,0 +1,285 @@
using System.Net.Sockets;
using System.Text;
namespace Sick.ColaB;
/// <summary>
/// ColaA (ASCII) session manager for handling send, receive and process telegrams
/// Uses ASCII protocol with STX/ETX framing
/// </summary>
public sealed class ColaASession : SopasSession
{
private readonly string _ipAddress;
private readonly ushort _port;
private readonly int _readTimeoutMs;
private readonly int _writeTimeoutMs;
private System.Net.Sockets.TcpClient? _tcpClient;
private System.Net.Sockets.NetworkStream? _networkStream;
private readonly Lock _lock = new();
private bool _disposed;
/// <summary>
/// Creates a new ColaA session
/// </summary>
public ColaASession(string ipAddress, ushort port, int readTimeoutMs = 5000, int writeTimeoutMs = 5000)
{
_ipAddress = ipAddress ?? throw new ArgumentNullException(nameof(ipAddress));
_port = port;
_readTimeoutMs = readTimeoutMs;
_writeTimeoutMs = writeTimeoutMs;
}
/// <summary>
/// Gets the protocol type of this session
/// </summary>
public override string ProtocolType => "ColaA";
/// <summary>
/// Gets whether the session is open
/// </summary>
public override bool IsOpen
{
get
{
lock (_lock)
{
return _tcpClient?.Connected ?? false;
}
}
}
/// <summary>
/// Opens the session (connects to scanner)
/// </summary>
public override async Task OpenAsync(int connectTimeoutMs, CancellationToken cancellationToken = default)
{
ObjectDisposedException.ThrowIf(_disposed, nameof(ColaASession));
if (IsOpen)
return;
var tcpClient = new System.Net.Sockets.TcpClient();
try
{
// Connect with timeout
var connectTask = tcpClient.ConnectAsync(_ipAddress, _port);
var timeoutTask = Task.Delay(connectTimeoutMs, cancellationToken);
var completedTask = await Task.WhenAny(connectTask, timeoutTask);
if (completedTask == timeoutTask)
{
tcpClient.Dispose();
throw new TimeoutException($"Connection timeout after {connectTimeoutMs}ms");
}
await connectTask;
var stream = tcpClient.GetStream();
stream.ReadTimeout = _readTimeoutMs;
stream.WriteTimeout = _writeTimeoutMs;
lock (_lock)
{
_tcpClient = tcpClient;
_networkStream = stream;
}
}
catch
{
tcpClient?.Dispose();
throw;
}
}
/// <summary>
/// Closes the session (disconnects from scanner)
/// </summary>
public override void Close()
{
if (_disposed)
return;
lock (_lock)
{
try
{
_networkStream?.Close();
_networkStream?.Dispose();
}
catch { }
_networkStream = null;
try
{
_tcpClient?.Close();
_tcpClient?.Dispose();
}
catch { }
_tcpClient = null;
}
}
/// <summary>
/// Sends a SOPAS command and waits for reply (ASCII format)
/// Command format: "sRN DeviceIdent", "sMN LMCstartmeas", etc.
/// </summary>
public override async Task<string> SendCommandAsync(string command, int timeoutMs, CancellationToken cancellationToken = default)
{
ObjectDisposedException.ThrowIf(_disposed, nameof(ColaASession));
NetworkStream? stream;
lock (_lock)
{
if (!IsOpen || _networkStream == null || _tcpClient == null || !_tcpClient.Connected)
throw new InvalidOperationException("Session is not open");
stream = _networkStream;
}
// Format SOPAS command (ASCII mode): STX + command + ETX
var commandBytes = Encoding.ASCII.GetBytes(command);
var message = new List<byte> { 0x02 }; // STX
message.AddRange(commandBytes);
message.Add(0x03); // ETX
// Send command
await stream.WriteAsync(message.ToArray().AsMemory(0, message.Count), cancellationToken);
await stream.FlushAsync(cancellationToken);
// Read reply with retry logic
string reply = string.Empty;
int retries = 3;
for (int i = 0; i < retries; i++)
{
try
{
reply = await ReadReplyAsync(stream, timeoutMs, cancellationToken);
if (!string.IsNullOrEmpty(reply))
break;
}
catch (TimeoutException) when (i < retries - 1)
{
// Retry on timeout
await Task.Delay(100, cancellationToken);
continue;
}
}
if (string.IsNullOrEmpty(reply))
{
throw new TimeoutException($"No reply received after {retries} attempts");
}
return reply;
}
/// <summary>
/// Reads a reply from the scanner (ASCII format)
/// </summary>
private async Task<string> ReadReplyAsync(NetworkStream stream, int timeoutMs, CancellationToken cancellationToken)
{
var buffer = new byte[4096];
var totalBytes = 0;
var startTime = DateTime.UtcNow;
while (DateTime.UtcNow - startTime < TimeSpan.FromMilliseconds(timeoutMs))
{
if (stream.DataAvailable)
{
var bytesRead = await stream.ReadAsync(
buffer.AsMemory(totalBytes, buffer.Length - totalBytes),
cancellationToken);
if (bytesRead == 0)
break;
totalBytes += bytesRead;
// Check if we have complete message (ends with ETX)
if (totalBytes > 0 && buffer[totalBytes - 1] == 0x03)
break;
}
else
{
await Task.Delay(10, cancellationToken);
}
}
if (totalBytes == 0)
throw new TimeoutException("No reply from scanner");
// Parse reply (skip STX, remove ETX)
var replyStart = buffer[0] == 0x02 ? 1 : 0;
var replyEnd = buffer[totalBytes - 1] == 0x03 ? totalBytes - 1 : totalBytes;
var replyLength = replyEnd - replyStart;
if (replyLength <= 0)
return string.Empty;
return Encoding.ASCII.GetString(buffer, replyStart, replyLength);
}
/// <summary>
/// Reads scan data telegram from scanner (for event-based reception)
/// </summary>
public override async Task<byte[]?> ReceiveTelegramAsync(int timeoutMs, CancellationToken cancellationToken = default)
{
ObjectDisposedException.ThrowIf(_disposed, nameof(ColaASession));
NetworkStream? stream;
lock (_lock)
{
if (!IsOpen || _networkStream == null)
return null;
stream = _networkStream;
}
var buffer = new byte[480000]; // Large buffer for scan data
var totalBytes = 0;
var startTime = DateTime.UtcNow;
while (DateTime.UtcNow - startTime < TimeSpan.FromMilliseconds(timeoutMs))
{
if (stream.DataAvailable)
{
var bytesRead = await stream.ReadAsync(
buffer.AsMemory(totalBytes, buffer.Length - totalBytes),
cancellationToken);
if (bytesRead == 0)
break;
totalBytes += bytesRead;
// Check if we have complete message (ends with ETX)
if (totalBytes > 0 && buffer[totalBytes - 1] == 0x03)
{
// Return complete telegram (including STX and ETX)
return buffer[..totalBytes];
}
}
else
{
await Task.Delay(10, cancellationToken);
}
}
if (totalBytes == 0)
return null;
// Return partial data if available
return buffer[..totalBytes];
}
public override void Dispose()
{
if (_disposed)
return;
Close();
_disposed = true;
}
}