/* =============================================================================
 * 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;
}
