/* * This program source code file is part of SystemK, a library in the KTag project. * * 🛡️ 🃞 * * Copyright © 2016-2025 Joseph P. Kearney and the KTag developers. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more * details. * * There should be a copy of the GNU Affero General Public License in the LICENSE * file in the root of this repository. If not, see . */ #include #include #include "SystemK.h" // #define DEBUG_PACKET_ENCODE #define DEBUG_PACKET_DECODE static const char *KLOG_TAG = "Protocols"; static const char *PROTOCOL_NAME[] = { FOREACH_PROTOCOL(GENERATE_STRING)}; static inline const char *ProtocolNameAsString(Protocol_T p) { if (p < LAST_PROTOCOL) { return PROTOCOL_NAME[p]; } return "Invalid Protocol!"; } static inline const char *DecodedPacketTypeAsString(DecodedPacketType_T p) { if (p == DECODED_PACKET_TYPE_IGNORED) { return "Ignored"; } if (p == DECODED_PACKET_TYPE_TAG_RECEIVED) { return "Tag Received"; } if (p == DECODED_PACKET_TYPE_COMMAND_RECEIVED) { return "Command Received"; } return "Unexpected Packet Type"; } static inline void PrintPulseTrainToConsole(TimedPulseTrain_T *train) { for (uint_fast16_t i = 0; i < train->count; i += 2) { KLOG_DEBUG(KLOG_TAG, "%2d: (%d, %4d) (%d, %4d)", i + 1, train->bitstream[i].symbol, train->bitstream[i].duration, train->bitstream[i + 1].symbol, train->bitstream[i + 1].duration); vTaskDelay(pdMS_TO_TICKS(10)); } } ModulationFrequency_T PROTOCOLS_GetModulationFrequency(Protocol_T protocol) { ModulationFrequency_T result = FREQUENCY_38kHz; switch (protocol) { default: case UNKNOWN_PROTOCOL: break; case TEST_PROTOCOL: result = FREQUENCY_38kHz; break; case DUBUQUE_PROTOCOL: result = FREQUENCY_38kHz; break; case MILES_TAG_II_PROTOCOL: result = FREQUENCY_56kHz; break; case NERF_LASER_OPS_PRO_PROTOCOL: result = FREQUENCY_38kHz; break; case NERF_PHOENIX_LTX_PROTOCOL: result = FREQUENCY_38kHz; break; case DYNASTY_PROTOCOL: result = FREQUENCY_38kHz; break; case SQUAD_HERO_PROTOCOL: result = FREQUENCY_38kHz; break; case NEC_PROTOCOL: result = FREQUENCY_38kHz; break; case LASER_X_PROTOCOL: result = FREQUENCY_38kHz; break; } return result; } color_t PROTOCOLS_GetColor(Protocol_T protocol, uint8_t team_ID, uint8_t player_ID) { color_t result = COLOR_AQUA; switch (protocol) { default: case UNKNOWN_PROTOCOL: break; case TEST_PROTOCOL: result = COLOR_WHITE; break; case DUBUQUE_PROTOCOL: result = DBQ_GetTeamColor(team_ID); break; case MILES_TAG_II_PROTOCOL: result = MILES_TAG_II_GetTeamColor(team_ID); break; case NERF_LASER_OPS_PRO_PROTOCOL: result = NERF_LASER_OPS_PRO_GetTeamColor(team_ID); break; case NERF_LASER_STRIKE_PROTOCOL: result = NERF_LASER_STRIKE_GetTeamColor(team_ID); break; case NERF_PHOENIX_LTX_PROTOCOL: result = NERF_PHOENIX_LTX_GetTeamColor(team_ID); break; case DYNASTY_PROTOCOL: result = DYNASTY_GetTeamColor(team_ID); break; case SQUAD_HERO_PROTOCOL: result = SQUAD_HERO_GetTeamColor(team_ID); break; case LASER_X_PROTOCOL: result = LASER_X_GetTeamColor(team_ID); break; } return result; } TimedPulseTrain_T *PROTOCOLS_EncodePacket(TagPacket_T *packet) { TimedPulseTrain_T *result = NULL; switch (packet->protocol) { default: case UNKNOWN_PROTOCOL: break; case TEST_PROTOCOL: result = TEST_EncodePacket(packet); break; case DUBUQUE_PROTOCOL: result = DBQ_EncodePacket(packet); break; case MILES_TAG_II_PROTOCOL: result = MILES_TAG_II_EncodePacket(packet); break; case NERF_LASER_OPS_PRO_PROTOCOL: result = NERF_LASER_OPS_PRO_EncodePacket(packet); break; case NERF_LASER_STRIKE_PROTOCOL: result = NERF_LASER_STRIKE_EncodePacket(packet); break; case NERF_PHOENIX_LTX_PROTOCOL: result = NERF_PHOENIX_LTX_EncodePacket(packet); break; case DYNASTY_PROTOCOL: result = DYNASTY_EncodePacket(packet); break; case SQUAD_HERO_PROTOCOL: result = SQUAD_HERO_EncodePacket(packet); break; case NEC_PROTOCOL: result = NULL; break; case LASER_X_PROTOCOL: result = LASER_X_EncodePacket(packet); break; } #ifdef DEBUG_PACKET_ENCODE KLOG_DEBUG(KLOG_TAG, "\nEncoded %s packet (%u):", ProtocolNameAsString(packet->protocol), result->count); PrintPulseTrainToConsole(result); #endif // DEBUG_PACKET_ENCODE return result; } //! Attempts to decode the pulse durations by comparing to the known protocols. DecodedPacket_T *PROTOCOLS_MaybeDecodePacket(TimedPulseTrain_T *packet) { DecodedPacket_T *result = NULL; if (result == NULL) { result = LASER_X_MaybeDecodePacket(packet); } if (result == NULL) { result = SQUAD_HERO_MaybeDecodePacket(packet); } if (result == NULL) { result = NEC_MaybeDecodePacket(packet); if (result != NULL) { // Many NEC remotes repeat packets when the button is held down. // Too avoid double-counting, endure 500ms of silence between packets. static TickType_t lastPacketTime = 0; const TickType_t minimumInterval = pdMS_TO_TICKS(500); TickType_t currentTime = xTaskGetTickCount(); TickType_t timeSinceLastPacket = currentTime - lastPacketTime; if (timeSinceLastPacket < minimumInterval) { // This packet is possibly the result of a held button--ignore it. KLOG_WARN("NEC", "Received potentially redundant packet--ignoring."); result->Generic.type = DECODED_PACKET_TYPE_IGNORED; } lastPacketTime = currentTime; } } if (result == NULL) { result = MILES_TAG_II_MaybeDecodePacket(packet); } if (result == NULL) { result = DYNASTY_MaybeDecodePacket(packet); } if (result == NULL) { result = NERF_LASER_OPS_PRO_MaybeDecodePacket(packet); } if (result == NULL) { result = NERF_LASER_STRIKE_MaybeDecodePacket(packet); if (result != NULL) { // The Nerf Laser Strike blasters send out three identical packets whenever the trigger is pulled, // over a time of about 200 milliseconds. Only one is necessary to hit. To avoid double-counting, // we ignore further packets for a short time after receiving a valid one. static TickType_t lastValidPacketTime = 0; const TickType_t minimumInterval = pdMS_TO_TICKS(200); TickType_t currentTime = xTaskGetTickCount(); TickType_t timeSinceLastValidPacket = currentTime - lastValidPacketTime; if (timeSinceLastValidPacket < minimumInterval) { // This packet is possibly redundant with the previous valid packet--ignore it. KLOG_WARN("Nerf Laser Strike", "Received potentially redundant packet--ignoring."); result->Generic.type = DECODED_PACKET_TYPE_IGNORED; } else { lastValidPacketTime = currentTime; } } } if (result == NULL) { result = NERF_PHOENIX_LTX_MaybeDecodePacket(packet); } // Keep this close to the end of the list; it's purposely sensitive, and prone to false positives. if (result == NULL) { result = DBQ_MaybeDecodePacket(packet); } // Keep this one last, so it doesn't slow down game play. if (result == NULL) { result = TEST_MaybeDecodePacket(packet); } #ifdef DEBUG_PACKET_DECODE #ifdef ESP_PLATFORM esp_log_level_set(KLOG_TAG, ESP_LOG_DEBUG); #endif // ESP_PLATFORM if (result != NULL) { KLOG_DEBUG(KLOG_TAG, "Successfully decoded packet as %s: %s", DecodedPacketTypeAsString(result->Generic.type), ProtocolNameAsString(result->Generic.protocol)); vTaskDelay(pdMS_TO_TICKS(10)); if (result->Generic.type == DECODED_PACKET_TYPE_COMMAND_RECEIVED) { KLOG_DEBUG(KLOG_TAG, "Command data: %lu", result->Command.data); vTaskDelay(pdMS_TO_TICKS(10)); } } else { KLOG_DEBUG(KLOG_TAG, "Couldn't decode packet. Size was %d symbols:", packet->count); vTaskDelay(pdMS_TO_TICKS(10)); PrintPulseTrainToConsole(packet); } #endif // DEBUG_PACKET_DECODE // Remember which receiver saw the packet. if (result != NULL) { result->Generic.receiver = packet->receiver; } return result; }