Dreamine.PLC.Omron.Fins 1.0.1
Dreamine.PLC.Omron.Fins 산업 자동화 및 I/O 기능을 제공합니다.
로딩중...
검색중...
일치하는것 없음
OmronFinsSimulatorProtocol.cs
이 파일의 문서화 페이지로 가기
1using Dreamine.PLC.Abstractions.Devices;
2using Dreamine.PLC.Abstractions.Results;
3using Dreamine.PLC.Core.Memory;
6
8
17public sealed class OmronFinsSimulatorProtocol
18{
27 private readonly InMemoryPlcMemory _memory;
37
70 public OmronFinsSimulatorProtocol(InMemoryPlcMemory memory, OmronFinsSimulatorServerOptions options)
71 {
72 _memory = memory ?? throw new ArgumentNullException(nameof(memory));
73 _options = options ?? throw new ArgumentNullException(nameof(options));
74 }
75
84 public event EventHandler<string>? StatusChanged;
85
118 public byte[] HandleRequest(IReadOnlyList<byte> requestFrame)
119 {
120 ArgumentNullException.ThrowIfNull(requestFrame);
121 var request = requestFrame as byte[] ?? requestFrame.ToArray();
122
123 if (!TryParseRequest(request, out var parsed, out var errorMessage))
124 {
125 StatusChanged?.Invoke(this, errorMessage ?? "Invalid FINS request.");
126 return BuildErrorResponse(request, 0x1101);
127 }
128
129 return parsed.Command switch
130 {
131 (ushort)OmronFinsCommand.MemoryAreaRead => HandleRead(request, parsed),
132 (ushort)OmronFinsCommand.MemoryAreaWrite => HandleWrite(request, parsed),
133 _ => BuildErrorResponse(request, 0x0101)
134 };
135 }
136
169 private byte[] HandleRead(byte[] requestFrame, FinsRequest request)
170 {
171 if (request.IsBitAccess)
172 {
173 var readResult = _memory.ReadBits(request.Address, request.Count);
174 if (!readResult.IsSuccess || readResult.Value is null)
175 {
176 return BuildErrorResponse(requestFrame, 0x2101);
177 }
178
179 var payload = readResult.Value.Select(value => value ? (byte)1 : (byte)0).ToArray();
180 return BuildResponse(requestFrame, request.Command, 0, payload);
181 }
182
183 var wordResult = _memory.ReadWords(request.Address, request.Count);
184 if (!wordResult.IsSuccess || wordResult.Value is null)
185 {
186 return BuildErrorResponse(requestFrame, 0x2101);
187 }
188
189 var wordPayload = new byte[wordResult.Value.Length * 2];
190 for (var index = 0; index < wordResult.Value.Length; index++)
191 {
192 OmronFinsEndian.WriteInt16(wordPayload, index * 2, wordResult.Value[index]);
193 }
194
195 return BuildResponse(requestFrame, request.Command, 0, wordPayload);
196 }
197
238 private byte[] HandleWrite(byte[] requestFrame, FinsRequest request)
239 {
240 if (request.IsBitAccess)
241 {
242 if (request.Data.Length < request.Count)
243 {
244 return BuildErrorResponse(requestFrame, 0x1101);
245 }
246
247 var values = new bool[request.Count];
248 for (var index = 0; index < request.Count; index++)
249 {
250 values[index] = request.Data[index] != 0;
251 }
252
253 var writeResult = _memory.WriteBits(request.Address, values);
254 if (!writeResult.IsSuccess)
255 {
256 return BuildErrorResponse(requestFrame, 0x2101);
257 }
258 }
259 else
260 {
261 var expectedLength = checked(request.Count * 2);
262 if (request.Data.Length < expectedLength)
263 {
264 return BuildErrorResponse(requestFrame, 0x1101);
265 }
266
267 var values = new short[request.Count];
268 for (var index = 0; index < request.Count; index++)
269 {
270 values[index] = OmronFinsEndian.ReadInt16(request.Data, index * 2);
271 }
272
273 var writeResult = _memory.WriteWords(request.Address, values);
274 if (!writeResult.IsSuccess)
275 {
276 return BuildErrorResponse(requestFrame, 0x2101);
277 }
278
279 ApplyAutoWordResponse(request.Address, values);
280 }
281
282 return BuildResponse(requestFrame, request.Command, 0, []);
283 }
284
309 private void ApplyAutoWordResponse(PlcAddress writeAddress, IReadOnlyList<short> values)
310 {
311 if (!_options.EnableAutoWordResponse || values.Count != 1)
312 {
313 return;
314 }
315
316 if (writeAddress.DeviceType != PlcDeviceType.D || writeAddress.Offset != _options.AutoResponseTriggerOffset)
317 {
318 return;
319 }
320
321 var rawResponseValue = values[0] + _options.AutoResponseIncrement;
322 if (rawResponseValue is < short.MinValue or > short.MaxValue)
323 {
324 StatusChanged?.Invoke(this, $"FINS auto response skipped: value overflow. value={rawResponseValue}");
325 return;
326 }
327
328 var responseValue = (short)rawResponseValue;
329 var responseAddress = new PlcAddress(PlcDeviceType.D, _options.AutoResponseOffset);
330 _memory.WriteWords(responseAddress, [responseValue]);
331 StatusChanged?.Invoke(this, $"FINS auto response: D{_options.AutoResponseOffset}={responseValue}");
332 }
333
374 private static bool TryParseRequest(byte[] frame, out FinsRequest request, out string? errorMessage)
375 {
376 request = default;
377 errorMessage = null;
378
379 if (frame.Length < 18)
380 {
381 errorMessage = $"The FINS request frame is too short. Length={frame.Length}.";
382 return false;
383 }
384
385 var command = OmronFinsEndian.ReadUInt16(frame, 10);
386 var areaCode = frame[12];
387 var offset = OmronFinsEndian.ReadUInt16(frame, 13);
388 var bitOffset = frame[15];
389 var count = OmronFinsEndian.ReadUInt16(frame, 16);
390 var data = frame.Length > 18 ? frame[18..] : [];
391 var deviceType = OmronFinsMemoryAreaMapper.ToDeviceType(areaCode);
392
393 if (deviceType == PlcDeviceType.Unknown)
394 {
395 errorMessage = $"Unsupported FINS memory area code: 0x{areaCode:X2}.";
396 return false;
397 }
398
399 request = new FinsRequest(
400 command,
401 areaCode,
402 new PlcAddress(deviceType, offset, OmronFinsMemoryAreaMapper.IsBitArea(areaCode) ? bitOffset : null),
403 count,
404 data);
405 return true;
406 }
407
440 private static byte[] BuildErrorResponse(byte[] requestFrame, ushort endCode)
441 {
442 var command = requestFrame.Length >= 12 ? OmronFinsEndian.ReadUInt16(requestFrame, 10) : (ushort)0;
443 return BuildResponse(requestFrame, command, endCode, []);
444 }
445
502 private static byte[] BuildResponse(byte[] requestFrame, ushort command, ushort endCode, IReadOnlyList<byte> payload)
503 {
504 var response = new byte[14 + payload.Count];
505 response[0] = requestFrame.Length > 0 ? requestFrame[0] : (byte)0xC0;
506 response[1] = requestFrame.Length > 1 ? requestFrame[1] : (byte)0x00;
507 response[2] = requestFrame.Length > 2 ? requestFrame[2] : (byte)0x02;
508
509 response[3] = requestFrame.Length > 6 ? requestFrame[6] : (byte)0x00;
510 response[4] = requestFrame.Length > 7 ? requestFrame[7] : (byte)0x00;
511 response[5] = requestFrame.Length > 8 ? requestFrame[8] : (byte)0x00;
512 response[6] = requestFrame.Length > 3 ? requestFrame[3] : (byte)0x00;
513 response[7] = requestFrame.Length > 4 ? requestFrame[4] : (byte)0x00;
514 response[8] = requestFrame.Length > 5 ? requestFrame[5] : (byte)0x00;
515 response[9] = requestFrame.Length > 9 ? requestFrame[9] : (byte)0x00;
516
517 OmronFinsEndian.WriteUInt16(response, 10, command);
518 OmronFinsEndian.WriteUInt16(response, 12, endCode);
519
520 for (var index = 0; index < payload.Count; index++)
521 {
522 response[14 + index] = payload[index];
523 }
524
525 return response;
526 }
527
576 private readonly record struct FinsRequest(
577 ushort Command,
578 byte AreaCode,
579 PlcAddress Address,
580 int Count,
581 byte[] Data)
582 {
592 }
593}
static void WriteUInt16(Span< byte > buffer, int offset, ushort value)
static short ReadInt16(ReadOnlySpan< byte > buffer, int offset)
static void WriteInt16(Span< byte > buffer, int offset, short value)
static ushort ReadUInt16(ReadOnlySpan< byte > buffer, int offset)
OmronFinsSimulatorProtocol(InMemoryPlcMemory memory, OmronFinsSimulatorServerOptions options)
static byte[] BuildResponse(byte[] requestFrame, ushort command, ushort endCode, IReadOnlyList< byte > payload)
void ApplyAutoWordResponse(PlcAddress writeAddress, IReadOnlyList< short > values)
static bool TryParseRequest(byte[] frame, out FinsRequest request, out string? errorMessage)
static byte[] BuildErrorResponse(byte[] requestFrame, ushort endCode)
byte[] HandleWrite(byte[] requestFrame, FinsRequest request)
byte[] HandleRead(byte[] requestFrame, FinsRequest request)