스마트수면 거실 댐퍼 버그 수정 완료했습니다.

## 원인
[My_system.c:893](Source/HECO2/User/My_system.c#L893) `Ext_Run_Mode == 4`(스마트수면) 블록에서:
- 진입 시 초기상태(L905)는 **거실(room1)을 올바르게 CLOSE** 했지만,
- **매 틱 CO2 루프가 `Room_Num = 1`부터 돌아(L916)** 거실까지 CO2 기준으로 다시 OPEN 시키고 있었습니다. (room1=거실, room2~4=침실1~3)

## 수정
```c
/* 거실(room1)은 사양상 CO2 무관하게 항상 CLOSE → 매 틱 강제 닫음 */
Memory_Diffuser_Dmp_Ang_SA[1] = 0; Memory_Diffuser_Dmp_Ang_RA[1] = 0;
Diffuser_Air_quality[1] = 0;

for(Room_Num = 2; Room_Num < 5; Room_Num++)   // ← 1에서 2로: 침실1~3만 CO2 개폐
{ ... }
```
이제 **거실은 CO2와 무관하게 항상 닫힘**, 침실1~3만 CO2 ≥1000 OPEN / ≤800 CLOSE로 동작합니다 (개발사양서 8p 일치).

## 결과
- 빌드: **성공, 경고/오류 0** (text 43940)
- 기록: [doc/260618_내부댐퍼_모드변경시_미동작_수정.md](doc/260618_내부댐퍼_모드변경시_미동작_수정.md)에 (2)번 항목으로 추가

실장비에서 스마트수면 진입 시 거실이 닫히는지, 침실 CO2 개폐가 정상인지 확인해 주세요.


스마트수면은 수정된 거 확인했어.
동작모드 변경시 내부댐퍼는 여전히 안돼.


② 제거로도 안 된다면 제 가설(팬이 0에 도달 못 함)이 틀렸거나 불완전합니다. 실제 코드를 다시 정밀하게 봐야 합니다. (1) 팬 타깃을 0이 아닌 값으로 만드는 다른 경로, (2) 스텝모터 구동(Timer1) 실행 여부, (3) 현재 게이팅 상태를 한꺼번에 확인합니다.


Source\HECO2\User\MyMotor.c:861:uint8_t Target_Fan1_Speed = 0, Target_Fan2_Speed = 0;
Source\HECO2\User\MyMotor.c:931:		Target_Fan1_Speed = 0;
Source\HECO2\User\MyMotor.c:932:		Target_Fan2_Speed = 0;
Source\HECO2\User\MyMotor.c:956:			Target_Fan1_Speed = 0;
Source\HECO2\User\MyMotor.c:957:			Target_Fan2_Speed = 0;
Source\HECO2\User\MyMotor.c:965:            Target_Fan1_Speed = Test_Fan1_Speed;
Source\HECO2\User\MyMotor.c:966:            Target_Fan2_Speed = Test_Fan2_Speed;
Source\HECO2\User\MyMotor.c:971:            Target_Fan1_Speed = Test_Fan1_Speed;
Source\HECO2\User\MyMotor.c:972:            Target_Fan2_Speed = Test_Fan2_Speed;
Source\HECO2\User\MyMotor.c:977:            Target_Fan1_Speed = Test_Fan1_Speed;
Source\HECO2\User\MyMotor.c:978:            Target_Fan2_Speed = Test_Fan2_Speed;
Source\HECO2\User\MyMotor.c:996:			Target_Fan1_Speed = 0; // off
Source\HECO2\User\MyMotor.c:997:			Target_Fan2_Speed = 0; // off
Source\HECO2\User\MyMotor.c:1035:			Target_Fan1_Speed = 0; // off
Source\HECO2\User\MyMotor.c:1036:			Target_Fan2_Speed = 0; // off
Source\HECO2\User\MyMotor.c:1072:			Target_Fan1_Speed = 0; // off
Source\HECO2\User\MyMotor.c:1073:			Target_Fan2_Speed = 0; // off
Source\HECO2\User\MyMotor.c:1106:			Target_Fan1_Speed = 0; // off
Source\HECO2\User\MyMotor.c:1107:			Target_Fan2_Speed = 0; // off
Source\HECO2\User\MyMotor.c:1149:		Target_Fan1_Speed = Test_Fan1_Speed;
Source\HECO2\User\MyMotor.c:1150:		Target_Fan2_Speed = Test_Fan2_Speed;
Source\HECO2\User\MyMotor.c:1244:				Target_Fan1_Speed = s_FAN1_VEN_1_DAN;
Source\HECO2\User\MyMotor.c:1245:				Target_Fan2_Speed = s_FAN2_VEN_1_DAN;
Source\HECO2\User\MyMotor.c:1248:				Target_Fan1_Speed = s_FAN1_VEN_2_DAN;
Source\HECO2\User\MyMotor.c:1249:				Target_Fan2_Speed = s_FAN2_VEN_2_DAN;
Source\HECO2\User\MyMotor.c:1252:				Target_Fan1_Speed = s_FAN1_VEN_3_DAN;
Source\HECO2\User\MyMotor.c:1253:				Target_Fan2_Speed = s_FAN2_VEN_3_DAN;
Source\HECO2\User\MyMotor.c:1256:				Target_Fan1_Speed = s_FAN1_VEN_4_DAN;
Source\HECO2\User\MyMotor.c:1257:				Target_Fan2_Speed = s_FAN2_VEN_4_DAN;
Source\HECO2\User\MyMotor.c:1260:				Target_Fan1_Speed = 0;
Source\HECO2\User\MyMotor.c:1261:				Target_Fan2_Speed = 0;
Source\HECO2\User\MyMotor.c:1272:					Target_Fan1_Speed = s_FAN1_VEN_1_DAN;
Source\HECO2\User\MyMotor.c:1273:					Target_Fan2_Speed = s_FAN2_VEN_1_DAN;
Source\HECO2\User\MyMotor.c:1276:					Target_Fan1_Speed = s_FAN1_VEN_2_DAN;
Source\HECO2\User\MyMotor.c:1277:					Target_Fan2_Speed = s_FAN2_VEN_2_DAN;
Source\HECO2\User\MyMotor.c:1280:					Target_Fan1_Speed = s_FAN1_VEN_3_DAN;
Source\HECO2\User\MyMotor.c:1281:					Target_Fan2_Speed = s_FAN2_VEN_3_DAN;
Source\HECO2\User\MyMotor.c:1284:                    Target_Fan1_Speed = s_FAN1_VEN_4_DAN;
Source\HECO2\User\MyMotor.c:1285:                    Target_Fan2_Speed = s_FAN2_VEN_4_DAN;
Source\HECO2\User\MyMotor.c:1288:					Target_Fan1_Speed = 0;
Source\HECO2\User\MyMotor.c:1289:					Target_Fan2_Speed = 0;
Source\HECO2\User\MyMotor.c:1298:					Target_Fan1_Speed = s_FAN1_AIR_1_DAN;
Source\HECO2\User\MyMotor.c:1299:					Target_Fan2_Speed = 0;//s2 400 // s_FAN2_AIR_1_DAN;
Source\HECO2\User\MyMotor.c:1302:					Target_Fan1_Speed = s_FAN1_AIR_2_DAN;
Source\HECO2\User\MyMotor.c:1303:					Target_Fan2_Speed = 0;//s2 400 / s_FAN2_AIR_2_DAN;
Source\HECO2\User\MyMotor.c:1306:					Target_Fan1_Speed = s_FAN1_AIR_3_DAN;
Source\HECO2\User\MyMotor.c:1307:					Target_Fan2_Speed = 0;//s2 400 / s_FAN2_AIR_3_DAN;
Source\HECO2\User\MyMotor.c:1310:					Target_Fan1_Speed = s_FAN1_AIR_4_DAN;
Source\HECO2\User\MyMotor.c:1311:					Target_Fan2_Speed = 0;//s2 400 / s_FAN2_AIR_3_DAN;
Source\HECO2\User\MyMotor.c:1314:					Target_Fan1_Speed = 0;
Source\HECO2\User\MyMotor.c:1315:					Target_Fan2_Speed = 0;
Source\HECO2\User\MyMotor.c:1324:					Target_Fan1_Speed = s_FAN1_VEN_1_DAN;
Source\HECO2\User\MyMotor.c:1325:					Target_Fan2_Speed = s_FAN2_VEN_1_DAN;
Source\HECO2\User\MyMotor.c:1328:					Target_Fan1_Speed = s_FAN1_VEN_2_DAN;
Source\HECO2\User\MyMotor.c:1329:					Target_Fan2_Speed = s_FAN2_VEN_2_DAN;
Source\HECO2\User\MyMotor.c:1332:					Target_Fan1_Speed = s_FAN1_VEN_3_DAN;
Source\HECO2\User\MyMotor.c:1333:					Target_Fan2_Speed = s_FAN2_VEN_3_DAN;
Source\HECO2\User\MyMotor.c:1336:					Target_Fan1_Speed = s_FAN1_VEN_4_DAN;
Source\HECO2\User\MyMotor.c:1337:					Target_Fan2_Speed = s_FAN2_VEN_4_DAN;
Source\HECO2\User\MyMotor.c:1340:					Target_Fan1_Speed = 0;
Source\HECO2\User\MyMotor.c:1341:					Target_Fan2_Speed = 0;
Source\HECO2\User\MyMotor.c:1351:				Target_Fan1_Speed = s_FAN1_AIR_1_DAN;
Source\HECO2\User\MyMotor.c:1352:				Target_Fan2_Speed = 0;//s2 400 // s_FAN2_AIR_1_DAN;
Source\HECO2\User\MyMotor.c:1355:				Target_Fan1_Speed = s_FAN1_AIR_2_DAN;
Source\HECO2\User\MyMotor.c:1356:				Target_Fan2_Speed = 0;//s2 400 / s_FAN2_AIR_2_DAN;
Source\HECO2\User\MyMotor.c:1359:				Target_Fan1_Speed = s_FAN1_AIR_3_DAN;
Source\HECO2\User\MyMotor.c:1360:				Target_Fan2_Speed = 0;//s2 400 / s_FAN2_AIR_3_DAN;
Source\HECO2\User\MyMotor.c:1363:				Target_Fan1_Speed = s_FAN1_AIR_4_DAN;
Source\HECO2\User\MyMotor.c:1364:				Target_Fan2_Speed = 0;//s2 400 / s_FAN2_AIR_3_DAN;
Source\HECO2\User\MyMotor.c:1367:				Target_Fan1_Speed = 0;
Source\HECO2\User\MyMotor.c:1368:				Target_Fan2_Speed = 0;
Source\HECO2\User\MyMotor.c:1377:				Target_Fan1_Speed = s_FAN1_BYPASS_1_DAN;                                                                                ;
Source\HECO2\User\MyMotor.c:1378:				Target_Fan2_Speed = s_FAN2_BYPASS_1_DAN;
Source\HECO2\User\MyMotor.c:1381:				Target_Fan1_Speed = s_FAN1_BYPASS_2_DAN;
Source\HECO2\User\MyMotor.c:1382:				Target_Fan2_Speed =   s_FAN2_BYPASS_2_DAN;
Source\HECO2\User\MyMotor.c:1385:				Target_Fan1_Speed = s_FAN1_BYPASS_3_DAN;
Source\HECO2\User\MyMotor.c:1386:				Target_Fan2_Speed = s_FAN2_BYPASS_3_DAN;
Source\HECO2\User\MyMotor.c:1389:				Target_Fan1_Speed = s_FAN1_BYPASS_4_DAN;
Source\HECO2\User\MyMotor.c:1390:				Target_Fan2_Speed = s_FAN2_BYPASS_4_DAN;
Source\HECO2\User\MyMotor.c:1393:				Target_Fan1_Speed = 0;
Source\HECO2\User\MyMotor.c:1394:				Target_Fan2_Speed = 0;
Source\HECO2\User\MyMotor.c:1422:			Target_Fan1_Speed = s_FAN1_VEN_2_DAN; ////////////////// S1400
Source\HECO2\User\MyMotor.c:1423:			Target_Fan2_Speed = s_FAN2_VEN_2_DAN;
Source\HECO2\User\MyMotor.c:1433:			Target_Fan1_Speed = s_FAN1_VEN_3_DAN; ////////////////// S400
Source\HECO2\User\MyMotor.c:1434:			Target_Fan2_Speed = s_FAN2_VEN_3_DAN;
Source\HECO2\User\MyMotor.c:1441:			Target_Fan1_Speed = 0;
Source\HECO2\User\MyMotor.c:1442:			Target_Fan2_Speed = 0;


84:	Timer0_Init();
85:	Timer1_Init();

84:	Timer0_Init();
85:	Timer1_Init();


스마트수면은 수정된 거 확인했어. 동작모드 변경시 내부댐퍼는 여전히 안돼.


HECO2 댐퍼 동작 문제 분석 및 비교


Timer1(스텝 구동)은 정상 기동됩니다. 팬 타깃 setter도 전부 MyMotor.c 내부에만 있고 명령경로엔 없습니다(② 제거 확인). 그런데도 안 된다면 제가 흐름을 잘못 읽고 있는 겁니다. 추측을 멈추고 현재 `Fan_Speed_process` 전체를 다시 정독하겠습니다.


1100		else if(Run_Mode == MODE_AIRCLEAN)
1101		{
1102			if((Pre_Run_Mode != MODE_AIRCLEAN)||(Power_On == 0))
1103			{
1104				Power_On = 1;
1105	            for(i=1; i<7; i++)Diffuser_Power[i] = 1; //////diffuser on
1106				Target_Fan1_Speed = 0; // off
1107				Target_Fan2_Speed = 0; // off
1108
1109				Damper_wait_time = 5; //0.5sec
1110			}
1111			else
1112			{
1113				if((Fan1_Speed != 0)||(Fan2_Speed != 0))
1114				{
1115					if(Damper_wait_time == 5)goto PASS1;
1116				}
1117				Damper_Mode(MODE_AIRCLEAN);
1118
1119				if(Step_Status != 0x3F)goto PASS1;
1120
1121				if(Damper_wait_time == 1)
1122				{
1123					/* 모드전환·댐퍼 정렬 후 새 모드 풍량(VSP) 복원 → 한 번에 반영. */
1124					Fan_Speed_Setting(Run_Mode, Fan_Mode);
1125				}
1126
1127				if(Damper_wait_time)Damper_wait_time--;
1128				else
1129				{
1130					;
1131				}
1132			}
1133
1134		}
1135		else if(Run_Mode == MODE_FAN_TEST)
1136		{
1137			if(Vsp_Select <= 4) // vent
1138			{
1139				Damper_Mode(MODE_VENTILATION);
1140			}
1141			else if(Vsp_Select == 5)//bypass
1142			{
1143				Damper_Mode(MODE_BYPASS);
1144			}
1145			else // air 6,7,8
1146			{
1147				Damper_Mode(MODE_AIRCLEAN);
1148			}
1149			Target_Fan1_Speed = Test_Fan1_Speed;
1150			Target_Fan2_Speed = Test_Fan2_Speed;
1151		}
1152
1153	PASS1:
1154
1155	    if((Vsp_Mode[1] == 0x10)||(RJ_Vsp_Mode == RX_DATA_MODE_VSP))
1156	    {
1157	      ;
1158	    }
1159	    else
1160	    {
1161	        Diffuser_Damper_process(Run_Mode);
1162	    }
1163
1164		if(Fan1_Speed > Target_Fan1_Speed)Fan1_Speed--;
1165		else if(Fan1_Speed < Target_Fan1_Speed)Fan1_Speed++;
1166
1167		if(Fan2_Speed > Target_Fan2_Speed)Fan2_Speed--;
1168		else if(Fan2_Speed < Target_Fan2_Speed)Fan2_Speed++;
1169
1170
1171		PWM_out_process();
1172
1173
1174		Pre_Run_Mode = Run_Mode;

	else if(Run_Mode == MODE_AIRCLEAN)
	{
		if((Pre_Run_Mode != MODE_AIRCLEAN)||(Power_On == 0))
		{
			Power_On = 1;
            for(i=1; i<7; i++)Diffuser_Power[i] = 1; //////diffuser on
			Target_Fan1_Speed = 0; // off
			Target_Fan2_Speed = 0; // off

			Damper_wait_time = 5; //0.5sec
		}
		else
		{
			if((Fan1_Speed != 0)||(Fan2_Speed != 0))
			{
				if(Damper_wait_time == 5)goto PASS1;
			}
			Damper_Mode(MODE_AIRCLEAN);

			if(Step_Status != 0x3F)goto PASS1;

			if(Damper_wait_time == 1)
			{
				/* 모드전환·댐퍼 정렬 후 새 모드 풍량(VSP) 복원 → 한 번에 반영. */
				Fan_Speed_Setting(Run_Mode, Fan_Mode);
			}

			if(Damper_wait_time)Damper_wait_time--;
			else
			{
				;
			}
		}

	}
	else if(Run_Mode == MODE_FAN_TEST)
	{
		if(Vsp_Select <= 4) // vent
		{
			Damper_Mode(MODE_VENTILATION);
		}
		else if(Vsp_Select == 5)//bypass
		{
			Damper_Mode(MODE_BYPASS);
		}
		else // air 6,7,8
		{
			Damper_Mode(MODE_AIRCLEAN);
		}
		Target_Fan1_Speed = Test_Fan1_Speed;
		Target_Fan2_Speed = Test_Fan2_Speed;
	}

PASS1:

    if((Vsp_Mode[1] == 0x10)||(RJ_Vsp_Mode == RX_DATA_MODE_VSP))
    {
      ;
    }
    else
    {
        Diffuser_Damper_process(Run_Mode);
    }

	if(Fan1_Speed > Target_Fan1_Speed)Fan1_Speed--;
	else if(Fan1_Speed < Target_Fan1_Speed)Fan1_Speed++;

	if(Fan2_Speed > Target_Fan2_Speed)Fan2_Speed--;
	else if(Fan2_Speed < Target_Fan2_Speed)Fan2_Speed++;

	PWM_out_process();

	Pre_Run_Mode = Run_Mode;


팬 타깃 setter는 전부 MyMotor.c 내부에 있고 명령경로(293 등)는 제거됐는데도 댐퍼가 안 움직인다면, **팬이 0까지 안 내려가는 다른 경로**가 있다는 뜻입니다. 핵심은 "어느 컨트롤러로 모드를 바꾸는가"입니다 — 룸컨(RJ2)은 `My_RJ2.c`, 대시보드는 `My_Homenet.c`로 경로가 다릅니다. RJ2의 모드 처리부를 확인합니다.


115:        RJ_Vsp_Mode = 0;
125:             RJ_Vsp_Mode = 0;
133:volatile uint8_t Set_Run_Mode = 0, Set_Fan_Mode = 0;
167:    if(Run_Mode == MODE_AUTO)Fan_Mode = Set_Fan_Mode;///////////////// DEMO
319:uint8_t RJ_Vsp_Mode = 0;
324:    RJ_Vsp_Mode = Rx_roomcon232_buffer[1];
389:			Run_Mode = Rx_roomcon232_buffer[2];
392:                Fan_Mode = Rx_roomcon232_buffer[3];
401:                    Run_Mode = Rx_roomcon232_buffer[2];
404:                        Fan_Mode = Rx_roomcon232_buffer[3];//DEMO
406:                    //if(Run_Mode != MODE_AUTO)Fan_Mode = Rx_roomcon232_buffer[3];//DEMO
407:                    //else Fan_Mode = Set_Fan_Mode;
412:                    Set_Run_Mode = Run_Mode = Rx_roomcon232_buffer[2];
413:                    Set_Fan_Mode = Fan_Mode = Rx_roomcon232_buffer[3];
415:                    if((Fan_Mode == 0)&&(Run_Mode == MODE_VENTILATION)&&(RJ_Vsp_Mode != RX_DATA_MODE_VSP))
422:                                Hood_Fan_Mode = 0;
428:                                Hood_Fan_Mode = Memory_Hood_Status;
435:                        Set_Run_Mode = My_Memory_Run_Mode = 0;
436:                        Set_Fan_Mode = My_Memory_Fan_Mode = 0;
439:                        Force_Damper_Mode = 0;
440:                        Ext_Run_Mode = 0;
442:                        Pre_Ext_Run_Mode = Ext_Run_Mode ;
467:            if(Run_Mode == MODE_VENTILATION)
471:            else if(Run_Mode == MODE_AUTO)
475:            else if(Run_Mode == MODE_BYPASS)
479:            else if(Run_Mode == MODE_AIRCLEAN)
492:            //if(Run_Mode == MODE_AUTO)Fan_Mode = Set_Fan_Mode;///////////////// DEMO
523:			Run_Mode = 0;
524:			Fan_Mode = 0;
652:			Run_Mode = Rx_roomcon232_buffer[2];
692:                    Run_Mode = Rx_roomcon232_buffer[2];
695:                        Fan_Mode = Rx_roomcon232_buffer[3];///////////////// DEMO
700:				Set_Run_Mode = Run_Mode = Rx_roomcon232_buffer[2];
703:                    Set_Fan_Mode = Fan_Mode = Rx_roomcon232_buffer[3];
715:			if(Set_Run_Mode == Run_Mode)Command_request_type &= ~TYPE_MODE;
716:			if(Set_Fan_Mode == Fan_Mode)Command_request_type &= ~TYPE_FAN_SPEED;


`My_RJ2.c`는 `Run_Mode`/`Fan_Mode`만 직접 셋팅하고 `Fan_Speed_Setting`은 호출하지 않습니다(마스터와 동일). 즉 **룸컨 경로면 팬 타깃은 `Fan_Speed_process`가 단독 관리**하므로 댐퍼가 움직여야 정상입니다. 룸컨 모드 처리부 전체를 읽어 빠진 곳이 없는지 확인합니다.


380	                Set_Reserve_timer_sec = (uint32_t)Rx_roomcon232_buffer[11]*3600 + (uint32_t)Rx_roomcon232_buffer[12]*60 ;
381					Txbuff_init();
382				}
383				SCUART_Write(SC0,Tx_roomcon232_buffer,15);
384
385				break;
386
387			case RX_DATA_MODE_EVENT:
388
389				Run_Mode = Rx_roomcon232_buffer[2];
390	            if(Run_Mode != MODE_AUTO)
391	            {
392	                Fan_Mode = Rx_roomcon232_buffer[3];
393	            }
394
395				if(Fan_Mode != 0)Err_Code &= ~(ERROR_EA_FAN|ERROR_SA_FAN);
396
397				if(Command_request_type & TYPE_SEND_FLAG)
398	            {
399	                    Command_request_type = 0;
400	                    Command_request_type &= ~TYPE_SEND_FLAG;
401	                    Run_Mode = Rx_roomcon232_buffer[2];
402	                    if(Run_Mode != MODE_AUTO)
403	                    {
404	                        Fan_Mode = Rx_roomcon232_buffer[3];//DEMO
405	                    }
406	                    //if(Run_Mode != MODE_AUTO)Fan_Mode = Rx_roomcon232_buffer[3];//DEMO
407	                    //else Fan_Mode = Set_Fan_Mode;
408	            }
409				else
410				{
411
412	                    Set_Run_Mode = Run_Mode = Rx_roomcon232_buffer[2];
413	                    Set_Fan_Mode = Fan_Mode = Rx_roomcon232_buffer[3];
414
415	                    if((Fan_Mode == 0)&&(Run_Mode == MODE_VENTILATION)&&(RJ_Vsp_Mode != RX_DATA_MODE_VSP))
416	                    {
417	                        if((Memory_Hood_Status != Hood_Status)&&(Hood_YeunDong_Enable == 1))
418	                        {
419	                            if(Memory_Hood_Status == 0)
420	                            {
421	                                Hood_Power_On = 0;
422	                                Hood_Fan_Mode = 0;
423	                                Hood_Control = 0x80;
424	                            }
425	                            else
426	                            {
427	                                Hood_Power_On = 1;
428	                                Hood_Fan_Mode = Memory_Hood_Status;
429	                                Hood_Control = 0x80;
430	                            }
431
432	                            Hood_Status = Memory_Hood_Status;
433	                        }
434
435	                        Set_Run_Mode = My_Memory_Run_Mode = 0;
436	                        Set_Fan_Mode = My_Memory_Fan_Mode = 0;
437	                        Command_request_type |= (TYPE_MODE|TYPE_FAN_SPEED);
438
439	                        Force_Damper_Mode = 0;
440	                        Ext_Run_Mode = 0;
441	                        Ext_Run_Mode_Off_Delay = 0;
442	                        Pre_Ext_Run_Mode = Ext_Run_Mode ;
443	                        Pre_Ext_Select_Room = Ext_Select_Room;
444
445	                        HREV_Hood_Control = 0;
446	                    }
447
448	                    if(Hood_Yeundong_flag == 1)
449	                    {
450	                        Hood_Yeundong_flag = 0;
451	                        Command_request_type |= (TYPE_HOOD_STATE);
452	                    }
453				}
454
455
456				//Auto_Mode = Rx_roomcon232_buffer[4];
457				Heater_OnOff = Rx_roomcon232_buffer[5]&0x01;
458				UV_OnOff = Rx_roomcon232_buffer[5]&0x10;
459
460				Filter_Reset_Flag |= Rx_roomcon232_buffer[7]&0x01;
461
462				if(Rx_roomcon232_buffer[10] == 1)
463				{
464					Set_Reserve_timer_sec = (uint32_t)Rx_roomcon232_buffer[11]*3600 + (uint32_t)Rx_roomcon232_buffer[12]*60 ;
465				}
466
467	            if(Run_Mode == MODE_VENTILATION)
468	            {
469	                for(i=1; i<7; i++)Diffuser_Run_Mode[i] = 1; ////// difuser run mode;
470	            }
471	            else if(Run_Mode == MODE_AUTO)
472	            {
473	                for(i=1; i<7; i++)Diffuser_Run_Mode[i] = 2; ////// difuser run mode;
474	            }
475	            else if(Run_Mode == MODE_BYPASS)
476	            {
477	                for(i=1; i<7; i++)Diffuser_Run_Mode[i] = 4; ////// difuser run mode;
478	            }
479	            else if(Run_Mode == MODE_AIRCLEAN)
480	            {
481	                for(i=1; i<7; i++)Diffuser_Run_Mode[i] = 8; ////// difuser run mode;
482	            }
483
484	            for(i=1; i<7; i++)Diffuser_Fan_Speed[i] = Fan_Mode; ////// difuser fan mode;
485
486
487
488
489				Tx_roomcon232_buffer[0] = 0xAA;
490				Tx_roomcon232_buffer[1] = 0x01;
491				Tx_roomcon232_buffer[2] = Run_Mode;
492	            //if(Run_Mode == MODE_AUTO)Fan_Mode = Set_Fan_Mode;///////////////// DEMO
493				Tx_roomcon232_buffer[3] = Fan_Mode;
494

                Set_Reserve_timer_sec = (uint32_t)Rx_roomcon232_buffer[11]*3600 + (uint32_t)Rx_roomcon232_buffer[12]*60 ;
				Txbuff_init();
			}
			SCUART_Write(SC0,Tx_roomcon232_buffer,15);

			break;

		case RX_DATA_MODE_EVENT:

			Run_Mode = Rx_roomcon232_buffer[2];
            if(Run_Mode != MODE_AUTO)
            {
                Fan_Mode = Rx_roomcon232_buffer[3];
            }

			if(Fan_Mode != 0)Err_Code &= ~(ERROR_EA_FAN|ERROR_SA_FAN);

			if(Command_request_type & TYPE_SEND_FLAG)
            {
                    Command_request_type = 0;
                    Command_request_type &= ~TYPE_SEND_FLAG;
                    Run_Mode = Rx_roomcon232_buffer[2];
                    if(Run_Mode != MODE_AUTO)
                    {
                        Fan_Mode = Rx_roomcon232_buffer[3];//DEMO
                    }
                    //if(Run_Mode != MODE_AUTO)Fan_Mode = Rx_roomcon232_buffer[3];//DEMO
                    //else Fan_Mode = Set_Fan_Mode;
            }
			else
			{

                    Set_Run_Mode = Run_Mode = Rx_roomcon232_buffer[2];
                    Set_Fan_Mode = Fan_Mode = Rx_roomcon232_buffer[3];

                    if((Fan_Mode == 0)&&(Run_Mode == MODE_VENTILATION)&&(RJ_Vsp_Mode != RX_DATA_MODE_VSP))
                    {
                        if((Memory_Hood_Status != Hood_Status)&&(Hood_YeunDong_Enable == 1))
                        {
                            if(Memory_Hood_Status == 0)
                            {
                                Hood_Power_On = 0;
                                Hood_Fan_Mode = 0;
                                Hood_Control = 0x80;
                            }
                            else
                            {
                                Hood_Power_On = 1;
                                Hood_Fan_Mode = Memory_Hood_Status;
                                Hood_Control = 0x80;
                            }

                            Hood_Status = Memory_Hood_Status;
                        }

                        Set_Run_Mode = My_Memory_Run_Mode = 0;
                        Set_Fan_Mode = My_Memory_Fan_Mode = 0;
                        Command_request_type |= (TYPE_MODE|TYPE_FAN_SPEED);

                        Force_Damper_Mode = 0;
                        Ext_Run_Mode = 0;
                        Ext_Run_Mode_Off_Delay = 0;
                        Pre_Ext_Run_Mode = Ext_Run_Mode ;
                        Pre_Ext_Select_Room = Ext_Select_Room;

                        HREV_Hood_Control = 0;
                    }

                    if(Hood_Yeundong_flag == 1)
                    {
                        Hood_Yeundong_flag = 0;
                        Command_request_type |= (TYPE_HOOD_STATE);
                    }
			}

			//Auto_Mode = Rx_roomcon232_buffer[4];
			Heater_OnOff = Rx_roomcon232_buffer[5]&0x01;
			UV_OnOff = Rx_roomcon232_buffer[5]&0x10;

			Filter_Reset_Flag |= Rx_roomcon232_buffer[7]&0x01;

			if(Rx_roomcon232_buffer[10] == 1)
			{
				Set_Reserve_timer_sec = (uint32_t)Rx_roomcon232_buffer[11]*3600 + (uint32_t)Rx_roomcon232_buffer[12]*60 ;
			}

            if(Run_Mode == MODE_VENTILATION)
            {
                for(i=1; i<7; i++)Diffuser_Run_Mode[i] = 1; ////// difuser run mode;
            }
            else if(Run_Mode == MODE_AUTO)
            {
                for(i=1; i<7; i++)Diffuser_Run_Mode[i] = 2; ////// difuser run mode;
            }
            else if(Run_Mode == MODE_BYPASS)
            {
                for(i=1; i<7; i++)Diffuser_Run_Mode[i] = 4; ////// difuser run mode;
            }
            else if(Run_Mode == MODE_AIRCLEAN)
            {
                for(i=1; i<7; i++)Diffuser_Run_Mode[i] = 8; ////// difuser run mode;
            }

            for(i=1; i<7; i++)Diffuser_Fan_Speed[i] = Fan_Mode; ////// difuser fan mode;

			Tx_roomcon232_buffer[0] = 0xAA;
			Tx_roomcon232_buffer[1] = 0x01;
			Tx_roomcon232_buffer[2] = Run_Mode;
            //if(Run_Mode == MODE_AUTO)Fan_Mode = Set_Fan_Mode;///////////////// DEMO
			Tx_roomcon232_buffer[3] = Fan_Mode;


`My_RJ2.c`(룸컨)도 `Run_Mode`/`Fan_Mode`만 설정하고 `Fan_Speed_Setting`은 호출하지 않습니다 — 즉 **룸컨 경로면 팬 타깃을 `Fan_Speed_process`가 단독 관리**하므로, 이 경로에서는 HECO2 댐퍼 코드가 마스터와 동일하게 동작해야 합니다.

지금 증상이 경로에 따라 원인이 갈립니다. 더 헛다리 짚지 않으려면 두 가지만 확인해 주세요. 이걸로 원인이 "팬이 0까지 안 내려감(명령경로 문제)"인지 "팬은 0까지 내려가는데 댐퍼가 안 움직임(스텝/Damper_Mode 문제)"인지 정확히 갈립니다.


AskUserQuestion

Question: 환기→공청 전환을 어떤 컨트롤러로 하나요? (명령이 어느 경로로 들어오는지 결정)

Choices:
물리 룸컨트롤러. 명령이 My_RJ2.c 경로 — Fan_Speed_Setting 직접호출 없음(마스터와 동일).
ErvDashboard(485). 명령이 My_Homenet.c 경로 — CTRL_FAN(306)이 함께 올 수 있음.
ErvCollector(WIFI). 역시 My_Homenet.c 경로.
Simulator/ERVSimulator. 어느 프로토콜로 보내는지에 따라 경로 다름.

Question: 환기→공청 전환 순간, 팬(송풍기) 거동은?

Choices:
팬이 0까지 안 내려감 → 댐퍼 게이트가 안 열림(명령경로가 팬을 계속 켜는 문제).
팬이 0까지 내려감 → 게이트는 열림. 그런데 댐퍼가 안 움직이면 스텝/Damper_Mode 문제.
팬 거동을 확실히 못 봤음. 다시 관찰 필요.


Your questions have been answered: "환기→공청 전환을 어떤 컨트롤러로 하나요? (명령이 어느 경로로 들어오는지 결정)"="PC 대시보드", "환기→공청 전환 순간, 팬(송풍기) 거동은?"="팬이 0까지는 안 내려감. 근데 이게 간헐적으로 발생. 어쩌다가는 정상 댐퍼 동작을 해.". You can now continue with these answers in mind.

Question: 환기→공청 전환을 어떤 컨트롤러로 하나요? (명령이 어느 경로로 들어오는지 결정)

Choices:
물리 룸컨트롤러. 명령이 My_RJ2.c 경로 — Fan_Speed_Setting 직접호출 없음(마스터와 동일).
ErvDashboard(485). 명령이 My_Homenet.c 경로 — CTRL_FAN(306)이 함께 올 수 있음.
ErvCollector(WIFI). 역시 My_Homenet.c 경로.
Simulator/ERVSimulator. 어느 프로토콜로 보내는지에 따라 경로 다름.

Question: 환기→공청 전환 순간, 팬(송풍기) 거동은?

Choices:
팬이 0까지 안 내려감 → 댐퍼 게이트가 안 열림(명령경로가 팬을 계속 켜는 문제).
팬이 0까지 내려감 → 게이트는 열림. 그런데 댐퍼가 안 움직이면 스텝/Damper_Mode 문제.
팬 거동을 확실히 못 봤음. 다시 관찰 필요.

팬이 0까지는 안 내려감. 근데 이게 간헐적으로 발생. 어쩌다가는 정상 댐퍼 동작을 해.


핵심 단서를 받았습니다: **PC 대시보드** 경로 + **팬이 0까지 안 내려감** + **간헐적**(어쩌다 정상). 이건 전형적인 **타이밍 레이스**입니다.

대시보드가 모드 변경과 함께 보내는 **CTRL_FAN(306, 아직 살아있음)**이, 모드전환 감속창(팬을 0으로 떨구는 구간)에 **끼어드는 타이밍이면** 팬 타깃을 다시 켜서 → 팬이 0에 도달 못 함 → 댐퍼 게이트가 안 열림. 끼어드는 타이밍이 아니면 정상 → **간헐적** 증상과 정확히 일치합니다.

가장 견고한 수정은 **"모드전환 진행 중에는 명령경로가 풍량을 올려도 무시하고 팬을 0으로 강제"**하는 것입니다. `Damper_wait_time == 5`가 전환 진행 신호이므로 이걸 단일 지점에서 처리하면 됩니다. 먼저 `Damper_wait_time` 대입을 확인해 `==5`가 전환 구간 전용인지 검증합니다.


Source\HECO2\User\MyMotor.c:865:uint16_t Damper_wait_time = 0;
Source\HECO2\User\MyMotor.c:999:			Damper_wait_time = 5; //0.5sec
Source\HECO2\User\MyMotor.c:1005:				if(Damper_wait_time == 5)goto PASS1;
Source\HECO2\User\MyMotor.c:1013:			if(Damper_wait_time == 1)
Source\HECO2\User\MyMotor.c:1038:			Damper_wait_time = 5; //0.5sec
Source\HECO2\User\MyMotor.c:1044:				if(Damper_wait_time == 5)goto PASS1;
Source\HECO2\User\MyMotor.c:1051:			if(Damper_wait_time == 1)
Source\HECO2\User\MyMotor.c:1075:			Damper_wait_time = 5; //0.5sec
Source\HECO2\User\MyMotor.c:1081:				if(Damper_wait_time == 5)goto PASS1;
Source\HECO2\User\MyMotor.c:1087:			if(Damper_wait_time == 1)
Source\HECO2\User\MyMotor.c:1109:			Damper_wait_time = 5; //0.5sec
Source\HECO2\User\MyMotor.c:1115:				if(Damper_wait_time == 5)goto PASS1;
Source\HECO2\User\MyMotor.c:1121:			if(Damper_wait_time == 1)


확인됐습니다 — `Damper_wait_time = 5`는 4개 모드 진입(999/1038/1075/1109)에서만 설정되고, 전환 완료(Step 정렬) 전까지 5로 유지됩니다. 즉 **`==5`는 "모드전환 진행 중" 전용 신호**입니다.

VSP 모드는 제외하고, 정상운전 경로에서만 전환 중 팬을 강제 0으로 만드는 단일 지점 수정을 적용합니다:


Edit

    if((Vsp_Mode[1] == 0x10)||(RJ_Vsp_Mode == RX_DATA_MODE_VSP))
    {
      ;
    }
    else
    {
        Diffuser_Damper_process(Run_Mode);
    }

    if((Vsp_Mode[1] == 0x10)||(RJ_Vsp_Mode == RX_DATA_MODE_VSP))
    {
      ;
    }
    else
    {
        /* [모드전환 댐퍼게이트 보호] 모드전환 진행중(Damper_wait_time==5)에는
           명령경로(대시보드 CTRL_FAN 등)가 풍량을 올려도 무시하고 팬을 0으로 강제.
           팬이 0까지 내려가야 Damper_Mode 가 실행되어 내부댐퍼가 이동한다.
           (CTRL_FAN 이 감속창에 간헐적으로 끼어들어 댐퍼가 안 움직이던 문제 수정) */
        if(Damper_wait_time == 5)
        {
            Target_Fan1_Speed = 0;
            Target_Fan2_Speed = 0;
        }
        Diffuser_Damper_process(Run_Mode);
    }


The file d:\Project\nuvoton\HECO2\Source\HECO2\User\MyMotor.c has been updated successfully. (file state is current in your context — no need to Read it back)

    if((Vsp_Mode[1] == 0x10)||(RJ_Vsp_Mode == RX_DATA_MODE_VSP))
    {
      ;
    }
    else
    {
        Diffuser_Damper_process(Run_Mode);
    }

    if((Vsp_Mode[1] == 0x10)||(RJ_Vsp_Mode == RX_DATA_MODE_VSP))
    {
      ;
    }
    else
    {
        /* [모드전환 댐퍼게이트 보호] 모드전환 진행중(Damper_wait_time==5)에는
           명령경로(대시보드 CTRL_FAN 등)가 풍량을 올려도 무시하고 팬을 0으로 강제.
           팬이 0까지 내려가야 Damper_Mode 가 실행되어 내부댐퍼가 이동한다.
           (CTRL_FAN 이 감속창에 간헐적으로 끼어들어 댐퍼가 안 움직이던 문제 수정) */
        if(Damper_wait_time == 5)
        {
            Target_Fan1_Speed = 0;
            Target_Fan2_Speed = 0;
        }
        Diffuser_Damper_process(Run_Mode);
    }

+        /* [모드전환 댐퍼게이트 보호] 모드전환 진행중(Damper_wait_time==5)에는

+           명령경로(대시보드 CTRL_FAN 등)가 풍량을 올려도 무시하고 팬을 0으로 강제.

+           팬이 0까지 내려가야 Damper_Mode 가 실행되어 내부댐퍼가 이동한다.

+           (CTRL_FAN 이 감속창에 간헐적으로 끼어들어 댐퍼가 안 움직이던 문제 수정) */


Build 확인합니다.


Bash

cd /d/Project/nuvoton/HECO2/Source/HECO2 && bash build.sh all 2>&1 | grep -iE "warning|error|text|HERV.elf"; echo "---done---"

Build firmware after damper-gate protection fix


[LD]  build/HERV.elf
   text	   data	    bss	    dec	    hex	filename
  43948	   1940	   3388	  49276	   c07c	build/HERV.elf
---done---


빌드 성공(경고/오류 0). 작업 기록을 추가합니다.


Edit

## 후속
- 실장비에서 스마트수면 진입 시 거실 닫힘 + 침실 CO2 개폐 확인.

## 후속
- 실장비에서 스마트수면 진입 시 거실 닫힘 + 침실 CO2 개폐 확인.

---

# 260618 (3) 내부댐퍼 미동작 — 진짜 원인(타이밍 레이스) 수정

## 추가 진단 (사용자 확인)
- 테스트 경로 = **PC 대시보드**(My_Homenet 경로).
- 팬 거동 = **0까지 안 내려감**, 그리고 **간헐적**(어쩌다 정상 댐퍼 동작).
- → 전형적 **타이밍 레이스**. (1)차 수정으로 CTRL_RUNMODE(293)는 막았지만, 대시보드가 모드변경과 함께 보내는 **CTRL_FAN(306, 유지됨)** 이 모드전환 감속창(팬→0 구간)에 끼어드는 타이밍이면 팬 타깃을 다시 켜서 팬이 0 도달 못 함 → Damper_Mode 게이트 안 열림.
- 끼어드는 타이밍이 아니면 정상 → 간헐성 설명됨.

## 수정 (2차 = 게이트 보호, 단일 지점)
- `Source/HECO2/User/MyMotor.c` `Fan_Speed_process()` PASS1 직후(정상운전 분기, VSP 제외):
  - `Damper_wait_time == 5`(=모드전환 진행중 전용 신호. 4개 모드 진입에서만 set, Step 정렬 전까지 5 유지)일 때
    `Target_Fan1_Speed = 0; Target_Fan2_Speed = 0;` **강제** → 명령경로(CTRL_FAN 등)가 감속창에 끼어들어도 무시하고 팬을 0으로 떨굼 → 댐퍼 이동 보장.
  - 전환 완료(Step_Status==0x3F) 후 wait가 5→1로 감소하며 wait==1에서 Fan_Speed_Setting(③)이 팬 복원.
- 풍량단수만 변경(같은 모드, wait==0)은 force-zero 미적용 → CTRL_FAN(306) 즉시 반응 유지.

## 동작 변화(주의)
- 이제 환기→공청 전환 시 **팬이 잠깐 0까지 멈췄다가**(댐퍼 이동) 다시 도는 게 정상(설계상 공기흐름 중 댐퍼 미동작). 기존 "안 멈추던" 거동에서 바뀜.

## 빌드 결과
- `bash build.sh all` → 성공, 경고/오류 0. text 43948.

## 후속
- 실장비에서 환기→공청 **반복 전환** 시 매번 댐퍼 이동(간헐성 사라짐) 확인.
- 같은 모드 풍량단수 변경 시 팬 즉시 반응 정상 확인.


The file d:\Project\nuvoton\HECO2\doc\260618_내부댐퍼_모드변경시_미동작_수정.md has been updated successfully. (file state is current in your context — no need to Read it back)

## 후속
- 실장비에서 스마트수면 진입 시 거실 닫힘 + 침실 CO2 개폐 확인.

---

# 260618 (3) 내부댐퍼 미동작 — 진짜 원인(타이밍 레이스) 수정

## 추가 진단 (사용자 확인)
- 테스트 경로 = **PC 대시보드**(My_Homenet 경로).
- 팬 거동 = **0까지 안 내려감**, 그리고 **간헐적**(어쩌다 정상 댐퍼 동작).
- → 전형적 **타이밍 레이스**. (1)차 수정으로 CTRL_RUNMODE(293)는 막았지만, 대시보드가 모드변경과 함께 보내는 **CTRL_FAN(306, 유지됨)** 이 모드전환 감속창(팬→0 구간)에 끼어드는 타이밍이면 팬 타깃을 다시 켜서 팬이 0 도달 못 함 → Damper_Mode 게이트 안 열림.
- 끼어드는 타이밍이 아니면 정상 → 간헐성 설명됨.

## 수정 (2차 = 게이트 보호, 단일 지점)
- `Source/HECO2/User/MyMotor.c` `Fan_Speed_process()` PASS1 직후(정상운전 분기, VSP 제외):
  - `Damper_wait_time == 5`(=모드전환 진행중 전용 신호. 4개 모드 진입에서만 set, Step 정렬 전까지 5 유지)일 때
    `Target_Fan1_Speed = 0; Target_Fan2_Speed = 0;` **강제** → 명령경로(CTRL_FAN 등)가 감속창에 끼어들어도 무시하고 팬을 0으로 떨굼 → 댐퍼 이동 보장.
  - 전환 완료(Step_Status==0x3F) 후 wait가 5→1로 감소하며 wait==1에서 Fan_Speed_Setting(③)이 팬 복원.
- 풍량단수만 변경(같은 모드, wait==0)은 force-zero 미적용 → CTRL_FAN(306) 즉시 반응 유지.

## 동작 변화(주의)
- 이제 환기→공청 전환 시 **팬이 잠깐 0까지 멈췄다가**(댐퍼 이동) 다시 도는 게 정상(설계상 공기흐름 중 댐퍼 미동작). 기존 "안 멈추던" 거동에서 바뀜.

## 빌드 결과
- `bash build.sh all` → 성공, 경고/오류 0. text 43948.

## 후속
- 실장비에서 환기→공청 **반복 전환** 시 매번 댐퍼 이동(간헐성 사라짐) 확인.
- 같은 모드 풍량단수 변경 시 팬 즉시 반응 정상 확인.

# 260618 내부댐퍼 — 운전모드 변경 시 미동작 수정 (1차: 명령경로 즉시 팬설정 제거)

## 증상
- 환기 → 공청 전환 시 **팬은 정상 동작하는데 내부댐퍼(본체 6개)가 안 움직임.**

## 원인 (마스터 `D:\Project\nuvoton\HERV_DL_MH_2nd\Program` 비교로 확정)
- 내부댐퍼 이동 트리거(`Fan_Speed_process`가 모드변경 시 `Damper_Mode()` 호출)는 HECO2/마스터 **동일하게 존재**. 빠진 게 아님.
- 그 트리거는 **"팬이 0까지 감속"되어야 발동**하도록 게이팅됨(공기 흐름 중 댐퍼 미동작 의도):
  `모드변경 → 팬타깃0 → 팬=0 도달 → Damper_Mode 호출(댐퍼 이동) → 정렬(Step_Status==0x3F) → 팬 복원`
- **마스터:** 명령 핸들러가 팬 타깃을 안 건드림 → 팬이 0 도달 → 댐퍼 이동.
- **HECO2:** 명령 핸들러가 모드변경 즉시 `Fan_Speed_Setting()` 직접 호출 → 팬 타깃이 곧바로 운전속도로 올라감 → **팬이 0에 도달 못 함 → `if(Fan!=0) if(wait==5) goto PASS1` 게이트가 `Damper_Mode` 영원히 스킵 → 댐퍼 미동작, 팬만 정상.**
- 마스터엔 없고 HECO2에만 추가된 명령경로 `Fan_Speed_Setting` 호출이 게이트를 막은 것.

## 변경 파일/내용 (1차 = 모드 변경 시 호출만 제거, 풍량단수 변경 호출은 유지)
주석 처리(삭제 아님, 복원 용이). 마커: `[모드변경댐퍼테스트]`
- `Source/HECO2/User/My_Homenet.c`
  - L259 CTRL_POWER(전원ON→환기) `Fan_Speed_Setting` 주석
  - L293 CTRL_RUNMODE(운전모드 변경) `Fan_Speed_Setting` 주석  ← 보고 증상(대시보드 환기→공청)의 직접 경로
  - **L306 CTRL_FAN(풍량단수만 변경)은 유지** (댐퍼 무관, 팬 단수 반응 필요)
- `Source/HECO2/User/My_Hood.c`
  - L192 후드ON(→환기) `Fan_Speed_Setting` 주석
  - L207 후드OFF(모드 복귀) `Fan_Speed_Setting` 주석
  - **L219 후드 단수변경(풍량만)은 유지**

## 핵심 결정
- 모드 변경 후 팬 복원은 `Fan_Speed_process`의 wait==1 호출(MyMotor.c 1018/1056/1090/1124, ③)이 담당 → 팬이 꺼진 채 남지 않음.
- RJ2(`My_RJ2.c`)·분배기(`My_bunbaegi.c`)는 두 소스 동일 → 미수정.
- 이번엔 "동작모드 변경 트리거 복원(①, 디퓨저 시퀀스 명령트리거)"은 보류 — ②(땜빵) 제거만으로 댐퍼가 움직이는지 먼저 검증.

## 빌드 결과
- `cd SOURCE/HECO2 && bash build.sh all` → 성공, 경고/오류 0.
- text 43920 / data 1940 / bss 3388 (HERV.elf). (이전 44008 → 코드 제거로 감소)

## 미해결 / 후속
- 실장비/시뮬레이터에서 **환기→공청 시 내부댐퍼 이동** 확인 필요(특히 고풍량 전환 시 팬 감속 대기시간 체감).
- 검증 후: 필요하면 ①(동작모드 변경 트리거 = MyMotor.c 디퓨저/팬복원 타이밍의 명령 트리거)도 마스터 정렬 검토.
- 풍량단수만 바꿀 때(L306/L219) 팬 즉시 반응 정상인지 확인.

---

# 260618 (2) 스마트수면 모드 — 거실 댐퍼 미닫힘 수정

## 증상
- 스마트수면 동작 중 **거실 댐퍼가 열려 있음**. 사양(개발사양서 8p)상 거실은 항상 CLOSE여야 함.

## 사양 (개발사양서 8p, 스마트수면)
- 환기 수동·풍량 1단 고정.
- 초기상태: **거실 CLOSE, 침실1~3 OPEN**.
- 거실 급기(SA)/배기(RA) 모두 **X(항상 닫힘)** — CO2 무관.
- 침실1~3: CO2 >= 1000 PPM → 해당 침실 OPEN, CO2 <= 800 PPM → CLOSE.

## 원인
- `My_system.c` `Air_Quality_damper_process()` `Ext_Run_Mode == 4` 블록.
- 진입 1회(L905)는 거실(room1)을 올바르게 CLOSE 하지만, **매 틱 CO2 루프가 `Room_Num = 1`부터 돌아(L916) 거실까지 CO2 기준으로 다시 OPEN** 시킴.
- 룸 인덱스: room1=거실, room2~4=침실1~3.

## 변경 파일/내용
- `Source/HECO2/User/My_system.c` (스마트수면 매 틱 처리)
  - 거실(room1) 매 틱 강제 CLOSE + LED off 추가.
  - CO2 히스테리시스 루프 시작을 `Room_Num = 1` → **`Room_Num = 2`** (침실1~3만 개폐).

## 빌드 결과
- `bash build.sh all` → 성공, 경고/오류 0. text 43940 / data 1940 / bss 3388.

## 후속
- 실장비에서 스마트수면 진입 시 거실 닫힘 + 침실 CO2 개폐 확인.

+# 260618 (3) 내부댐퍼 미동작 — 진짜 원인(타이밍 레이스) 수정

+## 추가 진단 (사용자 확인)

+- 팬 거동 = **0까지 안 내려감**, 그리고 **간헐적**(어쩌다 정상 댐퍼 동작).

+- → 전형적 **타이밍 레이스**. (1)차 수정으로 CTRL_RUNMODE(293)는 막았지만, 대시보드가 모드변경과 함께 보내는 **CTRL_FAN(306, 유지됨)** 이 모드전환 감속창(팬→0 구간)에 끼어드는 타이밍이면 팬 타깃을 다시 켜서 팬이 0 도달 못 함 → Damper_Mode 게이트 안 열림.

+- 끼어드는 타이밍이 아니면 정상 → 간헐성 설명됨.

+## 수정 (2차 = 게이트 보호, 단일 지점)

+- `Source/HECO2/User/MyMotor.c` `Fan_Speed_process()` PASS1 직후(정상운전 분기, VSP 제외):

+  - `Damper_wait_time == 5`(=모드전환 진행중 전용 신호. 4개 모드 진입에서만 set, Step 정렬 전까지 5 유지)일 때

+    `Target_Fan1_Speed = 0; Target_Fan2_Speed = 0;` **강제** → 명령경로(CTRL_FAN 등)가 감속창에 끼어들어도 무시하고 팬을 0으로 떨굼 → 댐퍼 이동 보장.

+  - 전환 완료(Step_Status==0x3F) 후 wait가 5→1로 감소하며 wait==1에서 Fan_Speed_Setting(③)이 팬 복원.

+- 풍량단수만 변경(같은 모드, wait==0)은 force-zero 미적용 → CTRL_FAN(306) 즉시 반응 유지.

+## 동작 변화(주의)

+- 이제 환기→공청 전환 시 **팬이 잠깐 0까지 멈췄다가**(댐퍼 이동) 다시 도는 게 정상(설계상 공기흐름 중 댐퍼 미동작). 기존 "안 멈추던" 거동에서 바뀜.

+- `bash build.sh all` → 성공, 경고/오류 0. text 43948.

+- 실장비에서 환기→공청 **반복 전환** 시 매번 댐퍼 이동(간헐성 사라짐) 확인.

+- 같은 모드 풍량단수 변경 시 팬 즉시 반응 정상 확인.


2차 수정(진짜 원인) 적용 완료했습니다.

## 진단 정리
대시보드 + 팬이 0까지 안 내려감 + **간헐적** → **타이밍 레이스**가 확정됐습니다.
1차로 CTRL_RUNMODE(293)는 막았지만, 대시보드가 모드 변경과 함께 보내는 **CTRL_FAN(306, 유지됨)**이 팬 감속창(0으로 떨구는 구간)에 **끼어드는 타이밍이면** 팬 타깃을 다시 켜서 → 팬이 0 도달 못 함 → 댐퍼 게이트 안 열림. 안 끼어들면 정상 → **간헐성**과 정확히 일치.

## 수정 (게이트 보호, 단일 지점)
[MyMotor.c PASS1 직후](Source/HECO2/User/MyMotor.c#L1153) — 정상운전 분기에 추가:
```c
if(Damper_wait_time == 5)   // ← 모드전환 진행중 전용 신호
{
    Target_Fan1_Speed = 0;
    Target_Fan2_Speed = 0;  // 명령경로가 풍량 올려도 무시, 팬 강제 0
}
```
- `Damper_wait_time==5`는 4개 모드 진입에서만 set + 댐퍼 정렬(Step 0x3F) 전까지 유지 → **모드전환 구간 전용**. 검증 완료.
- 전환 중엔 CTRL_FAN이 끼어들어도 무시 → 팬이 확실히 0 도달 → **Damper_Mode 실행 → 내부댐퍼 이동**. 정렬 후 wait==1에서 팬 복원(③).
- **풍량단수만 변경(같은 모드)**은 wait==0이라 미적용 → CTRL_FAN 즉시 반응 그대로 유지.

## 동작 변화 (참고)
이제 환기→공청 전환 시 **팬이 잠깐 0까지 멈췄다가 댐퍼 이동 후 다시 도는 게 정상**입니다(공기 흐르는 중 댐퍼 안 움직이는 설계). 기존 "안 멈추던" 거동에서 바뀝니다.

## 결과
- 빌드: **성공, 경고/오류 0** (text 43948)
- 기록: [doc/260618_내부댐퍼_모드변경시_미동작_수정.md](doc/260618_내부댐퍼_모드변경시_미동작_수정.md) (3)번 항목

펌웨어 올려서 **환기→공청 반복 전환** 시 이번엔 매번 댐퍼가 움직이는지(간헐성 사라졌는지) 확인해 주세요. 같은 모드에서 풍량 단수만 바꿀 때 팬 즉시 반응도 정상인지 같이 봐주시면 됩니다.


HECO2 댐퍼 동작 문제 분석 및 비교


지금 정상동작해.


확인했습니다. 정상 동작한다니 다행입니다 — 간헐적 타이밍 레이스가 원인이었던 게 맞았네요.

이번 디버깅에서 나온 **비자명한 설계 사실**(마스터 소스 위치 + 내부댐퍼 게이팅 원리)은 다음 세션을 위해 메모리에 남기겠습니다. 기존 인덱스를 먼저 확인합니다.


Bash

cd "/c/Users/전경선/.claude/projects/d--Project-nuvoton-HECO2/memory" 2>/dev/null && { echo "=== MEMORY.md 존재 ==="; cat MEMORY.md 2>/dev/null | head -40; echo "=== 파일목록 ==="; ls -1; } || echo "memory 디렉터리 비었거나 MEMORY.md 없음"

Check existing memory index


memory 디렉터리 비었거나 MEMORY.md 없음


Write

---
name: herv-master-source
description: 검증된 마스터 펌웨어 소스 위치 (HECO2 댐퍼/팬 로직 비교 기준)
metadata:
  type: reference
---

`D:\Project\nuvoton\HERV_DL_MH_2nd\Program` 가 사용자가 지정한 **마스터(검증된) 펌웨어 소스**다. HECO2 펌웨어의 댐퍼/팬 동작이 의심되면 이 트리와 diff 해서 회귀를 찾는다.

차이 요약: MyMotor.c 의 댐퍼 구동 코드(Damper_Mode/Step_process/Fan_Speed_process 게이팅)는 거의 동일. 통신부가 다름 — 마스터는 `My_Uart.c`(CVnet 월패드), HECO2 는 `My_Homenet.c`(RJ2/홈넷). My_RJ2.c·pwm_duty10000.c 는 동일. My_system.c 는 HECO2 가 크게 다름(DL 각실제어/시나리오 신규).

마스터 설계 원칙: 명령경로(CVnet/RJ2)는 `Fan_Speed_Setting` 을 절대 호출 안 함 — 팬 타깃은 `Fan_Speed_process`(게이팅) + `Diffuser_Damper_process` 만 관리. 관련 [[internal-damper-fan-gate]].


File created successfully at: C:\Users\전경선\.claude\projects\d--Project-nuvoton-HECO2\memory\herv-master-source.md (file state is current in your context — no need to Read it back)

---
name: herv-master-source
description: 검증된 마스터 펌웨어 소스 위치 (HECO2 댐퍼/팬 로직 비교 기준)
metadata:
  node_type: memory
  type: reference
  originSessionId: 5209cdb2-53ba-4f8b-9aa0-4a7911424cf1
---

`D:\Project\nuvoton\HERV_DL_MH_2nd\Program` 가 사용자가 지정한 **마스터(검증된) 펌웨어 소스**다. HECO2 펌웨어의 댐퍼/팬 동작이 의심되면 이 트리와 diff 해서 회귀를 찾는다.

차이 요약: MyMotor.c 의 댐퍼 구동 코드(Damper_Mode/Step_process/Fan_Speed_process 게이팅)는 거의 동일. 통신부가 다름 — 마스터는 `My_Uart.c`(CVnet 월패드), HECO2 는 `My_Homenet.c`(RJ2/홈넷). My_RJ2.c·pwm_duty10000.c 는 동일. My_system.c 는 HECO2 가 크게 다름(DL 각실제어/시나리오 신규).

마스터 설계 원칙: 명령경로(CVnet/RJ2)는 `Fan_Speed_Setting` 을 절대 호출 안 함 — 팬 타깃은 `Fan_Speed_process`(게이팅) + `Diffuser_Damper_process` 만 관리. 관련 [[internal-damper-fan-gate]].


---
name: internal-damper-fan-gate
description: 내부댐퍼(본체 6개)는 팬이 0까지 감속해야 모드별로 이동하는 게이팅 설계
metadata:
  type: project
---

HECO2 내부댐퍼(본체 6개: EA/OA/BYPASS/SA/RA/공청, `Damper_Mode()` in MyMotor.c)는 운전모드 변경 시 **"팬 타깃0 → 팬이 실제 0 도달 → Damper_Mode 호출(댐퍼 이동) → Step_Status==0x3F 정렬 → 팬 복원"** 순서로 동작한다. 공기 흐르는 중 댐퍼를 안 움직이려는 의도. `Damper_wait_time == 5` 가 "모드전환 진행중" 전용 신호.

**Why:** 명령경로(대시보드 `My_Homenet.c` CTRL_FAN 등)가 모드전환 감속창에 끼어들어 `Fan_Speed_Setting()` 으로 팬 타깃을 다시 켜면, 팬이 0에 도달 못 해 게이트가 안 열리고 내부댐퍼가 (간헐적으로) 안 움직인다. 2026-06-18 이 버그를 수정함.

**How to apply:** 모드전환 중 팬은 명령경로와 무관하게 0으로 강제해야 한다. 수정 위치 = MyMotor.c `Fan_Speed_process()` PASS1 직후 정상운전 분기: `if(Damper_wait_time == 5){ Target_Fan1_Speed=0; Target_Fan2_Speed=0; }`. 같은 모드 풍량단수만 변경(wait==0)은 즉시 반응 유지. 마스터 [[herv-master-source]] 는 애초에 명령경로에서 Fan_Speed_Setting 을 호출하지 않아 이 문제가 없었다.


File created successfully at: C:\Users\전경선\.claude\projects\d--Project-nuvoton-HECO2\memory\internal-damper-fan-gate.md (file state is current in your context — no need to Read it back)

---
name: internal-damper-fan-gate
description: 내부댐퍼(본체 6개)는 팬이 0까지 감속해야 모드별로 이동하는 게이팅 설계
metadata:
  node_type: memory
  type: project
  originSessionId: 5209cdb2-53ba-4f8b-9aa0-4a7911424cf1
---

HECO2 내부댐퍼(본체 6개: EA/OA/BYPASS/SA/RA/공청, `Damper_Mode()` in MyMotor.c)는 운전모드 변경 시 **"팬 타깃0 → 팬이 실제 0 도달 → Damper_Mode 호출(댐퍼 이동) → Step_Status==0x3F 정렬 → 팬 복원"** 순서로 동작한다. 공기 흐르는 중 댐퍼를 안 움직이려는 의도. `Damper_wait_time == 5` 가 "모드전환 진행중" 전용 신호.

**Why:** 명령경로(대시보드 `My_Homenet.c` CTRL_FAN 등)가 모드전환 감속창에 끼어들어 `Fan_Speed_Setting()` 으로 팬 타깃을 다시 켜면, 팬이 0에 도달 못 해 게이트가 안 열리고 내부댐퍼가 (간헐적으로) 안 움직인다. 2026-06-18 이 버그를 수정함.

**How to apply:** 모드전환 중 팬은 명령경로와 무관하게 0으로 강제해야 한다. 수정 위치 = MyMotor.c `Fan_Speed_process()` PASS1 직후 정상운전 분기: `if(Damper_wait_time == 5){ Target_Fan1_Speed=0; Target_Fan2_Speed=0; }`. 같은 모드 풍량단수만 변경(wait==0)은 즉시 반응 유지. 마스터 [[herv-master-source]] 는 애초에 명령경로에서 Fan_Speed_Setting 을 호출하지 않아 이 문제가 없었다.


# Memory Index

- [HERV 마스터 소스](herv-master-source.md) — 검증된 펌웨어 = D:\Project\nuvoton\HERV_DL_MH_2nd\Program, 댐퍼/팬 회귀 비교 기준
- [내부댐퍼 팬 게이트](internal-damper-fan-gate.md) — 본체 댐퍼는 팬이 0까지 내려가야 모드별 이동, 명령경로 끼어들면 미동작


File created successfully at: C:\Users\전경선\.claude\projects\d--Project-nuvoton-HECO2\memory\MEMORY.md (file state is current in your context — no need to Read it back)

# Memory Index

- [HERV 마스터 소스](herv-master-source.md) — 검증된 펌웨어 = D:\Project\nuvoton\HERV_DL_MH_2nd\Program, 댐퍼/팬 회귀 비교 기준
- [내부댐퍼 팬 게이트](internal-damper-fan-gate.md) — 본체 댐퍼는 팬이 0까지 내려가야 모드별 이동, 명령경로 끼어들면 미동작


정상 동작 확인 감사합니다. 마무리하겠습니다.

## 이번에 해결한 것 (오늘 260618)
1. **스마트수면 거실 댐퍼** — 매 틱 CO2 루프가 거실(room1)까지 열던 버그 수정 (My_system.c).
2. **내부댐퍼 모드변경 미동작 (간헐적)** — 대시보드 CTRL_FAN이 팬 감속창에 끼어들어 팬이 0에 도달 못 하던 타이밍 레이스. `Fan_Speed_process`에서 전환 중(`Damper_wait_time==5`) 팬을 강제 0으로 막아 해결 (MyMotor.c).

기록: [doc/260618_내부댐퍼_모드변경시_미동작_수정.md](doc/260618_내부댐퍼_모드변경시_미동작_수정.md), 다음 세션용 메모리 2건 저장(마스터 소스 위치 + 내부댐퍼 게이트 원리).

## 정리해두면 좋은 것 하나
1차로 주석 처리했던 명령경로 호출(`My_Homenet.c` 259/293, `My_Hood.c` 192/207)은 이제 2차 게이트 보호로 어차피 덮어써지므로 **중복(있어도 무해)** 상태입니다. 그대로 둬도 되지만, 깔끔하게 정리(원복 또는 주석 제거)를 원하시면 해드리겠습니다.

또 현재 변경은 커밋하지 않았습니다 — 원하시면 오늘 수정분(스마트수면 + 내부댐퍼)을 커밋할까요?


아직..조금더 수정하고..


알겠습니다. 커밋·정리는 보류하고 계속 진행하겠습니다.

현재 작업 상태 요약:
- 수정 완료(빌드 OK, 미커밋): `My_system.c`(스마트수면), `MyMotor.c`(내부댐퍼 게이트), `My_Homenet.c`·`My_Hood.c`(1차 명령경로 주석)
- 1차 주석 4곳은 중복이지만 무해 — 정리는 나중에

다음 수정할 내용 알려주세요.


쾌적조리 동작을 마치고 이전 모드로 복귀하고 다시 쾌적조리 버튼을 누르면 선택이 안돼..
pc대시보드 프로그램이 문제인지 먼저 봐..그 다음에는 펌웨어를 보고.


PC 대시보드의 쾌적조리 버튼 처리부터 보겠습니다.


Found 24 files
TestProgram\사양서_260604.txt
TestProgram\WebDashBoard\ErvCollector\wwwroot\index.html
TestProgram\WebDashBoard\ErvWebDashboard\index.html
TestProgram\WebDashBoard\ErvCollector\README.md
TestProgram\WebDashBoard\ErvCollector\Storage\HistoryDb.cs
TestProgram\WebDashBoard\ErvCollector\Storage\InfluxLineWriter.cs
TestProgram\WebDashBoard\ErvCollector\Storage\Sampler.cs
TestProgram\WebDashBoard\ErvCollector\Http\ControlServer.cs
TestProgram\PC_ERV_Protocol.md
TestProgram\PCDashBoard\Storage\LogDb.cs
TestProgram\PCDashBoard\MainWindow.xaml.cs
TestProgram\PCDashBoard\Model\DashboardState.cs
TestProgram\PCDashBoard\Model\LogSample.cs
TestProgram\PCDashBoard\GraphWindow.xaml.cs
TestProgram\PCDashBoard\MainWindow.xaml
TestProgram\PCDashBoard\Api\IErvApi.cs
TestProgram\PCDashBoard\Api\SerialErvApi.cs
TestProgram\PCDashBoard\Api\StatusMapper.cs
TestProgram\ErvProtocol\StatusDecoder.cs
TestProgram\ErvProtocol\StatusEncoder.cs
TestProgram\ErvProtocol\StatusRecord.cs
TestProgram\ErvProtocol\CtrlFrame.cs
TestProgram\ErvProtocol\DemoStatus.cs
TestProgram\ErvProtocol\Enums.cs


203:                SmartSleep = _state.SmartSleep, ComfortCook = _state.ComfortCook, ReliefRecover = _state.ReliefRecover,
382:            bool anyBefore = _state.SmartSleep || _state.ComfortCook || _state.ReliefRecover;
386:                "ComfortCook"   => (SubModeType.ComfortCook,   !_state.ComfortCook),
404:                if (tag != "ComfortCook"   && _state.ComfortCook)   _api.SetSubMode(SubModeType.ComfortCook,   false);
413:        bool NoScenarioActive() => !_state.SmartSleep && !_state.ComfortCook && !_state.ReliefRecover;
430:                _state.ComfortCook   = tag == "ComfortCook";
438:                    case "ComfortCook":   _state.ComfortCook = false; break;
568:            // 쾌적조리는 '연동운전중(HoodRunning=후드 가동중)' 기준으로 시나리오 활성 판단.
581:            SetActive(ComfortCookBtn,   _state.HoodRunning);   // 메이크업 실제 동작중(후드 가동)일 때만 강조 — 후드 OFF면 해제
585:            ComfortCookBtn.IsEnabled   = !subActive || _state.ComfortCook;


375	            _state.FanMode = (byte)speed;
376	            Log($"[제어] 풍량 → {speed}");
377	        }
378
379	        void SubMode_Click(object sender, RoutedEventArgs e)
380	        {
381	            if (sender is not Button b || b.Tag is not string tag) return;
382	            bool anyBefore = _state.SmartSleep || _state.ComfortCook || _state.ReliefRecover;
383	            (SubModeType type, bool next) = tag switch
384	            {
385	                "SmartSleep"    => (SubModeType.SmartSleep,    !_state.SmartSleep),
386	                "ComfortCook"   => (SubModeType.ComfortCook,   !_state.ComfortCook),
387	                _               => (SubModeType.ReliefRecover, !_state.ReliefRecover),
388	            };
389	            // 시나리오 첫 진입 → 직전 운전모드/풍량 기억(해제 시 복귀용)
390	            if (next && !anyBefore) { _modeBeforeScenario = _state.RunMode; _fanBeforeScenario = _state.FanMode; }
391	            if (tag == "SmartSleep" && !next) _sleepEndAt = null;   // 스마트수면 수동 해제 → 자동해제 예약 취소
392	            if (_demoTimer.IsEnabled)
393	            {
394	                ApplySubModeLocal(tag, next);
395	                if (NoScenarioActive()) RestorePreviousMode();
396	                return;
397	            }
398	            if (!CanSend()) return;
399	            // 상호배타: 새 모드를 켤 때 기존 활성 모드는 장치에도 OFF 전송
400	            // (펌웨어는 시나리오모드를 독립 변수로 유지 → status 재수신 시 부활 방지)
401	            if (next)
402	            {
403	                if (tag != "SmartSleep"    && _state.SmartSleep)    _api.SetSubMode(SubModeType.SmartSleep,    false);
404	                if (tag != "ComfortCook"   && _state.ComfortCook)   _api.SetSubMode(SubModeType.ComfortCook,   false);
405	                if (tag != "ReliefRecover" && _state.ReliefRecover) _api.SetSubMode(SubModeType.ReliefRecover, false);
406	            }
407	            _api.SetSubMode(type, next);
408	            ApplySubModeLocal(tag, next);
409	            Log($"[제어] 시나리오모드 {b.Content} → {(next ? "ON" : "OFF")}");
410	            if (NoScenarioActive()) RestorePreviousMode();
411	        }
412
413	        bool NoScenarioActive() => !_state.SmartSleep && !_state.ComfortCook && !_state.ReliefRecover;
414
415	        // 시나리오모드 해제 → 진입 직전 운전모드/풍량으로 동작 복귀(이전모드로 동작).
416	        // 실연결 시 ERV(펌웨어/시뮬)도 자체 복원하므로 로컬은 즉시 반영하고 STATUS 로 재동기화.
417	        void RestorePreviousMode()
418	        {
419	            _state.RunMode = _modeBeforeScenario;
420	            _state.FanMode = _fanBeforeScenario;
421	            Log($"[시나리오] 해제 → 이전 운전모드({ModeName(_modeBeforeScenario)} {_fanBeforeScenario}단) 복귀");
422	        }
423
424	        void ApplySubModeLocal(string tag, bool on)
425	        {
426	            // 시나리오모드는 상호배타: 하나를 켜면 나머지는 해제
427	            if (on)
428	            {
429	                _state.SmartSleep    = tag == "SmartSleep";
430	                _state.ComfortCook   = tag == "ComfortCook";
431	                _state.ReliefRecover = tag == "ReliefRecover";
432	            }
433	            else
434	            {
435	                switch (tag)
436	                {
437	                    case "SmartSleep":    _state.SmartSleep = false; break;
438	                    case "ComfortCook":   _state.ComfortCook = false; break;
439	                    case "ReliefRecover": _state.ReliefRecover = false; break;
440	                }
441	            }
442	        }
443
444	        void Hood_Click(object sender, RoutedEventArgs e)

            _state.FanMode = (byte)speed;
            Log($"[제어] 풍량 → {speed}");
        }

        void SubMode_Click(object sender, RoutedEventArgs e)
        {
            if (sender is not Button b || b.Tag is not string tag) return;
            bool anyBefore = _state.SmartSleep || _state.ComfortCook || _state.ReliefRecover;
            (SubModeType type, bool next) = tag switch
            {
                "SmartSleep"    => (SubModeType.SmartSleep,    !_state.SmartSleep),
                "ComfortCook"   => (SubModeType.ComfortCook,   !_state.ComfortCook),
                _               => (SubModeType.ReliefRecover, !_state.ReliefRecover),
            };
            // 시나리오 첫 진입 → 직전 운전모드/풍량 기억(해제 시 복귀용)
            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")}");
            if (NoScenarioActive()) RestorePreviousMode();
        }

        bool NoScenarioActive() => !_state.SmartSleep && !_state.ComfortCook && !_state.ReliefRecover;

        // 시나리오모드 해제 → 진입 직전 운전모드/풍량으로 동작 복귀(이전모드로 동작).
        // 실연결 시 ERV(펌웨어/시뮬)도 자체 복원하므로 로컬은 즉시 반영하고 STATUS 로 재동기화.
        void RestorePreviousMode()
        {
            _state.RunMode = _modeBeforeScenario;
            _state.FanMode = _fanBeforeScenario;
            Log($"[시나리오] 해제 → 이전 운전모드({ModeName(_modeBeforeScenario)} {_fanBeforeScenario}단) 복귀");
        }

        void ApplySubModeLocal(string tag, bool on)
        {
            // 시나리오모드는 상호배타: 하나를 켜면 나머지는 해제
            if (on)
            {
                _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;
                }
            }
        }

        void Hood_Click(object sender, RoutedEventArgs e)


버튼 토글이 `_state.ComfortCook` 상태에 의존합니다(`next = !_state.ComfortCook`). 쾌적조리가 끝난 뒤 이 상태가 어떻게 갱신되는지(특히 STATUS 수신 시)와 UI 활성/Enable 로직을 봐야 합니다. 560~590행과 상태 매핑을 확인합니다.


555	            {
556	                HoodConnText.Text = "";
557	            }
558
559	            // 운전모드 강조
560	            for (int i = 0; i < _modeButtons.Count; i++)
561	                SetActive(_modeButtons[i], ModeDefs[i].mode == _state.RunMode);
562
563	            // 풍량 : 현재 단수는 모드와 무관하게 항상 강조(자동은 ERV가 결정한 단수 표시).
564	            //   - 자동 : 수동 조절 불가(전 단 비활성)
565	            //   - 바이패스 : 최대 1단(2~4단 비활성)
566	            //   - 환기/공청 : 0~4단
567	            // 시나리오모드 활성 시: 운전모드·풍량·선택 안 된 시나리오모드 비활성화
568	            // 쾌적조리는 '연동운전중(HoodRunning=후드 가동중)' 기준으로 시나리오 활성 판단.
569	            // 후드 OFF(대기 상태)면 ERV는 본래 운전모드로 복귀하므로 운전모드를 다시 활성화해야 함(사양 3.1).
570	            bool subActive = _state.SmartSleep || _state.HoodRunning || _state.ReliefRecover;
571	            int fanMax = _state.RunMode == RunMode.Bypass ? 1 : 4;
572	            foreach (var fb in _fanButtons)
573	            {
574	                int sp = (int)fb.Tag!;
575	                fb.IsEnabled = !subActive && !_state.IsAuto && sp <= fanMax;
576	                SetActive(fb, sp == _state.FanMode);
577	            }
578
579	            // 시나리오모드
580	            SetActive(SmartSleepBtn,    _state.SmartSleep);
581	            SetActive(ComfortCookBtn,   _state.HoodRunning);   // 메이크업 실제 동작중(후드 가동)일 때만 강조 — 후드 OFF면 해제
582	            SetActive(ReliefRecoverBtn, _state.ReliefRecover);
583	            //   (활성 모드 버튼은 OFF 토글 가능해야 하므로 자기 자신은 유지)
584	            SmartSleepBtn.IsEnabled    = !subActive || _state.SmartSleep;
585	            ComfortCookBtn.IsEnabled   = !subActive || _state.ComfortCook;
586	            ReliefRecoverBtn.IsEnabled = !subActive || _state.ReliefRecover;
587	            // 스마트수면 시간설정 버튼 : 스마트수면 ON 일 때만 활성
588	            SmartSleepSetBtn.IsEnabled = _state.SmartSleep;
589	            foreach (var mb in _modeButtons) mb.IsEnabled = !subActive;
590
591	            // 자동모드 프리셋(ECO/NORMAL/TURBO) : 자동모드에서만 활성, 활성 프리셋 강조.
592	            // 선택 프리셋이 곧 공기질 판정 임계(=히스테리시스 임계). 기본값은 표준(NORMAL, 상태 초기값).
593	            // (HystWindow 팝업의 프리셋 버튼과 _state.HystPreset 으로 동기화)
594	            bool presetEnabled = _state.IsAuto && !subActive;
595	            var presets = new[] { HystPreset.Eco, HystPreset.Normal, HystPreset.Turbo };
596	            for (int i = 0; i < _presetButtons.Count; i++)
597	            {
598	                _presetButtons[i].IsEnabled = presetEnabled;
599	                SetActive(_presetButtons[i], presetEnabled && presets[i] == _state.HystPreset);

            {
                HoodConnText.Text = "";
            }

            // 운전모드 강조
            for (int i = 0; i < _modeButtons.Count; i++)
                SetActive(_modeButtons[i], ModeDefs[i].mode == _state.RunMode);

            // 풍량 : 현재 단수는 모드와 무관하게 항상 강조(자동은 ERV가 결정한 단수 표시).
            //   - 자동 : 수동 조절 불가(전 단 비활성)
            //   - 바이패스 : 최대 1단(2~4단 비활성)
            //   - 환기/공청 : 0~4단
            // 시나리오모드 활성 시: 운전모드·풍량·선택 안 된 시나리오모드 비활성화
            // 쾌적조리는 '연동운전중(HoodRunning=후드 가동중)' 기준으로 시나리오 활성 판단.
            // 후드 OFF(대기 상태)면 ERV는 본래 운전모드로 복귀하므로 운전모드를 다시 활성화해야 함(사양 3.1).
            bool subActive = _state.SmartSleep || _state.HoodRunning || _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;
                SetActive(fb, sp == _state.FanMode);
            }

            // 시나리오모드
            SetActive(SmartSleepBtn,    _state.SmartSleep);
            SetActive(ComfortCookBtn,   _state.HoodRunning);   // 메이크업 실제 동작중(후드 가동)일 때만 강조 — 후드 OFF면 해제
            SetActive(ReliefRecoverBtn, _state.ReliefRecover);
            //   (활성 모드 버튼은 OFF 토글 가능해야 하므로 자기 자신은 유지)
            SmartSleepBtn.IsEnabled    = !subActive || _state.SmartSleep;
            ComfortCookBtn.IsEnabled   = !subActive || _state.ComfortCook;
            ReliefRecoverBtn.IsEnabled = !subActive || _state.ReliefRecover;
            // 스마트수면 시간설정 버튼 : 스마트수면 ON 일 때만 활성
            SmartSleepSetBtn.IsEnabled = _state.SmartSleep;
            foreach (var mb in _modeButtons) mb.IsEnabled = !subActive;

            // 자동모드 프리셋(ECO/NORMAL/TURBO) : 자동모드에서만 활성, 활성 프리셋 강조.
            // 선택 프리셋이 곧 공기질 판정 임계(=히스테리시스 임계). 기본값은 표준(NORMAL, 상태 초기값).
            // (HystWindow 팝업의 프리셋 버튼과 _state.HystPreset 으로 동기화)
            bool presetEnabled = _state.IsAuto && !subActive;
            var presets = new[] { HystPreset.Eco, HystPreset.Normal, HystPreset.Turbo };
            for (int i = 0; i < _presetButtons.Count; i++)
            {
                _presetButtons[i].IsEnabled = presetEnabled;
                SetActive(_presetButtons[i], presetEnabled && presets[i] == _state.HystPreset);


쾌적조리 동작을 마치고 이전 모드로 복귀하고 다시 쾌적조리 버튼을 누르면 선택이 안돼.. pc대시보드 프로그램이 문제인지 먼저 봐..그 다음에는 펌웨어를 보고.


HECO2 댐퍼 동작 문제 분석 및 비교


토글이 `_state.ComfortCook`에 의존하는데 UI 강조는 `_state.HoodRunning` 기준입니다. 쾌적조리 종료 후 `_state.ComfortCook`이 리셋되는지가 핵심입니다. STATUS 수신 시 매핑을 확인합니다.


18:            state.HoodRunning   = s.HoodRunning;     // byte5 bit1 = 연동운전중(메이크업 동작중)


28:        public bool HoodRunning { get => _hoodRunning; set { if (_hoodRunning != value) { _hoodRunning = value; OnChanged(); } } }
51:        public bool ComfortCook   { get => _comfortCook;   set { if (_comfortCook != value)   { _comfortCook = value;   OnChanged(); } } }
60:                if (ComfortCook)   b |= SubModeBits.ComfortCook;
67:                ComfortCook   = (value & SubModeBits.ComfortCook)   != 0;


`StatusMapper`가 `HoodRunning`은 매핑하지만 `ComfortCook`은 매핑 안 하는 것으로 보입니다. SmartSleep/ReliefRecover와 비교해 확인합니다.


1	using ErvDashboard.Model;
2	using ErvProtocol;
3
4	namespace ErvDashboard.Api
5	{
6	    // StatusRecord(공용 프로토콜 디코드 결과) → DashboardState(WPF UI 모델) 매핑.
7	    // (이전 DashboardProtocol.ApplyStatus 로직 — API 는 UI 비종속, 매핑은 여기로 분리)
8	    public static class StatusMapper
9	    {
10	        public static void Apply(StatusRecord s, DashboardState state)
11	        {
12	            state.PowerOn   = s.Power != 0;
13	            state.RunMode   = (RunMode)s.RunMode;
14	            state.AutoState = (AutoState)s.AutoState;
15	            state.FanMode   = s.FanMode;
16	            state.SubModeBitmap = s.SubMode;
17	            state.Hood          = s.HoodEnable;      // byte5 bit0 = 연동 Enable
18	            state.HoodRunning   = s.HoodRunning;     // byte5 bit1 = 연동운전중(메이크업 동작중)
19	            state.HoodConnected = s.HoodConnected;   // byte5 bit2 = 후드 통신연결
20	            state.HystPreset = (HystPreset)s.HystPreset;
21	            state.HystPm25  = s.HystPm25;
22	            state.HystPm10  = s.HystPm10;
23	            state.HystVoc   = s.HystVoc;
24	            state.HystCo2   = s.HystCo2;
25	            state.ErrorCode = s.ErrorCode;
26	            state.Reset     = s.Reset != 0;
27	            state.ReserveRemainSec = s.ReserveRemainSec;
28
29	            for (int i = 0; i < state.Vsp.Count && i < s.Vsp.Length; i++)
30	            {
31	                state.Vsp[i].Sa = s.Vsp[i].Sa;
32	                state.Vsp[i].Ea = s.Vsp[i].Ea;
33	            }
34
35	            for (int i = 0; i < state.HystTable.Count && i < s.HystTable.Length; i++)
36	            {
37	                state.HystTable[i].Pm25 = s.HystTable[i].Pm25;
38	                state.HystTable[i].Pm10 = s.HystTable[i].Pm10;
39	                state.HystTable[i].Voc  = s.HystTable[i].Voc;
40	                state.HystTable[i].Co2  = s.HystTable[i].Co2;
41	            }
42
43	            // 모드별 오염단계 임계표
44	            for (int i = 0; i < 3 && i < s.ThrTable.Length; i++)
45	                for (int k = 0; k < 4; k++)
46	                {
47	                    state.Co2Thr[i][k]  = s.ThrTable[i].Co2[k];
48	                    state.Pm25Thr[i][k] = s.ThrTable[i].Pm25[k];
49	                    state.Pm10Thr[i][k] = s.ThrTable[i].Pm10[k];
50	                    state.VocThr[i][k]  = s.ThrTable[i].Voc[k];
51	                }
52
53	            int totalLoad = 0;
54	            for (int r = 0; r < 4; r++)
55	            {
56	                var src = s.Rooms[r];
57	                var room = state.Rooms[r];
58	                room.DamperSaOpen = src.DamperSa;   // 비트맵 bit0
59	                room.DamperEaOpen = src.DamperEa;   // 비트맵 bit1
60	                room.Pm25        = src.Pm25;
61	                room.Pm10        = src.Pm10;
62	                room.Voc         = src.Voc;
63	                room.Co2         = src.Co2;
64	                room.AirQuality  = (AirQuality)src.AirQuality;
65	                room.LedDim      = src.LedDim;
66	                room.LoadScore   = src.LoadScore;
67	                room.FinalVolume = src.FinalVolume;
68	                room.Temp        = src.Temp;
69	                room.Humi        = src.Humi;
70	                totalLoad += src.LoadScore;
71	            }
72	            state.TotalLoadScore = totalLoad;   // 합산 부하점수 (0~16)
73	        }
74	    }
75	}
76

using ErvDashboard.Model;
using ErvProtocol;

namespace ErvDashboard.Api
{
    // StatusRecord(공용 프로토콜 디코드 결과) → DashboardState(WPF UI 모델) 매핑.
    // (이전 DashboardProtocol.ApplyStatus 로직 — API 는 UI 비종속, 매핑은 여기로 분리)
    public static class StatusMapper
    {
        public static void Apply(StatusRecord s, DashboardState state)
        {
            state.PowerOn   = s.Power != 0;
            state.RunMode   = (RunMode)s.RunMode;
            state.AutoState = (AutoState)s.AutoState;
            state.FanMode   = s.FanMode;
            state.SubModeBitmap = s.SubMode;
            state.Hood          = s.HoodEnable;      // byte5 bit0 = 연동 Enable
            state.HoodRunning   = s.HoodRunning;     // byte5 bit1 = 연동운전중(메이크업 동작중)
            state.HoodConnected = s.HoodConnected;   // byte5 bit2 = 후드 통신연결
            state.HystPreset = (HystPreset)s.HystPreset;
            state.HystPm25  = s.HystPm25;
            state.HystPm10  = s.HystPm10;
            state.HystVoc   = s.HystVoc;
            state.HystCo2   = s.HystCo2;
            state.ErrorCode = s.ErrorCode;
            state.Reset     = s.Reset != 0;
            state.ReserveRemainSec = s.ReserveRemainSec;

            for (int i = 0; i < state.Vsp.Count && i < s.Vsp.Length; i++)
            {
                state.Vsp[i].Sa = s.Vsp[i].Sa;
                state.Vsp[i].Ea = s.Vsp[i].Ea;
            }

            for (int i = 0; i < state.HystTable.Count && i < s.HystTable.Length; i++)
            {
                state.HystTable[i].Pm25 = s.HystTable[i].Pm25;
                state.HystTable[i].Pm10 = s.HystTable[i].Pm10;
                state.HystTable[i].Voc  = s.HystTable[i].Voc;
                state.HystTable[i].Co2  = s.HystTable[i].Co2;
            }

            // 모드별 오염단계 임계표
            for (int i = 0; i < 3 && i < s.ThrTable.Length; i++)
                for (int k = 0; k < 4; k++)
                {
                    state.Co2Thr[i][k]  = s.ThrTable[i].Co2[k];
                    state.Pm25Thr[i][k] = s.ThrTable[i].Pm25[k];
                    state.Pm10Thr[i][k] = s.ThrTable[i].Pm10[k];
                    state.VocThr[i][k]  = s.ThrTable[i].Voc[k];
                }

            int totalLoad = 0;
            for (int r = 0; r < 4; r++)
            {
                var src = s.Rooms[r];
                var room = state.Rooms[r];
                room.DamperSaOpen = src.DamperSa;   // 비트맵 bit0
                room.DamperEaOpen = src.DamperEa;   // 비트맵 bit1
                room.Pm25        = src.Pm25;
                room.Pm10        = src.Pm10;
                room.Voc         = src.Voc;
                room.Co2         = src.Co2;
                room.AirQuality  = (AirQuality)src.AirQuality;
                room.LedDim      = src.LedDim;
                room.LoadScore   = src.LoadScore;
                room.FinalVolume = src.FinalVolume;
                room.Temp        = src.Temp;
                room.Humi        = src.Humi;
                totalLoad += src.LoadScore;
            }
            state.TotalLoadScore = totalLoad;   // 합산 부하점수 (0~16)
        }
    }
}


`SubModeBitmap`(status byte4)이 `_state.ComfortCook`을 갱신합니다(setter). 그러니 펌웨어가 status에서 쾌적조리 비트를 어떻게 채우는지가 핵심입니다. 또 대시보드는 **강조는 `HoodRunning`, 토글 판단은 `ComfortCook`** 으로 서로 다른 변수를 써서 불일치 소지가 있습니다. 펌웨어 status 빌드와 비트 정의를 확인합니다.


118	    uint16_t i;
119
120	    for(i = 0; i < HN_STATUS_LEN; i++) p[i] = 0;
121
122	    /* --- 글로벌 0~16 --- */
123	    p[0] = Power_On;
124	    p[1] = hn_runmode_code();
125	    p[2] = Auto_Concentrate;                 /* 0 분산 / 1 집중 */
126	    p[3] = Fan_Mode;
127	    p[4] = (uint8_t)(((Ext_Run_Mode == 4) ? 0x01 : 0)    /* 스마트수면 */
128	                   | (Hood_YeunDong_Enable ? 0x02 : 0)   /* 쾌적조리   */
129	                   | ((Ext_Run_Mode == 1) ? 0x04 : 0));  /* 안심회복   */
130	    /* bit0 연동Enable / bit1 연동운전중 / bit2 후드 통신연결(폴 응답 생존) */
131	    p[5] = (uint8_t)((Hood_YeunDong_Enable ? 0x01 : 0) | ((Hood_Status != 0) ? 0x02 : 0) | ((Hood_Conn_Timeout != 0) ? 0x04 : 0));
132	    p[6] = Hyst_Preset;
133	    hn_w16(&p[7],  Pm25_Db[Hyst_Preset]);
134	    hn_w16(&p[9],  Pm10_Db[Hyst_Preset]);
135	    hn_w16(&p[11], Voc_Db[Hyst_Preset]);
136	    hn_w16(&p[13], Co2_Db[Hyst_Preset]);
137	    hn_w16(&p[15], (uint16_t)Err_Code);

    uint16_t i;

    for(i = 0; i < HN_STATUS_LEN; i++) p[i] = 0;

    /* --- 글로벌 0~16 --- */
    p[0] = Power_On;
    p[1] = hn_runmode_code();
    p[2] = Auto_Concentrate;                 /* 0 분산 / 1 집중 */
    p[3] = Fan_Mode;
    p[4] = (uint8_t)(((Ext_Run_Mode == 4) ? 0x01 : 0)    /* 스마트수면 */
                   | (Hood_YeunDong_Enable ? 0x02 : 0)   /* 쾌적조리   */
                   | ((Ext_Run_Mode == 1) ? 0x04 : 0));  /* 안심회복   */
    /* bit0 연동Enable / bit1 연동운전중 / bit2 후드 통신연결(폴 응답 생존) */
    p[5] = (uint8_t)((Hood_YeunDong_Enable ? 0x01 : 0) | ((Hood_Status != 0) ? 0x02 : 0) | ((Hood_Conn_Timeout != 0) ? 0x04 : 0));
    p[6] = Hyst_Preset;
    hn_w16(&p[7],  Pm25_Db[Hyst_Preset]);
    hn_w16(&p[9],  Pm10_Db[Hyst_Preset]);
    hn_w16(&p[11], Voc_Db[Hyst_Preset]);
    hn_w16(&p[13], Co2_Db[Hyst_Preset]);
    hn_w16(&p[15], (uint16_t)Err_Code);


44	        public string SleepSummary => $"{DashboardState.FmtTime(SleepStartMin)} ~ {DashboardState.FmtTime(SleepEndMin)}";
45	        public static string FmtTime(int min)
46	        {
47	            min = ((min % 1440) + 1440) % 1440;
48	            int h = min / 60, m = min % 60, h12 = h % 12; if (h12 == 0) h12 = 12;
49	            return $"{(h < 12 ? "오전" : "오후")} {h12}:{m:00}";
50	        }
51	        public bool ComfortCook   { get => _comfortCook;   set { if (_comfortCook != value)   { _comfortCook = value;   OnChanged(); } } }
52	        public bool ReliefRecover { get => _reliefRecover; set { if (_reliefRecover != value) { _reliefRecover = value; OnChanged(); } } }
53
54	        public byte SubModeBitmap
55	        {
56	            get
57	            {
58	                byte b = 0;
59	                if (SmartSleep)    b |= SubModeBits.SmartSleep;
60	                if (ComfortCook)   b |= SubModeBits.ComfortCook;
61	                if (ReliefRecover) b |= SubModeBits.ReliefRecover;
62	                return b;
63	            }
64	            set
65	            {
66	                SmartSleep    = (value & SubModeBits.SmartSleep)    != 0;
67	                ComfortCook   = (value & SubModeBits.ComfortCook)   != 0;
68	                ReliefRecover = (value & SubModeBits.ReliefRecover) != 0;
69	            }
70	        }
71

        public string SleepSummary => $"{DashboardState.FmtTime(SleepStartMin)} ~ {DashboardState.FmtTime(SleepEndMin)}";
        public static string FmtTime(int min)
        {
            min = ((min % 1440) + 1440) % 1440;
            int h = min / 60, m = min % 60, h12 = h % 12; if (h12 == 0) h12 = 12;
            return $"{(h < 12 ? "오전" : "오후")} {h12}:{m:00}";
        }
        public bool ComfortCook   { get => _comfortCook;   set { if (_comfortCook != value)   { _comfortCook = value;   OnChanged(); } } }
        public bool ReliefRecover { get => _reliefRecover; set { if (_reliefRecover != value) { _reliefRecover = value; OnChanged(); } } }

        public byte SubModeBitmap
        {
            get
            {
                byte b = 0;
                if (SmartSleep)    b |= SubModeBits.SmartSleep;
                if (ComfortCook)   b |= SubModeBits.ComfortCook;
                if (ReliefRecover) b |= SubModeBits.ReliefRecover;
                return b;
            }
            set
            {
                SmartSleep    = (value & SubModeBits.SmartSleep)    != 0;
                ComfortCook   = (value & SubModeBits.ComfortCook)   != 0;
                ReliefRecover = (value & SubModeBits.ReliefRecover) != 0;
            }
        }


핵심 확인했습니다. 펌웨어 status byte4 bit1 = **`Hood_YeunDong_Enable`** 이고, 대시보드 `_state.ComfortCook`은 이 비트를 그대로 따라갑니다. 즉 **쾌적조리 버튼의 ON/OFF 판단 = `Hood_YeunDong_Enable`**.

문제 구조가 보입니다:
- **토글 판단**(SubMode_Click L386): `next = !_state.ComfortCook` = `!Hood_YeunDong_Enable`
- **강조 표시**(L581): `_state.HoodRunning`(후드 실제 가동중)

쾌적조리 켜면 `Hood_YeunDong_Enable=1`. 요리 끝나 후드 꺼지면 `HoodRunning`은 0이 되지만 `Hood_YeunDong_Enable`(연동 Enable)은 1로 남는지가 관건입니다. 남으면 → 버튼은 꺼져 보이는데(HoodRunning=0) 내부는 ON(ComfortCook=1) → 다시 누르면 `next=false`라 **OFF가 전송돼 "선택 안 됨"**. 펌웨어가 Enable을 언제 지우는지 확인합니다.


Source\HECO2\User\My_Hood.c:172:    if((Hood_YeunDong_Enable == 0)||(HREV_Hood_Control == 1))return(0);
Source\HECO2\User\My_Homenet.c:318:                else if(pl[0] == 2) Hood_YeunDong_Enable = pl[1] ? 1 : 0;
Source\HECO2\User\My_Homenet.c:326:            if(len >= 1) Hood_YeunDong_Enable = pl[0] ? 1 : 0; else result = 1;
Source\HECO2\User\My_RJ2.c:296:	if((Hood_Yeundong_flag == 1) && (Hood_YeunDong_Enable == 1))//2023.02.14 Hood Enable Add
Source\HECO2\User\My_RJ2.c:417:                        if((Memory_Hood_Status != Hood_Status)&&(Hood_YeunDong_Enable == 1))
Source\HECO2\User\My_Hood_old.c:182:    if((Hood_YeunDong_Enable == 0)||(HREV_Hood_Control == 1))return(0);
Source\HECO2\User\My_system.c:687:uint8_t Hood_YeunDong_Enable = 0;
Source\HECO2\User\My_system.c:848:        Hood_YeunDong_Enable = 1;
Source\HECO2\User\My_system.c:954:        Hood_YeunDong_Enable = 0;
Source\HECO2\User\My_system.c:1117:        if((Hood_YeunDong_Enable == 1)&&((Hood_Status != 0)||(Hood_Yeundong_flag == 1)))


대시보드 프로그램에서 눌러도 토글이 안되는 것 같은데..


대시보드 코드를 보니 토글 자체는 도는데 **눈에 안 보이는 구조**입니다. 원인을 정리합니다.

## 대시보드 쾌적조리 버튼의 모순 (이게 "토글 안 되는 것처럼" 보이는 이유)
한 변수가 아니라 **서로 다른 두 변수**를 씁니다:

| 용도 | 사용 변수 | 위치 |
|---|---|---|
| **켜짐 강조(하이라이트)** | `_state.HoodRunning` (후드 실제 가동중) | [MainWindow.xaml.cs:581](TestProgram/PCDashBoard/MainWindow.xaml.cs#L581) |
| **토글 방향 판단** | `_state.ComfortCook` (= 펌웨어 `Hood_YeunDong_Enable`) | [MainWindow.xaml.cs:386](TestProgram/PCDashBoard/MainWindow.xaml.cs#L386) |

펌웨어 status를 확인하면 `_state.ComfortCook` = byte4 bit1 = **`Hood_YeunDong_Enable`** ([My_Homenet.c:128](Source/HECO2/User/My_Homenet.c#L128))이고, `HoodRunning` = byte5 bit1 = **후드가 지금 돌고 있는지**(별개)입니다.

### 그래서 생기는 일
1. 쾌적조리 누름 → `ComfortCook=true`(연동 Enable). 하지만 후드가 실제로 안 돌면 `HoodRunning=false` → **버튼 하이라이트가 안 켜짐**. → 사용자: "눌러도 반응이 없다 / 토글이 안 된다."
2. 요리 끝나 후드 OFF → `HoodRunning=false`(원래도 false였음), 그런데 `ComfortCook`(Enable)은 **여전히 true로 남음**.
3. 다시 누름 → `next = !ComfortCook = false` → **OFF가 전송됨** → "선택이 안 됨"(켜지지 않고 꺼짐).

즉 **버튼은 'Enable(ComfortCook)'을 토글하는데, 화면 강조는 'HoodRunning'으로 하니** 토글 결과가 화면에 안 보이고, 방향도 반대로 먹습니다.

## 대시보드 수정안 (1차)
쾌적조리 버튼 강조를 토글 대상과 일치시키면 됩니다 — 강조를 `_state.ComfortCook`(Enable)로:
- L581: `SetActive(ComfortCookBtn, _state.HoodRunning);` → `SetActive(ComfortCookBtn, _state.ComfortCook);`

이러면 누르는 즉시 하이라이트가 켜지고(연동 armed 표시), 다시 누르면 꺼집니다 — 토글이 화면에 보입니다. (모드/풍량 잠금 `subActive`는 사양 3.1대로 `HoodRunning` 기준 유지 — 후드 대기 중엔 본래 모드 조작 가능해야 하므로 그대로 둠.)

## 다만, 진짜 결정이 필요한 부분
"쾌적조리를 **마치면** 자동으로 꺼져야 한다"가 사양이면, 위 대시보드 수정만으로는 부족하고 **펌웨어가 후드 OFF 복귀 시 `Hood_YeunDong_Enable`을 0으로 내려야** 합니다(현재는 연동 Enable이 계속 armed로 남음). 이건 다음 단계 펌웨어 작업입니다.

**확인 부탁:** 쾌적조리는 (A) **한 번 켜면 계속 armed**(후드 켤 때마다 자동 메이크업, 끄려면 수동 OFF) 인가요, 아니면 (B) **요리 1회 끝나면 자동 해제**(다시 쓰려면 또 켜야 함) 인가요?
- (A)면 → 위 대시보드 L581 수정만으로 해결.
- (B)면 → 대시보드 L581 + 펌웨어에서 후드 OFF 복귀 시 Enable 자동 클리어 추가.

어느 쪽인가요? 알려주시면 그에 맞게 적용하겠습니다.


개발사양서 9페이지를 참고해 봐


개발사양서 9페이지를 확인하겠습니다.


Bash

cd /d/Project/nuvoton/HECO2/개발사양서
tmp=$(mktemp -d)
unzip -o -q "각실제어시스템_개발사양서_DL_동작로직_260613.pptx" -d "$tmp" 2>/dev/null
echo "=== slide9.xml 텍스트 ==="
grep -o '<a:t>[^<]*</a:t>' "$tmp/ppt/slides/slide9.xml" | sed 's/<a:t>//;s/<\/a:t>//'
rm -rf "$tmp"

Extract slide 9 text (comfort cooking spec)


=== slide9.xml 텍스트 ===
동작모드
–
시나리오 모드
개발 사양서
-
각실제어
 시스템
3.
쾌적조리 모드
쾌적조리는 운전 모드가 아닌 '외부 장비(
렌지후드
) 연동
스위치'입니다
.
자동운전/수동운전 상태와 무관하게, 후드 가동 신호에 따라 즉각적인
메이크업 에어(
Make-up
 Air,
급기
 전용
양압
 형성)
 동작을 수행합니다.
거실
침실
1
침실
2
침실
3
급기

(SA)
O
O
O
O
배기
(RA)
X
X
X
X
쾌적조리 모드
각실

급배기
3.1.
연동작동 매트릭스
쾌적조리
토글
 (UI)
렌지후드
 상태
환기장치(ERV) 최종 작동 상태
OFF
켜짐
 / 꺼짐
[연동 없음]
 환기장치는 쾌적조리를 무시하고 본래 설정(자동/수동)대로 가동.
ON
꺼짐
[대기 상태]
 환기장치는 본래 설정(자동/수동)대로 가동.
ON
켜짐
[메이크업 에어 (강제 연동)]
1. 기존 제어(자동/수동) 무시 및 일시 정지
2.
전실
급기
(SA)
댐퍼
 100%
열림
 및 배기(
R
A
)
댐퍼

닫힘
3. 장비
풍량을

렌지후드

풍량에

동기화
3.2.
풍량
 동기화
(
단수 추종
)
매핑 기준
렌지후드
 1단 가동 시
→
 환기장치 1단 (약) 강제 가동
렌지후드
 2단 가동 시
→
 환기장치 2단 (중) 강제 가동
렌지후드
 3단 가동 시
→
 환기장치 3단 (강) 강제 가동
렌지후드

4
단 가동 시
→
 환기장치
4
단 (강) 강제 가동
렌지후드

5
단 가동 시
→
 환기장치
4
단 (강) 강제 가동
3.3. 조리 종료 후 복귀 (
Roll-back
) 로직
렌지후드
 전원이 꺼지면, 환기장치는 잔여 냄새 배출을 위해
[설정된 딜레이 시간, 예: 3
분
]
 동안 연동 상태(메이크업 에어)를 유지
딜레이 시간이 종료되면, 환기장치는 쾌적조리가 발동되기 직전에 사용자가 설정해

두었던
원래 상태(자동운전 ON 또는 수동 특정 단수)로 복귀(
Roll-back
)
후드
:
전원
OFF
후
30
초간 지연 배기
ERV :
후드 전원
OFF
후
20
분 환기 약 동작 후 이전 모드 복귀


9페이지가 결정적입니다. 정리하면:

**3.1 연동작동 매트릭스** — 쾌적조리는 명시적으로 **"쾌적조리 토글(UI)"** = 사용자가 ON/OFF 하는 **연동 스위치**입니다. 후드 상태(켜짐/꺼짐)는 메이크업/대기를 가를 뿐, **토글 자체는 사용자가 끌 때까지 ON 유지**입니다.

**3.3 Roll-back** — 후드 OFF 후 20분 환기 약 동작 후 이전 모드 복귀. 이건 **메이크업 에어의 복귀 타이밍**이지, 쾌적조리 토글을 끄는 게 아닙니다.

→ 즉 제 질문의 답은 **(A) 계속 armed**입니다. 그리고 이건 **순수 대시보드 버그**입니다 — 펌웨어는 `Hood_YeunDong_Enable`을 토글로 정상 유지하는데, 대시보드가 토글 버튼 강조를 `HoodRunning`(메이크업 동작중)에 묶어놔서 대기/복귀 상태에서 버튼이 꺼져 보이고 토글이 반대로 먹습니다.

수정합니다 — 쾌적조리 버튼 강조를 토글 상태(`_state.ComfortCook` = Enable)로:


Edit

SetActive(ComfortCookBtn,   _state.HoodRunning);   // 메이크업 실제 동작중(후드 가동)일 때만 강조 — 후드 OFF면 해제

            // 쾌적조리는 사양 9p 3.1 의 'UI 토글(연동 스위치)' — 후드 가동중이 아니라 토글 ON/OFF(=Hood_YeunDong_Enable)를 강조.
            // (HoodRunning 으로 강조하면 대기/Roll-back 상태에서 버튼이 꺼져 보여 재선택 시 토글이 반대로 먹던 문제 수정)
            SetActive(ComfortCookBtn,   _state.ComfortCook);


The file d:\Project\nuvoton\HECO2\TestProgram\PCDashBoard\MainWindow.xaml.cs has been updated successfully. (file state is current in your context — no need to Read it back)

            // 쾌적조리는 사양 9p 3.1 의 'UI 토글(연동 스위치)' — 후드 가동중이 아니라 토글 ON/OFF(=Hood_YeunDong_Enable)를 강조.
            // (HoodRunning 으로 강조하면 대기/Roll-back 상태에서 버튼이 꺼져 보여 재선택 시 토글이 반대로 먹던 문제 수정)
            SetActive(ComfortCookBtn,   _state.ComfortCook);

SetActive(SmartSleepBtn,    _state.SmartSleep);

-            SetActive(ComfortCookBtn,   _state.HoodRunning);   // 메이크업 실제 동작중(후드 가동)일 때만 강조 — 후드 OFF면 해제

+            // 쾌적조리는 사양 9p 3.1 의 'UI 토글(연동 스위치)' — 후드 가동중이 아니라 토글 ON/OFF(=Hood_YeunDong_Enable)를 강조.

+            // (HoodRunning 으로 강조하면 대기/Roll-back 상태에서 버튼이 꺼져 보여 재선택 시 토글이 반대로 먹던 문제 수정)

+            SetActive(ComfortCookBtn,   _state.ComfortCook);

//   (활성 모드 버튼은 OFF 토글 가능해야 하므로 자기 자신은 유지)

SmartSleepBtn.IsEnabled    = !subActive || _state.SmartSleep;


대시보드를 빌드해 확인합니다.


Bash

cd /d/Project/nuvoton/HECO2/TestProgram/PCDashBoard && dotnet build ErvDashboard.csproj -nologo 2>&1 | tail -15

Build PC dashboard after fix


HECO2 댐퍼 동작 문제 분석 및 비교


빌드했습니다.

D:\Project\nuvoton\HECO2\TestProgram\PCDashBoard\ErvDashboard.csproj : warning NU1701: Package 'OpenTK 3.3.1' was restored using '.NETFramework,Version=v4.6.1, .NETFramework,Version=v4.6.2, .NETFramework,Version=v4.7, .NETFramework,Version=v4.7.1, .NETFramework,Version=v4.7.2, .NETFramework,Version=v4.8, .NETFramework,Version=v4.8.1' instead of the project target framework 'net10.0-windows7.0'. This package may not be fully compatible with your project.
D:\Project\nuvoton\HECO2\TestProgram\PCDashBoard\ErvDashboard.csproj : warning NU1701: Package 'OpenTK.GLWpfControl 3.3.0' was restored using '.NETFramework,Version=v4.6.1, .NETFramework,Version=v4.6.2, .NETFramework,Version=v4.7, .NETFramework,Version=v4.7.1, .NETFramework,Version=v4.7.2, .NETFramework,Version=v4.8, .NETFramework,Version=v4.8.1' instead of the project target framework 'net10.0-windows7.0'. This package may not be fully compatible with your project.
D:\Project\nuvoton\HECO2\TestProgram\PCDashBoard\ErvDashboard.csproj : warning NU1701: Package 'SkiaSharp.Views.WPF 3.119.1' was restored using '.NETFramework,Version=v4.6.1, .NETFramework,Version=v4.6.2, .NETFramework,Version=v4.7, .NETFramework,Version=v4.7.1, .NETFramework,Version=v4.7.2, .NETFramework,Version=v4.8, .NETFramework,Version=v4.8.1' instead of the project target framework 'net10.0-windows7.0'. This package may not be fully compatible with your project.
D:\Project\nuvoton\HECO2\TestProgram\PCDashBoard\ErvDashboard.csproj : warning NU1701: Package 'OpenTK 3.3.1' was restored using '.NETFramework,Version=v4.6.1, .NETFramework,Version=v4.6.2, .NETFramework,Version=v4.7, .NETFramework,Version=v4.7.1, .NETFramework,Version=v4.7.2, .NETFramework,Version=v4.8, .NETFramework,Version=v4.8.1' instead of the project target framework 'net10.0-windows7.0'. This package may not be fully compatible with your project.
D:\Project\nuvoton\HECO2\TestProgram\PCDashBoard\ErvDashboard.csproj : warning NU1701: Package 'OpenTK.GLWpfControl 3.3.0' was restored using '.NETFramework,Version=v4.6.1, .NETFramework,Version=v4.6.2, .NETFramework,Version=v4.7, .NETFramework,Version=v4.7.1, .NETFramework,Version=v4.7.2, .NETFramework,Version=v4.8, .NETFramework,Version=v4.8.1' instead of the project target framework 'net10.0-windows7.0'. This package may not be fully compatible with your project.
D:\Project\nuvoton\HECO2\TestProgram\PCDashBoard\ErvDashboard.csproj : warning NU1701: Package 'SkiaSharp.Views.WPF 3.119.1' was restored using '.NETFramework,Version=v4.6.1, .NETFramework,Version=v4.6.2, .NETFramework,Version=v4.7, .NETFramework,Version=v4.7.1, .NETFramework,Version=v4.7.2, .NETFramework,Version=v4.8, .NETFramework,Version=v4.8.1' instead of the project target framework 'net10.0-windows7.0'. This package may not be fully compatible with your project.
D:\Project\nuvoton\HECO2\TestProgram\PCDashBoard\ErvDashboard_ayzbbueh_wpftmp.csproj : warning NU1701: Package 'OpenTK 3.3.1' was restored using '.NETFramework,Version=v4.6.1, .NETFramework,Version=v4.6.2, .NETFramework,Version=v4.7, .NETFramework,Version=v4.7.1, .NETFramework,Version=v4.7.2, .NETFramework,Version=v4.8, .NETFramework,Version=v4.8.1' instead of the project target framework 'net10.0-windows7.0'. This package may not be fully compatible with your project.
D:\Project\nuvoton\HECO2\TestProgram\PCDashBoard\ErvDashboard_ayzbbueh_wpftmp.csproj : warning NU1701: Package 'OpenTK.GLWpfControl 3.3.0' was restored using '.NETFramework,Version=v4.6.1, .NETFramework,Version=v4.6.2, .NETFramework,Version=v4.7, .NETFramework,Version=v4.7.1, .NETFramework,Version=v4.7.2, .NETFramework,Version=v4.8, .NETFramework,Version=v4.8.1' instead of the project target framework 'net10.0-windows7.0'. This package may not be fully compatible with your project.
D:\Project\nuvoton\HECO2\TestProgram\PCDashBoard\ErvDashboard_ayzbbueh_wpftmp.csproj : warning NU1701: Package 'SkiaSharp.Views.WPF 3.119.1' was restored using '.NETFramework,Version=v4.6.1, .NETFramework,Version=v4.6.2, .NETFramework,Version=v4.7, .NETFramework,Version=v4.7.1, .NETFramework,Version=v4.7.2, .NETFramework,Version=v4.8, .NETFramework,Version=v4.8.1' instead of the project target framework 'net10.0-windows7.0'. This package may not be fully compatible with your project.
    경고 9개
    오류 0개

경과 시간: 00:00:04.64


빌드 성공 — **오류 0개**, 경고 9개는 전부 기존 NU1701(패키지 타깃 호환) 경고로 이번 변경과 무관합니다. 작업 기록을 남깁니다.


Edit

## 후속
- 실장비에서 환기→공청 **반복 전환** 시 매번 댐퍼 이동(간헐성 사라짐) 확인.
- 같은 모드 풍량단수 변경 시 팬 즉시 반응 정상 확인.

## 후속
- 실장비에서 환기→공청 **반복 전환** 시 매번 댐퍼 이동(간헐성 사라짐) 확인.
- 같은 모드 풍량단수 변경 시 팬 즉시 반응 정상 확인.

---

# 260618 (4) 쾌적조리 재선택 안됨 — 대시보드 버튼 강조 버그

## 증상
- 쾌적조리 동작 마치고 이전 모드 복귀 후 다시 쾌적조리 버튼 누르면 선택(ON)이 안 됨. 눌러도 토글 반응이 안 보임.

## 사양 (개발사양서 9p)
- 쾌적조리 = 운전모드가 아닌 **'후드 연동 UI 토글 스위치'**. 사용자가 켤 때까지 ON 유지.
- 3.1 매트릭스: 토글 ON + 후드꺼짐 = 대기(본래 모드 가동), 토글 ON + 후드켜짐 = 메이크업 강제연동.
- 3.3 Roll-back: 후드 OFF 후 20분 환기 약 후 이전 모드 복귀 — 이는 메이크업 복귀 타이밍이지 토글 OFF가 아님.
- → 토글은 **계속 armed**가 정상. 펌웨어 `Hood_YeunDong_Enable`(토글)은 정상.

## 원인 (순수 대시보드 버그)
- `_state.ComfortCook` = status byte4 bit1 = 펌웨어 `Hood_YeunDong_Enable`(토글 Enable).
- 대시보드가 **토글 판단은 `_state.ComfortCook`(L386), 버튼 강조는 `_state.HoodRunning`(L581, 후드 실제 가동중)** 으로 서로 다른 변수 사용.
- 대기/Roll-back 상태(HoodRunning=0, ComfortCook=1)에서 버튼이 꺼져 보이고, 다시 누르면 `next=!ComfortCook=false` → OFF 전송 → "선택 안됨".

## 변경 파일/내용
- `TestProgram/PCDashBoard/MainWindow.xaml.cs` L581:
  - `SetActive(ComfortCookBtn, _state.HoodRunning);` → `SetActive(ComfortCookBtn, _state.ComfortCook);`
  - 토글 상태(=Enable)를 강조하도록 일치. (모드/풍량 잠금 `subActive`는 사양 3.1대로 HoodRunning 유지 — 대기 중 본래 모드 조작 허용.)

## 빌드 결과
- `dotnet build ErvDashboard.csproj` → 오류 0 (NU1701 경고 9개는 기존 패키지 호환 경고, 무관).

## 미해결 / 후속(선택)
- 사양상 쾌적조리는 운전모드 시나리오(스마트수면/안심회복)와 **독립**인데, 대시보드는 셋을 상호배타 처리(SubMode_Click L401~406, ApplySubModeLocal). 스마트수면/안심회복 켤 때 쾌적조리가 강제 OFF됨 → 사양과 불일치 가능. 필요 시 분리 검토.


The file d:\Project\nuvoton\HECO2\doc\260618_내부댐퍼_모드변경시_미동작_수정.md has been updated successfully. (file state is current in your context — no need to Read it back)

## 후속
- 실장비에서 환기→공청 **반복 전환** 시 매번 댐퍼 이동(간헐성 사라짐) 확인.
- 같은 모드 풍량단수 변경 시 팬 즉시 반응 정상 확인.

---

# 260618 (4) 쾌적조리 재선택 안됨 — 대시보드 버튼 강조 버그

## 증상
- 쾌적조리 동작 마치고 이전 모드 복귀 후 다시 쾌적조리 버튼 누르면 선택(ON)이 안 됨. 눌러도 토글 반응이 안 보임.

## 사양 (개발사양서 9p)
- 쾌적조리 = 운전모드가 아닌 **'후드 연동 UI 토글 스위치'**. 사용자가 켤 때까지 ON 유지.
- 3.1 매트릭스: 토글 ON + 후드꺼짐 = 대기(본래 모드 가동), 토글 ON + 후드켜짐 = 메이크업 강제연동.
- 3.3 Roll-back: 후드 OFF 후 20분 환기 약 후 이전 모드 복귀 — 이는 메이크업 복귀 타이밍이지 토글 OFF가 아님.
- → 토글은 **계속 armed**가 정상. 펌웨어 `Hood_YeunDong_Enable`(토글)은 정상.

## 원인 (순수 대시보드 버그)
- `_state.ComfortCook` = status byte4 bit1 = 펌웨어 `Hood_YeunDong_Enable`(토글 Enable).
- 대시보드가 **토글 판단은 `_state.ComfortCook`(L386), 버튼 강조는 `_state.HoodRunning`(L581, 후드 실제 가동중)** 으로 서로 다른 변수 사용.
- 대기/Roll-back 상태(HoodRunning=0, ComfortCook=1)에서 버튼이 꺼져 보이고, 다시 누르면 `next=!ComfortCook=false` → OFF 전송 → "선택 안됨".

## 변경 파일/내용
- `TestProgram/PCDashBoard/MainWindow.xaml.cs` L581:
  - `SetActive(ComfortCookBtn, _state.HoodRunning);` → `SetActive(ComfortCookBtn, _state.ComfortCook);`
  - 토글 상태(=Enable)를 강조하도록 일치. (모드/풍량 잠금 `subActive`는 사양 3.1대로 HoodRunning 유지 — 대기 중 본래 모드 조작 허용.)

## 빌드 결과
- `dotnet build ErvDashboard.csproj` → 오류 0 (NU1701 경고 9개는 기존 패키지 호환 경고, 무관).

## 미해결 / 후속(선택)
- 사양상 쾌적조리는 운전모드 시나리오(스마트수면/안심회복)와 **독립**인데, 대시보드는 셋을 상호배타 처리(SubMode_Click L401~406, ApplySubModeLocal). 스마트수면/안심회복 켤 때 쾌적조리가 강제 OFF됨 → 사양과 불일치 가능. 필요 시 분리 검토.

# 260618 내부댐퍼 — 운전모드 변경 시 미동작 수정 (1차: 명령경로 즉시 팬설정 제거)

## 증상
- 환기 → 공청 전환 시 **팬은 정상 동작하는데 내부댐퍼(본체 6개)가 안 움직임.**

## 원인 (마스터 `D:\Project\nuvoton\HERV_DL_MH_2nd\Program` 비교로 확정)
- 내부댐퍼 이동 트리거(`Fan_Speed_process`가 모드변경 시 `Damper_Mode()` 호출)는 HECO2/마스터 **동일하게 존재**. 빠진 게 아님.
- 그 트리거는 **"팬이 0까지 감속"되어야 발동**하도록 게이팅됨(공기 흐름 중 댐퍼 미동작 의도):
  `모드변경 → 팬타깃0 → 팬=0 도달 → Damper_Mode 호출(댐퍼 이동) → 정렬(Step_Status==0x3F) → 팬 복원`
- **마스터:** 명령 핸들러가 팬 타깃을 안 건드림 → 팬이 0 도달 → 댐퍼 이동.
- **HECO2:** 명령 핸들러가 모드변경 즉시 `Fan_Speed_Setting()` 직접 호출 → 팬 타깃이 곧바로 운전속도로 올라감 → **팬이 0에 도달 못 함 → `if(Fan!=0) if(wait==5) goto PASS1` 게이트가 `Damper_Mode` 영원히 스킵 → 댐퍼 미동작, 팬만 정상.**
- 마스터엔 없고 HECO2에만 추가된 명령경로 `Fan_Speed_Setting` 호출이 게이트를 막은 것.

## 변경 파일/내용 (1차 = 모드 변경 시 호출만 제거, 풍량단수 변경 호출은 유지)
주석 처리(삭제 아님, 복원 용이). 마커: `[모드변경댐퍼테스트]`
- `Source/HECO2/User/My_Homenet.c`
  - L259 CTRL_POWER(전원ON→환기) `Fan_Speed_Setting` 주석
  - L293 CTRL_RUNMODE(운전모드 변경) `Fan_Speed_Setting` 주석  ← 보고 증상(대시보드 환기→공청)의 직접 경로
  - **L306 CTRL_FAN(풍량단수만 변경)은 유지** (댐퍼 무관, 팬 단수 반응 필요)
- `Source/HECO2/User/My_Hood.c`
  - L192 후드ON(→환기) `Fan_Speed_Setting` 주석
  - L207 후드OFF(모드 복귀) `Fan_Speed_Setting` 주석
  - **L219 후드 단수변경(풍량만)은 유지**

## 핵심 결정
- 모드 변경 후 팬 복원은 `Fan_Speed_process`의 wait==1 호출(MyMotor.c 1018/1056/1090/1124, ③)이 담당 → 팬이 꺼진 채 남지 않음.
- RJ2(`My_RJ2.c`)·분배기(`My_bunbaegi.c`)는 두 소스 동일 → 미수정.
- 이번엔 "동작모드 변경 트리거 복원(①, 디퓨저 시퀀스 명령트리거)"은 보류 — ②(땜빵) 제거만으로 댐퍼가 움직이는지 먼저 검증.

## 빌드 결과
- `cd SOURCE/HECO2 && bash build.sh all` → 성공, 경고/오류 0.
- text 43920 / data 1940 / bss 3388 (HERV.elf). (이전 44008 → 코드 제거로 감소)

## 미해결 / 후속
- 실장비/시뮬레이터에서 **환기→공청 시 내부댐퍼 이동** 확인 필요(특히 고풍량 전환 시 팬 감속 대기시간 체감).
- 검증 후: 필요하면 ①(동작모드 변경 트리거 = MyMotor.c 디퓨저/팬복원 타이밍의 명령 트리거)도 마스터 정렬 검토.
- 풍량단수만 바꿀 때(L306/L219) 팬 즉시 반응 정상인지 확인.

---

# 260618 (2) 스마트수면 모드 — 거실 댐퍼 미닫힘 수정

## 증상
- 스마트수면 동작 중 **거실 댐퍼가 열려 있음**. 사양(개발사양서 8p)상 거실은 항상 CLOSE여야 함.

## 사양 (개발사양서 8p, 스마트수면)
- 환기 수동·풍량 1단 고정.
- 초기상태: **거실 CLOSE, 침실1~3 OPEN**.
- 거실 급기(SA)/배기(RA) 모두 **X(항상 닫힘)** — CO2 무관.
- 침실1~3: CO2 >= 1000 PPM → 해당 침실 OPEN, CO2 <= 800 PPM → CLOSE.

## 원인
- `My_system.c` `Air_Quality_damper_process()` `Ext_Run_Mode == 4` 블록.
- 진입 1회(L905)는 거실(room1)을 올바르게 CLOSE 하지만, **매 틱 CO2 루프가 `Room_Num = 1`부터 돌아(L916) 거실까지 CO2 기준으로 다시 OPEN** 시킴.
- 룸 인덱스: room1=거실, room2~4=침실1~3.

## 변경 파일/내용
- `Source/HECO2/User/My_system.c` (스마트수면 매 틱 처리)
  - 거실(room1) 매 틱 강제 CLOSE + LED off 추가.
  - CO2 히스테리시스 루프 시작을 `Room_Num = 1` → **`Room_Num = 2`** (침실1~3만 개폐).

## 빌드 결과
- `bash build.sh all` → 성공, 경고/오류 0. text 43940 / data 1940 / bss 3388.

## 후속
- 실장비에서 스마트수면 진입 시 거실 닫힘 + 침실 CO2 개폐 확인.

---

# 260618 (3) 내부댐퍼 미동작 — 진짜 원인(타이밍 레이스) 수정

## 추가 진단 (사용자 확인)
- 테스트 경로 = **PC 대시보드**(My_Homenet 경로).
- 팬 거동 = **0까지 안 내려감**, 그리고 **간헐적**(어쩌다 정상 댐퍼 동작).
- → 전형적 **타이밍 레이스**. (1)차 수정으로 CTRL_RUNMODE(293)는 막았지만, 대시보드가 모드변경과 함께 보내는 **CTRL_FAN(306, 유지됨)** 이 모드전환 감속창(팬→0 구간)에 끼어드는 타이밍이면 팬 타깃을 다시 켜서 팬이 0 도달 못 함 → Damper_Mode 게이트 안 열림.
- 끼어드는 타이밍이 아니면 정상 → 간헐성 설명됨.

## 수정 (2차 = 게이트 보호, 단일 지점)
- `Source/HECO2/User/MyMotor.c` `Fan_Speed_process()` PASS1 직후(정상운전 분기, VSP 제외):
  - `Damper_wait_time == 5`(=모드전환 진행중 전용 신호. 4개 모드 진입에서만 set, Step 정렬 전까지 5 유지)일 때
    `Target_Fan1_Speed = 0; Target_Fan2_Speed = 0;` **강제** → 명령경로(CTRL_FAN 등)가 감속창에 끼어들어도 무시하고 팬을 0으로 떨굼 → 댐퍼 이동 보장.
  - 전환 완료(Step_Status==0x3F) 후 wait가 5→1로 감소하며 wait==1에서 Fan_Speed_Setting(③)이 팬 복원.
- 풍량단수만 변경(같은 모드, wait==0)은 force-zero 미적용 → CTRL_FAN(306) 즉시 반응 유지.

## 동작 변화(주의)
- 이제 환기→공청 전환 시 **팬이 잠깐 0까지 멈췄다가**(댐퍼 이동) 다시 도는 게 정상(설계상 공기흐름 중 댐퍼 미동작). 기존 "안 멈추던" 거동에서 바뀜.

## 빌드 결과
- `bash build.sh all` → 성공, 경고/오류 0. text 43948.

## 후속
- 실장비에서 환기→공청 **반복 전환** 시 매번 댐퍼 이동(간헐성 사라짐) 확인.
- 같은 모드 풍량단수 변경 시 팬 즉시 반응 정상 확인.

- 같은 모드 풍량단수 변경 시 팬 즉시 반응 정상 확인.

+# 260618 (4) 쾌적조리 재선택 안됨 — 대시보드 버튼 강조 버그

+- 쾌적조리 동작 마치고 이전 모드 복귀 후 다시 쾌적조리 버튼 누르면 선택(ON)이 안 됨. 눌러도 토글 반응이 안 보임.

+## 사양 (개발사양서 9p)

+- 쾌적조리 = 운전모드가 아닌 **'후드 연동 UI 토글 스위치'**. 사용자가 켤 때까지 ON 유지.

+- 3.1 매트릭스: 토글 ON + 후드꺼짐 = 대기(본래 모드 가동), 토글 ON + 후드켜짐 = 메이크업 강제연동.

+- 3.3 Roll-back: 후드 OFF 후 20분 환기 약 후 이전 모드 복귀 — 이는 메이크업 복귀 타이밍이지 토글 OFF가 아님.

+- → 토글은 **계속 armed**가 정상. 펌웨어 `Hood_YeunDong_Enable`(토글)은 정상.

+## 원인 (순수 대시보드 버그)

+- `_state.ComfortCook` = status byte4 bit1 = 펌웨어 `Hood_YeunDong_Enable`(토글 Enable).

+- 대시보드가 **토글 판단은 `_state.ComfortCook`(L386), 버튼 강조는 `_state.HoodRunning`(L581, 후드 실제 가동중)** 으로 서로 다른 변수 사용.

+- 대기/Roll-back 상태(HoodRunning=0, ComfortCook=1)에서 버튼이 꺼져 보이고, 다시 누르면 `next=!ComfortCook=false` → OFF 전송 → "선택 안됨".

+## 변경 파일/내용

+  - `SetActive(ComfortCookBtn, _state.HoodRunning);` → `SetActive(ComfortCookBtn, _state.ComfortCook);`

+  - 토글 상태(=Enable)를 강조하도록 일치. (모드/풍량 잠금 `subActive`는 사양 3.1대로 HoodRunning 유지 — 대기 중 본래 모드 조작 허용.)

+- `dotnet build ErvDashboard.csproj` → 오류 0 (NU1701 경고 9개는 기존 패키지 호환 경고, 무관).

+## 미해결 / 후속(선택)

+- 사양상 쾌적조리는 운전모드 시나리오(스마트수면/안심회복)와 **독립**인데, 대시보드는 셋을 상호배타 처리(SubMode_Click L401~406, ApplySubModeLocal). 스마트수면/안심회복 켤 때 쾌적조리가 강제 OFF됨 → 사양과 불일치 가능. 필요 시 분리 검토.


쾌적조리 재선택 문제 — **순수 대시보드 버그**로 확정하고 수정했습니다.

## 사양 9페이지 근거
- 쾌적조리 = **"UI 토글(후드 연동 스위치)"** → 사용자가 끌 때까지 **계속 ON 유지**가 정상.
- 3.3 Roll-back(후드 OFF 후 20분 후 이전 모드 복귀)은 **메이크업 복귀 타이밍**일 뿐 토글 OFF가 아님.
- → 펌웨어 `Hood_YeunDong_Enable`(토글)은 사양대로 정상. 문제는 대시보드뿐.

## 수정 (대시보드 1줄)
[MainWindow.xaml.cs:581](TestProgram/PCDashBoard/MainWindow.xaml.cs#L581)
- 토글 판단은 `_state.ComfortCook`(=Enable)인데 버튼 강조만 `_state.HoodRunning`(후드 실제 가동중)으로 따로 놀던 것 → 강조도 `_state.ComfortCook`으로 일치:
```csharp
SetActive(ComfortCookBtn, _state.ComfortCook);  // was: _state.HoodRunning
```
이제 누르면 즉시 하이라이트 ON/OFF가 보이고, 대기/복귀 상태에서도 armed면 켜진 채로 표시 → 다시 누르면 정상 OFF, 토글이 화면과 일치합니다.

## 결과
- `dotnet build ErvDashboard.csproj` → **오류 0** (경고 9개는 기존 NU1701 패키지 경고, 무관)
- 기록: [doc/260618_...md](doc/260618_내부댐퍼_모드변경시_미동작_수정.md) (4)번 항목

## 참고 (선택적 후속)
사양상 쾌적조리는 스마트수면/안심회복과 **독립**인데, 대시보드는 셋을 상호배타로 처리합니다([SubMode_Click L401~406](TestProgram/PCDashBoard/MainWindow.xaml.cs#L401)) — 스마트수면/안심회복을 켜면 쾌적조리가 강제로 꺼집니다. 사양과 어긋날 수 있으니, 원하시면 분리하겠습니다.

펌웨어는 손댈 필요 없습니다. 대시보드 다시 빌드해서 쾌적조리 토글이 정상으로 보이는지 확인해 주세요.


개발사양서 연동매트릭스와 조금 다르게 동작해.
쾌적조리 선택 > 후드 on 후드 동작 > 후드 off > 쾌적조리 버튼 토글, 이전 모드 동작
후드 off 후 쾌적조리 버튼이 토글이 안돼.


대시보드 강조는 고쳤으니, 이제 "후드 OFF 후 토글이 안 먹는" 동작은 펌웨어 쪽일 가능성이 큽니다(OFF를 보내도 status가 `Hood_YeunDong_Enable=1`로 되돌리면 버튼이 안 꺼짐). 후드 연동/Roll-back 로직과 `Hood_YeunDong_Enable` 생애주기를 보겠습니다.


155
156
157
158	uint8_t HREV_Hood_Control = 0;
159	uint16_t Hood_Warming_up_Timer = 0;
160	uint8_t Pre_Hood_Status = 0;
161	uint8_t Tx_Yeundong_Delay = 0;
162
163	/* 후드 단수 -> 환기장치 풍량 추종 매핑(사양 260613 9p 3.2) : 1->1,2->2,3->3,4->4,5->4 */
164	static uint8_t Hood_Step_To_Fan(uint8_t hs)
165	{
166	    if(hs == 0) return 0;
167	    return (hs > 4) ? 4 : hs;
168	}
169
170	uint8_t Hood_process(void)//200ms
171	{
172	    if((Hood_YeunDong_Enable == 0)||(HREV_Hood_Control == 1))return(0);
173
174		if(Hood_Status != Pre_Hood_Status)
175		{
176			if((Pre_Hood_Status == 0)&&(Hood_Status != 0)) // 후드 ON : 메이크업 에어 진입(자동/수동 일시정지)
177			{
178				My_Memory_Run_Mode = Run_Mode;
179				if((Run_Mode == MODE_AUTO)&&(Fan_Mode == 0))My_Memory_Fan_Mode = 1;
180				else My_Memory_Fan_Mode = Fan_Mode;
181
182				Set_Run_Mode = Run_Mode = MODE_VENTILATION;
183				Set_Fan_Mode = Fan_Mode = Hood_Step_To_Fan(Hood_Status); // 후드 단수 추종(사양 260613 9p 3.2)
184
185				if(Set_Reserve_timer_sec != 0)
186				{
187					Set_Reserve_timer_sec = 0;
188					Command_request_type |= (TYPE_RESERVATION);
189				}
190
191				Command_request_type |= (TYPE_MODE|TYPE_FAN_SPEED);
192				/* [모드변경댐퍼테스트] 마스터 정렬 : 모드변경(→환기) 시 명령경로 즉시 팬설정 제거.
193				   팬 복원은 Fan_Speed_process wait==1 에서 수행. */
194				//Fan_Speed_Setting(Run_Mode, Fan_Mode);   /* 즉시 반영(룸컨 echo 대기 없이 — CTRL_FAN 동일) */
195	            Tx_Yeundong_Delay = 30;
196			}
197			else if(Hood_Status == 0) // 후드 OFF : 즉시 원래 모드/풍량 복귀 (메이크업 유지는 후드측 담당, 사양 260613 9p 3.3)
198			{
199				Set_Run_Mode = Run_Mode = My_Memory_Run_Mode;
200				Set_Fan_Mode = Fan_Mode = My_Memory_Fan_Mode;
201
202				if(Set_Reserve_timer_sec != 0)
203				{
204					Set_Reserve_timer_sec = 0;
205					Command_request_type |= (TYPE_RESERVATION);
206				}
207
208				Command_request_type |= (TYPE_MODE|TYPE_FAN_SPEED|TYPE_HOOD_STATE);
209				/* [모드변경댐퍼테스트] 마스터 정렬 : 모드복귀 시 명령경로 즉시 팬설정 제거.
210				   팬 복원은 Fan_Speed_process wait==1 에서 수행. */
211				//if(Run_Mode != MODE_AUTO) Fan_Speed_Setting(Run_Mode, Fan_Mode);   /* 즉시 복귀 반영 */
212				Hood_Yeundong_flag = 0;
213				Hood_Warming_up_Timer = 0;
214				Tx_Yeundong_Delay = 0;
215			}
216	        else // 후드 단수 변경(1~5) : 메이크업 풍량 단수 추종 갱신
217	        {
218	            uint8_t f = Hood_Step_To_Fan(Hood_Status);
219	            if(Set_Fan_Mode != f)
220	            {
221	                Set_Fan_Mode = Fan_Mode = f;
222	                Command_request_type |= (TYPE_FAN_SPEED);
223	                if(Run_Mode != MODE_AUTO) Fan_Speed_Setting(Run_Mode, Fan_Mode);   /* 즉시 반영(룸컨 echo 대기 없이) */
224	                Tx_Yeundong_Delay = 30;   // 룸컨(My_RJ2)이 연동 풍량변경을 수동명령으로 오인하지 않도록 디바운스
225	            }
226	        }
227		}
228
229	    /* 메이크업 진입/단수변경 MODE/FAN 전송 후 연동 플래그 set + 룸컨 통지.
230	       Tx_Yeundong_Delay 는 룸컨(My_RJ2)이 연동 명령을 사용자 수동명령과 구분하는 데 사용(불변).
231	       조리 종료 후 잔여 배출(메이크업 유지)은 후드측이 담당하므로 여기서 롤백 타이머는 두지 않음. */
232	    if(Tx_Yeundong_Delay)
233	    {
234	        Tx_Yeundong_Delay--;
235	        if(Tx_Yeundong_Delay == 0)
236	        {
237	            Hood_Yeundong_flag = 1;
238	            Command_request_type |= TYPE_HOOD_STATE;// add 2022.1.25
239	        }
240	    }
241
242		Pre_Hood_Status = Hood_Status;
243
244	    return(1);

uint8_t HREV_Hood_Control = 0;
uint16_t Hood_Warming_up_Timer = 0;
uint8_t Pre_Hood_Status = 0;
uint8_t Tx_Yeundong_Delay = 0;

/* 후드 단수 -> 환기장치 풍량 추종 매핑(사양 260613 9p 3.2) : 1->1,2->2,3->3,4->4,5->4 */
static uint8_t Hood_Step_To_Fan(uint8_t hs)
{
    if(hs == 0) return 0;
    return (hs > 4) ? 4 : hs;
}

uint8_t Hood_process(void)//200ms
{
    if((Hood_YeunDong_Enable == 0)||(HREV_Hood_Control == 1))return(0);

	if(Hood_Status != Pre_Hood_Status)
	{
		if((Pre_Hood_Status == 0)&&(Hood_Status != 0)) // 후드 ON : 메이크업 에어 진입(자동/수동 일시정지)
		{
			My_Memory_Run_Mode = Run_Mode;
			if((Run_Mode == MODE_AUTO)&&(Fan_Mode == 0))My_Memory_Fan_Mode = 1;
			else My_Memory_Fan_Mode = Fan_Mode;

			Set_Run_Mode = Run_Mode = MODE_VENTILATION;
			Set_Fan_Mode = Fan_Mode = Hood_Step_To_Fan(Hood_Status); // 후드 단수 추종(사양 260613 9p 3.2)

			if(Set_Reserve_timer_sec != 0)
			{
				Set_Reserve_timer_sec = 0;
				Command_request_type |= (TYPE_RESERVATION);
			}

			Command_request_type |= (TYPE_MODE|TYPE_FAN_SPEED);
			/* [모드변경댐퍼테스트] 마스터 정렬 : 모드변경(→환기) 시 명령경로 즉시 팬설정 제거.
			   팬 복원은 Fan_Speed_process wait==1 에서 수행. */
			//Fan_Speed_Setting(Run_Mode, Fan_Mode);   /* 즉시 반영(룸컨 echo 대기 없이 — CTRL_FAN 동일) */
            Tx_Yeundong_Delay = 30;
		}
		else if(Hood_Status == 0) // 후드 OFF : 즉시 원래 모드/풍량 복귀 (메이크업 유지는 후드측 담당, 사양 260613 9p 3.3)
		{
			Set_Run_Mode = Run_Mode = My_Memory_Run_Mode;
			Set_Fan_Mode = Fan_Mode = My_Memory_Fan_Mode;

			if(Set_Reserve_timer_sec != 0)
			{
				Set_Reserve_timer_sec = 0;
				Command_request_type |= (TYPE_RESERVATION);
			}

			Command_request_type |= (TYPE_MODE|TYPE_FAN_SPEED|TYPE_HOOD_STATE);
			/* [모드변경댐퍼테스트] 마스터 정렬 : 모드복귀 시 명령경로 즉시 팬설정 제거.
			   팬 복원은 Fan_Speed_process wait==1 에서 수행. */
			//if(Run_Mode != MODE_AUTO) Fan_Speed_Setting(Run_Mode, Fan_Mode);   /* 즉시 복귀 반영 */
			Hood_Yeundong_flag = 0;
			Hood_Warming_up_Timer = 0;
			Tx_Yeundong_Delay = 0;
		}
        else // 후드 단수 변경(1~5) : 메이크업 풍량 단수 추종 갱신
        {
            uint8_t f = Hood_Step_To_Fan(Hood_Status);
            if(Set_Fan_Mode != f)
            {
                Set_Fan_Mode = Fan_Mode = f;
                Command_request_type |= (TYPE_FAN_SPEED);
                if(Run_Mode != MODE_AUTO) Fan_Speed_Setting(Run_Mode, Fan_Mode);   /* 즉시 반영(룸컨 echo 대기 없이) */
                Tx_Yeundong_Delay = 30;   // 룸컨(My_RJ2)이 연동 풍량변경을 수동명령으로 오인하지 않도록 디바운스
            }
        }
	}

    /* 메이크업 진입/단수변경 MODE/FAN 전송 후 연동 플래그 set + 룸컨 통지.
       Tx_Yeundong_Delay 는 룸컨(My_RJ2)이 연동 명령을 사용자 수동명령과 구분하는 데 사용(불변).
       조리 종료 후 잔여 배출(메이크업 유지)은 후드측이 담당하므로 여기서 롤백 타이머는 두지 않음. */
    if(Tx_Yeundong_Delay)
    {
        Tx_Yeundong_Delay--;
        if(Tx_Yeundong_Delay == 0)
        {
            Hood_Yeundong_flag = 1;
            Command_request_type |= TYPE_HOOD_STATE;// add 2022.1.25
        }
    }

	Pre_Hood_Status = Hood_Status;

    return(1);


`Hood_process()`는 후드 OFF 시 이전 모드로 즉시 복귀시키지만 `Hood_YeunDong_Enable`은 그대로 둡니다(토글 유지 = 사양 맞음). 그러면 대시보드가 OFF를 보낼 때 펌웨어가 0으로 내려야 하는데, 무언가 다시 1로 만들거나 명령 자체가 안 가는지 봐야 합니다. 대시보드의 쾌적조리/후드 명령 인코딩과 Hood 버튼을 확인합니다.


92:        public void SetSubMode(SubModeType type, bool on) => SendFrame(CtrlFrame.SubMode((int)type, on ? 1 : 0));
93:        public void SetHood(bool on)                   => SendFrame(CtrlFrame.Hood(on ? 1 : 0));


444	        void Hood_Click(object sender, RoutedEventArgs e)
445	        {
446	            bool next = !_state.Hood;
447	            if (_demoTimer.IsEnabled) { _state.Hood = next; return; }
448	            if (!CanSend()) return;
449	            _api.SetHood(next);
450	            _state.Hood = next;
451	            Log($"[제어] 연동후드 → {(next ? "ON" : "OFF")}");
452	        }
453
454	        void Reset_Click(object sender, RoutedEventArgs e)
455	        {
456	            bool next = !_state.Reset;
457	            if (_demoTimer.IsEnabled) { _state.Reset = next; return; }
458	            if (!CanSend()) return;
459	            _api.SetReset(next);
460	            _state.Reset = next;
461	            Log($"[제어] ERV 리셋 → {(next ? "ON" : "OFF")}");
462	        }
463
464	        // ================= 각실 제어 =================
465	        // 급기(SA) 댐퍼 토글
466	        void DamperSa_Click(object sender, RoutedEventArgs e) => DamperToggle(sender, type: 0);
467	        // 배기(EA) 댐퍼 토글
468	        void DamperEa_Click(object sender, RoutedEventArgs e) => DamperToggle(sender, type: 1);
469
470	        // type : 0=급기(SA) / 1=배기(EA)
471	        void DamperToggle(object sender, int type)
472	        {
473	            if (sender is not Button b || b.Tag is not int roomId) return;

        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;
            Log($"[제어] 연동후드 → {(next ? "ON" : "OFF")}");
        }

        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;
            Log($"[제어] ERV 리셋 → {(next ? "ON" : "OFF")}");
        }

        // ================= 각실 제어 =================
        // 급기(SA) 댐퍼 토글
        void DamperSa_Click(object sender, RoutedEventArgs e) => DamperToggle(sender, type: 0);
        // 배기(EA) 댐퍼 토글
        void DamperEa_Click(object sender, RoutedEventArgs e) => DamperToggle(sender, type: 1);

        // type : 0=급기(SA) / 1=배기(EA)
        void DamperToggle(object sender, int type)
        {
            if (sender is not Button b || b.Tag is not int roomId) return;


개발사양서 연동매트릭스와 조금 다르게 동작해. 쾌적조리 선택 > 후드 on 후드 동작 > 후드 off > 쾌적조리 버튼 토글, 이전 모드 동작 후드 off 후 쾌적조리 버튼이 토글이 안돼.


중요한 발견: 대시보드에 `Hood_YeunDong_Enable`을 건드리는 컨트롤이 **두 개**입니다 — 쾌적조리 버튼(CTRL_SUBMODE type=2)과 별도 "연동후드" 버튼(Hood_Click → CTRL_HOOD). 그리고 `_state.ComfortCook`(byte4 bit1)과 `_state.Hood`(byte5 bit0)가 **둘 다 같은 펌웨어 변수**입니다. 이 중복이 토글을 꼬이게 할 수 있습니다. 펌웨어에서 `Ext_Run_Mode`/`Hood_YeunDong_Enable`를 다시 1로 되돌리는 경로가 있는지 확인합니다.


Source\HECO2\User\My_bunbaegi.c:984:uint8_t Ext_Run_Mode = 0;
Source\HECO2\User\My_define.h:476:extern uint16_t Ext_Run_Mode_Off_Delay;
Source\HECO2\User\My_RJ2.c:440:                        Ext_Run_Mode = 0;
Source\HECO2\User\My_RJ2.c:441:                        Ext_Run_Mode_Off_Delay = 0;
Source\HECO2\User\My_RJ2.c:442:                        Pre_Ext_Run_Mode = Ext_Run_Mode ;
Source\HECO2\User\My_Homenet.c:127:    p[4] = (uint8_t)(((Ext_Run_Mode == 4) ? 0x01 : 0)    /* 스마트수면 */
Source\HECO2\User\My_Homenet.c:129:                   | ((Ext_Run_Mode == 1) ? 0x04 : 0));  /* 안심회복   */
Source\HECO2\User\My_Homenet.c:317:                if(pl[0] == 1)      Ext_Run_Mode = pl[1] ? 4 : 0;
Source\HECO2\User\My_Homenet.c:319:                else if(pl[0] == 3){ Ext_Run_Mode = pl[1] ? 1 : 0; Ext_Select_Room = 2; }
Source\HECO2\User\My_system.c:680:uint8_t Pre_Ext_Run_Mode = 0;
Source\HECO2\User\My_system.c:681:uint16_t Ext_Run_Mode_Off_Delay = 0;
Source\HECO2\User\My_system.c:802:        Ext_Run_Mode_Off_Delay = 0;
Source\HECO2\User\My_system.c:806:    else if((Ext_Run_Mode == 1)&&(Pre_Ext_Run_Mode != 1)) //안심회복 모드
Source\HECO2\User\My_system.c:842:        Pre_Ext_Run_Mode = Ext_Run_Mode ;
Source\HECO2\User\My_system.c:846:    else if((Ext_Run_Mode == 2)&&(Pre_Ext_Run_Mode != 2)) //쾌적조리 En
Source\HECO2\User\My_system.c:849:        Pre_Ext_Run_Mode = Ext_Run_Mode ;
Source\HECO2\User\My_system.c:852:    else if((Ext_Run_Mode == 3)&&(Pre_Ext_Run_Mode != 3)) //집중청정 모드
Source\HECO2\User\My_system.c:889:        Pre_Ext_Run_Mode = Ext_Run_Mode ;
Source\HECO2\User\My_system.c:893:    else if(Ext_Run_Mode == 4) //스마트 수면 모드 (사양서 8p) : 환기 수동·1단 고정, 실별 CO2 기준 댐퍼 개폐
Source\HECO2\User\My_system.c:911:            Pre_Ext_Run_Mode = Ext_Run_Mode;
Source\HECO2\User\My_system.c:934:        Ext_Run_Mode_Off_Delay = 0;
Source\HECO2\User\My_system.c:938:    else if((Ext_Run_Mode == 0)&&((Pre_Ext_Run_Mode == 1)||(Pre_Ext_Run_Mode == 3)||(Pre_Ext_Run_Mode == 4)))
Source\HECO2\User\My_system.c:947:            Pre_Ext_Run_Mode = Ext_Run_Mode ;
Source\HECO2\User\My_system.c:950:        Ext_Run_Mode_Off_Delay = 0;
Source\HECO2\User\My_system.c:952:    else if((Ext_Run_Mode == 0)&&(Pre_Ext_Run_Mode == 2))// 쾌적조리(후드연동) 토글 OFF -> 연동 없음(사양 260613 9p 3.1)
Source\HECO2\User\My_system.c:964:        Pre_Ext_Run_Mode = Ext_Run_Mode ;
Source\HECO2\User\My_system.c:970:        Pre_Ext_Run_Mode = Ext_Run_Mode ;
Source\HECO2\User\My_system.c:1153:    if(Ext_Run_Mode == 4)


`Ext_Run_Mode`는 1(안심회복)/4(스마트수면)로만 설정되고 **2(쾌적조리)로 설정되는 곳이 아예 없습니다.** 즉 My_system.c의 쾌적조리 처리 블록(846 진입 / 952 OFF, "사양 9p 3.1")은 **죽은 코드(dead code)** 입니다. 실제 쾌적조리는 `Hood_YeunDong_Enable` + My_Hood만으로 돌아갑니다. 이 불일치가 사양 매트릭스와 다르게 동작하는 원인일 수 있습니다. 해당 블록을 읽겠습니다.