Files
I150/srcs/RobotNet10/RobotApp/Communication/Sick.SafetyScanners/Cola2/Cola2Session.cs
2026-07-03 16:37:12 +07:00

360 lines
12 KiB
C#

using Sick.SafetyScanners.Cola2.Commands;
using Sick.SafetyScanners.DataProcessing;
using Sick.SafetyScanners.DataStructures;
using Sick.SafetyScanners.Exceptions;
using Sick.SafetyScanners.Helpers;
using Sick.SafetyScanners.Interfaces;
using Sick.SafetyScanners.Types;
namespace Sick.SafetyScanners.Cola2;
/// <summary>
/// COLA2 session manager for handling send, receive and process telegrams
/// Thread-safe implementation
/// </summary>
public sealed class Cola2Session : ICola2Session
{
private readonly ITcpClient _tcpClient;
private readonly ParseTcpPacket _packetParser;
private uint? _sessionId;
private ushort _requestId;
private readonly object _lock = new();
private bool _disposed;
public uint? SessionId
{
get
{
lock (_lock)
{
return _sessionId;
}
}
private set
{
lock (_lock)
{
_sessionId = value;
}
}
}
public bool IsOpen
{
get
{
// Fast path: read without lock first (volatile-like behavior)
var sessionId = _sessionId;
var isConnected = _tcpClient.IsConnected;
// Double-check with lock if needed
if (!isConnected || !sessionId.HasValue)
return false;
// Verify with lock to ensure consistency
lock (_lock)
{
return _tcpClient.IsConnected && _sessionId.HasValue;
}
}
}
/// <summary>
/// Creates a new COLA2 session
/// </summary>
public Cola2Session(ITcpClient tcpClient)
{
_tcpClient = tcpClient ?? throw new ArgumentNullException(nameof(tcpClient));
_packetParser = new ParseTcpPacket();
_requestId = 0;
}
public ushort GetNextRequestId()
{
lock (_lock)
{
return ++_requestId;
}
}
public void Open()
{
if (_disposed)
ObjectDisposedException.ThrowIf(_disposed, nameof(Cola2Session));
// Close existing session first if open (avoid deadlock by not locking)
if (IsOpen)
{
Close();
}
// Connect TCP if not connected
if (!_tcpClient.IsConnected)
{
_tcpClient.Connect(TimeDuration.FromSeconds(5));
}
// Create session command
var createSessionCmd = new CreateSessionCommand();
// Send command without session ID check
if (!createSessionCmd.CanBeExecutedWithoutSessionId)
{
throw new InvalidOperationException("CreateSession command must be executable without session ID");
}
// Manually set request ID
createSessionCmd.RequestId = GetNextRequestId();
createSessionCmd.SessionId = 0; // No session ID for CreateSession
// Assemble and send telegram (GỬI CreateSession command)
AssembleAndSendTelegram(createSessionCmd);
// Receive and process response (NHẬN response từ scanner)
var response = ReceiveAndProcessResponse(createSessionCmd,
TimeDuration.FromSeconds(5));
// Parse response
var parseResult = _packetParser.ParseTcpSequence(response);
// Process reply
createSessionCmd.ProcessReply(parseResult.Data, parseResult.CommandType, parseResult.CommandMode);
// Check for error code
if (parseResult.ErrorCode.HasValue && parseResult.ErrorCode.Value != 0)
{
throw new CommandException(
0,
parseResult.RequestId,
parseResult.CommandType,
parseResult.CommandMode,
parseResult.ErrorCode.Value,
$"CreateSession failed with error code: 0x{parseResult.ErrorCode.Value:X4}"
);
}
if (!createSessionCmd.WasSuccessful)
{
throw new SessionException("Failed to create COLA2 session");
}
// Extract session ID from packet header (not from reply data)
// In C++ reference, session ID is read from packet header (offset 10) by ParseTCPPacket
// and set to Command via setCommandValuesFromPacket(), then retrieved via getSessionID()
// In C# implementation, we get it directly from parseResult.SessionId
if (parseResult.SessionId == 0)
{
throw new SessionException("Failed to extract session ID from CreateSession reply: Session ID is 0");
}
SessionId = parseResult.SessionId;
}
public void Close()
{
ObjectDisposedException.ThrowIf(_disposed, nameof(Cola2Session));
lock (_lock)
{
if (!IsOpen)
{
// Already closed
return;
}
}
try
{
var closeSessionCmd = new CloseSessionCommand
{
SessionId = SessionId ?? 0
};
SendCommand(closeSessionCmd, TimeDuration.FromSeconds(5));
}
catch (Exception)
{
// Ignore errors during close
}
finally
{
SessionId = null;
_tcpClient.Disconnect();
}
}
public void SendCommand(ICola2Command command, TimeDuration? timeout = null)
{
ObjectDisposedException.ThrowIf(_disposed, nameof(Cola2Session));
ArgumentNullException.ThrowIfNull(command);
// Check if session is required
lock (_lock)
{
if (!command.CanBeExecutedWithoutSessionId && !IsOpen)
{
throw new SessionException("Attempt to send command in closed Cola2 session state");
}
// Set session ID and request ID
// In C++ reference: cmd.setSessionID(getSessionID().get_value_or(0));
// We must ensure session ID is set correctly before sending
if (!_sessionId.HasValue && !command.CanBeExecutedWithoutSessionId)
{
throw new SessionException($"Cannot send command: Session ID is not set. Session is open: {IsOpen}");
}
command.SessionId = _sessionId ?? 0;
command.RequestId = GetNextRequestId();
// Debug: Verify session ID is set (only for commands that require session)
if (!command.CanBeExecutedWithoutSessionId && command.SessionId == 0)
{
throw new SessionException($"Session ID is 0 for command that requires session. Current session state: IsOpen={IsOpen}, _sessionId={_sessionId}");
}
}
// Assemble and send telegram
AssembleAndSendTelegram(command);
// Receive and process response
var response = ReceiveAndProcessResponse(command, timeout);
// Parse response
var parseResult = _packetParser.ParseTcpSequence(response);
// Check error code FIRST - if error code is present and non-zero, it indicates an error
// even if command type/mode is correct (R/A)
// Error code format: 0x[VARIABLE_INDEX]00 typically means "variable index not found"
if (parseResult.ErrorCode.HasValue && parseResult.ErrorCode.Value != 0)
{
var errorCode = parseResult.ErrorCode.Value;
var errorMessage = GetErrorMessage(errorCode, command);
throw new CommandException(
SessionId ?? 0,
parseResult.RequestId,
parseResult.CommandType,
parseResult.CommandMode,
errorCode,
errorMessage
);
}
// Process reply (this checks command type/mode and sets WasSuccessful)
command.ProcessReply(parseResult.Data, parseResult.CommandType, parseResult.CommandMode);
// Check if processReply failed (command type/mode mismatch)
if (!command.WasSuccessful)
{
// When command type/mode is wrong, check for error code in data
ushort? errorCode = null;
if (parseResult.Data.Length >= 2)
{
// Error code might be at the start of data payload (offset 18 of packet)
errorCode = ReadWriteHelper.ReadUint16BigEndian(parseResult.Data.Span, 0);
}
throw new CommandException(SessionId ?? 0,
parseResult.RequestId,
parseResult.CommandType,
parseResult.CommandMode,
parseResult.ErrorCode ?? 0,
$"Command failed: processReply returned false. Expected 'R'/'A' (0x52/0x41), got 0x{parseResult.CommandType:X2}/0x{parseResult.CommandMode:X2}"
);
}
// Command was successful (command type/mode is 'R'/'A')
// The 2 bytes at offset 18-19 are part of the data payload (variable index for VariableCommand)
// NOT an error code - error code only exists when command fails
// No need to check error code when command is acknowledged
// Command was successful (processReply returned true and no error code or error code matches variable index)
}
private void AssembleAndSendTelegram(ICola2Command command)
{
if (command is not CommandBase cmdBase)
throw new ArgumentException("Command must be derived from CommandBase", nameof(command));
var telegram = cmdBase.ConstructTelegram();
_tcpClient.Send(telegram);
}
private PacketBuffer ReceiveAndProcessResponse(ICola2Command command,
TimeDuration? timeout)
{
var packetMerger = new TcpPacketMerger();
try
{
while (!packetMerger.IsComplete)
{
var packet = _tcpClient.Receive(timeout);
if (packetMerger.IsEmpty)
{
var expectedLength = _packetParser.GetExpectedPacketLength(packet);
packetMerger.SetTargetSize(expectedLength);
}
// AddTcpPacket returns true if complete
var isComplete = packetMerger.AddTcpPacket(packet);
if (isComplete)
{
break; // Exit loop early when complete
}
}
return packetMerger.GetDeployedBuffer();
}
finally
{
packetMerger.Dispose();
}
}
private string GetErrorMessage(ushort errorCode, ICola2Command command)
{
// Common COLA2 error codes
// 0x0D00 = Variable index not found or not supported
// 0x0001 = General error
// 0x0002 = Invalid parameter
// etc.
var baseMessage = $"Command failed with error code: 0x{errorCode:X4}";
if (command is VariableCommand varCmd)
{
baseMessage += $" (Variable Index: 0x{varCmd.VariableIndex:X4})";
// Common error codes for variable commands
if (errorCode == 0x0D00)
{
baseMessage += " - Variable index not found or not supported by this scanner model";
}
else if (errorCode == 0x0001)
{
baseMessage += " - General error";
}
else if (errorCode == 0x0002)
{
baseMessage += " - Invalid parameter";
}
}
return baseMessage;
}
public void Dispose()
{
if (_disposed)
return;
Close();
_tcpClient.Dispose();
_disposed = true;
}
}