Initial commit

This commit is contained in:
2026-07-13 09:25:40 +07:00
parent c08ff54676
commit bccfb156d7
1938 changed files with 641646 additions and 0 deletions

View File

@@ -0,0 +1,22 @@
namespace Sick.SafetyScanners.DataStructures;
/// <summary>
/// Bundles application input and output data
/// </summary>
public sealed class ApplicationData
{
/// <summary>
/// Gets or sets the application inputs
/// </summary>
public ApplicationInputs Inputs { get; init; } = new ApplicationInputs();
/// <summary>
/// Gets or sets the application outputs
/// </summary>
public ApplicationOutputs Outputs { get; init; } = new ApplicationOutputs();
/// <summary>
/// Gets whether application data is empty (not enabled)
/// </summary>
public bool IsEmpty { get; init; }
}

View File

@@ -0,0 +1,62 @@
namespace Sick.SafetyScanners.DataStructures;
/// <summary>
/// Contains application inputs from a UDP data packet
/// </summary>
public sealed class ApplicationInputs
{
/// <summary>
/// Gets or sets the unsafe input sources (bits represent current state of static input sources)
/// </summary>
public IReadOnlyList<bool> UnsafeInputsInputSources { get; init; } = Array.Empty<bool>();
/// <summary>
/// Gets or sets the flags for unsafe input sources (one flag per static input source)
/// </summary>
public IReadOnlyList<bool> UnsafeInputsFlags { get; init; } = Array.Empty<bool>();
/// <summary>
/// Gets or sets the monitoring case numbers
/// </summary>
public IReadOnlyList<ushort> MonitoringCases { get; init; } = Array.Empty<ushort>();
/// <summary>
/// Gets or sets the monitoring case flags
/// </summary>
public IReadOnlyList<bool> MonitoringCaseFlags { get; init; } = Array.Empty<bool>();
/// <summary>
/// Gets or sets the first linear velocity input
/// </summary>
public short Velocity0 { get; init; }
/// <summary>
/// Gets or sets the second linear velocity input
/// </summary>
public short Velocity1 { get; init; }
/// <summary>
/// Gets or sets whether first linear velocity input is valid
/// </summary>
public bool IsVelocity0Valid { get; init; }
/// <summary>
/// Gets or sets whether second linear velocity input is valid
/// </summary>
public bool IsVelocity1Valid { get; init; }
/// <summary>
/// Gets or sets whether first linear velocity input is transmitted safely
/// </summary>
public bool IsVelocity0TransmittedSafely { get; init; }
/// <summary>
/// Gets or sets whether second linear velocity input is transmitted safely
/// </summary>
public bool IsVelocity1TransmittedSafely { get; init; }
/// <summary>
/// Gets or sets the state of the sleep mode input
/// </summary>
public sbyte SleepModeInput { get; init; }
}

View File

@@ -0,0 +1,38 @@
namespace Sick.SafetyScanners.DataStructures;
/// <summary>
/// Contains the application name from a COLA2 variable command response
/// </summary>
public sealed class ApplicationName
{
/// <summary>
/// Gets or sets the version indicator (e.g., "V" for version)
/// </summary>
public string VersionCVersion { get; init; } = string.Empty;
/// <summary>
/// Gets or sets the major version number
/// </summary>
public byte VersionMajorVersionNumber { get; init; }
/// <summary>
/// Gets or sets the minor version number
/// </summary>
public byte VersionMinorVersionNumber { get; init; }
/// <summary>
/// Gets or sets the version release number
/// </summary>
public byte VersionReleaseNumber { get; init; }
/// <summary>
/// Gets or sets the length of the application name
/// </summary>
public uint NameLength { get; init; }
/// <summary>
/// Gets or sets the application name string
/// </summary>
public string Name { get; init; } = string.Empty;
}

View File

@@ -0,0 +1,117 @@
namespace Sick.SafetyScanners.DataStructures;
/// <summary>
/// Contains application outputs from a UDP data packet
/// </summary>
public sealed class ApplicationOutputs
{
/// <summary>
/// Gets or sets the state of the non-safe cut-off paths (eval out)
/// </summary>
public IReadOnlyList<bool> EvalOut { get; init; } = Array.Empty<bool>();
/// <summary>
/// Gets or sets whether a cut-off path from the output paths is safe
/// </summary>
public IReadOnlyList<bool> EvalOutIsSafe { get; init; } = Array.Empty<bool>();
/// <summary>
/// Gets or sets whether the output path is valid
/// </summary>
public IReadOnlyList<bool> EvalOutIsValid { get; init; } = Array.Empty<bool>();
/// <summary>
/// Gets or sets the currently active monitoring case numbers
/// </summary>
public IReadOnlyList<ushort> MonitoringCases { get; init; } = Array.Empty<ushort>();
/// <summary>
/// Gets or sets whether the corresponding monitoring case number is valid
/// </summary>
public IReadOnlyList<bool> MonitoringCaseFlags { get; init; } = Array.Empty<bool>();
/// <summary>
/// Gets or sets the state of the sleep mode output
/// </summary>
public sbyte SleepModeOutput { get; init; }
/// <summary>
/// Gets or sets whether a contamination warning is present
/// </summary>
public bool HostErrorFlagContaminationWarning { get; init; }
/// <summary>
/// Gets or sets whether a contamination error is present
/// </summary>
public bool HostErrorFlagContaminationError { get; init; }
/// <summary>
/// Gets or sets whether a manipulation error is present
/// </summary>
public bool HostErrorFlagManipulationError { get; init; }
/// <summary>
/// Gets or sets whether glare is present
/// </summary>
public bool HostErrorFlagGlare { get; init; }
/// <summary>
/// Gets or sets whether a reference contour is intruded
/// </summary>
public bool HostErrorFlagReferenceContourIntruded { get; init; }
/// <summary>
/// Gets or sets whether a critical error is present
/// </summary>
public bool HostErrorFlagCriticalError { get; init; }
/// <summary>
/// Gets or sets the first linear velocity output
/// </summary>
public short Velocity0 { get; init; }
/// <summary>
/// Gets or sets the second linear velocity output
/// </summary>
public short Velocity1 { get; init; }
/// <summary>
/// Gets or sets whether the first linear velocity output is valid
/// </summary>
public bool IsVelocity0Valid { get; init; }
/// <summary>
/// Gets or sets whether the second linear velocity output is valid
/// </summary>
public bool IsVelocity1Valid { get; init; }
/// <summary>
/// Gets or sets whether the first linear velocity output is transmitted safely
/// </summary>
public bool IsVelocity0TransmittedSafely { get; init; }
/// <summary>
/// Gets or sets whether the second linear velocity output is transmitted safely
/// </summary>
public bool IsVelocity1TransmittedSafely { get; init; }
/// <summary>
/// Gets or sets the resulting velocity for each monitoring case table
/// </summary>
public IReadOnlyList<short> ResultingVelocity { get; init; } = Array.Empty<short>();
/// <summary>
/// Gets or sets whether the resulting velocities are valid
/// </summary>
public IReadOnlyList<bool> ResultingVelocityIsValid { get; init; } = Array.Empty<bool>();
/// <summary>
/// Gets or sets whether the sleep mode is valid
/// </summary>
public bool FlagsSleepModeOutputIsValid { get; init; }
/// <summary>
/// Gets or sets whether the error flags are valid
/// </summary>
public bool FlagsHostErrorFlagsAreValid { get; init; }
}

View File

@@ -0,0 +1,150 @@
namespace Sick.SafetyScanners.DataStructures;
/// <summary>
/// Communication settings used to configure the SICK Safety Scanner
/// This structure is used with ChangeCommSettingsCommand to configure scanner parameters
/// </summary>
public sealed class CommSettings
{
/// <summary>
/// Gets or sets the channel number (0-3)
/// </summary>
public byte Channel { get; init; }
/// <summary>
/// Gets or sets the publishing frequency (publish every n-th scan)
/// Example: 1 = publish every scan, 2 = publish every 2nd scan (half frequency)
/// </summary>
public ushort PublishingFrequency { get; init; } = 1;
/// <summary>
/// Gets or sets the interface type
/// 0: EFI-pro, 1: EtherNet/IP, 3: Profinet, 4: Non-safe Ethernet
/// </summary>
public InterfaceType EInterfaceType { get; init; }
/// <summary>
/// Gets or sets the start angle in radians
/// If start and end angles are equal, all angles are regarded
/// </summary>
public double StartAngle { get; init; }
/// <summary>
/// Gets or sets the end angle in radians
/// If start and end angles are equal, all angles are regarded
/// </summary>
public double EndAngle { get; init; }
/// <summary>
/// Gets or sets the enabled features as a bitset (ushort)
/// Use SensorDataFeatures constants or SensorDataFeatures.ToFeatureFlags() to set
/// </summary>
public ushort Features { get; init; } = SensorDataFeatures.All;
/// <summary>
/// Gets or sets whether general system state feature is enabled
/// </summary>
public bool GeneralSystemStateEnabled { get; init; } = true;
/// <summary>
/// Gets or sets whether derived settings feature is enabled
/// </summary>
public bool DerivedSettingsEnabled { get; init; } = true;
/// <summary>
/// Gets or sets whether measurement data feature is enabled
/// </summary>
public bool MeasurementDataEnabled { get; init; } = true;
/// <summary>
/// Gets or sets whether intrusion data feature is enabled
/// </summary>
public bool IntrusionDataEnabled { get; init; } = true;
/// <summary>
/// Gets or sets whether application data feature is enabled
/// </summary>
public bool ApplicationDataEnabled { get; init; } = true;
/// <summary>
/// Gets or sets whether the channel is enabled
/// </summary>
public bool Enabled { get; init; } = true;
/// <summary>
/// Gets or sets the host UDP port (0 = auto-assign)
/// </summary>
public ushort HostUdpPort { get; init; }
/// <summary>
/// Gets or sets the host IP address as a string (e.g., "192.168.1.100")
/// </summary>
public string HostIp { get; init; } = "192.168.1.100";
/// <summary>
/// Creates a CommSettings with all features enabled
/// </summary>
public static CommSettings CreateDefault()
{
return new CommSettings
{
Channel = 0,
PublishingFrequency = 1,
EInterfaceType = InterfaceType.NonSafeEthernet,
StartAngle = 0.0,
EndAngle = 0.0, // Equal to start angle means all angles
Features = SensorDataFeatures.All,
GeneralSystemStateEnabled = true,
DerivedSettingsEnabled = true,
MeasurementDataEnabled = true,
IntrusionDataEnabled = true,
ApplicationDataEnabled = true,
Enabled = true,
HostUdpPort = 0,
HostIp = "192.168.1.100"
};
}
/// <summary>
/// Creates a CommSettings with custom feature flags
/// </summary>
public static CommSettings Create(
byte channel,
string hostIp,
ushort hostUdpPort,
bool generalSystemState = true,
bool derivedSettings = true,
bool measurementData = true,
bool intrusionData = true,
bool applicationData = true,
ushort publishingFrequency = 1,
double startAngle = 0.0,
double endAngle = 0.0,
InterfaceType interfaceType = InterfaceType.NonSafeEthernet,
bool enabled = true)
{
return new CommSettings
{
Channel = channel,
PublishingFrequency = publishingFrequency,
EInterfaceType = interfaceType,
StartAngle = startAngle,
EndAngle = endAngle,
Features = SensorDataFeatures.ToFeatureFlags(
generalSystemState,
derivedSettings,
measurementData,
intrusionData,
applicationData),
GeneralSystemStateEnabled = generalSystemState,
DerivedSettingsEnabled = derivedSettings,
MeasurementDataEnabled = measurementData,
IntrusionDataEnabled = intrusionData,
ApplicationDataEnabled = applicationData,
Enabled = enabled,
HostUdpPort = hostUdpPort,
HostIp = hostIp
};
}
}

View File

@@ -0,0 +1,133 @@
namespace Sick.SafetyScanners.DataStructures;
/// <summary>
/// Contains the configuration data from a COLA2 variable command response
/// Used for current and persistent sensor configuration
/// </summary>
public sealed class ConfigData
{
/// <summary>
/// Gets or sets the version indicator (e.g., "V" for version)
/// </summary>
public string VersionCVersion { get; init; } = string.Empty;
/// <summary>
/// Gets or sets the major version number
/// </summary>
public byte VersionMajorVersionNumber { get; init; }
/// <summary>
/// Gets or sets the minor version number
/// </summary>
public byte VersionMinorVersionNumber { get; init; }
/// <summary>
/// Gets or sets the version release number
/// </summary>
public byte VersionReleaseNumber { get; init; }
/// <summary>
/// Gets or sets the host IP address
/// </summary>
public string HostIp { get; init; } = string.Empty;
/// <summary>
/// Gets or sets the host UDP port
/// </summary>
public ushort HostUdpPort { get; init; }
/// <summary>
/// Gets or sets the channel number (0-3)
/// </summary>
public byte Channel { get; init; }
/// <summary>
/// Gets or sets whether the channel is enabled
/// </summary>
public bool Enabled { get; init; }
/// <summary>
/// Gets or sets the interface type
/// </summary>
public InterfaceType EInterfaceType { get; init; }
/// <summary>
/// Gets or sets the publishing frequency (publish every n-th scan)
/// </summary>
public ushort PublishingFrequency { get; init; }
/// <summary>
/// Gets or sets the start angle in radians
/// </summary>
public double StartAngle { get; init; }
/// <summary>
/// Gets or sets the end angle in radians
/// </summary>
public double EndAngle { get; init; }
/// <summary>
/// Gets or sets the enabled features (bit flags)
/// Bit 0: GeneralSystemState, Bit 1: DerivedSettings, Bit 2: MeasurementData,
/// Bit 3: IntrusionData, Bit 4: ApplicationData
/// </summary>
public ushort Features { get; init; }
/// <summary>
/// Gets or sets whether general system state feature is enabled
/// </summary>
public bool GeneralSystemStateEnabled { get; init; }
/// <summary>
/// Gets or sets whether derived settings feature is enabled
/// </summary>
public bool DerivedSettingsEnabled { get; init; }
/// <summary>
/// Gets or sets whether measurement data feature is enabled
/// </summary>
public bool MeasurementDataEnabled { get; init; }
/// <summary>
/// Gets or sets whether intrusion data feature is enabled
/// </summary>
public bool IntrusionDataEnabled { get; init; }
/// <summary>
/// Gets or sets whether application data feature is enabled
/// </summary>
public bool ApplicationDataEnabled { get; init; }
// Derived values (similar to DerivedValues structure)
/// <summary>
/// Gets or sets the multiplication factor for beam distances
/// </summary>
public ushort DerivedMultiplicationFactor { get; init; }
/// <summary>
/// Gets or sets the number of beams
/// </summary>
public ushort DerivedNumberOfBeams { get; init; }
/// <summary>
/// Gets or sets the scan time in milliseconds
/// </summary>
public ushort DerivedScanTime { get; init; }
/// <summary>
/// Gets or sets the derived start angle in radians
/// </summary>
public double DerivedStartAngle { get; init; }
/// <summary>
/// Gets or sets the angular beam resolution in radians
/// </summary>
public double DerivedAngularBeamResolution { get; init; }
/// <summary>
/// Gets or sets the interbeam period in microseconds
/// </summary>
public uint DerivedInterbeamPeriod { get; init; }
}

View File

@@ -0,0 +1,63 @@
namespace Sick.SafetyScanners.DataStructures;
/// <summary>
/// Contains the configuration metadata from a COLA2 variable command response
/// </summary>
public sealed class ConfigMetadata
{
/// <summary>
/// Gets or sets the version indicator (e.g., "V" for version)
/// </summary>
public string VersionCVersion { get; init; } = string.Empty;
/// <summary>
/// Gets or sets the major version number
/// </summary>
public byte VersionMajorVersionNumber { get; init; }
/// <summary>
/// Gets or sets the minor version number
/// </summary>
public byte VersionMinorVersionNumber { get; init; }
/// <summary>
/// Gets or sets the version release number
/// </summary>
public byte VersionReleaseNumber { get; init; }
/// <summary>
/// Gets or sets the modification time date (days since 1980-01-01)
/// </summary>
public ushort ModificationTimeDate { get; init; }
/// <summary>
/// Gets or sets the modification time (milliseconds since midnight)
/// </summary>
public uint ModificationTimeTime { get; init; }
/// <summary>
/// Gets or sets the transfer time date (days since 1980-01-01)
/// </summary>
public ushort TransferTimeDate { get; init; }
/// <summary>
/// Gets or sets the transfer time (milliseconds since midnight)
/// </summary>
public uint TransferTimeTime { get; init; }
/// <summary>
/// Gets or sets the application checksum
/// </summary>
public uint AppChecksum { get; init; }
/// <summary>
/// Gets or sets the overall checksum
/// </summary>
public uint OverallChecksum { get; init; }
/// <summary>
/// Gets or sets the integrity hash (array of uint32 values)
/// </summary>
public IReadOnlyList<uint> IntegrityHash { get; init; } = Array.Empty<uint>();
}

View File

@@ -0,0 +1,117 @@
namespace Sick.SafetyScanners.DataStructures;
/// <summary>
/// Contains the content of the data header of a UDP data packet
/// </summary>
public sealed class DataHeader
{
/// <summary>
/// Gets or sets the version indicator (capital letter 'V' or 'R' for releases)
/// </summary>
public byte VersionIndicator { get; init; }
/// <summary>
/// Gets or sets the major version number
/// </summary>
public byte VersionMajor { get; init; }
/// <summary>
/// Gets or sets the minor version number
/// </summary>
public byte VersionMinor { get; init; }
/// <summary>
/// Gets or sets the release of the version
/// </summary>
public byte VersionRelease { get; init; }
/// <summary>
/// Gets or sets the serial number of the device
/// </summary>
public uint SerialNumberOfDevice { get; init; }
/// <summary>
/// Gets or sets the serial number of the system plug
/// </summary>
public uint SerialNumberOfSystemPlug { get; init; }
/// <summary>
/// Gets or sets the channel number (0-3)
/// </summary>
public byte ChannelNumber { get; init; }
/// <summary>
/// Gets or sets the sequence number (increases with each measurement)
/// </summary>
public uint SequenceNumber { get; init; }
/// <summary>
/// Gets or sets the scan number
/// </summary>
public uint ScanNumber { get; init; }
/// <summary>
/// Gets or sets the timestamp date
/// </summary>
public ushort TimestampDate { get; init; }
/// <summary>
/// Gets or sets the timestamp time
/// </summary>
public uint TimestampTime { get; init; }
/// <summary>
/// Gets or sets the general system state block offset
/// </summary>
public ushort GeneralSystemStateBlockOffset { get; init; }
/// <summary>
/// Gets or sets the general system state block size
/// </summary>
public ushort GeneralSystemStateBlockSize { get; init; }
/// <summary>
/// Gets or sets the derived values block offset
/// </summary>
public ushort DerivedValuesBlockOffset { get; init; }
/// <summary>
/// Gets or sets the derived values block size
/// </summary>
public ushort DerivedValuesBlockSize { get; init; }
/// <summary>
/// Gets or sets the measurement data block offset
/// </summary>
public ushort MeasurementDataBlockOffset { get; init; }
/// <summary>
/// Gets or sets the measurement data block size
/// </summary>
public ushort MeasurementDataBlockSize { get; init; }
/// <summary>
/// Gets or sets the intrusion data block offset
/// </summary>
public ushort IntrusionDataBlockOffset { get; init; }
/// <summary>
/// Gets or sets the intrusion data block size
/// </summary>
public ushort IntrusionDataBlockSize { get; init; }
/// <summary>
/// Gets or sets the application data block offset
/// </summary>
public ushort ApplicationDataBlockOffset { get; init; }
/// <summary>
/// Gets or sets the application data block size
/// </summary>
public ushort ApplicationDataBlockSize { get; init; }
/// <summary>
/// Gets whether the data header is empty
/// </summary>
public bool IsEmpty { get; init; }
}

View File

@@ -0,0 +1,53 @@
namespace Sick.SafetyScanners.DataStructures;
/// <summary>
/// Contains the contents of a UDP datagram header
/// Used to match datagrams together to form a complete data packet
/// </summary>
public sealed class DatagramHeader
{
/// <summary>
/// Size of the datagram header (24 bytes)
/// </summary>
public const int HeaderSize = 24;
/// <summary>
/// Gets or sets the datagram marker
/// </summary>
public uint DatagramMarker { get; init; }
/// <summary>
/// Gets or sets the protocol
/// </summary>
public ushort Protocol { get; init; }
/// <summary>
/// Gets or sets the major version number
/// </summary>
public byte MajorVersion { get; init; }
/// <summary>
/// Gets or sets the minor version number
/// </summary>
public byte MinorVersion { get; init; }
/// <summary>
/// Gets or sets the total length of the data packet (excluding headers)
/// Total length of the (possibly fragmented) measurement data instance
/// </summary>
public uint TotalLength { get; init; }
/// <summary>
/// Gets or sets the identification of the data
/// Datagrams (fragments) that belong to the same measurement data output instance share the same identifier
/// The number increases with each measurement data instance generated per channel
/// </summary>
public uint Identification { get; init; }
/// <summary>
/// Gets or sets the fragment offset (in bytes)
/// Offset of the measurement data carried in this datagram (fragment) relative to the start of the overall measurement data output instance
/// </summary>
public uint FragmentOffset { get; init; }
}

View File

@@ -0,0 +1,47 @@
namespace Sick.SafetyScanners.DataStructures;
/// <summary>
/// Contains the derived configuration of the measurement data channel
/// </summary>
public sealed class DerivedValues
{
/// <summary>
/// Angle resolution constant to convert sensor input to the right frame (4194304.0)
/// </summary>
public const double AngleResolution = 4194304.0;
/// <summary>
/// Gets or sets the multiplication factor to be applied to beam distance values to get distance in millimeter
/// </summary>
public ushort MultiplicationFactor { get; init; }
/// <summary>
/// Gets or sets the number of beams of the current scan
/// </summary>
public ushort NumberOfBeams { get; init; }
/// <summary>
/// Gets or sets the time of the scan (ms)
/// </summary>
public ushort ScanTime { get; init; }
/// <summary>
/// Gets or sets the start angle of the scan (radians)
/// </summary>
public double StartAngle { get; init; }
/// <summary>
/// Gets or sets the angular resolution between beams (radians)
/// </summary>
public double AngularBeamResolution { get; init; }
/// <summary>
/// Gets or sets the time between consecutive beams (microseconds)
/// </summary>
public uint InterbeamPeriod { get; init; }
/// <summary>
/// Gets whether derived values are empty (not enabled)
/// </summary>
public bool IsEmpty { get; init; }
}

View File

@@ -0,0 +1,13 @@
namespace Sick.SafetyScanners.DataStructures;
/// <summary>
/// Contains the device name from a COLA2 variable command response
/// </summary>
public sealed class DeviceName
{
/// <summary>
/// Gets or sets the device name string
/// </summary>
public string Name { get; init; } = string.Empty;
}

View File

@@ -0,0 +1,64 @@
namespace Sick.SafetyScanners.DataStructures;
/// <summary>
/// Device status enumeration for SICK Safety Scanner
/// </summary>
public enum SopasDeviceStatus : byte
{
/// <summary>
/// Unknown status
/// </summary>
Unknown = 0,
/// <summary>
/// Start up status
/// </summary>
StartUp = 1,
/// <summary>
/// Service mode
/// </summary>
ServiceMode = 2,
/// <summary>
/// Normal operation
/// </summary>
NormalOperation = 3,
/// <summary>
/// Suspended operation
/// </summary>
SuspendedOperation = 4,
/// <summary>
/// Service recommended
/// </summary>
ServiceRecommended = 5,
/// <summary>
/// Service required
/// </summary>
ServiceRequired = 6,
/// <summary>
/// Recoverable error
/// </summary>
RecoverableError = 7,
/// <summary>
/// Fatal error
/// </summary>
FatalError = 8
}
/// <summary>
/// Contains the device status from a COLA2 variable command response
/// </summary>
public sealed class DeviceStatus
{
/// <summary>
/// Gets or sets the device status value
/// </summary>
public SopasDeviceStatus Status { get; init; }
}

View File

@@ -0,0 +1,99 @@
namespace Sick.SafetyScanners.DataStructures;
/// <summary>
/// Contains field data for warning and protective fields from a COLA2 variable command response
/// </summary>
public sealed class FieldData
{
/// <summary>
/// Gets or sets whether the field data is valid
/// </summary>
public bool IsValid { get; init; }
/// <summary>
/// Gets or sets the version indicator (e.g., "V" for version)
/// </summary>
public string VersionCVersion { get; init; } = string.Empty;
/// <summary>
/// Gets or sets the major version number
/// </summary>
public byte VersionMajorVersionNumber { get; init; }
/// <summary>
/// Gets or sets the minor version number
/// </summary>
public byte VersionMinorVersionNumber { get; init; }
/// <summary>
/// Gets or sets the version release number
/// </summary>
public byte VersionReleaseNumber { get; init; }
/// <summary>
/// Gets or sets whether the field data is defined
/// </summary>
public bool IsDefined { get; init; }
/// <summary>
/// Gets or sets the evaluation method
/// </summary>
public byte EvalMethod { get; init; }
/// <summary>
/// Gets or sets the multiple sampling value
/// </summary>
public ushort MultiSampling { get; init; }
/// <summary>
/// Gets or sets the object resolution
/// </summary>
public ushort ObjectResolution { get; init; }
/// <summary>
/// Gets or sets the index of the field set this field belongs to
/// </summary>
public ushort FieldSetIndex { get; init; }
/// <summary>
/// Gets or sets the length of the field name
/// </summary>
public uint NameLength { get; init; }
/// <summary>
/// Gets or sets the field name string
/// </summary>
public string FieldName { get; init; } = string.Empty;
/// <summary>
/// Gets or sets whether this is a warning field
/// </summary>
public bool IsWarningField { get; init; }
/// <summary>
/// Gets or sets whether this is a protective field
/// </summary>
public bool IsProtectiveField { get; init; }
/// <summary>
/// Gets or sets the beam distances vector (in mm)
/// One distance value per beam
/// </summary>
public IReadOnlyList<ushort> BeamDistances { get; init; } = Array.Empty<ushort>();
/// <summary>
/// Gets or sets the start angle in radians
/// </summary>
public double StartAngle { get; init; }
/// <summary>
/// Gets or sets the end angle in radians
/// </summary>
public double EndAngle { get; init; }
/// <summary>
/// Gets or sets the angular beam resolution in radians
/// </summary>
public double AngularBeamResolution { get; init; }
}

View File

@@ -0,0 +1,43 @@
namespace Sick.SafetyScanners.DataStructures;
/// <summary>
/// Contains field sets data from a COLA2 variable command response
/// </summary>
public sealed class FieldSets
{
/// <summary>
/// Gets or sets the version indicator (e.g., "V" for version)
/// </summary>
public string VersionCVersion { get; init; } = string.Empty;
/// <summary>
/// Gets or sets the major version number
/// </summary>
public byte VersionMajorVersionNumber { get; init; }
/// <summary>
/// Gets or sets the minor version number
/// </summary>
public byte VersionMinorVersionNumber { get; init; }
/// <summary>
/// Gets or sets the version release number
/// </summary>
public byte VersionReleaseNumber { get; init; }
/// <summary>
/// Gets or sets the length of each field name (one per field set)
/// </summary>
public IReadOnlyList<uint> NameLengths { get; init; } = [];
/// <summary>
/// Gets or sets the field names (one per field set)
/// </summary>
public IReadOnlyList<string> FieldNames { get; init; } = [];
/// <summary>
/// Gets or sets whether each field set is defined
/// </summary>
public IReadOnlyList<bool> IsDefined { get; init; } = [];
}

View File

@@ -0,0 +1,13 @@
namespace Sick.SafetyScanners.DataStructures;
/// <summary>
/// Contains the firmware version from a COLA2 variable command response
/// </summary>
public sealed class FirmwareVersion
{
/// <summary>
/// Gets or sets the firmware version string
/// </summary>
public string Version { get; init; } = string.Empty;
}

View File

@@ -0,0 +1,87 @@
namespace Sick.SafetyScanners.DataStructures;
/// <summary>
/// Contains the general system state including run/standby modes, cut-off paths, monitoring cases, and errors
/// </summary>
public sealed class GeneralSystemState
{
/// <summary>
/// Gets or sets whether run mode is active
/// </summary>
public bool IsRunModeActive { get; init; }
/// <summary>
/// Gets or sets whether standby mode is active
/// </summary>
public bool IsStandbyModeActive { get; init; }
/// <summary>
/// Gets or sets whether a contamination warning exists
/// </summary>
public bool HasContaminationWarning { get; init; }
/// <summary>
/// Gets or sets whether a contamination error exists
/// </summary>
public bool HasContaminationError { get; init; }
/// <summary>
/// Gets or sets whether the reference contour status is true
/// </summary>
public bool ReferenceContourStatus { get; init; }
/// <summary>
/// Gets or sets whether manipulation status is set to true
/// </summary>
public bool ManipulationStatus { get; init; }
/// <summary>
/// Gets or sets the state for all safe cut-off paths
/// </summary>
public IReadOnlyList<bool> SafeCutOffPaths { get; init; } = [];
/// <summary>
/// Gets or sets the state of all non-safe cut-off paths
/// </summary>
public IReadOnlyList<bool> NonSafeCutOffPaths { get; init; } = [];
/// <summary>
/// Gets or sets whether a cut-off path has to be reset
/// </summary>
public IReadOnlyList<bool> ResetRequiredCutOffPaths { get; init; } = [];
/// <summary>
/// Gets or sets the current monitoring case number for table 1
/// </summary>
public byte CurrentMonitoringCaseNoTable1 { get; init; }
/// <summary>
/// Gets or sets the current monitoring case number for table 2
/// </summary>
public byte CurrentMonitoringCaseNoTable2 { get; init; }
/// <summary>
/// Gets or sets the current monitoring case number for table 3
/// </summary>
public byte CurrentMonitoringCaseNoTable3 { get; init; }
/// <summary>
/// Gets or sets the current monitoring case number for table 4
/// </summary>
public byte CurrentMonitoringCaseNoTable4 { get; init; }
/// <summary>
/// Gets or sets whether an application error exists
/// </summary>
public bool HasApplicationError { get; init; }
/// <summary>
/// Gets or sets whether a device error exists
/// </summary>
public bool HasDeviceError { get; init; }
/// <summary>
/// Gets whether general system state is empty (not enabled)
/// </summary>
public bool IsEmpty { get; init; }
}

View File

@@ -0,0 +1,17 @@
namespace Sick.SafetyScanners.DataStructures;
/// <summary>
/// Contains all intrusion data
/// </summary>
public sealed class IntrusionData
{
/// <summary>
/// Gets or sets all intrusion datums
/// </summary>
public IReadOnlyList<IntrusionDatum> IntrusionDatums { get; init; } = Array.Empty<IntrusionDatum>();
/// <summary>
/// Gets whether intrusion data is empty (not enabled)
/// </summary>
public bool IsEmpty { get; init; }
}

View File

@@ -0,0 +1,17 @@
namespace Sick.SafetyScanners.DataStructures;
/// <summary>
/// Represents a single intrusion datum
/// </summary>
public sealed class IntrusionDatum
{
/// <summary>
/// Gets or sets the size of the flag vector
/// </summary>
public int Size { get; init; }
/// <summary>
/// Gets or sets the flags vector (one flag per beam indicating intrusion)
/// </summary>
public IReadOnlyList<bool> Flags { get; init; } = Array.Empty<bool>();
}

View File

@@ -0,0 +1,29 @@
namespace Sick.SafetyScanners.DataStructures;
/// <summary>
/// Contains all scan points of a single measurement
/// </summary>
public sealed class MeasurementData
{
/// <summary>
/// Gets the number of beams in this measurement
/// </summary>
public uint NumberOfBeams { get; init; }
/// <summary>
/// Gets all scan points
/// </summary>
public IReadOnlyList<ScanPoint> ScanPoints { get; init; }
/// <summary>
/// Gets whether measurement data is empty (not enabled)
/// </summary>
public bool IsEmpty { get; init; }
public MeasurementData(uint numberOfBeams, IReadOnlyList<ScanPoint> scanPoints, bool isEmpty = false)
{
NumberOfBeams = numberOfBeams;
ScanPoints = scanPoints ?? Array.Empty<ScanPoint>();
IsEmpty = isEmpty;
}
}

View File

@@ -0,0 +1,28 @@
namespace Sick.SafetyScanners.DataStructures;
/// <summary>
/// Contains monitoring case data from a COLA2 variable command response
/// </summary>
public sealed class MonitoringCaseData
{
/// <summary>
/// Gets or sets whether the monitoring case data is valid
/// </summary>
public bool IsValid { get; init; }
/// <summary>
/// Gets or sets the monitoring case number
/// </summary>
public ushort MonitoringCaseNumber { get; init; }
/// <summary>
/// Gets or sets the field indices associated with this monitoring case
/// </summary>
public IReadOnlyList<ushort> FieldIndices { get; init; } = Array.Empty<ushort>();
/// <summary>
/// Gets or sets whether each field is configured and valid
/// </summary>
public IReadOnlyList<bool> FieldsValid { get; init; } = Array.Empty<bool>();
}

View File

@@ -0,0 +1,13 @@
namespace Sick.SafetyScanners.DataStructures;
/// <summary>
/// Contains the order number from a COLA2 variable command response
/// </summary>
public sealed class OrderNumber
{
/// <summary>
/// Gets or sets the order number string
/// </summary>
public string Number { get; init; } = string.Empty;
}

View File

@@ -0,0 +1,116 @@
namespace Sick.SafetyScanners.DataStructures;
/// <summary>
/// Packet buffer for COLA2 communication
/// Thread-safe wrapper around byte buffer - matches C++ shared_ptr<vector const> semantics
/// </summary>
public sealed class PacketBuffer : IDisposable
{
private readonly byte[] _buffer;
private readonly int _length;
private bool _disposed;
/// <summary>
/// Maximum size of packet buffer (matches C++ MAXSIZE = 10000)
/// </summary>
public const int MaxSize = 10000;
/// <summary>
/// Creates a new packet buffer from byte array
/// </summary>
public PacketBuffer(byte[] buffer, int length)
{
if (buffer == null)
throw new ArgumentNullException(nameof(buffer));
if (length < 0 || length > buffer.Length)
throw new ArgumentOutOfRangeException(nameof(length));
if (length > MaxSize)
throw new ArgumentException($"Length {length} exceeds MaxSize {MaxSize}", nameof(length));
// Copy buffer to ensure immutability (like C++ shared_ptr<vector const>)
_buffer = new byte[length];
Array.Copy(buffer, 0, _buffer, 0, length);
_length = length;
}
/// <summary>
/// Creates a new packet buffer from ReadOnlyMemory
/// </summary>
public PacketBuffer(ReadOnlyMemory<byte> memory)
{
if (memory.Length > MaxSize)
throw new ArgumentException($"Length {memory.Length} exceeds MaxSize {MaxSize}", nameof(memory));
_buffer = memory.ToArray();
_length = _buffer.Length;
}
/// <summary>
/// Creates a new packet buffer from ReadOnlySpan
/// </summary>
public PacketBuffer(ReadOnlySpan<byte> span)
{
if (span.Length > MaxSize)
throw new ArgumentException($"Length {span.Length} exceeds MaxSize {MaxSize}", nameof(span));
_buffer = span.ToArray();
_length = _buffer.Length;
}
/// <summary>
/// Gets the buffer as ReadOnlyMemory (zero-copy if possible)
/// </summary>
public ReadOnlyMemory<byte> GetBuffer()
{
if (_disposed)
throw new ObjectDisposedException(nameof(PacketBuffer));
return new ReadOnlyMemory<byte>(_buffer, 0, _length);
}
/// <summary>
/// Gets the buffer as ReadOnlySpan (zero-copy)
/// </summary>
public ReadOnlySpan<byte> GetBufferSpan()
{
if (_disposed)
throw new ObjectDisposedException(nameof(PacketBuffer));
return new ReadOnlySpan<byte>(_buffer, 0, _length);
}
/// <summary>
/// Gets the length of the buffer
/// </summary>
public int Length
{
get
{
if (_disposed)
throw new ObjectDisposedException(nameof(PacketBuffer));
return _length;
}
}
/// <summary>
/// Gets a copy of the buffer as byte array
/// </summary>
public byte[] ToArray()
{
if (_disposed)
throw new ObjectDisposedException(nameof(PacketBuffer));
var result = new byte[_length];
Array.Copy(_buffer, 0, result, 0, _length);
return result;
}
public void Dispose()
{
_disposed = true;
}
}

View File

@@ -0,0 +1,37 @@
namespace Sick.SafetyScanners.DataStructures;
/// <summary>
/// Represents a PacketBuffer with a parsed DatagramHeader
/// Used for merging fragmented UDP packets
/// </summary>
public sealed class ParsedPacketBuffer
{
/// <summary>
/// Gets the packet buffer
/// </summary>
public PacketBuffer PacketBuffer { get; init; }
/// <summary>
/// Gets the parsed datagram header
/// </summary>
public DatagramHeader DatagramHeader { get; init; }
public ParsedPacketBuffer(PacketBuffer packetBuffer, DatagramHeader datagramHeader)
{
PacketBuffer = packetBuffer ?? throw new ArgumentNullException(nameof(packetBuffer));
DatagramHeader = datagramHeader ?? throw new ArgumentNullException(nameof(datagramHeader));
}
/// <summary>
/// Compares two ParsedPacketBuffer instances by fragment offset for sorting
/// </summary>
public static int CompareByOffset(ParsedPacketBuffer x, ParsedPacketBuffer y)
{
if (x == null && y == null) return 0;
if (x == null) return -1;
if (y == null) return 1;
return x.DatagramHeader.FragmentOffset.CompareTo(y.DatagramHeader.FragmentOffset);
}
}

View File

@@ -0,0 +1,13 @@
namespace Sick.SafetyScanners.DataStructures;
/// <summary>
/// Contains the project name from a COLA2 variable command response
/// </summary>
public sealed class ProjectName
{
/// <summary>
/// Gets or sets the project name string
/// </summary>
public string Name { get; init; } = string.Empty;
}

View File

@@ -0,0 +1,49 @@
namespace Sick.SafetyScanners.DataStructures;
/// <summary>
/// Contains the required user action information from a COLA2 variable command response
/// Provides additional information about the SOPAS state
/// </summary>
public sealed class RequiredUserAction
{
/// <summary>
/// Gets or sets whether the configuration has to be confirmed
/// </summary>
public bool ConfirmConfiguration { get; init; }
/// <summary>
/// Gets or sets whether the configuration has to be checked
/// </summary>
public bool CheckConfiguration { get; init; }
/// <summary>
/// Gets or sets whether the environment has to be checked
/// </summary>
public bool CheckEnvironment { get; init; }
/// <summary>
/// Gets or sets whether the application interfaces have to be checked
/// </summary>
public bool CheckApplicationInterfaces { get; init; }
/// <summary>
/// Gets or sets whether the device has to be checked
/// </summary>
public bool CheckDevice { get; init; }
/// <summary>
/// Gets or sets whether the setup procedure has to be run
/// </summary>
public bool RunSetupProcedure { get; init; }
/// <summary>
/// Gets or sets whether the firmware has to be checked
/// </summary>
public bool CheckFirmware { get; init; }
/// <summary>
/// Gets or sets whether the user has to wait
/// </summary>
public bool Wait { get; init; }
}

View File

@@ -0,0 +1,66 @@
namespace Sick.SafetyScanners.DataStructures;
/// <summary>
/// Represents a single scan point from the SICK Safety Scanner
/// </summary>
public sealed class ScanPoint
{
/// <summary>
/// Gets the angle in sensor coordinates (radians)
/// </summary>
public double Angle { get; init; }
/// <summary>
/// Gets the distance of the measured scanpoint (mm)
/// </summary>
public ushort Distance { get; init; }
/// <summary>
/// Gets the reflectivity value (0-255)
/// </summary>
public byte Reflectivity { get; init; }
/// <summary>
/// Gets whether the scanpoint is valid
/// </summary>
public bool IsValid { get; init; }
/// <summary>
/// Gets whether the scanpoint is infinite (no object detected)
/// </summary>
public bool IsInfinite { get; init; }
/// <summary>
/// Gets whether there is glare in the scanpoint
/// </summary>
public bool HasGlare { get; init; }
/// <summary>
/// Gets whether the scanpoint detects a reflector
/// </summary>
public bool IsReflector { get; init; }
/// <summary>
/// Gets whether the scanpoint is contaminated
/// </summary>
public bool IsContaminated { get; init; }
/// <summary>
/// Gets whether there is a contamination warning
/// </summary>
public bool HasContaminationWarning { get; init; }
public ScanPoint(double angle, ushort distance, byte reflectivity, bool isValid,
bool isInfinite, bool hasGlare, bool isReflector, bool isContaminated, bool hasContaminationWarning)
{
Angle = angle;
Distance = distance;
Reflectivity = reflectivity;
IsValid = isValid;
IsInfinite = isInfinite;
HasGlare = hasGlare;
IsReflector = isReflector;
IsContaminated = isContaminated;
HasContaminationWarning = hasContaminationWarning;
}
}

View File

@@ -0,0 +1,80 @@
namespace Sick.SafetyScanners.DataStructures;
/// <summary>
/// Sensor data feature flags constants and helper methods
/// Used to configure which data blocks should be included in scan data output
/// </summary>
public static class SensorDataFeatures
{
/// <summary>
/// All features enabled (0b11111)
/// </summary>
public const ushort All = 0b11111;
/// <summary>
/// No features enabled
/// </summary>
public const ushort None = 0;
/// <summary>
/// General system state feature (bit 0)
/// </summary>
public const ushort GeneralSystemState = 1 << 0;
/// <summary>
/// Derived settings feature (bit 1)
/// </summary>
public const ushort DerivedSettings = 1 << 1;
/// <summary>
/// Measurement data feature (bit 2)
/// </summary>
public const ushort MeasurementData = 1 << 2;
/// <summary>
/// Intrusion data feature (bit 3)
/// </summary>
public const ushort IntrusionData = 1 << 3;
/// <summary>
/// Application data feature (bit 4)
/// </summary>
public const ushort ApplicationData = 1 << 4;
/// <summary>
/// Checks if a certain feature flag is set in the bitset
/// </summary>
/// <param name="bitset">The bitset expressed as ushort</param>
/// <param name="flag">The feature flag to check</param>
/// <returns>True if the flag is set, false otherwise</returns>
public static bool IsFlagSet(ushort bitset, ushort flag)
{
return (bitset & flag) == flag;
}
/// <summary>
/// Converts boolean indicators (for the sensor features to be streamed) into a bitset
/// </summary>
/// <param name="generalSystemState">Indicator for the general system state channel</param>
/// <param name="derivedSettings">Turn on/off derived values</param>
/// <param name="measurementData">Turn on/off measurement data</param>
/// <param name="intrusionData">Turn on/off safety field intrusion data</param>
/// <param name="applicationData">Turn on/off application data</param>
/// <returns>A bitset containing indication flags for each feature channel</returns>
public static ushort ToFeatureFlags(
bool generalSystemState,
bool derivedSettings,
bool measurementData,
bool intrusionData,
bool applicationData)
{
return (ushort)(
(generalSystemState ? GeneralSystemState : 0) +
(derivedSettings ? DerivedSettings : 0) +
(measurementData ? MeasurementData : 0) +
(intrusionData ? IntrusionData : 0) +
(applicationData ? ApplicationData : 0)
);
}
}

View File

@@ -0,0 +1,13 @@
namespace Sick.SafetyScanners.DataStructures;
/// <summary>
/// Contains the serial number from a COLA2 variable command response
/// </summary>
public sealed class SerialNumber
{
/// <summary>
/// Gets or sets the serial number string
/// </summary>
public string Number { get; init; } = string.Empty;
}

View File

@@ -0,0 +1,186 @@
namespace Sick.SafetyScanners.DataStructures;
/// <summary>
/// Device state enumeration for SICK Safety Scanner
/// </summary>
public enum DeviceState : byte
{
/// <summary>
/// Normal state
/// </summary>
Normal = 0,
/// <summary>
/// Error state
/// </summary>
Error = 1,
/// <summary>
/// Initialization state
/// </summary>
Initialization = 2,
/// <summary>
/// Shutdown state
/// </summary>
Shutdown = 3,
/// <summary>
/// Optics cover calibration state
/// </summary>
OpticsCoverCalibration = 4
}
/// <summary>
/// Config state enumeration for SICK Safety Scanner
/// </summary>
public enum ConfigState : byte
{
/// <summary>
/// Unknown config state
/// </summary>
Unknown = 0,
/// <summary>
/// Config required
/// </summary>
ConfigRequired = 1,
/// <summary>
/// Config in progress
/// </summary>
ConfigInProgress = 2,
/// <summary>
/// Not verified
/// </summary>
NotVerified = 3,
/// <summary>
/// Rejected
/// </summary>
Rejected = 4,
/// <summary>
/// Verified
/// </summary>
Verified = 5,
/// <summary>
/// Internal error
/// </summary>
InternalError = 6,
/// <summary>
/// Verification in progress
/// </summary>
VerificationInProgress = 7
}
/// <summary>
/// Application state enumeration for SICK Safety Scanner
/// </summary>
public enum ApplicationState : byte
{
/// <summary>
/// Application stopped
/// </summary>
Stopped = 0,
/// <summary>
/// Application starting
/// </summary>
Starting = 1,
/// <summary>
/// Waiting for partners
/// </summary>
WaitingForPartners = 2,
/// <summary>
/// Waiting for inputs
/// </summary>
WaitingForInputs = 3,
/// <summary>
/// Application started
/// </summary>
Started = 4,
/// <summary>
/// Sleep mode
/// </summary>
SleepMode = 5
}
/// <summary>
/// Contains the status overview from a COLA2 variable command response
/// </summary>
public sealed class StatusOverview
{
/// <summary>
/// Gets or sets the version indicator (e.g., "V" for version)
/// </summary>
public string VersionCVersion { get; init; } = string.Empty;
/// <summary>
/// Gets or sets the major version number
/// </summary>
public byte VersionMajorVersionNumber { get; init; }
/// <summary>
/// Gets or sets the minor version number
/// </summary>
public byte VersionMinorVersionNumber { get; init; }
/// <summary>
/// Gets or sets the version release number
/// </summary>
public byte VersionReleaseNumber { get; init; }
/// <summary>
/// Gets or sets the device state
/// </summary>
public DeviceState DeviceState { get; init; }
/// <summary>
/// Gets or sets the config state
/// </summary>
public ConfigState ConfigState { get; init; }
/// <summary>
/// Gets or sets the application state
/// </summary>
public ApplicationState ApplicationState { get; init; }
/// <summary>
/// Gets or sets the current time power on count
/// </summary>
public uint CurrentTimePowerOnCount { get; init; }
/// <summary>
/// Gets or sets the current time (milliseconds since midnight)
/// </summary>
public uint CurrentTimeTime { get; init; }
/// <summary>
/// Gets or sets the current date (days since 1980-01-01)
/// </summary>
public ushort CurrentTimeDate { get; init; }
/// <summary>
/// Gets or sets the error info code
/// </summary>
public uint ErrorInfoCode { get; init; }
/// <summary>
/// Gets or sets the error info time (milliseconds since midnight)
/// </summary>
public uint ErrorInfoTime { get; init; }
/// <summary>
/// Gets or sets the error info date (days since 1980-01-01)
/// </summary>
public ushort ErrorInfoDate { get; init; }
}

View File

@@ -0,0 +1,65 @@
namespace Sick.SafetyScanners.DataStructures;
/// <summary>
/// Interface type enumeration for SICK Safety Scanner
/// </summary>
public enum InterfaceType : byte
{
/// <summary>
/// EFI-pro interface
/// </summary>
EfiPro = 0,
/// <summary>
/// EtherNet/IP interface
/// </summary>
EthernetIp = 1,
/// <summary>
/// Profinet interface
/// </summary>
Profinet = 3,
/// <summary>
/// Non-safe Ethernet interface
/// </summary>
NonSafeEthernet = 4
}
/// <summary>
/// Range enumeration for SICK Safety Scanner
/// </summary>
public enum RangeType
{
/// <summary>
/// Normal range (40m)
/// </summary>
NormalRange = 40,
/// <summary>
/// Long range (64m)
/// </summary>
LongRange = 64
}
/// <summary>
/// Contains the type code from a COLA2 variable command response
/// </summary>
public sealed class TypeCode
{
/// <summary>
/// Gets or sets the type code string
/// </summary>
public string Code { get; init; } = string.Empty;
/// <summary>
/// Gets or sets the interface type
/// </summary>
public InterfaceType InterfaceType { get; init; }
/// <summary>
/// Gets or sets the maximum range in meters
/// </summary>
public double MaxRange { get; init; }
}

View File

@@ -0,0 +1,141 @@
namespace Sick.SafetyScanners.DataStructures;
/// <summary>
/// Contains all data blocks from a UDP scan data packet
/// </summary>
public sealed class UdpScanData
{
/// <summary>
/// Gets or sets the data header
/// </summary>
public DataHeader Header { get; init; } = new DataHeader();
/// <summary>
/// Gets or sets the general system state
/// </summary>
public GeneralSystemState? GeneralSystemState { get; init; }
/// <summary>
/// Gets or sets the derived values (configuration of data output)
/// </summary>
public DerivedValues? DerivedValues { get; init; }
/// <summary>
/// Gets or sets the measurement data (scan points)
/// </summary>
public MeasurementData? MeasurementData { get; init; }
/// <summary>
/// Gets or sets the intrusion data (field interruption)
/// </summary>
public IntrusionData? IntrusionData { get; init; }
/// <summary>
/// Gets or sets the application data (inputs and outputs)
/// </summary>
public ApplicationData? ApplicationData { get; init; }
/// <summary>
/// Gets the timestamp from the header as DateTime (if available)
/// Returns UTC DateTime to ensure consistency with Cartographer timestamp handling
///
/// SICK Timestamp Format (according to official documentation):
/// - TimestampDate (ushort, offset 24):
/// * If UTC server is time master: Days since 1972-01-01
/// * If Safety Designer device is time master: Number of full 24-hour cycles of time master
/// * If no time sync: Number of full 24-hour cycles since device was switched on
/// - TimestampTime (uint, offset 28): Milliseconds since midnight (or start of 24-hour cycle)
///
/// Since we cannot detect time sync status from UDP packet, we try both base dates:
/// 1. Try 1972-01-01 (UTC server case) - most common in production
/// 2. If result is unreasonable, try 1980-01-01 (legacy/fallback)
/// 3. If TimestampDate = 0: Use current date as base
/// </summary>
public DateTime? Timestamp
{
get
{
if (Header.TimestampDate == 0 && Header.TimestampTime == 0)
return null;
DateTime baseDate;
string baseDateSource = "unknown";
if (Header.TimestampDate == 0)
{
// Date not set on device - use current date as base
var now = DateTime.UtcNow;
baseDate = new DateTime(now.Year, now.Month, now.Day, 0, 0, 0, DateTimeKind.Utc);
baseDateSource = "current_date";
}
else
{
// Try 1972-01-01 first (UTC server case - most common according to documentation)
var baseDate1972 = new DateTime(1972, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var time1972 = baseDate1972.AddDays(Header.TimestampDate).AddMilliseconds(Header.TimestampTime);
// Check if timestamp with 1972 base is reasonable (within 1 day of now)
var currentTimeTicks = DateTime.UtcNow.Ticks;
var time1972Ticks = time1972.Ticks;
var diff1972Ms = Math.Abs(time1972Ticks - currentTimeTicks) / TimeSpan.TicksPerMillisecond;
if (diff1972Ms <= 86400000) // Within 1 day - reasonable
{
baseDate = baseDate1972;
baseDateSource = "1972-01-01 (UTC server)";
}
else
{
// Try 1980-01-01 as fallback (legacy format or no UTC sync)
var baseDate1980 = new DateTime(1980, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var time1980 = baseDate1980.AddDays(Header.TimestampDate).AddMilliseconds(Header.TimestampTime);
var time1980Ticks = time1980.Ticks;
var diff1980Ms = Math.Abs(time1980Ticks - currentTimeTicks) / TimeSpan.TicksPerMillisecond;
if (diff1980Ms <= diff1972Ms) // 1980 is closer to now
{
baseDate = baseDate1980;
baseDateSource = "1980-01-01 (legacy/fallback)";
}
else
{
// 1972 is closer, use it even if not perfect
baseDate = baseDate1972;
baseDateSource = "1972-01-01 (best match)";
}
}
}
// Add days and milliseconds to base date
var time = baseDate.AddDays(Header.TimestampDate).AddMilliseconds(Header.TimestampTime);
// Validate: timestamp should not be too far in the past or future
var nowTicks = DateTime.UtcNow.Ticks;
var timeTicks = time.Ticks;
var diffMs = Math.Abs(timeTicks - nowTicks) / TimeSpan.TicksPerMillisecond;
// Debug logging for timestamp calculation
if (diffMs > 3600000) // Log if more than 1 hour off
{
var timestampDateStr = Header.TimestampDate == 0 ? "0 (using current date)" : Header.TimestampDate.ToString();
var timestampTimeStr = $"{Header.TimestampTime}ms ({Header.TimestampTime / 3600000.0:F2} hours)";
var calculatedTime = time.ToString("yyyy-MM-dd HH:mm:ss.fff");
System.Diagnostics.Debug.WriteLine(
$"[SICK Timestamp Debug] TimestampDate={timestampDateStr}, TimestampTime={timestampTimeStr}, " +
$"BaseDateSource={baseDateSource}, CalculatedTime={calculatedTime}, " +
$"DiffFromNow={diffMs / 3600000.0:F2} hours");
}
// If timestamp is more than 1 day off, it's likely incorrect
// This can happen if device date is not set correctly or wrong base date
if (diffMs > 86400000) // 1 day in milliseconds
{
// Use current time instead of potentially incorrect device timestamp
return DateTime.UtcNow;
}
// Ensure the result is marked as UTC
return DateTime.SpecifyKind(time, DateTimeKind.Utc);
}
}
}

View File

@@ -0,0 +1,24 @@
namespace Sick.SafetyScanners.DataStructures;
/// <summary>
/// EventArgs for UdpScanDataReceived event
/// </summary>
public sealed class UdpScanDataEventArgs : EventArgs
{
/// <summary>
/// Gets the timestamp when scan data was received
/// </summary>
public DateTime Timestamp { get; init; }
/// <summary>
/// Gets the parsed UDP scan data
/// </summary>
public UdpScanData ScanData { get; init; }
public UdpScanDataEventArgs(DateTime timestamp, UdpScanData scanData)
{
Timestamp = timestamp;
ScanData = scanData ?? throw new ArgumentNullException(nameof(scanData));
}
}

View File

@@ -0,0 +1,38 @@
namespace Sick.SafetyScanners.DataStructures;
/// <summary>
/// Contains the user name from a COLA2 variable command response
/// </summary>
public sealed class UserName
{
/// <summary>
/// Gets or sets the version indicator (e.g., "V" for version)
/// </summary>
public string VersionCVersion { get; init; } = string.Empty;
/// <summary>
/// Gets or sets the major version number
/// </summary>
public byte VersionMajorVersionNumber { get; init; }
/// <summary>
/// Gets or sets the minor version number
/// </summary>
public byte VersionMinorVersionNumber { get; init; }
/// <summary>
/// Gets or sets the version release number
/// </summary>
public byte VersionReleaseNumber { get; init; }
/// <summary>
/// Gets or sets the length of the user name
/// </summary>
public uint NameLength { get; init; }
/// <summary>
/// Gets or sets the user name string
/// </summary>
public string Name { get; init; } = string.Empty;
}