/* * 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 #include "SystemK.h" static TimerHandle_t BLEStatusTimer = NULL; static StaticTimer_t xBLEStatusTimerBuffer; static TickType_t xBLEStatusTimerPeriod = 3000 / portTICK_PERIOD_MS; static TimerHandle_t GameDurationTimer = NULL; static StaticTimer_t xGameDurationTimerBuffer; static BLENearby_T BLE_RXd_data; static const uint_fast16_t MAX_TIME_TO_LIVE_in_ms = 1000; static const int_fast8_t STATUS_RSSI_THRESHOLD = -80; static const int_fast8_t TAG_RSSI_THRESHOLD = -75; static void BLEStatusTimerCallback(TimerHandle_t xTimer) { BLE_UpdateStatusPacket(); } static void GameDurationTimerCallback(TimerHandle_t xTimer) { KEvent_T game_over_event = { .ID = KEVENT_GAME_OVER, .Data = (void *)0x00 }; Post_KEvent(&game_over_event); } //! Sets up the Playing state. /*! * \param context Context in which this state is being run. */ void Playing_Entry(StateMachineContext_T *context) { LOG("Entering the Playing state."); static const char *KLOG_TAG = "STATE_PLAYING Entry"; for (uint_fast8_t slot = 0; slot < CONFIG_KTAG_MAX_NEOPIXELS_PER_CHANNEL; slot++) { BLE_RXd_data.neighbors[slot].TimeToLive_in_ms = 0; } BLE_UpdateStatusPacket(); if (BLE_ScanAndAdvertise() != SYSTEMK_RESULT_SUCCESS) { KLOG_ERROR(KLOG_TAG, "Couldn't start BLE!"); } NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_TEAM_COLORS, .Data = (void *)DISPLAY_STYLE_HEARTBEAT, .Prominence = NEOPIXELS_BACKGROUND}; xQueueSend(xQueueNeoPixels, &neopixels_action, 0); if (BLEStatusTimer == NULL) { BLEStatusTimer = xTimerCreateStatic( "BLEStatus", xBLEStatusTimerPeriod, pdTRUE, (void *)0, BLEStatusTimerCallback, &xBLEStatusTimerBuffer); } if (BLEStatusTimer != NULL) { if (xTimerStart(BLEStatusTimer, 0) != pdPASS) { KLOG_ERROR(KLOG_TAG, "Couldn't start the BLEStatusTimer!"); } } else { KLOG_ERROR(KLOG_TAG, "Couldn't create the BLEStatusTimer!"); } // The timer is only needed for timed games. if (Get_Time_Remaining_in_Game_in_ms() != UINT32_MAX) { if (GameDurationTimer == NULL) { static TickType_t xGameDurationTimerPeriod; xGameDurationTimerPeriod = Get_Time_Remaining_in_Game_in_ms() / portTICK_PERIOD_MS; GameDurationTimer = xTimerCreateStatic( "GameDuration", xGameDurationTimerPeriod, pdFALSE, (void *)0, GameDurationTimerCallback, &xGameDurationTimerBuffer); } if (GameDurationTimer != NULL) { if (xTimerStart(GameDurationTimer, 0) != pdPASS) { KLOG_ERROR(KLOG_TAG, "Couldn't start the GameDurationTimer!"); } } else { KLOG_ERROR(KLOG_TAG, "Couldn't create the GameDurationTimer!"); } } } //! Cleans up the Playing state. /*! * \param context Context in which this state is being run. */ void Playing_Exit(StateMachineContext_T *context) { LOG("Exiting the Playing state."); xTimerStop(BLEStatusTimer, portMAX_DELAY); BLE_UpdateStatusPacket(); } void HandleBLEStatusPacket(const BLE_StatusPacket_T *const packet) { bool found = false; uint8_t first_empty_slot = 0xFF; // Look through the neighbors, and see if this is one we already know about. for (uint_fast8_t slot = 0; slot < CONFIG_KTAG_MAX_NEOPIXELS_PER_CHANNEL; slot++) { if (BLE_RXd_data.neighbors[slot].TimeToLive_in_ms > 0) { if (memcmp(packet->BD_ADDR, BLE_RXd_data.neighbors[slot].BD_ADDR, 6) == 0) { if ((packet->health > 0) && (packet->RSSI > STATUS_RSSI_THRESHOLD)) { BLE_RXd_data.neighbors[slot].RSSI = packet->RSSI; BLE_RXd_data.neighbors[slot].Color = packet->primary_color; BLE_RXd_data.neighbors[slot].TimeToLive_in_ms = MAX_TIME_TO_LIVE_in_ms; } else { // Clear the slot. BLE_RXd_data.neighbors[slot].TimeToLive_in_ms = 0; } found = true; break; } } else if (first_empty_slot == 0xFF) { first_empty_slot = slot; } } if ((found == false) && (first_empty_slot < CONFIG_KTAG_MAX_NEOPIXELS_PER_CHANNEL) && (packet->health > 0) && (packet->RSSI > STATUS_RSSI_THRESHOLD)) { // Welcome the new neighbor. memcpy(BLE_RXd_data.neighbors[first_empty_slot].BD_ADDR, packet->BD_ADDR, 6); BLE_RXd_data.neighbors[first_empty_slot].RSSI = packet->RSSI; BLE_RXd_data.neighbors[first_empty_slot].Color = packet->primary_color; BLE_RXd_data.neighbors[first_empty_slot].TimeToLive_in_ms = MAX_TIME_TO_LIVE_in_ms; } // NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_BLE_NEARBY, .Data = (void *)&BLE_RXd_data, .Prominence = NEOPIXELS_BACKGROUND}; // xQueueSend(xQueueNeoPixels, &neopixels_action, 0); } void HandleBLETagPacket(const BLE_TagPacket_T *const packet) { static int16_t Health_In_Percent; static color_t ReceivedTagColor; // Use this code for tuning RSSI. //{ // static uint8_t normalized_rssi = 0; // normalized_rssi = (uint8_t)abs(packet->RSSI); // // AudioAction_T rssi_action = {.ID = AUDIO_PRONOUNCE_NUMBER_0_TO_100, .Play_To_Completion = true, .Data = (void *)&normalized_rssi}; // Perform_Audio_Action(&rssi_action); //} // Throw away quiet packets without processing them. if (packet->RSSI > TAG_RSSI_THRESHOLD) { if (BLE_IsBLEPacketForMe(packet->target_BD_ADDR)) { if (BLE_IsPacketNew(packet->BD_ADDR, BLE_PACKET_TYPE_TAG, packet->event_number)) { if (packet->damage > 0) { Reduce_Health(packet->damage); ReceivedTagColor = packet->color; AudioAction_T audio_action = {.ID = AUDIO_PLAY_TAG_RECEIVED, .Data = (void *)0x00}; Perform_Audio_Action(&audio_action); NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_TAG_RECEIVED, .Prominence = NEOPIXELS_FOREGROUND, .Data = (void *)&ReceivedTagColor}; xQueueSend(xQueueNeoPixels, &neopixels_action, 0); } else if (packet->damage < 0) { Health_In_Percent = (uint8_t)Get_Health(); Health_In_Percent -= packet->damage; if (Health_In_Percent > MAX_HEALTH) { Health_In_Percent = MAX_HEALTH; } Health_In_Percent = MAX_HEALTH; Set_Health(Health_In_Percent); AudioAction_T audio_action = {.ID = AUDIO_PRONOUNCE_NUMBER_0_TO_100, .Play_To_Completion = true, .Data = (void *)&Health_In_Percent}; Perform_Audio_Action(&audio_action); AudioAction_T audio_action_two = {.ID = AUDIO_PLAY_HEALTH_REMAINING, .Data = (void *)0x00}; Perform_Audio_Action(&audio_action_two); NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_HEALTH_REPORT, .Prominence = NEOPIXELS_FOREGROUND, .Data = (void *)&Health_In_Percent}; xQueueSend(xQueueNeoPixels, &neopixels_action, 0); } } } } }