/* * 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 NERF_RED_TEAM 0b00 #define NERF_BLUE_TEAM 0b01 #define NERF_PURPLE_TEAM 0b10 #define RED_TEAM_PACKET_DATA 0x0010 #define BLUE_TEAM_PACKET_DATA 0x0210 #define PURPLE_TEAM_PACKET_DATA 0x0110 #define NERF_LASER_OPS_PRO_HEADER_MARK_DURATION_IN_us 3000 #define NERF_LASER_OPS_PRO_HEADER_SPACE_DURATION_IN_us 6000 #define NERF_LASER_OPS_PRO_SPACE_DURATION_IN_us 2000 #define NERF_LASER_OPS_PRO_MARK_ZERO_DURATION_IN_us 800 #define NERF_LASER_OPS_PRO_MARK_ONE_DURATION_IN_us 2000 #define NERF_LASER_OPS_PRO_TOLERANCE_IN_us 400 #define MIN_SPACE_IN_us (NERF_LASER_OPS_PRO_SPACE_DURATION_IN_us - NERF_LASER_OPS_PRO_TOLERANCE_IN_us) #define MAX_SPACE_IN_us (NERF_LASER_OPS_PRO_SPACE_DURATION_IN_us + NERF_LASER_OPS_PRO_TOLERANCE_IN_us) #define MIN_MARK_ZERO_IN_us (NERF_LASER_OPS_PRO_MARK_ZERO_DURATION_IN_us - NERF_LASER_OPS_PRO_TOLERANCE_IN_us) #define MAX_MARK_ZERO_IN_us (NERF_LASER_OPS_PRO_MARK_ZERO_DURATION_IN_us + NERF_LASER_OPS_PRO_TOLERANCE_IN_us) #define MIN_MARK_ONE_IN_us (NERF_LASER_OPS_PRO_MARK_ONE_DURATION_IN_us - NERF_LASER_OPS_PRO_TOLERANCE_IN_us) #define MAX_MARK_ONE_IN_us (NERF_LASER_OPS_PRO_MARK_ONE_DURATION_IN_us + NERF_LASER_OPS_PRO_TOLERANCE_IN_us) static const TimedBit_T NERF_LASER_OPS_PRO_HEADER_MARK = {.symbol = MARK, .duration = NERF_LASER_OPS_PRO_HEADER_MARK_DURATION_IN_us}; static const TimedBit_T NERF_LASER_OPS_PRO_HEADER_SPACE = {.symbol = SPACE, .duration = NERF_LASER_OPS_PRO_HEADER_SPACE_DURATION_IN_us}; static const TimedBit_T NERF_LASER_OPS_PRO_SPACE = {.symbol = SPACE, .duration = NERF_LASER_OPS_PRO_SPACE_DURATION_IN_us}; static const TimedBit_T NERF_LASER_OPS_PRO_ONE = {.symbol = MARK, .duration = NERF_LASER_OPS_PRO_MARK_ONE_DURATION_IN_us}; static const TimedBit_T NERF_LASER_OPS_PRO_ZERO = {.symbol = MARK, .duration = NERF_LASER_OPS_PRO_MARK_ZERO_DURATION_IN_us}; static const TimedBit_T NERF_LASER_OPS_PRO_END = {.symbol = SPACE, .duration = LAST_PULSE}; static TimedPulseTrain_T Tag_Send_Buffer; static DecodedPacket_T Nerf_Laser_Ops_Pro_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 (Nerf_Laser_Ops_Pro_Tag_Received_Buffers[0].Tag.type == DECODED_PACKET_TYPE_BUFFER_FREE) { return &Nerf_Laser_Ops_Pro_Tag_Received_Buffers[0]; } else if (Nerf_Laser_Ops_Pro_Tag_Received_Buffers[1].Tag.type == DECODED_PACKET_TYPE_BUFFER_FREE) { return &Nerf_Laser_Ops_Pro_Tag_Received_Buffers[1]; } else { // Just use it. return &Nerf_Laser_Ops_Pro_Tag_Received_Buffers[2]; } } static inline void PackPulseTrain(TimedPulseTrain_T *pulsetrain, uint16_t data) { pulsetrain->bitstream[0] = NERF_LASER_OPS_PRO_HEADER_MARK; pulsetrain->bitstream[1] = NERF_LASER_OPS_PRO_HEADER_SPACE; pulsetrain->bitstream[2] = NERF_LASER_OPS_PRO_HEADER_MARK; pulsetrain->bitstream[3] = NERF_LASER_OPS_PRO_SPACE; for (uint8_t n = 0; n < 16; n++) { if ((data & 0x01) == 0x01) { pulsetrain->bitstream[4 + (n * 2)] = NERF_LASER_OPS_PRO_ONE; } else { pulsetrain->bitstream[4 + (n * 2)] = NERF_LASER_OPS_PRO_ZERO; } pulsetrain->bitstream[4 + (n * 2) + 1] = NERF_LASER_OPS_PRO_SPACE; data = data >> 1; } pulsetrain->bitstream[35] = NERF_LASER_OPS_PRO_END; } TimedPulseTrain_T *NERF_LASER_OPS_PRO_EncodePacket(TagPacket_T *packet) { uint16_t packed_data = 0; if (packet->team_ID == NERF_RED_TEAM) { packed_data = RED_TEAM_PACKET_DATA; } else if (packet->team_ID == NERF_BLUE_TEAM) { packed_data = BLUE_TEAM_PACKET_DATA; } else // NERF_PURPLE_TEAM { packed_data = PURPLE_TEAM_PACKET_DATA; } PackPulseTrain(&Tag_Send_Buffer, packed_data); return &Tag_Send_Buffer; } DecodedPacket_T *NERF_LASER_OPS_PRO_MaybeDecodePacket(TimedPulseTrain_T *packet) { // Some implementations (ESP32) do not report odd numbers of pulses. if ((packet->count != 35) && (packet->count != 36)) { // Fail fast! return NULL; } else { // Convert pulse durations to bits. // packet->bitstream[0] is the header mark--ignore it. // packet->bitstream[1] is the header space--ignore it also. // packet->bitstream[2] is the second header mark--ignore it as well. // packet->bitstream[3] is the second header space--also ignored. uint16_t data = 0; for (uint8_t n = 0; n < 16; n++) { // Even pulses are marks; odd pulses are spaces. if ((packet->bitstream[4 + (n * 2)].duration > MIN_MARK_ONE_IN_us) && (packet->bitstream[4 + (n * 2)].duration < MAX_MARK_ONE_IN_us)) { // One data |= ((uint16_t)1 << n); } else if ((packet->bitstream[4 + (n * 2)].duration > MIN_MARK_ZERO_IN_us) && (packet->bitstream[4 + (n * 2)].duration < MAX_MARK_ZERO_IN_us)) { // Zero--nothing to do. } else { // This mark is neither a zero or a one; abort. return NULL; } } if (data == RED_TEAM_PACKET_DATA) { DecodedPacket_T *Tag_Rx_Buffer = Get_Tag_Packet_Buffer(); Tag_Rx_Buffer->Tag.type = DECODED_PACKET_TYPE_TAG_RECEIVED; Tag_Rx_Buffer->Tag.protocol = NERF_LASER_OPS_PRO_PROTOCOL; Tag_Rx_Buffer->Tag.player_ID = 0; Tag_Rx_Buffer->Tag.team_ID = NERF_RED_TEAM; Tag_Rx_Buffer->Tag.damage = 10; Tag_Rx_Buffer->Tag.color = COLOR_RED; return Tag_Rx_Buffer; } else if (data == BLUE_TEAM_PACKET_DATA) { DecodedPacket_T *Tag_Rx_Buffer = Get_Tag_Packet_Buffer(); Tag_Rx_Buffer->Tag.type = DECODED_PACKET_TYPE_TAG_RECEIVED; Tag_Rx_Buffer->Tag.protocol = NERF_LASER_OPS_PRO_PROTOCOL; Tag_Rx_Buffer->Tag.player_ID = 0; Tag_Rx_Buffer->Tag.team_ID = NERF_BLUE_TEAM; Tag_Rx_Buffer->Tag.damage = 10; Tag_Rx_Buffer->Tag.color = COLOR_BLUE; return Tag_Rx_Buffer; } else if (data == PURPLE_TEAM_PACKET_DATA) { DecodedPacket_T *Tag_Rx_Buffer = Get_Tag_Packet_Buffer(); Tag_Rx_Buffer->Tag.type = DECODED_PACKET_TYPE_TAG_RECEIVED; Tag_Rx_Buffer->Tag.protocol = NERF_LASER_OPS_PRO_PROTOCOL; Tag_Rx_Buffer->Tag.player_ID = 0; Tag_Rx_Buffer->Tag.team_ID = NERF_PURPLE_TEAM; Tag_Rx_Buffer->Tag.damage = 10; Tag_Rx_Buffer->Tag.color = COLOR_PURPLE; return Tag_Rx_Buffer; } return NULL; } } color_t NERF_LASER_OPS_PRO_GetTeamColor(uint8_t team_ID) { color_t result = COLOR_PURPLE; if (team_ID == NERF_RED_TEAM) { result = COLOR_RED; } else if (team_ID == NERF_BLUE_TEAM) { result = COLOR_BLUE; } else { // Nothing to do. } return result; }