Dreamine.IO.Fastech.Ethernet 1.0.1
Dreamine.IO.Fastech.Ethernet 산업 자동화 및 I/O 기능을 제공합니다.
로딩중...
검색중...
일치하는것 없음
FastechPlusE16PointProtocol.cs
이 파일의 문서화 페이지로 가기
1using Dreamine.IO.Abstractions.Models;
2using Dreamine.IO.Abstractions.Results;
3
5
15{
24 private const byte Header = 0xAA;
33 private const byte Reserved = 0x00;
42 private const byte StatusOk = 0x00;
51 private const byte GetInputFrameType = 0xC0;
60 private const byte GetOutputFrameType = 0xC5;
69 private const byte SetOutputFrameType = 0xC6;
78 private const byte GetSlaveInfoFrameType = 0x01;
87 private const uint Digital16PointResetMask = 0xFFFF_FFFF;
96 private const string AnalogInputNotSupportedMessage = "Fastech Ezi-IO Plus-E 16-point DIO protocol does not support analog input.";
105 private const string AnalogOutputNotSupportedMessage = "Fastech Ezi-IO Plus-E 16-point DIO protocol does not support analog output.";
114 private readonly object _syncLock = new();
123 private byte _syncNo;
124
141 public byte[] BuildGetSlaveInfo()
142 {
144 }
145
170 public IoResult<string> ParseSlaveInfo(IReadOnlyList<byte> responseFrame)
171 {
172 var payload = ParseReply(responseFrame, GetSlaveInfoFrameType, 1);
173 if (!payload.IsSuccess || payload.Value is null)
174 {
175 return IoResult<string>.Failure(payload.Message ?? "Failed to parse Fastech slave information response.", payload.ErrorCode);
176 }
177
178 var slaveType = payload.Value[0];
179 var name = payload.Value.Length > 1
180 ? System.Text.Encoding.ASCII.GetString(payload.Value, 1, payload.Value.Length - 1).TrimEnd('\0')
181 : string.Empty;
182
183 return IoResult<string>.Success($"SlaveType={slaveType}, Name={name}");
184 }
185
218 public byte[] BuildReadDigitalInputs(IReadOnlyList<IoPoint> points)
219 {
220 ArgumentNullException.ThrowIfNull(points);
221
222 return BuildFrame(GetInputFrameType, []);
223 }
224
257 public IoResult<bool[]> ParseDigitalInputs(IReadOnlyList<byte> responseFrame, int count)
258 {
259 var payload = ParseReply(responseFrame, GetInputFrameType, 8);
260 if (!payload.IsSuccess || payload.Value is null)
261 {
262 return IoResult<bool[]>.Failure(payload.Message ?? "Failed to parse Fastech input response.", payload.ErrorCode);
263 }
264
265 return IoResult<bool[]>.Success(ToInputBits(payload.Value, count));
266 }
267
300 public byte[] BuildReadDigitalOutputs(IReadOnlyList<IoPoint> points)
301 {
302 ArgumentNullException.ThrowIfNull(points);
303
304 return BuildFrame(GetOutputFrameType, []);
305 }
306
339 public IoResult<bool[]> ParseDigitalOutputs(IReadOnlyList<byte> responseFrame, int count)
340 {
341 var payload = ParseReply(responseFrame, GetOutputFrameType, 8);
342 if (!payload.IsSuccess || payload.Value is null)
343 {
344 return IoResult<bool[]>.Failure(payload.Message ?? "Failed to parse Fastech output response.", payload.ErrorCode);
345 }
346
347 return IoResult<bool[]>.Success(ToOutputBits(payload.Value, count));
348 }
349
390 public byte[] BuildWriteDigitalOutputs(IReadOnlyDictionary<IoPoint, bool> values)
391 {
392 ArgumentNullException.ThrowIfNull(values);
393
394 uint setMask = 0;
395 uint clearMask = Digital16PointResetMask;
396
397 foreach (var value in values)
398 {
399 if (value.Key.Channel is < 0 or > 15)
400 {
401 throw new ArgumentOutOfRangeException(nameof(values), value.Key.Channel, "Channel must be between 0 and 15.");
402 }
403
404 var bit = GetDigital16PointOutputWriteBit(value.Key.Channel);
405 if (value.Value)
406 {
407 setMask |= bit;
408 clearMask &= ~bit;
409 }
410 }
411
412 var data = new byte[8];
413 WriteUInt32BigEndian(setMask, data, 0);
414 WriteUInt32BigEndian(clearMask, data, 4);
415
416 return BuildFrame(SetOutputFrameType, data);
417 }
418
451 public byte[] BuildReadAnalogInputs(IReadOnlyList<AnalogIoPoint> points)
452 {
454 }
455
488 public IoResult<double[]> ParseAnalogInputs(IReadOnlyList<byte> responseFrame, int count)
489 {
491 }
492
525 public byte[] BuildReadAnalogOutputs(IReadOnlyList<AnalogIoPoint> points)
526 {
528 }
529
562 public IoResult<double[]> ParseAnalogOutputs(IReadOnlyList<byte> responseFrame, int count)
563 {
565 }
566
599 public byte[] BuildWriteAnalogOutputs(IReadOnlyDictionary<AnalogIoPoint, double> values)
600 {
602 }
603
628 public IoResult ParseWriteResponse(IReadOnlyList<byte> responseFrame)
629 {
630 var payload = ParseReply(responseFrame, SetOutputFrameType, 0);
631 return payload.IsSuccess
632 ? IoResult.Success()
633 : IoResult.Failure(payload.Message ?? "Failed to parse Fastech write response.", payload.ErrorCode);
634 }
635
676 private byte[] BuildFrame(byte frameType, IReadOnlyList<byte> data)
677 {
678 if (data.Count > 252)
679 {
680 throw new ArgumentOutOfRangeException(nameof(data), data.Count, "Fastech frame data must be 252 bytes or less.");
681 }
682
683 var frame = new byte[5 + data.Count];
684 frame[0] = Header;
685 frame[1] = checked((byte)(3 + data.Count));
686 frame[2] = NextSyncNo();
687 frame[3] = Reserved;
688 frame[4] = frameType;
689
690 for (var i = 0; i < data.Count; i++)
691 {
692 frame[5 + i] = data[i];
693 }
694
695 return frame;
696 }
697
722 private static NotSupportedException CreateNotSupportedException(string message)
723 {
724 return new NotSupportedException(message);
725 }
726
751 private static IoResult<double[]> CreateUnsupportedAnalogResult(string message)
752 {
753 return IoResult<double[]>.Failure(message);
754 }
755
804 private IoResult<byte[]> ParseReply(IReadOnlyList<byte> responseFrame, byte expectedFrameType, int minimumPayloadLength)
805 {
806 ArgumentNullException.ThrowIfNull(responseFrame);
807
808 if (responseFrame.Count < 6)
809 {
810 return IoResult<byte[]>.Failure("The Fastech response frame is too short.");
811 }
812
813 if (responseFrame[0] != Header)
814 {
815 return IoResult<byte[]>.Failure($"Invalid Fastech response header: 0x{responseFrame[0]:X2}.");
816 }
817
818 if (responseFrame[1] != responseFrame.Count - 2)
819 {
820 return IoResult<byte[]>.Failure($"Invalid Fastech response length: {responseFrame[1]}.");
821 }
822
823 if (responseFrame[3] != Reserved)
824 {
825 return IoResult<byte[]>.Failure($"Invalid Fastech response reserved byte: 0x{responseFrame[3]:X2}.");
826 }
827
828 if (responseFrame[4] != expectedFrameType)
829 {
830 return IoResult<byte[]>.Failure($"Unexpected Fastech response frame type: 0x{responseFrame[4]:X2}.");
831 }
832
833 var status = responseFrame[5];
834 if (status != StatusOk)
835 {
836 return IoResult<byte[]>.Failure($"Fastech communication status error: 0x{status:X2}.", status);
837 }
838
839 var payloadLength = responseFrame.Count - 6;
840 if (payloadLength < minimumPayloadLength)
841 {
842 return IoResult<byte[]>.Failure($"The Fastech response payload length is {payloadLength}, expected at least {minimumPayloadLength}.");
843 }
844
845 var payload = new byte[payloadLength];
846 for (var i = 0; i < payload.Length; i++)
847 {
848 payload[i] = responseFrame[6 + i];
849 }
850
851 return IoResult<byte[]>.Success(payload);
852 }
853
870 private byte NextSyncNo()
871 {
872 lock (_syncLock)
873 {
874 _syncNo++;
875 return _syncNo;
876 }
877 }
878
911 private static bool[] ToInputBits(IReadOnlyList<byte> payload, int count)
912 {
913 count = Math.Clamp(count, 0, 16);
914 var values = new bool[count];
915
916 for (var i = 0; i < values.Length; i++)
917 {
918 var byteOffset = i < 8 ? 0 : 1;
919 var bitIndex = i % 8;
920 values[i] = (payload[byteOffset] & (1 << bitIndex)) != 0;
921 }
922
923 return values;
924 }
925
958 private static bool[] ToOutputBits(IReadOnlyList<byte> payload, int count)
959 {
960 count = Math.Clamp(count, 0, 16);
961 var values = new bool[count];
962
963 for (var i = 0; i < values.Length; i++)
964 {
965 var byteOffset = i < 8 ? 2 : 3;
966 var bitIndex = i % 8;
967 values[i] = (payload[byteOffset] & (1 << bitIndex)) != 0;
968 }
969
970 return values;
971 }
972
997 private static uint GetDigital16PointOutputWriteBit(int channel)
998 {
999 var normalizedChannel = Math.Clamp(channel, 0, 15);
1000 var bitIndex = normalizedChannel < 8
1001 ? 8 + normalizedChannel
1002 : normalizedChannel - 8;
1003
1004 return 1u << bitIndex;
1005 }
1006
1039 private static uint ReadUInt32BigEndian(IReadOnlyList<byte> bytes, int offset)
1040 {
1041 return ((uint)bytes[offset] << 24)
1042 | ((uint)bytes[offset + 1] << 16)
1043 | ((uint)bytes[offset + 2] << 8)
1044 | bytes[offset + 3];
1045 }
1046
1079 private static void WriteUInt32BigEndian(uint value, byte[] bytes, int offset)
1080 {
1081 bytes[offset] = (byte)(value >> 24);
1082 bytes[offset + 1] = (byte)(value >> 16);
1083 bytes[offset + 2] = (byte)(value >> 8);
1084 bytes[offset + 3] = (byte)value;
1085 }
1086}
IoResult< byte[]> ParseReply(IReadOnlyList< byte > responseFrame, byte expectedFrameType, int minimumPayloadLength)
static uint ReadUInt32BigEndian(IReadOnlyList< byte > bytes, int offset)
static void WriteUInt32BigEndian(uint value, byte[] bytes, int offset)
IoResult< bool[]> ParseDigitalInputs(IReadOnlyList< byte > responseFrame, int count)
byte[] BuildWriteAnalogOutputs(IReadOnlyDictionary< AnalogIoPoint, double > values)
byte[] BuildWriteDigitalOutputs(IReadOnlyDictionary< IoPoint, bool > values)
IoResult< double[]> ParseAnalogOutputs(IReadOnlyList< byte > responseFrame, int count)
IoResult< string > ParseSlaveInfo(IReadOnlyList< byte > responseFrame)
IoResult< double[]> ParseAnalogInputs(IReadOnlyList< byte > responseFrame, int count)
IoResult< bool[]> ParseDigitalOutputs(IReadOnlyList< byte > responseFrame, int count)
static bool[] ToOutputBits(IReadOnlyList< byte > payload, int count)
static bool[] ToInputBits(IReadOnlyList< byte > payload, int count)