IRremote
ir_Pronto.hpp
Go to the documentation of this file.
1 /*
2  * @file ir_Pronto.hpp
3  * @brief In this file, the functions IRrecv::compensateAndPrintPronto and IRsend::sendPronto are defined.
4  *
5  * Pronto is the standard for the professional audio and video hardware market.
6  *
7  * See http://www.harctoolbox.org/Glossary.html#ProntoSemantics
8  * Pronto database http://www.remotecentral.com/search.htm
9  *
10  * This file is part of Arduino-IRremote https://github.com/Arduino-IRremote/Arduino-IRremote.
11  *
12  ************************************************************************************
13  * MIT License
14  *
15  * Copyright (c) 2020-2025 Bengt Martensson, Armin Joachimsmeyer
16  *
17  * Permission is hereby granted, free of charge, to any person obtaining a copy
18  * of this software and associated documentation files (the "Software"), to deal
19  * in the Software without restriction, including without limitation the rights
20  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
21  * copies of the Software, and to permit persons to whom the Software is furnished
22  * to do so, subject to the following conditions:
23  *
24  * The above copyright notice and this permission notice shall be included in all
25  * copies or substantial portions of the Software.
26  *
27  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
28  * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
29  * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
30  * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
31  * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
32  * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33  *
34  ************************************************************************************
35  */
36 #ifndef _IR_PRONTO_HPP
37 #define _IR_PRONTO_HPP
38 
39 #if defined(DEBUG)
40 #define LOCAL_DEBUG
41 #else
42 //#define LOCAL_DEBUG // This enables debug output only for this file
43 #endif
44 
49 // DO NOT EXPORT from this file
51 static const uint16_t learnedToken = 0x0000U;
52 static const uint16_t learnedNonModulatedToken = 0x0100U;
53 static const uint16_t bitsInHexadecimal = 4U;
54 static const uint16_t digitsInProntoNumber = 4U;
55 static const uint16_t numbersInPreamble = 4U;
56 static const uint16_t hexMask = 0xFU;
57 static const uint32_t referenceFrequency = 4145146UL;
58 static const uint16_t fallbackFrequency = 64767U; // To use with frequency = 0;
59 static const uint32_t microsecondsInSeconds = 1000000UL;
60 static const uint16_t PRONTO_DEFAULT_GAP = 45000;
62 
63 static uint16_t toFrequencyKHz(uint16_t code) {
64  return ((referenceFrequency / code) + 500) / 1000;
65 }
66 
67 /*
68  * Parse the string given as Pronto Hex, and send it a number of times given as argument.
69  * The first number denotes the type of the signal. 0000 denotes a raw IR signal with modulation,
70  // The second number denotes a frequency code
71  */
72 void IRsend::sendPronto(const uint16_t *data, uint16_t length, int_fast8_t aNumberOfRepeats) {
73  uint16_t timebase = (microsecondsInSeconds * data[1] + referenceFrequency / 2) / referenceFrequency;
74  uint16_t khz;
75  switch (data[0]) {
76  case learnedToken: // normal, "learned"
77  khz = toFrequencyKHz(data[1]);
78  break;
79  case learnedNonModulatedToken: // non-demodulated, "learned"
80  khz = 0U;
81  break;
82  default:
83  return; // There are other types, but they are not handled yet.
84  }
85  uint16_t intros = 2 * data[2];
86  uint16_t repeats = 2 * data[3];
87 #if defined(LOCAL_DEBUG)
88  Serial.print(F("sendPronto intros="));
89  Serial.print(intros);
90  Serial.print(F(" repeats="));
91  Serial.println(repeats);
92 #endif
93  if (numbersInPreamble + intros + repeats != length) { // inconsistent sizes
94  return;
95  }
96 
97  /*
98  * Generate a new microseconds timing array for sendRaw.
99  * If recorded by IRremote, intro contains the whole IR data and repeat is empty
100  */
101  uint16_t durations[intros + repeats];
102  for (uint16_t i = 0; i < intros + repeats; i++) {
103  uint32_t duration = ((uint32_t) data[i + numbersInPreamble]) * timebase;
104  durations[i] = (uint16_t)((duration <= UINT16_MAX) ? duration : UINT16_MAX);
105  }
106 
107  /*
108  * Send the intro. intros is even.
109  * Do not send the trailing space here, send it if repeats are requested
110  */
111  if (intros >= 2) {
112  sendRaw(durations, intros - 1, khz);
113  }
114 
115  if (repeats == 0 || aNumberOfRepeats == 0) {
116  // only send intro once
117  return;
118  }
119 
120  /*
121  * Now send the trailing space/gap of the intro and all the repeats
122  */
123  if (intros >= 2) {
124  delay(durations[intros - 1] / MICROS_IN_ONE_MILLI); // equivalent to space(durations[intros - 1]); but allow bigger values for the gap
125  }
126  for (int i = 0; i < aNumberOfRepeats; i++) {
127  sendRaw(durations + intros, repeats - 1, khz);
128  if ((i + 1) < aNumberOfRepeats) { // skip last trailing space/gap, see above
129  delay(durations[intros + repeats - 1] / MICROS_IN_ONE_MILLI);
130  }
131  }
132 }
133 
151 void IRsend::sendPronto(const char *str, int_fast8_t aNumberOfRepeats) {
152  size_t len = strlen(str) / (digitsInProntoNumber + 1) + 1;
153  uint16_t data[len];
154  const char *p = str;
155  char *endptr[1];
156  for (uint16_t i = 0; i < len; i++) {
157  long x = strtol(p, endptr, 16);
158  if (x == 0 && i >= numbersInPreamble) {
159  // Alignment error?, bail immediately (often right result).
160  len = i;
161  break;
162  }
163  data[i] = static_cast<uint16_t>(x); // If input is conforming, there can be no overflow!
164  p = *endptr;
165  }
166  sendPronto(data, len, aNumberOfRepeats);
167 }
168 
169 #if defined(__AVR__)
170 
175 //far pointer (? for ATMega2560 etc.)
176 void IRsend::sendPronto_PF(uint_farptr_t str, int_fast8_t aNumberOfRepeats) {
177  size_t len = strlen_PF(str);
178  char work[len + 1];
179  strcpy_PF(work, str); // We know that string including terminating character fits in work
181 }
182 
183 //standard pointer
184 void IRsend::sendPronto_P(const char *str, int_fast8_t aNumberOfRepeats) {
185  size_t len = strlen_P(str);
186  char work[len + 1];
187  strcpy_P(work, str);
189 }
190 #endif
191 
192 /*
193  * Copy flash data to ram buffer in stack
194  */
195 void IRsend::sendPronto(const __FlashStringHelper *str, int_fast8_t aNumberOfRepeats) {
196  size_t len = strlen_P(reinterpret_cast<const char*>(str));
197  char work[len + 1];
198  strcpy_P(work, reinterpret_cast<const char*>(str));
199  return sendPronto(work, aNumberOfRepeats);
200 }
201 
202 static uint16_t effectiveFrequency(uint16_t frequency) {
203  return frequency > 0 ? frequency : fallbackFrequency;
204 }
205 
206 static uint16_t toTimebase(uint16_t frequency) {
207  return microsecondsInSeconds / effectiveFrequency(frequency);
208 }
209 
210 static uint16_t toFrequencyCode(uint16_t frequency) {
211  return referenceFrequency / effectiveFrequency(frequency);
212 }
213 
214 static char DigitToHex(uint8_t x) {
215  return (char) (x <= 9 ? ('0' + x) : ('A' + (x - 10)));
216 }
217 
218 static void dumpDigitHex(Print *aSerial, uint8_t number) {
219  aSerial->print(DigitToHex(number));
220 }
221 
222 static void dumpNumberHex(Print *aSerial, uint16_t number) {
223  // Loop through all 4 nibbles
224  for (uint16_t i = 0; i < digitsInProntoNumber; i++) {
225  uint16_t shifts = bitsInHexadecimal * (digitsInProntoNumber - 1 - i);
226  dumpDigitHex(aSerial, (number >> shifts) & hexMask);
227  }
228  aSerial->print(' ');
229 }
230 
231 static void dumpDurationHex(Print *aSerial, uint32_t duration, uint16_t timebase) {
232  dumpNumberHex(aSerial, (duration + timebase / 2) / timebase);
233 }
234 
235 /*
236  * Compensate received values by MARK_EXCESS_MICROS, like it is done for decoding!
237  */
238 static void compensateAndDumpSequence(Print *aSerial, const volatile IRRawbufType *data, size_t length, uint16_t timebase) {
239  for (size_t i = 0; i < length; i++) {
240  uint32_t tDuration = data[i] * MICROS_PER_TICK;
241  if (i & 1) {
242  // Mark
243  tDuration -= getMarkExcessMicros();
244  } else {
245  tDuration += getMarkExcessMicros();
246  }
247  dumpDurationHex(aSerial, tDuration, timebase);
248  }
249 
250  // append a gap
251  dumpDurationHex(aSerial, PRONTO_DEFAULT_GAP, timebase);
252 }
253 
261 void IRrecv::compensateAndPrintIRResultAsPronto(Print *aSerial, uint16_t aFrequencyHertz) {
262  aSerial->println(F("Pronto Hex as string without repeat sequence"));
263  aSerial->print(F("char prontoData[] = \""));
264  dumpNumberHex(aSerial, aFrequencyHertz > 0 ? learnedToken : learnedNonModulatedToken);
265  dumpNumberHex(aSerial, toFrequencyCode(aFrequencyHertz));
266  dumpNumberHex(aSerial, (decodedIRData.rawlen + 1) / 2);
267  dumpNumberHex(aSerial, 0); // no repeat data
268  uint16_t timebase = toTimebase(aFrequencyHertz);
269  compensateAndDumpSequence(aSerial, &irparams.rawbuf[1], decodedIRData.rawlen - 1, timebase); // skip leading space
270  aSerial->println(F("\";"));
271 }
272 
273 /*
274  * Functions for dumping Pronto to a String. This is not very time and space efficient
275  * and can lead to resource problems especially on small processors like AVR's
276  */
277 
278 static bool dumpDigitHex(String *aString, uint8_t number) {
279  aString->concat(DigitToHex(number));
280  return number;
281 }
282 
283 static size_t dumpNumberHex(String *aString, uint16_t number) {
284 
285  size_t size = 0;
286 
287  for (uint16_t i = 0; i < digitsInProntoNumber; i++) {
288  uint16_t shifts = bitsInHexadecimal * (digitsInProntoNumber - 1 - i);
289  size += dumpDigitHex(aString, (number >> shifts) & hexMask);
290  }
291  aString->concat(' ');
292  size++;
293 
294  return size;
295 }
296 
297 /*
298  * Compensate received values by MARK_EXCESS_MICROS, like it is done for decoding!
299  */
300 static size_t dumpDurationHex(String *aString, uint32_t duration, uint16_t timebase) {
301  return dumpNumberHex(aString, (duration + timebase / 2) / timebase);
302 }
303 
304 static size_t compensateAndDumpSequence(String *aString, const volatile IRRawbufType *data, size_t length, uint16_t timebase) {
305 
306  size_t size = 0;
307 
308  for (size_t i = 0; i < length; i++) {
309  uint32_t tDuration = data[i] * MICROS_PER_TICK;
310  if (i & 1) {
311  // Mark
312  tDuration -= getMarkExcessMicros();
313  } else {
314  tDuration += getMarkExcessMicros();
315  }
316  size += dumpDurationHex(aString, tDuration, timebase);
317  }
318 
319  // append minimum gap
320  size += dumpDurationHex(aString, PRONTO_DEFAULT_GAP, timebase);
321 
322  return size;
323 }
324 
325 /*
326  * Writes Pronto HEX to a String object.
327  * Returns the amount of characters added to the string.(360 characters for a NEC code!)
328  */
329 size_t IRrecv::compensateAndStorePronto(String *aString, uint16_t frequency) {
330 
331  size_t size = 0;
332  uint16_t timebase = toTimebase(frequency);
333 
334  size += dumpNumberHex(aString, frequency > 0 ? learnedToken : learnedNonModulatedToken);
335  size += dumpNumberHex(aString, toFrequencyCode(frequency));
336  size += dumpNumberHex(aString, (decodedIRData.rawlen + 1) / 2);
337  size += dumpNumberHex(aString, 0);
338  size += compensateAndDumpSequence(aString, &irparams.rawbuf[1], decodedIRData.rawlen - 1, timebase); // skip leading space
339 
340  return size;
341 }
342 
344 #if defined(LOCAL_DEBUG)
345 #undef LOCAL_DEBUG
346 #endif
347 #endif // _IR_PRONTO_HPP
MICROS_PER_TICK
#define MICROS_PER_TICK
microseconds per clock interrupt tick
Definition: IRremote.hpp:207
IRsend::aNumberOfRepeats
void int_fast8_t aNumberOfRepeats
Definition: IRremoteInt.h:538
MICROS_IN_ONE_MILLI
#define MICROS_IN_ONE_MILLI
Definition: IRremote.hpp:212
IRrecv::compensateAndStorePronto
size_t compensateAndStorePronto(String *aString, uint16_t frequency=38000U)
Definition: ir_Pronto.hpp:329
IRrecv::compensateAndPrintIRResultAsPronto
void compensateAndPrintIRResultAsPronto(Print *aSerial, uint16_t frequency=38000U)
Print the result (second argument) as Pronto Hex on the Print supplied as argument.
Definition: ir_Pronto.hpp:261
IRsend::sendRaw
void sendRaw(const uint8_t aBufferWithTicks[], uint_fast16_t aLengthOfBuffer, uint_fast8_t aIRFrequencyKilohertz)
Sends an 8 byte tick timing array to save program memory.
Definition: IRSend.hpp:422
toFrequencyKHz
static uint16_t toFrequencyKHz(uint16_t code)
Definition: ir_Pronto.hpp:63
IRrecv::decodedIRData
IRData decodedIRData
Definition: IRremoteInt.h:409
compensateAndDumpSequence
static void compensateAndDumpSequence(Print *aSerial, const volatile IRRawbufType *data, size_t length, uint16_t timebase)
Definition: ir_Pronto.hpp:238
dumpDigitHex
static void dumpDigitHex(Print *aSerial, uint8_t number)
Definition: ir_Pronto.hpp:218
dumpDurationHex
static void dumpDurationHex(Print *aSerial, uint32_t duration, uint16_t timebase)
Definition: ir_Pronto.hpp:231
getMarkExcessMicros
int getMarkExcessMicros()
Getter function for MARK_EXCESS_MICROS.
Definition: IRReceive.hpp:1504
irparams_struct::rawbuf
IRRawbufType rawbuf[RAW_BUFFER_LENGTH]
raw data / tick counts per mark/space. With 8 bit we can only store up to 12.7 ms....
Definition: IRremoteInt.h:140
toTimebase
static uint16_t toTimebase(uint16_t frequency)
Definition: ir_Pronto.hpp:206
IRrecv::irparams
irparams_struct irparams
Definition: IRremoteInt.h:408
IRsend::sendPronto
void sendPronto(const __FlashStringHelper *str, int_fast8_t aNumberOfRepeats=NO_REPEATS)
Definition: ir_Pronto.hpp:195
dumpNumberHex
static void dumpNumberHex(Print *aSerial, uint16_t number)
Definition: ir_Pronto.hpp:222
IRData::rawlen
IRRawlenType rawlen
Counter of entries in rawbuf of last received frame.
Definition: IRremoteInt.h:170
IRRawbufType
uint8_t IRRawbufType
Definition: IRremoteInt.h:99
effectiveFrequency
static uint16_t effectiveFrequency(uint16_t frequency)
Definition: ir_Pronto.hpp:202
toFrequencyCode
static uint16_t toFrequencyCode(uint16_t frequency)
Definition: ir_Pronto.hpp:210
DigitToHex
static char DigitToHex(uint8_t x)
Definition: ir_Pronto.hpp:214