포트폴리오/[IoT 기반 피지컬 AI 교육일지]

[ C언어 IoT 프로젝트 ] #5 Win32 API 대시보드 구현 및 최종 기능 점검 (29일차 기록)

개발자혜콩 2026. 3. 24. 10:56

1. 오늘 한 것

① 보호자 대시보드 UI 구조 완성

┌────────────────────────────────────────────┐
│  시계 (타이틀바, 어두운 배경)              │
├────────┬────────┬────────┬─────────────────┤
│ ↻버튼  │        │        │                │
├────────┴────────┴────────┴─────────────────┤
│  온도카드 │ 습도카드 │ 움직임카드 │ 낙상카드 │
├────────────────────────────────────────────┤
│  채팅 로그창                               │
├────────────────────────────────────────────┤
│  입력창  │ 전송 │ 알림판 │ 기록조회 │경고해제│
└────────────────────────────────────────────┘
  • 화면을 크게 4개 영역(타이틀바 시계, 센서 위젯 카드, 채팅 로그창, 하단 컨트롤 버튼)으로 분할하여 배치.
  • 센서 위젯 카드는 STATIC 컨트롤을 서브클래싱(CardProc)하고, WM_PAINT 메시지를 오버라이딩하여 센서 상태(온도, 습도, 움직임, 낙상위험)에 따라 배경색과 폰트를 동적으로 렌더링.
LRESULT CALLBACK CardProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    if (msg == WM_PAINT) {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hWnd, &ps);
        RECT rc; GetClientRect(hWnd, &rc);

        // 배경색 (센서 상태에 따라 다름)
        HBRUSH hbr = CreateSolidBrush(cardBgColor(hWnd));
        FillRect(hdc, &rc, hbr);
        DeleteObject(hbr);

        // 센서 이름 (작게, 상단)
        SetTextColor(hdc, RGB(100,100,100));
        DrawTextW(hdc, title, -1, &rcTitle, DT_CENTER|DT_SINGLELINE);

        // 센서 값 (크게, 중앙)
        SelectObject(hdc, g_hCardValFont);
        SetTextColor(hdc, RGB(30,30,30));
        DrawTextW(hdc, value, -1, &rcVal, DT_CENTER|DT_VCENTER|DT_SINGLELINE);

        EndPaint(hWnd, &ps);
        return 0;
    }
    return CallWindowProc(g_origCard, hWnd, msg, wParam, lParam);
}

 


② 이벤트 기록 조회 팝업 구현 (ListView 적용)

발생일시        구분  상세 내용                        조치 상태
----------------------------------------------------------------------
03/19 14:30     비상  SOS 호출 버튼 클릭               확인필요
03/19 11:20     경보  낙상위험 경보 (10cm 미만 100초)   확인필요
  • 초기 EDIT 컨트롤 사용 시 한글 가변 폭 문제로 컬럼 정렬이 어긋나는 현상 발생.
HWND hList = CreateWindowExW(0, WC_LISTVIEWW, L"",
    WS_CHILD|WS_VISIBLE|WS_BORDER|LVS_REPORT|LVS_SINGLESEL,
    8, 8, 786, 390, hDlg, nullptr, nullptr, nullptr);

ListView_SetExtendedListViewStyle(hList,
    LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES);

// 컬럼 추가
LVCOLUMNW col = {};
col.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_FMT;
col.fmt  = LVCFMT_LEFT;

col.cx = 110; col.pszText = L"발생 일시";
ListView_InsertColumn(hList, 0, &col);
col.cx = 70;  col.pszText = L"구분";
ListView_InsertColumn(hList, 1, &col);
col.cx = 430; col.pszText = L"상세 내용";
ListView_InsertColumn(hList, 2, &col);
col.cx = 110; col.pszText = L"조치 상태";
ListView_InsertColumn(hList, 3, &col);
  • 이를 해결하기 위해 WC_LISTVIEWW 컨트롤(Report 스타일)을 도입. '발생 일시, 구분, 상세 내용, 조치 상태' 4개의 컬럼을 명확히 나누어 텍스트 길이에 상관없이 표 형태로 깔끔하게 출력하도록 개선.

③ 거주자 전용 직관적 UI 구현

  • 알림판 분리: 보호자가 NOTICE: 접두사로 보낸 메시지만 필터링하여 노란 배경의 큰 글씨로 별도 표시. 어르신이 중요 메시지를 놓치지 않도록 일반 채팅과 시각적으로 분리.
  • 커스텀 SOS 버튼: BS_OWNERDRAW 스타일과 WM_DRAWITEM을 활용해 붉은색 SOS 버튼을 직접 그림. 클릭 시(ODS_SELECTED) 색상이 더 어두워지게 처리하여 사용자에게 명확한 조작 피드백 제공.
// 버튼 생성
HWND g_hSosBtn = CreateWindowW(L"BUTTON", L"SOS",
    WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON|BS_OWNERDRAW,
    554, 372, 138, 50, hWnd, (HMENU)ID_SOS, nullptr, nullptr);

// WM_DRAWITEM
case WM_DRAWITEM: {
    DRAWITEMSTRUCT *dis = (DRAWITEMSTRUCT *)lParam;
    if (dis->CtlID == ID_SOS) {
        COLORREF clr = (dis->itemState & ODS_SELECTED)
            ? RGB(140,10,10)   // 눌렸을 때 더 어둡게
            : RGB(210,30,30);  // 기본 빨간색
        HBRUSH hbr = CreateSolidBrush(clr);
        FillRect(dis->hDC, &dis->rcItem, hbr);
        DeleteObject(hbr);
        SetTextColor(dis->hDC, RGB(255,255,255));
        SetBkMode(dis->hDC, TRANSPARENT);
        DrawTextW(dis->hDC, L"SOS", -1, &dis->rcItem,
            DT_CENTER|DT_VCENTER|DT_SINGLELINE);
        return TRUE;
    }
}

 

 

④ 최종 기능 점검

기능 확인항목 결과
센서카드 ↻ 눌렀을 때 CARD: 패킷 요청·수신·갱신
채팅 양방향 전송, 발신자 표시, DB 저장
알림판 NOTICE: 접두사 → 거주자 노란 화면 표시
SOS(UI) 거주자 버튼 → 보호자 점멸 + alert_logs
SOS(HW) 아두이노 스위치 3초 → 서버 → 보호자
낙상주의 30초 → [WARN] 로그만
낙상경보 100초 → 빨간 점멸 + 경고음
고온경보 후 자동환기 임계값 초과 → FAN 가동 + alert_logs
무움직임 감지 후 자동알림 3분 무감지 → 경보 + alert_logs
기록조회 버튼 → HISTORY 패킷 → ListView 팝업
경고해제 버튼 → 점멸 중단 + UI 정상 복원
LED 타임아웃 10초 후 자동 소등

 


2. 문제 / 헷갈린 점

① 긴급 점멸 시 자식 컨트롤 증발 현상 (WM_ERASEBKGND의 함정)

  • 상황: 낙상/SOS 발생 시 500ms 타이머로 배경을 붉게 점멸시키려 InvalidateRect(hWnd, nullptr, TRUE);를 호출했으나, 버튼과 로그창이 함께 깜빡이거나 사라짐.
  • 해결: nullptr로 전체 영역을 무효화한 것이 원인. 점멸 영역을 상단으로 제한하고, 자식 컨트롤을 보호하는 RDW_NOCHILDREN 플래그를 조합하여 부드러운 배경 점멸을 구현.
// 점멸 시: 버튼 영역 제외 + 자식 컨트롤 보호
RECT flashRect = {0, 0, 710, 390};
RedrawWindow(hWnd, &flashRect, nullptr, RDW_INVALIDATE | RDW_ERASE | RDW_NOCHILDREN);

// 해제 시: 전체 복원
RedrawWindow(hWnd, nullptr, nullptr, RDW_INVALIDATE | RDW_ERASE | RDW_ALLCHILDREN | RDW_UPDATENOW);

 

 


② ID_HISTORY 버튼 버그

  • 상황: '기록 조회(ID_HISTORY)' 버튼을 눌러도 반응이 없고 센서 위젯만 갱신됨.
  • 원인 : 어느 순간 SendHistory() 대신 SendCardUpdate()가 들어간 갔음.기록조회 버튼을 누르면 카드만 갱신되고 있었음.
  • 해결: WM_COMMAND 핸들러 작성 중 SendHistory()가 들어가야 할 자리에 SendCardUpdate() 코드가 잘못 복사되어 있었음. UI 배치와 로직 연결은 별개의 문제임을 깨달음.
// 수정 후: DB 조회용 빈 패킷 전송 구조 확립
void SendHistory() {
    if (g_sock == INVALID_SOCKET) return;
    NetPacket pkt; memset(&pkt, 0, sizeof(pkt));
    pkt.type = PT_DB_QUERY;
    pkt.payload.chat.message[0] = '\0';  // 빈 payload → 서버에서 history로 분기
    sendPacket(pkt);
}

case ID_HISTORY: {
    SendHistory();
    break;
}

 

    서버에서는 PT_DB_QUERY payload가 "CARD"면 센서카드 조회,
    비어있으면 alert_logs 조회로 분기하는 구조.


③ 아두이노 긴급 경보 LED 점멸 무한 반복 버그

  • 상황: 위험 이벤트로 아두이노 LED가 켜진 후, 경고 상황이 끝났는데도 영원히 깜빡임.
  • 원인 : 원인은 setLedAlert(true)를 호출할 때 시작 시각을 기록하는 코드가 어느 순간 수정되면서 사라졌음.
  • 해결: 타임아웃 체크 로직이 누락되어 있었음. 타이머 변수(ledAlertStart)를 추가하여 경보 시작 10초(LED_ALERT_TIMEOUT) 후에는 서버의 명시적 해제 명령이 없어도 하드웨어 단에서 자동 소등되도록 방어 코드 추가.
// 수정 전 - 타임아웃 체크 없음
if (ledAlert && (now - lastBlink >= BLINK_MS)) {
    lastBlink  = now;
    blinkState = !blinkState;
    digitalWrite(LED_PIN, blinkState ? HIGH : LOW);
}

// 수정 후 - 10초 후 자동 해제
if (ledAlert) {
    if ((now - ledAlertStart) >= LED_ALERT_TIMEOUT) {
        setLedAlert(false);  // 자동 종료
    } else if (now - lastBlink >= BLINK_MS) {
        lastBlink  = now;
        blinkState = !blinkState;
        digitalWrite(LED_PIN, blinkState ? HIGH : LOW);
    }
}

3. 오늘 배운 핵심

  • UI 개발의 본질은 '동작의 투명성'이다: 버튼이 화면에 예쁘게 떠 있는 것과 실제 기능이 올바른 이벤트 핸들러(WM_COMMAND)에 맵핑되어 있는지는 꼼꼼한 교차 검증이 필수적이다.
  • 한글 UI 출력에는 ListView가 해답이다: 고정폭 폰트와 sprintf 서식만으로는 한글의 가변적인 렌더링 폭을 제어할 수 없다. 데이터를 표 형태로 정렬할 때는 반드시 ListView를 써야 한다.
  • 안전장치는 다중으로 걸어라: 알람 해제는 소프트웨어(클라이언트 조작)뿐만 아니라, 하드웨어(아두이노) 자체 타이머를 통한 자동 해제 로직도 마련해야 예외 상황에 대처할 수 있다.

4. 다음 목표

  • 오늘 점검한 최종 체크리스트(센서 연동, 알람 전송, DB 저장 등 10개 항목)를 바탕으로 포트폴리오용 시연 영상 촬영.
  • 프로젝트 전체의 시스템 아키텍처, 구현 한계점, 그리고 향후 개선 방향(보안 강화 등)을 종합하여 최종 기술 발표 자료(PPT) 완성하기.