820	        Memory_Diffuser_Dmp_Ang_RA[1] = 0;
821
822	        Memory_Diffuser_Dmp_Ang_SA[Ext_Select_Room] = 0;
823	        Memory_Diffuser_Dmp_Ang_RA[Ext_Select_Room] = 110;
824
825	        Diffuser_Air_quality[6] = 5;//ON
826	        Diffuser_Air_quality[5] = 5;//ON
827	        Diffuser_Air_quality[4] = 5;//ON
828	        Diffuser_Air_quality[3] = 5;//ON
829	        Diffuser_Air_quality[2] = 5;//ON
830	        Diffuser_Air_quality[1] = 5;//ON
831
832	        Tmp_Air_Volume = 2;//3;/////////////// 2026.1.8 전경선...
833
834	        Command_request_type |= (TYPE_MODE|TYPE_FAN_SPEED);
835
836	        My_Memory_Run_Mode = Run_Mode;
837	        My_Memory_Fan_Mode = Fan_Mode;
838
839	        Set_Run_Mode = MODE_VENTILATION;
840	        Set_Fan_Mode = 2;//3; /////////////// 2026.1.8 전경선...
841
842	        Pre_Ext_Run_Mode = Ext_Run_Mode ;
843	        Pre_Ext_Select_Room = Ext_Select_Room;
844	        goto PASS_VOLUME;
845	    }
846	    else if((Ext_Run_Mode == 2)&&(Pre_Ext_Run_Mode != 2)) //쾌적조리 En
847	    {
848	        Hood_YeunDong_Enable = 1;
849	        Pre_Ext_Run_Mode = Ext_Run_Mode ;
850	        Pre_Ext_Select_Room = Ext_Select_Room;
851	    }
852	    else if((Ext_Run_Mode == 3)&&(Pre_Ext_Run_Mode != 3)) //집중청정 모드
853	    {
854	        Memory_Diffuser_Dmp_Ang_SA[6] = 0;
855	        Memory_Diffuser_Dmp_Ang_SA[5] = 0;
856	        Memory_Diffuser_Dmp_Ang_SA[4] = 0;
857	        Memory_Diffuser_Dmp_Ang_SA[3] = 0;
858	        Memory_Diffuser_Dmp_Ang_SA[2] = 0;
859	        Memory_Diffuser_Dmp_Ang_SA[1] = 0;
860
861	        Memory_Diffuser_Dmp_Ang_RA[6] = 0;
862	        Memory_Diffuser_Dmp_Ang_RA[5] = 0;
863	        Memory_Diffuser_Dmp_Ang_RA[4] = 0;
864	        Memory_Diffuser_Dmp_Ang_RA[3] = 0;
865	        Memory_Diffuser_Dmp_Ang_RA[2] = 0;
866	        Memory_Diffuser_Dmp_Ang_RA[1] = 0;
867
868	        Memory_Diffuser_Dmp_Ang_SA[Ext_Select_Room] = 110;
869	        Memory_Diffuser_Dmp_Ang_RA[Ext_Select_Room] = 110;
870
871	        Diffuser_Air_quality[6] = 0;//OFF
872	        Diffuser_Air_quality[5] = 0;//OFF
873	        Diffuser_Air_quality[4] = 0;//OFF
874	        Diffuser_Air_quality[3] = 0;//OFF
875	        Diffuser_Air_quality[2] = 0;//OFF
876	        Diffuser_Air_quality[1] = 0;//OFF
877	        Diffuser_Air_quality[Ext_Select_Room] = 5;//ON
878
879	        Tmp_Air_Volume = 2;
880
881	        Command_request_type |= (TYPE_MODE|TYPE_FAN_SPEED);
882
883	        My_Memory_Run_Mode = Run_Mode;
884	        My_Memory_Fan_Mode = Fan_Mode;
885
886	        Set_Run_Mode =  MODE_VENTILATION;
887	        Set_Fan_Mode =  2;
888
889	        Pre_Ext_Run_Mode = Ext_Run_Mode ;
890	        Pre_Ext_Select_Room = Ext_Select_Room;
891	        goto PASS_VOLUME;
892	    }
893	    else if(Ext_Run_Mode == 4) //스마트 수면 모드 (사양서 8p) : 환기 수동·1단 고정, 실별 CO2 기준 댐퍼 개폐
894	    {
895	        if(Pre_Ext_Run_Mode != 4)   /* 진입 1회 : 모드/풍량 + 초기상태(거실 CLOSE, 침실1~3 OPEN) */
896	        {
897	            Command_request_type |= (TYPE_MODE|TYPE_FAN_SPEED);
898
899	            My_Memory_Run_Mode = Run_Mode;
900	            My_Memory_Fan_Mode = Fan_Mode;
901
902	            Set_Run_Mode = MODE_VENTILATION;
903	            Set_Fan_Mode = 1;
904
905	            Memory_Diffuser_Dmp_Ang_SA[1] = 0;   Memory_Diffuser_Dmp_Ang_RA[1] = 0;   /* 거실 CLOSE */
906	            for(Room_Num = 2; Room_Num < 5; Room_Num++)                                /* 침실1~3 OPEN */
907	            { Memory_Diffuser_Dmp_Ang_SA[Room_Num] = 110; Memory_Diffuser_Dmp_Ang_RA[Room_Num] = 110; }
908	            Memory_Diffuser_Dmp_Ang_SA[5] = 0; Memory_Diffuser_Dmp_Ang_RA[5] = 0;
909	            Memory_Diffuser_Dmp_Ang_SA[6] = 0; Memory_Diffuser_Dmp_Ang_RA[6] = 0;
910
911	            Pre_Ext_Run_Mode = Ext_Run_Mode;
912	            Pre_Ext_Select_Room = Ext_Select_Room;
913	        }
914
915	        /* 거실(room1)은 사양상 CO2 무관하게 항상 CLOSE → 매 틱 강제 닫음 */
916	        Memory_Diffuser_Dmp_Ang_SA[1] = 0; Memory_Diffuser_Dmp_Ang_RA[1] = 0;
917	        Diffuser_Air_quality[1] = 0;//OFF
918
919	        /* 매 틱 : 침실1~3(room2~4) 만 CO2 히스테리시스. CO2 >= 1000 OPEN, <= 800 CLOSE, 그 사이(데드존)는 현재 상태 유지 */
920	        for(Room_Num = 2; Room_Num < 5; Room_Num++)
921	        {
922	            if(SEN66_CO2_value[Room_Num] >= 1000)
923	            { Memory_Diffuser_Dmp_Ang_SA[Room_Num] = 110; Memory_Diffuser_Dmp_Ang_RA[Room_Num] = 110; }
924	            else if(SEN66_CO2_value[Room_Num] <= 800)
925	            { Memory_Diffuser_Dmp_Ang_SA[Room_Num] = 0;   Memory_Diffuser_Dmp_Ang_RA[Room_Num] = 0; }
926
927	            if((Memory_Diffuser_Dmp_Ang_SA[Room_Num] != 0)||(Memory_Diffuser_Dmp_Ang_RA[Room_Num] != 0))
928	                Diffuser_Air_quality[Room_Num] = 5;//ON
929	            else
930	                Diffuser_Air_quality[Room_Num] = 0;//OFF
931	        }
932
933	        Tmp_Air_Volume = 1;   /* 1단 고정 */
934	        Ext_Run_Mode_Off_Delay = 0;
935
936	        goto PASS_VOLUME;
937	    }
938	    else if((Ext_Run_Mode == 0)&&((Pre_Ext_Run_Mode == 1)||(Pre_Ext_Run_Mode == 3)||(Pre_Ext_Run_Mode == 4)))
939	    {
940	        if(Power_On == 1)
941	        {
942	            Command_request_type |= (TYPE_MODE|TYPE_FAN_SPEED);
943
944	            Set_Run_Mode = My_Memory_Run_Mode;
945	            Set_Fan_Mode = My_Memory_Fan_Mode;
946
947	            Pre_Ext_Run_Mode = Ext_Run_Mode ;
948	            Pre_Ext_Select_Room = Ext_Select_Room;
949	        }
950	        Ext_Run_Mode_Off_Delay = 0;
951	    }
952	    else if((Ext_Run_Mode == 0)&&(Pre_Ext_Run_Mode == 2))// 쾌적조리(후드연동) 토글 OFF -> 연동 없음(사양 260613 9p 3.1)
953	    {
954	        Hood_YeunDong_Enable = 0;
955	        /* 메이크업 에어 동작중(후드 가동/롤백 유지)이었으면 본래 운전모드/풍량으로 즉시 복귀(롤백 딜레이 없음) */
956	        if((Power_On == 1)&&((Hood_Status != 0)||(Hood_Yeundong_flag != 0)||(Hood_Warming_up_Timer != 0)))
957	        {
958	            Set_Run_Mode = My_Memory_Run_Mode;
959	            Set_Fan_Mode = My_Memory_Fan_Mode;
960	            Command_request_type |= (TYPE_MODE|TYPE_FAN_SPEED);
961	        }
962	        Hood_Yeundong_flag = 0;
963	        Hood_Warming_up_Timer = 0;
964	        Pre_Ext_Run_Mode = Ext_Run_Mode ;
965	        Pre_Ext_Select_Room = Ext_Select_Room;
966	        goto PASS_VOLUME;
967	    }
968	    else if((Ext_Run_Mode != 0)&&(Ext_Run_Mode != 2))
969	    {

        Memory_Diffuser_Dmp_Ang_RA[1] = 0;

        Memory_Diffuser_Dmp_Ang_SA[Ext_Select_Room] = 0;
        Memory_Diffuser_Dmp_Ang_RA[Ext_Select_Room] = 110;

        Diffuser_Air_quality[6] = 5;//ON
        Diffuser_Air_quality[5] = 5;//ON
        Diffuser_Air_quality[4] = 5;//ON
        Diffuser_Air_quality[3] = 5;//ON
        Diffuser_Air_quality[2] = 5;//ON
        Diffuser_Air_quality[1] = 5;//ON

        Tmp_Air_Volume = 2;//3;/////////////// 2026.1.8 전경선...

        Command_request_type |= (TYPE_MODE|TYPE_FAN_SPEED);

        My_Memory_Run_Mode = Run_Mode;
        My_Memory_Fan_Mode = Fan_Mode;

        Set_Run_Mode = MODE_VENTILATION;
        Set_Fan_Mode = 2;//3; /////////////// 2026.1.8 전경선...

        Pre_Ext_Run_Mode = Ext_Run_Mode ;
        Pre_Ext_Select_Room = Ext_Select_Room;
        goto PASS_VOLUME;
    }
    else if((Ext_Run_Mode == 2)&&(Pre_Ext_Run_Mode != 2)) //쾌적조리 En
    {
        Hood_YeunDong_Enable = 1;
        Pre_Ext_Run_Mode = Ext_Run_Mode ;
        Pre_Ext_Select_Room = Ext_Select_Room;
    }
    else if((Ext_Run_Mode == 3)&&(Pre_Ext_Run_Mode != 3)) //집중청정 모드
    {
        Memory_Diffuser_Dmp_Ang_SA[6] = 0;
        Memory_Diffuser_Dmp_Ang_SA[5] = 0;
        Memory_Diffuser_Dmp_Ang_SA[4] = 0;
        Memory_Diffuser_Dmp_Ang_SA[3] = 0;
        Memory_Diffuser_Dmp_Ang_SA[2] = 0;
        Memory_Diffuser_Dmp_Ang_SA[1] = 0;

        Memory_Diffuser_Dmp_Ang_RA[6] = 0;
        Memory_Diffuser_Dmp_Ang_RA[5] = 0;
        Memory_Diffuser_Dmp_Ang_RA[4] = 0;
        Memory_Diffuser_Dmp_Ang_RA[3] = 0;
        Memory_Diffuser_Dmp_Ang_RA[2] = 0;
        Memory_Diffuser_Dmp_Ang_RA[1] = 0;

        Memory_Diffuser_Dmp_Ang_SA[Ext_Select_Room] = 110;
        Memory_Diffuser_Dmp_Ang_RA[Ext_Select_Room] = 110;

        Diffuser_Air_quality[6] = 0;//OFF
        Diffuser_Air_quality[5] = 0;//OFF
        Diffuser_Air_quality[4] = 0;//OFF
        Diffuser_Air_quality[3] = 0;//OFF
        Diffuser_Air_quality[2] = 0;//OFF
        Diffuser_Air_quality[1] = 0;//OFF
        Diffuser_Air_quality[Ext_Select_Room] = 5;//ON

        Tmp_Air_Volume = 2;

        Command_request_type |= (TYPE_MODE|TYPE_FAN_SPEED);

        My_Memory_Run_Mode = Run_Mode;
        My_Memory_Fan_Mode = Fan_Mode;

        Set_Run_Mode =  MODE_VENTILATION;
        Set_Fan_Mode =  2;

        Pre_Ext_Run_Mode = Ext_Run_Mode ;
        Pre_Ext_Select_Room = Ext_Select_Room;
        goto PASS_VOLUME;
    }
    else if(Ext_Run_Mode == 4) //스마트 수면 모드 (사양서 8p) : 환기 수동·1단 고정, 실별 CO2 기준 댐퍼 개폐
    {
        if(Pre_Ext_Run_Mode != 4)   /* 진입 1회 : 모드/풍량 + 초기상태(거실 CLOSE, 침실1~3 OPEN) */
        {
            Command_request_type |= (TYPE_MODE|TYPE_FAN_SPEED);

            My_Memory_Run_Mode = Run_Mode;
            My_Memory_Fan_Mode = Fan_Mode;

            Set_Run_Mode = MODE_VENTILATION;
            Set_Fan_Mode = 1;

            Memory_Diffuser_Dmp_Ang_SA[1] = 0;   Memory_Diffuser_Dmp_Ang_RA[1] = 0;   /* 거실 CLOSE */
            for(Room_Num = 2; Room_Num < 5; Room_Num++)                                /* 침실1~3 OPEN */
            { Memory_Diffuser_Dmp_Ang_SA[Room_Num] = 110; Memory_Diffuser_Dmp_Ang_RA[Room_Num] = 110; }
            Memory_Diffuser_Dmp_Ang_SA[5] = 0; Memory_Diffuser_Dmp_Ang_RA[5] = 0;
            Memory_Diffuser_Dmp_Ang_SA[6] = 0; Memory_Diffuser_Dmp_Ang_RA[6] = 0;

            Pre_Ext_Run_Mode = Ext_Run_Mode;
            Pre_Ext_Select_Room = Ext_Select_Room;
        }

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

        /* 매 틱 : 침실1~3(room2~4) 만 CO2 히스테리시스. CO2 >= 1000 OPEN, <= 800 CLOSE, 그 사이(데드존)는 현재 상태 유지 */
        for(Room_Num = 2; Room_Num < 5; Room_Num++)
        {
            if(SEN66_CO2_value[Room_Num] >= 1000)
            { Memory_Diffuser_Dmp_Ang_SA[Room_Num] = 110; Memory_Diffuser_Dmp_Ang_RA[Room_Num] = 110; }
            else if(SEN66_CO2_value[Room_Num] <= 800)
            { Memory_Diffuser_Dmp_Ang_SA[Room_Num] = 0;   Memory_Diffuser_Dmp_Ang_RA[Room_Num] = 0; }

            if((Memory_Diffuser_Dmp_Ang_SA[Room_Num] != 0)||(Memory_Diffuser_Dmp_Ang_RA[Room_Num] != 0))
                Diffuser_Air_quality[Room_Num] = 5;//ON
            else
                Diffuser_Air_quality[Room_Num] = 0;//OFF
        }

        Tmp_Air_Volume = 1;   /* 1단 고정 */
        Ext_Run_Mode_Off_Delay = 0;

        goto PASS_VOLUME;
    }
    else if((Ext_Run_Mode == 0)&&((Pre_Ext_Run_Mode == 1)||(Pre_Ext_Run_Mode == 3)||(Pre_Ext_Run_Mode == 4)))
    {
        if(Power_On == 1)
        {
            Command_request_type |= (TYPE_MODE|TYPE_FAN_SPEED);

            Set_Run_Mode = My_Memory_Run_Mode;
            Set_Fan_Mode = My_Memory_Fan_Mode;

            Pre_Ext_Run_Mode = Ext_Run_Mode ;
            Pre_Ext_Select_Room = Ext_Select_Room;
        }
        Ext_Run_Mode_Off_Delay = 0;
    }
    else if((Ext_Run_Mode == 0)&&(Pre_Ext_Run_Mode == 2))// 쾌적조리(후드연동) 토글 OFF -> 연동 없음(사양 260613 9p 3.1)
    {
        Hood_YeunDong_Enable = 0;
        /* 메이크업 에어 동작중(후드 가동/롤백 유지)이었으면 본래 운전모드/풍량으로 즉시 복귀(롤백 딜레이 없음) */
        if((Power_On == 1)&&((Hood_Status != 0)||(Hood_Yeundong_flag != 0)||(Hood_Warming_up_Timer != 0)))
        {
            Set_Run_Mode = My_Memory_Run_Mode;
            Set_Fan_Mode = My_Memory_Fan_Mode;
            Command_request_type |= (TYPE_MODE|TYPE_FAN_SPEED);
        }
        Hood_Yeundong_flag = 0;
        Hood_Warming_up_Timer = 0;
        Pre_Ext_Run_Mode = Ext_Run_Mode ;
        Pre_Ext_Select_Room = Ext_Select_Room;
        goto PASS_VOLUME;
    }
    else if((Ext_Run_Mode != 0)&&(Ext_Run_Mode != 2))
    {


쾌적조리 선택 후 후드 동작 후 후드 전원 off했는데 쾌적조리 버튼이 토글이 안돼. 이전 모드로는 운전하고..이거부터 수정하자.


펌웨어 `Ext_Run_Mode==2` 경로는 대시보드가 안 쓰는 dead code가 맞고, 실제 토글은 `My_Homenet.c`의 명령 처리부에서 `Hood_YeunDong_Enable`을 직접 set/clear 합니다. 그 명령 처리부와 대시보드의 SetSubMode 전송부를 확인하겠습니다.


300	                }
301	                /* 모드 전환 시 수동 댐퍼 해제 → 새 모드는 기본(전실 개방)에서 시작 */
302	                for(i = 1; i <= 4; i++) Diffuser_Damper_Manual[i] = 0;
303	            } else result = 1;
304	            break;
305	        case 0x03:  /* CTRL_FAN [speed] */
306	            if(len >= 1)
307	            {
308	                Set_Fan_Mode = Fan_Mode = pl[0];
309	                Command_request_type |= TYPE_FAN_SPEED;   /* 룸컨(RJ2)이 새 풍량 푸시 */
310	                if(Run_Mode != MODE_AUTO) Fan_Speed_Setting(Run_Mode, Fan_Mode);   /* 즉시 반영(자동은 부하점수로 결정) */
311	            }
312	            else result = 1;
313	            break;
314	        case 0x04:  /* CTRL_SUBMODE [type][onoff] 1수면/2조리/3회복 */
315	            if(len >= 2)
316	            {
317	                if(pl[0] == 1)      Ext_Run_Mode = pl[1] ? 4 : 0;
318	                else if(pl[0] == 2) Hood_YeunDong_Enable = pl[1] ? 1 : 0;
319	                else if(pl[0] == 3){ Ext_Run_Mode = pl[1] ? 1 : 0; Ext_Select_Room = 2; }
320	                else result = 1;
321	                /* 부가모드 변경 시 수동 댐퍼 해제 (수동 댐퍼는 환기/공청/바이패스에서만) */
322	                if(result == 0) for(i = 1; i <= 4; i++) Diffuser_Damper_Manual[i] = 0;
323	            } else result = 1;
324	            break;
325	        case 0x05:  /* CTRL_HOOD [onoff] */
326	            if(len >= 1) Hood_YeunDong_Enable = pl[0] ? 1 : 0; else result = 1;
327	            break;
328	        case 0x06:  /* CTRL_HYST_PRESET [preset] */
329	            if(len >= 1 && pl[0] < 3) Hyst_Preset = pl[0]; else result = 1;
330	            break;
331	        case 0x07:  /* CTRL_HYST_VALUE [preset][pm25][pm10][voc][co2] u16 BE */
332	            if(len >= 9 && pl[0] < 3)
333	            {
334	                uint8_t ps = pl[0];
335	                Pm25_Db[ps] = (uint16_t)(((uint16_t)pl[1] << 8) | pl[2]);
336	                Pm10_Db[ps] = (uint16_t)(((uint16_t)pl[3] << 8) | pl[4]);
337	                Voc_Db[ps]  = (uint16_t)(((uint16_t)pl[5] << 8) | pl[6]);
338	                Co2_Db[ps]  = (uint16_t)(((uint16_t)pl[7] << 8) | pl[8]);
339	            } else result = 1;

                }
                /* 모드 전환 시 수동 댐퍼 해제 → 새 모드는 기본(전실 개방)에서 시작 */
                for(i = 1; i <= 4; i++) Diffuser_Damper_Manual[i] = 0;
            } else result = 1;
            break;
        case 0x03:  /* CTRL_FAN [speed] */
            if(len >= 1)
            {
                Set_Fan_Mode = Fan_Mode = pl[0];
                Command_request_type |= TYPE_FAN_SPEED;   /* 룸컨(RJ2)이 새 풍량 푸시 */
                if(Run_Mode != MODE_AUTO) Fan_Speed_Setting(Run_Mode, Fan_Mode);   /* 즉시 반영(자동은 부하점수로 결정) */
            }
            else result = 1;
            break;
        case 0x04:  /* CTRL_SUBMODE [type][onoff] 1수면/2조리/3회복 */
            if(len >= 2)
            {
                if(pl[0] == 1)      Ext_Run_Mode = pl[1] ? 4 : 0;
                else if(pl[0] == 2) Hood_YeunDong_Enable = pl[1] ? 1 : 0;
                else if(pl[0] == 3){ Ext_Run_Mode = pl[1] ? 1 : 0; Ext_Select_Room = 2; }
                else result = 1;
                /* 부가모드 변경 시 수동 댐퍼 해제 (수동 댐퍼는 환기/공청/바이패스에서만) */
                if(result == 0) for(i = 1; i <= 4; i++) Diffuser_Damper_Manual[i] = 0;
            } else result = 1;
            break;
        case 0x05:  /* CTRL_HOOD [onoff] */
            if(len >= 1) Hood_YeunDong_Enable = pl[0] ? 1 : 0; else result = 1;
            break;
        case 0x06:  /* CTRL_HYST_PRESET [preset] */
            if(len >= 1 && pl[0] < 3) Hyst_Preset = pl[0]; else result = 1;
            break;
        case 0x07:  /* CTRL_HYST_VALUE [preset][pm25][pm10][voc][co2] u16 BE */
            if(len >= 9 && pl[0] < 3)
            {
                uint8_t ps = pl[0];
                Pm25_Db[ps] = (uint16_t)(((uint16_t)pl[1] << 8) | pl[2]);
                Pm10_Db[ps] = (uint16_t)(((uint16_t)pl[3] << 8) | pl[4]);
                Voc_Db[ps]  = (uint16_t)(((uint16_t)pl[5] << 8) | pl[6]);
                Co2_Db[ps]  = (uint16_t)(((uint16_t)pl[7] << 8) | pl[8]);
            } else result = 1;


1	namespace ErvProtocol
2	{
3	    // PC↔ERV 고정패킷 프레임 빌더 (PC_ERV_Protocol.md Rev2.0)
4	    // 모든 프레임은 항상 244B 고정 : 0xAA | CMD | DATA[240] | CRC16-MODBUS(LE)
5	    //  - PC→ERV 제어 : DATA 앞쪽에 명령 인자, 나머지 0 패딩
6	    //  - ERV→PC      : STATUS(0x81) DATA=240B 상태, ACK(0x82) DATA[0]=echoCmd DATA[1]=result
7	    public static class CtrlFrame
8	    {
9	        public const int DataLen  = 240;             // DATA 고정 크기(=STATUS 페이로드)
10	        public const int FrameLen = 1 + 1 + DataLen + 2;  // STX+CMD+DATA+CRC = 244
11
12	        public const byte CTRL_POWER       = 0x01;
13	        public const byte CTRL_RUNMODE     = 0x02;
14	        public const byte CTRL_FAN         = 0x03;
15	        public const byte CTRL_SUBMODE     = 0x04;
16	        public const byte CTRL_HOOD        = 0x05;
17	        public const byte CTRL_HYST_PRESET = 0x06;
18	        public const byte CTRL_HYST_VALUE  = 0x07;
19	        public const byte CTRL_DAMPER      = 0x08;
20	        public const byte CTRL_LED         = 0x09;
21	        public const byte REQ_STATUS       = 0x0A;
22	        public const byte CTRL_RESET       = 0x0B;
23	        public const byte CTRL_VSP         = 0x0C;
24	        public const byte CTRL_HYST_THR    = 0x0D;   // 오염단계 임계 설정 [preset][pollutant][L1~L4]
25	        public const byte CTRL_RESERVE     = 0x0E;   // (꺼짐)예약 [hours 0~8] : N시간 후 전원 OFF (0=해제)
26
27	        // cmd + args 로 244B 고정 프레임 생성 (DATA 앞쪽에 args, 나머지 0, CRC 포함)
28	        public static byte[] Build(byte cmd, params byte[] args)
29	        {
30	            int argLen = args?.Length ?? 0;
31	            if (argLen > DataLen) argLen = DataLen;   // 240B 초과분은 잘림(설계상 발생하지 않음)
32
33	            var frame = new byte[FrameLen];           // 전부 0 초기화 = DATA 0 패딩
34	            frame[0] = 0xAA;
35	            frame[1] = cmd;
36	            if (argLen > 0) Array.Copy(args!, 0, frame, 2, argLen);   // DATA[0..] = args
37
38	            ushort crc = Crc16.Modbus(frame, 1, 1 + DataLen);   // CMD + DATA[240] = 241B
39	            frame[2 + DataLen]     = (byte)(crc & 0xFF);        // CRC_L
40	            frame[2 + DataLen + 1] = (byte)(crc >> 8);          // CRC_H
41	            return frame;
42	        }
43
44	        static byte[] U16BE(int v) => new[] { (byte)((v >> 8) & 0xFF), (byte)(v & 0xFF) };
45
46	        public static byte[] Power(int on)        => Build(CTRL_POWER, (byte)(on != 0 ? 1 : 0));
47	        public static byte[] RunModeCmd(int m)     => Build(CTRL_RUNMODE, (byte)m);
48	        public static byte[] Fan(int s)            => Build(CTRL_FAN, (byte)s);
49	        public static byte[] SubMode(int t, int on) => Build(CTRL_SUBMODE, (byte)t, (byte)(on != 0 ? 1 : 0));
50	        public static byte[] Hood(int on)          => Build(CTRL_HOOD, (byte)(on != 0 ? 1 : 0));
51	        public static byte[] Preset(int p)         => Build(CTRL_HYST_PRESET, (byte)p);
52	        // preset: 0 ECO / 1 NORMAL / 2 TURBO
53	        public static byte[] HystValue(int preset, int pm25, int pm10, int voc, int co2)
54	        {
55	            var p = new List<byte> { (byte)preset };
56	            p.AddRange(U16BE(pm25)); p.AddRange(U16BE(pm10)); p.AddRange(U16BE(voc)); p.AddRange(U16BE(co2));
57	            return Build(CTRL_HYST_VALUE, p.ToArray());
58	        }
59	        // type : 0=급기(SA) / 1=배기(EA)
60	        public static byte[] Damper(int room, int type, int on) => Build(CTRL_DAMPER, (byte)room, (byte)type, (byte)(on != 0 ? 1 : 0));
61	        public static byte[] Led(int room, int dim)    => Build(CTRL_LED, (byte)room, (byte)dim);
62	        public static byte[] RequestStatus()            => Build(REQ_STATUS);
63	        public static byte[] Reset(int on)              => Build(CTRL_RESET, (byte)(on != 0 ? 1 : 0));
64	        public static byte[] Reserve(int hours)         => Build(CTRL_RESERVE, (byte)hours);   // 0~8시간, 0=해제
65	        public static byte[] Vsp(int group, int index, int sa, int ea)
66	        {
67	            var p = new List<byte> { (byte)group, (byte)index };
68	            p.AddRange(U16BE(sa)); p.AddRange(U16BE(ea));
69	            return Build(CTRL_VSP, p.ToArray());
70	        }
71	        // 오염단계 임계 : preset(0 ECO/1 NORMAL/2 TURBO), pollutant(0 CO2/1 PM2.5/2 PM10/3 VOC), L1~L4 상한
72	        public static byte[] HystThr(int preset, int pollutant, int l1, int l2, int l3, int l4)
73	        {
74	            var p = new List<byte> { (byte)preset, (byte)pollutant };
75	            p.AddRange(U16BE(l1)); p.AddRange(U16BE(l2)); p.AddRange(U16BE(l3)); p.AddRange(U16BE(l4));
76	            return Build(CTRL_HYST_THR, p.ToArray());
77	        }
78	    }
79	}
80

namespace ErvProtocol
{
    // PC↔ERV 고정패킷 프레임 빌더 (PC_ERV_Protocol.md Rev2.0)
    // 모든 프레임은 항상 244B 고정 : 0xAA | CMD | DATA[240] | CRC16-MODBUS(LE)
    //  - PC→ERV 제어 : DATA 앞쪽에 명령 인자, 나머지 0 패딩
    //  - ERV→PC      : STATUS(0x81) DATA=240B 상태, ACK(0x82) DATA[0]=echoCmd DATA[1]=result
    public static class CtrlFrame
    {
        public const int DataLen  = 240;             // DATA 고정 크기(=STATUS 페이로드)
        public const int FrameLen = 1 + 1 + DataLen + 2;  // STX+CMD+DATA+CRC = 244

        public const byte CTRL_POWER       = 0x01;
        public const byte CTRL_RUNMODE     = 0x02;
        public const byte CTRL_FAN         = 0x03;
        public const byte CTRL_SUBMODE     = 0x04;
        public const byte CTRL_HOOD        = 0x05;
        public const byte CTRL_HYST_PRESET = 0x06;
        public const byte CTRL_HYST_VALUE  = 0x07;
        public const byte CTRL_DAMPER      = 0x08;
        public const byte CTRL_LED         = 0x09;
        public const byte REQ_STATUS       = 0x0A;
        public const byte CTRL_RESET       = 0x0B;
        public const byte CTRL_VSP         = 0x0C;
        public const byte CTRL_HYST_THR    = 0x0D;   // 오염단계 임계 설정 [preset][pollutant][L1~L4]
        public const byte CTRL_RESERVE     = 0x0E;   // (꺼짐)예약 [hours 0~8] : N시간 후 전원 OFF (0=해제)

        // cmd + args 로 244B 고정 프레임 생성 (DATA 앞쪽에 args, 나머지 0, CRC 포함)
        public static byte[] Build(byte cmd, params byte[] args)
        {
            int argLen = args?.Length ?? 0;
            if (argLen > DataLen) argLen = DataLen;   // 240B 초과분은 잘림(설계상 발생하지 않음)

            var frame = new byte[FrameLen];           // 전부 0 초기화 = DATA 0 패딩
            frame[0] = 0xAA;
            frame[1] = cmd;
            if (argLen > 0) Array.Copy(args!, 0, frame, 2, argLen);   // DATA[0..] = args

            ushort crc = Crc16.Modbus(frame, 1, 1 + DataLen);   // CMD + DATA[240] = 241B
            frame[2 + DataLen]     = (byte)(crc & 0xFF);        // CRC_L
            frame[2 + DataLen + 1] = (byte)(crc >> 8);          // CRC_H
            return frame;
        }

        static byte[] U16BE(int v) => new[] { (byte)((v >> 8) & 0xFF), (byte)(v & 0xFF) };

        public static byte[] Power(int on)        => Build(CTRL_POWER, (byte)(on != 0 ? 1 : 0));
        public static byte[] RunModeCmd(int m)     => Build(CTRL_RUNMODE, (byte)m);
        public static byte[] Fan(int s)            => Build(CTRL_FAN, (byte)s);
        public static byte[] SubMode(int t, int on) => Build(CTRL_SUBMODE, (byte)t, (byte)(on != 0 ? 1 : 0));
        public static byte[] Hood(int on)          => Build(CTRL_HOOD, (byte)(on != 0 ? 1 : 0));
        public static byte[] Preset(int p)         => Build(CTRL_HYST_PRESET, (byte)p);
        // preset: 0 ECO / 1 NORMAL / 2 TURBO
        public static byte[] HystValue(int preset, int pm25, int pm10, int voc, int co2)
        {
            var p = new List<byte> { (byte)preset };
            p.AddRange(U16BE(pm25)); p.AddRange(U16BE(pm10)); p.AddRange(U16BE(voc)); p.AddRange(U16BE(co2));
            return Build(CTRL_HYST_VALUE, p.ToArray());
        }
        // type : 0=급기(SA) / 1=배기(EA)
        public static byte[] Damper(int room, int type, int on) => Build(CTRL_DAMPER, (byte)room, (byte)type, (byte)(on != 0 ? 1 : 0));
        public static byte[] Led(int room, int dim)    => Build(CTRL_LED, (byte)room, (byte)dim);
        public static byte[] RequestStatus()            => Build(REQ_STATUS);
        public static byte[] Reset(int on)              => Build(CTRL_RESET, (byte)(on != 0 ? 1 : 0));
        public static byte[] Reserve(int hours)         => Build(CTRL_RESERVE, (byte)hours);   // 0~8시간, 0=해제
        public static byte[] Vsp(int group, int index, int sa, int ea)
        {
            var p = new List<byte> { (byte)group, (byte)index };
            p.AddRange(U16BE(sa)); p.AddRange(U16BE(ea));
            return Build(CTRL_VSP, p.ToArray());
        }
        // 오염단계 임계 : preset(0 ECO/1 NORMAL/2 TURBO), pollutant(0 CO2/1 PM2.5/2 PM10/3 VOC), L1~L4 상한
        public static byte[] HystThr(int preset, int pollutant, int l1, int l2, int l3, int l4)
        {
            var p = new List<byte> { (byte)preset, (byte)pollutant };
            p.AddRange(U16BE(l1)); p.AddRange(U16BE(l2)); p.AddRange(U16BE(l3)); p.AddRange(U16BE(l4));
            return Build(CTRL_HYST_THR, p.ToArray());
        }
    }
}


명령 경로는 정상입니다(`SubMode(2,0)` → `Hood_YeunDong_Enable=0`). 이제 대시보드 버튼 갱신부(560~600)와 `CanSend`, 그리고 펌웨어가 `Hood_YeunDong_Enable`을 다시 1로 되살리는 곳이 있는지 확인하겠습니다.


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	            // 쾌적조리는 사양 9p 3.1 의 'UI 토글(연동 스위치)' — 후드 가동중이 아니라 토글 ON/OFF(=Hood_YeunDong_Enable)를 강조.
582	            // (HoodRunning 으로 강조하면 대기/Roll-back 상태에서 버튼이 꺼져 보여 재선택 시 토글이 반대로 먹던 문제 수정)
583	            SetActive(ComfortCookBtn,   _state.ComfortCook);
584	            SetActive(ReliefRecoverBtn, _state.ReliefRecover);
585	            //   (활성 모드 버튼은 OFF 토글 가능해야 하므로 자기 자신은 유지)
586	            SmartSleepBtn.IsEnabled    = !subActive || _state.SmartSleep;
587	            // 쾌적조리는 사양 9p 3.1 의 '독립 토글(연동 스위치)' — 후드 연결/가동·다른 시나리오와 무관하게 항상 토글 가능.
588	            ComfortCookBtn.IsEnabled   = true;
589	            ReliefRecoverBtn.IsEnabled = !subActive || _state.ReliefRecover;
590	            // 스마트수면 시간설정 버튼 : 스마트수면 ON 일 때만 활성
591	            SmartSleepSetBtn.IsEnabled = _state.SmartSleep;
592	            foreach (var mb in _modeButtons) mb.IsEnabled = !subActive;
593
594	            // 자동모드 프리셋(ECO/NORMAL/TURBO) : 자동모드에서만 활성, 활성 프리셋 강조.
595	            // 선택 프리셋이 곧 공기질 판정 임계(=히스테리시스 임계). 기본값은 표준(NORMAL, 상태 초기값).
596	            // (HystWindow 팝업의 프리셋 버튼과 _state.HystPreset 으로 동기화)
597	            bool presetEnabled = _state.IsAuto && !subActive;
598	            var presets = new[] { HystPreset.Eco, HystPreset.Normal, HystPreset.Turbo };
599	            for (int i = 0; i < _presetButtons.Count; i++)

            {
                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);
            // 쾌적조리는 사양 9p 3.1 의 'UI 토글(연동 스위치)' — 후드 가동중이 아니라 토글 ON/OFF(=Hood_YeunDong_Enable)를 강조.
            // (HoodRunning 으로 강조하면 대기/Roll-back 상태에서 버튼이 꺼져 보여 재선택 시 토글이 반대로 먹던 문제 수정)
            SetActive(ComfortCookBtn,   _state.ComfortCook);
            SetActive(ReliefRecoverBtn, _state.ReliefRecover);
            //   (활성 모드 버튼은 OFF 토글 가능해야 하므로 자기 자신은 유지)
            SmartSleepBtn.IsEnabled    = !subActive || _state.SmartSleep;
            // 쾌적조리는 사양 9p 3.1 의 '독립 토글(연동 스위치)' — 후드 연결/가동·다른 시나리오와 무관하게 항상 토글 가능.
            ComfortCookBtn.IsEnabled   = true;
            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++)


288		}
289
290
291		Tx_roomcon232_buffer[4] = Auto_Mode;
292
293		Tx_roomcon232_buffer[5] = 0;
294
295
296		if((Hood_Yeundong_flag == 1) && (Hood_YeunDong_Enable == 1))//2023.02.14 Hood Enable Add
297		{
298			Tx_roomcon232_buffer[6] = 0x81; //ON
299		}
300		else
301		{
302			Tx_roomcon232_buffer[6] = 0x80; //OFF
303		}
304
305
306		Tx_roomcon232_buffer[7] = 0;
307		Tx_roomcon232_buffer[8] = 0;

	}

	Tx_roomcon232_buffer[4] = Auto_Mode;

	Tx_roomcon232_buffer[5] = 0;

	if((Hood_Yeundong_flag == 1) && (Hood_YeunDong_Enable == 1))//2023.02.14 Hood Enable Add
	{
		Tx_roomcon232_buffer[6] = 0x81; //ON
	}
	else
	{
		Tx_roomcon232_buffer[6] = 0x80; //OFF
	}

	Tx_roomcon232_buffer[7] = 0;
	Tx_roomcon232_buffer[8] = 0;


1108	    else
1109	    {
1110	        Focus_Mode = 0;
1111	        Focus_Mode_RunTime = 0;
1112
1113	        /* 쾌적조리 메이크업 에어(사양 260613 9p) : 후드 가동중(Hood_Status!=0 / 연동플래그)
1114	           전실 급기(SA) 100% 개방, 배기(RA) 닫힘. 풍량은 Hood_process() 가 후드 단수를
1115	           추종(1->1,2->2,3->3,4->4,5->4)하여 Set_Fan_Mode 로 반영.
1116	           조리 종료 후 잔여 배출(메이크업 유지)은 후드측이 담당 → 후드 OFF 시 즉시 복귀. */
1117	        if((Hood_YeunDong_Enable == 1)&&((Hood_Status != 0)||(Hood_Yeundong_flag == 1)))
1118	        {
1119	            Memory_Diffuser_Dmp_Ang_SA[1] = 110;
1120	            Memory_Diffuser_Dmp_Ang_RA[1] = 0;
1121
1122	            Memory_Diffuser_Dmp_Ang_SA[2] = 110;
1123	            Memory_Diffuser_Dmp_Ang_RA[2] = 0;
1124
1125	            Memory_Diffuser_Dmp_Ang_SA[3] = 110;
1126	            Memory_Diffuser_Dmp_Ang_RA[3] = 0;
1127

    else
    {
        Focus_Mode = 0;
        Focus_Mode_RunTime = 0;

        /* 쾌적조리 메이크업 에어(사양 260613 9p) : 후드 가동중(Hood_Status!=0 / 연동플래그)
           전실 급기(SA) 100% 개방, 배기(RA) 닫힘. 풍량은 Hood_process() 가 후드 단수를
           추종(1->1,2->2,3->3,4->4,5->4)하여 Set_Fan_Mode 로 반영.
           조리 종료 후 잔여 배출(메이크업 유지)은 후드측이 담당 → 후드 OFF 시 즉시 복귀. */
        if((Hood_YeunDong_Enable == 1)&&((Hood_Status != 0)||(Hood_Yeundong_flag == 1)))
        {
            Memory_Diffuser_Dmp_Ang_SA[1] = 110;
            Memory_Diffuser_Dmp_Ang_RA[1] = 0;

            Memory_Diffuser_Dmp_Ang_SA[2] = 110;
            Memory_Diffuser_Dmp_Ang_RA[2] = 0;

            Memory_Diffuser_Dmp_Ang_SA[3] = 110;
            Memory_Diffuser_Dmp_Ang_RA[3] = 0;


핵심을 짚었습니다. 정적 분석 결과 명령 경로(`SubMode(2,0)` → `Hood_YeunDong_Enable=0`)와 펌웨어 자동 재-arming 부재는 정상이고, 대시보드 버튼도 항상 활성(`IsEnabled=true`)이라 **기계적으로는** 토글이 동작해야 합니다.

그런데 한 가지 중요한 충돌이 있습니다. **이전 세션에 기록된 사양(개발사양서 260613 9p 3.3)**에 따르면:

> "후드 OFF 후 20분 환기 약 후 이전 모드 복귀 — 이는 메이크업 복귀 타이밍이지 **토글 OFF가 아님.** → 토글은 계속 armed가 정상."

즉 **사양상 후드 전원을 꺼도 쾌적조리 토글은 켜진(armed) 상태로 유지**되는 게 맞게 설계돼 있습니다. 지금 보고하신 증상이 "이 사양이 잘못됐다(꺼져야 한다)"는 건지, "사양대로 armed는 맞는데 버튼이 안 눌린다"는 건지에 따라 **정반대 방향의 수정**이 됩니다. 그래서 먼저 확인이 필요합니다.


AskUserQuestion

Question: 후드 전원을 OFF 했을 때 쾌적조리 버튼이 어떻게 동작하길 원하세요?

Choices:
후드 OFF 시 자동 해제
후드 전원을 끄면 쾌적조리 토글도 자동으로 꺼짐(버튼 OFF). 다음 조리 때 다시 켜는 방식. ※ 현재 문서화된 사양(armed 유지)과 반대 — 펌웨어가 후드 OFF 감지 시 Hood_YeunDong_Enable=0 처리하도록 변경.
armed 유지 + 수동 토글 보장
후드 OFF 후에도 버튼은 켜진(armed) 상태 유지(사양대로). 다만 버튼을 누르면 확실히 OFF로 토글되도록 보장. 현재 '안 꺼지는' 동작을 버그로 보고 수정.

Question: 지금 그 버튼을 눌렀을 때 실제로 어떻게 보이나요?

Choices:
눌러도 아무 반응 없음
버튼을 눌러도 강조/해제가 전혀 안 바뀜(클릭이 안 먹는 느낌).
잠깐 꺼졌다 다시 켜짐
누르면 잠깐 꺼졌다가 곧바로 다시 켜진 상태로 돌아옴(되살아남).
계속 켜진 채 안 꺼짐
눌러도 계속 켜진(강조) 상태로만 남아있음.


Your questions have been answered: "후드 전원을 OFF 했을 때 쾌적조리 버튼이 어떻게 동작하길 원하세요?"="후드 OFF 시 자동 해제", "지금 그 버튼을 눌렀을 때 실제로 어떻게 보이나요?"="후드 off 후 쾌적조리 버튼을 누르면 해제가 돼. 이걸 자동으로 해제해 달라는 얘기야.". You can now continue with these answers in mind.

Question: 후드 전원을 OFF 했을 때 쾌적조리 버튼이 어떻게 동작하길 원하세요?

Choices:
후드 OFF 시 자동 해제
후드 전원을 끄면 쾌적조리 토글도 자동으로 꺼짐(버튼 OFF). 다음 조리 때 다시 켜는 방식. ※ 현재 문서화된 사양(armed 유지)과 반대 — 펌웨어가 후드 OFF 감지 시 Hood_YeunDong_Enable=0 처리하도록 변경.
armed 유지 + 수동 토글 보장
후드 OFF 후에도 버튼은 켜진(armed) 상태 유지(사양대로). 다만 버튼을 누르면 확실히 OFF로 토글되도록 보장. 현재 '안 꺼지는' 동작을 버그로 보고 수정.

Question: 지금 그 버튼을 눌렀을 때 실제로 어떻게 보이나요?

Choices:
눌러도 아무 반응 없음
버튼을 눌러도 강조/해제가 전혀 안 바뀜(클릭이 안 먹는 느낌).
잠깐 꺼졌다 다시 켜짐
누르면 잠깐 꺼졌다가 곧바로 다시 켜진 상태로 돌아옴(되살아남).
계속 켜진 채 안 꺼짐
눌러도 계속 켜진(강조) 상태로만 남아있음.

후드 OFF 시 자동 해제

후드 off 후 쾌적조리 버튼을 누르면 해제가 돼. 이걸 자동으로 해제해 달라는 얘기야.


명확해졌습니다. 원하시는 건 **후드 전원 OFF 시 쾌적조리 토글이 자동 해제**(버튼이 저절로 꺼짐)되는 것이군요. 지금은 수동으로 누르면 꺼지지만, 그걸 자동으로요.

이건 문서화된 사양(armed 유지)과는 반대지만, 사장님 결정대로 진행하겠습니다. 구현 위치(펌웨어 vs 대시보드)와 타이밍(후드 off 즉시 vs 롤백 후)을 정확히 잡기 위해 후드 연동/롤백 로직을 먼저 확인하겠습니다.


1
2
3	#include <stdio.h>
4	#include <stdlib.h>
5	#include <string.h>
6	#include <stdint.h>
7	#include "Nano100Series.h"
8	#include "adc.h"
9	#include "gpio.h"
10	#include "pwm.h"
11	#include "timer.h"
12	#include "uart.h"
13	#include "sys.h"
14	#include "clk.h"
15
16	#include "My_define.h"
17
18
19	////////////////////////////////////////////////////////////////////////////////
20	//////////////////////// HOOD 485 //////////////////////////////////////////////
21	extern uint16_t Hood_polling_timer;
22	extern uint8_t rx_hood_485_TimeOut;
23	uint8_t Tx_Hood_Buff[20], Rx_Hood_Buff[20];
24	uint8_t Yeungong_Status = 0;
25	uint8_t Hood_Power_On = 0;
26	uint8_t Hood_Fan_Mode = 0;
27	uint8_t Hood_Control = 0;
28	uint8_t Hood_Rx_Complete = 0;
29	uint8_t Hood_Status = 0;
30
31	uint8_t CheckSum_Creator(uint8_t *buf, uint8_t len)
32	{
33		uint8_t i, rt;
34
35		rt = 0;
36
37		for(i=0; i<len; i++)
38		{
39			rt ^= buf[i];
40		}
41
42		return(rt);
43	}
44
45
46	uint8_t Rx_hood_Pos = 0;
47	/* 후드 응답 수신 파서 — Rev1.3 9바이트 : AA 11 ID FANSTATUS LIGHT 00 연동CMD ERROR CS(XOR byte0~7)
48	   (구버전 13바이트는 My_Hood_old.c 보관) */
49	void rx_hood_check(uint8_t data)
50	{
51		if(rx_hood_485_TimeOut == 0)Rx_hood_Pos = 0;
52
53		switch(Rx_hood_Pos)
54		{
55			case 0:
56				if(data != 0xAA)break;
57				Rx_Hood_Buff[Rx_hood_Pos++] = data;
58				break;
59			case 1:
60				if(data != 0x11)
61	            {
62	                Rx_hood_Pos = 0;
63	            }
64	            Rx_Hood_Buff[Rx_hood_Pos++] = data;
65				break;
66	        case 2:
67			case 3:
68			case 4:
69			case 5:
70			case 6:
71			case 7:
72	            Rx_Hood_Buff[Rx_hood_Pos++] = data;
73				break;
74			case 8:
75				Rx_Hood_Buff[Rx_hood_Pos++] = data;
76				if(data == CheckSum_Creator(Rx_Hood_Buff, 8))   /* CS = XOR(byte0~7) */
77				{
78					Hood_Rx_Complete = 1;
79				}
80				Rx_hood_Pos = 0;
81				break;
82			default:
83				Rx_hood_Pos = 0;
84				break;
85		}
86	}
87	//-------------------------------------
88	void UART0_HANDLE()
89	{
90	    uint8_t u8InChar=0xFF;
91	    uint32_t u32IntSts= UART0->ISR;
92
93	    if(u32IntSts & UART_ISR_RDA_IS_Msk)
94	    {
95	       u8InChar = UART_READ(UART0);           /* Rx trigger level is 1 byte*/
96
97			rx_hood_check(u8InChar);
98			rx_hood_485_TimeOut = 10;
99	    }
100
101	    if(u32IntSts & UART_ISR_THRE_IS_Msk)
102	    {
103	;
104	    }
105	}
106
107
108	void UART0_IRQHandler(void)
109	{
110	    UART0_HANDLE();
111	}
112	//----------------------------------
113
114	void Hood_Tx_packet(void)
115	{
116	    /* 후드 Rev1.3 마스터 프레임(9바이트) : AA 21 ID MODE FAN 연동EN 연동운전중 ERROR CS(XOR byte0~7)
117	       구버전 13B는 ERV→후드 제어필드(Power/Fan)를 포함했으나 Rev1.3 스펙엔 없음(ERV는 후드 단수 추종). */
118	    Tx_Hood_Buff[0] = 0xAA;
119	    Tx_Hood_Buff[1] = 0x21;
120	    Tx_Hood_Buff[2] = 0x01;                  /* ID */
121	    Tx_Hood_Buff[3] = Run_Mode;              /* MODE */
122	    Tx_Hood_Buff[4] = Fan_Mode;              /* FAN */
123	    Tx_Hood_Buff[5] = Hood_YeunDong_Enable;  /* 연동 Enable */
124	    Tx_Hood_Buff[6] = Yeungong_Status;       /* 연동 운전중 */
125	    Tx_Hood_Buff[7] = 0x00;                  /* ERROR */
126	    Tx_Hood_Buff[8] = CheckSum_Creator(Tx_Hood_Buff, 8);
127
128	    HOOD_485_DIR = 1; // DIR
129	    UART_Write(UART0,Tx_Hood_Buff, 9);
130	    while ( !(UART0->FSR & UART_FSR_TX_EMPTY_F_Msk) );
131	    while ( !(UART0->FSR & UART_FSR_TE_F_Msk) );
132	    HOOD_485_DIR = 0; // DIR;
133	}
134
135	void Hood_RS485_process(void)
136	{
137	    if(Hood_Rx_Complete == 1)
138	    {
139	        Hood_Rx_Complete = 0;
140	        Hood_Conn_Timeout = 1500;   /* 유효 응답 수신 -> 통신연결 생존(폴 500ms 기준 ~3회 누락 허용) */
141	        Hood_Status = Rx_Hood_Buff[3];
142	//        if((Hood_Status == 1)&&(Hood_Yeundong_flag == 0))Command_request_type |= TYPE_HOOD_STATE;// add 2022.1.25
143	//        if((Hood_Status == 0)&&(Hood_Yeundong_flag == 1))Command_request_type |= TYPE_HOOD_STATE;// add 2022.1.25
144	//        Hood_Yeundong_flag = Hood_Status;
145	    }
146	    else
147	    {
148	        if(Hood_polling_timer == 0)
149	        {
150	            Hood_polling_timer = 500;
151	            Hood_Tx_packet();
152	        }
153	    }
154	}
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);
245	}
246

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include "Nano100Series.h"
#include "adc.h"
#include "gpio.h"
#include "pwm.h"
#include "timer.h"
#include "uart.h"
#include "sys.h"
#include "clk.h"

#include "My_define.h"

////////////////////////////////////////////////////////////////////////////////
//////////////////////// HOOD 485 //////////////////////////////////////////////
extern uint16_t Hood_polling_timer;
extern uint8_t rx_hood_485_TimeOut;
uint8_t Tx_Hood_Buff[20], Rx_Hood_Buff[20];
uint8_t Yeungong_Status = 0;
uint8_t Hood_Power_On = 0;
uint8_t Hood_Fan_Mode = 0;
uint8_t Hood_Control = 0;
uint8_t Hood_Rx_Complete = 0;
uint8_t Hood_Status = 0;

uint8_t CheckSum_Creator(uint8_t *buf, uint8_t len)
{
	uint8_t i, rt;

	rt = 0;

	for(i=0; i<len; i++)
	{
		rt ^= buf[i];
	}

	return(rt);
}

uint8_t Rx_hood_Pos = 0;
/* 후드 응답 수신 파서 — Rev1.3 9바이트 : AA 11 ID FANSTATUS LIGHT 00 연동CMD ERROR CS(XOR byte0~7)
   (구버전 13바이트는 My_Hood_old.c 보관) */
void rx_hood_check(uint8_t data)
{
	if(rx_hood_485_TimeOut == 0)Rx_hood_Pos = 0;

	switch(Rx_hood_Pos)
	{
		case 0:
			if(data != 0xAA)break;
			Rx_Hood_Buff[Rx_hood_Pos++] = data;
			break;
		case 1:
			if(data != 0x11)
            {
                Rx_hood_Pos = 0;
            }
            Rx_Hood_Buff[Rx_hood_Pos++] = data;
			break;
        case 2:
		case 3:
		case 4:
		case 5:
		case 6:
		case 7:
            Rx_Hood_Buff[Rx_hood_Pos++] = data;
			break;
		case 8:
			Rx_Hood_Buff[Rx_hood_Pos++] = data;
			if(data == CheckSum_Creator(Rx_Hood_Buff, 8))   /* CS = XOR(byte0~7) */
			{
				Hood_Rx_Complete = 1;
			}
			Rx_hood_Pos = 0;
			break;
		default:
			Rx_hood_Pos = 0;
			break;
	}
}
//-------------------------------------
void UART0_HANDLE()
{
    uint8_t u8InChar=0xFF;
    uint32_t u32IntSts= UART0->ISR;

    if(u32IntSts & UART_ISR_RDA_IS_Msk)
    {
       u8InChar = UART_READ(UART0);           /* Rx trigger level is 1 byte*/

		rx_hood_check(u8InChar);
		rx_hood_485_TimeOut = 10;
    }

    if(u32IntSts & UART_ISR_THRE_IS_Msk)
    {
;
    }
}

void UART0_IRQHandler(void)
{
    UART0_HANDLE();
}
//----------------------------------

void Hood_Tx_packet(void)
{
    /* 후드 Rev1.3 마스터 프레임(9바이트) : AA 21 ID MODE FAN 연동EN 연동운전중 ERROR CS(XOR byte0~7)
       구버전 13B는 ERV→후드 제어필드(Power/Fan)를 포함했으나 Rev1.3 스펙엔 없음(ERV는 후드 단수 추종). */
    Tx_Hood_Buff[0] = 0xAA;
    Tx_Hood_Buff[1] = 0x21;
    Tx_Hood_Buff[2] = 0x01;                  /* ID */
    Tx_Hood_Buff[3] = Run_Mode;              /* MODE */
    Tx_Hood_Buff[4] = Fan_Mode;              /* FAN */
    Tx_Hood_Buff[5] = Hood_YeunDong_Enable;  /* 연동 Enable */
    Tx_Hood_Buff[6] = Yeungong_Status;       /* 연동 운전중 */
    Tx_Hood_Buff[7] = 0x00;                  /* ERROR */
    Tx_Hood_Buff[8] = CheckSum_Creator(Tx_Hood_Buff, 8);

    HOOD_485_DIR = 1; // DIR
    UART_Write(UART0,Tx_Hood_Buff, 9);
    while ( !(UART0->FSR & UART_FSR_TX_EMPTY_F_Msk) );
    while ( !(UART0->FSR & UART_FSR_TE_F_Msk) );
    HOOD_485_DIR = 0; // DIR;
}

void Hood_RS485_process(void)
{
    if(Hood_Rx_Complete == 1)
    {
        Hood_Rx_Complete = 0;
        Hood_Conn_Timeout = 1500;   /* 유효 응답 수신 -> 통신연결 생존(폴 500ms 기준 ~3회 누락 허용) */
        Hood_Status = Rx_Hood_Buff[3];
//        if((Hood_Status == 1)&&(Hood_Yeundong_flag == 0))Command_request_type |= TYPE_HOOD_STATE;// add 2022.1.25
//        if((Hood_Status == 0)&&(Hood_Yeundong_flag == 1))Command_request_type |= TYPE_HOOD_STATE;// add 2022.1.25
//        Hood_Yeundong_flag = Hood_Status;
    }
    else
    {
        if(Hood_polling_timer == 0)
        {
            Hood_polling_timer = 500;
            Hood_Tx_packet();
        }
    }
}

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()`(200ms)는 `Hood_YeunDong_Enable==1`일 때만 동작.
- **후드 OFF 전이**(`Hood_Status==0`, line 197~215)에서 이미 이전 모드/풍량 복귀 + `Hood_Yeundong_flag=0` 처리 중 → "이전 모드로 운전"이 여기서 발생.
- `Hood_YeunDong_Enable`은 STATUS byte4 bit1(SubMode 쾌적조리) **및** byte5 bit0(HoodEnable)로 보고 → 대시보드 `_state.ComfortCook` 그대로.

따라서 **이 후드 OFF 전이 지점에서 `Hood_YeunDong_Enable=0` 한 줄만 추가**하면, 펌웨어가 토글을 자동 해제하고 STATUS로 대시보드 버튼이 저절로 꺼집니다. 대시보드 수정은 불필요합니다. 또한 이 시점엔 `Hood_Status=0`·`Hood_Yeundong_flag=0`이라 댐퍼/룸컨 연동(`My_system.c:1117`, `My_RJ2.c:296`)도 이미 OFF 조건이라 부작용이 없습니다.

수정하겠습니다.


Edit

			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;

			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;
			/* [쾌적조리자동해제] 후드 전원 OFF(단수 0) → 쾌적조리 토글 자동 해제.
			   사장님 요청(2026-06-18) : 후드 OFF 후 대시보드 쾌적조리 버튼이 저절로 꺼지게.
			   Enable=0 이면 STATUS byte4 bit1/byte5 bit0 모두 0 → 대시보드 _state.ComfortCook 자동 해제.
			   (기존 사양 9p 3.3 'armed 유지'와 반대 — 본 요청으로 변경) */
			Hood_YeunDong_Enable = 0;


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

			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;
			/* [쾌적조리자동해제] 후드 전원 OFF(단수 0) → 쾌적조리 토글 자동 해제.
			   사장님 요청(2026-06-18) : 후드 OFF 후 대시보드 쾌적조리 버튼이 저절로 꺼지게.
			   Enable=0 이면 STATUS byte4 bit1/byte5 bit0 모두 0 → 대시보드 _state.ComfortCook 자동 해제.
			   (기존 사양 9p 3.3 'armed 유지'와 반대 — 본 요청으로 변경) */
			Hood_YeunDong_Enable = 0;

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include "Nano100Series.h"
#include "adc.h"
#include "gpio.h"
#include "pwm.h"
#include "timer.h"
#include "uart.h"
#include "sys.h"
#include "clk.h"

#include "My_define.h"

////////////////////////////////////////////////////////////////////////////////
//////////////////////// HOOD 485 //////////////////////////////////////////////
extern uint16_t Hood_polling_timer;
extern uint8_t rx_hood_485_TimeOut;
uint8_t Tx_Hood_Buff[20], Rx_Hood_Buff[20];
uint8_t Yeungong_Status = 0;
uint8_t Hood_Power_On = 0;
uint8_t Hood_Fan_Mode = 0;
uint8_t Hood_Control = 0;
uint8_t Hood_Rx_Complete = 0;
uint8_t Hood_Status = 0;

uint8_t CheckSum_Creator(uint8_t *buf, uint8_t len)
{
	uint8_t i, rt;

	rt = 0;

	for(i=0; i<len; i++)
	{
		rt ^= buf[i];
	}

	return(rt);
}

uint8_t Rx_hood_Pos = 0;
/* 후드 응답 수신 파서 — Rev1.3 9바이트 : AA 11 ID FANSTATUS LIGHT 00 연동CMD ERROR CS(XOR byte0~7)
   (구버전 13바이트는 My_Hood_old.c 보관) */
void rx_hood_check(uint8_t data)
{
	if(rx_hood_485_TimeOut == 0)Rx_hood_Pos = 0;

	switch(Rx_hood_Pos)
	{
		case 0:
			if(data != 0xAA)break;
			Rx_Hood_Buff[Rx_hood_Pos++] = data;
			break;
		case 1:
			if(data != 0x11)
            {
                Rx_hood_Pos = 0;
            }
            Rx_Hood_Buff[Rx_hood_Pos++] = data;
			break;
        case 2:
		case 3:
		case 4:
		case 5:
		case 6:
		case 7:
            Rx_Hood_Buff[Rx_hood_Pos++] = data;
			break;
		case 8:
			Rx_Hood_Buff[Rx_hood_Pos++] = data;
			if(data == CheckSum_Creator(Rx_Hood_Buff, 8))   /* CS = XOR(byte0~7) */
			{
				Hood_Rx_Complete = 1;
			}
			Rx_hood_Pos = 0;
			break;
		default:
			Rx_hood_Pos = 0;
			break;
	}
}
//-------------------------------------
void UART0_HANDLE()
{
    uint8_t u8InChar=0xFF;
    uint32_t u32IntSts= UART0->ISR;

    if(u32IntSts & UART_ISR_RDA_IS_Msk)
    {
       u8InChar = UART_READ(UART0);           /* Rx trigger level is 1 byte*/

		rx_hood_check(u8InChar);
		rx_hood_485_TimeOut = 10;
    }

    if(u32IntSts & UART_ISR_THRE_IS_Msk)
    {
;
    }
}

void UART0_IRQHandler(void)
{
    UART0_HANDLE();
}
//----------------------------------

void Hood_Tx_packet(void)
{
    /* 후드 Rev1.3 마스터 프레임(9바이트) : AA 21 ID MODE FAN 연동EN 연동운전중 ERROR CS(XOR byte0~7)
       구버전 13B는 ERV→후드 제어필드(Power/Fan)를 포함했으나 Rev1.3 스펙엔 없음(ERV는 후드 단수 추종). */
    Tx_Hood_Buff[0] = 0xAA;
    Tx_Hood_Buff[1] = 0x21;
    Tx_Hood_Buff[2] = 0x01;                  /* ID */
    Tx_Hood_Buff[3] = Run_Mode;              /* MODE */
    Tx_Hood_Buff[4] = Fan_Mode;              /* FAN */
    Tx_Hood_Buff[5] = Hood_YeunDong_Enable;  /* 연동 Enable */
    Tx_Hood_Buff[6] = Yeungong_Status;       /* 연동 운전중 */
    Tx_Hood_Buff[7] = 0x00;                  /* ERROR */
    Tx_Hood_Buff[8] = CheckSum_Creator(Tx_Hood_Buff, 8);

    HOOD_485_DIR = 1; // DIR
    UART_Write(UART0,Tx_Hood_Buff, 9);
    while ( !(UART0->FSR & UART_FSR_TX_EMPTY_F_Msk) );
    while ( !(UART0->FSR & UART_FSR_TE_F_Msk) );
    HOOD_485_DIR = 0; // DIR;
}

void Hood_RS485_process(void)
{
    if(Hood_Rx_Complete == 1)
    {
        Hood_Rx_Complete = 0;
        Hood_Conn_Timeout = 1500;   /* 유효 응답 수신 -> 통신연결 생존(폴 500ms 기준 ~3회 누락 허용) */
        Hood_Status = Rx_Hood_Buff[3];
//        if((Hood_Status == 1)&&(Hood_Yeundong_flag == 0))Command_request_type |= TYPE_HOOD_STATE;// add 2022.1.25
//        if((Hood_Status == 0)&&(Hood_Yeundong_flag == 1))Command_request_type |= TYPE_HOOD_STATE;// add 2022.1.25
//        Hood_Yeundong_flag = Hood_Status;
    }
    else
    {
        if(Hood_polling_timer == 0)
        {
            Hood_polling_timer = 500;
            Hood_Tx_packet();
        }
    }
}

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);
}

+      /* [쾌적조리자동해제] 후드 전원 OFF(단수 0) → 쾌적조리 토글 자동 해제.

+         사장님 요청(2026-06-18) : 후드 OFF 후 대시보드 쾌적조리 버튼이 저절로 꺼지게.

+         Enable=0 이면 STATUS byte4 bit1/byte5 bit0 모두 0 → 대시보드 _state.ComfortCook 자동 해제.

+         (기존 사양 9p 3.3 'armed 유지'와 반대 — 본 요청으로 변경) */


Now build to confirm:


Bash

cd "d:/project/nuvoton/HECO2/SOURCE/HECO2" && bash build.sh all 2>&1 | tail -8

Rebuild firmware after fix


[CC]  User/My_Hood.c
[LD]  build/HERV.elf
[HEX] build/HERV.hex
[BIN] build/HERV.bin

   text	   data	    bss	    dec	    hex	filename
  43952	   1940	   3388	  49280	   c080	build/HERV.elf


빌드 성공 — **경고/오류 0**, text 43948 → 43952 (+4B). 작업 기록 규칙에 따라 오늘자 문서에 이어서 기록하겠습니다.


170
171	## 빌드/배포
172	- `dotnet publish ErvDashboard.csproj -c Release` → 오류 0. publish 단일 exe 갱신.
173

## 빌드/배포
- `dotnet publish ErvDashboard.csproj -c Release` → 오류 0. publish 단일 exe 갱신.


Edit

## 빌드/배포
- `dotnet publish ErvDashboard.csproj -c Release` → 오류 0. publish 단일 exe 갱신.

## 빌드/배포
- `dotnet publish ErvDashboard.csproj -c Release` → 오류 0. publish 단일 exe 갱신.

---

# 260618 (6) 쾌적조리 — 후드 전원 OFF 시 토글 자동 해제

## 요청 (사장님)
- 쾌적조리 선택 → 후드 동작 → **후드 전원 OFF** 했을 때, 대시보드 쾌적조리 버튼이 **저절로 꺼지게**(자동 해제) 해달라.
- 현재: 후드 OFF 후 이전 모드로 운전은 복귀되나 쾌적조리 토글은 armed 유지 → 수동으로 눌러야 꺼짐.

## 사양 변경 주의
- 기존 문서화 사양(개발사양서 260613 9p 3.3)은 **"후드 OFF는 메이크업 복귀 타이밍이지 토글 OFF가 아님 → 토글 armed 유지"**. 이번 요청으로 **반대로 변경**(후드 OFF=토글 자동 해제).

## 원인/구조 (정적분석)
- 대시보드 `_state.ComfortCook` ← STATUS byte4 bit1 ← 펌웨어 `Hood_YeunDong_Enable`. (byte5 bit0 `HoodEnable` 도 동일 변수)
- `Hood_YeunDong_Enable` 은 명령(`My_Homenet.c` CTRL_SUBMODE/CTRL_HOOD)으로만 set/clear, 펌웨어 자동 해제 없음 → 후드 OFF 후에도 1 유지 → 토글 계속 ON.
- 펌웨어 `Ext_Run_Mode==2` 쾌적조리 상태머신(My_system.c 846/952)은 대시보드 경로에서 dead code.

## 변경 파일/내용
- `Source/HECO2/User/My_Hood.c` `Hood_process()` 후드 OFF 전이 분기(`Hood_Status==0`, L197~):
  - `Hood_YeunDong_Enable = 0;` 추가. 마커 `[쾌적조리자동해제]`.
  - 효과: 후드 단수 0 도달(전원 OFF) 시 펌웨어가 토글 해제 → STATUS byte4 bit1/byte5 bit0 모두 0 → 대시보드 버튼 자동 해제. **대시보드 수정 불필요.**
- 부작용 없음 확인: 이 시점 `Hood_Status=0`·`Hood_Yeundong_flag=0` 이라 댐퍼 메이크업(My_system.c L1117)·룸컨 통지(My_RJ2.c L296) 이미 OFF 조건. 모드/풍량 복귀는 같은 분기에서 그대로 수행.

## 빌드 결과
- `bash build.sh all` → 성공, 경고/오류 0. text 43948 → **43952** (+4B), data 1940, bss 3388.

## 미해결 / 후속
- 실장비: 쾌적조리 ON → 후드 ON → **후드 전원 OFF** 시 쾌적조리 버튼이 자동으로 꺼지는지 + 이전 모드 복귀 동시 확인.
- 엣지: 후드를 **485 통신 두절(플러그 뽑힘)** 로 끄면 `Hood_Status` 가 마지막 값 유지되어 OFF 전이가 안 뜰 수 있음 → 그 경우 자동해제 미동작. 필요 시 `Hood_Conn_Timeout==0`(통신두절)에도 해제 추가 검토.


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)

## 빌드/배포
- `dotnet publish ErvDashboard.csproj -c Release` → 오류 0. publish 단일 exe 갱신.

---

# 260618 (6) 쾌적조리 — 후드 전원 OFF 시 토글 자동 해제

## 요청 (사장님)
- 쾌적조리 선택 → 후드 동작 → **후드 전원 OFF** 했을 때, 대시보드 쾌적조리 버튼이 **저절로 꺼지게**(자동 해제) 해달라.
- 현재: 후드 OFF 후 이전 모드로 운전은 복귀되나 쾌적조리 토글은 armed 유지 → 수동으로 눌러야 꺼짐.

## 사양 변경 주의
- 기존 문서화 사양(개발사양서 260613 9p 3.3)은 **"후드 OFF는 메이크업 복귀 타이밍이지 토글 OFF가 아님 → 토글 armed 유지"**. 이번 요청으로 **반대로 변경**(후드 OFF=토글 자동 해제).

## 원인/구조 (정적분석)
- 대시보드 `_state.ComfortCook` ← STATUS byte4 bit1 ← 펌웨어 `Hood_YeunDong_Enable`. (byte5 bit0 `HoodEnable` 도 동일 변수)
- `Hood_YeunDong_Enable` 은 명령(`My_Homenet.c` CTRL_SUBMODE/CTRL_HOOD)으로만 set/clear, 펌웨어 자동 해제 없음 → 후드 OFF 후에도 1 유지 → 토글 계속 ON.
- 펌웨어 `Ext_Run_Mode==2` 쾌적조리 상태머신(My_system.c 846/952)은 대시보드 경로에서 dead code.

## 변경 파일/내용
- `Source/HECO2/User/My_Hood.c` `Hood_process()` 후드 OFF 전이 분기(`Hood_Status==0`, L197~):
  - `Hood_YeunDong_Enable = 0;` 추가. 마커 `[쾌적조리자동해제]`.
  - 효과: 후드 단수 0 도달(전원 OFF) 시 펌웨어가 토글 해제 → STATUS byte4 bit1/byte5 bit0 모두 0 → 대시보드 버튼 자동 해제. **대시보드 수정 불필요.**
- 부작용 없음 확인: 이 시점 `Hood_Status=0`·`Hood_Yeundong_flag=0` 이라 댐퍼 메이크업(My_system.c L1117)·룸컨 통지(My_RJ2.c L296) 이미 OFF 조건. 모드/풍량 복귀는 같은 분기에서 그대로 수행.

## 빌드 결과
- `bash build.sh all` → 성공, 경고/오류 0. text 43948 → **43952** (+4B), data 1940, bss 3388.

## 미해결 / 후속
- 실장비: 쾌적조리 ON → 후드 ON → **후드 전원 OFF** 시 쾌적조리 버튼이 자동으로 꺼지는지 + 이전 모드 복귀 동시 확인.
- 엣지: 후드를 **485 통신 두절(플러그 뽑힘)** 로 끄면 `Hood_Status` 가 마지막 값 유지되어 OFF 전이가 안 뜰 수 있음 → 그 경우 자동해제 미동작. 필요 시 `Hood_Conn_Timeout==0`(통신두절)에도 해제 추가 검토.

# 260618 수정 요약 (핸드오프용)

> 작업 PC 이전 시점 기준. 아래 상세 항목 (1)~(5) 의 최종 변경 정리.

## 변경 파일 (커밋 포함)
| 파일 | 변경 요지 | 관련 항목 |
|---|---|---|
| `Source/HECO2/User/MyMotor.c` | 모드전환 중(`Damper_wait_time==5`) 팬 강제 0 → 내부댐퍼 이동 게이트 보호 | (3) |
| `Source/HECO2/User/My_Homenet.c` | 모드변경 시 명령경로 즉시 `Fan_Speed_Setting` 호출 주석(L259/293) — 마스터 정렬 | (1) |
| `Source/HECO2/User/My_Hood.c` | 후드연동 모드변경 시 즉시 `Fan_Speed_Setting` 주석(L192/207) — 마스터 정렬 | (1) |
| `Source/HECO2/User/My_system.c` | 스마트수면 거실(room1) CO2 무관 항상 CLOSE | (2) |
| `TestProgram/PCDashBoard/MainWindow.xaml.cs` | 쾌적조리 버튼 강조=ComfortCook(L583), 활성=항상(L587) | (4)(5) |
| `개발사양서/...DL_동작로직_260613.pptx` | 사양서 갱신(바이너리) | - |

## 빌드 상태
- 펌웨어: `cd SOURCE/HECO2 && bash build.sh all` → 경고/오류 0.
- 대시보드: `dotnet publish ErvDashboard.csproj -c Release` → 오류 0 (NU1701 경고만).
- 대시보드 exe: `TestProgram/PCDashBoard/bin/Release/net10.0-windows/win-x64/publish/ErvDashboard.exe`

## 실장비/대시보드 확인 필요(테스트 대기)
- 환기→공청 반복 전환 시 내부댐퍼 매번 이동(간헐성 해소). 전환 시 팬이 잠깐 0까지 멈췄다 복귀하는 것이 정상.
- 스마트수면 진입 시 거실 닫힘 + 침실 CO2 개폐.
- 쾌적조리 버튼이 후드 연결/전원 무관하게 항상 토글되는지.

## 미적용/보류
- (선택) 쾌적조리를 스마트수면/안심회복과의 **상호배타에서 분리**(사양상 독립 토글) — SubMode_Click L401~406.
- (보류) 펌웨어 쾌적조리를 `Ext_Run_Mode==2` 상태머신으로 배선(현재 My_system 846/952 dead code). 1차 대시보드 수정으로 충분한지 검증 후 결정.
- 1차 명령경로 주석(My_Homenet/My_Hood)은 (3) 게이트 보호로 중복이나 무해 — 정리 보류.

---

# 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 전송 → "선택 안됨".

## 변경 파일/내용
- `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 (5) 쾌적조리 버튼 활성(Enable)이 후드 상태에 묶임 — 독립 토글로 수정

## 증상
- 쾌적조리 버튼이 디폴트 비활성, 전원 ON/후드 연결 시에야 토글됨. 사양 9p 3.1상 쾌적조리는 후드 연결과 무관한 독립 토글.

## 원인
- `MainWindow.xaml.cs` L587 `ComfortCookBtn.IsEnabled = !subActive || _state.ComfortCook;`
- `subActive = SmartSleep || HoodRunning || ReliefRecover` 에 **HoodRunning(후드 가동중)** 포함 → 후드 상태가 쾌적조리 버튼 활성을 좌우.

## 변경 파일/내용
- `TestProgram/PCDashBoard/MainWindow.xaml.cs` L587:
  - `ComfortCookBtn.IsEnabled = !subActive || _state.ComfortCook;` → `ComfortCookBtn.IsEnabled = true;`
  - 쾌적조리는 독립 토글 → 후드 연결/가동·다른 시나리오와 무관하게 항상 토글 가능. (모드/풍량 버튼은 기존대로 subActive 잠금 유지.)

## 빌드/배포
- `dotnet publish ErvDashboard.csproj -c Release` → 오류 0. publish 단일 exe 갱신.

+# 260618 (6) 쾌적조리 — 후드 전원 OFF 시 토글 자동 해제

+## 요청 (사장님)

+- 쾌적조리 선택 → 후드 동작 → **후드 전원 OFF** 했을 때, 대시보드 쾌적조리 버튼이 **저절로 꺼지게**(자동 해제) 해달라.

+- 현재: 후드 OFF 후 이전 모드로 운전은 복귀되나 쾌적조리 토글은 armed 유지 → 수동으로 눌러야 꺼짐.

+## 사양 변경 주의

+- 기존 문서화 사양(개발사양서 260613 9p 3.3)은 **"후드 OFF는 메이크업 복귀 타이밍이지 토글 OFF가 아님 → 토글 armed 유지"**. 이번 요청으로 **반대로 변경**(후드 OFF=토글 자동 해제).

+## 원인/구조 (정적분석)

+- 대시보드 `_state.ComfortCook` ← STATUS byte4 bit1 ← 펌웨어 `Hood_YeunDong_Enable`. (byte5 bit0 `HoodEnable` 도 동일 변수)

+- `Hood_YeunDong_Enable` 은 명령(`My_Homenet.c` CTRL_SUBMODE/CTRL_HOOD)으로만 set/clear, 펌웨어 자동 해제 없음 → 후드 OFF 후에도 1 유지 → 토글 계속 ON.

+- 펌웨어 `Ext_Run_Mode==2` 쾌적조리 상태머신(My_system.c 846/952)은 대시보드 경로에서 dead code.

+## 변경 파일/내용

+- `Source/HECO2/User/My_Hood.c` `Hood_process()` 후드 OFF 전이 분기(`Hood_Status==0`, L197~):

+  - `Hood_YeunDong_Enable = 0;` 추가. 마커 `[쾌적조리자동해제]`.

+  - 효과: 후드 단수 0 도달(전원 OFF) 시 펌웨어가 토글 해제 → STATUS byte4 bit1/byte5 bit0 모두 0 → 대시보드 버튼 자동 해제. **대시보드 수정 불필요.**

+- 부작용 없음 확인: 이 시점 `Hood_Status=0`·`Hood_Yeundong_flag=0` 이라 댐퍼 메이크업(My_system.c L1117)·룸컨 통지(My_RJ2.c L296) 이미 OFF 조건. 모드/풍량 복귀는 같은 분기에서 그대로 수행.

+## 미해결 / 후속

+- 실장비: 쾌적조리 ON → 후드 ON → **후드 전원 OFF** 시 쾌적조리 버튼이 자동으로 꺼지는지 + 이전 모드 복귀 동시 확인.

+- 엣지: 후드를 **485 통신 두절(플러그 뽑힘)** 로 끄면 `Hood_Status` 가 마지막 값 유지되어 OFF 전이가 안 뜰 수 있음 → 그 경우 자동해제 미동작. 필요 시 `Hood_Conn_Timeout==0`(통신두절)에도 해제 추가 검토.


수정 완료했습니다.

## 변경 요약 — 쾌적조리 후드 OFF 시 자동 해제

**수정 파일:** [My_Hood.c](SOURCE/HECO2/User/My_Hood.c) `Hood_process()` 후드 OFF 전이 분기 (1줄 추가)

```c
Hood_YeunDong_Enable = 0;   // [쾌적조리자동해제]
```

**동작 흐름:**
1. 후드 전원 OFF → 후드가 단수 0 보고 → `Hood_Status==0` 전이 발생
2. 같은 분기에서 이전 모드/풍량 복귀(기존) **+ 토글 자동 해제(추가)**
3. `Hood_YeunDong_Enable=0` → STATUS byte4 bit1 / byte5 bit0 모두 0
4. 대시보드 `_state.ComfortCook=false` → 쾌적조리 버튼 **저절로 꺼짐**

**대시보드 수정 불필요** — 펌웨어가 STATUS로 상태를 내려주면 버튼이 알아서 해제됩니다.

**부작용 없음 확인:** 후드 OFF 시점엔 `Hood_Status=0`·`Hood_Yeundong_flag=0`이라 댐퍼 메이크업(`My_system.c:1117`)·룸컨 통지(`My_RJ2.c:296`)가 이미 OFF 조건 → 추가 영향 없음.

**빌드:** `bash build.sh all` 성공, 경고/오류 0 (text 43948 → 43952, +4B).

⚠ 참고 2가지:
- 이건 문서화된 기존 사양(9p 3.3, "토글 armed 유지")과 **반대 방향** 변경입니다. 사장님 요청대로 했고 문서에도 그렇게 기록했습니다.
- **엣지 케이스:** 후드를 *485 통신 두절*(플러그 뽑기)로 끄면 `Hood_Status`가 마지막 값으로 남아 OFF 전이가 안 뜰 수 있습니다 — 그 경우엔 자동해제가 안 됩니다. 후드 단수를 0으로 내려서 끄는 정상 동작에선 문제없습니다. 필요하면 통신두절(`Hood_Conn_Timeout==0`) 시에도 해제하도록 추가할 수 있습니다.

실장비에서 확인해 보시고, 통신두절 케이스도 처리할지 알려주세요.


쾌적조리를 누르지 않고 후드를 켰는데도 쾌적조리외에 다른 시나리오모드는 선택이 안돼지..
쾌적조리를 선택하지 않고 후드를 켜도 다른 시나리오모드 버튼은 활성화 시켜야 돼.


새 이슈를 확인했습니다. 원인은 버튼 활성 판정의 `subActive` 정의입니다. [MainWindow.xaml.cs:570](TestProgram/PCDashBoard/MainWindow.xaml.cs#L570):

```csharp
bool subActive = _state.SmartSleep || _state.HoodRunning || _state.ReliefRecover;
```

여기서 `_state.HoodRunning` = STATUS byte5 bit1 = `Hood_Status != 0` (후드가 **물리적으로 가동중**, 쾌적조리 선택 여부와 무관). 그래서 쾌적조리를 안 눌러도 후드만 켜지면 `subActive=true`가 되어 스마트수면·안심회복 버튼이 잠깁니다.

**핵심:** 펌웨어 `Hood_process()`는 `Hood_YeunDong_Enable==1`(쾌적조리 armed)일 때만 동작 → 쾌적조리 OFF면 후드가 켜져도 ERV 모드/풍량을 **건드리지 않음**. 따라서 잠글 이유가 없습니다. 잠금은 "쾌적조리 armed + 후드 가동"(메이크업 실제 연동중)일 때만 맞습니다.

해당 부분 정확히 보고 수정하겠습니다.


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;

            // 시나리오모드 활성 시: 운전모드·풍량·선택 안 된 시나리오모드 비활성화
            // 쾌적조리는 '연동운전중(HoodRunning=후드 가동중)' 기준으로 시나리오 활성 판단.
            // 후드 OFF(대기 상태)면 ERV는 본래 운전모드로 복귀하므로 운전모드를 다시 활성화해야 함(사양 3.1).
            bool subActive = _state.SmartSleep || _state.HoodRunning || _state.ReliefRecover;
            int fanMax = _state.RunMode == RunMode.Bypass ? 1 : 4;


아..활성화가 아니고..선택할 수 있게..


쾌적조리를 누르지 않고 후드를 켰는데도 쾌적조리외에 다른 시나리오모드는 선택이 안돼지.. 쾌적조리를 선택하지 않고 후드를 켜도 다른 시나리오모드 버튼은 활성화 시켜야 돼.


네, 이해했습니다 — "활성화(저절로 켜짐)"가 아니라 **버튼을 누를 수 있게(선택 가능)** 해달라는 거죠. 제 수정 방향(`IsEnabled`을 풀어서 클릭 가능하게)이 바로 그것이라 그대로 진행합니다.


            // 시나리오모드 활성 시: 운전모드·풍량·선택 안 된 시나리오모드 비활성화
            // 쾌적조리는 '연동운전중(HoodRunning=후드 가동중)' 기준으로 시나리오 활성 판단.
            // 후드 OFF(대기 상태)면 ERV는 본래 운전모드로 복귀하므로 운전모드를 다시 활성화해야 함(사양 3.1).
            bool subActive = _state.SmartSleep || _state.HoodRunning || _state.ReliefRecover;

            // 시나리오모드 활성 시: 운전모드·풍량·선택 안 된 시나리오모드 비활성화(=클릭 불가)
            // 쾌적조리 잠금은 '메이크업 실제 연동중'(쾌적조리 토글 ON + 후드 가동) 일 때만.
            //   - 펌웨어 Hood_process() 는 Hood_YeunDong_Enable==1 일 때만 모드/풍량을 건드림.
            //     쾌적조리 OFF면 후드만 켜져도 ERV는 그대로 → 다른 시나리오모드/운전모드 선택 가능해야 함.
            //   - 후드 OFF(대기)면 메이크업 비연동 → 본래 운전모드로 복귀하므로 잠금 해제(사양 3.1).
            bool makeupActive = _state.ComfortCook && _state.HoodRunning;
            bool subActive = _state.SmartSleep || makeupActive || _state.ReliefRecover;


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)

            // 시나리오모드 활성 시: 운전모드·풍량·선택 안 된 시나리오모드 비활성화
            // 쾌적조리는 '연동운전중(HoodRunning=후드 가동중)' 기준으로 시나리오 활성 판단.
            // 후드 OFF(대기 상태)면 ERV는 본래 운전모드로 복귀하므로 운전모드를 다시 활성화해야 함(사양 3.1).
            bool subActive = _state.SmartSleep || _state.HoodRunning || _state.ReliefRecover;

            // 시나리오모드 활성 시: 운전모드·풍량·선택 안 된 시나리오모드 비활성화(=클릭 불가)
            // 쾌적조리 잠금은 '메이크업 실제 연동중'(쾌적조리 토글 ON + 후드 가동) 일 때만.
            //   - 펌웨어 Hood_process() 는 Hood_YeunDong_Enable==1 일 때만 모드/풍량을 건드림.
            //     쾌적조리 OFF면 후드만 켜져도 ERV는 그대로 → 다른 시나리오모드/운전모드 선택 가능해야 함.
            //   - 후드 OFF(대기)면 메이크업 비연동 → 본래 운전모드로 복귀하므로 잠금 해제(사양 3.1).
            bool makeupActive = _state.ComfortCook && _state.HoodRunning;
            bool subActive = _state.SmartSleep || makeupActive || _state.ReliefRecover;

//   - 자동 : 수동 조절 불가(전 단 비활성)

//   - 바이패스 : 최대 1단(2~4단 비활성)

-            // 시나리오모드 활성 시: 운전모드·풍량·선택 안 된 시나리오모드 비활성화

-            // 쾌적조리는 '연동운전중(HoodRunning=후드 가동중)' 기준으로 시나리오 활성 판단.

-            // 후드 OFF(대기 상태)면 ERV는 본래 운전모드로 복귀하므로 운전모드를 다시 활성화해야 함(사양 3.1).

-            bool subActive = _state.SmartSleep || _state.HoodRunning || _state.ReliefRecover;

+            // 시나리오모드 활성 시: 운전모드·풍량·선택 안 된 시나리오모드 비활성화(=클릭 불가)

+            // 쾌적조리 잠금은 '메이크업 실제 연동중'(쾌적조리 토글 ON + 후드 가동) 일 때만.

+            //   - 펌웨어 Hood_process() 는 Hood_YeunDong_Enable==1 일 때만 모드/풍량을 건드림.

+            //     쾌적조리 OFF면 후드만 켜져도 ERV는 그대로 → 다른 시나리오모드/운전모드 선택 가능해야 함.

+            //   - 후드 OFF(대기)면 메이크업 비연동 → 본래 운전모드로 복귀하므로 잠금 해제(사양 3.1).

+            bool makeupActive = _state.ComfortCook && _state.HoodRunning;

+            bool subActive = _state.SmartSleep || makeupActive || _state.ReliefRecover;

int fanMax = _state.RunMode == RunMode.Bypass ? 1 : 4;


Now build/publish the dashboard:


PowerShell

dotnet publish "d:\project\nuvoton\HECO2\TestProgram\PCDashBoard\ErvDashboard.csproj" -c Release 2>&1 | Select-String -Pattern "error|ErvDashboard ->|빌드했습니다|Build succeeded" | Select-Object -Last 8

Publish dashboard, filter build result


  ErvDashboard -> d:\project\nuvoton\HECO2\TestProgram\PCDashBoard\bin\Release\net10.0-windows\win-x64\ErvDashboard.dll
  ErvDashboard -> d:\project\nuvoton\HECO2\TestProgram\PCDashBoard\bin\Release\net10.0-windows\win-x64\publish\


대시보드 publish 성공 (오류 0). 작업 기록에 추가하겠습니다.


Edit

- 엣지: 후드를 **485 통신 두절(플러그 뽑힘)** 로 끄면 `Hood_Status` 가 마지막 값 유지되어 OFF 전이가 안 뜰 수 있음 → 그 경우 자동해제 미동작. 필요 시 `Hood_Conn_Timeout==0`(통신두절)에도 해제 추가 검토.

- 엣지: 후드를 **485 통신 두절(플러그 뽑힘)** 로 끄면 `Hood_Status` 가 마지막 값 유지되어 OFF 전이가 안 뜰 수 있음 → 그 경우 자동해제 미동작. 필요 시 `Hood_Conn_Timeout==0`(통신두절)에도 해제 추가 검토.

---

# 260618 (7) 쾌적조리 미선택 + 후드 ON 시 다른 시나리오모드 버튼 잠김 수정

## 요청 (사장님)
- 쾌적조리를 **누르지 않고** 후드를 켰는데도 스마트수면/안심회복 등 다른 시나리오모드 버튼이 **선택 불가**(클릭 안 됨).
- 쾌적조리 미선택 상태에서 후드만 켜도 다른 시나리오모드 버튼은 **선택 가능**해야 함. (자동 켜짐이 아니라 '클릭 가능'.)

## 원인 (대시보드)
- `MainWindow.xaml.cs` L570 `subActive` 에 `_state.HoodRunning` 포함.
- `HoodRunning` = STATUS byte5 bit1 = `Hood_Status != 0` = 후드 **물리 가동중**(쾌적조리 선택 무관, `My_Homenet.c:131`).
- 그래서 후드만 켜도 `subActive=true` → 스마트수면/안심회복/운전모드/풍량 버튼이 비활성(클릭 불가).
- 그러나 펌웨어 `Hood_process()` 는 `Hood_YeunDong_Enable==1`(쾌적조리 armed) 일 때만 모드/풍량을 건드림 → 쾌적조리 OFF면 후드가 켜져도 ERV 동작에 영향 없음 → 잠글 이유 없음.

## 변경 파일/내용
- `TestProgram/PCDashBoard/MainWindow.xaml.cs` L570 부근:
  - `bool makeupActive = _state.ComfortCook && _state.HoodRunning;` 신설.
  - `subActive = _state.SmartSleep || makeupActive || _state.ReliefRecover;` (기존 `HoodRunning` → `makeupActive` 교체).
  - 효과: 쾌적조리 미선택 + 후드 ON → `makeupActive=false` → 다른 시나리오모드/운전모드/풍량 버튼 선택 가능. 쾌적조리 ON + 후드 가동(메이크업 실제 연동중)일 때만 잠금 유지.
  - (쾌적조리 ON + 후드 OFF '대기'도 `makeupActive=false` → 본래 운전모드 조작 가능, 사양 3.1 일치.)

## 빌드/배포
- `dotnet publish ErvDashboard.csproj -c Release` → 오류 0 (NU1701 경고만). publish 단일 exe 갱신.

## 후속
- 실장비: 쾌적조리 미선택 + 후드 ON 시 스마트수면/안심회복 버튼 클릭되는지, 쾌적조리 ON + 후드 가동 시엔 잠기는지 확인.


- 엣지: 후드를 **485 통신 두절(플러그 뽑힘)** 로 끄면 `Hood_Status` 가 마지막 값 유지되어 OFF 전이가 안 뜰 수 있음 → 그 경우 자동해제 미동작. 필요 시 `Hood_Conn_Timeout==0`(통신두절)에도 해제 추가 검토.

---

# 260618 (7) 쾌적조리 미선택 + 후드 ON 시 다른 시나리오모드 버튼 잠김 수정

## 요청 (사장님)
- 쾌적조리를 **누르지 않고** 후드를 켰는데도 스마트수면/안심회복 등 다른 시나리오모드 버튼이 **선택 불가**(클릭 안 됨).
- 쾌적조리 미선택 상태에서 후드만 켜도 다른 시나리오모드 버튼은 **선택 가능**해야 함. (자동 켜짐이 아니라 '클릭 가능'.)

## 원인 (대시보드)
- `MainWindow.xaml.cs` L570 `subActive` 에 `_state.HoodRunning` 포함.
- `HoodRunning` = STATUS byte5 bit1 = `Hood_Status != 0` = 후드 **물리 가동중**(쾌적조리 선택 무관, `My_Homenet.c:131`).
- 그래서 후드만 켜도 `subActive=true` → 스마트수면/안심회복/운전모드/풍량 버튼이 비활성(클릭 불가).
- 그러나 펌웨어 `Hood_process()` 는 `Hood_YeunDong_Enable==1`(쾌적조리 armed) 일 때만 모드/풍량을 건드림 → 쾌적조리 OFF면 후드가 켜져도 ERV 동작에 영향 없음 → 잠글 이유 없음.

## 변경 파일/내용
- `TestProgram/PCDashBoard/MainWindow.xaml.cs` L570 부근:
  - `bool makeupActive = _state.ComfortCook && _state.HoodRunning;` 신설.
  - `subActive = _state.SmartSleep || makeupActive || _state.ReliefRecover;` (기존 `HoodRunning` → `makeupActive` 교체).
  - 효과: 쾌적조리 미선택 + 후드 ON → `makeupActive=false` → 다른 시나리오모드/운전모드/풍량 버튼 선택 가능. 쾌적조리 ON + 후드 가동(메이크업 실제 연동중)일 때만 잠금 유지.
  - (쾌적조리 ON + 후드 OFF '대기'도 `makeupActive=false` → 본래 운전모드 조작 가능, 사양 3.1 일치.)

## 빌드/배포
- `dotnet publish ErvDashboard.csproj -c Release` → 오류 0 (NU1701 경고만). publish 단일 exe 갱신.

## 후속
- 실장비: 쾌적조리 미선택 + 후드 ON 시 스마트수면/안심회복 버튼 클릭되는지, 쾌적조리 ON + 후드 가동 시엔 잠기는지 확인.

# 260618 수정 요약 (핸드오프용)

> 작업 PC 이전 시점 기준. 아래 상세 항목 (1)~(5) 의 최종 변경 정리.

## 변경 파일 (커밋 포함)
| 파일 | 변경 요지 | 관련 항목 |
|---|---|---|
| `Source/HECO2/User/MyMotor.c` | 모드전환 중(`Damper_wait_time==5`) 팬 강제 0 → 내부댐퍼 이동 게이트 보호 | (3) |
| `Source/HECO2/User/My_Homenet.c` | 모드변경 시 명령경로 즉시 `Fan_Speed_Setting` 호출 주석(L259/293) — 마스터 정렬 | (1) |
| `Source/HECO2/User/My_Hood.c` | 후드연동 모드변경 시 즉시 `Fan_Speed_Setting` 주석(L192/207) — 마스터 정렬 | (1) |
| `Source/HECO2/User/My_system.c` | 스마트수면 거실(room1) CO2 무관 항상 CLOSE | (2) |
| `TestProgram/PCDashBoard/MainWindow.xaml.cs` | 쾌적조리 버튼 강조=ComfortCook(L583), 활성=항상(L587) | (4)(5) |
| `개발사양서/...DL_동작로직_260613.pptx` | 사양서 갱신(바이너리) | - |

## 빌드 상태
- 펌웨어: `cd SOURCE/HECO2 && bash build.sh all` → 경고/오류 0.
- 대시보드: `dotnet publish ErvDashboard.csproj -c Release` → 오류 0 (NU1701 경고만).
- 대시보드 exe: `TestProgram/PCDashBoard/bin/Release/net10.0-windows/win-x64/publish/ErvDashboard.exe`

## 실장비/대시보드 확인 필요(테스트 대기)
- 환기→공청 반복 전환 시 내부댐퍼 매번 이동(간헐성 해소). 전환 시 팬이 잠깐 0까지 멈췄다 복귀하는 것이 정상.
- 스마트수면 진입 시 거실 닫힘 + 침실 CO2 개폐.
- 쾌적조리 버튼이 후드 연결/전원 무관하게 항상 토글되는지.

## 미적용/보류
- (선택) 쾌적조리를 스마트수면/안심회복과의 **상호배타에서 분리**(사양상 독립 토글) — SubMode_Click L401~406.
- (보류) 펌웨어 쾌적조리를 `Ext_Run_Mode==2` 상태머신으로 배선(현재 My_system 846/952 dead code). 1차 대시보드 수정으로 충분한지 검증 후 결정.
- 1차 명령경로 주석(My_Homenet/My_Hood)은 (3) 게이트 보호로 중복이나 무해 — 정리 보류.

---

# 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 전송 → "선택 안됨".

## 변경 파일/내용
- `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 (5) 쾌적조리 버튼 활성(Enable)이 후드 상태에 묶임 — 독립 토글로 수정

## 증상
- 쾌적조리 버튼이 디폴트 비활성, 전원 ON/후드 연결 시에야 토글됨. 사양 9p 3.1상 쾌적조리는 후드 연결과 무관한 독립 토글.

## 원인
- `MainWindow.xaml.cs` L587 `ComfortCookBtn.IsEnabled = !subActive || _state.ComfortCook;`
- `subActive = SmartSleep || HoodRunning || ReliefRecover` 에 **HoodRunning(후드 가동중)** 포함 → 후드 상태가 쾌적조리 버튼 활성을 좌우.

## 변경 파일/내용
- `TestProgram/PCDashBoard/MainWindow.xaml.cs` L587:
  - `ComfortCookBtn.IsEnabled = !subActive || _state.ComfortCook;` → `ComfortCookBtn.IsEnabled = true;`
  - 쾌적조리는 독립 토글 → 후드 연결/가동·다른 시나리오와 무관하게 항상 토글 가능. (모드/풍량 버튼은 기존대로 subActive 잠금 유지.)

## 빌드/배포
- `dotnet publish ErvDashboard.csproj -c Release` → 오류 0. publish 단일 exe 갱신.

---

# 260618 (6) 쾌적조리 — 후드 전원 OFF 시 토글 자동 해제

## 요청 (사장님)
- 쾌적조리 선택 → 후드 동작 → **후드 전원 OFF** 했을 때, 대시보드 쾌적조리 버튼이 **저절로 꺼지게**(자동 해제) 해달라.
- 현재: 후드 OFF 후 이전 모드로 운전은 복귀되나 쾌적조리 토글은 armed 유지 → 수동으로 눌러야 꺼짐.

## 사양 변경 주의
- 기존 문서화 사양(개발사양서 260613 9p 3.3)은 **"후드 OFF는 메이크업 복귀 타이밍이지 토글 OFF가 아님 → 토글 armed 유지"**. 이번 요청으로 **반대로 변경**(후드 OFF=토글 자동 해제).

## 원인/구조 (정적분석)
- 대시보드 `_state.ComfortCook` ← STATUS byte4 bit1 ← 펌웨어 `Hood_YeunDong_Enable`. (byte5 bit0 `HoodEnable` 도 동일 변수)
- `Hood_YeunDong_Enable` 은 명령(`My_Homenet.c` CTRL_SUBMODE/CTRL_HOOD)으로만 set/clear, 펌웨어 자동 해제 없음 → 후드 OFF 후에도 1 유지 → 토글 계속 ON.
- 펌웨어 `Ext_Run_Mode==2` 쾌적조리 상태머신(My_system.c 846/952)은 대시보드 경로에서 dead code.

## 변경 파일/내용
- `Source/HECO2/User/My_Hood.c` `Hood_process()` 후드 OFF 전이 분기(`Hood_Status==0`, L197~):
  - `Hood_YeunDong_Enable = 0;` 추가. 마커 `[쾌적조리자동해제]`.
  - 효과: 후드 단수 0 도달(전원 OFF) 시 펌웨어가 토글 해제 → STATUS byte4 bit1/byte5 bit0 모두 0 → 대시보드 버튼 자동 해제. **대시보드 수정 불필요.**
- 부작용 없음 확인: 이 시점 `Hood_Status=0`·`Hood_Yeundong_flag=0` 이라 댐퍼 메이크업(My_system.c L1117)·룸컨 통지(My_RJ2.c L296) 이미 OFF 조건. 모드/풍량 복귀는 같은 분기에서 그대로 수행.

## 빌드 결과
- `bash build.sh all` → 성공, 경고/오류 0. text 43948 → **43952** (+4B), data 1940, bss 3388.

## 미해결 / 후속
- 실장비: 쾌적조리 ON → 후드 ON → **후드 전원 OFF** 시 쾌적조리 버튼이 자동으로 꺼지는지 + 이전 모드 복귀 동시 확인.
- 엣지: 후드를 **485 통신 두절(플러그 뽑힘)** 로 끄면 `Hood_Status` 가 마지막 값 유지되어 OFF 전이가 안 뜰 수 있음 → 그 경우 자동해제 미동작. 필요 시 `Hood_Conn_Timeout==0`(통신두절)에도 해제 추가 검토.

+# 260618 (7) 쾌적조리 미선택 + 후드 ON 시 다른 시나리오모드 버튼 잠김 수정

+## 요청 (사장님)

+- 쾌적조리를 **누르지 않고** 후드를 켰는데도 스마트수면/안심회복 등 다른 시나리오모드 버튼이 **선택 불가**(클릭 안 됨).

+- 쾌적조리 미선택 상태에서 후드만 켜도 다른 시나리오모드 버튼은 **선택 가능**해야 함. (자동 켜짐이 아니라 '클릭 가능'.)

+## 원인 (대시보드)

+- `MainWindow.xaml.cs` L570 `subActive` 에 `_state.HoodRunning` 포함.

+- `HoodRunning` = STATUS byte5 bit1 = `Hood_Status != 0` = 후드 **물리 가동중**(쾌적조리 선택 무관, `My_Homenet.c:131`).

+- 그래서 후드만 켜도 `subActive=true` → 스마트수면/안심회복/운전모드/풍량 버튼이 비활성(클릭 불가).

+- 그러나 펌웨어 `Hood_process()` 는 `Hood_YeunDong_Enable==1`(쾌적조리 armed) 일 때만 모드/풍량을 건드림 → 쾌적조리 OFF면 후드가 켜져도 ERV 동작에 영향 없음 → 잠글 이유 없음.

+## 변경 파일/내용

+- `TestProgram/PCDashBoard/MainWindow.xaml.cs` L570 부근:

+  - `bool makeupActive = _state.ComfortCook && _state.HoodRunning;` 신설.

+  - `subActive = _state.SmartSleep || makeupActive || _state.ReliefRecover;` (기존 `HoodRunning` → `makeupActive` 교체).

+  - 효과: 쾌적조리 미선택 + 후드 ON → `makeupActive=false` → 다른 시나리오모드/운전모드/풍량 버튼 선택 가능. 쾌적조리 ON + 후드 가동(메이크업 실제 연동중)일 때만 잠금 유지.

+  - (쾌적조리 ON + 후드 OFF '대기'도 `makeupActive=false` → 본래 운전모드 조작 가능, 사양 3.1 일치.)

+- `dotnet publish ErvDashboard.csproj -c Release` → 오류 0 (NU1701 경고만). publish 단일 exe 갱신.

+- 실장비: 쾌적조리 미선택 + 후드 ON 시 스마트수면/안심회복 버튼 클릭되는지, 쾌적조리 ON + 후드 가동 시엔 잠기는지 확인.


수정 완료했습니다.

## 변경 요약 — 후드만 켰을 때 다른 시나리오 버튼 선택 가능하게

**수정 파일:** [MainWindow.xaml.cs:570](TestProgram/PCDashBoard/MainWindow.xaml.cs#L570) (대시보드)

```csharp
// 변경 전
bool subActive = _state.SmartSleep || _state.HoodRunning || _state.ReliefRecover;

// 변경 후
bool makeupActive = _state.ComfortCook && _state.HoodRunning;   // 쾌적조리 메이크업 실제 연동중
bool subActive = _state.SmartSleep || makeupActive || _state.ReliefRecover;
```

**동작 변화:**

| 상태 | 변경 전 | 변경 후 |
|------|---------|---------|
| 쾌적조리 OFF + 후드 ON | 시나리오/모드/풍량 버튼 **잠김** ❌ | **선택 가능** ✅ |
| 쾌적조리 ON + 후드 가동 (메이크업 연동중) | 잠김 | 잠김 (유지) |
| 쾌적조리 ON + 후드 OFF (대기) | 잠김 해제 | 잠김 해제 (사양 3.1) |

**근거:** 펌웨어 `Hood_process()`는 쾌적조리 armed(`Hood_YeunDong_Enable==1`)일 때만 모드/풍량을 제어합니다. 쾌적조리를 안 누르면 후드가 켜져도 ERV 동작엔 영향이 없으니 버튼을 잠글 이유가 없습니다. 그래서 잠금 조건을 "후드 가동중"에서 **"쾌적조리 + 후드 가동(=메이크업 실제 연동중)"**으로 좁혔습니다.

**빌드:** dotnet publish 오류 0 (NU1701 경고만), 단일 exe 갱신.

이로써 오늘 빌드 산출물은 펌웨어/대시보드 모두 최신입니다. 실장비에서 "쾌적조리 미선택 + 후드 ON 시 스마트수면·안심회복 버튼 클릭됨"을 확인해 주세요.


동작 잘돼.


확인 감사합니다. 두 가지 모두 정상 동작하는 걸로 정리하겠습니다.

오늘 마무리된 작업:
- **(6)** 후드 전원 OFF 시 쾌적조리 토글 자동 해제 — 펌웨어 [My_Hood.c](SOURCE/HECO2/User/My_Hood.c)
- **(7)** 쾌적조리 미선택 + 후드 ON 시 다른 시나리오 버튼 선택 가능 — 대시보드 [MainWindow.xaml.cs](TestProgram/PCDashBoard/MainWindow.xaml.cs)

빌드/문서 기록 모두 완료된 상태입니다. 변경분을 **커밋**해 둘까요? (현재 `main` 브랜치라, 원하시면 새 브랜치로 뺄 수도 있습니다.) 아니면 다른 작업 이어서 할까요?


그리고, 시나리오모드 버튼을 수정해 줘.
예를 들어 스마트수면을 선택하면 지금은 다른 버튼이 비활성화 되는데..그냥 모두 선택할 수 있게 해 줘.


시나리오 버튼이 서로 비활성화되는 부분을 풀겠습니다. 다만 펌웨어 구조상 짚어둘 점이 있어 먼저 확인하고 작업합니다.

펌웨어에서 시나리오는 단일 변수 `Ext_Run_Mode`로 관리됩니다 — **스마트수면(=4)**과 **안심회복(=1)**은 한 변수라 **물리적으로 동시 ON이 불가능**(하나면 다른 하나는 자동 0). 반면 **쾌적조리**는 별도 변수(`Hood_YeunDong_Enable`)라 독립입니다.

그래서 "모두 선택할 수 있게"를 이렇게 구현합니다:
- **세 버튼 모두 항상 클릭 가능**하게 (비활성 해제).
- 스마트수면 ↔ 안심회복은 클릭하면 **서로 전환**(하나 켜면 다른 하나 꺼짐) — 펌웨어상 동시 불가라 이게 맞음.

버튼 활성 부분을 수정하겠습니다.


582	            // 시나리오모드
583	            SetActive(SmartSleepBtn,    _state.SmartSleep);
584	            // 쾌적조리는 사양 9p 3.1 의 'UI 토글(연동 스위치)' — 후드 가동중이 아니라 토글 ON/OFF(=Hood_YeunDong_Enable)를 강조.
585	            // (HoodRunning 으로 강조하면 대기/Roll-back 상태에서 버튼이 꺼져 보여 재선택 시 토글이 반대로 먹던 문제 수정)
586	            SetActive(ComfortCookBtn,   _state.ComfortCook);
587	            SetActive(ReliefRecoverBtn, _state.ReliefRecover);
588	            //   (활성 모드 버튼은 OFF 토글 가능해야 하므로 자기 자신은 유지)
589	            SmartSleepBtn.IsEnabled    = !subActive || _state.SmartSleep;
590	            // 쾌적조리는 사양 9p 3.1 의 '독립 토글(연동 스위치)' — 후드 연결/가동·다른 시나리오와 무관하게 항상 토글 가능.
591	            ComfortCookBtn.IsEnabled   = true;
592	            ReliefRecoverBtn.IsEnabled = !subActive || _state.ReliefRecover;
593	            // 스마트수면 시간설정 버튼 : 스마트수면 ON 일 때만 활성
594	            SmartSleepSetBtn.IsEnabled = _state.SmartSleep;
595	            foreach (var mb in _modeButtons) mb.IsEnabled = !subActive;
596
597	            // 자동모드 프리셋(ECO/NORMAL/TURBO) : 자동모드에서만 활성, 활성 프리셋 강조.

            // 시나리오모드
            SetActive(SmartSleepBtn,    _state.SmartSleep);
            // 쾌적조리는 사양 9p 3.1 의 'UI 토글(연동 스위치)' — 후드 가동중이 아니라 토글 ON/OFF(=Hood_YeunDong_Enable)를 강조.
            // (HoodRunning 으로 강조하면 대기/Roll-back 상태에서 버튼이 꺼져 보여 재선택 시 토글이 반대로 먹던 문제 수정)
            SetActive(ComfortCookBtn,   _state.ComfortCook);
            SetActive(ReliefRecoverBtn, _state.ReliefRecover);
            //   (활성 모드 버튼은 OFF 토글 가능해야 하므로 자기 자신은 유지)
            SmartSleepBtn.IsEnabled    = !subActive || _state.SmartSleep;
            // 쾌적조리는 사양 9p 3.1 의 '독립 토글(연동 스위치)' — 후드 연결/가동·다른 시나리오와 무관하게 항상 토글 가능.
            ComfortCookBtn.IsEnabled   = true;
            ReliefRecoverBtn.IsEnabled = !subActive || _state.ReliefRecover;
            // 스마트수면 시간설정 버튼 : 스마트수면 ON 일 때만 활성
            SmartSleepSetBtn.IsEnabled = _state.SmartSleep;
            foreach (var mb in _modeButtons) mb.IsEnabled = !subActive;

            // 자동모드 프리셋(ECO/NORMAL/TURBO) : 자동모드에서만 활성, 활성 프리셋 강조.


그리고, 시나리오모드 버튼을 수정해 줘. 예를 들어 스마트수면을 선택하면 지금은 다른 버튼이 비활성화 되는데..그냥 모두 선택할 수 있게 해 줘.


            //   (활성 모드 버튼은 OFF 토글 가능해야 하므로 자기 자신은 유지)
            SmartSleepBtn.IsEnabled    = !subActive || _state.SmartSleep;
            // 쾌적조리는 사양 9p 3.1 의 '독립 토글(연동 스위치)' — 후드 연결/가동·다른 시나리오와 무관하게 항상 토글 가능.
            ComfortCookBtn.IsEnabled   = true;
            ReliefRecoverBtn.IsEnabled = !subActive || _state.ReliefRecover;

            // 시나리오모드 버튼은 항상 선택 가능(클릭 가능). 다른 시나리오가 켜져 있어도 비활성화하지 않음.
            //   - 스마트수면 ↔ 안심회복은 펌웨어상 단일 Ext_Run_Mode 라 동시 불가 → 클릭 시 SubMode_Click 이 서로 전환.
            //   - 쾌적조리는 별도 변수(Hood_YeunDong_Enable)라 독립 토글.
            SmartSleepBtn.IsEnabled    = true;
            ComfortCookBtn.IsEnabled   = true;
            ReliefRecoverBtn.IsEnabled = true;


            //   (활성 모드 버튼은 OFF 토글 가능해야 하므로 자기 자신은 유지)
            SmartSleepBtn.IsEnabled    = !subActive || _state.SmartSleep;
            // 쾌적조리는 사양 9p 3.1 의 '독립 토글(연동 스위치)' — 후드 연결/가동·다른 시나리오와 무관하게 항상 토글 가능.
            ComfortCookBtn.IsEnabled   = true;
            ReliefRecoverBtn.IsEnabled = !subActive || _state.ReliefRecover;

            // 시나리오모드 버튼은 항상 선택 가능(클릭 가능). 다른 시나리오가 켜져 있어도 비활성화하지 않음.
            //   - 스마트수면 ↔ 안심회복은 펌웨어상 단일 Ext_Run_Mode 라 동시 불가 → 클릭 시 SubMode_Click 이 서로 전환.
            //   - 쾌적조리는 별도 변수(Hood_YeunDong_Enable)라 독립 토글.
            SmartSleepBtn.IsEnabled    = true;
            ComfortCookBtn.IsEnabled   = true;
            ReliefRecoverBtn.IsEnabled = true;

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

SetActive(ComfortCookBtn,   _state.ComfortCook);

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

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

-            // 쾌적조리는 사양 9p 3.1 의 '독립 토글(연동 스위치)' — 후드 연결/가동·다른 시나리오와 무관하게 항상 토글 가능.

+            // 시나리오모드 버튼은 항상 선택 가능(클릭 가능). 다른 시나리오가 켜져 있어도 비활성화하지 않음.

+            //   - 스마트수면 ↔ 안심회복은 펌웨어상 단일 Ext_Run_Mode 라 동시 불가 → 클릭 시 SubMode_Click 이 서로 전환.

+            //   - 쾌적조리는 별도 변수(Hood_YeunDong_Enable)라 독립 토글.

+            SmartSleepBtn.IsEnabled    = true;

-            ReliefRecoverBtn.IsEnabled = !subActive || _state.ReliefRecover;

+            ReliefRecoverBtn.IsEnabled = true;

// 스마트수면 시간설정 버튼 : 스마트수면 ON 일 때만 활성

foreach (var mb in _modeButtons) mb.IsEnabled = !subActive;


PowerShell

dotnet publish "d:\project\nuvoton\HECO2\TestProgram\PCDashBoard\ErvDashboard.csproj" -c Release 2>&1 | Select-String -Pattern "error|ErvDashboard ->|Build succeeded" | Select-Object -Last 6

Publish dashboard after scenario button fix


대시보드 publish 성공 (오류 0). 문서에 기록합니다.


Edit

## 후속
- 실장비: 쾌적조리 미선택 + 후드 ON 시 스마트수면/안심회복 버튼 클릭되는지, 쾌적조리 ON + 후드 가동 시엔 잠기는지 확인.

## 후속
- 실장비: 쾌적조리 미선택 + 후드 ON 시 스마트수면/안심회복 버튼 클릭되는지, 쾌적조리 ON + 후드 가동 시엔 잠기는지 확인.

---

# 260618 (8) 시나리오모드 버튼 — 항상 선택 가능하게 (상호 비활성 해제)

## 요청 (사장님)
- 스마트수면을 선택하면 다른 시나리오 버튼이 비활성화됨 → **그냥 모두 선택(클릭) 가능**하게.

## 펌웨어 제약 (참고)
- 시나리오는 단일 `Ext_Run_Mode` : 스마트수면=4, 안심회복=1 → **동시 ON 물리적 불가**(전환만 가능). 쾌적조리는 별도 `Hood_YeunDong_Enable` → 독립.

## 변경 파일/내용
- `TestProgram/PCDashBoard/MainWindow.xaml.cs` L589~592:
  - `SmartSleepBtn.IsEnabled`/`ReliefRecoverBtn.IsEnabled` 의 `!subActive || _state.xxx` 조건 제거 → **세 버튼 모두 `IsEnabled = true`** (쾌적조리는 이미 true).
  - 클릭 시 동작은 기존 `SubMode_Click` 상호배타(L401~406) 그대로 → 스마트수면↔안심회복은 서로 전환.
- 운전모드/풍량/프리셋 버튼의 `subActive` 잠금은 유지(시나리오 동작 중엔 모드/풍량 시나리오가 제어).

## 빌드/배포
- `dotnet publish ErvDashboard.csproj -c Release` → 오류 0. publish 갱신.

## 후속/보류
- 현재 상호배타라 **쾌적조리 ON 중 스마트수면/안심회복 선택 시 쾌적조리도 꺼짐**(SubMode_Click L401~406). 사양상 쾌적조리는 독립이므로, 쾌적조리는 시나리오 전환과 무관하게 유지하길 원하면 별도 분리 필요(추후 결정).