Files
HECO2/TestProgram/ErvProtocol/CtrlFrame.cs
T
jeon a502322188 chore: HERV 통합 저장소 재초기화 커밋
손상된 .git 히스토리(missing tree)로 재초기화 후 작업트리 전체 커밋.
.claude/ 만 제외(로컬 에이전트 설정). 구 저장소 백업(.git_corrupt_backup/) 포함.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 09:32:17 +09:00

80 lines
4.5 KiB
C#

namespace ErvProtocol
{
// PC↔ERV 고정패킷 프레임 빌더 (PC_ERV_Protocol.md Rev2.0)
// 모든 프레임은 항상 244B 고정 : 0xAA | CMD | DATA[240] | CRC16-MODBUS(LE)
// - PC→ERV 제어 : DATA 앞쪽에 명령 인자, 나머지 0 패딩
// - ERV→PC : STATUS(0x81) DATA=240B 상태, ACK(0x82) DATA[0]=echoCmd DATA[1]=result
public static class CtrlFrame
{
public const int DataLen = 240; // DATA 고정 크기(=STATUS 페이로드)
public const int FrameLen = 1 + 1 + DataLen + 2; // STX+CMD+DATA+CRC = 244
public const byte CTRL_POWER = 0x01;
public const byte CTRL_RUNMODE = 0x02;
public const byte CTRL_FAN = 0x03;
public const byte CTRL_SUBMODE = 0x04;
public const byte CTRL_HOOD = 0x05;
public const byte CTRL_HYST_PRESET = 0x06;
public const byte CTRL_HYST_VALUE = 0x07;
public const byte CTRL_DAMPER = 0x08;
public const byte CTRL_LED = 0x09;
public const byte REQ_STATUS = 0x0A;
public const byte CTRL_RESET = 0x0B;
public const byte CTRL_VSP = 0x0C;
public const byte CTRL_HYST_THR = 0x0D; // 오염단계 임계 설정 [preset][pollutant][L1~L4]
public const byte CTRL_RESERVE = 0x0E; // (꺼짐)예약 [hours 0~8] : N시간 후 전원 OFF (0=해제)
// cmd + args 로 244B 고정 프레임 생성 (DATA 앞쪽에 args, 나머지 0, CRC 포함)
public static byte[] Build(byte cmd, params byte[] args)
{
int argLen = args?.Length ?? 0;
if (argLen > DataLen) argLen = DataLen; // 240B 초과분은 잘림(설계상 발생하지 않음)
var frame = new byte[FrameLen]; // 전부 0 초기화 = DATA 0 패딩
frame[0] = 0xAA;
frame[1] = cmd;
if (argLen > 0) Array.Copy(args!, 0, frame, 2, argLen); // DATA[0..] = args
ushort crc = Crc16.Modbus(frame, 1, 1 + DataLen); // CMD + DATA[240] = 241B
frame[2 + DataLen] = (byte)(crc & 0xFF); // CRC_L
frame[2 + DataLen + 1] = (byte)(crc >> 8); // CRC_H
return frame;
}
static byte[] U16BE(int v) => new[] { (byte)((v >> 8) & 0xFF), (byte)(v & 0xFF) };
public static byte[] Power(int on) => Build(CTRL_POWER, (byte)(on != 0 ? 1 : 0));
public static byte[] RunModeCmd(int m) => Build(CTRL_RUNMODE, (byte)m);
public static byte[] Fan(int s) => Build(CTRL_FAN, (byte)s);
public static byte[] SubMode(int t, int on) => Build(CTRL_SUBMODE, (byte)t, (byte)(on != 0 ? 1 : 0));
public static byte[] Hood(int on) => Build(CTRL_HOOD, (byte)(on != 0 ? 1 : 0));
public static byte[] Preset(int p) => Build(CTRL_HYST_PRESET, (byte)p);
// preset: 0 ECO / 1 NORMAL / 2 TURBO
public static byte[] HystValue(int preset, int pm25, int pm10, int voc, int co2)
{
var p = new List<byte> { (byte)preset };
p.AddRange(U16BE(pm25)); p.AddRange(U16BE(pm10)); p.AddRange(U16BE(voc)); p.AddRange(U16BE(co2));
return Build(CTRL_HYST_VALUE, p.ToArray());
}
// type : 0=급기(SA) / 1=배기(EA)
public static byte[] Damper(int room, int type, int on) => Build(CTRL_DAMPER, (byte)room, (byte)type, (byte)(on != 0 ? 1 : 0));
public static byte[] Led(int room, int dim) => Build(CTRL_LED, (byte)room, (byte)dim);
public static byte[] RequestStatus() => Build(REQ_STATUS);
public static byte[] Reset(int on) => Build(CTRL_RESET, (byte)(on != 0 ? 1 : 0));
public static byte[] Reserve(int hours) => Build(CTRL_RESERVE, (byte)hours); // 0~8시간, 0=해제
public static byte[] Vsp(int group, int index, int sa, int ea)
{
var p = new List<byte> { (byte)group, (byte)index };
p.AddRange(U16BE(sa)); p.AddRange(U16BE(ea));
return Build(CTRL_VSP, p.ToArray());
}
// 오염단계 임계 : preset(0 ECO/1 NORMAL/2 TURBO), pollutant(0 CO2/1 PM2.5/2 PM10/3 VOC), L1~L4 상한
public static byte[] HystThr(int preset, int pollutant, int l1, int l2, int l3, int l4)
{
var p = new List<byte> { (byte)preset, (byte)pollutant };
p.AddRange(U16BE(l1)); p.AddRange(U16BE(l2)); p.AddRange(U16BE(l3)); p.AddRange(U16BE(l4));
return Build(CTRL_HYST_THR, p.ToArray());
}
}
}