Initial public release of SystemK.
This commit is contained in:
parent
387f57cdda
commit
6f51f5b006
129 changed files with 11654 additions and 2 deletions
325
Protocols/Dubuque.c
Normal file
325
Protocols/Dubuque.c
Normal file
|
@ -0,0 +1,325 @@
|
|||
/*
|
||||
* This program source code file is part of SystemK, a library in the KTag project.
|
||||
*
|
||||
* 🛡️ <https://ktag.clubk.club> 🃞
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#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;
|
||||
}
|
39
Protocols/Dubuque.h
Normal file
39
Protocols/Dubuque.h
Normal file
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* This program source code file is part of SystemK, a library in the KTag project.
|
||||
*
|
||||
* 🛡️ <https://ktag.clubk.club> 🃞
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef DUBUQUE_H
|
||||
#define DUBUQUE_H
|
||||
|
||||
/** \file
|
||||
* \brief This is the Dubuque protocol.
|
||||
*
|
||||
* \note The Dubuque protocol uses a 38kHz carrier for On-Off Keying.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
TimedPulseTrain_T *DBQ_EncodePacket(TagPacket_T *packet);
|
||||
DecodedPacket_T *DBQ_MaybeDecodePacket(TimedPulseTrain_T *packet);
|
||||
color_t DBQ_GetTeamColor(uint8_t team_ID);
|
||||
|
||||
#endif // DUBUQUE_H
|
214
Protocols/Dynasty.c
Executable file
214
Protocols/Dynasty.c
Executable file
|
@ -0,0 +1,214 @@
|
|||
|
||||
/*
|
||||
* 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 DYNASTY_BLUE_TEAM 0x01
|
||||
#define DYNASTY_RED_TEAM 0x02
|
||||
#define DYNASTY_GREEN_TEAM 0x03
|
||||
#define DYNASTY_WHITE_TEAM 0x04
|
||||
|
||||
#define DYNASTY_HEADER_MARK_DURATION_IN_us 1700
|
||||
#define DYNASTY_ZERO_DURATION_IN_us 450
|
||||
#define DYNASTY_ONE_DURATION_IN_us 800
|
||||
#define DYNASTY_TOLERANCE_IN_us 150
|
||||
|
||||
#define MIN_ZERO_IN_us (DYNASTY_ZERO_DURATION_IN_us - DYNASTY_TOLERANCE_IN_us)
|
||||
#define MAX_ZERO_IN_us (DYNASTY_ZERO_DURATION_IN_us + DYNASTY_TOLERANCE_IN_us)
|
||||
#define MIN_ONE_IN_us (DYNASTY_ONE_DURATION_IN_us - DYNASTY_TOLERANCE_IN_us)
|
||||
#define MAX_ONE_IN_us (DYNASTY_ONE_DURATION_IN_us + DYNASTY_TOLERANCE_IN_us)
|
||||
|
||||
static const TimedBit_T DYNASTY_HEADER_MARK = {.symbol = MARK, .duration = DYNASTY_HEADER_MARK_DURATION_IN_us};
|
||||
static const TimedBit_T DYNASTY_ZERO_SPACE = {.symbol = SPACE, .duration = DYNASTY_ZERO_DURATION_IN_us};
|
||||
static const TimedBit_T DYNASTY_ONE_SPACE = {.symbol = SPACE, .duration = DYNASTY_ONE_DURATION_IN_us};
|
||||
static const TimedBit_T DYNASTY_ZERO_MARK = {.symbol = MARK, .duration = DYNASTY_ZERO_DURATION_IN_us};
|
||||
static const TimedBit_T DYNASTY_ONE_MARK = {.symbol = MARK, .duration = DYNASTY_ONE_DURATION_IN_us};
|
||||
static const TimedBit_T DYNASTY_END = {.symbol = SPACE, .duration = LAST_PULSE};
|
||||
|
||||
static TimedPulseTrain_T Tag_Send_Buffer;
|
||||
static DecodedPacket_T Dynasty_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 (Dynasty_Tag_Received_Buffers[0].Tag.type == DECODED_PACKET_TYPE_BUFFER_FREE)
|
||||
{
|
||||
return &Dynasty_Tag_Received_Buffers[0];
|
||||
}
|
||||
else if (Dynasty_Tag_Received_Buffers[1].Tag.type == DECODED_PACKET_TYPE_BUFFER_FREE)
|
||||
{
|
||||
return &Dynasty_Tag_Received_Buffers[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Just use it.
|
||||
return &Dynasty_Tag_Received_Buffers[2];
|
||||
}
|
||||
}
|
||||
|
||||
static inline uint8_t Calculate_Checksum(uint32_t data)
|
||||
{
|
||||
uint8_t result = 0;
|
||||
|
||||
// uint8_t byte_1 = (uint8_t)((data >> 8) & 0xFF);
|
||||
// uint8_t byte_0 = (uint8_t)(data & 0xFF);
|
||||
|
||||
// TODO: Finish implementing the Dynasty checksum.
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static inline void PackPulseTrain(TimedPulseTrain_T *pulsetrain, uint32_t data)
|
||||
{
|
||||
pulsetrain->bitstream[0] = DYNASTY_HEADER_MARK;
|
||||
|
||||
for (uint8_t n = 0; n < 16; n++)
|
||||
{
|
||||
if ((data & 0x0001) == 0x0001)
|
||||
{
|
||||
pulsetrain->bitstream[1 + (n * 2)] = DYNASTY_ONE_SPACE;
|
||||
}
|
||||
else
|
||||
{
|
||||
pulsetrain->bitstream[1 + (n * 2)] = DYNASTY_ZERO_SPACE;
|
||||
}
|
||||
data = data >> 1;
|
||||
if ((data & 0x0001) == 0x0001)
|
||||
{
|
||||
pulsetrain->bitstream[1 + (n * 2)] = DYNASTY_ONE_MARK;
|
||||
}
|
||||
else
|
||||
{
|
||||
pulsetrain->bitstream[1 + (n * 2)] = DYNASTY_ZERO_MARK;
|
||||
}
|
||||
data = data >> 1;
|
||||
}
|
||||
|
||||
// TODO: Append the Dynasty checksum.
|
||||
|
||||
pulsetrain->bitstream[(2 * 20) - 1] = DYNASTY_END;
|
||||
}
|
||||
|
||||
TimedPulseTrain_T *DYNASTY_EncodePacket(TagPacket_T *packet)
|
||||
{
|
||||
uint16_t packed_data = 0;
|
||||
|
||||
PackPulseTrain(&Tag_Send_Buffer, packed_data);
|
||||
|
||||
return &Tag_Send_Buffer;
|
||||
}
|
||||
|
||||
DecodedPacket_T *DYNASTY_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 = DYNASTY_PROTOCOL;
|
||||
Tag_Rx_Buffer->Tag.player_ID = 0;
|
||||
Tag_Rx_Buffer->Tag.team_ID = DYNASTY_RED_TEAM;
|
||||
Tag_Rx_Buffer->Tag.damage = 10;
|
||||
Tag_Rx_Buffer->Tag.color = COLOR_RED;
|
||||
return Tag_Rx_Buffer;
|
||||
}
|
||||
}
|
||||
|
||||
color_t DYNASTY_GetTeamColor(uint8_t team_ID)
|
||||
{
|
||||
color_t result = COLOR_RED;
|
||||
|
||||
if (team_ID == DYNASTY_RED_TEAM)
|
||||
{
|
||||
result = COLOR_RED;
|
||||
}
|
||||
else if (team_ID == DYNASTY_BLUE_TEAM)
|
||||
{
|
||||
result = COLOR_BLUE;
|
||||
}
|
||||
else if (team_ID == DYNASTY_GREEN_TEAM)
|
||||
{
|
||||
result = COLOR_GREEN;
|
||||
}
|
||||
else if (team_ID == DYNASTY_WHITE_TEAM)
|
||||
{
|
||||
result = COLOR_WHITE;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
44
Protocols/Dynasty.h
Executable file
44
Protocols/Dynasty.h
Executable file
|
@ -0,0 +1,44 @@
|
|||
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef DYNASTY_H
|
||||
#define DYNASTY_H
|
||||
|
||||
/** \file
|
||||
* \brief This is the Dynasty protocol.
|
||||
*
|
||||
* \note These devices use a 38kHz carrier. The modulation scheme is a little more complicated
|
||||
* than on-off keying: the width of each mark or space represents one bit of data. This
|
||||
* results in a more time-efficient data packet.
|
||||
*
|
||||
* https://dynastytoys.co/
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
TimedPulseTrain_T *DYNASTY_EncodePacket(TagPacket_T *packet);
|
||||
DecodedPacket_T *DYNASTY_MaybeDecodePacket(TimedPulseTrain_T *packet);
|
||||
color_t DYNASTY_GetTeamColor(uint8_t team_ID);
|
||||
|
||||
#endif // DYNASTY_H
|
212
Protocols/Laser_X.c
Executable file
212
Protocols/Laser_X.c
Executable file
|
@ -0,0 +1,212 @@
|
|||
|
||||
/*
|
||||
* 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 LASER_X_PURPLE_TEAM 0b00
|
||||
#define LASER_X_RED_TEAM 0b01
|
||||
#define LASER_X_BLUE_TEAM 0b10
|
||||
|
||||
#define RED_TEAM_PACKET_DATA 0x4A
|
||||
#define BLUE_TEAM_PACKET_DATA 0x8A
|
||||
#define PURPLE_TEAM_PACKET_DATA 0xCA
|
||||
|
||||
#define LASER_X_HEADER_MARK_DURATION_IN_us 6500
|
||||
#define LASER_X_SPACE_DURATION_IN_us 500
|
||||
#define LASER_X_MARK_ZERO_DURATION_IN_us 500
|
||||
#define LASER_X_MARK_ONE_DURATION_IN_us 1500
|
||||
#define LASER_X_TOLERANCE_IN_us 450
|
||||
|
||||
#define MIN_MARK_ZERO_IN_us (LASER_X_MARK_ZERO_DURATION_IN_us - LASER_X_TOLERANCE_IN_us)
|
||||
#define MAX_MARK_ZERO_IN_us (LASER_X_MARK_ZERO_DURATION_IN_us + LASER_X_TOLERANCE_IN_us)
|
||||
#define MIN_MARK_ONE_IN_us (LASER_X_MARK_ONE_DURATION_IN_us - LASER_X_TOLERANCE_IN_us)
|
||||
#define MAX_MARK_ONE_IN_us (LASER_X_MARK_ONE_DURATION_IN_us + LASER_X_TOLERANCE_IN_us)
|
||||
|
||||
static const TimedBit_T LASER_X_HEADER_MARK = {.symbol = MARK, .duration = LASER_X_HEADER_MARK_DURATION_IN_us};
|
||||
static const TimedBit_T LASER_X_SPACE = {.symbol = SPACE, .duration = LASER_X_SPACE_DURATION_IN_us};
|
||||
static const TimedBit_T LASER_X_ONE = {.symbol = MARK, .duration = LASER_X_MARK_ONE_DURATION_IN_us};
|
||||
static const TimedBit_T LASER_X_ZERO = {.symbol = MARK, .duration = LASER_X_MARK_ZERO_DURATION_IN_us};
|
||||
static const TimedBit_T LASER_X_END = {.symbol = SPACE, .duration = LAST_PULSE};
|
||||
|
||||
static TimedPulseTrain_T Tag_Send_Buffer;
|
||||
static DecodedPacket_T Laser_X_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 (Laser_X_Tag_Received_Buffers[0].Tag.type == DECODED_PACKET_TYPE_BUFFER_FREE)
|
||||
{
|
||||
return &Laser_X_Tag_Received_Buffers[0];
|
||||
}
|
||||
else if (Laser_X_Tag_Received_Buffers[1].Tag.type == DECODED_PACKET_TYPE_BUFFER_FREE)
|
||||
{
|
||||
return &Laser_X_Tag_Received_Buffers[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Just use it.
|
||||
return &Laser_X_Tag_Received_Buffers[2];
|
||||
}
|
||||
}
|
||||
|
||||
static inline void PackPulseTrain(TimedPulseTrain_T *pulsetrain, uint8_t data)
|
||||
{
|
||||
pulsetrain->bitstream[0] = LASER_X_HEADER_MARK;
|
||||
pulsetrain->bitstream[1] = LASER_X_SPACE;
|
||||
|
||||
for (uint8_t n = 0; n < 8; n++)
|
||||
{
|
||||
if ((data & 0x01) == 0x01)
|
||||
{
|
||||
pulsetrain->bitstream[2 + (n * 2)] = LASER_X_ONE;
|
||||
}
|
||||
else
|
||||
{
|
||||
pulsetrain->bitstream[2 + (n * 2)] = LASER_X_ZERO;
|
||||
}
|
||||
pulsetrain->bitstream[2 + (n * 2) + 1] = LASER_X_SPACE;
|
||||
data = data >> 1;
|
||||
}
|
||||
|
||||
pulsetrain->bitstream[17] = LASER_X_END;
|
||||
pulsetrain->count = 17;
|
||||
}
|
||||
|
||||
TimedPulseTrain_T *LASER_X_EncodePacket(TagPacket_T *packet)
|
||||
{
|
||||
uint16_t packed_data = 0;
|
||||
|
||||
if (packet->team_ID == LASER_X_RED_TEAM)
|
||||
{
|
||||
packed_data = RED_TEAM_PACKET_DATA;
|
||||
}
|
||||
else if (packet->team_ID == LASER_X_BLUE_TEAM)
|
||||
{
|
||||
packed_data = BLUE_TEAM_PACKET_DATA;
|
||||
}
|
||||
else // LASER_X_PURPLE_TEAM
|
||||
{
|
||||
packed_data = PURPLE_TEAM_PACKET_DATA;
|
||||
}
|
||||
|
||||
PackPulseTrain(&Tag_Send_Buffer, packed_data);
|
||||
|
||||
return &Tag_Send_Buffer;
|
||||
}
|
||||
|
||||
DecodedPacket_T *LASER_X_MaybeDecodePacket(TimedPulseTrain_T *packet)
|
||||
{
|
||||
// Some implementations (ESP32) do not report odd numbers of pulses.
|
||||
if ((packet->count != 17) && (packet->count != 18))
|
||||
{
|
||||
// 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.
|
||||
|
||||
uint8_t data = 0;
|
||||
for (uint8_t n = 0; n < 8; n++)
|
||||
{
|
||||
// Even pulses are marks; odd pulses are spaces.
|
||||
if ((packet->bitstream[2 + (n * 2)].duration > MIN_MARK_ONE_IN_us) && (packet->bitstream[2 + (n * 2)].duration < MAX_MARK_ONE_IN_us))
|
||||
{
|
||||
// One
|
||||
data |= ((uint8_t)1 << n);
|
||||
}
|
||||
else if ((packet->bitstream[2 + (n * 2)].duration > MIN_MARK_ZERO_IN_us) && (packet->bitstream[2 + (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 = LASER_X_PROTOCOL;
|
||||
Tag_Rx_Buffer->Tag.player_ID = 0;
|
||||
Tag_Rx_Buffer->Tag.team_ID = LASER_X_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 = LASER_X_PROTOCOL;
|
||||
Tag_Rx_Buffer->Tag.player_ID = 0;
|
||||
Tag_Rx_Buffer->Tag.team_ID = LASER_X_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 = LASER_X_PROTOCOL;
|
||||
Tag_Rx_Buffer->Tag.player_ID = 0;
|
||||
Tag_Rx_Buffer->Tag.team_ID = LASER_X_PURPLE_TEAM;
|
||||
Tag_Rx_Buffer->Tag.damage = 10;
|
||||
Tag_Rx_Buffer->Tag.color = COLOR_PURPLE;
|
||||
return Tag_Rx_Buffer;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
color_t LASER_X_GetTeamColor(uint8_t team_ID)
|
||||
{
|
||||
color_t result = COLOR_PURPLE;
|
||||
|
||||
if (team_ID == LASER_X_RED_TEAM)
|
||||
{
|
||||
result = COLOR_RED;
|
||||
}
|
||||
else if (team_ID == LASER_X_BLUE_TEAM)
|
||||
{
|
||||
result = COLOR_BLUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
42
Protocols/Laser_X.h
Executable file
42
Protocols/Laser_X.h
Executable file
|
@ -0,0 +1,42 @@
|
|||
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef LASER_X_H
|
||||
#define LASER_X_H
|
||||
|
||||
/** \file
|
||||
* \brief This is the Laser X protocol.
|
||||
*
|
||||
* \note These devices use 38kHz carrier for On-Off Keying.
|
||||
*
|
||||
* https://www.getlaserx.com/
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
TimedPulseTrain_T *LASER_X_EncodePacket(TagPacket_T *packet);
|
||||
DecodedPacket_T *LASER_X_MaybeDecodePacket(TimedPulseTrain_T *packet);
|
||||
color_t LASER_X_GetTeamColor(uint8_t team_ID);
|
||||
|
||||
#endif // LASER_X_H
|
195
Protocols/Miles_Tag_II.c
Executable file
195
Protocols/Miles_Tag_II.c
Executable file
|
@ -0,0 +1,195 @@
|
|||
|
||||
/*
|
||||
* 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 MILES_TAG_RED_TEAM 0b00
|
||||
#define MILES_TAG_BLUE_TEAM 0b01
|
||||
#define MILES_TAG_YELLOW_TEAM 0b10
|
||||
#define MILES_TAG_GREEN_TEAM 0b11
|
||||
|
||||
#define MILES_TAG_II_HEADER_PULSE_DURATION_IN_us 2400
|
||||
#define MILES_TAG_II_SPACE_DURATION_IN_us 600
|
||||
#define MILES_TAG_II_MARK_ZERO_DURATION_IN_us 600
|
||||
#define MILES_TAG_II_MARK_ONE_DURATION_IN_us 1200
|
||||
#define MILES_TAG_II_TOLERANCE_IN_us 300
|
||||
|
||||
#define MIN_SPACE_IN_us (MILES_TAG_II_SPACE_DURATION_IN_us - MILES_TAG_II_TOLERANCE_IN_us)
|
||||
#define MAX_SPACE_IN_us (MILES_TAG_II_SPACE_DURATION_IN_us + MILES_TAG_II_TOLERANCE_IN_us)
|
||||
#define MIN_MARK_ZERO_IN_us (MILES_TAG_II_MARK_ZERO_DURATION_IN_us - MILES_TAG_II_TOLERANCE_IN_us)
|
||||
#define MAX_MARK_ZERO_IN_us (MILES_TAG_II_MARK_ZERO_DURATION_IN_us + MILES_TAG_II_TOLERANCE_IN_us)
|
||||
#define MIN_MARK_ONE_IN_us (MILES_TAG_II_MARK_ONE_DURATION_IN_us - MILES_TAG_II_TOLERANCE_IN_us)
|
||||
#define MAX_MARK_ONE_IN_us (MILES_TAG_II_MARK_ONE_DURATION_IN_us + MILES_TAG_II_TOLERANCE_IN_us)
|
||||
|
||||
static const TimedBit_T MILES_TAG_HEADER = {.symbol = MARK, .duration = MILES_TAG_II_HEADER_PULSE_DURATION_IN_us};
|
||||
static const TimedBit_T MILES_TAG_SPACE = {.symbol = SPACE, .duration = MILES_TAG_II_SPACE_DURATION_IN_us};
|
||||
static const TimedBit_T MILES_TAG_ONE = {.symbol = MARK, .duration = MILES_TAG_II_MARK_ONE_DURATION_IN_us};
|
||||
static const TimedBit_T MILES_TAG_ZERO = {.symbol = MARK, .duration = MILES_TAG_II_MARK_ZERO_DURATION_IN_us};
|
||||
static const TimedBit_T MILES_TAG_END = {.symbol = SPACE, .duration = LAST_PULSE};
|
||||
|
||||
static TimedPulseTrain_T Tag_Send_Buffer;
|
||||
static DecodedPacket_T Miles_Tag_II_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 (Miles_Tag_II_Tag_Received_Buffers[0].Tag.type == DECODED_PACKET_TYPE_BUFFER_FREE)
|
||||
{
|
||||
return &Miles_Tag_II_Tag_Received_Buffers[0];
|
||||
}
|
||||
else if (Miles_Tag_II_Tag_Received_Buffers[1].Tag.type == DECODED_PACKET_TYPE_BUFFER_FREE)
|
||||
{
|
||||
return &Miles_Tag_II_Tag_Received_Buffers[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Just use it.
|
||||
return &Miles_Tag_II_Tag_Received_Buffers[2];
|
||||
}
|
||||
}
|
||||
|
||||
static const uint16_t Miles_Tag_II_Damage[] = {1, 2, 4, 5, 7, 10, 15, 17, 20, 25, 30, 35, 40, 50, 75, 100};
|
||||
static const color_t Miles_Tag_II_Team_Colors[] = {COLOR_RED, COLOR_BLUE, COLOR_YELLOW, COLOR_GREEN};
|
||||
|
||||
static inline uint8_t Encode_Damage(uint16_t damage)
|
||||
{
|
||||
uint8_t encoded_damage;
|
||||
|
||||
for (encoded_damage = 0; encoded_damage < 16; encoded_damage++)
|
||||
{
|
||||
if (damage <= Miles_Tag_II_Damage[encoded_damage])
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
return encoded_damage;
|
||||
}
|
||||
|
||||
// Use the 14 least-significant bits.
|
||||
static inline void PackPulseTrain(TimedPulseTrain_T *pulsetrain, uint16_t data)
|
||||
{
|
||||
pulsetrain->bitstream[0] = MILES_TAG_HEADER;
|
||||
pulsetrain->bitstream[1] = MILES_TAG_SPACE;
|
||||
|
||||
for (uint8_t n = 0; n < 14; n++)
|
||||
{
|
||||
if ((data & 0x01) == 0x01)
|
||||
{
|
||||
pulsetrain->bitstream[2 + (n * 2)] = MILES_TAG_ONE;
|
||||
}
|
||||
else
|
||||
{
|
||||
pulsetrain->bitstream[2 + (n * 2)] = MILES_TAG_ZERO;
|
||||
}
|
||||
pulsetrain->bitstream[2 + (n * 2) + 1] = MILES_TAG_SPACE;
|
||||
data = data >> 1;
|
||||
}
|
||||
|
||||
pulsetrain->bitstream[30] = MILES_TAG_END;
|
||||
}
|
||||
|
||||
TimedPulseTrain_T *MILES_TAG_II_EncodePacket(TagPacket_T *packet)
|
||||
{
|
||||
uint16_t packed_data = Encode_Damage(packet->damage);
|
||||
packed_data |= (packet->team_ID << 4);
|
||||
packed_data |= (packet->player_ID << 6);
|
||||
|
||||
PackPulseTrain(&Tag_Send_Buffer, packed_data);
|
||||
|
||||
return &Tag_Send_Buffer;
|
||||
}
|
||||
|
||||
DecodedPacket_T *MILES_TAG_II_MaybeDecodePacket(TimedPulseTrain_T *packet)
|
||||
{
|
||||
// Some implementations (ESP32) do not report odd numbers of pulses.
|
||||
if ((packet->count != 29) && (packet->count != 30))
|
||||
{
|
||||
// Fail fast!
|
||||
return NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Convert pulse durations to bits.
|
||||
// packet->bitstream[0] is the header--ignore it.
|
||||
// packet->bitstream[1] is the space after the header--ignore it also.
|
||||
uint16_t data = 0;
|
||||
for (uint8_t n = 0; n < 14; n++)
|
||||
{
|
||||
// Even pulses are marks; odd pulses are spaces.
|
||||
// We only check the marks against the tolerances, to save duration.
|
||||
if ((packet->bitstream[2 + (n * 2)].duration > MIN_MARK_ONE_IN_us) && (packet->bitstream[2 + (n * 2)].duration < MAX_MARK_ONE_IN_us))
|
||||
{
|
||||
// One
|
||||
data |= ((uint16_t)1 << n);
|
||||
}
|
||||
else if ((packet->bitstream[2 + (n * 2)].duration > MIN_MARK_ZERO_IN_us) && (packet->bitstream[2 + (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;
|
||||
}
|
||||
}
|
||||
|
||||
DecodedPacket_T *Tag_Rx_Buffer = Get_Tag_Packet_Buffer();
|
||||
Tag_Rx_Buffer->Tag.type = DECODED_PACKET_TYPE_TAG_RECEIVED;
|
||||
Tag_Rx_Buffer->Tag.protocol = MILES_TAG_II_PROTOCOL;
|
||||
Tag_Rx_Buffer->Tag.player_ID = 0x007F & (data >> 6);
|
||||
Tag_Rx_Buffer->Tag.team_ID = 0x0003 & (data >> 4);
|
||||
Tag_Rx_Buffer->Tag.damage = Miles_Tag_II_Damage[0x000F & data];
|
||||
Tag_Rx_Buffer->Tag.color = Miles_Tag_II_Team_Colors[Tag_Rx_Buffer->Tag.team_ID];
|
||||
|
||||
return Tag_Rx_Buffer;
|
||||
}
|
||||
}
|
||||
|
||||
color_t MILES_TAG_II_GetTeamColor(uint8_t team_ID)
|
||||
{
|
||||
color_t result = COLOR_RED;
|
||||
|
||||
if (team_ID == MILES_TAG_RED_TEAM)
|
||||
{
|
||||
// Nothing to do.
|
||||
}
|
||||
else if (team_ID == MILES_TAG_BLUE_TEAM)
|
||||
{
|
||||
result = COLOR_BLUE;
|
||||
}
|
||||
else if (team_ID == MILES_TAG_YELLOW_TEAM)
|
||||
{
|
||||
result = COLOR_YELLOW;
|
||||
}
|
||||
else if (team_ID == MILES_TAG_GREEN_TEAM)
|
||||
{
|
||||
result = COLOR_GREEN;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
33
Protocols/Miles_Tag_II.h
Executable file
33
Protocols/Miles_Tag_II.h
Executable file
|
@ -0,0 +1,33 @@
|
|||
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef MILES_TAG_H
|
||||
#define MILES_TAG_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
TimedPulseTrain_T *MILES_TAG_II_EncodePacket(TagPacket_T *packet);
|
||||
DecodedPacket_T *MILES_TAG_II_MaybeDecodePacket(TimedPulseTrain_T *packet);
|
||||
color_t MILES_TAG_II_GetTeamColor(uint8_t team_ID);
|
||||
|
||||
#endif // MILES_TAG_H
|
171
Protocols/NEC.c
Executable file
171
Protocols/NEC.c
Executable file
|
@ -0,0 +1,171 @@
|
|||
|
||||
/*
|
||||
* 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 NEC_BLUE_TEAM 0x01
|
||||
#define NEC_RED_TEAM 0x02
|
||||
|
||||
#define NEC_HEADER_MARK_DURATION_IN_us 9000
|
||||
#define NEC_HEADER_SPACE_DURATION_IN_us 4500
|
||||
#define NEC_ZERO_MARK_DURATION_IN_us 562
|
||||
#define NEC_ZERO_SPACE_DURATION_IN_us 562
|
||||
#define NEC_ONE_MARK_DURATION_IN_us 562
|
||||
#define NEC_ONE_SPACE_DURATION_IN_us 1688
|
||||
#define NEC_TOLERANCE_IN_us 200
|
||||
|
||||
#define MIN_ZERO_MARK_IN_us (NEC_ZERO_MARK_DURATION_IN_us - NEC_TOLERANCE_IN_us)
|
||||
#define MAX_ZERO_MARK_IN_us (NEC_ZERO_MARK_DURATION_IN_us + NEC_TOLERANCE_IN_us)
|
||||
#define MIN_ZERO_SPACE_IN_us (NEC_ZERO_SPACE_DURATION_IN_us - NEC_TOLERANCE_IN_us)
|
||||
#define MAX_ZERO_SPACE_IN_us (NEC_ZERO_SPACE_DURATION_IN_us + NEC_TOLERANCE_IN_us)
|
||||
#define MIN_ONE_MARK_IN_us (NEC_ONE_MARK_DURATION_IN_us - NEC_TOLERANCE_IN_us)
|
||||
#define MAX_ONE_MARK_IN_us (NEC_ONE_MARK_DURATION_IN_us + NEC_TOLERANCE_IN_us)
|
||||
#define MIN_ONE_SPACE_IN_us (NEC_ONE_SPACE_DURATION_IN_us - NEC_TOLERANCE_IN_us)
|
||||
#define MAX_ONE_SPACE_IN_us (NEC_ONE_SPACE_DURATION_IN_us + NEC_TOLERANCE_IN_us)
|
||||
|
||||
static const TimedBit_T NEC_HEADER_MARK = {.symbol = MARK, .duration = NEC_HEADER_MARK_DURATION_IN_us};
|
||||
static const TimedBit_T NEC_HEADER_SPACE = {.symbol = MARK, .duration = NEC_HEADER_SPACE_DURATION_IN_us};
|
||||
static const TimedBit_T NEC_ZERO_SPACE = {.symbol = SPACE, .duration = NEC_ZERO_SPACE_DURATION_IN_us};
|
||||
static const TimedBit_T NEC_ONE_SPACE = {.symbol = SPACE, .duration = NEC_ONE_SPACE_DURATION_IN_us};
|
||||
static const TimedBit_T NEC_ZERO_MARK = {.symbol = MARK, .duration = NEC_ZERO_MARK_DURATION_IN_us};
|
||||
static const TimedBit_T NEC_ONE_MARK = {.symbol = MARK, .duration = NEC_ONE_MARK_DURATION_IN_us};
|
||||
static const TimedBit_T NEC_END = {.symbol = SPACE, .duration = LAST_PULSE};
|
||||
|
||||
static TimedPulseTrain_T Command_Send_Buffer;
|
||||
static DecodedPacket_T NEC_Command_Buffers[3] =
|
||||
{
|
||||
{.Command.type = DECODED_PACKET_TYPE_BUFFER_FREE},
|
||||
{.Command.type = DECODED_PACKET_TYPE_BUFFER_FREE},
|
||||
{.Command.type = DECODED_PACKET_TYPE_BUFFER_FREE}};
|
||||
|
||||
static inline DecodedPacket_T *Get_Command_Packet_Buffer(void)
|
||||
{
|
||||
if (NEC_Command_Buffers[0].Command.type == DECODED_PACKET_TYPE_BUFFER_FREE)
|
||||
{
|
||||
return &NEC_Command_Buffers[0];
|
||||
}
|
||||
else if (NEC_Command_Buffers[1].Command.type == DECODED_PACKET_TYPE_BUFFER_FREE)
|
||||
{
|
||||
return &NEC_Command_Buffers[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Just use it.
|
||||
return &NEC_Command_Buffers[2];
|
||||
}
|
||||
}
|
||||
|
||||
static inline void PackPulseTrain(TimedPulseTrain_T *pulsetrain, uint32_t data)
|
||||
{
|
||||
pulsetrain->bitstream[0] = NEC_HEADER_MARK;
|
||||
pulsetrain->bitstream[1] = NEC_HEADER_SPACE;
|
||||
|
||||
for (uint8_t n = 0; n < 16; n++)
|
||||
{
|
||||
if ((data & 0x80000000) == 0x80000000)
|
||||
{
|
||||
pulsetrain->bitstream[1 + (n * 2)] = NEC_ONE_SPACE;
|
||||
}
|
||||
else
|
||||
{
|
||||
pulsetrain->bitstream[1 + (n * 2)] = NEC_ZERO_SPACE;
|
||||
}
|
||||
data = data << 1;
|
||||
if ((data & 0x80000000) == 0x80000000)
|
||||
{
|
||||
pulsetrain->bitstream[2 + (n * 2)] = NEC_ONE_MARK;
|
||||
}
|
||||
else
|
||||
{
|
||||
pulsetrain->bitstream[2 + (n * 2)] = NEC_ZERO_MARK;
|
||||
}
|
||||
data = data << 1;
|
||||
}
|
||||
|
||||
pulsetrain->bitstream[(2 * 20) + 1] = NEC_END;
|
||||
}
|
||||
|
||||
TimedPulseTrain_T *NEC_EncodePacket(CommandPacket_T *packet)
|
||||
{
|
||||
uint32_t packed_data = 0x00000000;
|
||||
|
||||
PackPulseTrain(&Command_Send_Buffer, packed_data);
|
||||
|
||||
return &Command_Send_Buffer;
|
||||
}
|
||||
|
||||
DecodedPacket_T *NEC_MaybeDecodePacket(TimedPulseTrain_T *packet)
|
||||
{
|
||||
// Sometimes the Roku remote will repeat keys, so only check for a minimum here.
|
||||
if (packet->count < 67)
|
||||
{
|
||||
// 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, too.
|
||||
|
||||
uint32_t data = 0;
|
||||
for (uint8_t n = 0; n < 32; n++)
|
||||
{
|
||||
uint8_t mark_index = (n * 2) + 2;
|
||||
uint8_t space_index = (n * 2) + 3;
|
||||
|
||||
if ((packet->bitstream[mark_index].duration > MIN_ONE_MARK_IN_us) && (packet->bitstream[mark_index].duration < MAX_ONE_MARK_IN_us) &&
|
||||
(packet->bitstream[space_index].duration > MIN_ONE_SPACE_IN_us) && (packet->bitstream[space_index].duration < MAX_ONE_SPACE_IN_us))
|
||||
{
|
||||
// One
|
||||
data |= ((uint32_t)1 << n);
|
||||
}
|
||||
else if ((packet->bitstream[mark_index].duration > MIN_ZERO_MARK_IN_us) && (packet->bitstream[mark_index].duration < MAX_ZERO_MARK_IN_us) &&
|
||||
(packet->bitstream[space_index].duration > MIN_ZERO_SPACE_IN_us) && (packet->bitstream[space_index].duration < MAX_ZERO_SPACE_IN_us))
|
||||
{
|
||||
// Zero--nothing to do.
|
||||
}
|
||||
else
|
||||
{
|
||||
// This mark is neither a zero or a one; abort.
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// PulseDurations[66] is the end-of-message mark. It can also be ignored.
|
||||
DecodedPacket_T *Command_Rx_Buffer = Get_Command_Packet_Buffer();
|
||||
Command_Rx_Buffer->Command.type = DECODED_PACKET_TYPE_COMMAND_RECEIVED;
|
||||
Command_Rx_Buffer->Command.protocol = NEC_PROTOCOL;
|
||||
Command_Rx_Buffer->Command.data = data;
|
||||
return Command_Rx_Buffer;
|
||||
}
|
||||
}
|
||||
|
||||
color_t NEC_GetTeamColor(uint8_t team_ID)
|
||||
{
|
||||
(void)team_ID;
|
||||
|
||||
return COLOR_BLACK;
|
||||
}
|
39
Protocols/NEC.h
Executable file
39
Protocols/NEC.h
Executable file
|
@ -0,0 +1,39 @@
|
|||
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef NEC_H
|
||||
#define NEC_H
|
||||
|
||||
/** \file
|
||||
* \brief This is the NEC Infrared Transmission Protocol.
|
||||
*
|
||||
* See https://techdocs.altium.com/display/FPGA/NEC+Infrared+Transmission+Protocol for more information.
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
TimedPulseTrain_T *NEC_EncodePacket(CommandPacket_T *packet);
|
||||
DecodedPacket_T *NEC_MaybeDecodePacket(TimedPulseTrain_T *packet);
|
||||
color_t NEC_GetTeamColor(uint8_t team_ID);
|
||||
|
||||
#endif // NEC_H
|
218
Protocols/Nerf_Laser_Ops_Pro.c
Executable file
218
Protocols/Nerf_Laser_Ops_Pro.c
Executable file
|
@ -0,0 +1,218 @@
|
|||
|
||||
/*
|
||||
* 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 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;
|
||||
}
|
44
Protocols/Nerf_Laser_Ops_Pro.h
Executable file
44
Protocols/Nerf_Laser_Ops_Pro.h
Executable file
|
@ -0,0 +1,44 @@
|
|||
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef NERF_LASER_OPS_PRO_H
|
||||
#define NERF_LASER_OPS_PRO_H
|
||||
|
||||
/** \file
|
||||
* \brief This is the Nerf Laser Ops Pro protocol.
|
||||
*
|
||||
* \note These devices use 38kHz carrier for On-Off Keying.
|
||||
*
|
||||
* https://nerf.hasbro.com/en-us/product/nerf-laser-ops-pro-alphapoint-2-pack:CA310BBE-E75A-4354-8D69-AC2B28C4E007
|
||||
*
|
||||
* https://forum.arduino.cc/index.php?topic=582222.0
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
TimedPulseTrain_T *NERF_LASER_OPS_PRO_EncodePacket(TagPacket_T *packet);
|
||||
DecodedPacket_T *NERF_LASER_OPS_PRO_MaybeDecodePacket(TimedPulseTrain_T *packet);
|
||||
color_t NERF_LASER_OPS_PRO_GetTeamColor(uint8_t team_ID);
|
||||
|
||||
#endif // NERF_LASER_OPS_PRO_H
|
295
Protocols/Nerf_Laser_Strike.c
Executable file
295
Protocols/Nerf_Laser_Strike.c
Executable file
|
@ -0,0 +1,295 @@
|
|||
|
||||
/*
|
||||
* This program source code file is part of SystemK, a library in the KTag project.
|
||||
*
|
||||
* 🛡️ <https://ktag.clubk.club> 🃞
|
||||
*
|
||||
* Copyright © 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"
|
||||
|
||||
static const char *KLOG_TAG = "Nerf Laser Strike";
|
||||
|
||||
#define NERF_LASER_STRIKE_SOLO_TEAM 0b00
|
||||
#define NERF_LASER_STRIKE_ONE_TEAM 0b01
|
||||
#define NERF_LASER_STRIKE_TWO_TEAM 0b10
|
||||
|
||||
#define NERF_LASER_STRIKE_BLASTER 0b000
|
||||
#define NERF_LASER_STRIKE_ROCKET 0b001
|
||||
#define NERF_LASER_STRIKE_PLASMA 0b010
|
||||
#define NERF_LASER_STRIKE_BURST 0b011
|
||||
#define NERF_LASER_STRIKE_LASER 0b100
|
||||
|
||||
static const uint16_t Packet_Data[5][3] = {
|
||||
// Solo, Team 1, Team 2
|
||||
{0x4D33, 0x34CC, 0x2B55}, // Blaster
|
||||
{0x52B2, 0x354B, 0x2CD4}, // Rocket
|
||||
{0x532D, 0x4ACA, 0x2D53}, // Plasma
|
||||
{0x54AC, 0x4B35, 0x32D2}, // Burst
|
||||
{0x552B, 0x4CB4, 0x334D} // Laser
|
||||
};
|
||||
|
||||
static inline uint16_t Get_Packet_Data(uint8_t team, uint8_t weapon)
|
||||
{
|
||||
uint16_t result = 0;
|
||||
|
||||
if ((team <= NERF_LASER_STRIKE_TWO_TEAM) && (weapon <= NERF_LASER_STRIKE_LASER))
|
||||
{
|
||||
result = Packet_Data[weapon][team];
|
||||
}
|
||||
else
|
||||
{
|
||||
KLOG_ERROR(KLOG_TAG, "Requested team (%u) or weapon (%u) out of range!", team, weapon);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//! Given decoded packet data, this function searches the table to determine if the packet is a valid one.
|
||||
/*!
|
||||
* If it is valid, it will set team and weapon to their valid values, and return true.
|
||||
* If it does not find the packet data in the table, it will return false.
|
||||
*/
|
||||
static inline bool Is_Valid_Packet_Data(const uint16_t data, uint8_t *team, uint8_t *weapon)
|
||||
{
|
||||
bool valid = false;
|
||||
|
||||
for (uint_fast8_t weapon_index = 0; (weapon_index < 5) && (valid == false); weapon_index++)
|
||||
{
|
||||
for (uint_fast8_t team_index = 0; (team_index < 3) && (valid == false); team_index++)
|
||||
{
|
||||
if (Packet_Data[weapon_index][team_index] == data)
|
||||
{
|
||||
*team = team_index;
|
||||
*weapon = weapon_index;
|
||||
valid = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
#define NERF_LASER_STRIKE_HEADER_MARK_DURATION_IN_us 6000
|
||||
#define NERF_LASER_STRIKE_ZERO_DURATION_IN_us 500
|
||||
#define NERF_LASER_STRIKE_ONE_DURATION_IN_us 1500
|
||||
|
||||
#define NERF_LASER_STRIKE_TOLERANCE_IN_us 450
|
||||
|
||||
#define MIN_ZERO_IN_us (NERF_LASER_STRIKE_ZERO_DURATION_IN_us - NERF_LASER_STRIKE_TOLERANCE_IN_us)
|
||||
#define MAX_ZERO_IN_us (NERF_LASER_STRIKE_ZERO_DURATION_IN_us + NERF_LASER_STRIKE_TOLERANCE_IN_us)
|
||||
#define MIN_ONE_IN_us (NERF_LASER_STRIKE_ONE_DURATION_IN_us - NERF_LASER_STRIKE_TOLERANCE_IN_us)
|
||||
#define MAX_ONE_IN_us (NERF_LASER_STRIKE_ONE_DURATION_IN_us + NERF_LASER_STRIKE_TOLERANCE_IN_us)
|
||||
|
||||
static const TimedBit_T NERF_LASER_STRIKE_HEADER_MARK = {.symbol = MARK, .duration = NERF_LASER_STRIKE_HEADER_MARK_DURATION_IN_us};
|
||||
static const TimedBit_T NERF_LASER_STRIKE_ZERO_SPACE = {.symbol = SPACE, .duration = NERF_LASER_STRIKE_ZERO_DURATION_IN_us};
|
||||
static const TimedBit_T NERF_LASER_STRIKE_ONE_SPACE = {.symbol = SPACE, .duration = NERF_LASER_STRIKE_ONE_DURATION_IN_us};
|
||||
static const TimedBit_T NERF_LASER_STRIKE_ZERO_MARK = {.symbol = MARK, .duration = NERF_LASER_STRIKE_ZERO_DURATION_IN_us};
|
||||
static const TimedBit_T NERF_LASER_STRIKE_ONE_MARK = {.symbol = MARK, .duration = NERF_LASER_STRIKE_ONE_DURATION_IN_us};
|
||||
static const TimedBit_T NERF_LASER_STRIKE_END = {.symbol = SPACE, .duration = LAST_PULSE};
|
||||
|
||||
static TimedPulseTrain_T Tag_Send_Buffer;
|
||||
static DecodedPacket_T Nerf_Laser_Strike_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_Strike_Tag_Received_Buffers[0].Tag.type == DECODED_PACKET_TYPE_BUFFER_FREE)
|
||||
{
|
||||
return &Nerf_Laser_Strike_Tag_Received_Buffers[0];
|
||||
}
|
||||
else if (Nerf_Laser_Strike_Tag_Received_Buffers[1].Tag.type == DECODED_PACKET_TYPE_BUFFER_FREE)
|
||||
{
|
||||
return &Nerf_Laser_Strike_Tag_Received_Buffers[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Just use it.
|
||||
return &Nerf_Laser_Strike_Tag_Received_Buffers[2];
|
||||
}
|
||||
}
|
||||
|
||||
// Packs data into a pulse train, most significant bit first.
|
||||
static inline void PackPulseTrain(TimedPulseTrain_T *pulsetrain, uint16_t data)
|
||||
{
|
||||
pulsetrain->bitstream[0] = NERF_LASER_STRIKE_HEADER_MARK;
|
||||
|
||||
for (uint8_t n = 0; n < 8; n++)
|
||||
{
|
||||
if ((data & 0x8000) == 0x8000)
|
||||
{
|
||||
pulsetrain->bitstream[1 + (n * 2)] = NERF_LASER_STRIKE_ONE_SPACE;
|
||||
}
|
||||
else
|
||||
{
|
||||
pulsetrain->bitstream[1 + (n * 2)] = NERF_LASER_STRIKE_ZERO_SPACE;
|
||||
}
|
||||
data = data << 1;
|
||||
if ((data & 0x8000) == 0x8000)
|
||||
{
|
||||
pulsetrain->bitstream[2 + (n * 2)] = NERF_LASER_STRIKE_ONE_MARK;
|
||||
}
|
||||
else
|
||||
{
|
||||
pulsetrain->bitstream[2 + (n * 2)] = NERF_LASER_STRIKE_ZERO_MARK;
|
||||
}
|
||||
data = data << 1;
|
||||
}
|
||||
|
||||
pulsetrain->bitstream[17] = NERF_LASER_STRIKE_END;
|
||||
pulsetrain->count = 17;
|
||||
}
|
||||
|
||||
TimedPulseTrain_T *NERF_LASER_STRIKE_EncodePacket(TagPacket_T *packet)
|
||||
{
|
||||
uint16_t packed_data = 0;
|
||||
|
||||
if (packet->team_ID == TEAM_RED)
|
||||
{
|
||||
packed_data = Get_Packet_Data(NERF_LASER_STRIKE_ONE_TEAM, NERF_LASER_STRIKE_BLASTER);
|
||||
}
|
||||
else if (packet->team_ID == TEAM_BLUE)
|
||||
{
|
||||
packed_data = Get_Packet_Data(NERF_LASER_STRIKE_TWO_TEAM, NERF_LASER_STRIKE_BLASTER);
|
||||
}
|
||||
else // NERF_LASER_STRIKE_SOLO_TEAM
|
||||
{
|
||||
packed_data = Get_Packet_Data(NERF_LASER_STRIKE_SOLO_TEAM, NERF_LASER_STRIKE_BLASTER);
|
||||
}
|
||||
|
||||
PackPulseTrain(&Tag_Send_Buffer, packed_data);
|
||||
|
||||
return &Tag_Send_Buffer;
|
||||
}
|
||||
|
||||
DecodedPacket_T *NERF_LASER_STRIKE_MaybeDecodePacket(TimedPulseTrain_T *packet)
|
||||
{
|
||||
// Some implementations (ESP32) do not report odd numbers of pulses.
|
||||
// The PSoC sees all three copies of the tag at once.
|
||||
if ((packet->count != 17) &&
|
||||
(packet->count != 18) &&
|
||||
(packet->count != 53))
|
||||
{
|
||||
// Fail fast!
|
||||
return NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Convert pulse durations to bits, assuming most significant bit is sent first.
|
||||
// packet->bitstream[0] is the header mark--ignore it.
|
||||
|
||||
uint16_t data = 0;
|
||||
for (uint16_t n = 16; n > 0; n--)
|
||||
{
|
||||
if ((packet->bitstream[n].duration > MIN_ONE_IN_us) && (packet->bitstream[n].duration < MAX_ONE_IN_us))
|
||||
{
|
||||
// One
|
||||
data |= ((uint16_t)1 << (16 - n));
|
||||
}
|
||||
else if ((packet->bitstream[n].duration > MIN_ZERO_IN_us) && (packet->bitstream[n].duration < MAX_ZERO_IN_us))
|
||||
{
|
||||
// Zero--nothing to do.
|
||||
}
|
||||
else
|
||||
{
|
||||
// This pulse is neither a zero or a one; abort.
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t team = 0;
|
||||
uint8_t weapon = 0;
|
||||
|
||||
if (Is_Valid_Packet_Data(data, &team, &weapon) == false)
|
||||
{
|
||||
KLOG_DEBUG(KLOG_TAG, "Decoded packet was not recognized: 0x%4X", data);
|
||||
}
|
||||
else
|
||||
{
|
||||
DecodedPacket_T *Tag_Rx_Buffer = Get_Tag_Packet_Buffer();
|
||||
|
||||
if (team == NERF_LASER_STRIKE_ONE_TEAM)
|
||||
{
|
||||
Tag_Rx_Buffer->Tag.type = DECODED_PACKET_TYPE_TAG_RECEIVED;
|
||||
Tag_Rx_Buffer->Tag.protocol = NERF_LASER_STRIKE_PROTOCOL;
|
||||
Tag_Rx_Buffer->Tag.player_ID = 0;
|
||||
Tag_Rx_Buffer->Tag.team_ID = TEAM_RED;
|
||||
Tag_Rx_Buffer->Tag.color = COLOR_RED;
|
||||
}
|
||||
else if (team == NERF_LASER_STRIKE_TWO_TEAM)
|
||||
{
|
||||
Tag_Rx_Buffer->Tag.type = DECODED_PACKET_TYPE_TAG_RECEIVED;
|
||||
Tag_Rx_Buffer->Tag.protocol = NERF_LASER_STRIKE_PROTOCOL;
|
||||
Tag_Rx_Buffer->Tag.player_ID = 0;
|
||||
Tag_Rx_Buffer->Tag.team_ID = TEAM_BLUE;
|
||||
Tag_Rx_Buffer->Tag.color = COLOR_BLUE;
|
||||
}
|
||||
else // NERF_LASER_STRIKE_SOLO_TEAM
|
||||
{
|
||||
Tag_Rx_Buffer->Tag.type = DECODED_PACKET_TYPE_TAG_RECEIVED;
|
||||
Tag_Rx_Buffer->Tag.protocol = NERF_LASER_STRIKE_PROTOCOL;
|
||||
Tag_Rx_Buffer->Tag.player_ID = 0;
|
||||
Tag_Rx_Buffer->Tag.team_ID = TEAM_PURPLE;
|
||||
Tag_Rx_Buffer->Tag.color = COLOR_PURPLE;
|
||||
}
|
||||
|
||||
// Damages are 10x, normalized for MAX_HEALTH of 100.
|
||||
if (weapon == NERF_LASER_STRIKE_BLASTER)
|
||||
{
|
||||
Tag_Rx_Buffer->Tag.damage = 10;
|
||||
}
|
||||
else if (weapon == NERF_LASER_STRIKE_ROCKET)
|
||||
{
|
||||
Tag_Rx_Buffer->Tag.damage = 30;
|
||||
}
|
||||
else // NERF_LASER_STRIKE_PLASMA, NERF_LASER_STRIKE_BURST, and NERF_LASER_STRIKE_LASER
|
||||
{
|
||||
Tag_Rx_Buffer->Tag.damage = 20;
|
||||
}
|
||||
|
||||
return Tag_Rx_Buffer;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
color_t NERF_LASER_STRIKE_GetTeamColor(uint8_t team_ID)
|
||||
{
|
||||
color_t result = COLOR_PURPLE;
|
||||
|
||||
if (team_ID == NERF_LASER_STRIKE_ONE_TEAM)
|
||||
{
|
||||
result = COLOR_RED;
|
||||
}
|
||||
else if (team_ID == NERF_LASER_STRIKE_TWO_TEAM)
|
||||
{
|
||||
result = COLOR_BLUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
40
Protocols/Nerf_Laser_Strike.h
Executable file
40
Protocols/Nerf_Laser_Strike.h
Executable file
|
@ -0,0 +1,40 @@
|
|||
|
||||
/*
|
||||
* This program source code file is part of SystemK, a library in the KTag project.
|
||||
*
|
||||
* 🛡️ <https://ktag.clubk.club> 🃞
|
||||
*
|
||||
* Copyright © 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/>.
|
||||
*/
|
||||
|
||||
#ifndef NERF_LASER_STRIKE_H
|
||||
#define NERF_LASER_STRIKE_H
|
||||
|
||||
/** \file
|
||||
* \brief This is the Nerf Laser Strike protocol.
|
||||
*
|
||||
* \note These devices use 38kHz carrier for On-Off Keying.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
TimedPulseTrain_T *NERF_LASER_STRIKE_EncodePacket(TagPacket_T *packet);
|
||||
DecodedPacket_T *NERF_LASER_STRIKE_MaybeDecodePacket(TimedPulseTrain_T *packet);
|
||||
color_t NERF_LASER_STRIKE_GetTeamColor(uint8_t team_ID);
|
||||
|
||||
#endif // NERF_LASER_STRIKE_H
|
221
Protocols/Nerf_Phoenix_LTX.c
Executable file
221
Protocols/Nerf_Phoenix_LTX.c
Executable file
|
@ -0,0 +1,221 @@
|
|||
|
||||
/*
|
||||
* 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 NERF_RED_TEAM 0b00
|
||||
#define NERF_BLUE_TEAM 0b01
|
||||
#define NERF_PURPLE_TEAM 0b10
|
||||
|
||||
//! *Team One* is the Red Team.
|
||||
#define RED_TEAM_PACKET_DATA 0x0010
|
||||
//! *Team Two* is the Blue Team.
|
||||
#define BLUE_TEAM_PACKET_DATA 0x0008
|
||||
//! *Solo* is the Purple Team.
|
||||
#define PURPLE_TEAM_PACKET_DATA 0x0000
|
||||
|
||||
#define NERF_PHOENIX_LTX_HEADER_MARK_DURATION_IN_us 3000
|
||||
#define NERF_PHOENIX_LTX_HEADER_SPACE_DURATION_IN_us 6000
|
||||
#define NERF_PHOENIX_LTX_SPACE_DURATION_IN_us 2000
|
||||
#define NERF_PHOENIX_LTX_MARK_ZERO_DURATION_IN_us 900
|
||||
#define NERF_PHOENIX_LTX_MARK_ONE_DURATION_IN_us 2000
|
||||
#define NERF_PHOENIX_LTX_TOLERANCE_IN_us 350
|
||||
|
||||
#define MIN_SPACE_IN_us (NERF_PHOENIX_LTX_SPACE_DURATION_IN_us - NERF_PHOENIX_LTX_TOLERANCE_IN_us)
|
||||
#define MAX_SPACE_IN_us (NERF_PHOENIX_LTX_SPACE_DURATION_IN_us + NERF_PHOENIX_LTX_TOLERANCE_IN_us)
|
||||
#define MIN_MARK_ZERO_IN_us (NERF_PHOENIX_LTX_MARK_ZERO_DURATION_IN_us - NERF_PHOENIX_LTX_TOLERANCE_IN_us)
|
||||
#define MAX_MARK_ZERO_IN_us (NERF_PHOENIX_LTX_MARK_ZERO_DURATION_IN_us + NERF_PHOENIX_LTX_TOLERANCE_IN_us)
|
||||
#define MIN_MARK_ONE_IN_us (NERF_PHOENIX_LTX_MARK_ONE_DURATION_IN_us - NERF_PHOENIX_LTX_TOLERANCE_IN_us)
|
||||
#define MAX_MARK_ONE_IN_us (NERF_PHOENIX_LTX_MARK_ONE_DURATION_IN_us + NERF_PHOENIX_LTX_TOLERANCE_IN_us)
|
||||
|
||||
static const TimedBit_T NERF_PHOENIX_LTX_HEADER_MARK = {.symbol = MARK, .duration = NERF_PHOENIX_LTX_HEADER_MARK_DURATION_IN_us};
|
||||
static const TimedBit_T NERF_PHOENIX_LTX_HEADER_SPACE = {.symbol = SPACE, .duration = NERF_PHOENIX_LTX_HEADER_SPACE_DURATION_IN_us};
|
||||
static const TimedBit_T NERF_PHOENIX_LTX_SPACE = {.symbol = SPACE, .duration = NERF_PHOENIX_LTX_SPACE_DURATION_IN_us};
|
||||
static const TimedBit_T NERF_PHOENIX_LTX_ONE = {.symbol = MARK, .duration = NERF_PHOENIX_LTX_MARK_ONE_DURATION_IN_us};
|
||||
static const TimedBit_T NERF_PHOENIX_LTX_ZERO = {.symbol = MARK, .duration = NERF_PHOENIX_LTX_MARK_ZERO_DURATION_IN_us};
|
||||
static const TimedBit_T NERF_PHOENIX_LTX_END = {.symbol = SPACE, .duration = LAST_PULSE};
|
||||
|
||||
static TimedPulseTrain_T Tag_Send_Buffer;
|
||||
static DecodedPacket_T Nerf_Phoenix_LTX_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_Phoenix_LTX_Tag_Received_Buffers[0].Tag.type == DECODED_PACKET_TYPE_BUFFER_FREE)
|
||||
{
|
||||
return &Nerf_Phoenix_LTX_Tag_Received_Buffers[0];
|
||||
}
|
||||
else if (Nerf_Phoenix_LTX_Tag_Received_Buffers[1].Tag.type == DECODED_PACKET_TYPE_BUFFER_FREE)
|
||||
{
|
||||
return &Nerf_Phoenix_LTX_Tag_Received_Buffers[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Just use it.
|
||||
return &Nerf_Phoenix_LTX_Tag_Received_Buffers[2];
|
||||
}
|
||||
}
|
||||
|
||||
static inline void PackPulseTrain(TimedPulseTrain_T *pulsetrain, uint16_t data)
|
||||
{
|
||||
pulsetrain->bitstream[0] = NERF_PHOENIX_LTX_HEADER_MARK;
|
||||
pulsetrain->bitstream[1] = NERF_PHOENIX_LTX_HEADER_SPACE;
|
||||
pulsetrain->bitstream[2] = NERF_PHOENIX_LTX_HEADER_MARK;
|
||||
pulsetrain->bitstream[3] = NERF_PHOENIX_LTX_SPACE;
|
||||
|
||||
for (uint8_t n = 0; n < 7; n++)
|
||||
{
|
||||
if ((data & 0x01) == 0x01)
|
||||
{
|
||||
pulsetrain->bitstream[4 + (n * 2)] = NERF_PHOENIX_LTX_ONE;
|
||||
}
|
||||
else
|
||||
{
|
||||
pulsetrain->bitstream[4 + (n * 2)] = NERF_PHOENIX_LTX_ZERO;
|
||||
}
|
||||
pulsetrain->bitstream[4 + (n * 2) + 1] = NERF_PHOENIX_LTX_SPACE;
|
||||
data = data >> 1;
|
||||
}
|
||||
|
||||
pulsetrain->bitstream[17] = NERF_PHOENIX_LTX_END;
|
||||
}
|
||||
|
||||
TimedPulseTrain_T *NERF_PHOENIX_LTX_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_PHOENIX_LTX_MaybeDecodePacket(TimedPulseTrain_T *packet)
|
||||
{
|
||||
// Some implementations (ESP32) do not report odd numbers of pulses.
|
||||
if ((packet->count != 17) && (packet->count != 18))
|
||||
{
|
||||
// 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 < 7; 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_PHOENIX_LTX_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_PHOENIX_LTX_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_PHOENIX_LTX_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_PHOENIX_LTX_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;
|
||||
}
|
42
Protocols/Nerf_Phoenix_LTX.h
Executable file
42
Protocols/Nerf_Phoenix_LTX.h
Executable file
|
@ -0,0 +1,42 @@
|
|||
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef NERF_PHOENIX_LTX_H
|
||||
#define NERF_PHOENIX_LTX_H
|
||||
|
||||
/** \file
|
||||
* \brief This is the Nerf Phoenix LTX protocol.
|
||||
*
|
||||
* \note These devices use 38kHz carrier for On-Off Keying.
|
||||
*
|
||||
* https://nerf.hasbro.com/en-us/product/nerf-lazer-tag-phoenix-ltx-tagger-2-pack:F0C4410E-19B9-F369-D914-B940ADA55500
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
TimedPulseTrain_T *NERF_PHOENIX_LTX_EncodePacket(TagPacket_T *packet);
|
||||
DecodedPacket_T *NERF_PHOENIX_LTX_MaybeDecodePacket(TimedPulseTrain_T *packet);
|
||||
color_t NERF_PHOENIX_LTX_GetTeamColor(uint8_t team_ID);
|
||||
|
||||
#endif // NERF_PHOENIX_LTX_H
|
365
Protocols/Protocols.c
Executable file
365
Protocols/Protocols.c
Executable file
|
@ -0,0 +1,365 @@
|
|||
|
||||
/*
|
||||
* 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;
|
||||
}
|
188
Protocols/Protocols.h
Executable file
188
Protocols/Protocols.h
Executable file
|
@ -0,0 +1,188 @@
|
|||
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef PROTOCOLS_H
|
||||
#define PROTOCOLS_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#define FOREACH_PROTOCOL(PROTOCOL) \
|
||||
PROTOCOL(UNKNOWN_PROTOCOL) \
|
||||
PROTOCOL(TEST_PROTOCOL) \
|
||||
PROTOCOL(DUBUQUE_PROTOCOL) \
|
||||
PROTOCOL(MILES_TAG_II_PROTOCOL) \
|
||||
PROTOCOL(NERF_LASER_OPS_PRO_PROTOCOL) \
|
||||
PROTOCOL(NERF_LASER_STRIKE_PROTOCOL) \
|
||||
PROTOCOL(NERF_PHOENIX_LTX_PROTOCOL) \
|
||||
PROTOCOL(DYNASTY_PROTOCOL) \
|
||||
PROTOCOL(SQUAD_HERO_PROTOCOL) \
|
||||
PROTOCOL(NEC_PROTOCOL) \
|
||||
PROTOCOL(LASER_X_PROTOCOL) \
|
||||
PROTOCOL(LAST_PROTOCOL)
|
||||
|
||||
#define GENERATE_ENUM(ENUM) ENUM,
|
||||
#define GENERATE_STRING(STRING) #STRING,
|
||||
|
||||
typedef enum
|
||||
{
|
||||
FOREACH_PROTOCOL(GENERATE_ENUM)
|
||||
} Protocol_T;
|
||||
|
||||
#define MAX_PULSES 125
|
||||
#define LAST_PULSE 0
|
||||
|
||||
typedef enum
|
||||
{
|
||||
FREQUENCY_38kHz,
|
||||
FREQUENCY_56kHz
|
||||
} ModulationFrequency_T;
|
||||
|
||||
//! On-Off Keying (OOK) symbols
|
||||
typedef enum
|
||||
{
|
||||
SPACE = 0,
|
||||
MARK = 1
|
||||
} OOK_Symbol_T;
|
||||
|
||||
//! Represents a length of time, in microseconds.
|
||||
typedef uint32_t Duration_in_us_T;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
TAG_SENSOR_UNKNOWN = 0,
|
||||
TAG_SENSOR_NONE,
|
||||
TAG_SENSOR_FORWARD,
|
||||
TAG_SENSOR_LEFT,
|
||||
TAG_SENSOR_RIGHT,
|
||||
TAG_SENSOR_REMOTE
|
||||
} TagSensorLocation_T;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
//! Use a value of #LAST_PULSE to mean the pulsetrain is over.
|
||||
Duration_in_us_T duration : 15;
|
||||
OOK_Symbol_T symbol : 1;
|
||||
} __attribute__((packed)) TimedBit_T;
|
||||
|
||||
//! Timing information for a single pulse, packed into a 32-bit integer.
|
||||
/*
|
||||
* \see #rmt_item32_t in the ESP-IDF.
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
union
|
||||
{
|
||||
struct
|
||||
{
|
||||
Duration_in_us_T duration0 : 15;
|
||||
OOK_Symbol_T symbol0 : 1;
|
||||
Duration_in_us_T duration1 : 15;
|
||||
OOK_Symbol_T symbol1 : 1;
|
||||
};
|
||||
uint32_t val;
|
||||
};
|
||||
} __attribute__((packed)) TimedPulse_T;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
union
|
||||
{
|
||||
TimedPulse_T pulsetrain[MAX_PULSES];
|
||||
TimedBit_T bitstream[2 * MAX_PULSES];
|
||||
};
|
||||
uint16_t count;
|
||||
TagSensorLocation_T receiver;
|
||||
} __attribute__((packed)) TimedPulseTrain_T;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
DECODED_PACKET_TYPE_BUFFER_FREE,
|
||||
DECODED_PACKET_TYPE_UNKNOWN,
|
||||
DECODED_PACKET_TYPE_IGNORED,
|
||||
DECODED_PACKET_TYPE_TAG_RECEIVED,
|
||||
DECODED_PACKET_TYPE_COMMAND_RECEIVED
|
||||
} DecodedPacketType_T;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
DecodedPacketType_T type;
|
||||
TagSensorLocation_T receiver;
|
||||
uint8_t protocol;
|
||||
} GenericDecodedPacket_T;
|
||||
|
||||
//! Contents of the decoded packet #DECODED_PACKET_TYPE_TAG_RECEIVED.
|
||||
typedef struct
|
||||
{
|
||||
DecodedPacketType_T type;
|
||||
TagSensorLocation_T receiver;
|
||||
uint8_t protocol;
|
||||
uint16_t team_ID;
|
||||
uint16_t player_ID;
|
||||
uint16_t damage;
|
||||
color_t color;
|
||||
} TagPacket_T;
|
||||
|
||||
//! Contents of the decoded packet #DECODED_PACKET_TYPE_COMMAND_RECEIVED.
|
||||
typedef struct
|
||||
{
|
||||
DecodedPacketType_T type;
|
||||
TagSensorLocation_T receiver;
|
||||
uint8_t protocol;
|
||||
uint16_t command;
|
||||
uint32_t data;
|
||||
} CommandPacket_T;
|
||||
|
||||
typedef union
|
||||
{
|
||||
GenericDecodedPacket_T Generic;
|
||||
TagPacket_T Tag;
|
||||
CommandPacket_T Command;
|
||||
} DecodedPacket_T;
|
||||
|
||||
static inline void FreeDecodedPacketBuffer(DecodedPacket_T *buffer)
|
||||
{
|
||||
if (buffer != NULL)
|
||||
{
|
||||
buffer->Generic.type = DECODED_PACKET_TYPE_BUFFER_FREE;
|
||||
}
|
||||
}
|
||||
|
||||
ModulationFrequency_T PROTOCOLS_GetModulationFrequency(Protocol_T protocol);
|
||||
color_t PROTOCOLS_GetColor(Protocol_T protocol, uint8_t team_ID, uint8_t player_ID);
|
||||
TimedPulseTrain_T *PROTOCOLS_EncodePacket(TagPacket_T *packet);
|
||||
DecodedPacket_T *PROTOCOLS_MaybeDecodePacket(TimedPulseTrain_T *packet);
|
||||
|
||||
// Include the supported protocols.
|
||||
#include "Test.h"
|
||||
#include "Dubuque.h"
|
||||
#include "Miles_Tag_II.h"
|
||||
#include "NEC.h"
|
||||
#include "Nerf_Laser_Ops_Pro.h"
|
||||
#include "Nerf_Laser_Strike.h"
|
||||
#include "Nerf_Phoenix_LTX.h"
|
||||
#include "Dynasty.h"
|
||||
#include "Squad_Hero.h"
|
||||
#include "Laser_X.h"
|
||||
|
||||
#endif // PROTOCOLS_H
|
244
Protocols/Squad_Hero.c
Executable file
244
Protocols/Squad_Hero.c
Executable file
|
@ -0,0 +1,244 @@
|
|||
|
||||
/*
|
||||
* 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 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;
|
||||
}
|
37
Protocols/Squad_Hero.h
Executable file
37
Protocols/Squad_Hero.h
Executable file
|
@ -0,0 +1,37 @@
|
|||
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef SQUAD_HERO_H
|
||||
#define SQUAD_HERO_H
|
||||
|
||||
/** \file
|
||||
* \brief This is the Squad Hero protocol.
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
TimedPulseTrain_T *SQUAD_HERO_EncodePacket(TagPacket_T *packet);
|
||||
DecodedPacket_T *SQUAD_HERO_MaybeDecodePacket(TimedPulseTrain_T *packet);
|
||||
color_t SQUAD_HERO_GetTeamColor(uint8_t team_ID);
|
||||
|
||||
#endif // SQUAD_HERO_H
|
144
Protocols/Test.c
Executable file
144
Protocols/Test.c
Executable file
|
@ -0,0 +1,144 @@
|
|||
|
||||
/*
|
||||
* 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"
|
||||
|
||||
static const char *KLOG_TAG = "Test Protocol";
|
||||
|
||||
#define TEST_PULSE_SUBTRAHEND_IN_us 300
|
||||
//! The shortest test packet is a two marks, the first of duration (3 * TEST_PULSE_SUBTRAHEND_IN_us), and the second of duration TEST_PULSE_SUBTRAHEND_IN_us.
|
||||
#define TEST_MINIMUM_LONGEST_PULSE_DURATION_IN_us (3 * TEST_PULSE_SUBTRAHEND_IN_us)
|
||||
#define TEST_TOLERANCE_IN_us ((TEST_PULSE_SUBTRAHEND_IN_us / 2) - 1)
|
||||
|
||||
static TimedPulseTrain_T Tag_Send_Buffer;
|
||||
static DecodedPacket_T Test_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 (Test_Tag_Received_Buffers[0].Tag.type == DECODED_PACKET_TYPE_BUFFER_FREE)
|
||||
{
|
||||
return &Test_Tag_Received_Buffers[0];
|
||||
}
|
||||
else if (Test_Tag_Received_Buffers[1].Tag.type == DECODED_PACKET_TYPE_BUFFER_FREE)
|
||||
{
|
||||
return &Test_Tag_Received_Buffers[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Just use it.
|
||||
return &Test_Tag_Received_Buffers[2];
|
||||
}
|
||||
}
|
||||
|
||||
static inline void PackPulseTrain(TimedPulseTrain_T *pulsetrain, uint32_t longest_pulse_duration_in_us)
|
||||
{
|
||||
uint_fast8_t index = 0;
|
||||
uint32_t next_pulse_duration_in_us = longest_pulse_duration_in_us;
|
||||
TimedBit_T next_bit;
|
||||
|
||||
if (next_pulse_duration_in_us < TEST_MINIMUM_LONGEST_PULSE_DURATION_IN_us)
|
||||
{
|
||||
next_pulse_duration_in_us = TEST_MINIMUM_LONGEST_PULSE_DURATION_IN_us;
|
||||
}
|
||||
|
||||
while ((next_pulse_duration_in_us >= TEST_PULSE_SUBTRAHEND_IN_us) && (index < MAX_PULSES))
|
||||
{
|
||||
// Even indicies are marks.
|
||||
if ((index % 2) == 0)
|
||||
{
|
||||
next_bit.symbol = MARK;
|
||||
}
|
||||
// Odd indicies are spaces.
|
||||
else
|
||||
{
|
||||
next_bit.symbol = SPACE;
|
||||
}
|
||||
next_bit.duration = next_pulse_duration_in_us;
|
||||
pulsetrain->bitstream[index] = next_bit;
|
||||
|
||||
next_pulse_duration_in_us = next_pulse_duration_in_us - TEST_PULSE_SUBTRAHEND_IN_us;
|
||||
index++;
|
||||
}
|
||||
|
||||
// Mark the end of the train.
|
||||
next_bit.symbol = SPACE;
|
||||
next_bit.duration = LAST_PULSE;
|
||||
pulsetrain->bitstream[index] = next_bit;
|
||||
pulsetrain->count = index;
|
||||
}
|
||||
|
||||
TimedPulseTrain_T *TEST_EncodePacket(TagPacket_T *packet)
|
||||
{
|
||||
uint32_t packed_data = 2100;
|
||||
|
||||
PackPulseTrain(&Tag_Send_Buffer, packed_data);
|
||||
|
||||
return &Tag_Send_Buffer;
|
||||
}
|
||||
|
||||
DecodedPacket_T *TEST_MaybeDecodePacket(TimedPulseTrain_T *packet)
|
||||
{
|
||||
// packet->bitstream[0] is the longest pulse--assume it was received correctly.
|
||||
uint32_t longest_pulse_duration_in_us = packet->bitstream[0].duration;
|
||||
|
||||
if ((longest_pulse_duration_in_us - TEST_TOLERANCE_IN_us) < TEST_MINIMUM_LONGEST_PULSE_DURATION_IN_us)
|
||||
{
|
||||
// The first pulse is too short!
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (uint_fast8_t index = 1; index < packet->count; index++)
|
||||
{
|
||||
uint32_t expected_pulse_duration_in_us = longest_pulse_duration_in_us - (index * TEST_PULSE_SUBTRAHEND_IN_us);
|
||||
|
||||
if (packet->bitstream[index].duration < (expected_pulse_duration_in_us - TEST_TOLERANCE_IN_us))
|
||||
{
|
||||
KLOG_WARN(KLOG_TAG, "Pulse %u is too short! Expected %lu; received %u.", index, expected_pulse_duration_in_us, packet->bitstream[index].duration);
|
||||
return NULL;
|
||||
}
|
||||
if (packet->bitstream[index].duration > (expected_pulse_duration_in_us + TEST_TOLERANCE_IN_us))
|
||||
{
|
||||
KLOG_WARN(KLOG_TAG, "Pulse %u is too long! Expected %lu; received %u.", index, expected_pulse_duration_in_us, packet->bitstream[index].duration);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
DecodedPacket_T *Command_Rx_Buffer = Get_Tag_Packet_Buffer();
|
||||
Command_Rx_Buffer->Command.type = DECODED_PACKET_TYPE_COMMAND_RECEIVED;
|
||||
Command_Rx_Buffer->Command.protocol = TEST_PROTOCOL;
|
||||
Command_Rx_Buffer->Command.data = longest_pulse_duration_in_us;
|
||||
return Command_Rx_Buffer;
|
||||
}
|
||||
|
||||
color_t TEST_GetTeamColor(__attribute__((unused)) uint8_t team_ID)
|
||||
{
|
||||
color_t result = COLOR_WHITE;
|
||||
|
||||
return result;
|
||||
}
|
38
Protocols/Test.h
Executable file
38
Protocols/Test.h
Executable file
|
@ -0,0 +1,38 @@
|
|||
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef TEST_H
|
||||
#define TEST_H
|
||||
|
||||
/** \file
|
||||
* \brief This is the test protocol.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
TimedPulseTrain_T *TEST_EncodePacket(TagPacket_T *packet);
|
||||
DecodedPacket_T *TEST_MaybeDecodePacket(TimedPulseTrain_T *packet);
|
||||
color_t TEST_GetTeamColor(uint8_t team_ID);
|
||||
|
||||
#endif // TEST_H
|
Loading…
Add table
Add a link
Reference in a new issue