chore: HERV 통합 저장소 재초기화 커밋
손상된 .git 히스토리(missing tree)로 재초기화 후 작업트리 전체 커밋. .claude/ 만 제외(로컬 에이전트 설정). 구 저장소 백업(.git_corrupt_backup/) 포함. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,214 @@
|
||||
using System;
|
||||
using ERVSimulator.Model;
|
||||
|
||||
namespace ERVSimulator.Protocol
|
||||
{
|
||||
// 룸콘 프로토콜 (UART2/SC0)
|
||||
// 패킷: AA | Cmd | D[2..12] | XOR_SUM[13] | EE (15 byte)
|
||||
// 펌웨어 [My_RJ2.c] rx_roomcon_check() + roomcon_parsing()
|
||||
public class RoomConProtocol
|
||||
{
|
||||
public const byte HEADER = 0xAA;
|
||||
public const byte TAIL = 0xEE;
|
||||
public const int PACKET_LEN = 15;
|
||||
|
||||
// Cmd (Rx_roomcon232_buffer[1])
|
||||
public const byte CMD_NORMAL = 0x00; // 상태 폴링
|
||||
public const byte CMD_EVENT = 0x01; // 모드/팬 변경 이벤트
|
||||
public const byte CMD_RESTART1 = 0x02; // 환기단 preset 요청
|
||||
public const byte CMD_RESTART2 = 0x12; // bypass/air preset 요청
|
||||
public const byte CMD_VSP = 0x03; // 테스트모드 진입
|
||||
public const byte CMD_EXIT = 0x04; // 테스트모드 종료
|
||||
public const byte CMD_HOOD_INFO = 0x0A; // ERV→룸콘 후드 연동 통지 (힘펠 V3.7 RX_DATA_HOOD_INFO)
|
||||
|
||||
readonly SerialChannel _ch;
|
||||
readonly ErvState _state;
|
||||
readonly DamperSequencer _seq;
|
||||
readonly System.Windows.Threading.Dispatcher _dispatcher;
|
||||
|
||||
readonly byte[] _rx = new byte[PACKET_LEN];
|
||||
int _rxPos;
|
||||
bool _hoodLinkReported; // 마지막으로 룸콘에 통지한 후드 연동 상태(변화 시에만 0x0A 송신)
|
||||
DateTime _lastByte = DateTime.MinValue;
|
||||
static readonly TimeSpan FrameGap = TimeSpan.FromMilliseconds(50);
|
||||
|
||||
public event Action<string>? PacketReceived;
|
||||
public event Action<string>? PacketSent;
|
||||
|
||||
public RoomConProtocol(SerialChannel ch, ErvState state, DamperSequencer seq,
|
||||
System.Windows.Threading.Dispatcher dispatcher)
|
||||
{
|
||||
_ch = ch; _state = state; _seq = seq; _dispatcher = dispatcher;
|
||||
_ch.ByteReceived += OnByte;
|
||||
}
|
||||
|
||||
void OnByte(byte b)
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
if (now - _lastByte > FrameGap) _rxPos = 0;
|
||||
_lastByte = now;
|
||||
|
||||
// 펌웨어와 동일한 byte 파서 (My_RJ2.c:37)
|
||||
if (_rxPos == 0)
|
||||
{
|
||||
if (b != HEADER) return;
|
||||
_rx[_rxPos++] = b;
|
||||
return;
|
||||
}
|
||||
if (_rxPos >= 1 && _rxPos <= 12)
|
||||
{
|
||||
_rx[_rxPos++] = b;
|
||||
return;
|
||||
}
|
||||
if (_rxPos == 13)
|
||||
{
|
||||
byte cksum = ChecksumHelper.Xor(_rx, 0, 13);
|
||||
if (cksum != b) { _rxPos = 0; return; }
|
||||
_rx[_rxPos++] = b;
|
||||
return;
|
||||
}
|
||||
if (_rxPos == 14)
|
||||
{
|
||||
_rxPos = 0;
|
||||
if (b != TAIL) return;
|
||||
byte[] copy = (byte[])_rx.Clone();
|
||||
_dispatcher.BeginInvoke(new Action(() => HandlePacket(copy)));
|
||||
}
|
||||
}
|
||||
|
||||
void HandlePacket(byte[] p)
|
||||
{
|
||||
PacketReceived?.Invoke($"RoomCon RX: {HexFormat.Bytes(p, 14)} EE");
|
||||
|
||||
byte cmd = p[1];
|
||||
switch (cmd)
|
||||
{
|
||||
case CMD_EVENT: HandleEvent(p); break;
|
||||
case CMD_NORMAL: HandleNormal(p); break;
|
||||
case CMD_RESTART1: HandleRestart1(); break;
|
||||
case CMD_RESTART2: HandleRestart2(); break;
|
||||
case CMD_VSP: HandleVsp(p); break;
|
||||
case CMD_EXIT: HandleExit(); break;
|
||||
default:
|
||||
PacketReceived?.Invoke($" (unknown RoomCon cmd 0x{cmd:X2})");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// [My_RJ2.c:387] RX_DATA_MODE_EVENT - 운전 모드/팬 변경
|
||||
void HandleEvent(byte[] p)
|
||||
{
|
||||
byte runMode = p[2];
|
||||
byte fanMode = p[3];
|
||||
_state.RunMode = (RunMode)runMode;
|
||||
_state.SetRunMode = (RunMode)runMode;
|
||||
_state.FanMode = fanMode;
|
||||
_state.SetFanMode = fanMode;
|
||||
// VENT && fan=0 ⇒ Power OFF 진입
|
||||
_state.PowerOn = !(runMode == 0 && fanMode == 0);
|
||||
|
||||
// 예약 (룸콘 EVENT [10]=flag / [11]=시 / [12]=분). HOMENET STATUS(reserve)로도 전달 → 대시보드 반영
|
||||
if (p[10] == 1)
|
||||
{
|
||||
int hours = p[11];
|
||||
_state.ReserveHours = hours; // 0이면 해제
|
||||
_state.ReserveRemainSec = hours * 3600 + p[12] * 60; // 카운트다운/전원OFF는 ReserveTick(1s) 처리
|
||||
}
|
||||
|
||||
_seq.NotifyCommandChanged();
|
||||
|
||||
// 응답: AA 01 RunMode FanMode 00 misc... XOR EE (펌웨어 [My_RJ2.c:489])
|
||||
var tx = NewPacket();
|
||||
tx[1] = 0x01;
|
||||
tx[2] = runMode;
|
||||
tx[3] = fanMode;
|
||||
tx[5] = 0; // Heater/UV/Kijer
|
||||
tx[7] = _state.ErrorCode; // ErrorCode (E02/E07/E09/E10/COLD 비트맵)
|
||||
tx[8] = 0; // Out_Temperature sign
|
||||
tx[9] = 20 + 25; // Out_Temperature = 25
|
||||
tx[10] = 20 + 22; // In_Temperature = 22
|
||||
FinalizeAndSend(tx);
|
||||
}
|
||||
|
||||
// [My_RJ2.c:327] RX_DATA_MODE_NORMAL - 상태 폴링 응답
|
||||
void HandleNormal(byte[] p)
|
||||
{
|
||||
// 후드 연동 상태가 바뀌면 HOOD_INFO(0x0A)로 통지 (힘펠 V3.7, 펌웨어 Hood_info_command).
|
||||
// HoodStatus = 연동운전중(후드 가동 → 메이크업 에어). 후드 OFF로 ERV 복귀 시 0x80(OFF) 전송.
|
||||
if (_state.HoodStatus != _hoodLinkReported)
|
||||
{
|
||||
_hoodLinkReported = _state.HoodStatus;
|
||||
var hi = NewPacket();
|
||||
hi[1] = CMD_HOOD_INFO;
|
||||
hi[2] = _state.HoodStatus ? (byte)RunMode.Ventilation : (byte)_state.SetRunMode; // 연동 시 환기
|
||||
hi[3] = _state.HoodStatus ? (byte)1 : _state.SetFanMode;
|
||||
hi[6] = _state.HoodStatus ? (byte)0x81 : (byte)0x80; // 후드 연동 ON / OFF
|
||||
FinalizeAndSend(hi);
|
||||
return;
|
||||
}
|
||||
|
||||
var tx = NewPacket();
|
||||
tx[1] = 0x07; // COMMAND_CONTROLL
|
||||
tx[2] = (byte)_state.SetRunMode;
|
||||
tx[3] = _state.SetFanMode;
|
||||
tx[4] = 0; // Auto_Mode
|
||||
tx[5] = 0;
|
||||
tx[7] = _state.ErrorCode; // ErrorCode 도 동봉
|
||||
FinalizeAndSend(tx);
|
||||
}
|
||||
|
||||
// [My_RJ2.c:522] RX_DATA_MODE_RESTART1 - 환기 1~4단 preset
|
||||
void HandleRestart1()
|
||||
{
|
||||
var tx = NewPacket();
|
||||
tx[1] = 0x02;
|
||||
tx[4] = 0x10;
|
||||
tx[5] = (byte)_state.FanSAPreset_Vent[1]; tx[6] = (byte)_state.FanEAPreset_Vent[1];
|
||||
tx[7] = (byte)_state.FanSAPreset_Vent[2]; tx[8] = (byte)_state.FanEAPreset_Vent[2];
|
||||
tx[9] = (byte)_state.FanSAPreset_Vent[3]; tx[10] = (byte)_state.FanEAPreset_Vent[3];
|
||||
tx[11] = (byte)_state.FanSAPreset_Vent[4]; tx[12] = (byte)_state.FanEAPreset_Vent[4];
|
||||
FinalizeAndSend(tx);
|
||||
}
|
||||
|
||||
// [My_RJ2.c:556] RX_DATA_MODE_RESTART2 - bypass/air preset
|
||||
void HandleRestart2()
|
||||
{
|
||||
var tx = NewPacket();
|
||||
tx[1] = 0x12;
|
||||
tx[4] = 0x10;
|
||||
tx[5] = (byte)_state.FanSAPreset_Bypass[1]; tx[6] = (byte)_state.FanEAPreset_Bypass[1];
|
||||
tx[7] = (byte)_state.FanSAPreset_Air[1];
|
||||
tx[8] = (byte)_state.FanSAPreset_Air[2];
|
||||
tx[9] = (byte)_state.FanSAPreset_Air[3];
|
||||
tx[10] = (byte)_state.FanSAPreset_Air[4];
|
||||
FinalizeAndSend(tx);
|
||||
}
|
||||
|
||||
// [My_RJ2.c:579] RX_DATA_MODE_VSP - 테스트 모드 진입 (preset 갱신)
|
||||
void HandleVsp(byte[] p)
|
||||
{
|
||||
// 본 시뮬레이터에선 RX만 기록, preset 변경은 생략
|
||||
PacketReceived?.Invoke($" VSP select={p[3]} sa={p[4]} ea={p[5]}");
|
||||
}
|
||||
|
||||
void HandleExit()
|
||||
{
|
||||
PacketReceived?.Invoke(" VSP exit");
|
||||
}
|
||||
|
||||
byte[] NewPacket()
|
||||
{
|
||||
var tx = new byte[PACKET_LEN];
|
||||
tx[0] = HEADER;
|
||||
return tx;
|
||||
}
|
||||
|
||||
void FinalizeAndSend(byte[] tx)
|
||||
{
|
||||
tx[13] = ChecksumHelper.Xor(tx, 0, 13);
|
||||
tx[14] = TAIL;
|
||||
if (_ch.Send(tx, PACKET_LEN))
|
||||
PacketSent?.Invoke($"RoomCon TX: {HexFormat.Bytes(tx, 15)}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user