diff --git a/src/main.c b/src/main.c index 75f6c63..605f4bb 100644 --- a/src/main.c +++ b/src/main.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include "main.h" @@ -31,6 +32,10 @@ LOG_MODULE_REGISTER(vesiscan, LOG_LEVEL_INF); #define BLE_CMD_MAX_LEN 256 #define BLE_CMD_WORKQ_STACK_SZ 8192 +#define DFU_RESUME_MAGIC 0xD5U +#define DFU_RESUME_GPREGRET_REG 1U +#define IMG_MGMT_ID_STATE 0U +#define IMG_MGMT_ID_UPLOAD 1U /* * BLE 명령 처리 스택 * - piezo sweep @@ -85,9 +90,42 @@ static bool power_btn_suspended; /* 측정 중 전원 버튼 상태 * main_s() 타이머 콜백에서 k_work_submit()으로 예약하여 사용한다. */ static bool resume_without_power_button; /* 리셋 후 버튼 없이 복귀 */ +static bool dfu_confirm_pending_boot; /* DFU test 이미지 확인 전 자동 복귀 */ +static bool dfu_reset_resume_request; /* DFU 완료 리셋 후 자동 복귀 */ static uint32_t boot_reset_reason; /* RESETREAS 원본 값 */ static struct k_work adv_start_work; /* advertising 시작 work */ static struct k_work adv_stop_work; /* advertising 중지 work */ +static bool dfu_led_active; /* DFU 업로드 중 초록 LED 고정 */ +static const char *power_off_pending_reason = "none"; + +static enum mgmt_cb_return dfu_status_cb(uint32_t event, + enum mgmt_cb_return prev_status, + int32_t *rc, + uint16_t *group, + bool *abort_more, + void *data, + size_t data_size); + +static enum mgmt_cb_return smp_cmd_status_cb(uint32_t event, + enum mgmt_cb_return prev_status, + int32_t *rc, + uint16_t *group, + bool *abort_more, + void *data, + size_t data_size); + +static struct mgmt_callback dfu_status_callback = { + .callback = dfu_status_cb, + .event_id = MGMT_EVT_OP_IMG_MGMT_DFU_STARTED | + MGMT_EVT_OP_IMG_MGMT_DFU_CHUNK | + MGMT_EVT_OP_IMG_MGMT_DFU_PENDING | + MGMT_EVT_OP_IMG_MGMT_DFU_STOPPED, +}; + +static struct mgmt_callback smp_cmd_status_callback = { + .callback = smp_cmd_status_cb, + .event_id = MGMT_EVT_OP_CMD_DONE, +}; /* * BLE RX 콜백 안에서 무거운 일을 바로 처리하지 않기 위해 별도 work queue를 둔다. @@ -224,6 +262,18 @@ static void boot_context_detect(void) /* 부팅 원인 확인 */ boot_reset_reason = nrf_power_resetreas_get(NRF_POWER); + /* DFU 완료 직후 리셋인지 확인 */ +#if NRF_POWER_HAS_GPREGRET + dfu_reset_resume_request = + (nrf_power_gpregret_get(NRF_POWER, DFU_RESUME_GPREGRET_REG) == DFU_RESUME_MAGIC); + if (dfu_reset_resume_request) + { + nrf_power_gpregret_set(NRF_POWER, DFU_RESUME_GPREGRET_REG, 0U); + } +#else + dfu_reset_resume_request = false; +#endif + /* 소프트 리셋 계열 자동 복귀 */ resume_without_power_button = (boot_reset_reason & (NRF_POWER_RESETREAS_SREQ_MASK | @@ -242,28 +292,40 @@ static void power_hold_init(void) { #if defined(CONFIG_BOOTLOADER_MCUBOOT) /* DFU 이미지 확인 전 전원 유지 */ - bool dfu_confirm_pending = !boot_is_img_confirmed(); + dfu_confirm_pending_boot = !boot_is_img_confirmed(); #else - bool dfu_confirm_pending = false; + dfu_confirm_pending_boot = false; #endif /* 리셋 복귀/DFU 확인 대기 시 즉시 래치 */ gpio_pin_configure_dt(&power_hold, - (resume_without_power_button || dfu_confirm_pending) ? GPIO_OUTPUT_ACTIVE - : GPIO_OUTPUT_INACTIVE); + (resume_without_power_button || + dfu_confirm_pending_boot || + dfu_reset_resume_request) ? GPIO_OUTPUT_ACTIVE + : GPIO_OUTPUT_INACTIVE); } /* 전원 ON/OFF 제어 - P0.08 핀으로 물리적 전원 래치/해제 */ -static void power_control_handler(on_off_cont_t device_power_st) +static void power_control_handler(on_off_cont_t device_power_st, const char *reason) { if (device_power_st == OFF) { + DBG_ERR("[PWR] OFF latch release reason=%s pending=%s device_on=%u btn=%d cnt=%u suspended=%u dfu_adv=%u dfu_confirm=%u dfu_resume=%u\r\n", + reason ? reason : "unknown", + power_off_pending_reason, + device_on ? 1U : 0U, + gpio_pin_get_dt(&power_btn), + cnt_s, + power_btn_suspended ? 1U : 0U, + ble_dfu_advertising_is_enabled() ? 1U : 0U, + dfu_confirm_pending_boot ? 1U : 0U, + dfu_reset_resume_request ? 1U : 0U); gpio_pin_set_dt(&power_hold, 0); /* P0.08 LOW → 전원 래치 해제 → 전원 차단 */ } else { gpio_pin_set_dt(&power_hold, 1); /* P0.08 HIGH → 전원 유지 */ - DBG_PRINTF("[PWR] ON\r\n"); + DBG_PRINTF("[PWR] ON reason=%s\r\n", reason ? reason : "unknown"); } } @@ -328,26 +390,52 @@ static void load_default_config(void) static void t_power_off_timeout_handler(struct k_timer *timer) { ARG_UNUSED(timer); - DBG_PRINTF("[PWR] Off timeout\r\n"); + DBG_ERR("[PWR] Off timeout reason=%s\r\n", power_off_pending_reason); led_set_state(LED_STATE_OFF); - power_control_handler(OFF); + power_control_handler(OFF, "off-timeout"); } /*============================================================================== * 슬립 모드 / 전원 OFF * LED로 사용자에게 알린 후 3초 뒤 전원 차단 *============================================================================*/ +static void power_off_schedule(const char *reason, led_state_t led_state) +{ + power_off_pending_reason = reason ? reason : "unknown"; + DBG_ERR("[PWR] OFF scheduled reason=%s delay_ms=%u device_on=%u btn=%d cnt=%u suspended=%u dfu_adv=%u\r\n", + power_off_pending_reason, + POWER_OFF_DELAY, + device_on ? 1U : 0U, + gpio_pin_get_dt(&power_btn), + cnt_s, + power_btn_suspended ? 1U : 0U, + ble_dfu_advertising_is_enabled() ? 1U : 0U); + led_set_state(led_state); + k_timer_start(&m_power_off_delay_timer, K_MSEC(POWER_OFF_DELAY), K_NO_WAIT); +} + +void sleep_mode_enter_reason(const char *reason) +{ + power_off_schedule(reason, LED_STATE_POWER_ON); +} + void sleep_mode_enter(void) { - led_set_state(LED_STATE_POWER_ON); - DBG_PRINTF("[SYS] Sleep\r\n"); - k_timer_start(&m_power_off_delay_timer, K_MSEC(POWER_OFF_DELAY), K_NO_WAIT); + sleep_mode_enter_reason("sleep_mode_enter"); } void device_power_off(void) { - led_set_state(LED_STATE_POWER_OFF); - k_timer_start(&m_power_off_delay_timer, K_MSEC(POWER_OFF_DELAY), K_NO_WAIT); + power_off_schedule("device_power_off", LED_STATE_POWER_OFF); +} + +void device_power_keep_on(void) +{ + k_timer_stop(&m_power_off_delay_timer); + device_on = true; + boot_btn_released = (gpio_pin_get_dt(&power_btn) != 1); + cnt_s = 0; + power_control_handler(ON, "keep-on"); } void power_button_suspend(bool suspend) @@ -383,12 +471,19 @@ static void main_s(struct k_timer *timer) bool button_pressed = (gpio_pin_get_dt(&power_btn) == 1); - if (!device_on) /* ── 부팅 시퀀스 (OFF → ON) ── */ + if (power_btn_suspended) + { + k_timer_start(&m_power_on_delay_timer, K_MSEC(POWER_ON_DELAY), K_NO_WAIT); + return; + } + + /* 부팅 시퀀스 (OFF → ON) */ + if (!device_on) { if (!button_pressed) /* 버튼 놓음 → 2초 미만이면 전원 OFF */ { //DBG_PRINTF("[BTN] Short press (%d) -> OFF\r\n", cnt_s); - power_control_handler(OFF); + power_control_handler(OFF, "boot-button-released-before-latch"); cnt_s = 0; } else /* 버튼 계속 누르고 있음 */ @@ -400,7 +495,7 @@ static void main_s(struct k_timer *timer) device_on = true; cnt_s = 0; /* 카운터 리셋: 안 하면 다음 틱에서 ON→OFF 분기가 * cnt_s >= 400 조건을 즉시 만족하여 전원 OFF됨 */ - power_control_handler(ON); + power_control_handler(ON, "button-2s-latch"); led_set_state(LED_STATE_ADVERTISING); k_work_submit(&adv_start_work); battery_timer_start(); @@ -433,7 +528,7 @@ static void main_s(struct k_timer *timer) device_on = false; boot_btn_released = false; cnt_s = 0; - sleep_mode_enter(); + sleep_mode_enter_reason("button-long-press"); return; } } @@ -469,6 +564,12 @@ static void timers_init(void) /* 전원 제어 타이머 */ power_timer_init(); + + /* DFU 업로드 상태를 LED에 반영 */ + mgmt_callback_register(&dfu_status_callback); + + /* DFU 명령 실패 시 LED 상태 복구 */ + mgmt_callback_register(&smp_cmd_status_callback); } /* 전원 버튼 폴링 시작 (5ms 후 main_s 콜백) */ @@ -477,10 +578,103 @@ static void timers_start(void) k_timer_start(&m_power_on_delay_timer, K_MSEC(POWER_ON_DELAY), K_NO_WAIT); } +static enum mgmt_cb_return dfu_status_cb(uint32_t event, + enum mgmt_cb_return prev_status, + int32_t *rc, + uint16_t *group, + bool *abort_more, + void *data, + size_t data_size) +{ + ARG_UNUSED(prev_status); + ARG_UNUSED(rc); + ARG_UNUSED(group); + ARG_UNUSED(abort_more); + ARG_UNUSED(data); + ARG_UNUSED(data_size); + + switch (event) + { + case MGMT_EVT_OP_IMG_MGMT_DFU_STARTED: + case MGMT_EVT_OP_IMG_MGMT_DFU_CHUNK: + /* DFU 업로드 중에는 초록 LED를 계속 켬 */ + dfu_led_active = true; + led_ble_solid(); + break; + + case MGMT_EVT_OP_IMG_MGMT_DFU_PENDING: + /* 업로드 완료 후 리셋 전까지 초록 LED 유지 */ + dfu_led_active = true; + led_ble_solid(); + + /* DFU 리셋 후 버튼 없이 전원 복구 */ +#if NRF_POWER_HAS_GPREGRET + nrf_power_gpregret_set(NRF_POWER, DFU_RESUME_GPREGRET_REG, DFU_RESUME_MAGIC); +#endif + break; + + case MGMT_EVT_OP_IMG_MGMT_DFU_STOPPED: + /* 실패나 중단 시 일반 광고 LED로 복귀 */ + if (dfu_led_active) + { + dfu_led_active = false; + led_set_state(LED_STATE_ADVERTISING); + } + break; + + default: + break; + } + + return MGMT_CB_OK; +} + +static enum mgmt_cb_return smp_cmd_status_cb(uint32_t event, + enum mgmt_cb_return prev_status, + int32_t *rc, + uint16_t *group, + bool *abort_more, + void *data, + size_t data_size) +{ + ARG_UNUSED(event); + ARG_UNUSED(prev_status); + ARG_UNUSED(rc); + ARG_UNUSED(group); + ARG_UNUSED(abort_more); + + if (data == NULL || data_size != sizeof(struct mgmt_evt_op_cmd_arg)) + { + return MGMT_CB_OK; + } + + const struct mgmt_evt_op_cmd_arg *cmd = data; + + /* 이미지 업로드/test 명령 실패 시 DFU LED 고정 해제 */ + if (cmd->group == MGMT_GROUP_ID_IMAGE && + (cmd->id == IMG_MGMT_ID_UPLOAD || cmd->id == IMG_MGMT_ID_STATE) && + cmd->err != MGMT_ERR_EOK) + { + DBG_ERR("[DFU] command failed id=%u err=%d\r\n", cmd->id, cmd->err); + + dfu_led_active = false; + led_set_state(LED_STATE_ADVERTISING); + + /* 실패 후 리셋 복구 표식이 남지 않게 정리 */ +#if NRF_POWER_HAS_GPREGRET + nrf_power_gpregret_set(NRF_POWER, DFU_RESUME_GPREGRET_REG, 0U); +#endif + } + + return MGMT_CB_OK; +} + static void resume_device_after_soft_reset(void) { /* 일반 버튼 부팅이면 skip */ - if (!resume_without_power_button) + if (!resume_without_power_button && + !dfu_confirm_pending_boot && + !dfu_reset_resume_request) { return; } @@ -492,7 +686,7 @@ static void resume_device_after_soft_reset(void) m_reset_status = 1; /* 정상 ON 상태 서비스 재시작 */ - power_control_handler(ON); + power_control_handler(ON, "resume-after-reset"); led_set_state(LED_STATE_ADVERTISING); battery_timer_start(); k_work_submit(&adv_start_work); @@ -619,6 +813,8 @@ int main(void) DBG_CORE(" ble/nus OK\r\n"); /* 소프트 리셋 후 서비스 복구 */ resume_device_after_soft_reset(); + /* BLE 준비까지 확인한 뒤 DFU test 이미지를 확정 */ + confirm_running_image(); } else {