diff --git a/Audio/Audio_HW_Interface.h b/Audio/Audio_HW_Interface.h new file mode 100644 index 0000000..69fae93 --- /dev/null +++ b/Audio/Audio_HW_Interface.h @@ -0,0 +1,68 @@ +/* + * 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 . + */ + +#ifndef AUDIO_HW_INTERFACE_H +#define AUDIO_HW_INTERFACE_H + +typedef enum +{ + AUDIO_SET_VOLUME, + AUDIO_SILENCE, + AUDIO_PLAY_STARTUP_SOUND, + AUDIO_PLAY_SHOT_FIRED, + AUDIO_PLAY_TAG_RECEIVED, + AUDIO_PLAY_TAGGED_OUT, + AUDIO_PLAY_MISFIRE, + AUDIO_PRONOUNCE_NUMBER_0_TO_100, + AUDIO_PLAY_MENU_PROMPT, + AUDIO_PLAY_SELECTION_INDICATOR, + AUDIO_PLAY_HEALTH_REMAINING, + AUDIO_PLAY_ELECTRONIC_DANCE_MUSIC, + AUDIO_PLAY_GENERIC_ERROR, + AUDIO_PLAY_VOLUME_PROMPT, + AUDIO_PLAY_RIGHT_HANDED, + AUDIO_PLAY_LEFT_HANDED, + AUDIO_PLAY_GAME_ON, + AUDIO_PLAY_HARDWARE_SETTINGS_PROMPT, + AUDIO_PLAY_GAME_SETTINGS_PROMPT, + AUDIO_PLAY_BONK, + AUDIO_PLAY_NEAR_MISS, + AUDIO_PLAY_PLAYER_ID_PROMPT, + AUDIO_PLAY_TEAM_ID_PROMPT, + AUDIO_PLAY_FRIENDLY_FIRE, + AUDIO_PLAY_STARTING_THEME, + AUDIO_PLAY_BOOP, + AUDIO_PLAY_BEEP, + AUDIO_PLAY_REPROGRAMMING, + AUDIO_PLAY_BOMB, + AUDIO_PLAY_GAME_OVER +} AudioActionID_T; + +typedef struct +{ + AudioActionID_T ID; + bool Play_To_Completion; + void * Data; +} AudioAction_T; + +SystemKResult_T Perform_Audio_Action(AudioAction_T * action); + +#endif // AUDIO_HW_INTERFACE_H diff --git a/BLE/BLE_HW_Interface.h b/BLE/BLE_HW_Interface.h new file mode 100644 index 0000000..6b3f31c --- /dev/null +++ b/BLE/BLE_HW_Interface.h @@ -0,0 +1,29 @@ +/* + * 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 . + */ + +#ifndef BLE_HW_INTERFACE_H +#define BLE_HW_INTERFACE_H + +SystemKResult_T BLE_GetMyAddress(uint8_t * BD_ADDR); +SystemKResult_T BLE_ScanAndAdvertise(void); +SystemKResult_T BLE_SetAdvertisingData(BLE_AdvertisingData_T * data); + +#endif // BLE_HW_INTERFACE_H diff --git a/BLE/BLE_Packet_Tracker.c b/BLE/BLE_Packet_Tracker.c new file mode 100644 index 0000000..7f1ff2d --- /dev/null +++ b/BLE/BLE_Packet_Tracker.c @@ -0,0 +1,98 @@ +/* + * 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 +#include "SystemK.h" +#include "BLE_Packets.h" +#include "BLE_Packet_Tracker.h" + +#define MAX_REMEMBERED_PACKETS 10 + +typedef struct +{ + uint8_t sender_BD_ADDR[BD_ADDR_SIZE]; + BLE_PacketType_T packet_type; + uint8_t event_number; +} TrackedPacket_T; + +typedef struct +{ + TrackedPacket_T packets[MAX_REMEMBERED_PACKETS]; + // Number of unique packets we've seen so far. This value saturates at MAX_REMEMBERED_PACKETS. + uint8_t count; + // Index of the oldest packet. + uint8_t head; +} PacketTracker_T; + +static PacketTracker_T Tracker = + { + .count = 0, + .head = 0, + }; + +// A packet is _new_ if the combination of BLE adress, 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) +{ + // Check if the packet already exists in the tracker. + for (int i = 0; i < Tracker.count; i++) + { + int index = (Tracker.head + i) % MAX_REMEMBERED_PACKETS; + if ((memcmp(Tracker.packets[index].sender_BD_ADDR, sender_BD_ADDR, BD_ADDR_SIZE) == 0) && + (Tracker.packets[index].packet_type == packet_type) && + (Tracker.packets[index].event_number == event_number)) + { + // We've already seen this packet. + return false; + } + } + + // The packet is new--find the location to store it. + int new_index; + if (Tracker.count < MAX_REMEMBERED_PACKETS) + { + // There's space, so just add the new packet at the end. + new_index = (Tracker.head + Tracker.count) % MAX_REMEMBERED_PACKETS; + Tracker.count++; + } + else + { + // No space, so overwrite the oldest packet. + new_index = Tracker.head; + Tracker.head = (Tracker.head + 1) % MAX_REMEMBERED_PACKETS; + } + + // Add the new packet to the tracker. + memcpy(Tracker.packets[new_index].sender_BD_ADDR, sender_BD_ADDR, BD_ADDR_SIZE); + Tracker.packets[new_index].packet_type = packet_type; + Tracker.packets[new_index].event_number = event_number; + + return true; +} + +// Initialize the MessageTracker +void BLE_ClearPacketTracker(void) +{ + Tracker.count = 0; + Tracker.head = 0; +} diff --git a/BLE/BLE_Packet_Tracker.h b/BLE/BLE_Packet_Tracker.h new file mode 100644 index 0000000..914c752 --- /dev/null +++ b/BLE/BLE_Packet_Tracker.h @@ -0,0 +1,42 @@ + +/* + * 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 . + */ + +/** \file + * \brief This file defines a tracker for Bluetooth Low Energy advertising packets used by KTag. + * + */ + +#ifndef BLE_PACKET_TRACKER_H +#define BLE_PACKET_TRACKER_H + +#ifdef __cplusplus +extern "C" { +#endif + +bool BLE_IsPacketNew(const uint8_t* sender_BD_ADDR, BLE_PacketType_T packet_type, uint8_t event_number); +void BLE_ClearPacketTracker(void); + +#ifdef __cplusplus +} +#endif + +#endif // BLE_PACKET_TRACKER_H diff --git a/BLE/BLE_Packets.c b/BLE/BLE_Packets.c new file mode 100644 index 0000000..c2cc0a9 --- /dev/null +++ b/BLE/BLE_Packets.c @@ -0,0 +1,301 @@ + +/* + * 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" + + +#define N_PACKET_BUFFERS 10 +static BLE_Packet_T Packet_Buffers[N_PACKET_BUFFERS]; +static BLE_AdvertisingData_T Advertising_Data; +static uint8_t my_BD_ADDR[6]; +static bool is_my_BD_ADDR_initialized = false; + +static const char *KLOG_TAG = "BLE"; + + +void BLE_InitPacketBuffers(void) +{ + for (uint_fast8_t i = 0; i < N_PACKET_BUFFERS; i++) + { + Packet_Buffers[i].Generic.type = BLE_PACKET_TYPE_BUFFER_FREE; + } +} + +//! This function always returns a buffer, but it will clobber data if there are no free buffers. +static inline BLE_Packet_T * BLE_GetPacketBuffer(void) +{ + for (uint_fast8_t i = 0; i < N_PACKET_BUFFERS; i++) + { + if (Packet_Buffers[i].Generic.type == BLE_PACKET_TYPE_BUFFER_FREE) + { + return &Packet_Buffers[i]; + } + } + + KLOG_ERROR(KLOG_TAG, "Overwrote a BLE packet buffer: consider increasing N_PACKET_BUFFERS."); + + // Just use the last one. + return &Packet_Buffers[N_PACKET_BUFFERS - 1]; +} + +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 * result = NULL; + + // All KTag packets are 31 bytes long. + if (received_data_length == BLE_KTAG_PACKET_TOTAL_SIZE) + { + // Is this a KTag packet? + if ( (received_data[0] == 0x1E) && + (received_data[1] == 0xFF) && + (received_data[2] == 0xFF) && + (received_data[3] == 0xFF) && + (received_data[4] == 'K') && + (received_data[5] == 'T') && + (received_data[6] == 'a') && + (received_data[7] == 'g') ) + { + result = BLE_GetPacketBuffer(); + memcpy(result->Generic.BD_ADDR, peer_BD_ADDR, BD_ADDR_SIZE); + result->Generic.RSSI = rssi_in_dBm; + + uint8_t packet_type = received_data[8]; + + // Validate the packet type. + if ( (packet_type < BLE_FIRST_VALID_PACKET_TYPE) || + (packet_type > BLE_LAST_VALID_PACKET_TYPE) ) + { + packet_type = BLE_PACKET_TYPE_UNKNOWN; + } + result->Generic.type = packet_type; + + uint8_t event_number = received_data[9]; + result->Generic.event_number = event_number; + + // Copy the remaining MAX_BLE_PACKET_DATA_SIZE bytes of data over. + memcpy(result->Generic.data, &received_data[10], BLE_KTAG_PACKET_DATA_SIZE); + } + } + + return result; +} + +void BLE_UpdateInstigationPacket(uint32_t Game_Length_in_ms, uint32_t Time_Remaining_Until_Countdown_in_ms) +{ + 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_INSTIGATE_GAME; + Advertising_Data.data[9] = EventNumber++; + Advertising_Data.data[10] = (Game_Length_in_ms >> 0) & 0xFF; + Advertising_Data.data[11] = (Game_Length_in_ms >> 8) & 0xFF; + Advertising_Data.data[12] = (Game_Length_in_ms >> 16) & 0xFF; + Advertising_Data.data[13] = (Game_Length_in_ms >> 24) & 0xFF; + Advertising_Data.data[14] = (Time_Remaining_Until_Countdown_in_ms >> 0) & 0xFF; + Advertising_Data.data[15] = (Time_Remaining_Until_Countdown_in_ms >> 8) & 0xFF; + Advertising_Data.data[16] = (Time_Remaining_Until_Countdown_in_ms >> 16) & 0xFF; + Advertising_Data.data[17] = (Time_Remaining_Until_Countdown_in_ms >> 24) & 0xFF; + Advertising_Data.data[18] = 0x00; + Advertising_Data.data[19] = 0xFF; + Advertising_Data.data[20] = 0xFF; + Advertising_Data.data[21] = 0xFF; + Advertising_Data.data[22] = 0xFF; + Advertising_Data.data[23] = 0xFF; + Advertising_Data.data[24] = 0xFF; + Advertising_Data.data[25] = 0xFF; + Advertising_Data.data[26] = 0xFF; + Advertising_Data.data[27] = 0xFF; + Advertising_Data.data[28] = 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 updating instigation packet!"); + } +} + +void BLE_UpdateStatusPacket() +{ + static uint8_t EventNumber = 0; + + uint8_t team_ID; + uint8_t player_ID; + uint8_t weapon_ID; + Protocol_T protocol; + (void) SETTINGS_get_uint8_t(SYSTEMK_SETTING_TEAMID, &team_ID); + (void) SETTINGS_get_uint8_t(SYSTEMK_SETTING_PLAYERID, &player_ID); + (void) SETTINGS_get_uint8_t(SYSTEMK_SETTING_WEAPONID, &weapon_ID); + protocol = GetWeaponFromID(weapon_ID).Protocol; + uint32_t Team_Color = (uint32_t)PROTOCOLS_GetColor(protocol, team_ID, player_ID); + + 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_STATUS; + Advertising_Data.data[9] = EventNumber++; + Advertising_Data.data[10] = 4; // Tx Power in dBm + Advertising_Data.data[11] = DUBUQUE_PROTOCOL; + Advertising_Data.data[12] = team_ID; + Advertising_Data.data[13] = player_ID; + Advertising_Data.data[14] = Get_Health(); + Advertising_Data.data[15] = 0x00; + Advertising_Data.data[16] = MAX_HEALTH; + Advertising_Data.data[17] = 0x00; + Advertising_Data.data[18] = (Team_Color >> 0) & 0xFF; + Advertising_Data.data[19] = (Team_Color >> 8) & 0xFF; + Advertising_Data.data[20] = (Team_Color >> 16) & 0xFF; + Advertising_Data.data[21] = (Team_Color >> 24) & 0xFF; + Advertising_Data.data[22] = (Team_Color >> 0) & 0xFF; // Secondary Color + Advertising_Data.data[23] = (Team_Color >> 8) & 0xFF; // Secondary Color + Advertising_Data.data[24] = (Team_Color >> 16) & 0xFF; // Secondary Color + Advertising_Data.data[25] = (Team_Color >> 24) & 0xFF; // Secondary Color + Advertising_Data.data[26] = 0xFF; + Advertising_Data.data[27] = 0xFF; + Advertising_Data.data[28] = 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 updating status packet!"); + } +} + +void BLE_UpdateTagPacket(int16_t damage, color_t color, uint8_t target_BD_ADDR[BD_ADDR_SIZE]) +{ + static uint8_t EventNumber = 0; + + uint8_t team_ID; + uint8_t player_ID; + + (void) SETTINGS_get_uint8_t(SYSTEMK_SETTING_TEAMID, &team_ID); + (void) SETTINGS_get_uint8_t(SYSTEMK_SETTING_PLAYERID, &player_ID); + + 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_TAG; + Advertising_Data.data[9] = EventNumber++; + Advertising_Data.data[10] = 4; // Tx Power in dBm + Advertising_Data.data[11] = DUBUQUE_PROTOCOL; + Advertising_Data.data[12] = team_ID; + Advertising_Data.data[13] = player_ID; + Advertising_Data.data[14] = (uint8_t)((damage >> 0) & 0xFF); + Advertising_Data.data[15] = (uint8_t)((damage >> 8) & 0xFF); + Advertising_Data.data[16] = (color >> 0) & 0xFF; + Advertising_Data.data[17] = (color >> 8) & 0xFF; + Advertising_Data.data[18] = (color >> 16) & 0xFF; + Advertising_Data.data[19] = (color >> 24) & 0xFF; + Advertising_Data.data[20] = target_BD_ADDR[0], + Advertising_Data.data[21] = target_BD_ADDR[1], + Advertising_Data.data[22] = target_BD_ADDR[2], + Advertising_Data.data[23] = target_BD_ADDR[3], + Advertising_Data.data[24] = target_BD_ADDR[4], + Advertising_Data.data[25] = target_BD_ADDR[5], + Advertising_Data.data[26] = 0xFF; + Advertising_Data.data[27] = 0xFF; + Advertising_Data.data[28] = 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 updating status packet!"); + } +} + +bool BLE_IsBLEPacketForMe(const uint8_t BD_ADDR[6]) +{ + bool for_me = false; + + if (is_my_BD_ADDR_initialized == false) + { + if (BLE_GetMyAddress(my_BD_ADDR) == SYSTEMK_RESULT_SUCCESS) + { + is_my_BD_ADDR_initialized = true; + } + else + { + KLOG_ERROR(KLOG_TAG, "Couldn't get my BD_ADDR!"); + } + } + + // Is this my address? + if ((is_my_BD_ADDR_initialized == true) && + (BD_ADDR[0] == my_BD_ADDR[0]) && + (BD_ADDR[1] == my_BD_ADDR[1]) && + (BD_ADDR[2] == my_BD_ADDR[2]) && + (BD_ADDR[3] == my_BD_ADDR[3]) && + (BD_ADDR[4] == my_BD_ADDR[4]) && + (BD_ADDR[5] == my_BD_ADDR[5])) + { + for_me = true; + } + + // Is this the broadcast address? + if ((BD_ADDR[0] == 0xFF) && + (BD_ADDR[1] == 0xFF) && + (BD_ADDR[2] == 0xFF) && + (BD_ADDR[3] == 0xFF) && + (BD_ADDR[4] == 0xFF) && + (BD_ADDR[5] == 0xFF)) + { + for_me = true; + } + + return for_me; +} + diff --git a/BLE/BLE_Packets.h b/BLE/BLE_Packets.h new file mode 100644 index 0000000..d1f0a03 --- /dev/null +++ b/BLE/BLE_Packets.h @@ -0,0 +1,191 @@ + +/* + * 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 . + */ + +/** \file + * \brief This file defines the Bluetooth Low Energy advertising packets used by KTag. + * + */ + +#ifndef BLE_PACKETS_H +#define BLE_PACKETS_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* Preprocessor and Type Definitions */ +#include + +#define BLE_MAX_ADVERTISING_BYTES 31 +#define BLE_MAX_SCAN_RESPONSE_BYTES 31 + +#define BD_ADDR_SIZE 6 +#define BLE_KTAG_PACKET_TOTAL_SIZE 31 +#define BLE_KTAG_PACKET_DATA_SIZE 21 + +typedef struct +{ + uint8_t data[BLE_MAX_ADVERTISING_BYTES]; + uint8_t length; +} BLE_AdvertisingData_T; + +typedef struct +{ + uint8_t data[BLE_MAX_SCAN_RESPONSE_BYTES]; + uint8_t length; +} BLE_ScanResponseData_T; + +typedef enum +{ + BLE_PACKET_TYPE_BUFFER_FREE = 0, + BLE_PACKET_TYPE_INSTIGATE_GAME = 1, + BLE_FIRST_VALID_PACKET_TYPE = BLE_PACKET_TYPE_INSTIGATE_GAME, + BLE_PACKET_TYPE_JOIN_NOW = 2, + BLE_PACKET_TYPE_TAG = 3, + BLE_PACKET_TYPE_CONSOLE = 4, + BLE_PACKET_TYPE_STATUS = 5, + BLE_LAST_VALID_PACKET_TYPE = BLE_PACKET_TYPE_STATUS, + BLE_PACKET_TYPE_UNKNOWN +} BLE_PacketType_T; + +typedef struct +{ + BLE_PacketType_T type; + 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; + +//! Contents of the BLE packet #BLE_PACKET_TYPE_INSTIGATE_GAME. +typedef struct +{ + BLE_PacketType_T type; + 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 random_time_after_countdown_in_ms_x100; + uint8_t unused[12]; +} __attribute__((packed, aligned(1))) BLE_InstigationPacket_T; + +//! Contents of the BLE packet #BLE_PACKET_TYPE_JOIN_NOW. +typedef struct +{ + BLE_PacketType_T type; + uint8_t BD_ADDR[BD_ADDR_SIZE]; + int8_t RSSI; + uint8_t event_number; + + uint32_t game_length_in_ms; + uint32_t time_remaining_in_game_in_ms; + uint8_t unused[13]; +} __attribute__((packed, aligned(1))) BLE_JoinNowPacket_T; + +//! Contents of the BLE packet #BLE_PACKET_TYPE_TAG. +typedef struct +{ + BLE_PacketType_T type; + uint8_t BD_ADDR[BD_ADDR_SIZE]; + int8_t RSSI; + uint8_t event_number; + + uint8_t tx_power_level; + uint8_t protocol; + uint8_t team_ID; + uint8_t player_ID; + int16_t damage; + color_t color; + uint8_t target_BD_ADDR[BD_ADDR_SIZE]; + uint8_t unused[5]; +} __attribute__((packed, aligned(1)))BLE_TagPacket_T; + +//! Contents of the BLE packet #BLE_PACKET_TYPE_CONSOLE. +typedef struct +{ + BLE_PacketType_T type; + 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; + +//! Contents of the BLE packet #BLE_PACKET_TYPE_STATUS. +typedef struct +{ + BLE_PacketType_T type; + 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; + uint8_t player_ID; + uint16_t health; + uint16_t maximum_health; + color_t primary_color; + color_t secondary_color; + uint8_t unused[5]; +} __attribute__((packed, aligned(1)))BLE_StatusPacket_T; + +typedef union +{ + BLE_GenericPacketType_T Generic; + BLE_InstigationPacket_T Instigation; + BLE_JoinNowPacket_T JoinNow; + BLE_TagPacket_T Tag; + BLE_ConsolePacket_T Console; + BLE_StatusPacket_T Status; +} BLE_Packet_T; + +/* Include Files */ + +/* Public Variables */ + +/* Public Functions */ + +inline void BLE_FreePacketBuffer(void * buffer) +{ + if (buffer != NULL) + { + ((BLE_GenericPacketType_T *)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); +void BLE_UpdateInstigationPacket(uint32_t Game_Length_in_ms, uint32_t Time_Remaining_Until_Countdown_in_ms); +void BLE_UpdateStatusPacket(); +void BLE_UpdateTagPacket(int16_t damage, color_t color, uint8_t target_BD_ADDR[BD_ADDR_SIZE]); +bool BLE_IsBLEPacketForMe(const uint8_t BD_ADDR[BD_ADDR_SIZE]); + +#ifdef __cplusplus +} +#endif + +#endif // BLE_PACKETS_H diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100755 index 0000000..c04f98e --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,72 @@ +idf_component_register( + SRCS + "BLE/BLE_Packets.c" + "BLE/BLE_Packet_Tracker.c" + "Events/KEvents.c" + "Game/Game.c" + "Game/Weapons.c" + "Logging/KLog.c" + "Menu/GameSettings/GameMenuItem.c" + "Menu/GameSettings/PlayerIDMenuItem.c" + "Menu/GameSettings/TeamIDMenuItem.c" + "Menu/HardwareSettings/HandedMenuItem.c" + "Menu/HardwareSettings/HardwareMenuItem.c" + "Menu/HardwareSettings/VolumeMenuItem.c" + "Menu/Menu.c" + "NeoPixels/Gamma.c" + "NeoPixels/NeoPixels.c" + "NeoPixels/Sine.c" + "NeoPixels/Animations/All_Off.c" + "NeoPixels/Animations/All_On.c" + "NeoPixels/Animations/BLE_Nearby.c" + "NeoPixels/Animations/BLE_RSSI.c" + "NeoPixels/Animations/Countdown.c" + "NeoPixels/Animations/Flamethrower.c" + "NeoPixels/Animations/Flashlight.c" + "NeoPixels/Animations/Health_Report.c" + "NeoPixels/Animations/Idle_Animation.c" + "NeoPixels/Animations/Menu_Animation.c" + "NeoPixels/Animations/Shot_Fired.c" + "NeoPixels/Animations/Tagged_Out.c" + "NeoPixels/Animations/Tag_Received.c" + "NeoPixels/Animations/Team_Colors.c" + "NeoPixels/Animations/Test_Pattern.c" + "Protocols/Dubuque.c" + "Protocols/Dynasty.c" + "Protocols/Laser_X.c" + "Protocols/Miles_Tag_II.c" + "Protocols/NEC.c" + "Protocols/Nerf_Laser_Ops_Pro.c" + "Protocols/Nerf_Laser_Strike.c" + "Protocols/Nerf_Phoenix_LTX.c" + "Protocols/Protocols.c" + "Protocols/Squad_Hero.c" + "Protocols/Test.c" + "States/Playing/State_Playing.c" + "States/Playing/State_Playing__Interacting.c" + "States/Playing/State_Playing__Tagged_Out.c" + "States/Starting_Game/State_Starting_Game.c" + "States/Starting_Game/State_Starting_Game__Counting_Down.c" + "States/Starting_Game/State_Starting_Game__Instigating.c" + "States/Starting_Game/State_Starting_Game__Responding.c" + "States/State_Configuring.c" + "States/State_Initializing.c" + "States/State_Machine.c" + "States/State_Ready.c" + "States/State_Reprogramming.c" + "States/State_Wrapping_Up.c" + "SystemK.c" + INCLUDE_DIRS + "." + "Audio" + "BLE" + "Events" + "Game" + "IR" + "Logging" + "Menu" + "NeoPixels" + "Protocols" + "Settings" + "States" +) diff --git a/Colors.h b/Colors.h new file mode 100755 index 0000000..b821e8a --- /dev/null +++ b/Colors.h @@ -0,0 +1,110 @@ + +/* + * 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 . + */ + +/** \file + * \brief This file defines colors used by the KTag system (SystemK). + * + */ + +#include + +#ifndef COLORS_H +#define COLORS_H + +//! Represents a color with 8 bits per color, plus an 8-bit brightness mask, as MMRRGGBB. +typedef uint32_t color_t; + +#define COLOR_BLACK ((color_t)0xFF000000) +#define COLOR_WHITE ((color_t)0xFFFFFFFF) +#define COLOR_RED ((color_t)0xFFFF0000) +#define COLOR_ORANGE ((color_t)0xFFFF7F00) +#define COLOR_YELLOW ((color_t)0xFFFFFF00) +#define COLOR_GREEN ((color_t)0xFF00FF00) +#define COLOR_CYAN ((color_t)0xFF00FFFF) +#define COLOR_BLUE ((color_t)0xFF0000FF) +#define COLOR_MAGENTA ((color_t)0xFFFF0033) +#define COLOR_PURPLE ((color_t)0xFFFF00FF) +#define COLOR_PINK ((color_t)0xFFFF3377) +#define COLOR_AQUA ((color_t)0xFF557DFF) + +inline color_t GetColorFromTeamID(uint8_t team_ID) +{ + color_t result = COLOR_BLACK; + + switch (team_ID) + { + default: + case 0: + result = COLOR_PURPLE; + break; + case 1: + result = COLOR_RED; + break; + case 2: + result = COLOR_GREEN; + break; + case 3: + result = COLOR_WHITE; + break; + case 4: + result = COLOR_BLUE; + break; + case 5: + result = COLOR_ORANGE; + break; + case 6: + result = COLOR_YELLOW; + break; + case 7: + result = COLOR_BLACK; + break; + } + + return result; +} + +inline color_t Color(uint8_t mask, uint8_t red, uint8_t green, uint8_t blue) +{ + return (((color_t)mask << 24) | ((color_t)red << 16) | ((color_t)green << 8) | ((color_t)blue)); +} + +inline color_t ApplyMask(color_t color, uint8_t mask) +{ + return (((color_t)mask << 24) | (color & 0x00FFFFFF)); +} + +inline uint8_t Red(color_t color) +{ + return ((color >> 24) & ((color >> 16) & 0xFF)); +} + +inline uint8_t Green(color_t color) +{ + return ((color >> 24) & ((color >> 8) & 0xFF)); +} + +inline uint8_t Blue(color_t color) +{ + return ((color >> 24) & (color & 0xFF)); +} + +#endif // COLORS_H diff --git a/Console_HW_Interface.h b/Console_HW_Interface.h new file mode 100644 index 0000000..9cb3424 --- /dev/null +++ b/Console_HW_Interface.h @@ -0,0 +1,28 @@ +/* + * 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 . + */ + + +#ifndef CONSOLE_HW_INTERFACE_H +#define CONSOLE_HW_INTERFACE_H + +SystemKResult_T HW_Execute_Console_Command(const uint8_t * const command); + +#endif // CONSOLE_HW_INTERFACE_H diff --git a/Developer Certificate of Origin.txt b/Developer Certificate of Origin.txt new file mode 100644 index 0000000..4d9bf40 --- /dev/null +++ b/Developer Certificate of Origin.txt @@ -0,0 +1,35 @@ +Developer Certificate of Origin +Version 1.1 + +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + + +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. + diff --git a/Events/Command_Mapping.h b/Events/Command_Mapping.h new file mode 100755 index 0000000..80c28c8 --- /dev/null +++ b/Events/Command_Mapping.h @@ -0,0 +1,148 @@ + +/* + * 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 . + */ + +/** \file + * \brief This file maps IR remote control codes to events used by the KTag system (SystemK). + * + */ + +#ifndef COMMAND_MAPPING_H +#define COMMAND_MAPPING_H + +// First Roku Remote Replacement +// https://www.amazon.com/gp/product/B01G7OX9WS/ +#define NEC_COMMAND_ROKU_01_BACK_ARROW 2573648618 +#define NEC_COMMAND_ROKU_01_HOME 4228104938 +#define NEC_COMMAND_ROKU_01_UP_ARROW 3860447978 +#define NEC_COMMAND_ROKU_01_RIGHT_ARROW 3526214378 +#define NEC_COMMAND_ROKU_01_DOWN_ARROW 3425944298 +#define NEC_COMMAND_ROKU_01_LEFT_ARROW 3776889578 +#define NEC_COMMAND_ROKU_01_OK 3576349418 +#define NEC_COMMAND_ROKU_01_COUNTERCLOCKWISE 3576349418 +#define NEC_COMMAND_ROKU_01_ASTERIX 2657207018 +#define NEC_COMMAND_ROKU_01_REWIND 3409232618 +#define NEC_COMMAND_ROKU_01_PLAY_PAUSE 3008152298 +#define NEC_COMMAND_ROKU_01_FAST_FORWARD 2857747178 +#define NEC_COMMAND_ROKU_01_CHANNEL_CRACKLE 2155856618 +#define NEC_COMMAND_ROKU_01_CHANNEL_PANDORA 2907882218 +#define NEC_COMMAND_ROKU_01_CHANNEL_MGO 4027564778 +#define NEC_COMMAND_ROKU_01_CHANNEL_AMAZON 4027564778 +#define NEC_COMMAND_ROKU_01_CHANNEL_NETFLIX 3024863978 +#define NEC_COMMAND_ROKU_01_CHANNEL_BLOCKBUSTER 3743466218 + +// Second Roku Remote Replacement +// https://www.amazon.com/dp/B09Z6Q2MLC/ +#define NEC_COMMAND_ROKU_02_BACK_ARROW 2573649898 +#define NEC_COMMAND_ROKU_02_HOME 4228106218 +#define NEC_COMMAND_ROKU_02_UP_ARROW 3860449258 +#define NEC_COMMAND_ROKU_02_RIGHT_ARROW 3526215658 +#define NEC_COMMAND_ROKU_02_DOWN_ARROW 3425945578 +#define NEC_COMMAND_ROKU_02_LEFT_ARROW 3776890858 +#define NEC_COMMAND_ROKU_02_OK 3576350698 +#define NEC_COMMAND_ROKU_02_COUNTERCLOCKWISE 2272839658 +#define NEC_COMMAND_ROKU_02_ASTERIX 2272839658 +#define NEC_COMMAND_ROKU_02_REWIND 3409233898 +#define NEC_COMMAND_ROKU_02_PLAY_PAUSE 3008153578 +#define NEC_COMMAND_ROKU_02_FAST_FORWARD 2857748458 +#define NEC_COMMAND_ROKU_02_CHANNEL_NETFLIX 2907883498 +#define NEC_COMMAND_ROKU_02_CHANNEL_HULU 2991441898 +#define NEC_COMMAND_ROKU_02_CHANNEL_YOUTUBE 2841036778 +#define NEC_COMMAND_ROKU_02_CHANNEL_DISNEYPLUS 4077701098 + +// Samsung BN59-01301A TV Remote Control +// https://samsungparts.com/products/bn59-01301a +#define NEC_COMMAND_SAMSUNG_TV_POWER 4244768519 +#define NEC_COMMAND_SAMSUNG_TV_SOURCE 4261480199 +#define NEC_COMMAND_SAMSUNG_TV_SLEEP 4228056839 +#define NEC_COMMAND_SAMSUNG_TV_HOME 2256078599 +#define NEC_COMMAND_SAMSUNG_TV_GUIDE 2957969159 +#define NEC_COMMAND_SAMSUNG_TV_SETTINGS 3843688199 +#define NEC_COMMAND_SAMSUNG_TV_INFO 3760129799 +#define NEC_COMMAND_SAMSUNG_TV_UP_ARROW 2673870599 +#define NEC_COMMAND_SAMSUNG_TV_RIGHT_ARROW 2640447239 +#define NEC_COMMAND_SAMSUNG_TV_DOWN_ARROW 2657158919 +#define NEC_COMMAND_SAMSUNG_TV_LEFT_ARROW 2590312199 +#define NEC_COMMAND_SAMSUNG_TV_CENTER_SELECT 2540177159 +#define NEC_COMMAND_SAMSUNG_TV_RETURN 2807564039 +#define NEC_COMMAND_SAMSUNG_TV_EXIT 3526166279 + +__attribute__((always_inline)) inline void Remap_NEC_Command(KEvent_T *event, uint32_t NEC_data) +{ + switch (NEC_data) + { + case NEC_COMMAND_ROKU_01_HOME: + case NEC_COMMAND_ROKU_02_HOME: + case NEC_COMMAND_SAMSUNG_TV_HOME: + case NEC_COMMAND_SAMSUNG_TV_SETTINGS: + event->ID = KEVENT_MENU_ENTER; + break; + + case NEC_COMMAND_ROKU_01_BACK_ARROW: + case NEC_COMMAND_ROKU_02_BACK_ARROW: + case NEC_COMMAND_SAMSUNG_TV_EXIT: + event->ID = KEVENT_MENU_EXIT; + break; + + case NEC_COMMAND_ROKU_01_OK: + case NEC_COMMAND_ROKU_02_OK: + case NEC_COMMAND_SAMSUNG_TV_CENTER_SELECT: + event->ID = KEVENT_MENU_SELECT; + break; + + case NEC_COMMAND_ROKU_01_UP_ARROW: + case NEC_COMMAND_ROKU_02_UP_ARROW: + case NEC_COMMAND_SAMSUNG_TV_UP_ARROW: + event->ID = KEVENT_MENU_UP; + break; + + case NEC_COMMAND_ROKU_01_DOWN_ARROW: + case NEC_COMMAND_ROKU_02_DOWN_ARROW: + case NEC_COMMAND_SAMSUNG_TV_DOWN_ARROW: + event->ID = KEVENT_MENU_DOWN; + break; + + case NEC_COMMAND_ROKU_01_LEFT_ARROW: + case NEC_COMMAND_ROKU_02_LEFT_ARROW: + case NEC_COMMAND_ROKU_01_RIGHT_ARROW: + case NEC_COMMAND_ROKU_02_RIGHT_ARROW: + case NEC_COMMAND_SAMSUNG_TV_LEFT_ARROW: + case NEC_COMMAND_SAMSUNG_TV_RIGHT_ARROW: + event->ID = KEVENT_MENU_BACK; + break; + + case NEC_COMMAND_ROKU_01_ASTERIX: + case NEC_COMMAND_ROKU_02_ASTERIX: + case NEC_COMMAND_SAMSUNG_TV_SOURCE: + event->ID = KEVENT_REPROGRAM; + break; + + case NEC_COMMAND_ROKU_01_PLAY_PAUSE: + case NEC_COMMAND_ROKU_02_PLAY_PAUSE: + event->ID = KEVENT_PLAY_PRESSED_ON_REMOTE; + break; + + default: + break; + } +} + +#endif // COMMAND_MAPPING_H diff --git a/Events/KEvents.c b/Events/KEvents.c new file mode 100755 index 0000000..d191ae5 --- /dev/null +++ b/Events/KEvents.c @@ -0,0 +1,76 @@ + +/* + * 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 + +#include "SystemK.h" +#include "Command_Mapping.h" + +#define QUEUE_LENGTH 10 +#define ITEM_SIZE sizeof(KEvent_T) +static StaticQueue_t StaticQueue; +static uint8_t QueueStorageArea[QUEUE_LENGTH * ITEM_SIZE]; +QueueHandle_t xQueueEvents; + +static void Remap_Event(KEvent_T *event) +{ + switch (event->ID) + { + case KEVENT_COMMAND_RECEIVED: + { + if (((DecodedPacket_T *)(event->Data))->Command.protocol == NEC_PROTOCOL) + { + uint32_t NEC_data = ((DecodedPacket_T *)(event->Data))->Command.data; + Remap_NEC_Command(event, NEC_data); + } + FreeDecodedPacketBuffer(event->Data); + } + break; + + default: + // No remapping necessary. + break; + } +} + +void Init_KEvents(void) +{ + xQueueEvents = xQueueCreateStatic(QUEUE_LENGTH, ITEM_SIZE, QueueStorageArea, &StaticQueue); +} + +void Post_KEvent(KEvent_T *event) +{ + Remap_Event(event); + xQueueSend(xQueueEvents, event, 0); +} + +void Post_KEvent_From_ISR(KEvent_T *event, portBASE_TYPE *xHigherPriorityTaskWoken) +{ + Remap_Event(event); + xQueueSendFromISR(xQueueEvents, event, xHigherPriorityTaskWoken); +} + +portBASE_TYPE Receive_KEvent(KEvent_T *event) +{ + return xQueueReceive(xQueueEvents, event, 0); +} diff --git a/Events/KEvents.h b/Events/KEvents.h new file mode 100755 index 0000000..a992606 --- /dev/null +++ b/Events/KEvents.h @@ -0,0 +1,104 @@ + +/* + * 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 . + */ + +/** \file + * \brief This file defines the events used by the KTag system (SystemK). + * + */ + +#ifndef EVENTS_H +#define EVENTS_H + +//! Event identifiers used by the SystemK. +typedef enum +{ + KEVENT_NO_EVENT = 0, + KEVENT_AUTOMATIC_TRANSITION = 1, + KEVENT_CAPSENSE_ONE_PRESSED = 2, + KEVENT_CAPSENSE_ONE_RELEASED = 3, + KEVENT_CAPSENSE_TWO_PRESSED = 4, + KEVENT_CAPSENSE_TWO_RELEASED = 5, + KEVENT_TRIGGER_SWITCH_PRESSED = 6, + KEVENT_CENTER_SWITCH_PRESSED = KEVENT_TRIGGER_SWITCH_PRESSED, + KEVENT_TRIGGER_SWITCH_RELEASED = 7, + //! For #KEVENT_CENTER_SWITCH_RELEASED, #KEvent_T::Data contains the duration of the press in milliseconds. + KEVENT_CENTER_SWITCH_RELEASED = KEVENT_TRIGGER_SWITCH_RELEASED, + KEVENT_UP_SWITCH_PRESSED = 8, + KEVENT_UP_SWITCH_LONG_PRESSED = 9, + KEVENT_UP_SWITCH_RELEASED = 10, + KEVENT_DOWN_SWITCH_PRESSED = 11, + KEVENT_DOWN_SWITCH_LONG_PRESSED = 12, + KEVENT_DOWN_SWITCH_RELEASED = 13, + KEVENT_FORWARD_SWITCH_PRESSED = 14, + KEVENT_FORWARD_SWITCH_LONG_PRESSED = 15, + KEVENT_FORWARD_SWITCH_RELEASED = 16, + KEVENT_BACKWARD_SWITCH_PRESSED = 17, + KEVENT_BACKWARD_SWITCH_LONG_PRESSED = 18, + KEVENT_BACKWARD_SWITCH_RELEASED = 19, + KEVENT_TAG_SENT = 20, + //! For #KEVENT_TAG_RECEIVED, #KEvent_T::Data points to #TagPacket_T. + KEVENT_TAG_RECEIVED = 21, + KEVENT_TAGGED_OUT = 22, + KEVENT_MISFIRE = 23, + KEVENT_NEAR_MISS = 24, + //! For #KEVENT_BLE_PACKET_RECEIVED, #KEvent_T::Data points to #COMM_BLE_Packet_T. + KEVENT_BLE_PACKET_RECEIVED = 25, + //! For #KEVENT_COMMAND_RECEIVED, #KEvent_T::Data points to #CommandPacket_T. + KEVENT_COMMAND_RECEIVED = 26, + KEVENT_MENU_ENTER = 27, + KEVENT_MENU_SELECT = 28, + KEVENT_MENU_BACK = 29, + KEVENT_MENU_UP = 30, + KEVENT_MENU_DOWN = 31, + KEVENT_MENU_EXIT = 32, + KEVENT_REPROGRAM = 33, + //! For #KEVENT_ACCESSORY_SWITCH_PRESSED, #KEvent_T::Data contains the duration the switch has not been pressed in milliseconds. + KEVENT_ACCESSORY_SWITCH_PRESSED = 34, + //! For #KEVENT_ACCESSORY_SWITCH_PRESSED, #KEvent_T::Data contains the duration of the press in milliseconds. + KEVENT_ACCESSORY_SWITCH_RELEASED = 35, + //! For #KEVENT_AUDIO_COMPLETED, #KEvent_T::Data contains the AudioActionID_T of the completed action. + KEVENT_AUDIO_COMPLETED = 36, + KEVENT_GAME_OVER = 37, + KEVENT_PLAY_PRESSED_ON_REMOTE = 38, + // KEVENT_IS_OUT_OF_RANGE is one more than the last valid event. + KEVENT_IS_OUT_OF_RANGE +} KEvent_ID_T; + +//! Events used by the SystemK. +/*! + * The #Data is event-dependent. + */ +typedef struct +{ + KEvent_ID_T ID; + void *Data; +} KEvent_T; + +// Verify the pointer size, since sometime we store uint32_t's in the KEvent_T::Data pointer. +_Static_assert(sizeof(uintptr_t) >= sizeof(uint32_t), "Pointer size is less than 32 bits! KEvents will not work properly on this system."); + +void Init_KEvents(void); +void Post_KEvent(KEvent_T *event); +void Post_KEvent_From_ISR(KEvent_T *event, portBASE_TYPE *xHigherPriorityTaskWoken); +portBASE_TYPE Receive_KEvent(KEvent_T *event); + +#endif // EVENTS_H diff --git a/Game/Game.c b/Game/Game.c new file mode 100644 index 0000000..8fa8744 --- /dev/null +++ b/Game/Game.c @@ -0,0 +1,31 @@ +/* + * 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 "SystemK.h" + +KTag_Game_Data_T KTAG_Game_Data = +{ + .My_Health = MAX_HEALTH, + .My_Weapon = TEST_PATTERN_ID, + .My_Shield_Strength = 100, + .Time_Remaining_in_Game_in_ms = UINT32_MAX, + .Time_Remaining_Until_Countdown_in_ms = CONFIG_KTAG_T_DEFAULT_START_GAME_in_ms +}; diff --git a/Game/Game.h b/Game/Game.h new file mode 100644 index 0000000..356e9c7 --- /dev/null +++ b/Game/Game.h @@ -0,0 +1,238 @@ +/* + * 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 . + */ + +#ifndef GAME_H +#define GAME_H + +#include "Weapons.h" + +#define MAX_HEALTH 10 + +//! Implemented according to the 2024-07-20 "KTag Teams Compatibility Matrix". +typedef enum +{ + TEAM_PURPLE = 0, + TEAM_RED = 1, + TEAM_BLUE = 2, + TEAM_WHITE = 3, + COMMON_TEAM_ID_MASK = 3, + TEAM_ORANGE = 4, + TEAM_GREEN = 5, + TEAM_YELLOW = 6, + TEAM_BLACK = 7, + EXTENDED_TEAM_ID_MASK = 7 +} TeamID_t; + +__attribute__((always_inline)) inline TeamID_t Resolve_Common_Team_ID(uint8_t team_ID) +{ + return (team_ID & COMMON_TEAM_ID_MASK); +} + +__attribute__((always_inline)) inline bool Team_Can_Tag_Me(uint8_t tagging_team_ID) +{ + bool can_tag = false; + uint8_t team_ID; + (void) SETTINGS_get_uint8_t(SYSTEMK_SETTING_TEAMID, &team_ID); + TeamID_t my_common_team_ID = Resolve_Common_Team_ID(team_ID); + + if (tagging_team_ID == TEAM_PURPLE) + { + can_tag = true; + } + else if (tagging_team_ID != my_common_team_ID) + { + if ((tagging_team_ID == TEAM_RED) || (tagging_team_ID == TEAM_BLUE)) + { + can_tag = true; + } + } + + return can_tag; +} + +typedef struct +{ + uint8_t My_Health; + WeaponID_t My_Weapon; + uint8_t My_Shield_Strength; + uint32_t Time_Remaining_in_Game_in_ms; + uint32_t Time_Remaining_Until_Countdown_in_ms; + uint16_t Shots_Fired; + uint16_t Tags_Received; + uint16_t Times_Tagged_Out; + uint8_t Bombs_Remaining; +} KTag_Game_Data_T; + +extern KTag_Game_Data_T KTAG_Game_Data; + +__attribute__((always_inline)) inline uint8_t Get_Health() +{ + return KTAG_Game_Data.My_Health; +} + +__attribute__((always_inline)) inline void Set_Health(uint8_t health) +{ + KTAG_Game_Data.My_Health = health; + + if (KTAG_Game_Data.My_Health == 0) + { + KEvent_T tagged_out_event = { .ID = KEVENT_TAGGED_OUT, .Data = (void *)0x00 }; + Post_KEvent(&tagged_out_event); + } +} + +__attribute__((always_inline)) inline void Reduce_Health(uint8_t reduction) +{ + if (reduction < KTAG_Game_Data.My_Health) + { + KTAG_Game_Data.My_Health -= reduction; + } + else + { + KTAG_Game_Data.My_Health = 0; + KEvent_T tagged_out_event = { .ID = KEVENT_TAGGED_OUT, .Data = (void *)0x00 }; + Post_KEvent(&tagged_out_event); + } +} + +__attribute__((always_inline)) inline WeaponID_t Get_Weapon() +{ + return KTAG_Game_Data.My_Weapon; +} + +__attribute__((always_inline)) inline void Set_Weapon(WeaponID_t weapon) +{ + KTAG_Game_Data.My_Weapon = weapon; +} + +__attribute__((always_inline)) inline uint16_t Get_Shield_Strength() +{ + return KTAG_Game_Data.My_Shield_Strength; +} + +__attribute__((always_inline)) inline void Set_Shield_Strength(uint16_t strength) +{ + KTAG_Game_Data.My_Shield_Strength = strength; +} + +__attribute__((always_inline)) inline void Reset_Shots_Fired() +{ + KTAG_Game_Data.Shots_Fired = 0; +} + +__attribute__((always_inline)) inline void Increment_Shots_Fired() +{ + if (KTAG_Game_Data.Shots_Fired < UINT16_MAX) + { + KTAG_Game_Data.Shots_Fired++; + } +} + +__attribute__((always_inline)) inline void Reset_Tags_Received() +{ + KTAG_Game_Data.Tags_Received = 0; +} + +__attribute__((always_inline)) inline void Increment_Tags_Received() +{ + if (KTAG_Game_Data.Tags_Received < UINT16_MAX) + { + KTAG_Game_Data.Tags_Received++; + } +} + +__attribute__((always_inline)) inline void Reset_Times_Tagged_Out() +{ + KTAG_Game_Data.Times_Tagged_Out = 0; +} + +__attribute__((always_inline)) inline void Increment_Times_Tagged_Out() +{ + if (KTAG_Game_Data.Times_Tagged_Out < UINT16_MAX) + { + KTAG_Game_Data.Times_Tagged_Out++; + } +} + +__attribute__((always_inline)) inline bool Still_Playing() +{ + return (KTAG_Game_Data.My_Health > 0); +} + +__attribute__((always_inline)) inline bool Back_In() +{ + return Still_Playing(); +} + +__attribute__((always_inline)) inline void Set_Time_Remaining_in_Game(uint32_t time_in_ms) +{ + KTAG_Game_Data.Time_Remaining_in_Game_in_ms = time_in_ms; +} + +__attribute__((always_inline)) inline uint32_t Get_Time_Remaining_in_Game_in_ms() +{ + return KTAG_Game_Data.Time_Remaining_in_Game_in_ms; +} + +__attribute__((always_inline)) inline void Set_Time_Remaining_Until_Countdown(uint32_t time_in_ms) +{ + KTAG_Game_Data.Time_Remaining_Until_Countdown_in_ms = time_in_ms; +} + +__attribute__((always_inline)) inline uint32_t Get_Time_Remaining_Until_Countdown_in_ms() +{ + return KTAG_Game_Data.Time_Remaining_Until_Countdown_in_ms; +} + +__attribute__((always_inline)) inline uint16_t Get_Shots_Fired() +{ + return KTAG_Game_Data.Shots_Fired; +} + +__attribute__((always_inline)) inline uint16_t Get_Tags_Received() +{ + return KTAG_Game_Data.Tags_Received; +} + +__attribute__((always_inline)) inline uint16_t Get_Times_Tagged_Out() +{ + return KTAG_Game_Data.Times_Tagged_Out; +} + +__attribute__((always_inline)) inline void Set_Available_Bombs(uint8_t n_bombs) +{ + KTAG_Game_Data.Bombs_Remaining = n_bombs; +} + +__attribute__((always_inline)) inline bool Use_Bomb_If_Available() +{ + bool available = false; + + if (KTAG_Game_Data.Bombs_Remaining > 0) + { + KTAG_Game_Data.Bombs_Remaining--; + available = true; + } + + return available; +} + +#endif // GAME_H diff --git a/Game/Weapons.c b/Game/Weapons.c new file mode 100644 index 0000000..f90f523 --- /dev/null +++ b/Game/Weapons.c @@ -0,0 +1,171 @@ +/* + * 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 "SystemK.h" + +//! All the weapons, in the same order as #WeaponID_t. +Weapon_t WeaponsByID[NUMBER_OF_WEAPONS] = +{ + // Test Pattern + { + .ID = TEST_PATTERN_ID, + .Type = ENERGY, + .Protocol = TEST_PROTOCOL, + .Damage_Per_Shot = 1, + .Shots_Per_Reload = UINT16_MAX, + .Delay_Before_Sending_Shot_in_ms = 0, + .Time_Between_Shots_in_ms = 1000, + .Time_To_Reload_in_ms = 0 + }, + + // Bayonette + { + .ID = BAYONETTE_ID, + .Type = EDGE, + .Protocol = MILES_TAG_II_PROTOCOL, // For now... + .Damage_Per_Shot = 1, + .Shots_Per_Reload = UINT16_MAX, + .Delay_Before_Sending_Shot_in_ms = 0, + .Time_Between_Shots_in_ms = 1000, + .Time_To_Reload_in_ms = 0 + }, + + // Pistol + { + .ID = PISTOL_ID, + .Type = PROJECTILE, + .Protocol = MILES_TAG_II_PROTOCOL, // For now... + .Damage_Per_Shot = 10, + .Shots_Per_Reload = 8, + .Delay_Before_Sending_Shot_in_ms = 0, + .Time_Between_Shots_in_ms = 300, + .Time_To_Reload_in_ms = 3000 + }, + + // Shotgun + { + .ID = SHOTGUN_ID, + .Type = PROJECTILE, + .Protocol = MILES_TAG_II_PROTOCOL, // For now... + .Damage_Per_Shot = 20, + .Shots_Per_Reload = 2, + .Delay_Before_Sending_Shot_in_ms = 0, + .Time_Between_Shots_in_ms = 500, + .Time_To_Reload_in_ms = 10000 + }, + + // Rifle + { + .ID = RIFLE_ID, + .Type = PROJECTILE, + .Protocol = MILES_TAG_II_PROTOCOL, // For now... + .Damage_Per_Shot = 10, + .Shots_Per_Reload = 8, + .Delay_Before_Sending_Shot_in_ms = 0, + .Time_Between_Shots_in_ms = 300, + .Time_To_Reload_in_ms = 5000 + }, + + // Machine Gun + { + .ID = MACHINE_GUN_ID, + .Type = PROJECTILE, + .Protocol = MILES_TAG_II_PROTOCOL, // For now... + .Damage_Per_Shot = 10, + .Shots_Per_Reload = 250, + .Delay_Before_Sending_Shot_in_ms = 0, + .Time_Between_Shots_in_ms = 0, + .Time_To_Reload_in_ms = 2*60*1000 + }, + + // Nerf Laser Ops Pro + { + .ID = NERF_LASER_OPS_ID, + .Type = ENERGY, + .Protocol = NERF_LASER_OPS_PRO_PROTOCOL, + .Damage_Per_Shot = 10, + .Shots_Per_Reload = 10, + .Delay_Before_Sending_Shot_in_ms = 0, + .Time_Between_Shots_in_ms = 300, + .Time_To_Reload_in_ms = 3000 + }, + + // Nerf Laser Strike Blaster + { + .ID = NERF_LASER_STRIKE_BLASTER_ID, + .Type = ENERGY, + .Protocol = NERF_LASER_STRIKE_PROTOCOL, + .Damage_Per_Shot = 10, + .Shots_Per_Reload = 12, + .Delay_Before_Sending_Shot_in_ms = 0, + .Time_Between_Shots_in_ms = 300, + .Time_To_Reload_in_ms = 3000 + }, + + // Nerf Phoenix LTX + { + .ID = NERF_PHOENIX_LTX_ID, + .Type = ENERGY, + .Protocol = NERF_PHOENIX_LTX_PROTOCOL, + .Damage_Per_Shot = 10, + .Shots_Per_Reload = 10, + .Delay_Before_Sending_Shot_in_ms = 0, + .Time_Between_Shots_in_ms = 300, + .Time_To_Reload_in_ms = 3000 + }, + + // Squad Hero Pistol + { + .ID = SQUAD_HERO_PISTOL, + .Type = ENERGY, + .Protocol = SQUAD_HERO_PROTOCOL, + .Damage_Per_Shot = 1, + .Shots_Per_Reload = 10, + .Delay_Before_Sending_Shot_in_ms = 0, + .Time_Between_Shots_in_ms = 300, + .Time_To_Reload_in_ms = 3000 + }, + + // Laser X + { + .ID = LASER_X_ID, + .Type = ENERGY, + .Protocol = LASER_X_PROTOCOL, + .Damage_Per_Shot = 1, + .Shots_Per_Reload = 10, + .Delay_Before_Sending_Shot_in_ms = 0, + .Time_Between_Shots_in_ms = 300, + .Time_To_Reload_in_ms = 3000 + }, + + // The Dubuque Protocol + { + .ID = LASER_X_ID, + .Type = ENERGY, + .Protocol = DUBUQUE_PROTOCOL, + .Damage_Per_Shot = 1, + .Shots_Per_Reload = 10, + .Delay_Before_Sending_Shot_in_ms = 0, + .Time_Between_Shots_in_ms = 300, + .Time_To_Reload_in_ms = 3000 + } +}; + diff --git a/Game/Weapons.h b/Game/Weapons.h new file mode 100644 index 0000000..e711ae7 --- /dev/null +++ b/Game/Weapons.h @@ -0,0 +1,82 @@ +/* + * 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 . + */ + +#ifndef WEAPON_H +#define WEAPON_H + +//! These have assigned values because they are stored in #NVM_WEAPON_ID. +typedef enum +{ + TEST_PATTERN_ID = 0, + BAYONETTE_ID = 1, + PISTOL_ID = 2, + SHOTGUN_ID = 3, + RIFLE_ID = 4, + MACHINE_GUN_ID = 5, + NERF_LASER_OPS_ID = 6, + NERF_LASER_STRIKE_BLASTER_ID = 7, + NERF_PHOENIX_LTX_ID = 8, + SQUAD_HERO_PISTOL = 9, + LASER_X_ID = 10, + DUBUQUE_PROTOCOL_ID = 11, + // Leave this last! + NUMBER_OF_WEAPONS +} WeaponID_t; + +typedef enum +{ + EDGE, + PROJECTILE, + EXPLOSION, + ENERGY +} WeaponType_t; + +typedef struct +{ + WeaponID_t ID; + WeaponType_t Type; + Protocol_T Protocol; + uint16_t Damage_Per_Shot; + uint16_t Shots_Per_Reload; + + uint32_t Delay_Before_Sending_Shot_in_ms; + //! This is the inverse of "Rate of Fire". + uint32_t Time_Between_Shots_in_ms; + uint32_t Time_To_Reload_in_ms; + +} Weapon_t; + +extern Weapon_t WeaponsByID[NUMBER_OF_WEAPONS]; + +__attribute__((always_inline)) inline Weapon_t GetWeaponFromID(WeaponID_t id) +{ + Weapon_t result = WeaponsByID[TEST_PATTERN_ID]; + + if (id < NUMBER_OF_WEAPONS) + { + result = WeaponsByID[id]; + } + + return result; +} + +#endif // WEAPON_H + diff --git a/IR/IR_HW_Interface.h b/IR/IR_HW_Interface.h new file mode 100644 index 0000000..4c4e6f9 --- /dev/null +++ b/IR/IR_HW_Interface.h @@ -0,0 +1,28 @@ +/* + * 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 . + */ + +#ifndef IR_HW_INTERFACE_H +#define IR_HW_INTERFACE_H + +SystemKResult_T Prepare_Tag(void); +SystemKResult_T Send_Tag(void); + +#endif // IR_HW_INTERFACE_H diff --git a/Kconfig b/Kconfig new file mode 100644 index 0000000..ee22b03 --- /dev/null +++ b/Kconfig @@ -0,0 +1,38 @@ +menu "KTag SystemK" + + config KTAG_N_NEOPIXEL_CHANNELS + int "Number of NeoPixel channels" + range 1 4 + default 4 + help + Only values of '1' or '4' are supported. + + config KTAG_MAX_NEOPIXELS_PER_CHANNEL + int "Maximum Neopixels per channel" + default 8 + help + Maximum number of NeoPixels on a single channel. + + config KTAG_ANIMATION_STEP_TIME_IN_ms + int "Time between animation updates (in milliseconds)" + default 10 + + config KTAG_T_DEFAULT_START_GAME_in_ms + int "Default time between game initiation and game start (in milliseconds)" + default 30000 + + config KTAG_MAX_AUDIO_VOLUME + int "Maximum audio volume" + range 1 100 + default 100 + help + Value of audio volume representing the maximum volume possible for this device. + + config KTAG_MIN_AUDIO_VOLUME + int "Minimum audio volume" + range 0 99 + default 0 + help + Value of audio volume representing the minimum volume possible for this device. + +endmenu \ No newline at end of file diff --git a/LICENSE b/LICENSE index e5e2cef..a43bcf7 100644 --- a/LICENSE +++ b/LICENSE @@ -219,8 +219,11 @@ If you develop a new program, and you want it to be of the greatest possible use To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. - SystemK - Copyright (C) 2025 Software + 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. diff --git a/Logging/KLog.c b/Logging/KLog.c new file mode 100755 index 0000000..4c63a6b --- /dev/null +++ b/Logging/KLog.c @@ -0,0 +1,29 @@ + +/* + * 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" + +#ifdef PSOC_PLATFORM +char8 KLog_Buffer[MAX_KLOG_STRING_LENGTH]; +#endif // PSOC_PLATFORM diff --git a/Logging/KLog.h b/Logging/KLog.h new file mode 100755 index 0000000..6629594 --- /dev/null +++ b/Logging/KLog.h @@ -0,0 +1,95 @@ + +/* + * 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 . + */ + +#ifdef ESP_PLATFORM + +#include + +#define KLOG_ERROR(tag, format, ...) ESP_LOGE(tag, format, ##__VA_ARGS__) +#define KLOG_WARN(tag, format, ...) ESP_LOGW(tag, format, ##__VA_ARGS__) +#define KLOG_INFO(tag, format, ...) ESP_LOGI(tag, format, ##__VA_ARGS__) +#define KLOG_DEBUG(tag, format, ...) ESP_LOGD(tag, format, ##__VA_ARGS__) +#define KLOG_TRACE(tag, format, ...) ESP_LOGV(tag, format, ##__VA_ARGS__) + +#endif // ESP_PLATFORM + +#ifdef PSOC_PLATFORM + +/* Include PSoC system and component APIs and defines */ +#include +#include + +#define MAX_KLOG_STRING_LENGTH (255) +extern char8 KLog_Buffer[MAX_KLOG_STRING_LENGTH]; + +#define KLOG_ERROR(tag, format, ...) \ + do \ + { \ + UART_Console_PutString("ERROR ["); \ + UART_Console_PutString(tag); \ + UART_Console_PutString("] "); \ + sprintf((char8 *)KLog_Buffer, format, ##__VA_ARGS__); \ + UART_Console_PutString((const char8 *)KLog_Buffer); \ + UART_Console_PutString("\n"); \ + } while (false) +#define KLOG_WARN(tag, format, ...) \ + do \ + { \ + UART_Console_PutString("WARN ["); \ + UART_Console_PutString(tag); \ + UART_Console_PutString("] "); \ + sprintf((char8 *)KLog_Buffer, format, ##__VA_ARGS__); \ + UART_Console_PutString((const char8 *)KLog_Buffer); \ + UART_Console_PutString("\n"); \ + } while (false) +#define KLOG_INFO(tag, format, ...) \ + do \ + { \ + UART_Console_PutString("INFO ["); \ + UART_Console_PutString(tag); \ + UART_Console_PutString("] "); \ + sprintf((char8 *)KLog_Buffer, format, ##__VA_ARGS__); \ + UART_Console_PutString((const char8 *)KLog_Buffer); \ + UART_Console_PutString("\n"); \ + } while (false) +#define KLOG_DEBUG(tag, format, ...) \ + do \ + { \ + UART_Console_PutString("DEBUG ["); \ + UART_Console_PutString(tag); \ + UART_Console_PutString("] "); \ + sprintf((char8 *)KLog_Buffer, format, ##__VA_ARGS__); \ + UART_Console_PutString((const char8 *)KLog_Buffer); \ + UART_Console_PutString("\n"); \ + } while (false) +#define KLOG_TRACE(tag, format, ...) \ + do \ + { \ + UART_Console_PutString("TRACE ["); \ + UART_Console_PutString(tag); \ + UART_Console_PutString("] "); \ + sprintf((char8 *)KLog_Buffer, format, ##__VA_ARGS__); \ + UART_Console_PutString((const char8 *)KLog_Buffer); \ + UART_Console_PutString("\n"); \ + } while (false) + +#endif // PSOC_PLATFORM diff --git a/Menu/GameSettings/GameMenuItem.c b/Menu/GameSettings/GameMenuItem.c new file mode 100644 index 0000000..9b6009d --- /dev/null +++ b/Menu/GameSettings/GameMenuItem.c @@ -0,0 +1,104 @@ +/* + * 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 "SystemK.h" + +static uint8_t SubmenuIndex = 0; +static MenuItem_T const * const Submenus[] = +{ + &TeamIDMenuItem, + &PlayerIDMenuItem +}; +static const uint8_t N_SUBMENUS = (sizeof(Submenus) / sizeof(MenuItem_T *)); + +static const char * KLOG_TAG = "Menu | Game Settings"; + +static void OnFocus(bool IncludeDetails); +static MenuItem_T const * OnSelect(); +static void OnIncrement(); +static void OnDecrement(); + +const MenuItem_T GameMenuItem = +{ + .OnFocus = OnFocus, + .OnSelect = OnSelect, + .OnIncrement = OnIncrement, + .OnDecrement = OnDecrement + +}; + +static void OnFocus(bool IncludeDetails) +{ + AudioAction_T audio_action = {.ID = AUDIO_PLAY_GAME_SETTINGS_PROMPT, .Data = (void *)0x00}; + Perform_Audio_Action(&audio_action); + + KLOG_INFO(KLOG_TAG, "<--"); + + if (IncludeDetails == true) + { + if (Submenus[SubmenuIndex]->OnFocus != NULL) + { + Submenus[SubmenuIndex]->OnFocus(false); + } + } +} + +static MenuItem_T const * OnSelect() +{ + return Submenus[SubmenuIndex]; +} + +static void OnIncrement() +{ + if (SubmenuIndex < (N_SUBMENUS -1)) + { + SubmenuIndex++; + } + else + { + // Wrap around. + SubmenuIndex = 0; + } + + if (Submenus[SubmenuIndex]->OnFocus != NULL) + { + Submenus[SubmenuIndex]->OnFocus(false); + } +} + +static void OnDecrement() +{ + if (SubmenuIndex > 0) + { + SubmenuIndex--; + } + else + { + // Wrap around. + SubmenuIndex = (N_SUBMENUS -1); + } + + if (Submenus[SubmenuIndex]->OnFocus != NULL) + { + Submenus[SubmenuIndex]->OnFocus(false); + } +} diff --git a/Menu/GameSettings/GameMenuItem.h b/Menu/GameSettings/GameMenuItem.h new file mode 100644 index 0000000..cc4e84d --- /dev/null +++ b/Menu/GameSettings/GameMenuItem.h @@ -0,0 +1,27 @@ +/* + * 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 . + */ + +#ifndef GAMEMENUITEM_H +#define GAMEMENUITEM_H + +extern const MenuItem_T GameMenuItem; + +#endif // GAMEMENUITEM_H diff --git a/Menu/GameSettings/PlayerIDMenuItem.c b/Menu/GameSettings/PlayerIDMenuItem.c new file mode 100644 index 0000000..fb7bcfe --- /dev/null +++ b/Menu/GameSettings/PlayerIDMenuItem.c @@ -0,0 +1,104 @@ +/* + * 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 "SystemK.h" + +#define MIN_PLAYER_ID 0b0000000 +#define MAX_PLAYER_ID 0b1111111 + +static uint8_t Player_ID; + +static void OnFocus(bool IncludeDetails); +static MenuItem_T const * OnSelect(); +static void OnIncrement(); +static void OnDecrement(); + +const MenuItem_T PlayerIDMenuItem = +{ + .OnFocus = OnFocus, + .OnSelect = OnSelect, + .OnIncrement = OnIncrement, + .OnDecrement = OnDecrement + +}; + +static void OnFocus(bool IncludeDetails) +{ + AudioAction_T audio_action = {.ID = AUDIO_PLAY_PLAYER_ID_PROMPT, .Play_To_Completion = true, .Data = (void *)0x00}; + Perform_Audio_Action(&audio_action); + + if (IncludeDetails == true) + { + (void) SETTINGS_get_uint8_t(SYSTEMK_SETTING_PLAYERID, &Player_ID); + AudioAction_T volume_action = {.ID = AUDIO_PRONOUNCE_NUMBER_0_TO_100, .Play_To_Completion = true, .Data = (void *)&Player_ID}; + Perform_Audio_Action(&volume_action); + } +} + +static MenuItem_T const * OnSelect() +{ + AudioAction_T audio_action = {.ID = AUDIO_PLAY_SELECTION_INDICATOR, .Data = (void *)0x00}; + Perform_Audio_Action(&audio_action); + return NULL; +} + +static void OnIncrement() +{ + (void) SETTINGS_get_uint8_t(SYSTEMK_SETTING_PLAYERID, &Player_ID); + + if (Player_ID < MAX_PLAYER_ID) + { + Player_ID++; + } + else + { + Player_ID = MAX_PLAYER_ID; + } + (void) SETTINGS_set_uint8_t(SYSTEMK_SETTING_PLAYERID, Player_ID); + + AudioAction_T audio_action = {.ID = AUDIO_PLAY_PLAYER_ID_PROMPT, .Play_To_Completion = true, .Data = (void *)0x00}; + Perform_Audio_Action(&audio_action); + + AudioAction_T volume_action = {.ID = AUDIO_PRONOUNCE_NUMBER_0_TO_100, .Play_To_Completion = true, .Data = (void *)&Player_ID}; + Perform_Audio_Action(&volume_action); +} + +static void OnDecrement() +{ + (void) SETTINGS_get_uint8_t(SYSTEMK_SETTING_PLAYERID, &Player_ID); + + if (Player_ID > MIN_PLAYER_ID) + { + Player_ID--; + } + else + { + Player_ID = MIN_PLAYER_ID; + } + (void) SETTINGS_set_uint8_t(SYSTEMK_SETTING_PLAYERID, Player_ID); + + AudioAction_T audio_action = {.ID = AUDIO_PLAY_PLAYER_ID_PROMPT, .Play_To_Completion = true, .Data = (void *)0x00}; + Perform_Audio_Action(&audio_action); + + AudioAction_T volume_action = {.ID = AUDIO_PRONOUNCE_NUMBER_0_TO_100, .Play_To_Completion = true, .Data = (void *)&Player_ID}; + Perform_Audio_Action(&volume_action); +} \ No newline at end of file diff --git a/Menu/GameSettings/PlayerIDMenuItem.h b/Menu/GameSettings/PlayerIDMenuItem.h new file mode 100644 index 0000000..33c3242 --- /dev/null +++ b/Menu/GameSettings/PlayerIDMenuItem.h @@ -0,0 +1,27 @@ +/* + * 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 . + */ + +#ifndef PLAYERIDMENUITEM_H +#define PLAYERIDMENUITEM_H + +extern const MenuItem_T PlayerIDMenuItem; + +#endif // PLAYERIDMENUITEM_H diff --git a/Menu/GameSettings/TeamIDMenuItem.c b/Menu/GameSettings/TeamIDMenuItem.c new file mode 100644 index 0000000..14d97bb --- /dev/null +++ b/Menu/GameSettings/TeamIDMenuItem.c @@ -0,0 +1,104 @@ +/* + * 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 "SystemK.h" + +#define MIN_TEAM_ID 0 +#define MAX_TEAM_ID 3 + +static uint8_t Team_ID; + +static void OnFocus(bool IncludeDetails); +static MenuItem_T const * OnSelect(); +static void OnIncrement(); +static void OnDecrement(); + +const MenuItem_T TeamIDMenuItem = +{ + .OnFocus = OnFocus, + .OnSelect = OnSelect, + .OnIncrement = OnIncrement, + .OnDecrement = OnDecrement + +}; + +static void OnFocus(bool IncludeDetails) +{ + AudioAction_T audio_action = {.ID = AUDIO_PLAY_TEAM_ID_PROMPT, .Play_To_Completion = true, .Data = (void *)0x00}; + Perform_Audio_Action(&audio_action); + + if (IncludeDetails == true) + { + (void) SETTINGS_get_uint8_t(SYSTEMK_SETTING_TEAMID, &Team_ID); + AudioAction_T volume_action = {.ID = AUDIO_PRONOUNCE_NUMBER_0_TO_100, .Play_To_Completion = true, .Data = (void *)&Team_ID}; + Perform_Audio_Action(&volume_action); + } +} + +static MenuItem_T const * OnSelect() +{ + AudioAction_T audio_action = {.ID = AUDIO_PLAY_SELECTION_INDICATOR, .Data = (void *)0x00}; + Perform_Audio_Action(&audio_action); + return NULL; +} + +static void OnIncrement() +{ + (void) SETTINGS_get_uint8_t(SYSTEMK_SETTING_TEAMID, &Team_ID); + + if (Team_ID < MAX_TEAM_ID) + { + Team_ID++; + } + else + { + Team_ID = MAX_TEAM_ID; + } + (void) SETTINGS_set_uint8_t(SYSTEMK_SETTING_TEAMID, Team_ID); + + AudioAction_T audio_action = {.ID = AUDIO_PLAY_TEAM_ID_PROMPT, .Play_To_Completion = true, .Data = (void *)0x00}; + Perform_Audio_Action(&audio_action); + + AudioAction_T volume_action = {.ID = AUDIO_PRONOUNCE_NUMBER_0_TO_100, .Play_To_Completion = true, .Data = (void *)&Team_ID}; + Perform_Audio_Action(&volume_action); +} + +static void OnDecrement() +{ + (void) SETTINGS_get_uint8_t(SYSTEMK_SETTING_TEAMID, &Team_ID); + + if (Team_ID > MIN_TEAM_ID) + { + Team_ID--; + } + else + { + Team_ID = MIN_TEAM_ID; + } + (void) SETTINGS_set_uint8_t(SYSTEMK_SETTING_TEAMID, Team_ID); + + AudioAction_T audio_action = {.ID = AUDIO_PLAY_TEAM_ID_PROMPT, .Play_To_Completion = true, .Data = (void *)0x00}; + Perform_Audio_Action(&audio_action); + + AudioAction_T volume_action = {.ID = AUDIO_PRONOUNCE_NUMBER_0_TO_100, .Play_To_Completion = true, .Data = (void *)&Team_ID}; + Perform_Audio_Action(&volume_action); +} \ No newline at end of file diff --git a/Menu/GameSettings/TeamIDMenuItem.h b/Menu/GameSettings/TeamIDMenuItem.h new file mode 100644 index 0000000..617f59c --- /dev/null +++ b/Menu/GameSettings/TeamIDMenuItem.h @@ -0,0 +1,27 @@ +/* + * 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 . + */ + +#ifndef TEAMIDMENUITEM_H +#define TEAMIDMENUITEM_H + +extern const MenuItem_T TeamIDMenuItem; + +#endif // TEAMIDMENUITEM_H diff --git a/Menu/HardwareSettings/HandedMenuItem.c b/Menu/HardwareSettings/HandedMenuItem.c new file mode 100644 index 0000000..8c4e232 --- /dev/null +++ b/Menu/HardwareSettings/HandedMenuItem.c @@ -0,0 +1,93 @@ +/* + * 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 "SystemK.h" + +static uint8_t Is_Right_Handed; + +static void OnFocus(bool IncludeDetails); +static MenuItem_T const * OnSelect(); +static void OnIncrement(); +static void OnDecrement(); +static void ToggleHanded(); + +const MenuItem_T HandedMenuItem = +{ + .OnFocus = OnFocus, + .OnSelect = OnSelect, + .OnIncrement = OnIncrement, + .OnDecrement = OnDecrement + +}; + +static void OnFocus(bool IncludeDetails) +{ + (void) SETTINGS_get_uint8_t(SYSTEMK_SETTING_IS_RIGHT_HANDED, &Is_Right_Handed); + if (Is_Right_Handed == true) + { + AudioAction_T audio_action = {.ID = AUDIO_PLAY_RIGHT_HANDED, .Play_To_Completion = true, .Data = (void *)0x00}; + Perform_Audio_Action(&audio_action); + } + else + { + AudioAction_T audio_action = {.ID = AUDIO_PLAY_LEFT_HANDED, .Play_To_Completion = true, .Data = (void *)0x00}; + Perform_Audio_Action(&audio_action);; + } +} + +static MenuItem_T const * OnSelect() +{ + AudioAction_T audio_action = {.ID = AUDIO_PLAY_SELECTION_INDICATOR, .Data = (void *)0x00}; + Perform_Audio_Action(&audio_action); + return NULL; +} + +static void OnIncrement() +{ + ToggleHanded(); +} + +static void OnDecrement() +{ + ToggleHanded(); +} + +static void ToggleHanded() +{ + uint8_t Is_Right_Handed; + (void) SETTINGS_get_uint8_t(SYSTEMK_SETTING_IS_RIGHT_HANDED, &Is_Right_Handed); + + if (Is_Right_Handed == true) + { + Is_Right_Handed = false; + (void) SETTINGS_set_uint8_t(SYSTEMK_SETTING_IS_RIGHT_HANDED, Is_Right_Handed); + AudioAction_T audio_action = {.ID = AUDIO_PLAY_LEFT_HANDED, .Play_To_Completion = true, .Data = (void *)0x00}; + Perform_Audio_Action(&audio_action); + } + else + { + Is_Right_Handed = true; + (void) SETTINGS_set_uint8_t(SYSTEMK_SETTING_IS_RIGHT_HANDED, Is_Right_Handed); + AudioAction_T audio_action = {.ID = AUDIO_PLAY_RIGHT_HANDED, .Play_To_Completion = true, .Data = (void *)0x00}; + Perform_Audio_Action(&audio_action);; + } +} diff --git a/Menu/HardwareSettings/HandedMenuItem.h b/Menu/HardwareSettings/HandedMenuItem.h new file mode 100644 index 0000000..e67b805 --- /dev/null +++ b/Menu/HardwareSettings/HandedMenuItem.h @@ -0,0 +1,27 @@ +/* + * 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 . + */ + +#ifndef HANDEDMENUITEM_H +#define HANDEDMENUITEM_H + +extern const MenuItem_T HandedMenuItem; + +#endif // HANDEDMENUITEM_H diff --git a/Menu/HardwareSettings/HardwareMenuItem.c b/Menu/HardwareSettings/HardwareMenuItem.c new file mode 100644 index 0000000..1f3204a --- /dev/null +++ b/Menu/HardwareSettings/HardwareMenuItem.c @@ -0,0 +1,104 @@ +/* + * 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 "SystemK.h" + +static uint8_t SubmenuIndex = 0; +static MenuItem_T const * const Submenus[] = +{ + &VolumeMenuItem, + &HandedMenuItem +}; +static const uint8_t N_SUBMENUS = (sizeof(Submenus) / sizeof(MenuItem_T *)); + +static const char * KLOG_TAG = "Menu | Hardware Settings"; + +static void OnFocus(bool IncludeDetails); +static MenuItem_T const * OnSelect(); +static void OnIncrement(); +static void OnDecrement(); + +const MenuItem_T HardwareMenuItem = +{ + .OnFocus = OnFocus, + .OnSelect = OnSelect, + .OnIncrement = OnIncrement, + .OnDecrement = OnDecrement + +}; + +static void OnFocus(bool IncludeDetails) +{ + AudioAction_T audio_action = {.ID = AUDIO_PLAY_HARDWARE_SETTINGS_PROMPT, .Play_To_Completion = true, .Data = (void *)0x00}; + Perform_Audio_Action(&audio_action); + + KLOG_INFO(KLOG_TAG, "<--"); + + if (IncludeDetails == true) + { + if (Submenus[SubmenuIndex]->OnFocus != NULL) + { + Submenus[SubmenuIndex]->OnFocus(false); + } + } +} + +static MenuItem_T const * OnSelect() +{ + return Submenus[SubmenuIndex]; +} + +static void OnIncrement() +{ + if (SubmenuIndex < (N_SUBMENUS -1)) + { + SubmenuIndex++; + } + else + { + // Wrap around. + SubmenuIndex = 0; + } + + if (Submenus[SubmenuIndex]->OnFocus != NULL) + { + Submenus[SubmenuIndex]->OnFocus(false); + } +} + +static void OnDecrement() +{ + if (SubmenuIndex > 0) + { + SubmenuIndex--; + } + else + { + // Wrap around. + SubmenuIndex = (N_SUBMENUS -1); + } + + if (Submenus[SubmenuIndex]->OnFocus != NULL) + { + Submenus[SubmenuIndex]->OnFocus(false); + } +} \ No newline at end of file diff --git a/Menu/HardwareSettings/HardwareMenuItem.h b/Menu/HardwareSettings/HardwareMenuItem.h new file mode 100644 index 0000000..b7d5e98 --- /dev/null +++ b/Menu/HardwareSettings/HardwareMenuItem.h @@ -0,0 +1,27 @@ +/* + * 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 . + */ + +#ifndef HARDWAREMENUITEM_H +#define HARDWAREMENUITEM_H + +extern const MenuItem_T HardwareMenuItem; + +#endif // HARDWAREMENUITEM_H diff --git a/Menu/HardwareSettings/VolumeMenuItem.c b/Menu/HardwareSettings/VolumeMenuItem.c new file mode 100644 index 0000000..330d4e8 --- /dev/null +++ b/Menu/HardwareSettings/VolumeMenuItem.c @@ -0,0 +1,116 @@ +/* + * 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 "SystemK.h" + +#define MAX_VOLUME (CONFIG_KTAG_MAX_AUDIO_VOLUME) +#if (MAX_VOLUME > 100) + #error "CONFIG_KTAG_MAX_AUDIO_VOLUME is too large. Volume has to be between 0 and 100." +#endif +#define MIN_VOLUME (CONFIG_KTAG_MIN_AUDIO_VOLUME) +#if (MAX_VOLUME < MIN_VOLUME) + #error "CONFIG_KTAG_MAX_AUDIO_VOLUME is less than CONFIG_KTAG_MIN_AUDIO_VOLUME. Something is not quite right." +#endif + +static uint8_t Volume; +static const char * KLOG_TAG = "Menu | Volume"; + +static void OnFocus(bool IncludeDetails); +static MenuItem_T const * OnSelect(); +static void OnIncrement(); +static void OnDecrement(); + +const MenuItem_T VolumeMenuItem = +{ + .OnFocus = OnFocus, + .OnSelect = OnSelect, + .OnIncrement = OnIncrement, + .OnDecrement = OnDecrement + +}; + +static void OnFocus(bool IncludeDetails) +{ + (void) SETTINGS_get_uint8_t(SYSTEMK_SETTING_AUDIO_VOLUME, &Volume); + + AudioAction_T audio_action = {.ID = AUDIO_PLAY_VOLUME_PROMPT, .Play_To_Completion = true, .Data = (void *)0x00}; + Perform_Audio_Action(&audio_action); + + if (IncludeDetails == true) + { + AudioAction_T volume_action = {.ID = AUDIO_PRONOUNCE_NUMBER_0_TO_100, .Play_To_Completion = true, .Data = (void *)&Volume}; + Perform_Audio_Action(&volume_action); + } +} + +static MenuItem_T const * OnSelect() +{ + AudioAction_T audio_action = {.ID = AUDIO_PLAY_SELECTION_INDICATOR, .Data = (void *)0x00}; + Perform_Audio_Action(&audio_action); + return NULL; +} + +static void OnIncrement() +{ + (void) SETTINGS_get_uint8_t(SYSTEMK_SETTING_AUDIO_VOLUME, &Volume); + + if (Volume < MAX_VOLUME) + { + Volume++; + } + + KLOG_INFO(KLOG_TAG, "Volume incremented--is now %u. (Max is %u.)", Volume, MAX_VOLUME); + + (void) SETTINGS_set_uint8_t(SYSTEMK_SETTING_AUDIO_VOLUME, Volume); + + AudioAction_T set_volume_action = {.ID = AUDIO_SET_VOLUME, .Data = (void *)&Volume}; + Perform_Audio_Action(&set_volume_action); + + AudioAction_T audio_action = {.ID = AUDIO_PLAY_VOLUME_PROMPT, .Play_To_Completion = true, .Data = (void *)0x00}; + Perform_Audio_Action(&audio_action); + + AudioAction_T volume_action = {.ID = AUDIO_PRONOUNCE_NUMBER_0_TO_100, .Play_To_Completion = true, .Data = (void *)&Volume}; + Perform_Audio_Action(&volume_action); +} + +static void OnDecrement() +{ + (void) SETTINGS_get_uint8_t(SYSTEMK_SETTING_AUDIO_VOLUME, &Volume); + + if (Volume > MIN_VOLUME) + { + Volume--; + } + + KLOG_INFO(KLOG_TAG, "Volume decremented--is now %u. (Min is %u.)", Volume, MIN_VOLUME); + + (void) SETTINGS_set_uint8_t(SYSTEMK_SETTING_AUDIO_VOLUME, Volume); + + AudioAction_T set_volume_action = {.ID = AUDIO_SET_VOLUME, .Data = (void *)&Volume}; + Perform_Audio_Action(&set_volume_action); + + AudioAction_T audio_action = {.ID = AUDIO_PLAY_VOLUME_PROMPT, .Play_To_Completion = true, .Data = (void *)0x00}; + Perform_Audio_Action(&audio_action); + + AudioAction_T volume_action = {.ID = AUDIO_PRONOUNCE_NUMBER_0_TO_100, .Play_To_Completion = true, .Data = (void *)&Volume}; + Perform_Audio_Action(&volume_action); +} \ No newline at end of file diff --git a/Menu/HardwareSettings/VolumeMenuItem.h b/Menu/HardwareSettings/VolumeMenuItem.h new file mode 100644 index 0000000..c8d5941 --- /dev/null +++ b/Menu/HardwareSettings/VolumeMenuItem.h @@ -0,0 +1,27 @@ +/* + * 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 . + */ + +#ifndef VOLUMEMENUITEM_H +#define VOLUMEMENUITEM_H + +extern const MenuItem_T VolumeMenuItem; + +#endif // VOLUMEMENUITEM_H diff --git a/Menu/Menu.c b/Menu/Menu.c new file mode 100644 index 0000000..47ecfa0 --- /dev/null +++ b/Menu/Menu.c @@ -0,0 +1,107 @@ +/* + * 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 "SystemK.h" + +static uint8_t SubmenuIndex = 0; +static MenuItem_T const * const Submenus[] = +{ + &GameMenuItem, + &HardwareMenuItem +}; +static const uint8_t N_SUBMENUS = (sizeof(Submenus) / sizeof(MenuItem_T *)); + +static void RootMenuOnFocus(bool IncludeDetails); +static MenuItem_T const * RootMenuOnSelect(); +static void RootMenuOnIncrement(); +static void RootMenuOnDecrement(); + +static const MenuItem_T Root_Menu_Item = +{ + .OnFocus = RootMenuOnFocus, + .OnSelect = RootMenuOnSelect, + .OnIncrement = RootMenuOnIncrement, + .OnDecrement = RootMenuOnDecrement + +}; + +MenuItem_T const * const RootMenu = &Root_Menu_Item; + +static void RootMenuOnFocus(bool IncludeDetails) +{ + AudioAction_T audio_action = {.ID = AUDIO_PLAY_MENU_PROMPT, .Play_To_Completion = true, .Data = (void *)0x00}; + Perform_Audio_Action(&audio_action); + + if (IncludeDetails == true) + { + if (Submenus[SubmenuIndex]->OnFocus != NULL) + { + Submenus[SubmenuIndex]->OnFocus(false); + } + } +} + +static MenuItem_T const * RootMenuOnSelect() +{ + if (Submenus[SubmenuIndex]->OnSelect != NULL) + { + Submenus[SubmenuIndex]->OnSelect(); + } + + return Submenus[SubmenuIndex]; +} + +static void RootMenuOnIncrement() +{ + if (SubmenuIndex < (N_SUBMENUS -1)) + { + SubmenuIndex++; + } + else + { + // Wrap around. + SubmenuIndex = 0; + } + + if (Submenus[SubmenuIndex]->OnFocus != NULL) + { + Submenus[SubmenuIndex]->OnFocus(false); + } +} + +static void RootMenuOnDecrement() +{ + if (SubmenuIndex > 0) + { + SubmenuIndex--; + } + else + { + // Wrap around. + SubmenuIndex = (N_SUBMENUS -1); + } + + if (Submenus[SubmenuIndex]->OnFocus != NULL) + { + Submenus[SubmenuIndex]->OnFocus(false); + } +} \ No newline at end of file diff --git a/Menu/Menu.h b/Menu/Menu.h new file mode 100644 index 0000000..95de2a5 --- /dev/null +++ b/Menu/Menu.h @@ -0,0 +1,49 @@ +/* + * 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 . + */ + +#ifndef MENU_H +#define MENU_H + +#include +#include + + +typedef struct MenuItem_S +{ + // Performs the actions required when this MenuItem receives focus. + void (*OnFocus)(bool IncludeDetails); + // Performs the actions required when this MenuItem receives focus. + struct MenuItem_S const * (*OnSelect)(void); + void (*OnIncrement)(void); + void (*OnDecrement)(void); + +} MenuItem_T; + +extern MenuItem_T const * const RootMenu; + +#include "GameSettings/GameMenuItem.h" +#include "GameSettings/PlayerIDMenuItem.h" +#include "GameSettings/TeamIDMenuItem.h" +#include "HardwareSettings/HardwareMenuItem.h" +#include "HardwareSettings/VolumeMenuItem.h" +#include "HardwareSettings/HandedMenuItem.h" + +#endif // MENU_H diff --git a/NeoPixels/Animation.h b/NeoPixels/Animation.h new file mode 100644 index 0000000..fb8b300 --- /dev/null +++ b/NeoPixels/Animation.h @@ -0,0 +1,39 @@ + +/* + * 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 . + */ + +#ifndef ANIMATION_H +#define ANIMATION_H + +typedef enum +{ + ANIMATION_ONGOING, + ANIMATION_COMPLETE +} AnimationStepResult_T; + +typedef struct +{ + void (*Reset)(void * Data); + AnimationStepResult_T (*NextStep)(void); +} Animation_T; + + +#endif // ANIMATION_H diff --git a/NeoPixels/Animations/All_Off.c b/NeoPixels/Animations/All_Off.c new file mode 100644 index 0000000..ee23d1a --- /dev/null +++ b/NeoPixels/Animations/All_Off.c @@ -0,0 +1,40 @@ + +/* + * 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 "SystemK.h" + +static void Reset(void * Data) +{ +} + +static AnimationStepResult_T NextStep(void) +{ + NeoPixels_Set_Color_Range_On_All_Channels(0, CONFIG_KTAG_MAX_NEOPIXELS_PER_CHANNEL - 1, COLOR_BLACK); + + return ANIMATION_COMPLETE; +} + +Animation_T All_Off_Animation = +{ + .Reset = Reset, + .NextStep = NextStep +}; diff --git a/NeoPixels/Animations/All_Off.h b/NeoPixels/Animations/All_Off.h new file mode 100644 index 0000000..2e59d6e --- /dev/null +++ b/NeoPixels/Animations/All_Off.h @@ -0,0 +1,28 @@ + +/* + * 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 . + */ + +#ifndef ALL_OFF_H +#define ALL_OFF_H + +extern Animation_T All_Off_Animation; + +#endif // ALL_OFF_H diff --git a/NeoPixels/Animations/All_On.c b/NeoPixels/Animations/All_On.c new file mode 100644 index 0000000..9d09a31 --- /dev/null +++ b/NeoPixels/Animations/All_On.c @@ -0,0 +1,108 @@ +/* + * 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 "SystemK.h" + +static color_t All_On_Color = COLOR_WHITE; +static DisplayStyle_T All_On_Style = DISPLAY_STYLE_DEFAULT; +static uint32_t Time_in_Animation_in_ms = 0; + +static void Reset(void *Data) +{ + All_On_Color = ((All_On_Data_T *)Data)->color; + All_On_Style = ((All_On_Data_T *)Data)->style; + Time_in_Animation_in_ms = 0; +} + +static AnimationStepResult_T NextStep(void) +{ + if (All_On_Style == DISPLAY_STYLE_ALTERNATE) + { + if ((Time_in_Animation_in_ms % 500) < 250) + { +#if (CONFIG_KTAG_N_NEOPIXEL_CHANNELS == 1) + Set_Barrel_Flash(All_On_Color); + Set_Heart(COLOR_BLACK); + Set_Square(All_On_Color); + Set_Circle(COLOR_BLACK); + Set_Arrow(All_On_Color); +#elif (CONFIG_KTAG_N_NEOPIXEL_CHANNELS == 4) + for (uint_fast8_t pixel = 0; pixel < CONFIG_KTAG_MAX_NEOPIXELS_PER_CHANNEL; pixel++) + { + if (pixel % 2) + { + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_BARREL, pixel, All_On_Color); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_RECEIVER, pixel, All_On_Color); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_DISPLAY, pixel, All_On_Color); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_EFFECTS, pixel, All_On_Color); + } + else + { + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_BARREL, pixel, COLOR_BLACK); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_RECEIVER, pixel, COLOR_BLACK); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_DISPLAY, pixel, COLOR_BLACK); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_EFFECTS, pixel, COLOR_BLACK); + } + } +#endif // CONFIG_KTAG_N_NEOPIXEL_CHANNELS + } + else + { +#if (CONFIG_KTAG_N_NEOPIXEL_CHANNELS == 1) + Set_Barrel_Flash(COLOR_BLACK); + Set_Heart(All_On_Color); + Set_Square(COLOR_BLACK); + Set_Circle(All_On_Color); + Set_Arrow(COLOR_BLACK); +#elif (CONFIG_KTAG_N_NEOPIXEL_CHANNELS == 4) + for (uint_fast8_t pixel = 0; pixel < CONFIG_KTAG_MAX_NEOPIXELS_PER_CHANNEL; pixel++) + { + if (pixel % 2) + { + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_BARREL, pixel, COLOR_BLACK); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_RECEIVER, pixel, COLOR_BLACK); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_DISPLAY, pixel, COLOR_BLACK); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_EFFECTS, pixel, COLOR_BLACK); + } + else + { + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_BARREL, pixel, All_On_Color); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_RECEIVER, pixel, All_On_Color); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_DISPLAY, pixel, All_On_Color); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_EFFECTS, pixel, All_On_Color); + } + } +#endif // CONFIG_KTAG_N_NEOPIXEL_CHANNELS + } + } + else + { + NeoPixels_Set_Color_Range_On_All_Channels(0, CONFIG_KTAG_MAX_NEOPIXELS_PER_CHANNEL - 1, All_On_Color); + } + + Time_in_Animation_in_ms += CONFIG_KTAG_ANIMATION_STEP_TIME_IN_ms; + return ANIMATION_ONGOING; +} + +Animation_T All_On_Animation = + { + .Reset = Reset, + .NextStep = NextStep}; diff --git a/NeoPixels/Animations/All_On.h b/NeoPixels/Animations/All_On.h new file mode 100644 index 0000000..be670f0 --- /dev/null +++ b/NeoPixels/Animations/All_On.h @@ -0,0 +1,27 @@ +/* + * 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 . + */ + +#ifndef ALL_ON_H +#define ALL_ON_H + +extern Animation_T All_On_Animation; + +#endif // ALL_ON_H diff --git a/NeoPixels/Animations/BLE_Nearby.c b/NeoPixels/Animations/BLE_Nearby.c new file mode 100644 index 0000000..90a93f3 --- /dev/null +++ b/NeoPixels/Animations/BLE_Nearby.c @@ -0,0 +1,87 @@ + +/* + * 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 "SystemK.h" + +static BLENearby_T * RXd_Data; + +static inline uint8_t RSSItoBrightness(int8_t RSSI) +{ + uint8_t brightness = 255; + + if (RSSI < 0) + { + brightness += (2*RSSI); + } + + return brightness; +} + +static void Reset(void * Data) +{ + RXd_Data = (BLENearby_T *)Data; +} + +static AnimationStepResult_T NextStep(void) +{ + AnimationStepResult_T result = ANIMATION_COMPLETE; + +#if (CONFIG_KTAG_N_NEOPIXEL_CHANNELS == 1) + // TODO: Implement the "BLE Nearby" animation for a single NeoPixel channel. +#elif (CONFIG_KTAG_N_NEOPIXEL_CHANNELS == 4) + for (uint_fast8_t slot = 0; slot < CONFIG_KTAG_MAX_NEOPIXELS_PER_CHANNEL; slot++) + { + if (RXd_Data->neighbors[slot].TimeToLive_in_ms > 0) + { + //NeoPixels_Set_Color(NEOPIXEL_CHANNEL_DISPLAY, slot, ApplyMask(RXd_Data->neighbors[slot].Color, RSSItoBrightness(RXd_Data->neighbors[slot].RSSI))); + + // TODO: Set a low brightness, and a blink rate proportional to RSSI. + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_DISPLAY, slot, ApplyMask(RXd_Data->neighbors[slot].Color, 25)); + } + else + { + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_DISPLAY, slot, COLOR_BLACK); + } + } +#endif // CONFIG_KTAG_N_NEOPIXEL_CHANNELS + + for (uint_fast8_t slot = 0; slot < CONFIG_KTAG_MAX_NEOPIXELS_PER_CHANNEL; slot++) + { + if (RXd_Data->neighbors[slot].TimeToLive_in_ms > CONFIG_KTAG_ANIMATION_STEP_TIME_IN_ms) + { + result = ANIMATION_ONGOING; + RXd_Data->neighbors[slot].TimeToLive_in_ms -= CONFIG_KTAG_ANIMATION_STEP_TIME_IN_ms; + } + else + { + RXd_Data->neighbors[slot].TimeToLive_in_ms = 0; + } + } + + return result; +} + +Animation_T BLE_Nearby_Animation = +{ + .Reset = Reset, + .NextStep = NextStep +}; diff --git a/NeoPixels/Animations/BLE_Nearby.h b/NeoPixels/Animations/BLE_Nearby.h new file mode 100644 index 0000000..ddee90b --- /dev/null +++ b/NeoPixels/Animations/BLE_Nearby.h @@ -0,0 +1,42 @@ + +/* + * 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 . + */ + +#ifndef BLE_NEARBY_H +#define BLE_NEARBY_H + +typedef struct +{ + uint8_t BD_ADDR[6]; + int8_t RSSI; + color_t Color; + uint16_t TimeToLive_in_ms; + +} BLENeighbor_T; + +typedef struct +{ + BLENeighbor_T neighbors[CONFIG_KTAG_MAX_NEOPIXELS_PER_CHANNEL]; +} BLENearby_T; + +extern Animation_T BLE_Nearby_Animation; + +#endif // BLE_NEARBY_H diff --git a/NeoPixels/Animations/BLE_RSSI.c b/NeoPixels/Animations/BLE_RSSI.c new file mode 100644 index 0000000..faef9de --- /dev/null +++ b/NeoPixels/Animations/BLE_RSSI.c @@ -0,0 +1,53 @@ + +/* + * 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 "SystemK.h" + +static color_t RSSSIColor = (color_t) 0x00000000; +static uint8_t Brightness = 255; + +static void Reset(void * Data) +{ + RSSSIColor = *((color_t *)Data); + Brightness = 255 + *((int8_t *)Data) + *((int8_t *)Data); +} + +static AnimationStepResult_T NextStep(void) +{ +#if (CONFIG_KTAG_N_NEOPIXEL_CHANNELS == 1) + Set_Barrel_Flash(Color(0x00, 0x00, 0x00, 0x00)); + Set_Heart(ApplyMask(RSSSIColor, Brightness)); + Set_Square(ApplyMask(RSSSIColor, Brightness)); + Set_Circle(ApplyMask(RSSSIColor, Brightness)); + Set_Arrow(ApplyMask(RSSSIColor, Brightness)); +#elif (CONFIG_KTAG_N_NEOPIXEL_CHANNELS == 4) + NeoPixels_Set_Color_Range_On_All_Channels(0, CONFIG_KTAG_MAX_NEOPIXELS_PER_CHANNEL-1, ApplyMask(RSSSIColor, Brightness)); +#endif // CONFIG_KTAG_N_NEOPIXEL_CHANNELS + + return ANIMATION_ONGOING; +} + +Animation_T BLE_RSSI_Animation = +{ + .Reset = Reset, + .NextStep = NextStep +}; diff --git a/NeoPixels/Animations/BLE_RSSI.h b/NeoPixels/Animations/BLE_RSSI.h new file mode 100644 index 0000000..4220285 --- /dev/null +++ b/NeoPixels/Animations/BLE_RSSI.h @@ -0,0 +1,28 @@ + +/* + * 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 . + */ + +#ifndef BLE_RSSI_H +#define BLE_RSSI_H + +extern Animation_T BLE_RSSI_Animation; + +#endif // BLE_RSSI_H diff --git a/NeoPixels/Animations/Countdown.c b/NeoPixels/Animations/Countdown.c new file mode 100644 index 0000000..20cf434 --- /dev/null +++ b/NeoPixels/Animations/Countdown.c @@ -0,0 +1,165 @@ + +/* + * 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 "SystemK.h" + +static uint8_t N_Lights; +static color_t My_Color = COLOR_BLACK; + +static const uint8_t BRIGHTNESS = 0x7F; + +static void Reset(void *Data) +{ + N_Lights = *((uint8_t *)Data); + My_Color = HW_NeoPixels_Get_My_Color(); +} + +static AnimationStepResult_T NextStep(void) +{ + switch (N_Lights) + { + case 0: +#if (CONFIG_KTAG_N_NEOPIXEL_CHANNELS == 1) + Set_Barrel_Flash(ApplyMask(My_Color, 0x00)); + Set_Heart(ApplyMask(My_Color, 0x00)); + Set_Square(ApplyMask(My_Color, 0x00)); + Set_Circle(ApplyMask(My_Color, 0x00)); + Set_Arrow(ApplyMask(My_Color, 0x00)); +#elif (CONFIG_KTAG_N_NEOPIXEL_CHANNELS == 4) + NeoPixels_Set_Color_Range(NEOPIXEL_CHANNEL_RECEIVER, 0, CONFIG_KTAG_MAX_NEOPIXELS_PER_CHANNEL - 1, ApplyMask(My_Color, 0x00)); + NeoPixels_Set_Color_Range(NEOPIXEL_CHANNEL_DISPLAY, 0, CONFIG_KTAG_MAX_NEOPIXELS_PER_CHANNEL - 1, ApplyMask(My_Color, 0x00)); + NeoPixels_Set_Color_Range(NEOPIXEL_CHANNEL_EFFECTS, 0, CONFIG_KTAG_MAX_NEOPIXELS_PER_CHANNEL - 1, ApplyMask(My_Color, 0x00)); +#endif // CONFIG_KTAG_N_NEOPIXEL_CHANNELS + break; + + case 1: +#if (CONFIG_KTAG_N_NEOPIXEL_CHANNELS == 1) + Set_Barrel_Flash(ApplyMask(My_Color, 0x00)); + Set_Heart(ApplyMask(My_Color, 0x00)); + Set_Square(ApplyMask(My_Color, 0x00)); + Set_Circle(ApplyMask(My_Color, 0x00)); + Set_Arrow(ApplyMask(My_Color, BRIGHTNESS)); +#elif (CONFIG_KTAG_N_NEOPIXEL_CHANNELS == 4) + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_RECEIVER, RIGHT_RECEIVER_INDICATOR_PIXEL, ApplyMask(My_Color, BRIGHTNESS)); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_RECEIVER, LEFT_RECEIVER_INDICATOR_PIXEL, COLOR_BLACK); + + NeoPixels_Set_Color_Range(NEOPIXEL_CHANNEL_DISPLAY, 0, CONFIG_KTAG_MAX_NEOPIXELS_PER_CHANNEL - 1, ApplyMask(My_Color, 0x00)); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_DISPLAY, 4, ApplyMask(My_Color, BRIGHTNESS)); + + NeoPixels_Set_Color_Range(NEOPIXEL_CHANNEL_EFFECTS, 0, CONFIG_KTAG_MAX_NEOPIXELS_PER_CHANNEL - 1, ApplyMask(My_Color, 0x00)); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_EFFECTS, 4, ApplyMask(My_Color, BRIGHTNESS)); +#endif // CONFIG_KTAG_N_NEOPIXEL_CHANNELS + break; + + case 2: +#if (CONFIG_KTAG_N_NEOPIXEL_CHANNELS == 1) + Set_Barrel_Flash(ApplyMask(My_Color, 0x00)); + Set_Heart(ApplyMask(My_Color, 0x00)); + Set_Square(ApplyMask(My_Color, 0x00)); + Set_Circle(ApplyMask(My_Color, BRIGHTNESS)); + Set_Arrow(ApplyMask(My_Color, BRIGHTNESS)); +#elif (CONFIG_KTAG_N_NEOPIXEL_CHANNELS == 4) + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_RECEIVER, RIGHT_RECEIVER_INDICATOR_PIXEL, COLOR_BLACK); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_RECEIVER, LEFT_RECEIVER_INDICATOR_PIXEL, ApplyMask(My_Color, BRIGHTNESS)); + + NeoPixels_Set_Color_Range(NEOPIXEL_CHANNEL_DISPLAY, 0, CONFIG_KTAG_MAX_NEOPIXELS_PER_CHANNEL - 1, ApplyMask(My_Color, 0x00)); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_DISPLAY, 3, ApplyMask(My_Color, BRIGHTNESS)); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_DISPLAY, 4, ApplyMask(My_Color, BRIGHTNESS)); + + NeoPixels_Set_Color_Range(NEOPIXEL_CHANNEL_EFFECTS, 0, CONFIG_KTAG_MAX_NEOPIXELS_PER_CHANNEL - 1, ApplyMask(My_Color, 0x00)); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_EFFECTS, 3, ApplyMask(My_Color, BRIGHTNESS)); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_EFFECTS, 4, ApplyMask(My_Color, BRIGHTNESS)); +#endif // CONFIG_KTAG_N_NEOPIXEL_CHANNELS + break; + + case 3: +#if (CONFIG_KTAG_N_NEOPIXEL_CHANNELS == 1) + Set_Barrel_Flash(ApplyMask(My_Color, 0x00)); + Set_Heart(ApplyMask(My_Color, 0x00)); + Set_Square(ApplyMask(My_Color, BRIGHTNESS)); + Set_Circle(ApplyMask(My_Color, BRIGHTNESS)); + Set_Arrow(ApplyMask(My_Color, BRIGHTNESS)); +#elif (CONFIG_KTAG_N_NEOPIXEL_CHANNELS == 4) + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_RECEIVER, RIGHT_RECEIVER_INDICATOR_PIXEL, ApplyMask(My_Color, BRIGHTNESS)); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_RECEIVER, LEFT_RECEIVER_INDICATOR_PIXEL, COLOR_BLACK); + + NeoPixels_Set_Color_Range(NEOPIXEL_CHANNEL_DISPLAY, 0, CONFIG_KTAG_MAX_NEOPIXELS_PER_CHANNEL - 1, ApplyMask(My_Color, 0x00)); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_DISPLAY, 2, ApplyMask(My_Color, BRIGHTNESS)); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_DISPLAY, 3, ApplyMask(My_Color, BRIGHTNESS)); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_DISPLAY, 4, ApplyMask(My_Color, BRIGHTNESS)); + + NeoPixels_Set_Color_Range(NEOPIXEL_CHANNEL_EFFECTS, 0, CONFIG_KTAG_MAX_NEOPIXELS_PER_CHANNEL - 1, ApplyMask(My_Color, 0x00)); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_EFFECTS, 2, ApplyMask(My_Color, BRIGHTNESS)); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_EFFECTS, 3, ApplyMask(My_Color, BRIGHTNESS)); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_EFFECTS, 4, ApplyMask(My_Color, BRIGHTNESS)); +#endif // CONFIG_KTAG_N_NEOPIXEL_CHANNELS + break; + + case 4: +#if (CONFIG_KTAG_N_NEOPIXEL_CHANNELS == 1) + Set_Barrel_Flash(ApplyMask(My_Color, 0x00)); + Set_Heart(ApplyMask(My_Color, BRIGHTNESS)); + Set_Square(ApplyMask(My_Color, BRIGHTNESS)); + Set_Circle(ApplyMask(My_Color, BRIGHTNESS)); + Set_Arrow(ApplyMask(My_Color, BRIGHTNESS)); +#elif (CONFIG_KTAG_N_NEOPIXEL_CHANNELS == 4) + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_RECEIVER, RIGHT_RECEIVER_INDICATOR_PIXEL, COLOR_BLACK); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_RECEIVER, LEFT_RECEIVER_INDICATOR_PIXEL, ApplyMask(My_Color, BRIGHTNESS)); + + NeoPixels_Set_Color_Range(NEOPIXEL_CHANNEL_DISPLAY, 0, CONFIG_KTAG_MAX_NEOPIXELS_PER_CHANNEL - 1, ApplyMask(My_Color, 0x00)); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_DISPLAY, 1, ApplyMask(My_Color, BRIGHTNESS)); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_DISPLAY, 2, ApplyMask(My_Color, BRIGHTNESS)); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_DISPLAY, 3, ApplyMask(My_Color, BRIGHTNESS)); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_DISPLAY, 4, ApplyMask(My_Color, BRIGHTNESS)); + + NeoPixels_Set_Color_Range(NEOPIXEL_CHANNEL_EFFECTS, 0, CONFIG_KTAG_MAX_NEOPIXELS_PER_CHANNEL - 1, ApplyMask(My_Color, 0x00)); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_EFFECTS, 1, ApplyMask(My_Color, BRIGHTNESS)); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_EFFECTS, 2, ApplyMask(My_Color, BRIGHTNESS)); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_EFFECTS, 3, ApplyMask(My_Color, BRIGHTNESS)); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_EFFECTS, 4, ApplyMask(My_Color, BRIGHTNESS)); +#endif // CONFIG_KTAG_N_NEOPIXEL_CHANNELS + break; + + case 5: + default: +#if (CONFIG_KTAG_N_NEOPIXEL_CHANNELS == 1) + Set_Barrel_Flash(ApplyMask(My_Color, BRIGHTNESS)); + Set_Heart(ApplyMask(My_Color, BRIGHTNESS)); + Set_Square(ApplyMask(My_Color, BRIGHTNESS)); + Set_Circle(ApplyMask(My_Color, BRIGHTNESS)); + Set_Arrow(ApplyMask(My_Color, BRIGHTNESS)); +#elif (CONFIG_KTAG_N_NEOPIXEL_CHANNELS == 4) + NeoPixels_Set_Color_Range(NEOPIXEL_CHANNEL_RECEIVER, 0, CONFIG_KTAG_MAX_NEOPIXELS_PER_CHANNEL - 1, ApplyMask(My_Color, BRIGHTNESS)); + NeoPixels_Set_Color_Range(NEOPIXEL_CHANNEL_DISPLAY, 0, CONFIG_KTAG_MAX_NEOPIXELS_PER_CHANNEL - 1, ApplyMask(My_Color, BRIGHTNESS)); + NeoPixels_Set_Color_Range(NEOPIXEL_CHANNEL_EFFECTS, 0, CONFIG_KTAG_MAX_NEOPIXELS_PER_CHANNEL - 1, ApplyMask(My_Color, BRIGHTNESS)); +#endif // CONFIG_KTAG_N_NEOPIXEL_CHANNELS + break; + } + + return ANIMATION_ONGOING; +} + +Animation_T Countdown_Animation = + { + .Reset = Reset, + .NextStep = NextStep + }; diff --git a/NeoPixels/Animations/Countdown.h b/NeoPixels/Animations/Countdown.h new file mode 100644 index 0000000..d965095 --- /dev/null +++ b/NeoPixels/Animations/Countdown.h @@ -0,0 +1,28 @@ + +/* + * 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 . + */ + +#ifndef COUNTDOWN_H +#define COUNTDOWN_H + +extern Animation_T Countdown_Animation; + +#endif // COUNTDOWN_H diff --git a/NeoPixels/Animations/Flamethrower.c b/NeoPixels/Animations/Flamethrower.c new file mode 100644 index 0000000..a154189 --- /dev/null +++ b/NeoPixels/Animations/Flamethrower.c @@ -0,0 +1,107 @@ + +/* + * 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 "SystemK.h" + +#define RED_DELTA_MAX 15 +#define GREEN_DELTA_MAX 8 +#define MAX_RED 128 +#define MIN_RED RED_DELTA_MAX +#define MIN_GREEN 1 +#define LESS_GREEN_THAN_RED 20 + +static int16_t RedLevel = 0; +static int16_t GreenLevel = 0; + +// From https://stackoverflow.com/a/1180465 +static uint8_t lfsr113_Bits (uint32_t min, uint32_t max) +{ + static uint32_t z1 = 12345, z2 = 12345, z3 = 12345, z4 = 12345; + uint32_t b; + b = ((z1 << 6) ^ z1) >> 13; + z1 = ((z1 & 4294967294U) << 18) ^ b; + b = ((z2 << 2) ^ z2) >> 27; + z2 = ((z2 & 4294967288U) << 2) ^ b; + b = ((z3 << 13) ^ z3) >> 21; + z3 = ((z3 & 4294967280U) << 7) ^ b; + b = ((z4 << 3) ^ z4) >> 12; + z4 = ((z4 & 4294967168U) << 13) ^ b; + uint64_t raw = (z1 ^ z2 ^ z3 ^ z4); + uint32_t scaled = ((((max - min) * raw) + (UINT32_MAX / 2)) / UINT32_MAX) + min; + return (uint8_t) scaled; +} + +static void Reset(void * Data) +{ + RedLevel = MIN_RED; + GreenLevel = MIN_GREEN; +} + +// TODO: Need to reimplement this with HSB--see https://blog.adafruit.com/2012/03/14/constant-brightness-hsb-to-rgb-algorithm/ +static AnimationStepResult_T NextStep(void) +{ + // get our next changes in red and green + int16_t delta_red = (lfsr113_Bits(0, RED_DELTA_MAX*2) - RED_DELTA_MAX); + int16_t delta_green = (lfsr113_Bits(0, GREEN_DELTA_MAX*2) - GREEN_DELTA_MAX); + + RedLevel = RedLevel + delta_red; + + if (RedLevel > MAX_RED) + { + RedLevel = MAX_RED; + } + else if (RedLevel < MIN_RED) + { + RedLevel = MIN_RED; + } + RedLevel = (uint8_t) RedLevel; + + GreenLevel = GreenLevel + delta_green; + + // Green has to be less than red, to keep the flame red-orange-yellow. + if (RedLevel > LESS_GREEN_THAN_RED) + { + if (GreenLevel > (RedLevel - LESS_GREEN_THAN_RED)) + { + GreenLevel = (RedLevel - LESS_GREEN_THAN_RED); + } + else if (GreenLevel < MIN_GREEN) + { + GreenLevel = MIN_GREEN; + } + } + else + { + GreenLevel = MIN_GREEN; + } + GreenLevel = (uint8_t) GreenLevel; + + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_BARREL, BARREL_FLASH_PIXEL, Color(0xFF, RedLevel, GreenLevel, 0x00)); + + return ANIMATION_ONGOING; +} + +Animation_T Flamethrower_Animation = +{ + .Reset = Reset, + .NextStep = NextStep +}; diff --git a/NeoPixels/Animations/Flamethrower.h b/NeoPixels/Animations/Flamethrower.h new file mode 100644 index 0000000..04c134f --- /dev/null +++ b/NeoPixels/Animations/Flamethrower.h @@ -0,0 +1,28 @@ + +/* + * 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 . + */ + +#ifndef FLAMETHROWER_H +#define FLAMETHROWER_H + +extern Animation_T Flamethrower_Animation; + +#endif // FLAMETHROWER_H diff --git a/NeoPixels/Animations/Flashlight.c b/NeoPixels/Animations/Flashlight.c new file mode 100644 index 0000000..8deb1c1 --- /dev/null +++ b/NeoPixels/Animations/Flashlight.c @@ -0,0 +1,40 @@ + +/* + * 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 "SystemK.h" + +static void Reset(void * Data) +{ +} + +static AnimationStepResult_T NextStep(void) +{ + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_BARREL, BARREL_FLASH_PIXEL, COLOR_WHITE); + + return ANIMATION_ONGOING; +} + +Animation_T Flashlight_Animation = +{ + .Reset = Reset, + .NextStep = NextStep +}; diff --git a/NeoPixels/Animations/Flashlight.h b/NeoPixels/Animations/Flashlight.h new file mode 100644 index 0000000..d608f88 --- /dev/null +++ b/NeoPixels/Animations/Flashlight.h @@ -0,0 +1,28 @@ + +/* + * 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 . + */ + +#ifndef FLASHLIGHT_H +#define FLASHLIGHT_H + +extern Animation_T Flashlight_Animation; + +#endif // FLASHLIGHT_H diff --git a/NeoPixels/Animations/Health_Report.c b/NeoPixels/Animations/Health_Report.c new file mode 100644 index 0000000..062bf3a --- /dev/null +++ b/NeoPixels/Animations/Health_Report.c @@ -0,0 +1,116 @@ + +/* + * 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 "SystemK.h" + +static uint8_t Health_in_percent; +static uint8_t On = 0xFF / 2; +static uint8_t Blink; +static uint8_t State = 0; +static uint16_t Time_in_Current_Scene_in_ms = 0; + +static void Reset(void * Data) +{ + Health_in_percent = *((uint8_t *)Data); + Time_in_Current_Scene_in_ms = 0; +} + +static AnimationStepResult_T NextStep(void) +{ + if (State > 1) + { + State = 0; + } + + Blink = On * State; + +#if (CONFIG_KTAG_N_NEOPIXEL_CHANNELS == 1) + if (Health_in_percent > 0) // 10 + { + Set_Heart(Color(Blink, 0xFF, 0x00, 0x00)); + } + + if (Health_in_percent > 10) // 20 - 30 + { + Set_Heart(Color(On, 0xFF, 0x00, 0x00)); + } + + if (Health_in_percent > 30) // 40 + { + Set_Heart(Color(On, 0xFF, 0xFF, 0x00)); + Set_Square(Color(Blink, 0xFF, 0xFF, 0x00)); + } + if (Health_in_percent > 40) // 50 + { + Set_Heart(Color(On, 0xFF, 0xFF, 0x00)); + Set_Square(Color(On, 0xFF, 0xFF, 0x00)); + } + + if (Health_in_percent > 50) // 60 + { + Set_Heart(Color(On, 0x00, 0xFF, 0x00)); + Set_Square(Color(On, 0x00, 0xFF, 0x00)); + Set_Circle(Color(Blink, 0x00, 0xFF, 0x00)); + } + + if (Health_in_percent > 60) // 70 - 80 + { + Set_Heart(Color(On, 0x00, 0xFF, 0x00)); + Set_Square(Color(On, 0x00, 0xFF, 0x00)); + Set_Circle(Color(On, 0x00, 0xFF, 0x00)); + } + + if (Health_in_percent > 80) // 90 - 100 + { + Set_Heart(Color(On, 0x00, 0xFF, 0x00)); + Set_Square(Color(On, 0x00, 0xFF, 0x00)); + Set_Circle(Color(On, 0x00, 0xFF, 0x00)); + Set_Arrow(Color(Blink, 0x00, 0xFF, 0x00)); + } + + if (Health_in_percent == 100) // 100 + { + Set_Heart(Color(On, 0x00, 0xFF, 0x00)); + Set_Square(Color(On, 0x00, 0xFF, 0x00)); + Set_Circle(Color(On, 0x00, 0xFF, 0x00)); + Set_Arrow(Color(On, 0x00, 0xFF, 0x00)); + } + + Time_in_Current_Scene_in_ms += CONFIG_KTAG_ANIMATION_STEP_TIME_IN_ms; + + if (Time_in_Current_Scene_in_ms > 100) + { + Time_in_Current_Scene_in_ms = 0; + State++; + } +#elif (CONFIG_KTAG_N_NEOPIXEL_CHANNELS == 4) + // TODO: Implement the "Health Report" animation for four NeoPixel channels. +#endif // CONFIG_KTAG_N_NEOPIXEL_CHANNELS + + return ANIMATION_ONGOING; +} + +Animation_T Health_Report_Animation = +{ + .Reset = Reset, + .NextStep = NextStep +}; diff --git a/NeoPixels/Animations/Health_Report.h b/NeoPixels/Animations/Health_Report.h new file mode 100644 index 0000000..96a647f --- /dev/null +++ b/NeoPixels/Animations/Health_Report.h @@ -0,0 +1,28 @@ + +/* + * 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 . + */ + +#ifndef HEALTH_REPORT_H +#define HEALTH_REPORT_H + +extern Animation_T Health_Report_Animation; + +#endif // HEALTH_REPORT_H diff --git a/NeoPixels/Animations/Idle_Animation.c b/NeoPixels/Animations/Idle_Animation.c new file mode 100644 index 0000000..e6a2f49 --- /dev/null +++ b/NeoPixels/Animations/Idle_Animation.c @@ -0,0 +1,121 @@ +/* + * 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 "SystemK.h" + +static uint8_t Scene = 0; +static uint16_t Time_in_Current_Scene_in_ms = 0; +static color_t My_Color = COLOR_BLACK; + +static void Reset(void * Data) +{ + Time_in_Current_Scene_in_ms = 0; + Scene = 0; + My_Color = HW_NeoPixels_Get_My_Color(); +} + +static AnimationStepResult_T NextStep(void) +{ + switch (Scene) + { + default: + case 0: + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_BARREL, BARREL_FLASH_PIXEL, ApplyMask(COLOR_WHITE, 0x70)); +#if (CONFIG_KTAG_N_NEOPIXEL_CHANNELS == 4) + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_RECEIVER, RECEIVER_INDICATOR_PIXEL, ApplyMask(COLOR_WHITE, 0x70)); +#endif // CONFIG_KTAG_N_NEOPIXEL_CHANNELS + break; + + case 1: +#if (CONFIG_KTAG_N_NEOPIXEL_CHANNELS == 1) + Set_Barrel_Flash(ApplyMask(COLOR_WHITE, 0x70)); + Set_Heart(COLOR_BLACK); + Set_Square(ApplyMask(My_Color, 0x70)); + Set_Circle(COLOR_BLACK); + Set_Arrow(ApplyMask(My_Color, 0x70)); +#elif (CONFIG_KTAG_N_NEOPIXEL_CHANNELS == 4) + for (uint_fast8_t pixel = 0; pixel < CONFIG_KTAG_MAX_NEOPIXELS_PER_CHANNEL; pixel++) + { + if (pixel % 2) + { + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_RECEIVER, pixel, ApplyMask(My_Color, 0x70)); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_DISPLAY, pixel, ApplyMask(My_Color, 0x70)); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_EFFECTS, pixel, ApplyMask(My_Color, 0x70)); + } + else + { + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_RECEIVER, pixel, COLOR_BLACK); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_DISPLAY, pixel, COLOR_BLACK); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_EFFECTS, pixel, COLOR_BLACK); + } + } + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_RECEIVER, RECEIVER_INDICATOR_PIXEL, ApplyMask(COLOR_WHITE, 0x70)); +#endif // CONFIG_KTAG_N_NEOPIXEL_CHANNELS + break; + + case 2: +#if (CONFIG_KTAG_N_NEOPIXEL_CHANNELS == 1) + Set_Barrel_Flash(ApplyMask(COLOR_WHITE, 0x70)); + Set_Heart(ApplyMask(My_Color, 0x70)); + Set_Square(COLOR_BLACK); + Set_Circle(ApplyMask(My_Color, 0x70)); + Set_Arrow(COLOR_BLACK); +#elif (CONFIG_KTAG_N_NEOPIXEL_CHANNELS == 4) + for (uint_fast8_t pixel = 0; pixel < CONFIG_KTAG_MAX_NEOPIXELS_PER_CHANNEL; pixel++) + { + if (pixel % 2) + { + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_RECEIVER, pixel, COLOR_BLACK); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_DISPLAY, pixel, COLOR_BLACK); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_EFFECTS, pixel, COLOR_BLACK); + } + else + { + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_RECEIVER, pixel, ApplyMask(My_Color, 0x70)); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_DISPLAY, pixel, ApplyMask(My_Color, 0x70)); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_EFFECTS, pixel, ApplyMask(My_Color, 0x70)); + } + } + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_RECEIVER, RECEIVER_INDICATOR_PIXEL, ApplyMask(COLOR_WHITE, 0x70)); +#endif // CONFIG_KTAG_N_NEOPIXEL_CHANNELS + break; + } + + Time_in_Current_Scene_in_ms += CONFIG_KTAG_ANIMATION_STEP_TIME_IN_ms; + + if (Time_in_Current_Scene_in_ms > 500) + { + Time_in_Current_Scene_in_ms = 0; + Scene++; + if (Scene > 2) + { + Scene = 1; + } + } + + return ANIMATION_ONGOING; +} + +Animation_T Idle_Animation = +{ + .Reset = Reset, + .NextStep = NextStep +}; diff --git a/NeoPixels/Animations/Idle_Animation.h b/NeoPixels/Animations/Idle_Animation.h new file mode 100644 index 0000000..cecf242 --- /dev/null +++ b/NeoPixels/Animations/Idle_Animation.h @@ -0,0 +1,28 @@ + +/* + * 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 . + */ + +#ifndef IDLE_ANIMATION_H +#define IDLE_ANIMATION_H + +extern Animation_T Idle_Animation; + +#endif // IDLE_ANIMATION_H diff --git a/NeoPixels/Animations/Menu_Animation.c b/NeoPixels/Animations/Menu_Animation.c new file mode 100644 index 0000000..90a8acd --- /dev/null +++ b/NeoPixels/Animations/Menu_Animation.c @@ -0,0 +1,115 @@ + +/* + * 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 "SystemK.h" + +static uint8_t Scene = 0; +static uint8_t N_SCENES = 4; +static uint16_t Time_in_Current_Scene_in_ms = 0; +static color_t My_Color = COLOR_BLACK; + +static void Reset(void * Data) +{ + Time_in_Current_Scene_in_ms = 0; + Scene = 0; +} + +static AnimationStepResult_T NextStep(void) +{ + My_Color = HW_NeoPixels_Get_My_Color(); + +#if (CONFIG_KTAG_N_NEOPIXEL_CHANNELS == 1) + switch (Scene) + { + default: + case 0: + Set_Barrel_Flash(Color(0x70, 0xFF, 0xFF, 0xFF)); + Set_Heart(Color(0x00, 0x00, 0x00, 0x00)); + Set_Square(Color(0x00, 0x00, 0x00, 0x00)); + Set_Circle(Color(0x00, 0x00, 0x00, 0x00)); + Set_Arrow(ApplyMask(My_Color, 0x70)); + break; + + case 1: + Set_Barrel_Flash(Color(0x00, 0x00, 0x00, 0x00)); + Set_Heart(Color(0x00, 0x00, 0x00, 0x00)); + Set_Square(Color(0x00, 0x00, 0x00, 0x00)); + Set_Circle(ApplyMask(My_Color, 0x70)); + Set_Arrow(Color(0x00, 0x00, 0x00, 0x00)); + break; + + case 2: + Set_Barrel_Flash(Color(0x70, 0xFF, 0xFF, 0xFF)); + Set_Heart(Color(0x00, 0x00, 0x00, 0x00)); + Set_Square(ApplyMask(My_Color, 0x70)); + Set_Circle(Color(0x00, 0x00, 0x00, 0x00)); + Set_Arrow(Color(0x00, 0x00, 0x00, 0x00)); + break; + + case 3: + Set_Barrel_Flash(Color(0x00, 0x00, 0x00, 0x00)); + Set_Heart(ApplyMask(My_Color, 0x70)); + Set_Square(Color(0x00, 0x00, 0x00, 0x00)); + Set_Circle(Color(0x00, 0x00, 0x00, 0x00)); + Set_Arrow(Color(0x00, 0x00, 0x00, 0x00)); + break; + } +#elif (CONFIG_KTAG_N_NEOPIXEL_CHANNELS == 4) + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_BARREL, BARREL_FLASH_PIXEL, ApplyMask(COLOR_WHITE, 0x70)); + for (uint_fast8_t pixel = 0; pixel < CONFIG_KTAG_MAX_NEOPIXELS_PER_CHANNEL; pixel++) + { + if ((pixel % N_SCENES) == Scene) + { + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_RECEIVER, pixel, ApplyMask(My_Color, 0x70)); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_DISPLAY, pixel, ApplyMask(My_Color, 0x70)); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_EFFECTS, pixel, ApplyMask(My_Color, 0x70)); + } + else + { + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_RECEIVER, pixel, COLOR_BLACK); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_DISPLAY, pixel, COLOR_BLACK); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_EFFECTS, pixel, COLOR_BLACK); + } + } + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_RECEIVER, RECEIVER_INDICATOR_PIXEL, ApplyMask(COLOR_WHITE, 0x70)); +#endif // CONFIG_KTAG_N_NEOPIXEL_CHANNELS + + Time_in_Current_Scene_in_ms += CONFIG_KTAG_ANIMATION_STEP_TIME_IN_ms; + + if (Time_in_Current_Scene_in_ms > 100) + { + Time_in_Current_Scene_in_ms = 0; + Scene++; + if (Scene >= N_SCENES) + { + Scene = 0; + } + } + + return ANIMATION_ONGOING; +} + +Animation_T Menu_Animation = +{ + .Reset = Reset, + .NextStep = NextStep +}; diff --git a/NeoPixels/Animations/Menu_Animation.h b/NeoPixels/Animations/Menu_Animation.h new file mode 100644 index 0000000..66ee0e8 --- /dev/null +++ b/NeoPixels/Animations/Menu_Animation.h @@ -0,0 +1,28 @@ + +/* + * 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 . + */ + +#ifndef MENU_ANIMATION_H +#define MENU_ANIMATION_H + +extern Animation_T Menu_Animation; + +#endif // MENU_ANIMATION_H diff --git a/NeoPixels/Animations/Shot_Fired.c b/NeoPixels/Animations/Shot_Fired.c new file mode 100644 index 0000000..484a7c4 --- /dev/null +++ b/NeoPixels/Animations/Shot_Fired.c @@ -0,0 +1,59 @@ + +/* + * 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 "SystemK.h" + +static uint16_t Time_in_Animation_in_ms = 0; +static color_t Flash_Color = COLOR_BLACK; + +static void Reset(void * Data) +{ + Time_in_Animation_in_ms = 0; + Flash_Color = HW_NeoPixels_Get_My_Color(); + NeoPixels_Set_Color_Range_On_All_Channels(0, CONFIG_KTAG_MAX_NEOPIXELS_PER_CHANNEL - 1, COLOR_BLACK); +} + +static AnimationStepResult_T NextStep(void) +{ + AnimationStepResult_T result; + + if (Time_in_Animation_in_ms < 250) + { + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_BARREL, BARREL_FLASH_PIXEL, Flash_Color); + + Time_in_Animation_in_ms += CONFIG_KTAG_ANIMATION_STEP_TIME_IN_ms; + result = ANIMATION_ONGOING; + } + else + { + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_BARREL, BARREL_FLASH_PIXEL, COLOR_BLACK); + result = ANIMATION_COMPLETE; + } + + return result; +} + +Animation_T Shot_Fired_Animation = +{ + .Reset = Reset, + .NextStep = NextStep +}; diff --git a/NeoPixels/Animations/Shot_Fired.h b/NeoPixels/Animations/Shot_Fired.h new file mode 100644 index 0000000..c80fd68 --- /dev/null +++ b/NeoPixels/Animations/Shot_Fired.h @@ -0,0 +1,28 @@ + +/* + * 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 . + */ + +#ifndef SHOT_FIRED_H +#define SHOT_FIRED_H + +extern Animation_T Shot_Fired_Animation; + +#endif // SHOT_FIRED_H diff --git a/NeoPixels/Animations/Tag_Received.c b/NeoPixels/Animations/Tag_Received.c new file mode 100644 index 0000000..51e63d8 --- /dev/null +++ b/NeoPixels/Animations/Tag_Received.c @@ -0,0 +1,70 @@ + +/* + * 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 "SystemK.h" + +static uint16_t Time_in_Animation_in_ms = 0; +static color_t TagTeamColor = (color_t) 0x00000000; +static uint8_t Fade = 0x40; + +static void Reset(void * Data) +{ + Time_in_Animation_in_ms = 0; + TagTeamColor = *((color_t *)Data); + Fade = 128; +} + +static AnimationStepResult_T NextStep(void) +{ + AnimationStepResult_T result; + + if (Fade < 255) + { +#if (CONFIG_KTAG_N_NEOPIXEL_CHANNELS == 1) + Set_Barrel_Flash(ApplyMask(TagTeamColor, Sine8[Fade])); + Set_Heart(ApplyMask(TagTeamColor, Sine8[Fade])); + Set_Square(ApplyMask(TagTeamColor, Sine8[Fade])); + Set_Circle(ApplyMask(TagTeamColor, Sine8[Fade])); + Set_Arrow(ApplyMask(TagTeamColor, Sine8[Fade])); +#elif (CONFIG_KTAG_N_NEOPIXEL_CHANNELS == 4) + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_BARREL, BARREL_FLASH_PIXEL, ApplyMask(TagTeamColor, Sine8[Fade])); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_RECEIVER, RECEIVER_INDICATOR_PIXEL, ApplyMask(TagTeamColor, Sine8[Fade])); +#endif // CONFIG_KTAG_N_NEOPIXEL_CHANNELS + + Fade += 1; + result = ANIMATION_ONGOING; + } + else + { + NeoPixels_Set_Color_Range_On_All_Channels(0, CONFIG_KTAG_MAX_NEOPIXELS_PER_CHANNEL - 1, COLOR_BLACK); + + result = ANIMATION_COMPLETE; + } + + return result; +} + +Animation_T Tag_Received_Animation = +{ + .Reset = Reset, + .NextStep = NextStep +}; diff --git a/NeoPixels/Animations/Tag_Received.h b/NeoPixels/Animations/Tag_Received.h new file mode 100644 index 0000000..cd3c358 --- /dev/null +++ b/NeoPixels/Animations/Tag_Received.h @@ -0,0 +1,28 @@ + +/* + * 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 . + */ + +#ifndef TAG_RECEIVED_H +#define TAG_RECEIVED_H + +extern Animation_T Tag_Received_Animation; + +#endif // TAG_RECEIVED_H diff --git a/NeoPixels/Animations/Tagged_Out.c b/NeoPixels/Animations/Tagged_Out.c new file mode 100644 index 0000000..d0e92f1 --- /dev/null +++ b/NeoPixels/Animations/Tagged_Out.c @@ -0,0 +1,82 @@ + +/* + * 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 "SystemK.h" + +static uint8_t Fade = 0xFF; +static color_t My_Color = COLOR_BLACK; + +static void Reset(void * Data) +{ + Fade = 0xFF; + My_Color = HW_NeoPixels_Get_My_Color(); +} + +static AnimationStepResult_T NextStep(void) +{ + uint8_t phase_0 = Fade; + uint8_t phase_1 = phase_0 - (uint8_t)64; + uint8_t phase_2 = phase_1 - (uint8_t)64; + uint8_t phase_3 = phase_2 - (uint8_t)64; + +#if (CONFIG_KTAG_N_NEOPIXEL_CHANNELS == 1) + Set_Barrel_Flash(Color(Sine8[phase_0], 0xFF, 0x00, 0x00)); + Set_Heart(Color(Sine8[phase_1], 0xFF, 0x00, 0x00)); + Set_Square(Color(Sine8[phase_2], 0xFF, 0x00, 0x00)); + Set_Circle(Color(Sine8[phase_3], 0xFF, 0x00, 0x00)); + Set_Arrow(Color(Sine8[phase_0], 0xFF, 0x00, 0x00)); +#elif (CONFIG_KTAG_N_NEOPIXEL_CHANNELS == 4) + for (uint_fast8_t pixel = 0; pixel < CONFIG_KTAG_MAX_NEOPIXELS_PER_CHANNEL; pixel++) + { + switch (pixel % 4) + { + default: + case 0: + NeoPixels_Set_Color_On_All_Channels(pixel, ApplyMask(My_Color, Sine8[phase_0])); + break; + + case 1: + NeoPixels_Set_Color_On_All_Channels(pixel, ApplyMask(My_Color, Sine8[phase_1])); + break; + + case 2: + NeoPixels_Set_Color_On_All_Channels(pixel, ApplyMask(My_Color, Sine8[phase_2])); + break; + + case 3: + NeoPixels_Set_Color_On_All_Channels(pixel, ApplyMask(My_Color, Sine8[phase_3])); + break; + } + } + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_BARREL, BARREL_FLASH_PIXEL, COLOR_WHITE); +#endif // CONFIG_KTAG_N_NEOPIXEL_CHANNELS + + Fade += 2; + + return ANIMATION_ONGOING; +} + +Animation_T Tagged_Out_Animation = +{ + .Reset = Reset, + .NextStep = NextStep +}; diff --git a/NeoPixels/Animations/Tagged_Out.h b/NeoPixels/Animations/Tagged_Out.h new file mode 100644 index 0000000..614df26 --- /dev/null +++ b/NeoPixels/Animations/Tagged_Out.h @@ -0,0 +1,28 @@ + +/* + * 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 . + */ + +#ifndef TAGGED_OUT_H +#define TAGGED_OUT_H + +extern Animation_T Tagged_Out_Animation; + +#endif // TAGGED_OUT_H diff --git a/NeoPixels/Animations/Team_Colors.c b/NeoPixels/Animations/Team_Colors.c new file mode 100644 index 0000000..f7e1067 --- /dev/null +++ b/NeoPixels/Animations/Team_Colors.c @@ -0,0 +1,162 @@ + +/* + * 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 "SystemK.h" + +typedef enum +{ + STEP_LONG_DARK, + STEP_BLINK_ONE, + STEP_BLINK_TWO, + STEP_BLINK_THREE, + STEP_HEARTBEAT_GLOW +} TeamColorsStep_T; + +static DisplayStyle_T Style = DISPLAY_STYLE_BLINK; +static TeamColorsStep_T Step = STEP_LONG_DARK; +static uint16_t Time_in_Animation_Step_in_ms = 0; +static color_t Team_Color = COLOR_BLACK; +static uint8_t Brightness = 0; +static uint8_t Phase = 0; +static const uint8_t LOW_BRIGHTNESS = 10; +static const uint8_t HIGH_BRIGHTNESS = 50; +static const uint16_t LONG_DARK_TIME_IN_ms = 20000; +static const uint16_t BLINK_TIME_ON_IN_ms = 100; +static const uint16_t BLINK_TIME_OFF_IN_ms = 100; + +static void Reset(void * Data) +{ + // Start just before the dawn. + Time_in_Animation_Step_in_ms = LONG_DARK_TIME_IN_ms; + Style = NeoPixels_ToDisplayStyle(Data); + Team_Color = HW_NeoPixels_Get_My_Color(); + NeoPixels_Set_Color_Range_On_All_Channels(0, CONFIG_KTAG_MAX_NEOPIXELS_PER_CHANNEL - 1, COLOR_BLACK); +} + +static AnimationStepResult_T NextStep(void) +{ + Time_in_Animation_Step_in_ms += CONFIG_KTAG_ANIMATION_STEP_TIME_IN_ms; + + switch (Step) + { + default: + case STEP_LONG_DARK: + Brightness = LOW_BRIGHTNESS; + if (Time_in_Animation_Step_in_ms > LONG_DARK_TIME_IN_ms) + { + Time_in_Animation_Step_in_ms = 0; + if (Style == DISPLAY_STYLE_HEARTBEAT) + { + Step = STEP_HEARTBEAT_GLOW; + Phase = 0; + } + else + { + Step = STEP_BLINK_ONE; + } + } + break; + + case STEP_BLINK_ONE: + if (Time_in_Animation_Step_in_ms > (BLINK_TIME_ON_IN_ms + BLINK_TIME_OFF_IN_ms)) + { + Brightness = HIGH_BRIGHTNESS; + Time_in_Animation_Step_in_ms = 0; + Step = STEP_BLINK_TWO; + } + else if (Time_in_Animation_Step_in_ms > BLINK_TIME_ON_IN_ms) + { + Brightness = LOW_BRIGHTNESS; + } + else + { + Brightness = HIGH_BRIGHTNESS; + } + break; + + case STEP_BLINK_TWO: + if (Time_in_Animation_Step_in_ms > (BLINK_TIME_ON_IN_ms + BLINK_TIME_OFF_IN_ms)) + { + Brightness = HIGH_BRIGHTNESS; + Time_in_Animation_Step_in_ms = 0; + Step = STEP_BLINK_THREE; + } + else if (Time_in_Animation_Step_in_ms > BLINK_TIME_ON_IN_ms) + { + Brightness = LOW_BRIGHTNESS; + } + else + { + Brightness = HIGH_BRIGHTNESS; + } + break; + + case STEP_BLINK_THREE: + if (Time_in_Animation_Step_in_ms > (BLINK_TIME_ON_IN_ms + BLINK_TIME_OFF_IN_ms)) + { + Brightness = LOW_BRIGHTNESS; + Time_in_Animation_Step_in_ms = 0; + Step = STEP_LONG_DARK; + } + else if (Time_in_Animation_Step_in_ms > BLINK_TIME_ON_IN_ms) + { + Brightness = LOW_BRIGHTNESS; + } + else + { + Brightness = HIGH_BRIGHTNESS; + } + break; + + case STEP_HEARTBEAT_GLOW: + Brightness = Sine8[Phase] / 2; + Phase++; + + if (Phase == 0) + { + Brightness = LOW_BRIGHTNESS; + Time_in_Animation_Step_in_ms = 0; + Step = STEP_LONG_DARK; + } + break; + } + +#if (CONFIG_KTAG_N_NEOPIXEL_CHANNELS == 1) + Set_Barrel_Flash(ApplyMask(Team_Color, Brightness)); + Set_Heart(COLOR_BLACK); + Set_Square(COLOR_BLACK); + Set_Circle(COLOR_BLACK); + Set_Arrow(COLOR_BLACK); +#elif (CONFIG_KTAG_N_NEOPIXEL_CHANNELS == 4) + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_RECEIVER, RECEIVER_INDICATOR_PIXEL, ApplyMask(Team_Color, Brightness)); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_RECEIVER, RIGHT_RECEIVER_INDICATOR_PIXEL, ApplyMask(Team_Color, Brightness)); + NeoPixels_Set_Color(NEOPIXEL_CHANNEL_RECEIVER, LEFT_RECEIVER_INDICATOR_PIXEL, ApplyMask(Team_Color, Brightness)); +#endif // CONFIG_KTAG_N_NEOPIXEL_CHANNELS + + return ANIMATION_ONGOING; +} + +Animation_T Team_Colors_Animation = +{ + .Reset = Reset, + .NextStep = NextStep +}; diff --git a/NeoPixels/Animations/Team_Colors.h b/NeoPixels/Animations/Team_Colors.h new file mode 100644 index 0000000..4f56435 --- /dev/null +++ b/NeoPixels/Animations/Team_Colors.h @@ -0,0 +1,28 @@ + +/* + * 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 . + */ + +#ifndef TEAM_COLORS_H +#define TEAM_COLORS_H + +extern Animation_T Team_Colors_Animation; + +#endif // TEAM_COLORS_H diff --git a/NeoPixels/Animations/Test_Pattern.c b/NeoPixels/Animations/Test_Pattern.c new file mode 100644 index 0000000..e341888 --- /dev/null +++ b/NeoPixels/Animations/Test_Pattern.c @@ -0,0 +1,116 @@ + +/* + * 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 "SystemK.h" + +static uint8_t Scene = 0; +static uint16_t Time_in_Current_Scene_in_ms = 0; + +static void Reset(void * Data) +{ + Time_in_Current_Scene_in_ms = 0; + Scene = 0; +} + +static AnimationStepResult_T NextStep(void) +{ +#if (CONFIG_KTAG_N_NEOPIXEL_CHANNELS == 1) + switch (Scene) + { + default: + case 0: + Set_Barrel_Flash(Color(0xFF, 0xFF, 0x00, 0x00)); + Set_Heart(Color(0xFF / 2, 0xFF, 0x00, 0x00)); + Set_Square(Color(0xFF / 2, 0x00, 0xFF, 0x00)); + Set_Circle(Color(0xFF / 2, 0xFF, 0x00, 0xFF)); + Set_Arrow(Color(0xFF / 2, 0xFF, 0xFF, 0x00)); + break; + + case 1: + Set_Barrel_Flash(Color(0xFF, 0xFF, 0xFF, 0xFF)); + Set_Heart(Color(0xFF / 2, 0xFF, 0xFF, 0x00)); + Set_Square(Color(0xFF / 2, 0xFF, 0x00, 0x00)); + Set_Circle(Color(0xFF / 2, 0x00, 0xFF, 0x00)); + Set_Arrow(Color(0xFF / 2, 0xFF, 0x00, 0xFF)); + break; + + case 2: + Set_Barrel_Flash(Color(0xFF, 0x00, 0x00, 0xFF)); + Set_Heart(Color(0xFF / 2, 0xFF, 0x00, 0xFF)); + Set_Square(Color(0xFF / 2, 0xFF, 0xFF, 0x00)); + Set_Circle(Color(0xFF / 2, 0xFF, 0x00, 0x00)); + Set_Arrow(Color(0xFF / 2, 0x00, 0xFF, 0x00)); + break; + + case 3: + Set_Barrel_Flash(Color(0xFF, 0x00, 0x00, 0x00)); + Set_Heart(Color(0xFF / 2, 0x00, 0xFF, 0x00)); + Set_Square(Color(0xFF / 2, 0xFF, 0x00, 0xFF)); + Set_Circle(Color(0xFF / 2, 0xFF, 0xFF, 0x00)); + Set_Arrow(Color(0xFF / 2, 0xFF, 0x00, 0x00)); + break; + } +#elif (CONFIG_KTAG_N_NEOPIXEL_CHANNELS == 4) + for (uint_fast8_t pixel = 0; pixel < CONFIG_KTAG_MAX_NEOPIXELS_PER_CHANNEL; pixel++) + { + switch ((pixel + Scene) % 4) + { + default: + case 0: + NeoPixels_Set_Color_On_All_Channels(pixel, ApplyMask(COLOR_RED, 0xFF / 2)); + break; + + case 1: + NeoPixels_Set_Color_On_All_Channels(pixel, ApplyMask(COLOR_WHITE, 0xFF / 2)); + break; + + case 2: + NeoPixels_Set_Color_On_All_Channels(pixel, ApplyMask(COLOR_BLUE, 0xFF / 2)); + break; + + case 3: + NeoPixels_Set_Color_On_All_Channels(pixel, COLOR_BLACK); + break; + } + } +#endif // CONFIG_KTAG_N_NEOPIXEL_CHANNELS + + Time_in_Current_Scene_in_ms += CONFIG_KTAG_ANIMATION_STEP_TIME_IN_ms; + + if (Time_in_Current_Scene_in_ms > 100) + { + Time_in_Current_Scene_in_ms = 0; + Scene++; + if (Scene > 3) + { + Scene = 0; + } + } + + return ANIMATION_ONGOING; +} + +Animation_T Test_Pattern_Animation = +{ + .Reset = Reset, + .NextStep = NextStep +}; diff --git a/NeoPixels/Animations/Test_Pattern.h b/NeoPixels/Animations/Test_Pattern.h new file mode 100644 index 0000000..d7dcfe5 --- /dev/null +++ b/NeoPixels/Animations/Test_Pattern.h @@ -0,0 +1,28 @@ + +/* + * 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 . + */ + +#ifndef TEST_PATTERN_H +#define TEST_PATTERN_H + +extern Animation_T Test_Pattern_Animation; + +#endif // TEST_PATTERN_H diff --git a/NeoPixels/Displays.h b/NeoPixels/Displays.h new file mode 100644 index 0000000..8f05806 --- /dev/null +++ b/NeoPixels/Displays.h @@ -0,0 +1,48 @@ + +/* + * 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 . + */ + +#ifndef DISPLAYS_H +#define DISPLAYS_H + +#include "Gamma.h" +#include "Sine.h" +#include "Animation.h" + +#include "NeoPixel_Hardware_Config.h" +#include "NeoPixels.h" + +#include "Animations/All_Off.h" +#include "Animations/BLE_Nearby.h" +#include "Animations/BLE_RSSI.h" +#include "Animations/Countdown.h" +#include "Animations/Flamethrower.h" +#include "Animations/Flashlight.h" +#include "Animations/Health_Report.h" +#include "Animations/Idle_Animation.h" +#include "Animations/Menu_Animation.h" +#include "Animations/Shot_Fired.h" +#include "Animations/Tag_Received.h" +#include "Animations/Tagged_Out.h" +#include "Animations/Team_Colors.h" +#include "Animations/Test_Pattern.h" + +#endif // DISPLAYS_H diff --git a/NeoPixels/Four_Channel_Helpers.h b/NeoPixels/Four_Channel_Helpers.h new file mode 100644 index 0000000..83743c9 --- /dev/null +++ b/NeoPixels/Four_Channel_Helpers.h @@ -0,0 +1,60 @@ + +/* + * 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 . + */ + + + +// /\ /\\ /\ /\\ TM +// ( ) || || ( ) || || |''||''| '||''|. ..|'''.| +// // || || // || || || || || .|' ' +// // || || // || || || ||...|' || +// /( || || /( || || || || '|. . +// {___ \\/ {___ \\/ .||. .||. ''|....' +// +// +// _____ _____ _____ ___________ _ _ +// |____ |/ __ \| ___/ ___| ___ \ (_) | | +// / /`' / /'| |__ \ `--.| |_/ /__ ___ _ __ _| | +// \ \ / / | __| `--. \ __/ _ \/ __| |/ _` | | +// .___/ /./ /___| |___/\__/ / | | __/ (__| | (_| | | +// \____/ \_____/\____/\____/\_| \___|\___|_|\__,_|_| + + + +//! Zero-based index of the Barrel Flash NeoPixel in the sequence of NeoPixels. +#define BARREL_FLASH_PIXEL 0 + +//! Zero-based index of the Receiver Indicator NeoPixel in the sequence of NeoPixels. +#define RECEIVER_INDICATOR_PIXEL 0 + +//! Zero-based index of the right Receiver Indicator NeoPixel in the sequence of NeoPixels. +#define RIGHT_RECEIVER_INDICATOR_PIXEL 1 + +//! Zero-based index of the left Receiver Indicator NeoPixel in the sequence of NeoPixels. +#define LEFT_RECEIVER_INDICATOR_PIXEL 2 + +static inline __attribute__((always_inline)) void NeoPixels_Set_Color_On_All_Channels(uint8_t position, color_t color) +{ + HW_NeoPixels_Set_RGB(NEOPIXEL_CHANNEL_BARREL, position, Gamma8[Red(color)], Gamma8[Green(color)], Gamma8[Blue(color)]); + HW_NeoPixels_Set_RGB(NEOPIXEL_CHANNEL_RECEIVER, position, Gamma8[Red(color)], Gamma8[Green(color)], Gamma8[Blue(color)]); + HW_NeoPixels_Set_RGB(NEOPIXEL_CHANNEL_DISPLAY, position, Gamma8[Red(color)], Gamma8[Green(color)], Gamma8[Blue(color)]); + HW_NeoPixels_Set_RGB(NEOPIXEL_CHANNEL_EFFECTS, position, Gamma8[Red(color)], Gamma8[Green(color)], Gamma8[Blue(color)]); +} diff --git a/NeoPixels/Gamma.c b/NeoPixels/Gamma.c new file mode 100644 index 0000000..5d091f5 --- /dev/null +++ b/NeoPixels/Gamma.c @@ -0,0 +1,44 @@ + +/* + * 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 "Gamma.h" + +// https://learn.adafruit.com/led-tricks-gamma-correction/the-quick-fix + +const uint_fast8_t Gamma8[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, + 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, + 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, + 10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16, + 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25, + 25, 26, 27, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36, + 37, 38, 39, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 50, + 51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68, + 69, 70, 72, 73, 74, 75, 77, 78, 79, 81, 82, 83, 85, 86, 87, 89, + 90, 92, 93, 95, 96, 98, 99,101,102,104,105,107,109,110,112,114, +115,117,119,120,122,124,126,127,129,131,133,135,137,138,140,142, +144,146,148,150,152,154,156,158,160,162,164,167,169,171,173,175, +177,180,182,184,186,189,191,193,196,198,200,203,205,208,210,213, +215,218,220,223,225,228,231,233,236,239,241,244,247,249,252,255 }; diff --git a/NeoPixels/Gamma.h b/NeoPixels/Gamma.h new file mode 100644 index 0000000..f6243a7 --- /dev/null +++ b/NeoPixels/Gamma.h @@ -0,0 +1,28 @@ + +/* + * 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 . + */ + +#ifndef GAMMA_H +#define GAMMA_H + +extern const uint_fast8_t Gamma8[]; + +#endif // GAMMA_H diff --git a/NeoPixels/NeoPixel_HW_Interface.h b/NeoPixels/NeoPixel_HW_Interface.h new file mode 100644 index 0000000..26604c7 --- /dev/null +++ b/NeoPixels/NeoPixel_HW_Interface.h @@ -0,0 +1,30 @@ +/* + * 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 . + */ + +#ifndef NEOPIXEL_HW_INTERFACE_H +#define NEOPIXEL_HW_INTERFACE_H + +SystemKResult_T HW_NeoPixels_Init(void); +SystemKResult_T HW_NeoPixels_Set_RGB(NeoPixelsChannel_T channel, uint8_t position, uint8_t red, uint8_t green, uint8_t blue); +SystemKResult_T HW_NeoPixels_Publish(void); +color_t HW_NeoPixels_Get_My_Color(void); + +#endif // NEOPIXEL_HW_INTERFACE_H diff --git a/NeoPixels/NeoPixels.c b/NeoPixels/NeoPixels.c new file mode 100644 index 0000000..789ccb7 --- /dev/null +++ b/NeoPixels/NeoPixels.c @@ -0,0 +1,164 @@ + +/* + * 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 "SystemK.h" + +QueueHandle_t xQueueNeoPixels; +TaskHandle_t NeoPixels_Task_Handle; +SemaphoreHandle_t NeoPixels_Semaphore; + +static const TickType_t Animation_Delay = CONFIG_KTAG_ANIMATION_STEP_TIME_IN_ms / portTICK_PERIOD_MS; +static Animation_T * Current_Foreground_Animation = &All_Off_Animation; +static Animation_T * Current_Background_Animation = &All_Off_Animation; + +void NeoPixels_Task(void * pvParameters) +{ + portBASE_TYPE xStatus; + TickType_t xLastWakeTime; + Animation_T * New_Animation; + + xQueueNeoPixels = xQueueCreate(5, sizeof(NeoPixelsAction_T)); + NeoPixels_Semaphore = xSemaphoreCreateBinary(); + xSemaphoreGive(NeoPixels_Semaphore); + + // TODO: Wait for NVM to be initialized and configurations read. + vTaskDelay(pdMS_TO_TICKS(1000)); + + HW_NeoPixels_Init(); + NeoPixels_Set_Color_Range_On_All_Channels(0, CONFIG_KTAG_MAX_NEOPIXELS_PER_CHANNEL - 1, COLOR_BLACK); + + // Initialize the xLastWakeTime variable with the current time. + xLastWakeTime = xTaskGetTickCount(); + + while (true) + { + NeoPixelsAction_T action; + + // Check for a new animation. + xStatus = xQueueReceive(xQueueNeoPixels, &action, 0); + + if (xStatus == pdPASS) + { + switch (action.ID) + { + case NEOPIXELS_ALL_OFF: + default: + New_Animation = &All_Off_Animation; + break; + + case NEOPIXELS_ALL_ON: + New_Animation = &All_On_Animation; + break; + + case NEOPIXELS_TEST_PATTERN: + New_Animation = &Test_Pattern_Animation; + break; + + case NEOPIXELS_PLAY_SHOT_FIRED: + New_Animation = &Shot_Fired_Animation; + break; + + case NEOPIXELS_FLASHLIGHT_ON: + New_Animation = &Flashlight_Animation; + break; + + case NEOPIXELS_FLAMETHROWER: + New_Animation = &Flamethrower_Animation; + break; + + case NEOPIXELS_TAG_RECEIVED: + New_Animation = &Tag_Received_Animation; + break; + + case NEOPIXELS_TAGGED_OUT: + New_Animation = &Tagged_Out_Animation; + break; + + case NEOPIXELS_MENU: + New_Animation = &Menu_Animation; + break; + + case NEOPIXELS_HEALTH_REPORT: + New_Animation = &Health_Report_Animation; + break; + + case NEOPIXELS_BLE_RSSI: + New_Animation = &BLE_RSSI_Animation; + break; + + case NEOPIXELS_IDLE: + New_Animation = &Idle_Animation; + break; + + case NEOPIXELS_COUNTDOWN: + New_Animation = &Countdown_Animation; + break; + + case NEOPIXELS_TEAM_COLORS: + New_Animation = &Team_Colors_Animation; + break; + + case NEOPIXELS_BLE_NEARBY: + New_Animation = &BLE_Nearby_Animation; + break; + } + + if (New_Animation != NULL) + { + New_Animation->Reset(action.Data); + + if (action.Prominence == NEOPIXELS_FOREGROUND) + { + Current_Foreground_Animation = New_Animation; + } + if (action.Prominence == NEOPIXELS_BACKGROUND) + { + Current_Background_Animation = New_Animation; + } + } + } + + if (Current_Foreground_Animation != NULL) + { + AnimationStepResult_T result = Current_Foreground_Animation->NextStep(); + + if (result == ANIMATION_COMPLETE) + { + Current_Foreground_Animation = NULL; + } + } + else if (Current_Background_Animation != NULL) + { + AnimationStepResult_T result = Current_Background_Animation->NextStep(); + + if (result == ANIMATION_COMPLETE) + { + Current_Background_Animation = NULL; + } + } + + // The animation has conigured all the colors. Now send them to the NeoPixels. + HW_NeoPixels_Publish(); + + vTaskDelayUntil(&xLastWakeTime, Animation_Delay); + } +} diff --git a/NeoPixels/NeoPixels.h b/NeoPixels/NeoPixels.h new file mode 100644 index 0000000..af75da8 --- /dev/null +++ b/NeoPixels/NeoPixels.h @@ -0,0 +1,211 @@ +/* + * 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 . + */ + +/** \file + * \brief This file defines the interface to the NeoPixels used by the KTag system (SystemK). + * + */ + +#ifndef NEOPIXELS_H +#define NEOPIXELS_H + + +#include + +#ifdef ESP_PLATFORM +#include "sdkconfig.h" +#endif // ESP_PLATFORM + +#ifdef PSOC_PLATFORM +#include "CONFIG.h" +#endif // PSOC_PLATFORM + +#include "Gamma.h" +#include "Sine.h" +#include "Animation.h" + +#if (CONFIG_KTAG_N_NEOPIXEL_CHANNELS == 1) + typedef enum + { + // The Lil' Bruv and the Little Boy BLuE only have one NeoPixel channel. + NEOPIXEL_CHANNEL_BARREL = 0, + NEOPIXEL_CHANNEL_RECEIVER = 0, + NEOPIXEL_CHANNEL_DISPLAY = 0, + NEOPIXEL_CHANNEL_EFFECTS = 0, + NEOPIXEL_CHANNEL_ALL = 100, + NEOPIXEL_CHANNEL_NONE = 200 + } NeoPixelsChannel_T; +#elif (CONFIG_KTAG_N_NEOPIXEL_CHANNELS == 4) + typedef enum + { + // The 2020TPC and the 32ESPecial have four NeoPixel channels. + NEOPIXEL_CHANNEL_BARREL = 0, + NEOPIXEL_CHANNEL_RECEIVER = 1, + NEOPIXEL_CHANNEL_DISPLAY = 2, + NEOPIXEL_CHANNEL_EFFECTS = 3, + NEOPIXEL_CHANNEL_ALL = 100, + NEOPIXEL_CHANNEL_NONE = 200 + } NeoPixelsChannel_T; +#else + #error "Unsupported number of NeoPixel channels defined. Supported configurations are 1 and 4." +#endif + +#include "NeoPixel_HW_Interface.h" + +#include "Animations/All_Off.h" +#include "Animations/All_On.h" +#include "Animations/BLE_Nearby.h" +#include "Animations/BLE_RSSI.h" +#include "Animations/Countdown.h" +#include "Animations/Flamethrower.h" +#include "Animations/Flashlight.h" +#include "Animations/Health_Report.h" +#include "Animations/Idle_Animation.h" +#include "Animations/Menu_Animation.h" +#include "Animations/Shot_Fired.h" +#include "Animations/Tag_Received.h" +#include "Animations/Tagged_Out.h" +#include "Animations/Team_Colors.h" +#include "Animations/Test_Pattern.h" + +extern QueueHandle_t xQueueNeoPixels; +extern TaskHandle_t NeoPixels_Task_Handle; +extern SemaphoreHandle_t NeoPixels_Semaphore; + +#if (CONFIG_KTAG_N_NEOPIXEL_CHANNELS == 1) + #include "Single_Channel_Helpers.h" +#elif (CONFIG_KTAG_N_NEOPIXEL_CHANNELS == 4) + #include "Four_Channel_Helpers.h" +#else + #error "Unsupported number of NeoPixel channels defined. Supported configurations are 1 and 4." +#endif + +typedef enum +{ + COLOR_ORDER_RGB = 0, + COLOR_ORDER_RBG = 1, + COLOR_ORDER_GRB = 2, + COLOR_ORDER_GBR = 3, + COLOR_ORDER_BRG = 4, + COLOR_ORDER_BGR = 5 +} ColorOrder_T; + +typedef enum +{ + DISPLAY_STYLE_DEFAULT = 0, + DISPLAY_STYLE_SOLID = 1, + DISPLAY_STYLE_ALTERNATE = 2, + DISPLAY_STYLE_BLINK = 3, + DISPLAY_STYLE_HEARTBEAT = 4, + //----------------------------------------------- + FIRST_DISPLAY_STYLE = DISPLAY_STYLE_DEFAULT, + LAST_DISPLAY_STYLE = DISPLAY_STYLE_HEARTBEAT + +} DisplayStyle_T; + +typedef enum +{ + NEOPIXELS_ALL_OFF, + NEOPIXELS_ALL_ON, + NEOPIXELS_TEST_PATTERN, + NEOPIXELS_PLAY_SHOT_FIRED, + NEOPIXELS_FLASHLIGHT_ON, + NEOPIXELS_FLAMETHROWER, + NEOPIXELS_TAG_RECEIVED, + NEOPIXELS_TAGGED_OUT, + NEOPIXELS_MENU, + NEOPIXELS_HEALTH_REPORT, + NEOPIXELS_BLE_RSSI, + NEOPIXELS_IDLE, + NEOPIXELS_COUNTDOWN, + //! For #NEOPIXELS_TEAM_COLORS, #NeoPixelsAction_T::Data is a #DisplayStyle_T. + NEOPIXELS_TEAM_COLORS, + //! For #NEOPIXELS_TEAM_COLORS, #NeoPixelsAction_T::Data is a #BLENearby_T. + NEOPIXELS_BLE_NEARBY +} NeoPixelsActionID_T; + +typedef enum +{ + NEOPIXELS_FOREGROUND, + NEOPIXELS_BACKGROUND, +} NeoPixelsProminence_T; + +typedef struct +{ + NeoPixelsActionID_T ID; + NeoPixelsProminence_T Prominence; + void * Data; +} NeoPixelsAction_T; + +typedef struct +{ + color_t color; + DisplayStyle_T style; +} All_On_Data_T; + +void NeoPixels_Task(void * pvParameters); + +static inline __attribute__((always_inline)) void NeoPixels_Set_Color(NeoPixelsChannel_T channel, uint8_t position, color_t color) +{ + HW_NeoPixels_Set_RGB(channel, position, Gamma8[Red(color)], Gamma8[Green(color)], Gamma8[Blue(color)]); +} + +static inline void NeoPixel_Set_Range(NeoPixelsChannel_T channel, uint8_t start, uint8_t end, uint8_t red, uint8_t green ,uint8_t blue) +{ + for (uint_fast8_t position = start; position <= end; position++) + { + HW_NeoPixels_Set_RGB(channel, position, red, green, blue); + } +} + +static inline __attribute__((always_inline)) void NeoPixels_Set_Color_Range(NeoPixelsChannel_T channel, uint8_t start, uint8_t end, color_t color) +{ + NeoPixel_Set_Range(channel, start, end, Gamma8[Red(color)], Gamma8[Green(color)], Gamma8[Blue(color)]); +} + +static inline __attribute__((always_inline)) void NeoPixels_Set_Color_Range_On_All_Channels(uint8_t start, uint8_t end, color_t color) +{ +#if (CONFIG_KTAG_N_NEOPIXEL_CHANNELS > 1) + NeoPixel_Set_Range(NEOPIXEL_CHANNEL_BARREL, start, end, Gamma8[Red(color)], Gamma8[Green(color)], Gamma8[Blue(color)]); + NeoPixel_Set_Range(NEOPIXEL_CHANNEL_RECEIVER, start, end, Gamma8[Red(color)], Gamma8[Green(color)], Gamma8[Blue(color)]); + NeoPixel_Set_Range(NEOPIXEL_CHANNEL_DISPLAY, start, end, Gamma8[Red(color)], Gamma8[Green(color)], Gamma8[Blue(color)]); + NeoPixel_Set_Range(NEOPIXEL_CHANNEL_EFFECTS, start, end, Gamma8[Red(color)], Gamma8[Green(color)], Gamma8[Blue(color)]); +#else // (CONFIG_KTAG_N_NEOPIXEL_CHANNELS > 1) + NeoPixel_Set_Range(NEOPIXEL_CHANNEL_BARREL, start, end, Gamma8[Red(color)], Gamma8[Green(color)], Gamma8[Blue(color)]); +#endif // (CONFIG_KTAG_N_NEOPIXEL_CHANNELS > 1) +} + +static inline __attribute__((always_inline)) DisplayStyle_T NeoPixels_ToDisplayStyle(void * value) +{ + DisplayStyle_T result = FIRST_DISPLAY_STYLE; + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" + if (((DisplayStyle_T)value >= FIRST_DISPLAY_STYLE) && ((DisplayStyle_T)value <= LAST_DISPLAY_STYLE)) + { + result = (DisplayStyle_T)value; + } +#pragma GCC diagnostic pop + + return result; +} + +#endif // NEOPIXELS_H diff --git a/NeoPixels/Sine.c b/NeoPixels/Sine.c new file mode 100644 index 0000000..a649a6a --- /dev/null +++ b/NeoPixels/Sine.c @@ -0,0 +1,42 @@ + +/* + * 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 "Sine.h" + +const uint_fast8_t Sine8[] = { + 0, 0, 0, 0, 1, 1, 1, 2, 2, 3, 4, 5, 5, 6, 7, 9, + 10, 11, 12, 14, 15, 17, 18, 20, 21, 23, 25, 27, 29, 31, 33, 35, + 37, 40, 42, 44, 47, 49, 52, 54, 57, 59, 62, 65, 67, 70, 73, 76, + 79, 82, 85, 88, 90, 93, 97,100,103,106,109,112,115,118,121,124, + 128,131,134,137,140,143,146,149,152,155,158,162,165,167,170,173, + 176,179,182,185,188,190,193,196,198,201,203,206,208,211,213,215, + 218,220,222,224,226,228,230,232,234,235,237,238,240,241,243,244, + 245,246,248,249,250,250,251,252,253,253,254,254,254,255,255,255, + 255,255,255,255,254,254,254,253,253,252,251,250,250,249,248,246, + 245,244,243,241,240,238,237,235,234,232,230,228,226,224,222,220, + 218,215,213,211,208,206,203,201,198,196,193,190,188,185,182,179, + 176,173,170,167,165,162,158,155,152,149,146,143,140,137,134,131, + 128,124,121,118,115,112,109,106,103,100, 97, 93, 90, 88, 85, 82, + 79, 76, 73, 70, 67, 65, 62, 59, 57, 54, 52, 49, 47, 44, 42, 40, + 37, 35, 33, 31, 29, 27, 25, 23, 21, 20, 18, 17, 15, 14, 12, 11, + 10, 9, 7, 6, 5, 5, 4, 3, 2, 2, 1, 1, 1, 0, 0, 0}; diff --git a/NeoPixels/Sine.h b/NeoPixels/Sine.h new file mode 100644 index 0000000..2fad7f6 --- /dev/null +++ b/NeoPixels/Sine.h @@ -0,0 +1,28 @@ + +/* + * 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 . + */ + +#ifndef SINE_H +#define SINE_H + +extern const uint_fast8_t Sine8[]; + +#endif // SINE_H diff --git a/NeoPixels/Single_Channel_Helpers.h b/NeoPixels/Single_Channel_Helpers.h new file mode 100644 index 0000000..be8d44f --- /dev/null +++ b/NeoPixels/Single_Channel_Helpers.h @@ -0,0 +1,87 @@ + +/* + * 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 . + */ + + + +// '||' || '|| '||''|. TM +// || ... || || || ... .. ... ... .... ... +// || || || ||'''|. ||' '' || || '|. | +// || || || || || || || || '|.| +// .||.....| .||. .||. .||...|' .||. '|..'|. '| + + + +//! Zero-based index of the Barrel Flash NeoPixel in the sequence of NeoPixels. +#define BARREL_FLASH_PIXEL 0 +//! Zero-based index of the Heart NeoPixel in the sequence of NeoPixels. +#define HEART_PIXEL 1 +//! Zero-based index of the Square NeoPixel in the sequence of NeoPixels. +#define SQUARE_PIXEL 2 +//! Zero-based index of the Circle NeoPixel in the sequence of NeoPixels. +#define CIRCLE_PIXEL 3 +//! Zero-based index of the Arrow NeoPixel in the sequence of NeoPixels. +#define ARROW_PIXEL 4 + +//! Sets the color of the Barrel Flash NeoPixel, applying gamma correction. +/*! + * \note The diffused 5mm LEDs (from AdaFruit and Ali Express) for barrel flash have a different color order (RGB vs. GRB)! + */ +static inline __attribute__((always_inline)) void Set_Barrel_Flash(color_t color) +{ + HW_NeoPixels_Set_RGB(NEOPIXEL_CHANNEL_BARREL, BARREL_FLASH_PIXEL, Gamma8[Red(color)], Gamma8[Green(color)], Gamma8[Blue(color)]); +} + +//! Sets the color of the Heart NeoPixel, applying gamma correction. +/*! + * \note The [WS2812B NeoPixels](https://cdn-shop.adafruit.com/datasheets/WS2812B.pdf) use a GRB color order. + */ +static inline __attribute__((always_inline)) void Set_Heart(color_t color) +{ + HW_NeoPixels_Set_RGB(NEOPIXEL_CHANNEL_BARREL, HEART_PIXEL, Gamma8[Green(color)], Gamma8[Red(color)], Gamma8[Blue(color)]); +} + +//! Sets the color of the Square NeoPixel, applying gamma correction. +/*! + * \note The [WS2812B NeoPixels](https://cdn-shop.adafruit.com/datasheets/WS2812B.pdf) use a GRB color order. + */ +static inline __attribute__((always_inline)) void Set_Square(color_t color) +{ + HW_NeoPixels_Set_RGB(NEOPIXEL_CHANNEL_BARREL, SQUARE_PIXEL, Gamma8[Green(color)], Gamma8[Red(color)], Gamma8[Blue(color)]); +} + +//! Sets the color of the Circle NeoPixel, applying gamma correction. +/*! + * \note The [WS2812B NeoPixels](https://cdn-shop.adafruit.com/datasheets/WS2812B.pdf) use a GRB color order. + */ +static inline __attribute__((always_inline)) void Set_Circle(color_t color) +{ + HW_NeoPixels_Set_RGB(NEOPIXEL_CHANNEL_BARREL, CIRCLE_PIXEL, Gamma8[Green(color)], Gamma8[Red(color)], Gamma8[Blue(color)]); +} + +//! Sets the color of the Arrow NeoPixel, applying gamma correction. +/*! + * \note The [WS2812B NeoPixels](https://cdn-shop.adafruit.com/datasheets/WS2812B.pdf) use a GRB color order. + */ +static inline __attribute__((always_inline)) void Set_Arrow(color_t color) +{ + HW_NeoPixels_Set_RGB(NEOPIXEL_CHANNEL_BARREL, ARROW_PIXEL, Gamma8[Green(color)], Gamma8[Red(color)], Gamma8[Blue(color)]); +} diff --git a/Protocols/Dubuque.c b/Protocols/Dubuque.c new file mode 100644 index 0000000..ba8c482 --- /dev/null +++ b/Protocols/Dubuque.c @@ -0,0 +1,325 @@ +/* + * This program source code file is part of SystemK, a library in the KTag project. + * + * 🛡️ 🃞 + * + * Copyright © 2024-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 "SystemK.h" + +#define DBQ_PURPLE_TEAM 0b00 +#define DBQ_RED_TEAM 0b01 +#define DBQ_BLUE_TEAM 0b10 +#define DBQ_WHITE_TEAM 0b11 + +#define DBQ_ZERO_MARK_DURATION_IN_us 400 +#define DBQ_ZERO_SPACE_DURATION_IN_us 800 +#define DBQ_ONE_MARK_DURATION_IN_us 800 +#define DBQ_ONE_SPACE_DURATION_IN_us 400 +#define DBQ_TOLERANCE_IN_us 199 + +#define DBQ_PURPLE_TX_GAP_IN_us 2600 +#define DBQ_RED_TX_GAP_IN_us 5000 +#define DBQ_BLUE_TX_GAP_IN_us 7400 +#define DBQ_WHITE_TX_GAP_IN_us 9800 + +#define MIN_ZERO_MARK_IN_us (DBQ_ZERO_MARK_DURATION_IN_us - DBQ_TOLERANCE_IN_us) +#define MAX_ZERO_MARK_IN_us (DBQ_ZERO_MARK_DURATION_IN_us + DBQ_TOLERANCE_IN_us) +#define MIN_ZERO_SPACE_IN_us (DBQ_ZERO_SPACE_DURATION_IN_us - DBQ_TOLERANCE_IN_us) +#define MAX_ZERO_SPACE_IN_us (DBQ_ZERO_SPACE_DURATION_IN_us + DBQ_TOLERANCE_IN_us) +#define MIN_ONE_MARK_IN_us (DBQ_ONE_MARK_DURATION_IN_us - DBQ_TOLERANCE_IN_us) +#define MAX_ONE_MARK_IN_us (DBQ_ONE_MARK_DURATION_IN_us + DBQ_TOLERANCE_IN_us) +#define MIN_ONE_SPACE_IN_us (DBQ_ONE_SPACE_DURATION_IN_us - DBQ_TOLERANCE_IN_us) +#define MAX_ONE_SPACE_IN_us (DBQ_ONE_SPACE_DURATION_IN_us + DBQ_TOLERANCE_IN_us) + +static const TimedBit_T DBQ_ZERO_MARK = {.symbol = MARK, .duration = DBQ_ZERO_MARK_DURATION_IN_us}; +static const TimedBit_T DBQ_ONE_MARK = {.symbol = MARK, .duration = DBQ_ONE_MARK_DURATION_IN_us}; +static const TimedBit_T DBQ_ZERO_SPACE = {.symbol = SPACE, .duration = DBQ_ZERO_SPACE_DURATION_IN_us}; +static const TimedBit_T DBQ_ONE_SPACE = {.symbol = SPACE, .duration = DBQ_ONE_SPACE_DURATION_IN_us}; +static const TimedBit_T DBQ_PURPLE_GAP = {.symbol = SPACE, .duration = DBQ_ZERO_SPACE_DURATION_IN_us + DBQ_PURPLE_TX_GAP_IN_us}; +static const TimedBit_T DBQ_RED_GAP = {.symbol = SPACE, .duration = DBQ_ONE_SPACE_DURATION_IN_us + DBQ_RED_TX_GAP_IN_us}; +static const TimedBit_T DBQ_BLUE_GAP = {.symbol = SPACE, .duration = DBQ_ZERO_SPACE_DURATION_IN_us + DBQ_BLUE_TX_GAP_IN_us}; +static const TimedBit_T DBQ_WHITE_GAP = {.symbol = SPACE, .duration = DBQ_ONE_SPACE_DURATION_IN_us + DBQ_WHITE_TX_GAP_IN_us}; +static const TimedBit_T DBQ_END = {.symbol = SPACE, .duration = LAST_PULSE}; + +static TimedPulseTrain_T Tag_Send_Buffer; +static DecodedPacket_T DBQ_Tag_Received_Buffers[3] = +{ + {.Tag.type = DECODED_PACKET_TYPE_BUFFER_FREE}, + {.Tag.type = DECODED_PACKET_TYPE_BUFFER_FREE}, + {.Tag.type = DECODED_PACKET_TYPE_BUFFER_FREE} +}; + +static uint_fast16_t Team_To_Ignore = DBQ_WHITE_TEAM; + +static inline DecodedPacket_T *Get_Tag_Packet_Buffer(void) +{ + if (DBQ_Tag_Received_Buffers[0].Tag.type == DECODED_PACKET_TYPE_BUFFER_FREE) + { + return &DBQ_Tag_Received_Buffers[0]; + } + else if (DBQ_Tag_Received_Buffers[1].Tag.type == DECODED_PACKET_TYPE_BUFFER_FREE) + { + return &DBQ_Tag_Received_Buffers[1]; + } + else + { + // Just use it. + return &DBQ_Tag_Received_Buffers[2]; + } +} + +TimedPulseTrain_T *DBQ_EncodePacket(TagPacket_T *packet) +{ + if (packet->team_ID == DBQ_PURPLE_TEAM) + { + Tag_Send_Buffer.bitstream[0] = DBQ_ZERO_MARK; + Tag_Send_Buffer.bitstream[1] = DBQ_ZERO_SPACE; + Tag_Send_Buffer.bitstream[2] = DBQ_ZERO_MARK; + Tag_Send_Buffer.bitstream[3] = DBQ_PURPLE_GAP; + Tag_Send_Buffer.bitstream[4] = DBQ_ZERO_MARK; + Tag_Send_Buffer.bitstream[5] = DBQ_ZERO_SPACE; + Tag_Send_Buffer.bitstream[6] = DBQ_ZERO_MARK; + Tag_Send_Buffer.bitstream[7] = DBQ_ZERO_SPACE; + Tag_Send_Buffer.bitstream[8] = DBQ_END; + } + else if (packet->team_ID == DBQ_RED_TEAM) + { + Team_To_Ignore = DBQ_RED_TEAM; + + Tag_Send_Buffer.bitstream[0] = DBQ_ZERO_MARK; + Tag_Send_Buffer.bitstream[1] = DBQ_ZERO_SPACE; + Tag_Send_Buffer.bitstream[2] = DBQ_ONE_MARK; + Tag_Send_Buffer.bitstream[3] = DBQ_RED_GAP; + Tag_Send_Buffer.bitstream[4] = DBQ_ZERO_MARK; + Tag_Send_Buffer.bitstream[5] = DBQ_ZERO_SPACE; + Tag_Send_Buffer.bitstream[6] = DBQ_ONE_MARK; + Tag_Send_Buffer.bitstream[7] = DBQ_ONE_SPACE; + Tag_Send_Buffer.bitstream[8] = DBQ_END; + } + else if (packet->team_ID == DBQ_BLUE_TEAM) + { + Team_To_Ignore = DBQ_BLUE_TEAM; + + Tag_Send_Buffer.bitstream[0] = DBQ_ONE_MARK; + Tag_Send_Buffer.bitstream[1] = DBQ_ONE_SPACE; + Tag_Send_Buffer.bitstream[2] = DBQ_ZERO_MARK; + Tag_Send_Buffer.bitstream[3] = DBQ_BLUE_GAP; + Tag_Send_Buffer.bitstream[4] = DBQ_ONE_MARK; + Tag_Send_Buffer.bitstream[5] = DBQ_ONE_SPACE; + Tag_Send_Buffer.bitstream[6] = DBQ_ZERO_MARK; + Tag_Send_Buffer.bitstream[7] = DBQ_ZERO_SPACE; + Tag_Send_Buffer.bitstream[8] = DBQ_END; + } + else // if (packet->team_ID == DBQ_WHITE_TEAM) + { + Tag_Send_Buffer.bitstream[0] = DBQ_ONE_MARK; + Tag_Send_Buffer.bitstream[1] = DBQ_ONE_SPACE; + Tag_Send_Buffer.bitstream[2] = DBQ_ONE_MARK; + Tag_Send_Buffer.bitstream[3] = DBQ_WHITE_GAP; + Tag_Send_Buffer.bitstream[4] = DBQ_ONE_MARK; + Tag_Send_Buffer.bitstream[5] = DBQ_ONE_SPACE; + Tag_Send_Buffer.bitstream[6] = DBQ_ONE_MARK; + Tag_Send_Buffer.bitstream[7] = DBQ_ONE_SPACE; + Tag_Send_Buffer.bitstream[8] = DBQ_END; + } + + Tag_Send_Buffer.count = 8; + + return &Tag_Send_Buffer; +} + +DecodedPacket_T *DBQ_MaybeDecodePacket(TimedPulseTrain_T *packet) +{ + uint_fast8_t decoded_data = UINT_FAST8_MAX; + uint_fast8_t consecutive_bits = 0; + uint_fast8_t ignored_data = UINT_FAST8_MAX; + + // Special handling for the purple team. + uint_fast16_t collisions = 0; + uint_fast16_t purple_completions = 0; + + // Loop through all the pulses, looking for a match. + for (uint8_t n = 0; n < packet->count; n += 2) + { + // Even pulses are marks; odd pulses are spaces. + if (consecutive_bits == 0) + { + // Perform full checking on the first bit. + if ((packet->bitstream[n].duration > MIN_ONE_MARK_IN_us) && (packet->bitstream[n].duration < MAX_ONE_MARK_IN_us) && + (packet->bitstream[n + 1].duration > MIN_ONE_SPACE_IN_us) && (packet->bitstream[n + 1].duration < MAX_ONE_SPACE_IN_us)) + { + // This is a "one". + decoded_data = 2; + consecutive_bits = 1; + } + else if ((packet->bitstream[n].duration > MIN_ZERO_MARK_IN_us) && (packet->bitstream[n].duration < MAX_ZERO_MARK_IN_us) && + (packet->bitstream[n + 1].duration > MIN_ZERO_SPACE_IN_us) && (packet->bitstream[n + 1].duration < MAX_ZERO_SPACE_IN_us)) + { + // This is a "zero". + decoded_data = 0; + consecutive_bits = 1; + } + else + { + // This mark/space combination is neither a zero or a one; continue. + collisions++; + } + } + else // consecutive_bits > 0 + { + // One the second bit, full check the mark, and make sure the space is long enough. + if ((packet->bitstream[n].duration > MIN_ONE_MARK_IN_us) && (packet->bitstream[n].duration < MAX_ONE_MARK_IN_us) && + ((packet->bitstream[n + 1].duration > MIN_ONE_SPACE_IN_us) || (packet->bitstream[n + 1].duration == LAST_PULSE))) + { + // This is a "one". + decoded_data += 1; + + if (decoded_data == Team_To_Ignore) + { + // We received a packet we should ignore. Remember this, and keep looking. + ignored_data = decoded_data; + decoded_data = UINT_FAST8_MAX; + consecutive_bits = 0; + } + else if (decoded_data == DBQ_PURPLE_TEAM) + { + // We received a purple packet. Remember this, and keep looking. + purple_completions++; + decoded_data = UINT_FAST8_MAX; + consecutive_bits = 0; + } + else + { + break; + } + } + else if ((packet->bitstream[n].duration > MIN_ZERO_MARK_IN_us) && (packet->bitstream[n].duration < MAX_ZERO_MARK_IN_us) && + ((packet->bitstream[n + 1].duration > MIN_ZERO_SPACE_IN_us) || (packet->bitstream[n + 1].duration == LAST_PULSE))) + { + // This is a "zero". + decoded_data += 0; + + if (decoded_data == Team_To_Ignore) + { + // We received a packet we should ignore. Remember this, and keep looking. + ignored_data = decoded_data; + decoded_data = UINT_FAST8_MAX; + consecutive_bits = 0; + } + else if (decoded_data == DBQ_PURPLE_TEAM) + { + // We received a purple packet. Remember this, and keep looking. + purple_completions++; + decoded_data = UINT_FAST8_MAX; + consecutive_bits = 0; + } + else + { + break; + } + } + else + { + // This mark/space combination is neither a zero or a one; reset. + collisions++; + decoded_data = UINT_FAST8_MAX; + consecutive_bits = 0; + } + } + } + + // If we ignored the only data we received, restore it here. + if ((ignored_data != UINT_FAST8_MAX) && (decoded_data == UINT_FAST8_MAX)) + { + decoded_data = ignored_data; + } + + if (purple_completions > 0) + { + DecodedPacket_T *Tag_Rx_Buffer = Get_Tag_Packet_Buffer(); + Tag_Rx_Buffer->Tag.type = DECODED_PACKET_TYPE_TAG_RECEIVED; + Tag_Rx_Buffer->Tag.protocol = DUBUQUE_PROTOCOL; + Tag_Rx_Buffer->Tag.player_ID = 0; + Tag_Rx_Buffer->Tag.team_ID = DBQ_PURPLE_TEAM; + Tag_Rx_Buffer->Tag.damage = 10 * (purple_completions + collisions); + Tag_Rx_Buffer->Tag.color = COLOR_PURPLE; + return Tag_Rx_Buffer; + } + else if (decoded_data == DBQ_RED_TEAM) + { + DecodedPacket_T *Tag_Rx_Buffer = Get_Tag_Packet_Buffer(); + Tag_Rx_Buffer->Tag.type = DECODED_PACKET_TYPE_TAG_RECEIVED; + Tag_Rx_Buffer->Tag.protocol = DUBUQUE_PROTOCOL; + Tag_Rx_Buffer->Tag.player_ID = 0; + Tag_Rx_Buffer->Tag.team_ID = DBQ_RED_TEAM; + Tag_Rx_Buffer->Tag.damage = 10; + Tag_Rx_Buffer->Tag.color = COLOR_RED; + return Tag_Rx_Buffer; + } + else if (decoded_data == DBQ_BLUE_TEAM) + { + DecodedPacket_T *Tag_Rx_Buffer = Get_Tag_Packet_Buffer(); + Tag_Rx_Buffer->Tag.type = DECODED_PACKET_TYPE_TAG_RECEIVED; + Tag_Rx_Buffer->Tag.protocol = DUBUQUE_PROTOCOL; + Tag_Rx_Buffer->Tag.player_ID = 0; + Tag_Rx_Buffer->Tag.team_ID = DBQ_BLUE_TEAM; + Tag_Rx_Buffer->Tag.damage = 10; + Tag_Rx_Buffer->Tag.color = COLOR_BLUE; + return Tag_Rx_Buffer; + } + else if (decoded_data == DBQ_WHITE_TEAM) + { + DecodedPacket_T *Tag_Rx_Buffer = Get_Tag_Packet_Buffer(); + Tag_Rx_Buffer->Tag.type = DECODED_PACKET_TYPE_TAG_RECEIVED; + Tag_Rx_Buffer->Tag.protocol = DUBUQUE_PROTOCOL; + Tag_Rx_Buffer->Tag.player_ID = 0; + Tag_Rx_Buffer->Tag.team_ID = DBQ_WHITE_TEAM; + Tag_Rx_Buffer->Tag.damage = 10; + Tag_Rx_Buffer->Tag.color = COLOR_WHITE; + return Tag_Rx_Buffer; + } + + return NULL; +} + +color_t DBQ_GetTeamColor(uint8_t team_ID) +{ + color_t result = COLOR_PURPLE; + + if (team_ID == DBQ_RED_TEAM) + { + result = COLOR_RED; + } + else if (team_ID == DBQ_BLUE_TEAM) + { + result = COLOR_BLUE; + } + else if (team_ID == DBQ_WHITE_TEAM) + { + result = COLOR_WHITE; + } + else + { + // Nothing to do. + } + + return result; +} diff --git a/Protocols/Dubuque.h b/Protocols/Dubuque.h new file mode 100644 index 0000000..63e65ba --- /dev/null +++ b/Protocols/Dubuque.h @@ -0,0 +1,39 @@ +/* + * This program source code file is part of SystemK, a library in the KTag project. + * + * 🛡️ 🃞 + * + * Copyright © 2024-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 . + */ + +#ifndef DUBUQUE_H +#define DUBUQUE_H + +/** \file + * \brief This is the Dubuque protocol. + * + * \note The Dubuque protocol uses a 38kHz carrier for On-Off Keying. + * + */ + +#include +#include + +TimedPulseTrain_T *DBQ_EncodePacket(TagPacket_T *packet); +DecodedPacket_T *DBQ_MaybeDecodePacket(TimedPulseTrain_T *packet); +color_t DBQ_GetTeamColor(uint8_t team_ID); + +#endif // DUBUQUE_H diff --git a/Protocols/Dynasty.c b/Protocols/Dynasty.c new file mode 100755 index 0000000..269fa38 --- /dev/null +++ b/Protocols/Dynasty.c @@ -0,0 +1,214 @@ + +/* + * 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 + +#include "SystemK.h" + +#define DYNASTY_BLUE_TEAM 0x01 +#define DYNASTY_RED_TEAM 0x02 +#define DYNASTY_GREEN_TEAM 0x03 +#define DYNASTY_WHITE_TEAM 0x04 + +#define DYNASTY_HEADER_MARK_DURATION_IN_us 1700 +#define DYNASTY_ZERO_DURATION_IN_us 450 +#define DYNASTY_ONE_DURATION_IN_us 800 +#define DYNASTY_TOLERANCE_IN_us 150 + +#define MIN_ZERO_IN_us (DYNASTY_ZERO_DURATION_IN_us - DYNASTY_TOLERANCE_IN_us) +#define MAX_ZERO_IN_us (DYNASTY_ZERO_DURATION_IN_us + DYNASTY_TOLERANCE_IN_us) +#define MIN_ONE_IN_us (DYNASTY_ONE_DURATION_IN_us - DYNASTY_TOLERANCE_IN_us) +#define MAX_ONE_IN_us (DYNASTY_ONE_DURATION_IN_us + DYNASTY_TOLERANCE_IN_us) + +static const TimedBit_T DYNASTY_HEADER_MARK = {.symbol = MARK, .duration = DYNASTY_HEADER_MARK_DURATION_IN_us}; +static const TimedBit_T DYNASTY_ZERO_SPACE = {.symbol = SPACE, .duration = DYNASTY_ZERO_DURATION_IN_us}; +static const TimedBit_T DYNASTY_ONE_SPACE = {.symbol = SPACE, .duration = DYNASTY_ONE_DURATION_IN_us}; +static const TimedBit_T DYNASTY_ZERO_MARK = {.symbol = MARK, .duration = DYNASTY_ZERO_DURATION_IN_us}; +static const TimedBit_T DYNASTY_ONE_MARK = {.symbol = MARK, .duration = DYNASTY_ONE_DURATION_IN_us}; +static const TimedBit_T DYNASTY_END = {.symbol = SPACE, .duration = LAST_PULSE}; + +static TimedPulseTrain_T Tag_Send_Buffer; +static DecodedPacket_T Dynasty_Tag_Received_Buffers[3] = + { + {.Tag.type = DECODED_PACKET_TYPE_BUFFER_FREE}, + {.Tag.type = DECODED_PACKET_TYPE_BUFFER_FREE}, + {.Tag.type = DECODED_PACKET_TYPE_BUFFER_FREE}}; + +static inline DecodedPacket_T *Get_Tag_Packet_Buffer(void) +{ + if (Dynasty_Tag_Received_Buffers[0].Tag.type == DECODED_PACKET_TYPE_BUFFER_FREE) + { + return &Dynasty_Tag_Received_Buffers[0]; + } + else if (Dynasty_Tag_Received_Buffers[1].Tag.type == DECODED_PACKET_TYPE_BUFFER_FREE) + { + return &Dynasty_Tag_Received_Buffers[1]; + } + else + { + // Just use it. + return &Dynasty_Tag_Received_Buffers[2]; + } +} + +static inline uint8_t Calculate_Checksum(uint32_t data) +{ + uint8_t result = 0; + + // uint8_t byte_1 = (uint8_t)((data >> 8) & 0xFF); + // uint8_t byte_0 = (uint8_t)(data & 0xFF); + + // TODO: Finish implementing the Dynasty checksum. + + return result; +} + +static inline void PackPulseTrain(TimedPulseTrain_T *pulsetrain, uint32_t data) +{ + pulsetrain->bitstream[0] = DYNASTY_HEADER_MARK; + + for (uint8_t n = 0; n < 16; n++) + { + if ((data & 0x0001) == 0x0001) + { + pulsetrain->bitstream[1 + (n * 2)] = DYNASTY_ONE_SPACE; + } + else + { + pulsetrain->bitstream[1 + (n * 2)] = DYNASTY_ZERO_SPACE; + } + data = data >> 1; + if ((data & 0x0001) == 0x0001) + { + pulsetrain->bitstream[1 + (n * 2)] = DYNASTY_ONE_MARK; + } + else + { + pulsetrain->bitstream[1 + (n * 2)] = DYNASTY_ZERO_MARK; + } + data = data >> 1; + } + + // TODO: Append the Dynasty checksum. + + pulsetrain->bitstream[(2 * 20) - 1] = DYNASTY_END; +} + +TimedPulseTrain_T *DYNASTY_EncodePacket(TagPacket_T *packet) +{ + uint16_t packed_data = 0; + + PackPulseTrain(&Tag_Send_Buffer, packed_data); + + return &Tag_Send_Buffer; +} + +DecodedPacket_T *DYNASTY_MaybeDecodePacket(TimedPulseTrain_T *packet) +{ + // Some implementations (ESP32) do not report odd numbers of pulses. + if ((packet->count != 41) && (packet->count != 42)) + { + // Fail fast! + return NULL; + } + else + { + // Convert pulse durations to bits. + // packet->bitstream[0] is the header mark--ignore it. + + uint32_t data = 0; + for (uint8_t n = 0; n < 32; n++) + { + if ((packet->bitstream[n + 1].duration > MIN_ONE_IN_us) && (packet->bitstream[n + 1].duration < MAX_ONE_IN_us)) + { + // One + data |= ((uint32_t)1 << (31 - n)); + } + else if ((packet->bitstream[n + 1].duration > MIN_ZERO_IN_us) && (packet->bitstream[n + 1].duration < MAX_ZERO_IN_us)) + { + // Zero--nothing to do. + } + else + { + // This mark is neither a zero or a one; abort. + return NULL; + } + } + + uint8_t checksum = 0; + for (uint8_t n = 0; n < 8; n++) + { + if ((packet->bitstream[n + 33].duration > MIN_ONE_IN_us) && (packet->bitstream[n + 33].duration < MAX_ONE_IN_us)) + { + // One + checksum |= ((uint8_t)1 << (7 - n)); + } + else if ((packet->bitstream[n + 33].duration > MIN_ZERO_IN_us) && (packet->bitstream[n + 33].duration < MAX_ZERO_IN_us)) + { + // Zero--nothing to do. + } + else + { + // This mark is neither a zero or a one; abort. + return NULL; + } + } + + DecodedPacket_T *Tag_Rx_Buffer = Get_Tag_Packet_Buffer(); + Tag_Rx_Buffer->Tag.type = DECODED_PACKET_TYPE_TAG_RECEIVED; + Tag_Rx_Buffer->Tag.protocol = DYNASTY_PROTOCOL; + Tag_Rx_Buffer->Tag.player_ID = 0; + Tag_Rx_Buffer->Tag.team_ID = DYNASTY_RED_TEAM; + Tag_Rx_Buffer->Tag.damage = 10; + Tag_Rx_Buffer->Tag.color = COLOR_RED; + return Tag_Rx_Buffer; + } +} + +color_t DYNASTY_GetTeamColor(uint8_t team_ID) +{ + color_t result = COLOR_RED; + + if (team_ID == DYNASTY_RED_TEAM) + { + result = COLOR_RED; + } + else if (team_ID == DYNASTY_BLUE_TEAM) + { + result = COLOR_BLUE; + } + else if (team_ID == DYNASTY_GREEN_TEAM) + { + result = COLOR_GREEN; + } + else if (team_ID == DYNASTY_WHITE_TEAM) + { + result = COLOR_WHITE; + } + else + { + // Nothing to do. + } + + return result; +} diff --git a/Protocols/Dynasty.h b/Protocols/Dynasty.h new file mode 100755 index 0000000..c2fcfc5 --- /dev/null +++ b/Protocols/Dynasty.h @@ -0,0 +1,44 @@ + +/* + * 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 . + */ + +#ifndef DYNASTY_H +#define DYNASTY_H + +/** \file + * \brief This is the Dynasty protocol. + * + * \note These devices use a 38kHz carrier. The modulation scheme is a little more complicated + * than on-off keying: the width of each mark or space represents one bit of data. This + * results in a more time-efficient data packet. + * + * https://dynastytoys.co/ + * + */ + +#include +#include + +TimedPulseTrain_T *DYNASTY_EncodePacket(TagPacket_T *packet); +DecodedPacket_T *DYNASTY_MaybeDecodePacket(TimedPulseTrain_T *packet); +color_t DYNASTY_GetTeamColor(uint8_t team_ID); + +#endif // DYNASTY_H diff --git a/Protocols/Laser_X.c b/Protocols/Laser_X.c new file mode 100755 index 0000000..4ff5087 --- /dev/null +++ b/Protocols/Laser_X.c @@ -0,0 +1,212 @@ + +/* + * 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 + +#include "SystemK.h" + +#define LASER_X_PURPLE_TEAM 0b00 +#define LASER_X_RED_TEAM 0b01 +#define LASER_X_BLUE_TEAM 0b10 + +#define RED_TEAM_PACKET_DATA 0x4A +#define BLUE_TEAM_PACKET_DATA 0x8A +#define PURPLE_TEAM_PACKET_DATA 0xCA + +#define LASER_X_HEADER_MARK_DURATION_IN_us 6500 +#define LASER_X_SPACE_DURATION_IN_us 500 +#define LASER_X_MARK_ZERO_DURATION_IN_us 500 +#define LASER_X_MARK_ONE_DURATION_IN_us 1500 +#define LASER_X_TOLERANCE_IN_us 450 + +#define MIN_MARK_ZERO_IN_us (LASER_X_MARK_ZERO_DURATION_IN_us - LASER_X_TOLERANCE_IN_us) +#define MAX_MARK_ZERO_IN_us (LASER_X_MARK_ZERO_DURATION_IN_us + LASER_X_TOLERANCE_IN_us) +#define MIN_MARK_ONE_IN_us (LASER_X_MARK_ONE_DURATION_IN_us - LASER_X_TOLERANCE_IN_us) +#define MAX_MARK_ONE_IN_us (LASER_X_MARK_ONE_DURATION_IN_us + LASER_X_TOLERANCE_IN_us) + +static const TimedBit_T LASER_X_HEADER_MARK = {.symbol = MARK, .duration = LASER_X_HEADER_MARK_DURATION_IN_us}; +static const TimedBit_T LASER_X_SPACE = {.symbol = SPACE, .duration = LASER_X_SPACE_DURATION_IN_us}; +static const TimedBit_T LASER_X_ONE = {.symbol = MARK, .duration = LASER_X_MARK_ONE_DURATION_IN_us}; +static const TimedBit_T LASER_X_ZERO = {.symbol = MARK, .duration = LASER_X_MARK_ZERO_DURATION_IN_us}; +static const TimedBit_T LASER_X_END = {.symbol = SPACE, .duration = LAST_PULSE}; + +static TimedPulseTrain_T Tag_Send_Buffer; +static DecodedPacket_T Laser_X_Tag_Received_Buffers[3] = + { + {.Tag.type = DECODED_PACKET_TYPE_BUFFER_FREE}, + {.Tag.type = DECODED_PACKET_TYPE_BUFFER_FREE}, + {.Tag.type = DECODED_PACKET_TYPE_BUFFER_FREE}}; + +static inline DecodedPacket_T *Get_Tag_Packet_Buffer(void) +{ + if (Laser_X_Tag_Received_Buffers[0].Tag.type == DECODED_PACKET_TYPE_BUFFER_FREE) + { + return &Laser_X_Tag_Received_Buffers[0]; + } + else if (Laser_X_Tag_Received_Buffers[1].Tag.type == DECODED_PACKET_TYPE_BUFFER_FREE) + { + return &Laser_X_Tag_Received_Buffers[1]; + } + else + { + // Just use it. + return &Laser_X_Tag_Received_Buffers[2]; + } +} + +static inline void PackPulseTrain(TimedPulseTrain_T *pulsetrain, uint8_t data) +{ + pulsetrain->bitstream[0] = LASER_X_HEADER_MARK; + pulsetrain->bitstream[1] = LASER_X_SPACE; + + for (uint8_t n = 0; n < 8; n++) + { + if ((data & 0x01) == 0x01) + { + pulsetrain->bitstream[2 + (n * 2)] = LASER_X_ONE; + } + else + { + pulsetrain->bitstream[2 + (n * 2)] = LASER_X_ZERO; + } + pulsetrain->bitstream[2 + (n * 2) + 1] = LASER_X_SPACE; + data = data >> 1; + } + + pulsetrain->bitstream[17] = LASER_X_END; + pulsetrain->count = 17; +} + +TimedPulseTrain_T *LASER_X_EncodePacket(TagPacket_T *packet) +{ + uint16_t packed_data = 0; + + if (packet->team_ID == LASER_X_RED_TEAM) + { + packed_data = RED_TEAM_PACKET_DATA; + } + else if (packet->team_ID == LASER_X_BLUE_TEAM) + { + packed_data = BLUE_TEAM_PACKET_DATA; + } + else // LASER_X_PURPLE_TEAM + { + packed_data = PURPLE_TEAM_PACKET_DATA; + } + + PackPulseTrain(&Tag_Send_Buffer, packed_data); + + return &Tag_Send_Buffer; +} + +DecodedPacket_T *LASER_X_MaybeDecodePacket(TimedPulseTrain_T *packet) +{ + // Some implementations (ESP32) do not report odd numbers of pulses. + if ((packet->count != 17) && (packet->count != 18)) + { + // Fail fast! + return NULL; + } + else + { + // Convert pulse durations to bits. + // packet->bitstream[0] is the header mark--ignore it. + // packet->bitstream[1] is the header space--ignore it also. + + uint8_t data = 0; + for (uint8_t n = 0; n < 8; n++) + { + // Even pulses are marks; odd pulses are spaces. + if ((packet->bitstream[2 + (n * 2)].duration > MIN_MARK_ONE_IN_us) && (packet->bitstream[2 + (n * 2)].duration < MAX_MARK_ONE_IN_us)) + { + // One + data |= ((uint8_t)1 << n); + } + else if ((packet->bitstream[2 + (n * 2)].duration > MIN_MARK_ZERO_IN_us) && (packet->bitstream[2 + (n * 2)].duration < MAX_MARK_ZERO_IN_us)) + { + // Zero--nothing to do. + } + else + { + // This mark is neither a zero or a one; abort. + return NULL; + } + } + + if (data == RED_TEAM_PACKET_DATA) + { + DecodedPacket_T *Tag_Rx_Buffer = Get_Tag_Packet_Buffer(); + Tag_Rx_Buffer->Tag.type = DECODED_PACKET_TYPE_TAG_RECEIVED; + Tag_Rx_Buffer->Tag.protocol = LASER_X_PROTOCOL; + Tag_Rx_Buffer->Tag.player_ID = 0; + Tag_Rx_Buffer->Tag.team_ID = LASER_X_RED_TEAM; + Tag_Rx_Buffer->Tag.damage = 10; + Tag_Rx_Buffer->Tag.color = COLOR_RED; + return Tag_Rx_Buffer; + } + else if (data == BLUE_TEAM_PACKET_DATA) + { + DecodedPacket_T *Tag_Rx_Buffer = Get_Tag_Packet_Buffer(); + Tag_Rx_Buffer->Tag.type = DECODED_PACKET_TYPE_TAG_RECEIVED; + Tag_Rx_Buffer->Tag.protocol = LASER_X_PROTOCOL; + Tag_Rx_Buffer->Tag.player_ID = 0; + Tag_Rx_Buffer->Tag.team_ID = LASER_X_BLUE_TEAM; + Tag_Rx_Buffer->Tag.damage = 10; + Tag_Rx_Buffer->Tag.color = COLOR_BLUE; + return Tag_Rx_Buffer; + } + else if (data == PURPLE_TEAM_PACKET_DATA) + { + DecodedPacket_T *Tag_Rx_Buffer = Get_Tag_Packet_Buffer(); + Tag_Rx_Buffer->Tag.type = DECODED_PACKET_TYPE_TAG_RECEIVED; + Tag_Rx_Buffer->Tag.protocol = LASER_X_PROTOCOL; + Tag_Rx_Buffer->Tag.player_ID = 0; + Tag_Rx_Buffer->Tag.team_ID = LASER_X_PURPLE_TEAM; + Tag_Rx_Buffer->Tag.damage = 10; + Tag_Rx_Buffer->Tag.color = COLOR_PURPLE; + return Tag_Rx_Buffer; + } + + return NULL; + } +} + +color_t LASER_X_GetTeamColor(uint8_t team_ID) +{ + color_t result = COLOR_PURPLE; + + if (team_ID == LASER_X_RED_TEAM) + { + result = COLOR_RED; + } + else if (team_ID == LASER_X_BLUE_TEAM) + { + result = COLOR_BLUE; + } + else + { + // Nothing to do. + } + + return result; +} diff --git a/Protocols/Laser_X.h b/Protocols/Laser_X.h new file mode 100755 index 0000000..57ec76f --- /dev/null +++ b/Protocols/Laser_X.h @@ -0,0 +1,42 @@ + +/* + * 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 . + */ + +#ifndef LASER_X_H +#define LASER_X_H + +/** \file + * \brief This is the Laser X protocol. + * + * \note These devices use 38kHz carrier for On-Off Keying. + * + * https://www.getlaserx.com/ + * + */ + +#include +#include + +TimedPulseTrain_T *LASER_X_EncodePacket(TagPacket_T *packet); +DecodedPacket_T *LASER_X_MaybeDecodePacket(TimedPulseTrain_T *packet); +color_t LASER_X_GetTeamColor(uint8_t team_ID); + +#endif // LASER_X_H diff --git a/Protocols/Miles_Tag_II.c b/Protocols/Miles_Tag_II.c new file mode 100755 index 0000000..357cbd2 --- /dev/null +++ b/Protocols/Miles_Tag_II.c @@ -0,0 +1,195 @@ + +/* + * 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 + +#include "SystemK.h" + +#define MILES_TAG_RED_TEAM 0b00 +#define MILES_TAG_BLUE_TEAM 0b01 +#define MILES_TAG_YELLOW_TEAM 0b10 +#define MILES_TAG_GREEN_TEAM 0b11 + +#define MILES_TAG_II_HEADER_PULSE_DURATION_IN_us 2400 +#define MILES_TAG_II_SPACE_DURATION_IN_us 600 +#define MILES_TAG_II_MARK_ZERO_DURATION_IN_us 600 +#define MILES_TAG_II_MARK_ONE_DURATION_IN_us 1200 +#define MILES_TAG_II_TOLERANCE_IN_us 300 + +#define MIN_SPACE_IN_us (MILES_TAG_II_SPACE_DURATION_IN_us - MILES_TAG_II_TOLERANCE_IN_us) +#define MAX_SPACE_IN_us (MILES_TAG_II_SPACE_DURATION_IN_us + MILES_TAG_II_TOLERANCE_IN_us) +#define MIN_MARK_ZERO_IN_us (MILES_TAG_II_MARK_ZERO_DURATION_IN_us - MILES_TAG_II_TOLERANCE_IN_us) +#define MAX_MARK_ZERO_IN_us (MILES_TAG_II_MARK_ZERO_DURATION_IN_us + MILES_TAG_II_TOLERANCE_IN_us) +#define MIN_MARK_ONE_IN_us (MILES_TAG_II_MARK_ONE_DURATION_IN_us - MILES_TAG_II_TOLERANCE_IN_us) +#define MAX_MARK_ONE_IN_us (MILES_TAG_II_MARK_ONE_DURATION_IN_us + MILES_TAG_II_TOLERANCE_IN_us) + +static const TimedBit_T MILES_TAG_HEADER = {.symbol = MARK, .duration = MILES_TAG_II_HEADER_PULSE_DURATION_IN_us}; +static const TimedBit_T MILES_TAG_SPACE = {.symbol = SPACE, .duration = MILES_TAG_II_SPACE_DURATION_IN_us}; +static const TimedBit_T MILES_TAG_ONE = {.symbol = MARK, .duration = MILES_TAG_II_MARK_ONE_DURATION_IN_us}; +static const TimedBit_T MILES_TAG_ZERO = {.symbol = MARK, .duration = MILES_TAG_II_MARK_ZERO_DURATION_IN_us}; +static const TimedBit_T MILES_TAG_END = {.symbol = SPACE, .duration = LAST_PULSE}; + +static TimedPulseTrain_T Tag_Send_Buffer; +static DecodedPacket_T Miles_Tag_II_Tag_Received_Buffers[3] = + { + {.Tag.type = DECODED_PACKET_TYPE_BUFFER_FREE}, + {.Tag.type = DECODED_PACKET_TYPE_BUFFER_FREE}, + {.Tag.type = DECODED_PACKET_TYPE_BUFFER_FREE}}; + +static inline DecodedPacket_T *Get_Tag_Packet_Buffer(void) +{ + if (Miles_Tag_II_Tag_Received_Buffers[0].Tag.type == DECODED_PACKET_TYPE_BUFFER_FREE) + { + return &Miles_Tag_II_Tag_Received_Buffers[0]; + } + else if (Miles_Tag_II_Tag_Received_Buffers[1].Tag.type == DECODED_PACKET_TYPE_BUFFER_FREE) + { + return &Miles_Tag_II_Tag_Received_Buffers[1]; + } + else + { + // Just use it. + return &Miles_Tag_II_Tag_Received_Buffers[2]; + } +} + +static const uint16_t Miles_Tag_II_Damage[] = {1, 2, 4, 5, 7, 10, 15, 17, 20, 25, 30, 35, 40, 50, 75, 100}; +static const color_t Miles_Tag_II_Team_Colors[] = {COLOR_RED, COLOR_BLUE, COLOR_YELLOW, COLOR_GREEN}; + +static inline uint8_t Encode_Damage(uint16_t damage) +{ + uint8_t encoded_damage; + + for (encoded_damage = 0; encoded_damage < 16; encoded_damage++) + { + if (damage <= Miles_Tag_II_Damage[encoded_damage]) + { + break; + } + } + return encoded_damage; +} + +// Use the 14 least-significant bits. +static inline void PackPulseTrain(TimedPulseTrain_T *pulsetrain, uint16_t data) +{ + pulsetrain->bitstream[0] = MILES_TAG_HEADER; + pulsetrain->bitstream[1] = MILES_TAG_SPACE; + + for (uint8_t n = 0; n < 14; n++) + { + if ((data & 0x01) == 0x01) + { + pulsetrain->bitstream[2 + (n * 2)] = MILES_TAG_ONE; + } + else + { + pulsetrain->bitstream[2 + (n * 2)] = MILES_TAG_ZERO; + } + pulsetrain->bitstream[2 + (n * 2) + 1] = MILES_TAG_SPACE; + data = data >> 1; + } + + pulsetrain->bitstream[30] = MILES_TAG_END; +} + +TimedPulseTrain_T *MILES_TAG_II_EncodePacket(TagPacket_T *packet) +{ + uint16_t packed_data = Encode_Damage(packet->damage); + packed_data |= (packet->team_ID << 4); + packed_data |= (packet->player_ID << 6); + + PackPulseTrain(&Tag_Send_Buffer, packed_data); + + return &Tag_Send_Buffer; +} + +DecodedPacket_T *MILES_TAG_II_MaybeDecodePacket(TimedPulseTrain_T *packet) +{ + // Some implementations (ESP32) do not report odd numbers of pulses. + if ((packet->count != 29) && (packet->count != 30)) + { + // Fail fast! + return NULL; + } + else + { + // Convert pulse durations to bits. + // packet->bitstream[0] is the header--ignore it. + // packet->bitstream[1] is the space after the header--ignore it also. + uint16_t data = 0; + for (uint8_t n = 0; n < 14; n++) + { + // Even pulses are marks; odd pulses are spaces. + // We only check the marks against the tolerances, to save duration. + if ((packet->bitstream[2 + (n * 2)].duration > MIN_MARK_ONE_IN_us) && (packet->bitstream[2 + (n * 2)].duration < MAX_MARK_ONE_IN_us)) + { + // One + data |= ((uint16_t)1 << n); + } + else if ((packet->bitstream[2 + (n * 2)].duration > MIN_MARK_ZERO_IN_us) && (packet->bitstream[2 + (n * 2)].duration < MAX_MARK_ZERO_IN_us)) + { + // Zero--nothing to do. + } + else + { + // This mark is neither a zero or a one; abort; + return NULL; + } + } + + DecodedPacket_T *Tag_Rx_Buffer = Get_Tag_Packet_Buffer(); + Tag_Rx_Buffer->Tag.type = DECODED_PACKET_TYPE_TAG_RECEIVED; + Tag_Rx_Buffer->Tag.protocol = MILES_TAG_II_PROTOCOL; + Tag_Rx_Buffer->Tag.player_ID = 0x007F & (data >> 6); + Tag_Rx_Buffer->Tag.team_ID = 0x0003 & (data >> 4); + Tag_Rx_Buffer->Tag.damage = Miles_Tag_II_Damage[0x000F & data]; + Tag_Rx_Buffer->Tag.color = Miles_Tag_II_Team_Colors[Tag_Rx_Buffer->Tag.team_ID]; + + return Tag_Rx_Buffer; + } +} + +color_t MILES_TAG_II_GetTeamColor(uint8_t team_ID) +{ + color_t result = COLOR_RED; + + if (team_ID == MILES_TAG_RED_TEAM) + { + // Nothing to do. + } + else if (team_ID == MILES_TAG_BLUE_TEAM) + { + result = COLOR_BLUE; + } + else if (team_ID == MILES_TAG_YELLOW_TEAM) + { + result = COLOR_YELLOW; + } + else if (team_ID == MILES_TAG_GREEN_TEAM) + { + result = COLOR_GREEN; + } + + return result; +} diff --git a/Protocols/Miles_Tag_II.h b/Protocols/Miles_Tag_II.h new file mode 100755 index 0000000..b2e2a66 --- /dev/null +++ b/Protocols/Miles_Tag_II.h @@ -0,0 +1,33 @@ + +/* + * 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 . + */ + +#ifndef MILES_TAG_H +#define MILES_TAG_H + +#include +#include + +TimedPulseTrain_T *MILES_TAG_II_EncodePacket(TagPacket_T *packet); +DecodedPacket_T *MILES_TAG_II_MaybeDecodePacket(TimedPulseTrain_T *packet); +color_t MILES_TAG_II_GetTeamColor(uint8_t team_ID); + +#endif // MILES_TAG_H diff --git a/Protocols/NEC.c b/Protocols/NEC.c new file mode 100755 index 0000000..263fa7e --- /dev/null +++ b/Protocols/NEC.c @@ -0,0 +1,171 @@ + +/* + * 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 + +#include "SystemK.h" + +#define NEC_BLUE_TEAM 0x01 +#define NEC_RED_TEAM 0x02 + +#define NEC_HEADER_MARK_DURATION_IN_us 9000 +#define NEC_HEADER_SPACE_DURATION_IN_us 4500 +#define NEC_ZERO_MARK_DURATION_IN_us 562 +#define NEC_ZERO_SPACE_DURATION_IN_us 562 +#define NEC_ONE_MARK_DURATION_IN_us 562 +#define NEC_ONE_SPACE_DURATION_IN_us 1688 +#define NEC_TOLERANCE_IN_us 200 + +#define MIN_ZERO_MARK_IN_us (NEC_ZERO_MARK_DURATION_IN_us - NEC_TOLERANCE_IN_us) +#define MAX_ZERO_MARK_IN_us (NEC_ZERO_MARK_DURATION_IN_us + NEC_TOLERANCE_IN_us) +#define MIN_ZERO_SPACE_IN_us (NEC_ZERO_SPACE_DURATION_IN_us - NEC_TOLERANCE_IN_us) +#define MAX_ZERO_SPACE_IN_us (NEC_ZERO_SPACE_DURATION_IN_us + NEC_TOLERANCE_IN_us) +#define MIN_ONE_MARK_IN_us (NEC_ONE_MARK_DURATION_IN_us - NEC_TOLERANCE_IN_us) +#define MAX_ONE_MARK_IN_us (NEC_ONE_MARK_DURATION_IN_us + NEC_TOLERANCE_IN_us) +#define MIN_ONE_SPACE_IN_us (NEC_ONE_SPACE_DURATION_IN_us - NEC_TOLERANCE_IN_us) +#define MAX_ONE_SPACE_IN_us (NEC_ONE_SPACE_DURATION_IN_us + NEC_TOLERANCE_IN_us) + +static const TimedBit_T NEC_HEADER_MARK = {.symbol = MARK, .duration = NEC_HEADER_MARK_DURATION_IN_us}; +static const TimedBit_T NEC_HEADER_SPACE = {.symbol = MARK, .duration = NEC_HEADER_SPACE_DURATION_IN_us}; +static const TimedBit_T NEC_ZERO_SPACE = {.symbol = SPACE, .duration = NEC_ZERO_SPACE_DURATION_IN_us}; +static const TimedBit_T NEC_ONE_SPACE = {.symbol = SPACE, .duration = NEC_ONE_SPACE_DURATION_IN_us}; +static const TimedBit_T NEC_ZERO_MARK = {.symbol = MARK, .duration = NEC_ZERO_MARK_DURATION_IN_us}; +static const TimedBit_T NEC_ONE_MARK = {.symbol = MARK, .duration = NEC_ONE_MARK_DURATION_IN_us}; +static const TimedBit_T NEC_END = {.symbol = SPACE, .duration = LAST_PULSE}; + +static TimedPulseTrain_T Command_Send_Buffer; +static DecodedPacket_T NEC_Command_Buffers[3] = + { + {.Command.type = DECODED_PACKET_TYPE_BUFFER_FREE}, + {.Command.type = DECODED_PACKET_TYPE_BUFFER_FREE}, + {.Command.type = DECODED_PACKET_TYPE_BUFFER_FREE}}; + +static inline DecodedPacket_T *Get_Command_Packet_Buffer(void) +{ + if (NEC_Command_Buffers[0].Command.type == DECODED_PACKET_TYPE_BUFFER_FREE) + { + return &NEC_Command_Buffers[0]; + } + else if (NEC_Command_Buffers[1].Command.type == DECODED_PACKET_TYPE_BUFFER_FREE) + { + return &NEC_Command_Buffers[1]; + } + else + { + // Just use it. + return &NEC_Command_Buffers[2]; + } +} + +static inline void PackPulseTrain(TimedPulseTrain_T *pulsetrain, uint32_t data) +{ + pulsetrain->bitstream[0] = NEC_HEADER_MARK; + pulsetrain->bitstream[1] = NEC_HEADER_SPACE; + + for (uint8_t n = 0; n < 16; n++) + { + if ((data & 0x80000000) == 0x80000000) + { + pulsetrain->bitstream[1 + (n * 2)] = NEC_ONE_SPACE; + } + else + { + pulsetrain->bitstream[1 + (n * 2)] = NEC_ZERO_SPACE; + } + data = data << 1; + if ((data & 0x80000000) == 0x80000000) + { + pulsetrain->bitstream[2 + (n * 2)] = NEC_ONE_MARK; + } + else + { + pulsetrain->bitstream[2 + (n * 2)] = NEC_ZERO_MARK; + } + data = data << 1; + } + + pulsetrain->bitstream[(2 * 20) + 1] = NEC_END; +} + +TimedPulseTrain_T *NEC_EncodePacket(CommandPacket_T *packet) +{ + uint32_t packed_data = 0x00000000; + + PackPulseTrain(&Command_Send_Buffer, packed_data); + + return &Command_Send_Buffer; +} + +DecodedPacket_T *NEC_MaybeDecodePacket(TimedPulseTrain_T *packet) +{ + // Sometimes the Roku remote will repeat keys, so only check for a minimum here. + if (packet->count < 67) + { + // Fail fast! + return NULL; + } + else + { + // Convert pulse durations to bits. + // packet->bitstream[0] is the header mark--ignore it. + // packet->bitstream[1] is the header space--ignore it, too. + + uint32_t data = 0; + for (uint8_t n = 0; n < 32; n++) + { + uint8_t mark_index = (n * 2) + 2; + uint8_t space_index = (n * 2) + 3; + + if ((packet->bitstream[mark_index].duration > MIN_ONE_MARK_IN_us) && (packet->bitstream[mark_index].duration < MAX_ONE_MARK_IN_us) && + (packet->bitstream[space_index].duration > MIN_ONE_SPACE_IN_us) && (packet->bitstream[space_index].duration < MAX_ONE_SPACE_IN_us)) + { + // One + data |= ((uint32_t)1 << n); + } + else if ((packet->bitstream[mark_index].duration > MIN_ZERO_MARK_IN_us) && (packet->bitstream[mark_index].duration < MAX_ZERO_MARK_IN_us) && + (packet->bitstream[space_index].duration > MIN_ZERO_SPACE_IN_us) && (packet->bitstream[space_index].duration < MAX_ZERO_SPACE_IN_us)) + { + // Zero--nothing to do. + } + else + { + // This mark is neither a zero or a one; abort. + return NULL; + } + } + + // PulseDurations[66] is the end-of-message mark. It can also be ignored. + DecodedPacket_T *Command_Rx_Buffer = Get_Command_Packet_Buffer(); + Command_Rx_Buffer->Command.type = DECODED_PACKET_TYPE_COMMAND_RECEIVED; + Command_Rx_Buffer->Command.protocol = NEC_PROTOCOL; + Command_Rx_Buffer->Command.data = data; + return Command_Rx_Buffer; + } +} + +color_t NEC_GetTeamColor(uint8_t team_ID) +{ + (void)team_ID; + + return COLOR_BLACK; +} diff --git a/Protocols/NEC.h b/Protocols/NEC.h new file mode 100755 index 0000000..b9626c3 --- /dev/null +++ b/Protocols/NEC.h @@ -0,0 +1,39 @@ + +/* + * 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 . + */ + +#ifndef NEC_H +#define NEC_H + +/** \file + * \brief This is the NEC Infrared Transmission Protocol. + * + * See https://techdocs.altium.com/display/FPGA/NEC+Infrared+Transmission+Protocol for more information. + */ + +#include +#include + +TimedPulseTrain_T *NEC_EncodePacket(CommandPacket_T *packet); +DecodedPacket_T *NEC_MaybeDecodePacket(TimedPulseTrain_T *packet); +color_t NEC_GetTeamColor(uint8_t team_ID); + +#endif // NEC_H diff --git a/Protocols/Nerf_Laser_Ops_Pro.c b/Protocols/Nerf_Laser_Ops_Pro.c new file mode 100755 index 0000000..d38490b --- /dev/null +++ b/Protocols/Nerf_Laser_Ops_Pro.c @@ -0,0 +1,218 @@ + +/* + * 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 + +#include "SystemK.h" + +#define NERF_RED_TEAM 0b00 +#define NERF_BLUE_TEAM 0b01 +#define NERF_PURPLE_TEAM 0b10 + +#define RED_TEAM_PACKET_DATA 0x0010 +#define BLUE_TEAM_PACKET_DATA 0x0210 +#define PURPLE_TEAM_PACKET_DATA 0x0110 + +#define NERF_LASER_OPS_PRO_HEADER_MARK_DURATION_IN_us 3000 +#define NERF_LASER_OPS_PRO_HEADER_SPACE_DURATION_IN_us 6000 +#define NERF_LASER_OPS_PRO_SPACE_DURATION_IN_us 2000 +#define NERF_LASER_OPS_PRO_MARK_ZERO_DURATION_IN_us 800 +#define NERF_LASER_OPS_PRO_MARK_ONE_DURATION_IN_us 2000 +#define NERF_LASER_OPS_PRO_TOLERANCE_IN_us 400 + +#define MIN_SPACE_IN_us (NERF_LASER_OPS_PRO_SPACE_DURATION_IN_us - NERF_LASER_OPS_PRO_TOLERANCE_IN_us) +#define MAX_SPACE_IN_us (NERF_LASER_OPS_PRO_SPACE_DURATION_IN_us + NERF_LASER_OPS_PRO_TOLERANCE_IN_us) +#define MIN_MARK_ZERO_IN_us (NERF_LASER_OPS_PRO_MARK_ZERO_DURATION_IN_us - NERF_LASER_OPS_PRO_TOLERANCE_IN_us) +#define MAX_MARK_ZERO_IN_us (NERF_LASER_OPS_PRO_MARK_ZERO_DURATION_IN_us + NERF_LASER_OPS_PRO_TOLERANCE_IN_us) +#define MIN_MARK_ONE_IN_us (NERF_LASER_OPS_PRO_MARK_ONE_DURATION_IN_us - NERF_LASER_OPS_PRO_TOLERANCE_IN_us) +#define MAX_MARK_ONE_IN_us (NERF_LASER_OPS_PRO_MARK_ONE_DURATION_IN_us + NERF_LASER_OPS_PRO_TOLERANCE_IN_us) + +static const TimedBit_T NERF_LASER_OPS_PRO_HEADER_MARK = {.symbol = MARK, .duration = NERF_LASER_OPS_PRO_HEADER_MARK_DURATION_IN_us}; +static const TimedBit_T NERF_LASER_OPS_PRO_HEADER_SPACE = {.symbol = SPACE, .duration = NERF_LASER_OPS_PRO_HEADER_SPACE_DURATION_IN_us}; +static const TimedBit_T NERF_LASER_OPS_PRO_SPACE = {.symbol = SPACE, .duration = NERF_LASER_OPS_PRO_SPACE_DURATION_IN_us}; +static const TimedBit_T NERF_LASER_OPS_PRO_ONE = {.symbol = MARK, .duration = NERF_LASER_OPS_PRO_MARK_ONE_DURATION_IN_us}; +static const TimedBit_T NERF_LASER_OPS_PRO_ZERO = {.symbol = MARK, .duration = NERF_LASER_OPS_PRO_MARK_ZERO_DURATION_IN_us}; +static const TimedBit_T NERF_LASER_OPS_PRO_END = {.symbol = SPACE, .duration = LAST_PULSE}; + +static TimedPulseTrain_T Tag_Send_Buffer; +static DecodedPacket_T Nerf_Laser_Ops_Pro_Tag_Received_Buffers[3] = + { + {.Tag.type = DECODED_PACKET_TYPE_BUFFER_FREE}, + {.Tag.type = DECODED_PACKET_TYPE_BUFFER_FREE}, + {.Tag.type = DECODED_PACKET_TYPE_BUFFER_FREE}}; + +static inline DecodedPacket_T *Get_Tag_Packet_Buffer(void) +{ + if (Nerf_Laser_Ops_Pro_Tag_Received_Buffers[0].Tag.type == DECODED_PACKET_TYPE_BUFFER_FREE) + { + return &Nerf_Laser_Ops_Pro_Tag_Received_Buffers[0]; + } + else if (Nerf_Laser_Ops_Pro_Tag_Received_Buffers[1].Tag.type == DECODED_PACKET_TYPE_BUFFER_FREE) + { + return &Nerf_Laser_Ops_Pro_Tag_Received_Buffers[1]; + } + else + { + // Just use it. + return &Nerf_Laser_Ops_Pro_Tag_Received_Buffers[2]; + } +} + +static inline void PackPulseTrain(TimedPulseTrain_T *pulsetrain, uint16_t data) +{ + pulsetrain->bitstream[0] = NERF_LASER_OPS_PRO_HEADER_MARK; + pulsetrain->bitstream[1] = NERF_LASER_OPS_PRO_HEADER_SPACE; + pulsetrain->bitstream[2] = NERF_LASER_OPS_PRO_HEADER_MARK; + pulsetrain->bitstream[3] = NERF_LASER_OPS_PRO_SPACE; + + for (uint8_t n = 0; n < 16; n++) + { + if ((data & 0x01) == 0x01) + { + pulsetrain->bitstream[4 + (n * 2)] = NERF_LASER_OPS_PRO_ONE; + } + else + { + pulsetrain->bitstream[4 + (n * 2)] = NERF_LASER_OPS_PRO_ZERO; + } + pulsetrain->bitstream[4 + (n * 2) + 1] = NERF_LASER_OPS_PRO_SPACE; + data = data >> 1; + } + + pulsetrain->bitstream[35] = NERF_LASER_OPS_PRO_END; +} + +TimedPulseTrain_T *NERF_LASER_OPS_PRO_EncodePacket(TagPacket_T *packet) +{ + uint16_t packed_data = 0; + + if (packet->team_ID == NERF_RED_TEAM) + { + packed_data = RED_TEAM_PACKET_DATA; + } + else if (packet->team_ID == NERF_BLUE_TEAM) + { + packed_data = BLUE_TEAM_PACKET_DATA; + } + else // NERF_PURPLE_TEAM + { + packed_data = PURPLE_TEAM_PACKET_DATA; + } + + PackPulseTrain(&Tag_Send_Buffer, packed_data); + + return &Tag_Send_Buffer; +} + +DecodedPacket_T *NERF_LASER_OPS_PRO_MaybeDecodePacket(TimedPulseTrain_T *packet) +{ + // Some implementations (ESP32) do not report odd numbers of pulses. + if ((packet->count != 35) && (packet->count != 36)) + { + // Fail fast! + return NULL; + } + else + { + // Convert pulse durations to bits. + // packet->bitstream[0] is the header mark--ignore it. + // packet->bitstream[1] is the header space--ignore it also. + // packet->bitstream[2] is the second header mark--ignore it as well. + // packet->bitstream[3] is the second header space--also ignored. + uint16_t data = 0; + for (uint8_t n = 0; n < 16; n++) + { + // Even pulses are marks; odd pulses are spaces. + if ((packet->bitstream[4 + (n * 2)].duration > MIN_MARK_ONE_IN_us) && (packet->bitstream[4 + (n * 2)].duration < MAX_MARK_ONE_IN_us)) + { + // One + data |= ((uint16_t)1 << n); + } + else if ((packet->bitstream[4 + (n * 2)].duration > MIN_MARK_ZERO_IN_us) && (packet->bitstream[4 + (n * 2)].duration < MAX_MARK_ZERO_IN_us)) + { + // Zero--nothing to do. + } + else + { + // This mark is neither a zero or a one; abort. + return NULL; + } + } + + if (data == RED_TEAM_PACKET_DATA) + { + DecodedPacket_T *Tag_Rx_Buffer = Get_Tag_Packet_Buffer(); + Tag_Rx_Buffer->Tag.type = DECODED_PACKET_TYPE_TAG_RECEIVED; + Tag_Rx_Buffer->Tag.protocol = NERF_LASER_OPS_PRO_PROTOCOL; + Tag_Rx_Buffer->Tag.player_ID = 0; + Tag_Rx_Buffer->Tag.team_ID = NERF_RED_TEAM; + Tag_Rx_Buffer->Tag.damage = 10; + Tag_Rx_Buffer->Tag.color = COLOR_RED; + return Tag_Rx_Buffer; + } + else if (data == BLUE_TEAM_PACKET_DATA) + { + DecodedPacket_T *Tag_Rx_Buffer = Get_Tag_Packet_Buffer(); + Tag_Rx_Buffer->Tag.type = DECODED_PACKET_TYPE_TAG_RECEIVED; + Tag_Rx_Buffer->Tag.protocol = NERF_LASER_OPS_PRO_PROTOCOL; + Tag_Rx_Buffer->Tag.player_ID = 0; + Tag_Rx_Buffer->Tag.team_ID = NERF_BLUE_TEAM; + Tag_Rx_Buffer->Tag.damage = 10; + Tag_Rx_Buffer->Tag.color = COLOR_BLUE; + return Tag_Rx_Buffer; + } + else if (data == PURPLE_TEAM_PACKET_DATA) + { + DecodedPacket_T *Tag_Rx_Buffer = Get_Tag_Packet_Buffer(); + Tag_Rx_Buffer->Tag.type = DECODED_PACKET_TYPE_TAG_RECEIVED; + Tag_Rx_Buffer->Tag.protocol = NERF_LASER_OPS_PRO_PROTOCOL; + Tag_Rx_Buffer->Tag.player_ID = 0; + Tag_Rx_Buffer->Tag.team_ID = NERF_PURPLE_TEAM; + Tag_Rx_Buffer->Tag.damage = 10; + Tag_Rx_Buffer->Tag.color = COLOR_PURPLE; + return Tag_Rx_Buffer; + } + + return NULL; + } +} + +color_t NERF_LASER_OPS_PRO_GetTeamColor(uint8_t team_ID) +{ + color_t result = COLOR_PURPLE; + + if (team_ID == NERF_RED_TEAM) + { + result = COLOR_RED; + } + else if (team_ID == NERF_BLUE_TEAM) + { + result = COLOR_BLUE; + } + else + { + // Nothing to do. + } + + return result; +} diff --git a/Protocols/Nerf_Laser_Ops_Pro.h b/Protocols/Nerf_Laser_Ops_Pro.h new file mode 100755 index 0000000..77ecbdc --- /dev/null +++ b/Protocols/Nerf_Laser_Ops_Pro.h @@ -0,0 +1,44 @@ + +/* + * 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 . + */ + +#ifndef NERF_LASER_OPS_PRO_H +#define NERF_LASER_OPS_PRO_H + +/** \file + * \brief This is the Nerf Laser Ops Pro protocol. + * + * \note These devices use 38kHz carrier for On-Off Keying. + * + * https://nerf.hasbro.com/en-us/product/nerf-laser-ops-pro-alphapoint-2-pack:CA310BBE-E75A-4354-8D69-AC2B28C4E007 + * + * https://forum.arduino.cc/index.php?topic=582222.0 + * + */ + +#include +#include + +TimedPulseTrain_T *NERF_LASER_OPS_PRO_EncodePacket(TagPacket_T *packet); +DecodedPacket_T *NERF_LASER_OPS_PRO_MaybeDecodePacket(TimedPulseTrain_T *packet); +color_t NERF_LASER_OPS_PRO_GetTeamColor(uint8_t team_ID); + +#endif // NERF_LASER_OPS_PRO_H diff --git a/Protocols/Nerf_Laser_Strike.c b/Protocols/Nerf_Laser_Strike.c new file mode 100755 index 0000000..5b4ac93 --- /dev/null +++ b/Protocols/Nerf_Laser_Strike.c @@ -0,0 +1,295 @@ + +/* + * 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 "SystemK.h" + +static const char *KLOG_TAG = "Nerf Laser Strike"; + +#define NERF_LASER_STRIKE_SOLO_TEAM 0b00 +#define NERF_LASER_STRIKE_ONE_TEAM 0b01 +#define NERF_LASER_STRIKE_TWO_TEAM 0b10 + +#define NERF_LASER_STRIKE_BLASTER 0b000 +#define NERF_LASER_STRIKE_ROCKET 0b001 +#define NERF_LASER_STRIKE_PLASMA 0b010 +#define NERF_LASER_STRIKE_BURST 0b011 +#define NERF_LASER_STRIKE_LASER 0b100 + +static const uint16_t Packet_Data[5][3] = { + // Solo, Team 1, Team 2 + {0x4D33, 0x34CC, 0x2B55}, // Blaster + {0x52B2, 0x354B, 0x2CD4}, // Rocket + {0x532D, 0x4ACA, 0x2D53}, // Plasma + {0x54AC, 0x4B35, 0x32D2}, // Burst + {0x552B, 0x4CB4, 0x334D} // Laser +}; + +static inline uint16_t Get_Packet_Data(uint8_t team, uint8_t weapon) +{ + uint16_t result = 0; + + if ((team <= NERF_LASER_STRIKE_TWO_TEAM) && (weapon <= NERF_LASER_STRIKE_LASER)) + { + result = Packet_Data[weapon][team]; + } + else + { + KLOG_ERROR(KLOG_TAG, "Requested team (%u) or weapon (%u) out of range!", team, weapon); + } + + return result; +} + +//! Given decoded packet data, this function searches the table to determine if the packet is a valid one. +/*! + * If it is valid, it will set team and weapon to their valid values, and return true. + * If it does not find the packet data in the table, it will return false. + */ +static inline bool Is_Valid_Packet_Data(const uint16_t data, uint8_t *team, uint8_t *weapon) +{ + bool valid = false; + + for (uint_fast8_t weapon_index = 0; (weapon_index < 5) && (valid == false); weapon_index++) + { + for (uint_fast8_t team_index = 0; (team_index < 3) && (valid == false); team_index++) + { + if (Packet_Data[weapon_index][team_index] == data) + { + *team = team_index; + *weapon = weapon_index; + valid = true; + } + } + } + + return valid; +} + +#define NERF_LASER_STRIKE_HEADER_MARK_DURATION_IN_us 6000 +#define NERF_LASER_STRIKE_ZERO_DURATION_IN_us 500 +#define NERF_LASER_STRIKE_ONE_DURATION_IN_us 1500 + +#define NERF_LASER_STRIKE_TOLERANCE_IN_us 450 + +#define MIN_ZERO_IN_us (NERF_LASER_STRIKE_ZERO_DURATION_IN_us - NERF_LASER_STRIKE_TOLERANCE_IN_us) +#define MAX_ZERO_IN_us (NERF_LASER_STRIKE_ZERO_DURATION_IN_us + NERF_LASER_STRIKE_TOLERANCE_IN_us) +#define MIN_ONE_IN_us (NERF_LASER_STRIKE_ONE_DURATION_IN_us - NERF_LASER_STRIKE_TOLERANCE_IN_us) +#define MAX_ONE_IN_us (NERF_LASER_STRIKE_ONE_DURATION_IN_us + NERF_LASER_STRIKE_TOLERANCE_IN_us) + +static const TimedBit_T NERF_LASER_STRIKE_HEADER_MARK = {.symbol = MARK, .duration = NERF_LASER_STRIKE_HEADER_MARK_DURATION_IN_us}; +static const TimedBit_T NERF_LASER_STRIKE_ZERO_SPACE = {.symbol = SPACE, .duration = NERF_LASER_STRIKE_ZERO_DURATION_IN_us}; +static const TimedBit_T NERF_LASER_STRIKE_ONE_SPACE = {.symbol = SPACE, .duration = NERF_LASER_STRIKE_ONE_DURATION_IN_us}; +static const TimedBit_T NERF_LASER_STRIKE_ZERO_MARK = {.symbol = MARK, .duration = NERF_LASER_STRIKE_ZERO_DURATION_IN_us}; +static const TimedBit_T NERF_LASER_STRIKE_ONE_MARK = {.symbol = MARK, .duration = NERF_LASER_STRIKE_ONE_DURATION_IN_us}; +static const TimedBit_T NERF_LASER_STRIKE_END = {.symbol = SPACE, .duration = LAST_PULSE}; + +static TimedPulseTrain_T Tag_Send_Buffer; +static DecodedPacket_T Nerf_Laser_Strike_Tag_Received_Buffers[3] = + { + {.Tag.type = DECODED_PACKET_TYPE_BUFFER_FREE}, + {.Tag.type = DECODED_PACKET_TYPE_BUFFER_FREE}, + {.Tag.type = DECODED_PACKET_TYPE_BUFFER_FREE}}; + +static inline DecodedPacket_T *Get_Tag_Packet_Buffer(void) +{ + if (Nerf_Laser_Strike_Tag_Received_Buffers[0].Tag.type == DECODED_PACKET_TYPE_BUFFER_FREE) + { + return &Nerf_Laser_Strike_Tag_Received_Buffers[0]; + } + else if (Nerf_Laser_Strike_Tag_Received_Buffers[1].Tag.type == DECODED_PACKET_TYPE_BUFFER_FREE) + { + return &Nerf_Laser_Strike_Tag_Received_Buffers[1]; + } + else + { + // Just use it. + return &Nerf_Laser_Strike_Tag_Received_Buffers[2]; + } +} + +// Packs data into a pulse train, most significant bit first. +static inline void PackPulseTrain(TimedPulseTrain_T *pulsetrain, uint16_t data) +{ + pulsetrain->bitstream[0] = NERF_LASER_STRIKE_HEADER_MARK; + + for (uint8_t n = 0; n < 8; n++) + { + if ((data & 0x8000) == 0x8000) + { + pulsetrain->bitstream[1 + (n * 2)] = NERF_LASER_STRIKE_ONE_SPACE; + } + else + { + pulsetrain->bitstream[1 + (n * 2)] = NERF_LASER_STRIKE_ZERO_SPACE; + } + data = data << 1; + if ((data & 0x8000) == 0x8000) + { + pulsetrain->bitstream[2 + (n * 2)] = NERF_LASER_STRIKE_ONE_MARK; + } + else + { + pulsetrain->bitstream[2 + (n * 2)] = NERF_LASER_STRIKE_ZERO_MARK; + } + data = data << 1; + } + + pulsetrain->bitstream[17] = NERF_LASER_STRIKE_END; + pulsetrain->count = 17; +} + +TimedPulseTrain_T *NERF_LASER_STRIKE_EncodePacket(TagPacket_T *packet) +{ + uint16_t packed_data = 0; + + if (packet->team_ID == TEAM_RED) + { + packed_data = Get_Packet_Data(NERF_LASER_STRIKE_ONE_TEAM, NERF_LASER_STRIKE_BLASTER); + } + else if (packet->team_ID == TEAM_BLUE) + { + packed_data = Get_Packet_Data(NERF_LASER_STRIKE_TWO_TEAM, NERF_LASER_STRIKE_BLASTER); + } + else // NERF_LASER_STRIKE_SOLO_TEAM + { + packed_data = Get_Packet_Data(NERF_LASER_STRIKE_SOLO_TEAM, NERF_LASER_STRIKE_BLASTER); + } + + PackPulseTrain(&Tag_Send_Buffer, packed_data); + + return &Tag_Send_Buffer; +} + +DecodedPacket_T *NERF_LASER_STRIKE_MaybeDecodePacket(TimedPulseTrain_T *packet) +{ + // Some implementations (ESP32) do not report odd numbers of pulses. + // The PSoC sees all three copies of the tag at once. + if ((packet->count != 17) && + (packet->count != 18) && + (packet->count != 53)) + { + // Fail fast! + return NULL; + } + else + { + // Convert pulse durations to bits, assuming most significant bit is sent first. + // packet->bitstream[0] is the header mark--ignore it. + + uint16_t data = 0; + for (uint16_t n = 16; n > 0; n--) + { + if ((packet->bitstream[n].duration > MIN_ONE_IN_us) && (packet->bitstream[n].duration < MAX_ONE_IN_us)) + { + // One + data |= ((uint16_t)1 << (16 - n)); + } + else if ((packet->bitstream[n].duration > MIN_ZERO_IN_us) && (packet->bitstream[n].duration < MAX_ZERO_IN_us)) + { + // Zero--nothing to do. + } + else + { + // This pulse is neither a zero or a one; abort. + return NULL; + } + } + + uint8_t team = 0; + uint8_t weapon = 0; + + if (Is_Valid_Packet_Data(data, &team, &weapon) == false) + { + KLOG_DEBUG(KLOG_TAG, "Decoded packet was not recognized: 0x%4X", data); + } + else + { + DecodedPacket_T *Tag_Rx_Buffer = Get_Tag_Packet_Buffer(); + + if (team == NERF_LASER_STRIKE_ONE_TEAM) + { + Tag_Rx_Buffer->Tag.type = DECODED_PACKET_TYPE_TAG_RECEIVED; + Tag_Rx_Buffer->Tag.protocol = NERF_LASER_STRIKE_PROTOCOL; + Tag_Rx_Buffer->Tag.player_ID = 0; + Tag_Rx_Buffer->Tag.team_ID = TEAM_RED; + Tag_Rx_Buffer->Tag.color = COLOR_RED; + } + else if (team == NERF_LASER_STRIKE_TWO_TEAM) + { + Tag_Rx_Buffer->Tag.type = DECODED_PACKET_TYPE_TAG_RECEIVED; + Tag_Rx_Buffer->Tag.protocol = NERF_LASER_STRIKE_PROTOCOL; + Tag_Rx_Buffer->Tag.player_ID = 0; + Tag_Rx_Buffer->Tag.team_ID = TEAM_BLUE; + Tag_Rx_Buffer->Tag.color = COLOR_BLUE; + } + else // NERF_LASER_STRIKE_SOLO_TEAM + { + Tag_Rx_Buffer->Tag.type = DECODED_PACKET_TYPE_TAG_RECEIVED; + Tag_Rx_Buffer->Tag.protocol = NERF_LASER_STRIKE_PROTOCOL; + Tag_Rx_Buffer->Tag.player_ID = 0; + Tag_Rx_Buffer->Tag.team_ID = TEAM_PURPLE; + Tag_Rx_Buffer->Tag.color = COLOR_PURPLE; + } + + // Damages are 10x, normalized for MAX_HEALTH of 100. + if (weapon == NERF_LASER_STRIKE_BLASTER) + { + Tag_Rx_Buffer->Tag.damage = 10; + } + else if (weapon == NERF_LASER_STRIKE_ROCKET) + { + Tag_Rx_Buffer->Tag.damage = 30; + } + else // NERF_LASER_STRIKE_PLASMA, NERF_LASER_STRIKE_BURST, and NERF_LASER_STRIKE_LASER + { + Tag_Rx_Buffer->Tag.damage = 20; + } + + return Tag_Rx_Buffer; + } + + return NULL; + } +} + +color_t NERF_LASER_STRIKE_GetTeamColor(uint8_t team_ID) +{ + color_t result = COLOR_PURPLE; + + if (team_ID == NERF_LASER_STRIKE_ONE_TEAM) + { + result = COLOR_RED; + } + else if (team_ID == NERF_LASER_STRIKE_TWO_TEAM) + { + result = COLOR_BLUE; + } + else + { + // Nothing to do. + } + + return result; +} diff --git a/Protocols/Nerf_Laser_Strike.h b/Protocols/Nerf_Laser_Strike.h new file mode 100755 index 0000000..140e9ca --- /dev/null +++ b/Protocols/Nerf_Laser_Strike.h @@ -0,0 +1,40 @@ + +/* + * 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 . + */ + +#ifndef NERF_LASER_STRIKE_H +#define NERF_LASER_STRIKE_H + +/** \file + * \brief This is the Nerf Laser Strike protocol. + * + * \note These devices use 38kHz carrier for On-Off Keying. + * + */ + +#include +#include + +TimedPulseTrain_T *NERF_LASER_STRIKE_EncodePacket(TagPacket_T *packet); +DecodedPacket_T *NERF_LASER_STRIKE_MaybeDecodePacket(TimedPulseTrain_T *packet); +color_t NERF_LASER_STRIKE_GetTeamColor(uint8_t team_ID); + +#endif // NERF_LASER_STRIKE_H diff --git a/Protocols/Nerf_Phoenix_LTX.c b/Protocols/Nerf_Phoenix_LTX.c new file mode 100755 index 0000000..caedb11 --- /dev/null +++ b/Protocols/Nerf_Phoenix_LTX.c @@ -0,0 +1,221 @@ + +/* + * 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 + +#include "SystemK.h" + +#define NERF_RED_TEAM 0b00 +#define NERF_BLUE_TEAM 0b01 +#define NERF_PURPLE_TEAM 0b10 + +//! *Team One* is the Red Team. +#define RED_TEAM_PACKET_DATA 0x0010 +//! *Team Two* is the Blue Team. +#define BLUE_TEAM_PACKET_DATA 0x0008 +//! *Solo* is the Purple Team. +#define PURPLE_TEAM_PACKET_DATA 0x0000 + +#define NERF_PHOENIX_LTX_HEADER_MARK_DURATION_IN_us 3000 +#define NERF_PHOENIX_LTX_HEADER_SPACE_DURATION_IN_us 6000 +#define NERF_PHOENIX_LTX_SPACE_DURATION_IN_us 2000 +#define NERF_PHOENIX_LTX_MARK_ZERO_DURATION_IN_us 900 +#define NERF_PHOENIX_LTX_MARK_ONE_DURATION_IN_us 2000 +#define NERF_PHOENIX_LTX_TOLERANCE_IN_us 350 + +#define MIN_SPACE_IN_us (NERF_PHOENIX_LTX_SPACE_DURATION_IN_us - NERF_PHOENIX_LTX_TOLERANCE_IN_us) +#define MAX_SPACE_IN_us (NERF_PHOENIX_LTX_SPACE_DURATION_IN_us + NERF_PHOENIX_LTX_TOLERANCE_IN_us) +#define MIN_MARK_ZERO_IN_us (NERF_PHOENIX_LTX_MARK_ZERO_DURATION_IN_us - NERF_PHOENIX_LTX_TOLERANCE_IN_us) +#define MAX_MARK_ZERO_IN_us (NERF_PHOENIX_LTX_MARK_ZERO_DURATION_IN_us + NERF_PHOENIX_LTX_TOLERANCE_IN_us) +#define MIN_MARK_ONE_IN_us (NERF_PHOENIX_LTX_MARK_ONE_DURATION_IN_us - NERF_PHOENIX_LTX_TOLERANCE_IN_us) +#define MAX_MARK_ONE_IN_us (NERF_PHOENIX_LTX_MARK_ONE_DURATION_IN_us + NERF_PHOENIX_LTX_TOLERANCE_IN_us) + +static const TimedBit_T NERF_PHOENIX_LTX_HEADER_MARK = {.symbol = MARK, .duration = NERF_PHOENIX_LTX_HEADER_MARK_DURATION_IN_us}; +static const TimedBit_T NERF_PHOENIX_LTX_HEADER_SPACE = {.symbol = SPACE, .duration = NERF_PHOENIX_LTX_HEADER_SPACE_DURATION_IN_us}; +static const TimedBit_T NERF_PHOENIX_LTX_SPACE = {.symbol = SPACE, .duration = NERF_PHOENIX_LTX_SPACE_DURATION_IN_us}; +static const TimedBit_T NERF_PHOENIX_LTX_ONE = {.symbol = MARK, .duration = NERF_PHOENIX_LTX_MARK_ONE_DURATION_IN_us}; +static const TimedBit_T NERF_PHOENIX_LTX_ZERO = {.symbol = MARK, .duration = NERF_PHOENIX_LTX_MARK_ZERO_DURATION_IN_us}; +static const TimedBit_T NERF_PHOENIX_LTX_END = {.symbol = SPACE, .duration = LAST_PULSE}; + +static TimedPulseTrain_T Tag_Send_Buffer; +static DecodedPacket_T Nerf_Phoenix_LTX_Tag_Received_Buffers[3] = + { + {.Tag.type = DECODED_PACKET_TYPE_BUFFER_FREE}, + {.Tag.type = DECODED_PACKET_TYPE_BUFFER_FREE}, + {.Tag.type = DECODED_PACKET_TYPE_BUFFER_FREE}}; + +static inline DecodedPacket_T *Get_Tag_Packet_Buffer(void) +{ + if (Nerf_Phoenix_LTX_Tag_Received_Buffers[0].Tag.type == DECODED_PACKET_TYPE_BUFFER_FREE) + { + return &Nerf_Phoenix_LTX_Tag_Received_Buffers[0]; + } + else if (Nerf_Phoenix_LTX_Tag_Received_Buffers[1].Tag.type == DECODED_PACKET_TYPE_BUFFER_FREE) + { + return &Nerf_Phoenix_LTX_Tag_Received_Buffers[1]; + } + else + { + // Just use it. + return &Nerf_Phoenix_LTX_Tag_Received_Buffers[2]; + } +} + +static inline void PackPulseTrain(TimedPulseTrain_T *pulsetrain, uint16_t data) +{ + pulsetrain->bitstream[0] = NERF_PHOENIX_LTX_HEADER_MARK; + pulsetrain->bitstream[1] = NERF_PHOENIX_LTX_HEADER_SPACE; + pulsetrain->bitstream[2] = NERF_PHOENIX_LTX_HEADER_MARK; + pulsetrain->bitstream[3] = NERF_PHOENIX_LTX_SPACE; + + for (uint8_t n = 0; n < 7; n++) + { + if ((data & 0x01) == 0x01) + { + pulsetrain->bitstream[4 + (n * 2)] = NERF_PHOENIX_LTX_ONE; + } + else + { + pulsetrain->bitstream[4 + (n * 2)] = NERF_PHOENIX_LTX_ZERO; + } + pulsetrain->bitstream[4 + (n * 2) + 1] = NERF_PHOENIX_LTX_SPACE; + data = data >> 1; + } + + pulsetrain->bitstream[17] = NERF_PHOENIX_LTX_END; +} + +TimedPulseTrain_T *NERF_PHOENIX_LTX_EncodePacket(TagPacket_T *packet) +{ + uint16_t packed_data = 0; + + if (packet->team_ID == NERF_RED_TEAM) + { + packed_data = RED_TEAM_PACKET_DATA; + } + else if (packet->team_ID == NERF_BLUE_TEAM) + { + packed_data = BLUE_TEAM_PACKET_DATA; + } + else // NERF_PURPLE_TEAM + { + packed_data = PURPLE_TEAM_PACKET_DATA; + } + + PackPulseTrain(&Tag_Send_Buffer, packed_data); + + return &Tag_Send_Buffer; +} + +DecodedPacket_T *NERF_PHOENIX_LTX_MaybeDecodePacket(TimedPulseTrain_T *packet) +{ + // Some implementations (ESP32) do not report odd numbers of pulses. + if ((packet->count != 17) && (packet->count != 18)) + { + // Fail fast! + return NULL; + } + else + { + // Convert pulse durations to bits. + // packet->bitstream[0] is the header mark--ignore it. + // packet->bitstream[1] is the header space--ignore it also. + // packet->bitstream[2] is the second header mark--ignore it as well. + // packet->bitstream[3] is the second header space--also ignored.. + uint16_t data = 0; + for (uint8_t n = 0; n < 7; n++) + { + // Even pulses are marks; odd pulses are spaces. + if ((packet->bitstream[4 + (n * 2)].duration > MIN_MARK_ONE_IN_us) && (packet->bitstream[4 + (n * 2)].duration < MAX_MARK_ONE_IN_us)) + { + // One + data |= ((uint16_t)1 << n); + } + else if ((packet->bitstream[4 + (n * 2)].duration > MIN_MARK_ZERO_IN_us) && (packet->bitstream[4 + (n * 2)].duration < MAX_MARK_ZERO_IN_us)) + { + // Zero--nothing to do. + } + else + { + // This mark is neither a zero or a one; abort. + return NULL; + } + } + + if (data == RED_TEAM_PACKET_DATA) + { + DecodedPacket_T *Tag_Rx_Buffer = Get_Tag_Packet_Buffer(); + Tag_Rx_Buffer->Tag.type = DECODED_PACKET_TYPE_TAG_RECEIVED; + Tag_Rx_Buffer->Tag.protocol = NERF_PHOENIX_LTX_PROTOCOL; + Tag_Rx_Buffer->Tag.player_ID = 0; + Tag_Rx_Buffer->Tag.team_ID = NERF_RED_TEAM; + Tag_Rx_Buffer->Tag.damage = 10; + Tag_Rx_Buffer->Tag.color = COLOR_RED; + return Tag_Rx_Buffer; + } + else if (data == BLUE_TEAM_PACKET_DATA) + { + DecodedPacket_T *Tag_Rx_Buffer = Get_Tag_Packet_Buffer(); + Tag_Rx_Buffer->Tag.type = DECODED_PACKET_TYPE_TAG_RECEIVED; + Tag_Rx_Buffer->Tag.protocol = NERF_PHOENIX_LTX_PROTOCOL; + Tag_Rx_Buffer->Tag.player_ID = 0; + Tag_Rx_Buffer->Tag.team_ID = NERF_BLUE_TEAM; + Tag_Rx_Buffer->Tag.damage = 10; + Tag_Rx_Buffer->Tag.color = COLOR_BLUE; + return Tag_Rx_Buffer; + } + else if (data == PURPLE_TEAM_PACKET_DATA) + { + DecodedPacket_T *Tag_Rx_Buffer = Get_Tag_Packet_Buffer(); + Tag_Rx_Buffer->Tag.type = DECODED_PACKET_TYPE_TAG_RECEIVED; + Tag_Rx_Buffer->Tag.protocol = NERF_PHOENIX_LTX_PROTOCOL; + Tag_Rx_Buffer->Tag.player_ID = 0; + Tag_Rx_Buffer->Tag.team_ID = NERF_PURPLE_TEAM; + Tag_Rx_Buffer->Tag.damage = 10; + Tag_Rx_Buffer->Tag.color = COLOR_PURPLE; + return Tag_Rx_Buffer; + } + + return NULL; + } +} + +color_t NERF_PHOENIX_LTX_GetTeamColor(uint8_t team_ID) +{ + color_t result = COLOR_PURPLE; + + if (team_ID == NERF_RED_TEAM) + { + result = COLOR_RED; + } + else if (team_ID == NERF_BLUE_TEAM) + { + result = COLOR_BLUE; + } + else + { + // Nothing to do. + } + + return result; +} diff --git a/Protocols/Nerf_Phoenix_LTX.h b/Protocols/Nerf_Phoenix_LTX.h new file mode 100755 index 0000000..3243a10 --- /dev/null +++ b/Protocols/Nerf_Phoenix_LTX.h @@ -0,0 +1,42 @@ + +/* + * 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 . + */ + +#ifndef NERF_PHOENIX_LTX_H +#define NERF_PHOENIX_LTX_H + +/** \file + * \brief This is the Nerf Phoenix LTX protocol. + * + * \note These devices use 38kHz carrier for On-Off Keying. + * + * https://nerf.hasbro.com/en-us/product/nerf-lazer-tag-phoenix-ltx-tagger-2-pack:F0C4410E-19B9-F369-D914-B940ADA55500 + * + */ + +#include +#include + +TimedPulseTrain_T *NERF_PHOENIX_LTX_EncodePacket(TagPacket_T *packet); +DecodedPacket_T *NERF_PHOENIX_LTX_MaybeDecodePacket(TimedPulseTrain_T *packet); +color_t NERF_PHOENIX_LTX_GetTeamColor(uint8_t team_ID); + +#endif // NERF_PHOENIX_LTX_H diff --git a/Protocols/Protocols.c b/Protocols/Protocols.c new file mode 100755 index 0000000..4b0bf60 --- /dev/null +++ b/Protocols/Protocols.c @@ -0,0 +1,365 @@ + +/* + * 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 + +#include "SystemK.h" + +// #define DEBUG_PACKET_ENCODE +#define DEBUG_PACKET_DECODE + +static const char *KLOG_TAG = "Protocols"; + +static const char *PROTOCOL_NAME[] = { + FOREACH_PROTOCOL(GENERATE_STRING)}; + +static inline const char *ProtocolNameAsString(Protocol_T p) +{ + if (p < LAST_PROTOCOL) + { + return PROTOCOL_NAME[p]; + } + + return "Invalid Protocol!"; +} + +static inline const char *DecodedPacketTypeAsString(DecodedPacketType_T p) +{ + if (p == DECODED_PACKET_TYPE_IGNORED) + { + return "Ignored"; + } + if (p == DECODED_PACKET_TYPE_TAG_RECEIVED) + { + return "Tag Received"; + } + if (p == DECODED_PACKET_TYPE_COMMAND_RECEIVED) + { + return "Command Received"; + } + + return "Unexpected Packet Type"; +} + +static inline void PrintPulseTrainToConsole(TimedPulseTrain_T *train) +{ + for (uint_fast16_t i = 0; i < train->count; i += 2) + { + KLOG_DEBUG(KLOG_TAG, "%2d: (%d, %4d) (%d, %4d)", i + 1, train->bitstream[i].symbol, train->bitstream[i].duration, train->bitstream[i + 1].symbol, train->bitstream[i + 1].duration); + vTaskDelay(pdMS_TO_TICKS(10)); + } +} + +ModulationFrequency_T PROTOCOLS_GetModulationFrequency(Protocol_T protocol) +{ + ModulationFrequency_T result = FREQUENCY_38kHz; + + switch (protocol) + { + default: + case UNKNOWN_PROTOCOL: + break; + + case TEST_PROTOCOL: + result = FREQUENCY_38kHz; + break; + + case DUBUQUE_PROTOCOL: + result = FREQUENCY_38kHz; + break; + + case MILES_TAG_II_PROTOCOL: + result = FREQUENCY_56kHz; + break; + + case NERF_LASER_OPS_PRO_PROTOCOL: + result = FREQUENCY_38kHz; + break; + + case NERF_PHOENIX_LTX_PROTOCOL: + result = FREQUENCY_38kHz; + break; + + case DYNASTY_PROTOCOL: + result = FREQUENCY_38kHz; + break; + + case SQUAD_HERO_PROTOCOL: + result = FREQUENCY_38kHz; + break; + + case NEC_PROTOCOL: + result = FREQUENCY_38kHz; + break; + + case LASER_X_PROTOCOL: + result = FREQUENCY_38kHz; + break; + } + + return result; +} + +color_t PROTOCOLS_GetColor(Protocol_T protocol, uint8_t team_ID, uint8_t player_ID) +{ + color_t result = COLOR_AQUA; + + switch (protocol) + { + default: + case UNKNOWN_PROTOCOL: + break; + + case TEST_PROTOCOL: + result = COLOR_WHITE; + break; + + case DUBUQUE_PROTOCOL: + result = DBQ_GetTeamColor(team_ID); + break; + + case MILES_TAG_II_PROTOCOL: + result = MILES_TAG_II_GetTeamColor(team_ID); + break; + + case NERF_LASER_OPS_PRO_PROTOCOL: + result = NERF_LASER_OPS_PRO_GetTeamColor(team_ID); + break; + + case NERF_LASER_STRIKE_PROTOCOL: + result = NERF_LASER_STRIKE_GetTeamColor(team_ID); + break; + + case NERF_PHOENIX_LTX_PROTOCOL: + result = NERF_PHOENIX_LTX_GetTeamColor(team_ID); + break; + + case DYNASTY_PROTOCOL: + result = DYNASTY_GetTeamColor(team_ID); + break; + + case SQUAD_HERO_PROTOCOL: + result = SQUAD_HERO_GetTeamColor(team_ID); + break; + + case LASER_X_PROTOCOL: + result = LASER_X_GetTeamColor(team_ID); + break; + } + + return result; +} + +TimedPulseTrain_T *PROTOCOLS_EncodePacket(TagPacket_T *packet) +{ + TimedPulseTrain_T *result = NULL; + + switch (packet->protocol) + { + default: + case UNKNOWN_PROTOCOL: + break; + + case TEST_PROTOCOL: + result = TEST_EncodePacket(packet); + break; + + case DUBUQUE_PROTOCOL: + result = DBQ_EncodePacket(packet); + break; + + case MILES_TAG_II_PROTOCOL: + result = MILES_TAG_II_EncodePacket(packet); + break; + + case NERF_LASER_OPS_PRO_PROTOCOL: + result = NERF_LASER_OPS_PRO_EncodePacket(packet); + break; + + case NERF_LASER_STRIKE_PROTOCOL: + result = NERF_LASER_STRIKE_EncodePacket(packet); + break; + + case NERF_PHOENIX_LTX_PROTOCOL: + result = NERF_PHOENIX_LTX_EncodePacket(packet); + break; + + case DYNASTY_PROTOCOL: + result = DYNASTY_EncodePacket(packet); + break; + + case SQUAD_HERO_PROTOCOL: + result = SQUAD_HERO_EncodePacket(packet); + break; + + case NEC_PROTOCOL: + result = NULL; + break; + + case LASER_X_PROTOCOL: + result = LASER_X_EncodePacket(packet); + break; + } + +#ifdef DEBUG_PACKET_ENCODE + KLOG_DEBUG(KLOG_TAG, "\nEncoded %s packet (%u):", ProtocolNameAsString(packet->protocol), result->count); + PrintPulseTrainToConsole(result); +#endif // DEBUG_PACKET_ENCODE + + return result; +} + +//! Attempts to decode the pulse durations by comparing to the known protocols. +DecodedPacket_T *PROTOCOLS_MaybeDecodePacket(TimedPulseTrain_T *packet) +{ + DecodedPacket_T *result = NULL; + + if (result == NULL) + { + result = LASER_X_MaybeDecodePacket(packet); + } + + if (result == NULL) + { + result = SQUAD_HERO_MaybeDecodePacket(packet); + } + + if (result == NULL) + { + result = NEC_MaybeDecodePacket(packet); + + if (result != NULL) + { + // Many NEC remotes repeat packets when the button is held down. + // Too avoid double-counting, endure 500ms of silence between packets. + static TickType_t lastPacketTime = 0; + const TickType_t minimumInterval = pdMS_TO_TICKS(500); + + TickType_t currentTime = xTaskGetTickCount(); + TickType_t timeSinceLastPacket = currentTime - lastPacketTime; + + if (timeSinceLastPacket < minimumInterval) + { + // This packet is possibly the result of a held button--ignore it. + KLOG_WARN("NEC", "Received potentially redundant packet--ignoring."); + result->Generic.type = DECODED_PACKET_TYPE_IGNORED; + } + + lastPacketTime = currentTime; + } + } + + if (result == NULL) + { + result = MILES_TAG_II_MaybeDecodePacket(packet); + } + + if (result == NULL) + { + result = DYNASTY_MaybeDecodePacket(packet); + } + + if (result == NULL) + { + result = NERF_LASER_OPS_PRO_MaybeDecodePacket(packet); + } + + if (result == NULL) + { + result = NERF_LASER_STRIKE_MaybeDecodePacket(packet); + + if (result != NULL) + { + // The Nerf Laser Strike blasters send out three identical packets whenever the trigger is pulled, + // over a time of about 200 milliseconds. Only one is necessary to hit. To avoid double-counting, + // we ignore further packets for a short time after receiving a valid one. + static TickType_t lastValidPacketTime = 0; + const TickType_t minimumInterval = pdMS_TO_TICKS(200); + + TickType_t currentTime = xTaskGetTickCount(); + TickType_t timeSinceLastValidPacket = currentTime - lastValidPacketTime; + + if (timeSinceLastValidPacket < minimumInterval) + { + // This packet is possibly redundant with the previous valid packet--ignore it. + KLOG_WARN("Nerf Laser Strike", "Received potentially redundant packet--ignoring."); + result->Generic.type = DECODED_PACKET_TYPE_IGNORED; + } + else + { + lastValidPacketTime = currentTime; + } + } + } + + if (result == NULL) + { + result = NERF_PHOENIX_LTX_MaybeDecodePacket(packet); + } + + // Keep this close to the end of the list; it's purposely sensitive, and prone to false positives. + if (result == NULL) + { + result = DBQ_MaybeDecodePacket(packet); + } + + // Keep this one last, so it doesn't slow down game play. + if (result == NULL) + { + result = TEST_MaybeDecodePacket(packet); + } + +#ifdef DEBUG_PACKET_DECODE + +#ifdef ESP_PLATFORM + esp_log_level_set(KLOG_TAG, ESP_LOG_DEBUG); +#endif // ESP_PLATFORM + + if (result != NULL) + { + KLOG_DEBUG(KLOG_TAG, "Successfully decoded packet as %s: %s", DecodedPacketTypeAsString(result->Generic.type), ProtocolNameAsString(result->Generic.protocol)); + vTaskDelay(pdMS_TO_TICKS(10)); + + if (result->Generic.type == DECODED_PACKET_TYPE_COMMAND_RECEIVED) + { + KLOG_DEBUG(KLOG_TAG, "Command data: %lu", result->Command.data); + vTaskDelay(pdMS_TO_TICKS(10)); + } + } + else + { + KLOG_DEBUG(KLOG_TAG, "Couldn't decode packet. Size was %d symbols:", packet->count); + vTaskDelay(pdMS_TO_TICKS(10)); + + PrintPulseTrainToConsole(packet); + } +#endif // DEBUG_PACKET_DECODE + + // Remember which receiver saw the packet. + if (result != NULL) + { + result->Generic.receiver = packet->receiver; + } + + return result; +} diff --git a/Protocols/Protocols.h b/Protocols/Protocols.h new file mode 100755 index 0000000..fef6e40 --- /dev/null +++ b/Protocols/Protocols.h @@ -0,0 +1,188 @@ + +/* + * 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 . + */ + +#ifndef PROTOCOLS_H +#define PROTOCOLS_H + +#include +#include +#include + +#define FOREACH_PROTOCOL(PROTOCOL) \ + PROTOCOL(UNKNOWN_PROTOCOL) \ + PROTOCOL(TEST_PROTOCOL) \ + PROTOCOL(DUBUQUE_PROTOCOL) \ + PROTOCOL(MILES_TAG_II_PROTOCOL) \ + PROTOCOL(NERF_LASER_OPS_PRO_PROTOCOL) \ + PROTOCOL(NERF_LASER_STRIKE_PROTOCOL) \ + PROTOCOL(NERF_PHOENIX_LTX_PROTOCOL) \ + PROTOCOL(DYNASTY_PROTOCOL) \ + PROTOCOL(SQUAD_HERO_PROTOCOL) \ + PROTOCOL(NEC_PROTOCOL) \ + PROTOCOL(LASER_X_PROTOCOL) \ + PROTOCOL(LAST_PROTOCOL) + +#define GENERATE_ENUM(ENUM) ENUM, +#define GENERATE_STRING(STRING) #STRING, + +typedef enum +{ + FOREACH_PROTOCOL(GENERATE_ENUM) +} Protocol_T; + +#define MAX_PULSES 125 +#define LAST_PULSE 0 + +typedef enum +{ + FREQUENCY_38kHz, + FREQUENCY_56kHz +} ModulationFrequency_T; + +//! On-Off Keying (OOK) symbols +typedef enum +{ + SPACE = 0, + MARK = 1 +} OOK_Symbol_T; + +//! Represents a length of time, in microseconds. +typedef uint32_t Duration_in_us_T; + +typedef enum +{ + TAG_SENSOR_UNKNOWN = 0, + TAG_SENSOR_NONE, + TAG_SENSOR_FORWARD, + TAG_SENSOR_LEFT, + TAG_SENSOR_RIGHT, + TAG_SENSOR_REMOTE +} TagSensorLocation_T; + +typedef struct +{ + //! Use a value of #LAST_PULSE to mean the pulsetrain is over. + Duration_in_us_T duration : 15; + OOK_Symbol_T symbol : 1; +} __attribute__((packed)) TimedBit_T; + +//! Timing information for a single pulse, packed into a 32-bit integer. +/* + * \see #rmt_item32_t in the ESP-IDF. + */ +typedef struct +{ + union + { + struct + { + Duration_in_us_T duration0 : 15; + OOK_Symbol_T symbol0 : 1; + Duration_in_us_T duration1 : 15; + OOK_Symbol_T symbol1 : 1; + }; + uint32_t val; + }; +} __attribute__((packed)) TimedPulse_T; + +typedef struct +{ + union + { + TimedPulse_T pulsetrain[MAX_PULSES]; + TimedBit_T bitstream[2 * MAX_PULSES]; + }; + uint16_t count; + TagSensorLocation_T receiver; +} __attribute__((packed)) TimedPulseTrain_T; + +typedef enum +{ + DECODED_PACKET_TYPE_BUFFER_FREE, + DECODED_PACKET_TYPE_UNKNOWN, + DECODED_PACKET_TYPE_IGNORED, + DECODED_PACKET_TYPE_TAG_RECEIVED, + DECODED_PACKET_TYPE_COMMAND_RECEIVED +} DecodedPacketType_T; + +typedef struct +{ + DecodedPacketType_T type; + TagSensorLocation_T receiver; + uint8_t protocol; +} GenericDecodedPacket_T; + +//! Contents of the decoded packet #DECODED_PACKET_TYPE_TAG_RECEIVED. +typedef struct +{ + DecodedPacketType_T type; + TagSensorLocation_T receiver; + uint8_t protocol; + uint16_t team_ID; + uint16_t player_ID; + uint16_t damage; + color_t color; +} TagPacket_T; + +//! Contents of the decoded packet #DECODED_PACKET_TYPE_COMMAND_RECEIVED. +typedef struct +{ + DecodedPacketType_T type; + TagSensorLocation_T receiver; + uint8_t protocol; + uint16_t command; + uint32_t data; +} CommandPacket_T; + +typedef union +{ + GenericDecodedPacket_T Generic; + TagPacket_T Tag; + CommandPacket_T Command; +} DecodedPacket_T; + +static inline void FreeDecodedPacketBuffer(DecodedPacket_T *buffer) +{ + if (buffer != NULL) + { + buffer->Generic.type = DECODED_PACKET_TYPE_BUFFER_FREE; + } +} + +ModulationFrequency_T PROTOCOLS_GetModulationFrequency(Protocol_T protocol); +color_t PROTOCOLS_GetColor(Protocol_T protocol, uint8_t team_ID, uint8_t player_ID); +TimedPulseTrain_T *PROTOCOLS_EncodePacket(TagPacket_T *packet); +DecodedPacket_T *PROTOCOLS_MaybeDecodePacket(TimedPulseTrain_T *packet); + +// Include the supported protocols. +#include "Test.h" +#include "Dubuque.h" +#include "Miles_Tag_II.h" +#include "NEC.h" +#include "Nerf_Laser_Ops_Pro.h" +#include "Nerf_Laser_Strike.h" +#include "Nerf_Phoenix_LTX.h" +#include "Dynasty.h" +#include "Squad_Hero.h" +#include "Laser_X.h" + +#endif // PROTOCOLS_H diff --git a/Protocols/Squad_Hero.c b/Protocols/Squad_Hero.c new file mode 100755 index 0000000..634e076 --- /dev/null +++ b/Protocols/Squad_Hero.c @@ -0,0 +1,244 @@ + +/* + * 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 + +#include "SystemK.h" + +#define SQUAD_HERO_BLUE_TEAM 0x01 +#define SQUAD_HERO_RED_TEAM 0x02 +#define SQUAD_HERO_GREEN_TEAM 0x03 +#define SQUAD_HERO_ORANGE_TEAM 0x04 +#define SQUAD_HERO_WHITE_TEAM 0x06 + +#define SQUAD_HERO_HEADER_MARK_DURATION_IN_us 1500 +#define SQUAD_HERO_ZERO_DURATION_IN_us 400 +#define SQUAD_HERO_ONE_DURATION_IN_us 800 +#define SQUAD_HERO_TOLERANCE_IN_us 175 + +#define MIN_ZERO_IN_us (SQUAD_HERO_ZERO_DURATION_IN_us - SQUAD_HERO_TOLERANCE_IN_us) +#define MAX_ZERO_IN_us (SQUAD_HERO_ZERO_DURATION_IN_us + SQUAD_HERO_TOLERANCE_IN_us) +#define MIN_ONE_IN_us (SQUAD_HERO_ONE_DURATION_IN_us - SQUAD_HERO_TOLERANCE_IN_us) +#define MAX_ONE_IN_us (SQUAD_HERO_ONE_DURATION_IN_us + SQUAD_HERO_TOLERANCE_IN_us) + +static const TimedBit_T SQUAD_HERO_HEADER_MARK = {.symbol = MARK, .duration = SQUAD_HERO_HEADER_MARK_DURATION_IN_us}; +static const TimedBit_T SQUAD_HERO_ZERO_SPACE = {.symbol = SPACE, .duration = SQUAD_HERO_ZERO_DURATION_IN_us}; +static const TimedBit_T SQUAD_HERO_ONE_SPACE = {.symbol = SPACE, .duration = SQUAD_HERO_ONE_DURATION_IN_us}; +static const TimedBit_T SQUAD_HERO_ZERO_MARK = {.symbol = MARK, .duration = SQUAD_HERO_ZERO_DURATION_IN_us}; +static const TimedBit_T SQUAD_HERO_ONE_MARK = {.symbol = MARK, .duration = SQUAD_HERO_ONE_DURATION_IN_us}; +static const TimedBit_T SQUAD_HERO_END = {.symbol = SPACE, .duration = LAST_PULSE}; + +#define SQUAD_HERO_FOOTER 0x9E + +static TimedPulseTrain_T Tag_Send_Buffer; +static DecodedPacket_T Squad_Hero_Tag_Received_Buffers[3] = + { + {.Tag.type = DECODED_PACKET_TYPE_BUFFER_FREE}, + {.Tag.type = DECODED_PACKET_TYPE_BUFFER_FREE}, + {.Tag.type = DECODED_PACKET_TYPE_BUFFER_FREE}}; + +static inline DecodedPacket_T *Get_Tag_Packet_Buffer(void) +{ + if (Squad_Hero_Tag_Received_Buffers[0].Tag.type == DECODED_PACKET_TYPE_BUFFER_FREE) + { + return &Squad_Hero_Tag_Received_Buffers[0]; + } + else if (Squad_Hero_Tag_Received_Buffers[1].Tag.type == DECODED_PACKET_TYPE_BUFFER_FREE) + { + return &Squad_Hero_Tag_Received_Buffers[1]; + } + else + { + // Just use it. + return &Squad_Hero_Tag_Received_Buffers[2]; + } +} + +static inline uint8_t Calculate_Checksum(uint32_t data) +{ + uint8_t result = 0; + + return result; +} + +static inline void PackPulseTrain(TimedPulseTrain_T *pulsetrain, uint32_t data) +{ + pulsetrain->bitstream[0] = SQUAD_HERO_HEADER_MARK; + + for (uint8_t n = 0; n < 16; n++) + { + if ((data & 0x80000000) == 0x80000000) + { + pulsetrain->bitstream[1 + (n * 2)] = SQUAD_HERO_ONE_SPACE; + } + else + { + pulsetrain->bitstream[1 + (n * 2)] = SQUAD_HERO_ZERO_SPACE; + } + data = data << 1; + if ((data & 0x80000000) == 0x80000000) + { + pulsetrain->bitstream[2 + (n * 2)] = SQUAD_HERO_ONE_MARK; + } + else + { + pulsetrain->bitstream[2 + (n * 2)] = SQUAD_HERO_ZERO_MARK; + } + data = data << 1; + } + + // Append the footer. + uint8_t footer = SQUAD_HERO_FOOTER; + for (uint8_t n = 16; n < 20; n++) + { + if ((footer & 0x80) == 0x80) + { + pulsetrain->bitstream[1 + (n * 2)] = SQUAD_HERO_ONE_SPACE; + } + else + { + pulsetrain->bitstream[1 + (n * 2)] = SQUAD_HERO_ZERO_SPACE; + } + footer = footer << 1; + if ((footer & 0x80) == 0x80) + { + pulsetrain->bitstream[2 + (n * 2)] = SQUAD_HERO_ONE_MARK; + } + else + { + pulsetrain->bitstream[2 + (n * 2)] = SQUAD_HERO_ZERO_MARK; + } + footer = footer << 1; + } + + pulsetrain->bitstream[(2 * 20) + 1] = SQUAD_HERO_END; +} + +TimedPulseTrain_T *SQUAD_HERO_EncodePacket(TagPacket_T *packet) +{ + uint32_t packed_data = 0x10AA0000; + packed_data |= (packet->team_ID << 8); + if (packet->damage > 4) + { + packet->damage = 4; + } + packed_data |= packet->damage; + + PackPulseTrain(&Tag_Send_Buffer, packed_data); + + return &Tag_Send_Buffer; +} + +DecodedPacket_T *SQUAD_HERO_MaybeDecodePacket(TimedPulseTrain_T *packet) +{ + // Some implementations (ESP32) do not report odd numbers of pulses. + if ((packet->count != 41) && (packet->count != 42)) + { + // Fail fast! + return NULL; + } + else + { + // Convert pulse durations to bits. + // packet->bitstream[0] is the header mark--ignore it. + + uint32_t data = 0; + for (uint8_t n = 0; n < 32; n++) + { + if ((packet->bitstream[n + 1].duration > MIN_ONE_IN_us) && (packet->bitstream[n + 1].duration < MAX_ONE_IN_us)) + { + // One + data |= ((uint32_t)1 << (31 - n)); + } + else if ((packet->bitstream[n + 1].duration > MIN_ZERO_IN_us) && (packet->bitstream[n + 1].duration < MAX_ZERO_IN_us)) + { + // Zero--nothing to do. + } + else + { + // This mark is neither a zero or a one; abort. + return NULL; + } + } + + uint8_t checksum = 0; + for (uint8_t n = 0; n < 8; n++) + { + if ((packet->bitstream[n + 33].duration > MIN_ONE_IN_us) && (packet->bitstream[n + 33].duration < MAX_ONE_IN_us)) + { + // One + checksum |= ((uint8_t)1 << (7 - n)); + } + else if ((packet->bitstream[n + 33].duration > MIN_ZERO_IN_us) && (packet->bitstream[n + 33].duration < MAX_ZERO_IN_us)) + { + // Zero--nothing to do. + } + else + { + // This mark is neither a zero or a one; abort. + return NULL; + } + } + + DecodedPacket_T *Tag_Rx_Buffer = Get_Tag_Packet_Buffer(); + Tag_Rx_Buffer->Tag.type = DECODED_PACKET_TYPE_TAG_RECEIVED; + Tag_Rx_Buffer->Tag.protocol = SQUAD_HERO_PROTOCOL; + Tag_Rx_Buffer->Tag.player_ID = 0; + Tag_Rx_Buffer->Tag.team_ID = (data & 0x0000FF00) >> 8; + Tag_Rx_Buffer->Tag.damage = (data & 0x000000FF); + Tag_Rx_Buffer->Tag.color = SQUAD_HERO_GetTeamColor(Tag_Rx_Buffer->Tag.team_ID); + return Tag_Rx_Buffer; + } +} + +color_t SQUAD_HERO_GetTeamColor(uint8_t team_ID) +{ + color_t result = COLOR_WHITE; + + if (team_ID == SQUAD_HERO_BLUE_TEAM) + { + result = COLOR_BLUE; + } + else if (team_ID == SQUAD_HERO_RED_TEAM) + { + result = COLOR_RED; + } + else if (team_ID == SQUAD_HERO_GREEN_TEAM) + { + result = COLOR_GREEN; + } + else if (team_ID == SQUAD_HERO_ORANGE_TEAM) + { + result = COLOR_ORANGE; + } + else if (team_ID == SQUAD_HERO_WHITE_TEAM) + { + result = COLOR_WHITE; + } + else + { + // Nothing to do. + } + + return result; +} diff --git a/Protocols/Squad_Hero.h b/Protocols/Squad_Hero.h new file mode 100755 index 0000000..5447783 --- /dev/null +++ b/Protocols/Squad_Hero.h @@ -0,0 +1,37 @@ + +/* + * 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 . + */ + +#ifndef SQUAD_HERO_H +#define SQUAD_HERO_H + +/** \file + * \brief This is the Squad Hero protocol. + */ + +#include +#include + +TimedPulseTrain_T *SQUAD_HERO_EncodePacket(TagPacket_T *packet); +DecodedPacket_T *SQUAD_HERO_MaybeDecodePacket(TimedPulseTrain_T *packet); +color_t SQUAD_HERO_GetTeamColor(uint8_t team_ID); + +#endif // SQUAD_HERO_H diff --git a/Protocols/Test.c b/Protocols/Test.c new file mode 100755 index 0000000..91518b0 --- /dev/null +++ b/Protocols/Test.c @@ -0,0 +1,144 @@ + +/* + * 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 + +#include "SystemK.h" + +static const char *KLOG_TAG = "Test Protocol"; + +#define TEST_PULSE_SUBTRAHEND_IN_us 300 +//! The shortest test packet is a two marks, the first of duration (3 * TEST_PULSE_SUBTRAHEND_IN_us), and the second of duration TEST_PULSE_SUBTRAHEND_IN_us. +#define TEST_MINIMUM_LONGEST_PULSE_DURATION_IN_us (3 * TEST_PULSE_SUBTRAHEND_IN_us) +#define TEST_TOLERANCE_IN_us ((TEST_PULSE_SUBTRAHEND_IN_us / 2) - 1) + +static TimedPulseTrain_T Tag_Send_Buffer; +static DecodedPacket_T Test_Tag_Received_Buffers[3] = + { + {.Tag.type = DECODED_PACKET_TYPE_BUFFER_FREE}, + {.Tag.type = DECODED_PACKET_TYPE_BUFFER_FREE}, + {.Tag.type = DECODED_PACKET_TYPE_BUFFER_FREE}}; + +static inline DecodedPacket_T *Get_Tag_Packet_Buffer(void) +{ + if (Test_Tag_Received_Buffers[0].Tag.type == DECODED_PACKET_TYPE_BUFFER_FREE) + { + return &Test_Tag_Received_Buffers[0]; + } + else if (Test_Tag_Received_Buffers[1].Tag.type == DECODED_PACKET_TYPE_BUFFER_FREE) + { + return &Test_Tag_Received_Buffers[1]; + } + else + { + // Just use it. + return &Test_Tag_Received_Buffers[2]; + } +} + +static inline void PackPulseTrain(TimedPulseTrain_T *pulsetrain, uint32_t longest_pulse_duration_in_us) +{ + uint_fast8_t index = 0; + uint32_t next_pulse_duration_in_us = longest_pulse_duration_in_us; + TimedBit_T next_bit; + + if (next_pulse_duration_in_us < TEST_MINIMUM_LONGEST_PULSE_DURATION_IN_us) + { + next_pulse_duration_in_us = TEST_MINIMUM_LONGEST_PULSE_DURATION_IN_us; + } + + while ((next_pulse_duration_in_us >= TEST_PULSE_SUBTRAHEND_IN_us) && (index < MAX_PULSES)) + { + // Even indicies are marks. + if ((index % 2) == 0) + { + next_bit.symbol = MARK; + } + // Odd indicies are spaces. + else + { + next_bit.symbol = SPACE; + } + next_bit.duration = next_pulse_duration_in_us; + pulsetrain->bitstream[index] = next_bit; + + next_pulse_duration_in_us = next_pulse_duration_in_us - TEST_PULSE_SUBTRAHEND_IN_us; + index++; + } + + // Mark the end of the train. + next_bit.symbol = SPACE; + next_bit.duration = LAST_PULSE; + pulsetrain->bitstream[index] = next_bit; + pulsetrain->count = index; +} + +TimedPulseTrain_T *TEST_EncodePacket(TagPacket_T *packet) +{ + uint32_t packed_data = 2100; + + PackPulseTrain(&Tag_Send_Buffer, packed_data); + + return &Tag_Send_Buffer; +} + +DecodedPacket_T *TEST_MaybeDecodePacket(TimedPulseTrain_T *packet) +{ + // packet->bitstream[0] is the longest pulse--assume it was received correctly. + uint32_t longest_pulse_duration_in_us = packet->bitstream[0].duration; + + if ((longest_pulse_duration_in_us - TEST_TOLERANCE_IN_us) < TEST_MINIMUM_LONGEST_PULSE_DURATION_IN_us) + { + // The first pulse is too short! + return NULL; + } + + for (uint_fast8_t index = 1; index < packet->count; index++) + { + uint32_t expected_pulse_duration_in_us = longest_pulse_duration_in_us - (index * TEST_PULSE_SUBTRAHEND_IN_us); + + if (packet->bitstream[index].duration < (expected_pulse_duration_in_us - TEST_TOLERANCE_IN_us)) + { + KLOG_WARN(KLOG_TAG, "Pulse %u is too short! Expected %lu; received %u.", index, expected_pulse_duration_in_us, packet->bitstream[index].duration); + return NULL; + } + if (packet->bitstream[index].duration > (expected_pulse_duration_in_us + TEST_TOLERANCE_IN_us)) + { + KLOG_WARN(KLOG_TAG, "Pulse %u is too long! Expected %lu; received %u.", index, expected_pulse_duration_in_us, packet->bitstream[index].duration); + return NULL; + } + } + + DecodedPacket_T *Command_Rx_Buffer = Get_Tag_Packet_Buffer(); + Command_Rx_Buffer->Command.type = DECODED_PACKET_TYPE_COMMAND_RECEIVED; + Command_Rx_Buffer->Command.protocol = TEST_PROTOCOL; + Command_Rx_Buffer->Command.data = longest_pulse_duration_in_us; + return Command_Rx_Buffer; +} + +color_t TEST_GetTeamColor(__attribute__((unused)) uint8_t team_ID) +{ + color_t result = COLOR_WHITE; + + return result; +} diff --git a/Protocols/Test.h b/Protocols/Test.h new file mode 100755 index 0000000..d02ded0 --- /dev/null +++ b/Protocols/Test.h @@ -0,0 +1,38 @@ + +/* + * 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 . + */ + +#ifndef TEST_H +#define TEST_H + +/** \file + * \brief This is the test protocol. + * + */ + +#include +#include + +TimedPulseTrain_T *TEST_EncodePacket(TagPacket_T *packet); +DecodedPacket_T *TEST_MaybeDecodePacket(TimedPulseTrain_T *packet); +color_t TEST_GetTeamColor(uint8_t team_ID); + +#endif // TEST_H diff --git a/Results.h b/Results.h new file mode 100644 index 0000000..45959aa --- /dev/null +++ b/Results.h @@ -0,0 +1,50 @@ +/* + * 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 . + */ + +/** \file + * \brief This file defines the possible results of calls to the KTag system (SystemK). + * + */ + +#ifndef RESULTS_H +#define RESULTS_H + +typedef enum +{ + SYSTEMK_RESULT_SUCCESS = 0, + SYSTEMK_RESULT_UNSPECIFIED_FAILURE, + SYSTEMK_RESULT_NOT_IMPLEMENTED, + SYSTEMK_RESULT_NOT_ENOUGH_MEMORY, + SYSTEMK_RESULT_WRONG_DATATYPE, + SYSTEMK_RESULT_TOO_FEW_DATA, + SYSTEMK_RESULT_TOO_MANY_DATA, + SYSTEMK_RESULT_QUEUE_IS_FULL, + SYSTEMK_RESULT_NOT_READY, + SYSTEMK_RESULT_FAILED_TO_CREATE_RTOS_TASK, + SYSTEMK_RESULT_FILE_NOT_FOUND, + SYSTEMK_RESULT_KEY_NOT_FOUND, + SYSTEMK_RESULT_READ_FAILED, + SYSTEMK_RESULT_WRITE_FAILED, + SYSTEMK_RESULT_OVERFLOW, + SYSTEMK_RESULT_UNDERFLOW +} SystemKResult_T; + +#endif // RESULTS_H diff --git a/Settings/Settings_Interface.h b/Settings/Settings_Interface.h new file mode 100644 index 0000000..4f80805 --- /dev/null +++ b/Settings/Settings_Interface.h @@ -0,0 +1,41 @@ +/* + * 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 . + */ + +#ifndef SETTINGS_INTERFACE_H +#define SETTINGS_INTERFACE_H + +typedef enum +{ + SYSTEMK_SETTING_IS_RIGHT_HANDED, + SYSTEMK_SETTING_AUDIO_VOLUME, + SYSTEMK_SETTING_TEAMID, + SYSTEMK_SETTING_PLAYERID, + SYSTEMK_SETTING_WEAPONID, + SYSTEMK_SETTING_T_START_GAME_in_ms +} SystemKSettingID_T; + +SystemKResult_T SETTINGS_get_uint8_t(SystemKSettingID_T id, uint8_t * value); +SystemKResult_T SETTINGS_set_uint8_t(SystemKSettingID_T id, uint8_t value); +SystemKResult_T SETTINGS_get_uint32_t(SystemKSettingID_T id, uint32_t * value); +SystemKResult_T SETTINGS_set_uint32_t(SystemKSettingID_T id, uint32_t value); +SystemKResult_T SETTINGS_Save(void); + +#endif // SETTINGS_INTERFACE_H diff --git a/States/Playing/State_Playing.c b/States/Playing/State_Playing.c new file mode 100644 index 0000000..832d2c1 --- /dev/null +++ b/States/Playing/State_Playing.c @@ -0,0 +1,239 @@ +/* + * 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); + } + } + } + } +} diff --git a/States/Playing/State_Playing.h b/States/Playing/State_Playing.h new file mode 100644 index 0000000..4f949d0 --- /dev/null +++ b/States/Playing/State_Playing.h @@ -0,0 +1,47 @@ +/* + * 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 . + */ + +#ifndef STATE_PLAYING_H +#define STATE_PLAYING_H + + +inline bool STATE_IsPlayingSubstate(StateID_T state) +{ + bool is_substate = false; + + if ((state == STATE_PLAYING__INTERACTING) || + (state == STATE_PLAYING__TAGGED_OUT)) + { + is_substate = true; + } + return is_substate; +} + +void Playing_Entry(StateMachineContext_T * context); +void Playing_Exit(StateMachineContext_T * context); +void HandleBLEStatusPacket(const BLE_StatusPacket_T * const packet); +void HandleBLETagPacket(const BLE_TagPacket_T * const packet); + +/* Include Files */ +#include "States/Playing/State_Playing__Interacting.h" +#include "States/Playing/State_Playing__Tagged_Out.h" + +#endif // STATE_PLAYING_H diff --git a/States/Playing/State_Playing__Interacting.c b/States/Playing/State_Playing__Interacting.c new file mode 100644 index 0000000..f8d968f --- /dev/null +++ b/States/Playing/State_Playing__Interacting.c @@ -0,0 +1,425 @@ +/* + * 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" +#include + +#define LOG_INTERACTING_SUBSTATE + +static void Playing__Interacting_Entry(StateMachineContext_T * context); +static void Playing__Interacting_Do(StateMachineContext_T * context); +static void Playing__Interacting_Exit(StateMachineContext_T * context); +static uint8_t Health_in_percent; + +//! Activities for the **Interacting** substate of the **Playing** state. +const StateActivity_T STATE_PLAYING__INTERACTING_Activities = +{ + .Entry = Playing__Interacting_Entry, + .Do = Playing__Interacting_Do, + .Exit = Playing__Interacting_Exit +}; + +static TickType_t Time_Of_Last_Shot = 0; +static const uint32_t FIXED_SHOT_HOLDOFF_in_ms = 3000; +static const uint32_t RANDOM_SHOT_HOLDOFF_in_ms = 1000; +static TickType_t Shot_Holdoff_Time; +static color_t ReceivedTagColor = 0x00000000; +static const uint32_t PURPLE_TAG_INVINCIBILITY_WINDOW_in_ms = 1000; + +#ifdef LOG_INTERACTING_SUBSTATE + static const char *KLOG_TAG = "STATE_PLAYING__INTERACTING"; +#endif // LOG_INTERACTING_SUBSTATE + +//! Sets up the Interacting substate. +/*! + * \param context Context in which this substate is being run. + */ +static void Playing__Interacting_Entry(StateMachineContext_T * context) +{ + if (STATE_IsPlayingSubstate(context->States.Previous_State) == false) + { + Playing_Entry(context); + srand(xTaskGetTickCount()); + } + LOG("Entering the Interacting substate of the Playing state."); +} + +//! Executes the Interacting substate. +/*! + * \param context Context in which this substate is being run. + */ +static void Playing__Interacting_Do(StateMachineContext_T * context) +{ + portBASE_TYPE xStatus; + static KEvent_T Event; + + xStatus = Receive_KEvent(&Event); + + if (xStatus == pdPASS) + { + switch (Event.ID) + { + case KEVENT_TRIGGER_SWITCH_PRESSED: + +#ifdef LOG_INTERACTING_SUBSTATE + KLOG_INFO(KLOG_TAG, "Trigger pressed."); +#endif // LOG_INTERACTING_SUBSTATE + + if ((xTaskGetTickCount() - Time_Of_Last_Shot) > Shot_Holdoff_Time) + { + if (Send_Tag() != SYSTEMK_RESULT_SUCCESS) + { + KEvent_T misfire_event = { .ID = KEVENT_MISFIRE, .Data = (void *)0x00 }; + Post_KEvent(&misfire_event); + } + } + else + { + KEvent_T misfire_event = { .ID = KEVENT_MISFIRE, .Data = (void *)0x00 }; + Post_KEvent(&misfire_event); + } + break; + + case KEVENT_TRIGGER_SWITCH_RELEASED: + +#ifdef LOG_INTERACTING_SUBSTATE + KLOG_INFO(KLOG_TAG, "Trigger released."); +#endif // LOG_INTERACTING_SUBSTATE + + { + uint32_t duration_of_press_in_ms = (uint32_t)Event.Data; + + if (duration_of_press_in_ms > 10000) + { + AudioAction_T audio_action = {.ID = AUDIO_PLAY_ELECTRONIC_DANCE_MUSIC, .Data = (void *)0x00}; + Perform_Audio_Action(&audio_action); + NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_TEST_PATTERN, .Prominence = NEOPIXELS_FOREGROUND, .Data = (void *)0x00}; + xQueueSend(xQueueNeoPixels, &neopixels_action, 0); + } + + // Was it a "long" press? + else if (duration_of_press_in_ms > 3000) + { + Health_in_percent = (uint8_t)Get_Health(); + 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); + } + } + break; + + case KEVENT_UP_SWITCH_LONG_PRESSED: + { + AudioAction_T audio_action = {.ID = AUDIO_PLAY_ELECTRONIC_DANCE_MUSIC, .Data = (void *)0x00}; + Perform_Audio_Action(&audio_action); + NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_TEST_PATTERN, .Prominence = NEOPIXELS_FOREGROUND, .Data = (void *)0x00}; + xQueueSend(xQueueNeoPixels, &neopixels_action, 0); + } + break; + + case KEVENT_UP_SWITCH_RELEASED: + { + //AudioAction_T audio_action = {.ID = AUDIO_SILENCE, .Data = (void *)0x00}; + //xQueueSend(xQueueAudio, &audio_action, 0); + //NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_ALL_OFF, .Prominence = NEOPIXELS_FOREGROUND, .Data = (void *)0x00}; + //xQueueSend(xQueueNeoPixels, &neopixels_action, 0); + } + break; + + case KEVENT_DOWN_SWITCH_LONG_PRESSED: + { + NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_FLASHLIGHT_ON, .Prominence = NEOPIXELS_FOREGROUND, .Data = (void *)0x00}; + xQueueSend(xQueueNeoPixels, &neopixels_action, 0); + } + break; + + case KEVENT_DOWN_SWITCH_RELEASED: + { + NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_ALL_OFF, .Prominence = NEOPIXELS_FOREGROUND, .Data = (void *)0x00}; + xQueueSend(xQueueNeoPixels, &neopixels_action, 0); + } + break; + + case KEVENT_FORWARD_SWITCH_PRESSED: + { + NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_FLAMETHROWER, .Prominence = NEOPIXELS_FOREGROUND, .Data = (void *)0x00}; + xQueueSend(xQueueNeoPixels, &neopixels_action, 0); + } + break; + + case KEVENT_FORWARD_SWITCH_RELEASED: + { + NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_ALL_OFF, .Prominence = NEOPIXELS_FOREGROUND, .Data = (void *)0x00}; + xQueueSend(xQueueNeoPixels, &neopixels_action, 0); + } + break; + + case KEVENT_BACKWARD_SWITCH_PRESSED: + { + Health_in_percent = (uint8_t)Get_Health(); + 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); + } + break; + + case KEVENT_BACKWARD_SWITCH_RELEASED: + { + NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_ALL_OFF, .Prominence = NEOPIXELS_FOREGROUND, .Data = (void *)0x00}; + xQueueSend(xQueueNeoPixels, &neopixels_action, 0); + } + break; + + case KEVENT_TAG_SENT: + { + // "Flip ya'? Double or nuthin'!" + uint32_t holdoff_in_ms = (rand() % 2) * RANDOM_SHOT_HOLDOFF_in_ms; + holdoff_in_ms += FIXED_SHOT_HOLDOFF_in_ms; + Shot_Holdoff_Time = (holdoff_in_ms / portTICK_PERIOD_MS); +#ifdef LOG_INTERACTING_SUBSTATE + KLOG_INFO(KLOG_TAG, "Tag sent. Holdoff: %lu ms", holdoff_in_ms); +#endif // LOG_INTERACTING_SUBSTATE + + Time_Of_Last_Shot = xTaskGetTickCount(); + + AudioAction_T audio_action = {.ID = AUDIO_PLAY_SHOT_FIRED, .Data = (void *)0x00}; + Perform_Audio_Action(&audio_action); + NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_PLAY_SHOT_FIRED, .Prominence = NEOPIXELS_FOREGROUND, .Data = (void *)0x00}; + xQueueSend(xQueueNeoPixels, &neopixels_action, 0); + + Increment_Shots_Fired(); + } + break; + + case KEVENT_TAG_RECEIVED: + { + uint8_t protocol = ((DecodedPacket_T*)Event.Data)->Tag.protocol; + uint16_t team_ID = ((DecodedPacket_T*)Event.Data)->Tag.team_ID; + uint16_t player_ID = ((DecodedPacket_T*)Event.Data)->Tag.player_ID; + uint16_t damage = ((DecodedPacket_T*)Event.Data)->Tag.damage; + ReceivedTagColor = ((DecodedPacket_T*)Event.Data)->Tag.color; + TagSensorLocation_T receiver = ((DecodedPacket_T*)Event.Data)->Tag.receiver; + TeamID_t rxd_common_team_ID = Resolve_Common_Team_ID(team_ID); + +#ifdef LOG_INTERACTING_SUBSTATE + switch (receiver) + { + default: + KLOG_INFO(KLOG_TAG, "Tag from unknown sensor: Team: %u Player: %u Damage: %u", team_ID, player_ID, damage); + break; + + case TAG_SENSOR_FORWARD: + KLOG_INFO(KLOG_TAG, "Tag from forward sensor: Team: %u Player: %u Damage: %u", team_ID, player_ID, damage); + break; + + case TAG_SENSOR_LEFT: + KLOG_INFO(KLOG_TAG, "Tag from left sensor: Team: %u Player: %u Damage: %u", team_ID, player_ID, damage); + break; + + case TAG_SENSOR_RIGHT: + KLOG_INFO(KLOG_TAG, "Tag from right sensor: Team: %u Player: %u Damage: %u", team_ID, player_ID, damage); + break; + + case TAG_SENSOR_REMOTE: + KLOG_INFO(KLOG_TAG, "Tag from remote sensor: Team: %u Player: %u Damage: %u", team_ID, player_ID, damage); + break; + + } +#endif // LOG_INTERACTING_SUBSTATE + + if (Still_Playing() == true) + { + uint8_t my_team_ID; + uint8_t my_player_ID; + + (void) SETTINGS_get_uint8_t(SYSTEMK_SETTING_TEAMID, &my_team_ID); + (void) SETTINGS_get_uint8_t(SYSTEMK_SETTING_PLAYERID, &my_player_ID); + + if (Team_Can_Tag_Me(rxd_common_team_ID) == true) + { + // Checks to make sure the tagger is not seeing its own tag. + bool tag_might_be_mine = false; + + if (Resolve_Common_Team_ID(team_ID) == TEAM_PURPLE) + { + if ((xTaskGetTickCount() - Time_Of_Last_Shot) < (PURPLE_TAG_INVINCIBILITY_WINDOW_in_ms / portTICK_PERIOD_MS)) + { + // Special handling for receiving a Purple Tag while sending a Purple Tag using the DBQ Protocol. + if (protocol == DUBUQUE_PROTOCOL) + { + if (damage <= 20) + { + tag_might_be_mine = true; + } + else + { + // If the accumulated damage is more than 20, it means I've received my own tag AND another tag. + // Set the damage to the value of a single tag. + damage = 10; + } + } + // For protocols other than DBQ, we cannot distinguish my tag from yours. The solution is to + // allow a period of invincibility from Purple tags after sending ours. + // Note that this means for most protocols, simultaneous Purple vs. Purple results in no tags. + else + { + tag_might_be_mine = true; + } + } + } + + // If the tag is clearly not mine, process it. + if (tag_might_be_mine == false) + { + Reduce_Health(damage); + + 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); + + Increment_Tags_Received(); + } + } + else if (player_ID != my_player_ID) + { + // Friendly fire! + AudioAction_T audio_action = {.ID = AUDIO_PLAY_FRIENDLY_FIRE, .Data = (void *)0x00}; + Perform_Audio_Action(&audio_action); + } + } + } + FreeDecodedPacketBuffer(Event.Data); + break; + + case KEVENT_TAGGED_OUT: + { + context->States.Next_State = STATE_PLAYING__TAGGED_OUT; + } + break; + + case KEVENT_MISFIRE: + { + AudioAction_T audio_action = {.ID = AUDIO_PLAY_MISFIRE, .Data = (void *)0x00}; + Perform_Audio_Action(&audio_action); + } + break; + + case KEVENT_NEAR_MISS: + { + AudioAction_T audio_action = {.ID = AUDIO_PLAY_NEAR_MISS, .Data = (void *)0x00}; + Perform_Audio_Action(&audio_action); + } + break; + + case KEVENT_COMMAND_RECEIVED: + FreeDecodedPacketBuffer(Event.Data); + break; + + case KEVENT_BLE_PACKET_RECEIVED: +#ifdef LOG_INTERACTING_SUBSTATE + //KLOG_INFO(KLOG_TAG, "KEVENT_BLE_PACKET_RECEIVED from %02X:%02X:%02X:%02X:%02X:%02X", + // ((BLE_Packet_T *)Event.Data)->Generic.BD_ADDR[5], + // ((BLE_Packet_T *)Event.Data)->Generic.BD_ADDR[4], + // ((BLE_Packet_T *)Event.Data)->Generic.BD_ADDR[3], + // ((BLE_Packet_T *)Event.Data)->Generic.BD_ADDR[2], + // ((BLE_Packet_T *)Event.Data)->Generic.BD_ADDR[1], + // ((BLE_Packet_T *)Event.Data)->Generic.BD_ADDR[0] + //); +#endif // LOG_INTERACTING_SUBSTATE + if (((BLE_Packet_T *)Event.Data)->Generic.type == BLE_PACKET_TYPE_STATUS) + { + HandleBLEStatusPacket((BLE_StatusPacket_T *)Event.Data); + } + else if (((BLE_Packet_T *)Event.Data)->Generic.type == BLE_PACKET_TYPE_TAG) + { + HandleBLETagPacket((BLE_TagPacket_T *)Event.Data); + } + + BLE_FreePacketBuffer(Event.Data); + break; + + case KEVENT_ACCESSORY_SWITCH_PRESSED: + { +#ifdef LOG_INTERACTING_SUBSTATE + uint32_t time_since_last_press_in_ms = (uint32_t)Event.Data; + KLOG_INFO(KLOG_TAG, "Accessory pressed after %lu ms.", time_since_last_press_in_ms); +#endif // LOG_INTERACTING_SUBSTATE + + if (Use_Bomb_If_Available() == true) + { + // BOOM! + BLE_UpdateTagPacket(100, COLOR_YELLOW, (uint8_t[BD_ADDR_SIZE]){0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}); + AudioAction_T audio_action = {.ID = AUDIO_PLAY_BOMB, .Data = (void *)0x00}; + Perform_Audio_Action(&audio_action); + } + else + { + AudioAction_T audio_action = {.ID = AUDIO_PLAY_BONK, .Data = (void *)0x00}; + Perform_Audio_Action(&audio_action); + } + } + break; + + case KEVENT_ACCESSORY_SWITCH_RELEASED: + { +#ifdef LOG_INTERACTING_SUBSTATE + uint32_t duration_of_press_in_ms = (uint32_t)Event.Data; + KLOG_INFO(KLOG_TAG, "Accessory released after %lu ms.", duration_of_press_in_ms); +#endif // LOG_INTERACTING_SUBSTATE + } + break; + + case KEVENT_GAME_OVER: + { + AudioAction_T audio_action = {.ID = AUDIO_PLAY_GAME_OVER, .Data = (void *)0x00}; + Perform_Audio_Action(&audio_action); + Transition_For_Event(context, STATE_WRAPPING_UP, &Event); + } + break; + + default: + // All other events are ignored in this state. + ProcessUnhandledEvent(&Event); + break; + } + } +} + +//! Cleans up the Interacting substate. +/*! + * \param context Context in which this substate is being run. + */ +static void Playing__Interacting_Exit(StateMachineContext_T * context) +{ + if (STATE_IsPlayingSubstate(context->States.Next_State) == false) + { + Playing_Exit(context); + } +} diff --git a/States/Playing/State_Playing__Interacting.h b/States/Playing/State_Playing__Interacting.h new file mode 100644 index 0000000..385d2a0 --- /dev/null +++ b/States/Playing/State_Playing__Interacting.h @@ -0,0 +1,32 @@ +/* + * 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 . + */ + +#ifndef STATE_PLAYING__INTERACTING_H +#define STATE_PLAYING__INTERACTING_H + +/* Include Files */ + +/* Definitions */ + +/* External Data */ +extern const StateActivity_T STATE_PLAYING__INTERACTING_Activities; + +#endif // STATE_PLAYING__INTERACTING_H diff --git a/States/Playing/State_Playing__Tagged_Out.c b/States/Playing/State_Playing__Tagged_Out.c new file mode 100644 index 0000000..87e445a --- /dev/null +++ b/States/Playing/State_Playing__Tagged_Out.c @@ -0,0 +1,146 @@ +/* + * 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 Playing__Tagged_Out_Entry(StateMachineContext_T * context); +static void Playing__Tagged_Out_Do(StateMachineContext_T * context); +static void Playing__Tagged_Out_Exit(StateMachineContext_T * context); + +//! Activities for the **Tagged Out** substate of the **Playing** state. +const StateActivity_T STATE_PLAYING__TAGGED_OUT_Activities = +{ + .Entry = Playing__Tagged_Out_Entry, + .Do = Playing__Tagged_Out_Do, + .Exit = Playing__Tagged_Out_Exit +}; + +//! Sets up the Tagged Out substate. +/*! + * \param context Context in which this substate is being run. + */ +static void Playing__Tagged_Out_Entry(StateMachineContext_T * context) +{ + if (STATE_IsPlayingSubstate(context->States.Previous_State) == false) + { + Playing_Entry(context); + } + + LOG("Entering the Tagged Out substate of the Playing state."); + AudioAction_T audio_action = {.ID = AUDIO_PLAY_TAGGED_OUT, .Data = (void *)0x00}; + Perform_Audio_Action(&audio_action); + NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_TAGGED_OUT, .Prominence = NEOPIXELS_FOREGROUND, .Data = (void *)0x00}; + xQueueSend(xQueueNeoPixels, &neopixels_action, 0); + + BLE_UpdateStatusPacket(); + + Increment_Times_Tagged_Out(); +} + +//! Executes the Tagged Out substate. +/*! + * \param context Context in which this substate is being run. + */ +static void Playing__Tagged_Out_Do(StateMachineContext_T * context) +{ + portBASE_TYPE xStatus; + static KEvent_T Event; + + xStatus = Receive_KEvent(&Event); + + if (xStatus == pdPASS) + { + switch (Event.ID) + { + case KEVENT_BLE_PACKET_RECEIVED: + if (((BLE_Packet_T *)Event.Data)->Generic.type == BLE_PACKET_TYPE_TAG) + { + HandleBLETagPacket((BLE_TagPacket_T *)Event.Data); + + if (Back_In() == true) + { + // TODO: Get this from settings. + Set_Available_Bombs(1); + Transition_For_Event(context, STATE_PLAYING__INTERACTING, &Event); + } + } + else + { + BLE_FreePacketBuffer(Event.Data); + } + break; + + + case KEVENT_TRIGGER_SWITCH_RELEASED: + { + uint32_t duration_of_press_in_ms = (uint32_t)Event.Data; + + // Was it a "long" press? + if (duration_of_press_in_ms > 5000) + { + Set_Health(MAX_HEALTH); + // TODO: Get this from settings. + Set_Available_Bombs(1); + Transition_For_Event(context, STATE_PLAYING__INTERACTING, &Event); + } + } + break; + + case KEVENT_GAME_OVER: + { + AudioAction_T audio_action = {.ID = AUDIO_PLAY_GAME_OVER, .Data = (void *)0x00}; + Perform_Audio_Action(&audio_action); + Transition_For_Event(context, STATE_WRAPPING_UP, &Event); + } + break; + + case KEVENT_PLAY_PRESSED_ON_REMOTE: + Set_Health(MAX_HEALTH); + // TODO: Get this from settings. + Set_Available_Bombs(1); + Transition_For_Event(context, STATE_PLAYING__INTERACTING, &Event); + break; + + default: + // All other events are ignored in this state. + ProcessUnhandledEvent(&Event); + break; + } + } +} + +//! Cleans up the Tagged Out substate. +/*! + * \param context Context in which this substate is being run. + */ +static void Playing__Tagged_Out_Exit(StateMachineContext_T * context) +{ + NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_ALL_OFF, .Prominence = NEOPIXELS_FOREGROUND, .Data = (void *)0x00}; + xQueueSend(xQueueNeoPixels, &neopixels_action, 0); + AudioAction_T audio_action = {.ID = AUDIO_SILENCE, .Data = (void *)0x00}; + Perform_Audio_Action(&audio_action); + + if (STATE_IsPlayingSubstate(context->States.Next_State) == false) + { + Playing_Exit(context); + } +} diff --git a/States/Playing/State_Playing__Tagged_Out.h b/States/Playing/State_Playing__Tagged_Out.h new file mode 100644 index 0000000..fc67c1b --- /dev/null +++ b/States/Playing/State_Playing__Tagged_Out.h @@ -0,0 +1,32 @@ +/* + * 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 . + */ + +#ifndef STATE_PLAYING__TAGGED_OUT_H +#define STATE_PLAYING__TAGGED_OUT_H + +/* Include Files */ + +/* Definitions */ + +/* External Data */ +extern const StateActivity_T STATE_PLAYING__TAGGED_OUT_Activities; + +#endif // STATE_PLAYING__TAGGED_OUT_H diff --git a/States/Starting_Game/State_Starting_Game.c b/States/Starting_Game/State_Starting_Game.c new file mode 100644 index 0000000..8f75a90 --- /dev/null +++ b/States/Starting_Game/State_Starting_Game.c @@ -0,0 +1,39 @@ +/* + * 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" + +void Starting_Game_Entry(StateMachineContext_T * context) +{ + +#ifdef LOG_STATE_MACHINE + LOG("Entering the Starting Game state."); + vTaskDelay(pdMS_TO_TICKS(10)); +#endif // LOG_STATE_MACHINE + + AudioAction_T audio_action = {.ID = AUDIO_PLAY_STARTING_THEME, .Data = (void *)0x00}; + Perform_Audio_Action(&audio_action); +} + +void Starting_Game_Exit(StateMachineContext_T * context) +{ +} \ No newline at end of file diff --git a/States/Starting_Game/State_Starting_Game.h b/States/Starting_Game/State_Starting_Game.h new file mode 100644 index 0000000..ff02203 --- /dev/null +++ b/States/Starting_Game/State_Starting_Game.h @@ -0,0 +1,34 @@ +/* + * 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 . + */ + +#ifndef STATE_STARTING_GAME_H +#define STATE_STARTING_GAME_H + + +extern void Starting_Game_Entry(StateMachineContext_T * context); +extern void Starting_Game_Exit(StateMachineContext_T * context); + +/* Include Files */ +#include "States/Starting_Game/State_Starting_Game__Counting_Down.h" +#include "States/Starting_Game/State_Starting_Game__Instigating.h" +#include "States/Starting_Game/State_Starting_Game__Responding.h" + +#endif // STATE_STARTING_GAME_H diff --git a/States/Starting_Game/State_Starting_Game__Counting_Down.c b/States/Starting_Game/State_Starting_Game__Counting_Down.c new file mode 100644 index 0000000..da80484 --- /dev/null +++ b/States/Starting_Game/State_Starting_Game__Counting_Down.c @@ -0,0 +1,145 @@ +/* + * 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 Starting_Game__Counting_Down_Entry(StateMachineContext_T * context); +static void Starting_Game__Counting_Down_Do(StateMachineContext_T * context); +static void Starting_Game__Counting_Down_Exit(StateMachineContext_T * context); + +static uint_fast8_t N_Lights_Lit = 0; + +//! Activities for the **Counting Down** substate of the **Starting Game** state. +const StateActivity_T STATE_STARTING_GAME__COUNTING_DOWN_Activities = +{ + .Entry = Starting_Game__Counting_Down_Entry, + .Do = Starting_Game__Counting_Down_Do, + .Exit = Starting_Game__Counting_Down_Exit +}; + +//! Sets up the Counting Down substate. +/*! + * \param context Context in which this substate is being run. + */ +static void Starting_Game__Counting_Down_Entry(StateMachineContext_T * context) +{ + LOG("Entering the Counting Down substate of the Starting Game state."); + + Prepare_Tag(); + Reset_Shots_Fired(); + Reset_Tags_Received(); + Reset_Times_Tagged_Out(); + // TODO: Get this from settings. + Set_Available_Bombs(1); + + N_Lights_Lit = 0; + AudioAction_T audio_action = {.ID = AUDIO_SILENCE, .Data = (void *)0x00}; + Perform_Audio_Action(&audio_action); + NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_ALL_OFF, .Prominence = NEOPIXELS_FOREGROUND, .Data = (void *)0}; + xQueueSend(xQueueNeoPixels, &neopixels_action, 0); +} + +//! Executes the Counting Down substate. +/*! + * \param context Context in which this substate is being run. + */ +static void Starting_Game__Counting_Down_Do(StateMachineContext_T * context) +{ + portBASE_TYPE xStatus; + static KEvent_T Event; + + xStatus = Receive_KEvent(&Event); + + if (xStatus == pdPASS) + { + switch (Event.ID) + { + default: + // All events are ignored in this state. + ProcessUnhandledEvent(&Event); + break; + } + } + + if ((N_Lights_Lit == 0) && (GetTimeInState_in_ms(context) >= 1000)) + { + N_Lights_Lit++; + AudioAction_T audio_action = {.ID = AUDIO_PLAY_BOOP, .Data = (void *)0x00}; + Perform_Audio_Action(&audio_action); + NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_COUNTDOWN, .Prominence = NEOPIXELS_FOREGROUND, .Data = (void *)&N_Lights_Lit}; + xQueueSend(xQueueNeoPixels, &neopixels_action, 0); + } + else if ((N_Lights_Lit == 1) && (GetTimeInState_in_ms(context) >= 2000)) + { + N_Lights_Lit++; + AudioAction_T audio_action = {.ID = AUDIO_PLAY_BOOP, .Data = (void *)0x00}; + Perform_Audio_Action(&audio_action); + NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_COUNTDOWN, .Prominence = NEOPIXELS_FOREGROUND, .Data = (void *)&N_Lights_Lit}; + xQueueSend(xQueueNeoPixels, &neopixels_action, 0); + } + else if ((N_Lights_Lit == 2) && (GetTimeInState_in_ms(context) >= 3000)) + { + N_Lights_Lit++; + AudioAction_T audio_action = {.ID = AUDIO_PLAY_BOOP, .Data = (void *)0x00}; + Perform_Audio_Action(&audio_action); + NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_COUNTDOWN, .Prominence = NEOPIXELS_FOREGROUND, .Data = (void *)&N_Lights_Lit}; + xQueueSend(xQueueNeoPixels, &neopixels_action, 0); + } + else if ((N_Lights_Lit == 3) && (GetTimeInState_in_ms(context) >= 4000)) + { + N_Lights_Lit++; + AudioAction_T audio_action = {.ID = AUDIO_PLAY_BOOP, .Data = (void *)0x00}; + Perform_Audio_Action(&audio_action); + NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_COUNTDOWN, .Prominence = NEOPIXELS_FOREGROUND, .Data = (void *)&N_Lights_Lit}; + xQueueSend(xQueueNeoPixels, &neopixels_action, 0); + } + else if ((N_Lights_Lit == 4) && (GetTimeInState_in_ms(context) >= 5000)) + { + N_Lights_Lit++; + AudioAction_T audio_action = {.ID = AUDIO_PLAY_BOOP, .Data = (void *)0x00}; + Perform_Audio_Action(&audio_action); + NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_COUNTDOWN, .Prominence = NEOPIXELS_FOREGROUND, .Data = (void *)&N_Lights_Lit}; + xQueueSend(xQueueNeoPixels, &neopixels_action, 0); + } + else if ((N_Lights_Lit == 5) && (GetTimeInState_in_ms(context) >= 6000)) + { + N_Lights_Lit++; + AudioAction_T audio_action = {.ID = AUDIO_PLAY_BEEP, .Data = (void *)0x00}; + Perform_Audio_Action(&audio_action); + NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_ALL_OFF, .Prominence = NEOPIXELS_FOREGROUND, .Data = (void *)0}; + xQueueSend(xQueueNeoPixels, &neopixels_action, 0); + + Event.ID = KEVENT_AUTOMATIC_TRANSITION; + Event.Data = NULL; + Transition_For_Event(context, STATE_PLAYING__INTERACTING, &Event); + } +} + +//! Cleans up the Counting Down substate. +/*! + * \param context Context in which this substate is being run. + */ +static void Starting_Game__Counting_Down_Exit(StateMachineContext_T * context) +{ + // Last of all, exit the state containing this substate. + Starting_Game_Exit(context); +} diff --git a/States/Starting_Game/State_Starting_Game__Counting_Down.h b/States/Starting_Game/State_Starting_Game__Counting_Down.h new file mode 100644 index 0000000..925f7bf --- /dev/null +++ b/States/Starting_Game/State_Starting_Game__Counting_Down.h @@ -0,0 +1,32 @@ +/* + * 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 . + */ + +#ifndef STATE_STARTING_GAME__COUNTING_DOWN_H +#define STATE_STARTING_GAME__COUNTING_DOWN_H + +/* Include Files */ + +/* Definitions */ + +/* External Data */ +extern const StateActivity_T STATE_STARTING_GAME__COUNTING_DOWN_Activities; + +#endif // STATE_STARTING_GAME__COUNTING_DOWN_H diff --git a/States/Starting_Game/State_Starting_Game__Instigating.c b/States/Starting_Game/State_Starting_Game__Instigating.c new file mode 100644 index 0000000..e4664b3 --- /dev/null +++ b/States/Starting_Game/State_Starting_Game__Instigating.c @@ -0,0 +1,137 @@ +/* + * 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 Starting_Game__Instigating_Entry(StateMachineContext_T * context); +static void Starting_Game__Instigating_Do(StateMachineContext_T * context); +static void Starting_Game__Instigating_Exit(StateMachineContext_T * context); + +static TickType_t Period_Start = 0; +static uint_fast8_t N_Lights_Lit = 0; + +static const char *KLOG_TAG = "STATE_STARTING_GAME__INSTIGATING"; + +//! Activities for the **Instigating** substate of the **Starting Game** state. +const StateActivity_T STATE_STARTING_GAME__INSTIGATING_Activities = +{ + .Entry = Starting_Game__Instigating_Entry, + .Do = Starting_Game__Instigating_Do, + .Exit = Starting_Game__Instigating_Exit +}; + +//! Sets up the Instigating substate. +/*! + * \param context Context in which this substate is being run. + */ +static void Starting_Game__Instigating_Entry(StateMachineContext_T * context) +{ + // First of all, enter the state containing this substate. + Starting_Game_Entry(context); + + LOG("Entering the Instigating substate of the Starting Game state."); + + Set_Time_Remaining_in_Game(UINT32_MAX); + uint32_t start_game_time_in_ms; + (void) SETTINGS_get_uint32_t(SYSTEMK_SETTING_T_START_GAME_in_ms, &start_game_time_in_ms); + Set_Time_Remaining_Until_Countdown(start_game_time_in_ms); + BLE_UpdateInstigationPacket(Get_Time_Remaining_in_Game_in_ms(), Get_Time_Remaining_Until_Countdown_in_ms()); + if (BLE_ScanAndAdvertise() != SYSTEMK_RESULT_SUCCESS) + { + KLOG_ERROR(KLOG_TAG, "Couldn't start BLE!"); + } + + Period_Start = xTaskGetTickCount(); +} + +//! Executes the Instigating substate. +/*! + * \param context Context in which this substate is being run. + */ +static void Starting_Game__Instigating_Do(StateMachineContext_T * context) +{ + portBASE_TYPE xStatus; + static KEvent_T Event; + + xStatus = Receive_KEvent(&Event); + + if (xStatus == pdPASS) + { + switch (Event.ID) + { + case KEVENT_PLAY_PRESSED_ON_REMOTE: + Transition_For_Event(context, STATE_STARTING_GAME__COUNTING_DOWN, &Event); + break; + + default: + // All other events are ignored in this state. + ProcessUnhandledEvent(&Event); + break; + } + } + + // Wait for T_start_game before proceeding. + uint32_t start_game_time_in_ms; + (void) SETTINGS_get_uint32_t(SYSTEMK_SETTING_T_START_GAME_in_ms, &start_game_time_in_ms); + if (GetTimeInState_in_ms(context) > start_game_time_in_ms) + { + static KEvent_T Event; + Event.ID = KEVENT_AUTOMATIC_TRANSITION; + Event.Data = NULL; + Transition_For_Event(context, STATE_STARTING_GAME__COUNTING_DOWN, &Event); + } + else + { + uint32_t period_time_in_ms = (xTaskGetTickCount() - Period_Start) * portTICK_PERIOD_MS; + uint32_t time_remaining_until_countdown_in_ms = start_game_time_in_ms - GetTimeInState_in_ms(context); + Set_Time_Remaining_Until_Countdown(time_remaining_until_countdown_in_ms); + + if (period_time_in_ms > 150) + { + BLE_UpdateInstigationPacket(Get_Time_Remaining_in_Game_in_ms(), Get_Time_Remaining_Until_Countdown_in_ms()); + + // Toggle the lights. + if (N_Lights_Lit == 0) + { + N_Lights_Lit = 5; + } + else + { + N_Lights_Lit = 0; + } + NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_COUNTDOWN, .Prominence = NEOPIXELS_FOREGROUND, .Data = (void *)&N_Lights_Lit}; + xQueueSend(xQueueNeoPixels, &neopixels_action, 0); + + // Start a new period. + Period_Start = xTaskGetTickCount(); + } + } +} + +//! Cleans up the Instigating substate. +/*! + * \param context Context in which this substate is being run. + */ +static void Starting_Game__Instigating_Exit(StateMachineContext_T * context) +{ + // Nothing to do. +} diff --git a/States/Starting_Game/State_Starting_Game__Instigating.h b/States/Starting_Game/State_Starting_Game__Instigating.h new file mode 100644 index 0000000..fe5ba66 --- /dev/null +++ b/States/Starting_Game/State_Starting_Game__Instigating.h @@ -0,0 +1,32 @@ +/* + * 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 . + */ + +#ifndef STATE_STARTING_GAME__INSTIGATING_H +#define STATE_STARTING_GAME__INSTIGATING_H + +/* Include Files */ + +/* Definitions */ + +/* External Data */ +extern const StateActivity_T STATE_STARTING_GAME__INSTIGATING_Activities; + +#endif // STATE_STARTING_GAME__INSTIGATING_H diff --git a/States/Starting_Game/State_Starting_Game__Responding.c b/States/Starting_Game/State_Starting_Game__Responding.c new file mode 100644 index 0000000..7651254 --- /dev/null +++ b/States/Starting_Game/State_Starting_Game__Responding.c @@ -0,0 +1,134 @@ +/* + * 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 Starting_Game__Responding_Entry(StateMachineContext_T * context); +static void Starting_Game__Responding_Do(StateMachineContext_T * context); +static void Starting_Game__Responding_Exit(StateMachineContext_T * context); + +static TickType_t Period_Start = 0; +static uint_fast8_t N_Lights_Lit = 0; + +static const char *KLOG_TAG = "STATE_STARTING_GAME__RESPONDING"; + +//! Activities for the **Responding** substate of the **Starting Game** state. +const StateActivity_T STATE_STARTING_GAME__RESPONDING_Activities = +{ + .Entry = Starting_Game__Responding_Entry, + .Do = Starting_Game__Responding_Do, + .Exit = Starting_Game__Responding_Exit +}; + +//! Sets up the Responding substate. +/*! + * \param context Context in which this substate is being run. + */ +static void Starting_Game__Responding_Entry(StateMachineContext_T * context) +{ + // First of all, enter the state containing this substate. + Starting_Game_Entry(context); + + LOG("Entering the Responding substate of the Starting Game state."); + + if (context->Cause_Of_Transition->ID == KEVENT_BLE_PACKET_RECEIVED) + { + if (((BLE_Packet_T *)context->Cause_Of_Transition->Data)->Generic.type == BLE_PACKET_TYPE_INSTIGATE_GAME) + { + uint32_t game_length_in_ms = ((BLE_Packet_T *)context->Cause_Of_Transition->Data)->Instigation.game_length_in_ms; + Set_Time_Remaining_in_Game(game_length_in_ms); + + uint32_t time_remaining_until_countdown_in_ms = ((BLE_Packet_T *)context->Cause_Of_Transition->Data)->Instigation.time_remaining_until_countdown_in_ms; + Set_Time_Remaining_Until_Countdown(time_remaining_until_countdown_in_ms); + +#ifdef LOG_STATE_MACHINE + KLOG_TRACE(KLOG_TAG, "Game length: %lu ms", game_length_in_ms); + vTaskDelay(pdMS_TO_TICKS(10)); + KLOG_TRACE(KLOG_TAG, "Time until countdown: %lu ms", time_remaining_until_countdown_in_ms); +#endif // LOG_STATE_MACHINE + } + + BLE_FreePacketBuffer(context->Cause_Of_Transition->Data); + } +} + +//! Executes the Responding substate. +/*! + * \param context Context in which this substate is being run. + */ +static void Starting_Game__Responding_Do(StateMachineContext_T * context) +{ + portBASE_TYPE xStatus; + static KEvent_T Event; + + xStatus = Receive_KEvent(&Event); + + if (xStatus == pdPASS) + { + switch (Event.ID) + { + default: + // All events are ignored in this state. + ProcessUnhandledEvent(&Event); + break; + } + } + + // Wait for (T_start_game - T_instigation) before proceeding. + if (GetTimeInState_in_ms(context) > Get_Time_Remaining_Until_Countdown_in_ms()) + { + static KEvent_T Event; + Event.ID = KEVENT_AUTOMATIC_TRANSITION; + Event.Data = NULL; + Transition_For_Event(context, STATE_STARTING_GAME__COUNTING_DOWN, &Event); + } + else + { + uint32_t period_time_in_ms = (xTaskGetTickCount() - Period_Start) * portTICK_PERIOD_MS; + + if (period_time_in_ms > 150) + { + // Toggle the lights. + if (N_Lights_Lit == 0) + { + N_Lights_Lit = 5; + } + else + { + N_Lights_Lit = 0; + } + NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_COUNTDOWN, .Prominence = NEOPIXELS_FOREGROUND, .Data = (void *)&N_Lights_Lit}; + xQueueSend(xQueueNeoPixels, &neopixels_action, 0); + + // Start a new period. + Period_Start = xTaskGetTickCount(); + } + } +} + +//! Cleans up the Responding substate. +/*! + * \param context Context in which this substate is being run. + */ +static void Starting_Game__Responding_Exit(StateMachineContext_T * context) +{ +} diff --git a/States/Starting_Game/State_Starting_Game__Responding.h b/States/Starting_Game/State_Starting_Game__Responding.h new file mode 100644 index 0000000..d8c3b4b --- /dev/null +++ b/States/Starting_Game/State_Starting_Game__Responding.h @@ -0,0 +1,32 @@ +/* + * 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 . + */ + +#ifndef STATE_STARTING_GAME__RESPONDING_H +#define STATE_STARTING_GAME__RESPONDING_H + +/* Include Files */ + +/* Definitions */ + +/* External Data */ +extern const StateActivity_T STATE_STARTING_GAME__RESPONDING_Activities; + +#endif // STATE_STARTING_GAME__RESPONDING_H diff --git a/States/State_Configuring.c b/States/State_Configuring.c new file mode 100644 index 0000000..13141cd --- /dev/null +++ b/States/State_Configuring.c @@ -0,0 +1,197 @@ +/* + * 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); + +#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); + + BLE_UpdateStatusPacket(); + if (BLE_ScanAndAdvertise() != SYSTEMK_RESULT_SUCCESS) + { + KLOG_ERROR(KLOG_TAG, "Couldn't start BLE!"); + } + + // 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; + + 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); + } + } + 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_PLAY_PRESSED_ON_REMOTE: + Transition_For_Event(context, STATE_READY, &Event); + break; + + case KEVENT_REPROGRAM: + Transition_For_Event(context, STATE_REPROGRAMMING, &Event); + 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); +} diff --git a/States/State_Configuring.h b/States/State_Configuring.h new file mode 100644 index 0000000..9f66299 --- /dev/null +++ b/States/State_Configuring.h @@ -0,0 +1,32 @@ +/* + * 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 . + */ + +#ifndef STATE_CONFIGURING_H +#define STATE_CONFIGURING_H + +/* Include Files */ + +/* Definitions */ + +/* External Data */ +extern const StateActivity_T STATE_CONFIGURING_Activities; + +#endif // STATE_CONFIGURING_H diff --git a/States/State_Initializing.c b/States/State_Initializing.c new file mode 100644 index 0000000..2f52060 --- /dev/null +++ b/States/State_Initializing.c @@ -0,0 +1,126 @@ +/* + * 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 Initializing_Entry(StateMachineContext_T * context); +static void Initializing_Do(StateMachineContext_T * context); +static void Initializing_Exit(StateMachineContext_T * context); + +static const char *KLOG_TAG = "STATE_INITIALIZING"; + +static bool Startup_Sound_Complete = false; + +//! Activities for the **Initializing** state. +const StateActivity_T STATE_INITIALIZING_Activities = +{ + .Entry = Initializing_Entry, + .Do = Initializing_Do, + .Exit = Initializing_Exit +}; + +//! Sets up the Initializing state. +/*! + * \param context Context in which this state is being run. + */ +static void Initializing_Entry(StateMachineContext_T * context) +{ + LOG("Entering the Initializing state."); + NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_TEST_PATTERN, .Prominence = NEOPIXELS_FOREGROUND, .Data = (void *)0x00}; + xQueueSend(xQueueNeoPixels, &neopixels_action, 0); + AudioAction_T audio_action = {.ID = AUDIO_PLAY_STARTUP_SOUND, .Play_To_Completion = true, .Data = (void *)0x00}; + Perform_Audio_Action(&audio_action); + Startup_Sound_Complete = false; +} + +//! Executes the Initializing state. +/*! + * \param context Context in which this state is being run. + */ +static void Initializing_Do(StateMachineContext_T * context) +{ + portBASE_TYPE xStatus; + static KEvent_T Event; + + xStatus = Receive_KEvent(&Event); + + if (xStatus == pdPASS) + { + switch (Event.ID) + { + case KEVENT_TRIGGER_SWITCH_PRESSED: + Transition_For_Event(context, STATE_REPROGRAMMING, &Event); + break; + + case KEVENT_AUDIO_COMPLETED: + if (((AudioActionID_T)Event.Data) == AUDIO_PLAY_STARTUP_SOUND) + { + Startup_Sound_Complete = true; + } + break; + + default: + // All other events are ignored in this state. + ProcessUnhandledEvent(&Event); + break; + } + } + + // Wait for the STARTUP_SOUND to complete, but at least three seconds. + if ((xTaskGetTickCount() - context->Time_At_State_Entry_In_Ticks) > (3 * 1000 / portTICK_PERIOD_MS)) + { + if (Startup_Sound_Complete == true) + { + Event.ID = KEVENT_AUTOMATIC_TRANSITION; + Event.Data = NULL; + Transition_For_Event(context, STATE_CONFIGURING, &Event); + } + } +} + +//! Cleans up the Initializing state. +/*! + * \param context Context in which this state is being run. + */ +static void Initializing_Exit(StateMachineContext_T * context) +{ + NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_ALL_OFF, .Prominence = NEOPIXELS_FOREGROUND, .Data = (void *)0x00}; + xQueueSend(xQueueNeoPixels, &neopixels_action, 0); + AudioAction_T audio_action = {.ID = AUDIO_SILENCE, .Data = (void *)0x00}; + Perform_Audio_Action(&audio_action); + + //! \todo Add a menu item to change the weapon. + KLOG_WARN(KLOG_TAG, "Weapon hardcoded to the Nerf Laser Strike Blaster!"); + uint8_t weapon_ID = NERF_LASER_STRIKE_BLASTER_ID; + (void) SETTINGS_set_uint8_t(SYSTEMK_SETTING_WEAPONID, weapon_ID); + (void) SETTINGS_Save(); + + //! \todo TEAM_PURPLE is always one-hit kill. + uint8_t team_ID; + (void) SETTINGS_get_uint8_t(SYSTEMK_SETTING_TEAMID, &team_ID); + TeamID_t my_common_team_ID = Resolve_Common_Team_ID(team_ID); + + if (my_common_team_ID == TEAM_PURPLE) + { + Set_Health(10); + } +} diff --git a/States/State_Initializing.h b/States/State_Initializing.h new file mode 100644 index 0000000..6b4a018 --- /dev/null +++ b/States/State_Initializing.h @@ -0,0 +1,32 @@ +/* + * 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 . + */ + +#ifndef STATE_INITIALIZING_H +#define STATE_INITIALIZING_H + +/* Include Files */ + +/* Definitions */ + +/* External Data */ +extern const StateActivity_T STATE_INITIALIZING_Activities; + +#endif // STATE_INITIALIZING_H diff --git a/States/State_Machine.c b/States/State_Machine.c new file mode 100644 index 0000000..f3e702b --- /dev/null +++ b/States/State_Machine.c @@ -0,0 +1,133 @@ +/* + * 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" + +const StateActivity_T STATE_DEFAULT_Activities = +{ + .Entry = NULL, + .Do = NULL, + .Exit = NULL +}; + +//! Activities for the top-level state machine. +/*! + * This array is indexed by #StateID_T (except there is no entry for + * #STATE_OUT_OF_RANGE or #STATE_NONE), and the order of activities + * must match the order of the enumeration. + */ +static const StateActivity_T * Activities[] = +{ + &STATE_DEFAULT_Activities, + &STATE_INITIALIZING_Activities, + &STATE_REPROGRAMMING_Activities, + &STATE_CONFIGURING_Activities, + &STATE_READY_Activities, + &STATE_STARTING_GAME__INSTIGATING_Activities, + &STATE_STARTING_GAME__RESPONDING_Activities, + &STATE_STARTING_GAME__COUNTING_DOWN_Activities, + &STATE_PLAYING__INTERACTING_Activities, + &STATE_PLAYING__TAGGED_OUT_Activities, + &STATE_WRAPPING_UP_Activities +}; + +//! Context in which the top-level state machine is run. +static StateMachineContext_T Context = +{ + .States = + { + .Previous_State = STATE_NONE, + .Current_State = STATE_NONE, + .Next_State = STATE_INITIALIZING + }, + .Time_At_State_Entry_In_Ticks = 0, +}; + +TaskHandle_t State_Machine_Task_Handle; + +void State_Machine_Task(void * pvParameters) +{ + while (true) + { + // Handle state changes. + if (Context.States.Next_State != Context.States.Current_State) + { + // Exit the current state. + if (Context.States.Current_State < STATE_IS_OUT_OF_RANGE) + { + if (Activities[Context.States.Current_State]->Exit != NULL) + { + Activities[Context.States.Current_State]->Exit(&Context); + } + } + + // Save off the time of the state change. + Context.Time_At_State_Entry_In_Ticks = xTaskGetTickCount(); + + // Update the current state. + Context.States.Previous_State = Context.States.Current_State; + Context.States.Current_State = Context.States.Next_State; + + if (Context.States.Current_State < STATE_IS_OUT_OF_RANGE) + { + // Enter the current state. + if (Activities[Context.States.Current_State]->Entry != NULL) + { + Activities[Context.States.Current_State]->Entry(&Context); + } + } + } + + if (Context.States.Current_State < STATE_IS_OUT_OF_RANGE) + { + // Execute the current state. + if (Activities[Context.States.Current_State]->Do != NULL) + { + Activities[Context.States.Current_State]->Do(&Context); + } + } + + vTaskDelay(1); + } +} + +void ProcessUnhandledEvent(KEvent_T * Event) +{ + switch (Event->ID) + { + case KEVENT_TAG_RECEIVED: + FreeDecodedPacketBuffer(Event->Data); + break; + + case KEVENT_COMMAND_RECEIVED: + FreeDecodedPacketBuffer(Event->Data); + break; + + case KEVENT_BLE_PACKET_RECEIVED: + BLE_FreePacketBuffer(Event->Data); + break; + + default: + // All other events can be safely ignored. + break; + } +} diff --git a/States/State_Machine.h b/States/State_Machine.h new file mode 100644 index 0000000..31d375a --- /dev/null +++ b/States/State_Machine.h @@ -0,0 +1,131 @@ +/* + * 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 . + */ + +#ifndef STATE_MACHINE_H +#define STATE_MACHINE_H + +#define LOG_STATE_MACHINE TRUE + +#ifdef LOG_STATE_MACHINE + #define LOG(message) KLOG_INFO("STATE", message) +#else // LOG_STATE_MACHINE + #define LOG(message) +#endif // LOG_STATE_MACHINE + +//! Names of the states and substates of the top-level state machine. +/*! The enumerators are explicitly specified for (some of) these states + * to aid in tracing and debugging (\see #LOG_STATE_MACHINE). + */ +typedef enum +{ + STATE_DEFAULT = 0, + STATE_INITIALIZING = 1, + STATE_REPROGRAMMING = 2, + STATE_CONFIGURING = 3, + STATE_READY = 4, + + // Substates of STATE_STARTING_GAME + STATE_STARTING_GAME__INSTIGATING = 5, + STATE_STARTING_GAME__RESPONDING = 6, + STATE_STARTING_GAME__COUNTING_DOWN = 7, + + // Substates of STATE_PLAYING + STATE_PLAYING__INTERACTING = 8, + STATE_PLAYING__TAGGED_OUT = 9, + + STATE_WRAPPING_UP = 10, + + // STATE_IS_OUT_OF_RANGE is one more than the last valid state. + STATE_IS_OUT_OF_RANGE, + STATE_NONE = 255 +} StateID_T; + +//! State information used by the top-level state machine. +typedef struct +{ + //! Most recent state of the top-level state machine not currently executing. + StateID_T Previous_State; + + //! Currently executing state of the top-level state machine. + StateID_T Current_State; + + //! Requested state of the top-level state machine. + StateID_T Next_State; +} State_T; + +typedef struct +{ + //! State information used by the top-level state machine. + State_T States; + + //! The most recent event to have caused a state transition. + /*! + * This is used by state entry logic to determine, for example, + * which substate should be entered. + */ + KEvent_T* Cause_Of_Transition; + + // Time (in RTOS ticks) when the current state was entered. + TickType_t Time_At_State_Entry_In_Ticks; +} StateMachineContext_T; + +//! Activity actions for a given state, following UML 2.5.1 State Diagrams. +/*! + * \see https://www.omg.org/spec/UML/2.5.1/PDF + */ +typedef struct +{ + //! The function called when a state is entered. + void (* const Entry)(StateMachineContext_T * context); + + //! The function called periodically while in a state. + void (* const Do)(StateMachineContext_T * context); + + //! The function called when a state is exited. + void (* const Exit)(StateMachineContext_T * context); +} StateActivity_T; + +extern TaskHandle_t State_Machine_Task_Handle; + +void State_Machine_Task(void * pvParameters); +void ProcessUnhandledEvent(KEvent_T * Event); + +inline void Transition_For_Event(StateMachineContext_T* context, StateID_T next_state, KEvent_T* event) +{ + context->States.Next_State = next_state; + context->Cause_Of_Transition = event; +} + +inline uint32_t GetTimeInState_in_ms(StateMachineContext_T * context) +{ + uint32_t result = (xTaskGetTickCount() - context->Time_At_State_Entry_In_Ticks) * portTICK_PERIOD_MS; + return result; +} + +#include "State_Configuring.h" +#include "State_Ready.h" +#include "State_Initializing.h" +#include "Playing/State_Playing.h" +#include "Starting_Game/State_Starting_Game.h" +#include "State_Reprogramming.h" +#include "State_Wrapping_Up.h" + +#endif // STATE_MACHINE_H diff --git a/States/State_Ready.c b/States/State_Ready.c new file mode 100644 index 0000000..21b4af4 --- /dev/null +++ b/States/State_Ready.c @@ -0,0 +1,126 @@ +/* + * 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 const char *KLOG_TAG = "STATE_READY"; + +static const uint16_t LONG_PRESS_TO_INITIATE_GAME_in_ms = 5000; + +static void Ready_Entry(StateMachineContext_T * context); +static void Ready_Do(StateMachineContext_T * context); +static void Ready_Exit(StateMachineContext_T * context); + +//! Activities for the **Ready** state. +const StateActivity_T STATE_READY_Activities = +{ + .Entry = Ready_Entry, + .Do = Ready_Do, + .Exit = Ready_Exit +}; + +//! Sets up the Ready state. +/*! + * \param context Context in which this state is being run. + */ +static void Ready_Entry(StateMachineContext_T * context) +{ + LOG("Entering the Ready state."); + NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_IDLE, .Prominence = NEOPIXELS_FOREGROUND, .Data = (void *)0x00}; + xQueueSend(xQueueNeoPixels, &neopixels_action, 0); + BLE_UpdateStatusPacket(); + if (BLE_ScanAndAdvertise() != SYSTEMK_RESULT_SUCCESS) + { + KLOG_ERROR(KLOG_TAG, "Couldn't start BLE!"); + } + AudioAction_T audio_action = {.ID = AUDIO_PLAY_GAME_ON, .Data = (void *)0x00}; + Perform_Audio_Action(&audio_action); +} + +//! Executes the Ready state. +/*! + * \param context Context in which this state is being run. + */ +static void Ready_Do(StateMachineContext_T * context) +{ + portBASE_TYPE xStatus; + static KEvent_T Event; + + xStatus = Receive_KEvent(&Event); + + if (xStatus == pdPASS) + { + switch (Event.ID) + { + case KEVENT_TRIGGER_SWITCH_PRESSED: + 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_TO_INITIATE_GAME_in_ms) + { + Transition_For_Event(context, STATE_STARTING_GAME__INSTIGATING, &Event); + } + } + break; + + case KEVENT_BLE_PACKET_RECEIVED: + if (((BLE_Packet_T *)Event.Data)->Generic.type == BLE_PACKET_TYPE_INSTIGATE_GAME) + { + Transition_For_Event(context, STATE_STARTING_GAME__RESPONDING, &Event); + } + else + { + BLE_FreePacketBuffer(Event.Data); + } + break; + + case KEVENT_MENU_ENTER: + case KEVENT_BACKWARD_SWITCH_LONG_PRESSED: + Transition_For_Event(context, STATE_CONFIGURING, &Event); + break; + + case KEVENT_PLAY_PRESSED_ON_REMOTE: + Transition_For_Event(context, STATE_STARTING_GAME__INSTIGATING, &Event); + break; + + default: + // All other events are ignored in this state. + ProcessUnhandledEvent(&Event); + break; + } + } +} + +//! Cleans up the Ready state. +/*! + * \param context Context in which this state is being run. + */ +static void Ready_Exit(StateMachineContext_T * context) +{ + NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_ALL_OFF, .Prominence = NEOPIXELS_FOREGROUND, .Data = (void *)0x00}; + xQueueSend(xQueueNeoPixels, &neopixels_action, 0); + AudioAction_T audio_action = {.ID = AUDIO_SILENCE, .Data = (void *)0x00}; + Perform_Audio_Action(&audio_action); +} diff --git a/States/State_Ready.h b/States/State_Ready.h new file mode 100644 index 0000000..71c2608 --- /dev/null +++ b/States/State_Ready.h @@ -0,0 +1,32 @@ +/* + * 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 . + */ + +#ifndef STATE_READY_H +#define STATE_READY_H + +/* Include Files */ + +/* Definitions */ + +/* External Data */ +extern const StateActivity_T STATE_READY_Activities; + +#endif // STATE_READY_H diff --git a/States/State_Reprogramming.c b/States/State_Reprogramming.c new file mode 100644 index 0000000..88b1351 --- /dev/null +++ b/States/State_Reprogramming.c @@ -0,0 +1,107 @@ +/* + * 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 Reprogramming_Entry(StateMachineContext_T * context); +static void Reprogramming_Do(StateMachineContext_T * context); +static void Reprogramming_Exit(StateMachineContext_T * context); +static All_On_Data_T Data; +static bool Reprogramming_Sound_Complete = false; + +//! Activities for the **Reprogramming** state. +const StateActivity_T STATE_REPROGRAMMING_Activities = +{ + .Entry = Reprogramming_Entry, + .Do = Reprogramming_Do, + .Exit = Reprogramming_Exit +}; + +//! Sets up the Reprogramming state. +/*! + * \param context Context in which this state is being run. + */ +static void Reprogramming_Entry(StateMachineContext_T * context) +{ + LOG("Entering the Reprogramming state."); + Data.color = ApplyMask(COLOR_YELLOW, 0x70); + Data.style = DISPLAY_STYLE_ALTERNATE; + NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_ALL_ON, .Prominence = NEOPIXELS_FOREGROUND, .Data = (void *)&Data}; + xQueueSend(xQueueNeoPixels, &neopixels_action, 0); + Reprogramming_Sound_Complete = false; + AudioAction_T audio_action = {.ID = AUDIO_PLAY_REPROGRAMMING, .Play_To_Completion = true, .Data = (void *)0x00}; + Perform_Audio_Action(&audio_action); +} + +//! Executes the Reprogramming state. +/*! + * \param context Context in which this state is being run. + */ +static void Reprogramming_Do(StateMachineContext_T * context) +{ + portBASE_TYPE xStatus; + static KEvent_T Event; + static bool reprogramming_requested = false; + + xStatus = Receive_KEvent(&Event); + + if (xStatus == pdPASS) + { + switch (Event.ID) + { + case KEVENT_AUDIO_COMPLETED: + if (((AudioActionID_T)Event.Data) == AUDIO_PLAY_REPROGRAMMING) + { + Reprogramming_Sound_Complete = true; + } + break; + + default: + // All events are ignored in this state. + ProcessUnhandledEvent(&Event); + break; + } + } + + // Wait one second to initiate reprogramming, to allow the UI to update. + // Note that the `reprogram` command behaves differently on different platforms--see the documentation. + if ((xTaskGetTickCount() - context->Time_At_State_Entry_In_Ticks) > (1 * 1000 / portTICK_PERIOD_MS)) + { + if ((reprogramming_requested == false) && (Reprogramming_Sound_Complete == true)) + { + (void) HW_Execute_Console_Command((uint8_t *)"reprogram"); + reprogramming_requested = true; + Data.color = ApplyMask(COLOR_ORANGE, 0x70); + Data.style = DISPLAY_STYLE_ALTERNATE; + NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_ALL_ON, .Prominence = NEOPIXELS_FOREGROUND, .Data = (void *)&Data}; + xQueueSend(xQueueNeoPixels, &neopixels_action, 0); + } + } +} + +//! Cleans up the Reprogramming state. +/*! + * \param context Context in which this state is being run. + */ +static void Reprogramming_Exit(StateMachineContext_T * context) +{ +} diff --git a/States/State_Reprogramming.h b/States/State_Reprogramming.h new file mode 100644 index 0000000..bf5e39e --- /dev/null +++ b/States/State_Reprogramming.h @@ -0,0 +1,32 @@ +/* + * 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 . + */ + +#ifndef STATE_REPROGRAMMING_H +#define STATE_REPROGRAMMING_H + +/* Include Files */ + +/* Definitions */ + +/* External Data */ +extern const StateActivity_T STATE_REPROGRAMMING_Activities; + +#endif // STATE_REPROGRAMMING_H diff --git a/States/State_Wrapping_Up.c b/States/State_Wrapping_Up.c new file mode 100644 index 0000000..1361f93 --- /dev/null +++ b/States/State_Wrapping_Up.c @@ -0,0 +1,109 @@ +/* + * 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 const char *KLOG_TAG = "STATE_WRAPPING_UP"; + +static const uint16_t LONG_PRESS_FOR_CONFIGURING_in_ms = 5000; + +static void Wrapping_Up_Entry(StateMachineContext_T * context); +static void Wrapping_Up_Do(StateMachineContext_T * context); +static void Wrapping_Up_Exit(StateMachineContext_T * context); + +//! Activities for the **Wrapping Up** state. +const StateActivity_T STATE_WRAPPING_UP_Activities = +{ + .Entry = Wrapping_Up_Entry, + .Do = Wrapping_Up_Do, + .Exit = Wrapping_Up_Exit +}; + +//! Sets up the Wrapping Up state. +/*! + * \param context Context in which this state is being run. + */ +static void Wrapping_Up_Entry(StateMachineContext_T * context) +{ + LOG("Entering the Wrapping Up state."); + NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_IDLE, .Prominence = NEOPIXELS_FOREGROUND, .Data = (void *)0x00}; + xQueueSend(xQueueNeoPixels, &neopixels_action, 0); + BLE_UpdateStatusPacket(); + if (BLE_ScanAndAdvertise() != SYSTEMK_RESULT_SUCCESS) + { + KLOG_ERROR(KLOG_TAG, "Couldn't start BLE!"); + } +} + +//! Executes the Wrapping Up state. +/*! + * \param context Context in which this state is being run. + */ +static void Wrapping_Up_Do(StateMachineContext_T * context) +{ + portBASE_TYPE xStatus; + static KEvent_T Event; + + xStatus = Receive_KEvent(&Event); + + if (xStatus == pdPASS) + { + switch (Event.ID) + { + case KEVENT_TRIGGER_SWITCH_PRESSED: + 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_CONFIGURING_in_ms) + { + Transition_For_Event(context, STATE_CONFIGURING, &Event); + } + } + break; + + case KEVENT_BLE_PACKET_RECEIVED: + // TODO + BLE_FreePacketBuffer(Event.Data); + break; + + default: + // All other events are ignored in this state. + ProcessUnhandledEvent(&Event); + break; + } + } +} + +//! Cleans up the Wrapping Up state. +/*! + * \param context Context in which this state is being run. + */ +static void Wrapping_Up_Exit(StateMachineContext_T * context) +{ + NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_ALL_OFF, .Prominence = NEOPIXELS_FOREGROUND, .Data = (void *)0x00}; + xQueueSend(xQueueNeoPixels, &neopixels_action, 0); + AudioAction_T audio_action = {.ID = AUDIO_SILENCE, .Data = (void *)0x00}; + Perform_Audio_Action(&audio_action); +} diff --git a/States/State_Wrapping_Up.h b/States/State_Wrapping_Up.h new file mode 100644 index 0000000..6ff1d94 --- /dev/null +++ b/States/State_Wrapping_Up.h @@ -0,0 +1,32 @@ +/* + * 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 . + */ + +#ifndef STATE_WRAPPING_UP_H +#define STATE_WRAPPING_UP_H + +/* Include Files */ + +/* Definitions */ + +/* External Data */ +extern const StateActivity_T STATE_WRAPPING_UP_Activities; + +#endif // STATE_WRAPPING_UP_H diff --git a/SystemK.c b/SystemK.c new file mode 100644 index 0000000..60440fc --- /dev/null +++ b/SystemK.c @@ -0,0 +1,49 @@ +/* + * 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 + +#define STACK_SIZE 4096 +static StaticTask_t xTaskBuffer; +static StackType_t xStack[STACK_SIZE]; + +SystemKResult_T Initialize_SystemK(void) +{ + SystemKResult_T result = SYSTEMK_RESULT_SUCCESS; + + Init_KEvents(); + + State_Machine_Task_Handle = xTaskCreateStatic( + State_Machine_Task, // Function that implements the task. + "SystemK", // Text name for the task. + STACK_SIZE, // Stack size in bytes, not words. + 0, // Parameter passed into the task. + tskIDLE_PRIORITY, // Priority at which the task is created. + xStack, // Array to use as the task's stack. + &xTaskBuffer); // Variable to hold the task's data structure. + + if (State_Machine_Task_Handle == NULL) + { + result = SYSTEMK_RESULT_FAILED_TO_CREATE_RTOS_TASK; + } + + return result; +} diff --git a/SystemK.h b/SystemK.h new file mode 100755 index 0000000..cd0e29e --- /dev/null +++ b/SystemK.h @@ -0,0 +1,112 @@ +/* + * 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 . + */ + +/** \file + * \brief This is the top-level include file for the KTag system (SystemK). + * + * By including this file (and only this file) from SystemK, include dependency order is maintained. + * + */ + +// KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK +// KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK +// KKKKKKKKKKKKKKKKKKKKKKKKKKKKky+.`/ykKKKKKKKKKKKKKKKKKKKKKKKKKKKK +// KKKKKKKKKKKKKKKKKKKKKKKKds/. -+o:` ./sdNKKKKKKKKKKKKKKKKKKKKKKK +// KKKKKKKKKKKKKKKKKKNds+-` `-+hNKKKKNho:` `-+shNKKKKKKKKKKKKKKKKKK +// KKKKKKKKKKNkhyo+:. `-/sdNKKKKKKKKKKKKKky+:` .-/oyhdNKKKKKKKKKK +// KKys++:-.````.-:+oykNKKKKKKKKKKKKKKKKKKKKKKNkhs+/-.````.-:/+syKK +// KK -/+osydkNNNKKKkkkkkkkNKKKKKKKKKKKkkkkkkkkNKKKKNNkdhyso/: KK +// KK sKKKKKKKKKKKKK```````/KKKKKKKKKd-```````:kKKKKKKKKKKKKKd `KK +// KK- oKKKKKKKKKKKKK :KKKKKKKKo` `oNKKKKKKKKKKKKKKh :KK +// KK/ +KKKKKKKKKKKKK :KKKKKKd- -dKKKKKKKKKKKKKKKKy /KK +// KK+ /KKKKKKKKKKKKK :KKKKKs` +NKKKKKKKKKKKKKKKKKs +KK +// KKo :KKKKKKKKKKKKK :KKKk: .hKKKKKKKKKKKKKKKKKKKo oKK +// KKy -KKKKKKKKKKKKK :KKy` +NKKKKKKKKKKKKKKKKKKKK/ yKK +// KKd `KKKKKKKKKKKKK :k/ .hKKKKKKKKKKKKKKKKKKKKKK: dKK +// KKN NKKKKKKKKKKKK .. /kKKKKKKKKKKKKKKKKKKKKKKK. NKK +// KKK. dKKKKKKKKKKKK .yKKKKKKKKKKKKKKKKKKKKKKKKN .KKK +// KKK+ oKKKKKKKKKKKK -kKKKKKKKKKKKKKKKKKKKKKKKKKh +KKK +// KKKd .KKKKKKKKKKKK `sNKKKKKKKKKKKKKKKKKKKKKKKK/ dKKK +// KKKK: hKKKKKKKKKKK :kKKKKKKKKKKKKKKKKKKKKKKk :KKKK +// KKKKh -KKKKKKKKKKK `` .yKKKKKKKKKKKKKKKKKKKKK+ hKKKK +// KKKKK/ yKKKKKKKKKK T :d: /kKKKKKKKKKKKKKKKKKKk`:KKKKK +// KKKKKk`.NKKKKKKKKK :KNo` .hKKKKKKKKKKKKKKKKK:`kKKKKK +// KKKKKKy /KKKKKKKKK A :KKKd- +NKKKKKKKKKKKKKKo yKKKKKK +// KKKKKKK+ oKKKKKKKK :KKKKN+` -hKKKKKKKKKKKKy`+KKKKKKK +// KKKKKKKN/ sKKKKKKK G :KKKKKKh. `oNKKKKKKKKKh`/KKKKKKKK +// KKKKKKKKN/`sKKKKKK :KKKKKKKN/ -dKKKKKKKh`/NKKKKKKKK +// KKKKKKKKKK+ +NKKKK :KKKKKKKKKy. `sNKKKKs`+KKKKKKKKKK +// KKKKKKKKKKKs`:kKKK-------+KKKKKKKKKKk/--------oKKN+`sKKKKKKKKKKK +// KKKKKKKKKKKKh..yKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKd--dKKKKKKKKKKKK +// KKKKKKKKKKKKKN+`/kKKKKKKKKKKKKKKKKKKKKKKKKKKKKNo`+NKKKKKKKKKKKKK +// KKKKKKKKKKKKKKKh-`sNKKKKKKKKKKKKKKKKKKKKKKKKNy.-hKKKKKKKKKKKKKKK +// KKKKKKKKKKKKKKKKKs..sNKKKKKKKKKKKKKKKKKKKKNy-.yKKKKKKKKKKKKKKKKK +// KKKKKKKKKKKKKKKKKKNs..okKKKKKKKKKKKKKKKKNs-.sNKKKKKKKKKKKKKKKKKK +// KKKKKKKKKKKKKKKKKKKKKy-`/hKKKKKKKKKKKKd+`-yKKKKKKKKKKKKKKKKKKKKK +// KKKKKKKKKKKKKKKKKKKKKKKd/`.odKKKKKKks-`/dKKKKKKKKKKKKKKKKKKKKKKK +// KKKKKKKKKKKKKKKKKKKKKKKKKNs: .+yy+-`:sNKKKKKKKKKKKKKKKKKKKKKKKKK +// KKKKKKKKKKKKKKKKKKKKKKKKKKKKNy/..+yNKKKKKKKKKKKKKKKKKKKKKKKKKKKK +// KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK +// KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK + +#ifndef SYSTEMK_H +#define SYSTEMK_H + +#ifdef ESP_PLATFORM +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "freertos/semphr.h" +#include "freertos/timers.h" +#endif // ESP_PLATFORM + +#ifdef PSOC_PLATFORM +#include "FreeRTOS.h" +#include "task.h" +#include "queue.h" +#include "semphr.h" +#include "timers.h" +#endif // PSOC_PLATFORM + +#include "Results.h" +#include "Colors.h" +#include "Console_HW_Interface.h" +#include "Events/KEvents.h" +#include "Logging/KLog.h" +#include "Protocols/Protocols.h" +#include "Settings/Settings_Interface.h" +#include "Game/Game.h" +#include "Audio/Audio_HW_Interface.h" +#include "NeoPixels/NeoPixels.h" +#include "BLE/BLE_Packets.h" +#include "BLE/BLE_Packet_Tracker.h" +#include "BLE/BLE_HW_Interface.h" +#include "IR/IR_HW_Interface.h" +#include "States/State_Machine.h" +#include "Menu/Menu.h" + +SystemKResult_T Initialize_SystemK(void); + +#ifndef pdTICKS_TO_MS + #define pdTICKS_TO_MS(xTicks) ((TickType_t)((((uint64_t)(xTicks) * 1000) + (configTICK_RATE_HZ/2)) / configTICK_RATE_HZ)) +#endif // pdTICKS_TO_MS + +#endif // SYSTEMK_H