IRremote
ir_DistanceWidthProtocol.hpp
Go to the documentation of this file.
1 /*
2  * ir_DistanceWidthProtocol.hpp
3  *
4  * Contains only the decoder functions for universal pulse width or pulse distance protocols!
5  * The send functions are used by almost all protocols and therefore in IRSend.hh.
6  *
7  * This decoder tries to decode a pulse distance or pulse distance width with constant period (or pulse width - not enabled yet) protocol.
8  * 1. Analyze all space and mark length
9  * 2. Decide which protocol we have
10  * 3. Try to decode with the mark and space data found in step 1
11  * 4. Assume one start bit / header and one stop bit, since pulse distance data must have a stop bit!
12  * No data and address decoding, only raw data as result.
13  *
14  * Pulse distance data can be sent with the generic function as in SendDemo example line 155:
15  * https://github.com/Arduino-IRremote/Arduino-IRremote/blob/d51b540cb2ddf1424888d2d9e6b62fe1ef46859d/examples/SendDemo/SendDemo.ino#L155
16  * void sendPulseDistanceWidthData(unsigned int aOneMarkMicros, unsigned int aOneSpaceMicros, unsigned int aZeroMarkMicros,
17  * unsigned int aZeroSpaceMicros, uint32_t aData, uint8_t aNumberOfBits, bool aMSBfirst, bool aSendStopBit = false)
18  * The header must be sent manually with:
19  * IrSender.mark(MarkMicros)
20  * IrSender.space(SpaceMicros);
21  *
22  * Or send it by filling a DecodedRawDataArray and with the sendPulseDistanceWidthFromArray() function as in SendDemo example line 175:
23  * https://github.com/Arduino-IRremote/Arduino-IRremote/blob/d51b540cb2ddf1424888d2d9e6b62fe1ef46859d/examples/SendDemo/SendDemo.ino#L175
24  * sendPulseDistanceWidthFromArray(uint_fast8_t aFrequencyKHz, unsigned int aHeaderMarkMicros,
25  * unsigned int aHeaderSpaceMicros, unsigned int aOneMarkMicros, unsigned int aOneSpaceMicros, unsigned int aZeroMarkMicros,
26  * unsigned int aZeroSpaceMicros, uint32_t *aDecodedRawDataArray, unsigned int aNumberOfBits, bool aMSBFirst,
27  * bool aSendStopBit, unsigned int aRepeatPeriodMillis, int_fast8_t aNumberOfRepeats)
28  *
29  * This file is part of Arduino-IRremote https://github.com/Arduino-IRremote/Arduino-IRremote.
30  *
31  ************************************************************************************
32  * MIT License
33  *
34  * Copyright (c) 2022 Armin Joachimsmeyer
35  *
36  * Permission is hereby granted, free of charge, to any person obtaining a copy
37  * of this software and associated documentation files (the "Software"), to deal
38  * in the Software without restriction, including without limitation the rights
39  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
40  * copies of the Software, and to permit persons to whom the Software is furnished
41  * to do so, subject to the following conditions:
42  *
43  * The above copyright notice and this permission notice shall be included in all
44  * copies or substantial portions of the Software.
45  *
46  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
47  * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
48  * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
49  * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
50  * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
51  * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
52  *
53  ************************************************************************************
54  */
55 #ifndef _IR_DISTANCE_WIDTH_HPP
56 #define _IR_DISTANCE_WIDTH_HPP
57 
58 #if defined(DEBUG) && !defined(LOCAL_DEBUG)
59 #define LOCAL_DEBUG
60 #else
61 //#define LOCAL_DEBUG // This enables debug output only for this file
62 #endif
63 
64 // accept durations up to 50 * 50 (MICROS_PER_TICK) 2500 microseconds
65 #define DURATION_ARRAY_SIZE 50
66 
67 // Switch the decoding according to your needs
68 //#define DISTANCE_DO_MSB_DECODING // If active, it resembles the JVC + Denon, otherwise LSB first as e.g. for NEC and Kaseikyo/Panasonic
69 
73 // see: https://www.mikrocontroller.net/articles/IRMP_-_english#Codings
74 #if defined(LOCAL_DEBUG)
75 void printDurations(uint8_t aArray[], uint8_t aMaxIndex) {
76  for (uint_fast8_t i = 0; i <= aMaxIndex; i++) {
77  if (i % 10 == 0) {
78  if (i == 0) {
79  Serial.print(' '); // indentation for the 0
80  } else {
81  Serial.println();
82  }
83  Serial.print(i);
84  Serial.print(F(": "));
85  }
86  Serial.print(aArray[i]);
87  if (aArray[i] != 0) {
88  Serial.print(' ');
89  Serial.print(i * (uint16_t)MICROS_PER_TICK);
90  }
91  Serial.print(F(" | "));
92  }
93  Serial.println();
94 }
95 #endif
96 
97 /*
98  * @return false if more than 2 distinct duration values found
99  */
100 bool aggregateArrayCounts(uint8_t aArray[], uint8_t aMaxIndex, uint8_t *aShortIndex, uint8_t *aLongIndex) {
101  uint8_t tSum = 0;
102  uint16_t tWeightedSum = 0;
103  for (uint_fast8_t i = 0; i <= aMaxIndex; i++) {
104  uint8_t tCurrentDurations = aArray[i];
105  if (tCurrentDurations != 0) {
106  // Add it to sum and remove array content
107  tSum += tCurrentDurations;
108  tWeightedSum += (tCurrentDurations * i);
109  aArray[i] = 0;
110  }
111  if ((tCurrentDurations == 0 || i == aMaxIndex) && tSum != 0) {
112  // here we have a sum and a gap after the values
113  uint8_t tAggregateIndex = (tWeightedSum + (tSum / 2)) / tSum; // with rounding
114  aArray[tAggregateIndex] = tSum; // disabling this line increases code size by 2 - unbelievable!
115  // store aggregate for later decoding
116  if (*aShortIndex == 0) {
117  *aShortIndex = tAggregateIndex;
118  } else if (*aLongIndex == 0) {
119  *aLongIndex = tAggregateIndex;
120  } else {
121  // we have 3 bins => this is likely no pulse width or distance protocol. e.g. it can be RC5.
122  return false;
123  }
124  // initialize for next aggregation
125  tSum = 0;
126  tWeightedSum = 0;
127  }
128  }
129  return true;
130 }
131 
132 /*
133  * Try to decode a pulse distance or pulse width protocol.
134  * 1. Analyze all space and mark length
135  * 2. Decide if we have an pulse width or distance protocol
136  * 3. Try to decode with the mark and space data found in step 1
137  * No data and address decoding, only raw data as result.
138  */
140  uint8_t tDurationArray[DURATION_ARRAY_SIZE]; // For up to 49 ticks / 2450 us
141 
142  /*
143  * Accept only protocols with at least 8 bits
144  */
145  if (decodedIRData.rawDataPtr->rawlen < (2 * 8) + 4) {
146  IR_DEBUG_PRINT(F("PULSE_DISTANCE_WIDTH: "));
147  IR_DEBUG_PRINT(F("Data length="));
149  IR_DEBUG_PRINTLN(F(" is less than 20"));
150  return false;
151  }
152 
153  uint_fast8_t i;
154 
155  // Reset duration array
156  memset(tDurationArray, 0, DURATION_ARRAY_SIZE);
157 
158  uint8_t tIndexOfMaxDuration = 0;
159  /*
160  * Count number of mark durations up to 49 ticks. Skip leading start and trailing stop bit.
161  */
162  for (i = 3; i < (uint_fast8_t) decodedIRData.rawDataPtr->rawlen - 2; i += 2) {
163  auto tDurationTicks = decodedIRData.rawDataPtr->rawbuf[i];
164  if (tDurationTicks < DURATION_ARRAY_SIZE) {
165  tDurationArray[tDurationTicks]++; // count duration if less than DURATION_ARRAY_SIZE (50)
166  if (tIndexOfMaxDuration < tDurationTicks) {
167  tIndexOfMaxDuration = tDurationTicks;
168  }
169  } else {
170 #if defined(LOCAL_DEBUG)
171  Serial.print(F("PULSE_DISTANCE_WIDTH: "));
172  Serial.print(F("Mark "));
173  Serial.print(tDurationTicks * MICROS_PER_TICK);
174  Serial.print(F(" is longer than "));
175  Serial.print(DURATION_ARRAY_SIZE * MICROS_PER_TICK);
176  Serial.print(F(" us. Index="));
177  Serial.println(i);
178 #endif
179  return false;
180  }
181  }
182 
183  /*
184  * Aggregate mark counts to one duration bin
185  */
186  uint8_t tMarkTicksShort = 0;
187  uint8_t tMarkTicksLong = 0;
188  bool tSuccess = aggregateArrayCounts(tDurationArray, tIndexOfMaxDuration, &tMarkTicksShort, &tMarkTicksLong);
189 #if defined(LOCAL_DEBUG)
190  Serial.println(F("Mark:"));
191  printDurations(tDurationArray, tIndexOfMaxDuration);
192 #endif
193 
194  if (!tSuccess) {
195 #if defined(LOCAL_DEBUG)
196  Serial.print(F("PULSE_DISTANCE_WIDTH: "));
197  Serial.println(F("Mark aggregation failed, more than 2 distinct mark duration values found"));
198 #endif
199  return false;
200  }
201 
202  // Reset duration array
203  memset(tDurationArray, 0, DURATION_ARRAY_SIZE);
204 
205  /*
206  * Count number of space durations. Skip leading start and trailing stop bit.
207  */
208  tIndexOfMaxDuration = 0;
209  for (i = 4; i < (uint_fast8_t) decodedIRData.rawDataPtr->rawlen - 2; i += 2) {
210  auto tDurationTicks = decodedIRData.rawDataPtr->rawbuf[i];
211  if (tDurationTicks < DURATION_ARRAY_SIZE) {
212  tDurationArray[tDurationTicks]++;
213  if (tIndexOfMaxDuration < tDurationTicks) {
214  tIndexOfMaxDuration = tDurationTicks;
215  }
216  } else {
217 #if defined(LOCAL_DEBUG)
218  Serial.print(F("PULSE_DISTANCE_WIDTH: "));
219  Serial.print(F("Space "));
220  Serial.print(tDurationTicks * MICROS_PER_TICK);
221  Serial.print(F(" is longer than "));
222  Serial.print(DURATION_ARRAY_SIZE * MICROS_PER_TICK);
223  Serial.print(F(" us. Index="));
224  Serial.println(i);
225 #endif
226  return false;
227  }
228  }
229 
230  /*
231  * Aggregate space counts to one duration bin
232  */
233  uint8_t tSpaceTicksShort = 0;
234  uint8_t tSpaceTicksLong = 0;
235  tSuccess = aggregateArrayCounts(tDurationArray, tIndexOfMaxDuration, &tSpaceTicksShort, &tSpaceTicksLong);
236 #if defined(LOCAL_DEBUG)
237  Serial.println(F("Space:"));
238  printDurations(tDurationArray, tIndexOfMaxDuration);
239 #endif
240 
241  if (!tSuccess) {
242 #if defined(LOCAL_DEBUG)
243  Serial.print(F("PULSE_DISTANCE_WIDTH: "));
244  Serial.println(F("Space aggregation failed, more than 2 distinct space duration values found"));
245 #endif
246  return false;
247  }
248 
249  /*
250  * Print characteristics of this protocol. Durations are in ticks.
251  * Number of bits, start bit, start pause, short mark, long mark, short space, long space
252  *
253  * NEC: 32, 180, 90, 11, 0, 11, 34
254  * Samsung32: 32, 90, 90, 11, 0, 11, 34
255  * LG: 28, 180, 84, 10, 0, 11, 32
256  * JVC: 16, 168, 84, 10, 0, 10, 32
257  * Kaseikyo: 48. 69, 35, 9, 0, 9, 26
258  * Sony: 12|15|20, 48, 12, 12, 24, 12, 0 // the only known pulse width protocol
259  */
260 #if defined(LOCAL_DEBUG)
261  Serial.print(F("ProtocolConstants: "));
262  Serial.print(decodedIRData.rawDataPtr->rawbuf[1] * MICROS_PER_TICK);
263  Serial.print(F(", "));
264  Serial.print(decodedIRData.rawDataPtr->rawbuf[2] * MICROS_PER_TICK);
265  Serial.print(F(", "));
266  Serial.print(tMarkTicksShort * MICROS_PER_TICK);
267  Serial.print(F(", "));
268  Serial.print(tSpaceTicksLong * MICROS_PER_TICK);
269  Serial.print(F(", "));
270  if (tMarkTicksLong == 0) {
271  Serial.print(tMarkTicksShort * MICROS_PER_TICK);
272  } else {
273  Serial.print(tMarkTicksLong * MICROS_PER_TICK);
274  }
275  Serial.print(F(", "));
276  Serial.print(tSpaceTicksShort * MICROS_PER_TICK);
277  Serial.println();
278 #endif
279  uint8_t tStartIndex = 3;
280  // skip leading start bit for decoding.
281  uint16_t tNumberOfBits = (decodedIRData.rawDataPtr->rawlen / 2) - 1;
282  if (tSpaceTicksLong > 0 && tMarkTicksLong == 0) {
283  // For PULSE DISTANCE a stop bit is mandatory, for PULSE_DISTANCE_WIDTH it is not required!
284  tNumberOfBits--; // Correct for stop bit
285  }
286  decodedIRData.numberOfBits = tNumberOfBits;
287 #if __INT_WIDTH__ < 32
288  uint8_t tNumberOfAdditionalArrayValues = (tNumberOfBits - 1) / 32;
289 #else
290  uint8_t tNumberOfAdditionalArrayValues = (tNumberOfBits - 1) / 64;
291 #endif
292 
293  /*
294  * We can have the following protocol timings
295  * Pulse distance: Pulses/marks are constant, pause/spaces have different length, like NEC.
296  * Pulse width: Pulses/marks have different length, pause/spaces are constant, like Sony.
297  * Pulse distance width: Pulses/marks and pause/spaces have different length, often the bit length is constant, like MagiQuest.
298  * Pulse distance width can be decoded by pulse width decoder, if this decoder does not check the length of pause/spaces.
299  */
300 
301  if (tMarkTicksLong == 0 && tSpaceTicksLong == 0) {
302 #if defined(LOCAL_DEBUG)
303  Serial.print(F("PULSE_DISTANCE: "));
304  Serial.println(F("Only 1 distinct duration value for each space and mark found"));
305 #endif
306  return false;
307  }
308 #if defined DECODE_STRICT_CHECKS
309  if(tMarkTicksLong > 0 && tSpaceTicksLong > 0) {
310  // We have different mark and space length here, so signal decodePulseDistanceWidthData() not to check against constant lenght decodePulseDistanceWidthData
311  tSpaceTicksShort = 0;
312  }
313 #endif
314 
315  for (uint_fast8_t i = 0; i <= tNumberOfAdditionalArrayValues; ++i) {
316  uint8_t tNumberOfBitsForOneDecode = tNumberOfBits;
317  /*
318  * Decode in 32/64 bit chunks. Only the last chunk can contain less than 32/64 bits
319  */
320 #if __INT_WIDTH__ < 32
321  if (tNumberOfBitsForOneDecode > 32) {
322  tNumberOfBitsForOneDecode = 32;
323  }
324 #else
325  if (tNumberOfBitsForOneDecode > 64) {
326  tNumberOfBitsForOneDecode = 64;
327  }
328 #endif
329  bool tResult;
330  if (tMarkTicksLong > 0) {
331  /*
332  * Here short and long mark durations found.
333  */
335  tResult = decodePulseDistanceWidthData(tNumberOfBitsForOneDecode, tStartIndex, tMarkTicksLong * MICROS_PER_TICK,
336  tMarkTicksShort * MICROS_PER_TICK, tSpaceTicksShort * MICROS_PER_TICK, 0,
337 #if defined(DISTANCE_DO_MSB_DECODING)
338  true
339 #else
340  false
341 #endif
342  );
343  } else {
344  /*
345  * Here short and long space durations found.
346  */
348  tResult = decodePulseDistanceWidthData(tNumberOfBitsForOneDecode, tStartIndex, tMarkTicksShort * MICROS_PER_TICK,
349  tMarkTicksShort * MICROS_PER_TICK, tSpaceTicksLong * MICROS_PER_TICK, tSpaceTicksShort * MICROS_PER_TICK,
350 #if defined(DISTANCE_DO_MSB_DECODING)
351  true
352 #else
353  false
354 #endif
355  );
356  }
357  if (!tResult) {
358 #if defined(LOCAL_DEBUG)
359  Serial.print(F("PULSE_WIDTH: "));
360  Serial.println(F("Decode failed"));
361 #endif
362  return false;
363  }
364 #if defined(LOCAL_DEBUG)
365  Serial.print(F("PULSE_WIDTH: "));
366  Serial.print(F("decodedRawData=0x"));
367  Serial.println(decodedIRData.decodedRawData);
368 #endif
369  // fill array with decoded data
370  decodedIRData.decodedRawDataArray[i] = decodedIRData.decodedRawData;
371 #if __INT_WIDTH__ < 32
372  tStartIndex += 64;
373  tNumberOfBits -= 32;
374 #else
375  tStartIndex += 128;
376  tNumberOfBits -= 64;
377 #endif
378  }
379 
380 #if defined(DISTANCE_DO_MSB_DECODING)
382 #else
384 #endif
385 // Store data to reproduce frame for sending
387  if (tMarkTicksLong == 0) {
388  decodedIRData.address = (tMarkTicksShort << 8) | tSpaceTicksLong;
389  } else {
390  decodedIRData.address = (tMarkTicksLong << 8) | tSpaceTicksLong;
391  }
392  decodedIRData.command = (tMarkTicksShort << 8) | tSpaceTicksShort;
393 
394  return true;
395 }
396 
398 #if defined(LOCAL_DEBUG)
399 #undef LOCAL_DEBUG
400 #endif
401 #endif // _IR_DISTANCE_WIDTH_HPP
IRData::address
uint16_t address
Decoded address, Distance protocol (tMarkTicksLong (if tMarkTicksLong == 0, then tMarkTicksShort) << ...
Definition: IRProtocol.h:117
MICROS_PER_TICK
#define MICROS_PER_TICK
microseconds per clock interrupt tick
Definition: IRremote.hpp:258
IRData::numberOfBits
uint16_t numberOfBits
Number of bits received for data (address + command + parity) - to determine protocol length if diffe...
Definition: IRProtocol.h:120
aggregateArrayCounts
bool aggregateArrayCounts(uint8_t aArray[], uint8_t aMaxIndex, uint8_t *aShortIndex, uint8_t *aLongIndex)
Definition: ir_DistanceWidthProtocol.hpp:100
IRrecv::decodeDistanceWidth
bool decodeDistanceWidth()
Definition: ir_DistanceWidthProtocol.hpp:139
PULSE_WIDTH
@ PULSE_WIDTH
Definition: IRProtocol.h:42
IRData::rawDataPtr
irparams_struct * rawDataPtr
Pointer of the raw timing data to be decoded. Mainly the OverflowFlag and the data buffer filled by r...
Definition: IRProtocol.h:126
IRData::decodedRawData
IRRawDataType decodedRawData
Up to 32/64 bit decoded raw data, to be used for send functions.
Definition: IRProtocol.h:122
PULSE_DISTANCE
@ PULSE_DISTANCE
Definition: IRProtocol.h:43
IR_DEBUG_PRINT
#define IR_DEBUG_PRINT(...)
If DEBUG, print the arguments, otherwise do nothing.
Definition: IRremoteInt.h:133
irparams_struct::rawlen
uint_fast8_t rawlen
counter of entries in rawbuf
Definition: IRremoteInt.h:109
IRrecv::decodePulseDistanceWidthData
bool decodePulseDistanceWidthData(PulseDistanceWidthProtocolConstants *aProtocolConstants, uint_fast8_t aNumberOfBits, uint_fast8_t aStartOffset=3)
Decode pulse distance protocols for PulseDistanceWidthProtocolConstants.
Definition: IRReceive.hpp:768
IRrecv::decodedIRData
IRData decodedIRData
Definition: IRremoteInt.h:305
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: IRProtocol.h:98
IRData::flags
uint8_t flags
See IRDATA_FLAGS_* definitions above.
Definition: IRProtocol.h:121
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:113
IRData::command
uint16_t command
Decoded command, Distance protocol (tMarkTicksShort << 8) | tSpaceTicksShort.
Definition: IRProtocol.h:118
IRDATA_FLAGS_IS_MSB_FIRST
#define IRDATA_FLAGS_IS_MSB_FIRST
Value is mainly determined by the (known) protocol.
Definition: IRProtocol.h:100
DURATION_ARRAY_SIZE
#define DURATION_ARRAY_SIZE
Definition: ir_DistanceWidthProtocol.hpp:65
IRData::extra
uint16_t extra
Contains upper 16 bit of Magiquest WandID, Kaseikyo unknown vendor ID and Distance protocol (HeaderMa...
Definition: IRProtocol.h:119
IR_DEBUG_PRINTLN
#define IR_DEBUG_PRINTLN(...)
If DEBUG, print the arguments as a line, otherwise do nothing.
Definition: IRremoteInt.h:137
IRData::protocol
decode_type_t protocol
UNKNOWN, NEC, SONY, RC5, PULSE_DISTANCE, ...
Definition: IRProtocol.h:116
if
if(irparams.TickCounterForISR< UINT16_MAX)
Definition: IRReceive.hpp:147