using System;
namespace ICT_WPF.Services
{
/// <summary>
/// LDEICT REV03 UDP 프로토콜
///
/// 프레임 구조:
/// STX(1)
/// CH(2) : ASCII HEX ("01"~"FF")
/// CMD(2) : ASCII HEX
/// LEN(4) : ASCII HEX (payload byte 수)
/// DATA(N) : 바이너리
/// CS(2) : ASCII HEX
/// ETX(1)
///
/// 전체 길이 = LEN + 12
/// </summary>
public static class IctUdpProtocolLdeict
{
public const byte STX = 0x02;
public const byte ETX = 0x03;
/// <summary>
/// CH(0-based) → 보드 IP
/// </summary>
public static string GetBoardIp(int ch0Base)
=> $"192.168.0.{ch0Base + 1}";
/// <summary>
/// ASCII HEX 2자리 쓰기
/// </summary>
private static void WriteHex2(Span<byte> dst, int offset, int value)
{
string s = value.ToString("X2");
dst[offset + 0] = (byte)s[0];
dst[offset + 1] = (byte)s[1];
}
/// <summary>
/// ASCII HEX 4자리 쓰기
/// </summary>
private static void WriteHex4(Span<byte> dst, int offset, int value)
{
string s = value.ToString("X4");
dst[offset + 0] = (byte)s[0];
dst[offset + 1] = (byte)s[1];
dst[offset + 2] = (byte)s[2];
dst[offset + 3] = (byte)s[3];
}
/// <summary>
/// 체크섬 계산
/// (CH+CMD+LEN+DATA 바이트 합 & 0xFF)
/// </summary>
private static byte CalcChecksum(ReadOnlySpan<byte> src, int count)
{
int sum = 0;
for (int i = 0; i < count; i++)
sum += src[i];
return (byte)(sum & 0xFF);
}
/// <summary>
/// 요청 프레임 생성
/// </summary>
public static byte[] BuildFrame(int ch0Base, byte cmd, byte[] payload)
{
payload ??= Array.Empty<byte>();
int payloadLen = payload.Length;
int totalLen = payloadLen + 12;
byte[] frame = new byte[totalLen];
int o = 0;
frame[o++] = STX;
WriteHex2(frame, o, ch0Base + 1); o += 2; // CH
WriteHex2(frame, o, cmd); o += 2; // CMD
WriteHex4(frame, o, payloadLen); o += 4; // LEN
// DATA
if (payloadLen > 0)
{
Buffer.BlockCopy(payload, 0, frame, o, payloadLen);
o += payloadLen;
}
// CS
byte cs = CalcChecksum(frame.AsSpan(3), payloadLen + 6);
WriteHex2(frame, o, cs);
o += 2;
frame[o] = ETX;
return frame;
}
}
}
파일 이름 IctUdpProtocolLdeict.cs
payload(byte[])를 UDP규격에 맞는 완전한 프레임(byte[])로 만드는 파일
형식(STX~ETX 프레임 규칙)
❌ 스위치 해석 안 함
❌ LED 비트 매핑 안 함
❌ UDP 소켓 안 만짐
❌ UI 모름
STX(1)
CH(2)
CMD(2)
LEN(4)
DATA(N)
CS(2)
ETX(1)
이 규칙을 박아 넣은 파일
buildframe() 함수가 핵심
문서대로 프레임 구조
[STX 1바이트]
[CH 2바이트 ASCII HEX]
[CMD 2바이트 ASCII HEX]
[LEN 4바이트 ASCII HEX] // payload 길이
[DATA N바이트 바이너리] // payload
[CS 2바이트 ASCII HEX] // 체크섬
[ETX 1바이트]
=> payload를 규격 포멧으로 포장해서 UDP로 바로 보내는 byte[]만들어줌
public const byte STX = 0x02;
public const byte ETX = 0x03;
프레임시작과 끝 표시 , 프레임의 경계
public static string GetBoardIp(int ch0Base)
private static void WriteHex2(Span<byte> dst, int offset, int value)
private static void WriteHex4(Span<byte> dst, int offset, int value)
ex) payloadLed = 2
-> 0002 => dst에 '0''0''0''2'가 들어감
private static byte CalcChecksum(ReadOnlySpan<byte> src, int count)
하는 일 딱 두개

0x30 = 48
0x31 = 49
0x30 = 48
0x45 = 69
0x30 = 48
0x30 = 48
0x30 = 48
0x32 = 50
0x30 = 48
0x0A = 10
=> sum = 466
하위 1바이트만 남겨야함 (return (byte)(sum & 0xFF);)
466 & 256 = 210(10진수)
0xD2 (16진수)
-> 체크섬 프레임에 드가는 방식
- 0xD2 = 'D2'
- 'D' = 0x44
- '2' = 0x32
즉, 체크섬은 프레임에 실제로 들어간 바이트(ASCII 포함)를 그대로 다 더해서 하위 8비트만 취한 값임
그래서 왜 이렇게 하냐? (이해 포인트)
public static byte[] BuildFrame(int ch0Base, byte cmd, byte[] payload)
payload null 방지, 길이 계산
payload ??= Array.Empty<byte>();
int payloadLen = payload.Length;
전체 프레임 길이 계산
int totalLen = payloadLen + 12;
byte[] frame = new byte[totalLen];
왜 + 12? : 고정필드합
STX 1
CH 2
CMD 2
LEN 4
CS 2
ETX 1
= 1+2+2+4+2+1 = 12
=> payloadLen + 12
STX 쓰기
frame[o++] = STX;
이 문장을 두 단계로 나눠서 봐야 한다.
(1) frame[o] = STX
o = 0
frame[0]에 STX(0x02) 저장
(2) o++
o를 1 증가
결과:
index: 0 1 2 3 4 5 6 7 8 9 10 11 12 13
frame: [02][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ]
↑
o = 1
👉 “STX 썼고, 다음은 index 1부터 써라”
5️⃣ CH 쓰기 (2바이트)
WriteHex2(frame, o, ch0Base + 1);
o += 2;
예: CH = "01" → '0' '1' → 0x30 0x31
WriteHex2 내부에서 일어나는 일
frame[o + 0] = '0'; // frame[1]
frame[o + 1] = '1'; // frame[2]
쓴 뒤 o를 2 증가:
index: 0 1 2 3 4 5 6 7 8 9 10 11 12 13
frame: [02][30][31][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ]
↑
o = 3
👉 CH가 2바이트니까 o도 2만큼 점프
6️⃣ CMD 쓰기 (2바이트)
WriteHex2(frame, o, cmd);
o += 2;
예: CMD = 0x0E → "0E" → 0x30 0x45
index: 0 1 2 3 4 5 6 7 8 9 10 11 12 13
frame: [02][30][31][30][45][ ][ ][ ][ ][ ][ ][ ][ ][ ]
↑
o = 5
7️⃣ LEN 쓰기 (4바이트)
WriteHex4(frame, o, payloadLen);
o += 4;
예: payloadLen = 2 → "0002"
index: 0 1 2 3 4 5 6 7 8 9 10 11 12 13
frame: [02][30][31][30][45][30][30][30][32][ ][ ][ ][ ][ ]
↑
o = 9
8️⃣ DATA 쓰기 (payloadLen = 2)
Buffer.BlockCopy(payload, 0, frame, o, payloadLen);
o += payloadLen;
예: payload = { 0x30, 0x0A }
index: 0 1 2 3 4 5 6 7 8 9 10 11 12 13
frame: [02][30][31][30][45][30][30][30][32][30][0A][ ][ ][ ]
↑
o = 11
9️⃣ CS 쓰기 (2바이트)
WriteHex2(frame, o, cs);
o += 2;
예: CS = 0xD2 → "D2"
index: 0 1 2 3 4 5 6 7 8 9 10 11 12 13
frame: [02][30][31][30][45][30][30][30][32][30][0A][44][32][ ]
↑
o = 13
🔟 ETX 쓰기
frame[o] = ETX;
index: 0 1 2 3 4 5 6 7 8 9 10 11 12 13
frame: [02][30][31][30][45][30][30][30][32][30][0A][44][32][03]