#include <zephyr/kernel.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/gatt.h>
#include <string.h>
#include "gatt_profile.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(control_service, LOG_LEVEL_INF);
struct peripheral_state g_state;
static struct k_work_delayable notify_work;
static bool periodic_notify_active;
extern struct bt_gatt_service_static notify_svc;
static void periodic_notify_handler(struct k_work *work)
{
if (!periodic_notify_active || !g_state.conn) {
return;
}
if (g_state.notify_enabled) {
static uint8_t counter;
uint8_t data[] = {counter++};
bt_gatt_notify(g_state.conn, ¬ify_svc.attrs[2], data, sizeof(data));
}
if (g_state.indicate_enabled) {
static struct bt_gatt_indicate_params ind_params;
static uint8_t ind_counter;
uint8_t data[] = {ind_counter++};
ind_params.attr = ¬ify_svc.attrs[5];
ind_params.func = NULL;
ind_params.destroy = NULL;
ind_params.data = data;
ind_params.len = sizeof(data);
bt_gatt_indicate(g_state.conn, &ind_params);
}
if (g_state.configurable_notify_enabled && g_state.notify_payload_len > 0) {
bt_gatt_notify(g_state.conn, ¬ify_svc.attrs[8],
g_state.notify_payload, g_state.notify_payload_len);
}
k_work_reschedule(¬ify_work, K_MSEC(NOTIFICATION_INTERVAL_MS));
}
void start_periodic_notifications(void)
{
periodic_notify_active = true;
k_work_reschedule(¬ify_work, K_MSEC(NOTIFICATION_INTERVAL_MS));
LOG_INF("Periodic notifications started");
}
void stop_periodic_notifications(void)
{
periodic_notify_active = false;
k_work_cancel_delayable(¬ify_work);
LOG_INF("Periodic notifications stopped");
}
void reset_peripheral_state(void)
{
stop_periodic_notifications();
g_state.read_counter = 0;
g_state.rw_value_len = 0;
g_state.long_value_len = 0;
g_state.write_with_resp_len = 0;
g_state.notify_payload_len = 0;
g_state.rw_descriptor_len = 0;
memset(g_state.rw_value, 0, sizeof(g_state.rw_value));
memset(g_state.long_value, 0, sizeof(g_state.long_value));
memset(g_state.write_with_resp_value, 0, sizeof(g_state.write_with_resp_value));
memset(g_state.notify_payload, 0, sizeof(g_state.notify_payload));
memset(g_state.rw_descriptor_value, 0, sizeof(g_state.rw_descriptor_value));
LOG_INF("Peripheral state reset");
}
static struct k_work_delayable disconnect_work;
static void disconnect_handler(struct k_work *work)
{
if (g_state.conn) {
bt_conn_disconnect(g_state.conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN);
}
}
void control_handle_command(const uint8_t *data, uint16_t len)
{
if (len < 1) {
LOG_WRN("Empty control command");
return;
}
uint8_t opcode = data[0];
LOG_INF("Control command: 0x%02x", opcode);
switch (opcode) {
case CMD_START_NOTIFICATIONS:
start_periodic_notifications();
break;
case CMD_STOP_NOTIFICATIONS:
stop_periodic_notifications();
break;
case CMD_TRIGGER_DISCONNECT:
k_work_reschedule(&disconnect_work, K_MSEC(500));
break;
case CMD_CHANGE_ADVERTISEMENTS:
LOG_INF("Change advertisements (deferred — not tested in initial suite)");
break;
case CMD_RESET_STATE:
reset_peripheral_state();
break;
case CMD_SET_NOTIFICATION_PAYLOAD:
if (len > 1) {
uint16_t payload_len = len - 1;
if (payload_len > sizeof(g_state.notify_payload)) {
payload_len = sizeof(g_state.notify_payload);
}
memcpy(g_state.notify_payload, &data[1], payload_len);
g_state.notify_payload_len = payload_len;
LOG_INF("Notification payload set: %u bytes", payload_len);
}
break;
default:
LOG_WRN("Unknown control opcode: 0x%02x", opcode);
break;
}
}
ssize_t write_control_point(struct bt_conn *conn,
const struct bt_gatt_attr *attr,
const void *buf, uint16_t len,
uint16_t offset, uint8_t flags)
{
control_handle_command(buf, len);
return len;
}
void control_response_ccc_changed(const struct bt_gatt_attr *attr, uint16_t value)
{
LOG_INF("Control Response CCC: 0x%04x", value);
}
void control_service_init(void)
{
k_work_init_delayable(¬ify_work, periodic_notify_handler);
k_work_init_delayable(&disconnect_work, disconnect_handler);
reset_peripheral_state();
}