생산용 BLE write 명령 잠금 추가

- serial number / HW version / passkey write 명령 해당
- production_done NVS 플래그 추가
- 기존 passkey_changed 플래그 제거
- production_done=0 상태에서만 Serial/HW/Passkey Write 허용
- production_done=1 상태에서는 생산용 Write 명령을 rxd 응답으로 차단
- msr 본딩 삭제 성공 시 production_done=1 저장
This commit is contained in:
2026-07-30 11:51:06 +09:00
parent 41137a1c72
commit eab562d52e
6 changed files with 70 additions and 47 deletions
+25 -13
View File
@@ -12,6 +12,7 @@
#include "cmd_common.h" #include "cmd_common.h"
#include "main.h" #include "main.h"
#include "debug_print.h" #include "debug_print.h"
#include "app_nvs.h"
#include "ble_service.h" #include "ble_service.h"
#include "cmd_device.h" #include "cmd_device.h"
#include "led_control.h" #include "led_control.h"
@@ -64,15 +65,6 @@ int cmd_msr(const uint8_t *data, uint8_t data_len)
ARG_UNUSED(data); ARG_UNUSED(data);
ARG_UNUSED(data_len); ARG_UNUSED(data_len);
int tx_err = cmd_send_response_u16("rsr:", status);
if (tx_err)
{
DBG_ERR("[CMD] msr tx failed err=%d\r\n", tx_err);
return 1;
}
k_msleep(100);
#if IS_ENABLED(CONFIG_BT_SMP) #if IS_ENABLED(CONFIG_BT_SMP)
int err = bt_unpair(BT_ID_DEFAULT, NULL); int err = bt_unpair(BT_ID_DEFAULT, NULL);
if (err) if (err)
@@ -82,10 +74,9 @@ int cmd_msr(const uint8_t *data, uint8_t data_len)
} }
else else
{ {
led_set_state(LED_STATE_BOND_DELETE); // 삭제 완료 확인 주황 LED ON led_set_state(LED_STATE_BOND_DELETE);
bond_data_delete = true; bond_data_delete = true;
DBG_PRINTF("[CMD] msr bond data deleted\r\n"); DBG_PRINTF("[CMD] msr bond data deleted\r\n");
k_msleep(1500);
} }
#else #else
bond_data_delete = true; bond_data_delete = true;
@@ -94,13 +85,34 @@ int cmd_msr(const uint8_t *data, uint8_t data_len)
if (status == 0x0000) if (status == 0x0000)
{ {
DBG_PRINTF("[CMD] msr complete -> reboot\r\n"); int nvs_err = app_nvs_save_production_done(1U);
if (nvs_err)
{
status = (uint16_t)(-nvs_err);
DBG_ERR("[CMD] msr production_done save failed err=%d\r\n", nvs_err);
}
else
{
m_production_done = 1U;
}
}
int tx_err = cmd_send_response_u16("rsr:", status);
if (tx_err)
{
DBG_ERR("[CMD] msr tx failed err=%d\r\n", tx_err);
return 1;
}
if (status == 0x0000)
{
DBG_PRINTF("[CMD] msr complete -> production done, reboot\r\n");
k_msleep(1500);
} }
cmd_reboot_after_response(); cmd_reboot_after_response();
return 1; return 1;
} }
/* /*
* mdf: DFU mode enter * mdf: DFU mode enter
*/ */
+28 -15
View File
@@ -11,6 +11,19 @@
#include "app_nvs.h" #include "app_nvs.h"
#include "cmd_info.h" #include "cmd_info.h"
/* Production Mode - Serial Number, HW Version, Passkey write command lock */
static bool cmd_info_write_locked(const char *cmd_tag)
{
if (m_production_done == 0U)
{
return false;
}
cmd_send_response_tag_echo("rxd:", cmd_tag);
DBG_PRINTF("[CMD] production write disabled: %s\r\n", cmd_tag);
return true;
}
/* /*
* mfv: firmware version read * mfv: firmware version read
*/ */
@@ -54,6 +67,11 @@ int cmd_mid(const uint8_t *data, uint8_t data_len)
*/ */
int cmd_mwh(const uint8_t *data, uint8_t data_len) int cmd_mwh(const uint8_t *data, uint8_t data_len)
{ {
if (cmd_info_write_locked("mwh?"))
{
return 1;
}
if (data_len < HW_NO_LENGTH) if (data_len < HW_NO_LENGTH)
{ {
cmd_send_response_u16("rwh:", 0xFFFF); cmd_send_response_u16("rwh:", 0xFFFF);
@@ -96,6 +114,11 @@ int cmd_mrh(const uint8_t *data, uint8_t data_len)
*/ */
int cmd_mws(const uint8_t *data, uint8_t data_len) int cmd_mws(const uint8_t *data, uint8_t data_len)
{ {
if (cmd_info_write_locked("mws?"))
{
return 1;
}
if (data_len < SERIAL_NO_LENGTH) if (data_len < SERIAL_NO_LENGTH)
{ {
cmd_send_response_u16("rws:", 0xFFFF); cmd_send_response_u16("rws:", 0xFFFF);
@@ -138,19 +161,17 @@ int cmd_mrs(const uint8_t *data, uint8_t data_len)
*/ */
int cmd_mpz(const uint8_t *data, uint8_t data_len) int cmd_mpz(const uint8_t *data, uint8_t data_len)
{ {
if (cmd_info_write_locked("mpz?"))
{
return 1;
}
if (data_len < PASSKEY_LENGTH) if (data_len < PASSKEY_LENGTH)
{ {
cmd_send_response_u16("rpz:", 0xFFFF); cmd_send_response_u16("rpz:", 0xFFFF);
return 1; return 1;
} }
if (m_passkey_changed != 0U)
{
// 패스키가 변경된 경우 다시 바꿀 수 없음(FFFF 반환)
cmd_send_response_u16("rpz:", 0xFFFF);
DBG_PRINTF("[CMD] mpz: passkey already changed\r\n");
return 1;
}
// NVS 저장이 모두 성공한 뒤 RAM 값 확정 // NVS 저장이 모두 성공한 뒤 RAM 값 확정
char new_passkey[PASSKEY_BUF_SIZE]; char new_passkey[PASSKEY_BUF_SIZE];
@@ -164,17 +185,9 @@ int cmd_mpz(const uint8_t *data, uint8_t data_len)
return 1; return 1;
} }
// 변경 완료 플래그 세트 : 다음 mpz? 명령 차단
err = app_nvs_save_passkey_changed(1U);
if (err)
{
cmd_send_response_u16("rpz:", 0xFFFD);
return 1;
}
memset(m_static_passkey, 0, sizeof(m_static_passkey)); memset(m_static_passkey, 0, sizeof(m_static_passkey));
memcpy(m_static_passkey, new_passkey, PASSKEY_LENGTH); memcpy(m_static_passkey, new_passkey, PASSKEY_LENGTH);
m_passkey_changed = 1U;
cmd_send_response_ascii("rpz:", m_static_passkey, PASSKEY_LENGTH); cmd_send_response_ascii("rpz:", m_static_passkey, PASSKEY_LENGTH);
return 1; return 1;
+2 -2
View File
@@ -34,7 +34,7 @@ uint8_t m_reset_status = 1; // 리셋 상태 코드
char SERIAL_NO[SERIAL_NO_BUF_SIZE]; // 시리얼 번호 char SERIAL_NO[SERIAL_NO_BUF_SIZE]; // 시리얼 번호
char HW_NO[HW_NO_BUF_SIZE]; // 하드웨어 번호 char HW_NO[HW_NO_BUF_SIZE]; // 하드웨어 번호
char m_static_passkey[PASSKEY_BUF_SIZE]; // BLE 정적 패스키 char m_static_passkey[PASSKEY_BUF_SIZE]; // BLE 정적 패스키
uint8_t m_passkey_changed; // 패스키 변경은 출고 후 1회만 허용 uint8_t m_production_done; // 생산용 기기 정보 변경 제한(0: production writes allowed, 1: operation mode locked)
static bool resume_without_power_button; // 리셋 후 버튼 없이 복귀 static bool resume_without_power_button; // 리셋 후 버튼 없이 복귀
static bool dfu_confirm_pending_boot; // DFU test 이미지 확인 전 자동 복귀 static bool dfu_confirm_pending_boot; // DFU test 이미지 확인 전 자동 복귀
@@ -108,7 +108,7 @@ static void load_default_config(void)
memset(m_static_passkey, 0, sizeof(m_static_passkey)); memset(m_static_passkey, 0, sizeof(m_static_passkey));
memcpy(m_static_passkey, DEFAULT_PASSKEY, passkey_len); memcpy(m_static_passkey, DEFAULT_PASSKEY, passkey_len);
m_passkey_changed = 0U; // 패스키 변경 여부 초기화 m_production_done = 0U; // 생산용 기기 정보 변경 여부 초기화
m_reset_status = 1; // 기본 reset 상태 코드 설정 m_reset_status = 1; // 기본 reset 상태 코드 설정
bond_data_delete = true; // 기본 상태는 bond 삭제 완료 삭제로 초기화 bond_data_delete = true; // 기본 상태는 bond 삭제 완료 삭제로 초기화
+1 -1
View File
@@ -75,7 +75,7 @@ extern volatile bool processing;
extern char SERIAL_NO[SERIAL_NO_BUF_SIZE]; extern char SERIAL_NO[SERIAL_NO_BUF_SIZE];
extern char HW_NO[HW_NO_BUF_SIZE]; extern char HW_NO[HW_NO_BUF_SIZE];
extern char m_static_passkey[PASSKEY_BUF_SIZE]; extern char m_static_passkey[PASSKEY_BUF_SIZE];
extern uint8_t m_passkey_changed; extern uint8_t m_production_done;
extern bool bond_data_delete; extern bool bond_data_delete;
extern uint32_t m_life_cycle; extern uint32_t m_life_cycle;
extern uint8_t m_reset_status; extern uint8_t m_reset_status;
+13 -15
View File
@@ -22,14 +22,14 @@
#define APP_NVS_KEY_HW_NO "hw" #define APP_NVS_KEY_HW_NO "hw"
#define APP_NVS_KEY_SERIAL_NO "serial" #define APP_NVS_KEY_SERIAL_NO "serial"
#define APP_NVS_KEY_PASSKEY "passkey" #define APP_NVS_KEY_PASSKEY "passkey"
#define APP_NVS_KEY_PASSKEY_CHANGED "passkey_changed" // 패스키 1회 변경 완료 여부 #define APP_NVS_KEY_PRODUCTION_DONE "production_done"
/* settings_save_one에 넘기는 전체 키 */ /* settings_save_one에 넘기는 전체 키 */
#define APP_NVS_FULL_KEY_PIEZO "vesiscan/piezo" #define APP_NVS_FULL_KEY_PIEZO "vesiscan/piezo"
#define APP_NVS_FULL_KEY_HW_NO "vesiscan/hw" #define APP_NVS_FULL_KEY_HW_NO "vesiscan/hw"
#define APP_NVS_FULL_KEY_SERIAL_NO "vesiscan/serial" #define APP_NVS_FULL_KEY_SERIAL_NO "vesiscan/serial"
#define APP_NVS_FULL_KEY_PASSKEY "vesiscan/passkey" #define APP_NVS_FULL_KEY_PASSKEY "vesiscan/passkey"
#define APP_NVS_FULL_KEY_PASSKEY_CHANGED "vesiscan/passkey_changed" // 0은 변경 가능, 1은 변경 차단 #define APP_NVS_FULL_KEY_PRODUCTION_DONE "vesiscan/production_done"
/* parser.c가 가진 피에조 설정을 NVS 로드 시 갱신하기 위한 포인터 */ /* parser.c가 가진 피에조 설정을 NVS 로드 시 갱신하기 위한 포인터 */
static piezo_config_t *m_piezo_cfg; static piezo_config_t *m_piezo_cfg;
@@ -119,7 +119,7 @@ static int app_nvs_settings_set(const char *name, size_t len, settings_read_cb r
return read_exact(read_cb, cb_arg, SERIAL_NO, SERIAL_NO_LENGTH, APP_NVS_KEY_SERIAL_NO); return read_exact(read_cb, cb_arg, SERIAL_NO, SERIAL_NO_LENGTH, APP_NVS_KEY_SERIAL_NO);
} }
// 패스키는 mpz? 명령으로 최초 1회만 저장 // 패스키는 생산 완료 전 mpz? 명령으로 저장
if (settings_name_steq(name, APP_NVS_KEY_PASSKEY, &next) && (next == NULL)) if (settings_name_steq(name, APP_NVS_KEY_PASSKEY, &next) && (next == NULL))
{ {
if (len != PASSKEY_LENGTH) if (len != PASSKEY_LENGTH)
@@ -131,17 +131,15 @@ static int app_nvs_settings_set(const char *name, size_t len, settings_read_cb r
memset(m_static_passkey, 0, sizeof(m_static_passkey)); memset(m_static_passkey, 0, sizeof(m_static_passkey));
return read_exact(read_cb, cb_arg, m_static_passkey, PASSKEY_LENGTH, APP_NVS_KEY_PASSKEY); return read_exact(read_cb, cb_arg, m_static_passkey, PASSKEY_LENGTH, APP_NVS_KEY_PASSKEY);
} }
if (settings_name_steq(name, APP_NVS_KEY_PRODUCTION_DONE, &next) && (next == NULL))
if (settings_name_steq(name, APP_NVS_KEY_PASSKEY_CHANGED, &next) && (next == NULL))
{ {
/* 패스키 재변경 차단 플래그 복원 */ if (len != sizeof(m_production_done))
if (len != sizeof(m_passkey_changed))
{ {
DBG_ERR("[NVS] passkey_changed size mismatch (%u)\r\n", (uint32_t)len); DBG_ERR("[NVS] production_done size mismatch (%u)\r\n", (uint32_t)len);
return -EINVAL; return -EINVAL;
} }
return read_exact(read_cb, cb_arg, &m_passkey_changed, sizeof(m_passkey_changed), APP_NVS_KEY_PASSKEY_CHANGED); return read_exact(read_cb, cb_arg, &m_production_done, sizeof(m_production_done), APP_NVS_KEY_PRODUCTION_DONE);
} }
return -ENOENT; return -ENOENT;
@@ -229,7 +227,7 @@ int app_nvs_save_serial_no(const char *serial_no)
int app_nvs_save_passkey(const char *passkey) int app_nvs_save_passkey(const char *passkey)
{ {
#if IS_ENABLED(CONFIG_SETTINGS) #if IS_ENABLED(CONFIG_SETTINGS)
// mpz? 명령이 허용된 뒤 고정 길이 패스키 저장 // 고정 길이 패스키 저장
int err = settings_save_one(APP_NVS_FULL_KEY_PASSKEY, passkey, PASSKEY_LENGTH); int err = settings_save_one(APP_NVS_FULL_KEY_PASSKEY, passkey, PASSKEY_LENGTH);
if (err) if (err)
{ {
@@ -242,18 +240,18 @@ int app_nvs_save_passkey(const char *passkey)
#endif #endif
} }
int app_nvs_save_passkey_changed(uint8_t changed) /* Production Mode Done */
int app_nvs_save_production_done(uint8_t done)
{ {
#if IS_ENABLED(CONFIG_SETTINGS) #if IS_ENABLED(CONFIG_SETTINGS)
// 재부팅 후에도 mpz?(패스키 변경) 재실행을 막기 위한 플래그 저장(NVS) int err = settings_save_one(APP_NVS_FULL_KEY_PRODUCTION_DONE, &done, sizeof(done));
int err = settings_save_one(APP_NVS_FULL_KEY_PASSKEY_CHANGED, &changed, sizeof(changed));
if (err) if (err)
{ {
DBG_ERR("[NVS] passkey_changed save failed err=%d\r\n", err); DBG_ERR("[NVS] production_done save failed err=%d\r\n", err);
} }
return err; return err;
#else #else
ARG_UNUSED(changed); ARG_UNUSED(done);
return 0; return 0;
#endif #endif
} }
+1 -1
View File
@@ -20,6 +20,6 @@ int app_nvs_save_piezo(const piezo_config_t *cfg);
int app_nvs_save_hw_no(const char *hw_no); int app_nvs_save_hw_no(const char *hw_no);
int app_nvs_save_serial_no(const char *serial_no); int app_nvs_save_serial_no(const char *serial_no);
int app_nvs_save_passkey(const char *passkey); int app_nvs_save_passkey(const char *passkey);
int app_nvs_save_passkey_changed(uint8_t changed); int app_nvs_save_production_done(uint8_t done);
#endif /* APP_NVS_H__ */ #endif /* APP_NVS_H__ */