#ifndef PROJECT_MODULE_H
#define PROJECT_MODULE_H
// 1. 시스템 헤더
#include <Arduino.h>
#include <stdint.h>
// 2. 외부 라이브러리
#include <BLEDevice.h>
// 3. 프로젝트 헤더
#include "config.h"
// 4. 매크로 상수
#define MAX_BUFFER_SIZE 256
// 5. 타입 정의
typedef enum {
STATE_IDLE,
STATE_BUSY
} SystemState_t;
// 6. 전역 변수 선언 (extern)
extern volatile bool g_interruptFlag;
// 7. 함수 선언
void init_system(void);
bool process_command(const char* cmd);
#endif // PROJECT_MODULE_H
소스 파일 (.c/.cpp)
// 1. 대응 헤더
#include "module.h"
// 2. 기타 헤더들
#include <string.h>
// 3. 파일 스코프 상수
static const uint32_t TIMEOUT_MS = 5000;
// 4. 파일 스코프 변수
static uint8_t s_buffer[MAX_BUFFER_SIZE];
static volatile bool s_dataReady = false;
// 5. 함수 구현 (public 먼저, static 나중)
void init_system(void) {
// ...
}
1.2 네이밍 규칙
대상
규칙
예시
비고
매크로/상수
UPPER_SNAKE_CASE
LED_PIN, MAX_RETRY_COUNT
#define, const 전역
전역 변수
g_camelCase
g_systemState, g_bufferIndex
접두사 g_ 필수
정적 변수
s_camelCase
s_timestamp, s_configMode
파일 스코프, 접두사 s_
지역 변수
camelCase
retryCount, isValid
함수 내부
함수
snake_case
init_hardware(), read_sensor()
동사+명사 조합
타입/구조체
PascalCase_t
SensorData_t, ConfigState_t
접미사 _t 권장
클래스
PascalCase
BleScanner, RelayController
C++만 해당
열거형 멤버
UPPER_SNAKE_CASE
CMD_OPEN, STATE_IDLE
타입 접두사 권장
⚡ 실시간 코드 특별 규칙:
• ISR(인터럽트) 함수: ISR_ 접두사 (ISR_timer_callback)
• 하드웨어 레지스터 접근: HW_ 접두사 (HW_set_gpio)
1.3 포맷팅
들여쓰기
2칸 스페이스 (기존 코드 스타일 유지)
탭 절대 금지 (에디터 설정 필수)
중괄호
✅ 권장: K&R 스타일 (기존 코드 스타일)
if (condition) {
action();
} else {
other();
}
// ⚠️ 단일 문장도 중괄호 사용 (MISRA-C 준수)
if (flag) {
return; // 중괄호로 감싸기
}
줄 길이
80자 제한 (가독성)
120자까지 허용 (긴 문자열, URL 등)
1.4 안전성 규칙 (MISRA-C 핵심)
1) 포인터 안전
// ✅ NULL 체크 필수
void process_data(const uint8_t* data, size_t len) {
if (data == NULL || len == 0) {
return; // 조기 리턴
}
// 처리 로직
}
// ⚠️ const 키워드로 의도 명확화
const char* get_version(void); // 읽기 전용 반환
2) 정수 오버플로우 방지
// ❌ 나쁜 예
uint8_t counter = 255;
counter++; // 오버플로우!
// ✅ 좋은 예
if (counter < UINT8_MAX) {
counter++;
}
3) 부동소수점 금지
실시간 시스템에서 float, double 사용 최소화
필요시 고정소수점 또는 정수 연산으로 대체
4) goto 금지
⚠️ 예외: 에러 처리 정리 패턴만 허용
int init_system(void) {
if (!init_hw()) goto cleanup;
if (!init_ble()) goto cleanup;
return 0;
cleanup:
deinit_all();
return -1;
}
1.5 실시간 제약 고려사항
타이밍 크리티컬 코드
// ✅ 인터럽트 핸들러는 최소한으로
void IRAM_ATTR ISR_gpio_handler(void) {
g_interruptFlag = true; // 플래그만 설정
// ❌ Serial.print() 금지
// ❌ 동적 메모리 할당 금지
// ❌ 복잡한 연산 금지
}
// ✅ 메인 루프에서 처리
void loop() {
if (g_interruptFlag) {
g_interruptFlag = false;
handle_interrupt(); // 실제 처리
}
}
타이밍 측정
// ✅ millis() 오버플로우 안전 비교
unsigned long current = millis();
if ((current - lastTime) >= INTERVAL_MS) {
lastTime = current;
do_periodic_task();
}
/**
* @file relay_controller.c
* @brief BLE 기반 릴레이 제어 모듈
* @author [이름]
* @date 2025-10-20
* @version 1.0
*
* @note ESP32 전용, FreeRTOS 미사용
*/
함수 주석
/**
* @brief BLE 광고 데이터에서 명령어 추출
*
* @param[in] data 광고 데이터 버퍼
* @param[in] dataLen 버퍼 길이
* @param[out] cmdOut 추출된 명령어 (최소 16바이트)
*
* @return true 명령어 추출 성공
* @return false 데이터 형식 오류
*
* @warning cmdOut 버퍼 오버플로우 체크 필수
* @note 2바이트 헤더는 제외하고 파싱
*/
bool parse_ble_command(const uint8_t* data, size_t dataLen, char* cmdOut);
from typing import List, Optional, Tuple
def process_logs(
log_file: str,
filter_level: Optional[str] = None
) -> Tuple[int, List[str]]:
"""로그 파일 파싱 및 필터링
Args:
log_file: 로그 파일 경로
filter_level: 필터링할 로그 레벨 (예: "ERROR")
Returns:
(총 라인 수, 필터링된 메시지 리스트)
"""
return 0, []
2.4 펌웨어 인터페이스
import serial
import time
class DeviceInterface:
BAUDRATE = 115200
TIMEOUT_SEC = 2.0
def __init__(self, port: str):
self.ser = serial.Serial(
port=port,
baudrate=self.BAUDRATE,
timeout=self.TIMEOUT_SEC
)
def send_command(self, cmd: str) -> bool:
"""명령어 전송 및 ACK 대기"""
try:
self.ser.write(f"{cmd}\n".encode())
response = self.ser.readline().decode().strip()
return response == "OK"
except serial.SerialException as e:
print(f"Serial error: {e}")
return False
2.5 테스트 코드 (pytest)
import pytest
def test_command_parsing():
# Given
raw_data = b"abcd1234-open"
# When
cmd = parse_command(raw_data)
# Then
assert cmd == "open"
assert is_valid_command(cmd)
3. Git 워크플로우
3.1 브랜치 전략 (GitHub Flow 간소화)
main (배포 가능 상태)
├─ feature/ble-scan-improve
├─ fix/relay-timing-bug
└─ test/integration-pytest
브랜치 네이밍
타입
용도
예시
feature/
새 기능 개발
feature/add-config-mode
fix/
버그 수정
fix/relay-timing-bug
test/
테스트 추가
test/integration-pytest
docs/
문서 작업
docs/api-reference
refactor/
코드 개선
refactor/split-functions
3.2 커밋 메시지 (Conventional Commits)
형식
<타입>: <제목> (50자 이내)
<본문> (선택, 72자 줄바꿈)
<푸터> (선택)
타입
feat: 새 기능
fix: 버그 수정
refactor: 리팩토링
docs: 문서
test: 테스트
chore: 빌드/설정
예시
feat: EEPROM 기반 UUID 저장 기능 추가
- 첫 부팅 시 설정 모드 진입
- GPIO22 3초 롱프레스로 공장 초기화
- ConfigCallbacks로 BLE 설정 수신
Closes #12
3.3 코드 리뷰 프로세스
PR(Pull Request) 생성
AI 도구로 1차 검토 (Claude/GitHub Copilot)
팀원 리뷰 (최소 1명)
수정 반영 후 main에 머지
4. 코드 리뷰 체크리스트
4.1 AI 활용 검토 (1차)
💡 Claude에게 요청할 내용:
이 코드에서 버퍼 오버플로우 위험이 있나요?
타이밍 경쟁 상태(race condition)가 있나요?
MISRA-C 위반 사항은?
메모리 누수 가능성은?
4.2 사람 리뷰 (2차)
기능성
요구사항 충족 확인
엣지 케이스 처리 (NULL, 0, 오버플로우)
에러 복구 가능성
안전성
포인터 NULL 체크
배열 인덱스 범위 검증
인터럽트 안전성 (volatile, IRAM_ATTR)
하드웨어 초기화 순서
성능
불필요한 delay() 제거
CPU 블로킹 최소화
메모리 사용량 확인
가독성
함수 길이 (50줄 이내 권장)
매직 넘버 → 상수화
주석 충분한가
일관된 네이밍
5. 문서화 규칙
5.1 README.md 템플릿
# 프로젝트명
## 개요
- 목적: BLE 기반 원격 릴레이 제어
- 타겟: ESP32-WROOM-32
## 하드웨어 구성
- ESP32 DevKit v1
- 릴레이 모듈: GPIO19(CLOSE), GPIO21(OPEN)
- 리셋 버튼: GPIO22
## 빌드 방법
```bash
pio run -t upload
pio device monitor
```
## 설정
1. GPIO22를 GND에 연결 후 부팅 → 공장 초기화
2. "ESP32-Config" BLE 장치 연결
3. Characteristic FFF1에 UUID prefix 전송 (예: "myid1234")
## 테스트
```bash
pytest tests/
```
## 라이센스
MIT
5.2 API 문서 (Doxygen)
/**
* @defgroup RelayControl 릴레이 제어 모듈
* @{
*/
/**
* @brief 릴레이 펄스 출력
*
* @param[in] pin GPIO 번호
* @param[in] duration 펄스 지속 시간 (ms)
*
* @return void
*
* @pre init_relay()가 호출되어야 함
* @post 지정된 시간 후 자동으로 릴레이 OFF
*
* @code
* pulse_relay(OPEN_PIN, 500);
* @endcode
*/
void pulse_relay(int pin, unsigned long duration);
/** @} */