365 lines
9.4 KiB
C
Executable file
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;
|
|
}
|