Arduino-IRremote/src/IRReceive.hpp

1751 lines
62 KiB
C++

/*
* IRReceive.hpp
* This file is exclusively included by IRremote.h to enable easy configuration of library switches
*
* Contains all IRrecv class functions as well as other receiver related functions.
*
* This file is part of Arduino-IRremote https://github.com/Arduino-IRremote/Arduino-IRremote.
*
************************************************************************************
* MIT License
*
* Copyright (c) 2009-2023 Ken Shirriff, Rafi Khan, 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.
*
************************************************************************************
*/
#ifndef _IR_RECEIVE_HPP
#define _IR_RECEIVE_HPP
#if defined(DEBUG) && !defined(LOCAL_DEBUG)
#define LOCAL_DEBUG
#else
//#define LOCAL_DEBUG // This enables debug output only for this file
#endif
#if defined(TRACE) && !defined(LOCAL_TRACE)
#define LOCAL_TRACE
#else
//#define LOCAL_TRACE // This enables debug output only for this file
#endif
/*
* Low level hardware timing measurement
*/
//#define _IR_MEASURE_TIMING // for ISR
//#define _IR_TIMING_TEST_PIN 7 // "pinModeFast(_IR_TIMING_TEST_PIN, OUTPUT);" is executed at start()
//
/*
* Check for additional characteristics of timing like length of mark for a constant mark protocol,
* where space length determines the bit value. Requires up to 194 additional bytes of program memory.
*/
//#define DECODE_STRICT_CHECKS
/** \addtogroup Receiving Receiving IR data for multiple protocols
* @{
*/
/**
* The receiver instance
*/
IRrecv IrReceiver;
/*
* The control structure instance
*/
struct irparams_struct irparams; // the irparams instance
/**
* Instantiate the IRrecv class. Multiple instantiation is not supported.
* @param IRReceivePin Arduino pin to use. No sanity check is made.
*/
IRrecv::IRrecv() {
decodedIRData.rawDataPtr = &irparams; // for decodePulseDistanceData() etc.
setReceivePin(0);
#if !defined(NO_LED_FEEDBACK_CODE)
setLEDFeedback(0, DO_NOT_ENABLE_LED_FEEDBACK);
#endif
}
IRrecv::IRrecv(uint_fast8_t aReceivePin) {
decodedIRData.rawDataPtr = &irparams; // for decodePulseDistanceData() etc.
setReceivePin(aReceivePin);
#if !defined(NO_LED_FEEDBACK_CODE)
setLEDFeedback(0, DO_NOT_ENABLE_LED_FEEDBACK);
#endif
}
/**
* Instantiate the IRrecv class. Multiple instantiation is not supported.
* @param aReceivePin Arduino pin to use, where a demodulating IR receiver is connected.
* @param aFeedbackLEDPin if 0, then take board specific FEEDBACK_LED_ON() and FEEDBACK_LED_OFF() functions
*/
IRrecv::IRrecv(uint_fast8_t aReceivePin, uint_fast8_t aFeedbackLEDPin) {
decodedIRData.rawDataPtr = &irparams; // for decodePulseDistanceData() etc.
setReceivePin(aReceivePin);
#if !defined(NO_LED_FEEDBACK_CODE)
setLEDFeedback(aFeedbackLEDPin, DO_NOT_ENABLE_LED_FEEDBACK);
#else
(void) aFeedbackLEDPin;
#endif
}
/**********************************************************************************************************************
* Interrupt Service Routine - Called every 50 us
*
* Duration in ticks of 50 us of alternating SPACE, MARK are recorded in irparams.rawbuf array.
* 'rawlen' counts the number of entries recorded so far.
* First entry is the SPACE between transmissions.
*
* As soon as one SPACE entry gets longer than RECORD_GAP_TICKS, state switches to STOP (frame received). Timing of SPACE continues.
* A call of resume() switches from STOP to IDLE.
* As soon as first MARK arrives in IDLE, gap width is recorded and new logging starts.
*
* With digitalRead and Feedback LED
* 15 pushs, 1 in, 1 eor before start of code = 2 us @16MHz + * 7.2 us computation time (6us idle time) + * pop + reti = 2.25 us @16MHz => 10.3 to 11.5 us @16MHz
* With portInputRegister and mask and Feedback LED code commented
* 9 pushs, 1 in, 1 eor before start of code = 1.25 us @16MHz + * 2.25 us computation time + * pop + reti = 1.5 us @16MHz => 5 us @16MHz
* => Minimal CPU frequency is 4 MHz
*
**********************************************************************************************************************/
#if defined(TIMER_INTR_NAME)
ISR (TIMER_INTR_NAME) // for ISR definitions
#else
ISR()
// for functions definitions which are called by separate (board specific) ISR
#endif
{
#if defined(_IR_MEASURE_TIMING) && defined(_IR_TIMING_TEST_PIN)
digitalWriteFast(_IR_TIMING_TEST_PIN, HIGH); // 2 clock cycles
#endif
// 7 - 8.5 us for ISR body (without pushes and pops) for ATmega328 @16MHz
#if defined(TIMER_REQUIRES_RESET_INTR_PENDING)
timerResetInterruptPending(); // reset TickCounterForISR interrupt flag if required (currently only for Teensy and ATmega4809)
#endif
// Read if IR Receiver -> SPACE [xmt LED off] or a MARK [xmt LED on]
#if defined(__AVR__)
uint8_t tIRInputLevel = *irparams.IRReceivePinPortInputRegister & irparams.IRReceivePinMask;
#else
uint_fast8_t tIRInputLevel = (uint_fast8_t) digitalReadFast(irparams.IRReceivePin);
#endif
/*
* Increase TickCounter and clip it at maximum 0xFFFF / 3.2 seconds at 50 us ticks
*/
if (irparams.TickCounterForISR < UINT16_MAX) {
irparams.TickCounterForISR++; // One more 50uS tick
}
/*
* Due to a ESP32 compiler bug https://github.com/espressif/esp-idf/issues/1552 no switch statements are possible for ESP32
* So we change the code to if / else if
*/
// switch (irparams.StateForISR) {
//
if (irparams.StateForISR == IR_REC_STATE_IDLE) {
/*
* Here we are just resumed and maybe in the middle of a transmission
*/
if (tIRInputLevel == INPUT_MARK) {
// check if we did not start in the middle of a transmission by checking the minimum length of leading space
if (irparams.TickCounterForISR > RECORD_GAP_TICKS) {
#if defined(_IR_MEASURE_TIMING) && defined(_IR_TIMING_TEST_PIN)
// digitalWriteFast(_IR_TIMING_TEST_PIN, HIGH); // 2 clock cycles
#endif
/*
* Gap between two transmissions just ended; Record gap duration + start recording transmission
* Initialize all state machine variables
*/
irparams.OverflowFlag = false;
irparams.rawbuf[0] = irparams.TickCounterForISR;
irparams.rawlen = 1;
irparams.StateForISR = IR_REC_STATE_MARK;
} // otherwise stay in idle state
irparams.TickCounterForISR = 0;// reset counter in both cases
}
} else if (irparams.StateForISR == IR_REC_STATE_MARK) { // Timing mark
if (tIRInputLevel != INPUT_MARK) {
/*
* Mark ended here. Record mark time in rawbuf array
*/
#if defined(_IR_MEASURE_TIMING) && defined(_IR_TIMING_TEST_PIN)
// digitalWriteFast(_IR_TIMING_TEST_PIN, HIGH); // 2 clock cycles
#endif
irparams.rawbuf[irparams.rawlen++] = irparams.TickCounterForISR; // record mark
irparams.StateForISR = IR_REC_STATE_SPACE;
irparams.TickCounterForISR = 0;// This resets the tick counter also at end of frame :-)
}
} else if (irparams.StateForISR == IR_REC_STATE_SPACE) { // Timing space
if (tIRInputLevel == INPUT_MARK) {
/*
* Space ended here. Check for overflow and record space time in rawbuf array
*/
if (irparams.rawlen >= RAW_BUFFER_LENGTH) {
// Flag up a read OverflowFlag; Stop the state machine
irparams.OverflowFlag = true;
irparams.StateForISR = IR_REC_STATE_STOP;
#if !IR_REMOTE_DISABLE_RECEIVE_COMPLETE_CALLBACK
/*
* Call callback if registered (not NULL)
*/
if (irparams.ReceiveCompleteCallbackFunction != NULL) {
irparams.ReceiveCompleteCallbackFunction();
}
#endif
} else {
#if defined(_IR_MEASURE_TIMING) && defined(_IR_TIMING_TEST_PIN)
// digitalWriteFast(_IR_TIMING_TEST_PIN, HIGH); // 2 clock cycles
#endif
irparams.rawbuf[irparams.rawlen++] = irparams.TickCounterForISR; // record space
irparams.StateForISR = IR_REC_STATE_MARK;
}
irparams.TickCounterForISR = 0;
} else if (irparams.TickCounterForISR > RECORD_GAP_TICKS) {
/*
* Maximum space duration reached here.
* Current code is ready for processing!
* We received a long space, which indicates gap between codes.
* Switch to IR_REC_STATE_STOP
* Don't reset TickCounterForISR; keep counting width of next leading space
*/
irparams.StateForISR = IR_REC_STATE_STOP;
#if !IR_REMOTE_DISABLE_RECEIVE_COMPLETE_CALLBACK
/*
* Call callback if registered (not NULL)
*/
if (irparams.ReceiveCompleteCallbackFunction != NULL) {
irparams.ReceiveCompleteCallbackFunction();
}
#endif
}
} else if (irparams.StateForISR == IR_REC_STATE_STOP) {
/*
* Complete command received
* stay here until resume() is called, which switches state to IR_REC_STATE_IDLE
*/
#if defined(_IR_MEASURE_TIMING) && defined(_IR_TIMING_TEST_PIN)
// digitalWriteFast(_IR_TIMING_TEST_PIN, HIGH); // 2 clock cycles
#endif
if (tIRInputLevel == INPUT_MARK) {
// Reset gap TickCounterForISR, to prepare for detection if we are in the middle of a transmission after call of resume()
irparams.TickCounterForISR = 0;
}
}
#if !defined(NO_LED_FEEDBACK_CODE)
if (FeedbackLEDControl.LedFeedbackEnabled == LED_FEEDBACK_ENABLED_FOR_RECEIVE) {
setFeedbackLED(tIRInputLevel == INPUT_MARK);
}
#endif
#ifdef _IR_MEASURE_TIMING
digitalWriteFast(_IR_TIMING_TEST_PIN, LOW); // 2 clock cycles
#endif
}
/**********************************************************************************************************************
* Stream like API
**********************************************************************************************************************/
/**
* Initializes the receive and feedback pin
* @param aReceivePin The Arduino pin number, where a demodulating IR receiver is connected.
* @param aEnableLEDFeedback if true / ENABLE_LED_FEEDBACK, then let the feedback led blink on receiving IR signal
* @param aFeedbackLEDPin if 0 / USE_DEFAULT_FEEDBACK_LED_PIN, then take board specific FEEDBACK_LED_ON() and FEEDBACK_LED_OFF() functions
*/
void IRrecv::begin(uint_fast8_t aReceivePin, bool aEnableLEDFeedback, uint_fast8_t aFeedbackLEDPin) {
setReceivePin(aReceivePin);
#if !defined(NO_LED_FEEDBACK_CODE)
bool tEnableLEDFeedback = DO_NOT_ENABLE_LED_FEEDBACK;
if (aEnableLEDFeedback) {
tEnableLEDFeedback = LED_FEEDBACK_ENABLED_FOR_RECEIVE;
}
setLEDFeedback(aFeedbackLEDPin, tEnableLEDFeedback);
#else
(void) aEnableLEDFeedback;
(void) aFeedbackLEDPin;
#endif
#if defined(_IR_MEASURE_TIMING) && defined(_IR_TIMING_TEST_PIN)
pinModeFast(_IR_TIMING_TEST_PIN, OUTPUT);
#endif
start();
}
/**
* Sets / changes the receiver pin number
*/
void IRrecv::setReceivePin(uint_fast8_t aReceivePinNumber) {
irparams.IRReceivePin = aReceivePinNumber;
#if defined(__AVR__)
irparams.IRReceivePinMask = digitalPinToBitMask(aReceivePinNumber);
irparams.IRReceivePinPortInputRegister = portInputRegister(digitalPinToPort(aReceivePinNumber));
#endif
// Set pin mode once. pinModeFast makes no difference here :-(
pinMode(aReceivePinNumber, INPUT); // Seems to be at least required by ESP32
}
/**
* Sets the function to call if a protocol message has arrived
*/
void IRrecv::registerReceiveCompleteCallback(void (*aReceiveCompleteCallbackFunction)(void)) {
irparams.ReceiveCompleteCallbackFunction = aReceiveCompleteCallbackFunction;
}
/**
* Start the receiving process.
* This configures the timer and the state machine for IR reception
* and enables the receive sample timer interrupt which consumes a small amount of CPU every 50 us.
*/
void IRrecv::start() {
// Setup for cyclic 50 us interrupt
timerConfigForReceive(); // no interrupts enabled here!
// Initialize state machine state
resume();
// Timer interrupt is enabled after state machine reset
timerEnableReceiveInterrupt(); // Enables the receive sample timer interrupt which consumes a small amount of CPU every 50 us.
#ifdef _IR_MEASURE_TIMING
pinModeFast(_IR_TIMING_TEST_PIN, OUTPUT);
#endif
}
/**
* Alias for start().
*/
void IRrecv::enableIRIn() {
start();
}
/**
* Configures the timer and the state machine for IR reception.
* The tick counter value is already at 100 when decode() gets true, because of the 5000 us minimal gap defined in RECORD_GAP_MICROS.
* @param aMicrosecondsToAddToGapCounter To compensate for the amount of microseconds the timer was stopped / disabled.
*/
void IRrecv::start(uint32_t aMicrosecondsToAddToGapCounter) {
irparams.TickCounterForISR += aMicrosecondsToAddToGapCounter / MICROS_PER_TICK;
start();
}
void IRrecv::startWithTicksToAdd(uint16_t aTicksToAddToGapCounter) {
irparams.TickCounterForISR += aTicksToAddToGapCounter;
start();
}
/**
* Restarts receiver after send. Is a NOP if sending does not require a timer.
*/
void IRrecv::restartAfterSend() {
#if defined(SEND_PWM_BY_TIMER) && !defined(SEND_PWM_DOES_NOT_USE_RECEIVE_TIMER)
start();
#endif
}
/**
* Disables the timer for IR reception.
*/
void IRrecv::stop() {
timerDisableReceiveInterrupt();
}
/**
* Alias for stop().
*/
void IRrecv::disableIRIn() {
stop();
}
/**
* Alias for stop().
*/
void IRrecv::end() {
stop();
}
/**
* Returns status of reception
* @return true if no reception is on-going.
*/
bool IRrecv::isIdle() {
return (irparams.StateForISR == IR_REC_STATE_IDLE || irparams.StateForISR == IR_REC_STATE_STOP) ? true : false;
}
/**
* Restart the ISR (Interrupt Service Routine) state machine, to enable receiving of the next IR frame
*/
void IRrecv::resume() {
// check allows to call resume at arbitrary places or more than once
if (irparams.StateForISR == IR_REC_STATE_STOP) {
irparams.StateForISR = IR_REC_STATE_IDLE;
}
}
/**
* Is internally called by decode before calling decoders.
* Must be used to setup data, if you call decoders manually.
*/
void IRrecv::initDecodedIRData() {
if (irparams.OverflowFlag) {
decodedIRData.flags = IRDATA_FLAGS_WAS_OVERFLOW;
#if defined(LOCAL_DEBUG)
Serial.print(F("Overflow happened, try to increase the \"RAW_BUFFER_LENGTH\" value of "));
Serial.print(RAW_BUFFER_LENGTH);
Serial.println(F(" with #define RAW_BUFFER_LENGTH=<biggerValue>"));
#endif
} else {
decodedIRData.flags = IRDATA_FLAGS_EMPTY;
// save last protocol, command and address for repeat handling (where they are compared or copied back :-))
lastDecodedProtocol = decodedIRData.protocol; // repeat patterns can be equal between protocols (e.g. NEC and LG), so we must keep the original one
lastDecodedCommand = decodedIRData.command;
lastDecodedAddress = decodedIRData.address;
}
decodedIRData.protocol = UNKNOWN;
decodedIRData.command = 0;
decodedIRData.address = 0;
decodedIRData.decodedRawData = 0;
decodedIRData.numberOfBits = 0;
}
/**
* Returns true if IR receiver data is available.
*/
bool IRrecv::available() {
return (irparams.StateForISR == IR_REC_STATE_STOP);
}
/**
* If IR receiver data is available, returns pointer to IrReceiver.decodedIRData, else NULL.
*/
IRData* IRrecv::read() {
if (irparams.StateForISR != IR_REC_STATE_STOP) {
return NULL;
}
if (decode()) {
return &decodedIRData;
} else {
return NULL;
}
}
/**
* The main decode function, attempts to decode the recently receive IR signal.
* The set of decoders used is determined by active definitions of the DECODE_<PROTOCOL> macros.
* Results of decoding are stored in IrReceiver.decodedIRData.* like e.g. IrReceiver.decodedIRData.command.
* @return false if no IR receiver data available, true if data available.
*/
bool IRrecv::decode() {
if (irparams.StateForISR != IR_REC_STATE_STOP) {
return false;
}
initDecodedIRData(); // sets IRDATA_FLAGS_WAS_OVERFLOW
if (decodedIRData.flags & IRDATA_FLAGS_WAS_OVERFLOW) {
/*
* Set OverflowFlag flag and return true here, to let the loop call resume or print raw data.
*/
decodedIRData.protocol = UNKNOWN;
return true;
}
#if defined(DECODE_NEC)
IR_TRACE_PRINTLN(F("Attempting NEC decode"));
if (decodeNEC()) {
return true;
}
#endif
#if defined(DECODE_PANASONIC) || defined(DECODE_KASEIKYO)
IR_TRACE_PRINTLN(F("Attempting Panasonic/Kaseikyo decode"));
if (decodeKaseikyo()) {
return true;
}
#endif
#if defined(DECODE_DENON)
IR_TRACE_PRINTLN(F("Attempting Denon/Sharp decode"));
if (decodeDenon()) {
return true;
}
#endif
#if defined(DECODE_SONY)
IR_TRACE_PRINTLN(F("Attempting Sony decode"));
if (decodeSony()) {
return true;
}
#endif
#if defined(DECODE_RC5)
IR_TRACE_PRINTLN(F("Attempting RC5 decode"));
if (decodeRC5()) {
return true;
}
#endif
#if defined(DECODE_RC6)
IR_TRACE_PRINTLN(F("Attempting RC6 decode"));
if (decodeRC6()) {
return true;
}
#endif
#if defined(DECODE_LG)
IR_TRACE_PRINTLN(F("Attempting LG decode"));
if (decodeLG()) {
return true;
}
#endif
#if defined(DECODE_JVC)
IR_TRACE_PRINTLN(F("Attempting JVC decode"));
if (decodeJVC()) {
return true;
}
#endif
#if defined(DECODE_SAMSUNG)
IR_TRACE_PRINTLN(F("Attempting Samsung decode"));
if (decodeSamsung()) {
return true;
}
#endif
/*
* Start of the exotic protocols
*/
#if defined(DECODE_BEO)
IR_TRACE_PRINTLN(F("Attempting Bang & Olufsen decode"));
if (decodeBangOlufsen()) {
return true;
}
#endif
#if defined(DECODE_FAST)
IR_TRACE_PRINTLN(F("Attempting FAST decode"));
if (decodeFAST()) {
return true;
}
#endif
#if defined(DECODE_WHYNTER)
IR_TRACE_PRINTLN(F("Attempting Whynter decode"));
if (decodeWhynter()) {
return true;
}
#endif
#if defined(DECODE_LEGO_PF)
IR_TRACE_PRINTLN(F("Attempting Lego Power Functions"));
if (decodeLegoPowerFunctions()) {
return true;
}
#endif
#if defined(DECODE_BOSEWAVE)
IR_TRACE_PRINTLN(F("Attempting Bosewave decode"));
if (decodeBoseWave()) {
return true;
}
#endif
#if defined(DECODE_MAGIQUEST)
IR_TRACE_PRINTLN(F("Attempting MagiQuest decode"));
if (decodeMagiQuest()) {
return true;
}
#endif
/*
* Try the universal decoder for pulse distance protocols
*/
#if defined(DECODE_DISTANCE_WIDTH)
IR_TRACE_PRINTLN(F("Attempting universal Distance Width decode"));
if (decodeDistanceWidth()) {
return true;
}
#endif
/*
* Last resort is the universal hash decode which always return true
*/
#if defined(DECODE_HASH)
IR_TRACE_PRINTLN(F("Hash decode"));
// decodeHash returns a hash on any input.
// Thus, it needs to be last in the list.
// If you add any decodes, add them before this.
if (decodeHash()) {
return true;
}
#endif
/*
* Return true here, to let the loop decide to call resume or to print raw data.
*/
return true;
}
/**********************************************************************************************************************
* Common decode functions
**********************************************************************************************************************/
/**
* Decode pulse distance width protocols.
*
* We can have the following protocol timings
* Pulse distance: Pulses/marks are constant, pause/spaces have different length, like NEC.
* Pulse width: Pulses/marks have different length, pause/spaces are constant, like Sony.
* Pulse distance width: Pulses/marks and pause/spaces have different length, often the bit length is constant, like MagiQuest.
* Pulse distance width can be decoded like pulse width decoder, if this decoder does not check the length of pause/spaces.
*
* Input is IrReceiver.decodedIRData.rawDataPtr->rawbuf[]
* Output is IrReceiver.decodedIRData.decodedRawData
*
* Assume pulse distance if aOneMarkMicros == aZeroMarkMicros
*
* @param aNumberOfBits Number of bits to decode from decodedIRData.rawDataPtr->rawbuf[] array.
* @param aStartOffset Offset in decodedIRData.rawDataPtr->rawbuf[] to start decoding. Must point to a mark.
* @param aOneMarkMicros Taken as constant BitMarkMicros for pulse distance.
* @param aZeroMarkMicros Not required if DECODE_STRICT_CHECKS is not defined.
* @param aOneSpaceMicros Taken as (constant) BitSpaceMicros for pulse width.
* @param aZeroSpaceMicros Not required if DECODE_STRICT_CHECKS is not defined.
* @param aMSBfirst If true send Most Significant Bit first, else send Least Significant Bit (lowest bit) first.
* @return true If decoding was successful
*/
bool IRrecv::decodePulseDistanceWidthData(uint_fast8_t aNumberOfBits, uint_fast8_t aStartOffset, uint16_t aOneMarkMicros,
uint16_t aZeroMarkMicros, uint16_t aOneSpaceMicros, uint16_t aZeroSpaceMicros, bool aMSBfirst) {
auto *tRawBufPointer = &decodedIRData.rawDataPtr->rawbuf[aStartOffset];
bool isPulseDistanceProtocol = (aOneMarkMicros == aZeroMarkMicros); // If true, we have a constant mark -> pulse distance protocol
IRRawDataType tDecodedData = 0; // For MSB first tDecodedData is shifted left each loop
IRRawDataType tMask = 1UL; // Mask is only used for LSB first
for (uint_fast8_t i = aNumberOfBits; i > 0; i--) {
// get one mark and space pair
unsigned int tMarkTicks;
unsigned int tSpaceTicks;
if (isPulseDistanceProtocol) {
/*
* Pulse distance here, it is not required to check constant mark duration (aOneMarkMicros) and zero space duration.
*/
#if defined DECODE_STRICT_CHECKS
tMarkTicks = *tRawBufPointer++;
#else
(void) aZeroSpaceMicros;
tRawBufPointer++;
#endif
tSpaceTicks = *tRawBufPointer++; // maybe buffer overflow for last bit, but we do not evaluate this value :-)
#if defined DECODE_STRICT_CHECKS
// Check for constant length mark
if (!matchMark(tMarkTicks, aOneMarkMicros)) {
# if defined(LOCAL_DEBUG)
Serial.print(F("Mark="));
Serial.print(tMarkTicks * MICROS_PER_TICK);
Serial.print(F(" is not "));
Serial.print(aOneMarkMicros);
Serial.print(F(". Index="));
Serial.print(aNumberOfBits - i);
Serial.print(' ');
# endif
return false;
}
#endif
} else {
/*
* Pulse width here, it is not required to check (constant) space duration and zero mark duration.
*/
tMarkTicks = *tRawBufPointer++;
#if defined DECODE_STRICT_CHECKS
tSpaceTicks = *tRawBufPointer++; // maybe buffer overflow for last bit, but we do not evaluate this value :-)
#else
(void) aZeroMarkMicros;
(void) aZeroSpaceMicros;
tRawBufPointer++;
#endif
}
if (aMSBfirst) {
tDecodedData <<= 1;
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
bool tBitValue;
if (isPulseDistanceProtocol) {
// Check for variable length space indicating a 1 or 0
tBitValue = matchSpace(tSpaceTicks, aOneSpaceMicros); // tSpaceTicks is initialized here, even if some compiler are complaining!
} else {
// Check for variable length mark indicating a 1 or 0
tBitValue = matchMark(tMarkTicks, aOneMarkMicros); // tMarkTicks is initialized here, even if some compiler are complaining!
}
#pragma GCC diagnostic pop
if (tBitValue) {
// It's a 1 -> set the bit
if (aMSBfirst) {
tDecodedData |= 1;
} else {
tDecodedData |= tMask;
}
IR_TRACE_PRINTLN('1');
} else {
#if defined DECODE_STRICT_CHECKS
/*
* Additionally check length of length parameter which determine a zero
*/
if (isPulseDistanceProtocol) {
if (!matchSpace(tSpaceTicks, aZeroSpaceMicros)) {
# if defined(LOCAL_DEBUG)
Serial.print(F("Space="));
Serial.print(tSpaceTicks * MICROS_PER_TICK);
Serial.print(F(" is not "));
Serial.print(aOneSpaceMicros);
Serial.print(F(" or "));
Serial.print(aZeroSpaceMicros);
Serial.print(F(". Index="));
Serial.print(aNumberOfBits - i);
Serial.print(' ');
# endif
return false;
}
} else {
if (!matchMark(tMarkTicks, aZeroMarkMicros)) {
# if defined(LOCAL_DEBUG)
Serial.print(F("Mark="));
Serial.print(tMarkTicks * MICROS_PER_TICK);
Serial.print(F(" is not "));
Serial.print(aOneMarkMicros);
Serial.print(F(" or "));
Serial.print(aZeroMarkMicros);
Serial.print(F(". Index="));
Serial.print(aNumberOfBits - i);
Serial.print(' ');
# endif
return false;
}
}
#endif
// do not set the bit
IR_TRACE_PRINTLN('0');
}
#if defined DECODE_STRICT_CHECKS
// If we have no stop bit, assume that last space, which is not recorded, is correct, since we can not check it
if (aZeroSpaceMicros == aOneSpaceMicros
&& tRawBufPointer < &decodedIRData.rawDataPtr->rawbuf[decodedIRData.rawDataPtr->rawlen]) {
// Check for constant length space (of pulse width protocol) here
if (!matchSpace(tSpaceTicks, aOneSpaceMicros)) {
# if defined(LOCAL_DEBUG)
Serial.print(F("Space="));
Serial.print(tSpaceTicks * MICROS_PER_TICK);
Serial.print(F(" is not "));
Serial.print(aOneSpaceMicros);
Serial.print(F(". Index="));
Serial.print(aNumberOfBits - i);
Serial.print(' ');
# endif
return false;
}
}
#endif
tMask <<= 1;
}
decodedIRData.decodedRawData = tDecodedData;
return true;
}
/**
* Decode pulse distance protocols for PulseDistanceWidthProtocolConstants.
* @return true if decoding was successful
*/
bool IRrecv::decodePulseDistanceWidthData(PulseDistanceWidthProtocolConstants *aProtocolConstants, uint_fast8_t aNumberOfBits,
uint_fast8_t aStartOffset) {
return decodePulseDistanceWidthData(aNumberOfBits, aStartOffset, aProtocolConstants->DistanceWidthTimingInfo.OneMarkMicros,
aProtocolConstants->DistanceWidthTimingInfo.ZeroMarkMicros, aProtocolConstants->DistanceWidthTimingInfo.OneSpaceMicros,
aProtocolConstants->DistanceWidthTimingInfo.ZeroSpaceMicros, aProtocolConstants->Flags);
}
/*
* Static variables for the getBiphaselevel function
*/
uint_fast8_t sBiphaseDecodeRawbuffOffset; // Index into raw timing array
uint16_t sBiphaseCurrentTimingIntervals; // 1, 2 or 3. Number of aBiphaseTimeUnit intervals of the current rawbuf[sBiphaseDecodeRawbuffOffset] timing.
uint_fast8_t sBiphaseUsedTimingIntervals; // Number of already used intervals of sCurrentTimingIntervals.
uint16_t sBiphaseTimeUnit;
void IRrecv::initBiphaselevel(uint_fast8_t aRCDecodeRawbuffOffset, uint16_t aBiphaseTimeUnit) {
sBiphaseDecodeRawbuffOffset = aRCDecodeRawbuffOffset;
sBiphaseTimeUnit = aBiphaseTimeUnit;
sBiphaseUsedTimingIntervals = 0;
}
/**
* Gets the level of one time interval (aBiphaseTimeUnit) at a time from the raw buffer.
* The RC5/6 decoding is easier if the data is broken into time intervals.
* E.g. if the buffer has mark for 2 time intervals and space for 1,
* successive calls to getBiphaselevel will return 1, 1, 0.
*
* _ _ _ _ _ _ _ _ _ _ _ _ _
* _____| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |
* ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ Significant clock edge
* _ _ _ ___ _ ___ ___ _ - Mark
* Data _____| |___| |_| |_| |_| |___| |___| |_| | - Data starts with a mark->space bit
* 1 0 0 0 1 1 0 1 0 1 1 - Space
* A mark to space at a significant clock edge results in a 1
* A space to mark at a significant clock edge results in a 0 (for RC6)
* Returns current level [MARK or SPACE] or -1 for error (measured time interval is not a multiple of sBiphaseTimeUnit).
*/
uint_fast8_t IRrecv::getBiphaselevel() {
uint_fast8_t tLevelOfCurrentInterval; // 0 (SPACE) or 1 (MARK)
if (sBiphaseDecodeRawbuffOffset >= decodedIRData.rawDataPtr->rawlen) {
return SPACE; // After end of recorded buffer, assume space.
}
tLevelOfCurrentInterval = (sBiphaseDecodeRawbuffOffset) & 1; // on odd rawbuf offsets we have mark timings
/*
* Setup data if sUsedTimingIntervals is 0
*/
if (sBiphaseUsedTimingIntervals == 0) {
uint16_t tCurrentTimingWith = decodedIRData.rawDataPtr->rawbuf[sBiphaseDecodeRawbuffOffset];
uint16_t tMarkExcessCorrection = (tLevelOfCurrentInterval == MARK) ? MARK_EXCESS_MICROS : -MARK_EXCESS_MICROS;
if (matchTicks(tCurrentTimingWith, sBiphaseTimeUnit + tMarkExcessCorrection)) {
sBiphaseCurrentTimingIntervals = 1;
} else if (matchTicks(tCurrentTimingWith, (2 * sBiphaseTimeUnit) + tMarkExcessCorrection)) {
sBiphaseCurrentTimingIntervals = 2;
} else if (matchTicks(tCurrentTimingWith, (3 * sBiphaseTimeUnit) + tMarkExcessCorrection)) {
sBiphaseCurrentTimingIntervals = 3;
} else {
return -1;
}
}
// We use another interval from tCurrentTimingIntervals
sBiphaseUsedTimingIntervals++;
// keep track of current timing offset
if (sBiphaseUsedTimingIntervals >= sBiphaseCurrentTimingIntervals) {
// we have used all intervals of current timing, switch to next timing value
sBiphaseUsedTimingIntervals = 0;
sBiphaseDecodeRawbuffOffset++;
}
IR_TRACE_PRINTLN(tLevelOfCurrentInterval);
return tLevelOfCurrentInterval;
}
#if defined(DECODE_HASH)
/**********************************************************************************************************************
* Internal Hash decode function
**********************************************************************************************************************/
/**
* Compare two (tick) values for Hash decoder
* Use a tolerance of 20% to enable e.g. 500 and 600 (NEC timing) to be equal
* @return 0 if newval is shorter, 1 if newval is equal, and 2 if newval is longer
*/
uint_fast8_t IRrecv::compare(uint16_t oldval, uint16_t newval) {
if (newval * 10 < oldval * 8) {
return 0;
}
if (oldval * 10 < newval * 8) {
return 2;
}
return 1;
}
#define FNV_PRIME_32 16777619 ///< used for decodeHash()
#define FNV_BASIS_32 2166136261 ///< used for decodeHash()
/**
* decodeHash - decode an arbitrary IR code.
* Instead of decoding using a standard encoding scheme
* (e.g. Sony, NEC, RC5), the code is hashed to a 32-bit value.
*
* The algorithm: look at the sequence of MARK signals, and see if each one
* is shorter (0), the same length (1), or longer (2) than the previous.
* Do the same with the SPACE signals. Hash the resulting sequence of 0's,
* 1's, and 2's to a 32-bit value. This will give a unique value for each
* different code (probably), for most code systems.
*
* Use FNV hash algorithm: http://isthe.com/chongo/tech/comp/fnv/#FNV-param
* Converts the raw code values into a 32-bit hash code.
* Hopefully this code is unique for each button.
* This isn't a "real" decoding, just an arbitrary value.
*
* see: http://www.righto.com/2010/01/using-arbitrary-remotes-with-arduino.html
*/
bool IRrecv::decodeHash() {
unsigned long hash = FNV_BASIS_32; // the result is the same no matter if we use a long or unsigned long variable
// Require at least 6 samples to prevent triggering on noise
if (decodedIRData.rawDataPtr->rawlen < 6) {
return false;
}
#if RAW_BUFFER_LENGTH <= 254 // saves around 75 bytes program memory and speeds up ISR
uint_fast8_t i;
#else
unsigned int i;
#endif
for (i = 1; (i + 2) < decodedIRData.rawDataPtr->rawlen; i++) {
uint_fast8_t value = compare(decodedIRData.rawDataPtr->rawbuf[i], decodedIRData.rawDataPtr->rawbuf[i + 2]);
// Add value into the hash
hash = (hash * FNV_PRIME_32) ^ value;
}
decodedIRData.decodedRawData = hash;
decodedIRData.numberOfBits = 32;
decodedIRData.protocol = UNKNOWN;
return true;
}
bool IRrecv::decodeHashOld(decode_results *aResults) {
unsigned long hash = FNV_BASIS_32;
// Require at least 6 samples to prevent triggering on noise
if (aResults->rawlen < 6) {
return false;
}
for (uint8_t i = 3; i < aResults->rawlen; i++) {
uint_fast8_t value = compare(aResults->rawbuf[i - 2], aResults->rawbuf[i]);
// Add value into the hash
hash = (hash * FNV_PRIME_32) ^ value;
}
aResults->value = hash;
aResults->bits = 32;
aResults->decode_type = UNKNOWN;
decodedIRData.protocol = UNKNOWN;
return true;
}
#endif // DECODE_HASH
/**********************************************************************************************************************
* Match functions
**********************************************************************************************************************/
/*
* returns true if values do match
*/
bool IRrecv::checkHeader(PulseDistanceWidthProtocolConstants *aProtocolConstants) {
// Check header "mark" and "space"
if (!matchMark(decodedIRData.rawDataPtr->rawbuf[1], aProtocolConstants->DistanceWidthTimingInfo.HeaderMarkMicros)) {
#if defined(LOCAL_TRACE)
Serial.print(::getProtocolString(aProtocolConstants->ProtocolIndex));
Serial.println(F(": Header mark length is wrong"));
#endif
return false;
}
if (!matchSpace(decodedIRData.rawDataPtr->rawbuf[2], aProtocolConstants->DistanceWidthTimingInfo.HeaderSpaceMicros)) {
#if defined(LOCAL_TRACE)
Serial.print(::getProtocolString(aProtocolConstants->ProtocolIndex));
Serial.println(F(": Header space length is wrong"));
#endif
return false;
}
return true;
}
/*
* Do not check for same address and command, because it is almost not possible to press 2 different buttons on the remote within around 100 ms.
* And if really required, it can be enabled here, or done manually in user program.
* And we have still no RC6 toggle bit check for detecting a second press on the same button.
*/
void IRrecv::checkForRepeatSpaceTicksAndSetFlag(uint16_t aMaximumRepeatSpaceTicks) {
if (decodedIRData.rawDataPtr->rawbuf[0] < aMaximumRepeatSpaceTicks
#if defined(ENABLE_FULL_REPEAT_CHECK)
&& decodedIRData.address == lastDecodedAddress && decodedIRData.command == lastDecodedCommand /* requires around 85 bytes program space */
#endif
) {
decodedIRData.flags |= IRDATA_FLAGS_IS_REPEAT;
}
}
/**
* Match function without compensating for marks exceeded or spaces shortened by demodulator hardware
* Currently not used
*/
bool matchTicks(uint16_t aMeasuredTicks, uint16_t aMatchValueMicros) {
#if defined(LOCAL_TRACE)
Serial.print(F("Testing: "));
Serial.print(TICKS_LOW(aMatchValueMicros), DEC);
Serial.print(F(" <= "));
Serial.print(aMeasuredTicks, DEC);
Serial.print(F(" <= "));
Serial.print(TICKS_HIGH(aMatchValueMicros), DEC);
#endif
bool passed = ((aMeasuredTicks >= TICKS_LOW(aMatchValueMicros)) && (aMeasuredTicks <= TICKS_HIGH(aMatchValueMicros)));
#if defined(LOCAL_TRACE)
if (passed) {
Serial.println(F(" => passed"));
} else {
Serial.println(F(" => FAILED"));
}
#endif
return passed;
}
bool MATCH(uint16_t measured_ticks, uint16_t desired_us) {
return matchTicks(measured_ticks, desired_us);
}
/**
* Compensate for marks exceeded by demodulator hardware
*/
bool matchMark(uint16_t aMeasuredTicks, uint16_t aMatchValueMicros) {
#if defined(LOCAL_TRACE)
Serial.print(F("Testing mark (actual vs desired): "));
Serial.print(aMeasuredTicks * MICROS_PER_TICK, DEC);
Serial.print(F("us vs "));
Serial.print(aMatchValueMicros, DEC);
Serial.print(F("us: "));
Serial.print(TICKS_LOW(aMatchValueMicros + MARK_EXCESS_MICROS) * MICROS_PER_TICK, DEC);
Serial.print(F(" <= "));
Serial.print(aMeasuredTicks * MICROS_PER_TICK, DEC);
Serial.print(F(" <= "));
Serial.print(TICKS_HIGH(aMatchValueMicros + MARK_EXCESS_MICROS) * MICROS_PER_TICK, DEC);
#endif
// compensate for marks exceeded by demodulator hardware
bool passed = ((aMeasuredTicks >= TICKS_LOW(aMatchValueMicros + MARK_EXCESS_MICROS))
&& (aMeasuredTicks <= TICKS_HIGH(aMatchValueMicros + MARK_EXCESS_MICROS)));
#if defined(LOCAL_TRACE)
if (passed) {
Serial.println(F(" => passed"));
} else {
Serial.println(F(" => FAILED"));
}
#endif
return passed;
}
bool MATCH_MARK(uint16_t measured_ticks, uint16_t desired_us) {
return matchMark(measured_ticks, desired_us);
}
/**
* Compensate for spaces shortened by demodulator hardware
*/
bool matchSpace(uint16_t aMeasuredTicks, uint16_t aMatchValueMicros) {
#if defined(LOCAL_TRACE)
Serial.print(F("Testing space (actual vs desired): "));
Serial.print(aMeasuredTicks * MICROS_PER_TICK, DEC);
Serial.print(F("us vs "));
Serial.print(aMatchValueMicros, DEC);
Serial.print(F("us: "));
Serial.print(TICKS_LOW(aMatchValueMicros - MARK_EXCESS_MICROS) * MICROS_PER_TICK, DEC);
Serial.print(F(" <= "));
Serial.print(aMeasuredTicks * MICROS_PER_TICK, DEC);
Serial.print(F(" <= "));
Serial.print(TICKS_HIGH(aMatchValueMicros - MARK_EXCESS_MICROS) * MICROS_PER_TICK, DEC);
#endif
// compensate for spaces shortened by demodulator hardware
bool passed = ((aMeasuredTicks >= TICKS_LOW(aMatchValueMicros - MARK_EXCESS_MICROS))
&& (aMeasuredTicks <= TICKS_HIGH(aMatchValueMicros - MARK_EXCESS_MICROS)));
#if defined(LOCAL_TRACE)
if (passed) {
Serial.println(F(" => passed"));
} else {
Serial.println(F(" => FAILED"));
}
#endif
return passed;
}
bool MATCH_SPACE(uint16_t measured_ticks, uint16_t desired_us) {
return matchSpace(measured_ticks, desired_us);
}
/**
* Getter function for MARK_EXCESS_MICROS
*/
int getMarkExcessMicros() {
return MARK_EXCESS_MICROS;
}
/*
* Check if protocol is not detected and detected space between two transmissions
* is smaller than known value for protocols (Sony with around 24 ms)
* @return true, if CheckForRecordGapsMicros() has printed a message, i.e. gap < 15ms (RECORD_GAP_MICROS_WARNING_THRESHOLD)
*/
bool IRrecv::checkForRecordGapsMicros(Print *aSerial) {
/*
* Check if protocol is not detected and detected space between two transmissions
* is smaller than known value for protocols (Sony with around 24 ms)
*/
if (decodedIRData.protocol <= PULSE_DISTANCE
&& decodedIRData.rawDataPtr->rawbuf[0] < (RECORD_GAP_MICROS_WARNING_THRESHOLD / MICROS_PER_TICK)) {
aSerial->println();
aSerial->print(F("Space of "));
aSerial->print(decodedIRData.rawDataPtr->rawbuf[0] * MICROS_PER_TICK);
aSerial->print(F(" us between two detected transmission is smaller than the minimal gap of "));
aSerial->print(RECORD_GAP_MICROS_WARNING_THRESHOLD);
aSerial->println(F(" us known for implemented protocols like NEC, Sony, RC% etc.."));
aSerial->println(F("But it can be OK for some yet unsupported protocols, and especially for repeats."));
aSerial->println(F("If you get unexpected results, try to increase the RECORD_GAP_MICROS in IRremote.h."));
aSerial->println();
return true;
}
return false;
}
/**********************************************************************************************************************
* Print functions
* Since a library should not allocate the "Serial" object, all functions require a pointer to a Print object.
**********************************************************************************************************************/
void IRrecv::printActiveIRProtocols(Print *aSerial) {
// call no class function with same name
::printActiveIRProtocols(aSerial);
}
void printActiveIRProtocols(Print *aSerial) {
#if defined(DECODE_NEC)
aSerial->print(F("NEC/NEC2/Onkyo/Apple, "));
#endif
#if defined(DECODE_PANASONIC) || defined(DECODE_KASEIKYO)
aSerial->print(F("Panasonic/Kaseikyo, "));
#endif
#if defined(DECODE_DENON)
aSerial->print(F("Denon/Sharp, "));
#endif
#if defined(DECODE_SONY)
aSerial->print(F("Sony, "));
#endif
#if defined(DECODE_RC5)
aSerial->print(F("RC5, "));
#endif
#if defined(DECODE_RC6)
aSerial->print(F("RC6, "));
#endif
#if defined(DECODE_LG)
aSerial->print(F("LG, "));
#endif
#if defined(DECODE_JVC)
aSerial->print(F("JVC, "));
#endif
#if defined(DECODE_SAMSUNG)
aSerial->print(F("Samsung, "));
#endif
/*
* Start of the exotic protocols
*/
#if defined(DECODE_BEO)
aSerial->print(F("Bang & Olufsen, "));
#endif
#if defined(DECODE_FAST)
aSerial->print(F("FAST, "));
#endif
#if defined(DECODE_WHYNTER)
aSerial->print(F("Whynter, "));
#endif
#if defined(DECODE_LEGO_PF)
aSerial->print(F("Lego Power Functions, "));
#endif
#if defined(DECODE_BOSEWAVE)
aSerial->print(F("Bosewave , "));
#endif
#if defined(DECODE_MAGIQUEST)
aSerial->print(F("MagiQuest, "));
#endif
#if defined(DECODE_DISTANCE_WIDTH)
aSerial->print(F("Universal Pulse Distance Width, "));
#endif
#if defined(DECODE_HASH)
aSerial->print(F("Hash "));
#endif
#if defined(NO_DECODER) // for sending raw only
(void)aSerial; // to avoid compiler warnings
#endif
}
/**
* Function to print values and flags of IrReceiver.decodedIRData in one line.
* Ends with println().
*
* @param aSerial The Print object on which to write, for Arduino you can use &Serial.
* @param aPrintRepeatGap If true also print the gap before repeats.
* @param aCheckForRecordGapsMicros If true, call CheckForRecordGapsMicros() which may do a long printout,
* which in turn may block the proper detection of repeats.*
* @return true, if CheckForRecordGapsMicros() has printed a message, i.e. gap < 15ms (RECORD_GAP_MICROS_WARNING_THRESHOLD).
*/
bool IRrecv::printIRResultShort(Print *aSerial, bool aPrintRepeatGap, bool aCheckForRecordGapsMicros) {
// call no class function with same name
::printIRResultShort(aSerial, &decodedIRData, aPrintRepeatGap);
if (aCheckForRecordGapsMicros && decodedIRData.protocol != UNKNOWN) {
return checkForRecordGapsMicros(aSerial);
}
return false;
}
void IRrecv::printDistanceWidthTimingInfo(Print *aSerial, DistanceWidthTimingInfoStruct *aDistanceWidthTimingInfo) {
aSerial->print(aDistanceWidthTimingInfo->HeaderMarkMicros);
aSerial->print(F(", "));
aSerial->print(aDistanceWidthTimingInfo->HeaderSpaceMicros);
aSerial->print(F(", "));
aSerial->print(aDistanceWidthTimingInfo->OneMarkMicros);
aSerial->print(F(", "));
aSerial->print(aDistanceWidthTimingInfo->OneSpaceMicros);
aSerial->print(F(", "));
aSerial->print(aDistanceWidthTimingInfo->ZeroMarkMicros);
aSerial->print(F(", "));
aSerial->print(aDistanceWidthTimingInfo->ZeroSpaceMicros);
}
uint32_t IRrecv::getTotalDurationOfRawData() {
uint16_t tSumOfDurationTicks = 0;
for (uint_fast8_t i = 1; i < decodedIRData.rawDataPtr->rawlen; i++) {
tSumOfDurationTicks += decodedIRData.rawDataPtr->rawbuf[i];
}
return tSumOfDurationTicks * (uint32_t) MICROS_PER_TICK;
}
/**
* Function to print values and flags of IrReceiver.decodedIRData in one line.
* Ends with println().
*
* @param aSerial The Print object on which to write, for Arduino you can use &Serial.
*/
void IRrecv::printIRSendUsage(Print *aSerial) {
if (decodedIRData.protocol != UNKNOWN
&& (decodedIRData.flags & (IRDATA_FLAGS_IS_AUTO_REPEAT | IRDATA_FLAGS_IS_REPEAT)) == 0x00) {
#if defined(DECODE_DISTANCE_WIDTH)
aSerial->print(F("Send with:"));
uint_fast8_t tNumberOfArrayData = 0;
if (decodedIRData.protocol == PULSE_DISTANCE || decodedIRData.protocol == PULSE_WIDTH) {
# if __INT_WIDTH__ < 32
tNumberOfArrayData = ((decodedIRData.numberOfBits - 1) / 32) + 1;
if(tNumberOfArrayData > 1) {
aSerial->println();
aSerial->print(F(" uint32_t tRawData[]={0x"));
# else
tNumberOfArrayData = ((decodedIRData.numberOfBits - 1) / 64) + 1;
if(tNumberOfArrayData > 1) {
aSerial->println();
aSerial->print(F(" uint64_t tRawData[]={0x"));
# endif
for (uint_fast8_t i = 0; i < tNumberOfArrayData; ++i) {
# if (__INT_WIDTH__ < 32)
aSerial->print(decodedIRData.decodedRawDataArray[i], HEX);
# else
PrintULL::print(aSerial, decodedIRData.decodedRawDataArray[i], HEX);
# endif
if (i != tNumberOfArrayData - 1) {
aSerial->print(F(", 0x"));
}
}
aSerial->println(F("};"));
aSerial->print(F(" "));
}
}
aSerial->print(F(" IrSender.send"));
#else
aSerial->print(F("Send with: IrSender.send"));
#endif
#if defined(DECODE_DISTANCE_WIDTH)
if (decodedIRData.protocol != PULSE_DISTANCE && decodedIRData.protocol != PULSE_WIDTH) {
#endif
aSerial->print(getProtocolString());
aSerial->print(F("(0x"));
#if defined(DECODE_MAGIQUEST)
if (decodedIRData.protocol == MAGIQUEST) {
# if (__INT_WIDTH__ < 32)
aSerial->print(decodedIRData.decodedRawData, HEX);
# else
PrintULL::print(aSerial, decodedIRData.decodedRawData, HEX);
# endif
} else {
aSerial->print(decodedIRData.address, HEX);
}
#else
/*
* New decoders have address and command
*/
aSerial->print(decodedIRData.address, HEX);
#endif
aSerial->print(F(", 0x"));
aSerial->print(decodedIRData.command, HEX);
if (decodedIRData.protocol == SONY) {
aSerial->print(F(", 2, "));
aSerial->print(decodedIRData.numberOfBits);
} else {
aSerial->print(F(", <numberOfRepeats>"));
}
#if defined(DECODE_DISTANCE_WIDTH)
} else {
/*
* Pulse distance or pulse width here
*/
aSerial->print("PulseDistanceWidth");
if(tNumberOfArrayData > 1) {
aSerial->print("FromArray(38, ");
} else {
aSerial->print("(38, ");
}
printDistanceWidthTimingInfo(aSerial, &decodedIRData.DistanceWidthTimingInfo);
if(tNumberOfArrayData > 1) {
aSerial->print(F(", &tRawData[0], "));
} else {
aSerial->print(F(", 0x"));
# if (__INT_WIDTH__ < 32)
aSerial->print(decodedIRData.decodedRawData, HEX);
# else
PrintULL::print(aSerial, decodedIRData.decodedRawData, HEX);
# endif
aSerial->print(F(", "));
}
aSerial->print(decodedIRData.numberOfBits);// aNumberOfBits
aSerial->print(F(", PROTOCOL_IS_"));
if (decodedIRData.flags & IRDATA_FLAGS_IS_MSB_FIRST) {
aSerial->print('M');
} else {
aSerial->print('L');
}
aSerial->print(F("SB_FIRST, <RepeatPeriodMillis>, <numberOfRepeats>"));
}
#endif
#if defined(DECODE_PANASONIC) || defined(DECODE_KASEIKYO)
if ((decodedIRData.flags & IRDATA_FLAGS_EXTRA_INFO) && decodedIRData.protocol == KASEIKYO) {
aSerial->print(F(", 0x"));
aSerial->print(decodedIRData.extra, HEX);
}
#endif
aSerial->print(F(");"));
aSerial->println();
}
}
/**
* Function to print protocol number, address, command, raw data and repeat flag of IrReceiver.decodedIRData in one short line.
* Does not print a Newline / does not end with println().
*
* @param aSerial The Print object on which to write, for Arduino you can use &Serial.
*/
void IRrecv::printIRResultMinimal(Print *aSerial) {
aSerial->print(F("P="));
aSerial->print(decodedIRData.protocol);
if (decodedIRData.protocol == UNKNOWN) {
#if defined(DECODE_HASH)
aSerial->print(F(" #=0x"));
# if (__INT_WIDTH__ < 32)
aSerial->print(decodedIRData.decodedRawData, HEX);
# else
PrintULL::print(aSerial, decodedIRData.decodedRawData, HEX);
# endif
#endif
aSerial->print(' ');
aSerial->print((decodedIRData.rawDataPtr->rawlen + 1) / 2, DEC);
aSerial->println(F(" bits received"));
} else {
/*
* New decoders have address and command
*/
aSerial->print(F(" A=0x"));
aSerial->print(decodedIRData.address, HEX);
aSerial->print(F(" C=0x"));
aSerial->print(decodedIRData.command, HEX);
aSerial->print(F(" Raw=0x"));
#if (__INT_WIDTH__ < 32)
aSerial->print(decodedIRData.decodedRawData, HEX);
#else
PrintULL::print(aSerial, decodedIRData.decodedRawData, HEX);
#endif
if (decodedIRData.flags & (IRDATA_FLAGS_IS_AUTO_REPEAT | IRDATA_FLAGS_IS_REPEAT)) {
aSerial->print(F(" R"));
}
}
}
/**
* Dump out the timings in IrReceiver.decodedIRData.rawDataPtr->rawbuf[] array 8 values per line.
*
* @param aSerial The Print object on which to write, for Arduino you can use &Serial.
* @param aOutputMicrosecondsInsteadOfTicks Output the (rawbuf_values * MICROS_PER_TICK) for better readability.
*/
void IRrecv::printIRResultRawFormatted(Print *aSerial, bool aOutputMicrosecondsInsteadOfTicks) {
uint8_t tRawlen = decodedIRData.rawDataPtr->rawlen; // Get it once here in order to print quite consistent data, even if ISR is running
// Print Raw data
aSerial->print(F("rawData["));
aSerial->print(tRawlen, DEC);
aSerial->println(F("]: "));
/*
* Print initial gap
*/
aSerial->print(F(" -"));
if (aOutputMicrosecondsInsteadOfTicks) {
aSerial->println((uint32_t) decodedIRData.rawDataPtr->rawbuf[0] * MICROS_PER_TICK, DEC);
} else {
aSerial->println(decodedIRData.rawDataPtr->rawbuf[0], DEC);
}
#if RAW_BUFFER_LENGTH <= 254 // saves around 75 bytes program memory and speeds up ISR
uint_fast8_t i;
#else
unsigned int i;
#endif
// Newline is printed every 8. value, if tCounterForNewline % 8 == 0
uint_fast8_t tCounterForNewline = 6; // first newline is after the 2 values of the start bit
// check if we have a protocol with no or 8 start bits
#if defined(DECODE_DENON) || defined(DECODE_MAGIQUEST)
if (
# if defined(DECODE_DENON)
decodedIRData.protocol == DENON || decodedIRData.protocol == SHARP ||
# endif
# if defined(DECODE_MAGIQUEST)
decodedIRData.protocol == MAGIQUEST ||
# endif
false) {
tCounterForNewline = 0; // no or 8 start bits
}
#endif
uint32_t tDuration;
uint16_t tSumOfDurationTicks = 0;
for (i = 1; i < tRawlen; i++) {
auto tCurrentTicks = decodedIRData.rawDataPtr->rawbuf[i];
if (aOutputMicrosecondsInsteadOfTicks) {
tDuration = tCurrentTicks * MICROS_PER_TICK;
} else {
tDuration = tCurrentTicks;
}
tSumOfDurationTicks += tCurrentTicks; // compute length of protocol frame
if (!(i & 1)) { // even
aSerial->print('-');
} else { // odd
aSerial->print(F(" +"));
}
// padding only for big values
if (aOutputMicrosecondsInsteadOfTicks && tDuration < 1000) {
aSerial->print(' ');
}
if (aOutputMicrosecondsInsteadOfTicks && tDuration < 100) {
aSerial->print(' ');
}
if (tDuration < 10) {
aSerial->print(' ');
}
aSerial->print(tDuration, DEC);
if ((i & 1) && (i + 1) < tRawlen) {
aSerial->print(','); //',' not required for last one
}
tCounterForNewline++;
if ((tCounterForNewline % 8) == 0) {
aSerial->println();
}
}
aSerial->println();
aSerial->print("Sum: ");
if (aOutputMicrosecondsInsteadOfTicks) {
aSerial->println((uint32_t) tSumOfDurationTicks * MICROS_PER_TICK, DEC);
} else {
aSerial->println(tSumOfDurationTicks, DEC);
}
}
/**
* Dump out the IrReceiver.decodedIRData.rawDataPtr->rawbuf[] to be used as C definition for sendRaw().
*
* Compensate received values by MARK_EXCESS_MICROS, like it is done for decoding!
* Print ticks in 8 bit format to save space.
* Maximum is 255*50 microseconds = 12750 microseconds = 12.75 ms, which hardly ever occurs inside an IR sequence.
* Recording of IRremote anyway stops at a gap of RECORD_GAP_MICROS (5 ms).
*
* @param aSerial The Print object on which to write, for Arduino you can use &Serial.
* @param aOutputMicrosecondsInsteadOfTicks Output the (rawbuf_values * MICROS_PER_TICK) for better readability.
*/
void IRrecv::compensateAndPrintIRResultAsCArray(Print *aSerial, bool aOutputMicrosecondsInsteadOfTicks) {
// Start declaration
if (aOutputMicrosecondsInsteadOfTicks) {
aSerial->print(F("uint16_t rawData[")); // variable type, array name
} else {
aSerial->print(F("uint8_t rawTicks[")); // variable type, array name
}
aSerial->print(decodedIRData.rawDataPtr->rawlen - 1, DEC); // array size
aSerial->print(F("] = {")); // Start declaration
// Dump data
#if RAW_BUFFER_LENGTH <= 254 // saves around 75 bytes program memory and speeds up ISR
uint_fast8_t i;
#else
unsigned int i;
#endif
for (i = 1; i < decodedIRData.rawDataPtr->rawlen; i++) {
uint32_t tDuration = decodedIRData.rawDataPtr->rawbuf[i] * MICROS_PER_TICK;
if (i & 1) {
// Mark
tDuration -= MARK_EXCESS_MICROS;
} else {
tDuration += MARK_EXCESS_MICROS;
}
if (aOutputMicrosecondsInsteadOfTicks) {
aSerial->print(tDuration);
} else {
unsigned int tTicks = (tDuration + (MICROS_PER_TICK / 2)) / MICROS_PER_TICK;
/*
* Clip to 8 bit value
*/
tTicks = (tTicks > UINT8_MAX) ? UINT8_MAX : tTicks;
aSerial->print(tTicks);
}
if (i + 1 < decodedIRData.rawDataPtr->rawlen)
aSerial->print(','); // ',' not required on last one
if (!(i & 1))
aSerial->print(' ');
}
// End declaration
aSerial->print(F("};")); //
// Comment
aSerial->print(F(" // "));
printIRResultShort(aSerial);
// Newline
aSerial->println("");
}
/**
* Store the decodedIRData to be used for sendRaw().
*
* Compensate received values by MARK_EXCESS_MICROS, like it is done for decoding and store it in an array provided.
*
* Maximum for uint8_t is 255*50 microseconds = 12750 microseconds = 12.75 ms, which hardly ever occurs inside an IR sequence.
* Recording of IRremote anyway stops at a gap of RECORD_GAP_MICROS (5 ms).
* @param aArrayPtr Address of an array provided by the caller.
*/
void IRrecv::compensateAndStoreIRResultInArray(uint8_t *aArrayPtr) {
// Store data, skip leading space#
#if RAW_BUFFER_LENGTH <= 254 // saves around 75 bytes program memory and speeds up ISR
uint_fast8_t i;
#else
unsigned int i;
#endif
for (i = 1; i < decodedIRData.rawDataPtr->rawlen; i++) {
uint32_t tDuration = decodedIRData.rawDataPtr->rawbuf[i] * MICROS_PER_TICK;
if (i & 1) {
// Mark
tDuration -= MARK_EXCESS_MICROS;
} else {
tDuration += MARK_EXCESS_MICROS;
}
unsigned int tTicks = (tDuration + (MICROS_PER_TICK / 2)) / MICROS_PER_TICK;
*aArrayPtr = (tTicks > UINT8_MAX) ? UINT8_MAX : tTicks; // we store it in an 8 bit array
aArrayPtr++;
}
}
/**
* Print results as C variables to be used for sendXXX()
* @param aSerial The Print object on which to write, for Arduino you can use &Serial.
*/
void IRrecv::printIRResultAsCVariables(Print *aSerial) {
// Now dump "known" codes
if (decodedIRData.protocol != UNKNOWN) {
/*
* New decoders have address and command
*/
aSerial->print(F("uint16_t"));
aSerial->print(F(" address = 0x"));
aSerial->print(decodedIRData.address, HEX);
aSerial->println(';');
aSerial->print(F("uint16_t"));
aSerial->print(F(" command = 0x"));
aSerial->print(decodedIRData.command, HEX);
aSerial->println(';');
// All protocols have raw data
#if __INT_WIDTH__ < 32
aSerial->print(F("uint32_t rawData = 0x"));
#else
aSerial->print(F("uint64_t rawData = 0x"));
#endif
#if (__INT_WIDTH__ < 32)
aSerial->print(decodedIRData.decodedRawData, HEX);
#else
PrintULL::print(aSerial, decodedIRData.decodedRawData, HEX);
#endif
aSerial->println(';');
aSerial->println();
}
}
#if defined(__AVR__)
const __FlashStringHelper* IRrecv::getProtocolString() {
// call no class function with same name
return ::getProtocolString(decodedIRData.protocol);
}
#else
const char* IRrecv::getProtocolString() {
// call no class function with same name
return ::getProtocolString(decodedIRData.protocol);
}
#endif
/**********************************************************************************************************************
* The OLD and DEPRECATED decode function with parameter aResults, kept for backward compatibility to old 2.0 tutorials
* This function calls the old MSB first decoders and fills only the 3 variables:
* aResults->value
* aResults->bits
* aResults->decode_type
**********************************************************************************************************************/
bool IRrecv::decode(decode_results *aResults) {
static bool sDeprecationMessageSent = false;
if (irparams.StateForISR != IR_REC_STATE_STOP) {
return false;
}
if (!sDeprecationMessageSent) {
#if !(defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) || defined(__AVR_ATtiny87__) || defined(__AVR_ATtiny167__))
// Serial.println(
// "The function decode(&results)) is deprecated and may not work as expected! Just use decode() without a parameter and IrReceiver.decodedIRData.<fieldname> .");
#endif
sDeprecationMessageSent = true;
}
// copy for usage by legacy programs
aResults->rawbuf = irparams.rawbuf;
aResults->rawlen = irparams.rawlen;
if (irparams.OverflowFlag) {
// Copy overflow flag to decodedIRData.flags
irparams.OverflowFlag = false;
irparams.rawlen = 0; // otherwise we have OverflowFlag again at next ISR call
IR_DEBUG_PRINTLN(F("Overflow happened"));
}
aResults->overflow = irparams.OverflowFlag;
aResults->value = 0;
decodedIRData.flags = IRDATA_FLAGS_IS_MSB_FIRST; // for print
#if defined(DECODE_NEC)
IR_DEBUG_PRINTLN(F("Attempting old NEC decode"));
if (decodeNECMSB(aResults)) {
return true;
}
#endif
#if defined(DECODE_SONY)
IR_DEBUG_PRINTLN(F("Attempting old Sony decode"));
if (decodeSonyMSB(aResults)) {
return true;
}
#endif
#if defined(DECODE_RC5)
IR_DEBUG_PRINTLN(F("Attempting RC5 decode"));
if (decodeRC5()) {
aResults->bits = decodedIRData.numberOfBits;
aResults->value = decodedIRData.decodedRawData;
aResults->decode_type = RC5;
return true;
}
#endif
#if defined(DECODE_RC6)
IR_DEBUG_PRINTLN(F("Attempting RC6 decode"));
if (decodeRC6()) {
aResults->bits = decodedIRData.numberOfBits;
aResults->value = decodedIRData.decodedRawData;
aResults->decode_type = RC6;
return true;
}
#endif
// Removed bool IRrecv::decodePanasonicMSB(decode_results *aResults) since implementations was wrong (wrong length), and nobody recognized it
#if defined(DECODE_LG)
IR_DEBUG_PRINTLN(F("Attempting old LG decode"));
if (decodeLGMSB(aResults)) {return true;}
#endif
#if defined(DECODE_JVC)
IR_DEBUG_PRINTLN(F("Attempting old JVC decode"));
if (decodeJVCMSB(aResults)) {
return true;
}
#endif
#if defined(DECODE_SAMSUNG)
IR_DEBUG_PRINTLN(F("Attempting old SAMSUNG decode"));
if (decodeSAMSUNG(aResults)) {
return true;
}
#endif
#if defined(DECODE_DENON)
IR_DEBUG_PRINTLN(F("Attempting old Denon decode"));
if (decodeDenonOld(aResults)) {
return true;
}
#endif
// decodeHash returns a hash on any input.
// Thus, it needs to be last in the list.
// If you add any decodes, add them before this.
if (decodeHashOld(aResults)) {
return true;
}
// Throw away and start over
resume();
return false;
}
/** @}*/
#if defined(_IR_MEASURE_TIMING)
#undef _IR_MEASURE_TIMING
#endif
#if defined(LOCAL_TRACE)
#undef LOCAL_TRACE
#endif
#if defined(LOCAL_DEBUG)
#undef LOCAL_DEBUG
#endif
#endif // _IR_RECEIVE_HPP