/*
 *  TinyIRReceiver.hpp
 *
 *  Receives IR protocol data of NEC protocol using pin change interrupts.
 *  NEC is the protocol of most cheap remote controls for Arduino.
 *
 *  Parity check is done for address and data.
 *  On a completely received IR command, the user function handleReceivedIRData(uint8_t aAddress, uint8_t aCommand, uint8_t aFlags)
 *  is called in interrupt context but with interrupts being enabled to enable use of delay() etc.
 *  !!!!!!!!!!!!!!!!!!!!!!
 *  Functions called in interrupt context should be running as short as possible,
 *  so if you require longer action, save the data (address + command) and handle them in the main loop.
 *  !!!!!!!!!!!!!!!!!!!!!
 *  aFlags can contain one of IRDATA_FLAGS_EMPTY, IRDATA_FLAGS_IS_REPEAT and IRDATA_FLAGS_PARITY_FAILED bits
 *
 * The FAST protocol is a proprietary modified JVC protocol without address, with parity and with a shorter header.
 *  FAST Protocol characteristics:
 * - Bit timing is like NEC or JVC
 * - The header is shorter, 3156 vs. 12500
 * - No address and 16 bit data, interpreted as 8 bit command and 8 bit inverted command,
 *     leading to a fixed protocol length of (6 + (16 * 3) + 1) * 526 = 55 * 526 = 28930 microseconds or 29 ms.
 * - Repeats are sent as complete frames but in a 50 ms period / with a 21 ms distance.
 *
 *
 *  This file is part of IRMP https://github.com/IRMP-org/IRMP.
 *  This file is part of Arduino-IRremote https://github.com/Arduino-IRremote/Arduino-IRremote.
 *
 ************************************************************************************
 * MIT License
 *
 * Copyright (c) 2022-2024 Armin Joachimsmeyer
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is furnished
 * to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
 * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 ************************************************************************************
 */

/*
 * This library can be configured at compile time by the following options / macros:
 * For more details see: https://github.com/Arduino-IRremote/Arduino-IRremote#compile-options--macros-for-this-library (scroll down)
 *
 * - IR_RECEIVE_PIN         The pin number for TinyIRReceiver IR input.
 * - IR_FEEDBACK_LED_PIN    The pin number for TinyIRReceiver feedback LED.
 * - NO_LED_FEEDBACK_CODE   Disables the feedback LED function. Saves 14 bytes program memory.
 * - DISABLE_PARITY_CHECKS  Disable parity checks. Saves 48 bytes of program memory.
 * - USE_EXTENDED_NEC_PROTOCOL Like NEC, but take the 16 bit address as one 16 bit value and not as 8 bit normal and 8 bit inverted value.
 * - USE_ONKYO_PROTOCOL     Like NEC, but take the 16 bit address and command each as one 16 bit value and not as 8 bit normal and 8 bit inverted value.
 * - USE_FAST_PROTOCOL      Use FAST protocol (no address and 16 bit data, interpreted as 8 bit command and 8 bit inverted command) instead of NEC.
 * - ENABLE_NEC2_REPEATS    Instead of sending / receiving the NEC special repeat code, send / receive the original frame for repeat.
 * - USE_CALLBACK_FOR_TINY_RECEIVER   Call the fixed function "void handleReceivedTinyIRData()" each time a frame or repeat is received.
 */

#ifndef _TINY_IR_RECEIVER_HPP
#define _TINY_IR_RECEIVER_HPP

#include <Arduino.h>

#if defined(DEBUG) && !defined(LOCAL_DEBUG)
#define LOCAL_DEBUG
#else
//#define LOCAL_DEBUG // This enables debug output only for this file
#endif

/*
 * Protocol selection
 */
//#define DISABLE_PARITY_CHECKS // Disable parity checks. Saves 48 bytes of program memory.
//#define USE_EXTENDED_NEC_PROTOCOL // Like NEC, but take the 16 bit address as one 16 bit value and not as 8 bit normal and 8 bit inverted value.
//#define USE_ONKYO_PROTOCOL    // Like NEC, but take the 16 bit address and command each as one 16 bit value and not as 8 bit normal and 8 bit inverted value.
//#define USE_FAST_PROTOCOL     // Use FAST protocol instead of NEC / ONKYO.
//#define ENABLE_NEC2_REPEATS // Instead of sending / receiving the NEC special repeat code, send / receive the original frame for repeat.

#include "TinyIR.h" // If not defined, it defines IR_RECEIVE_PIN, IR_FEEDBACK_LED_PIN and TINY_RECEIVER_USE_ARDUINO_ATTACH_INTERRUPT

#include "digitalWriteFast.h"
/** \addtogroup TinyReceiver Minimal receiver for NEC and FAST protocol
 * @{
 */

#if defined(DEBUG)
#define LOCAL_DEBUG_ATTACH_INTERRUPT
#else
//#define LOCAL_DEBUG_ATTACH_INTERRUPT  // to see if attachInterrupt() or static interrupt (by register tweaking) is used
#endif
#if defined(TRACE)
#define LOCAL_TRACE_STATE_MACHINE
#else
//#define LOCAL_TRACE_STATE_MACHINE  // to see the state of the ISR (Interrupt Service Routine) state machine
#endif

//#define _IR_MEASURE_TIMING        // Activate this if you want to enable internal hardware timing measurement.
//#define _IR_TIMING_TEST_PIN 7
TinyIRReceiverStruct TinyIRReceiverControl;
volatile TinyIRReceiverCallbackDataStruct TinyIRReceiverData;

/*
 * Set input pin and output pin definitions etc.
 */
#if defined(IR_INPUT_PIN)
#warning "IR_INPUT_PIN is deprecated, use IR_RECEIVE_PIN"
#define IR_RECEIVE_PIN  IR_INPUT_PIN
#endif
#if !defined(IR_RECEIVE_PIN)
#if defined(__AVR_ATtiny1616__) || defined(__AVR_ATtiny3216__) || defined(__AVR_ATtiny3217__)
#warning "IR_RECEIVE_PIN is not defined, so it is set to 10"
#define IR_RECEIVE_PIN    10
#elif defined(__AVR_ATtiny816__)
#warning "IR_RECEIVE_PIN is not defined, so it is set to 14"
#define IR_RECEIVE_PIN    14
#else
#warning "IR_RECEIVE_PIN is not defined, so it is set to 2"
#define IR_RECEIVE_PIN    2
#endif
#endif

#if !defined(IR_FEEDBACK_LED_PIN) && defined(LED_BUILTIN)
#define IR_FEEDBACK_LED_PIN    LED_BUILTIN
#endif

#if !( \
   (defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)) /* ATtinyX5 */ \
|| defined(__AVR_ATtiny88__) /* MH-ET LIVE Tiny88 */ \
|| defined(__AVR_ATmega1280__) || defined(__AVR_ATmega1281__) || defined(__AVR_ATmega2560__) || defined(__AVR_ATmega2561__) \
|| defined(__AVR_ATmega16U4__) || defined(__AVR_ATmega32U4__) \
|| defined(__AVR_ATmega8__) || defined(__AVR_ATmega48__) || defined(__AVR_ATmega48P__) || defined(__AVR_ATmega48PB__) || defined(__AVR_ATmega88P__) || defined(__AVR_ATmega88PB__) \
|| defined(__AVR_ATmega168__) || defined(__AVR_ATmega168PA__) || defined(__AVR_ATmega168PB__) || defined(__AVR_ATmega328__) || defined(__AVR_ATmega328P__) || defined(__AVR_ATmega328PB__) \
  /* ATmegas with ports 0,1,2 above and ATtiny167 only 2 pins below */ \
|| ( (defined(__AVR_ATtiny87__) || defined(__AVR_ATtiny167__)) && ( (defined(ARDUINO_AVR_DIGISPARKPRO) && ((IR_RECEIVE_PIN == 3) || (IR_RECEIVE_PIN == 9))) /*ATtinyX7(digisparkpro) and pin 3 or 9 */\
        || (! defined(ARDUINO_AVR_DIGISPARKPRO) && ((IR_RECEIVE_PIN == 3) || (IR_RECEIVE_PIN == 14)))) ) /*ATtinyX7(ATTinyCore) and pin 3 or 14 */ \
)
#define TINY_RECEIVER_USE_ARDUINO_ATTACH_INTERRUPT // Cannot use any static ISR vector here. In other cases we have code provided for generating interrupt on pin change.
#endif

/**
 * Declaration of the callback function provided by the user application.
 * It is called every time a complete IR command or repeat was received.
 */
extern void handleTinyReceivedIRData();

#if defined(LOCAL_DEBUG)
uint32_t sMicrosOfGap; // The length of the gap before the start bit
#endif
/**
 * The ISR (Interrupt Service Routine) of TinyIRRreceiver.
 * It handles the NEC protocol decoding and calls the user callback function on complete.
 * 5 us + 3 us for push + pop for a 16MHz ATmega
 */
#if defined(ESP8266) || defined(ESP32)
IRAM_ATTR
#endif
void IRPinChangeInterruptHandler(void) {
#if defined(_IR_MEASURE_TIMING) && defined(_IR_TIMING_TEST_PIN)
    digitalWriteFast(_IR_TIMING_TEST_PIN, HIGH); // 2 clock cycles
#endif
    /*
     * Save IR input level
     * Negative logic, true / HIGH means inactive / IR space, LOW / false means IR mark.
     */
    uint_fast8_t tIRLevel = digitalReadFast(IR_RECEIVE_PIN);

#if !defined(NO_LED_FEEDBACK_CODE) && defined(IR_FEEDBACK_LED_PIN)
    digitalWriteFast(IR_FEEDBACK_LED_PIN, !tIRLevel);
#endif

    /*
     * 1. compute microseconds after last change
     */
    // Repeats can be sent after a pause, which is longer than 64000 microseconds, so we need a 32 bit value for check of repeats
    uint32_t tCurrentMicros = micros();
    uint32_t tMicrosOfMarkOrSpace32 = tCurrentMicros - TinyIRReceiverControl.LastChangeMicros;
    uint16_t tMicrosOfMarkOrSpace = tMicrosOfMarkOrSpace32;

    TinyIRReceiverControl.LastChangeMicros = tCurrentMicros;

    uint8_t tState = TinyIRReceiverControl.IRReceiverState;

#if defined(LOCAL_TRACE_STATE_MACHINE)
    Serial.print(tState);
    Serial.print(F(" D="));
    Serial.print(tMicrosOfMarkOrSpace);
//    Serial.print(F(" I="));
//    Serial.print(tIRLevel);
    Serial.print('|');
#endif

    if (tIRLevel == LOW) {
        /*
         * We have a mark here
         */
        if (tMicrosOfMarkOrSpace > 2 * TINY_RECEIVER_HEADER_MARK) {
            // timeout -> must reset state machine
            tState = IR_RECEIVER_STATE_WAITING_FOR_START_MARK;
        }
        if (tState == IR_RECEIVER_STATE_WAITING_FOR_START_MARK) {
            // We are at the beginning of the header mark, check timing at the next transition
            tState = IR_RECEIVER_STATE_WAITING_FOR_START_SPACE;
            TinyIRReceiverControl.Flags = IRDATA_FLAGS_EMPTY; // If we do it here, it saves 4 bytes
#if defined(LOCAL_TRACE)
            sMicrosOfGap = tMicrosOfMarkOrSpace32;
#endif
#if defined(ENABLE_NEC2_REPEATS)
            // Check for repeat, where full frame is sent again after TINY_RECEIVER_REPEAT_PERIOD ms
            // Not required for NEC, where repeats are detected by a special header space duration
            // Must use 32 bit arithmetic here!
            if (tMicrosOfMarkOrSpace32 < TINY_RECEIVER_MAXIMUM_REPEAT_DISTANCE) {
                TinyIRReceiverControl.Flags = IRDATA_FLAGS_IS_REPEAT;
            }
#endif
        }

        else if (tState == IR_RECEIVER_STATE_WAITING_FOR_FIRST_DATA_MARK) {
            if (tMicrosOfMarkOrSpace >= lowerValue25Percent(TINY_RECEIVER_HEADER_SPACE)
                    && tMicrosOfMarkOrSpace <= upperValue25Percent(TINY_RECEIVER_HEADER_SPACE)) {
                /*
                 * We have a valid data header space here -> initialize data
                 */
                TinyIRReceiverControl.IRRawDataBitCounter = 0;
#if (TINY_RECEIVER_BITS > 16)
                TinyIRReceiverControl.IRRawData.ULong = 0;
#else
                TinyIRReceiverControl.IRRawData.UWord = 0;
#endif
                TinyIRReceiverControl.IRRawDataMask = 1;
                tState = IR_RECEIVER_STATE_WAITING_FOR_DATA_SPACE;
#if !defined(ENABLE_NEC2_REPEATS)
                // Check for NEC repeat header
            } else if (tMicrosOfMarkOrSpace >= lowerValue25Percent(NEC_REPEAT_HEADER_SPACE)
                    && tMicrosOfMarkOrSpace <= upperValue25Percent(NEC_REPEAT_HEADER_SPACE)
                    && TinyIRReceiverControl.IRRawDataBitCounter >= TINY_RECEIVER_BITS) {
                /*
                 * We have a repeat header here and no broken receive before -> set repeat flag
                 */
                TinyIRReceiverControl.Flags = IRDATA_FLAGS_IS_REPEAT;
                tState = IR_RECEIVER_STATE_WAITING_FOR_DATA_SPACE;
#endif
            } else {
                // This parts are optimized by the compiler into jumps to one code :-)
                // Wrong length -> reset state
                tState = IR_RECEIVER_STATE_WAITING_FOR_START_MARK;
            }
        }

        else if (tState == IR_RECEIVER_STATE_WAITING_FOR_DATA_MARK) {
            // Check data space length
            if (tMicrosOfMarkOrSpace >= lowerValue50Percent(TINY_RECEIVER_ZERO_SPACE)
                    && tMicrosOfMarkOrSpace <= upperValue50Percent(TINY_RECEIVER_ONE_SPACE)) {
                // We have a valid bit here
                tState = IR_RECEIVER_STATE_WAITING_FOR_DATA_SPACE;
                if (tMicrosOfMarkOrSpace >= 2 * TINY_RECEIVER_UNIT) {
                    // we received a 1
#if (TINY_RECEIVER_BITS > 16)
                    TinyIRReceiverControl.IRRawData.ULong |= TinyIRReceiverControl.IRRawDataMask;
#else
                    TinyIRReceiverControl.IRRawData.UWord |= TinyIRReceiverControl.IRRawDataMask;
#endif
                } else {
                    // we received a 0 - empty code for documentation
                }
                // prepare for next bit
                TinyIRReceiverControl.IRRawDataMask = TinyIRReceiverControl.IRRawDataMask << 1;
                TinyIRReceiverControl.IRRawDataBitCounter++;
            } else {
                // Wrong length -> reset state
                tState = IR_RECEIVER_STATE_WAITING_FOR_START_MARK;
            }
        } else {
            // error wrong state for the received level, e.g. if we missed one change interrupt -> reset state
            tState = IR_RECEIVER_STATE_WAITING_FOR_START_MARK;
        }
    }

    else {
        /*
         * We have a space here
         */
        if (tState == IR_RECEIVER_STATE_WAITING_FOR_START_SPACE) {
            /*
             * Check length of header mark here
             */
            if (tMicrosOfMarkOrSpace >= lowerValue25Percent(TINY_RECEIVER_HEADER_MARK)
                    && tMicrosOfMarkOrSpace <= upperValue25Percent(TINY_RECEIVER_HEADER_MARK)) {
                tState = IR_RECEIVER_STATE_WAITING_FOR_FIRST_DATA_MARK;
            } else {
                // Wrong length of header mark -> reset state
                tState = IR_RECEIVER_STATE_WAITING_FOR_START_MARK;
            }
        }

        else if (tState == IR_RECEIVER_STATE_WAITING_FOR_DATA_SPACE) {
            // Check data mark length
            if (tMicrosOfMarkOrSpace >= lowerValue50Percent(TINY_RECEIVER_BIT_MARK)
                    && tMicrosOfMarkOrSpace <= upperValue50Percent(TINY_RECEIVER_BIT_MARK)) {
                /*
                 * We have a valid mark here, check for transmission complete, i.e. the mark of the stop bit
                 */
                if (TinyIRReceiverControl.IRRawDataBitCounter >= TINY_RECEIVER_BITS
#if !defined(ENABLE_NEC2_REPEATS)
                        || (TinyIRReceiverControl.Flags & IRDATA_FLAGS_IS_REPEAT) // Do not check for full length received, if we have a short repeat frame
#endif
                        ) {
                    /*
                     * Code complete -> optionally check parity
                     */
                    // Reset state for new start
                    tState = IR_RECEIVER_STATE_WAITING_FOR_START_MARK;

#if !defined(DISABLE_PARITY_CHECKS) && (TINY_RECEIVER_ADDRESS_BITS == 16) && TINY_RECEIVER_ADDRESS_HAS_8_BIT_PARITY
                    /*
                     * Check address parity
                     * Address is sent first and contained in the lower word
                     */
                    if (TinyIRReceiverControl.IRRawData.UBytes[0] != (uint8_t) (~TinyIRReceiverControl.IRRawData.UBytes[1])) {
#if defined(ENABLE_NEC2_REPEATS)
                    TinyIRReceiverControl.Flags |= IRDATA_FLAGS_PARITY_FAILED; // here we can have the repeat flag already set
#else
                        TinyIRReceiverControl.Flags = IRDATA_FLAGS_PARITY_FAILED; // here we do not check anything, if we have a repeat
#endif
                    }
#endif
#if !defined(DISABLE_PARITY_CHECKS) && (TINY_RECEIVER_COMMAND_BITS == 16) && TINY_RECEIVER_COMMAND_HAS_8_BIT_PARITY
                    /*
                     * Check command parity
                     */
#if (TINY_RECEIVER_ADDRESS_BITS > 0)
                    if (TinyIRReceiverControl.IRRawData.UBytes[2] != (uint8_t) (~TinyIRReceiverControl.IRRawData.UBytes[3])) {
#if defined(ENABLE_NEC2_REPEATS)
                    TinyIRReceiverControl.Flags |= IRDATA_FLAGS_PARITY_FAILED;
#else
                        TinyIRReceiverControl.Flags = IRDATA_FLAGS_PARITY_FAILED;
#endif
#  if defined(LOCAL_DEBUG)
                    Serial.print(F("Parity check for command failed. Command="));
                    Serial.print(TinyIRReceiverControl.IRRawData.UBytes[2], HEX);
                    Serial.print(F(" parity="));
                    Serial.println(TinyIRReceiverControl.IRRawData.UBytes[3], HEX);
#  endif
#else
                    // No address, so command and parity are in the lowest bytes
                    if (TinyIRReceiverControl.IRRawData.UBytes[0] != (uint8_t) (~TinyIRReceiverControl.IRRawData.UBytes[1])) {
                        TinyIRReceiverControl.Flags |= IRDATA_FLAGS_PARITY_FAILED;
#  if defined(LOCAL_DEBUG)
                        Serial.print(F("Parity check for command failed. Command="));
                        Serial.print(TinyIRReceiverControl.IRRawData.UBytes[0], HEX);
                        Serial.print(F(" parity="));
                        Serial.println(TinyIRReceiverControl.IRRawData.UBytes[1], HEX);
#  endif
#endif
                    }
#endif
                    /*
                     * Call user provided callback here
                     * The parameter size is dependent of the code variant used in order to save program memory.
                     * We have 6 cases: 0, 8 bit or 16 bit address, each with 8 or 16 bit command
                     */
#if !defined(ARDUINO_ARCH_MBED) && !defined(ESP32) // no Serial etc. in callback for ESP -> no interrupt required, WDT is running!
                    interrupts(); // enable interrupts, so delay() etc. works in callback
#endif
                    TinyIRReceiverData.justWritten = true;
                    TinyIRReceiverData.Flags = TinyIRReceiverControl.Flags;
#if (TINY_RECEIVER_ADDRESS_BITS > 0)
#  if TINY_RECEIVER_ADDRESS_HAS_8_BIT_PARITY
                    // Here we have 8 bit address
                    TinyIRReceiverData.Address = TinyIRReceiverControl.IRRawData.UBytes[0];
#  else
                    // Here we have 16 bit address
                    TinyIRReceiverData.Address = TinyIRReceiverControl.IRRawData.UWord.LowWord;
#  endif
#  if TINY_RECEIVER_COMMAND_HAS_8_BIT_PARITY
                    // Here we have 8 bit command
                    TinyIRReceiverData.Command = TinyIRReceiverControl.IRRawData.UBytes[2];
#  else
                            // Here we have 16 bit command
                    TinyIRReceiverData.Command = TinyIRReceiverControl.IRRawData.UWord.HighWord;
#  endif

#else
                    // Here we have NO address
#  if TINY_RECEIVER_COMMAND_HAS_8_BIT_PARITY
                    // Here we have 8 bit command
                    TinyIRReceiverData.Command = TinyIRReceiverControl.IRRawData.UBytes[0];
#  else
                    // Here we have 16 bit command
                    TinyIRReceiverData.Command = TinyIRReceiverControl.IRRawData.UWord;
#  endif
#endif
#if defined(USE_CALLBACK_FOR_TINY_RECEIVER)
                    handleReceivedTinyIRData();
#endif

                } else {
                    // not finished yet
                    tState = IR_RECEIVER_STATE_WAITING_FOR_DATA_MARK;
                }
            } else {
                // Wrong length -> reset state
                tState = IR_RECEIVER_STATE_WAITING_FOR_START_MARK;
            }
        } else {
            // error wrong state for the received level, e.g. if we missed one change interrupt -> reset state
            tState = IR_RECEIVER_STATE_WAITING_FOR_START_MARK;
        }
    }

    TinyIRReceiverControl.IRReceiverState = tState;
#ifdef _IR_MEASURE_TIMING
    digitalWriteFast(_IR_TIMING_TEST_PIN, LOW); // 2 clock cycles
#endif
}

bool isTinyReceiverIdle() {
    return (TinyIRReceiverControl.IRReceiverState == IR_RECEIVER_STATE_WAITING_FOR_START_MARK);
}

/*
 * Checks if IR_RECEIVE_PIN is connected and high
 * @return true, if IR Receiver is attached
 */
bool isIRReceiverAttachedForTinyReceiver() {
    pinModeFast(IR_RECEIVE_PIN, OUTPUT);
    digitalWriteFast(IR_RECEIVE_PIN, LOW); // discharge pin capacity
    pinModeFast(IR_RECEIVE_PIN, INPUT);
    return digitalRead(IR_RECEIVE_PIN); // use slow digitalRead here, since the pin capacity is not fully charged again if we use digitalReadFast.
}

/**
 * Sets IR_RECEIVE_PIN mode to INPUT, and if IR_FEEDBACK_LED_PIN is defined, sets feedback LED output mode.
 * Then call enablePCIInterruptForTinyReceiver()
 */
bool initPCIInterruptForTinyReceiver() {
    pinModeFast(IR_RECEIVE_PIN, INPUT);

#if !defined(NO_LED_FEEDBACK_CODE) && defined(IR_FEEDBACK_LED_PIN)
    pinModeFast(IR_FEEDBACK_LED_PIN, OUTPUT);
#endif
    return enablePCIInterruptForTinyReceiver();
}

void printTinyReceiverResultMinimal(Print *aSerial) {
// Print only very short output, since we are in an interrupt context and do not want to miss the next interrupts of the repeats coming soon
    // Print only very short output, since we are in an interrupt context and do not want to miss the next interrupts of the repeats coming soon
#if defined(USE_FAST_PROTOCOL)
    aSerial->print(F("C=0x"));
#else
    aSerial->print(F("A=0x"));
    aSerial->print(TinyIRReceiverData.Address, HEX);
    aSerial->print(F(" C=0x"));
#endif
    aSerial->print(TinyIRReceiverData.Command, HEX);
    if (TinyIRReceiverData.Flags == IRDATA_FLAGS_IS_REPEAT) {
        aSerial->print(F(" R"));
    }
#if !defined(DISABLE_PARITY_CHECKS)
    if (TinyIRReceiverData.Flags == IRDATA_FLAGS_PARITY_FAILED) {
        aSerial->print(F(" P"));
    }
#endif
    aSerial->println();
}

#if defined (LOCAL_DEBUG_ATTACH_INTERRUPT) && !defined(STR)
// Helper macro for getting a macro definition as string
#define STR_HELPER(x) #x
#define STR(x) STR_HELPER(x)
#endif

/**************************************************
 * Pin to interrupt mapping for different platforms
 **************************************************/
#if defined(__AVR_ATtiny816__) || defined(__AVR_ATtiny1616__) || defined(__AVR_ATtiny3216__) || defined(__AVR_ATtiny3217__)
#define USE_ATTACH_INTERRUPT_DIRECT

#elif !defined(__AVR__) || defined(TINY_RECEIVER_USE_ARDUINO_ATTACH_INTERRUPT)
// Default for all NON AVR platforms
#define USE_ATTACH_INTERRUPT

#else
#  if defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
#define USE_PCIE

#  elif defined(__AVR_ATtiny87__) || defined(__AVR_ATtiny167__)
#    if defined(ARDUINO_AVR_DIGISPARKPRO)
#      if (IR_RECEIVE_PIN == 3)
#define USE_INT0
#      elif (IR_RECEIVE_PIN == 9)
#define USE_INT1
#      else
#        error "IR_RECEIVE_PIN must be 9 or 3."
#      endif // if (IR_RECEIVE_PIN == 9)
#    else // defined(ARDUINO_AVR_DIGISPARKPRO)
#      if (IR_RECEIVE_PIN == 14)
#define USE_INT0
#      elif (IR_RECEIVE_PIN == 3)
#define USE_INT1
#      else
#        error "IR_RECEIVE_PIN must be 14 or 3."
#      endif // if (IR_RECEIVE_PIN == 14)
#    endif

#  elif (defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__))
#    if (IR_RECEIVE_PIN == 21)
#define USE_INT0
#    elif (IR_RECEIVE_PIN == 20)
#define USE_INT1
#    else
#warning "No pin mapping for IR_RECEIVE_PIN to interrupt found -> attachInterrupt() is used now."
#define USE_ATTACH_INTERRUPT
#    endif

#  else // defined(__AVR_ATtiny25__)
/*
 * ATmegas + ATtiny88 here
 */
#    if (IR_RECEIVE_PIN == 2)
#define USE_INT0
#    elif (IR_RECEIVE_PIN == 3)
#define USE_INT1

#    elif IR_RECEIVE_PIN == 4 || IR_RECEIVE_PIN == 5 || IR_RECEIVE_PIN == 6 || IR_RECEIVE_PIN == 7
    //ATmega328 (Uno, Nano ) etc. Enable pin change interrupt 20 to 23 for port PD4 to PD7 (Arduino pin 4 to 7)
#define USE_PCINT2
#    elif IR_RECEIVE_PIN == 8 || IR_RECEIVE_PIN == 9 || IR_RECEIVE_PIN == 10 || IR_RECEIVE_PIN == 11 || IR_RECEIVE_PIN == 12 || IR_RECEIVE_PIN == 13
    //ATmega328 (Uno, Nano ) etc. Enable pin change interrupt 0 to 5 for port PB0 to PB5 (Arduino pin 8 to 13)
#define USE_PCINT0
#    elif IR_RECEIVE_PIN == A0 || IR_RECEIVE_PIN == A1 || IR_RECEIVE_PIN == A2 || IR_RECEIVE_PIN == A3 || IR_RECEIVE_PIN == A4 || IR_RECEIVE_PIN == A5
    //ATmega328 (Uno, Nano ) etc. Enable pin change interrupt 8 to 13 for port PC0 to PC5 (Arduino pin A0 to A5)
#define USE_PCINT1

#    else
#warning "No pin mapping for IR_RECEIVE_PIN to interrupt found -> attachInterrupt() is used now."
#define USE_ATTACH_INTERRUPT
#    endif // if (IR_RECEIVE_PIN == 2)
#  endif // defined(__AVR_ATtiny25__)
#endif // ! defined(__AVR__) || defined(TINY_RECEIVER_USE_ARDUINO_ATTACH_INTERRUPT)

/**
 * Initializes hardware interrupt generation according to IR_RECEIVE_PIN or use attachInterrupt() function.
 * @return true if interrupt was successfully enabled
 */
bool enablePCIInterruptForTinyReceiver() {
#if defined(_IR_MEASURE_TIMING) && defined(_IR_TIMING_TEST_PIN)
    pinModeFast(_IR_TIMING_TEST_PIN, OUTPUT);
#endif

#if defined(USE_ATTACH_INTERRUPT) || defined(USE_ATTACH_INTERRUPT_DIRECT)
#  if defined(USE_ATTACH_INTERRUPT)
#if defined(NOT_AN_INTERRUPT)
    if(digitalPinToInterrupt(IR_RECEIVE_PIN) == NOT_AN_INTERRUPT){
        return false;
    }
#endif
    // costs 112 bytes program memory + 4 bytes RAM
    attachInterrupt(digitalPinToInterrupt(IR_RECEIVE_PIN), IRPinChangeInterruptHandler, CHANGE);
#  else
    // 2.2 us more than version configured with macros and not compatible
    attachInterrupt(IR_RECEIVE_PIN, IRPinChangeInterruptHandler, CHANGE); // no extra pin mapping here
#  endif

#  if defined(LOCAL_DEBUG_ATTACH_INTERRUPT)
    Serial.println(F("Use attachInterrupt for pin=" STR(IR_RECEIVE_PIN)));
#  endif

#else
#  if defined(LOCAL_DEBUG_ATTACH_INTERRUPT)
    Serial.println(F("Use static interrupt for pin=" STR(IR_RECEIVE_PIN)));
#  endif
#  if defined(USE_INT0)
    // interrupt on any logical change
    EICRA |= _BV(ISC00);
    // clear interrupt bit
    EIFR |= 1 << INTF0;
    // enable interrupt on next change
    EIMSK |= 1 << INT0;

#  elif defined(USE_INT1)
    EICRA |= _BV(ISC10);
// clear interrupt bit
    EIFR |= 1 << INTF1;
// enable interrupt on next change
    EIMSK |= 1 << INT1;

#  elif defined(USE_PCIE) // For ATtiny85 etc.
    // use PinChangeInterrupt no INT0 for pin PB2
    PCMSK = _BV(IR_RECEIVE_PIN);
    // clear interrupt bit
    GIFR |= 1 << PCIF;
    // enable interrupt on next change
    GIMSK |= 1 << PCIE;

#  elif defined(USE_PCINT0)
    PCICR |= _BV(PCIE0);
    PCMSK0 = digitalPinToBitMask(IR_RECEIVE_PIN);
#  elif defined(USE_PCINT1)
    PCICR |= _BV(PCIE1);
    PCMSK1 = digitalPinToBitMask(IR_RECEIVE_PIN);
#  elif defined(USE_PCINT2)
    PCICR |= _BV(PCIE2);
    PCMSK2 = digitalPinToBitMask(IR_RECEIVE_PIN);
#  else
    return false;
#  endif
#endif // defined(USE_ATTACH_INTERRUPT)
    return true;
}

void disablePCIInterruptForTinyReceiver() {
#if defined(_IR_MEASURE_TIMING) && defined(_IR_TIMING_TEST_PIN)
    pinModeFast(_IR_TIMING_TEST_PIN, OUTPUT);
#endif

#if defined(USE_ATTACH_INTERRUPT) || defined(USE_ATTACH_INTERRUPT_DIRECT)
#  if defined(USE_ATTACH_INTERRUPT)
    detachInterrupt(digitalPinToInterrupt(IR_RECEIVE_PIN));
#  else
    detachInterrupt(IR_RECEIVE_PIN);
#  endif

#else
#  if defined(USE_INT0)
    // clear interrupt bit
    EIFR |= 1 << INTF0;
    // disable interrupt on next change
    EIMSK &= ~(1 << INT0);

#  elif defined(USE_INT1)
    // clear interrupt bit
    EIFR |= 1 << INTF1;
    // disable interrupt on next change
    EIMSK &= ~(1 << INT1);

#  elif defined(USE_PCIE) // For ATtiny85 etc.
    // clear interrupt bit
    GIFR |= 1 << PCIF;
    // disable interrupt on next change
    GIMSK &= ~(1 << PCIE);

#  elif defined(USE_PCINT0)
    PCICR &= ~(_BV(PCIE0));
#  elif defined(USE_PCINT1)
    PCICR &= ~(_BV(PCIE1));
#  elif defined(USE_PCINT2)
    PCICR &= ~(_BV(PCIE2));

#  endif
#endif // defined(USE_ATTACH_INTERRUPT)
}

/*
 * Specify the right INT0, INT1 or PCINT0 interrupt vector according to different pins and cores.
 * The default value of TINY_RECEIVER_USE_ARDUINO_ATTACH_INTERRUPT is set in TinyIRReceiver.h
 */
#if !(defined(USE_ATTACH_INTERRUPT) || defined(USE_ATTACH_INTERRUPT_DIRECT))
#  if defined(USE_INT0)
ISR(INT0_vect)

#  elif defined(USE_INT1)
ISR(INT1_vect)

#  elif defined(USE_PCIE) // For ATtiny85 etc.
// on ATtinyX5 we do not have a INT1_vect but we can use the PCINT0_vect
ISR(PCINT0_vect)

#  elif defined(USE_PCINT0)
ISR(PCINT0_vect)
#  elif defined(USE_PCINT1)
ISR(PCINT1_vect)
#  elif defined(USE_PCINT2)
ISR(PCINT2_vect)
#  else
void dummyFunctionToAvoidCompilerErrors()
#  endif
{
    IRPinChangeInterruptHandler();
}
#endif // !(defined(USE_ATTACH_INTERRUPT) || defined(USE_ATTACH_INTERRUPT_DIRECT))

/** @}*/

#if defined(LOCAL_DEBUG_ATTACH_INTERRUPT)
#undef LOCAL_DEBUG_ATTACH_INTERRUPT
#endif
#if defined(LOCAL_TRACE_STATE_MACHINE)
#undef LOCAL_TRACE_STATE_MACHINE
#endif

#if defined(LOCAL_DEBUG)
#undef LOCAL_DEBUG
#endif
#endif // _TINY_IR_RECEIVER_HPP
