Arduino-IRremote/src/ir_Pronto.hpp

342 lines
12 KiB
C++

/*
* @file ir_Pronto.hpp
* @brief In this file, the functions IRrecv::compensateAndPrintPronto and IRsend::sendPronto are defined.
*
* See http://www.harctoolbox.org/Glossary.html#ProntoSemantics
* Pronto database http://www.remotecentral.com/search.htm
*
* This file is part of Arduino-IRremote https://github.com/Arduino-IRremote/Arduino-IRremote.
*
************************************************************************************
* MIT License
*
* Copyright (c) 2020 Bengt Martensson
*
* 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_PRONTO_HPP
#define _IR_PRONTO_HPP
#if defined(DEBUG) && !defined(LOCAL_DEBUG)
#define LOCAL_DEBUG
#else
//#define LOCAL_DEBUG // This enables debug output only for this file
#endif
/** \addtogroup Decoder Decoders and encoders for different protocols
* @{
*/
//! @cond
// DO NOT EXPORT from this file
static const uint16_t learnedToken = 0x0000U;
static const uint16_t learnedNonModulatedToken = 0x0100U;
static const uint16_t bitsInHexadecimal = 4U;
static const uint16_t digitsInProntoNumber = 4U;
static const uint16_t numbersInPreamble = 4U;
static const uint16_t hexMask = 0xFU;
static const uint32_t referenceFrequency = 4145146UL;
static const uint16_t fallbackFrequency = 64767U; // To use with frequency = 0;
static const uint32_t microsecondsInSeconds = 1000000UL;
static const uint16_t PRONTO_DEFAULT_GAP = 45000;
//! @endcond
static uint16_t toFrequencyKHz(uint16_t code) {
return ((referenceFrequency / code) + 500) / 1000;
}
/*
* Parse the string given as Pronto Hex, and send it a number of times given as argument.
* The first number denotes the type of the signal. 0000 denotes a raw IR signal with modulation,
// The second number denotes a frequency code
*/
void IRsend::sendPronto(const uint16_t *data, uint16_t length, int_fast8_t aNumberOfRepeats) {
uint16_t timebase = (microsecondsInSeconds * data[1] + referenceFrequency / 2) / referenceFrequency;
uint16_t khz;
switch (data[0]) {
case learnedToken: // normal, "learned"
khz = toFrequencyKHz(data[1]);
break;
case learnedNonModulatedToken: // non-demodulated, "learned"
khz = 0U;
break;
default:
return; // There are other types, but they are not handled yet.
}
uint16_t intros = 2 * data[2];
uint16_t repeats = 2 * data[3];
#if defined(LOCAL_DEBUG)
Serial.print(F("sendPronto intros="));
Serial.print(intros);
Serial.print(F(" repeats="));
Serial.println(repeats);
#endif
if (numbersInPreamble + intros + repeats != length) { // inconsistent sizes
return;
}
/*
* Generate a new microseconds timing array for sendRaw.
* If recorded by IRremote, intro contains the whole IR data and repeat is empty
*/
uint16_t durations[intros + repeats];
for (uint16_t i = 0; i < intros + repeats; i++) {
uint32_t duration = ((uint32_t) data[i + numbersInPreamble]) * timebase;
durations[i] = (uint16_t) ((duration <= UINT16_MAX) ? duration : UINT16_MAX);
}
/*
* Send the intro. intros is even.
* Do not send the trailing space here, send it if repeats are requested
*/
if (intros >= 2) {
sendRaw(durations, intros - 1, khz);
}
if (repeats == 0 || aNumberOfRepeats == 0) {
// only send intro once
return;
}
/*
* Now send the trailing space/gap of the intro and all the repeats
*/
if (intros >= 2) {
delay(durations[intros - 1] / MICROS_IN_ONE_MILLI); // equivalent to space(durations[intros - 1]); but allow bigger values for the gap
}
for (int i = 0; i < aNumberOfRepeats; i++) {
sendRaw(durations + intros, repeats - 1, khz);
if ((i + 1) < aNumberOfRepeats) { // skip last trailing space/gap, see above
delay(durations[intros + repeats - 1] / MICROS_IN_ONE_MILLI);
}
}
}
/**
* Parse the string given as Pronto Hex, and send it a number of times given
* as the second argument. Thereby the division of the Pronto Hex into
* an intro-sequence and a repeat sequence is taken into account:
* First the intro sequence is sent, then the repeat sequence is sent times-1 times.
* However, if the intro sequence is empty, the repeat sequence is sent times times.
* <a href="http://www.harctoolbox.org/Glossary.html#ProntoSemantics">Reference</a>.
*
* Note: Using this function is very wasteful for the memory consumption on
* a small board.
* Normally it is a much better idea to use a tool like e.g. IrScrutinizer
* to transform Pronto type signals offline
* to a more memory efficient format.
*
* @param str C type string (null terminated) containing a Pronto Hex representation.
* @param aNumberOfRepeats Number of times to send the signal.
*/
void IRsend::sendPronto(const char *str, int_fast8_t aNumberOfRepeats) {
size_t len = strlen(str) / (digitsInProntoNumber + 1) + 1;
uint16_t data[len];
const char *p = str;
char *endptr[1];
for (uint16_t i = 0; i < len; i++) {
long x = strtol(p, endptr, 16);
if (x == 0 && i >= numbersInPreamble) {
// Alignment error?, bail immediately (often right result).
len = i;
break;
}
data[i] = static_cast<uint16_t>(x); // If input is conforming, there can be no overflow!
p = *endptr;
}
sendPronto(data, len, aNumberOfRepeats);
}
#if defined(__AVR__)
/**
* Version of sendPronto that reads from PROGMEM, saving RAM memory.
* @param str pronto C type string (null terminated) containing a Pronto Hex representation.
* @param aNumberOfRepeats Number of times to send the signal.
*/
//far pointer (? for ATMega2560 etc.)
void IRsend::sendPronto_PF(uint_farptr_t str, int_fast8_t aNumberOfRepeats) {
size_t len = strlen_PF(str);
char work[len + 1];
strncpy_PF(work, str, len);
sendPronto(work, aNumberOfRepeats);
}
//standard pointer
void IRsend::sendPronto_P(const char *str, int_fast8_t aNumberOfRepeats) {
size_t len = strlen_P(str);
char work[len + 1];
strncpy_P(work, str, len);
sendPronto(work, aNumberOfRepeats);
}
#endif
void IRsend::sendPronto(const __FlashStringHelper *str, int_fast8_t aNumberOfRepeats) {
size_t len = strlen_P(reinterpret_cast<const char*>(str));
char work[len + 1];
strncpy_P(work, reinterpret_cast<const char*>(str), len);
return sendPronto(work, aNumberOfRepeats);
}
static uint16_t effectiveFrequency(uint16_t frequency) {
return frequency > 0 ? frequency : fallbackFrequency;
}
static uint16_t toTimebase(uint16_t frequency) {
return microsecondsInSeconds / effectiveFrequency(frequency);
}
static uint16_t toFrequencyCode(uint16_t frequency) {
return referenceFrequency / effectiveFrequency(frequency);
}
static char hexDigit(uint16_t x) {
return (char) (x <= 9 ? ('0' + x) : ('A' + (x - 10)));
}
static void dumpDigit(Print *aSerial, uint16_t number) {
aSerial->print(hexDigit(number));
}
static void dumpNumber(Print *aSerial, uint16_t number) {
for (uint16_t i = 0; i < digitsInProntoNumber; i++) {
uint16_t shifts = bitsInHexadecimal * (digitsInProntoNumber - 1 - i);
dumpDigit(aSerial, (number >> shifts) & hexMask);
}
aSerial->print(' ');
}
static void dumpDuration(Print *aSerial, uint32_t duration, uint16_t timebase) {
dumpNumber(aSerial, (duration + timebase / 2) / timebase);
}
/*
* Compensate received values by MARK_EXCESS_MICROS, like it is done for decoding!
*/
static void compensateAndDumpSequence(Print *aSerial, const volatile uint16_t *data, size_t length, uint16_t timebase) {
for (size_t i = 0; i < length; i++) {
uint32_t tDuration = data[i] * MICROS_PER_TICK;
if (i & 1) {
// Mark
tDuration -= getMarkExcessMicros();
} else {
tDuration += getMarkExcessMicros();
}
dumpDuration(aSerial, tDuration, timebase);
}
// append a gap
dumpDuration(aSerial, PRONTO_DEFAULT_GAP, timebase);
}
/**
* Print the result (second argument) as Pronto Hex on the Print supplied as argument.
* Used in the ReceiveDump example.
* @param aSerial The Print object on which to write, for Arduino you can use &Serial.
* @param aFrequencyHertz Modulation frequency in Hz. Often 38000Hz.
*/
void IRrecv::compensateAndPrintIRResultAsPronto(Print *aSerial, uint16_t aFrequencyHertz) {
aSerial->println(F("Pronto Hex as string"));
aSerial->print(F("char prontoData[] = \""));
dumpNumber(aSerial, aFrequencyHertz > 0 ? learnedToken : learnedNonModulatedToken);
dumpNumber(aSerial, toFrequencyCode(aFrequencyHertz));
dumpNumber(aSerial, (decodedIRData.rawDataPtr->rawlen + 1) / 2);
dumpNumber(aSerial, 0);
uint16_t timebase = toTimebase(aFrequencyHertz);
compensateAndDumpSequence(aSerial, &decodedIRData.rawDataPtr->rawbuf[1], decodedIRData.rawDataPtr->rawlen - 1, timebase); // skip leading space
aSerial->println("\";");
}
/*
* Functions for dumping Pronto to a String. This is not very time and space efficient
* and can lead to resource problems especially on small processors like AVR's
*/
static bool dumpDigit(String *aString, uint16_t number) {
aString->concat(hexDigit(number));
return number;
}
static size_t dumpNumber(String *aString, uint16_t number) {
size_t size = 0;
for (uint16_t i = 0; i < digitsInProntoNumber; i++) {
uint16_t shifts = bitsInHexadecimal * (digitsInProntoNumber - 1 - i);
size += dumpDigit(aString, (number >> shifts) & hexMask);
}
aString->concat(' ');
size++;
return size;
}
/*
* Compensate received values by MARK_EXCESS_MICROS, like it is done for decoding!
*/
static size_t dumpDuration(String *aString, uint32_t duration, uint16_t timebase) {
return dumpNumber(aString, (duration + timebase / 2) / timebase);
}
static size_t compensateAndDumpSequence(String *aString, const volatile uint16_t *data, size_t length, uint16_t timebase) {
size_t size = 0;
for (size_t i = 0; i < length; i++) {
uint32_t tDuration = data[i] * MICROS_PER_TICK;
if (i & 1) {
// Mark
tDuration -= getMarkExcessMicros();
} else {
tDuration += getMarkExcessMicros();
}
size += dumpDuration(aString, tDuration, timebase);
}
// append minimum gap
size += dumpDuration(aString, PRONTO_DEFAULT_GAP, timebase);
return size;
}
/*
* Writes Pronto HEX to a String object.
* Returns the amount of characters added to the string.(360 characters for a NEC code!)
*/
size_t IRrecv::compensateAndStorePronto(String *aString, uint16_t frequency) {
size_t size = 0;
uint16_t timebase = toTimebase(frequency);
size += dumpNumber(aString, frequency > 0 ? learnedToken : learnedNonModulatedToken);
size += dumpNumber(aString, toFrequencyCode(frequency));
size += dumpNumber(aString, (decodedIRData.rawDataPtr->rawlen + 1) / 2);
size += dumpNumber(aString, 0);
size += compensateAndDumpSequence(aString, &decodedIRData.rawDataPtr->rawbuf[1], decodedIRData.rawDataPtr->rawlen - 1,
timebase); // skip leading space
return size;
}
/** @}*/
#if defined(LOCAL_DEBUG)
#undef LOCAL_DEBUG
#endif
#endif // _IR_PRONTO_HPP