/* * This program source code file is part of SystemK, a library in the KTag project. * * 🛡️ 🃞 * * Copyright © 2016-2025 Joseph P. Kearney and the KTag developers. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more * details. * * There should be a copy of the GNU Affero General Public License in the LICENSE * file in the root of this repository. If not, see . */ #include #include #include "SystemK.h" #define 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; }