/* * This program source code file is part of SystemK, a library in the KTag project. * * 🛡️ 🃞 * * Copyright © 2016-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 Files */ #include "SystemK.h" 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_ParametersPacket_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) { // Don't send HELLO packets once configuration has started. BLE_StopAdvertising(); } #define MAX_MENU_DEPTH 10 static MenuItem_T const *CurrentMenuItem; static uint8_t MenuItemHistoryIndex = 0; static MenuItem_T const *MenuItemHistory[MAX_MENU_DEPTH]; static const uint16_t LONG_PRESS_FOR_READY_in_ms = 5000; static const char *KLOG_TAG = "STATE_CONFIGURING"; //! Activities for the **Configuring** state. const StateActivity_T STATE_CONFIGURING_Activities = { .Entry = Configuring_Entry, .Do = Configuring_Do, .Exit = Configuring_Exit}; //! Sets up the Configuring state. /*! * \param context Context in which this state is being run. */ static void Configuring_Entry(StateMachineContext_T *context) { LOG("Entering the Configuring state."); NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_MENU, .Prominence = NEOPIXELS_FOREGROUND, .Data = (void *)0x00}; xQueueSend(xQueueNeoPixels, &neopixels_action, 0); if (context->Cause_Of_Transition->ID == KEVENT_BLE_EVENT_RECEIVED) { AudioAction_T audio_action = {.ID = AUDIO_PLAY_BEEP, .Data = (void *)0x00}; Perform_Audio_Action(&audio_action); } BLE_UpdateHelloPacket(); if (BLE_ScanAndAdvertise() != SYSTEMK_RESULT_SUCCESS) { KLOG_ERROR(KLOG_TAG, "Couldn't start BLE!"); } if (BLEConfigurationResponseTimer == NULL) { BLEConfigurationResponseTimer = xTimerCreateStatic( "BLEConfigResponse", xBLEConfigurationResponseTimerPeriod, pdFALSE, (void *)0, BLEConfigurationResponseTimerCallback, &xBLEConfigurationResponseTimerBuffer); } if (BLEConfigurationResponseTimer == NULL) { KLOG_ERROR(KLOG_TAG, "Couldn't create the BLEConfigurationResponseTimer!"); } // Reset the menu. CurrentMenuItem = RootMenu; MenuItemHistory[MenuItemHistoryIndex] = CurrentMenuItem; } //! Executes the Configuring state. /*! * \param context Context in which this state is being run. */ static void Configuring_Do(StateMachineContext_T *context) { portBASE_TYPE xStatus; static KEvent_T Event; // For the first hundred milliseconds, keep updating the Hello packet, since on some platforms (PSoC6), one call is not enough. // TODO: Fix the Hello packet hack on PSoC6. if ((xTaskGetTickCount() - context->Time_At_State_Entry_In_Ticks) < (100 / portTICK_PERIOD_MS)) { BLE_UpdateHelloPacket(); } xStatus = Receive_KEvent(&Event); if (xStatus == pdPASS) { switch (Event.ID) { case KEVENT_MENU_ENTER: if (CurrentMenuItem->OnFocus != NULL) { CurrentMenuItem->OnFocus(true); } break; case KEVENT_MENU_SELECT: { if (CurrentMenuItem->OnSelect != NULL) { MenuItem_T const *menuItem = CurrentMenuItem->OnSelect(); // Check to see if we have entered a submenu. if (menuItem != NULL) { // Save off the old menu item, so we can get back to it later. MenuItemHistoryIndex++; MenuItemHistory[MenuItemHistoryIndex] = menuItem; CurrentMenuItem = menuItem; if (CurrentMenuItem->OnFocus != NULL) { CurrentMenuItem->OnFocus(true); } } } } break; case KEVENT_MENU_BACK: if (MenuItemHistoryIndex > 0) { // Go up a menu. MenuItemHistoryIndex--; CurrentMenuItem = MenuItemHistory[MenuItemHistoryIndex]; if (CurrentMenuItem->OnFocus != NULL) { CurrentMenuItem->OnFocus(true); } } break; case KEVENT_MENU_UP: { if (CurrentMenuItem->OnIncrement != NULL) { CurrentMenuItem->OnIncrement(); } } break; case KEVENT_MENU_DOWN: { if (CurrentMenuItem->OnDecrement != NULL) { CurrentMenuItem->OnDecrement(); } } break; case KEVENT_MENU_EXIT: while (MenuItemHistoryIndex > 0) { // Go up a menu. MenuItemHistoryIndex--; CurrentMenuItem = MenuItemHistory[MenuItemHistoryIndex]; if (CurrentMenuItem->OnFocus != NULL) { CurrentMenuItem->OnFocus(true); } } BLE_UpdateHelloPacket(); break; case KEVENT_TRIGGER_SWITCH_RELEASED: { uint32_t duration_of_press_in_ms = (uint32_t)Event.Data; if (duration_of_press_in_ms > LONG_PRESS_FOR_READY_in_ms) { Transition_For_Event(context, STATE_READY, &Event); } } break; case KEVENT_ACCESSORY_SWITCH_PRESSED: { uint8_t Team_ID; (void)SETTINGS_get_uint8_t(SYSTEMK_SETTING_TEAMID, &Team_ID); Team_ID++; if (Team_ID > BASIC_TEAMS_MAXIMUM) { Team_ID = BASIC_TEAMS_MINIMUM; } if (Set_Team_With_Audio_Feedback(Team_ID) != SYSTEMK_RESULT_SUCCESS) { KLOG_WARN(KLOG_TAG, "Failed to increment team!"); } BLE_UpdateHelloPacket(); } break; case KEVENT_PLAY_PRESSED_ON_REMOTE: Transition_For_Event(context, STATE_READY, &Event); break; case KEVENT_REPROGRAM: Transition_For_Event(context, STATE_REPROGRAMMING, &Event); break; case KEVENT_BLE_PACKET_RECEIVED: if (((BLE_Packet_T *)Event.Data)->Generic.type == BLE_PACKET_TYPE_PARAMETERS) { HandleBLEConfigurationPacket((BLE_ParametersPacket_T *)Event.Data); } else if (((BLE_Packet_T *)Event.Data)->Generic.type == BLE_PACKET_TYPE_EVENT) { HandleBLEEventPacket((BLE_EventPacket_T *)Event.Data, context); } else { BLE_FreePacketBuffer(Event.Data); } break; default: // All other events are ignored in this state. ProcessUnhandledEvent(&Event); break; } } } //! Cleans up the Configuring state. /*! * \param context Context in which this state is being run. */ static void Configuring_Exit(StateMachineContext_T *context) { // Save any changes that were made to NVM. if (SETTINGS_Save() != SYSTEMK_RESULT_SUCCESS) { KLOG_ERROR(KLOG_TAG, "Couldn't save the settings to NVM!"); } NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_ALL_OFF, .Prominence = NEOPIXELS_FOREGROUND, .Data = (void *)0x00}; xQueueSend(xQueueNeoPixels, &neopixels_action, 0); } static SystemKResult_T HandleParameterChangeRequest(BLE_ParameterKey_T key, uint32_t value) { SystemKResult_T result = SYSTEMK_RESULT_UNSPECIFIED_FAILURE; if (key == BLE_PARAMETER_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); } if (result == SYSTEMK_RESULT_SUCCESS) { KLOG_INFO(KLOG_TAG, "Team set to %u over BLE.", team_ID); } } else if (key == BLE_PARAMETER_KEY_PLAYER_ID) { uint8_t player_ID = (uint8_t)value; result = Set_Player_With_Audio_Feedback(player_ID); if (result == SYSTEMK_RESULT_SUCCESS) { KLOG_INFO(KLOG_TAG, "Player set to %u over BLE.", player_ID); } } else if (key == BLE_PARAMETER_KEY_GAME_LENGTH) { result = SETTINGS_set_uint32_t(SYSTEMK_SETTING_T_GAME_LENGTH_in_ms, value); if (result == SYSTEMK_RESULT_SUCCESS) { AudioAction_T audio_action = {.ID = AUDIO_PLAY_BEEP, .Data = (void *)0x00}; Perform_Audio_Action(&audio_action); KLOG_INFO(KLOG_TAG, "Game length set to %lu ms over BLE.", value); } else { AudioAction_T audio_action = {.ID = AUDIO_PLAY_BONK, .Data = (void *)0x00}; Perform_Audio_Action(&audio_action); } } else if (key == BLE_PARAMETER_KEY_MAX_HEALTH) { uint8_t max_health = (uint8_t)value; Set_Max_Health(max_health); result = SETTINGS_set_uint8_t(SYSTEMK_SETTING_MAX_HEALTH, max_health); if (result == SYSTEMK_RESULT_SUCCESS) { AudioAction_T audio_action = {.ID = AUDIO_PLAY_BEEP, .Data = (void *)0x00}; Perform_Audio_Action(&audio_action); KLOG_INFO(KLOG_TAG, "Max health set to %u over BLE.", max_health); } else { AudioAction_T audio_action = {.ID = AUDIO_PLAY_BONK, .Data = (void *)0x00}; Perform_Audio_Action(&audio_action); } } else if (key == BLE_PARAMETER_KEY_SECONDARY_COLOR) { result = SETTINGS_set_uint32_t(SYSTEMK_SETTING_SECONDARY_COLOR, value); if (result == SYSTEMK_RESULT_SUCCESS) { AudioAction_T audio_action = {.ID = AUDIO_PLAY_BEEP, .Data = (void *)0x00}; Perform_Audio_Action(&audio_action); KLOG_INFO(KLOG_TAG, "Secondary color set to %lu over BLE.", value); } else { AudioAction_T audio_action = {.ID = AUDIO_PLAY_BONK, .Data = (void *)0x00}; Perform_Audio_Action(&audio_action); } } else if (key == BLE_PARAMETER_KEY_SPECIAL_WEAPONS_ON_REENTRY) { uint8_t n_weapons_on_reentry = (uint8_t)value; Set_Available_Bombs(n_weapons_on_reentry); result = SETTINGS_set_uint8_t(SYSTEMK_SETTING_N_SPECIAL_WEAPONS_ON_REENTRY, n_weapons_on_reentry); if (result == SYSTEMK_RESULT_SUCCESS) { AudioAction_T audio_action = {.ID = AUDIO_PLAY_BEEP, .Data = (void *)0x00}; Perform_Audio_Action(&audio_action); KLOG_INFO(KLOG_TAG, "Number of special weapons granted on reentry set to %u over BLE.", n_weapons_on_reentry); } else { AudioAction_T audio_action = {.ID = AUDIO_PLAY_BONK, .Data = (void *)0x00}; Perform_Audio_Action(&audio_action); } } return result; } void HandleBLEConfigurationPacket(const BLE_ParametersPacket_T *const packet) { if (BLE_IsBLEPacketForMe(packet->target_BD_ADDR)) { if (BLE_IsPacketNew(packet->BD_ADDR, BLE_PACKET_TYPE_PARAMETERS, packet->event_number)) { if (packet->subtype == BLE_REQUEST_PARAMETER_CHANGE) { SystemKResult_T result = SYSTEMK_RESULT_SUCCESS; BLE_ParameterKey_T key_one = BLE_GetValidConfigKey(packet->key_one); if (key_one != BLE_PARAMETER_KEY_NONE) { result = HandleParameterChangeRequest(key_one, packet->value_one); } if (result == SYSTEMK_RESULT_SUCCESS) { BLE_ParameterKey_T key_two = BLE_GetValidConfigKey(packet->key_two); if (key_two != BLE_PARAMETER_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!"); } } } } else { // Once configuration has begun, stop advertising the HELLO packet to free up bandwidth. BLE_StopAdvertising(); } BLE_FreePacketBuffer((BLE_GenericPacketType_T *)packet); }