/* * This program source code file is part of SystemK, a library in the KTag project. * * 🛡️ 🃞 * * Copyright © 2024-2025 Joseph P. Kearney and the KTag developers. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more * details. * * There should be a copy of the GNU Affero General Public License in the LICENSE * file in the root of this repository. If not, see . */ #include #include #include "SystemK.h" #define DBQ_PURPLE_TEAM 0b00 #define DBQ_RED_TEAM 0b01 #define DBQ_BLUE_TEAM 0b10 #define DBQ_WHITE_TEAM 0b11 #define DBQ_ZERO_MARK_DURATION_IN_us 400 #define DBQ_ZERO_SPACE_DURATION_IN_us 800 #define DBQ_ONE_MARK_DURATION_IN_us 800 #define DBQ_ONE_SPACE_DURATION_IN_us 400 #define DBQ_TOLERANCE_IN_us 199 #define DBQ_PURPLE_TX_GAP_IN_us 2600 #define DBQ_RED_TX_GAP_IN_us 5000 #define DBQ_BLUE_TX_GAP_IN_us 7400 #define DBQ_WHITE_TX_GAP_IN_us 9800 #define MIN_ZERO_MARK_IN_us (DBQ_ZERO_MARK_DURATION_IN_us - DBQ_TOLERANCE_IN_us) #define MAX_ZERO_MARK_IN_us (DBQ_ZERO_MARK_DURATION_IN_us + DBQ_TOLERANCE_IN_us) #define MIN_ZERO_SPACE_IN_us (DBQ_ZERO_SPACE_DURATION_IN_us - DBQ_TOLERANCE_IN_us) #define MAX_ZERO_SPACE_IN_us (DBQ_ZERO_SPACE_DURATION_IN_us + DBQ_TOLERANCE_IN_us) #define MIN_ONE_MARK_IN_us (DBQ_ONE_MARK_DURATION_IN_us - DBQ_TOLERANCE_IN_us) #define MAX_ONE_MARK_IN_us (DBQ_ONE_MARK_DURATION_IN_us + DBQ_TOLERANCE_IN_us) #define MIN_ONE_SPACE_IN_us (DBQ_ONE_SPACE_DURATION_IN_us - DBQ_TOLERANCE_IN_us) #define MAX_ONE_SPACE_IN_us (DBQ_ONE_SPACE_DURATION_IN_us + DBQ_TOLERANCE_IN_us) static const TimedBit_T DBQ_ZERO_MARK = {.symbol = MARK, .duration = DBQ_ZERO_MARK_DURATION_IN_us}; static const TimedBit_T DBQ_ONE_MARK = {.symbol = MARK, .duration = DBQ_ONE_MARK_DURATION_IN_us}; static const TimedBit_T DBQ_ZERO_SPACE = {.symbol = SPACE, .duration = DBQ_ZERO_SPACE_DURATION_IN_us}; static const TimedBit_T DBQ_ONE_SPACE = {.symbol = SPACE, .duration = DBQ_ONE_SPACE_DURATION_IN_us}; static const TimedBit_T DBQ_PURPLE_GAP = {.symbol = SPACE, .duration = DBQ_ZERO_SPACE_DURATION_IN_us + DBQ_PURPLE_TX_GAP_IN_us}; static const TimedBit_T DBQ_RED_GAP = {.symbol = SPACE, .duration = DBQ_ONE_SPACE_DURATION_IN_us + DBQ_RED_TX_GAP_IN_us}; static const TimedBit_T DBQ_BLUE_GAP = {.symbol = SPACE, .duration = DBQ_ZERO_SPACE_DURATION_IN_us + DBQ_BLUE_TX_GAP_IN_us}; static const TimedBit_T DBQ_WHITE_GAP = {.symbol = SPACE, .duration = DBQ_ONE_SPACE_DURATION_IN_us + DBQ_WHITE_TX_GAP_IN_us}; static const TimedBit_T DBQ_END = {.symbol = SPACE, .duration = LAST_PULSE}; static TimedPulseTrain_T Tag_Send_Buffer; static DecodedPacket_T DBQ_Tag_Received_Buffers[3] = { {.Tag.type = DECODED_PACKET_TYPE_BUFFER_FREE}, {.Tag.type = DECODED_PACKET_TYPE_BUFFER_FREE}, {.Tag.type = DECODED_PACKET_TYPE_BUFFER_FREE} }; static uint_fast16_t Team_To_Ignore = DBQ_WHITE_TEAM; static inline DecodedPacket_T *Get_Tag_Packet_Buffer(void) { if (DBQ_Tag_Received_Buffers[0].Tag.type == DECODED_PACKET_TYPE_BUFFER_FREE) { return &DBQ_Tag_Received_Buffers[0]; } else if (DBQ_Tag_Received_Buffers[1].Tag.type == DECODED_PACKET_TYPE_BUFFER_FREE) { return &DBQ_Tag_Received_Buffers[1]; } else { // Just use it. return &DBQ_Tag_Received_Buffers[2]; } } TimedPulseTrain_T *DBQ_EncodePacket(TagPacket_T *packet) { if (packet->team_ID == DBQ_PURPLE_TEAM) { Tag_Send_Buffer.bitstream[0] = DBQ_ZERO_MARK; Tag_Send_Buffer.bitstream[1] = DBQ_ZERO_SPACE; Tag_Send_Buffer.bitstream[2] = DBQ_ZERO_MARK; Tag_Send_Buffer.bitstream[3] = DBQ_PURPLE_GAP; Tag_Send_Buffer.bitstream[4] = DBQ_ZERO_MARK; Tag_Send_Buffer.bitstream[5] = DBQ_ZERO_SPACE; Tag_Send_Buffer.bitstream[6] = DBQ_ZERO_MARK; Tag_Send_Buffer.bitstream[7] = DBQ_ZERO_SPACE; Tag_Send_Buffer.bitstream[8] = DBQ_END; } else if (packet->team_ID == DBQ_RED_TEAM) { Team_To_Ignore = DBQ_RED_TEAM; Tag_Send_Buffer.bitstream[0] = DBQ_ZERO_MARK; Tag_Send_Buffer.bitstream[1] = DBQ_ZERO_SPACE; Tag_Send_Buffer.bitstream[2] = DBQ_ONE_MARK; Tag_Send_Buffer.bitstream[3] = DBQ_RED_GAP; Tag_Send_Buffer.bitstream[4] = DBQ_ZERO_MARK; Tag_Send_Buffer.bitstream[5] = DBQ_ZERO_SPACE; Tag_Send_Buffer.bitstream[6] = DBQ_ONE_MARK; Tag_Send_Buffer.bitstream[7] = DBQ_ONE_SPACE; Tag_Send_Buffer.bitstream[8] = DBQ_END; } else if (packet->team_ID == DBQ_BLUE_TEAM) { Team_To_Ignore = DBQ_BLUE_TEAM; Tag_Send_Buffer.bitstream[0] = DBQ_ONE_MARK; Tag_Send_Buffer.bitstream[1] = DBQ_ONE_SPACE; Tag_Send_Buffer.bitstream[2] = DBQ_ZERO_MARK; Tag_Send_Buffer.bitstream[3] = DBQ_BLUE_GAP; Tag_Send_Buffer.bitstream[4] = DBQ_ONE_MARK; Tag_Send_Buffer.bitstream[5] = DBQ_ONE_SPACE; Tag_Send_Buffer.bitstream[6] = DBQ_ZERO_MARK; Tag_Send_Buffer.bitstream[7] = DBQ_ZERO_SPACE; Tag_Send_Buffer.bitstream[8] = DBQ_END; } else // if (packet->team_ID == DBQ_WHITE_TEAM) { Tag_Send_Buffer.bitstream[0] = DBQ_ONE_MARK; Tag_Send_Buffer.bitstream[1] = DBQ_ONE_SPACE; Tag_Send_Buffer.bitstream[2] = DBQ_ONE_MARK; Tag_Send_Buffer.bitstream[3] = DBQ_WHITE_GAP; Tag_Send_Buffer.bitstream[4] = DBQ_ONE_MARK; Tag_Send_Buffer.bitstream[5] = DBQ_ONE_SPACE; Tag_Send_Buffer.bitstream[6] = DBQ_ONE_MARK; Tag_Send_Buffer.bitstream[7] = DBQ_ONE_SPACE; Tag_Send_Buffer.bitstream[8] = DBQ_END; } Tag_Send_Buffer.count = 8; return &Tag_Send_Buffer; } DecodedPacket_T *DBQ_MaybeDecodePacket(TimedPulseTrain_T *packet) { uint_fast8_t decoded_data = UINT_FAST8_MAX; uint_fast8_t consecutive_bits = 0; uint_fast8_t ignored_data = UINT_FAST8_MAX; // Special handling for the purple team. uint_fast16_t collisions = 0; uint_fast16_t purple_completions = 0; // Loop through all the pulses, looking for a match. for (uint8_t n = 0; n < packet->count; n += 2) { // Even pulses are marks; odd pulses are spaces. if (consecutive_bits == 0) { // Perform full checking on the first bit. if ((packet->bitstream[n].duration > MIN_ONE_MARK_IN_us) && (packet->bitstream[n].duration < MAX_ONE_MARK_IN_us) && (packet->bitstream[n + 1].duration > MIN_ONE_SPACE_IN_us) && (packet->bitstream[n + 1].duration < MAX_ONE_SPACE_IN_us)) { // This is a "one". decoded_data = 2; consecutive_bits = 1; } else if ((packet->bitstream[n].duration > MIN_ZERO_MARK_IN_us) && (packet->bitstream[n].duration < MAX_ZERO_MARK_IN_us) && (packet->bitstream[n + 1].duration > MIN_ZERO_SPACE_IN_us) && (packet->bitstream[n + 1].duration < MAX_ZERO_SPACE_IN_us)) { // This is a "zero". decoded_data = 0; consecutive_bits = 1; } else { // This mark/space combination is neither a zero or a one; continue. collisions++; } } else // consecutive_bits > 0 { // One the second bit, full check the mark, and make sure the space is long enough. if ((packet->bitstream[n].duration > MIN_ONE_MARK_IN_us) && (packet->bitstream[n].duration < MAX_ONE_MARK_IN_us) && ((packet->bitstream[n + 1].duration > MIN_ONE_SPACE_IN_us) || (packet->bitstream[n + 1].duration == LAST_PULSE))) { // This is a "one". decoded_data += 1; if (decoded_data == Team_To_Ignore) { // We received a packet we should ignore. Remember this, and keep looking. ignored_data = decoded_data; decoded_data = UINT_FAST8_MAX; consecutive_bits = 0; } else if (decoded_data == DBQ_PURPLE_TEAM) { // We received a purple packet. Remember this, and keep looking. purple_completions++; decoded_data = UINT_FAST8_MAX; consecutive_bits = 0; } else { break; } } else if ((packet->bitstream[n].duration > MIN_ZERO_MARK_IN_us) && (packet->bitstream[n].duration < MAX_ZERO_MARK_IN_us) && ((packet->bitstream[n + 1].duration > MIN_ZERO_SPACE_IN_us) || (packet->bitstream[n + 1].duration == LAST_PULSE))) { // This is a "zero". decoded_data += 0; if (decoded_data == Team_To_Ignore) { // We received a packet we should ignore. Remember this, and keep looking. ignored_data = decoded_data; decoded_data = UINT_FAST8_MAX; consecutive_bits = 0; } else if (decoded_data == DBQ_PURPLE_TEAM) { // We received a purple packet. Remember this, and keep looking. purple_completions++; decoded_data = UINT_FAST8_MAX; consecutive_bits = 0; } else { break; } } else { // This mark/space combination is neither a zero or a one; reset. collisions++; decoded_data = UINT_FAST8_MAX; consecutive_bits = 0; } } } // If we ignored the only data we received, restore it here. if ((ignored_data != UINT_FAST8_MAX) && (decoded_data == UINT_FAST8_MAX)) { decoded_data = ignored_data; } if (purple_completions > 0) { DecodedPacket_T *Tag_Rx_Buffer = Get_Tag_Packet_Buffer(); Tag_Rx_Buffer->Tag.type = DECODED_PACKET_TYPE_TAG_RECEIVED; Tag_Rx_Buffer->Tag.protocol = DUBUQUE_PROTOCOL; Tag_Rx_Buffer->Tag.player_ID = 0; Tag_Rx_Buffer->Tag.team_ID = DBQ_PURPLE_TEAM; Tag_Rx_Buffer->Tag.damage = 10 * (purple_completions + collisions); Tag_Rx_Buffer->Tag.color = COLOR_PURPLE; return Tag_Rx_Buffer; } else if (decoded_data == DBQ_RED_TEAM) { DecodedPacket_T *Tag_Rx_Buffer = Get_Tag_Packet_Buffer(); Tag_Rx_Buffer->Tag.type = DECODED_PACKET_TYPE_TAG_RECEIVED; Tag_Rx_Buffer->Tag.protocol = DUBUQUE_PROTOCOL; Tag_Rx_Buffer->Tag.player_ID = 0; Tag_Rx_Buffer->Tag.team_ID = DBQ_RED_TEAM; Tag_Rx_Buffer->Tag.damage = 10; Tag_Rx_Buffer->Tag.color = COLOR_RED; return Tag_Rx_Buffer; } else if (decoded_data == DBQ_BLUE_TEAM) { DecodedPacket_T *Tag_Rx_Buffer = Get_Tag_Packet_Buffer(); Tag_Rx_Buffer->Tag.type = DECODED_PACKET_TYPE_TAG_RECEIVED; Tag_Rx_Buffer->Tag.protocol = DUBUQUE_PROTOCOL; Tag_Rx_Buffer->Tag.player_ID = 0; Tag_Rx_Buffer->Tag.team_ID = DBQ_BLUE_TEAM; Tag_Rx_Buffer->Tag.damage = 10; Tag_Rx_Buffer->Tag.color = COLOR_BLUE; return Tag_Rx_Buffer; } else if (decoded_data == DBQ_WHITE_TEAM) { DecodedPacket_T *Tag_Rx_Buffer = Get_Tag_Packet_Buffer(); Tag_Rx_Buffer->Tag.type = DECODED_PACKET_TYPE_TAG_RECEIVED; Tag_Rx_Buffer->Tag.protocol = DUBUQUE_PROTOCOL; Tag_Rx_Buffer->Tag.player_ID = 0; Tag_Rx_Buffer->Tag.team_ID = DBQ_WHITE_TEAM; Tag_Rx_Buffer->Tag.damage = 10; Tag_Rx_Buffer->Tag.color = COLOR_WHITE; return Tag_Rx_Buffer; } return NULL; } color_t DBQ_GetTeamColor(uint8_t team_ID) { color_t result = COLOR_PURPLE; if (team_ID == DBQ_RED_TEAM) { result = COLOR_RED; } else if (team_ID == DBQ_BLUE_TEAM) { result = COLOR_BLUE; } else if (team_ID == DBQ_WHITE_TEAM) { result = COLOR_WHITE; } else { // Nothing to do. } return result; }