a502322188
손상된 .git 히스토리(missing tree)로 재초기화 후 작업트리 전체 커밋. .claude/ 만 제외(로컬 에이전트 설정). 구 저장소 백업(.git_corrupt_backup/) 포함. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
70 lines
2.3 KiB
C#
70 lines
2.3 KiB
C#
namespace ErvProtocol
|
|
{
|
|
// 고정패킷 파서 (PC_ERV_Protocol.md Rev2.0)
|
|
// 0xAA | CMD | DATA[240] | CRC16-MODBUS(LE) = 244B 고정
|
|
// 연결(소켓/시리얼)마다 1개 인스턴스. CRC 통과 프레임만 OnFrame 으로 전달.
|
|
public sealed class FrameParser
|
|
{
|
|
public const byte STX = 0xAA;
|
|
public const int DataLen = CtrlFrame.DataLen; // 240
|
|
|
|
public event Action<byte, byte[]>? OnFrame; // (cmd, data[240])
|
|
public event Action<string>? OnError;
|
|
|
|
enum Step { Stx, Cmd, Data, CrcLo, CrcHi }
|
|
Step _step = Step.Stx;
|
|
byte _cmd, _crcLo;
|
|
readonly byte[] _data = new byte[DataLen];
|
|
int _dataPos;
|
|
|
|
public void Feed(ReadOnlySpan<byte> data)
|
|
{
|
|
foreach (var b in data) FeedByte(b);
|
|
}
|
|
|
|
public void FeedByte(byte b)
|
|
{
|
|
switch (_step)
|
|
{
|
|
case Step.Stx:
|
|
if (b == STX) _step = Step.Cmd;
|
|
break;
|
|
case Step.Cmd:
|
|
_cmd = b; _dataPos = 0; _step = Step.Data;
|
|
break;
|
|
case Step.Data:
|
|
_data[_dataPos++] = b;
|
|
if (_dataPos >= DataLen) _step = Step.CrcLo;
|
|
break;
|
|
case Step.CrcLo:
|
|
_crcLo = b; _step = Step.CrcHi;
|
|
break;
|
|
case Step.CrcHi:
|
|
ushort rxCrc = (ushort)(_crcLo | (b << 8));
|
|
_step = Step.Stx;
|
|
Verify(rxCrc);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// 프레임 동기 리셋 (시리얼 프레임 갭 발생 시 등)
|
|
public void Reset() => _step = Step.Stx;
|
|
|
|
void Verify(ushort rxCrc)
|
|
{
|
|
var buf = new byte[1 + DataLen]; // CMD + DATA[240]
|
|
buf[0] = _cmd;
|
|
Array.Copy(_data, 0, buf, 1, DataLen);
|
|
ushort calc = Crc16.Modbus(buf, 0, buf.Length);
|
|
if (calc != rxCrc)
|
|
{
|
|
OnError?.Invoke($"CRC ERR cmd=0x{_cmd:X2} calc=0x{calc:X4} rx=0x{rxCrc:X4}");
|
|
return;
|
|
}
|
|
var data = new byte[DataLen];
|
|
Array.Copy(_data, data, DataLen);
|
|
OnFrame?.Invoke(_cmd, data);
|
|
}
|
|
}
|
|
}
|