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