# =============================================================================
# CMakeLists.txt - SHT30 온습도 모니터링 STM32F407 펌웨어
#
# 타깃:
#   sht30_fw  : SHT30 온습도 보드(-DBOARD_SHT30, sensor_id=2) board_sht30/*
#
# 스택: STM32CubeF4 HAL + CMSIS / FreeRTOS-Kernel(네이티브 API) / LwIP / mbedTLS.
# 전부 firmware/third_party 아래에 "벤더링"되어 있어야 한다(폐쇄망 — 빌드/런타임
# 모두 네트워크 금지). 벤더링은 networked 빌드 머신에서 scripts/vendor.{ps1,sh}
# 로 1회 수행한다.
#
# 구성(configure):
#   cmake -S firmware -B firmware/build -G Ninja \
#     -DCMAKE_TOOLCHAIN_FILE=firmware/cmake/arm-none-eabi-toolchain.cmake \
#     -DCMAKE_BUILD_TYPE=Release
#   cmake --build firmware/build
#
# 산출물(타깃별): <name>.elf / <name>.bin / <name>.hex / <name>.map + 크기 출력.
# =============================================================================
cmake_minimum_required(VERSION 3.20)

# 툴체인 파일 없이 호스트 컴파일러로 들어오는 사고 방지(친절한 에러).
if(NOT CMAKE_TOOLCHAIN_FILE AND NOT CMAKE_CROSSCOMPILING)
    message(WARNING
        "툴체인 파일이 지정되지 않았습니다. arm-none-eabi 크로스 빌드를 위해 "
        "-DCMAKE_TOOLCHAIN_FILE=cmake/arm-none-eabi-toolchain.cmake 를 사용하세요.")
endif()

project(sht30_sensor_fw
    VERSION 26.6.0
    DESCRIPTION "STM32F407 SHT30 temperature/humidity sensor firmware (air-gapped)"
    LANGUAGES C ASM)

set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)

if(NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Build type" FORCE)
endif()

# 크기/성능 균형: -Os(크기 최적화) + 디버그 심볼(.elf 에만, .bin 비대화 없음).
set(CMAKE_C_FLAGS_RELEASE        "-Os -g")
set(CMAKE_C_FLAGS_RELWITHDEBINFO "-Os -g")
set(CMAKE_C_FLAGS_DEBUG          "-Og -g3")

# =============================================================================
# 경로 변수
# =============================================================================
set(FW_ROOT   "${CMAKE_CURRENT_SOURCE_DIR}")
set(FW_COMMON "${FW_ROOT}/common")
set(FW_CONFIG "${FW_ROOT}/config")
set(FW_CERTS  "${FW_ROOT}/certs")
set(FW_LD     "${FW_ROOT}/ld/STM32F407VGTx_FLASH.ld")
set(TP        "${FW_ROOT}/third_party")   # 벤더링 루트 (폐쇄망)

# 벤더 트리 (scripts/vendor.* 가 채운다). 핀 고정 버전 가정.
#   TODO(vendor): 아래 하위 경로/파일명은 핀 고정 버전에 맞춰 vendor 스크립트와
#   동기화할 것. 버전이 바뀌면 GLOB 결과 및 startup/포트 경로가 달라질 수 있다.
set(CUBE      "${TP}/STM32CubeF4")
set(CMSIS     "${CUBE}/Drivers/CMSIS")
set(HAL       "${CUBE}/Drivers/STM32F4xx_HAL_Driver")
set(FREERTOS  "${TP}/FreeRTOS-Kernel")
set(LWIP      "${TP}/lwip")
set(MBEDTLS   "${TP}/mbedtls")

# 벤더 트리 부재/미벤더링 시 configure 단계에서 명확히 안내한다.
# third_party 디렉터리 자체가 없거나, 디렉터리는 있으나 각 의존성 트리가 아직
# 없으면(README 만 있는 미벤더링 상태) "Cannot find source file" 같은 모호한
# 에러 대신 친절한 안내를 띄운다.
set(_vendor_missing "")
foreach(_v "${CUBE}" "${FREERTOS}" "${LWIP}" "${MBEDTLS}")
    if(NOT EXISTS "${_v}")
        list(APPEND _vendor_missing "${_v}")
    endif()
endforeach()
if(_vendor_missing)
    string(REPLACE ";" "\n    " _vendor_missing_str "${_vendor_missing}")
    message(FATAL_ERROR
        "벤더링되지 않은 의존성이 있습니다(폐쇄망 빌드 전 벤더링 필요).\n"
        "  누락:\n    ${_vendor_missing_str}\n"
        "  networked 빌드 머신에서 먼저 실행하세요:\n"
        "    pwsh firmware/scripts/vendor.ps1   (또는  firmware/scripts/vendor.sh)")
endif()

# =============================================================================
# 컴파일 정의 (전 타깃 공통)
# =============================================================================
#   STM32F407xx        : CMSIS 디바이스 선택
#   USE_HAL_DRIVER     : HAL 활성화
#   (USE_FULL_LL_DRIVER 는 정의하지 않음 → LL 미사용)
#   MBEDTLS_CONFIG_FILE: 메모리 절약형 mbedtls 설정(firmware/common 또는 config 에 위치)
set(COMMON_DEFS
    STM32F407xx
    USE_HAL_DRIVER
    MBEDTLS_CONFIG_FILE="mbedtls_config.h"
)

# =============================================================================
# 인클루드 디렉터리 (전 타깃 공통)
#   - 프로젝트 헤더: common(컨트랙트), config(FreeRTOSConfig/lwipopts/mbedtls_config),
#                    certs(server_ca)
#   - 벤더 헤더: CMSIS, HAL Inc, FreeRTOS include + portable/GCC/ARM_CM4F,
#               lwip src/include, mbedtls include
#
#   참고: mbedtls_config.h / lwipopts.h / stm32f4xx_hal_conf.h 는 프로젝트 측
#   config 또는 common 에 둔다(타 서브에이전트 산출). 두 경로 모두 추가하여
#   배치 위치에 무관하게 찾도록 한다. TODO(hw): 실제 배치 확정 시 한 곳으로 정리.
# =============================================================================
set(COMMON_INCLUDES
    ${FW_COMMON}
    ${FW_CONFIG}
    ${FW_CERTS}

    # ── CMSIS / HAL ──────────────────────────────────────────────────────────
    ${CMSIS}/Include
    ${CMSIS}/Device/ST/STM32F4xx/Include
    ${HAL}/Inc
    ${HAL}/Inc/Legacy

    # ── FreeRTOS (네이티브 API) ──────────────────────────────────────────────
    ${FREERTOS}/include
    ${FREERTOS}/portable/GCC/ARM_CM4F

    # ── LwIP ─────────────────────────────────────────────────────────────────
    ${LWIP}/src/include
    ${LWIP}/src/include/lwip
    ${LWIP}/src/include/netif
    # LwIP 의 sys_arch/cc.h 포트 헤더(FreeRTOS 통합)는 프로젝트 port 에 둔다.
    #   TODO(hw): lwip port(sys_arch.c + arch/cc.h, sys_arch.h) 경로 확정 시 추가.
    #   기본 후보: ${FW_ROOT}/net/lwip_port 또는 ${FW_CONFIG}
    ${LWIP}/contrib/ports/freertos/include

    # ── mbedTLS ──────────────────────────────────────────────────────────────
    ${MBEDTLS}/include
)

# =============================================================================
# 소스 수집
# =============================================================================

# ── (A) 검증된 포터블 코어 + 모든 공통 모듈 .c ──────────────────────────────
# common/*.c 를 GLOB 한다. 검증 코어(sha256_sw/hexutil/sig/jsonbody/sht30_convert/
# httpapi)와 보드 무관 모듈(reporter/sht30/bsp 등)이 모두 포함된다.
# CONFIGURE_DEPENDS: 새 .c 추가 시 재구성 트리거(개발 편의).
file(GLOB COMMON_SOURCES CONFIGURE_DEPENDS "${FW_COMMON}/*.c")

# 임베드된 서버 CA(PEM → C 배열).
list(APPEND COMMON_SOURCES "${FW_CERTS}/server_ca.c")

# ── (B) 벤더: CMSIS startup(어셈블리) + system 초기화 ───────────────────────
# startup 은 GCC 변형(Templates/gcc) 사용. 정확한 경로는 벤더 버전에 따라 다름.
#   TODO(vendor): 핀 고정 CubeF4 의 startup/system 경로를 확인하여 갱신.
set(STARTUP_S   "${CMSIS}/Device/ST/STM32F4xx/Source/Templates/gcc/startup_stm32f407xx.s")
set(SYSTEM_C    "${CMSIS}/Device/ST/STM32F4xx/Source/Templates/system_stm32f4xx.c")

# ── (C) 벤더: STM32F4xx HAL 드라이버 ────────────────────────────────────────
# 필요한 HAL 모듈만 컴파일하면 크기/시간이 줄지만, 의존이 많아 GLOB 후
# 템플릿(_template) 파일만 제외하는 방식이 견고하다.
#   TODO(vendor): 미사용 모듈(예: ll_*, 사용 안 하는 주변장치)은 빌드 시간 절약을
#   위해 선택적으로 제외 가능. 우선 정확성 위해 전체 컴파일.
file(GLOB HAL_SOURCES CONFIGURE_DEPENDS "${HAL}/Src/*.c")
# *_template.c 는 사용자 재정의용 예제이므로 제외(중복 심볼/약한 정의 회피).
list(FILTER HAL_SOURCES EXCLUDE REGEX ".*_template\\.c$")

# ── (D) 벤더: FreeRTOS 커널 + heap_4 + Cortex-M4F 포트 ──────────────────────
#   TODO(vendor): FreeRTOS-Kernel 루트의 *.c (tasks/queue/list/timers/event_groups/
#   stream_buffer/croutine) 를 컴파일. heap_4 와 ARM_CM4F port 는 명시 지정.
file(GLOB FREERTOS_CORE CONFIGURE_DEPENDS "${FREERTOS}/*.c")
set(FREERTOS_SOURCES
    ${FREERTOS_CORE}
    ${FREERTOS}/portable/MemMang/heap_4.c
    ${FREERTOS}/portable/GCC/ARM_CM4F/port.c
)

# ── (E) 벤더: LwIP 코어 + IPv4 + netif + apps/sntp + FreeRTOS sys_arch ──────
# LwIP 는 Filelists.cmake 를 제공하지만 버전에 따라 변수명이 다르므로 GLOB 후
# 필요한 하위 트리만 포함한다.
#   TODO(vendor): 핀 고정 lwip 버전의 src/Filelists.cmake 를 include 하여
#   ${lwipcore_SRCS} 등 공식 목록을 쓰는 편이 더 견고함. 우선 GLOB 로 구성.
file(GLOB LWIP_CORE      CONFIGURE_DEPENDS "${LWIP}/src/core/*.c")
file(GLOB LWIP_CORE_IPV4 CONFIGURE_DEPENDS "${LWIP}/src/core/ipv4/*.c")
file(GLOB LWIP_NETIF     CONFIGURE_DEPENDS "${LWIP}/src/netif/*.c")
file(GLOB LWIP_API       CONFIGURE_DEPENDS "${LWIP}/src/api/*.c")
# SNTP 앱(시간 동기 — timesync.c 가 사용).
set(LWIP_SNTP "${LWIP}/src/apps/sntp/sntp.c")
# FreeRTOS sys_arch 포트(LwIP ↔ FreeRTOS 통합). 위치는 벤더 contrib.
#   TODO(hw): sys_arch.c 가 프로젝트 net/lwip_port 에 있다면 그쪽을 사용.
set(LWIP_SYS_ARCH "${LWIP}/contrib/ports/freertos/sys_arch.c")
set(LWIP_SOURCES
    ${LWIP_CORE}
    ${LWIP_CORE_IPV4}
    ${LWIP_NETIF}
    ${LWIP_API}
    ${LWIP_SNTP}
    ${LWIP_SYS_ARCH}
)

# ── (F) 벤더: mbedTLS 라이브러리 ────────────────────────────────────────────
#   TODO(vendor): library/*.c 전체 컴파일(설정 헤더에서 미사용 기능은 컴파일
#   아웃되므로 코드 크기에는 큰 영향 없음). psa/ 하위는 사용 안 하면 제외 가능.
file(GLOB MBEDTLS_SOURCES CONFIGURE_DEPENDS "${MBEDTLS}/library/*.c")

# =============================================================================
# 공유 펌웨어 INTERFACE 라이브러리(정의/인클루드/플래그 모음)
#  - 두 실행 타깃이 동일 베이스를 공유하되, 보드 매크로만 다르게 한다.
# =============================================================================
add_library(fw_base INTERFACE)
target_include_directories(fw_base INTERFACE ${COMMON_INCLUDES})
target_compile_definitions(fw_base INTERFACE ${COMMON_DEFS})
target_compile_options(fw_base INTERFACE
    -Wall -Wextra
    # HAL/LwIP/mbedtls 벤더 코드의 경고로 빌드가 막히지 않게 -Werror 는 미사용.
    $<$<COMPILE_LANGUAGE:C>:-fno-common>
)

# 벤더 소스(타깃 무관 동일). 두 실행파일에 그대로 포함된다.
set(VENDOR_SOURCES
    ${STARTUP_S}
    ${SYSTEM_C}
    ${HAL_SOURCES}
    ${FREERTOS_SOURCES}
    ${LWIP_SOURCES}
    ${MBEDTLS_SOURCES}
)

# =============================================================================
# 실행 타깃 생성 헬퍼
#   board_dir: board_sht30 (보드 전용 main/app 소스)
#   board_def: BOARD_SHT30
# =============================================================================
function(add_board_firmware target_name board_dir board_def)
    # 보드 전용 소스 수집(main.c + app_*.c 등). GLOB + CONFIGURE_DEPENDS.
    file(GLOB BOARD_SOURCES CONFIGURE_DEPENDS "${FW_ROOT}/${board_dir}/*.c")
    if(NOT BOARD_SOURCES)
        message(FATAL_ERROR "${board_dir} 에 소스(.c)가 없습니다: ${target_name}")
    endif()

    add_executable(${target_name}
        ${BOARD_SOURCES}
        ${COMMON_SOURCES}
        ${VENDOR_SOURCES}
    )

    target_link_libraries(${target_name} PRIVATE fw_base)
    target_compile_definitions(${target_name} PRIVATE ${board_def})

    # 링커 스크립트 + 맵 파일. -T 로 .ld 지정, .map 으로 메모리 맵 출력.
    target_link_options(${target_name} PRIVATE
        -T${FW_LD}
        -Wl,-Map=$<TARGET_FILE_DIR:${target_name}>/${target_name}.map,--cref
    )
    # .ld 변경 시 재링크 트리거.
    set_target_properties(${target_name} PROPERTIES
        LINK_DEPENDS "${FW_LD}"
        SUFFIX ".elf")

    # ── 후처리: .bin / .hex 생성 + 크기 출력 ───────────────────────────────
    set(elf "$<TARGET_FILE:${target_name}>")
    set(out_dir "$<TARGET_FILE_DIR:${target_name}>")
    add_custom_command(TARGET ${target_name} POST_BUILD
        COMMAND ${CMAKE_OBJCOPY} -O binary   ${elf} ${out_dir}/${target_name}.bin
        COMMAND ${CMAKE_OBJCOPY} -O ihex     ${elf} ${out_dir}/${target_name}.hex
        COMMAND ${CMAKE_SIZE} --format=berkeley ${elf}
        COMMENT "[${target_name}] objcopy → .bin/.hex, size 출력"
        VERBATIM)
endfunction()

# =============================================================================
# 펌웨어 타깃 (SHT30 단일 보드)
# =============================================================================
add_board_firmware(sht30_fw board_sht30 BOARD_SHT30)

# =============================================================================
# Configure 요약
# =============================================================================
message(STATUS "──────────────────────────────────────────────")
message(STATUS " ${PROJECT_NAME}  ${PROJECT_VERSION}")
message(STATUS "  toolchain : ${CMAKE_C_COMPILER}")
message(STATUS "  build type: ${CMAKE_BUILD_TYPE}")
message(STATUS "  ld script : ${FW_LD}")
message(STATUS "  third_party: ${TP}")
message(STATUS "  targets   : sht30_fw (BOARD_SHT30)")
message(STATUS "──────────────────────────────────────────────")
