From 58f379dc1871b06a7cde07e5e2b77e93f5aebaeb Mon Sep 17 00:00:00 2001 From: Joe Kearney Date: Sat, 15 Feb 2025 16:13:58 -0600 Subject: [PATCH] WIP: More new BLE --- BLE/BLE_Packet_Tracker.c | 5 +- BLE/BLE_Packets.c | 47 +++++++++++++++ BLE/BLE_Packets.h | 74 ++++++++++++++++------- BLE/BLE_Utils.c | 52 ++++++++++++++++ BLE/BLE_Utils.h | 22 +++++++ CMakeLists.txt | 1 + Game/Game.h | 12 ++++ States/Playing/State_Playing.c | 4 +- States/State_Configuring.c | 105 +++++++++++++++++++++++++++++++++ SystemK.h | 1 + 10 files changed, 301 insertions(+), 22 deletions(-) create mode 100644 BLE/BLE_Utils.c create mode 100644 BLE/BLE_Utils.h diff --git a/BLE/BLE_Packet_Tracker.c b/BLE/BLE_Packet_Tracker.c index 7f1ff2d..11f698a 100644 --- a/BLE/BLE_Packet_Tracker.c +++ b/BLE/BLE_Packet_Tracker.c @@ -49,11 +49,14 @@ static PacketTracker_T Tracker = .head = 0, }; -// A packet is _new_ if the combination of BLE adress, type, and event number have not been seen recently. +// A packet is _new_ if the combination of BLE address, type, and event number have not been seen recently. bool BLE_IsPacketNew(const uint8_t *sender_BD_ADDR, BLE_PacketType_T packet_type, uint8_t event_number) { + //esp_log_level_set("BLE", ESP_LOG_DEBUG); + //KLOG_DEBUG("BLE", "Packet from %s", BLE_ADDR_To_Str(sender_BD_ADDR)); + // Check if the packet already exists in the tracker. for (int i = 0; i < Tracker.count; i++) { diff --git a/BLE/BLE_Packets.c b/BLE/BLE_Packets.c index 14a548c..dcc2c3f 100644 --- a/BLE/BLE_Packets.c +++ b/BLE/BLE_Packets.c @@ -257,6 +257,53 @@ void BLE_UpdateTagPacket(int16_t damage, color_t color, uint8_t target_BD_ADDR[B } } +void BLE_RespondToConfigurationPacket(const BLE_ConfigurationPacket_T *const packet, BLE_ConfigurationSubtype_T response) +{ + static uint8_t EventNumber = 0; + + Advertising_Data.length = BLE_KTAG_PACKET_TOTAL_SIZE; + + // Manufacturer Specific Data + Advertising_Data.data[0] = 0x1E; + Advertising_Data.data[1] = 0xFF; + Advertising_Data.data[2] = 0xFF; + Advertising_Data.data[3] = 0xFF; + Advertising_Data.data[4] = 'K'; + Advertising_Data.data[5] = 'T'; + Advertising_Data.data[6] = 'a'; + Advertising_Data.data[7] = 'g'; + Advertising_Data.data[8] = BLE_PACKET_TYPE_CONFIGURATION; + Advertising_Data.data[9] = EventNumber++; + Advertising_Data.data[10] = packet->BD_ADDR[0]; + Advertising_Data.data[11] = packet->BD_ADDR[1]; + Advertising_Data.data[12] = packet->BD_ADDR[2]; + Advertising_Data.data[13] = packet->BD_ADDR[3]; + Advertising_Data.data[14] = packet->BD_ADDR[4]; + Advertising_Data.data[15] = packet->BD_ADDR[5]; + Advertising_Data.data[16] = (uint8_t)response; + Advertising_Data.data[17] = (uint8_t)((packet->key_one >> 0) & 0xFF); + Advertising_Data.data[18] = (uint8_t)((packet->key_one >> 8) & 0xFF); + Advertising_Data.data[19] = (uint8_t)((packet->value_one >> 0) & 0xFF); + Advertising_Data.data[20] = (uint8_t)((packet->value_one >> 8) & 0xFF); + Advertising_Data.data[21] = (uint8_t)((packet->value_one >> 16) & 0xFF); + Advertising_Data.data[22] = (uint8_t)((packet->value_one >> 24) & 0xFF); + Advertising_Data.data[23] = (uint8_t)((packet->key_two >> 0) & 0xFF); + Advertising_Data.data[24] = (uint8_t)((packet->key_two >> 8) & 0xFF); + Advertising_Data.data[25] = (uint8_t)((packet->value_two >> 0) & 0xFF); + Advertising_Data.data[26] = (uint8_t)((packet->value_two >> 8) & 0xFF); + Advertising_Data.data[27] = (uint8_t)((packet->value_two >> 16) & 0xFF); + Advertising_Data.data[28] = (uint8_t)((packet->value_two >> 24) & 0xFF); + Advertising_Data.data[29] = 0xFF; + Advertising_Data.data[30] = 0xFF; + + SystemKResult_T result = BLE_SetAdvertisingData(&Advertising_Data); + + if (result != SYSTEMK_RESULT_SUCCESS) + { + KLOG_ERROR(KLOG_TAG, "Error responding to configuration packet!"); + } +} + void BLE_UpdateHelloPacket() { static uint8_t EventNumber = 0; diff --git a/BLE/BLE_Packets.h b/BLE/BLE_Packets.h index d535b46..a849a07 100644 --- a/BLE/BLE_Packets.h +++ b/BLE/BLE_Packets.h @@ -29,7 +29,8 @@ #define BLE_PACKETS_H #ifdef __cplusplus -extern "C" { +extern "C" +{ #endif /* Preprocessor and Type Definitions */ @@ -75,7 +76,7 @@ typedef struct uint8_t BD_ADDR[BD_ADDR_SIZE]; int8_t RSSI; uint8_t event_number; - + uint8_t data[BLE_KTAG_PACKET_DATA_SIZE]; } __attribute__((packed, aligned(1))) BLE_GenericPacketType_T; @@ -86,7 +87,7 @@ typedef struct uint8_t BD_ADDR[BD_ADDR_SIZE]; int8_t RSSI; uint8_t event_number; - + uint32_t game_length_in_ms; uint32_t time_remaining_until_countdown_in_ms; uint8_t unused[13]; @@ -99,7 +100,7 @@ typedef struct uint8_t BD_ADDR[BD_ADDR_SIZE]; int8_t RSSI; uint8_t event_number; - + uint8_t target_BD_ADDR[BD_ADDR_SIZE]; uint32_t event_ID; uint32_t event_data; @@ -122,7 +123,7 @@ typedef struct color_t color; uint8_t target_BD_ADDR[BD_ADDR_SIZE]; uint8_t unused[5]; -} __attribute__((packed, aligned(1)))BLE_TagPacket_T; +} __attribute__((packed, aligned(1))) BLE_TagPacket_T; //! Contents of the BLE packet #BLE_PACKET_TYPE_CONSOLE. typedef struct @@ -131,9 +132,9 @@ typedef struct uint8_t BD_ADDR[BD_ADDR_SIZE]; int8_t RSSI; uint8_t event_number; - + uint8_t console_data[BLE_KTAG_PACKET_DATA_SIZE]; -} __attribute__((packed, aligned(1)))BLE_ConsolePacket_T; +} __attribute__((packed, aligned(1))) BLE_ConsolePacket_T; //! Contents of the BLE packet #BLE_PACKET_TYPE_STATUS. typedef struct @@ -142,7 +143,7 @@ typedef struct uint8_t BD_ADDR[BD_ADDR_SIZE]; int8_t RSSI; uint8_t event_number; - + int8_t tx_power_level; uint8_t protocol; uint8_t team_ID; @@ -153,7 +154,39 @@ typedef struct color_t secondary_color; uint8_t SystemK_top_level_state; // StateID_T uint8_t unused[4]; -} __attribute__((packed, aligned(1)))BLE_StatusPacket_T; +} __attribute__((packed, aligned(1))) BLE_StatusPacket_T; + +typedef enum +{ + BLE_REQUEST_CURRENT_PARAMETER_INFORMATION = 0x00, + BLE_CURRENT_PARAMETER_INFORMATION = 0x01, + BLE_REQUEST_PARAMETER_CHANGE = 0x02, + BLE_ACKNOWLEDGE_PARAMETER_CHANGE = 0x03, + BLE_ERROR_CHANGING_PARAMETERS = 0x04, + BLE_ERROR_RESPONDING_TO_REQUEST = 0xFF +} BLE_ConfigurationSubtype_T; + +typedef enum +{ + BLE_CONFIGURATION_KEY_NONE = 0x0000, + BLE_CONFIGURATION_KEY_TEAM_ID = 0x0001, + BLE_FIRST_VALID_CONFIGURATION_KEY = BLE_CONFIGURATION_KEY_TEAM_ID, + BLE_LAST_VALID_CONFIGURATION_KEY = BLE_CONFIGURATION_KEY_TEAM_ID, + BLE_DEVICE_TYPE_UNUSED = 0xFFFF +} BLE_ConfigurationKey_T; + +inline BLE_ConfigurationKey_T BLE_GetValidConfigKey(uint16_t key) +{ + BLE_ConfigurationKey_T result = BLE_CONFIGURATION_KEY_NONE; + + if ((key >= BLE_FIRST_VALID_CONFIGURATION_KEY) && + (key <= BLE_LAST_VALID_CONFIGURATION_KEY)) + { + result = (BLE_ConfigurationKey_T)key; + } + + return result; +} //! Contents of the BLE packet #BLE_PACKET_TYPE_CONFIGURATION. typedef struct @@ -162,7 +195,7 @@ typedef struct uint8_t BD_ADDR[BD_ADDR_SIZE]; int8_t RSSI; uint8_t event_number; - + uint8_t target_BD_ADDR[BD_ADDR_SIZE]; uint8_t subtype; uint16_t key_one; @@ -170,15 +203,15 @@ typedef struct uint16_t key_two; uint32_t value_two; uint8_t unused[2]; -} __attribute__((packed, aligned(1)))BLE_ConfigurationPacket_T; +} __attribute__((packed, aligned(1))) BLE_ConfigurationPacket_T; typedef enum { BLE_DEVICE_TYPE_LITTLE_BOY_BLUE = 0x0000, - BLE_DEVICE_TYPE_2020TPC = 0x0001, - BLE_DEVICE_TYPE_MOBILE_APP = 0x0002, - BLE_DEVICE_TYPE_32ESPECIAL = 0x0003, - BLE_DEVICE_TYPE_UNKNOWN = 0xFFFF + BLE_DEVICE_TYPE_2020TPC = 0x0001, + BLE_DEVICE_TYPE_MOBILE_APP = 0x0002, + BLE_DEVICE_TYPE_32ESPECIAL = 0x0003, + BLE_DEVICE_TYPE_UNKNOWN = 0xFFFF } BLE_DeviceType_T; //! Contents of the BLE packet #BLE_PACKET_TYPE_HELLO. @@ -188,13 +221,13 @@ typedef struct uint8_t BD_ADDR[BD_ADDR_SIZE]; int8_t RSSI; uint8_t event_number; - + uint8_t SystemK_major_version; uint8_t SystemK_minor_version; uint16_t device_type; uint8_t team_ID; uint8_t device_name[SYSTEMK_MAX_CHARS_IN_DEVICE_NAME]; -} __attribute__((packed, aligned(1)))BLE_HelloPacket_T; +} __attribute__((packed, aligned(1))) BLE_HelloPacket_T; typedef union { @@ -214,20 +247,21 @@ typedef union /* Public Functions */ -inline void BLE_FreePacketBuffer(void * buffer) +inline void BLE_FreePacketBuffer(BLE_GenericPacketType_T *buffer) { if (buffer != NULL) { - ((BLE_GenericPacketType_T *)buffer)->type = BLE_PACKET_TYPE_BUFFER_FREE; + buffer->type = BLE_PACKET_TYPE_BUFFER_FREE; } } void BLE_InitPacketBuffers(void); -BLE_Packet_T * BLE_DecodeKTagPacket(const uint8_t * received_data, uint8_t received_data_length, uint8_t peer_BD_ADDR[BD_ADDR_SIZE], int8_t rssi_in_dBm); +BLE_Packet_T *BLE_DecodeKTagPacket(const uint8_t *received_data, uint8_t received_data_length, uint8_t peer_BD_ADDR[BD_ADDR_SIZE], int8_t rssi_in_dBm); void BLE_UpdateInstigationPacket(uint32_t Game_Length_in_ms, uint32_t Time_Remaining_Until_Countdown_in_ms); void BLE_UpdateStatusPacket(uint8_t current_state); void BLE_UpdateTagPacket(int16_t damage, color_t color, uint8_t target_BD_ADDR[BD_ADDR_SIZE]); +void BLE_RespondToConfigurationPacket(const BLE_ConfigurationPacket_T *const packet, BLE_ConfigurationSubtype_T response); void BLE_UpdateHelloPacket(); bool BLE_IsBLEPacketForMe(const uint8_t BD_ADDR[BD_ADDR_SIZE]); diff --git a/BLE/BLE_Utils.c b/BLE/BLE_Utils.c new file mode 100644 index 0000000..15ec5cd --- /dev/null +++ b/BLE/BLE_Utils.c @@ -0,0 +1,52 @@ +/* + * This program source code file is part of SystemK, a library in the KTag project. + * + * 🛡️ 🃞 + * + * Copyright © 2025 Joseph P. Kearney and the KTag developers. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU Affero General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * There should be a copy of the GNU Affero General Public License in the LICENSE + * file in the root of this repository. If not, see . + */ + +#include +#include +#include + + //! Converts a 6-byte Bluetooth Low Energy address to a human-readable string (for logging). + /*! + * \param bd_addr The Bluetooth address as a 6-byte array + * \return Pointer to a static string containing the formatted address + * + * Output format: "XX:XX:XX:XX:XX:XX" where XX are uppercase hexadecimal values + * \note This function is not reentrant as it uses a static buffer! + */ +const char *BLE_ADDR_To_Str(const uint8_t bd_addr[6]) +{ + static char str_addr[18]; // 17 characters + null terminator + + // Convert each byte and add separating colons. + for (int i = 0; i < 6; i++) + { + snprintf(&str_addr[i * 3], sizeof(str_addr) - (i * 3), "%02X", bd_addr[i]); + + // Add colon separator except after the last byte. + if (i < 5) + { + str_addr[(i * 3) + 2] = ':'; + } + } + + str_addr[17] = '\0'; // Ensure null termination. + return str_addr; +} \ No newline at end of file diff --git a/BLE/BLE_Utils.h b/BLE/BLE_Utils.h new file mode 100644 index 0000000..42cd2b2 --- /dev/null +++ b/BLE/BLE_Utils.h @@ -0,0 +1,22 @@ +/* + * This program source code file is part of SystemK, a library in the KTag project. + * + * 🛡️ 🃞 + * + * Copyright © 2025 Joseph P. Kearney and the KTag developers. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU Affero General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * There should be a copy of the GNU Affero General Public License in the LICENSE + * file in the root of this repository. If not, see . + */ + + const char* BLE_ADDR_To_Str(const uint8_t bd_addr[6]); \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index c04f98e..c9e2bb9 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,6 +2,7 @@ idf_component_register( SRCS "BLE/BLE_Packets.c" "BLE/BLE_Packet_Tracker.c" + "BLE/BLE_Utils.c" "Events/KEvents.c" "Game/Game.c" "Game/Weapons.c" diff --git a/Game/Game.h b/Game/Game.h index a9ca257..0d9fc02 100644 --- a/Game/Game.h +++ b/Game/Game.h @@ -44,6 +44,18 @@ typedef enum MAXIMUM_TEAM_ID = 7 } TeamID_t; +inline bool Is_Valid_Team_ID(uint8_t team_ID) +{ + bool result = false; + + if (team_ID <= MAXIMUM_TEAM_ID) + { + result = true; + } + + return result; +} + __attribute__((always_inline)) static inline SystemKResult_T Set_Team_With_Audio_Feedback(uint8_t team_ID) { static uint8_t Team_ID = 0; // This is static because AUDIO_PRONOUNCE_NUMBER_0_TO_100 needs a *pointer*. diff --git a/States/Playing/State_Playing.c b/States/Playing/State_Playing.c index 9efedcd..a347799 100644 --- a/States/Playing/State_Playing.c +++ b/States/Playing/State_Playing.c @@ -181,6 +181,8 @@ void HandleBLEStatusPacket(const BLE_StatusPacket_T *const packet) // NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_BLE_NEARBY, .Data = (void *)&BLE_RXd_data, .Prominence = NEOPIXELS_BACKGROUND}; // xQueueSend(xQueueNeoPixels, &neopixels_action, 0); + + BLE_FreePacketBuffer((BLE_GenericPacketType_T *)packet); } void HandleBLETagPacket(const BLE_TagPacket_T *const packet) @@ -243,5 +245,5 @@ void HandleBLETagPacket(const BLE_TagPacket_T *const packet) } } - BLE_FreePacketBuffer(packet); + BLE_FreePacketBuffer((BLE_GenericPacketType_T *)packet); } diff --git a/States/State_Configuring.c b/States/State_Configuring.c index 24cf594..a399ec4 100644 --- a/States/State_Configuring.c +++ b/States/State_Configuring.c @@ -26,6 +26,18 @@ static void Configuring_Entry(StateMachineContext_T *context); static void Configuring_Do(StateMachineContext_T *context); static void Configuring_Exit(StateMachineContext_T *context); +static void HandleBLEConfigurationPacket(const BLE_ConfigurationPacket_T *const packet); +static void HandleBLEEventPacket(const BLE_EventPacket_T *const packet); + +static TimerHandle_t BLEConfigurationResponseTimer = NULL; +static StaticTimer_t xBLEConfigurationResponseTimerBuffer; +static TickType_t xBLEConfigurationResponseTimerPeriod = 3000 / portTICK_PERIOD_MS; + +static void BLEConfigurationResponseTimerCallback(TimerHandle_t xTimer) +{ + BLE_UpdateHelloPacket(); +} + #define MAX_MENU_DEPTH 10 static MenuItem_T const *CurrentMenuItem; static uint8_t MenuItemHistoryIndex = 0; @@ -59,6 +71,22 @@ static void Configuring_Entry(StateMachineContext_T *context) KLOG_ERROR(KLOG_TAG, "Couldn't start BLE!"); } + if (BLEConfigurationResponseTimer == NULL) + { + BLEConfigurationResponseTimer = xTimerCreateStatic( + "BLEConfigResponse", + xBLEConfigurationResponseTimerPeriod, + pdTRUE, + (void *)0, + BLEConfigurationResponseTimerCallback, + &xBLEConfigurationResponseTimerBuffer); + } + + if (BLEConfigurationResponseTimer == NULL) + { + KLOG_ERROR(KLOG_TAG, "Couldn't create the BLEConfigurationResponseTimer!"); + } + // Reset the menu. CurrentMenuItem = RootMenu; MenuItemHistory[MenuItemHistoryIndex] = CurrentMenuItem; @@ -195,6 +223,19 @@ static void Configuring_Do(StateMachineContext_T *context) Transition_For_Event(context, STATE_REPROGRAMMING, &Event); break; + case KEVENT_BLE_PACKET_RECEIVED: + if (((BLE_Packet_T *)Event.Data)->Generic.type == BLE_PACKET_TYPE_CONFIGURATION) + { + HandleBLEConfigurationPacket((BLE_ConfigurationPacket_T *)Event.Data); + } + else if (((BLE_Packet_T *)Event.Data)->Generic.type == BLE_PACKET_TYPE_EVENT) + { + HandleBLEEventPacket((BLE_EventPacket_T *)Event.Data); + } + + BLE_FreePacketBuffer(Event.Data); + break; + default: // All other events are ignored in this state. ProcessUnhandledEvent(&Event); @@ -218,3 +259,67 @@ static void Configuring_Exit(StateMachineContext_T *context) NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_ALL_OFF, .Prominence = NEOPIXELS_FOREGROUND, .Data = (void *)0x00}; xQueueSend(xQueueNeoPixels, &neopixels_action, 0); } + +static SystemKResult_T HandleParameterChangeRequest(BLE_ConfigurationKey_T key, uint32_t value) +{ + SystemKResult_T result = SYSTEMK_RESULT_UNSPECIFIED_FAILURE; + + if (key == BLE_CONFIGURATION_KEY_TEAM_ID) + { + uint8_t team_ID = (uint8_t)value; + if (Is_Valid_Team_ID(team_ID)) + { + result = Set_Team_With_Audio_Feedback(team_ID); + } + } + + return result; +} + +void HandleBLEConfigurationPacket(const BLE_ConfigurationPacket_T *const packet) +{ + if (BLE_IsBLEPacketForMe(packet->target_BD_ADDR)) + { + if (BLE_IsPacketNew(packet->BD_ADDR, BLE_PACKET_TYPE_CONFIGURATION, packet->event_number)) + { + if (packet->subtype == BLE_REQUEST_PARAMETER_CHANGE) + { + SystemKResult_T result = SYSTEMK_RESULT_SUCCESS; + + BLE_ConfigurationKey_T key_one = BLE_GetValidConfigKey(packet->key_one); + if (key_one != BLE_CONFIGURATION_KEY_NONE) + { + result = HandleParameterChangeRequest(key_one, packet->value_one); + } + + if (result == SYSTEMK_RESULT_SUCCESS) + { + BLE_ConfigurationKey_T key_two = BLE_GetValidConfigKey(packet->key_two); + if (key_two != BLE_CONFIGURATION_KEY_NONE) + { + result = HandleParameterChangeRequest(key_two, packet->value_two); + } + } + + if (result == SYSTEMK_RESULT_SUCCESS) + { + BLE_RespondToConfigurationPacket(packet, BLE_ACKNOWLEDGE_PARAMETER_CHANGE); + } + else + { + KLOG_ERROR(KLOG_TAG, "Error changing parameter(s)!"); + BLE_RespondToConfigurationPacket(packet, BLE_ERROR_CHANGING_PARAMETERS); + } + + if (xTimerStart(BLEConfigurationResponseTimer, 0) != pdPASS) + { + KLOG_ERROR(KLOG_TAG, "Couldn't start the BLEConfigurationResponseTimer!"); + } + } + } + } +} + +static void HandleBLEEventPacket(const BLE_EventPacket_T *const packet) +{ +} diff --git a/SystemK.h b/SystemK.h index aeadecb..2ff8590 100755 --- a/SystemK.h +++ b/SystemK.h @@ -103,6 +103,7 @@ #include "BLE/BLE_Packets.h" #include "BLE/BLE_Packet_Tracker.h" #include "BLE/BLE_HW_Interface.h" +#include "BLE/BLE_Utils.h" #include "IR/IR_HW_Interface.h" #include "States/State_Machine.h" #include "Menu/Menu.h"