공간 센서화 기술 교재 — 20장
Sensor Guide

공간을 데이터로 만드는
센서 기술 교재

mmWave 레이더 · ESP32 · CO₂ 센서로 존재 감지부터 대시보드까지, 20장 실습 교재

작성이지에이전시
구성20장 · 실습 포함
대상센서를 처음 배우는 창업자·운영자
작성일2026. 10. 18
이렇게 읽으면 좋습니다

학원 공간을 예시로 쓰지만 사무실·매장·스터디카페 어디든 같은 원리입니다. 1~7장(원리와 선택) → 8~14장(아키텍처와 실습 코드) → 15~20장(검증·보안·로드맵) 순서로 이어집니다. 제조사 공식 문서로 확인하지 못한 값은 추정 · 미확인으로 표기했고, 실험 수치는 미리 채우지 않았습니다 — 직접 측정하는 것까지가 이 교재입니다.

24G HLK-LD2410C mmWave 존재 감지 · UART "사람이 있는가"를 본다 ESP32-WROOM-32 ESP32 DevKit V1 제어 · Wi-Fi 전송 (3.3V 로직) 센서를 읽어 서버로 보낸다 Sensirion SCD41 CO₂ · 온도 · 습도 · I²C (3.3V) 공기로 공간 상태를 읽는다
이 교재에서 쓰는 세 부품 — 왼쪽부터 존재 감지, 제어·전송, 공기질. 일러스트이며 실제 외형은 판매 페이지에서 확인하세요.

01문서 개요

1.1 이 교재의 목적

학원은 "사람이 모이는 공간"이다. 그런데 원장이 매일 묻는 질문 대부분은 눈으로 확인하거나 사람에게 물어야 답이 나온다.

  • 지금 3강의실에 수업 중인가, 비어 있는가?
  • 이 강의실 공기가 답답하다. 환기가 필요한가?
  • 에어컨과 조명이 빈 방에서 계속 켜져 있지 않은가?
  • 시간표상 수업이 있는데 실제로 사람이 있었나?

이 교재의 목적은 두 가지다.

  1. 센서가 무엇을 측정하고 무엇을 측정하지 못하는지 정확히 이해한다.
  2. ESP32, HLK-LD2410C, SCD41로 강의실 1개짜리 프로토타입을 직접 만들고, 데이터가 맞는지 검증하는 방법을 익힌다.

센서를 "마법 상자"로 보면 실패한다. 센서는 물리량을 숫자로 바꾸는 장치일 뿐이다. 그 숫자를 "학생 12명이 집중하고 있다"로 해석하는 것은 센서가 아니라 사람의 가정이다. 이 교재는 그 가정을 분리해서 다루는 훈련을 반복한다.

1.2 대상 독자

항목내용
주 독자학원 공간 센서화 사업을 준비하는 창업자
이미 아는 것프로그래밍 기초(파이썬, 아두이노 수준)
처음 접하는 것센서 원리, 하드웨어 배선, 신호 처리, 오탐·미탐 개념
이 교재가 주는 것개념 지도, 선택 기준, 실습 경로, 검증 방법, 개인정보 원칙
이 교재가 주지 않는 것전기·전파 인증 법무 자문, 상용 제품 양산 설계

1.3 전제 조건

구분전제
지식변수, 함수, JSON, HTTP 요청이 무엇인지 안다. 옴의 법칙은 몰라도 된다.
도구Windows 또는 macOS PC, USB 케이블, Wi-Fi 환경
부품ESP32 DevKit V1, HLK-LD2410C, Sensirion SCD41, 브레드보드, 점퍼선(M-F), USB 케이블, 5V USB 전원 어댑터
소프트웨어Arduino IDE 2.x, Python 3.11 이상
시간1-4주 학습과 1개 강의실 프로토타입 기준(자세한 것은 17장)
마음가짐측정값을 의심하고, 틀렸을 때 기록하는 습관

1.4 학원 센서화의 범위와 비범위

범위(MVP 에서 다루는 것)

영역내용
공간 존재강의실에 사람이 있는가(존재/움직임/정지)
공기 환경CO₂, 온도, 습도
이벤트입실·퇴실 추정, 환기 필요, 센서 고장
시각화공간별 현재 상태와 시계열 그래프
검증사람이 직접 센 값과 비교한 오탐·미탐 측정

비범위(MVP 에서 제외하는 것)

제외 항목이유
영상 상시 수집학생(미성년자 다수)의 얼굴·행동이 남는다. 개인정보 위험이 가장 크다.
음성 상시 수집대화 내용이 녹음될 수 있다.
개인 식별·추적레이더만으로 신원을 알 수 없고, 알려고 하면 감시가 된다.
집중도·감정 판단센서가 측정하지 못하는 것을 추론으로 포장하게 된다.
정확한 인원 계수MVP 부품(LD2410C, SCD41)으로는 불가능하다. 필요하면 다른 센서나 출결 기록을 쓴다.
출결의 법적 근거센서 값은 출결 기록이 아니다. 출결은 별도 검증된 기록이다(2장).
원칙: 개인정보 최소 수집이 기본값이다. "수집할 수 있으니 수집한다"가 아니라 "꼭 필요한 최소만 수집한다"로 설계한다.

1.5 교재 구성과 읽는 순서

flowchart LR
    A[1-2장 개념] --> B[3-4장 센서 지도와 선택]
    B --> C[5-6장 레이더 심화]
    C --> D[7장 공간 설계]
    D --> E[8-14장 구현]
    E --> F[15-20장 검증·운영]

기초 개념(1-4장) → 레이더 심화(5-6장) → 공간 설계(7장) → 구현(8-14장) → 검증과 운영(15-20장) 순서다. 급하면 2장, 4장, 7장만 먼저 읽어도 방향은 잡힌다.

1.6 정확성 약속

이 교재는 다음 규칙을 지킨다.

  1. 제조사 공식 문서를 우선 인용하고 URL과 확인일을 남긴다.
  2. 확인하지 못한 값은 추정 또는 미확인 — 구매 전 확인으로 쓴다.
  3. 실험 결과 수치를 지어내지 않는다. 예시는 "예시"로 표시한다.
  4. 센서 감지값(관측), 인원수 추정(추론), 출결 기록(검증된 사실)을 섞지 않는다.

ESP32 GPIO에 5V 신호를 직접 입력하지 마라. ESP32 는 3.3V 로직이다. 이 경고는 배선이 나오는 모든 장에서 반복한다.

02학원 공간 데이터화의 기본 개념

1층 · 관측 (Observation) 센서가 보낸 원시값 — presence: true · co2 850ppm · 거리 210cm 2층 · 추론 (Inference) 규칙·모델의 해석 — "강의실 사용 중", "환기 필요", "약 12명 추정" 3층 · 검증된 사실 (Fact) 사람이 확인한 기록 — 출결 체크, 담당자가 센 인원 섞으면 안 된다 "CO₂ 850 = 12명" (관측을 사실처럼) 같은 건너뛰기가 센서 프로젝트의 가장 흔한 실패 원인이다. 위로 갈수록 믿을 수 있지만 비싸고 드물다 — 아래층으로 위층을 "대체"할 수 없다 (2장 본문)
같은 숫자라도 어느 층의 정보인지에 따라 쓰임이 다르다.

2.1 세 층의 구분: 관측, 추론, 사실

센서 프로젝트에서 가장 흔한 실수는 서로 다른 근거를 한 덩어리로 취급하는 것이다. 먼저 세 층을 분리한다.

층이름정의예시신뢰의 근거
1직접 관측값 (Observation)센서가 물리량을 측정해 낸 원시 값co2_ppm = 850, presence = true, 거리 210 cm센서 사양과 보정 상태
2파생 이벤트 (추론, Inference)규칙이나 모델이 관측값으로 계산한 해석"강의실 사용 중", "환기 필요", "약 12명 추정"규칙의 타당성, 검증 실험
3검증된 사실 (Verified fact)사람이나 시스템이 확인해 기록한 것출결 체크, 담당 교사가 센 인원, 수업 일지확인 절차와 책임자
flowchart TB
    P[물리 세계: 사람, 공기, 전파] --> S[센서]
    S --> O["층1 관측값<br/>presence, co2_ppm"]
    O --> R[규칙·모델]
    R --> E["층2 파생 이벤트<br/>ROOM_OCCUPIED 등"]
    V["층3 검증된 사실<br/>출결 기록, 교사 계수"] -. 비교·검증 .-> E

핵심 규칙은 한 줄이다.

층2 는 층3 로 검증하기 전까지 "추정"이다. 층1 이 정확해도 층2 가 틀릴 수 있다.

같은 상황, 세 층으로 쓰기

상황층1 관측층2 추론층3 사실
오후 4시 3강의실presence true, 정지 표적 true, CO₂ 1,350 ppm사용 중, 환기 필요출석부에 학생 9명
방금 환기 후CO₂ 하락 중환기 효과 있음(추정)교사가 창문을 열었다고 기록
센서 오류데이터 5분간 없음SENSOR_FAULT기술자가 전원 케이블 이탈 확인

수치는 설명용 예시이며 실측 결과가 아니다.

2.2 직접 관측값의 성질

관측값에도 한계가 있다. 센서 값은 "참값"이 아니라 "센서가 본 값"이다.

성질설명학원 예시
오차참값과 센서 값의 차이CO₂ 센서 보정이 어긋나면 일정하게 높거나 낮게 나옴
노이즈무작위 흔들림온도가 0.1도 단위로 흔들림
지연변화가 값에 반영되기까지의 시간CO₂는 공기가 센서에 닿고 나서야 변함
오탐 (false positive)없는데 있다고 말함선풍기, 커튼 움직임을 사람으로 오인
미탐 (false negative)있는데 없다고 말함정지한 사람을 놓침
결측값이 아예 없음Wi-Fi 끊김, 센서 초기화 실패

관측값을 저장할 때는 "측정 실패"를 숫자 0 이 아니라 null 과 품질 플래그(quality)로 구분한다. 0 은 "측정했는데 0"이고, null 은 "측정 못 함"이다(9장).

2.3 CO₂ 만으로 인원수를 계산할 수 없다

흔한 오해가 있다. "CO₂ 농도는 사람 수에 비례하니 인원을 알 수 있다."

사람은 숨을 쉴 때 CO₂ 를 내뿜는다. 그러므로 CO₂ 는 사람 존재의 간접 신호다. 그러나 인원수를 계산하려면 다음을 알아야 한다.

필요한 정보왜 변수인가
방 부피같은 사람 수라도 큰 방은 천천히 오른다
환기량문·창문·공조 상태에 따라 CO₂ 가 빠져나감
외기 CO₂ 농도기준선이 장소·시간마다 다름
개인별 배출량나이, 활동량, 체격에 따라 다름
센서 위치출입구 근처인지 구석인지에 따라 값이 다름
시간농도는 천천히 변해서 방금 나간 사람이 반영되지 않음

이 변수들을 모르는 상태에서 인원 = (현재 CO₂ - 외기) / 상수 같은 식은 근거가 없다. CO₂ 는 다음 용도로만 쓴다.

  • 환기 필요 판단(VENTILATION_NEEDED)
  • 사용 여부의 보조 신호(퓨전, 8장)
  • 추세를 보는 지표

CO₂ 만으로 정확한 인원수를 계산할 수 없다. 인원 추정에 쓰려면 방마다 사람이 직접 센 값과 비교하는 교정 실험이 필요하고, 그 결과도 "이 방, 이 조건에서의 대략적 경향"일 뿐이다(15장).

2.4 레이더만으로 신원·집중도를 알 수 없다

mmWave 레이더(5장)는 "거기에 무언가 움직이거나 반사하는 물체가 있다"를 알려 준다. 그것이 누구인지는 알려 주지 않는다.

질문레이더(LD2410C 등)로 가능한가
사람이 있는가가능(존재 감지, 한계는 6-7장)
움직이는가, 정지해 있는가가능(제품 기능 범위 내)
대략 어느 거리인가가능(해상도 제한 있음)
몇 명인가LD2410C 는 불가. 다중 표적 제품도 제한적
누구인가불가
집중하고 있는가불가
졸고 있는가불가
공부하는가, 휴대폰을 보는가불가

"움직임이 적다 = 집중하고 있다"는 센서가 측정한 사실이 아니라 근거 없는 해석이다. 이 교재는 집중도 판단을 목표로 삼지 않는다. 개인 행동 감시로 이어질 수 있고, 학부모·학생과의 신뢰도 해친다.

레이더만으로 신원이나 집중도를 판단할 수 없다.

2.5 이벤트란 무엇인가

이벤트(Event)는 "상태가 바뀌었거나 조건이 충족되었다는 기록"이다. 관측값은 계속 쌓이지만, 이벤트는 의미 있는 순간만 남긴다.

용어정의예시
관측(Observation)센서가 보낸 원시 측정값30초마다 오는 JSON
상태(State)규칙이 계산한 공간의 현재 상황"3강의실: 사용 중"
이벤트(Event)상태 변화나 조건 충족의 기록ROOM_OCCUPIED 시작 시각 16:02

이 프로젝트의 기본 이벤트 4종(상세는 8장, 14장, 별도 파일 08_event_rules.md)은 다음과 같다.

이벤트조건(요약)해제
ROOM_OCCUPIED최근 5분 창에서 presence true 비율 60% 이상EMPTY 성립 시
ROOM_EMPTY최근 10분 presence true 없음OCCUPIED 성립 시
VENTILATION_NEEDEDco2_ppm 1200 초과 상태 10분 지속co2_ppm 1000 미만 10분 지속
SENSOR_FAULT장치 데이터 5분 이상 미수신 또는 quality false 지속정상 수신 재개

시간 창과 임계값은 초기값이며 실측 후 조정한다. 이 값들이 정답이라는 뜻이 아니다.

이벤트가 필요한 이유

  • 원장은 30초마다 쌓인 숫자를 읽지 않는다. "환기 필요"라는 한 줄을 읽는다.
  • 이벤트는 시작과 끝이 있어 지속 시간을 계산할 수 있다("3강의실 어제 총 사용 시간").
  • 같은 기록에 근거(evidence)를 붙이면 "왜 그렇게 판단했는지" 되짚을 수 있다.

2.6 디지털 트윈 개념

디지털 트윈(digital twin)은 실제 공간·설비의 상태를 소프트웨어에 비춰 둔 "디지털 쌍둥이"다. 거창한 3D 모델을 뜻하지 않는다.

수준내용이 교재의 위치
0실제 세계만 있음 (원장의 눈과 기억)출발점
1공간 목록과 현재 상태 표MVP 목표
2시계열 이력과 이벤트 타임라인MVP 목표
3시간표·출결과 연동, 예측·최적화확장 단계 (18장)
43D 시각화·시뮬레이션범위 밖

MVP 의 디지털 트윈은 다음 세 가지다.

  1. 학원(academies) → 공간(spaces) → 장치(devices) 계층 구조
  2. 공간별 현재 상태와 최근 관측 이력
  3. 상태 변화를 기록한 이벤트(events) 목록
flowchart LR
    A[학원] --> S1[3강의실]
    A --> S2[입구]
    A --> S3[사무실]
    S1 --> D1["장치 academy_room_A_01"]
    D1 --> O1[관측 시계열]
    S1 --> ST1[현재 상태]
    ST1 --> EV1[이벤트 기록]

트윈이 현실과 어긋나는 경우(센서가 떨어졌는데 서버는 "비어 있음"으로 표시)를 항상 가정한다. 그래서 SENSOR_FAULT 이벤트가 있다.

2.7 데이터화의 위험: 숫자가 사실처럼 보인다

대시보드에 "현재 인원 12명"이라고 크게 표시하면 읽는 사람은 사실로 받아들인다. 그것이 추정이라면 UI 에도 "추정"이라고 쓴다. 층1, 층2, 층3 를 UI 에서도 분리하는 것이 설계 원칙이다.

나쁜 표시좋은 표시
현재 인원 12명사용 중 (센서 감지). 출석부 인원은 시간표 연동 시 별도 표시
집중도 높음(표시하지 않음)
환기 불필요CO₂ 850 ppm (관측), 환기 필요 이벤트 없음

03센서 기술 지도

전파 광학 환경 음향 · 진동 전기 · 접촉 출입 인증 mmWave · UWB · RFID벽 너머 ×, 어둠 OK, 익명 PIR · ToF · LiDAR · 카메라정밀하지만 시야 필요 CO₂ · 온습도 · VOC · 미세먼지느리지만 공간 전체를 요약 소리 · 진동 감지간접 신호, 오탐 관리 필요 도어 접점 · 전류싸고 확실한 이벤트 QR · NFC · 키오스크신원이 필요한 유일한 갈래 이번 실습 이번 실습 개인정보 주의 6갈래 중 이번 프로토타입은 전파(LD2410C)와 환경(SCD41)을 쓴다 — 나머지는 3장에서 비교
센서 기술 지도 — 한 갈래가 만능이 아니라서 보통 2~3갈래를 섞는다.

이 장은 학원에서 쓸 수 있는 센서 기술을 계열별로 훑는다. 각 센서마다 측정 원리, 출력값, 해상도·샘플링, 정확도와 오차 원인, 설치 조건, 개인정보 위험을 정리한다.

일반 수치 표기 원칙: 이 장의 수치는 계열의 일반적 경향을 설명하는 것이다. 제품마다 다르므로 구매 전 해당 제품의 공식 데이터시트를 확인해야 한다. 구체 제품 값은 6장과 별도 문서에서 출처와 함께 다룬다.

3.0 데이터 유형 먼저 이해하기

센서가 내놓는 데이터의 형태에 따라 처리 방법과 개인정보 위험이 달라진다.

유형설명예시처리 난이도개인정보 민감도
Boolean참/거짓 하나PIR 감지됨, 문 열림매우 낮음낮음
scalar숫자 하나CO₂ ppm, 온도, 거리낮음낮음
vector숫자 묶음표적의 x,y 좌표와 속도, 거리 구간별 에너지중간중간
time series시간에 따라 쌓인 값30초 간격 CO₂ 기록중간맥락에 따라 다름
point cloud3차원 점들의 집합레이더·LiDAR 점군높음중간-높음
image / audio영상, 음성카메라, 마이크매우 높음높음
flowchart LR
    B[Boolean] --> S[scalar] --> V[vector] --> T[time series] --> PC[point cloud] --> IA[image / audio]
    B -. 정보량·처리 부담·개인정보 위험이 커지는 방향 .-> IA

일반적으로 오른쪽으로 갈수록 풍부한 정보를 주지만 처리가 어렵고 개인정보 위험이 커진다. 목적에 필요한 가장 왼쪽 유형을 고르는 것이 기본 전략이다.

3.1 전파 계열

3.1.1 mmWave 레이더

항목내용
측정 원리전파를 쏘고 반사파를 받아 거리·속도·각도를 추정(5장)
대표 대역24GHz, 60GHz 등
출력값제품에 따라 존재 Boolean, 거리 scalar, 표적 vector, 점군(point cloud), 원시 신호까지 다양
해상도·샘플링제품·모드마다 다름. 가공 출력 모듈은 초당 수 회에서 수십 회 수준이 일반적(추정, 제품별 확인)
정확도·오차 원인다중 경로 반사, 선풍기·커튼 같은 움직임, 금속 반사체, 벽·가구 가림, 설치 각도
설치 조건전원 필요. 감지 방향에 금속·큰 반사체가 없을 것. 플라스틱 커버 너머 동작 가능한 경우가 많으나 제품별 확인
개인정보 위험영상 없음. 신원 식별 불가. 다만 위치·움직임 이력은 행동 패턴 정보가 될 수 있어 목적 제한 필요

장점은 정지한 사람을 미세 움직임(호흡 등)으로 감지할 수 있는 제품이 있다는 점이다. 단점은 제품마다 출력이 매우 다르다는 점이다(5.7절).

3.1.2 UWB (Ultra-Wideband)

항목내용
측정 원리매우 짧은 펄스의 도착 시간으로 태그와 앵커 사이 거리 측정(ToF 방식), 여러 앵커로 위치 산출
출력값태그 위치(vector), 거리(scalar)
해상도수십 cm 급 정확도를 내세우는 제품이 많음(추정, 환경에 따라 달라짐)
오차 원인벽·사람 몸에 의한 신호 가림(NLOS), 앵커 배치
설치 조건공간에 앵커 여러 개 설치. 사람이 태그를 지녀야 함
개인정보 위험태그 소지자의 위치를 지속 추적하므로 위험이 큼. 동의와 목적 제한이 필수

개인 식별이 가능하다는 것은 장점이자 위험이다. 학생에게 태그를 지급해 위치를 추적하는 것은 MVP 범위가 아니다.

3.1.3 RFID

항목내용
측정 원리리더가 보낸 전파에 태그(카드·스티커)가 응답하여 고유 ID 전달
종류저주파(LF), 고주파(HF, NFC 포함), 초고주파(UHF)
출력값태그 ID (문자열), 경우에 따라 신호 세기(RSSI)
인식 거리HF 는 보통 수 cm, UHF 는 더 멀 수 있음(제품별 확인)
오차 원인금속·액체 근처 인식률 저하, 태그 방향
설치 조건리더 전원과 통신, 태그 배포
개인정보 위험태그 ID 가 사람과 연결되면 개인정보. 접촉·근접 시점만 기록하면 위험이 상대적으로 낮음

RFID 는 "누가 태그했는가"를 알려 주지만 "지금 방에 있는가"는 알려 주지 않는다. 출입 시점 기록용이다.

3.2 광학 계열

3.2.1 PIR (수동 적외선)

항목내용
측정 원리사람 체온이 만드는 적외선의 변화를 감지. 움직임 감지
출력값Boolean (움직임 있음/없음), 일부는 아날로그
해상도·샘플링거리 정보 없음. 반응은 빠르나 한번 감지 후 일정 시간 출력을 유지하는 제품이 많음
오차 원인정지한 사람은 놓침(미탐), 햇빛·히터·에어컨 바람에 오탐, 유리 너머 불투과
설치 조건시야각 내 장애물 없을 것. 천장 또는 벽
개인정보 위험매우 낮음
비고가격이 낮고 단순하다. 조용히 앉아 공부하는 강의실에는 단독으로 부적합

3.2.2 ToF (Time of Flight) 거리 센서

항목내용
측정 원리적외선 레이저 펄스가 물체에 반사돼 돌아오는 시간으로 거리 측정
출력값거리 scalar. 다중 구역(multi-zone) 제품은 격자형 거리 값(vector)
해상도단일 포인트부터 수 x 수 격자까지 제품별로 다름(제품별 확인)
오차 원인검은색·반짝이는 표면, 강한 햇빛, 유리
설치 조건출입구 위 천장에서 아래로 향해 지나가는 사람의 높이를 측정하는 방식으로 쓰임
개인정보 위험낮음. 해상도가 높아져도 얼굴을 알아볼 수준이 아니나, 높이·체형 정보는 얻을 수 있음
비고출입구 통과 계수에 쓸 수 있으나, 두 명이 나란히 지나가면 오차가 난다

3.2.3 LiDAR

항목내용
측정 원리레이저를 회전·스캔하며 ToF 로 거리를 재서 2D 또는 3D 점군 생성
출력값점군(point cloud)
해상도·샘플링각도 해상도와 회전 속도에 따라 다름. 제품별 확인
오차 원인투명·반사 표면, 햇빛
설치 조건시야 확보, 전원·통신. 상대적으로 고가(미확인 — 구매 전 확인)
개인정보 위험얼굴은 안 보이나 사람의 위치·동선·형태를 정밀 추적할 수 있음. 목적 제한 필요
비고학원 MVP 에는 과한 사양. 확장 논의용

3.2.4 카메라

항목내용
측정 원리이미지 센서가 가시광(또는 적외선)을 영상으로 기록
출력값image / video
인원 계수 능력이미지 처리(컴퓨터 비전)로 상대적으로 높은 정확도를 낼 수 있음. 단 가림·조명·각도 영향
오차 원인조명, 가림, 학습 데이터와 환경 차이
설치 조건시야, 조명, 전원, 대역폭, 연산 자원
개인정보 위험가장 높음. 얼굴·행동·복장이 기록됨. 미성년자 포함
비고이 교재에서는 상시 수집 제외. 법적·윤리적 검토가 선행되어야 한다

"엣지에서 영상은 버리고 인원수만 보낸다" 방식도 있지만, 장치가 영상을 처리하는 순간 개인정보 처리가 된다. 해당 방식도 MVP 범위 밖이다.

3.3 환경 계열

3.3.1 NDIR CO₂ 센서

항목내용
측정 원리적외선이 공기를 지날 때 CO₂ 가 특정 파장을 흡수하는 정도로 농도 측정(NDIR: 비분산 적외선)
변형광음향(photoacoustic) 방식 등 다른 원리의 제품도 있음 (SCD41 은 제조사가 광음향 NDIR 방식이라 설명 — 구매 전 데이터시트로 확인)
출력값CO₂ ppm (scalar)
샘플링보통 수 초 간격. 제품별 확인
오차 원인보정(캘리브레이션) 편차, 온습도, 센서 위치, 기압
설치 조건공기 흐름이 있는 곳. 사람 입김이 직접 닿는 위치, 창문·문·에어컨 송풍구 바로 앞은 피함
개인정보 위험낮음
한계반응이 느림. CO₂ 만으로 인원수 계산 불가(2.3절)

"CO₂ 센서" 중 값싼 MOX 방식은 실제 CO₂ 가 아니라 가스 반응으로 CO₂ 를 "추정"하는 경우가 있다(eCO₂). 학습용이라도 실제 NDIR 계열인지 확인한다.

3.3.2 온습도 센서

항목내용
측정 원리정전용량형 습도 소자, 반도체 온도 소자
출력값온도 scalar, 상대습도 scalar
오차 원인센서 자체 발열, 직사광선, 보드의 열
설치 조건발열 부품(ESP32, 전원부)에서 떨어뜨리기
개인정보 위험낮음
비고SCD41 에 온습도 측정이 포함되어 있으나 CO₂ 보정용 보조 값 성격이 있다. 정밀 환경 측정이 필요하면 별도 센서를 검토

3.3.3 VOC / 가스 센서

항목내용
측정 원리금속산화물(MOX) 소자의 저항이 가스에 따라 변함
출력값지수(index) 또는 상대 값. 제품에 따라 ppb 환산 값
오차 원인종류 구분이 어려움, 습도·시간에 따른 드리프트
설치 조건공기 흐름
개인정보 위험낮음
비고"냄새가 변했다"는 추세 지표에 적합. 정확한 농도 측정 장비가 아니다

3.3.4 미세먼지 센서

항목내용
측정 원리레이저 산란: 공기 중 입자가 레이저를 산란시키는 양을 측정
출력값PM1.0, PM2.5, PM10 질량 농도 (scalar)
오차 원인습도, 입자 종류, 팬 수명
설치 조건팬으로 공기를 흡입하므로 소음과 먼지 쌓임에 유의
개인정보 위험낮음

3.4 음향·진동 계열

항목내용
측정 원리마이크는 음압 변화, 진동 센서는 가속도 변화를 전기 신호로 변환
출력값소음 수준(dB) scalar 또는 오디오 파형(audio)
오차 원인마이크 보정, 위치, 배경 소음
설치 조건위치와 방향에 민감
개인정보 위험파형을 저장하면 대화 내용이 남는다 — 높음. 장치 내에서 dB 값만 계산하고 파형을 버리는 설계도 가능하나, 장치가 음성을 처리한다는 사실 자체가 우려가 된다
비고이 교재에서는 음성 상시 수집 제외. 소음 수준(dB)만 필요한 경우도 전용 소음 센서 모듈 검토 후 별도 동의 절차

진동 센서(가속도계)는 문이 열리고 닫힘, 책상 충격 등을 감지할 수 있으나, 사람 존재 판단에는 간접적이다.

3.5 전기·접촉 계열

센서측정 원리출력학원 용도개인정보 위험
도어 마그네틱(리드) 센서자석이 가까이 있으면 접점이 닫힘Boolean문 열림/닫힘낮음
압력·좌석 센서눌림에 따라 저항 변화Boolean 또는 scalar좌석 점유낮음-중간(좌석이 특정인과 연결되면 개인정보)
전류 센서도체 주변 자기장 또는 션트 저항전류 scalar조명·에어컨 가동 여부낮음
스위치/버튼접점Boolean수동 입력낮음

접촉·전기 센서는 단순하고 신뢰도가 높다. "문이 열렸다"는 거의 사실에 가까운 관측이다. 단, 문 열림 = 사람 출입은 아니다(추론).

3.6 출입 인증 계열 (QR / NFC)

항목내용
측정 원리QR: 카메라/스캐너가 코드 판독. NFC: 근거리 무선으로 ID 읽기
출력값인증 토큰, 학생 ID (문자열)
오차 원인스캔 환경(밝기), 카드 훼손
설치 조건입구 단말, 서버와 통신
개인정보 위험ID 가 사람과 연결되므로 개인정보에 해당. 접근 권한, 보관 기간, 동의 절차 필요
비고출결 기록(층3, 검증된 사실)의 후보 소스. 센서 이벤트를 검증할 때 비교 기준이 됨

QR/NFC 는 센서라기보다 "본인이 입장했음을 확인하는 절차"에 가깝다. 센서 관측(층1)과 출결(층3)을 이으면 오탐 검증이 가능해지지만, 연동은 MVP 이후 의사결정 사항이다(17장).

3.7 비교표

비교 기준은 "일반적 경향"이다. 제품·설치·환경에 따라 달라지므로 실험으로 검증해야 한다. 가격은 구체 금액을 쓰지 않고 상대적 구간으로 표시하며, 실제 가격은 구매 전 확인한다.

센서정지 인원 감지인원 계수개인 식별다중 인원·가림 대응가격대(상대)개발 난이도
PIR약함(정지 시 놓침)불가불가약함낮음매우 낮음
mmWave 존재 감지 모듈 (LD2410C 류)가능(제품 기능 범위)불가불가개별 구분 불가, 가림에 중간낮음-중간낮음
mmWave 다중 표적 모듈 (LD2450 류)제한적제한적(표적 수 한계)불가제한적낮음-중간중간
mmWave 개발 보드 (IWR6843 류)가능(자체 알고리즘 구현 시)알고리즘에 따라 가능성불가중간(구현 의존)높음높음
ToF 다중 구역정지 시 가능출입구 통과 계수에 제한적불가약함-중간낮음-중간중간
LiDAR가능가능성 있음불가중간높음높음
카메라가능가능성 높음가능(위험)상대적으로 좋음낮음-중간높음
NDIR CO₂간접 신호불가불가해당 없음중간낮음
UWB태그 소지자만태그 수만큼가능(태그 연결 시)앵커 배치에 의존높음높음
RFID/NFC해당 없음태그 수만큼(출입 시점)가능(태그 연결 시)해당 없음낮음-중간낮음-중간
마이크소음으로 간접불가가능(음성 저장 시)약함낮음중간
도어 센서해당 없음불가불가해당 없음매우 낮음매우 낮음

비교표 읽는 법

  • "정지 인원 감지"는 조용히 앉은 학생을 놓치지 않는가이다. 강의실에서 가장 중요한 항목이다.
  • "인원 계수"와 "개인 식별"은 다르다. 인원 계수만 필요하면 식별 능력이 있는 센서를 쓰지 않는 편이 낫다.
  • 이 표의 어느 칸도 실측 결과가 아니다. 우리 강의실에서 성능은 15장의 실험으로 확인한다.

04센서 선택의 공학적 기준

4.1 선택 절차: 질문에서 시작한다

센서부터 고르면 실패한다. 먼저 "어떤 결정을 내리려는가"를 쓴다.

flowchart TB
    Q[1 결정할 질문] --> M[2 필요한 정보 유형]
    M --> C[3 후보 센서]
    C --> F[4 기준별 평가]
    F --> P[5 소규모 실험으로 검증]
    P --> D[6 채택 / 폐기]
단계예시
질문빈 강의실의 에어컨을 자동으로 끄고 싶다
필요한 정보사람이 있는가(Boolean 수준이면 충분). 몇 명인지는 불필요
후보PIR, mmWave 존재 감지, 카메라
평가PIR 은 정지 학생 미탐. 카메라는 프라이버시 위험. mmWave 존재 감지가 균형
검증한 강의실에서 2주 측정 후 오탐·미탐 확인

필요한 정보가 "존재 여부"인데 "인원 수"를 측정하는 센서를 사면 비용·복잡도·개인정보 위험만 늘어난다.

4.2 여덟 가지 기준

기준질문확인 위치
측정 범위우리 방 크기를 덮는가데이터시트의 최대 거리, 각도
정확도결정에 필요한 오차 이내인가데이터시트, 실험
샘플링변화를 놓치지 않을 속도인가출력 주기, 응답 시간
설치천장/벽, 전원 위치, 가림, 간섭현장 답사
전력USB 5V 로 구동되는가, 상시 전원 가능한가데이터시트의 전압·전류
통신무엇으로 연결되는가(UART, I²C, Wi-Fi)인터페이스 사양
가격방 개수만큼 곱했을 때 감당 가능한가시장 조사(조사일 명시)
프라이버시영상·음성·식별 정보를 만드는가3장의 개인정보 위험 항목

4.2.1 측정 범위

  • 최대 감지 거리만 보지 말고 방의 실제 형상에 맞춰 본다. 책상, 칠판, 벽 때문에 유효 범위는 줄어든다.
  • 각도(화각)는 설치 위치에 따라 사각지대를 만든다(7장).
  • "최대 5 m"는 이상적 조건의 값일 수 있다. 정지 표적은 움직이는 표적보다 감지 거리가 짧은 제품이 많으므로 데이터시트에서 구분해 읽는다.

4.2.2 정확도

  • 정확도는 "오차의 크기"이고, 이 프로젝트에서 더 중요한 것은 오탐·미탐 비율이다.
  • 오탐과 미탐은 서로 교환 관계다. 감도를 높이면 미탐이 줄고 오탐이 늘어난다. 어느 쪽이 더 곤란한지 먼저 정한다.
시나리오더 곤란한 오류이유
빈 방 에어컨 자동 종료미탐 (사람이 있는데 꺼짐)학생이 불편하다
사용 시간 통계균형장기 평균이 중요
보안·야간 침입 알림미탐놓치면 안 됨
원장 알림오탐잦은 오알림은 신뢰를 잃음

4.2.3 샘플링

대상변화 속도필요한 주기(일반적 판단)
입퇴실수 초-수 분수 초 단위
공간 사용 여부분 단위30초 정도면 충분
CO₂수 분 단위수십 초
온습도수 분-수십 분수십 초 이상

이 프로젝트는 전송 주기를 30초로 고정하고 presence 상태 변화 시 즉시 1회 전송한다(9장, 10장). 센서 내부 측정 주기와 서버 전송 주기는 다르다는 점에 주의한다.

4.2.4 설치

점검 항목내용
위치천장 중앙 / 모서리 벽 상단 / 책상 위
전원가까운 콘센트, USB 전원 어댑터
간섭에어컨 바람, 선풍기, 커튼, 금속 캐비닛
외관눈에 띄는지, 학생이 만질 수 있는지
유지보수천장 위 설치는 교체가 어려움

4.2.5 전력

  • 상시 전원이 있는 강의실 센서는 USB 5V 어댑터로 충분한 경우가 많다.
  • 배터리 센서(도어 센서 등)는 전력 소모와 교체 주기를 따로 계산한다.
  • 센서마다 허용 전압이 다르다. 예: SCD41 은 3.3V 이며 5V 를 걸지 않는다. LD2410C 는 공식 소개 페이지 기준 5-12V 공급이다(6장).

4.2.6 통신

방식특징이 프로젝트 사용처
UART2선 직렬 통신. 속도 설정 필요ESP32 ↔ LD2410C
I²C2선, 주소로 장치 구분ESP32 ↔ SCD41 (주소 0x62)
Wi-FiESP32 내장장치 → 서버 HTTP POST
GPIO단순 high/lowLD2410C 의 OUT 핀(옵션)

4.2.7 가격

  • 가격은 부품 단가 외에 방 수 x 단가 + 케이스·전원·설치 인건비·유지보수를 본다.
  • 이 교재는 가격을 알리익스프레스·국내 오픈마켓 기준 범위로 적고 조사일을 표기한다. 미조사 시 미확인이다(18장).

4.2.8 프라이버시

  • 센서는 아래 순서로 위험이 커진다: 환경 센서 < 접점 센서 < PIR < 레이더 < LiDAR < 카메라·마이크.
  • "개인정보를 수집하지 않는다"고 말하려면 무엇이 저장되는지를 정확히 설명할 수 있어야 한다. 위치 좌표 이력도 행동 정보가 될 수 있다.
  • 기본값은 최소 수집이다(16장).

4.3 학원 시나리오별 판단 예시

아래는 "무엇을 쓰면 좋은가"의 판단 근거를 보여 주는 예시이며, 실측 결과가 아니다.

시나리오 A: 빈 강의실 감지 (에어컨/조명 절약)

항목판단
필요 정보존재 여부 (Boolean)
부적합PIR 단독 (정지 학생을 놓침), 카메라 (프라이버시)
후보mmWave 존재 감지(LD2410C), CO₂ 보조
주의미탐 시 수업 중 에어컨이 꺼질 수 있으므로 자동 제어는 지연 시간을 길게 잡고 수동 해제를 둔다
검증수업 중 정지 상태의 학생을 놓치지 않는지 확인(15장)

시나리오 B: 환기 알림

항목판단
필요 정보공기 질 추세
후보NDIR CO₂ (SCD41)
주의환기 알림은 인원 계수 목적이 아니다. 임계값은 초기값이며 현장에서 조정
검증창문을 열었을 때 값이 내려가는지 확인

시나리오 C: 출입구 통과 계수

항목판단
필요 정보들어온 사람 수와 나간 사람 수
후보ToF 다중 구역, 출입구용 양방향 계수 센서, 출입 인증(QR/NFC)
부적합LD2410C (방향·개수 판별 불가)
주의둘이 동시에 지나가면 한 명으로 셈

시나리오 D: 정확한 출석 확인

항목판단
필요 정보누가 출석했는가
정답출결 시스템 (QR/NFC/교사 확인). 센서는 출결을 대신하지 않는다
센서 역할출결 기록과 비교하여 센서 값을 검증

시나리오 E: 화장실·탈의 공간

항목판단
필요 정보사용 중 여부, 청소 시점
후보도어 센서(잠금 상태), 환기 센서
금지카메라, 마이크. 개인 위치 추적
주의레이더를 쓰더라도 존재 여부 외 데이터를 저장하지 않고, 학부모·학생에게 안내

시나리오 F: 학생 집중도 평가

항목판단
요구"졸고 있는 학생 알림"
답변센서로는 불가능하며 시도하지 않는다
이유측정하지 못하는 것을 추론으로 포장하면 오판과 감시 문제가 생긴다

4.4 선택 체크리스트

  • 결정할 질문을 한 문장으로 썼다.
  • 필요한 정보가 Boolean 인지, 개수인지, 위치인지 구분했다.
  • 후보 센서마다 정지 인원 감지 능력을 확인했다.
  • 개인정보 위험을 3장 기준으로 평가했다.
  • 전압과 로직 레벨(3.3V/5V)을 확인했다.
  • 소규모 실험으로 오탐·미탐을 측정할 계획이 있다.
  • 센서 없이 해결 가능한 문제인지 검토했다(수동 기록으로 충분한가).

05mmWave 레이더 기초와 심화

벽·천장의 LD2410C 24GHz 송신파 반사파 — 호흡·미세한 움직임까지 담겨 돌아온다 정지해 앉아 있어도 호흡·심박의 미세 움직임으로 존재를 감지한다 거리는 FMCW 주파수 차이로, 움직임 속도는 도플러 효과로 계산한다 (5장 본문)
mmWave 레이더의 감지 원리 — 송신파가 사람에게 반사되어 돌아오는 변화를 읽는다.

mmWave(밀리미터파) 레이더는 이 교재의 핵심 센서다. 수식보다 직관을 먼저 잡고, 필요한 만큼만 수식을 쓴다.

5.1 전파 반사: 어둠 속의 손전등과 메아리

산에서 "야호" 하고 외치면 메아리가 돌아온다. 소리가 돌아오는 시간이 길수록 산이 멀다. 레이더는 소리 대신 전파를 쓴다.

sequenceDiagram
    participant R as 레이더
    participant O as 사람/물체
    R->>O: 전파 송신 (TX)
    O-->>R: 반사파 수신 (RX)
    Note over R: 시간 지연 → 거리<br/>주파수 변화 → 속도<br/>안테나 간 차이 → 각도

전파는 빛과 같은 속도(초속 약 30만 km)로 간다. 그래서 반사파가 돌아오는 시간은 너무 짧아 직접 재기 어렵다. FMCW 는 이 문제를 영리하게 푼다(5.2절).

사람 몸은 전파를 반사한다. 수분이 많아 전파를 잘 반사하는 편이다. 금속은 더 강하게 반사한다. 얇은 플라스틱·종이·일부 벽재는 전파를 상당히 통과시키지만, 콘크리트·금속은 크게 막는다(제품·주파수별 차이).

5.2 FMCW 로 거리 추정: 사이렌 소리의 비유

FMCW(Frequency Modulated Continuous Wave, 주파수 변조 연속파)는 다음과 같이 이해한다.

비유: 구급차의 사이렌이 "삐~~~" 하고 높은 음으로 계속 올라간다고 하자. 소리를 내보낸 뒤 메아리가 돌아올 때, 그 사이 사이렌 음은 이미 더 높아져 있다. 보낸 음과 돌아온 음의 높낮이 차이를 알면, 메아리가 돌아오는 데 걸린 시간, 즉 거리를 알 수 있다.

flowchart LR
    A["송신: 주파수가 직선으로 상승(chirp)"] --> B[물체에 반사]
    B --> C["수신: 같은 모양, 시간만 늦음"]
    C --> D["송신과 수신을 섞음(mixing)"]
    D --> E["주파수 차이(비트 주파수)"]
    E --> F["비트 주파수 → 거리"]
용어뜻비유
chirp주파수가 오르는 한 번의 전파 신호사이렌 한 번
대역폭chirp 가 훑는 주파수 범위사이렌이 오르는 음역 폭
비트 주파수송신과 수신 주파수의 차이두 음의 높낮이 차이

핵심 관계는 다음 한 가지다.

  • 대역폭이 넓을수록 가까이 붙은 두 물체를 구분하는 능력(거리 분해능)이 좋아진다.

LD2410C 소개 페이지는 거리 해상도가 0.75 m 라고 기재한다(6.1절). 이는 0.75 m 보다 가까이 붙은 두 사람을 구분하기 어렵다는 뜻으로 해석할 수 있다. 그래서 인원 계수에 부적합하다.

5.3 도플러: 지나가는 구급차의 소리 변화

구급차가 다가올 때는 사이렌이 높게, 멀어질 때는 낮게 들린다. 이것이 도플러 효과다. 전파도 마찬가지다. 움직이는 물체에 반사된 전파는 주파수가 미세하게 바뀐다.

  • 다가오는 물체: 반사파 주파수 상승
  • 멀어지는 물체: 반사파 주파수 하락
  • 변화 크기: 속도에 비례

여러 chirp 를 연속으로 보내면, 같은 거리의 물체가 chirp 마다 위상이 조금씩 변하는 것으로부터 속도를 구할 수 있다.

정지한 사람은 어떻게 보이나

정지한 사람도 숨을 쉬고 가슴이 미세하게 움직인다. 일부 존재 감지 모듈은 이 미세 움직임을 별도로 분석해 "정지 표적(stationary target)"으로 보고한다. 이것이 PIR 과 가장 큰 차이다.

구분움직임 표적 (moving)정지 표적 (stationary)
신호큰 도플러 변화미세 움직임, 반사 에너지
특징감지 쉬움감도 설정에 민감, 감지 거리가 더 짧을 수 있음(제품별 확인)
주 오탐 원인선풍기, 커튼, 사람 지나감벽·가구 반사 에너지

완전히 숨을 참고 정지한 물체는 없지만, 정지 표적 감지는 제품 알고리즘과 설정에 크게 좌우되므로 "정지한 사람은 항상 감지된다"고 가정하면 안 된다. 현장 실험이 필수다.

5.4 안테나 배열: 두 귀로 소리 방향 알기

사람은 귀가 두 개라서 소리가 어느 쪽에서 오는지 안다. 소리가 한쪽 귀에 아주 조금 먼저 도착하기 때문이다.

레이더도 수신 안테나(RX)를 여러 개 두면, 같은 반사파가 안테나마다 위상이 조금 다르게 도착한다. 이 위상 차이로 반사체의 방향(각도)을 추정한다.

flowchart TB
    subgraph 안테나 배열
        RX1[RX1]
        RX2[RX2]
        RX3[RX3]
        RX4[RX4]
    end
    W[반사파] --> RX1 & RX2 & RX3 & RX4
    RX1 & RX2 & RX3 & RX4 --> PH[안테나 간 위상차]
    PH --> ANG[각도 추정]
  • 안테나 수가 많을수록, 배열 간격이 적절할수록 각도 분해능이 좋아진다.
  • 거리(FMCW) + 속도(도플러) + 각도(배열)를 합치면 점군(point cloud) 을 만들 수 있다. 점 하나는 "거리, 각도, 속도"를 가진 반사점이다.
  • 송신 안테나(TX)와 수신 안테나(RX) 개수가 늘면 가상 안테나(virtual array)가 생겨 각도 해상도를 더 높일 수 있다. IWR6843ISK 는 TI 설명 기준 4 RX, 3 TX 구성이다(6.3절).

존재 감지 전용 모듈은 보통 안테나 수가 적거나 각도 정보를 외부에 노출하지 않는다. 그래서 "어디에 있는지"는 거리만 알려 주는 경우가 많다.

5.5 24GHz vs 60GHz

항목24GHz 대역60GHz 대역
파장(약)약 12.5 mm약 5 mm
안테나 크기상대적으로 큼작게 만들 수 있음
대역폭규제상 좁은 편(24GHz ISM 대역, 지역별 상이)넓게 쓸 수 있음(예: TI 60-64 GHz)
거리 분해능대역폭이 좁아 상대적으로 낮음대역폭이 넓어 상대적으로 높음
미세 움직임다소 둔감할 수 있음파장이 짧아 미세 움직임에 민감
감쇠대기·물체에 의한 감쇠 적음대기 흡수가 커 장거리에는 불리(산소 흡수 대역)
실내 사용가정·소형 존재 감지 제품에 흔함정밀 센싱·점군 제품에 흔함
제품 가격 경향존재 감지 모듈은 저가개발 보드는 고가인 경우가 많음
규제지역별 인증 필요지역별 인증 필요

위 표는 물리적 일반 경향이다. "60GHz 라서 항상 더 좋다"는 말은 틀리다. 같은 대역이라도 칩, 안테나, 펌웨어 알고리즘에 따라 성능이 크게 달라진다.

규제와 전파 인증(국내 KC 등)은 제품 구매 전에 확인해야 하며, 이 교재는 법무 자문을 대신하지 않는다. 판매용 상용 제품으로 만들 경우 인증 비용이 발생할 수 있다(미확인 — 구매 전 확인).

5.6 레이더의 흔한 오류 원인

원인현상대응
다중 경로 반사벽·천장에 튕긴 전파가 "유령 표적"을 만듦설치 각도 조정, 감지 범위 제한
움직이는 비인체 물체선풍기, 커튼, 화분, 청소로봇거리 구간 마스킹, 감도 조정
가림사람 뒤의 사람, 가구 뒤의 사람설치 위치 변경, 센서 수 증가
금속 근접캐비닛·칠판 프레임 반사금속에서 거리 확보
전원 노이즈불안정한 USB 전원이 오동작 유발안정적인 5V 어댑터 사용
설정 부적합감도 임계값이 부적절현장 튜닝, 문서화

5.7 "모든 mmWave 제품이 같은 정보를 출력하지는 않는다"

이 문장은 이 교재에서 가장 중요한 경고 중 하나다. 같은 "mmWave 레이더"라는 이름이어도 제품이 외부로 주는 정보는 완전히 다르다.

flowchart LR
    ADC["원시 ADC 신호"] --> RP["거리-도플러 처리"]
    RP --> PC["점군 point cloud"]
    PC --> TR["표적 추적 tracking"]
    TR --> PR["존재 / 인원 추정 / 상태"]
    subgraph 개발 보드 영역
        ADC
        RP
        PC
    end
    subgraph 가공 모듈 영역
        TR
        PR
    end
구분원시 point cloud 출력 보드가공된 presence 출력 모듈
대표 예TI IWR6843ISK (개발 보드)HLK-LD2410C (존재 감지 모듈)
사용자가 받는 것점군, (추가 장비 사용 시) 원시 신호까지존재 여부, 움직임/정지 구분, 거리, 에너지 값
알고리즘사용자가 구현하거나 TI 예제 사용제조사 펌웨어가 내장
개발 난이도높음낮음
자유도높음낮음 (제조사가 허용한 설정 범위 내)
인원 계수알고리즘을 구현해야 가능성 있음불가 (LD2410C)
가격높음낮음
적합 단계연구, 고급 확장MVP

따라서 "mmWave 레이더로 인원수 센다"는 문장은 불완전하다. 어떤 제품이, 어떤 펌웨어로, 어떤 출력을 주는지를 반드시 확인해야 한다. 점군을 주는 보드는 인원 계수 알고리즘을 만들 수 있는 재료를 줄 뿐이고, 그 알고리즘의 정확도는 직접 검증해야 한다.

06레이더 장치 비교 및 학원 적합성

HLK-LD2410C 존재 감지 전용 · 최대 5m 존재 감지 O · 인원 계수 X 난이도 하 · 가격 수천 원대 이번 실습 선택 HLK-LD2450 다중 표적 추적 · 최대 6m 최대 3개 표적 좌표 추적 난이도 중 · 혼잡하면 한계 "몇 명이 어디에" 가 필요해지면 다음 단계로 검토 TI IWR6843ISK 60GHz 개발 보드 · 점군 출력 원시 point cloud · 알고리즘 자유 난이도 상 · 가격 수십만 원대 연구·정밀 인원 추정 단계에서 도입 검토 (6장 본문) 같은 "mmWave" 라도 출력이 전혀 다르다 — 존재 신호 / 표적 좌표 / 원시 점군
레이더 모듈 3종 — 가격이 아니라 "어떤 출력이 필요한가"로 고른다.
확인일: 2026-10-18. 제조사 공식 페이지를 우선하되, 공식 데이터시트 PDF 에 접근하지 못한 값은 출처를 구분해 표기한다. 접근 못 한 값은 미확인으로 둔다.
사양은 펌웨어·판매처 버전에 따라 다를 수 있으므로 구매 전 최신 데이터시트를 직접 확인한다.

6.1 HLK-LD2410C — 존재 감지 전용

확인된 사양

항목값출처 구분
대역24GHz ISM 대역제조사 공식 소개 페이지 (확인 2026-10-18)
공급 전압5-12 V제조사 공식 소개 페이지
최대 감지 거리5 m제조사 공식 소개 페이지
거리 해상도0.75 m제조사 공식 소개 페이지
감지 각도최대 ±60도제조사 공식 소개 페이지
출력GPIO 및 UART제조사 공식 소개 페이지
감지 대상움직이는 사람과 정지한 사람제조사 공식 소개 페이지
크기최소 16 mm x 22 mm제조사 공식 소개 페이지
설치천장·벽 설치 옵션 언급제조사 공식 소개 페이지
기본 UART 설정256000 baud, 정지 비트 1, 패리티 없음2차 출처(OpenELAB 안내 글), 공식 데이터시트 직접 확인 필요
I/O 로직 레벨3.3 V2차 출처(OpenELAB 안내 글), 공식 데이터시트 직접 확인 필요
권장 전원 용량5V, 200 mA 초과2차 출처(OpenELAB), 공식 확인 필요
동작 전류79 mA 로 언급한 글 있음2차 출처 일부 언어판에만 존재, 미확인 — 구매 전 확인
시리얼 프로토콜 문서Hi-Link 시리얼 통신 프로토콜 v1.00 PDF (제3자 호스팅)목차만 확인

출처 URL

이 교재의 배선 전제와 확인 필요 사항

SPEC 의 배선은 모듈 전원을 ESP32 VIN(5V)에서 받고, UART 를 GPIO16/17 에 직결한다. 2차 출처 기준 I/O 가 3.3V 이므로 직결이 가능한 구성이지만, 공식 데이터시트의 로직 레벨 확인은 구매 후 배선 전에 반드시 수행한다. 확인되기 전에는 TX 신호를 5V 로 가정하고 레벨 변환기 사용을 고려한다. ESP32 GPIO 에 5V 신호를 직접 입력하지 마라.

기능 요약(존재 감지의 의미)

LD2410C 는 "사람이 있는가, 움직이는가 정지해 있는가, 대략 어느 거리인가"를 알려 주는 모듈이다. 거리 구간(게이트)별 감도를 설정할 수 있다고 제조사가 소개한다(range division). 관측 JSON 의 presence, moving_target, stationary_target, detect_distance_cm 가 이 출력에서 온다.

학원 적합성

장점한계
정지한 사람을 감지하는 기능이 있다(제품 기능 범위)정확한 인원 계수를 할 수 없다
영상·음성이 없다두 사람이 가까우면 하나로 보인다 (거리 해상도 0.75 m 기준)
저가, UART 로 쉬운 연동방향·좌표를 쓸 수 없다(거리 중심)
ESP32 라이브러리 존재(ld2410, ncmreynolds)가림·다중 경로 반사에 취약
경고: LD2410C 로 정확한 인원 계수를 시도하지 마라. 이 모듈은 "있음/없음"과 "거리"를 보고하는 장치이며 사람 수를 세는 장치가 아니다. 표시되는 거리 하나를 "인원 1명"으로 해석하는 순간 모든 후속 분석이 오염된다. 인원이 필요하면 출결 기록(층3)을 쓰거나 다른 센서를 검토한다(18장).

6.2 HLK-LD2450 — 제한적 다중 표적 추적

확인된 사양

항목값출처 구분
최대 표적 수3ESPHome 공식 문서: "A maximum of 3 targets can be detected" (확인 2026-10-18). 제조사 소개 페이지에는 최대 표적 수가 명시되지 않음
최대 감지 거리6 m제조사 소개 페이지 (확인 2026-10-18)
감지 각도방위 ±60도, 피치 ±35도제조사 소개 페이지
출력시리얼 출력(감지 데이터)제조사 소개 페이지
기본 UART256000 baud, 패리티 없음, 정지 비트 1ESPHome 공식 문서 (제3자 문서)
권장 펌웨어V2.02.23090617 이상 (ESPHome 통합 기준)ESPHome 문서
공급 전압5V (4.5-5.5V) 로 기재한 판매처 자료 있음2차 출처(ShillehTek), 공식 미확인 — 구매 전 확인
로직 레벨3.3V TTL 로 기재한 판매처 자료 있음2차 출처, 공식 미확인
보고 주기약 10Hz 로 기재한 판매처 자료 있음2차 출처, 공식 미확인
평균 전류약 90 mA 로 기재한 판매처 자료 있음2차 출처, 공식 미확인

출처 URL

학원 적합성

장점한계
표적의 위치(좌표)와 속도를 받을 수 있다고 소개됨최대 3 표적이면 20명 강의실의 인원 계수에는 부족
출입구 통과 방향 판단에 활용할 여지(실험 필요)표적 병합·소실이 일어날 수 있음 (가림, 근접)
영상 없음좌표 이력은 위치 정보이므로 보관 기간·목적을 제한해야 함

LD2450 은 "작은 공간의 소수 인원 추적"에 맞는 제품이다. 학원 강의실 전체 인원을 세는 용도는 아니며, 출입구·상담실처럼 인원이 적은 구역의 보조 센서로 검토할 수 있다. 이는 이 교재의 MVP 구매 대상이 아니다.

6.3 TI IWR6843ISK — 고급 개발 보드

확인된 사양

항목값출처 구분
대역60-64 GHzTI 공식 제품 페이지 (확인 2026-10-18)
안테나4 RX, 3 TXTI 공식 페이지
시야각방위 120도, 고도 30도 (ISK 공식 페이지 기준)TI 공식 페이지
인터페이스60핀 고속 인터페이스(호스트 제어), USB 를 통한 점군 데이터 접근TI 공식 페이지
전원USB 로 전원 공급, 보드 내 전력 소모 모니터링TI 공식 페이지
필수/관련 보드MMWAVEICBOOST(소프트웨어 개발·디버그용 캐리어), 원시 ADC 캡처에는 DCA1000EVMTI 공식 페이지
소프트웨어mmWave Studio, mmWave SDKTI 공식 페이지
UART 상세, 정확한 전압·전류미확인 — 구매 전 확인사용자 가이드·회로도 필요
가격미확인 — 구매 전 확인조사 안 함

참고: TI 검색 결과 중에는 같은 칩 계열의 다른 보드(예: IWR6843ISK-ODS, IWR6843AOPEVM)도 나타났다. 이름이 비슷하므로 안테나 구성과 시야각이 다른 제품이라는 점을 구분해서 구매한다. 위 표의 값은 IWR6843ISK 페이지 기준이다. 검색 요약에서는 120도 고도 시야각이 보였으나 페이지 직접 열람에서는 30도로 확인되었으며, 제품 혼동 가능성이 있으므로 구매 전 TI 공식 페이지의 해당 제품 사양을 다시 확인한다.

출처 URL

학원 적합성

장점한계
점군 접근이 가능해 인원 계수·추적 알고리즘 개발 재료가 됨알고리즘을 직접 구현하거나 TI 예제를 학습해야 함(개발 난이도 높음)
60GHz 대역폭 이점가격이 높음
연구·성능 상한 탐색에 유용신원·집중도 판단은 여전히 불가
개발 보드이므로 상시 운영용 장치로 쓰기에는 제품화 작업이 추가로 필요

MVP 구매 대상이 아니다. 1개 강의실 실증에서 LD2410C 의 한계가 명확히 확인된 뒤, 인원 계수가 정말 필요한지 사업적으로 판단하고 나서 도입을 검토한다(18장).

6.4 세 장치 비교

항목HLK-LD2410CHLK-LD2450TI IWR6843ISK
성격존재 감지 모듈다중 표적 추적 모듈개발 보드
대역24GHz24GHz(추정, 제조사 문서 확인 필요)60-64 GHz
최대 표적 수개수 보고 없음(존재/거리)3 (ESPHome 문서)점군(개수는 알고리즘에 의존)
최대 거리5 m6 m미확인
거리 해상도0.75 m미확인미확인 (설정 의존)
각도 정보없음(거리 중심)좌표 제공점군에 각도 포함
인터페이스UART, GPIOUARTUSB, 60핀
전원5-12V (제조사)5V 로 기재한 2차 자료, 미확인USB 전원(제조사), 상세 미확인
인원 계수불가제한적 (최대 3)알고리즘 구현 시 가능성
개발 난이도낮음중간높음
가격미확인 — 구매 전 확인미확인 — 구매 전 확인미확인 — 구매 전 확인
MVP 채택채택확장 논의용확장 논의용
flowchart TB
    Q{필요한 정보는?}
    Q -->|방에 사람이 있는가| A[LD2410C]
    Q -->|소수 인원의 위치·방향| B[LD2450]
    Q -->|다수 인원 계수·고급 연구| C[IWR6843ISK 등 + 알고리즘]
    Q -->|정확한 출석| D[출결 시스템]

6.5 학원 적합성 결론

  1. MVP 는 LD2410C 로 "공간 사용 여부"만 다룬다. 이것이 센서가 책임질 수 있는 범위다.
  2. 인원 계수는 센서로 해결하기 전에 사업 가치를 따진다. 출결이 이미 사람 수를 알려 주고 있다면, 센서로 같은 정보를 다시 만들 필요가 없을 수 있다.
  3. 고급 레이더 도입 결정 근거는 1개 강의실 실증에서 얻은 오탐·미탐 데이터여야 한다(15장, 17장).

07학원 공간별 센싱 설계

출입문 LD2410C — 전면 모서리, ±60° 가 책상 전체를 보게 수납장 가구 뒤 사각지대 SCD41 문·창·에어컨 바람 피해서 벽 중간 높이 책상(학생) — 정지 상태가 많아 "미세 움직임 감지"가 핵심 (7장 본문)
강의실 배치 예 — 레이더는 전면 상단 모서리, CO₂ 는 기류를 피한 벽면.

이 장은 공간 유형별로 무엇을 측정하고 어디에 설치하며 센서를 몇 개 두는지 판단하는 방법을 다룬다. 공간 유형은 DB 의 spaces.type(classroom, entrance, hallway, office, facility)과 일치한다.

이 장의 판단은 설계 가이드이며 검증된 성능이 아니다. 현장마다 구조가 다르므로 각 설치 후 15장의 실험을 수행한다.

7.1 공통 설계 원칙

원칙내용
목적 우선공간마다 "무엇을 결정하려는가"를 한 문장으로 쓴다
최소 수집존재와 환경만 수집하고 개인을 추적하지 않는다
사각지대 인정센서는 모든 곳을 보지 못한다. 사각지대를 도면에 표시한다
측정으로 확인설치 후 사람이 걸어 보며 감지 영역을 확인한다
수정 가능성위치를 옮길 수 있게 임시 고정부터 시작한다
안내센서 설치 사실과 수집 항목을 구성원에게 알린다(16장)
flowchart LR
    A[도면 확보] --> B[목적 정의]
    B --> C[센서 후보 위치]
    C --> D[감지 범위·사각지대 표시]
    D --> E[임시 설치]
    E --> F[걸어서 감지 확인]
    F --> G{문제?}
    G -->|예| C
    G -->|아니오| H[고정 설치·기록]

7.2 출입구

목적

  • 입퇴실 시점 추정, 사용 시작/종료 시각, 출입 통과 감지

센서 후보

센서용도비고
도어 마그네틱 센서문 열림/닫힘신뢰도 높은 접점 정보
mmWave 존재 감지출입구 앞 존재움직임 방향·개수는 LD2410C 로 판별 불가
ToF 다중 구역통과 방향·계수확장 논의. 동시 통과 오차 가능
QR/NFC출결(층3)개인정보로 취급. 별도 설계

설계 포인트

  • 문 근처는 문이 열릴 때 복도 쪽 움직임이 센서에 잡혀 오탐이 나기 쉽다. 센서를 문 바깥을 향하지 않게 하고 감지 거리를 제한한다.
  • 입구 센서의 목적은 "누가 들어왔나"가 아니라 "언제 사람이 드나들었나"이다.
  • 방향이 중요하면 LD2410C 로는 부족하다. 도어 센서와 존재 이벤트의 시간 순서로 추정하되 그것이 추론이라는 점을 표시한다.

7.3 강의실

강의실은 이 교재의 핵심 공간이다. 센서 설계의 어려움이 모두 모여 있다.

강의실 특유의 문제

문제설명영향
책상 밀집학생들이 가까이 앉음거리 해상도(예: LD2410C 0.75 m)보다 가까워 개별 구분 불가 → 인원 계수 불가
정지 학생수업 중 대부분 앉아 거의 움직이지 않음움직임 기반 센서(PIR) 미탐. 정지 표적 감도 설정이 핵심
벽·가구 가림칠판, 사물함, 파티션뒤쪽 학생이 안 보임, 사각지대 발생
다중 경로 반사벽·칠판 프레임·유리에 반사유령 표적, 거리 값 흔들림
움직이는 비인체선풍기, 에어컨 바람, 커튼오탐
불규칙 사용쉬는 시간 대량 이동상태 변화가 잦아 이벤트 규칙의 창 설정이 중요
flowchart TB
    subgraph 강의실 평면 개념도
        direction TB
        T[칠판] 
        D1[책상 줄 1]
        D2[책상 줄 2]
        D3[책상 줄 3]
        DR[출입문]
    end
    T --- D1 --- D2 --- D3 --- DR

설치 위치 비교

위치장점단점적합
천장 중앙 아래 향함가림이 적고 전체를 균등하게 봄시공·전원 어려움, 높이에 따른 감지 영역 확인 필요신축·정식 설치
벽 상단 모서리전원 확보 쉬움, 대각선 시야먼 쪽 사각지대, 뒤쪽 가림시제품·MVP
칠판 쪽 벽학생을 정면으로 봄칠판 금속 프레임 반사 가능, 교사 이동이 많이 잡힘교사 활동 제외 필요 시 불리
뒷벽교사 이동 영향 적음앞줄이 먼 거리에 있어 감도 저하 가능소형 강의실
책상 위/아래수치 가까움학생 접촉·훼손, 가림비권장

LD2410C 는 소개 페이지 기준 최대 거리 5 m, 각도 ±60도다. 이 범위 안에서 실제 감지가 되는지는 설치 후 걸어서 확인한다. 정지 표적 감지 거리는 최대 거리보다 짧을 수 있다(제품별 확인, 실험 필요).

감지 범위와 사각지대

센서 위치를 정했다면 다음 순서로 확인한다.

  1. 도면에 센서 위치와 화각(±60도 부채꼴)을 그린다.
  2. 가구·칠판·사물함의 위치를 겹쳐 그려 가려지는 영역을 표시한다.
  3. 학생이 앉는 자리 중 부채꼴 밖 또는 가려진 자리를 사각지대로 표시한다.
  4. 임시 설치 후 각 자리에 사람이 앉아 정지 상태를 유지하며 stationary_target 이 true 로 유지되는지 확인한다.
  5. 결과를 표로 기록한다. 아래는 기록 양식 예시이며 실측 값이 아니다.
자리거리(m, 줄자 측정)정지 감지 여부비고
(예시) A1(측정 후 기록)(측정 후 기록)
(예시) B3(측정 후 기록)(측정 후 기록)

공간당 센서 수 판단

조건판단
한 개 센서의 유효 범위가 방 전체를 덮음1개
방이 길거나 L자형, 뒤쪽 사각지대가 큼2개 이상 검토
목적이 "방 사용 여부"1개로도 충분한 경우가 많음 (실험으로 확인)
목적이 "구역별 사용"구역 수만큼 센서 필요
목적이 "정확한 인원"센서 수를 늘려도 LD2410C 로는 해결 안 됨. 다른 접근 필요

센서를 여러 개 둘 경우 서로의 전파가 간섭할 수 있는지 데이터시트와 실험으로 확인한다(미확인 — 구매 전 확인). 같은 방에 두 센서를 두면 장치 ID 를 구분한다(academy_room_A_01, academy_room_A_02).

CO₂ 센서 위치

  • 학생 호흡 높이에 가깝고 공기가 순환하는 곳.
  • 창문, 문, 에어컨 송풍구 바로 앞은 피한다.
  • 사람의 입김이 직접 닿는 거리는 피한다.
  • 한 방에서 위치마다 값이 다를 수 있다. 대표 위치 한 곳을 정하고 일관되게 쓴다.

7.4 복도

목적

  • 이동 감지, 쉬는 시간 혼잡 정도(간접), 야간 침입 알림(옵션)

설계 포인트

항목내용
센서mmWave 존재 감지, 도어 센서, PIR(이동 감지에는 적합)
설치천장 또는 복도 끝 벽. 직선 구조라 센서 축을 복도 방향으로 맞추면 먼 거리까지 볼 수 있음
특징사람이 정지하지 않고 지나간다 → PIR 이 적합해지는 드문 공간
주의벽 반대편 강의실의 움직임을 얇은 벽 너머로 감지할 수 있음 → 감지 거리 제한 필요
프라이버시학생 동선 추적이 되지 않게 이력을 최소화

복도 센서는 MVP 필수 아님. 강의실 센서의 오탐 원인(벽 너머 복도 움직임)을 이해하는 대조군으로 쓸 수는 있다.

7.5 사무실

목적

  • 직원 부재/재실, 상담실 사용 여부, 환기

설계 포인트

항목내용
센서mmWave 존재 감지(앉아서 일하는 정지 상태 감지에 유리), CO₂
주의직원은 근로자다. 재실 데이터를 근태 감시에 쓰면 노동 관련 문제와 신뢰 문제가 생긴다
원칙개인별이 아니라 공간 단위로만 집계. 개인 근태 용도 금지를 문서화
상담실학부모 상담 내용이 있으므로 마이크·카메라 금지, 사용 여부 Boolean 정도만

7.6 시설(화장실·탈의·휴게)

프라이버시 주의 (최우선)

공간허용 가능한 수집금지
화장실문 잠금/열림, 환기·악취 지표(VOC), 사용 중 여부(최소)카메라, 마이크, 위치 좌표 이력, 사람 식별
탈의·샤워설치하지 않는 것을 원칙으로 한다모든 센서 (필요성이 분명하고 법적 검토를 마친 경우만 예외)
휴게실CO₂, 온습도, 존재 여부(공간 단위)영상, 음성
서버실/창고온습도, 도어, 침입 알림해당 없음

화장실에 레이더를 설치하는 경우에도 다음을 지킨다.

  1. 설치 목적을 문서화하고 이용자(학생, 학부모, 직원)에게 안내한다.
  2. 저장 항목은 존재 여부 Boolean 으로 제한하고 좌표·거리 이력은 저장하지 않는다.
  3. 법적 검토를 거치고 가능하면 대안(도어 센서)을 먼저 쓴다.

spaces.type = facility 공간의 데이터는 일반 강의실보다 더 짧은 보관 기간을 기본값으로 한다(16장).

7.7 공간별 요약표

공간주 목적우선 센서설치 위치센서 수(초기 판단)주요 위험
출입구출입 시점도어 센서, 존재 감지문 상단/옆 벽1문 열림 오탐, 방향 판별 한계
강의실사용 여부, 환기mmWave 존재 + CO₂벽 상단 모서리(MVP), 천장(정식)1에서 시작, 사각지대에 따라 증가정지 학생, 가림, 다중 경로
복도이동·야간PIR, 존재 감지천장 또는 끝 벽구간별 1벽 너머 감지
사무실재실, 환기mmWave 존재 + CO₂천장 또는 벽1근태 감시로의 오용
시설사용 중, 환기도어, VOC문, 천장최소프라이버시

7.8 MVP 첫 설치 권장 절차

  1. 가장 단순한 강의실 하나를 고른다(가구가 적고 벽이 단단하며 전원이 가깝다).
  2. ESP32 + LD2410C + SCD41 을 브레드보드로 구성하고 벽 상단 모서리에 임시 고정한다(배선은 11장, 핀맵은 4번 문서에 따름).
  3. 사람이 직접 걸어 다니며 감지 영역을 확인한다.
  4. 정지 상태로 자리별 감지를 확인한다.
  5. 선풍기, 에어컨, 문 열림 같은 방해 요인을 의도적으로 만들어 오탐을 기록한다.
  6. 위치·감도를 조정하고 변경 내용을 기록한다.
  7. 2주 이상 데이터를 수집하고 사람이 센 기록과 비교한다(15장).

이 단계에서 얻은 오탐·미탐 데이터가 이후 센서 추가·교체 결정의 유일한 근거다.

08센서 퓨전과 공간 상태 추정

센서 하나가 보내는 값은 "그 순간의 관측"일 뿐이다. 학원 운영자가 알고 싶은 것은 "A강의실이 지금 수업 중인가", "환기가 필요한가" 같은 공간의 상태다. 이 장은 관측을 상태로 바꾸는 과정을 다룬다. 센서 종류와 원리는 3~5장, 공간별 배치는 7장을 참고한다.

8.1 세 가지를 구분한다

구분의미예신뢰 수준
관측(Observation)센서가 보낸 원시 측정값presence: true, co2_ppm: 850센서 오차를 포함
상태(State)규칙이 계산한 공간의 현재 상황"사용 중", "환기 필요"규칙과 임계값에 의존
사실(Fact)사람이 입력·검증한 기록출결 기록, 시간표가장 높음

세 가지를 섞으면 안 된다. 특히 다음 한계를 항상 기억한다.

  • CO₂만으로 정확한 인원수를 계산할 수 없다.
  • HLK-LD2410C로 정확한 인원 계수를 할 수 없다. 존재/움직임 감지 전용이다.
  • 레이더만으로 신원이나 집중도를 판단할 수 없다.

8.2 처리 파이프라인

flowchart LR
    A["센서 수집<br/>LD2410C, SCD41"] --> B["시간 동기화<br/>observed_at, received_at"]
    B --> C["정규화<br/>단위, 필드명, 스키마"]
    C --> D["품질 검증<br/>quality 플래그, 범위, 결측"]
    D --> E["상태 추정<br/>규칙 + 시간표 + 출결"]
    E --> F["이벤트 생성<br/>ROOM_OCCUPIED 등"]
    F --> G["서비스 실행<br/>대시보드, 알림, AI 질의"]

단계별 역할은 다음과 같다.

  1. 센서 수집: ESP32가 LD2410C(UART)와 SCD41(I²C)을 읽는다. 구현은 12장.
  2. 시간 동기화: 장치가 찍은 observed_at과 서버가 찍은 received_at을 둘 다 보존한다. 장치 시계가 틀리거나 전송이 지연되어도 추적할 수 있다.
  3. 정규화: 단위(cm, ppm, ℃, %)와 필드명을 스키마(9장)에 맞춘다.
  4. 품질 검증: quality 플래그, 값 범위, 결측, 중복(seq)을 확인한다. 검증을 통과하지 못한 값은 상태 추정에서 제외하거나 가중치를 낮춘다.
  5. 상태 추정: 규칙으로 현재 상태를 계산한다. 구현은 14장.
  6. 이벤트 생성: 상태가 바뀌거나 조건이 충족되면 events에 기록한다.
  7. 서비스 실행: 대시보드 표시, 알림, AI 질의 응답에 쓴다. 구조는 10장.

8.3 예시: 강의실 상태 표현

네 가지 정보를 결합해 강의실 상태를 표현하는 예다. 아래 값은 모두 예시다.

입력출처종류예시 값
레이더 존재 감지LD2410C관측최근 5분 presence true 비율 90%
CO₂SCD41관측1,250 ppm, 12분 지속
강의실 시간표class_sessions사실(계획)16:00-17:30 수학
출결 기록학원 시스템(MVP 범위 밖)사실(검증)출석 8명

결합 결과의 예시는 다음과 같다.

{
  "space": "A강의실",
  "state": "IN_USE",
  "flags": ["VENTILATION_NEEDED"],
  "confidence": "high",
  "reasons": [
    "radar: 최근 5분 presence 비율 >= 60% (관측)",
    "schedule: 16:00-17:30 수학 수업 시간 (사실-계획)",
    "co2: 1200 ppm 초과 10분 지속 (관측)"
  ],
  "not_claimed": ["정확한 인원수", "학생 신원", "집중도"]
}

여기서 not_claimed 필드는 시스템이 주장하지 않는 것을 명시한다. 레이더가 "사람이 있다"고 말할 뿐 "8명이 있다"고 말하지 않는다는 점을 응답에 남겨 두면 오해를 줄인다. 출결 기록과 연결하는 것은 MVP 이후 확장이며 17장에서 다룬다.

8.4 규칙 기반 vs 추론

구분규칙 기반추론(통계·머신러닝)
방식"CO₂ > 1200이 10분 지속이면 환기 필요"과거 데이터로 패턴을 학습해 확률 계산
설명 가능성높음. 왜 그런지 말할 수 있다낮을 수 있음
필요한 데이터거의 없음정답(라벨) 데이터가 충분히 필요
실패 양상임계값 근처에서 경계 오류학습 범위 밖에서 예측 불가
본 교재의 위치MVP의 기본데이터가 쌓인 뒤 확장(17장)

권장 순서는 이렇다. 먼저 규칙으로 시작한다. 2주 이상 데이터를 모으고 오탐·미탐을 측정한 뒤(15장), 규칙으로 부족한 부분에만 추론을 검토한다. 사람 수 추정 같은 추론 결과는 반드시 "추정"으로 표시하고, 출결 기록과 같은 등급으로 취급하지 않는다.

8.5 신뢰도 개념

신뢰도는 "이 상태를 얼마나 믿어도 되는가"를 나타낸다. 본 교재는 복잡한 확률 대신 세 단계 라벨을 쓴다.

신뢰도조건(초기 설계)
high필요한 센서 모두 quality 정상 + 서로 다른 근거 2개 이상이 일치
medium근거 1개만 있거나, 근거끼리 부분적으로 어긋남
low센서 일부 오류, 데이터 지연, 근거가 서로 충돌

예를 들어 시간표상 수업 시간인데 레이더가 "비어 있음"을 보고하면 근거가 충돌한다. 이때 시스템은 어느 한쪽을 임의로 골라 확정하지 않고 low로 표시하고 사람이 확인하게 한다. 이 라벨의 기준도 초기값이며 실측 후 조정한다.

09데이터 아키텍처와 표준 스키마

9.1 테이블 관계

7개 테이블의 관계는 다음과 같다. 개념 이름(Academy, Space, Device, Person, ClassSession, Observation, Event)은 각각 academies, spaces, devices, persons, class_sessions, observations, events에 대응한다. 상세 DDL은 database/schema.sql을 참고한다.

erDiagram
    academies ||--o{ spaces : "has"
    academies ||--o{ persons : "has"
    spaces ||--o{ devices : "hosts"
    spaces ||--o{ class_sessions : "schedules"
    spaces ||--o{ events : "has"
    devices ||--o{ observations : "sends"

    academies {
        int id PK
        text name
        timestamptz created_at
    }
    spaces {
        int id PK
        int academy_id FK
        text name
        text type "classroom|entrance|hallway|office|facility"
        numeric area_m2 "null 허용"
    }
    devices {
        int id PK
        int space_id FK
        text device_id UK
        text model
        text firmware_version
        timestamptz installed_at
        boolean active
    }
    persons {
        int id PK
        int academy_id FK
        text role "student|teacher|staff"
        text name_masked
        timestamptz consent_at
    }
    class_sessions {
        int id PK
        int space_id FK
        text name
        timestamptz starts_at
        timestamptz ends_at
        int expected_headcount
    }
    observations {
        bigserial id PK
        int device_id FK
        int seq
        timestamptz observed_at
        timestamptz received_at
        jsonb measurements
        jsonb quality
        text schema_version
    }
    events {
        int id PK
        int space_id FK
        text type
        text severity
        timestamptz started_at
        timestamptz ended_at "null 허용"
        text source
        jsonb evidence
        timestamptz created_at
    }
  • observations에는 UNIQUE(device_id, seq) 제약이 있다. 같은 장치가 같은 seq를 다시 보내도 한 번만 저장된다(13장).
  • persons는 MVP에서 테이블만 두고 센서 데이터와 연결하지 않는다. 개인정보 최소 수집이 기본값이다(16장).
  • events.source는 이벤트를 만든 주체(예: 규칙 엔진)를 기록한다.

9.2 관측 JSON 표준

장치가 서버로 보내는 JSON(POST /api/v1/observations)은 다음과 같다. 필드명과 구조는 고정이다.

{
  "device_id": "academy_room_A_01",
  "seq": 1024,
  "observed_at": "2026-10-18T16:30:00+09:00",
  "firmware_version": "0.1.0",
  "schema_version": "1.0",
  "measurements": {
    "presence": true,
    "moving_target": false,
    "stationary_target": true,
    "detect_distance_cm": 210,
    "co2_ppm": 850,
    "temperature_c": 24.5,
    "humidity_percent": 48.2
  },
  "quality": {
    "radar_valid": true,
    "co2_valid": true
  }
}

규칙은 다음과 같다.

  • seq는 장치가 증가시키는 번호다. 재전송 시 중복 제거에 쓴다.
  • received_at은 서버가 기록한다. 장치는 보내지 않는다.
  • 측정에 실패한 필드는 null로 보내고, 해당 quality 플래그를 false로 둔다.
  • 전송 주기는 30초 고정이며, presence 상태가 바뀌면 즉시 1회 더 보낸다.

9.3 보존하는 항목과 그 이유

항목보존 이유
device_id (source)어느 장치가 보냈는지 추적. 장치 교체·고장 분석에 필요
observed_at측정 시각. 상태 계산의 시간 기준
received_at수신 시각. 지연·시계 오차·미수신 판단(SENSOR_FAULT)에 필요
단위필드명에 단위를 넣는다(_cm, _ppm, _c, _percent). 나중에 단위가 헷갈려 잘못 계산하는 일을 막는다
quality값을 믿어도 되는지의 표시. 오류 값이 상태 계산에 섞이지 않게 한다
schema_version필드가 바뀌어도 과거 데이터를 올바르게 해석
firmware_version동작 차이가 생겼을 때 원인을 펌웨어까지 좁힘
오류 상태(null + 플래그)"값이 0"과 "측정 실패"를 구별. 실패를 0으로 저장하면 평균·임계값이 왜곡된다
원시 measurements (JSONB)규칙이 바뀌어도 과거 원자료로 재계산 가능

핵심 원칙은 원자료를 지우거나 고쳐 쓰지 않는다는 것이다. 정제와 해석은 상태·이벤트 계층에서 하고, 관측 테이블은 받은 그대로 둔다. 이벤트의 evidence(JSONB)에는 판단 근거가 된 관측 요약을 남겨, 나중에 "왜 이 이벤트가 생겼나"를 되짚을 수 있게 한다.

10통신·엣지·서버 아키텍처

10.1 UART와 I²C 기초

항목UARTI²C
선 수2개(TX, RX) + GND2개(SDA, SCL) + GND
연결 대상1:11:다 (주소로 구분)
속도설정값(baud)을 양쪽이 맞춰야 함. LD2410C 기본 256000 bps표준 100 kHz, 고속 400 kHz가 일반적
거리짧은 배선(수십 cm~수 m 수준). 길어지면 노이즈기판·브레드보드 수준(수십 cm 이내 권장)
본 실습 용도LD2410C ↔ ESP32 (GPIO16/GPIO17)SCD41 ↔ ESP32 (GPIO21/GPIO22, 주소 0x62)
  • UART는 TX와 RX를 교차 연결한다. 장치의 TX가 ESP32의 RX로 간다.
  • I²C 선 길이·풀업 저항 등 세부 한계는 데이터시트 확인 후 설치한다(04_wiring_and_installation.md).
  • 두 방식 모두 3.3V 로직 기준으로 쓴다. 5V 신호를 ESP32 GPIO에 직접 넣지 않는다(11장).

10.2 Wi-Fi

ESP32 DevKit V1(ESP32-WROOM-32)은 Wi-Fi를 내장한다. 일반적으로 2.4GHz 대역을 쓴다. 학원 공유기의 SSID·암호는 config.h에 분리해 두고 소스 저장소에 올리지 않는다. 신호 세기, 공유기 위치, 벽 재질에 따라 끊길 수 있으므로 재접속 로직이 필수다(12장).

10.3 HTTP vs MQTT

항목HTTP POST (기본)MQTT (확장)
방식요청-응답브로커 경유 발행-구독
구성 요소장치 + 서버장치 + 브로커 + 구독 서비스
구현 난이도낮음중간(브로커 운영 필요)
응답 확인즉시(상태 코드)QoS 설정에 따름
적합한 규모장치 수 적고 30초 주기장치 수 많거나 양방향 제어·실시간성 필요

기본은 HTTP다. 30초 주기로 JSON 한 건을 보내는 구조에는 HTTP로 충분하고, 디버깅이 쉽고, 서버 코드도 단순하다. 다음 신호가 보이면 MQTT를 검토한다.

  • 장치가 수십 대로 늘어 서버 연결 관리가 부담될 때
  • 서버가 장치에 명령을 내려야 할 때(설정 변경, 재시작)
  • 전송 주기를 크게 줄여야 할 때
  • 네트워크가 불안정해 재전송·보장 수준(QoS) 제어가 필요할 때

전환 시점은 12주 로드맵의 확장 단계(9~12주)에서 판단한다(17장).

10.4 Edge Gateway의 역할

Edge Gateway는 센서 노드와 서버 사이에 놓이는 중간 장치(예: 학원 내 소형 PC)다. 본 MVP는 ESP32가 서버로 직접 보내므로 게이트웨이를 쓰지 않는다. 확장 시 맡을 역할은 다음과 같다.

  • 여러 장치 데이터를 모아 한 번에 전송
  • 인터넷 단절 시 임시 저장 후 재전송
  • 학원 내부에서만 쓰는 데이터와 외부로 보낼 데이터의 분리(개인정보 최소화)
  • 로컬 규칙 실행(서버 단절 시에도 동작)

10.5 전체 아키텍처

flowchart TB
    subgraph L1["센서 계층"]
        S1["HLK-LD2410C<br/>존재 감지"]
        S2["SCD41<br/>CO2, 온습도"]
    end
    subgraph L2["엣지 계층"]
        E1["ESP32 DevKit V1<br/>수집, JSON 생성, Wi-Fi 전송"]
        E2["Edge Gateway<br/>(확장 단계, 선택)"]
    end
    subgraph L3["서버 계층"]
        B1["FastAPI<br/>수신, 검증"]
        B2[("PostgreSQL 16<br/>observations, events")]
        B3["상태·이벤트 엔진<br/>규칙 기반"]
    end
    subgraph L4["서비스 계층"]
        V1["대시보드<br/>Chart.js"]
        V2["상태 조회 API<br/>이벤트 이력 API"]
        V3["AI 질의 응답"]
    end
    S1 -- UART --> E1
    S2 -- "I2C" --> E1
    E1 -- "HTTP POST JSON" --> B1
    E1 -.-> E2
    E2 -.-> B1
    B1 --> B2
    B2 --> B3
    B3 --> B2
    B2 --> V1
    B2 --> V2
    V2 --> V3

실선이 MVP 경로, 점선이 확장 경로다. 서비스 계층의 AI는 데이터베이스를 직접 읽지 않고 API를 통해서만 접근한다.

10.6 AI가 검증된 데이터로만 답하는 구조

LLM에게 원시 센서 로그를 통째로 읽히고 "지금 강의실에 사람 있어?"라고 물으면 위험하다. 결측값, 중복, 오류 값을 모델이 스스로 걸러내지 못할 수 있고, 근거 없이 그럴듯한 답을 만들 수 있다. 그래서 구조를 다음과 같이 나눈다.

flowchart LR
    Q["사용자 질문"] --> AI["AI"]
    AI -- "도구 호출" --> API["상태 조회 API<br/>이벤트 이력 API"]
    API --> ST["검증된 상태<br/>quality, 신뢰도 포함"]
    API --> EV["이벤트 이력<br/>evidence 포함"]
    ST --> AI
    EV --> AI
    AI --> A["답변<br/>근거와 한계 함께 제시"]
  • 품질 검증과 상태 계산은 결정적인 코드(14장)가 한다. AI는 계산하지 않는다.
  • AI는 API가 돌려준 상태·이벤트·evidence·신뢰도를 문장으로 옮기는 역할만 한다.
  • 답변에는 근거(어느 시각의 어떤 이벤트인지)와 한계("정확한 인원수는 알 수 없음")를 함께 적는다.
  • API가 SENSOR_FAULT 상태를 돌려주면 AI는 "현재 센서 오류로 판단 불가"라고 답해야 한다. 추측하지 않는다.

질의 예시는 14.6절에 있다.

11실습 환경 준비 및 하드웨어 배선

ESP32 DevKit V1 ESP32-WROOM-32 VIN (5V)GNDGPIO16 (RX2)GPIO17 (TX2) 3V3GNDGPIO21 (SDA)GPIO22 (SCL) HLK-LD2410C — UART VCCGNDTXRX Sensirion SCD41 — I²C VDDGNDSDASCL 5V 는 전원(VIN→VCC)만 — ESP32 GPIO 에 5V 직결 금지 5V 전원 GND UART (교차 연결) I²C · 3.3V
표준 배선 — 아래 11장 핀맵 표와 동일합니다. UART 는 TX↔RX 를 교차로 잇습니다.

이 장은 흐름만 요약한다. 부품 목록·호환성·구매 시 확인 사항은 03_bom_and_compatibility.md, 배선 순서·설치 위치·주의 사항 상세는 04_wiring_and_installation.md를 참고한다.

11.1 준비물

역할부품비고
MCUESP32 DevKit V1 (ESP32-WROOM-32)Wi-Fi 내장, 3.3V 로직
존재 감지HLK-LD2410C24GHz mmWave, UART, 존재/움직임 감지 전용(인원 계수 아님)
CO₂·온습도Sensirion SCD41NDIR CO₂ + 온도 + 습도, I²C(주소 0x62)
기타브레드보드, 점퍼선(M-F), USB 케이블, 5V USB 전원 어댑터

개발 PC에는 Arduino IDE 2.x(12장), Python 3.11+와 PostgreSQL 16(13장)을 설치한다.

11.2 작업 흐름

  1. 부품 도착 후 외관과 핀 표기를 확인한다.
  2. 전원을 끈 상태에서 아래 핀맵대로 배선한다.
  3. 배선을 한 번 더 눈으로 대조한다(전원 연결 전).
  4. USB로 ESP32만 먼저 연결해 PC가 포트를 인식하는지 확인한다.
  5. 센서를 하나씩 확인한다: SCD41 먼저, 이후 LD2410C.
  6. 12장의 펌웨어 업로드 후 직렬 모니터에서 값을 확인한다.

11.3 핀맵

연결ESP32 핀상대 핀주의
LD2410C 전원VIN(5V)VCC모듈 공급 전압은 데이터시트로 확인 후 기재
LD2410C GNDGNDGND
LD2410C UARTGPIO16 (RX2)TXLD2410C 로직은 3.3V TTL — 직결 가능 여부 데이터시트 확인
LD2410C UARTGPIO17 (TX2)RX
SCD41 전원3V3VDDSCD41은 3.3V. 5V 금지
SCD41 GNDGNDGND
SCD41 I²CGPIO21 (SDA)SDA
SCD41 I²CGPIO22 (SCL)SCL
경고: ESP32 GPIO에 5V 신호를 직접 입력하지 않는다. ESP32 GPIO는 3.3V 로직이다. 5V 신호가 들어가면 칩이 손상될 수 있다. LD2410C의 UART 출력 레벨이 3.3V인지는 구매한 모듈의 데이터시트로 반드시 확인한다. 확인되지 않으면 레벨 변환기를 쓴다. SCD41 전원은 3V3에만 연결한다.

11.4 흔한 배선 실수

  • TX-TX, RX-RX 직결(교차해야 한다)
  • SCD41 전원을 VIN(5V)에 연결
  • SDA/SCL 바꿔 연결
  • GND 공통 누락
  • 전원이 켜진 상태에서 점퍼선 이동

설치 위치(천장·벽 방향, 금속·유리 반사 등)는 7장과 04_wiring_and_installation.md를 참고한다.

12ESP32 펌웨어 실습

전체 코드는 firmware/를 참고한다(firmware/academy_sensor_node/academy_sensor_node.ino, 같은 폴더의 config.h). 여기서는 흐름과 핵심 조각만 다룬다. ESPHome으로도 비슷한 구성이 가능하나(대안), 이 교재는 Arduino IDE 2.x를 기본으로 한다.

12.1 개발 환경 설치

1단계. Arduino IDE 2.x 설치
Arduino 공식 사이트에서 IDE 2.x를 내려받아 설치한다. 버전은 설치 시점의 최신 2.x를 쓴다(실습 전 확인).

2단계. esp32 보드 패키지 추가

  1. 파일 > 환경설정 > 추가 보드 매니저 URLs에 Espressif의 ESP32 패키지 인덱스 URL을 추가한다. URL은 Espressif 공식 문서에서 확인해 붙여 넣는다(실습 전 확인).
  2. 도구 > 보드 > 보드 매니저에서 esp32 (by Espressif Systems)를 검색해 설치한다.
  3. 도구 > 보드에서 ESP32 Dev Module을 선택한다(DevKit V1에 일반적으로 쓰는 선택지. 문제가 있으면 board 문서 확인).

3단계. 라이브러리 설치 (스케치 > 라이브러리 포함하기 > 라이브러리 관리)

  • ld2410 (by ncmreynolds)
  • Sensirion I2C SCD4x (의존성인 Sensirion Core가 함께 설치되는지 확인, 없으면 직접 설치)

버전은 라이브러리 매니저의 최신 버전을 쓰며, 실습 전 확인한다. 라이브러리 버전에 따라 함수 이름이 다를 수 있다. 아래 코드가 컴파일되지 않으면 설치한 버전의 예제 스케치(파일 > 예제)와 대조한다.

4단계. 포트 선택
USB로 연결한 뒤 도구 > 포트에서 ESP32 포트(Windows는 COM 번호)를 고른다.

12.2 구현 흐름

flowchart TD
    A["setup: 직렬, Wi-Fi, 센서 초기화"] --> B["loop 시작"]
    B --> C["LD2410C 읽기"]
    C --> D["SCD41 읽기"]
    D --> E["quality 플래그 계산"]
    E --> F["직렬 모니터 출력"]
    F --> G{"30초 경과 또는<br/>presence 변화?"}
    G -- "아니오" --> B
    G -- "예" --> H{"Wi-Fi 연결?"}
    H -- "아니오" --> I["재접속 시도"] --> B
    H -- "예" --> J["HTTP POST JSON"]
    J --> K{"2xx 응답?"}
    K -- "예" --> L["seq 증가"] --> B
    K -- "아니오" --> M["오류 로그, 다음 주기에 재시도"] --> B

12.3 단계 1: 센서 읽기

UART는 LD2410C 기본 속도 256000 bps로 연다. 핀은 GPIO16(RX2), GPIO17(TX2)이다.

#include <Wire.h>
#include <ld2410.h>
#include <SensirionI2cScd4x.h>

#define RADAR_RX_PIN 16   // ESP32 RX2 <- LD2410C TX
#define RADAR_TX_PIN 17   // ESP32 TX2 -> LD2410C RX
#define I2C_SDA_PIN  21
#define I2C_SCL_PIN  22

ld2410 radar;
SensirionI2cScd4x scd4x;

void setup() {
  Serial.begin(115200);                       // 직렬 모니터 속도
  Serial2.begin(256000, SERIAL_8N1, RADAR_RX_PIN, RADAR_TX_PIN);
  if (!radar.begin(Serial2)) {
    Serial.println("LD2410C 초기화 실패: 배선 또는 baud 확인");
  }

  Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
  scd4x.begin(Wire, SCD41_I2C_ADDR_62);       // 주소 0x62
  scd4x.stopPeriodicMeasurement();            // 이전 상태 정리
  scd4x.startPeriodicMeasurement();           // 약 5초 주기 측정 시작
}

SCD41 첫 측정값은 시작 후 수 초가 지나야 나온다. 값이 준비되었는지 확인한 뒤 읽는다. (준비 확인 함수 이름은 라이브러리 버전에 따라 다르다. 설치한 버전의 예제를 따른다.)

struct Reading {
  bool presence, moving, stationary;
  int  distance_cm;
  bool radar_valid;
  float co2, temp, hum;
  bool co2_valid;
};

Reading readSensors() {
  Reading r = {};
  radar.read();                                // 라이브러리가 프레임을 파싱
  r.radar_valid = radar.isConnected();
  if (r.radar_valid) {
    r.presence    = radar.presenceDetected();
    r.moving      = radar.movingTargetDetected();
    r.stationary  = radar.stationaryTargetDetected();
    r.distance_cm = radar.detectedDistance();
  }

  uint16_t co2 = 0; float t = 0, h = 0;
  uint16_t err = scd4x.readMeasurement(co2, t, h);
  r.co2_valid = (err == 0 && co2 != 0);        // 0 ppm 또는 오류면 무효
  if (r.co2_valid) { r.co2 = co2; r.temp = t; r.hum = h; }
  return r;
}

주의: radar.read()는 자주 호출해야 프레임을 놓치지 않는다. loop() 안에서 delay()를 길게 쓰지 말고, 시간 비교(millis())로 전송 주기를 관리한다.

12.4 단계 2: 직렬 출력 확인

Wi-Fi를 붙이기 전에 센서 값만 직렬 모니터(115200)에 출력해 확인한다.

void printReading(const Reading& r) {
  Serial.printf("presence=%d moving=%d stationary=%d dist=%dcm radar_valid=%d | "
                "co2=%.0fppm temp=%.1fC hum=%.1f%% co2_valid=%d\n",
                r.presence, r.moving, r.stationary, r.distance_cm, r.radar_valid,
                r.co2, r.temp, r.hum, r.co2_valid);
}

예상 시리얼 출력 (예시, 실제 값은 환경에 따라 다름):

presence=1 moving=0 stationary=1 dist=210cm radar_valid=1 | co2=850ppm temp=24.5C hum=48.2% co2_valid=1
presence=1 moving=1 stationary=0 dist=180cm radar_valid=1 | co2=853ppm temp=24.5C hum=48.1% co2_valid=1
presence=0 moving=0 stationary=0 dist=0cm radar_valid=1 | co2=848ppm temp=24.4C hum=48.3% co2_valid=1

확인 항목: 센서 앞에서 움직이면 moving=1, 가만히 앉으면 stationary=1로 바뀌는가. 방을 비우면 presence=0이 되는가. CO₂ 값이 출력되는가. 이 단계에서 문제가 있으면 Wi-Fi 이전에 해결한다.

12.5 단계 3: Wi-Fi 접속과 HTTP 전송

Wi-Fi 이름·암호와 서버 주소는 config.h에 둔다.

// config.h (저장소에 올리지 않는다. 값은 예시)
#define WIFI_SSID   "your-wifi-ssid"
#define WIFI_PASS   "your-wifi-password"
#define SERVER_URL  "http://192.168.0.10:8000/api/v1/observations"
#define DEVICE_ID   "academy_room_A_01"
#define FW_VERSION  "0.1.0"
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>   // JSON 생성용. 라이브러리 관리자에서 설치
#include "config.h"

uint32_t seq = 0;

String buildJson(const Reading& r, const String& observedAt) {
  JsonDocument doc;
  doc["device_id"]        = DEVICE_ID;
  doc["seq"]              = seq;
  doc["observed_at"]      = observedAt;      // 시각 동기화 필요(NTP)
  doc["firmware_version"] = FW_VERSION;
  doc["schema_version"]   = "1.0";

  JsonObject m = doc["measurements"].to<JsonObject>();
  if (r.radar_valid) {
    m["presence"]           = r.presence;
    m["moving_target"]      = r.moving;
    m["stationary_target"]  = r.stationary;
    m["detect_distance_cm"] = r.distance_cm;
  } else {
    m["presence"] = nullptr;  m["moving_target"] = nullptr;
    m["stationary_target"] = nullptr;  m["detect_distance_cm"] = nullptr;
  }
  if (r.co2_valid) {
    m["co2_ppm"] = (int)r.co2;
    m["temperature_c"] = r.temp;
    m["humidity_percent"] = r.hum;
  } else {
    m["co2_ppm"] = nullptr; m["temperature_c"] = nullptr; m["humidity_percent"] = nullptr;
  }

  JsonObject q = doc["quality"].to<JsonObject>();
  q["radar_valid"] = r.radar_valid;
  q["co2_valid"]   = r.co2_valid;

  String out; serializeJson(doc, out); return out;
}

bool postJson(const String& body) {
  HTTPClient http;
  http.begin(SERVER_URL);
  http.addHeader("Content-Type", "application/json");
  int code = http.POST(body);
  http.end();
  Serial.printf("POST seq=%u -> HTTP %d\n", seq, code);
  return code >= 200 && code < 300;
}

observed_at은 +09:00 오프셋이 붙은 ISO 8601 문자열이어야 한다. ESP32는 전원이 켜질 때 시각을 모르므로, Wi-Fi 연결 후 NTP로 시각을 맞춘다(configTime(...)). NTP 동기화 전의 시각은 보내지 않는다. observed_at 생성 방법의 전체 구현은 firmware/를 참고한다.

전송 시점은 두 조건이다: 30초마다, 그리고 presence 값이 바뀔 때 즉시 1회.

const uint32_t SEND_INTERVAL_MS = 30000;
uint32_t lastSend = 0;
bool lastPresence = false;

void loop() {
  Reading r = readSensors();
  bool changed = (r.radar_valid && r.presence != lastPresence);
  if (changed || millis() - lastSend >= SEND_INTERVAL_MS) {
    printReading(r);
    sendWithRetry(r);
    lastSend = millis();
    if (r.radar_valid) lastPresence = r.presence;
  }
}

12.6 단계 4: 재접속과 오류 처리

현장에서는 Wi-Fi가 끊기고 서버가 잠시 꺼지는 일이 생긴다. 기본 대응 원칙은 다음과 같다.

  • 전송 전에 WiFi.status()를 확인하고, 끊겨 있으면 재접속한다.
  • 전송 실패 시 같은 seq를 유지해 다음 주기에 다시 보낸다. 성공했을 때만 seq를 증가시킨다. 서버가 UNIQUE(device_id, seq)로 중복을 걸러낸다.
  • 센서 읽기 실패는 null + quality 플래그 false로 보낸다. 값을 0으로 채우지 않는다.
  • 오랫동안 연결이 복구되지 않으면 재시작(ESP.restart())을 고려한다.
bool ensureWifi() {
  if (WiFi.status() == WL_CONNECTED) return true;
  WiFi.disconnect();
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  uint32_t t0 = millis();
  while (WiFi.status() != WL_CONNECTED && millis() - t0 < 10000) delay(200);
  Serial.println(WiFi.status() == WL_CONNECTED ? "Wi-Fi 재접속 성공" : "Wi-Fi 재접속 실패");
  return WiFi.status() == WL_CONNECTED;
}

void sendWithRetry(const Reading& r) {
  if (!ensureWifi()) return;                   // 실패 시 seq 유지, 다음 주기에 재시도
  String body = buildJson(r, nowIso8601());    // nowIso8601(): NTP 기반 시각 문자열
  if (postJson(body)) seq++;
}

예상 시리얼 출력 (예시):

Wi-Fi 연결됨, IP=192.168.0.37
POST seq=1024 -> HTTP 201
presence=1 moving=0 stationary=1 dist=210cm radar_valid=1 | co2=850ppm temp=24.5C hum=48.2% co2_valid=1
POST seq=1025 -> HTTP 201
Wi-Fi 재접속 실패
POST seq=1026 -> HTTP -1

HTTP 코드는 서버 구현(13장)에 따라 다를 수 있다. 음수 값은 연결 자체가 실패했다는 뜻이다.

12.7 흔한 오류와 해결

증상원인 후보해결
포트가 보이지 않음충전 전용 USB 케이블, USB-UART 드라이버 미설치데이터 케이블로 교체. 보드에 쓰인 USB-UART 칩(CP210x 또는 CH340 등)에 맞는 드라이버 설치. 다른 USB 포트 시도
업로드 실패 (Failed to connect to ESP32)부트로더 진입 실패업로드 시작 메시지(Connecting...)가 나올 때 보드의 BOOT 버튼을 누른 채 있다가 연결되면 놓는다. 일부 보드는 EN 버튼 조합 필요
업로드는 되나 실행 안 됨, 계속 재부팅전원 부족, GPIO에 부하전원 어댑터 용량 확인, 센서 일부를 분리해 원인 격리
컴파일 오류: 헤더 없음라이브러리 미설치라이브러리 관리에서 설치, Sensirion Core 의존성 확인
컴파일 오류: 함수 없음/인자 불일치라이브러리 버전 차이설치한 버전의 예제 스케치와 대조해 함수 이름 수정
같은 기능의 라이브러리가 둘 이상 설치되어 충돌구버전 Sensirion I2C SCD4x와 신버전 클래스명 혼재, 수동 설치본과 매니저 설치본 중복하나만 남기고 삭제(문서 폴더의 Arduino/libraries 확인), IDE 재시작
LD2410C 값이 항상 0/미연결TX/RX 교차 안 됨, baud 불일치, 전원 부족11장 핀맵 재확인, 256000 확인, 5V 전원 확인
SCD41 읽기 실패I²C 주소/배선, 3V3 미연결SDA(GPIO21)/SCL(GPIO22) 확인, I²C 스캐너 스케치로 0x62 응답 확인
업로드 후 직렬 모니터 글자 깨짐직렬 모니터 속도 불일치모니터를 115200으로 맞춘다
Wi-Fi 접속 안 됨5GHz 전용 SSID, 암호 오타2.4GHz SSID 사용, config.h 확인

13백엔드·DB·대시보드 실습

강의실 A — 실시간 현황 (값은 예시) 재실 상태 사용 중 CO₂ 862 ppm 온도 · 습도 24.5°C · 48% CO₂ 추이 (최근 2시간) 1,200 ppm — 환기 기준(초기값) 재실
최종 목표 화면 — 14장까지 마치면 이런 대시보드가 돌아갑니다 (수치는 예시).

전체 코드는 backend/(app/main.py, app/models.py, app/schemas.py, app/dashboard.html, requirements.txt)와 database/(schema.sql, sample_data.json)를 참고한다. 여기서는 설치 흐름과 핵심 조각만 다룬다.

13.1 설치 흐름

구성 요소버전용도
Python3.11+서버 언어
FastAPI최신(requirements.txt 확인)웹 API
uvicorn최신ASGI 서버
Pydanticv2입력 검증
SQLAlchemy2.0DB 접근
PostgreSQL16저장소
  1. PostgreSQL 16을 설치하고 데이터베이스와 계정을 만든다(예: DB명 academy_sensor. 이름은 자유).
  2. database/schema.sql을 실행해 테이블을 만든다.
  3. 가상환경을 만들고 backend/requirements.txt를 설치한다.
  4. 접속 문자열을 환경변수로 지정하고 uvicorn으로 실행한다.
# 예시 명령 (Windows PowerShell 기준 환경변수 문법은 다를 수 있음)
cd backend
python -m venv .venv
.venv\Scripts\activate
pip install -r requirements.txt

psql -U postgres -c "CREATE DATABASE academy_sensor;"
psql -U postgres -d academy_sensor -f ../database/schema.sql

# DATABASE_URL 변수명은 backend/README.md 확인
uvicorn app.main:app --host 0.0.0.0 --port 8000

--host 0.0.0.0은 같은 네트워크의 ESP32가 접속할 수 있게 한다. 외부 인터넷에 그대로 노출하지 않는다(16장).

예상 출력 (예시):

INFO:     Started server process [12345]
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)

13.2 수신 → 검증 → 저장

검증 스키마 (Pydantic v2). 관측 JSON(9장)과 필드를 일치시킨다. 측정 필드는 null을 허용한다.

from datetime import datetime
from pydantic import BaseModel

class Measurements(BaseModel):
    presence: bool | None = None
    moving_target: bool | None = None
    stationary_target: bool | None = None
    detect_distance_cm: int | None = None
    co2_ppm: int | None = None
    temperature_c: float | None = None
    humidity_percent: float | None = None

class Quality(BaseModel):
    radar_valid: bool
    co2_valid: bool

class ObservationIn(BaseModel):
    device_id: str
    seq: int
    observed_at: datetime          # 오프셋 포함 ISO 8601
    firmware_version: str
    schema_version: str
    measurements: Measurements
    quality: Quality

형식이 틀린 요청(필수 필드 누락, 시각 형식 오류)은 FastAPI가 자동으로 422로 거부한다.

저장 모델 (SQLAlchemy 2.0). UNIQUE(device_id, seq)를 모델에도 선언한다.

from sqlalchemy import BigInteger, ForeignKey, UniqueConstraint, DateTime, String, Integer
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm import Mapped, mapped_column, DeclarativeBase

class Base(DeclarativeBase): pass

class Observation(Base):
    __tablename__ = "observations"
    __table_args__ = (UniqueConstraint("device_id", "seq", name="uq_observations_device_seq"),)

    id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True)
    device_id: Mapped[int] = mapped_column(ForeignKey("devices.id"))
    seq: Mapped[int] = mapped_column(Integer)
    observed_at: Mapped[datetime] = mapped_column(DateTime(timezone=True))
    received_at: Mapped[datetime] = mapped_column(DateTime(timezone=True))
    measurements: Mapped[dict] = mapped_column(JSONB)
    quality: Mapped[dict] = mapped_column(JSONB)
    schema_version: Mapped[str] = mapped_column(String)

수신 엔드포인트. device_id 문자열(예: academy_room_A_01)로 devices 행을 찾고, 없으면 거부한다. received_at은 서버가 채운다. 중복(device_id, seq)은 오류가 아니라 "이미 저장됨"으로 응답한다.

from datetime import datetime, timezone
from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy import select
from sqlalchemy.dialects.postgresql import insert

app = FastAPI()

@app.post("/api/v1/observations", status_code=201)
def receive(obs: ObservationIn, db=Depends(get_db)):
    dev = db.scalar(select(Device).where(Device.device_id == obs.device_id))
    if dev is None or not dev.active:
        raise HTTPException(status_code=404, detail="알 수 없는 장치")

    stmt = insert(Observation).values(
        device_id=dev.id,
        seq=obs.seq,
        observed_at=obs.observed_at,
        received_at=datetime.now(timezone.utc),       # 서버가 기록
        measurements=obs.measurements.model_dump(),
        quality=obs.quality.model_dump(),
        schema_version=obs.schema_version,
    ).on_conflict_do_nothing(index_elements=["device_id", "seq"])
    result = db.execute(stmt)
    db.commit()

    if result.rowcount == 0:
        return {"status": "duplicate"}               # 이미 받은 seq
    return {"status": "stored"}
위 조각은 흐름을 보이기 위한 요약이다. 중복 시 응답 코드(200/201)는 backend/app/main.py의 정의를 따른다. 12장 펌웨어는 2xx 응답을 성공으로 본다.

13.3 중복 제거

ESP32는 응답을 받지 못하면 같은 seq를 다시 보낸다. 서버가 이미 저장했는데 응답만 유실된 경우 중복 행이 생길 수 있다. UNIQUE(device_id, seq) 제약과 ON CONFLICT DO NOTHING으로 DB가 직접 막는다. 코드에서 "먼저 조회 후 저장"으로 처리하면 동시 요청에서 틈이 생기므로, 제약을 최종 방어선으로 둔다.

중복 전송 확인 (예시):

curl -X POST http://localhost:8000/api/v1/observations \
  -H "Content-Type: application/json" \
  -d @sample_observation.json
# {"status":"stored"}

curl -X POST http://localhost:8000/api/v1/observations \
  -H "Content-Type: application/json" \
  -d @sample_observation.json
# {"status":"duplicate"}

13.4 시계열 조회 API

대시보드와 상태·이벤트 엔진이 쓰는 조회 API다. 경로 이름은 backend/app/main.py를 따르며, 아래는 예시 형태다.

from datetime import datetime
from fastapi import Query

@app.get("/api/v1/spaces/{space_id}/observations")
def list_observations(space_id: int,
                      since: datetime,
                      until: datetime | None = None,
                      limit: int = Query(500, le=5000),
                      db=Depends(get_db)):
    q = (select(Observation)
         .join(Device, Device.id == Observation.device_id)
         .where(Device.space_id == space_id, Observation.observed_at >= since)
         .order_by(Observation.observed_at.desc())
         .limit(limit))
    if until:
        q = q.where(Observation.observed_at < until)
    rows = db.scalars(q).all()
    return [{"observed_at": r.observed_at, "measurements": r.measurements,
             "quality": r.quality} for r in reversed(rows)]

limit 상한을 두어 한 번에 너무 많은 행을 반환하지 않게 한다. 조회가 느려지면 (device_id, observed_at) 인덱스를 검토한다(database/schema.sql에 정의된 인덱스를 확인).

호출 예시와 응답 (예시 데이터):

curl "http://localhost:8000/api/v1/spaces/1/observations?since=2026-10-18T16:00:00%2B09:00&limit=2"
[
  {"observed_at": "2026-10-18T16:30:00+09:00",
   "measurements": {"presence": true, "co2_ppm": 850},
   "quality": {"radar_valid": true, "co2_valid": true}},
  {"observed_at": "2026-10-18T16:30:30+09:00",
   "measurements": {"presence": true, "co2_ppm": 855},
   "quality": {"radar_valid": true, "co2_valid": true}}
]

13.5 Chart.js 대시보드

복잡한 프런트엔드 프레임워크는 쓰지 않는다. FastAPI가 단일 HTML(app/dashboard.html)을 서빙하고, 이 파일이 Chart.js(cdnjs CDN)로 시계열을 그린다.

<canvas id="co2Chart" height="120"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.min.js"></script>
<script>
  async function load() {
    const since = new Date(Date.now() - 3 * 3600 * 1000).toISOString();
    const res = await fetch(`/api/v1/spaces/1/observations?since=${encodeURIComponent(since)}`);
    const rows = await res.json();
    new Chart(document.getElementById("co2Chart"), {
      type: "line",
      data: {
        labels: rows.map(r => r.observed_at.slice(11, 16)),
        datasets: [{ label: "CO2 (ppm)",
                     data: rows.map(r => r.measurements.co2_ppm),   // null은 끊김으로 표시
                     spanGaps: false }]
      }
    });
  }
  load();
</script>
  • 결측(null)은 선을 이어 붙이지 않고 끊어서 보여 준다(spanGaps: false). 센서 오류를 눈에 보이게 하기 위해서다.
  • 차트 라이브러리 버전은 실습 전 cdnjs에서 확인한다.
  • 대시보드는 presence 타임라인, CO₂/온습도 선 그래프, 최근 이벤트 목록을 최소 구성으로 한다.

브라우저에서 http://localhost:8000/(경로는 main.py 확인)로 접속해 그래프가 표시되면 성공이다.

14센서 퓨전·이벤트 엔진 구현

presence 관측 (30초 간격) 5분 창 — true 비율 60% 넘는지 계산 이벤트 (상태 변화의 기록) ROOM_EMPTY ROOM_OCCUPIED (started_at 기록) 관측 1개 빠져도(일시 미감지) 창 비율로 판단해 상태는 안 흔들린다 임계값(5분 · 60%)은 초기값 — 15장 실험으로 오탐·미탐을 재고 조정한다
개별 관측이 아니라 "시간 창"이 이벤트를 만든다 — 그래서 깜빡임에 강하다.

규칙의 정식 정의는 08_event_rules.md에 있다. 이 장은 구현 흐름과 핵심 조각을 보인다.

임계값에 대한 주의. 아래 시간 창과 임계값(5분, 10분, 60%, 1200 ppm, 1000 ppm)은 초기값이며 실측 후 조정한다. 학원의 강의실 크기, 환기 방식, 설치 위치에 따라 적절한 값이 다르다. 근거 없이 고정하지 말고 15장의 실험 설계로 오탐·미탐을 측정해 바꾼다.

14.1 규칙 4종

이벤트조건(요약)해제
ROOM_OCCUPIED최근 5분 창에서 presence true 비율 ≥ 60%아래 EMPTY 성립 시
ROOM_EMPTY최근 10분 presence true 없음OCCUPIED 성립 시
VENTILATION_NEEDEDco2_ppm > 1200 상태 10분 지속co2_ppm < 1000 10분 지속
SENSOR_FAULT장치 데이터 5분 이상 미수신 또는 quality false 지속정상 수신 재개

14.2 데이터 품질 플래그 처리

규칙을 적용하기 전에 관측을 걸러 낸다. 원칙은 다음과 같다.

  1. quality.radar_valid == false인 관측은 presence 계산에서 제외한다(false로 취급하지 않는다).
  2. quality.co2_valid == false인 관측은 CO₂ 계산에서 제외한다.
  3. 제외된 비율이 높으면 해당 규칙을 판단 불가로 두고, SENSOR_FAULT 조건을 따로 평가한다.
  4. 레이더가 무효인 동안에 "비어 있음"으로 확정하지 않는다. 측정 실패와 빈 방은 다르다.
def valid_presence(rows):
    """radar_valid 인 관측의 presence 값만 반환 (True/False 리스트)"""
    return [r.measurements["presence"] for r in rows
            if r.quality.get("radar_valid") and r.measurements.get("presence") is not None]

def valid_co2(rows):
    """co2_valid 인 관측의 (observed_at, co2_ppm) 반환"""
    return [(r.observed_at, r.measurements["co2_ppm"]) for r in rows
            if r.quality.get("co2_valid") and r.measurements.get("co2_ppm") is not None]

14.3 ROOM_OCCUPIED / ROOM_EMPTY

의사코드:

입력: 공간의 최근 관측 목록 (observed_at 순)

window5  = 현재 시각 기준 최근 5분 관측 중 radar_valid 인 것
window10 = 현재 시각 기준 최근 10분 관측 중 radar_valid 인 것

if window5 가 비어 있으면: 판단 불가 (SENSOR_FAULT 평가로 넘김)

ratio = window5 에서 presence == true 인 개수 / window5 개수
if ratio >= 0.60:        상태 = OCCUPIED   (ROOM_OCCUPIED 시작, 열려 있는 ROOM_EMPTY 종료)
elif window10 에 presence == true 가 하나도 없음:
                          상태 = EMPTY      (ROOM_EMPTY 시작, 열려 있는 ROOM_OCCUPIED 종료)
else:                     상태 유지 (히스테리시스: 경계에서 흔들리지 않게)
from datetime import timedelta

OCC_WINDOW = timedelta(minutes=5)    # 초기값: 실측 후 조정
OCC_RATIO = 0.60                     # 초기값: 실측 후 조정
EMPTY_WINDOW = timedelta(minutes=10) # 초기값: 실측 후 조정

def evaluate_occupancy(rows, now, current_state):
    """반환: 'OCCUPIED' | 'EMPTY' | current_state(유지) | None(판단 불가)"""
    w5 = [r for r in rows if now - r.observed_at <= OCC_WINDOW and r.quality.get("radar_valid")
          and r.measurements.get("presence") is not None]
    w10 = [r for r in rows if now - r.observed_at <= EMPTY_WINDOW and r.quality.get("radar_valid")
           and r.measurements.get("presence") is not None]
    if not w5:
        return None                                   # 판단 불가

    ratio = sum(1 for r in w5 if r.measurements["presence"]) / len(w5)
    if ratio >= OCC_RATIO:
        return "OCCUPIED"
    if w10 and not any(r.measurements["presence"] for r in w10):
        return "EMPTY"
    return current_state                              # 경계 구간: 이전 상태 유지

주의: 처음 실행해 이전 상태가 없으면 current_state가 None이다. 이 경우 경계 구간에서는 상태를 만들지 않는다. 또한 OCCUPIED는 "사람이 있다"일 뿐 몇 명인지는 말해 주지 않는다. 가만히 앉은 사람의 정지 감지는 LD2410C 설정과 환경에 따라 놓칠 수 있다(미탐 가능). 이 위험은 15장에서 측정한다.

14.4 VENTILATION_NEEDED

의사코드:

CO2 유효 관측만 사용
시작: co2_ppm > 1200 이 연속으로 10분 이상 지속 → VENTILATION_NEEDED 시작
해제: co2_ppm < 1000 이 연속으로 10분 이상 지속 → 종료
(1000~1200 사이는 현재 상태 유지)
VENT_HIGH = 1200      # ppm, 초기값: 실측 후 조정
VENT_LOW = 1000       # ppm, 초기값: 실측 후 조정
VENT_HOLD = timedelta(minutes=10)

def evaluate_ventilation(co2_series, now, active):
    """co2_series: [(observed_at, ppm)] 시간순. 반환: True(필요) / False / None(판단 불가)"""
    if not co2_series:
        return None
    recent = [(t, v) for t, v in co2_series if now - t <= VENT_HOLD]
    if not recent or (now - recent[0][0]) < VENT_HOLD * 0.9:  # 10분 분량 데이터가 없음
        return active if active is not None else False
    if not active and all(v > VENT_HIGH for _, v in recent):
        return True
    if active and all(v < VENT_LOW for _, v in recent):
        return False
    return active

CO₂는 환기 필요 여부의 참고 지표일 뿐 인원수를 알려 주지 않는다. 사람이 적어도 밀폐되어 있으면 올라가고, 사람이 많아도 환기가 잘 되면 낮을 수 있다.

14.5 SENSOR_FAULT

의사코드:

조건 A: 장치의 마지막 received_at 이 현재보다 5분 이상 과거 → 미수신
조건 B: 최근 5분 관측 중 radar_valid == false 또는 co2_valid == false 가 계속 이어짐
A 또는 B → SENSOR_FAULT 시작
정상 수신이 재개되고 quality 가 true 로 돌아오면 종료
FAULT_SILENCE = timedelta(minutes=5)   # 초기값: 실측 후 조정

def evaluate_fault(rows, last_received_at, now):
    if now - last_received_at >= FAULT_SILENCE:
        return "NO_DATA"
    recent = [r for r in rows if now - r.observed_at <= FAULT_SILENCE]
    if recent and all(not r.quality.get("radar_valid") for r in recent):
        return "RADAR_INVALID"
    if recent and all(not r.quality.get("co2_valid") for r in recent):
        return "CO2_INVALID"
    return None

미수신 판단에는 장치 시계가 아니라 서버가 기록한 received_at을 쓴다. 장치 시계가 틀려도 판단이 흔들리지 않는다. 이 이벤트는 다른 규칙의 신뢰도에 영향을 준다. SENSOR_FAULT가 열려 있는 동안 상태 API는 해당 공간의 신뢰도를 low로 반환하거나 "판단 불가"로 응답한다.

14.6 이벤트 생성과 이력 기록

이벤트는 상태가 바뀔 때 만든다. 매 평가마다 새 행을 쓰면 이력이 쓰레기로 가득 찬다.

def apply_state(db, space_id, event_type, active, now, evidence):
    """event_type 에 대해 열린 이벤트가 있는지 보고 시작/종료를 기록"""
    open_ev = db.scalar(select(Event).where(Event.space_id == space_id,
                                            Event.type == event_type,
                                            Event.ended_at.is_(None)))
    if active and open_ev is None:
        db.add(Event(space_id=space_id, type=event_type, severity=SEVERITY[event_type],
                     started_at=now, source="rule_engine", evidence=evidence,
                     created_at=now))
    elif not active and open_ev is not None:
        open_ev.ended_at = now
    db.commit()

evidence에는 판단 근거를 요약해 남긴다(예시).

{ "rule": "ROOM_OCCUPIED", "window_minutes": 5, "presence_ratio": 0.9,
  "samples": 10, "excluded_invalid": 0, "thresholds_version": "initial-v1" }

thresholds_version처럼 임계값 버전을 함께 기록하면, 나중에 임계값을 바꿨을 때 과거 이벤트를 어느 기준으로 만들었는지 알 수 있다.

엔진은 주기적으로(예: 30초~1분 간격, 간격은 초기값) 각 공간에 대해 14.2~14.5의 평가를 순서대로 실행한다. 실행 방식(백그라운드 스레드, 별도 프로세스, cron)은 MVP에서는 단순한 쪽을 택한다. 구현은 backend/를 참고한다.

14.7 시간표·출결과 결합하기 (확장)

규칙 4종만으로 8장의 "IN_USE" 같은 종합 상태를 만들 수 있다. 시간표(class_sessions)를 결합하면 "수업 시간에 비어 있음", "수업 시간이 아닌데 사용 중" 같은 의미 있는 불일치를 찾는다. 출결 기록 연동은 MVP 이후 확장이며 17장에서 다룬다. 이 경우에도 persons와 센서 데이터를 개별 학생 단위로 연결하지 않는 것이 기본값이다(16장).

14.8 검증 가능한 AI 질의 예시

10장의 구조에서 AI는 아래 API를 호출하고 그 결과로만 답한다. 아래 응답은 모두 예시 데이터다.

질문: "지금 A강의실 비어 있어?"

  1. AI가 상태 조회 API를 호출한다.
curl "http://localhost:8000/api/v1/spaces/1/state"

(상태 API의 정확한 경로·응답 필드는 backend/를 참고한다. 아래는 형태 예시다.)

{
  "space": "A강의실",
  "as_of": "2026-10-18T16:40:00+09:00",
  "occupancy": "OCCUPIED",
  "open_events": ["ROOM_OCCUPIED", "VENTILATION_NEEDED"],
  "confidence": "high",
  "last_received_at": "2026-10-18T16:39:45+09:00",
  "limitations": "사람의 존재 여부만 판단. 정확한 인원수는 알 수 없음"
}
  1. 필요하면 이벤트 이력 API로 근거를 확인한다.
curl "http://localhost:8000/api/v1/spaces/1/events?since=2026-10-18T16:00:00%2B09:00"
  1. AI의 답변 예시:
현재 A강의실은 사용 중입니다(16:40 기준, 신뢰도 높음). 16:21부터 사람이 있는 상태로 기록되어 있고, CO₂가 기준을 넘어 환기 필요 이벤트도 열려 있습니다. 다만 센서는 사람의 존재 여부만 판단하므로 몇 명인지는 알 수 없습니다.

(위 시각과 내용은 예시다.)

센서 오류 시의 답변 예시. 상태 API가 SENSOR_FAULT를 돌려주면:

A강의실은 센서 데이터가 5분 이상 들어오지 않아 현재 상태를 판단할 수 없습니다. 마지막으로 확인된 시각은 16:31입니다. 직접 확인이 필요합니다.

이 구조가 주는 이점은 다음과 같다.

  • 답변의 근거를 추적할 수 있다(이벤트 ID, evidence, 시각).
  • 오류일 때 "모르겠다"고 답하게 강제할 수 있다.
  • 규칙을 바꿔도 AI 프롬프트를 고칠 필요가 없다. 규칙은 코드에, AI는 설명에 둔다.

AI가 인원수, 학생 신원, 집중도를 답하려 하면 안 된다. 센서가 제공하지 않는 정보이기 때문이다. API 응답의 limitations 필드를 답변에 반영하게 한다.

14.9 다음 단계

  • 규칙 정식 정의: 08_event_rules.md
  • 오탐·미탐 측정과 임계값 조정: 15장, 09_test_protocol.md
  • 개인정보·보안 점검: 16장, 11_privacy_security_checklist.md
  • 출결 연동과 다중 장치 확장: 17장, 10_roadmap.md

15실험 설계와 성능 검증

센서를 붙였다고 끝이 아니다. "붙였더니 되더라"는 검증이 아니다. 이 장은 요청서의 필수 실험 7종을 정리한다. 각 실험의 상세 프로토콜(체크 항목, 양식 원본)은 09_test_protocol.md 에 둔다. 여기서는 설계 논리와 기록 양식을 다룬다.

원칙. 이 장의 모든 결과 수치는 "예상/가설"이다. 실제 값은 직접 측정해서 기록표에 채운다. 측정하지 않은 수치를 보고서에 쓰지 않는다.

15.1 공통 개념

관측, 추론, 사실을 섞지 않는다

구분예실험에서의 역할
관측LD2410C 의 presence=true, SCD41 의 co2_ppm평가 대상
추론규칙이 계산한 ROOM_OCCUPIED, 인원수 추정평가 대상
사실(ground truth)사람이 직접 센 인원, 스톱워치 시각, 출결부정답 기준

센서 출력끼리 비교하면 검증이 아니다. 반드시 센서와 독립적인 정답 기준을 따로 확보한다.

평가 지표 정의

시간 단위를 1분 슬롯(또는 30초 관측 1건)으로 나누고, 슬롯마다 정답과 센서 판정을 비교한다.

지표정의계산
감지율(재현율)실제 사람이 있는 슬롯 중 센서가 있다고 한 비율TP / (TP + FN)
미탐률실제 사람이 있는데 센서가 없다고 한 비율FN / (TP + FN)
오탐률실제 빈 공간인데 센서가 있다고 한 비율FP / (FP + TN)
지연실제 변화 시각과 센서·규칙이 변화를 반영한 시각의 차반영 시각 - 정답 시각 (초)
인원수 추정 오차추정 인원 - 실제 인원절대오차 평균(MAE) 등
  • TP: 사람 있음·센서 있음 / FP: 사람 없음·센서 있음 / FN: 사람 있음·센서 없음 / TN: 사람 없음·센서 없음
  • 인원수 추정 오차는 LD2410C 단독으로는 계산 대상이 아니다. 인원 계수 기능이 없기 때문이다(실험 3 참고).

실험 환경 기록 (모든 실험 공통)

실험마다 다음을 기록표 머리에 적는다. 이 정보가 없으면 나중에 결과를 재현할 수 없다.

항목기록 내용
일시·실험자
장치 ID·펌웨어 버전예: academy_room_A_01, 0.1.0
설치 높이·방향·각도줄자·각도기 값
강의실 크기·가구 배치약식 도면 사진
문·창문 상태열림/닫힘
에어컨·환기 장치켜짐/꺼짐, 설정 온도·풍량
센서 설정감도·최대 거리 게이트 설정 값

정답 확보의 공통 방법

  1. 사람이 직접 센다. 실험자 1명이 문 앞 또는 구석에서 인원과 상태(앉음·걸음·퇴실)를 스톱워치와 함께 기록한다.
  2. 시각 동기화. 기록자 스마트폰 시각과 장치 시각(NTP 동기)의 차이를 실험 시작 전에 확인한다.
  3. 영상 녹화는 쓰지 않는다. 학생이 있는 환경에서는 영상 정답을 확보하지 않는다(16장). 실험 참여자가 성인 스태프·동의한 지원자인 경우에 한해, 별도 동의 하에 제한적으로 쓸 수 있는지는 법률 검토 필요 항목이다.
  4. 미성년자는 실험 참여자로 쓰지 않는 것을 기본값으로 한다. 정규 수업 중 측정이 필요하면 기관장 승인과 보호자 고지 후 인원 수만 수기로 기록한다(16장).

15.2 실험 1. 빈 강의실 오탐률 측정

항목내용
목적아무도 없을 때 센서가 "있음"이라고 잘못 보고하는 빈도와 원인 파악
환경 통제문 닫기, 잠금. 실험자 퇴실. 에어컨·선풍기·환풍기·블라인드 등 움직이는 물체 상태를 고정하거나 조건별로 구분
절차1) 센서 설치·설정 기록 2) 실험자가 퇴실 후 문 닫음 3) 일정 시간 연속 기록 4) 조건(에어컨 켬/끔 등)을 바꿔 반복
반복 횟수조건별 최소 3회(권장). 시간 길이는 조건마다 최소 1시간 이상, 야간 포함 1회 이상 권장. 초기 계획값이며 상황에 따라 조정
정답 확보"빈 방"이라는 사실 자체가 정답. 실험 중 출입이 없었음을 문 앞 확인(출입 전 사진 또는 도어 봉인 메모)으로 보장
지표오탐률(FP / (FP + TN)), 오탐 지속 시간, 오탐 거리값 분포
예상/가설에어컨 바람·커튼·팬 등 움직이는 물체가 있으면 오탐이 늘어날 수 있다(가설). 정지 물체만 있는 구성에서는 오탐이 적을 것으로 예상(가설). 실제 값은 측정으로 확인

기록표 양식:

일시조건(에어컨/팬/창)기록 시간(분)전체 슬롯 수presence=true 슬롯 수오탐률오탐 시 거리(cm)비고

15.3 실험 2. 1명 보행 vs 정지 착석 존재 감지 비교

항목내용
목적움직이는 사람과 가만히 앉은 사람의 감지 차이 확인. 정지 상태 감지가 유지되는 시간·거리 한계 파악
환경 통제실험자 1명만 입실. 기타 조건은 실험 1과 동일하게 고정
절차A) 보행: 센서 앞 일정 거리를 오가며 걷기 B) 착석: 책상에 앉아 거의 움직이지 않기(필기·독서 자세) C) 완전 정지: 눈 감고 최대한 움직이지 않기. 각 자세를 거리별(예: 1m, 3m, 5m)로 반복
반복 횟수자세 x 거리 조합마다 최소 3회. 1회 길이는 5분 이상 권장(초기 계획값)
정답 확보실험자 본인이 자세 전환 시각을 스톱워치 기록. 별도 기록자가 있으면 더 좋다
지표자세별 감지율, 미탐률, 감지 지연(입실부터 첫 presence=true 까지 초), 거리 추정값과 실제 거리의 차
예상/가설보행은 moving_target 으로 쉽게 잡히고, 정지 착석은 stationary_target 으로 잡히되 에너지가 약해 먼 거리에서 미탐이 늘 수 있다(가설)

기록표 양식:

일시자세(보행/착석/완전정지)실제 거리(cm)지속(분)감지 슬롯/전체 슬롯감지율미탐률첫 감지까지 지연(초)moving/stationary 구분센서 거리값(cm)

15.4 실험 3. 2-5명 감지 변화 관찰

명시. LD2410C 로는 정확한 인원 계수를 할 수 없다. 이 실험의 목적은 인원수를 맞히는 것이 아니다. 사람이 늘어날 때 센서 출력(거리값, 에너지, 이동/정지 구분)이 어떻게 달라지는지 관찰하는 것이다. 정확한 다중 인원 추적이 필요하면 LD2450 등 다른 센서를 검토한다(6장).
항목내용
목적인원 증가에 따라 존재 판정, 에너지 값, 거리값, CO₂ 가 어떻게 변하는지 관찰하고, "인원수 추정에는 부적합"하다는 근거를 직접 확인
환경 통제성인 지원자(스태프)만 참여. 좌석 위치 고정. 1명, 2명, 3명, 4명, 5명 순서로 입실
절차1) 인원별로 착석 상태 유지 2) 단계당 일정 시간 기록 3) 일부 단계에서는 보행·이동 포함 4) 단계 사이에 빈 방 구간 확보
반복 횟수인원 단계별 최소 2회. 좌석 배치를 바꿔 1회 추가 권장(초기 계획값)
정답 확보실험자가 인원 변화 시각과 인원수 기록
지표단계별 presence 유지율, 에너지·거리값 분포 변화 요약. 인원수 추정 오차는 "센서 단독 판정이 가능한가"가 아니라 "SCD41 CO₂ 증가 폭과 인원수의 관계 참고용"으로만 본다
예상/가설presence 는 인원이 늘어도 true 로 유지되고 인원수는 구분되지 않을 것(가설). CO₂ 는 인원·환기 상태에 따라 증가하지만 CO₂ 만으로 인원수를 정확히 계산할 수 없다(방 크기·환기·시간에 따라 달라짐)

기록표 양식:

일시실제 인원좌석 배치지속(분)presence 유지 슬롯 비율센서 거리값 범위(cm)에너지 값 범위CO₂ 시작→끝(ppm)비고

15.5 실험 4. 출입문·창문·에어컨·금속·가구의 영향

항목내용
목적환경 요인이 mmWave 판정과 CO₂ 측정에 주는 영향 파악. 설치 위치 선정 근거 확보
환경 통제한 번에 한 요인만 바꾼다(변수 통제). 빈 방 기준(실험 1)과 1명 착석 기준(실험 2) 두 가지에서 비교
절차기준 상태 기록 → 요인 하나 변경 → 기록 → 원복 후 재기록. 요인: 문 열림/닫힘, 문 밖 복도 보행, 창문 열림, 에어컨/팬 켬, 금속 캐비닛·화이트보드 근접 배치, 책상·파티션 배치 변경, 센서 방향 변경
반복 횟수요인 상태당 최소 3회(초기 계획값)
정답 확보방 안 사람 유무는 실험자가 통제. 요인 변경 시각을 수기 기록
지표요인별 오탐률 변화, 감지율 변화, 거리값 이동, CO₂ 안정화 시간(창문 열림 시)
예상/가설얇은 벽·문 너머 이동이 감지될 수 있고 금속 반사로 거리값이 달라질 수 있다(가설). 확인은 측정으로

기록표 양식:

일시변경 요인변경 시각기준 상태변경 후 오탐률변경 후 감지율거리값 변화(cm)비고(사진 번호)

15.6 실험 5. CO₂ 환기 반응·안정화 시간

항목내용
목적환기 조치 후 SCD41 값이 얼마나 빨리 반응하고 안정되는지 확인. VENTILATION_NEEDED 임계값(1200/1000 ppm, 10분 지속)을 현장에 맞게 조정할 근거 확보
환경 통제인원·환기 설정을 기록. 센서를 공기 순환이 있는 위치에 설치하고 사람 호흡이 직접 닿지 않게 한다
절차1) 문·창문 닫고 인원 입실, CO₂ 상승 기록 2) 목표 농도에 도달하면 창문·문 열기 또는 환기 장치 가동 3) 하락 곡선과 안정 시점 기록 4) 같은 절차를 인원·시간대를 바꿔 반복
반복 횟수시나리오당 최소 3회(초기 계획값)
정답 확보별도 기준 CO₂ 측정기가 있으면 병행 비교. 없으면 "기준 없음"으로 기록하고 절대 정확도는 주장하지 않는다. SCD41 데이터시트 정확도는 공식 문서 확인 후 인용
지표환기 조치 후 하락 시작까지 지연(초), 목표 농도 도달 시간(분), 안정화 시간(변화가 일정 폭 이하로 유지된 시점)
예상/가설센서 응답 자체의 시간 상수와 방 공기 혼합 시간이 합쳐져 수 분 단위의 지연이 있을 것으로 예상(가설)

기록표 양식:

일시인원환기 조치(종류·시각)조치 시점 CO₂하락 시작 시각목표(예: 1000ppm) 도달 시각안정화 시각기준 측정기 값비고

15.7 실험 6. Wi-Fi 단절 후 재전송·누락 확인

항목내용
목적Wi-Fi 가 끊겼다 복구될 때 데이터가 누락되는지, 중복되는지, 순서가 맞는지 확인
환경 통제서버는 로컬 네트워크에서 가동. 장치 seq 증가를 기준으로 비교
절차1) 정상 전송 확인 2) 공유기 전원 차단 또는 Wi-Fi 해제로 단절 3) 일정 시간 후 복구 4) 서버 observations 의 seq 연속성·중복 확인 5) 서버 중단 후 복구 시나리오도 별도 수행
반복 횟수단절 길이별(예: 1분, 10분, 1시간) 최소 3회(초기 계획값)
정답 확보장치가 단절 시간 동안 만든 seq 범위. 시리얼 모니터 로그와 서버 저장 건수 비교
지표누락률 = 누락 seq 수 / 장치 생성 seq 수, 중복 건수(UNIQUE(device_id, seq) 위반 거부 횟수), 복구 후 첫 정상 전송까지 시간, 시간 순서 역전 여부
주의현재 펌웨어가 오프라인 큐(재전송 버퍼)를 구현했는지에 따라 결과가 달라진다. 구현하지 않았다면 단절 구간은 누락되며, 이것을 "한계"로 기록한다. 버퍼 크기·구현 여부는 펌웨어 문서(05)에서 확인
예상/가설버퍼가 없으면 단절 시간만큼 데이터가 누락될 것(가설). 버퍼가 있으면 누락이 줄지만 메모리 한도를 넘으면 오래된 것부터 버려질 수 있다(가설)

기록표 양식:

일시단절 방식단절 길이장치 생성 seq 범위서버 저장 건수누락 seq중복 시도복구 후 첫 전송 지연(초)비고

15.8 실험 7. 무인 상태에서 센서 오류를 정상 이벤트로 오인하지 않는지

항목내용
목적센서 고장·배선 불량·통신 끊김이 "빈 방(ROOM_EMPTY)" 또는 "사용 중"이라는 정상 이벤트로 기록되지 않고 SENSOR_FAULT 로 구분되는지 확인
환경 통제빈 방. 고장을 인위적으로 주입하되 안전을 지킨다(전원 연결 상태에서 배선을 만지지 않는다. 항상 전원 차단 후 작업)
절차1) LD2410C UART 선 분리 2) SCD41 I²C 선 분리 3) 장치 전원 차단 4) 서버 중단 5) 각 조건에서 quality 플래그, null 값, 이벤트 테이블 확인
반복 횟수고장 유형별 최소 3회(초기 계획값)
정답 확보고장 주입 시각을 기록. 정상 동작 시 기대되는 결과를 사전에 표로 적어 둔다
지표SENSOR_FAULT 발생 여부, 발생까지 지연(5분 미수신 규칙 기준, 초기값), 오인 건수(고장인데 ROOM_EMPTY/ROOM_OCCUPIED 이벤트가 생긴 건수)
예상/가설quality 플래그를 규칙이 확인하지 않으면 고장이 "빈 방"으로 오인될 수 있다(가설). 규칙 설계 시 quality false 는 퓨전 입력에서 제외한다(8장·08_event_rules.md)

기록표 양식:

일시주입한 고장주입 시각quality 값measurements 값생성된 이벤트SENSOR_FAULT 지연오인 여부(O/X)비고

15.9 실험 결과를 보고하는 방법

  1. 원시 기록표를 먼저 첨부한다.
  2. 지표는 정의대로 계산하고 분모를 같이 적는다(예: 12 / 480 슬롯).
  3. 표본이 작으면 "표본 부족, 경향 참고용"이라고 쓴다.
  4. 실험 조건을 벗어난 일반화를 하지 않는다. 한 강의실의 결과를 모든 학원에 적용하지 않는다.
  5. 임계값 조정은 실험 결과에 근거해서만 하고, 조정 이력을 남긴다.

상세 프로토콜: 09_test_protocol.md

16보안·개인정보·운영 안전

이 교재의 시스템은 미성년자가 있는 공간에 설치된다. 기술보다 규칙이 먼저다. 이 장은 설계 원칙과 체크 항목을 안내하며, 법적 판단은 제공하지 않는다. 법령 해석이 필요한 부분은 법률 검토 필요로 표시한다. 세부 체크리스트는 11_privacy_security_checklist.md 를 따른다.

16.1 기본 전제

  • 대상 공간에 미성년자가 있다. 학생과 보호자의 신뢰가 사업의 기반이다.
  • MVP 에서 수집하는 것은 공간의 존재 여부, 이동/정지 여부, 거리, CO₂, 온도, 습도다. 개인 식별 정보는 센서 데이터에 포함하지 않는다.
  • 레이더만으로 신원이나 집중도를 판단하지 않는다. 그런 용도로 쓰지 않는다.
  • 학생 영상·음성의 상시 수집, 개인 행동 감시는 MVP 범위에서 제외한다.

16.2 개인정보 최소 수집 원칙

원칙적용
목적 한정수집 목적을 "강의실 사용 상태와 실내 공기질 관리"로 한정하고 문서화
최소 수집필요 없는 필드는 수집하지 않는다. 센서 관측 JSON 에 이름·학번 등 개인 식별 필드를 넣지 않는다
분리persons 테이블은 MVP 에서 만들어 두기만 하고 센서 데이터와 연결하지 않는다
마스킹이름이 필요한 경우에도 name_masked 형태로만 저장
집계 우선가능하면 원시 관측보다 집계(시간대별 점유율)를 보고용으로 쓴다

법률 검토 필요: 센서 데이터가 개인정보에 해당하는지 여부, 센서 데이터와 출결·수강 정보를 결합할 때의 법적 지위, 14세 미만 아동 관련 별도 의무 여부는 법무 전문가 검토가 필요하다. 이 문서는 단정하지 않는다.

16.3 동의와 고지

  • 현장 고지: 센서 설치 공간 입구에 안내문을 게시한다. 내용: 설치 사실, 측정 항목(존재 여부·CO₂·온습도), 영상·음성은 수집하지 않음, 문의처.
  • 보호자 고지: 학원 등록·안내 채널로 목적, 수집 항목, 보관 기간, 문의 방법을 알린다. 고지 문안은 짧고 평이하게 쓴다.
  • 동의 기록: 필요하다고 판단되는 경우 동의 일시와 방법을 기록한다. 미성년자의 동의 방식(법정대리인 동의 등)은 법률 검토 필요.
  • 거부·철회 대응: 보호자나 학생이 불편을 제기할 때 응대 절차(담당자, 응답 기한)를 사전에 정한다.
  • 센서 설치 전 학원장 서면 승인을 받는다. 건물주·임대차 계약상 제한이 있는지도 확인한다.

법률 검토 필요: 고지만으로 충분한지, 별도 동의서가 필요한지, 정보주체별(학생·교사·직원) 처리 근거를 어떻게 정리할지는 전문가 검토 대상이다. 교사·직원에 대한 근로 감시 해석 가능성도 점검한다.

16.4 접근 통제 (역할별)

역할볼 수 있는 것할 수 있는 것
원장강의실 상태·이벤트 요약, 보고서열람, 임계값 조정 요청
교사담당 강의실의 실시간 상태(점유, CO₂)열람만
운영 담당(스태프)모든 강의실 상태, 장치 상태, 장애 알림장치 등록·교체 보고
개발·관리자원시 관측, DB, 로그설정 변경, 배포. 계정 최소화, 개인 계정 사용
외부(보호자·방문자)없음MVP 에서는 대시보드 접근 불가
  • 공유 계정 금지. 계정마다 비밀번호를 발급하고 퇴사·역할 변경 시 즉시 회수한다.
  • 관리자 접근 기록(누가 언제 무엇을 조회·변경했는지)을 남긴다.
  • DB 는 외부 인터넷에 직접 노출하지 않는다. 서버 포트는 필요한 것만 연다.
  • 실습용 서버의 기본 계정·기본 비밀번호는 즉시 변경한다.

16.5 보관 기간과 삭제

  • 보관 기간은 목적에 필요한 최소로 정한다. 값은 이 교재가 정하지 않는다. 초기 가이드 후보: 원시 관측은 짧게(예: 수 주), 집계는 더 길게. 이 값은 초기안이며 학원 정책과 법률 검토로 확정한다.
  • 자동 삭제 작업(예약 작업)을 두고, 삭제 로그를 남긴다.
  • 백업에도 같은 보관 정책을 적용한다.
  • 학원 계약 종료·장치 폐기 시 데이터 반환·삭제 절차를 계약서에 넣는다.

법률 검토 필요: 법정 보관 의무가 있는 정보와의 관계, 삭제 요청 처리 기한은 단정하지 않는다.

16.6 전송 보안

구분방식사용처주의
실습용평문 HTTP (POST /api/v1/observations)실습실 내 사설망, 학습·프로토타입도청·변조 가능. 인터넷 구간 통과 금지. 학원 운영망과 분리된 별도 SSID/공유기 권장
실서비스HTTPS(TLS)외부 서버·클라우드서버 인증서 발급·갱신 필요. ESP32 에서 인증서 검증 설정이 필요하며 구현은 별도 작업

추가 항목:

  • 장치 인증: 장치별 API 키(토큰)를 두고 서버가 검증한다. 키를 소스에 하드코딩하지 않고 config.h 로 분리하며, 저장소에 올리지 않는다.
  • Wi-Fi 는 WPA2 이상. 학원 직원·학생용 Wi-Fi 와 분리한다.
  • 평문 HTTP 로 수집하는 데이터라도 개인 식별 정보는 포함하지 않는다는 원칙을 유지한다.

16.7 장치 물리 보안

  • 천장·벽의 손이 닿지 않는 위치에 설치하고 케이블을 정리한다.
  • 장치 케이스에 장치 ID 라벨을 붙이되 비밀번호·키는 적지 않는다.
  • ESP32 시리얼 포트(USB)는 외부에서 접근하기 어렵게 한다. 시리얼 접속으로 Wi-Fi 설정이 노출될 수 있다.
  • 전원 어댑터는 인증받은 제품을 쓰고 과열·합선 위험을 점검한다. 5V 와 3.3V 를 혼동하지 않는다(SCD41 은 3.3V, ESP32 GPIO 에 5V 신호 직접 입력 금지).
  • 장치 분실·교체 시 해당 장치 키를 폐기하고 devices.active 를 비활성화한다.

16.8 운영 안전

  • 임시 배선은 사람이 지나가는 곳에 두지 않는다. 걸림·감전 위험을 줄인다.
  • 장비 설치 작업은 전원 차단 후 한다. 천장 작업은 안전 수칙을 지킨다.
  • 24GHz 레이더 모듈의 인증·전파 관련 요건은 국가별로 다르다. 국내 사용 가능 여부와 인증 상태는 미확인 — 구매 전 확인, 법률 검토 필요(전파 인증 포함).
  • 사고·민원·유출 의심 시 대응 절차(중단, 기록, 통지 담당)를 사전에 문서화한다. 통지 의무 요건은 법률 검토 필요.

체크리스트 원본: 11_privacy_security_checklist.md

174주 학습 및 12주 학원 MVP 로드맵

1주 · 원리와 선정 2주 · 레이더 + ESP32 3주 · CO₂ + 퓨전 4주 · 서버 + 대시보드 센서 비교표 존재 감지 로그 상태 데이터·규칙 디지털 트윈 V0.1 5~8주 · 실제 공간 1곳 실증 2주 이상 데이터 · 오탐/미탐 실측 · 운영 보고 9~12주 · 3~5개 공간 확장 다중 장치 운영 · 장애 대응 · 다음 단계 결정 각 칸의 통과 기준(다음 주로 넘어가는 조건)은 17장 표에 — 기준 미달이면 다음 주로 넘어가지 않는다
4주에 프로토타입, 12주에 작은 운영 — 한 번에 크게가 아니라 단계 통과형.

17.1 전체 구조

구간기간목표결과물
학습·프로토타입1-4주개념 학습, 1개 강의실 프로토타입(디지털 트윈 V0.1)센서 비교표, 로그, 규칙, 대시보드
실증5-8주실제 학원 1개 강의실설치, 2주 이상 수집, 오탐·미탐 측정, 원장 보고 1회
확장9-12주3-5개 강의실다중 장치 운영, 장애 대응, 대시보드 공유, 다음 단계 의사결정
flowchart LR
  A[1주 원리·통신·데이터] --> B[2주 LD2410C+ESP32 측정]
  B --> C[3주 SCD41·퓨전]
  C --> D[4주 API·DB·대시보드 V0.1]
  D --> E[5-8주 1개 강의실 실증]
  E --> F[9-12주 3-5개 강의실 확장]

각 주차는 "통과 조건"을 만족해야 다음으로 넘어간다. 통과하지 못하면 다음 주로 가지 않고 원인을 해결한다. 일정이 늘어나는 것은 실패가 아니다. 통과 조건 없이 넘어가는 것이 실패다.

17.2 1-4주 상세

1주. 센서 원리·통신·데이터 구조

  • 목표: mmWave·NDIR CO₂ 센서 원리, UART·I²C·Wi-Fi·HTTP 이해, 관측-상태-이벤트 구분, 스키마 읽기
  • 선행조건: 파이썬·아두이노 기초
  • 결과물: 센서 비교표(02_sensor_selection_matrix.csv 기반), 선정 근거 1쪽
  • 통과 조건:
    • 관측/상태/이벤트를 자기 말로 설명할 수 있다
    • LD2410C 가 할 수 있는 것과 없는 것(인원 계수 불가)을 설명할 수 있다
    • 부품 주문이 완료되었거나 수령 일정이 확정되었다
  • 미통과 시: 용어 복습 후 재평가. 부품 지연이면 시뮬레이션 데이터로 2-4주 준비 일부 병행

2주. LD2410C + ESP32 연결·측정

  • 목표: 배선, 펌웨어 업로드, 시리얼로 존재 값 확인, 시간별 로그 수집
  • 선행조건: 1주 통과, 부품 수령, Arduino IDE 2.x 설정
  • 결과물: 시간별 존재 감지 로그(CSV 또는 시리얼 캡처)
  • 통과 조건:
    • 핀맵(GPIO16/17, VIN, GND)대로 배선하고 배선 확인을 두 번 했다
    • 빈 방·보행·착석 상태에서 값이 구분되어 기록된다
    • 실험 1(빈 강의실 오탐)의 예비 측정을 1회 이상 했다
  • 미통과 시: UART 속도·배선·전원 용량 점검. 5V 신호가 GPIO 에 들어가지 않는지 재확인

3주. SCD41 추가·센서 퓨전

  • 목표: I²C 연결(GPIO21/22, 3V3), CO₂·온습도 수집, 규칙으로 강의실 상태 계산
  • 선행조건: 2주 통과
  • 결과물: 강의실 상태 데이터, 이벤트 규칙(ROOM_OCCUPIED, ROOM_EMPTY, VENTILATION_NEEDED, SENSOR_FAULT) 초기 구현
  • 통과 조건:
    • SCD41 값이 안정적으로 수신된다(주소 0x62 인식)
    • 4개 이벤트가 시나리오 시험에서 의도대로 발생한다
    • 센서 한 쪽 분리 시 SENSOR_FAULT 판정 동작을 확인했다(실험 7 예비)
  • 미통과 시: 규칙 임계값을 바꾸기 전에 원시 값부터 확인

4주. API·DB·대시보드 (디지털 트윈 V0.1)

  • 목표: 장치 → HTTP POST → FastAPI → PostgreSQL → 단일 HTML 대시보드
  • 선행조건: 3주 통과, Python 3.11+, PostgreSQL 16
  • 결과물: 디지털 트윈 V0.1(1개 강의실 현재 상태·최근 추이·이벤트 목록)
  • 통과 조건:
    • 24시간 연속 수집에서 서버가 중단되지 않았다
    • seq 기준 누락·중복이 기록표로 확인된다(실험 6 예비)
    • 대시보드에서 현재 상태와 최근 이벤트가 보인다
    • 16장 보안 최소 항목(기본 비밀번호 변경, 별도 네트워크)을 적용했다
  • 의사결정: 4주 말에 "실제 학원 실증으로 간다/보완한다"를 결정한다. 통과 조건 미달 항목이 있으면 보완 후 진행

17.3 5-8주 실제 학원 1개 강의실 실증

  • 목표: 현장 조건에서 성능 측정
  • 선행조건: 원장 승인, 현장 고지(16.3), 보안 점검, 장치 안전 점검
  • 활동: 설치 → 2주 이상 연속 데이터 수집 → 정답 기준(수기 기록) 병행 샘플링 → 오탐·미탐 계산 → 임계값 조정 → 원장 보고 1회
  • 통과 조건:
    • 2주 이상 연속 수집했고 누락률을 계산했다
    • 오탐률·미탐률을 정의대로 산출하고 근거 기록을 남겼다
    • 원장 보고에서 "무엇이 되고 안 되는지"를 한계 포함으로 설명했다
    • 보호자·학생 민원이 없거나 대응 절차대로 처리했다
  • 의사결정: 확장하려면 원장의 지속 의사와 성능이 사용 목적에 충분한지(환기 알림, 공실 확인 등 목적별) 판단한다. 기준 수치는 이 교재가 정하지 않고 실증 결과로 정한다

17.4 9-12주 3-5개 강의실 확장

  • 목표: 다중 장치 운영 안정화
  • 선행조건: 5-8주 통과
  • 활동: 장치 추가 설치(스키마 변경 없이 devices·spaces 등록), 장애 대응 절차 운영, 대시보드 공유 범위 설정, 비용 정리(18장), 다음 단계(출결 연동 등) 의사결정
  • 통과 조건:
    • 3개 이상 강의실이 동시에 동작하고 장치별 상태를 확인할 수 있다
    • 장애 알림과 복구 절차를 1회 이상 실제로 수행했다
    • 장치 교체를 문서만 보고 수행할 수 있다
    • 다음 단계 결정 문서(진행/보류/중단과 근거)를 작성했다
  • 출결 연동은 개인정보 영향이 크므로 별도 검토를 거친다(16장, 법률 검토 필요)

17.5 위험 신호 (중단·재검토 기준)

  • 현장 오탐·미탐이 사용 목적에 비해 너무 커서 원장이 신뢰하지 않는 경우
  • 민원이 반복되는 경우
  • 전파·안전 관련 문제 제기가 있는 경우
  • 운영 부담이 이익을 초과하는 경우

상세는 10_roadmap.md.

18비용·유지보수·확장 전략

18.1 비용 시나리오

부품 가격은 변동이 크고 조사일에 따라 달라진다. 이 교재에서는 BOM 기준 부품 목록만 확정하고, 가격은 구매 전에 확인한다. 가격 조사가 되지 않은 항목은 미확인으로 둔다. 가격 근거 문서는 03_bom_and_compatibility.md.

노드 1개당 부품

항목수량단가비고
ESP32 DevKit V11미확인 — 구매 전 확인BOM 범위는 03 문서 참조
HLK-LD2410C1미확인 — 구매 전 확인
Sensirion SCD41 모듈1미확인 — 구매 전 확인센서 모듈 형태(브레이크아웃)로 구매. 정품 여부 확인
브레드보드·점퍼선·USB 케이블·5V 어댑터1 세트미확인 — 구매 전 확인
케이스·고정 부품1미확인프로토타입 이후 필요

시나리오 비교

항목1개 강의실5개 강의실
노드 수15 (예비 1-2개 별도 권장)
부품비노드 단가 x 1노드 단가 x 5, 단가 합계 = 미확인
서버노트북·미니 PC 또는 저가 클라우드. 비용 미확인동일하나 데이터 증가 고려. 비용 미확인
네트워크기존 공유기 활용 또는 별도 공유기 1대공유기 추가 또는 AP 확장 필요 여부 점검
설치 인건비직접 수행 시 시간 비용현장 방문 횟수 증가
운영 인력장애 대응 겸임담당 지정 필요
합계미확인미확인
  • 개발 인력 시간(펌웨어·백엔드·대시보드)은 가장 큰 비용이지만 이 교재가 추정하지 않는다.
  • 창업자가 직접 구축하면 현금 비용은 작아도 시간 비용이 크다. 시간 비용도 계산해야 한다.
  • 가격은 알리익스프레스·국내 오픈마켓마다 크게 다르다. 세금·배송·전파 인증 관련 비용은 구매 전 확인한다.

18.2 유지보수와 운영

장애 유형과 대응

유형징후1차 조치
장치 전원·통신 단절5분 이상 미수신 → SENSOR_FAULT전원·Wi-Fi 확인, 재부팅
센서 값 이상quality false, null 값 지속배선 점검, 센서 교체
서버 중단모든 장치 동시 미수신서버 프로세스·DB 확인
오탐 증가빈 방에서 점유 판정설치 환경 변경 확인, 감도 재조정
CO₂ 값 드리프트환기된 방에서도 기준 이상으로 높음SCD41 보정 옵션 점검(공식 문서 확인 후 수행)

교체 절차 (요약)

  1. 새 장치에 device_id 부여(규칙 academy_room_{공간}_{번호})
  2. 서버 devices 에 등록, 이전 장치는 active=false
  3. 설치 후 시리얼·서버에서 값 확인
  4. 교체 일시를 기록하고 이전 장치 키 폐기

펌웨어 업데이트

  • 버전 번호를 firmware_version 으로 보고하고 서버에서 확인한다.
  • 업데이트는 1대에 먼저 적용하고 24시간 관찰 후 확대한다(카나리 방식).
  • OTA 업데이트는 보안 위험이 있어 MVP 에서는 USB 수동 업데이트를 기본으로 한다. OTA 는 확장 단계 검토 사항이다.
  • 업데이트 전후 schema_version 호환성을 확인한다.

정기 점검 (초기 권장 주기, 조정 가능)

  • 매일: 장치 미수신 알림 확인
  • 매주: 누락률·오탐 의심 구간 점검
  • 분기: 센서 위치·고정 상태, 먼지, 전원 어댑터 점검

18.3 센서 추가 시 확장 방법

핵심은 스키마는 그대로 두고 장치만 등록한다.

  1. spaces 에 새 공간 등록 (type 지정)
  2. devices 에 장치 등록 (space_id, device_id, model)
  3. 장치는 같은 JSON 형식으로 /api/v1/observations 에 전송
  4. 대시보드는 공간 목록을 spaces 에서 읽어 자동 표시

새 센서 종류 추가(예: 출입구용 센서, LD2450):

  • measurements 는 JSONB 이므로 새 필드를 추가해도 테이블 구조를 바꾸지 않는다.
  • 다만 schema_version 을 올리고 필드 의미를 문서화한다.
  • 이벤트 규칙은 새 필드를 쓰도록 별도 추가한다. 기존 규칙에 영향이 없는지 시험한다.
  • LD2450 은 확장 논의용이며 구매 대상이 아니다. TI IWR6843ISK 는 고급 개발 보드로 이 교재 범위를 벗어난다.

18.4 직접 구축 vs 외주 판단

판단 기준직접 구축외주
학습 목적적합. 이해도가 쌓인다부적합
시간느림빠름(일정은 계약으로 관리)
현금 비용낮음높음(견적 미확인)
유지보수직접 대응유지보수 계약 필요
보안·법적 책임직접 책임계약으로 분담해도 최종 책임은 사업자에게 남을 수 있음 — 법률 검토 필요
맞춤 요구유연변경 비용 발생

권장 판단 순서:

  1. 1-4주(프로토타입)는 직접 구축한다. 이해 없이 외주하면 요구사항을 정의할 수 없다.
  2. 5-8주 실증에서 요구사항이 안정되면, 반복 설치·대량 하드웨어·인증이 필요한 부분만 외주를 검토한다.
  3. 외주 시 소스 코드·스키마·데이터 소유권, 보안 요건, 유지보수 범위를 계약에 명시한다.

19프로토타입 최종 검수 체크리스트

4주 프로토타입(디지털 트윈 V0.1) 완료 시점의 검수 항목이다. 모든 항목은 "확인 방법"과 함께 기록한다. 체크만 하고 근거를 남기지 않으면 검수가 아니다.

19.1 기능

존재 감지

  • 빈 방에서 presence=false 로 기록된다
  • 보행 시 moving_target=true 가 기록된다
  • 착석 시 stationary_target 이 기록되고 거리값이 있다
  • 센서 값이 없을 때 null + radar_valid=false 가 기록된다
  • 인원수를 표시하지 않는다(LD2410C 로 인원 계수 불가를 UI에도 명시)

환경 측정

  • co2_ppm, temperature_c, humidity_percent 가 주기적으로 기록된다
  • SCD41 3.3V 연결을 확인했다(5V 금지)
  • SCD41 실패 시 co2_valid=false 가 기록된다

저장

  • 30초 주기 + presence 변화 시 즉시 1회 전송이 확인된다
  • observations 에 received_at 이 서버 시각으로 기록된다
  • UNIQUE(device_id, seq) 로 중복이 방지된다
  • 이벤트 4종(ROOM_OCCUPIED, ROOM_EMPTY, VENTILATION_NEEDED, SENSOR_FAULT)이 events 에 기록된다

대시보드

  • 현재 상태가 표시된다
  • 최근 CO₂·온습도 추이가 표시된다
  • 최근 이벤트 목록이 표시된다
  • 장치 마지막 수신 시각이 표시된다

19.2 데이터 품질

  • 24시간 이상 수집에서 누락률을 계산했다 (누락 seq / 생성 seq): 값 기록 ______
  • 중복 시도가 서버에서 거부된다(건수 기록 ______)
  • 장치 시각과 서버 시각의 차이를 측정했다 (차이 ______ 초). 시각 동기화 방식(NTP)을 확인했다
  • observed_at 이 시간대 +09:00 로 기록된다
  • 이상값(예: 비현실적 CO₂, 온도)을 걸러내는 규칙을 정했다
  • 센서 설정 값(감도, 게이트)이 문서화되어 있다
  • 실험 1, 2, 6, 7 의 기록표가 채워져 있다

19.3 보안

  • 서버·DB·대시보드의 기본 비밀번호를 변경했다
  • 실습/운영 네트워크를 학원 직원·학생용 Wi-Fi 와 분리했다
  • DB 포트가 외부에 노출되지 않는다
  • 장치 API 키가 소스 저장소에 포함되어 있지 않다
  • 역할별 접근 권한이 설정되어 있다(16.4)
  • 평문 HTTP 는 사설망에서만 쓰고, 인터넷 구간은 HTTPS(TLS) 계획이 있다
  • 센서 데이터에 개인 식별 정보가 포함되지 않는다
  • 현장 고지문이 준비되어 있다
  • 보관 기간 초안이 있다(법률 검토 필요 항목은 표시됨)

19.4 운영

  • 장치 전원을 껐다 켰을 때 자동으로 Wi-Fi 에 재접속하고 전송을 재개한다
  • 서버 재시작 후 정상 수신을 재개한다
  • 5분 이상 미수신 시 SENSOR_FAULT 가 생성된다
  • 장애 알림 수단(이메일, 메신저 등)과 담당자가 정해져 있다
  • 장치 교체 절차를 한 번 실제로 수행했다
  • 설치 위치·배선 사진과 장치 ID 목록이 문서로 정리되어 있다

19.5 미해결 위험 목록

검수 시점에 남아 있는 위험을 숨기지 않고 적는다. 아래는 이 교재 기준으로 기본 포함되는 항목이다. 현장 상황에 맞게 추가·삭제한다.

번호위험상태담당조치 계획
1LD2410C 정지 사람 미탐 가능성실측 필요실험 2
2에어컨·팬·커튼에 의한 오탐 가능성실측 필요실험 1, 4
3인원 계수 불가 — 이용자의 오해 가능성안내 필요UI 문구, 원장 교육
4CO₂ 센서 정확도·보정 상태미확인공식 문서 확인, 기준 측정기 비교
5Wi-Fi 단절 시 데이터 누락(오프라인 버퍼 유무)실측 필요실험 6
6평문 HTTP 의 도청·변조알려진 한계실서비스 전 TLS 적용
724GHz 모듈의 국내 전파 인증미확인법률 검토 필요, 구매 전 확인
8미성년자 관련 고지·동의 방식법률 검토 필요전문가 검토
9시계 동기 오차실측 필요NTP 점검
10단일 장애점(서버 1대)알려진 한계확장 단계에서 검토

20참고자료 및 용어집

20.1 제조사 공식 문서

확인일: 2026-10-18 (웹 검색으로 확인). 문서는 개정되므로 사용 전에 최신 개정판 여부를 확인한다. 아래에 URL 이 없는 항목은 이번 검색에서 공식 URL 을 확인하지 못한 것이다.

대상문서링크비고
Hi-Link HLK-LD2410C데이터시트·사용자 매뉴얼공식 사이트(Hi-Link, hlktech.com 등)에서 "LD2410C" 검색이번 검색에서 공식 URL 미확인. 타사 판매 페이지(예: OpenELAB)는 2차 자료이므로 사양은 공식 문서로 재확인. 5V 전원, 3.3V IO, 기본 UART 256000 bps 는 2차 자료 기준 — 공식 문서 확인 전까지 추정으로 취급
Hi-Link HLK-LD2450데이터시트공식 사이트(Hi-Link)에서 "LD2450" 검색공식 URL 미확인. ESPHome 문서(https://esphome.io/components/sensor/ld2450/)는 통합 방법 참고용이며 제조사 문서가 아니다. 최대 감지 거리는 판매처마다 6m/8m 로 달라 미확인
Sensirion SCD41SCD4x 데이터시트https://sensirion.com/media/documents/E0F04247/631EF271/CD_DS_SCD40_SCD41_Datasheet_D1.pdf검색 결과(판매처 목록)에 인용된 sensirion.com 링크. 최신 개정판은 Sensirion 공식 사이트(sensirion.com)의 SCD4x 제품 페이지에서 확인
Espressif ESP32ESP32-WROOM-32 데이터시트https://documentation.espressif.com/esp32-wroom-32_datasheet_en.html확인일 기준 NRND(신규 설계 비권장) 표기. 학습용으로는 사용 가능하나 신규 제품 설계 전 최신 권장 모듈 확인
Espressif ESP32데이터시트 PDFhttps://www.espressif.com/sites/default/files/documentation/esp32-wroom-32_datasheet_en.pdf개정 번호가 문서마다 다르므로 Espressif 다운로드 페이지(https://www.espressif.com/en/support/download/documents)에서 최신 확인
TI IWR6843ISK평가 모듈 제품 페이지https://www.ti.com/tool/iwr6843isk60GHz 평가 보드. 이 교재의 구매 대상 아님(확장 논의용)

참고: 라이브러리 ld2410(ncmreynolds), Sensirion I2C SCD4x + Sensirion Core 는 Arduino 라이브러리 매니저 최신 버전을 쓰며, 실습 전 버전을 확인한다.

20.2 용어집

용어한 줄 설명
관측(Observation)센서가 보낸 원시 측정값
상태(State)규칙이 계산한 공간의 현재 상황
이벤트(Event)상태 변화나 조건 충족의 기록
존재 감지(Presence)공간에 사람이 있는지 여부 판정. 인원 계수가 아님
mmWave밀리미터파 대역의 전파를 쓰는 레이더 방식. 이 교재의 LD2410C 는 24GHz
정지 목표(Stationary target)움직이지 않지만 호흡·미세 움직임 등으로 감지되는 대상
오탐(False positive)실제로는 없는데 있다고 판단한 오류
미탐(False negative)실제로는 있는데 없다고 판단한 오류
정답 기준(Ground truth)센서와 독립적으로 확보한 실제 값
NDIR비분산 적외선 방식. 가스가 흡수하는 적외선 양으로 CO₂ 를 측정
ppm100만분율 농도 단위. CO₂ 농도 표기에 사용
센서 퓨전여러 센서 값을 합쳐 상태를 추정하는 방법
UART2선 직렬 통신. LD2410C 와 ESP32 연결에 사용
I²C2선 버스 통신. SCD41 연결에 사용 (SDA/SCL, 주소 0x62)
ESP32Wi-Fi 내장 마이크로컨트롤러. 이 교재의 장치 두뇌
seq장치가 증가시키는 전송 번호. 중복 제거와 누락 확인에 사용
UNIQUE(device_id, seq)같은 장치의 같은 seq 가 두 번 저장되지 않게 하는 DB 제약
HTTP POST장치가 서버로 JSON 을 보내는 기본 전송 방식
TLS(HTTPS)전송 구간 암호화. 실서비스에서 필요
디지털 트윈실제 공간의 상태를 데이터로 반영한 가상 모델. V0.1 은 대시보드 수준
SENSOR_FAULT장치 미수신 또는 품질 불량 지속 시 기록하는 이벤트
법률 검토 필요이 교재가 법적 판단을 하지 않고 전문가 검토를 요구하는 표시

화면 테마

밝기100%

글꼴

글자 크기

목차 — 전체 20장

  1. 1문서 개요
  2. 2학원 공간 데이터화의 기본 개념
  3. 3센서 기술 지도
  4. 4센서 선택의 공학적 기준
  5. 5mmWave 레이더 기초와 심화
  6. 6레이더 장치 비교 및 학원 적합성
  7. 7학원 공간별 센싱 설계
  8. 8센서 퓨전과 공간 상태 추정
  9. 9데이터 아키텍처와 표준 스키마
  10. 10통신·엣지·서버 아키텍처
  11. 11실습 환경 준비 및 하드웨어 배선
  12. 12ESP32 펌웨어 실습
  13. 13백엔드·DB·대시보드 실습
  14. 14센서 퓨전·이벤트 엔진 구현
  15. 15실험 설계와 성능 검증
  16. 16보안·개인정보·운영 안전
  17. 174주 학습 및 12주 학원 MVP 로드맵
  18. 18비용·유지보수·확장 전략
  19. 19프로토타입 최종 검수 체크리스트
  20. 20참고자료 및 용어집