feat: 06-17 신규 작업본 반영 (개발사양서/기능검토/승인원/Source 등 추가)

.claude/ 제외(.gitignore 추가). 기존 초기커밋(5a96a69) 위에 신규·수정·이동분 커밋.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-18 07:54:58 +09:00
parent 5a96a696b1
commit 096111e983
529 changed files with 12439 additions and 1166 deletions
+225
View File
@@ -0,0 +1,225 @@
# 260617 수정내용
작업일: 2026-06-17 / 작성: Claude (jeon 지시)
대상: 실보드(룸컨+HERV+PC) 연동 검증 중 발견 이슈 + 대시보드 기능 추가.
제약: `My_RJ2.c`(룸컨), `My_bunbaegi.c`(분배기) 수정 금지. 펌웨어 수정 후 `bash build.sh all` 빌드 확인.
※ 본 문서의 펌웨어 경로 `program/User/...`는 2026-06-17 폴더 이동 후 **`SOURCE/HECO2/User/...`** 로 읽을 것(아래 #14 참조).
---
## 1. 운전모드/풍량 "두 번 눌러야 바뀜" (실제 팬/댐퍼)
- **원인:** 운전모드 전환 시 `Fan_Speed_process()`(100ms)가 첫 틱에 `Pre_Run_Mode != Run_Mode`를 보고 `Target_Fan=0`으로 비움(댐퍼 정렬 대기). 그런데 정렬 완료 후 풍량을 새 모드 VSP로 **복원하는 코드가 주석처리**돼 있었음. 복원은 디퓨저 시퀀스(18초 주기)가, 그것도 풍량 단수가 바뀔 때만 수행 → 환기1단↔공청1단처럼 단수 동일 전환은 팬이 0으로 꺼진 채 방치. 두 번째 누르면 `Pre_Run_Mode==Run_Mode`라 wipe가 안 일어나 적용됨.
- **수정:** `program/User/MyMotor.c` — 환기/자동/바이패스/공청 4개 모드 전환 블록에서 댐퍼 정렬 후(`Damper_wait_time==1`, `Step_Status==0x3F` 통과) `Fan_Speed_Setting(Run_Mode, Fan_Mode)` 호출로 새 모드 VSP 복원. 전환당 1회 실행(정상 운전 중엔 미호출).
- 어제 추가했던 `My_Homenet.c`의 즉시 `Fan_Speed_Setting` 직접호출은 wipe로 지워져 무효였음 → 근본 위치(Fan_Speed_process)에서 해결.
## 2. RJ2 베이스라인 복원 + 죽은 코드 정리
- `My_RJ2.c`를 외부 원본(`HERV_DL_MH_2nd`)으로 되돌림 → `Homenet_RJ_Request`(PC 푸시 래치) 소비 코드 제거됨.
- `My_Homenet.c`/`My_define.h`에서 더 이상 소비되지 않는 `Homenet_RJ_Request` 정의·extern·쓰기 4곳 제거. PC→룸컨 푸시는 `Command_request_type |= TYPE_MODE/TYPE_FAN_SPEED` 경로로만 동작.
## 3. 전원 OFF인데 디퓨저(시뮬) 댐퍼 열림·LED 9 유지
- **원인:** DiffuserSim 슬레이브 파서가 명령/폴링을 `byte5(Power) != 0`로 구분. 전원 OFF면 펌웨어가 `byte5=0`(Power OFF)로 댐퍼0·LED0을 보내는데, 시뮬이 이를 폴링으로 오인해 닫힘 명령을 무시.
- dump.txt 스펙: byte5 = 하위비트 전원(0/1), 상위 0x80 Control Cmd.
- **수정:** `Simulator/DiffuserSimulator/SlaveProtocol.cs``powerOff = ((rxBuf[5] & 0x7F)==0)` 분기 추가. 전원 OFF 프레임이면 급기/배기 댐퍼 0, LED 0으로 닫음(전원 우선). 전원 ON 경로는 기존 유지.
## 4. 공기질 등급 불일치 (대시보드 좋음 vs 시뮬 보통)
- **시뮬 임계 버그:** `Simulator/DiffuserSimulator/MainWindow.xaml.cs` `ThrCO2`의 NORMAL/TURBO가 사양과 달랐음 → NORMAL `{700,1000,1300,1600}``{800,1100,1400,1700}`, TURBO `{600,800,1000,1200}``{700,1000,1300,1600}`. (PM2.5/PM10/VOC는 이미 일치)
- **프리셋 중앙값:** `PreCO2` NORMAL/TURBO가 옛 임계 기준이라 어긋나 재배치(밴드 중앙). NORMAL `{400,950,1250,1550,1850}`, TURBO `{350,850,1150,1450,1750}`.
- **전수 감사(개발사양서 p.10):** 펌웨어/DemoStatus/ErvState/DashboardState/웹 THR_DEMO 모두 사양과 일치 확인. 시뮬 ThrCO2만 불일치였음.
- **결론:** 위 값(PM2.5=22 등)은 사양상 어느 프리셋에서도 보통(레벨1)이라 둘 다 보통이 맞음. 대시보드가 좋음이면 **ERV에 실린 런타임 임계값(EEPROM)이 사양 기본값이 아님** → 보드에서 임계 재설정 필요(코드 아님).
## 5. VSP 값 사양(개발사양서 p.12) 정합
- p.12 첫 표(휴벤 ECO2/좌타입 HRD1-150EPI) = 펌웨어 `MyControl.c` 기본값. 환기 SA 56/63/70/86·EA 57/63/70/85, 바이패스 SA67/EA75, 공청 SA 65/72/78/80·EA 0. (환기 터보-4=자동 250CMH 행)
- **CLAUDE.md VSP 실측표**가 구버전(SA57/EA56…)이라 p.12 값으로 갱신. 기저(50CMH)는 사용자 지시로 제외.
## 6. PC 대시보드 — 히스테리시스/VSP 창 버튼 추가
- `TestProgram/PCDashBoard` — 히스테리시스·VSP 창에 **읽어오기(STATUS값)/프리셋(사양기본값)/변경(전송)** 버튼 추가.
- `DashboardState.Def*`(불변 기본값: 임계 p.10, VSP p.12) 추가.
- VSP 창은 로컬 작업본 분리 → STATUS(1초)가 편집값 덮어쓰던 문제 해결.
## 7. 웹 대시보드 — 동일 버튼 + VSP 값
- `TestProgram/WebDashBoard/ErvCollector/wwwroot/index.html`(+ `ErvWebDashboard/index.html` 동기화):
- VSP 모달 `읽어오기/프리셋(기본값)/변경`, 히스테리시스 모달 `읽어오기/프리셋(기본값)`(전송은 기존 데드밴드/임계 변경).
- `VSP_DEFAULT`(p.12)/`DB_DEFAULT`(p.10)/`THR_DEFAULT` 추가.
- 편집 버퍼(`bufVsp/bufHyst/bufThr`) 분리 → `poll()`(1초)이 편집값 덮어쓰던 문제 해결. VSP도 즉시전송 대신 변경 버튼.
---
## 8. 공기질 등급 여전히 비정상 (깨끗한 공기인데 '최악') — EEPROM 임계 로드 버그
- **증상:** 센서 PM2.5=7/PM10=14/CO2=400/VOC=60(전부 좋음 수준)인데 대시보드가 '최악'. 펌웨어 재플래시 후에도 지속.
- **원인:** `MyControl.c Hyst_From_Page()`가 EEPROM에서 임계값을 로드할 때 유효성 검사가 `page_data[EEP_HYST_PRESET](인덱스43) <= 2` 한 바이트뿐. 구 펌웨어 EEPROM은 시그니처(0x55AA55AA)가 유효하고 인덱스43에 우연히 0~2 값이 있으면, 임계영역(56~127)의 가비지(0 등)를 그대로 로드 → 임계 0 → 모든 센서가 레벨4(최악).
- **수정:** `Hyst_Page_Valid()` 추가 — 프리셋 마커 + **임계 단조증가(L0<L1<L2<L3, L0>0)** 검증. 위배(0/가비지)면 로드 거부 → 컴파일 기본값(사양 p.10) 유지. `Hyst_From_Page()`가 이 검사를 사용.
- 자가치유 불필요: 매 부팅 가비지 거부 → 사양값 사용. STATUS도 RAM 사양값 송신 → 대시보드 일치. 대시보드에서 임계 변경 시 유효(단조)값 저장 → 다음 부팅 정상 로드.
- **후속:** 재플래시 후 대시보드 히스테리시스 '읽어오기'로 ERV 임계가 사양 기본값인지 확인. 사양값 유지 원하면 '프리셋→임계 변경/데드밴드 변경'으로 EEPROM에 영속화.
## 9. NANO100 메모리 맵 정리 + EEPROM vs Data Flash 영역 확인
- **계기:** EEPROM 가비지를 지우려 Nu-Link ICP로 칩 Data Flash(0x1CC00)에 FF를 썼는데도 안 지워짐.
- **확인(DS_Nano100(B) Rev1.09 p.106/111 + fmc.h, EEPROM_Emulate.h):**
- 칩: Nano100SE3BN = LDROM 4K / APROM 115K(0x0~0x1CC00) / Data Flash 8K(0x1CC00~0x1EC00, DFBA=Config1=0x1CC00) / SRAM 16K(0x20000000).
- **펌웨어 EEPROM 에뮬레이션은 `EEP_FLASH_BASE=0xFC00` → APROM 내부(0xFC00~0xFFFF, 512B×2페이지). 칩 Data Flash(0x1CC00)와 다른 영역.**
- 펌웨어 코드는 0x0~0xB338(~46KB). EEPROM(0xFC00)과 18KB 여유 → 코드/EEPROM 안 겹침.
- **결론:** Data Flash에 FF 써도 펌웨어 EEPROM(APROM 0xFC00)은 안 지워짐. 비우려면 칩 이레이즈 또는 0xFC00 포함 APROM 이레이즈. (또는 #8 코드수정으로 가비지 자동거부.)
- **잠재 위험:** `gcc_arm.ld`가 EEPROM/Data Flash 영역 미예약(FLASH 0x0~0x20000 통짜) → 펌웨어가 0xFC00 넘으면 충돌. 현재 여유 있음.
- **문서화:** CLAUDE.md에 "■ NANO100 메모리 맵" 섹션 추가(시스템 주소공간/플래시 내부구조/우리가 쓰는 영역/주의사항). 향후 EEPROM을 칩 Data Flash(0x1CC00)로 이전할지는 보류(이전 시 사장님 FF 워크플로로 초기화 가능 + 코드충돌 위험 0).
## 10. 스마트수면(시나리오) 모드에서 공기질 등급 멈춤 — 대시보드 불일치
- **증상:** 스마트수면 모드에서 대시보드 공기질 등급이 센서 변화에 안 따라옴(멈춤). 시뮬과 불일치.
- **원인:** `My_system.c Air_Quality_damper_process()`에서 등급(`Room_Level`) 계산 루프가 함수 **맨 아래(메인 루프)**에 있는데, 시나리오 모드(안심회복=1/집중청정=3/스마트수면=4/쾌적조리)는 `goto PASS_VOLUME`로 그 루프를 **건너뜀** → 등급이 시나리오 진입 시점 값으로 동결.
- **수정:** 등급 계산(sensor_level 4종 → 최고치 → Room_Level/ROOM_air_volume + Prev_*_Lv 갱신)을 함수 **앞쪽(Force_Damper_Mode/Ext_Run_Mode 분기 이전)으로 이동**해 모드 무관 매 틱 수행. 기존 메인 루프는 재계산 없이 Room_Level 사용만(히스테리시스 prev 이중적용 방지). 댐퍼/자동판정 로직 영향 없음.
- **주의:** 이 수정은 등급이 "갱신"되게 함. 등급이 "정확"하려면 ERV 임계값이 사양값이어야 함(#4/#8, EEPROM). 둘은 별개.
## 11. 후드 통신 "연결안됨" — 펌웨어 후드 프로토콜이 Rev1.3와 불일치
- **증상:** 후드시뮬을 ERV 보드에 연결하고 쾌적조리 선택 → "후드 연결안됨".
- **원인:** 후드시뮬은 사양서(주신전자_protocol_hood Rev1.3_20241125, **9바이트·XOR**)대로 검증돼 있는데, ERV 펌웨어 `My_Hood.c`**구버전 13바이트**(CheckSum_Creator 12B + ERV→후드 제어필드 byte8/9). ERV가 13B 응답을 기다리는데 시뮬은 9B만 보내 → 프레임 미완성 → `Hood_Rx_Complete` 안 켜짐 → `Hood_Conn_Timeout` 미갱신 → 연결안됨. (헤더 AA21/AA11은 같으나 길이·체크섬 위치가 다름)
- **수정(`My_Hood.c`, 사용자 "스펙대로"):** Rev1.3 9바이트로 통일.
- 폴 송신: `AA 21 ID(01) MODE(Run_Mode) FAN(Fan_Mode) 연동EN 연동운전중(Yeungong) ERROR(0) CS(XOR byte0~7)`, 9B 송신.
- 수신 파서: `AA 11 ... [8]=CS(XOR0~7)`, `Hood_Status = byte[3]`(FANSTATUS). case 0~8.
- CheckSum_Creator는 이미 XOR이라 그대로(길이 8). ERV→후드 직접제어필드(Hood_Power_On/Fan/Control) 제거 — Rev1.3엔 없음(ERV는 Hood_Status 읽어 단수 추종).
- **백업:** 구 13B 소스를 `program/User/My_Hood_old.c`로 보관. Makefile `USER_EXCLUDE`로 빌드 제외(중복심볼 방지).
- **후속:** 플래시 후 후드시뮬 연결 시 "연결됨"·쾌적조리 연동(단수 추종) 확인.
### 11-1. 후드 프로토콜 상세 비교 (구버전 My_Hood_old.c ↔ 수정본 My_Hood.c)
공통: RS-485, 115200 8N1. 헤더 마스터=AA 21 / 슬레이브=AA 11. 체크섬 알고리즘은 양쪽 다 **XOR**(`CheckSum_Creator`)로 동일 — 차이는 **프레임 길이·CS 범위·제어필드**.
**(A) 프레임 구조 요약**
| 항목 | 구버전(13B) | 수정본(9B, Rev1.3) |
|---|---|---|
| 프레임 길이 | 13바이트 | 9바이트 |
| CS 위치 | byte[12] | byte[8] |
| CS 범위 | XOR(byte0~11) | XOR(byte0~7) |
| ERV→후드 제어필드 | 있음(byte8/9 Power/Fan) | **없음**(스펙에 없음, ERV는 후드 단수 추종) |
| RX 상태머신 | case 0~12 | case 0~8 |
**(B) ERV→후드 폴(Master, Hood_Tx_packet) 바이트별**
| byte | 구버전 13B | 수정본 9B (Rev1.3) |
|---|---|---|
| 0 | 0xAA | 0xAA |
| 1 | 0x21 | 0x21 |
| 2 | 0x01 (ID) | 0x01 (ID) |
| 3 | Run_Mode | Run_Mode (MODE) |
| 4 | Fan_Mode | Fan_Mode (FAN) |
| 5 | Hood_YeunDong_Enable | Hood_YeunDong_Enable (연동EN) |
| 6 | Yeungong_Status | Yeungong_Status (연동운전중) |
| 7 | 0x00 | 0x00 (ERROR) |
| 8 | Hood_Power_On \| Hood_Control | **CS = XOR(0~7)** |
| 9 | Hood_Fan_Mode \| Hood_Control | — (삭제) |
| 10 | 0x00 | — (삭제) |
| 11 | 0x00 | — (삭제) |
| 12 | CS = XOR(0~11) | — (삭제) |
| 송신길이 | UART_Write(...,13) | UART_Write(...,9) |
| 기타 | `if(Hood_Control==0x80)Hood_Control=0;` | (제거) |
**(C) 후드→ERV 응답(Slave, rx_hood_check 가 기대하는 포맷) 바이트별**
| byte | 구버전 13B | 수정본 9B (Rev1.3) | 후드시뮬 BuildResponse |
|---|---|---|---|
| 0 | 0xAA | 0xAA | 0xAA |
| 1 | 0x11 | 0x11 | 0x11 |
| 2 | (data) | ID | 0x01 |
| 3 | **Hood_Status←[3]** | **FANSTATUS (Hood_Status←[3])** | fanStatus |
| 4 | (data) | LIGHTSTATUS | light |
| 5 | (data) | 0x00 | 0x00 |
| 6 | (data) | 연동CMD | cmd(연동) |
| 7 | (data) | ERROR | errorCode |
| 8 | (data) | **CS = XOR(0~7)** | XOR(0~7) |
| 9~11 | (data) | — | — |
| 12 | CS = XOR(0~11) | — | — |
| 수신길이 | 13바이트 완성 시 처리 | 9바이트 완성 시 처리 | 9바이트 송신 |
→ 양쪽 모두 `Hood_Status`는 **byte[3]**에서 읽음(인덱스 불변). 수정본은 시뮬 BuildResponse와 바이트 배치·길이·CS가 정확히 일치.
**(D) 구버전이 시뮬과 통신 실패한 이유**
1. **길이 불일치:** ERV가 13바이트를 기다리는데 시뮬은 9바이트만 송신 → 수신 파서가 case 8(시뮬 CS)을 데이터로 받고 pos=9에서 멈춰 case 12에 영영 도달 못 함 → `Hood_Rx_Complete` 미설정.
2. 설령 길이가 같아도 **CS 범위 다름**(XOR 0~11 vs 0~7) → 체크섬 불일치로 거부.
3. 결과: `Hood_Conn_Timeout` 미갱신 → 1500ms 후 0 → STATUS byte5 bit2(후드 통신연결)=0 → 대시보드 "연결안됨".
**(E) 의미적 변화**
- 구버전: ERV가 후드에 Power/Fan을 직접 명령(byte8/9)할 수 있었음.
- 수정본(Rev1.3): ERV→후드 직접제어 없음. ERV는 응답의 FANSTATUS(byte3=Hood_Status)를 읽어 풍량을 **추종**(Hood_Step_To_Fan, 1→1…5→4, 사양 260613). `Hood_Power_On/Hood_Fan_Mode/Hood_Control` 변수는 정의는 남으나 후드 송신엔 미사용.
### 11-2. 후드 "연결 감지"와 "쾌적조리 동작"은 별개 (개념 정리)
질문: 쾌적조리(후드 풍량 추종) 선택해도 후드를 안 켜면 연결상태를 알 수 없나? → **아니오, 알 수 있음.** 두 로직이 분리돼 있음.
| 로직 | 함수 | 게이트 조건 | 역할 |
|---|---|---|---|
| **연결 감지(폴링)** | `Hood_RS485_process()` | **없음 — 항상 500ms 폴** | 유효 응답 오면 `Hood_Conn_Timeout=1500` → STATUS byte5 bit2 "연결됨" |
| **쾌적조리(추종)** | `Hood_process()` | `if(Hood_YeunDong_Enable==0) return` | 후드 단수 추종(메이크업 에어). 연결 감지와 무관 |
- ERV는 **쾌적조리 선택·후드 팬 상태와 무관하게 항상 후드를 폴**한다. 슬레이브(후드/시뮬)는 팬이 꺼져 있어도 폴에 응답하면 "연결됨".
- 경우별:
- 후드 제어보드 ON, 팬/조명 OFF → 응답함(FANSTATUS=0) → **연결됨**
- 후드 제어보드 자체 전원 OFF → 무응답 → **연결안됨**
- 후드시뮬 '통신 시작' 상태(PowerOn 토글 무관) → 무조건 응답 → **연결됨**
- 한계: 후드 제어보드가 완전히 꺼지면 "전원OFF인지 / 케이블 빠짐인지" 구분 못 함 — 둘 다 "연결안됨"(제3의 '알수없음' 상태는 없음).
- ⇒ 연결상태는 항상 알 수 있고, 시뮬은 통신 시작만 했으면 연결됨이어야 정상. 직전 "연결안됨"은 #11 프로토콜 길이 불일치(13B↔9B) 때문 → 플래시로 해결.
### 11-3. 후드 UART baud 불일치 (9600 → 115200) — 통신 자체 불가의 진짜 원인
- **증상:** #11(프레임 9B) 수정·플래시 후에도 후드 통신 계속 안 됨.
- **원인:** `My_system.c UART0_Init()`**`UART_Open(UART0, 9600)`** — 후드 UART가 9600 baud. 후드시뮬·Rev1.3 스펙은 **115200**. baud가 다르면 프레임 형식과 무관하게 한 바이트도 못 읽음. 프레임은 Rev1.3로 고쳤지만 baud는 구버전(9600)이 남아 있었음.
- **수정:** `UART_Open(UART0, 115200)`. (다른 채널 UART1=HOMENET 115200과 동일선상)
-#11(프레임 9B) + #11-3(baud 115200) 둘 다 적용해야 후드 통신 성립. 플래시 후 재확인.
## 12. 후드 단수 추종이 2단 이상 안 올라감 — Set_Fan_Mode만 잡고 Fan_Mode 미반영
- **증상:** 후드시뮬 단수를 5로 올려도(후드 FANSTATUS=5 정상 송신) ERV 폴엔 FAN=2 고정. 통신은 정상(9B/115200).
- **원인:** `My_Hood.c Hood_process()`(makeup-air 후드추종)가 `Set_Fan_Mode`(목표)와 `Command_request_type`만 설정하고, 실제 `Fan_Mode`(폴 byte4에 실리는 적용값)는 **룸컨(RJ2) echo로만 반영**. 룸컨 echo 지연/미연결 시 Fan_Mode가 옛값(2)에 머물러 폴에 2가 실림.
- **수정:** PC 풍량변경(CTRL_FAN)과 동일하게 **직접 반영**. 3개 분기 모두:
- 후드 ON 진입: `Set_Run_Mode=Run_Mode=MODE_VENTILATION`, `Set_Fan_Mode=Fan_Mode=Hood_Step_To_Fan(...)`, `Fan_Speed_Setting(Run_Mode,Fan_Mode)` 추가.
- 단수 변경: `Set_Fan_Mode=Fan_Mode=f`, `if(Run_Mode!=MODE_AUTO)Fan_Speed_Setting(...)` 추가.
- 후드 OFF 복귀: `Set_*=Run_Mode/Fan_Mode=My_Memory_*`, `if(Run_Mode!=MODE_AUTO)Fan_Speed_Setting(...)` 추가.
- 룸컨 푸시(Command_request_type)·Tx_Yeundong_Delay는 유지(연동/수동 구분 불변). 직접반영은 #1(MyMotor 전환복원)과 함께 동작.
- **후드 OFF 복귀(개발사양서 p.9 3.3 관련, 사용자 결정 2026-06-17):**
- **즉시 복귀** 채택(스펙의 180s 롤백 딜레이는 미구현 — 잔여 냄새배출은 후드측이 담당).
- 감지는 **FANSTATUS=0(Hood_Status==0)만**. 사용자 확인: 후드는 전원 OFF여도 통신으로 FANSTATUS=0을 보냄. 진짜 연결끊김은 별도 통신에러로 처리(복귀 트리거 아님).
- #12의 OFF 분기 직접반영(`Run_Mode=Fan_Mode=My_Memory_*` + `Fan_Speed_Setting`)으로 즉시 복귀 성립. 이전엔 Set_* 만 설정해 룸컨 echo 의존 → 복귀 누락이 "복귀 안됨"의 원인이었음.
- **추가 관찰(별개, 미수정):**
- `Yeungong_Status`(연동운전중, 폴 byte6)가 펌웨어에서 1로 설정되는 곳이 없어 **항상 0**(시뮬 "정지" 표시). 후드에 연동운전중을 알릴 필요 있으면 set 로직 추가 필요.
- `MODE_VENTILATION=0`이라 후드 폴 byte3(MODE)=0이 환기인데, 후드시뮬 ModeName이 0→"OFF"로 표시(시뮬 라벨 불일치, 표시상 문제).
## 13. 쾌적조리 후드 OFF 시 화면 복귀 안됨 (펌웨어 복귀는 정상, 대시보드 표시만)
- **증상:** 후드 OFF로 ERV는 본래 운전모드로 복귀(펌웨어 OK)하는데, 대시보드는 **쾌적조리 버튼이 계속 강조·운전모드 버튼 잠김** 상태로 남음.
- **원인:** 대시보드가 시나리오 활성/운전모드 잠금을 **쾌적조리(=연동 Enable, byte4 bit1)** 기준으로 판단. 후드가 꺼져도(연동운전중=0) 연동 Enable은 1로 유지되므로 계속 잠김.
- **수정(대시보드만):** 판단 기준을 **연동운전중(HoodRunning, STATUS byte5 bit1 = Hood_Status!=0)** 으로 변경.
- `DashboardState.HoodRunning` 추가, `StatusMapper`에서 `s.HoodRunning` 매핑(이미 디코드돼 있던 byte5 bit1).
- `RefreshControls`: `subActive`(운전모드/풍량/프리셋 잠금)와 `ComfortCookBtn` 강조를 `ComfortCook``HoodRunning`으로.
- **결과:** 후드 ON(가동중)=쾌적조리 강조+운전모드 잠김(메이크업), 후드 OFF=쾌적조리 해제+운전모드 활성(복귀된 모드 강조). 연동 Enable 자체는 '후드 연동' 토글로 계속 표시(사양 3.1 대기 상태 = 본래 모드 가동). 펌웨어 불변.
## 14. 후드시뮬 전원 OFF 표시 텍스트 변경 (시간 불변)
- 후드시뮬 전원 OFF 시 잔여배출 카운트다운 표시: `메이크업 {N}s`**`지연배기(원래는 30초) {N}s`** (HoodSimulator `MainWindow.xaml.cs`).
- **유지 시간은 변경 없음**(`MakeupHoldSec=10`, 사용자 지시 "시간은 바꾸지마"). "(원래는 30초)"는 사양 원래값 표기용 글씨. 로그/주석도 "메이크업"→"지연배기" 일관 정리(내부 변수명 유지).
## 15. 펌웨어 폴더 이동 (사용자 작업)
- **2026-06-17 사용자가 펌웨어 폴더를 `program/``SOURCE/HECO2/`로 이동.** (User, Library, Makefile, build.sh, openocd.cfg, build 모두). 같은 SOURCE 하위에 `FND_display`도 있음.
- `build.sh`는 자기 위치 기준(BASH_SOURCE) 경로라 새 위치에서 정상 빌드 확인(`cd SOURCE/HECO2; bash build.sh all` → 오류 0).
- 갱신: CLAUDE.md(빌드 규칙·경로), 메모리(herv-firmware-build/work-scope/MEMORY). 본 문서의 기존 `program/User/...` 경로는 `SOURCE/HECO2/User/...`로 해석.
## 빌드 결과 (전체)
- 펌웨어 `cd SOURCE/HECO2 && bash build.sh all` → 경고/오류 0 (HERV.elf/hex/bin, text 43976).
- DiffuserSimulator / PCDashBoard(ErvDashboard) / ErvCollector(웹) / HoodSimulator → 모두 오류 0.
## 오늘 요약 (#1~#15)
1. 운전모드/풍량 두번눌림(MyMotor 전환복원) · 2. RJ2 복원+죽은코드정리 · 3. 전원OFF 디퓨저 닫힘(SlaveProtocol) · 4. 공기질 ThrCO2/PreCO2 + p.10 전수감사 · 5. VSP p.12 정합(CLAUDE.md) · 6. PC대시보드 읽어오기/프리셋/변경 버튼 · 7. 웹대시보드 동일+VSP · 8. EEPROM 히스테리시스 가비지 거부(Hyst_Page_Valid) · 9. NANO100 메모리맵 문서화 · 10. 스마트수면 공기질등급 갱신 · 11. 후드 프로토콜 9B/Rev1.3(+#11-3 baud 115200) · 12. 후드 단수추종 직접반영 · 13. 쾌적조리 후드OFF 화면복귀(대시보드) · 14. 후드시뮬 지연배기 텍스트 · 15. 펌웨어 폴더 이동.
## 미해결 / 후속 (다음 작업)
- **플래시 후 실보드 검증**: 운전모드/풍량 1회 적용, 전원OFF 디퓨저 닫힘, 스마트수면 등급, 후드 통신/추종/복귀(9B·115200). (스핀다운 ~5~6초는 설계상 정상)
- **공기질 등급 정확도**: ERV EEPROM 임계값이 사양 기본값인지 대시보드 히스테리시스 '읽어오기'로 확인, 아니면 '프리셋→변경'으로 사양값 기록(또는 칩 이레이즈).
- **보류**: EEPROM을 칩 Data Flash(0x1CC00)로 이전 + 링커 영역 예약(내일 결정). 기저 VSP 실구현. 후드 `Yeungong_Status`(연동운전중) set 로직, 후드시뮬 MODE 라벨(0=환기).
@@ -0,0 +1,121 @@
# HERV 펌웨어 정적분석 버그 리포트
작성일: 2026-06-15
대상: `program/User/*.c`, `*.h` (펌웨어 전체)
방식: 영역별 정적분석(읽기 전용). **코드 수정 없음 — 검토용 목록**
표기: ✓ = 작성자가 직접 코드 확인 / (분석) = 분석에서 도출(미재확인) / [의심] = 추가 검증 필요
---
## 0. 우선순위 요약 (먼저 고칠 것)
| # | 위치 | 심각도 | 한줄 | 검증 |
|---|------|--------|------|------|
| 1 | My_RJ2.c:617 | High | switch case 8 `break` 누락 → 공청3단 세팅이 4단 프리셋 덮어씀 | ✓ |
| 2 | My_RJ2.c:400 | High | `Command_request_type = 0;` 로 보류비트(HOOD/예약/Homenet래치) 전멸 | ✓ |
| 3 | My_bunbaegi.c:869 | High | 예약시간 offset 오류: `[8]` 검사하고 값은 `[7]`(풍량)에서 읽음 | ✓ |
| 4 | MyControl.c:595 | High | 필터리셋 조건에 `Filter_timer_change` 중복(`Soja_timer_change` 오타) → 소자 리셋 누락 | (분석) |
| 5 | My_bunbaegi.c:708·898 | Med | 수신 id(`buffer[3]`) 범위검증 없이 크기7 배열 인덱싱 → OOB 쓰기 가능 | (분석) |
| 6 | My_bunbaegi.c:280 | Med | `Light_Bright[6]` 만 크기6(형제 배열은 7) → 거실2(id_2=6) 접근 시 OOB | ✓ |
| 7 | My_RJ2.c:110 | Med | `Reservation_process()` 주석처리 → 룸컨 예약(`Reserve_timer_sec`) 카운트다운 안 함 | ✓ |
---
## 1. 통신 / 프로토콜
### My_RJ2.c (룸컨)
- **[High] My_RJ2.c:617 — `case 8` break 누락 (fall-through)** ✓
- `case 8`(공청 3단 VSP) 끝에 `break`가 없어 `case 9`(4단)로 흘러감 → 공청 3단 세팅 시 4단 프리셋(`Test_Fan*_Air_4_dan`)까지 같은 값으로 오염.
- 제안: `case 8` 끝에 `break;` 추가.
- **[High] My_RJ2.c:400 — `Command_request_type = 0;` 전체 클리어** ✓
- `if(Command_request_type & TYPE_SEND_FLAG)` 블록에서 `= 0;``&= ~TYPE_SEND_FLAG;`(죽은 코드). SEND_FLAG만 소비해야 하는데 같은 사이클에 set된 `TYPE_HOOD_STATE`/`TYPE_RESERVATION`/병합된 `Homenet_RJ_Request` 비트가 한 번에 사라져 후드·예약 변경이 룸컨에 유실.
- 비교: line 690~의 같은 패턴은 `&= ~TYPE_SEND_FLAG`만 함(불일치).
- 제안: `= 0;``&= ~TYPE_SEND_FLAG;` 로 교체, 필요한 비트만 명시적으로 클리어.
- **[Med] My_RJ2.c:712 — `Filter_Reset_Flag |= buffer[7]` 마스크 누락**
- EVENT 경로(line 462)는 `& 0x01` 마스크를 쓰는데 여기선 통째 OR → 상위비트 오염으로 의도치 않은 필터 리셋 가능. 제안: `& 0x01` 적용.
- **[Med][의심] My_RJ2.c:702-705 — AUTO 수신 시 `Fan_Mode` 미갱신**
- else 분기에서 AUTO면 `Set_Fan_Mode/Fan_Mode`를 갱신 안 한 채 line 718 `Set_Fan_Mode==Fan_Mode` 비교 → TYPE_FAN_SPEED 비트가 옛값으로 클리어/유지되어 풍량 명령 어긋남 가능. EVENT 분기(414-415)와 기준 통일 검토.
### My_bunbaegi.c (분배기/디퓨저 마스터)
- **[High] My_bunbaegi.c:869 — 예약시간 byte offset 오류** ✓
- `if(Rx_bunbaegi_buffer[8] & 0x80){ Set_Reserve_timer_sec = (uint32_t)(Rx_bunbaegi_buffer[7] & 0x7f)*3600; }` — 요청비트는 `[8]`에서 보고 값은 `[7]`(풍량 바이트)에서 읽음 → 예약시간이 풍량값으로 들어감. 제안: `[8]`에서 읽기.
- **[Med] My_bunbaegi.c:708·898 (Diffuser/EachRoomCon parsing) — 수신 id 범위검증 없음** (분석)
- `id = Rx_bunbaegi_buffer[3];``SEN66_*[id]`, `Diffuser_Power[id]` 등 크기7 배열에 상한 체크 없이 인덱싱. 오염된 id(≥7)면 OOB 쓰기로 전역 손상. 제안: `if(id < 7)` 가드.
- **[Med] My_bunbaegi.c:183/191 — RoomCon `Packet_Length`가 절대 29가 안 됨** (분석)
- case 3에서 무조건 39, case 4 RoomCon 분기도 39(주석은 `//29byte`). → case 28의 29B 완료처리 분기가 죽은 코드. 29B 응답 파싱 불가. 제안: 의도대로 case 4에서 `Packet_Length = 29;`.
- **[Low] My_bunbaegi.c:813 — VSP 저장 트리거가 case 3(BYPASS)에만 존재** (분석)
- VEN/AIR VSP를 룸컨에서 바꾸면 `EEP_Save_Flag` 미설정 → 재부팅 후 유실 가능. 제안: case 1/2에도 동일 저장 조건.
### My_Homenet.c (PC 대시보드)
- **[Low/설계확인] My_Homenet.c:366 — CTRL_VSP u16→u8 truncation** ✓
- `(uint8_t)(((uint16_t)pl[2]<<8)|pl[3])` 는 상위바이트를 시프트 후 캐스팅으로 버려 사실상 `pl[3]`(하위)만 사용. VSP가 0~255라 실사용은 무해하나, 의도 명확화를 위해 `pl[3]`만 쓰는 게 안전. (확정 손상 아님)
- **[Low/의심] My_Homenet.c:410 — `hn_apply_cmd(cmd, pl, HN_DATA_LEN)` 로 len 항상 240** (분석)
- 모든 `if(len >= N)` 검증이 무조건 통과 → 짧은 명령 의미검증이 사실상 무력. 고정 244B 구조라 오버런은 없음. 동작 영향 낮음.
### My_Hood.c (후드)
- **[Med] My_Hood.c:64 — 헤더 불일치 바이트를 버퍼에 기록** (분석)
- case 1에서 0x11 불일치 시 `Rx_hood_Pos=0` 후에도 `Rx_Hood_Buff[Pos++]=data` 실행 → 잘못된 바이트가 buffer[0]에 들어가 프레임 한 칸 밀림. 제안: 불일치 시 `Pos=0; break;` 로 즉시 반환.
---
## 2. 시스템 / 전원 / 예약
### main.c
- **[Low] main.c:116-119 — `Process_10ms` 재장전값 3000, 본문 비어있음(데드/오타)** ✓
- `Process_5ms`/`Process_10ms` 블록 모두 카운터만 재장전하고 작업 없음(no-op). 동작 영향은 없으나 `=3000`은 명백한 leftover. 제안: 블록 제거 또는 의도값 복원.
### My_system.c
- **[Med] My_system.c (예약 카운터 이원화) — `Reserve_Remain_Sec`(HomeNet) vs `Reserve_timer_sec`(룸컨)** ✓
- main.c 1초 루프는 `Reserve_Remain_Sec`만 감산. 룸컨용 `Reserve_timer_sec`를 감산하는 `Reservation_process()`**My_RJ2.c:110에서 주석처리** → 룸컨이 설정한 예약은 카운트다운/전원OFF가 안 됨. 제안: 카운터 단일화 또는 `Reservation_process()` 호출 복원.
- **[Med] My_system.c:728 부근 `sensor_level()``T[i] - db` unsigned 언더플로** (분석)
- `uint16_t` 임계-데드밴드. 대시보드로 임계를 작게 설정하면 래핑되어 단계판정 폭주. 정상 사양값에선 미발생. 제안: 뺄셈 전 `T>=db` 가드 또는 signed 후 0 클램프.
- **[Med] My_system.c (Air_Quality_color_process 선두 `return(0)`) — 본문 도달불가(데드코드)** (분석)
- 매초 호출되나 색상/quality 계산 전체가 실행 안 됨. 의도된 비활성화인지 확인 필요(색상은 Air_Quality_damper_process가 별도 세팅).
---
## 3. 모터 / 댐퍼 / PWM (MyMotor.c)
- **[Med] MyMotor.c:893·897 — 팬 PWM 보정항 정수 오버플로/truncation** (분석)
- `BLDC_SPEED_TABLE[..]*Volum_value/1000` 이 int로 먼저 계산 → 큰 값/음수 Volum에서 오버플로·정밀손실. 제안: float 우선 캐스팅 또는 Volum 범위 클램프.
- **[Med] MyMotor.c — `BLDC_SPEED_TABLE[Fan_Speed]`(크기100), `Target_Step_Count[damper_num]`(크기7) 인덱스 미검증** (분석)
- 정상 VSP/모드값에선 안전하나 방어 없음. Fan_Speed≥100 또는 damper_num≥7 시 OOB. 제안: 접근 전 클램프/가드.
- **[Med][의심] MyMotor.c:1011 외 — `Step_Status != 0x3F` 수렴 대기 무한블록 가능**
- 특정 타이밍에 6댐퍼 전부 0x3F 미도달 시 후속(팬 목표갱신) 진입 불가. 타임아웃/완료플래그 보강 검토.
- **[Low][의심] MyMotor.c:962-979 — `Vsp_Select` if-else 체인 공백구간**
- 10~15, 0x21~0x24 등 미정의 값은 어느 분기에도 안 걸려 Target/Damper 미설정. 정상 범위면 무해. default 처리 검토.
---
## 4. 제어 / EEPROM (MyControl.c)
- **[High] MyControl.c:595 — 필터리셋 조건 `Filter_timer_change` 중복(오타)** (분석)
- `if((Filter_timer_clean==0)&&(Filter_timer_change==0)&&(Filter_timer_change==0))` 세 번째는 `Soja_timer_change==0` 이어야 함 → 소자 청소/교체 카운터 리셋 누락. 제안: 세 번째를 `Soja_timer_change`로 교정.
- **[Med] MyControl.c:659 — `Pre_Mode_Control` 부분쓰기 루프 상한이 127** (분석)
- 정전복귀 3바이트(40~42)만 필요한데 `i<EEP_SIZE`(127)까지 순회 → 히스테리시스 영역(43~127)을 RAM 미러로 재기록. 동기화 타이밍에 따라 임계 롤백 위험 + 불필요 스캔. 제안: 상한을 42로 제한.
- **[Med][의심] MyControl.c:531·536 — `Volum1/2_value` 변환식이 주석범위(-100~100) 초과(+175~-75)**
- 다운스트림에서 -100~100 가정 시 오버레인지. 계수/오프셋 재검토 또는 결과 클램프.
---
## 5. Light_Bright 배열 불일치 (교차 이슈)
- **[Med] My_bunbaegi.c:280 / My_define.h:495 — `Light_Bright[6]` 만 크기6** ✓
- 형제 각실배열(`Diffuser_*`, `SEN66_*` 등)은 모두 `[7]`(인덱스 0~6, 거실2=6). `Light_Bright``[6]`(0~5). My_bunbaegi.c:622/645 `Light_Bright[id_2]` 에서 id_2=6(거실2) 도달 시 OOB 읽기 → 인접 전역 오염. 제안: `Light_Bright[7]` 로 통일.
---
## 6. 점검했으나 "버그 아님" (오판 방지 기록)
- **MODE_* 중복정의(My_define.h 265-268 / 275-278)**: `#if((SPEC_MODE_INFO&0x0F)==0x03||==0x06)` 가드. 현재 `SPEC_MODE_INFO=0x16`(&0x0F=0x06) → **첫 블록만 컴파일**(VENT=0/AUTO=1/AIRCLEAN=2/BYPASS=3). 충돌 아님. 단 사양값 변경 시 BYPASS/AIRCLEAN(2↔3) 매핑이 바뀌는 잠재위험 → 프로토콜 코드값 고정 가정과 대조 필요.
- **CRC 계산범위/바이트순서(My_bunbaegi, My_Homenet)**: 송수신 내부 일관(27B/37B 데이터 + 2B CRC). `CRC16()`이 표준MODBUS 바이트스왑값을 반환하고 `[n]=icrc>>8` 배치로 상쇄 → 와이어는 표준 리틀엔디안. 정상. (시뮬레이터는 lo-first로 일치시킴)
- **CTRL_RESERVE 곱셈(My_Homenet.c:385)**: `pl[0]*3600` int 승격으로 최대 28800 정상.
- **EEPROM 인덱스/페이지 경계(MyControl.c)**: 히스테리시스/임계 영역 최댓값 127(=EEP_SIZE-1), 페이지(128B) 침범 없음. 서명 0x55AA55AA + 별도 유효성 마커 검증 정상.
- **pwm_duty10000.c**: 룩업테이블이 아니라 표준 Nuvoton PWM 드라이버. 범위초과 없음.
---
## 참고
- 본 리포트는 분석만 수행했고 소스는 수정하지 않았습니다.
- (분석) 표기 항목은 영역별 분석에서 도출됐고 작성자 재확인 전입니다. 수정 착수 전 해당 라인을 함께 열어 확정하는 것을 권장합니다.
- 우선순위 1~4(High)는 동작 증상으로 이어질 가능성이 가장 높습니다.