SystemK/Protocols/Protocols.c
2025-01-25 13:45:14 -06:00

365 lines
9.4 KiB
C
Executable file

/*
* 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;
}