POSA_LEAKSMS/firmware/common/sht30.c
유창욱 90f121e14c chore: import codebase with security hardening
SHT30 온습도 모니터링 시스템 전체 소스(서버 PHP, STM32 펌웨어, SQL, 테스트).
전체 코드리뷰에서 도출된 보안 하드닝 10건 반영:
- 요청 서명 HMAC-SHA256 전환(펌웨어 sig.c/서버 config.php/호스트 패리티 동시)
- 재전송 방어 + 기본 API_KEY fail-closed + 디바이스 문자열 정제(api/sensor_data.php)
- 오프라인 SMS 중복 발송 경합 제거(cron_heartbeat.php, 원자적 선점)
- CSV 수식 주입 방지(monthly_report.php), 감사로그 회전 락(retention_cleanup.php)
- 브루트포스 카운터 원자화(login.php), 예시 TOTP 비밀키 무효화, 마이그레이션 멱등화

_backup/(하드코딩 실 비밀값 포함)·config.local.php·런타임 상태는 .gitignore 제외.
2026-06-20 09:37:40 +09:00

181 lines
7.2 KiB
C

/* =============================================================================
* sht30.c - SHT30 I2C 드라이버 구현 (sht30.h)
*
* RPi sht30_monitor.py read_sht30() 대응:
* - 0x2C 0x06 명령 전송(고반복성, clock stretching 비활성)
* - 측정 완료까지 대기(고반복성 ~15ms, 마진 포함 20ms)
* - 6바이트 read (T_msb,T_lsb,T_crc, RH_msb,RH_lsb,RH_crc)
* - CRC 검증/물리값 변환은 이식성 있는 sht30_convert.c(sht30_parse)에 위임
*
* 버스/핀: I2C1 (TODO(hw): PB6=SCL, PB7=SDA, AF4, Open-Drain, 100~400kHz)
* F407 의 I2C1 SCL/SDA 기본 핀은 PB6/PB7. 보드에 따라 PB8/PB9(AF4)도 가능하니
* 배선에 맞게 핀/AF 만 조정하면 된다(아래 SHT30_I2C_* 매크로).
*
* 동시성: HAL_I2C 블로킹 API 사용. 호출은 reporter 태스크(SHT30 보드)에서만
* 직렬로 이뤄진다는 전제(단일 소유). 다중 태스크에서 공유한다면 호출측에서
* 뮤텍스로 감싸야 한다.
*
* 재시도: 단발 read 만 수행한다. 재시도/주기 정책은 호출측(reporter)이 담당.
* ===========================================================================*/
#include "sht30.h"
#include "app_config.h" /* APP_SHT30_I2C_ADDR */
#include "sht30_convert.h" /* sht30_parse */
#include "applog.h" /* LOGW/LOGE (진단 로그) */
#include "stm32f4xx_hal.h" /* CubeF4 HAL (third_party 에 vendored) */
#include <stddef.h>
/* ── 버스/핀 구성 (TODO(hw): 배선에 맞게 확인) ───────────────────────────── */
#define SHT30_I2C_INSTANCE I2C1
#define SHT30_I2C_CLK_ENABLE() __HAL_RCC_I2C1_CLK_ENABLE()
#define SHT30_I2C_CLK_DISABLE() __HAL_RCC_I2C1_CLK_DISABLE()
#define SHT30_I2C_FORCE_RESET() __HAL_RCC_I2C1_FORCE_RESET()
#define SHT30_I2C_RELEASE_RESET() __HAL_RCC_I2C1_RELEASE_RESET()
/* SCL = PB6, SDA = PB7 (I2C1, AF4). 둘 다 같은 포트(GPIOB)라는 전제. */
#define SHT30_GPIO_PORT GPIOB
#define SHT30_GPIO_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE()
#define SHT30_SCL_PIN GPIO_PIN_6
#define SHT30_SDA_PIN GPIO_PIN_7
#define SHT30_GPIO_AF GPIO_AF4_I2C1
/* 표준 모드 100kHz (SHT30 은 최대 1MHz 까지 가능하나 배선/풀업 여유 위해 보수적).
* 필요 시 400000 으로 올릴 수 있다(Fast Mode). */
#ifndef SHT30_I2C_CLOCK_HZ
#define SHT30_I2C_CLOCK_HZ 100000u
#endif
/* 측정 명령: 고반복성, clock stretching 비활성(0x2C06).
* MSB=0x2C, LSB=0x06. */
#define SHT30_CMD_MSB 0x2Cu
#define SHT30_CMD_LSB 0x06u
/* 고반복성 측정 최대 시간 15ms. 마진 포함 20ms 대기. */
#define SHT30_MEAS_DELAY_MS 20u
/* 단일 트랜잭션 타임아웃(블로킹 HAL). 측정 대기는 별도(HAL_Delay)로 처리. */
#define SHT30_I2C_TIMEOUT_MS 50u
#define SHT30_FRAME_LEN 6u
/* HAL 은 7-bit 주소를 상위 7비트에 정렬(좌측 1비트 시프트)하여 사용한다. */
#define SHT30_I2C_HAL_ADDR ((uint16_t)(APP_SHT30_I2C_ADDR << 1))
/* I2C 핸들. 본 모듈에서 소유/초기화한다(헤더 계약상 sht30_init 책임). */
static I2C_HandleTypeDef s_i2c;
static int s_inited = 0;
/* -----------------------------------------------------------------------------
* MSP: GPIO/클럭 초기화. HAL_I2C_Init() 내부에서 콜백된다.
* 약한 심볼(__weak)을 오버라이드하므로 다른 I2C 인스턴스가 있어도 안전하게
* 인스턴스별로 분기한다.
* ---------------------------------------------------------------------------*/
void HAL_I2C_MspInit(I2C_HandleTypeDef *hi2c)
{
if (hi2c->Instance != SHT30_I2C_INSTANCE) {
return; /* 다른 I2C 는 해당 모듈이 처리 */
}
GPIO_InitTypeDef gpio = {0};
SHT30_GPIO_CLK_ENABLE();
SHT30_I2C_CLK_ENABLE();
/* SCL/SDA: AF, Open-Drain, 풀업은 외부 저항 사용 권장.
* TODO(hw): 외부 풀업이 없다면 GPIO_PULLUP 으로 바꾸되, I2C 신뢰성을 위해
* 보드에 4.7kΩ 외부 풀업 장착을 권장한다. */
gpio.Pin = SHT30_SCL_PIN | SHT30_SDA_PIN;
gpio.Mode = GPIO_MODE_AF_OD;
gpio.Pull = GPIO_NOPULL;
gpio.Speed = GPIO_SPEED_FREQ_HIGH;
gpio.Alternate = SHT30_GPIO_AF;
HAL_GPIO_Init(SHT30_GPIO_PORT, &gpio);
}
void HAL_I2C_MspDeInit(I2C_HandleTypeDef *hi2c)
{
if (hi2c->Instance != SHT30_I2C_INSTANCE) {
return;
}
SHT30_I2C_FORCE_RESET();
SHT30_I2C_RELEASE_RESET();
SHT30_I2C_CLK_DISABLE();
HAL_GPIO_DeInit(SHT30_GPIO_PORT, SHT30_SCL_PIN | SHT30_SDA_PIN);
}
/* -----------------------------------------------------------------------------
* sht30_init: I2C1 주변장치 초기화. 0 성공, 음수 실패.
* ---------------------------------------------------------------------------*/
int sht30_init(void)
{
if (s_inited) {
return 0; /* 멱등 */
}
s_i2c.Instance = SHT30_I2C_INSTANCE;
s_i2c.Init.ClockSpeed = SHT30_I2C_CLOCK_HZ;
s_i2c.Init.DutyCycle = I2C_DUTYCYCLE_2; /* 표준/패스트 공통 안전값 */
s_i2c.Init.OwnAddress1 = 0; /* 마스터: 자기 주소 불필요 */
s_i2c.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
s_i2c.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
s_i2c.Init.OwnAddress2 = 0;
s_i2c.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
s_i2c.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
if (HAL_I2C_Init(&s_i2c) != HAL_OK) {
LOGE("sht30: HAL_I2C_Init failed");
return -1;
}
s_inited = 1;
return 0;
}
/* -----------------------------------------------------------------------------
* sht30_measure: 단발 측정.
* 반환: 0 성공, -1 I2C 통신 오류, -2 CRC 오류.
* ---------------------------------------------------------------------------*/
int sht30_measure(double *temp_c, double *rh)
{
if (!s_inited) {
/* 초기화 안 된 상태를 통신 오류로 취급(호출측이 재시도/복구). */
LOGW("sht30: measure before init");
return -1;
}
/* 1) 측정 명령 전송: 0x2C 0x06 (high repeatability, no clock stretch) */
uint8_t cmd[2] = { SHT30_CMD_MSB, SHT30_CMD_LSB };
HAL_StatusTypeDef st = HAL_I2C_Master_Transmit(
&s_i2c, SHT30_I2C_HAL_ADDR, cmd, sizeof(cmd), SHT30_I2C_TIMEOUT_MS);
if (st != HAL_OK) {
LOGW("sht30: cmd tx fail (hal=%d)", (int)st);
return -1;
}
/* 2) 측정 완료 대기(~15ms + 마진). clock stretching 을 끈 모드이므로
* 호스트가 명시적으로 대기해야 한다. HAL 타임베이스(TIM)에 의존하므로
* 스케줄러 유무와 무관하게 동작한다. */
HAL_Delay(SHT30_MEAS_DELAY_MS);
/* 3) 6바이트 read: T_msb,T_lsb,T_crc, RH_msb,RH_lsb,RH_crc */
uint8_t frame[SHT30_FRAME_LEN] = {0};
st = HAL_I2C_Master_Receive(
&s_i2c, SHT30_I2C_HAL_ADDR, frame, SHT30_FRAME_LEN, SHT30_I2C_TIMEOUT_MS);
if (st != HAL_OK) {
LOGW("sht30: data rx fail (hal=%d)", (int)st);
return -1;
}
/* 4) CRC 검증 + 물리값 변환(이식성 코어). -1/-2 -> CRC 오류(-2)로 매핑. */
int rc = sht30_parse(frame, temp_c, rh);
if (rc != 0) {
LOGW("sht30: CRC error (parse=%d)", rc);
return -2;
}
return 0;
}