IRremote
ir_DistanceProtocol.hpp
Go to the documentation of this file.
1 /*
2  * ir_DistanceProtocol.hpp
3  *
4  * This decoder tries to decode a pulse width or pulse distance protocol.
5  * 1. Analyze all space and mark length
6  * 2. Decide if we have an pulse width or distance protocol
7  * 3. Try to decode with the mark and space data found in step 1
8  * No data and address decoding, only raw data as result.
9  *
10  * Pulse distance data can be sent with the generic function:
11  * void sendPulseDistanceWidthData(unsigned int aOneMarkMicros, unsigned int aOneSpaceMicros, unsigned int aZeroMarkMicros,
12  * unsigned int aZeroSpaceMicros, uint32_t aData, uint8_t aNumberOfBits, bool aMSBfirst, bool aSendStopBit = false)
13  * The header must be sent manually with:
14  * IrSender.mark(MarkMicros)
15  * IrSender.space(SpaceMicros);
16  * see also: SendDemo example line 150
17  *
18  * This file is part of Arduino-IRremote https://github.com/Arduino-IRremote/Arduino-IRremote.
19  *
20  ************************************************************************************
21  * MIT License
22  *
23  * Permission is hereby granted, free of charge, to any person obtaining a copy
24  * of this software and associated documentation files (the "Software"), to deal
25  * in the Software without restriction, including without limitation the rights
26  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
27  * copies of the Software, and to permit persons to whom the Software is furnished
28  * to do so, subject to the following conditions:
29  *
30  * The above copyright notice and this permission notice shall be included in all
31  * copies or substantial portions of the Software.
32  *
33  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
34  * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
35  * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
36  * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
37  * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
38  * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
39  *
40  ************************************************************************************
41  */
42 #ifndef _IR_DISTANCE_HPP
43 #define _IR_DISTANCE_HPP
44 
45 #include <Arduino.h>
46 
47 // accept durations up to 50 * 50 (MICROS_PER_TICK) 2500 microseconds
48 #define DURATION_ARRAY_SIZE 50
49 
50 // Switch the decoding according to your needs
51 //#define DISTANCE_DO_MSB_DECODING // If active, it resembles the JVC + Denon, otherwise LSB first as e.g. for NEC and Kaseikyo/Panasonic
52 
53 //#define DEBUG // Activate this for lots of lovely debug output from this decoder.
54 #include "IRremoteInt.h" // evaluates the DEBUG for IR_DEBUG_PRINT
55 //#include "LongUnion.h"
56 
60 // see: https://www.mikrocontroller.net/articles/IRMP_-_english#Codings
61 /*
62  * Send function for up to 64 bit
63  */
64 void IRsend::sendPulseDistance(unsigned int aHeaderMarkMicros, unsigned int aHeaderSpaceMicros, unsigned int aOneMarkMicros,
65  unsigned int aOneSpaceMicros, unsigned int aZeroMarkMicros, unsigned int aZeroSpaceMicros, uint32_t *aDecodedRawDataArray,
66  unsigned int aNumberOfBits, bool aMSBfirst, unsigned int aRepeatSpaceMillis, uint_fast8_t aNumberOfRepeats) {
67  // Set IR carrier frequency
68  enableIROut(38);
69 
70  // Header
71  mark(aHeaderMarkMicros);
72  space(aHeaderSpaceMicros);
73 
74  uint_fast8_t tNumberOfCommands = aNumberOfRepeats + 1;
75  uint_fast8_t tNumberOf32BitChunks = ((aNumberOfBits - 1) / 32) + 1;
76 
77  while (tNumberOfCommands > 0) {
78 
79  for (uint_fast8_t i = 0; i < tNumberOf32BitChunks; ++i) {
80  uint8_t tNumberOfBitsForOneSend;
81  if (aNumberOfBits > 32) {
82  tNumberOfBitsForOneSend = 32;
83  } else {
84  tNumberOfBitsForOneSend = aNumberOfBits;
85  }
86  sendPulseDistanceWidthData(aOneMarkMicros, aOneSpaceMicros, aZeroMarkMicros, aZeroSpaceMicros, aDecodedRawDataArray[i],
87  tNumberOfBitsForOneSend, aMSBfirst, (i == (tNumberOf32BitChunks - 1)));
88 
89  aNumberOfBits -= 32;
90  }
91 
92  tNumberOfCommands--;
93  // skip last delay!
94  if (tNumberOfCommands > 0) {
95  delay(aRepeatSpaceMillis);
96  }
97  }
99 }
100 
101 #if defined(DEBUG)
102 void printDurations(uint8_t aArray[], uint8_t aMaxIndex) {
103  for (uint_fast8_t i = 0; i <= aMaxIndex; i++) {
104  if (i % 10 == 0) {
105  if (i == 0) {
106  Serial.print(' '); // indentation for the 0
107  } else {
108  Serial.println();
109  }
110  Serial.print(i);
111  Serial.print(F(":"));
112  }
113  Serial.print(F(" | "));
114  Serial.print(aArray[i]);
115  }
116  Serial.println();
117 }
118 #endif
119 
120 /*
121  * @return false if more than 2 distinct duration values found
122  */
123 bool aggregateArrayCounts(uint8_t aArray[], uint8_t aMaxIndex, uint8_t *aShortIndex, uint8_t *aLongIndex) {
124  uint8_t tSum = 0;
125  uint16_t tWeightedSum = 0;
126  for (uint_fast8_t i = 0; i <= aMaxIndex; i++) {
127  uint8_t tCurrentDurations = aArray[i];
128  if (tCurrentDurations != 0) {
129  // Add it to sum and remove array content
130  tSum += tCurrentDurations;
131  tWeightedSum += (tCurrentDurations * i);
132  aArray[i] = 0;
133  }
134  if ((tCurrentDurations == 0 || i == aMaxIndex) && tSum != 0) {
135  // here we have a sum and a gap after the values
136  uint8_t tAggregateIndex = (tWeightedSum + (tSum / 2)) / tSum; // with rounding
137  aArray[tAggregateIndex] = tSum; // disabling this line increases code size by 2 - unbelievable!
138  // store aggregate for later decoding
139  if (*aShortIndex == 0) {
140  *aShortIndex = tAggregateIndex;
141  } else if (*aLongIndex == 0) {
142  *aLongIndex = tAggregateIndex;
143  } else {
144  // we have 3 bins => this is likely no pulse width or distance protocol. e.g. it can be RC5.
145  return false;
146  }
147  // initialize for next aggregation
148  tSum = 0;
149  tWeightedSum = 0;
150  }
151  }
152  return true;
153 }
154 
155 /*
156  * Try to decode a pulse width or pulse distance protocol.
157  * 1. Analyze all space and mark length
158  * 2. Decide if we have an pulse width or distance protocol
159  * 3. Try to decode with the mark and space data found in step 1
160  * No data and address decoding, only raw data as result.
161  */
163  uint8_t tDurationArray[DURATION_ARRAY_SIZE];
164 
165  /*
166  * Accept only protocols with at least 8 bits
167  */
168  if (decodedIRData.rawDataPtr->rawlen < (2 * 8) + 4) {
169  IR_DEBUG_PRINT(F("PULSE_DISTANCE: "));
170  IR_DEBUG_PRINT(F("Data length="));
172  IR_DEBUG_PRINTLN(F(" is less than 20"));
173  return false;
174  }
175 
176  uint_fast8_t i;
177 
178  // Reset duration array
179  memset(tDurationArray, 0, sizeof(tDurationArray));
180 
181  uint8_t tMaxDurationIndex = 0;
182  /*
183  * Count number of mark durations. Skip leading start and trailing stop bit.
184  */
185  for (i = 3; i < (uint_fast8_t) decodedIRData.rawDataPtr->rawlen - 2; i += 2) {
186  uint8_t tDurationTicks = decodedIRData.rawDataPtr->rawbuf[i];
187  if (tDurationTicks < sizeof(tDurationArray)) {
188  tDurationArray[tDurationTicks]++;
189  if (tMaxDurationIndex < tDurationTicks) {
190  tMaxDurationIndex = tDurationTicks;
191  }
192  }
193  }
194 
195  /*
196  * Aggregate mark counts to one duration bin
197  */
198  uint8_t tMarkTicksShort = 0;
199  uint8_t tMarkTicksLong = 0;
200  bool tSuccess = aggregateArrayCounts(tDurationArray, tMaxDurationIndex, &tMarkTicksShort, &tMarkTicksLong);
201 #if defined(DEBUG)
202  Serial.println(F("Mark:"));
203  printDurations(tDurationArray, tMaxDurationIndex);
204 #endif
205 
206  if (!tSuccess) {
207  IR_DEBUG_PRINT(F("PULSE_DISTANCE: "));
208  IR_DEBUG_PRINTLN(F("Mark aggregation failed, more than 2 distinct mark duration values found"));
209  }
210 
211  // Reset duration array
212  memset(tDurationArray, 0, sizeof(tDurationArray));
213 
214  /*
215  * Count number of space durations. Skip leading start and trailing stop bit.
216  */
217  tMaxDurationIndex = 0;
218  for (i = 4; i < (uint_fast8_t) decodedIRData.rawDataPtr->rawlen - 2; i += 2) {
219  uint8_t tDurationTicks = decodedIRData.rawDataPtr->rawbuf[i];
220  if (tDurationTicks < sizeof(tDurationArray)) {
221  tDurationArray[tDurationTicks]++;
222  if (tMaxDurationIndex < tDurationTicks) {
223  tMaxDurationIndex = tDurationTicks;
224  }
225  }
226  }
227 
228  /*
229  * Aggregate space counts to one duration bin
230  */
231  uint8_t tSpaceTicksShort = 0;
232  uint8_t tSpaceTicksLong = 0;
233  tSuccess = aggregateArrayCounts(tDurationArray, tMaxDurationIndex, &tSpaceTicksShort, &tSpaceTicksLong);
234 #if defined(DEBUG)
235  Serial.println(F("Space:"));
236  printDurations(tDurationArray, tMaxDurationIndex);
237 #endif
238 
239  if (!tSuccess) {
240  IR_DEBUG_PRINT(F("PULSE_DISTANCE: "));
241  IR_DEBUG_PRINTLN(F("Space aggregation failed, more than 2 distinct space duration values found"));
242  return false;
243  }
244 
245  // skip leading start and trailing stop bit.
246  uint16_t tNumberOfBits = (decodedIRData.rawDataPtr->rawlen / 2) - 2;
247  // Store data to reproduce frame for sending
248  decodedIRData.numberOfBits = tNumberOfBits;
250  decodedIRData.address = (tMarkTicksShort << 8) | tSpaceTicksLong;
251  decodedIRData.command = (tMarkTicksShort << 8) | tSpaceTicksShort;
252 
253  /*
254  * Print characteristics of this protocol. Durations are in ticks.
255  * Number of bits, start bit, start pause, short mark, long mark, short space, long space
256  *
257  * NEC: 32, 180, 90, 11, 0, 11, 34
258  * Samsung32: 32, 90, 90, 11, 0, 11, 34
259  * LG: 28, 180, 84, 10, 0, 11, 32
260  * JVC: 16, 168, 84, 10, 0, 10, 32
261  * Kaseikyo: 48. 69, 35, 9, 0, 9, 26
262  * Sony: 12|15|20, 48, 12, 12, 24, 12, 0 // the only known pulse width protocol
263  */
264  IR_DEBUG_PRINT(F("Protocol characteristics for a " STR(MICROS_PER_TICK) " us tick: "));
266  IR_DEBUG_PRINT(F(", "));
268  IR_DEBUG_PRINT(F(", "));
270  IR_DEBUG_PRINT(F(", "));
271  IR_DEBUG_PRINT(tMarkTicksShort);
272  IR_DEBUG_PRINT(F(", "));
273  IR_DEBUG_PRINT(tMarkTicksLong);
274  IR_DEBUG_PRINT(F(", "));
275  IR_DEBUG_PRINT(tSpaceTicksShort);
276  IR_DEBUG_PRINT(F(", "));
277  IR_DEBUG_PRINTLN(tSpaceTicksLong);
278 
279  uint8_t tStartIndex = 3;
280  uint8_t tNumberOfAdditionalLong = (tNumberOfBits - 1) / 32;
281 
282  /*
283  * decide, if we have an pulse width or distance protocol
284  */
285  if (tSpaceTicksLong > 0) {
286  // // check if last bit can be decoded as data or not, in this case take it as a stop bit
287  // if (decodePulseDistanceData(1, decodedIRData.rawDataPtr->rawlen - 3, tMarkTicksShort * MICROS_PER_TICK,
288  // tSpaceTicksLong * MICROS_PER_TICK, tSpaceTicksShort * MICROS_PER_TICK, DISTANCE_DO_MSB_DECODING)) {
289  // Serial.print(F("tNumberOfBits++ "));
290  // tNumberOfBits++;
291  // }
292 
294 
295  /*
296  * Here short and long space duration found. Decode in 32 bit chunks.
297  */
298  for (uint_fast8_t i = 0; i <= tNumberOfAdditionalLong; ++i) {
299  uint8_t tNumberOfBitsForOneDecode = tNumberOfBits;
300  if (tNumberOfBitsForOneDecode > 32) {
301  tNumberOfBitsForOneDecode = 32;
302  }
303  if (!decodePulseDistanceData(tNumberOfBitsForOneDecode, tStartIndex, tMarkTicksShort * MICROS_PER_TICK,
304  tSpaceTicksLong * MICROS_PER_TICK, tSpaceTicksShort * MICROS_PER_TICK,
305 #if defined(DISTANCE_DO_MSB_DECODING)
306  true
307 #else
308  false
309 #endif
310  )) {
311  IR_DEBUG_PRINT(F("PULSE_DISTANCE: "));
312  IR_DEBUG_PRINTLN(F("Decode failed"));
313  return false;
314  } else {
315  // fill array with decoded data
316  decodedIRData.decodedRawDataArray[i] = decodedIRData.decodedRawData;
317  tStartIndex += 64;
318  tNumberOfBits -= 32;
319  }
320  }
321 
322 
323  } else {
324  if (tMarkTicksLong == 0) {
325  IR_DEBUG_PRINT(F("PULSE_DISTANCE: "));
326  IR_DEBUG_PRINTLN(F("Only 1 distinct duration value for each space and mark found"));
327  return false;
328  }
329 
330 //#define SUPPORT_PULSE_WIDTH_DECODING
331 #if defined(SUPPORT_PULSE_WIDTH_DECODING) // The only known pulse width protocol is Sony
332 
333 // // check if last bit can be decoded as data or not, in this case take it as a stop bit
334 // if (decodePulseWidthData(1, decodedIRData.rawDataPtr->rawlen - 3, tMarkTicksLong * MICROS_PER_TICK,
335 // tMarkTicksShort * MICROS_PER_TICK, tSpaceTicksShort * MICROS_PER_TICK, DISTANCE_DO_MSB_DECODING)) {
336 // tNumberOfBits++;
337 // }
338  // decode without leading start bit. Currently only seen for sony protocol
339  for (uint_fast8_t i = 0; i <= tNumberOfAdditionalLong; ++i) {
340  uint8_t tNumberOfBitsForOneDecode = tNumberOfBits;
341  if (tNumberOfBitsForOneDecode > 32) {
342  tNumberOfBitsForOneDecode = 32;
343  }
344  if (!decodePulseWidthData(tNumberOfBitsForOneDecode, tStartIndex, tMarkTicksLong * MICROS_PER_TICK,
345  tMarkTicksShort * MICROS_PER_TICK, tSpaceTicksShort * MICROS_PER_TICK, DISTANCE_DO_MSB_DECODING)) {
346  IR_DEBUG_PRINT(F("PULSE_WIDTH: "));
347  IR_DEBUG_PRINTLN(F("Decode failed"));
348  return false;
349  }
350  tStartIndex += 64;
351  tNumberOfBits -= 32;
352  }
353 
354  // Store ticks used for decoding in extra
355  decodedIRData.extra = (tMarkTicksShort << 8) | tMarkTicksLong;
356  decodedIRData.protocol = PULSE_WIDTH;
357 #endif
358  }
359 
360 #if defined(DISTANCE_DO_MSB_DECODING)
362 #else
364 #endif
365 
366  return true;
367 }
368 
370 #endif // _IR_DISTANCE_HPP
IRData::address
uint16_t address
Decoded address, Distance protocol (OneMarkTicks << 8) | OneSpaceTicks.
Definition: IRremoteInt.h:152
MICROS_PER_TICK
#define MICROS_PER_TICK
microseconds per clock interrupt tick
Definition: IRremote.hpp:274
IRData::numberOfBits
uint16_t numberOfBits
Number of bits received for data (address + command + parity) - to determine protocol length if diffe...
Definition: IRremoteInt.h:155
IRsend::aNumberOfRepeats
void uint8_t uint_fast8_t aNumberOfRepeats
Definition: IRremoteInt.h:455
aggregateArrayCounts
bool aggregateArrayCounts(uint8_t aArray[], uint8_t aMaxIndex, uint8_t *aShortIndex, uint8_t *aLongIndex)
Definition: ir_DistanceProtocol.hpp:123
IRsend::sendPulseDistance
void sendPulseDistance(unsigned int aHeaderMarkMicros, unsigned int aHeaderSpaceMicros, unsigned int aOneMarkMicros, unsigned int aOneSpaceMicros, unsigned int aZeroMarkMicros, unsigned int aZeroSpaceMicros, uint32_t *aDecodedRawDataArray, unsigned int aNumberOfBits, bool aMSBfirst, unsigned int aRepeatSpaceMillis=110, uint_fast8_t aNumberOfRepeats=0)
Definition: ir_DistanceProtocol.hpp:64
DURATION_ARRAY_SIZE
#define DURATION_ARRAY_SIZE
Definition: ir_DistanceProtocol.hpp:48
IRsend::mark
void mark(unsigned int aMarkMicros)
Sends an IR mark for the specified number of microseconds.
Definition: IRSend.hpp:433
IRrecv::restartAfterSend
void restartAfterSend()
Restarts receiver after send.
Definition: IRReceive.hpp:161
IRDATA_FLAGS_IS_MSB_FIRST
#define IRDATA_FLAGS_IS_MSB_FIRST
Just for info. Value is mainly determined by the protocol.
Definition: IRremoteInt.h:140
IRData::rawDataPtr
irparams_struct * rawDataPtr
Pointer of the raw timing data to be decoded. Mainly the data buffer filled by receiving ISR.
Definition: IRremoteInt.h:161
IRDATA_FLAGS_EXTRA_INFO
#define IRDATA_FLAGS_EXTRA_INFO
there is extra info not contained in address and data (e.g. Kaseikyo unknown vendor ID)
Definition: IRremoteInt.h:137
PULSE_DISTANCE
@ PULSE_DISTANCE
Definition: IRProtocol.h:44
IR_DEBUG_PRINT
#define IR_DEBUG_PRINT(...)
If DEBUG, print the arguments, otherwise do nothing.
Definition: IRremoteInt.h:110
IRrecv::decodePulseDistanceData
bool decodePulseDistanceData(uint_fast8_t aNumberOfBits, uint_fast8_t aStartOffset, unsigned int aBitMarkMicros, unsigned int aOneSpaceMicros, unsigned int aZeroSpaceMicros, bool aMSBfirst)
Decode pulse distance protocols.
Definition: IRReceive.hpp:514
irparams_struct::rawlen
uint_fast8_t rawlen
counter of entries in rawbuf
Definition: IRremoteInt.h:93
IRrecv::decodedIRData
IRData decodedIRData
Definition: IRremoteInt.h:308
IRData::flags
uint8_t flags
See IRDATA_FLAGS_* definitions above.
Definition: IRremoteInt.h:156
IRrecv::decodePulseWidthData
bool decodePulseWidthData(uint_fast8_t aNumberOfBits, uint_fast8_t aStartOffset, unsigned int aOneMarkMicros, unsigned int aZeroMarkMicros, unsigned int aBitSpaceMicros, bool aMSBfirst)
Decode pulse width protocols.
Definition: IRReceive.hpp:414
IRData::decodedRawData
uint32_t decodedRawData
Up to 32 bit decoded raw data, to be used for send functions.
Definition: IRremoteInt.h:157
irparams_struct::rawbuf
unsigned int rawbuf[RAW_BUFFER_LENGTH]
raw data / tick counts per mark/space, first entry is the length of the gap between previous and curr...
Definition: IRremoteInt.h:97
IRData::command
uint16_t command
Decoded command, Distance protocol (ZeroMarkTicks << 8) | ZeroSpaceTicks.
Definition: IRremoteInt.h:153
IRsend::space
void space(unsigned int aSpaceMicros)
Sends an IR space for the specified number of microseconds.
Definition: IRSend.hpp:593
IRData::extra
uint16_t extra
Contains MagiQuest magnitude, Kaseikyo unknown vendor ID and Distance protocol (HeaderMarkTicks << 8)...
Definition: IRremoteInt.h:154
IR_DEBUG_PRINTLN
#define IR_DEBUG_PRINTLN(...)
If DEBUG, print the arguments as a line, otherwise do nothing.
Definition: IRremoteInt.h:114
IRrecv::decodeDistance
bool decodeDistance()
Definition: ir_DistanceProtocol.hpp:162
IRData::protocol
decode_type_t protocol
UNKNOWN, NEC, SONY, RC5, ...
Definition: IRremoteInt.h:151
IRsend::sendPulseDistanceWidthData
void sendPulseDistanceWidthData(unsigned int aOneMarkMicros, unsigned int aOneSpaceMicros, unsigned int aZeroMarkMicros, unsigned int aZeroSpaceMicros, uint32_t aData, uint_fast8_t aNumberOfBits, bool aMSBfirst, bool aSendStopBit=false)
Sends PulseDistance data The output always ends with a space.
Definition: IRSend.hpp:345
IRremoteInt.h
Contains all declarations required for the interface to IRremote. Could not be named IRremote....
IrReceiver
IRrecv IrReceiver
The receiver instance.
Definition: IRReceive.hpp:42
IRsend::enableIROut
void enableIROut(uint_fast8_t aFrequencyKHz)
Enables IR output.
Definition: IRSend.hpp:617