Dreamine.PLC.Mitsubishi.MC 1.0.1
Dreamine.PLC.Mitsubishi.MC 산업 자동화 및 I/O 기능을 제공합니다.
로딩중...
검색중...
일치하는것 없음
MitsubishiMcBinary3ESimulatorProtocol.cs
이 파일의 문서화 페이지로 가기
1using Dreamine.PLC.Abstractions.Devices;
2using Dreamine.PLC.Abstractions.Results;
3using Dreamine.PLC.Core.Memory;
5
7
17{
26 private const ushort RequestSubHeader = 0x5000;
35 private const ushort ResponseSubHeader = 0xD000;
44 private readonly InMemoryPlcMemory _memory;
54
86 InMemoryPlcMemory memory,
88 {
89 _memory = memory ?? throw new ArgumentNullException(nameof(memory));
90 _options = options ?? throw new ArgumentNullException(nameof(options));
91 }
92
101 public event EventHandler<string>? StatusChanged;
102
126 public byte[] Execute(ReadOnlySpan<byte> requestFrame)
127 {
128 if (!TryParseRequest(requestFrame, out var request, out var errorMessage))
129 {
130 StatusChanged?.Invoke(this, errorMessage ?? "Invalid MC request frame.");
131 return BuildErrorResponse(requestFrame, 0xC051);
132 }
133
134 return request.Command switch
135 {
136 (ushort)MitsubishiMcCommand.BatchRead => ExecuteRead(request),
137 (ushort)MitsubishiMcCommand.BatchWrite => ExecuteWrite(request),
138 _ => BuildResponse(request.Header, 0xC059, [])
139 };
140 }
141
164 private byte[] ExecuteRead(McRequest request)
165 {
166 if (request.Points <= 0)
167 {
168 return BuildResponse(request.Header, 0xC051, []);
169 }
170
171 var address = new PlcAddress(ToDeviceType(request.DeviceCode), request.DeviceOffset);
172
173 if (request.SubCommand == (ushort)MitsubishiMcSubCommand.Word)
174 {
175 var readResult = _memory.ReadWords(address, request.Points);
176 if (!readResult.IsSuccess || readResult.Value is null)
177 {
178 return BuildResponse(request.Header, 0xC051, []);
179 }
180
181 var data = new List<byte>(request.Points * 2);
182 foreach (var value in readResult.Value)
183 {
184 MitsubishiMcEndian.WriteUInt16LittleEndian(data, unchecked((ushort)value));
185 }
186
187 return BuildResponse(request.Header, 0x0000, data);
188 }
189
190 if (request.SubCommand == (ushort)MitsubishiMcSubCommand.Bit)
191 {
192 var readResult = _memory.ReadBits(address, request.Points);
193 if (!readResult.IsSuccess || readResult.Value is null)
194 {
195 return BuildResponse(request.Header, 0xC051, []);
196 }
197
198 var data = PackBits(readResult.Value);
199 return BuildResponse(request.Header, 0x0000, data);
200 }
201
202 return BuildResponse(request.Header, 0xC059, []);
203 }
204
227 private byte[] ExecuteWrite(McRequest request)
228 {
229 if (request.Points <= 0)
230 {
231 return BuildResponse(request.Header, 0xC051, []);
232 }
233
234 var address = new PlcAddress(ToDeviceType(request.DeviceCode), request.DeviceOffset);
235
236 if (request.SubCommand == (ushort)MitsubishiMcSubCommand.Word)
237 {
238 var requiredLength = request.Points * 2;
239 if (request.Data.Length < requiredLength)
240 {
241 return BuildResponse(request.Header, 0xC051, []);
242 }
243
244 var values = new short[request.Points];
245 for (var i = 0; i < request.Points; i++)
246 {
247 values[i] = unchecked((short)ReadUInt16LittleEndian(request.Data, i * 2));
248 }
249
250 var writeResult = _memory.WriteWords(address, values);
251 if (!writeResult.IsSuccess)
252 {
253 return BuildResponse(request.Header, 0xC051, []);
254 }
255
256 ApplyAutoWordResponse(request, values);
257 return BuildResponse(request.Header, 0x0000, []);
258 }
259
260 if (request.SubCommand == (ushort)MitsubishiMcSubCommand.Bit)
261 {
262 var requiredLength = (request.Points + 1) / 2;
263 if (request.Data.Length < requiredLength)
264 {
265 return BuildResponse(request.Header, 0xC051, []);
266 }
267
268 var values = UnpackBits(request.Data, request.Points);
269 var writeResult = _memory.WriteBits(address, values);
270 return writeResult.IsSuccess
271 ? BuildResponse(request.Header, 0x0000, [])
272 : BuildResponse(request.Header, 0xC051, []);
273 }
274
275 return BuildResponse(request.Header, 0xC059, []);
276 }
277
300 private void ApplyAutoWordResponse(McRequest request, IReadOnlyList<short> values)
301 {
302 if (!_options.EnableAutoWordResponse || values.Count != 1)
303 {
304 return;
305 }
306
307 if (request.DeviceCode != _options.AutoResponseTriggerDeviceCode ||
308 request.DeviceOffset != _options.AutoResponseTriggerOffset)
309 {
310 return;
311 }
312
313 var rawResponseValue = values[0] + _options.AutoResponseIncrement;
314 if (rawResponseValue is < short.MinValue or > short.MaxValue)
315 {
316 StatusChanged?.Invoke(this, $"MC auto response skipped: value overflow. value={rawResponseValue}");
317 return;
318 }
319
320 var responseValue = (short)rawResponseValue;
321 var responseAddress = new PlcAddress(ToDeviceType(_options.AutoResponseDeviceCode), _options.AutoResponseOffset);
322 _memory.WriteWords(responseAddress, [responseValue]);
323 StatusChanged?.Invoke(this, $"MC auto response: D{_options.AutoResponseOffset}={responseValue}");
324 }
325
362 private static bool TryParseRequest(
363 ReadOnlySpan<byte> frame,
364 out McRequest request,
365 out string? errorMessage)
366 {
367 request = default;
368 errorMessage = null;
369
370 if (frame.Length < 21)
371 {
372 errorMessage = $"The MC request frame is too short. Length={frame.Length}.";
373 return false;
374 }
375
376 var subHeader = ReadUInt16LittleEndian(frame, 0);
377 if (subHeader != RequestSubHeader)
378 {
379 errorMessage = $"Invalid MC request sub-header: 0x{subHeader:X4}.";
380 return false;
381 }
382
383 var requestDataLength = ReadUInt16LittleEndian(frame, 7);
384 var expectedFrameLength = 9 + requestDataLength;
385 if (frame.Length < expectedFrameLength)
386 {
387 errorMessage = $"The MC request frame is incomplete. Expected={expectedFrameLength}, Actual={frame.Length}.";
388 return false;
389 }
390
391 var header = frame[..7].ToArray();
392 var command = ReadUInt16LittleEndian(frame, 11);
393 var subCommand = ReadUInt16LittleEndian(frame, 13);
394 var deviceOffset = ReadUInt24LittleEndian(frame, 15);
395 var deviceCode = frame[18];
396 var points = ReadUInt16LittleEndian(frame, 19);
397 var dataStart = 21;
398 var dataLength = expectedFrameLength - dataStart;
399 var data = dataLength > 0 ? frame.Slice(dataStart, dataLength).ToArray() : [];
400
401 if (!IsSupportedDeviceCode(deviceCode))
402 {
403 errorMessage = $"Unsupported MC device code: 0x{deviceCode:X2}.";
404 return false;
405 }
406
407 request = new McRequest(header, command, subCommand, deviceOffset, deviceCode, points, data);
408 return true;
409 }
410
440 private static byte[] BuildErrorResponse(ReadOnlySpan<byte> requestFrame, ushort endCode)
441 {
442 var header = new byte[7];
443 if (requestFrame.Length >= 7)
444 {
445 requestFrame[..7].CopyTo(header);
446 }
447 else
448 {
449 header[0] = 0x00;
450 header[1] = 0x50;
451 }
452
453 return BuildResponse(header, endCode, []);
454 }
455
499 private static byte[] BuildResponse(IReadOnlyList<byte> requestHeader, ushort endCode, IReadOnlyList<byte> data)
500 {
501 var frame = new List<byte>(11 + data.Count);
503
504 frame.Add(requestHeader.Count > 2 ? requestHeader[2] : (byte)0x00);
505 frame.Add(requestHeader.Count > 3 ? requestHeader[3] : (byte)0xFF);
506 frame.Add(requestHeader.Count > 4 ? requestHeader[4] : (byte)0xFF);
507 frame.Add(requestHeader.Count > 5 ? requestHeader[5] : (byte)0x03);
508 frame.Add(requestHeader.Count > 6 ? requestHeader[6] : (byte)0x00);
509
510 var responseDataLength = checked((ushort)(data.Count + 2));
511 MitsubishiMcEndian.WriteUInt16LittleEndian(frame, responseDataLength);
513 foreach (var item in data)
514 {
515 frame.Add(item);
516 }
517
518 return frame.ToArray();
519 }
520
543 private static byte[] PackBits(IReadOnlyList<bool> values)
544 {
545 var bytes = new byte[(values.Count + 1) / 2];
546 for (var i = 0; i < values.Count; i++)
547 {
548 if (!values[i])
549 {
550 continue;
551 }
552
553 if (i % 2 == 0)
554 {
555 bytes[i / 2] |= 0x10;
556 }
557 else
558 {
559 bytes[i / 2] |= 0x01;
560 }
561 }
562
563 return bytes;
564 }
565
595 private static bool[] UnpackBits(ReadOnlySpan<byte> data, int count)
596 {
597 var values = new bool[count];
598 for (var i = 0; i < count; i++)
599 {
600 var packed = data[i / 2];
601 values[i] = i % 2 == 0
602 ? (packed & 0x10) != 0
603 : (packed & 0x01) != 0;
604 }
605
606 return values;
607 }
608
638 private static ushort ReadUInt16LittleEndian(ReadOnlySpan<byte> buffer, int offset)
639 {
640 return (ushort)(buffer[offset] | (buffer[offset + 1] << 8));
641 }
642
672 private static int ReadUInt24LittleEndian(ReadOnlySpan<byte> buffer, int offset)
673 {
674 return buffer[offset] | (buffer[offset + 1] << 8) | (buffer[offset + 2] << 16);
675 }
676
699 private static bool IsSupportedDeviceCode(byte deviceCode)
700 {
701 return deviceCode is 0xA8 or 0x90 or 0x9C or 0x9D or 0xA0 or 0xB4 or 0xAF or 0xB0;
702 }
703
726 private static PlcDeviceType ToDeviceType(byte deviceCode)
727 {
728 return deviceCode switch
729 {
730 0xA8 => PlcDeviceType.D,
731 0x90 => PlcDeviceType.M,
732 0x9C => PlcDeviceType.X,
733 0x9D => PlcDeviceType.Y,
734 0xA0 => PlcDeviceType.B,
735 0xB4 => PlcDeviceType.W,
736 0xAF => PlcDeviceType.R,
737 0xB0 => PlcDeviceType.ZR,
738 _ => PlcDeviceType.Unknown
739 };
740 }
741
800 private readonly record struct McRequest(
801 byte[] Header,
802 ushort Command,
803 ushort SubCommand,
804 int DeviceOffset,
805 byte DeviceCode,
806 int Points,
807 byte[] Data);
808}
static void WriteUInt16LittleEndian(ICollection< byte > buffer, ushort value)
static byte[] BuildErrorResponse(ReadOnlySpan< byte > requestFrame, ushort endCode)
static byte[] BuildResponse(IReadOnlyList< byte > requestHeader, ushort endCode, IReadOnlyList< byte > data)
static bool TryParseRequest(ReadOnlySpan< byte > frame, out McRequest request, out string? errorMessage)
readonly record struct McRequest(byte[] Header, ushort Command, ushort SubCommand, int DeviceOffset, byte DeviceCode, int Points, byte[] Data)
MitsubishiMcBinary3ESimulatorProtocol(InMemoryPlcMemory memory, MitsubishiMcSimulatorServerOptions options)