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 제외.
181 lines
7.2 KiB
C
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;
|
|
}
|