fix: 260618 룸컨 동기·쾌적조리·시나리오·그래프DB 등 수정
펌웨어(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) <noreply@anthropic.com>
This commit is contained in:
@@ -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바이패스 */
|
||||
|
||||
@@ -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) : 메이크업 풍량 단수 추종 갱신
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
_state.SmartSleep = tag == "SmartSleep";
|
||||
_state.ComfortCook = tag == "ComfortCook";
|
||||
_state.ReliefRecover = tag == "ReliefRecover";
|
||||
}
|
||||
else
|
||||
{
|
||||
// 시나리오모드 상호배타 없음 — 클릭한 모드만 on/off (사용자가 하나만 선택).
|
||||
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);
|
||||
|
||||
@@ -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 경로 수정 이후 기록분부터 누적.
|
||||
|
||||
@@ -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 전용 플래그로 해결
|
||||
|
||||
@@ -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]].
|
||||
Reference in New Issue
Block a user