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