chore: HERV 통합 저장소 초기 커밋

- 펌웨어(program), C# 대시보드(TestProgram), 시뮬레이터(Simulator),
  프로토콜/문서(Protocol, doc) 전체를 단일 저장소로 통합
- program 폴더의 별도 git 저장소를 제거하고 통합 저장소에 흡수
- 빌드 산출물(program/build, bin/obj, *.o/.elf/.bin/.hex 등) .gitignore 처리
- 사내 Synology NAS Git 원격 연결 예정

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-15 21:44:23 +09:00
commit 5a96a696b1
265 changed files with 76458 additions and 0 deletions
+102
View File
@@ -0,0 +1,102 @@
namespace ErvProtocol
{
// StatusRecord → STATUS(0x81) 134B payload. StatusDecoder 의 역(逆).
// ERV(메인보드/시뮬레이터) 측이 STATUS 를 송신할 때 사용. PC_ERV_Protocol.md 4장.
public static class StatusEncoder
{
static void W16(byte[] p, int off, int v) // 빅엔디안
{
p[off] = (byte)((v >> 8) & 0xFF);
p[off + 1] = (byte)(v & 0xFF);
}
// StatusRecord → 134B payload
public static byte[] Encode(StatusRecord s)
{
var p = new byte[StatusDecoder.STATUS_LEN];
// 글로벌 0~16
p[0] = s.Power;
p[1] = s.RunMode;
p[2] = s.AutoState;
p[3] = s.FanMode;
p[4] = s.SubMode;
p[5] = s.Hood;
p[6] = s.HystPreset;
W16(p, 7, s.HystPm25);
W16(p, 9, s.HystPm10);
W16(p, 11, s.HystVoc);
W16(p, 13, s.HystCo2);
W16(p, 15, s.ErrorCode);
// 각실 17~ (14B x 4)
const int baseOff = 17;
for (int r = 0; r < 4; r++)
{
int o = baseOff + r * 14;
var room = s.Rooms[r];
p[o + 0] = room.Damper;
W16(p, o + 1, room.Pm25);
W16(p, o + 3, room.Pm10);
W16(p, o + 5, room.Voc);
W16(p, o + 7, room.Co2);
p[o + 9] = room.AirQuality;
p[o + 10] = room.LedDim;
W16(p, o + 11, room.LoadScore);
p[o + 13] = room.FinalVolume;
}
// ERV 리셋 (offset 73)
p[73] = s.Reset;
// 풍량 VSP (offset 74~, 9엔트리 × u16 SA·EA)
const int vspOff = 74;
for (int i = 0; i < 9; i++)
{
int o = vspOff + i * 4;
W16(p, o, s.Vsp[i].Sa);
W16(p, o + 2, s.Vsp[i].Ea);
}
// 히스테리시스 프리셋 테이블 (offset 110~, 3프리셋 × PM2.5/PM10/VOC/CO2 u16)
const int hystOff = 110;
for (int i = 0; i < 3; i++)
{
int o = hystOff + i * 8;
W16(p, o, s.HystTable[i].Pm25);
W16(p, o + 2, s.HystTable[i].Pm10);
W16(p, o + 4, s.HystTable[i].Voc);
W16(p, o + 6, s.HystTable[i].Co2);
}
// 모드별 오염단계 임계표 (offset 134~, 3프리셋 × [CO2,PM2.5,PM10,VOC] × L1~L4)
for (int i = 0; i < 3; i++)
{
int o = StatusDecoder.THR_OFF + i * 32;
for (int k = 0; k < 4; k++) W16(p, o + 0 + k * 2, s.ThrTable[i].Co2[k]);
for (int k = 0; k < 4; k++) W16(p, o + 8 + k * 2, s.ThrTable[i].Pm25[k]);
for (int k = 0; k < 4; k++) W16(p, o + 16 + k * 2, s.ThrTable[i].Pm10[k]);
for (int k = 0; k < 4; k++) W16(p, o + 24 + k * 2, s.ThrTable[i].Voc[k]);
}
// 각실 온도/습도 (offset 230~, 4실 × [Temp, Humi])
for (int r = 0; r < 4; r++)
{
int o = StatusDecoder.TEMPHUMI_OFF + r * 2;
p[o + 0] = s.Rooms[r].Temp;
p[o + 1] = s.Rooms[r].Humi;
}
// (꺼짐)예약 잔여초 (offset 238, u16)
W16(p, StatusDecoder.RESERVE_OFF, s.ReserveRemainSec);
return p;
}
// STATUS(0x81) 완성 프레임 (CRC 포함)
public static byte[] BuildStatusFrame(StatusRecord s) => CtrlFrame.Build(StatusDecoder.STATUS, Encode(s));
// ACK(0x82) 프레임 : [echoCmd][result(0 OK/1 ERR)]
public static byte[] BuildAckFrame(byte echoCmd, byte result) => CtrlFrame.Build(StatusDecoder.ACK, echoCmd, result);
}
}