Initial public release of SystemK.

This commit is contained in:
Joe Kearney 2025-01-25 13:45:14 -06:00
parent 387f57cdda
commit 6f51f5b006
129 changed files with 11654 additions and 2 deletions

View file

@ -0,0 +1,68 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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

29
BLE/BLE_HW_Interface.h Normal file
View file

@ -0,0 +1,29 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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

98
BLE/BLE_Packet_Tracker.c Normal file
View file

@ -0,0 +1,98 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <stdbool.h>
#include <string.h>
#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;
}

42
BLE/BLE_Packet_Tracker.h Normal file
View file

@ -0,0 +1,42 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
/** \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

301
BLE/BLE_Packets.c Normal file
View file

@ -0,0 +1,301 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#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;
}

191
BLE/BLE_Packets.h Normal file
View file

@ -0,0 +1,191 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
/** \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 <stdint.h>
#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

72
CMakeLists.txt Executable file
View file

@ -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"
)

110
Colors.h Executable file
View file

@ -0,0 +1,110 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
/** \file
* \brief This file defines colors used by the KTag system (SystemK).
*
*/
#include <stdint.h>
#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

28
Console_HW_Interface.h Normal file
View file

@ -0,0 +1,28 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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

View file

@ -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.

148
Events/Command_Mapping.h Executable file
View file

@ -0,0 +1,148 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
/** \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

76
Events/KEvents.c Executable file
View file

@ -0,0 +1,76 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <stdbool.h>
#include <stdio.h>
#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);
}

104
Events/KEvents.h Executable file
View file

@ -0,0 +1,104 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
/** \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

31
Game/Game.c Normal file
View file

@ -0,0 +1,31 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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
};

238
Game/Game.h Normal file
View file

@ -0,0 +1,238 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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

171
Game/Weapons.c Normal file
View file

@ -0,0 +1,171 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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
}
};

82
Game/Weapons.h Normal file
View file

@ -0,0 +1,82 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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

28
IR/IR_HW_Interface.h Normal file
View file

@ -0,0 +1,28 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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

38
Kconfig Normal file
View file

@ -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

View file

@ -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.
🛡️ <https://ktag.clubk.club> 🃞
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.

29
Logging/KLog.c Executable file
View file

@ -0,0 +1,29 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include "SystemK.h"
#ifdef PSOC_PLATFORM
char8 KLog_Buffer[MAX_KLOG_STRING_LENGTH];
#endif // PSOC_PLATFORM

95
Logging/KLog.h Executable file
View file

@ -0,0 +1,95 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifdef ESP_PLATFORM
#include <esp_log.h>
#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 <project.h>
#include <stdio.h>
#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

View file

@ -0,0 +1,104 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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);
}
}

View file

@ -0,0 +1,27 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef GAMEMENUITEM_H
#define GAMEMENUITEM_H
extern const MenuItem_T GameMenuItem;
#endif // GAMEMENUITEM_H

View file

@ -0,0 +1,104 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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);
}

View file

@ -0,0 +1,27 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef PLAYERIDMENUITEM_H
#define PLAYERIDMENUITEM_H
extern const MenuItem_T PlayerIDMenuItem;
#endif // PLAYERIDMENUITEM_H

View file

@ -0,0 +1,104 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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);
}

View file

@ -0,0 +1,27 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef TEAMIDMENUITEM_H
#define TEAMIDMENUITEM_H
extern const MenuItem_T TeamIDMenuItem;
#endif // TEAMIDMENUITEM_H

View file

@ -0,0 +1,93 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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);;
}
}

View file

@ -0,0 +1,27 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef HANDEDMENUITEM_H
#define HANDEDMENUITEM_H
extern const MenuItem_T HandedMenuItem;
#endif // HANDEDMENUITEM_H

View file

@ -0,0 +1,104 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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);
}
}

View file

@ -0,0 +1,27 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef HARDWAREMENUITEM_H
#define HARDWAREMENUITEM_H
extern const MenuItem_T HardwareMenuItem;
#endif // HARDWAREMENUITEM_H

View file

@ -0,0 +1,116 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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);
}

View file

@ -0,0 +1,27 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef VOLUMEMENUITEM_H
#define VOLUMEMENUITEM_H
extern const MenuItem_T VolumeMenuItem;
#endif // VOLUMEMENUITEM_H

107
Menu/Menu.c Normal file
View file

@ -0,0 +1,107 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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);
}
}

49
Menu/Menu.h Normal file
View file

@ -0,0 +1,49 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef MENU_H
#define MENU_H
#include <stdbool.h>
#include <stdint.h>
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

39
NeoPixels/Animation.h Normal file
View file

@ -0,0 +1,39 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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

View file

@ -0,0 +1,40 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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
};

View file

@ -0,0 +1,28 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef ALL_OFF_H
#define ALL_OFF_H
extern Animation_T All_Off_Animation;
#endif // ALL_OFF_H

View file

@ -0,0 +1,108 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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};

View file

@ -0,0 +1,27 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef ALL_ON_H
#define ALL_ON_H
extern Animation_T All_On_Animation;
#endif // ALL_ON_H

View file

@ -0,0 +1,87 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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
};

View file

@ -0,0 +1,42 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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

View file

@ -0,0 +1,53 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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
};

View file

@ -0,0 +1,28 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef BLE_RSSI_H
#define BLE_RSSI_H
extern Animation_T BLE_RSSI_Animation;
#endif // BLE_RSSI_H

View file

@ -0,0 +1,165 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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
};

View file

@ -0,0 +1,28 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef COUNTDOWN_H
#define COUNTDOWN_H
extern Animation_T Countdown_Animation;
#endif // COUNTDOWN_H

View file

@ -0,0 +1,107 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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
};

View file

@ -0,0 +1,28 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef FLAMETHROWER_H
#define FLAMETHROWER_H
extern Animation_T Flamethrower_Animation;
#endif // FLAMETHROWER_H

View file

@ -0,0 +1,40 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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
};

View file

@ -0,0 +1,28 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef FLASHLIGHT_H
#define FLASHLIGHT_H
extern Animation_T Flashlight_Animation;
#endif // FLASHLIGHT_H

View file

@ -0,0 +1,116 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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
};

View file

@ -0,0 +1,28 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef HEALTH_REPORT_H
#define HEALTH_REPORT_H
extern Animation_T Health_Report_Animation;
#endif // HEALTH_REPORT_H

View file

@ -0,0 +1,121 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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
};

View file

@ -0,0 +1,28 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef IDLE_ANIMATION_H
#define IDLE_ANIMATION_H
extern Animation_T Idle_Animation;
#endif // IDLE_ANIMATION_H

View file

@ -0,0 +1,115 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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
};

View file

@ -0,0 +1,28 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef MENU_ANIMATION_H
#define MENU_ANIMATION_H
extern Animation_T Menu_Animation;
#endif // MENU_ANIMATION_H

View file

@ -0,0 +1,59 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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
};

View file

@ -0,0 +1,28 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef SHOT_FIRED_H
#define SHOT_FIRED_H
extern Animation_T Shot_Fired_Animation;
#endif // SHOT_FIRED_H

View file

@ -0,0 +1,70 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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
};

View file

@ -0,0 +1,28 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef TAG_RECEIVED_H
#define TAG_RECEIVED_H
extern Animation_T Tag_Received_Animation;
#endif // TAG_RECEIVED_H

View file

@ -0,0 +1,82 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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
};

View file

@ -0,0 +1,28 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef TAGGED_OUT_H
#define TAGGED_OUT_H
extern Animation_T Tagged_Out_Animation;
#endif // TAGGED_OUT_H

View file

@ -0,0 +1,162 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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
};

View file

@ -0,0 +1,28 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef TEAM_COLORS_H
#define TEAM_COLORS_H
extern Animation_T Team_Colors_Animation;
#endif // TEAM_COLORS_H

View file

@ -0,0 +1,116 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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
};

View file

@ -0,0 +1,28 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef TEST_PATTERN_H
#define TEST_PATTERN_H
extern Animation_T Test_Pattern_Animation;
#endif // TEST_PATTERN_H

48
NeoPixels/Displays.h Normal file
View file

@ -0,0 +1,48 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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

View file

@ -0,0 +1,60 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
// /\ /\\ /\ /\\ 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)]);
}

44
NeoPixels/Gamma.c Normal file
View file

@ -0,0 +1,44 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <stdint.h>
#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 };

28
NeoPixels/Gamma.h Normal file
View file

@ -0,0 +1,28 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef GAMMA_H
#define GAMMA_H
extern const uint_fast8_t Gamma8[];
#endif // GAMMA_H

View file

@ -0,0 +1,30 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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

164
NeoPixels/NeoPixels.c Normal file
View file

@ -0,0 +1,164 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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);
}
}

211
NeoPixels/NeoPixels.h Normal file
View file

@ -0,0 +1,211 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
/** \file
* \brief This file defines the interface to the NeoPixels used by the KTag system (SystemK).
*
*/
#ifndef NEOPIXELS_H
#define NEOPIXELS_H
#include <stdint.h>
#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

42
NeoPixels/Sine.c Normal file
View file

@ -0,0 +1,42 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <stdint.h>
#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};

28
NeoPixels/Sine.h Normal file
View file

@ -0,0 +1,28 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef SINE_H
#define SINE_H
extern const uint_fast8_t Sine8[];
#endif // SINE_H

View file

@ -0,0 +1,87 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
// '||' || '|| '||''|. 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)]);
}

325
Protocols/Dubuque.c Normal file
View file

@ -0,0 +1,325 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <stdbool.h>
#include <stdio.h>
#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;
}

39
Protocols/Dubuque.h Normal file
View file

@ -0,0 +1,39 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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 <stdbool.h>
#include <stdint.h>
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

214
Protocols/Dynasty.c Executable file
View file

@ -0,0 +1,214 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <stdbool.h>
#include <stdio.h>
#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;
}

44
Protocols/Dynasty.h Executable file
View file

@ -0,0 +1,44 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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 <stdbool.h>
#include <stdint.h>
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

212
Protocols/Laser_X.c Executable file
View file

@ -0,0 +1,212 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <stdbool.h>
#include <stdio.h>
#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;
}

42
Protocols/Laser_X.h Executable file
View file

@ -0,0 +1,42 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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 <stdbool.h>
#include <stdint.h>
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

195
Protocols/Miles_Tag_II.c Executable file
View file

@ -0,0 +1,195 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <stdbool.h>
#include <stdio.h>
#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;
}

33
Protocols/Miles_Tag_II.h Executable file
View file

@ -0,0 +1,33 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef MILES_TAG_H
#define MILES_TAG_H
#include <stdbool.h>
#include <stdint.h>
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

171
Protocols/NEC.c Executable file
View file

@ -0,0 +1,171 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <stdbool.h>
#include <stdio.h>
#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;
}

39
Protocols/NEC.h Executable file
View file

@ -0,0 +1,39 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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 <stdbool.h>
#include <stdint.h>
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

218
Protocols/Nerf_Laser_Ops_Pro.c Executable file
View file

@ -0,0 +1,218 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <stdbool.h>
#include <stdio.h>
#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;
}

44
Protocols/Nerf_Laser_Ops_Pro.h Executable file
View file

@ -0,0 +1,44 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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 <stdbool.h>
#include <stdint.h>
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

295
Protocols/Nerf_Laser_Strike.c Executable file
View file

@ -0,0 +1,295 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <stdbool.h>
#include <stdio.h>
#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;
}

40
Protocols/Nerf_Laser_Strike.h Executable file
View file

@ -0,0 +1,40 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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 <stdbool.h>
#include <stdint.h>
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

221
Protocols/Nerf_Phoenix_LTX.c Executable file
View file

@ -0,0 +1,221 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <stdbool.h>
#include <stdio.h>
#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;
}

42
Protocols/Nerf_Phoenix_LTX.h Executable file
View file

@ -0,0 +1,42 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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 <stdbool.h>
#include <stdint.h>
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

365
Protocols/Protocols.c Executable file
View file

@ -0,0 +1,365 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <stdbool.h>
#include <stdio.h>
#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;
}

188
Protocols/Protocols.h Executable file
View file

@ -0,0 +1,188 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef PROTOCOLS_H
#define PROTOCOLS_H
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
#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

244
Protocols/Squad_Hero.c Executable file
View file

@ -0,0 +1,244 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <stdbool.h>
#include <stdio.h>
#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;
}

37
Protocols/Squad_Hero.h Executable file
View file

@ -0,0 +1,37 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef SQUAD_HERO_H
#define SQUAD_HERO_H
/** \file
* \brief This is the Squad Hero protocol.
*/
#include <stdbool.h>
#include <stdint.h>
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

144
Protocols/Test.c Executable file
View file

@ -0,0 +1,144 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <stdbool.h>
#include <stdio.h>
#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;
}

38
Protocols/Test.h Executable file
View file

@ -0,0 +1,38 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef TEST_H
#define TEST_H
/** \file
* \brief This is the test protocol.
*
*/
#include <stdbool.h>
#include <stdint.h>
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

50
Results.h Normal file
View file

@ -0,0 +1,50 @@
/*
* This program source code file is part of SystemK, a library in the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* 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 <http://www.gnu.org/licenses/>.
*/
/** \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

Some files were not shown because too many files have changed in this diff Show more