/******************************************************************************* * @file main.c * @brief Medithings VesiScan BASIC * @version 1.17 * @date 2025-12-09 * @author Charles KWON * * @note Build Modes: * DEBUG_MINIMAL_BOOT = 1 : Power + BLE only (no sensors, no EEPROM) * DEBUG_MINIMAL_BOOT = 0 : Full initialization * BLE_DEV_MODE = 1 : No security (fast pairing) * BLE_DEV_MODE = 0 : Full security (passkey required) ******************************************************************************/ /*============================================================================== * INCLUDES *============================================================================*/ #include #include #include "nordic_common.h" #include "nrf.h" #include "ble_hci.h" #include "ble_advdata.h" #include "ble_advertising.h" #include "ble_srv_common.h" #include "ble_conn_params.h" #include "nrf_sdh.h" #include "nrf_sdh_soc.h" #include "nrf_sdh_ble.h" #include "nrf_ble_gatt.h" #include "nrf_ble_qwr.h" #include "app_timer.h" #include "ble_nus.h" #include "app_uart.h" #include "app_util_platform.h" #include "bsp_btn_ble.h" #include "nrf_pwr_mgmt.h" #include "nrf_delay.h" #include "math.h" #include "crc16.h" #if defined (UART_PRESENT) #include "nrf_uart.h" #endif #if defined (UARTE_PRESENT) #include "nrf_uarte.h" #endif #include "nrf_log.h" #include "nrf_log_ctrl.h" #include "nrf_log_default_backends.h" #if BLE_DFU_ENABLED #include "nrf_pwr_mgmt.h" #include "ble_dfu.h" #include "nrf_power.h" #include "nrf_bootloader_info.h" #include "nrf_dfu_ble_svci_bond_sharing.h" #include "nrf_svci_async_function.h" #include "nrf_svci_async_handler.h" #endif #include "system_interface.h" #include "main.h" #include "app_raw_main.h" #include "main_timer.h" #include "power_control.h" #include "tmp235_q1.h" #include "fds.h" #include "battery_saadc.h" #if FEATURE_SECURE_CONNECTION #include "peer_manager.h" #include "peer_manager_handler.h" #include "nrf_ble_lesc.h" #include "ble_quick_security.h" #include "i2c_manager.h" #endif #include "nrf_crypto.h" #include #include "debug_print.h" #include "fstorage.h" #define HARDWARE_VERSION "VB0HW0000" #define FIRMWARE_VERSION "VB0FW0000" #define FIRMWARE_SERIAL_NO "VB026030000" /*============================================================================== * BUILD CONFIGURATION *============================================================================*/ #define BLE_DEV_MODE 1 /**< 1: DEV (no security), 0: PROD (full security) */ #define DEBUG_MINIMAL_BOOT 1 /**< 1: Power+BLE only, 0: Full boot */ /*============================================================================== * HARDWARE PIN DEFINITIONS *============================================================================*/ #define POWER_HOLD NRF_GPIO_PIN_MAP(0,8) #define POWER_BUTTON NRF_GPIO_PIN_MAP(1,8) /*============================================================================== * BLE CONFIGURATION *============================================================================*/ #define APP_BLE_CONN_CFG_TAG 1 #define NUS_SERVICE_UUID_TYPE BLE_UUID_TYPE_VENDOR_BEGIN #define APP_BLE_OBSERVER_PRIO 3 #define APP_ADV_INTERVAL 64 #if FEATURE_NO_SLEEP #define APP_ADV_DURATION 0 #else #define APP_ADV_DURATION 18000 #endif #define MIN_CONN_INTERVAL MSEC_TO_UNITS(20, UNIT_1_25_MS) #define MAX_CONN_INTERVAL MSEC_TO_UNITS(75, UNIT_1_25_MS) #define SLAVE_LATENCY 0 #define CONN_SUP_TIMEOUT MSEC_TO_UNITS(4000, UNIT_10_MS) #define FIRST_CONN_PARAMS_UPDATE_DELAY APP_TIMER_TICKS(5000) #define NEXT_CONN_PARAMS_UPDATE_DELAY APP_TIMER_TICKS(30000) #define MAX_CONN_PARAMS_UPDATE_COUNT 3 #if FEATURE_SECURE_CONNECTION #define LESC_DEBUG_MODE 0 #define SEC_PARAM_BOND 1 #define SEC_PARAM_MITM 1 #if FEATURE_STATIC_PASSKEY #define SEC_PARAM_LESC 0 #else #define SEC_PARAM_LESC 1 #endif #define SEC_PARAM_KEYPRESS 0 #define SEC_PARAM_IO_CAPABILITIES BLE_GAP_IO_CAPS_DISPLAY_ONLY #define SEC_PARAM_OOB 0 #define SEC_PARAM_MIN_KEY_SIZE 7 #define SEC_PARAM_MAX_KEY_SIZE 16 #define PASSKEY_TXT_LENGTH 8 #endif /*============================================================================== * SYSTEM CONSTANTS *============================================================================*/ #define DEAD_BEEF 0xDEADBEEF #define UART_TX_BUF_SIZE 16384 #define UART_RX_BUF_SIZE 512 #define POWER_ON_DELAY 5 #define POWER_OFF_DELAY 3000 #define POWER_RESET_DELAY 2000 #define LED_NUM 24 #define AES_KEY_SIZE 16 #define AES_BLOCK_SIZE 16 /*============================================================================== * BLE INSTANCES *============================================================================*/ BLE_NUS_DEF(m_nus, NRF_SDH_BLE_TOTAL_LINK_COUNT); NRF_BLE_GATT_DEF(m_gatt); NRF_BLE_QWR_DEF(m_qwr); BLE_ADVERTISING_DEF(m_advertising); /*============================================================================== * TIMER INSTANCES *============================================================================*/ APP_TIMER_DEF(m_power_on_delay_timer_id); APP_TIMER_DEF(m_power_off_delay_timer_id); APP_TIMER_DEF(m_PM_timer_id); /*============================================================================== * STATIC VARIABLES *============================================================================*/ #if FEATURE_SECURE_CONNECTION static pm_peer_id_t m_peer_to_be_deleted = PM_PEER_ID_INVALID; #endif static uint16_t m_conn_handle = BLE_CONN_HANDLE_INVALID; static uint16_t m_ble_nus_max_data_len = BLE_GATT_ATT_MTU_DEFAULT - 3; static ble_uuid_t m_adv_uuids[] = {{BLE_UUID_NUS_SERVICE, NUS_SERVICE_UUID_TYPE}}; static uint8_t m_tx_buffer[BLE_NUS_MAX_DATA_LEN]; static uint16_t m_tx_len = 0; static volatile bool m_tx_in_progress = false; static volatile bool m_tx_complete_pending = false; /* TX completion wait flag */ static uint8_t c_addr[6]; static char * roles_str[] = {"INVALID_ROLE", "CENTRAL", "PERIPHERAL"}; /*============================================================================== * GLOBAL VARIABLES *============================================================================*/ uint8_t m_encrypted_text[AES_BLOCK_SIZE]; uint8_t m_encrypted_text2[AES_BLOCK_SIZE]; uint8_t m_decrypted_text[AES_BLOCK_SIZE]; volatile uint8_t Sj_type; volatile bool processing; bool power_off_duble_prohibit = false; volatile bool power_state = false; extern bool go_device_power_off; extern bool go_sleep_mode_enter; extern bool go_NVIC_SystemReset; extern bool ble_got_new_data; extern bool motion_data_once; extern bool con_single; extern bool info4; extern uint8_t add_cycle; extern bool motion_raw_data_enabled; uint16_t cnt_s; char ble_tx_buffer[BLE_NUS_MAX_DATA_LEN]; uint16_t ble_bin_buff[BLE_NUS_MAX_DATA_LEN/2]; which_cmd_t cmd_type_t; bool device_status = false; bool device_reset = true; #if FEATURE_SECURE_CONNECTION bool erase_bonds; #endif volatile bool ble_connection_st; volatile bool data_tx_in_progress = false; extern char m_static_passkey[PASSKEY_LENGTH]; extern char SERIAL_NO[SERIAL_NO_LENGTH]; extern bool bond_data_delete; uint8_t m_reset_status; extern uint32_t m_life_cycle; /*============================================================================== * SUPPRESS WARNINGS *============================================================================*/ #if FEATURE_SECURE_CONNECTION static void __attribute__((unused)) suppress_unused_warnings(void) { (void)m_peer_to_be_deleted; (void)roles_str; (void)c_addr; } #endif /*============================================================================== * POWER MANAGEMENT *============================================================================*/ static void power_hold_init(void) { NRF_P0->DIRSET = (1 << 8); NRF_P0->OUTSET = (1 << 8); } static void power_control_handler(on_off_cont_t device_power_st) { if (device_power_st == OFF) { nrf_gpio_pin_clear(POWER_HOLD); DBG_PRINTF("[PWR] OFF\r\n"); } else if (device_power_st == ON) { nrf_gpio_pin_set(POWER_HOLD); DBG_PRINTF("[PWR] ON\r\n"); } } /*============================================================================== * GPIO INITIALIZATION *============================================================================*/ static void minimal_gpio_init(void) { nrf_gpio_cfg_input(POWER_BUTTON, NRF_GPIO_PIN_NOPULL); nrf_gpio_cfg_output(POWER_HOLD); nrf_gpio_pin_set(POWER_HOLD); power_gpio_init(); DBG_PRINTF("[GPIO] Minimal OK (BTN=%d)\r\n", nrf_gpio_pin_read(POWER_BUTTON)); } #if !DEBUG_MINIMAL_BOOT static void full_gpio_init(void) { minimal_gpio_init(); eeprom_control(OFF); DBG_PRINTF("[GPIO] Full OK\r\n"); } #endif /*============================================================================== * CONFIGURATION LOADING *============================================================================*/ static void load_default_config(void) { memset(SERIAL_NO, 0, SERIAL_NO_LENGTH); memcpy(SERIAL_NO, FIRMWARE_SERIAL_NO, strlen(FIRMWARE_SERIAL_NO)); memset(m_static_passkey, 0, PASSKEY_LENGTH); memcpy(m_static_passkey, "123456", PASSKEY_LENGTH); m_reset_status = 1; bond_data_delete = 1; DBG_PRINTF("[CFG] Default (S/N=%s)\r\n", SERIAL_NO); } /**@brief Load configuration from internal Flash (FDS) into global variables. * * Must be called AFTER ble_stack_init() → fs_storage_init() → config_load(). * Reads m_config (populated by config_load) and copies to runtime globals. */ static bool m_need_save_defaults = false; static void load_flash_config(void) { m_need_save_defaults = false; /* Hardware Number — fill default if empty */ if (m_config.hw_no[0] == 0 || m_config.hw_no[0] == (char)0xFF) { memset(m_config.hw_no, 0, HW_NO_LENGTH); memcpy(m_config.hw_no, HARDWARE_VERSION, strlen(HARDWARE_VERSION)); DBG_PRINTF("[CFG] HW empty, set default: %s\r\n", HARDWARE_VERSION); m_need_save_defaults = true; } /* Serial Number — fill default if empty */ if (m_config.serial_no[0] == 0 || m_config.serial_no[0] == (char)0xFF) { memset(m_config.serial_no, 0, SERIAL_NO_LENGTH); memcpy(m_config.serial_no, FIRMWARE_SERIAL_NO, strlen(FIRMWARE_SERIAL_NO)); DBG_PRINTF("[CFG] S/N empty, set default: %s\r\n", FIRMWARE_SERIAL_NO); m_need_save_defaults = true; } /* Serial Number → BLE device name */ memset(SERIAL_NO, 0, SERIAL_NO_LENGTH); memcpy(SERIAL_NO, m_config.serial_no, SERIAL_NO_LENGTH); /* Passkey */ memset(m_static_passkey, 0, PASSKEY_LENGTH); memcpy(m_static_passkey, m_config.static_passkey, PASSKEY_LENGTH); /* Bond data delete flag */ bond_data_delete = m_config.bond_data_delete; /* Reset status */ m_reset_status = m_config.reset_status; DBG_PRINTF("[CFG] HW=%.12s S/N=%s passkey=%.6s bond=%d rst=%d\r\n", m_config.hw_no, SERIAL_NO, m_static_passkey, bond_data_delete, m_reset_status); } /* load_eeprom_config() 삭제 — EEPROM 제거됨, FDS가 대체 - jhChun 26.03.16 */ /*============================================================================== * TIMER CALLBACKS *============================================================================*/ static void main_s(void * p_context); static void t_power_off_timeout_handler(void * p_context); static void PM_s(void * p_context); static void t_power_off_timeout_handler(void * p_context) { UNUSED_PARAMETER(p_context); APP_ERROR_CHECK(app_timer_stop(m_power_off_delay_timer_id)); DBG_PRINTF("[PWR] Off timeout\r\n"); bsp_indication_set(BSP_INDICATE_USER_STATE_OFF); power_control_handler(OFF); } static void PM_s(void * p_context) { UNUSED_PARAMETER(p_context); APP_ERROR_CHECK(app_timer_stop(m_PM_timer_id)); if (m_reset_status == 5) { DBG_PRINTF("[PM] Kill\r\n"); sd_ble_gap_disconnect(m_conn_handle, BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION); } } /*============================================================================== * TIMER FUNCTIONS *============================================================================*/ static void timers_init(void) { ret_code_t err_code = app_timer_init(); APP_ERROR_CHECK(err_code); APP_ERROR_CHECK(app_timer_create(&m_power_on_delay_timer_id, APP_TIMER_MODE_SINGLE_SHOT, main_s)); APP_ERROR_CHECK(app_timer_create(&m_power_off_delay_timer_id, APP_TIMER_MODE_SINGLE_SHOT, t_power_off_timeout_handler)); APP_ERROR_CHECK(app_timer_create(&m_PM_timer_id, APP_TIMER_MODE_SINGLE_SHOT, PM_s)); main_timer_init(); battery_timer_init(); power_timer_init(); #if FEATURE_PRINTF full_timer_init(); #endif } static void timers_start(void) { ret_code_t err_code; err_code = app_timer_start(m_power_on_delay_timer_id, APP_TIMER_TICKS(POWER_ON_DELAY), NULL); APP_ERROR_CHECK(err_code); } /*============================================================================== * BLE DFU HANDLER *============================================================================*/ #if BLE_DFU_ENABLED static void ble_dfu_evt_handler(ble_dfu_buttonless_evt_type_t event) { switch (event) { case BLE_DFU_EVT_BOOTLOADER_ENTER_PREPARE: DBG_PRINTF("[DFU] Prepare\r\n"); break; case BLE_DFU_EVT_BOOTLOADER_ENTER: DBG_PRINTF("[DFU] Enter\r\n"); break; case BLE_DFU_EVT_BOOTLOADER_ENTER_FAILED: DBG_PRINTF("[DFU] Failed\r\n"); break; case BLE_DFU_EVT_RESPONSE_SEND_ERROR: DBG_PRINTF("[DFU] Error\r\n"); APP_ERROR_CHECK(false); break; default: break; } } #endif /*============================================================================== * BLE GAP FUNCTIONS *============================================================================*/ static void gap_params_init(void) { uint32_t err_code; ble_gap_conn_params_t gap_conn_params; ble_gap_conn_sec_mode_t sec_mode; #if FEATURE_STATIC_PASSKEY ble_opt_t ble_opt; #endif BLE_GAP_CONN_SEC_MODE_SET_OPEN(&sec_mode); err_code = sd_ble_gap_device_name_set(&sec_mode, (const uint8_t *) SERIAL_NO, strlen(SERIAL_NO)); APP_ERROR_CHECK(err_code); memset(&gap_conn_params, 0, sizeof(gap_conn_params)); gap_conn_params.min_conn_interval = MIN_CONN_INTERVAL; gap_conn_params.max_conn_interval = MAX_CONN_INTERVAL; gap_conn_params.slave_latency = SLAVE_LATENCY; gap_conn_params.conn_sup_timeout = CONN_SUP_TIMEOUT; err_code = sd_ble_gap_ppcp_set(&gap_conn_params); APP_ERROR_CHECK(err_code); #if FEATURE_STATIC_PASSKEY ble_opt.gap_opt.passkey.p_passkey = (const uint8_t *)m_static_passkey; err_code = sd_ble_opt_set(BLE_GAP_OPT_PASSKEY, &ble_opt); APP_ERROR_CHECK(err_code); #endif } /*============================================================================== * BLE SERVICE FUNCTIONS *============================================================================*/ static void nrf_qwr_error_handler(uint32_t nrf_error) { APP_ERROR_HANDLER(nrf_error); } /* Forward declaration for async MAA TX ready handler */ extern bool maa_async_on_tx_ready(void); extern bool maa_async_is_busy(void); static void nus_data_handler(ble_nus_evt_t * p_evt) { if (p_evt->type == BLE_NUS_EVT_RX_DATA) { cmd_type_t = CMD_BLE; DBG_PRINTF("[NUS] RX len=%d\r\n", p_evt->params.rx_data.length); received_command_process(p_evt->params.rx_data.p_data, CMD_BLE, p_evt->params.rx_data.length); } else if (p_evt->type == BLE_NUS_EVT_TX_RDY) { /* BLE TX buffer has space - continue async MAA transmission */ if (maa_async_is_busy()) { maa_async_on_tx_ready(); } } } static void services_init(void) { uint32_t err_code; ble_nus_init_t nus_init; nrf_ble_qwr_init_t qwr_init = {0}; qwr_init.error_handler = nrf_qwr_error_handler; err_code = nrf_ble_qwr_init(&m_qwr, &qwr_init); APP_ERROR_CHECK(err_code); memset(&nus_init, 0, sizeof(nus_init)); nus_init.data_handler = nus_data_handler; err_code = ble_nus_init(&m_nus, &nus_init); APP_ERROR_CHECK(err_code); #if BLE_DFU_ENABLED ble_dfu_buttonless_init_t dfus_init = {0}; dfus_init.evt_handler = ble_dfu_evt_handler; err_code = ble_dfu_buttonless_init(&dfus_init); APP_ERROR_CHECK(err_code); #endif } /*============================================================================== * BLE CONNECTION PARAMETERS *============================================================================*/ static void on_conn_params_evt(ble_conn_params_evt_t * p_evt) { if (p_evt->evt_type == BLE_CONN_PARAMS_EVT_FAILED) { uint32_t err_code = sd_ble_gap_disconnect(m_conn_handle, BLE_HCI_CONN_INTERVAL_UNACCEPTABLE); APP_ERROR_CHECK(err_code); } } static void conn_params_error_handler(uint32_t nrf_error) { APP_ERROR_HANDLER(nrf_error); } static void conn_params_init(void) { uint32_t err_code; ble_conn_params_init_t cp_init; memset(&cp_init, 0, sizeof(cp_init)); cp_init.p_conn_params = NULL; cp_init.first_conn_params_update_delay = FIRST_CONN_PARAMS_UPDATE_DELAY; cp_init.next_conn_params_update_delay = NEXT_CONN_PARAMS_UPDATE_DELAY; cp_init.max_conn_params_update_count = MAX_CONN_PARAMS_UPDATE_COUNT; cp_init.start_on_notify_cccd_handle = BLE_GATT_HANDLE_INVALID; cp_init.disconnect_on_fail = false; cp_init.evt_handler = on_conn_params_evt; cp_init.error_handler = conn_params_error_handler; err_code = ble_conn_params_init(&cp_init); APP_ERROR_CHECK(err_code); } /*============================================================================== * BLE STACK INITIALIZATION *============================================================================*/ static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context); static void ble_stack_init(void) { ret_code_t err_code; err_code = nrf_sdh_enable_request(); APP_ERROR_CHECK(err_code); uint32_t ram_start = 0; err_code = nrf_sdh_ble_default_cfg_set(APP_BLE_CONN_CFG_TAG, &ram_start); APP_ERROR_CHECK(err_code); err_code = nrf_sdh_ble_enable(&ram_start); APP_ERROR_CHECK(err_code); NRF_SDH_BLE_OBSERVER(m_ble_observer, APP_BLE_OBSERVER_PRIO, ble_evt_handler, NULL); } /*============================================================================== * BLE GATT *============================================================================*/ void gatt_evt_handler(nrf_ble_gatt_t * p_gatt, nrf_ble_gatt_evt_t const * p_evt) { if ((m_conn_handle == p_evt->conn_handle) && (p_evt->evt_id == NRF_BLE_GATT_EVT_ATT_MTU_UPDATED)) { m_ble_nus_max_data_len = p_evt->params.att_mtu_effective - OPCODE_LENGTH - HANDLE_LENGTH; } } static void gatt_init(void) { ret_code_t err_code; err_code = nrf_ble_gatt_init(&m_gatt, gatt_evt_handler); APP_ERROR_CHECK(err_code); err_code = nrf_ble_gatt_att_mtu_periph_set(&m_gatt, NRF_SDH_BLE_GATT_MAX_MTU_SIZE); APP_ERROR_CHECK(err_code); } /*============================================================================== * BLE ADVERTISING *============================================================================*/ static void on_adv_evt(ble_adv_evt_t ble_adv_evt) { switch (ble_adv_evt) { case BLE_ADV_EVT_FAST: bsp_indication_set(BSP_INDICATE_ADVERTISING); DBG_PRINTF("[ADV] Fast\r\n"); break; case BLE_ADV_EVT_IDLE: DBG_PRINTF("[ADV] Idle\r\n"); go_sleep_mode_enter = true; main_timer_start(); break; default: break; } } static void advertising_init(void) { uint32_t err_code; ble_advertising_init_t init; memset(&init, 0, sizeof(init)); init.advdata.name_type = BLE_ADVDATA_FULL_NAME; init.advdata.include_appearance = true; #if FEATURE_NO_SLEEP init.advdata.flags = BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE; #else init.advdata.flags = BLE_GAP_ADV_FLAGS_LE_ONLY_LIMITED_DISC_MODE; #endif init.srdata.uuids_complete.uuid_cnt = sizeof(m_adv_uuids) / sizeof(m_adv_uuids[0]); init.srdata.uuids_complete.p_uuids = m_adv_uuids; init.config.ble_adv_fast_enabled = true; init.config.ble_adv_fast_interval = APP_ADV_INTERVAL; init.config.ble_adv_fast_timeout = APP_ADV_DURATION; init.evt_handler = on_adv_evt; err_code = ble_advertising_init(&m_advertising, &init); APP_ERROR_CHECK(err_code); ble_advertising_conn_cfg_tag_set(&m_advertising, APP_BLE_CONN_CFG_TAG); } #if FEATURE_SECURE_CONNECTION static void delete_bonds(void) { ret_code_t err_code; DBG_PRINTF("[PM] Erase bonds\r\n"); err_code = pm_peers_delete(); APP_ERROR_CHECK(err_code); } static void advertising_start(bool erase_bonds_flag) { if (erase_bonds_flag == true) { bond_data_delete = false; delete_bonds(); } else { ret_code_t err_code = ble_advertising_start(&m_advertising, BLE_ADV_MODE_FAST); APP_ERROR_CHECK(err_code); } } #else static void advertising_start(void) { uint32_t err_code = ble_advertising_start(&m_advertising, BLE_ADV_MODE_FAST); APP_ERROR_CHECK(err_code); } #endif /*============================================================================== * BLE SECURITY (PEER MANAGER) *============================================================================*/ #if FEATURE_SECURE_CONNECTION static void pm_evt_handler(pm_evt_t const * p_evt) { pm_peer_data_bonding_t peer_bonding_data; uint32_t return_code; ret_code_t err_code; pm_handler_on_pm_evt(p_evt); pm_handler_disconnect_on_sec_failure(p_evt); pm_handler_flash_clean(p_evt); ble_security_quick_pm_handler(p_evt); switch (p_evt->evt_id) { case PM_EVT_CONN_SEC_SUCCEEDED: { pm_conn_sec_status_t conn_sec_status; err_code = pm_conn_sec_status_get(p_evt->conn_handle, &conn_sec_status); APP_ERROR_CHECK(err_code); if (conn_sec_status.mitm_protected || BLE_DEV_MODE) { DBG_PRINTF("[PM] Secured\r\n"); ble_connection_st = 1; battery_timer_start(); } else { DBG_PRINTF("[PM] Sec FAIL\r\n"); err_code = pm_peer_id_get(m_conn_handle, &m_peer_to_be_deleted); APP_ERROR_CHECK(err_code); err_code = sd_ble_gap_disconnect(m_conn_handle, BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION); APP_ERROR_CHECK(err_code); } } break; case PM_EVT_CONN_SEC_FAILED: DBG_PRINTF("[PM] Sec failed\r\n"); if (p_evt->params.conn_sec_failed.error == PM_CONN_SEC_ERROR_PIN_OR_KEY_MISSING) { err_code = pm_conn_secure(p_evt->conn_handle, true); if (err_code != NRF_ERROR_INVALID_STATE) { APP_ERROR_CHECK(err_code); } } break; case PM_EVT_PEERS_DELETE_SUCCEEDED: advertising_start(false); break; case PM_EVT_CONN_SEC_CONFIG_REQ: { pm_conn_sec_config_t conn_sec_config = {.allow_repairing = true}; pm_conn_sec_config_reply(p_evt->conn_handle, &conn_sec_config); } break; case PM_EVT_PEER_DATA_UPDATE_SUCCEEDED: { return_code = pm_peer_data_bonding_load(p_evt->peer_id, &peer_bonding_data); if (return_code == NRF_SUCCESS) { memcpy(c_addr, peer_bonding_data.peer_ble_id.id_addr_info.addr, sizeof(c_addr)); } m_reset_status = 10; } break; default: break; } } static void peer_manager_init(void) { ret_code_t err_code; ble_security_quick_init(BLE_DEV_MODE); err_code = pm_register(pm_evt_handler); APP_ERROR_CHECK(err_code); DBG_PRINTF("[PM] Init (mode=%d)\r\n", BLE_DEV_MODE); } #endif /*============================================================================== * BLE EVENT HANDLER *============================================================================*/ static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context) { uint32_t err_code; #if FEATURE_STATIC_PASSKEY ble_gap_evt_t const * p_gap_evt = &p_ble_evt->evt.gap_evt; #endif #if FEATURE_SECURE_CONNECTION pm_handler_secure_on_connection(p_ble_evt); #endif switch (p_ble_evt->header.evt_id) { case BLE_GAP_EVT_DISCONNECTED: DBG_PRINTF("[BLE] Disconnected\r\n"); ble_connection_st = 0; m_conn_handle = BLE_CONN_HANDLE_INVALID; m_tx_in_progress = false; if (device_status == true) { if (device_sleep_mode() == 0) { device_status = false; } } break; case BLE_GAP_EVT_CONNECTED: DBG_PRINTF("[BLE] Connected\r\n"); #if FEATURE_SECURE_CONNECTION ble_connection_st = 1; battery_timer_start(); #endif m_conn_handle = p_ble_evt->evt.gap_evt.conn_handle; err_code = nrf_ble_qwr_conn_handle_assign(&m_qwr, m_conn_handle); APP_ERROR_CHECK(err_code); sd_ble_gap_tx_power_set(BLE_GAP_TX_POWER_ROLE_CONN, m_conn_handle, 8); err_code = bsp_indication_set(BSP_INDICATE_CONNECTED); APP_ERROR_CHECK(err_code); #if DEBUG_MINIMAL_BOOT DBG_PRINTF("[BLE] Minimal mode\r\n"); #endif break; case BLE_GAP_EVT_PHY_UPDATE_REQUEST: { ble_gap_phys_t const phys = { .rx_phys = BLE_GAP_PHY_AUTO, .tx_phys = BLE_GAP_PHY_AUTO, }; err_code = sd_ble_gap_phy_update(p_ble_evt->evt.gap_evt.conn_handle, &phys); APP_ERROR_CHECK(err_code); } break; case BLE_GATTC_EVT_TIMEOUT: case BLE_GATTS_EVT_TIMEOUT: DBG_PRINTF("[BLE] Timeout\r\n"); err_code = sd_ble_gap_disconnect(p_ble_evt->evt.gap_evt.conn_handle, BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION); APP_ERROR_CHECK(err_code); break; case BLE_GAP_EVT_SEC_PARAMS_REQUEST: #if !FEATURE_SECURE_CONNECTION err_code = sd_ble_gap_sec_params_reply(m_conn_handle, BLE_GAP_SEC_STATUS_PAIRING_NOT_SUPP, NULL, NULL); APP_ERROR_CHECK(err_code); #endif break; #if FEATURE_SECURE_CONNECTION case BLE_GAP_EVT_PASSKEY_DISPLAY: { char passkey[PASSKEY_LENGTH + 1]; memcpy(passkey, p_ble_evt->evt.gap_evt.params.passkey_display.passkey, PASSKEY_LENGTH); passkey[PASSKEY_LENGTH] = 0; DBG_PRINTF("[BLE] Passkey: %s\r\n", passkey); } break; case BLE_GAP_EVT_AUTH_KEY_REQUEST: #if FEATURE_STATIC_PASSKEY if (p_gap_evt->params.auth_key_request.key_type == BLE_GAP_AUTH_KEY_TYPE_PASSKEY) { err_code = sd_ble_gap_auth_key_reply(p_gap_evt->conn_handle, BLE_GAP_AUTH_KEY_TYPE_PASSKEY, (const uint8_t *)m_static_passkey); APP_ERROR_CHECK(err_code); } #endif break; case BLE_GAP_EVT_LESC_DHKEY_REQUEST: break; #endif case BLE_GATTS_EVT_HVN_TX_COMPLETE: m_tx_in_progress = false; m_tx_complete_pending = false; /* Signal TX completion to waiting functions */ break; default: break; } } /*============================================================================== * BSP EVENT HANDLER *============================================================================*/ void bsp_event_handler(bsp_event_t event) { uint32_t err_code; switch (event) { case BSP_EVENT_SLEEP: DBG_PRINTF("[BSP] Sleep\r\n"); go_sleep_mode_enter = true; main_timer_start(); break; case BSP_EVENT_DISCONNECT: err_code = sd_ble_gap_disconnect(m_conn_handle, BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION); if (err_code != NRF_SUCCESS) { m_reset_status = 2; } break; case BSP_EVENT_WHITELIST_OFF: if (m_conn_handle == BLE_CONN_HANDLE_INVALID) { err_code = ble_advertising_restart_without_whitelist(&m_advertising); if (err_code != NRF_ERROR_INVALID_STATE) { APP_ERROR_CHECK(err_code); } } break; case BSP_EVENT_POWER_CONTROL: if (processing == false) { DBG_PRINTF("[BSP] Power\r\n"); bsp_indication_set(BSP_INDICATE_USER_STATE_ON); go_device_power_off = true; main_timer_start(); } break; default: break; } } /*============================================================================== * UTILITY FUNCTIONS *============================================================================*/ static void buttons_leds_init(bool * p_erase_bonds) { bsp_event_t startup_event; uint32_t err_code = bsp_init(BSP_INIT_LEDS | BSP_INIT_BUTTONS, bsp_event_handler); APP_ERROR_CHECK(err_code); err_code = bsp_btn_ble_init(NULL, &startup_event); APP_ERROR_CHECK(err_code); *p_erase_bonds = (startup_event == BSP_EVENT_CLEAR_BONDING_DATA); } static void log_init(void) { ret_code_t err_code = NRF_LOG_INIT(NULL); APP_ERROR_CHECK(err_code); NRF_LOG_DEFAULT_BACKENDS_INIT(); } void uart_event_handle(app_uart_evt_t * p_event) { static uint8_t data_array[BLE_NUS_MAX_DATA_LEN] = {0}; static uint8_t index = 0; switch (p_event->evt_type) { case APP_UART_DATA_READY: UNUSED_VARIABLE(app_uart_get(&data_array[index])); index++; if ((data_array[index - 1] == '\n') || (data_array[index - 1] == '\r') || (index >= m_ble_nus_max_data_len)) { if (index > 1) { cmd_type_t = CMD_UART; received_command_process(data_array, CMD_UART, index); } index = 0; } break; case APP_UART_COMMUNICATION_ERROR: break; case APP_UART_FIFO_ERROR: APP_ERROR_HANDLER(p_event->data.error_code); break; default: break; } } static void uart_init(void) { uint32_t err_code; app_uart_comm_params_t const comm_params = { .rx_pin_no = RX_PIN_NUMBER, .tx_pin_no = TX_PIN_NUMBER, .rts_pin_no = RTS_PIN_NUMBER, .cts_pin_no = CTS_PIN_NUMBER, .flow_control = APP_UART_FLOW_CONTROL_DISABLED, .use_parity = false, #if defined (UART_PRESENT) .baud_rate = NRF_UART_BAUDRATE_1000000 #else .baud_rate = NRF_UARTE_BAUDRATE_1000000 #endif }; APP_UART_FIFO_INIT(&comm_params, UART_RX_BUF_SIZE, UART_TX_BUF_SIZE, uart_event_handle, APP_IRQ_PRIORITY_LOWEST, err_code); APP_ERROR_CHECK(err_code); } static void power_management_init(void) { ret_code_t err_code; err_code = nrf_pwr_mgmt_init(); APP_ERROR_CHECK(err_code); } static void idle_state_handle(void) { #if FEATURE_SECURE_CONNECTION ret_code_t err_code; err_code = nrf_ble_lesc_request_handler(); APP_ERROR_CHECK(err_code); #endif if (NRF_LOG_PROCESS() == false) { nrf_pwr_mgmt_run(); } } void assert_nrf_callback(uint16_t line_num, const uint8_t * p_file_name) { app_error_handler(DEAD_BEEF, line_num, p_file_name); } void sleep_mode_enter(void) { bsp_indication_set(BSP_INDICATE_USER_STATE_ON); DBG_PRINTF("[SYS] Sleep\r\n"); APP_ERROR_CHECK(app_timer_start(m_power_off_delay_timer_id, APP_TIMER_TICKS(POWER_OFF_DELAY), NULL)); } void device_power_off(void) { bsp_indication_set(BSP_INDICATE_USER_STATE_ON); APP_ERROR_CHECK(app_timer_start(m_power_off_delay_timer_id, APP_TIMER_TICKS(POWER_OFF_DELAY), NULL)); } /*============================================================================== * DATA TRANSMISSION *============================================================================*/ void data_tx_handler(char const *p_data_to_send) // 문자열 (ASCII 텍스트) 전송 함수 { if (m_tx_in_progress) return; char const *p_end_char = strchr(p_data_to_send, '\r'); if (p_end_char == NULL) return; uint16_t data_len = p_end_char - p_data_to_send; if (data_len > (BLE_NUS_MAX_DATA_LEN - 2)) return; memcpy(m_tx_buffer, p_data_to_send, data_len); uint16_t crc = crc16_compute(m_tx_buffer, data_len, NULL); m_tx_buffer[data_len] = (uint8_t)(crc & 0xFF); m_tx_buffer[data_len + 1] = (uint8_t)((crc >> 8) & 0xFF); m_tx_len = data_len + 2; if (ble_connection_st == BLE_CONNECTED_ST) { m_tx_in_progress = true; uint32_t err_code = ble_nus_data_send(&m_nus, m_tx_buffer, &m_tx_len, m_conn_handle); if (err_code == NRF_SUCCESS) { // OK } else if (err_code == NRF_ERROR_RESOURCES) { // Retry later } else if (err_code == NRF_ERROR_INVALID_STATE || err_code == NRF_ERROR_NOT_FOUND) { ble_connection_st = BLE_DISCONNECTED_ST; m_conn_handle = BLE_CONN_HANDLE_INVALID; m_tx_in_progress = false; } else { DBG_PRINTF("[BLE TX] Err:0x%X\r\n", err_code); // APP_ERROR_CHECK 대신 로그 - jhChun 26.03.16 m_tx_in_progress = false; //APP_ERROR_CHECK(err_code); } } } void single_format_data(uint8_t *buffer, const char *tag, const uint16_t value) { uint16_t tag1 = ((uint16_t)tag[1] << 8) | (uint16_t)tag[0]; uint16_t tag2 = ((uint16_t)tag[3] << 8) | (uint16_t)tag[2]; buffer[0] = (uint8_t)(tag1 & 0xFF); buffer[1] = (uint8_t)((tag1 >> 8) & 0xFF); buffer[2] = (uint8_t)(tag2 & 0xFF); buffer[3] = (uint8_t)((tag2 >> 8) & 0xFF); buffer[5] = (uint8_t)(value & 0xFF); buffer[4] = (uint8_t)((value >> 8) & 0xFF); } void format_data(uint8_t *buffer, const char *tag, const uint16_t *data_array, size_t length) { uint16_t tag1 = ((uint16_t)tag[1] << 8) | (uint16_t)tag[0]; uint16_t tag2 = ((uint16_t)tag[3] << 8) | (uint16_t)tag[2]; buffer[0] = (uint8_t)(tag1 & 0xFF); buffer[1] = (uint8_t)((tag1 >> 8) & 0xFF); buffer[2] = (uint8_t)(tag2 & 0xFF); buffer[3] = (uint8_t)((tag2 >> 8) & 0xFF); for (size_t i = 0; i < length; i++) { buffer[4 + i * 2 + 1] = (uint8_t)(data_array[i] & 0xFF); buffer[4 + i * 2] = (uint8_t)((data_array[i] >> 8) & 0xFF); } } void format_data_byte(uint8_t *buffer, const char *tag, const uint8_t *data_array, size_t length) { uint16_t tag1 = ((uint16_t)tag[1] << 8) | (uint16_t)tag[0]; uint16_t tag2 = ((uint16_t)tag[3] << 8) | (uint16_t)tag[2]; buffer[0] = (uint8_t)(tag1 & 0xFF); buffer[1] = (uint8_t)((tag1 >> 8) & 0xFF); buffer[2] = (uint8_t)(tag2 & 0xFF); buffer[3] = (uint8_t)((tag2 >> 8) & 0xFF); for (size_t i = 0; i < length; i++) { buffer[4 + i] = (uint8_t)(data_array[i] & 0xFF); } } void ascii_format_data(uint8_t *buffer, const char *tag, const char *data_ascii, size_t length) { uint16_t tag1 = ((uint16_t)tag[1] << 8) | (uint16_t)tag[0]; uint16_t tag2 = ((uint16_t)tag[3] << 8) | (uint16_t)tag[2]; buffer[0] = (uint8_t)(tag1 & 0xFF); buffer[1] = (uint8_t)((tag1 >> 8) & 0xFF); buffer[2] = (uint8_t)(tag2 & 0xFF); buffer[3] = (uint8_t)((tag2 >> 8) & 0xFF); for (size_t i = 0; i < length; i++) { buffer[4 + i] = (uint8_t)(data_ascii[i] & 0xFF); } } /** * @brief Safe BLE binary transmission with proper error handling * * Properly handles NRF_ERROR_RESOURCES by retrying * and returns gracefully on connection errors. * * @param ble_bin_buff Data buffer to send * @param length Length in uint16_t words (actual bytes = length * 2) */ /** * @brief Delay for BLE operations - simple and safe * * Uses pure nrf_delay_ms() which is interrupt-safe. * BLE stack runs on interrupts, so TX completion events * are processed even during delay. * * NOTE: Do NOT use __WFE() or sd_app_evt_wait() here! * They conflict with SoftDevice power management. * * @param ms Delay in milliseconds */ void dr_sd_delay_ms(uint32_t ms) { if (ms == 0) return; nrf_delay_ms(ms); } /* 기존 binary_tx_handler 함수 삭제 및 호출처 교체 - jhChun 26.03.16 */ void dr_binary_tx_safe(uint8_t const *ble_bin_buff, uint16_t length) // BLE로 바이너리 데이터를 안전하게 전송하는 함수 { uint32_t err_code; static uint8_t tx_buffer[BLE_NUS_MAX_DATA_LEN] = {0}; uint16_t retry_count = 0; const uint16_t MAX_RETRIES = 100; /* Max retries (~500ms at 5ms each) */ if (ble_connection_st == 0) return; // 연결 확인 data_tx_in_progress = true; if (length * sizeof(uint16_t) > (BLE_NUS_MAX_DATA_LEN - 2)) { // 길이 검증 (CRC 2바이트 공간 확보) data_tx_in_progress = false; return; } if (ble_connection_st == BLE_CONNECTED_ST) { memcpy(tx_buffer, ble_bin_buff, length * sizeof(uint16_t)); uint16_t crc = crc16_compute(tx_buffer, length * sizeof(uint16_t), NULL); // 버퍼 복사 tx_buffer[length * sizeof(uint16_t)] = (uint8_t)(crc & 0xFF); tx_buffer[length * sizeof(uint16_t) + 1] = (uint8_t)((crc >> 8) & 0xFF); // CRC16 2바이트 추가 uint16_t total_len = length * sizeof(uint16_t) + 2; /* Retry loop - allow SoftDevice to process events between retries */ do { uint16_t send_len = total_len; /* MUST reset each iteration - ble_nus_data_send modifies it! */ err_code = ble_nus_data_send(&m_nus, tx_buffer, &send_len, m_conn_handle); if (err_code == NRF_SUCCESS) { // 전송 성공 -> 루프 탈출 /* TX queued successfully */ break; } else if (err_code == NRF_ERROR_RESOURCES) { // TX 큐가 가득 찬 경우 5ms 대기 후 재시도 (최대 100회, ~500ms) /* BLE TX queue full - wait for connection event to complete TX */ /* Use small delay to allow BLE stack to process TX complete events */ nrf_delay_ms(5); /* Wait ~5ms for TX slot to free up */ retry_count++; } else if (err_code == NRF_ERROR_INVALID_STATE || err_code == NRF_ERROR_NOT_FOUND) { // 연결 끊김, 리턴 DBG_PRINTF("[BLE TX] Disconnected\r\n"); data_tx_in_progress = false; return; } else { // 기타 에러 -> 로그 출력 후 리턴 DBG_PRINTF("[BLE TX] Err:0x%X\r\n", err_code); data_tx_in_progress = false; return; } } while (retry_count < MAX_RETRIES); if (retry_count >= MAX_RETRIES) { // 최대 재시도(100회) 초과 시 해당 패킷 드롭, 연결은 유지 DBG_PRINTF("[BLE TX] FAIL %u retries\r\n", retry_count); data_tx_in_progress = false; /* Don't set ble_connection_st = 0 here - just drop this packet and continue */ return; } data_tx_in_progress = false; } } /*============================================================================== * MAIN TIMER CALLBACK (BUTTON HANDLING) *============================================================================*/ static void main_s(void * p_context) { UNUSED_PARAMETER(p_context); APP_ERROR_CHECK(app_timer_stop(m_power_on_delay_timer_id)); bool button_released = nrf_gpio_pin_read(POWER_BUTTON); if (button_released) { if ((cnt_s < 150) && (m_reset_status != 2)) { DBG_PRINTF("[BTN] Short->OFF\r\n"); bsp_indication_set(BSP_INDICATE_USER_STATE_OFF); power_control_handler(OFF); cnt_s = 0; } else if (cnt_s > 1000) { DBG_PRINTF("[BTN] Long->Reset\r\n"); power_control_handler(ON); nrf_delay_ms(100); bond_data_delete = true; m_config.bond_data_delete = (uint8_t)bond_data_delete; const char pass_init[PASSKEY_LENGTH] = "123456"; memcpy(m_config.static_passkey, pass_init, PASSKEY_LENGTH); config_save(); nrf_delay_ms(1000); go_device_power_off = true; main_timer_start(); } else if (cnt_s > 150 || (m_reset_status == 2)) { DBG_PRINTF("[BTN] Boot (cnt=%d)\r\n", cnt_s); device_reset = false; power_control_handler(ON); battery_timer_start(); #if DEBUG_MINIMAL_BOOT DBG_PRINTF("[BOOT] Minimal\r\n"); #if FEATURE_SECURE_CONNECTION advertising_start(erase_bonds); #else advertising_start(); #endif DBG_PRINTF("[BOOT] ADV started\r\n"); #else DBG_PRINTF("[BOOT] Full\r\n"); icm42670_init(); nrf_delay_ms(2); #if FEATURE_SECURE_CONNECTION advertising_start(erase_bonds); #else advertising_start(); #endif m_reset_status = 1; m_config.reset_status = m_reset_status; config_save(); #endif m_reset_status = 1; DBG_PRINTF("[BOOT] Ready\r\n"); } } else { cnt_s++; device_reset = false; if (cnt_s == 150) { bsp_indication_set(BSP_INDICATE_USER_STATE_ON); DBG_PRINTF("[BTN] 1.5s\r\n"); } timers_start(); } } /*============================================================================== * MAIN FUNCTION *============================================================================*/ int main(void) { bool erase_bonds_local = false; // PHASE 0: 전원 유지 power_hold_init(); // PHASE 1: 기본 시스템 if (power_off_duble_prohibit) return 0; cnt_s = 0; uart_init(); log_init(); DBG_PRINTF("\r\n========================================\r\n"); DBG_PRINTF(" Medithings v1.17 [%s]\r\n", DEBUG_MINIMAL_BOOT ? "MIN" : "FULL"); DBG_PRINTF("========================================\r\n"); DBG_PRINTF("[0] PWR_HOLD\r\n"); // PHASE 2: GPIO DBG_PRINTF("[1] GPIO\r\n"); #if DEBUG_MINIMAL_BOOT minimal_gpio_init(); #else full_gpio_init(); #endif info4 = false; // PHASE 3: 타이머 DBG_PRINTF("[2] Timers\r\n"); timers_init(); // PHASE 4: 설정 (기본값 로드) DBG_PRINTF("[3] Config (defaults)\r\n"); load_default_config(); // PHASE 5: 버튼/LED DBG_PRINTF("[4] Buttons\r\n"); buttons_leds_init(&erase_bonds_local); #if FEATURE_SECURE_CONNECTION erase_bonds = erase_bonds_local; #endif // PHASE 6: BLE 스택 DBG_PRINTF("[5] BLE\r\n"); power_management_init(); DBG_PRINTF(" pwr OK\r\n"); ble_stack_init(); DBG_PRINTF(" stack OK\r\n"); // PHASE 6.5: 내장 Flash (FDS) - BLE 스택 이후에 초기화해야 함 DBG_PRINTF("[5.5] FDS\r\n"); fs_storage_init(); config_load(); load_flash_config(); DBG_PRINTF(" fds OK\r\n"); gap_params_init(); DBG_PRINTF(" gap OK\r\n"); gatt_init(); DBG_PRINTF(" gatt OK\r\n"); services_init(); DBG_PRINTF(" svc OK\r\n"); advertising_init(); DBG_PRINTF(" adv OK\r\n"); conn_params_init(); DBG_PRINTF(" conn OK\r\n"); // PHASE 7: 보안 DBG_PRINTF("[6] Security\r\n"); #if FEATURE_SECURE_CONNECTION peer_manager_init(); #endif // PHASE 7.5: FDS 기본값 저장 (advertising 이후) if (m_need_save_defaults) { DBG_PRINTF("[FDS] Saving defaults after ADV\r\n"); config_save(); m_need_save_defaults = false; } // PHASE 8: 완료 DBG_PRINTF("\r\n========================================\r\n"); DBG_PRINTF(" READY [%s]\r\n", SERIAL_NO); DBG_PRINTF("========================================\r\n\r\n"); // PHASE 9: 시작 timers_start(); // MAIN LOOP for (;;) { idle_state_handle(); } }