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); } }