using Sick.SafetyScanners.DataStructures;
using Sick.SafetyScanners.Helpers;
using System.Net;
namespace Sick.SafetyScanners.DataProcessing;
///
/// Parser for MeasurementCurrentConfigData response from COLA2 variable command
/// Thread-safe implementation
///
public sealed class ParseMeasurementCurrentConfigData
{
private const double AngleResolution = 4194304.0; // Sensor units per radian
///
/// Parses the current measurement config from a TCP sequence (COLA2 response)
/// Matches: ParseMeasurementCurrentConfigData::parseTCPSequence in C++
///
public ConfigData ParseTcpSequence(PacketBuffer buffer)
{
var span = buffer.GetBufferSpan();
var features = ReadFeatures(span);
return new ConfigData
{
VersionCVersion = ReadVersionIndicator(span),
VersionMajorVersionNumber = ReadMajorNumber(span),
VersionMinorVersionNumber = ReadMinorNumber(span),
VersionReleaseNumber = ReadReleaseNumber(span),
Enabled = ReadEnabled(span),
EInterfaceType = (InterfaceType)ReadInterfaceType(span),
HostIp = ReadHostIp(span),
HostUdpPort = ReadHostPort(span),
PublishingFrequency = ReadPublishingFreq(span),
StartAngle = ConvertToRadians(ReadStartAngle(span)),
EndAngle = ConvertToRadians(ReadEndAngle(span)),
Features = features,
GeneralSystemStateEnabled = SensorDataFeatures.IsFlagSet(features, SensorDataFeatures.GeneralSystemState),
DerivedSettingsEnabled = SensorDataFeatures.IsFlagSet(features, SensorDataFeatures.DerivedSettings),
MeasurementDataEnabled = SensorDataFeatures.IsFlagSet(features, SensorDataFeatures.MeasurementData),
IntrusionDataEnabled = SensorDataFeatures.IsFlagSet(features, SensorDataFeatures.IntrusionData),
ApplicationDataEnabled = SensorDataFeatures.IsFlagSet(features, SensorDataFeatures.ApplicationData),
DerivedMultiplicationFactor = ReadDerivedMultiplicationFactor(span),
DerivedNumberOfBeams = ReadDerivedNumBeams(span),
DerivedScanTime = ReadDerivedScanTime(span),
DerivedStartAngle = ConvertToRadians(ReadDerivedStartAngle(span)),
DerivedAngularBeamResolution = ConvertToRadians(ReadDerivedAngularBeamResolution(span)),
DerivedInterbeamPeriod = ReadDerivedInterbeamPeriod(span)
};
}
///
/// Matches: ParseMeasurementCurrentConfigData::readVersionIndicator in C++
///
private string ReadVersionIndicator(ReadOnlySpan span)
{
byte ch = ReadWriteHelper.ReadUint8(span, 0);
return ((char)ch).ToString();
}
///
/// Matches: ParseMeasurementCurrentConfigData::readMajorNumber in C++
///
private byte ReadMajorNumber(ReadOnlySpan span)
{
return ReadWriteHelper.ReadUint8(span, 1);
}
///
/// Matches: ParseMeasurementCurrentConfigData::readMinorNumber in C++
///
private byte ReadMinorNumber(ReadOnlySpan span)
{
return ReadWriteHelper.ReadUint8(span, 2);
}
///
/// Matches: ParseMeasurementCurrentConfigData::readReleaseNumber in C++
///
private byte ReadReleaseNumber(ReadOnlySpan span)
{
return ReadWriteHelper.ReadUint8(span, 3);
}
///
/// Matches: ParseMeasurementCurrentConfigData::readEnabled in C++
///
private bool ReadEnabled(ReadOnlySpan span)
{
return ReadWriteHelper.ReadUint8(span, 4) != 0;
}
///
/// Matches: ParseMeasurementCurrentConfigData::readInterfaceType in C++
///
private byte ReadInterfaceType(ReadOnlySpan span)
{
return ReadWriteHelper.ReadUint8(span, 5);
}
private string ReadHostIp(ReadOnlySpan span)
{
// IPAddress constructor expects bytes in network byte order (big endian)
// word is little endian: [b0, b1, b2, b3] represents IP b0.b1.b2.b3
// We need to extract bytes in order: b0, b1, b2, b3
var address = new IPAddress([span[11], span[10], span[9], span[8]]);
return address.ToString();
}
private ushort ReadHostPort(ReadOnlySpan span)
{
return ReadWriteHelper.ReadUint16LittleEndian(span, 12);
}
private ushort ReadPublishingFreq(ReadOnlySpan span)
{
return ReadWriteHelper.ReadUint16LittleEndian(span, 14);
}
private uint ReadStartAngle(ReadOnlySpan span)
{
return ReadWriteHelper.ReadUint32LittleEndian(span, 16);
}
private uint ReadEndAngle(ReadOnlySpan span)
{
return ReadWriteHelper.ReadUint32LittleEndian(span, 20);
}
private ushort ReadFeatures(ReadOnlySpan span)
{
return ReadWriteHelper.ReadUint16LittleEndian(span, 24);
}
private ushort ReadDerivedMultiplicationFactor(ReadOnlySpan span)
{
return ReadWriteHelper.ReadUint16LittleEndian(span, 28);
}
private ushort ReadDerivedNumBeams(ReadOnlySpan span)
{
return ReadWriteHelper.ReadUint16LittleEndian(span, 30);
}
private ushort ReadDerivedScanTime(ReadOnlySpan span)
{
return ReadWriteHelper.ReadUint16LittleEndian(span, 32);
}
private uint ReadDerivedStartAngle(ReadOnlySpan span)
{
return ReadWriteHelper.ReadUint32LittleEndian(span, 36);
}
private uint ReadDerivedAngularBeamResolution(ReadOnlySpan span)
{
return ReadWriteHelper.ReadUint32LittleEndian(span, 40);
}
private uint ReadDerivedInterbeamPeriod(ReadOnlySpan span)
{
return ReadWriteHelper.ReadUint32LittleEndian(span, 44);
}
///
/// Converts sensor units to radians
/// According to COLA2 documentation: "This value, divided by 4194304, equals the actual start angle"
/// According to C++ reference: value / 4194304.0 gives degrees (same as setDerivedAngularBeamResolutionDegrees)
/// So we convert: sensorUnits / 4194304.0 = degrees, then degrees * π/180 = radians
///
private double ConvertToRadians(uint sensorUnits)
{
var degrees = (sensorUnits / AngleResolution);
return (degrees * Math.PI / 180.0);
}
}