Skip to content

Commit

Permalink
Experimental support for AIRWELL protocol. (crankyoldgit#1070)
Browse files Browse the repository at this point in the history
* Add `matchManchester()` to IRrecv class.
* Add `sendManchester()` to IRsend class.
* Add `sendAirwell()` & `decodeAirwell`.
* Update support functions.
* Add unit tests.

Fixes crankyoldgit#1069
  • Loading branch information
crankyoldgit authored Apr 4, 2020
1 parent a98de7f commit 75726b2
Show file tree
Hide file tree
Showing 14 changed files with 827 additions and 7 deletions.
5 changes: 4 additions & 1 deletion SupportedProtocols.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
<!--- WARNING: Do NOT edit this file directly.
It is generated by './tools/scrape_supported_devices.py'.
Last generated: Wed Mar 25 11:38:37 2020 --->
Last generated: Sat Mar 28 15:56:36 2020 --->
# IR Protocols supported by this library

| Protocol | Brand | Model | A/C Model | Detailed A/C Support |
| --- | --- | --- | --- | --- |
| [Airwell](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Airwell.cpp) | **Airwell** | RC08W remote | | - |
| [Aiwa](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Aiwa.cpp) | **Aiwa** | RC-T501 RCU | | - |
| [Amcor](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Amcor.cpp) | **[Amcor](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Amcor.h)** | ADR-853H A/C<BR>ADR-853H A/C<BR>TAC-444 remote<BR>TAC-444 remote<BR>TAC-495 remote<BR>TAC-495 remote | | Yes |
| [Argo](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Argo.cpp) | **[Argo](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Argo.h)** | Ulisse 13 DCI Mobile Split A/C | | Yes |
| [Carrier](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Carrier.cpp) | **Carrier/Surrey** | 42QG5A55970 remote<BR>53NGK009/012 Inverter<BR>619EGX0090E0 A/C<BR>619EGX0120E0 A/C<BR>619EGX0180E0 A/C<BR>619EGX0220E0 A/C | | - |
| [Coolix](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Coolix.cpp) | **[Airwell](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Coolix.h)** | RC08B remote | | Yes |
| [Coolix](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Coolix.cpp) | **[Beko](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Coolix.h)** | BINR 070/071 split-type A/C<BR>BINR 070/071 split-type A/C<BR>RG57K7(B)/BGEF Remote<BR>RG57K7(B)/BGEF Remote | | Yes |
| [Coolix](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Coolix.cpp) | **[Midea](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Coolix.h)** | MS12FU-10HRDN1-QRD0GW(B) A/C<BR>MS12FU-10HRDN1-QRD0GW(B) A/C<BR>MSABAU-07HRFN1-QRD0GW A/C (circa 2016)<BR>MSABAU-07HRFN1-QRD0GW A/C (circa 2016)<BR>RG52D/BGE Remote<BR>RG52D/BGE Remote | | Yes |
| [Coolix](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Coolix.cpp) | **[Tokio](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Coolix.h)** | AATOEMF17-12CHR1SW split-type RG51\|50/BGE Remote<BR>AATOEMF17-12CHR1SW split-type RG51\|50/BGE Remote | | Yes |
Expand Down Expand Up @@ -82,6 +84,7 @@

## Send & decodable protocols:

- AIRWELL
- AIWA_RC_T501
- AMCOR
- ARGO
Expand Down
166 changes: 166 additions & 0 deletions src/IRrecv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,10 @@ bool IRrecv::decode(decode_results *results, irparams_t *save,
DPRINTLN("Attempting Daikin64 decode");
if (decodeDaikin64(results, offset)) return true;
#endif // DECODE_DAIKIN64
#if DECODE_AIRWELL
DPRINTLN("Attempting Airwell decode");
if (decodeAirwell(results, offset)) return true;
#endif // DECODE_AIRWELL
// Typically new protocols are added above this line.
}
#if DECODE_HASH
Expand Down Expand Up @@ -1292,4 +1296,166 @@ uint16_t IRrecv::matchGeneric(volatile uint16_t *data_ptr,
zeromark, zerospace, footermark, footerspace, atleast,
tolerance, excess, MSBfirst);
}

// Match & decode a Manchester Code <= 64bit IR message.
// The data is stored at result_ptr.
// Values of 0 for hdrmark, hdrspace, footermark, or footerspace mean skip
// that requirement.
//
// Args:
// data_ptr: A pointer to where we are at in the capture buffer.
// NOTE: It is assumed to be pointing to a "Mark", not a "Space".
// result_ptr: A pointer to where to start storing the bits we decoded.
// remaining: The size of the capture buffer are remaining.
// nbits: Nr. of data bits we expect.
// hdrmark: Nr. of uSeconds for the expected header mark signal.
// hdrspace: Nr. of uSeconds for the expected header space signal.
// half_period: Nr. of uSeconds for half the clock's period. (1/2 wavelength)
// footermark: Nr. of uSeconds for the expected footer mark signal.
// footerspace: Nr. of uSeconds for the expected footer space/gap signal.
// atleast: Is the match on the footerspace a matchAtLeast or matchSpace?
// tolerance: Percentage error margin to allow. (Def: kUseDefTol)
// excess: Nr. of useconds. (Def: kMarkExcess)
// MSBfirst: Bit order to save the data in. (Def: true)
// GEThomas: Use G.E. Thomas (true/default) or IEEE 802.3 (false) convention?
// Returns:
// A uint16_t: If successful, how many buffer entries were used. Otherwise 0.
//
// Ref:
// https://en.wikipedia.org/wiki/Manchester_code
// http://ww1.microchip.com/downloads/en/AppNotes/Atmel-9164-Manchester-Coding-Basics_Application-Note.pdf
uint16_t IRrecv::matchManchester(volatile const uint16_t *data_ptr,
uint64_t *result_ptr,
const uint16_t remaining,
const uint16_t nbits,
const uint16_t hdrmark,
const uint32_t hdrspace,
const uint16_t half_period,
const uint16_t footermark,
const uint32_t footerspace,
const bool atleast,
const uint8_t tolerance,
const int16_t excess,
const bool MSBfirst,
const bool GEThomas) {
uint16_t offset = 0;
uint64_t data = 0;
uint16_t nr_of_half_periods = GEThomas;
// 2 per bit, and 4 extra for the timing sync.
uint16_t expected_half_periods = 2 * nbits + 4;
bool currentBit = false;

// Calculate how much remaining buffer is required.
// Shortest case. Longest case is 2 * nbits.
uint16_t min_remaining = nbits + 2;

if (hdrmark) min_remaining++;
if (hdrspace) min_remaining++;
if (footermark) min_remaining++;
// Don't need to extend for footerspace because it could be the end of message

// Check if there is enough capture buffer to possibly have the message.
if (remaining < min_remaining) return 0; // Nope, so abort.

// Header
if (hdrmark && !matchMark(*(data_ptr + offset++), hdrmark, tolerance, excess))
return 0;
// Manchester Code always has a guaranteed 2x half_period (T2) at the start
// of the data section. e.g. a sync header. If it is a GEThomas-style, then
// it is space(T);mark(2xT);space(T), thus we need to check for that space
// plus any requested "header" space.
if ((hdrspace || GEThomas) &&
!matchSpace(*(data_ptr + offset++),
hdrspace + ((GEThomas) ? half_period : 0), tolerance, excess))
return 0;

// Data
// Loop until we find a 'long' pulse. This is the timing sync per protocol.
while ((offset < remaining) && (nr_of_half_periods < expected_half_periods) &&
!match(*(data_ptr + offset), half_period * 2, tolerance, excess)) {
// Was it not a short pulse?
if (!match(*(data_ptr + offset), half_period, tolerance, excess))
return 0;
nr_of_half_periods++;
offset++;
}

// Data (cont.)

// We are now pointing to the first 'long' pulse.
// Loop through the buffer till we run out of buffer, or nr of half periods.
while (offset < remaining && nr_of_half_periods < expected_half_periods) {
// Only if there is enough half_periods left for a long pulse &
// Is it a 'long' pulse?
if (nr_of_half_periods < expected_half_periods - 1 &&
match(*(data_ptr + offset), half_period * 2, tolerance, excess)) {
// Yes, so invert the value we will append.
currentBit = !currentBit;
nr_of_half_periods += 2; // A 'long' pulse is two half periods.
offset++;
// Append the bit value.
data <<= 1;
data |= currentBit;
} else if (match(*(data_ptr + offset), half_period, tolerance, excess)) {
// or is it part of a 'short' pulse pair?
nr_of_half_periods++;
offset++;
// Look for the second half of the 'short' pulse pair.
// Do we have enough buffer or nr of half periods?
if (offset < remaining && nr_of_half_periods < expected_half_periods) {
// We do, so look for it.
if (match(*(data_ptr + offset), half_period, tolerance, excess)) {
// Found it!
nr_of_half_periods++;
// No change of the polarity of the bit we will append.
// Append the bit value.
data <<= 1;
data |= currentBit;
offset++;
} else {
// It's not what we expected.
return 0;
}
}
} else if (nr_of_half_periods == expected_half_periods - 1 &&
matchAtLeast(*(data_ptr + offset), half_period, tolerance,
excess)) {
// Special case when we are at the end of the expected nr of periods.
// i.e. The pulse could be merged with the footer.
nr_of_half_periods++;
break;
} else {
// It's neither, so abort.
return 0;
}
}
// Did we collect the expected amount of data?
if (nr_of_half_periods < expected_half_periods) return 0;

// Footer
if (footermark &&
!(matchMark(*(data_ptr + offset), footermark + half_period,
tolerance, excess) ||
matchMark(*(data_ptr + offset), footermark,
tolerance, excess)))
return 0;
offset++;
// If we have something still to match & haven't reached the end of the buffer
if (footerspace && offset < remaining) {
if (atleast) {
if (!matchAtLeast(*(data_ptr + offset), footerspace, tolerance, excess))
return 0;
} else {
if (!matchSpace(*(data_ptr + offset), footerspace, tolerance, excess))
return 0;
}
offset++;
}

// Clean up and process the data.
if (!MSBfirst) data = reverseBits(data, nbits);
// Trim the data to size to remove timing sync.
*result_ptr = GETBITS64(data, 0, nbits);
return offset;
}
// End of IRrecv class -------------------
19 changes: 19 additions & 0 deletions src/IRrecv.h
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,20 @@ class IRrecv {
const uint8_t tolerance = kUseDefTol,
const int16_t excess = kMarkExcess,
const bool MSBfirst = true);
uint16_t matchManchester(volatile const uint16_t *data_ptr,
uint64_t *result_ptr,
const uint16_t remaining,
const uint16_t nbits,
const uint16_t hdrmark,
const uint32_t hdrspace,
const uint16_t clock_period,
const uint16_t footermark,
const uint32_t footerspace,
const bool atleast = false,
const uint8_t tolerance = kUseDefTol,
const int16_t excess = kMarkExcess,
const bool MSBfirst = true,
const bool GEThomas = true);
void crudeNoiseFilter(decode_results *results, const uint16_t floor = 0);
bool decodeHash(decode_results *results);
#if (DECODE_NEC || DECODE_SHERWOOD || DECODE_AIWA_RC_T501 || SEND_SANYO)
Expand Down Expand Up @@ -574,6 +588,11 @@ class IRrecv {
const uint16_t nbits = kSymphonyBits,
const bool strict = true);
#endif // DECODE_SYMPHONY
#if DECODE_AIRWELL
bool decodeAirwell(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kAirwellBits,
const bool strict = true);
#endif // DECODE_AIRWELL
};

#endif // IRRECV_H_
12 changes: 11 additions & 1 deletion src/IRremoteESP8266.h
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,13 @@
#define SEND_DAIKIN64 _IR_ENABLE_DEFAULT_
#endif // SEND_DAIKIN64

#ifndef DECODE_AIRWELL
#define DECODE_AIRWELL _IR_ENABLE_DEFAULT_
#endif // DECODE_AIRWELL
#ifndef SEND_AIRWELL
#define SEND_AIRWELL _IR_ENABLE_DEFAULT_
#endif // SEND_AIRWELL

#if (DECODE_ARGO || DECODE_DAIKIN || DECODE_FUJITSU_AC || DECODE_GREE || \
DECODE_KELVINATOR || DECODE_MITSUBISHI_AC || DECODE_TOSHIBA_AC || \
DECODE_TROTEC || DECODE_HAIER_AC || DECODE_HITACHI_AC || \
Expand Down Expand Up @@ -719,14 +726,17 @@ enum decode_type_t {
SYMPHONY,
HITACHI_AC3,
DAIKIN64,
AIRWELL,
// Add new entries before this one, and update it to point to the last entry.
kLastDecodeType = DAIKIN64,
kLastDecodeType = AIRWELL,
};

// Message lengths & required repeat values
const uint16_t kNoRepeat = 0;
const uint16_t kSingleRepeat = 1;

const uint16_t kAirwellBits = 32;
const uint16_t kAirwellMinRepeats = 2;
const uint16_t kAiwaRcT501Bits = 15;
const uint16_t kAiwaRcT501MinRepeats = kSingleRepeat;
const uint16_t kAlokaBits = 32;
Expand Down
Loading

0 comments on commit 75726b2

Please sign in to comment.