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; } }