using Sick.SafetyScanners.Cola2.Commands;
using Sick.SafetyScanners.Helpers;
namespace Sick.SafetyScanners.Cola2.Commands;
///
/// Base class for all COLA2 commands
/// Thread-safe implementation
///
public abstract class CommandBase : ICola2Command
{
private readonly byte _commandType;
private readonly byte _commandMode;
private uint _sessionId;
private ushort _requestId;
private bool _wasSuccessful;
private readonly List _dataVector;
private readonly object _lock = new();
protected CommandBase(byte commandType, byte commandMode)
{
_commandType = commandType;
_commandMode = commandMode;
_dataVector = new List();
}
public byte CommandType => _commandType;
public byte CommandMode => _commandMode;
public uint SessionId
{
get
{
lock (_lock)
{
return _sessionId;
}
}
set
{
lock (_lock)
{
_sessionId = value;
}
}
}
public ushort RequestId
{
get
{
lock (_lock)
{
return _requestId;
}
}
set
{
lock (_lock)
{
_requestId = value;
}
}
}
public bool WasSuccessful
{
get
{
lock (_lock)
{
return _wasSuccessful;
}
}
protected set
{
lock (_lock)
{
_wasSuccessful = value;
}
}
}
public abstract bool CanBeExecutedWithoutSessionId { get; }
///
/// Gets the data vector for the command payload
///
public ReadOnlyMemory GetDataVector()
{
lock (_lock)
{
return _dataVector.ToArray();
}
}
///
/// Sets the data vector
///
protected void SetDataVector(ReadOnlyMemory data)
{
lock (_lock)
{
_dataVector.Clear();
_dataVector.AddRange(data.ToArray());
}
}
///
/// Adds data to the data vector
///
protected void AddData(ReadOnlyMemory data)
{
lock (_lock)
{
_dataVector.AddRange(data.ToArray());
}
}
///
/// Constructs the complete telegram including header
///
public byte[] ConstructTelegram()
{
lock (_lock)
{
var data = AddTelegramData();
return AddTelegramHeader(data);
}
}
///
/// Adds command-specific data to the telegram
///
protected abstract ReadOnlyMemory AddTelegramData();
///
/// Processes the reply from the sensor
/// In C++ reference, ParseTCPPacket::parseTCPSequence calls command.setDataVector(byte_vector)
/// to store the reply data in the command. We need to do the same here.
///
public bool ProcessReply(ReadOnlyMemory replyData, byte replyCommandType, byte replyCommandMode)
{
lock (_lock)
{
// Store reply data in _dataVector (matching C++ reference behavior)
// In C++: command.setDataVector(byte_vector) is called by ParseTCPPacket
SetDataVector(replyData);
_wasSuccessful = ProcessReplyInternal(replyData, replyCommandType, replyCommandMode);
return _wasSuccessful;
}
}
///
/// Internal method to process reply (implemented by derived classes)
///
protected abstract bool ProcessReplyInternal(ReadOnlyMemory replyData,
byte replyCommandType, byte replyCommandMode);
///
/// Adds the COLA2 header to the telegram
///
private byte[] AddTelegramHeader(ReadOnlyMemory data)
{
const int headerSize = 18;
var totalLength = headerSize + data.Length;
var telegram = new byte[totalLength];
var span = telegram.AsSpan();
// STX (4 bytes): 0x02020202
ReadWriteHelper.WriteUint32BigEndian(span, 0, 0x02020202);
// Length (4 bytes): 10 + data.Length
ReadWriteHelper.WriteUint32BigEndian(span, 4, (uint)(10 + data.Length));
// HubCntr (1 byte): 0x00
ReadWriteHelper.WriteUint8BigEndian(span, 8, 0x00);
// NoC (1 byte): 0x00
ReadWriteHelper.WriteUint8BigEndian(span, 9, 0x00);
// Session ID (4 bytes)
ReadWriteHelper.WriteUint32BigEndian(span, 10, _sessionId);
// Request ID (2 bytes)
ReadWriteHelper.WriteUint16BigEndian(span, 14, _requestId);
// Command Type (1 byte)
ReadWriteHelper.WriteUint8BigEndian(span, 16, _commandType);
// Command Mode (1 byte)
ReadWriteHelper.WriteUint8BigEndian(span, 17, _commandMode);
// Copy data
data.CopyTo(telegram.AsMemory(headerSize));
return telegram;
}
}