From b18d9c84bfb1408111b3d8679d9b5ae5b168bbfb Mon Sep 17 00:00:00 2001 From: jeon Date: Thu, 18 Jun 2026 23:41:34 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20260618=20=EB=A3=B8=EC=BB=A8=20=EB=8F=99?= =?UTF-8?q?=EA=B8=B0=C2=B7=EC=BE=8C=EC=A0=81=EC=A1=B0=EB=A6=AC=C2=B7?= =?UTF-8?q?=EC=8B=9C=EB=82=98=EB=A6=AC=EC=98=A4=C2=B7=EA=B7=B8=EB=9E=98?= =?UTF-8?q?=ED=94=84DB=20=EB=93=B1=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 펌웨어(SOURCE/HECO2/User): - My_Hood.c: 후드 전원 OFF 시 쾌적조리 토글 자동 해제 (6) - My_RJ2.c/My_Homenet.c/My_define.h: 룸컨 전용 pending(RoomCtrl_Push) 도입 → 전원 ON 간헐 미동작·전원 OFF 후 룸컨 옛모드(공청) 표시 해소 (11)(12) (공유 Command_request_type 레이스 + 716 equalize 조기클리어 + 분배기 wipe 회피) 대시보드(TestProgram/PCDashBoard): - 쾌적조리 미선택+후드 ON 시 다른 시나리오 버튼 선택 가능 (7) - 시나리오 버튼 항상 선택 가능 + 상호배타 로직 삭제 (8)(9) - 데모 루틴 전체 삭제 (DemoStatus.cs 포함) (9) - 전원 OFF 시 풍량 버튼(0 포함) 비활성 (13) - 그래프 DB(HERV_Log.db)를 임시폴더가 아닌 exe 폴더에 저장 (14) 문서/메모리: doc/260618_*.md 정리, command-request-type-shared-race 메모리 추가 Co-Authored-By: Claude Opus 4.8 (1M context) --- Source/HECO2/User/My_Homenet.c | 4 + Source/HECO2/User/My_Hood.c | 5 + Source/HECO2/User/My_RJ2.c | 9 +- Source/HECO2/User/My_define.h | 1 + TestProgram/ErvProtocol/DemoStatus.cs | 88 -------- TestProgram/PCDashBoard/Api/IErvApi.cs | 3 - TestProgram/PCDashBoard/Api/SerialErvApi.cs | 8 - TestProgram/PCDashBoard/MainWindow.xaml.cs | 91 ++------ doc/260618_내부댐퍼_모드변경시_미동작_수정.md | 213 ++++++++++++++++++ doc/claude-memory/MEMORY.md | 1 + .../command-request-type-shared-race.md | 16 ++ 11 files changed, 271 insertions(+), 168 deletions(-) delete mode 100644 TestProgram/ErvProtocol/DemoStatus.cs create mode 100644 doc/claude-memory/command-request-type-shared-race.md diff --git a/Source/HECO2/User/My_Homenet.c b/Source/HECO2/User/My_Homenet.c index 56fa368..6f1105d 100644 --- a/Source/HECO2/User/My_Homenet.c +++ b/Source/HECO2/User/My_Homenet.c @@ -265,6 +265,10 @@ static void hn_apply_cmd(uint8_t cmd, uint8_t *pl, uint8_t len) for(i = 1; i <= 4; i++) { Diffuser_Damper_Manual[i] = 0; Diffuser_Led_Manual[i] = 0; } /* 각실분배기 : 전원 비트(TYPE_POWER) + 모드/풍량. 룸컨(RJ2)도 Command_request_type 로 푸시 */ Command_request_type |= (TYPE_POWER | TYPE_MODE | TYPE_FAN_SPEED); + /* [룸컨푸시] 전원 ON/OFF 는 룸컨 전용 pending 으로도 표시 → 분배기(My_bunbaegi L522 Command_request_type=0)나 + My_RJ2 L716(전원OFF는 모터정지 위해 Set==현재 equalize 강제 → TYPE_*_조기클리어)로 룸컨 푸시가 유실되어 + 룸컨이 옛 모드(예: 공청)를 계속 표시하던 문제 해소. RJ2 ack(echo) 시 자동 해제. */ + RoomCtrl_Push = 1; } else result = 1; break; case 0x02: /* CTRL_RUNMODE [mode] 0off/1환기/2자동/3공청/4바이패스 */ diff --git a/Source/HECO2/User/My_Hood.c b/Source/HECO2/User/My_Hood.c index 03009e2..569dd94 100644 --- a/Source/HECO2/User/My_Hood.c +++ b/Source/HECO2/User/My_Hood.c @@ -212,6 +212,11 @@ uint8_t Hood_process(void)//200ms Hood_Yeundong_flag = 0; Hood_Warming_up_Timer = 0; Tx_Yeundong_Delay = 0; + /* [쾌적조리자동해제] 후드 전원 OFF(단수 0) → 쾌적조리 토글 자동 해제. + 사장님 요청(2026-06-18) : 후드 OFF 후 대시보드 쾌적조리 버튼이 저절로 꺼지게. + Enable=0 이면 STATUS byte4 bit1/byte5 bit0 모두 0 → 대시보드 _state.ComfortCook 자동 해제. + (기존 사양 9p 3.3 'armed 유지'와 반대 — 본 요청으로 변경) */ + Hood_YeunDong_Enable = 0; } else // 후드 단수 변경(1~5) : 메이크업 풍량 단수 추종 갱신 { diff --git a/Source/HECO2/User/My_RJ2.c b/Source/HECO2/User/My_RJ2.c index d76b9d3..0ae296a 100644 --- a/Source/HECO2/User/My_RJ2.c +++ b/Source/HECO2/User/My_RJ2.c @@ -140,6 +140,7 @@ uint8_t Filter_Reset_Flag = 0; uint8_t EEP_Save_Flag = 0; uint8_t Command_request_type = 0; +uint8_t RoomCtrl_Push = 0; /* [룸컨푸시] 대시보드 전원변경의 룸컨 전용 pending. 분배기(My_bunbaegi L522 Command_request_type=0)·716 equalize 조기클리어와 무관하게 룸컨에 푸시 보장, RJ2 ack 로만 해제 */ uint8_t Roomcon_Filter_Error = 0; // 2021.5.31 @@ -329,7 +330,7 @@ void roomcon_parsing(void) Err_Code &= ~(ERROR_FILTER_CLEAN|ERROR_FILTER_CHANGE|ERROR_SOJA_CHANGE|ERROR_PROTECT|ERROR_SOMETIME); Err_Code |= Rx_roomcon232_buffer[7]&(ERROR_FILTER_CLEAN|ERROR_FILTER_CHANGE|ERROR_SOJA_CHANGE|ERROR_PROTECT|ERROR_SOMETIME); - if(Command_request_type & (TYPE_MODE|TYPE_FAN_SPEED|TYPE_RESERVATION) ) + if((Command_request_type & (TYPE_MODE|TYPE_FAN_SPEED|TYPE_RESERVATION)) || RoomCtrl_Push) /* [룸컨푸시] 전용 pending 도 푸시 트리거 */ { if((Hood_Yeundong_flag == 1)&&(Tx_Yeundong_Delay == 0)) { @@ -398,6 +399,7 @@ void roomcon_parsing(void) { Command_request_type = 0; Command_request_type &= ~TYPE_SEND_FLAG; + RoomCtrl_Push = 0; /* [룸컨푸시] 룸컨 ack(echo) → 전용 pending 해제 */ Run_Mode = Rx_roomcon232_buffer[2]; if(Run_Mode != MODE_AUTO) { @@ -406,7 +408,7 @@ void roomcon_parsing(void) //if(Run_Mode != MODE_AUTO)Fan_Mode = Rx_roomcon232_buffer[3];//DEMO //else Fan_Mode = Set_Fan_Mode; } - else + else if(!RoomCtrl_Push && !(Command_request_type & (TYPE_MODE|TYPE_FAN_SPEED))) /* [룸컨푸시] 명령 푸시 대기중엔 룸컨 자기보고로 Set_* 를 덮지 않음 */ { Set_Run_Mode = Run_Mode = Rx_roomcon232_buffer[2]; @@ -689,13 +691,14 @@ void roomcon_parsing(void) if(Command_request_type & TYPE_SEND_FLAG) { Command_request_type &= ~TYPE_SEND_FLAG; + RoomCtrl_Push = 0; /* [룸컨푸시] 룸컨 ack(echo) → 전용 pending 해제 */ Run_Mode = Rx_roomcon232_buffer[2]; if(Run_Mode != MODE_AUTO) { Fan_Mode = Rx_roomcon232_buffer[3];///////////////// DEMO } } - else + else if(!RoomCtrl_Push && !(Command_request_type & (TYPE_MODE|TYPE_FAN_SPEED))) /* [룸컨푸시] 명령 푸시 대기중엔 룸컨 자기보고로 Set_* 를 덮지 않음 */ { Set_Run_Mode = Run_Mode = Rx_roomcon232_buffer[2]; if(Run_Mode != MODE_AUTO) diff --git a/Source/HECO2/User/My_define.h b/Source/HECO2/User/My_define.h index 3dbf2bc..83a1d16 100644 --- a/Source/HECO2/User/My_define.h +++ b/Source/HECO2/User/My_define.h @@ -371,6 +371,7 @@ extern uint8_t BlackOut_Run_Mode; extern uint8_t BlackOut_Fan_Mode; extern uint8_t Command_request_type; +extern uint8_t RoomCtrl_Push; /* [룸컨푸시] 대시보드 전원변경의 룸컨(RJ2) 전용 pending. 분배기와 공유 안 함, RJ2 ack(echo)로만 클리어 */ uint16_t Diffuser_Damper_process(uint8_t mode);//100ms diff --git a/TestProgram/ErvProtocol/DemoStatus.cs b/TestProgram/ErvProtocol/DemoStatus.cs deleted file mode 100644 index 927f3e7..0000000 --- a/TestProgram/ErvProtocol/DemoStatus.cs +++ /dev/null @@ -1,88 +0,0 @@ -namespace ErvProtocol -{ - // 펌웨어 없이 UI/파이프라인 검증용 합성 STATUS payload(73B) 생성. - public static class DemoStatus - { - public static byte[] BuildPayload(int tick) - { - var p = new byte[StatusDecoder.STATUS_LEN]; - p[0] = 1; // power - p[1] = (byte)RunMode.Auto; // runMode - p[2] = (byte)((tick / 5) % 2); // autoState 분산/집중 - p[3] = (byte)(2 + (tick % 3)); // fanMode 2~4 - p[4] = SubModeBits.SmartSleep; // subMode - p[5] = (byte)(tick % 2); // hood - p[6] = (byte)HystPreset.Normal; // preset - WriteU16(p, 7, 30); WriteU16(p, 9, 50); WriteU16(p, 11, 300); WriteU16(p, 13, 700); - WriteU16(p, 15, 0x0000); // errorCode - - for (int r = 0; r < 4; r++) - { - int o = 17 + r * 14; - int seed = tick + r * 13; - p[o + 0] = (byte)((seed % 2) | (((seed % 3) == 0) ? 0x02 : 0)); // bit0 급기 / bit1 배기 - WriteU16(p, o + 1, 10 + (seed * 3) % 60); - WriteU16(p, o + 3, 15 + (seed * 5) % 90); - WriteU16(p, o + 5, 100 + (seed * 7) % 400); - WriteU16(p, o + 7, 450 + (seed * 11) % 700); - p[o + 9] = (byte)(1 + (seed % 4)); - p[o + 10] = (byte)(seed % 10); - WriteU16(p, o + 11, (seed * 17) % 100); - p[o + 13] = (byte)(seed % 5); - } - - p[73] = 0; // reset (토글 off) - - // 풍량 VSP 설정값 (1바이트, 사양서 DL H-ERV VSP 실측표) : 환기1~4, 바이패스, 공청1~4 의 SA/EA - int[] sa = { 56, 63, 70, 86, 67, 65, 72, 78, 80 }; - int[] ea = { 57, 63, 70, 85, 75, 0, 0, 0, 0 }; - for (int i = 0; i < 9; i++) - { - int o = 74 + i * 4; - WriteU16(p, o, sa[i]); - WriteU16(p, o + 2, ea[i]); - } - - // 히스테리시스 데드밴드(하강) (ECO/NORMAL/TURBO 의 PM2.5/PM10/VOC/CO2) - 사양서 - int[,] hyst = { { 2, 5, 5, 50 }, { 2, 5, 5, 50 }, { 2, 5, 3, 30 } }; - for (int i = 0; i < 3; i++) - { - int o = 110 + i * 8; - WriteU16(p, o, hyst[i, 0]); - WriteU16(p, o + 2, hyst[i, 1]); - WriteU16(p, o + 4, hyst[i, 2]); - WriteU16(p, o + 6, hyst[i, 3]); - } - - // 모드별 오염단계 임계표 (3프리셋 × [CO2,PM2.5,PM10,VOC] × L1~L4 상한) - 사양서 - int[][,] thr = - { - new int[,] { {1000,1300,1600,2000}, {20,38,60,86}, {40,86,126,173}, {171,195,308,438} }, // ECO - new int[,] { {800,1100,1400,1700}, {14,29,49,69}, {28,66,102,138}, {120,150,250,350} }, // NORMAL - new int[,] { {700,1000,1300,1600}, {12,23,38,52}, {24,53,78,104}, {103,120,192,263} }, // TURBO - }; - for (int i = 0; i < 3; i++) - { - int o = StatusDecoder.THR_OFF + i * 32; - for (int g = 0; g < 4; g++) // g: 0 CO2,1 PM2.5,2 PM10,3 VOC - for (int k = 0; k < 4; k++) - WriteU16(p, o + g * 8 + k * 2, thr[i][g, k]); - } - - // 각실 온도/습도 (offset 230~, 4실 × [Temp, Humi]) - for (int r = 0; r < 4; r++) - { - int o = StatusDecoder.TEMPHUMI_OFF + r * 2; - p[o + 0] = (byte)(22 + (tick + r) % 6); // 22~27℃ - p[o + 1] = (byte)(40 + (tick + r * 7) % 30); // 40~69% - } - return p; - } - - static void WriteU16(byte[] p, int off, int v) - { - p[off] = (byte)((v >> 8) & 0xFF); - p[off + 1] = (byte)(v & 0xFF); - } - } -} diff --git a/TestProgram/PCDashBoard/Api/IErvApi.cs b/TestProgram/PCDashBoard/Api/IErvApi.cs index 123ba32..0b631bc 100644 --- a/TestProgram/PCDashBoard/Api/IErvApi.cs +++ b/TestProgram/PCDashBoard/Api/IErvApi.cs @@ -41,8 +41,5 @@ namespace ErvDashboard.Api void SetHystPreset(HystPreset preset); // 0x06 void SetHystDeadband(int preset, int pm25, int pm10, int voc, int co2); // 0x07 void SetHystThreshold(int preset, int pollutant, int l1, int l2, int l3, int l4); // 0x0D - - // ---- 데모/테스트 (합성 STATUS를 수신 경로로 주입) ---- - void InjectDemoStatus(int tick); } } diff --git a/TestProgram/PCDashBoard/Api/SerialErvApi.cs b/TestProgram/PCDashBoard/Api/SerialErvApi.cs index 602db70..86474ea 100644 --- a/TestProgram/PCDashBoard/Api/SerialErvApi.cs +++ b/TestProgram/PCDashBoard/Api/SerialErvApi.cs @@ -100,14 +100,6 @@ namespace ErvDashboard.Api public void SetHystDeadband(int preset, int pm25, int pm10, int voc, int co2) => SendFrame(CtrlFrame.HystValue(preset, pm25, pm10, voc, co2)); public void SetHystThreshold(int preset, int pollutant, int l1, int l2, int l3, int l4) => SendFrame(CtrlFrame.HystThr(preset, pollutant, l1, l2, l3, l4)); - // ================= 데모/테스트 ================= - public void InjectDemoStatus(int tick) - { - var frame = CtrlFrame.Build(StatusDecoder.STATUS, DemoStatus.BuildPayload(tick)); - _parser.Reset(); - _parser.Feed(frame); - } - public void Dispose() => _ch.Dispose(); } } diff --git a/TestProgram/PCDashBoard/MainWindow.xaml.cs b/TestProgram/PCDashBoard/MainWindow.xaml.cs index d29a202..701b999 100644 --- a/TestProgram/PCDashBoard/MainWindow.xaml.cs +++ b/TestProgram/PCDashBoard/MainWindow.xaml.cs @@ -22,8 +22,6 @@ namespace ErvDashboard readonly DashboardState _state = new(); readonly IErvApi _api = new SerialErvApi(); - readonly DispatcherTimer _demoTimer; - int _demoTick; bool _commActive; bool _ledDragging; // LED 슬라이더 thumb 드래그 중 (드래그 중엔 전송 보류 → 완료 시 1회) bool _suppressLed; // STATUS 동기 적용 중 LedDim→슬라이더 갱신으로 인한 ValueChanged 전송 차단 @@ -61,7 +59,11 @@ namespace ErvDashboard readonly DispatcherTimer _clockTimer = new() { Interval = TimeSpan.FromSeconds(1) }; // ---- 그래프용 시계열 샘플링 (5초 간격) → SQLite 실시간 저장(무제한 누적) ---- - readonly Storage.LogDb _logDb = new(System.IO.Path.Combine(AppContext.BaseDirectory, "HERV_Log.db")); + // 단일 exe(IncludeAllContentForSelfExtract=true)는 임시폴더로 추출 실행 → AppContext.BaseDirectory 가 임시폴더를 가리킴. + // DB 는 실제 exe 위치(Environment.ProcessPath)에 저장해야 publish 폴더에서 보이고 재실행 간 누적 유지됨. + static string LogDbPath => System.IO.Path.Combine( + System.IO.Path.GetDirectoryName(Environment.ProcessPath) ?? AppContext.BaseDirectory, "HERV_Log.db"); + readonly Storage.LogDb _logDb = new(LogDbPath); readonly DispatcherTimer _sampleTimer = new() { Interval = TimeSpan.FromSeconds(5) }; GraphWindow? _graphWin; @@ -94,9 +96,6 @@ namespace ErvDashboard BuildModeButtons(); BuildPresetButtons(); - _demoTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(700) }; - _demoTimer.Tick += (_, _) => DemoTick(); - _clockTimer.Tick += (_, _) => CheckSleepSchedule(); _clockTimer.Start(); @@ -163,7 +162,7 @@ namespace ErvDashboard { if (!_state.SmartSleep || _sleepEndAt is not { } endAt || DateTime.Now < endAt) return; _sleepEndAt = null; - if (!_demoTimer.IsEnabled && CanSend()) _api.SetSubMode(SubModeType.SmartSleep, false); + if (CanSend()) _api.SetSubMode(SubModeType.SmartSleep, false); ApplySubModeLocal("SmartSleep", false); Log("[스마트수면] 종료 시각 도달 → 자동 해제"); if (NoScenarioActive()) RestorePreviousMode(); @@ -219,7 +218,6 @@ namespace ErvDashboard // 팝업에서 호출 (제어 송신은 메인이 담당) public void SelectPreset(HystPreset preset) { - if (_demoTimer.IsEnabled) { _state.HystPreset = preset; return; } if (!CanSend()) return; _api.SetHystPreset(preset); _state.HystPreset = preset; @@ -237,7 +235,6 @@ namespace ErvDashboard // 활성 프리셋의 오염단계 임계 + 데드밴드 송신 (HystWindow '변경') public void ApplyHystPreset(int preset) { - if (_demoTimer.IsEnabled) return; // 데모: 상태만 갱신됨 if (!CanSend()) return; _api.SetHystThreshold(preset, 0, _state.Co2Thr[preset][0], _state.Co2Thr[preset][1], _state.Co2Thr[preset][2], _state.Co2Thr[preset][3]); _api.SetHystThreshold(preset, 1, _state.Pm25Thr[preset][0], _state.Pm25Thr[preset][1], _state.Pm25Thr[preset][2], _state.Pm25Thr[preset][3]); @@ -321,7 +318,6 @@ namespace ErvDashboard bool CanSend() { - if (_demoTimer.IsEnabled) return false; // 데모 모드에선 송신 안 함 if (!_api.IsConnected) { Log("연결 후 제어 가능합니다."); @@ -334,7 +330,6 @@ namespace ErvDashboard void Power_Click(object sender, RoutedEventArgs e) { bool next = !_state.PowerOn; - if (_demoTimer.IsEnabled) { _state.PowerOn = next; return; } if (!CanSend()) return; _api.SetPower(next); _state.PowerOn = next; @@ -346,7 +341,6 @@ namespace ErvDashboard if (sender is not Button b || b.Tag is not string tag) return; var def = Array.Find(ModeDefs, d => d.tag == tag); // 운전모드 전환 시 풍량 1단 (자동 제외). 실연결 시 ERV STATUS 로 최종 확정. - if (_demoTimer.IsEnabled) { _state.RunMode = def.mode; if (def.mode != RunMode.Auto) _state.FanMode = 1; return; } if (!CanSend()) return; _api.SetRunMode(def.mode); _state.RunMode = def.mode; @@ -369,7 +363,6 @@ namespace ErvDashboard if (sender is not Button b || b.Tag is not int speed) return; if (_state.IsAuto) { Log("자동모드에서는 풍량 조절 불가"); return; } if (_state.RunMode == RunMode.Bypass && speed > 1) { Log("바이패스는 1단 고정"); return; } - if (_demoTimer.IsEnabled) { _state.FanMode = (byte)speed; return; } if (!CanSend()) return; _api.SetFan(speed); _state.FanMode = (byte)speed; @@ -389,21 +382,7 @@ namespace ErvDashboard // 시나리오 첫 진입 → 직전 운전모드/풍량 기억(해제 시 복귀용) if (next && !anyBefore) { _modeBeforeScenario = _state.RunMode; _fanBeforeScenario = _state.FanMode; } if (tag == "SmartSleep" && !next) _sleepEndAt = null; // 스마트수면 수동 해제 → 자동해제 예약 취소 - if (_demoTimer.IsEnabled) - { - ApplySubModeLocal(tag, next); - if (NoScenarioActive()) RestorePreviousMode(); - return; - } if (!CanSend()) return; - // 상호배타: 새 모드를 켤 때 기존 활성 모드는 장치에도 OFF 전송 - // (펌웨어는 시나리오모드를 독립 변수로 유지 → status 재수신 시 부활 방지) - if (next) - { - if (tag != "SmartSleep" && _state.SmartSleep) _api.SetSubMode(SubModeType.SmartSleep, false); - if (tag != "ComfortCook" && _state.ComfortCook) _api.SetSubMode(SubModeType.ComfortCook, false); - if (tag != "ReliefRecover" && _state.ReliefRecover) _api.SetSubMode(SubModeType.ReliefRecover, false); - } _api.SetSubMode(type, next); ApplySubModeLocal(tag, next); Log($"[제어] 시나리오모드 {b.Content} → {(next ? "ON" : "OFF")}"); @@ -423,28 +402,18 @@ namespace ErvDashboard void ApplySubModeLocal(string tag, bool on) { - // 시나리오모드는 상호배타: 하나를 켜면 나머지는 해제 - if (on) + // 시나리오모드 상호배타 없음 — 클릭한 모드만 on/off (사용자가 하나만 선택). + switch (tag) { - _state.SmartSleep = tag == "SmartSleep"; - _state.ComfortCook = tag == "ComfortCook"; - _state.ReliefRecover = tag == "ReliefRecover"; - } - else - { - switch (tag) - { - case "SmartSleep": _state.SmartSleep = false; break; - case "ComfortCook": _state.ComfortCook = false; break; - case "ReliefRecover": _state.ReliefRecover = false; break; - } + case "SmartSleep": _state.SmartSleep = on; break; + case "ComfortCook": _state.ComfortCook = on; break; + case "ReliefRecover": _state.ReliefRecover = on; break; } } void Hood_Click(object sender, RoutedEventArgs e) { bool next = !_state.Hood; - if (_demoTimer.IsEnabled) { _state.Hood = next; return; } if (!CanSend()) return; _api.SetHood(next); _state.Hood = next; @@ -454,7 +423,6 @@ namespace ErvDashboard void Reset_Click(object sender, RoutedEventArgs e) { bool next = !_state.Reset; - if (_demoTimer.IsEnabled) { _state.Reset = next; return; } if (!CanSend()) return; _api.SetReset(next); _state.Reset = next; @@ -474,11 +442,6 @@ namespace ErvDashboard var room = _state.Room(roomId); bool cur = type == 0 ? room.DamperSaOpen : room.DamperEaOpen; bool next = !cur; - if (_demoTimer.IsEnabled) - { - if (type == 0) room.DamperSaOpen = next; else room.DamperEaOpen = next; - return; - } if (!CanSend()) return; _api.SetDiffuserDamper(roomId, type, next); if (type == 0) room.DamperSaOpen = next; else room.DamperEaOpen = next; @@ -511,7 +474,6 @@ namespace ErvDashboard // 직전 송신/수신값과 같으면 전송 안 함 — STATUS 역갱신이 유발한 ValueChanged(에코) 차단. // 사용자가 값을 실제로 바꾸면 dim 이 달라지므로 정상 전송됨. if (_lastLed.TryGetValue(roomId, out var last) && last == dim) return; - if (_demoTimer.IsEnabled) return; if (!CanSend()) return; _api.SetDiffuserLed(roomId, dim); _lastLed[roomId] = dim; @@ -525,18 +487,11 @@ namespace ErvDashboard if (!IsLoaded || _suppressReserve) return; if (ReserveCombo.SelectedIndex < 0) return; int hours = ReserveCombo.SelectedIndex; // 0=해제, 1~8시간 - if (_demoTimer.IsEnabled) { _state.ReserveRemainSec = hours * 3600; return; } if (!CanSend()) return; _api.SetReserve(hours); Log(hours == 0 ? "[제어] 예약 해제" : $"[제어] {hours}시간 후 꺼짐 예약"); } - // ================= 데모 모드 (버튼 제거됨 — 내부 합성 STATUS 경로는 비활성 상태로 유지) ================= - void DemoTick() - { - _api.InjectDemoStatus(_demoTick++); - } - // ================= UI 갱신 ================= void RefreshControls() { @@ -564,15 +519,19 @@ namespace ErvDashboard // - 자동 : 수동 조절 불가(전 단 비활성) // - 바이패스 : 최대 1단(2~4단 비활성) // - 환기/공청 : 0~4단 - // 시나리오모드 활성 시: 운전모드·풍량·선택 안 된 시나리오모드 비활성화 - // 쾌적조리는 '연동운전중(HoodRunning=후드 가동중)' 기준으로 시나리오 활성 판단. - // 후드 OFF(대기 상태)면 ERV는 본래 운전모드로 복귀하므로 운전모드를 다시 활성화해야 함(사양 3.1). - bool subActive = _state.SmartSleep || _state.HoodRunning || _state.ReliefRecover; + // 시나리오모드 활성 시: 운전모드·풍량·선택 안 된 시나리오모드 비활성화(=클릭 불가) + // 쾌적조리 잠금은 '메이크업 실제 연동중'(쾌적조리 토글 ON + 후드 가동) 일 때만. + // - 펌웨어 Hood_process() 는 Hood_YeunDong_Enable==1 일 때만 모드/풍량을 건드림. + // 쾌적조리 OFF면 후드만 켜져도 ERV는 그대로 → 다른 시나리오모드/운전모드 선택 가능해야 함. + // - 후드 OFF(대기)면 메이크업 비연동 → 본래 운전모드로 복귀하므로 잠금 해제(사양 3.1). + bool makeupActive = _state.ComfortCook && _state.HoodRunning; + bool subActive = _state.SmartSleep || makeupActive || _state.ReliefRecover; int fanMax = _state.RunMode == RunMode.Bypass ? 1 : 4; foreach (var fb in _fanButtons) { int sp = (int)fb.Tag!; - fb.IsEnabled = !subActive && !_state.IsAuto && sp <= fanMax; + // 전원 OFF면 풍량 조절 불가 → 0 포함 전 단 비활성 + fb.IsEnabled = _state.PowerOn && !subActive && !_state.IsAuto && sp <= fanMax; SetActive(fb, sp == _state.FanMode); } @@ -582,11 +541,12 @@ namespace ErvDashboard // (HoodRunning 으로 강조하면 대기/Roll-back 상태에서 버튼이 꺼져 보여 재선택 시 토글이 반대로 먹던 문제 수정) SetActive(ComfortCookBtn, _state.ComfortCook); SetActive(ReliefRecoverBtn, _state.ReliefRecover); - // (활성 모드 버튼은 OFF 토글 가능해야 하므로 자기 자신은 유지) - SmartSleepBtn.IsEnabled = !subActive || _state.SmartSleep; - // 쾌적조리는 사양 9p 3.1 의 '독립 토글(연동 스위치)' — 후드 연결/가동·다른 시나리오와 무관하게 항상 토글 가능. + // 시나리오모드 버튼은 항상 선택 가능(클릭 가능). 다른 시나리오가 켜져 있어도 비활성화하지 않음. + // - 스마트수면 ↔ 안심회복은 펌웨어상 단일 Ext_Run_Mode 라 동시 불가 → 클릭 시 SubMode_Click 이 서로 전환. + // - 쾌적조리는 별도 변수(Hood_YeunDong_Enable)라 독립 토글. + SmartSleepBtn.IsEnabled = true; ComfortCookBtn.IsEnabled = true; - ReliefRecoverBtn.IsEnabled = !subActive || _state.ReliefRecover; + ReliefRecoverBtn.IsEnabled = true; // 스마트수면 시간설정 버튼 : 스마트수면 ON 일 때만 활성 SmartSleepSetBtn.IsEnabled = _state.SmartSleep; foreach (var mb in _modeButtons) mb.IsEnabled = !subActive; @@ -676,7 +636,6 @@ namespace ErvDashboard protected override void OnClosed(EventArgs e) { - _demoTimer.Stop(); _api.Dispose(); _logDb.Dispose(); base.OnClosed(e); diff --git a/doc/260618_내부댐퍼_모드변경시_미동작_수정.md b/doc/260618_내부댐퍼_모드변경시_미동작_수정.md index 81fc506..a1df33f 100644 --- a/doc/260618_내부댐퍼_모드변경시_미동작_수정.md +++ b/doc/260618_내부댐퍼_모드변경시_미동작_수정.md @@ -170,3 +170,216 @@ ## 빌드/배포 - `dotnet publish ErvDashboard.csproj -c Release` → 오류 0. publish 단일 exe 갱신. + +--- + +# 260618 (6) 쾌적조리 — 후드 전원 OFF 시 토글 자동 해제 + +## 요청 (사장님) +- 쾌적조리 선택 → 후드 동작 → **후드 전원 OFF** 했을 때, 대시보드 쾌적조리 버튼이 **저절로 꺼지게**(자동 해제) 해달라. +- 현재: 후드 OFF 후 이전 모드로 운전은 복귀되나 쾌적조리 토글은 armed 유지 → 수동으로 눌러야 꺼짐. + +## 사양 변경 주의 +- 기존 문서화 사양(개발사양서 260613 9p 3.3)은 **"후드 OFF는 메이크업 복귀 타이밍이지 토글 OFF가 아님 → 토글 armed 유지"**. 이번 요청으로 **반대로 변경**(후드 OFF=토글 자동 해제). + +## 원인/구조 (정적분석) +- 대시보드 `_state.ComfortCook` ← STATUS byte4 bit1 ← 펌웨어 `Hood_YeunDong_Enable`. (byte5 bit0 `HoodEnable` 도 동일 변수) +- `Hood_YeunDong_Enable` 은 명령(`My_Homenet.c` CTRL_SUBMODE/CTRL_HOOD)으로만 set/clear, 펌웨어 자동 해제 없음 → 후드 OFF 후에도 1 유지 → 토글 계속 ON. +- 펌웨어 `Ext_Run_Mode==2` 쾌적조리 상태머신(My_system.c 846/952)은 대시보드 경로에서 dead code. + +## 변경 파일/내용 +- `Source/HECO2/User/My_Hood.c` `Hood_process()` 후드 OFF 전이 분기(`Hood_Status==0`, L197~): + - `Hood_YeunDong_Enable = 0;` 추가. 마커 `[쾌적조리자동해제]`. + - 효과: 후드 단수 0 도달(전원 OFF) 시 펌웨어가 토글 해제 → STATUS byte4 bit1/byte5 bit0 모두 0 → 대시보드 버튼 자동 해제. **대시보드 수정 불필요.** +- 부작용 없음 확인: 이 시점 `Hood_Status=0`·`Hood_Yeundong_flag=0` 이라 댐퍼 메이크업(My_system.c L1117)·룸컨 통지(My_RJ2.c L296) 이미 OFF 조건. 모드/풍량 복귀는 같은 분기에서 그대로 수행. + +## 빌드 결과 +- `bash build.sh all` → 성공, 경고/오류 0. text 43948 → **43952** (+4B), data 1940, bss 3388. + +## 미해결 / 후속 +- 실장비: 쾌적조리 ON → 후드 ON → **후드 전원 OFF** 시 쾌적조리 버튼이 자동으로 꺼지는지 + 이전 모드 복귀 동시 확인. +- 엣지: 후드를 **485 통신 두절(플러그 뽑힘)** 로 끄면 `Hood_Status` 가 마지막 값 유지되어 OFF 전이가 안 뜰 수 있음 → 그 경우 자동해제 미동작. 필요 시 `Hood_Conn_Timeout==0`(통신두절)에도 해제 추가 검토. + +--- + +# 260618 (7) 쾌적조리 미선택 + 후드 ON 시 다른 시나리오모드 버튼 잠김 수정 + +## 요청 (사장님) +- 쾌적조리를 **누르지 않고** 후드를 켰는데도 스마트수면/안심회복 등 다른 시나리오모드 버튼이 **선택 불가**(클릭 안 됨). +- 쾌적조리 미선택 상태에서 후드만 켜도 다른 시나리오모드 버튼은 **선택 가능**해야 함. (자동 켜짐이 아니라 '클릭 가능'.) + +## 원인 (대시보드) +- `MainWindow.xaml.cs` L570 `subActive` 에 `_state.HoodRunning` 포함. +- `HoodRunning` = STATUS byte5 bit1 = `Hood_Status != 0` = 후드 **물리 가동중**(쾌적조리 선택 무관, `My_Homenet.c:131`). +- 그래서 후드만 켜도 `subActive=true` → 스마트수면/안심회복/운전모드/풍량 버튼이 비활성(클릭 불가). +- 그러나 펌웨어 `Hood_process()` 는 `Hood_YeunDong_Enable==1`(쾌적조리 armed) 일 때만 모드/풍량을 건드림 → 쾌적조리 OFF면 후드가 켜져도 ERV 동작에 영향 없음 → 잠글 이유 없음. + +## 변경 파일/내용 +- `TestProgram/PCDashBoard/MainWindow.xaml.cs` L570 부근: + - `bool makeupActive = _state.ComfortCook && _state.HoodRunning;` 신설. + - `subActive = _state.SmartSleep || makeupActive || _state.ReliefRecover;` (기존 `HoodRunning` → `makeupActive` 교체). + - 효과: 쾌적조리 미선택 + 후드 ON → `makeupActive=false` → 다른 시나리오모드/운전모드/풍량 버튼 선택 가능. 쾌적조리 ON + 후드 가동(메이크업 실제 연동중)일 때만 잠금 유지. + - (쾌적조리 ON + 후드 OFF '대기'도 `makeupActive=false` → 본래 운전모드 조작 가능, 사양 3.1 일치.) + +## 빌드/배포 +- `dotnet publish ErvDashboard.csproj -c Release` → 오류 0 (NU1701 경고만). publish 단일 exe 갱신. + +## 후속 +- 실장비: 쾌적조리 미선택 + 후드 ON 시 스마트수면/안심회복 버튼 클릭되는지, 쾌적조리 ON + 후드 가동 시엔 잠기는지 확인. + +--- + +# 260618 (8) 시나리오모드 버튼 — 항상 선택 가능하게 (상호 비활성 해제) + +## 요청 (사장님) +- 스마트수면을 선택하면 다른 시나리오 버튼이 비활성화됨 → **그냥 모두 선택(클릭) 가능**하게. + +## 펌웨어 제약 (참고) +- 시나리오는 단일 `Ext_Run_Mode` : 스마트수면=4, 안심회복=1 → **동시 ON 물리적 불가**(전환만 가능). 쾌적조리는 별도 `Hood_YeunDong_Enable` → 독립. + +## 변경 파일/내용 +- `TestProgram/PCDashBoard/MainWindow.xaml.cs` L589~592: + - `SmartSleepBtn.IsEnabled`/`ReliefRecoverBtn.IsEnabled` 의 `!subActive || _state.xxx` 조건 제거 → **세 버튼 모두 `IsEnabled = true`** (쾌적조리는 이미 true). + - 클릭 시 동작은 기존 `SubMode_Click` 상호배타(L401~406) 그대로 → 스마트수면↔안심회복은 서로 전환. +- 운전모드/풍량/프리셋 버튼의 `subActive` 잠금은 유지(시나리오 동작 중엔 모드/풍량 시나리오가 제어). + +## 빌드/배포 +- `dotnet publish ErvDashboard.csproj -c Release` → 오류 0. publish 갱신. + +## 후속/보류 +- 현재 상호배타라 **쾌적조리 ON 중 스마트수면/안심회복 선택 시 쾌적조리도 꺼짐**(SubMode_Click L401~406). 사양상 쾌적조리는 독립이므로, 쾌적조리는 시나리오 전환과 무관하게 유지하길 원하면 별도 분리 필요(추후 결정). + - → 260618 (9) 에서 상호배타 로직 전체 삭제로 해소. + +--- + +# 260618 (9) 시나리오 상호배타 로직 삭제 + 데모 루틴 전체 삭제 + +## 요청 (사장님) +- 시나리오 상호배타 로직 삭제(어차피 하나만 선택). 데모 루틴도 모두 삭제. + +## 변경 — 상호배타 삭제 +- `TestProgram/PCDashBoard/MainWindow.xaml.cs` + - `SubMode_Click` : 새 모드 켤 때 기존 모드 OFF 전송하던 블록(구 L399~406) 삭제. + - `ApplySubModeLocal` : on 시 나머지 해제하던 로직 → **클릭한 태그만 on/off** 로 단순화. +- 동작: 시나리오 버튼은 서로 영향 없이 각각 토글. (스마트수면↔안심회복은 펌웨어 단일 Ext_Run_Mode 라 장치단에서 전환되고 STATUS 로 재동기.) + +## 변경 — 데모 루틴 전체 삭제 +- `MainWindow.xaml.cs` : `_demoTimer`/`_demoTick` 필드, 생성자 타이머 초기화, `DemoTick()`, OnClosed `_demoTimer.Stop()`, 그리고 제어 핸들러 곳곳의 `if(_demoTimer.IsEnabled){...}` 분기(전원/모드/풍량/시나리오/후드/리셋/댐퍼/LED/예약/프리셋) 전부 제거. `CanSend()` 의 데모 가드도 제거. +- `Api/IErvApi.cs` : `InjectDemoStatus` 선언 삭제. +- `Api/SerialErvApi.cs` : `InjectDemoStatus` 구현 삭제. +- `ErvProtocol/DemoStatus.cs` : 파일 삭제(타 프로젝트 참조 없음 확인). + +## 빌드/배포 +- `dotnet publish ErvDashboard.csproj -c Release` → 오류 0. publish 갱신. + +## (10) 안심회복 기본 선택 표시 — 해결됨(대시보드 버그 아님) +- 증상: 대시보드 실행 시 안심회복이 선택된 것으로 표시. +- 정적분석: 대시보드는 기본 미선택이 맞음(_state 전부 false, RefreshControls 가 _state 값으로만 강조). 안심회복 강조는 오직 STATUS byte4 bit2(=펌웨어 `Ext_Run_Mode==1`) 수신 시에만 켜짐. +- 펌웨어 `Ext_Run_Mode` 기본값 0(My_bunbaegi.c:984), 1로 설정되는 곳은 명령(My_Homenet.c:319, ReliefRecover ON)뿐 → 펌웨어 자가진입 없음. +- **원인 확정**: 연결된 ERV가 이전 테스트에서 안심회복 켠 상태로 RAM에 남아있었고(전원 미재투입), 대시보드만 재실행해 그 상태를 그대로 표시한 것. +- **검증 완료**: ERV 전원 OFF→ON(콜드부트) 후 대시보드 실행 시 안심회복 버튼 해제됨(사장님 확인 2026-06-18). 코드 수정 불필요. + +--- + +# 260618 (11) 대시보드 전원 ON 시 룸컨 간헐적 미동작 (타이밍 레이스) 수정 + +## 증상 +- 대시보드에서 전원 ON 하면 ERV에 연결된 룸컨이 **간헐적으로 안 켜짐**. + +## 원인 (룸컨 echo ↔ 푸시 레이스) +- 룸컨(RJ2)은 마스터로 폴링, ERV는 응답(slave). 전원/모드/풍량 변경은 ERV가 `Command_request_type`의 `TYPE_MODE|TYPE_FAN_SPEED` 플래그를 보고 `RX_DATA_MODE_NORMAL` 응답에 `COMMAND_CONTROLL`(Set_Run/Set_Fan)을 실어 푸시. +- CTRL_POWER ON 핸들러(`My_Homenet.c`)가 `Set_Fan_Mode = Fan_Mode = 1` 로 **Set==현재**로 만듦. +- 푸시(NORMAL)보다 룸컨 상태 echo `RX_DATA_CONTROLL`(My_RJ2.c L698 else)/`RX_DATA_MODE_EVENT`(L409 else)가 먼저 도착하면: + - else가 `Set_Fan_Mode = Fan_Mode = Rx[3]`로 **룸컨 현재(OFF=0) 상태를 Set_* 에 덮음**, + - 이어 L715~716이 `Set==현재`라 `TYPE_FAN_SPEED`를 **조기 클리어** → 푸시가 영영 안 나감 → 룸컨 미동작. +- 도착 순서(NORMAL vs CONTROLL/EVENT)에 따라 간헐적. + +## 수정 (2파일 3곳) +- `Source/HECO2/User/My_Homenet.c` CTRL_POWER ON: `Set_Fan_Mode = Fan_Mode = 1;` → **`Set_Fan_Mode = 1;`** (Fan_Mode 즉시 1로 안 올림). Set_Fan(1)!=Fan(0) 유지 → echo가 먼저 와도(아래 가드와 함께) TYPE_FAN_SPEED 가 살아 NORMAL 푸시 보장. 팬은 룸컨 echo가 Fan_Mode=1 로 갱신하면 켜짐. 마커 `[전원ON룸컨레이스]`. +- `Source/HECO2/User/My_RJ2.c` 룸컨 echo else 2곳을 가드 : + - L409 `RX_DATA_MODE_EVENT` else → `else if(!(Command_request_type & (TYPE_MODE|TYPE_FAN_SPEED)))` + - L698 `RX_DATA_CONTROLL` else → 동일 가드 + - 효과: 대시보드 명령(전원/모드/풍량)이 푸시 대기중이면 룸컨 자기보고로 Set_* 를 덮지 않음 → 명령 유실/조기 클리어 방지. ack(SEND_FLAG) 경로는 if-branch라 가드 영향 없음. 평상시(명령 없음) 룸컨 패널 조작 반영은 그대로(가드 통과). + +## 빌드 결과 +- `bash build.sh all` → 성공, 경고/오류 0. text 43952 → **43972**. + +## 후속 / 참고 +- 실장비: 대시보드 전원 ON 반복 시 룸컨이 매번 켜지는지 확인. +- 동일 패턴 잠재: `CTRL_RUNMODE`(L287/292)·`CTRL_FAN`(L308)도 `Set==현재`로 equalize → 같은 716 조기클리어 레이스 소지. else 가드(L409/698)로 클로버는 막히나, 모드/풍량 변경 시에도 룸컨 푸시 누락이 보이면 동일하게 de-equalize 적용 검토. (이번엔 보고된 전원 ON 만 수정.) + - → 260618 (12) 에서 룸컨 전용 pending(`RoomCtrl_Push`)으로 전원 ON/OFF 모두 견고화. (11)의 de-equalize 는 원복(불필요). + +--- + +# 260618 (12) 전원 OFF 시 룸컨이 옛 모드(공청) 계속 표시 + (11) 통합 — 룸컨 전용 푸시 플래그 + +## 증상 +- 공청에서 대시보드 전원 OFF → ERV(모터)는 정지하는데 **룸컨은 계속 "공청" 표시**. (전원 ON 간헐 미동작과 같은 계열) + +## 근본 원인 (공유 플래그 레이스) +- `Command_request_type` 을 **룸컨(RJ2)·각실분배기(bunbagi) 두 소비자가 공유**. + - `My_bunbaegi.c:522` 가 프레임 송신 후 `Command_request_type = 0;` 로 **전체를 wipe** → 분배기가 먼저 처리하면 RJ2가 플래그를 못 봐 룸컨 푸시 유실. + - 전원 OFF는 모터 정지를 위해 `Set_Run=Run=VENT, Set_Fan=Fan=0` **equalize 강제**(주석 250~253) → `My_RJ2.c:715~716`(Set==현재 → TYPE_MODE/FAN 클리어)가 NORMAL 푸시 전에 플래그 제거 → 룸컨 푸시 유실. + - 둘 다 도착순서 의존 → 간헐/상황적. +- (부수발견) `TYPE_POWER(0x40)` == `TYPE_HOOD_STATE(0x40)` **비트 충돌**(My_define.h). 전원명령↔후드상태가 서로 오인될 잠재 버그. 이번 수정은 별도 변수를 써서 이 충돌을 우회(미해결로 남김 — 후속 정리 권장). + +## 수정 — 룸컨 전용 pending 플래그 `RoomCtrl_Push` +- `My_define.h` : `extern uint8_t RoomCtrl_Push;` 추가. +- `My_RJ2.c` : + - 전역 `uint8_t RoomCtrl_Push = 0;` 정의. + - NORMAL 푸시 조건(L332)에 `|| RoomCtrl_Push` 추가 → 분배기 wipe·716 클리어와 무관하게 룸컨에 푸시. + - echo else 가드(L409/698) : `!RoomCtrl_Push && !(...)` → pending 중 룸컨 자기보고로 Set_* 안 덮음. + - ack(SEND_FLAG) 경로(EVENT L399 / CONTROLL L691) : `RoomCtrl_Push = 0;` → 룸컨이 명령 수신·확인하면 해제. +- `My_Homenet.c` CTRL_POWER : `Command_request_type |= (TYPE_POWER|TYPE_MODE|TYPE_FAN_SPEED);` 직후 `RoomCtrl_Push = 1;`. (11)의 ON de-equalize 는 원복(`Set_Fan_Mode = Fan_Mode = 1;`) — RoomCtrl_Push 가 푸시를 보장하므로 불필요, 모터는 즉시 ON. + +## 효과 +- 전원 ON/OFF 시 분배기·716 타이밍과 무관하게 룸컨에 Set_Run/Set_Fan(전원 OFF=VENT/0) 이 반드시 푸시 → 룸컨이 즉시 OFF(또는 ON) 동기. ack 시 pending 해제로 평상시 룸컨 패널 조작 반영은 그대로. + +## 빌드 결과 +- `bash build.sh all` → 성공, 경고/오류 0. text 43972 → **44024**. + +## 후속 / 주의 +- **실장비 검증 필수** : 핵심 통신 상태머신(RJ2) 변경. 전원 ON/OFF 반복 시 룸컨이 매번 동기되는지, 룸컨 패널 직접 조작·후드연동·분배기 동작에 회귀 없는지 확인. +- `TYPE_POWER`/`TYPE_HOOD_STATE` 0x40 비트 충돌은 미해결(빈 비트 0x08 로 분리 권장). 이번 수정과 독립. +- 모드/풍량 변경(CTRL_RUNMODE/CTRL_FAN)도 룸컨 동기 누락이 보이면 동일하게 `RoomCtrl_Push = 1;` 적용 가능. +- **검증 완료**: 전원 ON/OFF 시 룸컨 동기 정상(사장님 확인 2026-06-18). + +--- + +# 260618 (13) 전원 OFF 시 풍량 버튼(0 포함) 비활성화 + +## 요청 +- 전원 OFF 하면 풍량 0 버튼도 비활성화. + +## 변경 +- `TestProgram/PCDashBoard/MainWindow.xaml.cs` 풍량 버튼 활성 조건: + - `fb.IsEnabled = !subActive && !_state.IsAuto && sp <= fanMax;` + - → `fb.IsEnabled = _state.PowerOn && !subActive && !_state.IsAuto && sp <= fanMax;` + - 전원 OFF면 0~4 전 단 비활성(풍량 조절 불가). 운전모드 버튼은 그대로(전원 OFF에서 모드 누르면 전원 ON). + +## 빌드/배포 +- `dotnet publish ErvDashboard.csproj -c Release` → 오류 0. (BAML stale 캐시로 1회 실패 → obj/bin clean 후 성공.) publish 갱신. + +--- + +# 260618 (14) 그래프 로그 DB(HERV_Log.db)가 publish 폴더에 안 생기던 문제 + +## 증상 +- 그래프 시계열 DB `HERV_Log.db` 가 publish 폴더에 안 보임. 재실행 간 그래프 이력도 이어지지 않음. + +## 원인 +- DB 경로가 `AppContext.BaseDirectory` 기준이었음(`MainWindow.xaml.cs`). +- csproj 가 `PublishSingleFile=true` + **`IncludeAllContentForSelfExtract=true`** → 단일 exe 실행 시 **매번 `%TEMP%\.net\ErvDashboard\<랜덤해시>\` 로 추출**해 실행 → `AppContext.BaseDirectory` 가 그 임시폴더를 가리킴. +- 결과: DB 가 임시폴더에 생기고(publish 폴더엔 없음), **실행마다 추출 해시가 달라 DB 가 매번 새로 생겨 데이터가 흩어짐**(이력 유실). (디스크 검색으로 `%TEMP%\.net\ErvDashboard\*\HERV_Log.db` 다수 확인.) + +## 변경 +- `TestProgram/PCDashBoard/MainWindow.xaml.cs` : DB 경로를 실제 exe 위치 기준으로. + - `AppContext.BaseDirectory` → `Path.GetDirectoryName(Environment.ProcessPath) ?? AppContext.BaseDirectory` + - 이제 `HERV_Log.db` 가 exe 와 같은 publish 폴더에 고정 생성 → 재실행해도 같은 파일에 누적. + +## 빌드/배포 +- `dotnet publish` → 오류 0 (clean 후). publish 갱신. + +## 참고 +- 임시폴더에 흩어진 옛 DB 는 고아. 보존하려면 최신 것을 publish 폴더 `HERV_Log.db` 로 복사하면 이력 이어짐. +- 그래프 '불러오기'→'엑셀저장' 은 `_selectedDate` 로 DB 재조회(`LoadByDate`)하므로 과거 날짜 데이터가 정상 저장됨(코드 확인, 수정 불필요). 단 위 DB 경로 수정 이후 기록분부터 누적. diff --git a/doc/claude-memory/MEMORY.md b/doc/claude-memory/MEMORY.md index 7eeeaae..b9e4f7e 100644 --- a/doc/claude-memory/MEMORY.md +++ b/doc/claude-memory/MEMORY.md @@ -2,3 +2,4 @@ - [HERV 마스터 소스](herv-master-source.md) — 검증된 펌웨어 = D:\Project\nuvoton\HERV_DL_MH_2nd\Program, 댐퍼/팬 회귀 비교 기준 - [내부댐퍼 팬 게이트](internal-damper-fan-gate.md) — 본체 댐퍼는 팬이 0까지 내려가야 모드별 이동, 명령경로 끼어들면 미동작 +- [Command_request_type 공유 레이스](command-request-type-shared-race.md) — 룸컨·분배기 공유 플래그 → 전원 푸시 유실, RoomCtrl_Push 전용 플래그로 해결 diff --git a/doc/claude-memory/command-request-type-shared-race.md b/doc/claude-memory/command-request-type-shared-race.md new file mode 100644 index 0000000..b584062 --- /dev/null +++ b/doc/claude-memory/command-request-type-shared-race.md @@ -0,0 +1,16 @@ +--- +name: command-request-type-shared-race +description: ERV의 Command_request_type은 룸컨(RJ2)·분배기(bunbagi) 공유 플래그라 대시보드 명령 푸시에 레이스가 있음 +metadata: + node_type: memory + type: project +--- + +HECO2 ERV 펌웨어에서 `Command_request_type`(My_RJ2.c 전역, My_define.h TYPE_* 비트)은 **룸컨(RJ2, SC0)과 각실분배기(My_bunbaegi.c) 두 통신 소비자가 공유**한다. 대시보드 명령(전원/모드/풍량)이 이 플래그를 set 하면 두 버스가 각자 소비·클리어하는데: +- `My_bunbaegi.c:522` 가 프레임 송신 후 `Command_request_type = 0;` 로 **전체 wipe** → 분배기가 먼저 돌면 RJ2가 플래그를 못 봐 룸컨 푸시 유실. +- `My_RJ2.c:715~716` 이 `Set==현재` 면 TYPE_MODE/FAN 클리어. 전원 OFF는 모터정지 위해 `Set_Run=Run=VENT, Set_Fan=Fan=0` equalize 강제(My_Homenet.c CTRL_POWER 주석 250~253) → NORMAL 푸시 전에 플래그 제거 → 룸컨이 옛 모드(예: 공청) 계속 표시. +- 둘 다 도착순서 의존 → 간헐적. + +**Why:** 전원 ON 간헐 미동작 / 전원 OFF 후 룸컨 옛모드 표시의 근본 원인. 2026-06-18 수정. + +**How to apply:** 룸컨에 반드시 보내야 하는 대시보드 명령은 공유 `Command_request_type` 대신 **룸컨 전용 pending `RoomCtrl_Push`**(My_RJ2.c 전역)로 표시한다. 분배기가 못 건드리고 RJ2 ack(SEND_FLAG echo, EVENT L399/CONTROLL L691)로만 해제. RJ2 NORMAL 푸시조건(L332)에 `|| RoomCtrl_Push`, echo else 가드(L409/698)에 `!RoomCtrl_Push &&` 추가됨. 모드/풍량도 동기 누락 보이면 `CTRL_RUNMODE/CTRL_FAN`에 `RoomCtrl_Push=1` 적용. 별개 잠재버그: `TYPE_POWER==TYPE_HOOD_STATE==0x40` 비트 충돌(빈 비트 0x08 분리 권장). 관련 [[internal-damper-fan-gate]], [[herv-master-source]].