전원 + BLE + 배터리

This commit is contained in:
jhChun
2026-04-10 17:57:09 +09:00
parent 750f2d139e
commit 8dcf4adf31
22 changed files with 1791 additions and 175 deletions
+229
View File
@@ -0,0 +1,229 @@
/*******************************************************************************
* @file ble_service.c
* @brief BLE NUS service module (Zephyr port)
*
* Original VesiScan-Basic BLE implementation ported to Zephyr/NCS:
* - NUS (Nordic UART Service) for data exchange
* - Advertising: 40ms interval, 3-min timeout
* - Connection: 30ms interval, 0 latency, 4s supervision timeout
* - TX power +8 dBm, 2M PHY preferred
* - Dev mode: no security / Production mode: bonding + passkey (TODO)
******************************************************************************/
#include <zephyr/kernel.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gatt.h>
#include <bluetooth/services/nus.h>
#include "ble_service.h"
#include "main.h"
#include "debug_print.h"
#include "led_control.h"
LOG_MODULE_REGISTER(ble_svc, LOG_LEVEL_INF);
/*==============================================================================
* Module variables
*============================================================================*/
static struct bt_conn *current_conn;
static ble_data_rx_cb_t rx_callback;
/* BLE API는 ISR/콜백에서 직접 호출하면 안전하지 않을 수 있으므로 k_work 사용 */
static struct k_work adv_restart_work;
static void adv_restart_handler(struct k_work *work)
{
ARG_UNUSED(work);
ble_advertising_start();
led_set_state(LED_STATE_ADVERTISING);
}
/*==============================================================================
* Advertising data
*============================================================================*/
static const struct bt_data ad[] =
{
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
BT_DATA(BT_DATA_NAME_COMPLETE, SERIAL_NUMBER, sizeof(SERIAL_NUMBER) - 1),
};
static const struct bt_data sd[] =
{
BT_DATA_BYTES(BT_DATA_UUID128_ALL, BT_UUID_NUS_VAL),
};
/*==============================================================================
* Connection callbacks
*============================================================================*/
static void connected(struct bt_conn *conn, uint8_t err)
{
if (err)
{
DBG_PRINTF("[BLE] Connect failed (err %d) -> re-adv\r\n", err);
k_work_submit(&adv_restart_work);
return;
}
current_conn = bt_conn_ref(conn);
ble_connection_st = true;
/* 연결 정보 출력 */
char addr_str[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(bt_conn_get_dst(conn), addr_str, sizeof(addr_str));
DBG_PRINTF("[BLE] Peer: %s\r\n", addr_str);
/* Connection parameters (30ms interval) */
struct bt_le_conn_param conn_param =
{
.interval_min = BLE_MIN_CONN_INTERVAL,
.interval_max = BLE_MAX_CONN_INTERVAL,
.latency = BLE_SLAVE_LATENCY,
.timeout = BLE_CONN_SUP_TIMEOUT,
};
bt_conn_le_param_update(conn, &conn_param);
led_set_state(LED_STATE_OFF);
DBG_PRINTF("[BLE] Connected\r\n");
}
static void disconnected(struct bt_conn *conn, uint8_t reason)
{
if (current_conn)
{
bt_conn_unref(current_conn);
current_conn = NULL;
}
ble_connection_st = false;
DBG_PRINTF("[BLE] Disconnected (reason 0x%02x)\r\n", reason);
/* 워크큐에서 advertising 재시작 */
k_work_submit(&adv_restart_work);
}
static void le_param_updated(struct bt_conn *conn, uint16_t interval,
uint16_t latency, uint16_t timeout)
{
DBG_PRINTF("[BLE] Params: interval=%d latency=%d timeout=%d\r\n",
interval, latency, timeout);
}
BT_CONN_CB_DEFINE(conn_callbacks) =
{
.connected = connected,
.disconnected = disconnected,
.le_param_updated = le_param_updated,
};
/*==============================================================================
* NUS callbacks
*============================================================================*/
static void nus_received(struct bt_conn *conn, const uint8_t *data, uint16_t len)
{
if (rx_callback)
{
rx_callback(data, len);
}
}
static void nus_sent(struct bt_conn *conn)
{
DBG_PRINTF("[NUS TX] complete\r\n");
}
static struct bt_nus_cb nus_cb =
{
.received = nus_received,
.sent = nus_sent,
};
/*==============================================================================
* Public functions
*============================================================================*/
int ble_service_init(ble_data_rx_cb_t rx_cb)
{
int err;
rx_callback = rx_cb;
k_work_init(&adv_restart_work, adv_restart_handler);
/* Enable BLE stack */
err = bt_enable(NULL);
if (err)
{
DBG_PRINTF("[BLE] bt_enable failed (err %d)\r\n", err);
return err;
}
DBG_PRINTF("[BLE] Stack enabled\r\n");
/* Initialize NUS */
err = bt_nus_init(&nus_cb);
if (err)
{
DBG_PRINTF("[BLE] NUS init failed (err %d)\r\n", err);
return err;
}
DBG_PRINTF("[BLE] NUS initialized\r\n");
return 0;
}
int ble_advertising_start(void)
{
int err;
struct bt_le_adv_param adv_param = BT_LE_ADV_PARAM_INIT(
BT_LE_ADV_OPT_CONN,
APP_ADV_INTERVAL, /* min interval */
APP_ADV_INTERVAL + 16, /* max interval (slight window) */
NULL /* undirected */
);
err = bt_le_adv_start(&adv_param, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd));
if (err)
{
DBG_PRINTF("[BLE] Adv start failed (err %d)\r\n", err);
return err;
}
DBG_PRINTF("[BLE] Advertising started\r\n");
return 0;
}
int ble_advertising_stop(void)
{
int err = bt_le_adv_stop();
if (err)
{
DBG_PRINTF("[BLE] Adv stop failed (err %d)\r\n", err);
return err;
}
DBG_PRINTF("[BLE] Advertising stopped\r\n");
return 0;
}
int ble_data_send(const uint8_t *data, uint16_t len)
{
if (!current_conn)
{
DBG_PRINTF("[NUS TX] not connected\r\n");
return -ENOTCONN;
}
DBG_PRINTF("[NUS TX] %d bytes\r\n", len);
int err = bt_nus_send(current_conn, data, len);
if (err)
{
DBG_PRINTF("[NUS TX] failed (err %d)\r\n", err);
}
return err;
}
bool ble_is_connected(void)
{
return (current_conn != NULL);
}