#include "ble.h" #include "state.h" #include "nimble/nimble_port.h" #include "nimble/nimble_port_freertos.h" #include "services/gap/ble_svc_gap.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include #include volatile int g_ble_scanning; volatile int g_ble_initialized; uint8_t g_own_addr_type; uint8_t g_own_addr[6]; uint8_t g_player_id = 0xFF; volatile int g_reset_requested; int g_reset_cmd_ticks; static int ble_gap_event_handler(struct ble_gap_event *event, void *arg); static void ble_adv_start_internal(void) { ble_payload_t payload; payload.magic[0] = BLE_MFR_MAGIC_0; payload.magic[1] = BLE_MFR_MAGIC_1; payload.game_id[0] = g_game_id[0]; payload.game_id[1] = g_game_id[1]; memcpy(payload.name, g_player_name, PLAYER_NAME_LEN); payload.life = (int16_t)g_life; payload.poison = (uint8_t)g_counters[0]; payload.eliminated = (uint8_t)g_eliminated; payload.player_id = g_player_id; payload.reset_cmd = (g_reset_cmd_ticks > 0) ? 1 : 0; for (int i = 0; i < MAX_OPPONENTS; i++) payload.cmdr_dmg[i] = (uint8_t)g_cmdr_damage[i]; static uint8_t mfr[2 + sizeof(ble_payload_t)]; mfr[0] = 0xFF; mfr[1] = 0xFF; memcpy(mfr + 2, &payload, sizeof(payload)); struct ble_hs_adv_fields fields = {0}; fields.mfg_data = mfr; fields.mfg_data_len = sizeof(mfr); ble_gap_adv_set_fields(&fields); struct ble_gap_adv_params params = {0}; params.conn_mode = BLE_GAP_CONN_MODE_NON; params.disc_mode = BLE_GAP_DISC_MODE_NON; params.itvl_min = 0x0320; // 500ms params.itvl_max = 0x0320; ble_gap_adv_start(g_own_addr_type, NULL, BLE_HS_FOREVER, ¶ms, ble_gap_event_handler, NULL); #ifdef DEBUG printf("DBG ADV_TX name=%-8.8s life=%d poison=%u game_id=%02X%02X eliminated=%u pid=%u reset=%u\n", payload.name, (int)payload.life, (unsigned)payload.poison, payload.game_id[0], payload.game_id[1], (unsigned)payload.eliminated, (unsigned)payload.player_id, (unsigned)payload.reset_cmd); fflush(stdout); #endif } void ble_adv_update(void) { if (!g_ble_initialized || g_ble_scanning) return; ble_gap_adv_stop(); if (g_ble_enabled) ble_adv_start_internal(); } static void ble_update_peer(const ble_addr_t *addr, const ble_payload_t *p) { if (p->game_id[0] != g_game_id[0] || p->game_id[1] != g_game_id[1]) return; int slot = -1; for (int i = 0; i < MAX_BLE_PEERS; i++) { if (g_peers[i].active && memcmp(&g_peers[i].addr, addr, sizeof(ble_addr_t)) == 0) { slot = i; break; } } if (slot < 0) { for (int i = 0; i < MAX_BLE_PEERS; i++) { if (!g_peers[i].active) { slot = i; break; } } if (slot < 0) { slot = 0; for (int i = 1; i < MAX_BLE_PEERS; i++) if (g_peers[i].last_seen < g_peers[slot].last_seen) slot = i; } } uint8_t prev_reset_cmd = g_peers[slot].reset_cmd; g_peers[slot].addr = *addr; g_peers[slot].life = p->life; g_peers[slot].poison = p->poison; g_peers[slot].eliminated = p->eliminated; g_peers[slot].player_id = p->player_id; g_peers[slot].reset_cmd = p->reset_cmd; memcpy(g_peers[slot].cmdr_dmg, p->cmdr_dmg, MAX_OPPONENTS); g_peers[slot].last_seen = g_tick; g_peers[slot].active = 1; memcpy(g_peers[slot].name, p->name, PLAYER_NAME_LEN); g_peers[slot].name[PLAYER_NAME_LEN] = '\0'; if (p->reset_cmd && !prev_reset_cmd) g_reset_requested = 1; #ifdef DEBUG printf("DBG PEER_RX slot=%d addr=%02X:%02X:%02X:%02X:%02X:%02X name=%-8.8s life=%d poison=%u game_id=%02X%02X eliminated=%u pid=%u reset=%u cmdr=[%u,%u,%u,%u]\n", slot, addr->val[5], addr->val[4], addr->val[3], addr->val[2], addr->val[1], addr->val[0], p->name, (int)p->life, (unsigned)p->poison, p->game_id[0], p->game_id[1], (unsigned)p->eliminated, (unsigned)p->player_id, (unsigned)p->reset_cmd, (unsigned)p->cmdr_dmg[0], (unsigned)p->cmdr_dmg[1], (unsigned)p->cmdr_dmg[2], (unsigned)p->cmdr_dmg[3]); fflush(stdout); #endif } static int ble_gap_event_handler(struct ble_gap_event *event, void *arg) { if (event->type == BLE_GAP_EVENT_DISC) { const uint8_t *adv_data = event->disc.data; int len = event->disc.length_data; int i = 0; while (i < len) { uint8_t adlen = adv_data[i]; if (adlen == 0 || i + adlen >= len) break; uint8_t adtype = adv_data[i+1]; if (adtype == 0xFF && adlen >= 1 + 2 + (int)sizeof(ble_payload_t)) { const uint8_t *payload_ptr = adv_data + i + 4; if (payload_ptr[0] == BLE_MFR_MAGIC_0 && payload_ptr[1] == BLE_MFR_MAGIC_1) { ble_update_peer(&event->disc.addr, (const ble_payload_t *)payload_ptr); } } i += 1 + adlen; } } else if (event->type == BLE_GAP_EVENT_DISC_COMPLETE) { g_ble_scanning = 0; if (g_ble_enabled) ble_adv_start_internal(); } return 0; } static void ble_on_sync(void) { ble_hs_id_infer_auto(0, &g_own_addr_type); ble_hs_id_copy_addr(g_own_addr_type, g_own_addr, NULL); if (g_player_id == 0xFF) g_player_id = 0; g_ble_initialized = 1; if (g_ble_enabled) ble_adv_start_internal(); } static void ble_on_reset(int reason) { (void)reason; } static void ble_host_task(void *arg) { nimble_port_run(); nimble_port_freertos_deinit(); } static void ble_manager_task(void *arg) { while (!g_ble_initialized) vTaskDelay(pdMS_TO_TICKS(100)); while (1) { vTaskDelay(pdMS_TO_TICKS(10000)); if (!g_ble_enabled || g_ble_scanning) continue; g_ble_scanning = 1; ble_gap_adv_stop(); struct ble_gap_disc_params disc_params = {0}; disc_params.itvl = 0x0100; disc_params.window = 0x0080; disc_params.passive = 1; int rc = ble_gap_disc(g_own_addr_type, 5000, &disc_params, ble_gap_event_handler, NULL); if (rc != 0) { g_ble_scanning = 0; if (g_ble_enabled) ble_adv_start_internal(); } vTaskDelay(pdMS_TO_TICKS(6000)); if (g_ble_scanning) { ble_gap_disc_cancel(); g_ble_scanning = 0; if (g_ble_enabled) ble_adv_start_internal(); } } } void player_id_resolve(void) { if (!g_ble_initialized || !g_ble_enabled) return; for (int i = 0; i < MAX_BLE_PEERS; i++) { if (!g_peers[i].active || g_peers[i].player_id != g_player_id) continue; int we_lose = 0; for (int b = 5; b >= 0; b--) { if (g_peers[i].addr.val[b] < g_own_addr[b]) { we_lose = 1; break; } if (g_peers[i].addr.val[b] > g_own_addr[b]) break; } if (!we_lose) continue; uint8_t used[BLE_MAX_PLAYERS] = {0}; for (int j = 0; j < MAX_BLE_PEERS; j++) if (g_peers[j].active && g_peers[j].player_id < BLE_MAX_PLAYERS) used[g_peers[j].player_id] = 1; for (int id = 0; id < BLE_MAX_PLAYERS; id++) { if (!used[id]) { g_player_id = (uint8_t)id; break; } } ble_adv_update(); break; } } void ble_init(void) { nimble_port_init(); ble_hs_cfg.sync_cb = ble_on_sync; ble_hs_cfg.reset_cb = ble_on_reset; ble_svc_gap_init(); nimble_port_freertos_init(ble_host_task); xTaskCreate(ble_manager_task, "ble_mgr", 4096, NULL, 5, NULL); }