Ir esp8266 lib update and code impacts (#235)
* integrate new IRremoteESP8266 lib 2.4.2 and add new protocols IR_RC6 IR_MITSUBISHI IR_GICABLE IR_MITSUBISHI2 IR_LASERTAG IR_CARRIER_AC IR_MIDEA IR_NIKAI IR_SHERWOOD IR_DENON IR_AIWA_RC_T501 IR_JVC
This commit is contained in:
1 parent
9b258723a8
commit
b12ee19979
130 files changed
+16548
-1693
No files matched your search
@@ -90,7 +90,7 @@ static void ICACHE_RAM_ATTR gpio_intr() {
|
||||
// (Default: TIMEOUT_MS)
|
||||
// save_buffer: Use a second (save) buffer to decode from. (Def: false)
|
||||
// Returns:
|
||||
// A IRrecv class object.
|
||||
// An IRrecv class object.
|
||||
IRrecv::IRrecv(uint16_t recvpin, uint16_t bufsize, uint8_t timeout,
|
||||
bool save_buffer) {
|
||||
irparams.recvpin = recvpin;
|
||||
@@ -121,6 +121,9 @@ IRrecv::IRrecv(uint16_t recvpin, uint16_t bufsize, uint8_t timeout,
|
||||
} else {
|
||||
irparams_save = NULL;
|
||||
}
|
||||
#if DECODE_HASH
|
||||
unknown_threshold = UNKNOWN_THRESHOLD;
|
||||
#endif // DECODE_HASH
|
||||
}
|
||||
|
||||
// Class destructor
|
||||
@@ -198,6 +201,13 @@ uint16_t IRrecv::getBufSize() {
|
||||
return irparams.bufsize;
|
||||
}
|
||||
|
||||
#if DECODE_HASH
|
||||
// Set the minimum length we will consider for reporting UNKNOWN message types.
|
||||
void IRrecv::setUnknownThreshold(uint16_t length) {
|
||||
unknown_threshold = length;
|
||||
}
|
||||
#endif // DECODE_HASH
|
||||
|
||||
// Decodes the received IR message.
|
||||
// If the interrupt state is saved, we will immediately resume waiting
|
||||
// for the next IR message to avoid missing messages.
|
||||
@@ -275,6 +285,15 @@ bool IRrecv::decode(decode_results *results, irparams_t *save) {
|
||||
if (decodeSanyoLC7461(results))
|
||||
return true;
|
||||
#endif
|
||||
#if DECODE_CARRIER_AC
|
||||
DPRINTLN("Attempting Carrier AC decode");
|
||||
// Try decodeCarrierAC() before decodeNEC() because the protocols are
|
||||
// similar in timings & structure, but the Carrier one is much longer than the
|
||||
// NEC protocol (3x32 bits vs 1x32 bits) so this one should be tried first to
|
||||
// try to reduce false detection as a NEC packet.
|
||||
if (decodeCarrierAC(results))
|
||||
return true;
|
||||
#endif
|
||||
#if DECODE_NEC
|
||||
DPRINTLN("Attempting NEC decode");
|
||||
if (decodeNEC(results))
|
||||
@@ -290,6 +309,11 @@ bool IRrecv::decode(decode_results *results, irparams_t *save) {
|
||||
if (decodeMitsubishi(results))
|
||||
return true;
|
||||
#endif
|
||||
#if DECODE_MITSUBISHI2
|
||||
DPRINTLN("Attempting Mitsubishi2 decode");
|
||||
if (decodeMitsubishi2(results))
|
||||
return true;
|
||||
#endif
|
||||
#if DECODE_RC5
|
||||
DPRINTLN("Attempting RC5 decode");
|
||||
if (decodeRC5(results))
|
||||
@@ -305,11 +329,16 @@ bool IRrecv::decode(decode_results *results, irparams_t *save) {
|
||||
if (decodeRCMM(results))
|
||||
return true;
|
||||
#endif
|
||||
#if DECODE_FUJITSU_AC
|
||||
// Fujitsu A/C needs to precede Panasonic and Denon as it has a short
|
||||
// message which looks exactly the same as a Panasonic/Denon message.
|
||||
DPRINTLN("Attempting Fujitsu A/C decode");
|
||||
if (decodeFujitsuAC(results))
|
||||
return true;
|
||||
#endif
|
||||
#if DECODE_DENON
|
||||
// Denon needs to precede Panasonic as it is a special case of Panasonic.
|
||||
#ifdef DEBUG
|
||||
DPRINTLN("Attempting Denon decode");
|
||||
#endif
|
||||
if (decodeDenon(results, DENON_48_BITS) ||
|
||||
decodeDenon(results, DENON_BITS) ||
|
||||
decodeDenon(results, DENON_LEGACY_BITS))
|
||||
@@ -329,6 +358,13 @@ bool IRrecv::decode(decode_results *results, irparams_t *save) {
|
||||
if (decodeLG(results, LG32_BITS, true))
|
||||
return true;
|
||||
#endif
|
||||
#if DECODE_GICABLE
|
||||
// Note: Needs to happen before JVC decode, because it looks similar except
|
||||
// with a required NEC-like repeat code.
|
||||
DPRINTLN("Attempting GICable decode");
|
||||
if (decodeGICable(results))
|
||||
return true;
|
||||
#endif
|
||||
#if DECODE_JVC
|
||||
DPRINTLN("Attempting JVC decode");
|
||||
if (decodeJVC(results))
|
||||
@@ -359,6 +395,38 @@ bool IRrecv::decode(decode_results *results, irparams_t *save) {
|
||||
if (decodeCOOLIX(results))
|
||||
return true;
|
||||
#endif
|
||||
#if DECODE_NIKAI
|
||||
DPRINTLN("Attempting Nikai decode");
|
||||
if (decodeNikai(results))
|
||||
return true;
|
||||
#endif
|
||||
#if DECODE_KELVINATOR
|
||||
// Kelvinator based-devices use a similar code to Gree ones, to avoid false
|
||||
// matches this needs to happen before decodeGree().
|
||||
DPRINTLN("Attempting Kelvinator decode");
|
||||
if (decodeKelvinator(results))
|
||||
return true;
|
||||
#endif
|
||||
#if DECODE_DAIKIN
|
||||
DPRINTLN("Attempting Daikin decode");
|
||||
if (decodeDaikin(results))
|
||||
return true;
|
||||
#endif
|
||||
#if DECODE_TOSHIBA_AC
|
||||
DPRINTLN("Attempting Toshiba AC decode");
|
||||
if (decodeToshibaAC(results))
|
||||
return true;
|
||||
#endif
|
||||
#if DECODE_MIDEA
|
||||
DPRINTLN("Attempting Midea decode");
|
||||
if (decodeMidea(results))
|
||||
return true;
|
||||
#endif
|
||||
#if DECODE_MAGIQUEST
|
||||
DPRINTLN("Attempting Magiquest decode");
|
||||
if (decodeMagiQuest(results))
|
||||
return true;
|
||||
#endif
|
||||
/* NOTE: Disabled due to poor quality.
|
||||
#if DECODE_SANYO
|
||||
// The Sanyo S866500B decoder is very poor quality & depricated.
|
||||
@@ -375,18 +443,53 @@ bool IRrecv::decode(decode_results *results, irparams_t *save) {
|
||||
// This needs to be done after all other codes that use strict and some
|
||||
// other protocols that are NEC-like as well, as turning off strict may
|
||||
// cause this to match other valid protocols.
|
||||
DPRINTLN("Attempting NEC (non-stict) decode");
|
||||
DPRINTLN("Attempting NEC (non-strict) decode");
|
||||
if (decodeNEC(results, NEC_BITS, false)) {
|
||||
results->decode_type = NEC_LIKE;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#if DECODE_LASERTAG
|
||||
DPRINTLN("Attempting Lasertag decode");
|
||||
if (decodeLasertag(results))
|
||||
return true;
|
||||
#endif
|
||||
#if DECODE_GREE
|
||||
// Gree based-devices use a similar code to Kelvinator ones, to avoid false
|
||||
// matches this needs to happen after decodeKelvinator().
|
||||
DPRINTLN("Attempting Gree decode");
|
||||
if (decodeGree(results))
|
||||
return true;
|
||||
#endif
|
||||
#if DECODE_HAIER_AC
|
||||
DPRINTLN("Attempting Haier AC decode");
|
||||
if (decodeHaierAC(results))
|
||||
return true;
|
||||
#endif
|
||||
#if DECODE_HITACHI_AC2
|
||||
// HitachiAC2 should be checked before HitachiAC
|
||||
DPRINTLN("Attempting Hitachi AC2 decode");
|
||||
if (decodeHitachiAC(results, HITACHI_AC2_BITS))
|
||||
return true;
|
||||
#endif
|
||||
#if DECODE_HITACHI_AC
|
||||
DPRINTLN("Attempting Hitachi AC decode");
|
||||
if (decodeHitachiAC(results, HITACHI_AC_BITS))
|
||||
return true;
|
||||
#endif
|
||||
#if DECODE_HITACHI_AC1
|
||||
DPRINTLN("Attempting Hitachi AC1 decode");
|
||||
if (decodeHitachiAC(results, HITACHI_AC1_BITS))
|
||||
return true;
|
||||
#endif
|
||||
#if DECODE_HASH
|
||||
// decodeHash returns a hash on any input.
|
||||
// Thus, it needs to be last in the list.
|
||||
// If you add any decodes, add them before this.
|
||||
if (decodeHash(results)) {
|
||||
return true;
|
||||
}
|
||||
#endif // DECODE_HASH
|
||||
// Throw away and start over
|
||||
if (!resumed) // Check if we have already resumed.
|
||||
resume();
|
||||
@@ -398,11 +501,13 @@ bool IRrecv::decode(decode_results *results, irparams_t *save) {
|
||||
// Args:
|
||||
// usecs: Nr. of uSeconds.
|
||||
// tolerance: Percent as an integer. e.g. 10 is 10%
|
||||
// delta: A non-scaling amount to reduce usecs by.
|
||||
// Returns:
|
||||
// Nr. of ticks.
|
||||
uint32_t IRrecv::ticksLow(uint32_t usecs, uint8_t tolerance) {
|
||||
uint32_t IRrecv::ticksLow(uint32_t usecs, uint8_t tolerance, uint16_t delta) {
|
||||
// max() used to ensure the result can't drop below 0 before the cast.
|
||||
return((uint32_t) std::max((int32_t) (usecs * (1.0 - tolerance / 100.0)), 0));
|
||||
return((uint32_t) std::max(
|
||||
(int32_t) (usecs * (1.0 - tolerance / 100.0) - delta), 0));
|
||||
}
|
||||
|
||||
// Calculate the upper bound of the nr. of ticks.
|
||||
@@ -410,48 +515,52 @@ uint32_t IRrecv::ticksLow(uint32_t usecs, uint8_t tolerance) {
|
||||
// Args:
|
||||
// usecs: Nr. of uSeconds.
|
||||
// tolerance: Percent as an integer. e.g. 10 is 10%
|
||||
// delta: A non-scaling amount to increase usecs by.
|
||||
// Returns:
|
||||
// Nr. of ticks.
|
||||
uint32_t IRrecv::ticksHigh(uint32_t usecs, uint8_t tolerance) {
|
||||
return((uint32_t) (usecs * (1.0 + tolerance / 100.0)) + 1);
|
||||
uint32_t IRrecv::ticksHigh(uint32_t usecs, uint8_t tolerance, uint16_t delta) {
|
||||
return((uint32_t) (usecs * (1.0 + tolerance / 100.0)) + 1 + delta);
|
||||
}
|
||||
|
||||
// Check if we match a pulse(measured) with the desired within
|
||||
// +/-tolerance percent.
|
||||
// +/-tolerance percent and/or +/- a fixed delta range.
|
||||
//
|
||||
// Args:
|
||||
// measured: The recorded period of the signal pulse.
|
||||
// desired: The expected period (in useconds) we are matching against.
|
||||
// tolerance: A percentage expressed as an integer. e.g. 10 is 10%.
|
||||
// delta: A non-scaling (+/-) error margin (in useconds).
|
||||
//
|
||||
// Returns:
|
||||
// Boolean: true if it matches, false if it doesn't.
|
||||
bool IRrecv::match(uint32_t measured, uint32_t desired,
|
||||
uint8_t tolerance) {
|
||||
uint8_t tolerance, uint16_t delta) {
|
||||
measured *= RAWTICK; // Convert to uSecs.
|
||||
DPRINT("Matching: ");
|
||||
DPRINT(ticksLow(desired, tolerance));
|
||||
DPRINT(ticksLow(desired, tolerance, delta));
|
||||
DPRINT(" <= ");
|
||||
DPRINT(measured);
|
||||
DPRINT(" <= ");
|
||||
DPRINTLN(ticksHigh(desired, tolerance));
|
||||
return (measured >= ticksLow(desired, tolerance) &&
|
||||
measured <= ticksHigh(desired, tolerance));
|
||||
DPRINTLN(ticksHigh(desired, tolerance, delta));
|
||||
return (measured >= ticksLow(desired, tolerance, delta) &&
|
||||
measured <= ticksHigh(desired, tolerance, delta));
|
||||
}
|
||||
|
||||
|
||||
// Check if we match a pulse(measured) of at least desired within
|
||||
// +/-tolerance percent.
|
||||
// tolerance percent and/or a fixed delta margin.
|
||||
//
|
||||
// Args:
|
||||
// measured: The recorded period of the signal pulse.
|
||||
// desired: The expected period (in useconds) we are matching against.
|
||||
// tolerance: A percentage expressed as an integer. e.g. 10 is 10%.
|
||||
// delta: A non-scaling amount to reduce usecs by.
|
||||
|
||||
//
|
||||
// Returns:
|
||||
// Boolean: true if it matches, false if it doesn't.
|
||||
bool IRrecv::matchAtLeast(uint32_t measured, uint32_t desired,
|
||||
uint8_t tolerance) {
|
||||
uint8_t tolerance, uint16_t delta) {
|
||||
measured *= RAWTICK; // Convert to uSecs.
|
||||
DPRINT("Matching ATLEAST ");
|
||||
DPRINT(measured);
|
||||
@@ -460,17 +569,18 @@ bool IRrecv::matchAtLeast(uint32_t measured, uint32_t desired,
|
||||
DPRINT(". Matching: ");
|
||||
DPRINT(measured);
|
||||
DPRINT(" >= ");
|
||||
DPRINT(ticksLow(std::min(desired, MS_TO_USEC(irparams.timeout)), tolerance));
|
||||
DPRINT(ticksLow(std::min(desired, MS_TO_USEC(irparams.timeout)), tolerance,
|
||||
delta));
|
||||
DPRINT(" [min(");
|
||||
DPRINT(ticksLow(desired, tolerance));
|
||||
DPRINT(ticksLow(desired, tolerance, delta));
|
||||
DPRINT(", ");
|
||||
DPRINT(ticksLow(MS_TO_USEC(irparams.timeout), tolerance));
|
||||
DPRINT(ticksLow(MS_TO_USEC(irparams.timeout), tolerance, delta));
|
||||
DPRINTLN(")]");
|
||||
// We really should never get a value of 0, except as the last value
|
||||
// in the buffer. If that is the case, then assume infinity and return true.
|
||||
if (measured == 0) return true;
|
||||
return measured >= ticksLow(std::min(desired, MS_TO_USEC(irparams.timeout)),
|
||||
tolerance);
|
||||
tolerance, delta);
|
||||
}
|
||||
|
||||
// Check if we match a mark signal(measured) with the desired within
|
||||
@@ -545,13 +655,14 @@ int16_t IRrecv::compare(uint16_t oldval, uint16_t newval) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
#if DECODE_HASH
|
||||
/* Converts the raw code values into a 32-bit hash code.
|
||||
* Hopefully this code is unique for each button.
|
||||
* This isn't a "real" decoding, just an arbitrary value.
|
||||
*/
|
||||
bool IRrecv::decodeHash(decode_results *results) {
|
||||
// Require at least 6 samples to prevent triggering on noise
|
||||
if (results->rawlen < 6)
|
||||
// Require at least some samples to prevent triggering on noise
|
||||
if (results->rawlen < unknown_threshold)
|
||||
return false;
|
||||
int32_t hash = FNV_BASIS_32;
|
||||
// 'rawlen - 2' to avoid the look ahead from going out of bounds.
|
||||
@@ -570,6 +681,7 @@ bool IRrecv::decodeHash(decode_results *results) {
|
||||
results->decode_type = UNKNOWN;
|
||||
return true;
|
||||
}
|
||||
#endif // DECODE_HASH
|
||||
|
||||
// Match & decode the typical data section of an IR message.
|
||||
// The data value constructed as the Most Significant Bit first.
|
||||
@@ -581,46 +693,34 @@ bool IRrecv::decodeHash(decode_results *results) {
|
||||
// onespace: Nr. of uSeconds in an expected space signal for a '1' bit.
|
||||
// zeromark: Nr. of uSeconds in an expected mark signal for a '0' bit.
|
||||
// zerospace: Nr. of uSeconds in an expected space signal for a '0' bit.
|
||||
// tolerance: Percentage error margin to allow.
|
||||
// Returns:
|
||||
// A match_result_t structure containing the success (or not), the data value,
|
||||
// and how many buffer entries were used.
|
||||
match_result_t IRrecv::matchData(volatile uint16_t *data_ptr, uint16_t nbits,
|
||||
uint16_t onemark, uint32_t onespace,
|
||||
uint16_t zeromark, uint32_t zerospace) {
|
||||
match_result_t IRrecv::matchData(volatile uint16_t *data_ptr,
|
||||
const uint16_t nbits, const uint16_t onemark,
|
||||
const uint32_t onespace,
|
||||
const uint16_t zeromark,
|
||||
const uint32_t zerospace,
|
||||
const uint8_t tolerance) {
|
||||
match_result_t result;
|
||||
result.success = false;
|
||||
result.success = false; // Fail by default.
|
||||
result.data = 0;
|
||||
if (onemark == zeromark) { // Is this space encoded data format?
|
||||
for (result.used = 0;
|
||||
result.used < nbits * 2;
|
||||
result.used += 2, data_ptr++) {
|
||||
if (!matchMark(*data_ptr, onemark))
|
||||
return result; // Fail
|
||||
data_ptr++;
|
||||
if (matchSpace(*data_ptr, onespace))
|
||||
result.data = (result.data << 1) | 1;
|
||||
else if (matchSpace(*data_ptr, zerospace))
|
||||
result.data <<= 1;
|
||||
else
|
||||
return result; // Fail
|
||||
}
|
||||
result.success = true;
|
||||
} else if (onespace == zerospace) { // Is this mark encoded data format?
|
||||
for (result.used = 0;
|
||||
result.used < nbits * 2;
|
||||
result.used += 2, data_ptr++) {
|
||||
if (matchMark(*data_ptr, onemark))
|
||||
result.data = (result.data << 1) | 1;
|
||||
else if (matchMark(*data_ptr, zeromark))
|
||||
result.data <<= 1;
|
||||
else
|
||||
return result; // Fail
|
||||
data_ptr++;
|
||||
if (!matchSpace(*data_ptr, onespace))
|
||||
return result; // Fail
|
||||
}
|
||||
result.success = true;
|
||||
for (result.used = 0;
|
||||
result.used < nbits * 2;
|
||||
result.used += 2, data_ptr += 2) {
|
||||
// Is the bit a '1'?
|
||||
if (matchMark(*data_ptr, onemark, tolerance) &&
|
||||
matchSpace(*(data_ptr + 1), onespace, tolerance))
|
||||
result.data = (result.data << 1) | 1;
|
||||
// or is the bit a '0'?
|
||||
else if (matchMark(*data_ptr, zeromark, tolerance) &&
|
||||
matchSpace(*(data_ptr + 1), zerospace, tolerance))
|
||||
result.data <<= 1;
|
||||
else
|
||||
return result; // It's neither, so fail.
|
||||
}
|
||||
result.success = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#define MARK_EXCESS 50U
|
||||
#define RAWBUF 100U // Default length of raw capture buffer
|
||||
#define REPEAT UINT64_MAX
|
||||
#define UNKNOWN_THRESHOLD 6U // Default min size of reported UNKNOWN messages.
|
||||
// receiver states
|
||||
#define STATE_IDLE 2U
|
||||
#define STATE_MARK 3U
|
||||
@@ -46,6 +47,9 @@
|
||||
#define FNV_PRIME_32 16777619UL
|
||||
#define FNV_BASIS_32 2166136261UL
|
||||
|
||||
// Hitachi AC is the current largest state size.
|
||||
#define STATE_SIZE_MAX HITACHI_AC2_STATE_LENGTH
|
||||
|
||||
// Types
|
||||
// information for the interrupt handler
|
||||
typedef struct {
|
||||
@@ -74,14 +78,24 @@ typedef struct {
|
||||
class decode_results {
|
||||
public:
|
||||
decode_type_t decode_type; // NEC, SONY, RC5, UNKNOWN
|
||||
uint64_t value; // Decoded value
|
||||
// value, address, & command are all mutually exclusive with state.
|
||||
// i.e. They MUST NOT be used at the same time as state, so we can use a union
|
||||
// structure to save us a handful of valuable bytes of memory.
|
||||
union {
|
||||
struct {
|
||||
uint64_t value; // Decoded value
|
||||
uint32_t address; // Decoded device address.
|
||||
uint32_t command; // Decoded command.
|
||||
};
|
||||
#if DECODE_AC // Only include state if we must. It's big.
|
||||
uint8_t state[STATE_SIZE_MAX]; // Complex multi-byte A/C result.
|
||||
#endif
|
||||
};
|
||||
uint16_t bits; // Number of bits in decoded value
|
||||
volatile uint16_t *rawbuf; // Raw intervals in .5 us ticks
|
||||
uint16_t rawlen; // Number of records in rawbuf.
|
||||
bool overflow;
|
||||
bool repeat; // Is the result a repeat code?
|
||||
uint32_t address; // Decoded device address.
|
||||
uint32_t command; // Decoded command.
|
||||
};
|
||||
|
||||
// main class for receiving IR
|
||||
@@ -96,28 +110,38 @@ class IRrecv {
|
||||
void disableIRIn();
|
||||
void resume();
|
||||
uint16_t getBufSize();
|
||||
|
||||
#if DECODE_HASH
|
||||
void setUnknownThreshold(uint16_t length);
|
||||
#endif
|
||||
static bool match(uint32_t measured, uint32_t desired,
|
||||
uint8_t tolerance = TOLERANCE, uint16_t delta = 0);
|
||||
static bool matchMark(uint32_t measured, uint32_t desired,
|
||||
uint8_t tolerance = TOLERANCE,
|
||||
int16_t excess = MARK_EXCESS);
|
||||
static bool matchSpace(uint32_t measured, uint32_t desired,
|
||||
uint8_t tolerance = TOLERANCE,
|
||||
int16_t excess = MARK_EXCESS);
|
||||
#ifndef UNIT_TEST
|
||||
|
||||
private:
|
||||
#endif
|
||||
irparams_t *irparams_save;
|
||||
#if DECODE_HASH
|
||||
uint16_t unknown_threshold;
|
||||
#endif
|
||||
// These are called by decode
|
||||
void copyIrParams(volatile irparams_t *src, irparams_t *dst);
|
||||
int16_t compare(uint16_t oldval, uint16_t newval);
|
||||
uint32_t ticksLow(uint32_t usecs, uint8_t tolerance = TOLERANCE);
|
||||
uint32_t ticksHigh(uint32_t usecs, uint8_t tolerance = TOLERANCE);
|
||||
bool match(uint32_t measured, uint32_t desired,
|
||||
uint8_t tolerance = TOLERANCE);
|
||||
static uint32_t ticksLow(uint32_t usecs, uint8_t tolerance = TOLERANCE,
|
||||
uint16_t delta = 0);
|
||||
static uint32_t ticksHigh(uint32_t usecs, uint8_t tolerance = TOLERANCE,
|
||||
uint16_t delta = 0);
|
||||
bool matchAtLeast(uint32_t measured, uint32_t desired,
|
||||
uint8_t tolerance = TOLERANCE);
|
||||
bool matchMark(uint32_t measured, uint32_t desired,
|
||||
uint8_t tolerance = TOLERANCE, int16_t excess = MARK_EXCESS);
|
||||
bool matchSpace(uint32_t measured, uint32_t desired,
|
||||
uint8_t tolerance = TOLERANCE, int16_t excess = MARK_EXCESS);
|
||||
match_result_t matchData(volatile uint16_t *data_ptr, uint16_t nbits,
|
||||
uint16_t onemark, uint32_t onespace,
|
||||
uint16_t zeromark, uint32_t zerospace);
|
||||
uint8_t tolerance = TOLERANCE, uint16_t delta = 0);
|
||||
match_result_t matchData(volatile uint16_t *data_ptr, const uint16_t nbits,
|
||||
const uint16_t onemark, const uint32_t onespace,
|
||||
const uint16_t zeromark, const uint32_t zerospace,
|
||||
const uint8_t tolerance = TOLERANCE);
|
||||
bool decodeHash(decode_results *results);
|
||||
#if (DECODE_NEC || DECODE_SHERWOOD || DECODE_AIWA_RC_T501 || SEND_SANYO)
|
||||
bool decodeNEC(decode_results *results, uint16_t nbits = NEC_BITS,
|
||||
@@ -141,9 +165,15 @@ class IRrecv {
|
||||
uint16_t nbits = MITSUBISHI_BITS,
|
||||
bool strict = true);
|
||||
#endif
|
||||
#if (DECODE_RC5 || DECODE_R6)
|
||||
#if DECODE_MITSUBISHI2
|
||||
bool decodeMitsubishi2(decode_results *results,
|
||||
uint16_t nbits = MITSUBISHI_BITS,
|
||||
bool strict = true);
|
||||
#endif
|
||||
#if (DECODE_RC5 || DECODE_R6 || DECODE_LASERTAG)
|
||||
int16_t getRClevel(decode_results *results, uint16_t *offset, uint16_t *used,
|
||||
uint16_t bitTime);
|
||||
uint16_t bitTime, uint8_t tolerance = TOLERANCE,
|
||||
int16_t excess = MARK_EXCESS, uint16_t delta = 0);
|
||||
#endif
|
||||
#if DECODE_RC5
|
||||
bool decodeRC5(decode_results *results, uint16_t nbits = RC5X_BITS,
|
||||
@@ -198,6 +228,66 @@ class IRrecv {
|
||||
bool decodeAiwaRCT501(decode_results *results,
|
||||
uint16_t nbits = AIWA_RC_T501_BITS, bool strict = true);
|
||||
#endif
|
||||
#if DECODE_NIKAI
|
||||
bool decodeNikai(decode_results *results, uint16_t nbits = NIKAI_BITS,
|
||||
bool strict = true);
|
||||
#endif
|
||||
#if DECODE_MAGIQUEST
|
||||
bool decodeMagiQuest(decode_results *results, uint16_t nbits = MAGIQUEST_BITS,
|
||||
bool strict = true);
|
||||
#endif
|
||||
#if DECODE_KELVINATOR
|
||||
bool decodeKelvinator(decode_results *results,
|
||||
uint16_t nbits = KELVINATOR_BITS,
|
||||
bool strict = true);
|
||||
#endif
|
||||
#if DECODE_DAIKIN
|
||||
bool decodeDaikin(decode_results *results, uint16_t nbits = DAIKIN_RAW_BITS,
|
||||
bool strict = true);
|
||||
#endif
|
||||
#if DECODE_TOSHIBA_AC
|
||||
bool decodeToshibaAC(decode_results *results,
|
||||
uint16_t nbytes = TOSHIBA_AC_BITS,
|
||||
bool strict = true);
|
||||
#endif
|
||||
#if DECODE_MIDEA
|
||||
bool decodeMidea(decode_results *results, uint16_t nbits = MIDEA_BITS,
|
||||
bool strict = true);
|
||||
#endif
|
||||
#if DECODE_FUJITSU_AC
|
||||
bool decodeFujitsuAC(decode_results *results,
|
||||
uint16_t nbits = FUJITSU_AC_BITS,
|
||||
bool strict = false);
|
||||
#endif
|
||||
#if DECODE_LASERTAG
|
||||
bool decodeLasertag(decode_results *results, uint16_t nbits = LASERTAG_BITS,
|
||||
bool strict = true);
|
||||
#endif
|
||||
#if DECODE_CARRIER_AC
|
||||
bool decodeCarrierAC(decode_results *results,
|
||||
uint16_t nbits = CARRIER_AC_BITS,
|
||||
bool strict = true);
|
||||
#endif
|
||||
#if DECODE_GREE
|
||||
bool decodeGree(decode_results *results,
|
||||
uint16_t nbits = GREE_BITS, bool strict = true);
|
||||
#endif
|
||||
#if DECODE_HAIER_AC
|
||||
bool decodeHaierAC(decode_results *results,
|
||||
uint16_t nbits = HAIER_AC_BITS, bool strict = true);
|
||||
#endif
|
||||
#if (DECODE_HITACHI_AC || DECODE_HITACHI_AC2)
|
||||
bool decodeHitachiAC(decode_results *results,
|
||||
uint16_t nbits = HITACHI_AC_BITS, bool strict = true);
|
||||
#endif
|
||||
#if DECODE_HITACHI_AC1
|
||||
bool decodeHitachiAC1(decode_results *results,
|
||||
uint16_t nbits = HITACHI_AC1_BITS, bool strict = true);
|
||||
#endif
|
||||
#if DECODE_GICABLE
|
||||
bool decodeGICable(decode_results *results, uint16_t nbits = GICABLE_BITS,
|
||||
bool strict = true);
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // IRRECV_H_
|
||||
@@ -21,7 +21,7 @@
|
||||
* Kelvinator A/C and Sherwood added by crankyoldgit
|
||||
* Mitsubishi (TV) sending added by crankyoldgit
|
||||
* Pronto code sending added by crankyoldgit
|
||||
* Mitsubishi A/C added by crankyoldgit
|
||||
* Mitsubishi & Toshiba A/C added by crankyoldgit
|
||||
* (derived from https://github.com/r45635/HVAC-IR-Control)
|
||||
* DISH decode by marcosamarinho
|
||||
* Gree Heatpump sending added by Ville Skyttä (scop)
|
||||
@@ -29,10 +29,12 @@
|
||||
* Updated by markszabo (https://github.com/markszabo/IRremoteESP8266) for sending IR code on ESP8266
|
||||
* Updated by Sebastien Warin (http://sebastien.warin.fr) for receiving IR code on ESP8266
|
||||
*
|
||||
* Updated by sillyfrog for Daikin, adopted from
|
||||
* Updated by sillyfrog for Daikin, adopted from
|
||||
* (https://github.com/mharizanov/Daikin-AC-remote-control-over-the-Internet/)
|
||||
* Fujitsu A/C code added by jonnygraham
|
||||
* Trotec AC code by stufisher
|
||||
* Carrier AC code by crankyoldgit
|
||||
*
|
||||
* GPL license, all text above must be included in any redistribution
|
||||
****************************************************/
|
||||
|
||||
@@ -46,11 +48,16 @@
|
||||
#endif
|
||||
|
||||
// Library Version
|
||||
#define _IRREMOTEESP8266_VERSION_ "2.2.0"
|
||||
#define _IRREMOTEESP8266_VERSION_ "2.4.2"
|
||||
// Supported IR protocols
|
||||
// Each protocol you include costs memory and, during decode, costs time
|
||||
// Disable (set to false) all the protocols you do not need/want!
|
||||
// The Air Conditioner protocols are the most expensive memory-wise.
|
||||
//
|
||||
#define DECODE_HASH true // Semi-unique code for unknown messages
|
||||
|
||||
#define SEND_RAW true
|
||||
|
||||
#define DECODE_NEC true
|
||||
#define SEND_NEC true
|
||||
|
||||
@@ -93,6 +100,9 @@
|
||||
#define DECODE_MITSUBISHI true
|
||||
#define SEND_MITSUBISHI true
|
||||
|
||||
#define DECODE_MITSUBISHI2 true
|
||||
#define SEND_MITSUBISHI2 true
|
||||
|
||||
#define DECODE_DISH true
|
||||
#define SEND_DISH true
|
||||
|
||||
@@ -102,16 +112,16 @@
|
||||
#define DECODE_DENON true
|
||||
#define SEND_DENON true
|
||||
|
||||
#define DECODE_KELVINATOR false // Not written.
|
||||
#define DECODE_KELVINATOR true
|
||||
#define SEND_KELVINATOR true
|
||||
|
||||
#define DECODE_MITSUBISHI_AC false // Not written.
|
||||
#define SEND_MITSUBISHI_AC true
|
||||
|
||||
#define DECODE_FUJITSU_AC false // Not written.
|
||||
#define SEND_FUJITSU_AC true
|
||||
#define DECODE_FUJITSU_AC true
|
||||
#define SEND_FUJITSU_AC true
|
||||
|
||||
#define DECODE_DAIKIN false // Not finished.
|
||||
#define DECODE_DAIKIN true
|
||||
#define SEND_DAIKIN true
|
||||
|
||||
#define DECODE_COOLIX true
|
||||
@@ -120,7 +130,7 @@
|
||||
#define DECODE_GLOBALCACHE false // Not written.
|
||||
#define SEND_GLOBALCACHE true
|
||||
|
||||
#define DECODE_GREE false // Not written.
|
||||
#define DECODE_GREE true
|
||||
#define SEND_GREE true
|
||||
|
||||
#define DECODE_PRONTO false // Not written.
|
||||
@@ -132,6 +142,54 @@
|
||||
#define DECODE_TROTEC false // Not implemented.
|
||||
#define SEND_TROTEC true
|
||||
|
||||
#define DECODE_NIKAI true
|
||||
#define SEND_NIKAI true
|
||||
|
||||
#define DECODE_TOSHIBA_AC true
|
||||
#define SEND_TOSHIBA_AC true
|
||||
|
||||
#define DECODE_MAGIQUEST true
|
||||
#define SEND_MAGIQUEST true
|
||||
|
||||
#define DECODE_MIDEA true
|
||||
#define SEND_MIDEA true
|
||||
|
||||
#define DECODE_LASERTAG true
|
||||
#define SEND_LASERTAG true
|
||||
|
||||
#define DECODE_CARRIER_AC true
|
||||
#define SEND_CARRIER_AC true
|
||||
|
||||
#define DECODE_HAIER_AC true
|
||||
#define SEND_HAIER_AC true
|
||||
|
||||
#define DECODE_HITACHI_AC true
|
||||
#define SEND_HITACHI_AC true
|
||||
|
||||
#define DECODE_HITACHI_AC1 true
|
||||
#define SEND_HITACHI_AC1 true
|
||||
|
||||
#define DECODE_HITACHI_AC2 true
|
||||
#define SEND_HITACHI_AC2 true
|
||||
|
||||
#define DECODE_GICABLE true
|
||||
#define SEND_GICABLE true
|
||||
|
||||
#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 || \
|
||||
DECODE_HITACHI_AC1 || DECODE_HITACHI_AC2)
|
||||
#define DECODE_AC true // We need some common infrastructure for decoding A/Cs.
|
||||
#else
|
||||
#define DECODE_AC false // We don't need that infrastructure.
|
||||
#endif
|
||||
|
||||
// Use millisecond 'delay()' calls where we can to avoid tripping the WDT.
|
||||
// Note: If you plan to send IR messages in the callbacks of the AsyncWebserver
|
||||
// library, you need to set ALLOW_DELAY_CALLS to false.
|
||||
// Ref: https://github.com/markszabo/IRremoteESP8266/issues/430
|
||||
#define ALLOW_DELAY_CALLS true
|
||||
|
||||
/*
|
||||
* Always add to the end of the list and should never remove entries
|
||||
* or change order. Projects may save the type number for later usage
|
||||
@@ -164,27 +222,57 @@ enum decode_type_t {
|
||||
SANYO_LC7461,
|
||||
RC5X,
|
||||
GREE,
|
||||
PRONTO,
|
||||
PRONTO, // Technically not a protocol, but an encoding.
|
||||
NEC_LIKE,
|
||||
ARGO,
|
||||
TROTEC
|
||||
TROTEC,
|
||||
NIKAI,
|
||||
RAW, // Technically not a protocol, but an encoding.
|
||||
GLOBALCACHE, // Technically not a protocol, but an encoding.
|
||||
TOSHIBA_AC,
|
||||
FUJITSU_AC,
|
||||
MIDEA,
|
||||
MAGIQUEST,
|
||||
LASERTAG,
|
||||
CARRIER_AC,
|
||||
HAIER_AC,
|
||||
MITSUBISHI2,
|
||||
HITACHI_AC,
|
||||
HITACHI_AC1,
|
||||
HITACHI_AC2,
|
||||
GICABLE
|
||||
};
|
||||
|
||||
// Message lengths & required repeat values
|
||||
#define AIWA_RC_T501_BITS 15U
|
||||
#define AIWA_RC_T501_MIN_REPEAT 1U
|
||||
#define COOLIX_BITS 24U
|
||||
#define DAIKIN_BITS 99U
|
||||
#define CARRIER_AC_BITS 32U
|
||||
#define CARRIER_AC_MIN_REPEAT 0U
|
||||
// Daikin has a lot of static stuff that is discarded
|
||||
#define DAIKIN_RAW_BITS 583U
|
||||
#define DAIKIN_COMMAND_LENGTH 27U
|
||||
#define DAIKIN_BITS (DAIKIN_COMMAND_LENGTH * 8)
|
||||
#define DENON_BITS SHARP_BITS
|
||||
#define DENON_48_BITS PANASONIC_BITS
|
||||
#define DENON_LEGACY_BITS 14U
|
||||
#define DISH_BITS 16U
|
||||
#define DISH_MIN_REPEAT 3U
|
||||
#define GICABLE_BITS 16U
|
||||
#define GICABLE_MIN_REPEAT 1U
|
||||
#define GREE_STATE_LENGTH 8U
|
||||
#define GREE_BITS (GREE_STATE_LENGTH * 8)
|
||||
#define HAIER_AC_STATE_LENGTH 9U
|
||||
#define HAIER_AC_BITS (HAIER_AC_STATE_LENGTH * 8)
|
||||
#define HITACHI_AC_STATE_LENGTH 28U
|
||||
#define HITACHI_AC_BITS (HITACHI_AC_STATE_LENGTH * 8)
|
||||
#define HITACHI_AC1_STATE_LENGTH 13U
|
||||
#define HITACHI_AC1_BITS (HITACHI_AC1_STATE_LENGTH * 8)
|
||||
#define HITACHI_AC2_STATE_LENGTH 53U
|
||||
#define HITACHI_AC2_BITS (HITACHI_AC2_STATE_LENGTH * 8)
|
||||
#define JVC_BITS 16U
|
||||
#define KELVINATOR_STATE_LENGTH 16U
|
||||
#define KELVINATOR_BITS (KELVINATOR_STATE_LENGTH * 8)
|
||||
#define LG_BITS 28U
|
||||
#define LG32_BITS 32U
|
||||
#define MITSUBISHI_BITS 16U
|
||||
@@ -193,6 +281,10 @@ enum decode_type_t {
|
||||
#define MITSUBISHI_AC_STATE_LENGTH 18U
|
||||
#define MITSUBISHI_AC_MIN_REPEAT 1U
|
||||
#define FUJITSU_AC_MIN_REPEAT 0U
|
||||
#define FUJITSU_AC_STATE_LENGTH 16U
|
||||
#define FUJITSU_AC_STATE_LENGTH_SHORT 7U
|
||||
#define FUJITSU_AC_BITS (FUJITSU_AC_STATE_LENGTH * 8)
|
||||
#define FUJITSU_AC_MIN_BITS ((FUJITSU_AC_STATE_LENGTH_SHORT - 1) * 8)
|
||||
#define NEC_BITS 32U
|
||||
#define PANASONIC_BITS 48U
|
||||
#define PANASONIC_MANUFACTURER 0x4004ULL
|
||||
@@ -219,9 +311,18 @@ enum decode_type_t {
|
||||
#define SONY_20_BITS 20U
|
||||
#define SONY_MIN_BITS SONY_12_BITS
|
||||
#define SONY_MIN_REPEAT 2U
|
||||
#define TOSHIBA_AC_STATE_LENGTH 9U
|
||||
#define TOSHIBA_AC_BITS (TOSHIBA_AC_STATE_LENGTH * 8)
|
||||
#define TOSHIBA_AC_MIN_REPEAT 1U
|
||||
#define TROTEC_COMMAND_LENGTH 9U
|
||||
#define WHYNTER_BITS 32U
|
||||
#define ARGO_COMMAND_LENGTH 12U
|
||||
#define NIKAI_BITS 24U
|
||||
#define MAGIQUEST_BITS 56U
|
||||
#define MIDEA_BITS 48U
|
||||
#define MIDEA_MIN_REPEAT 0U
|
||||
#define LASERTAG_BITS 13U
|
||||
#define LASERTAG_MIN_REPEAT 0U
|
||||
|
||||
// Turn on Debugging information by uncommenting the following line.
|
||||
// #define DEBUG 1
|
||||
|
||||
@@ -29,10 +29,14 @@
|
||||
// Setting this to something other than the default could
|
||||
// easily destroy your IR LED if you are overdriving it.
|
||||
// Unless you *REALLY* know what you are doing, don't change this.
|
||||
// use_modulation: Do we do frequency modulation during transmission?
|
||||
// i.e. If not, assume a 100% duty cycle. Ignore attempts
|
||||
// to change the duty cycle etc.
|
||||
// Returns:
|
||||
// An IRsend object.
|
||||
IRsend::IRsend(uint16_t IRsendPin, bool inverted) : IRpin(IRsendPin),
|
||||
periodOffset(PERIOD_OFFSET) {
|
||||
IRsend::IRsend(uint16_t IRsendPin, bool inverted,
|
||||
bool use_modulation) : IRpin(IRsendPin),
|
||||
periodOffset(PERIOD_OFFSET) {
|
||||
if (inverted) {
|
||||
outputOn = LOW;
|
||||
outputOff = HIGH;
|
||||
@@ -40,6 +44,11 @@ IRsend::IRsend(uint16_t IRsendPin, bool inverted) : IRpin(IRsendPin),
|
||||
outputOn = HIGH;
|
||||
outputOff = LOW;
|
||||
}
|
||||
modulation = use_modulation;
|
||||
if (modulation)
|
||||
_dutycycle = DUTY_DEFAULT;
|
||||
else
|
||||
_dutycycle = DUTY_MAX;
|
||||
}
|
||||
|
||||
// Enable the pin for output.
|
||||
@@ -47,6 +56,7 @@ void IRsend::begin() {
|
||||
#ifndef UNIT_TEST
|
||||
pinMode(IRpin, OUTPUT);
|
||||
#endif
|
||||
ledOff(); // Ensure the LED is in a known safe state when we start.
|
||||
}
|
||||
|
||||
// Turn off the IR LED.
|
||||
@@ -56,6 +66,13 @@ void IRsend::ledOff() {
|
||||
#endif
|
||||
}
|
||||
|
||||
// Turn on the IR LED.
|
||||
void IRsend::ledOn() {
|
||||
#ifndef UNIT_TEST
|
||||
digitalWrite(IRpin, outputOn);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Calculate the period for a given frequency. (T = 1/f)
|
||||
//
|
||||
// Args:
|
||||
@@ -78,23 +95,65 @@ uint32_t IRsend::calcUSecPeriod(uint32_t hz, bool use_offset) {
|
||||
// Args:
|
||||
// freq: The freq we want to modulate at. Assumes < 1000 means kHz else Hz.
|
||||
// duty: Percentage duty cycle of the LED. e.g. 25 = 25% = 1/4 on, 3/4 off.
|
||||
// This is ignored if modulation is disabled at object instantiation.
|
||||
//
|
||||
// Note:
|
||||
// Integer timing functions & math mean we can't do fractions of
|
||||
// microseconds timing. Thus minor changes to the freq & duty values may have
|
||||
// limited effect. You've been warned.
|
||||
void IRsend::enableIROut(uint32_t freq, uint8_t duty) {
|
||||
// Can't have more than 100% duty cycle.
|
||||
duty = std::min(duty, (uint8_t) 100);
|
||||
// Set the duty cycle to use if we want freq. modulation.
|
||||
if (modulation) {
|
||||
_dutycycle = std::min(duty, (uint8_t) DUTY_MAX);
|
||||
} else {
|
||||
_dutycycle = DUTY_MAX;
|
||||
}
|
||||
if (freq < 1000) // Were we given kHz? Supports the old call usage.
|
||||
freq *= 1000;
|
||||
uint32_t period = calcUSecPeriod(freq);
|
||||
// Nr. of uSeconds the LED will be on per pulse.
|
||||
onTimePeriod = (period * duty) / 100;
|
||||
onTimePeriod = (period * _dutycycle) / DUTY_MAX;
|
||||
// Nr. of uSeconds the LED will be off per pulse.
|
||||
offTimePeriod = period - onTimePeriod;
|
||||
}
|
||||
|
||||
#if ALLOW_DELAY_CALLS
|
||||
// An ESP8266 RTOS watch-dog timer friendly version of delayMicroseconds().
|
||||
// Args:
|
||||
// usec: Nr. of uSeconds to delay for.
|
||||
void IRsend::_delayMicroseconds(uint32_t usec) {
|
||||
// delayMicroseconds() is only accurate to 16383us.
|
||||
// Ref: https://www.arduino.cc/en/Reference/delayMicroseconds
|
||||
if (usec <= MAX_ACCURATE_USEC_DELAY) {
|
||||
#ifndef UNIT_TEST
|
||||
delayMicroseconds(usec);
|
||||
#endif
|
||||
} else {
|
||||
#ifndef UNIT_TEST
|
||||
// Invoke a delay(), where possible, to avoid triggering the WDT.
|
||||
delay(usec / 1000UL); // Delay for as many whole milliseconds as we can.
|
||||
// Delay the remaining sub-millisecond.
|
||||
delayMicroseconds(static_cast<uint16_t>(usec % 1000UL));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#else // ALLOW_DELAY_CALLS
|
||||
// A version of delayMicroseconds() that handles large values and does NOT use
|
||||
// the watch-dog friendly delay() calls where appropriate.
|
||||
// Args:
|
||||
// usec: Nr. of uSeconds to delay for.
|
||||
//
|
||||
// NOTE: Use this only if you know what you are doing as it may cause the WDT
|
||||
// to reset the ESP8266.
|
||||
void IRsend::_delayMicroseconds(uint32_t usec) {
|
||||
for (; usec > MAX_ACCURATE_USEC_DELAY; usec -= MAX_ACCURATE_USEC_DELAY)
|
||||
#ifndef UNIT_TEST
|
||||
delayMicroseconds(MAX_ACCURATE_USEC_DELAY);
|
||||
delayMicroseconds(static_cast<uint16_t>(usec));
|
||||
#endif // UNIT_TEST
|
||||
}
|
||||
#endif // ALLOW_DELAY_CALLS
|
||||
|
||||
// Modulate the IR LED for the given period (usec) and at the duty cycle set.
|
||||
//
|
||||
// Args:
|
||||
@@ -112,6 +171,15 @@ void IRsend::enableIROut(uint32_t freq, uint8_t duty) {
|
||||
// Ref:
|
||||
// https://www.analysir.com/blog/2017/01/29/updated-esp8266-nodemcu-backdoor-upwm-hack-for-ir-signals/
|
||||
uint16_t IRsend::mark(uint16_t usec) {
|
||||
// Handle the simple case of no required frequency modulation.
|
||||
if (!modulation || _dutycycle >= 100) {
|
||||
ledOn();
|
||||
_delayMicroseconds(usec);
|
||||
ledOff();
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Not simple, so do it assuming frequency modulation.
|
||||
uint16_t counter = 0;
|
||||
IRtimer usecTimer = IRtimer();
|
||||
// Cache the time taken so far. This saves us calling time, and we can be
|
||||
@@ -119,21 +187,17 @@ uint16_t IRsend::mark(uint16_t usec) {
|
||||
uint32_t elapsed = usecTimer.elapsed();
|
||||
|
||||
while (elapsed < usec) { // Loop until we've met/exceeded our required time.
|
||||
#ifndef UNIT_TEST
|
||||
digitalWrite(IRpin, outputOn); // Turn the LED on.
|
||||
ledOn();
|
||||
// Calculate how long we should pulse on for.
|
||||
// e.g. Are we to close to the end of our requested mark time (usec)?
|
||||
delayMicroseconds(std::min((uint32_t) onTimePeriod, usec - elapsed));
|
||||
digitalWrite(IRpin, outputOff); // Turn the LED off.
|
||||
#endif
|
||||
_delayMicroseconds(std::min((uint32_t) onTimePeriod, usec - elapsed));
|
||||
ledOff();
|
||||
counter++;
|
||||
if (elapsed + onTimePeriod >= usec)
|
||||
return counter; // LED is now off & we've passed our allotted time.
|
||||
// Wait for the lesser of the rest of the duty cycle, or the time remaining.
|
||||
#ifndef UNIT_TEST
|
||||
delayMicroseconds(std::min(usec - elapsed - onTimePeriod,
|
||||
(uint32_t) offTimePeriod));
|
||||
#endif
|
||||
_delayMicroseconds(std::min(usec - elapsed - onTimePeriod,
|
||||
(uint32_t) offTimePeriod));
|
||||
elapsed = usecTimer.elapsed(); // Update & recache the actual elapsed time.
|
||||
}
|
||||
return counter;
|
||||
@@ -148,18 +212,7 @@ uint16_t IRsend::mark(uint16_t usec) {
|
||||
void IRsend::space(uint32_t time) {
|
||||
ledOff();
|
||||
if (time == 0) return;
|
||||
#ifndef UNIT_TEST
|
||||
// delayMicroseconds is only accurate to 16383us.
|
||||
// Ref: https://www.arduino.cc/en/Reference/delayMicroseconds
|
||||
if (time <= 16383) {
|
||||
delayMicroseconds(time);
|
||||
} else {
|
||||
// Invoke a delay(), where possible, to avoid triggering the WDT.
|
||||
delay(time / 1000UL); // Delay for as many whole milliseconds as we can.
|
||||
// Delay the remaining sub-millisecond.
|
||||
delayMicroseconds(static_cast<uint16_t>(time % 1000UL));
|
||||
}
|
||||
#endif
|
||||
_delayMicroseconds(time);
|
||||
}
|
||||
|
||||
// Calculate & set any offsets to account for execution times.
|
||||
@@ -167,12 +220,15 @@ void IRsend::space(uint32_t time) {
|
||||
// Args:
|
||||
// hz: The frequency to calibrate at >= 1000Hz. Default is 38000Hz.
|
||||
//
|
||||
// Status: ALPHA / Untested.
|
||||
// Returns:
|
||||
// The calculated period offset (in uSeconds) which is now in use. e.g. -5.
|
||||
//
|
||||
// Status: Stable / Working.
|
||||
//
|
||||
// NOTE:
|
||||
// This will generate an 65535us mark() IR LED signal.
|
||||
// This only needs to be called once, if at all.
|
||||
void IRsend::calibrate(uint16_t hz) {
|
||||
int8_t IRsend::calibrate(uint16_t hz) {
|
||||
if (hz < 1000) // Were we given kHz? Supports the old call usage.
|
||||
hz *= 1000;
|
||||
periodOffset = 0; // Turn off any existing offset while we calibrate.
|
||||
@@ -197,6 +253,7 @@ void IRsend::calibrate(uint16_t hz) {
|
||||
double_t actualPeriod = (double_t) timeTaken / (double_t) pulses;
|
||||
// Store the difference between the actual time per period vs. calculated.
|
||||
periodOffset = (int8_t) ((double_t) calcPeriod - actualPeriod);
|
||||
return periodOffset;
|
||||
}
|
||||
|
||||
// Generic method for sending data that is common to most protocols.
|
||||
@@ -244,6 +301,165 @@ void IRsend::sendData(uint16_t onemark, uint32_t onespace,
|
||||
}
|
||||
}
|
||||
|
||||
// Generic method for sending simple protocol messages.
|
||||
// Will send leading or trailing 0's if the nbits is larger than the number
|
||||
// of bits in data.
|
||||
//
|
||||
// Args:
|
||||
// headermark: Nr. of usecs for the led to be pulsed for the header mark.
|
||||
// A value of 0 means no header mark.
|
||||
// headerspace: Nr. of usecs for the led to be off after the header mark.
|
||||
// A value of 0 means no header space.
|
||||
// onemark: Nr. of usecs for the led to be pulsed for a '1' bit.
|
||||
// onespace: Nr. of usecs for the led to be fully off for a '1' bit.
|
||||
// zeromark: Nr. of usecs for the led to be pulsed for a '0' bit.
|
||||
// zerospace: Nr. of usecs for the led to be fully off for a '0' bit.
|
||||
// footermark: Nr. of usecs for the led to be pulsed for the footer mark.
|
||||
// A value of 0 means no footer mark.
|
||||
// gap: Nr. of usecs for the led to be off after the footer mark.
|
||||
// This is effectively the gap between messages.
|
||||
// A value of 0 means no gap space.
|
||||
// data: The data to be transmitted.
|
||||
// nbits: Nr. of bits of data to be sent.
|
||||
// frequency: The frequency we want to modulate at.
|
||||
// Assumes < 1000 means kHz otherwise it is in Hz.
|
||||
// Most common value is 38000 or 38, for 38kHz.
|
||||
// MSBfirst: Flag for bit transmission order. Defaults to MSB->LSB order.
|
||||
// repeat: Nr. of extra times the message will be sent.
|
||||
// e.g. 0 = 1 message sent, 1 = 1 initial + 1 repeat = 2 messages
|
||||
// dutycycle: Percentage duty cycle of the LED.
|
||||
// e.g. 25 = 25% = 1/4 on, 3/4 off.
|
||||
// If you are not sure, try 50 percent.
|
||||
void IRsend::sendGeneric(const uint16_t headermark, const uint32_t headerspace,
|
||||
const uint16_t onemark, const uint32_t onespace,
|
||||
const uint16_t zeromark, const uint32_t zerospace,
|
||||
const uint16_t footermark, const uint32_t gap,
|
||||
const uint64_t data, const uint16_t nbits,
|
||||
const uint16_t frequency, const bool MSBfirst,
|
||||
const uint16_t repeat, const uint8_t dutycycle) {
|
||||
sendGeneric(headermark, headerspace, onemark, onespace, zeromark, zerospace,
|
||||
footermark, gap, 0U, data, nbits, frequency, MSBfirst, repeat,
|
||||
dutycycle);
|
||||
}
|
||||
|
||||
// Generic method for sending simple protocol messages.
|
||||
// Will send leading or trailing 0's if the nbits is larger than the number
|
||||
// of bits in data.
|
||||
//
|
||||
// Args:
|
||||
// headermark: Nr. of usecs for the led to be pulsed for the header mark.
|
||||
// A value of 0 means no header mark.
|
||||
// headerspace: Nr. of usecs for the led to be off after the header mark.
|
||||
// A value of 0 means no header space.
|
||||
// onemark: Nr. of usecs for the led to be pulsed for a '1' bit.
|
||||
// onespace: Nr. of usecs for the led to be fully off for a '1' bit.
|
||||
// zeromark: Nr. of usecs for the led to be pulsed for a '0' bit.
|
||||
// zerospace: Nr. of usecs for the led to be fully off for a '0' bit.
|
||||
// footermark: Nr. of usecs for the led to be pulsed for the footer mark.
|
||||
// A value of 0 means no footer mark.
|
||||
// gap: Min. nr. of usecs for the led to be off after the footer mark.
|
||||
// This is effectively the absolute minimum gap between messages.
|
||||
// mesgtime: Min. nr. of usecs a single message needs to be.
|
||||
// This is effectively the min. total length of a single message.
|
||||
// data: The data to be transmitted.
|
||||
// nbits: Nr. of bits of data to be sent.
|
||||
// frequency: The frequency we want to modulate at.
|
||||
// Assumes < 1000 means kHz otherwise it is in Hz.
|
||||
// Most common value is 38000 or 38, for 38kHz.
|
||||
// MSBfirst: Flag for bit transmission order. Defaults to MSB->LSB order.
|
||||
// repeat: Nr. of extra times the message will be sent.
|
||||
// e.g. 0 = 1 message sent, 1 = 1 initial + 1 repeat = 2 messages
|
||||
// dutycycle: Percentage duty cycle of the LED.
|
||||
// e.g. 25 = 25% = 1/4 on, 3/4 off.
|
||||
// If you are not sure, try 50 percent.
|
||||
void IRsend::sendGeneric(const uint16_t headermark, const uint32_t headerspace,
|
||||
const uint16_t onemark, const uint32_t onespace,
|
||||
const uint16_t zeromark, const uint32_t zerospace,
|
||||
const uint16_t footermark, const uint32_t gap,
|
||||
const uint32_t mesgtime,
|
||||
const uint64_t data, const uint16_t nbits,
|
||||
const uint16_t frequency, const bool MSBfirst,
|
||||
const uint16_t repeat, const uint8_t dutycycle) {
|
||||
// Setup
|
||||
enableIROut(frequency, dutycycle);
|
||||
IRtimer usecs = IRtimer();
|
||||
|
||||
// We always send a message, even for repeat=0, hence '<= repeat'.
|
||||
for (uint16_t r = 0; r <= repeat; r++) {
|
||||
usecs.reset();
|
||||
|
||||
// Header
|
||||
if (headermark) mark(headermark);
|
||||
if (headerspace) space(headerspace);
|
||||
|
||||
// Data
|
||||
sendData(onemark, onespace, zeromark, zerospace, data, nbits, MSBfirst);
|
||||
|
||||
// Footer
|
||||
if (footermark) mark(footermark);
|
||||
uint32_t elapsed = usecs.elapsed();
|
||||
// Avoid potential unsigned integer underflow. e.g. when mesgtime is 0.
|
||||
if (elapsed >= mesgtime)
|
||||
space(gap);
|
||||
else
|
||||
space(std::max(gap, mesgtime - elapsed));
|
||||
}
|
||||
}
|
||||
|
||||
// Generic method for sending simple protocol messages.
|
||||
//
|
||||
// Args:
|
||||
// headermark: Nr. of usecs for the led to be pulsed for the header mark.
|
||||
// A value of 0 means no header mark.
|
||||
// headerspace: Nr. of usecs for the led to be off after the header mark.
|
||||
// A value of 0 means no header space.
|
||||
// onemark: Nr. of usecs for the led to be pulsed for a '1' bit.
|
||||
// onespace: Nr. of usecs for the led to be fully off for a '1' bit.
|
||||
// zeromark: Nr. of usecs for the led to be pulsed for a '0' bit.
|
||||
// zerospace: Nr. of usecs for the led to be fully off for a '0' bit.
|
||||
// footermark: Nr. of usecs for the led to be pulsed for the footer mark.
|
||||
// A value of 0 means no footer mark.
|
||||
// gap: Nr. of usecs for the led to be off after the footer mark.
|
||||
// This is effectively the gap between messages.
|
||||
// A value of 0 means no gap space.
|
||||
// dataptr: Pointer to the data to be transmitted.
|
||||
// nbytes: Nr. of bytes of data to be sent.
|
||||
// frequency: The frequency we want to modulate at.
|
||||
// Assumes < 1000 means kHz otherwise it is in Hz.
|
||||
// Most common value is 38000 or 38, for 38kHz.
|
||||
// MSBfirst: Flag for bit transmission order. Defaults to MSB->LSB order.
|
||||
// repeat: Nr. of extra times the message will be sent.
|
||||
// e.g. 0 = 1 message sent, 1 = 1 initial + 1 repeat = 2 messages
|
||||
// dutycycle: Percentage duty cycle of the LED.
|
||||
// e.g. 25 = 25% = 1/4 on, 3/4 off.
|
||||
// If you are not sure, try 50 percent.
|
||||
void IRsend::sendGeneric(const uint16_t headermark, const uint32_t headerspace,
|
||||
const uint16_t onemark, const uint32_t onespace,
|
||||
const uint16_t zeromark, const uint32_t zerospace,
|
||||
const uint16_t footermark, const uint32_t gap,
|
||||
const uint8_t *dataptr, const uint16_t nbytes,
|
||||
const uint16_t frequency, const bool MSBfirst,
|
||||
const uint16_t repeat, const uint8_t dutycycle) {
|
||||
// Setup
|
||||
enableIROut(frequency, dutycycle);
|
||||
// We always send a message, even for repeat=0, hence '<= repeat'.
|
||||
for (uint16_t r = 0; r <= repeat; r++) {
|
||||
// Header
|
||||
if (headermark) mark(headermark);
|
||||
if (headerspace) space(headerspace);
|
||||
|
||||
// Data
|
||||
for (uint16_t i = 0; i < nbytes; i++)
|
||||
sendData(onemark, onespace, zeromark, zerospace,
|
||||
*(dataptr + i), 8, MSBfirst);
|
||||
|
||||
// Footer
|
||||
if (footermark) mark(footermark);
|
||||
space(gap);
|
||||
}
|
||||
}
|
||||
|
||||
#if SEND_RAW
|
||||
// Send a raw IRremote message.
|
||||
//
|
||||
// Args:
|
||||
@@ -270,6 +486,7 @@ void IRsend::sendRaw(uint16_t buf[], uint16_t len, uint16_t hz) {
|
||||
}
|
||||
ledOff(); // We potentially have ended with a mark(), so turn of the LED.
|
||||
}
|
||||
#endif // SEND_RAW
|
||||
|
||||
#ifndef UNIT_TEST
|
||||
void IRsend::send(uint16_t type, uint64_t data, uint16_t nbits) {
|
||||
@@ -316,11 +533,20 @@ void IRsend::send(uint16_t type, uint64_t data, uint16_t nbits) {
|
||||
#if SEND_MITSUBISHI
|
||||
case MITSUBISHI: sendMitsubishi(data, nbits); break;
|
||||
#endif
|
||||
#if SEND_MITSUBISHI2
|
||||
case MITSUBISHI2: sendMitsubishi2(data, nbits); break;
|
||||
#endif
|
||||
#if SEND_SHARP
|
||||
case SHARP: sendSharpRaw(data, nbits); break;
|
||||
#endif
|
||||
#if SEND_AIWA_RC_T501
|
||||
case AIWA_RC_T501: sendAiwaRCT501(data, nbits); break;
|
||||
#endif
|
||||
#if SEND_MIDEA
|
||||
case MIDEA: sendMidea(data, nbits); break;
|
||||
#endif
|
||||
#if SEND_GICABLE
|
||||
case GICABLE: sendGICable(data, nbits); break;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,24 +21,52 @@
|
||||
// Constants
|
||||
// Offset (in microseconds) to use in Period time calculations to account for
|
||||
// code excution time in producing the software PWM signal.
|
||||
// Value determined in https://github.com/markszabo/IRremoteESP8266/issues/62
|
||||
#define PERIOD_OFFSET -3
|
||||
// Value was calculated on Wemos D1 mini using v2.4.1 with v2.4.0 ESP core
|
||||
#define PERIOD_OFFSET -5
|
||||
#define DUTY_DEFAULT 50
|
||||
#define DUTY_MAX 100 // Percentage
|
||||
// delayMicroseconds() is only accurate to 16383us.
|
||||
// Ref: https://www.arduino.cc/en/Reference/delayMicroseconds
|
||||
#define MAX_ACCURATE_USEC_DELAY 16383U
|
||||
|
||||
// Classes
|
||||
class IRsend {
|
||||
public:
|
||||
explicit IRsend(uint16_t IRsendPin, bool inverted = false);
|
||||
explicit IRsend(uint16_t IRsendPin, bool inverted = false,
|
||||
bool use_modulation = true);
|
||||
void begin();
|
||||
void enableIROut(uint32_t freq, uint8_t duty = DUTY_DEFAULT);
|
||||
VIRTUAL void _delayMicroseconds(uint32_t usec);
|
||||
VIRTUAL uint16_t mark(uint16_t usec);
|
||||
VIRTUAL void space(uint32_t usec);
|
||||
void calibrate(uint16_t hz = 38000U);
|
||||
int8_t calibrate(uint16_t hz = 38000U);
|
||||
void sendRaw(uint16_t buf[], uint16_t len, uint16_t hz);
|
||||
void sendData(uint16_t onemark, uint32_t onespace, uint16_t zeromark,
|
||||
uint32_t zerospace, uint64_t data, uint16_t nbits,
|
||||
bool MSBfirst = true);
|
||||
void send(uint16_t type, uint64_t data, uint16_t nbits);
|
||||
void sendGeneric(const uint16_t headermark, const uint32_t headerspace,
|
||||
const uint16_t onemark, const uint32_t onespace,
|
||||
const uint16_t zeromark, const uint32_t zerospace,
|
||||
const uint16_t footermark, const uint32_t gap,
|
||||
const uint64_t data, const uint16_t nbits,
|
||||
const uint16_t frequency, const bool MSBfirst,
|
||||
const uint16_t repeat, const uint8_t dutycycle);
|
||||
void sendGeneric(const uint16_t headermark, const uint32_t headerspace,
|
||||
const uint16_t onemark, const uint32_t onespace,
|
||||
const uint16_t zeromark, const uint32_t zerospace,
|
||||
const uint16_t footermark, const uint32_t gap,
|
||||
const uint32_t mesgtime,
|
||||
const uint64_t data, const uint16_t nbits,
|
||||
const uint16_t frequency, const bool MSBfirst,
|
||||
const uint16_t repeat, const uint8_t dutycycle);
|
||||
void sendGeneric(const uint16_t headermark, const uint32_t headerspace,
|
||||
const uint16_t onemark, const uint32_t onespace,
|
||||
const uint16_t zeromark, const uint32_t zerospace,
|
||||
const uint16_t footermark, const uint32_t gap,
|
||||
const uint8_t *dataptr, const uint16_t nbytes,
|
||||
const uint16_t frequency, const bool MSBfirst,
|
||||
const uint16_t repeat, const uint8_t dutycycle);
|
||||
void send(uint16_t type, uint64_t data, uint16_t nbits);
|
||||
#if (SEND_NEC || SEND_SHERWOOD || SEND_AIWA_RC_T501 || SEND_SANYO)
|
||||
void sendNEC(uint64_t data, uint16_t nbits = NEC_BITS, uint16_t repeat = 0);
|
||||
uint32_t encodeNEC(uint16_t address, uint16_t command);
|
||||
@@ -135,6 +163,10 @@ class IRsend {
|
||||
void sendMitsubishi(uint64_t data, uint16_t nbits = MITSUBISHI_BITS,
|
||||
uint16_t repeat = MITSUBISHI_MIN_REPEAT);
|
||||
#endif
|
||||
#if SEND_MITSUBISHI2
|
||||
void sendMitsubishi2(uint64_t data, uint16_t nbits = MITSUBISHI_BITS,
|
||||
uint16_t repeat = MITSUBISHI_MIN_REPEAT);
|
||||
#endif
|
||||
#if SEND_MITSUBISHI_AC
|
||||
void sendMitsubishiAC(unsigned char data[],
|
||||
uint16_t nbytes = MITSUBISHI_AC_STATE_LENGTH,
|
||||
@@ -157,6 +189,7 @@ class IRsend {
|
||||
void sendDaikin(unsigned char data[],
|
||||
uint16_t nbytes = DAIKIN_COMMAND_LENGTH,
|
||||
uint16_t repeat = 0);
|
||||
void sendDaikinGapHeader();
|
||||
#endif
|
||||
#if SEND_AIWA_RC_T501
|
||||
void sendAiwaRCT501(uint64_t data, uint16_t nbits = AIWA_RC_T501_BITS,
|
||||
@@ -175,12 +208,61 @@ class IRsend {
|
||||
uint16_t nbytes = ARGO_COMMAND_LENGTH,
|
||||
uint16_t repeat = 0);
|
||||
#endif
|
||||
|
||||
#if SEND_TROTEC
|
||||
void sendTrotec(unsigned char data[],
|
||||
uint16_t nbytes = TROTEC_COMMAND_LENGTH,
|
||||
uint16_t repeat = 0);
|
||||
#endif
|
||||
#if SEND_NIKAI
|
||||
void sendNikai(uint64_t data, uint16_t nbits = NIKAI_BITS,
|
||||
uint16_t repeat = 0);
|
||||
#endif
|
||||
#if SEND_TOSHIBA_AC
|
||||
void sendToshibaAC(unsigned char data[],
|
||||
uint16_t nbytes = TOSHIBA_AC_STATE_LENGTH,
|
||||
uint16_t repeat = TOSHIBA_AC_MIN_REPEAT);
|
||||
#endif
|
||||
#if SEND_MIDEA
|
||||
void sendMidea(uint64_t data, uint16_t nbits = MIDEA_BITS,
|
||||
uint16_t repeat = MIDEA_MIN_REPEAT);
|
||||
#endif
|
||||
#if SEND_MAGIQUEST
|
||||
void sendMagiQuest(uint64_t data, uint16_t nbits = MAGIQUEST_BITS,
|
||||
uint16_t repeat = 0);
|
||||
uint64_t encodeMagiQuest(uint32_t wand_id, uint16_t magnitude);
|
||||
#endif
|
||||
#if SEND_LASERTAG
|
||||
void sendLasertag(uint64_t data, uint16_t nbits = LASERTAG_BITS,
|
||||
uint16_t repeat = LASERTAG_MIN_REPEAT);
|
||||
#endif
|
||||
#if SEND_CARRIER_AC
|
||||
void sendCarrierAC(uint64_t data, uint16_t nbits = CARRIER_AC_BITS,
|
||||
uint16_t repeat = CARRIER_AC_MIN_REPEAT);
|
||||
#endif
|
||||
#if SEND_HAIER_AC
|
||||
void sendHaierAC(unsigned char data[],
|
||||
uint16_t nbytes = HAIER_AC_STATE_LENGTH,
|
||||
uint16_t repeat = 0);
|
||||
#endif
|
||||
#if SEND_HITACHI_AC
|
||||
void sendHitachiAC(unsigned char data[],
|
||||
uint16_t nbytes = HITACHI_AC_STATE_LENGTH,
|
||||
uint16_t repeat = 0);
|
||||
#endif
|
||||
#if SEND_HITACHI_AC1
|
||||
void sendHitachiAC1(unsigned char data[],
|
||||
uint16_t nbytes = HITACHI_AC1_STATE_LENGTH,
|
||||
uint16_t repeat = 0);
|
||||
#endif
|
||||
#if SEND_HITACHI_AC2
|
||||
void sendHitachiAC2(unsigned char data[],
|
||||
uint16_t nbytes = HITACHI_AC2_STATE_LENGTH,
|
||||
uint16_t repeat = 0);
|
||||
#endif
|
||||
#if SEND_GICABLE
|
||||
void sendGICable(uint64_t data, uint16_t nbits = GICABLE_BITS,
|
||||
uint16_t repeat = GICABLE_MIN_REPEAT);
|
||||
#endif
|
||||
|
||||
protected:
|
||||
#ifdef UNIT_TEST
|
||||
@@ -193,13 +275,16 @@ class IRsend {
|
||||
#endif // UNIT_TEST
|
||||
uint8_t outputOn;
|
||||
uint8_t outputOff;
|
||||
VIRTUAL void ledOff();
|
||||
VIRTUAL void ledOn();
|
||||
|
||||
private:
|
||||
uint16_t onTimePeriod;
|
||||
uint16_t offTimePeriod;
|
||||
uint16_t IRpin;
|
||||
int8_t periodOffset;
|
||||
void ledOff();
|
||||
uint8_t _dutycycle;
|
||||
bool modulation;
|
||||
uint32_t calcUSecPeriod(uint32_t hz, bool use_offset = true);
|
||||
};
|
||||
|
||||
|
||||
@@ -5,6 +5,11 @@
|
||||
#include <Arduino.h>
|
||||
#endif
|
||||
|
||||
#ifdef UNIT_TEST
|
||||
// Used to help simulate elapsed time in unit tests.
|
||||
extern uint32_t _IRtimer_unittest_now;
|
||||
#endif // UNIT_TEST
|
||||
|
||||
// This class performs a simple time in useconds since instantiated.
|
||||
// Handles when the system timer wraps around (once).
|
||||
|
||||
@@ -16,7 +21,7 @@ void IRtimer::reset() {
|
||||
#ifndef UNIT_TEST
|
||||
start = micros();
|
||||
#else
|
||||
start = 0;
|
||||
start = _IRtimer_unittest_now;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -24,10 +29,17 @@ uint32_t IRtimer::elapsed() {
|
||||
#ifndef UNIT_TEST
|
||||
uint32_t now = micros();
|
||||
#else
|
||||
uint32_t now = 0;
|
||||
uint32_t now = _IRtimer_unittest_now;
|
||||
#endif
|
||||
if (start <= now) // Check if the system timer has wrapped.
|
||||
return now - start; // No wrap.
|
||||
else
|
||||
return UINT32_MAX - start + now; // Has wrapped.
|
||||
}
|
||||
|
||||
// Only used in unit testing.
|
||||
#ifdef UNIT_TEST
|
||||
void IRtimer::add(uint32_t usecs) {
|
||||
_IRtimer_unittest_now += usecs;
|
||||
}
|
||||
#endif // UNIT_TEST
|
||||
@@ -12,6 +12,9 @@ class IRtimer {
|
||||
IRtimer();
|
||||
void reset();
|
||||
uint32_t elapsed();
|
||||
#ifdef UNIT_TEST
|
||||
static void add(uint32_t usecs);
|
||||
#endif // UNIT_TEST
|
||||
|
||||
private:
|
||||
uint32_t start;
|
||||
|
||||
@@ -4,10 +4,15 @@
|
||||
#ifndef UNIT_TEST
|
||||
#include <Arduino.h>
|
||||
#endif
|
||||
|
||||
#define __STDC_LIMIT_MACROS
|
||||
#include <stdint.h>
|
||||
#include <algorithm>
|
||||
#ifndef ARDUINO
|
||||
#include <string>
|
||||
#endif
|
||||
#include "IRrecv.h"
|
||||
#include "IRremoteESP8266.h"
|
||||
|
||||
// Reverse the order of the requested least significant nr. of bits.
|
||||
// Args:
|
||||
@@ -30,30 +35,306 @@ uint64_t reverseBits(uint64_t input, uint16_t nbits) {
|
||||
return (input << nbits) | output;
|
||||
}
|
||||
|
||||
// Convert a uint64_t (unsigned long long) to a string.
|
||||
// Arduino String/toInt/Serial.print() can't handle printing 64 bit values.
|
||||
//
|
||||
// Args:
|
||||
// input: The value to print
|
||||
// base: The output base.
|
||||
// Returns:
|
||||
// A string representation of the integer.
|
||||
// Note: Based on Arduino's Print::printNumber()
|
||||
#ifdef ARDUINO // Arduino's & C++'s string implementations can't co-exist.
|
||||
String uint64ToString(uint64_t input, uint8_t base) {
|
||||
String result = "";
|
||||
#else
|
||||
std::string uint64ToString(uint64_t input, uint8_t base) {
|
||||
std::string result = "";
|
||||
#endif
|
||||
// prevent issues if called with base <= 1
|
||||
if (base < 2) base = 10;
|
||||
// Check we have a base that we can actually print.
|
||||
// i.e. [0-9A-Z] == 36
|
||||
if (base > 36) base = 10;
|
||||
|
||||
do {
|
||||
char c = input % base;
|
||||
input /= base;
|
||||
|
||||
if (c < 10)
|
||||
c +='0';
|
||||
else
|
||||
c += 'A' - 10;
|
||||
result = c + result;
|
||||
} while (input);
|
||||
return result;
|
||||
}
|
||||
|
||||
#ifdef ARDUINO
|
||||
// Print a uint64_t/unsigned long long to the Serial port
|
||||
// Serial.print() can't handle printing long longs. (uint64_t)
|
||||
//
|
||||
// Args:
|
||||
// input: The value to print
|
||||
// base: The output base.
|
||||
// Note: Based on Arduino's Print::printNumber()
|
||||
void serialPrintUint64(uint64_t input, uint8_t base) {
|
||||
char buf[8 * sizeof(input) + 1]; // Assumes 8-bit chars plus zero byte.
|
||||
char *str = &buf[sizeof(buf) - 1];
|
||||
Serial.print(uint64ToString(input, base));
|
||||
}
|
||||
#endif
|
||||
|
||||
// Convert a protocol type (enum etc) to a human readable string.
|
||||
// Args:
|
||||
// protocol: Nr. (enum) of the protocol.
|
||||
// isRepeat: A flag indicating if it is a repeat message of the protocol.
|
||||
// Returns:
|
||||
// A string containing the protocol name.
|
||||
#ifdef ARDUINO // Arduino's & C++'s string implementations can't co-exist.
|
||||
String typeToString(const decode_type_t protocol, const bool isRepeat) {
|
||||
String result = "";
|
||||
#else
|
||||
std::string typeToString(const decode_type_t protocol,
|
||||
const bool isRepeat) {
|
||||
std::string result = "";
|
||||
#endif
|
||||
switch (protocol) {
|
||||
default:
|
||||
case UNKNOWN: result = "UNKNOWN"; break;
|
||||
case UNUSED: result = "UNUSED"; break;
|
||||
case AIWA_RC_T501: result = "AIWA_RC_T501"; break;
|
||||
case ARGO: result = "ARGO"; break;
|
||||
case CARRIER_AC: result = "CARRIER_AC"; break;
|
||||
case COOLIX: result = "COOLIX"; break;
|
||||
case DAIKIN: result = "DAIKIN"; break;
|
||||
case DENON: result = "DENON"; break;
|
||||
case DISH: result = "DISH"; break;
|
||||
case FUJITSU_AC: result = "FUJITSU_AC"; break;
|
||||
case GICABLE: result = "GICABLE"; break;
|
||||
case GLOBALCACHE: result = "GLOBALCACHE"; break;
|
||||
case GREE: result = "GREE"; break;
|
||||
case HAIER_AC: result = "HAIER_AC"; break;
|
||||
case HITACHI_AC: result = "HITACHI_AC"; break;
|
||||
case HITACHI_AC1: result = "HITACHI_AC1"; break;
|
||||
case HITACHI_AC2: result = "HITACHI_AC2"; break;
|
||||
case JVC: result = "JVC"; break;
|
||||
case KELVINATOR: result = "KELVINATOR"; break;
|
||||
case LG: result = "LG"; break;
|
||||
case LASERTAG: result = "LASERTAG"; break;
|
||||
case MAGIQUEST: result = "MAGIQUEST"; break;
|
||||
case MIDEA: result = "MIDEA"; break;
|
||||
case MITSUBISHI: result = "MITSUBISHI"; break;
|
||||
case MITSUBISHI2: result = "MITSUBISHI2"; break;
|
||||
case MITSUBISHI_AC: result = "MITSUBISHI_AC"; break;
|
||||
case NEC: result = "NEC"; break;
|
||||
case NEC_LIKE: result = "NEC (non-strict)"; break;
|
||||
case NIKAI: result = "NIKAI"; break;
|
||||
case PANASONIC: result = "PANASONIC"; break;
|
||||
case PRONTO: result = "PRONTO"; break;
|
||||
case RAW: result = "RAW"; break;
|
||||
case RC5: result = "RC5"; break;
|
||||
case RC5X: result = "RC5X"; break;
|
||||
case RC6: result = "RC6"; break;
|
||||
case RCMM: result = "RCMM"; break;
|
||||
case SAMSUNG: result = "SAMSUNG"; break;
|
||||
case SANYO: result = "SANYO"; break;
|
||||
case SANYO_LC7461: result = "SANYO_LC7461"; break;
|
||||
case SHARP: result = "SHARP"; break;
|
||||
case SHERWOOD: result = "SHERWOOD"; break;
|
||||
case SONY: result = "SONY"; break;
|
||||
case TOSHIBA_AC: result = "TOSHIBA_AC"; break;
|
||||
case TROTEC: result = "TROTEC"; break;
|
||||
case WHYNTER: result = "WHYNTER"; break;
|
||||
}
|
||||
if (isRepeat) result += " (Repeat)";
|
||||
return result;
|
||||
}
|
||||
|
||||
// Does the given protocol use a complex state as part of the decode?
|
||||
bool hasACState(const decode_type_t protocol) {
|
||||
switch (protocol) {
|
||||
case DAIKIN:
|
||||
case FUJITSU_AC:
|
||||
case GREE:
|
||||
case HAIER_AC:
|
||||
case HITACHI_AC:
|
||||
case HITACHI_AC1:
|
||||
case HITACHI_AC2:
|
||||
case KELVINATOR:
|
||||
case MITSUBISHI_AC:
|
||||
case TOSHIBA_AC:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Return the corrected length of a 'raw' format array structure
|
||||
// after over-large values are converted into multiple entries.
|
||||
// Args:
|
||||
// results: A ptr to a decode result.
|
||||
// Returns:
|
||||
// A uint16_t containing the length.
|
||||
uint16_t getCorrectedRawLength(const decode_results *results) {
|
||||
uint16_t extended_length = results->rawlen - 1;
|
||||
for (uint16_t i = 0; i < results->rawlen - 1; i++) {
|
||||
uint32_t usecs = results->rawbuf[i] * RAWTICK;
|
||||
// Add two extra entries for multiple larger than UINT16_MAX it is.
|
||||
extended_length += (usecs / (UINT16_MAX + 1)) * 2;
|
||||
}
|
||||
return extended_length;
|
||||
}
|
||||
|
||||
// Return a string containing the key values of a decode_results structure
|
||||
// in a C/C++ code style format.
|
||||
#ifdef ARDUINO
|
||||
String resultToSourceCode(const decode_results *results) {
|
||||
String output = "";
|
||||
#else
|
||||
std::string resultToSourceCode(const decode_results *results) {
|
||||
std::string output = "";
|
||||
#endif
|
||||
// Start declaration
|
||||
output += "uint16_t "; // variable type
|
||||
output += "rawData["; // array name
|
||||
output += uint64ToString(getCorrectedRawLength(results), 10);
|
||||
// array size
|
||||
output += "] = {"; // Start declaration
|
||||
|
||||
*str = '\0';
|
||||
// Dump data
|
||||
for (uint16_t i = 1; i < results->rawlen; i++) {
|
||||
uint32_t usecs;
|
||||
for (usecs = results->rawbuf[i] * RAWTICK;
|
||||
usecs > UINT16_MAX;
|
||||
usecs -= UINT16_MAX) {
|
||||
output += uint64ToString(UINT16_MAX);
|
||||
if (i % 2)
|
||||
output += ", 0, ";
|
||||
else
|
||||
output += ", 0, ";
|
||||
}
|
||||
output += uint64ToString(usecs, 10);
|
||||
if (i < results->rawlen - 1)
|
||||
output += ", "; // ',' not needed on the last one
|
||||
if (i % 2 == 0) output += " "; // Extra if it was even.
|
||||
}
|
||||
|
||||
// prevent crash if called with base == 1
|
||||
if (base < 2) base = 10;
|
||||
// End declaration
|
||||
output +="};";
|
||||
|
||||
do {
|
||||
char c = input % base;
|
||||
input /= base;
|
||||
// Comment
|
||||
output += " // " + typeToString(results->decode_type, results->repeat);
|
||||
// Only display the value if the decode type doesn't have an A/C state.
|
||||
if (!hasACState(results->decode_type))
|
||||
output += " " + uint64ToString(results->value, 16);
|
||||
output += "\n";
|
||||
|
||||
*--str = c < 10 ? c + '0' : c + 'A' - 10;
|
||||
} while (input);
|
||||
// Now dump "known" codes
|
||||
if (results->decode_type != UNKNOWN) {
|
||||
if (hasACState(results->decode_type)) {
|
||||
#if DECODE_AC
|
||||
uint16_t nbytes = results->bits / 8;
|
||||
output += "uint8_t state[" + uint64ToString(nbytes) + "] = {";
|
||||
for (uint16_t i = 0; i < nbytes; i++) {
|
||||
output += "0x";
|
||||
if (results->state[i] < 0x10) output += "0";
|
||||
output += uint64ToString(results->state[i], 16);
|
||||
if (i < nbytes - 1)
|
||||
output += ", ";
|
||||
}
|
||||
output += "};\n";
|
||||
#endif // DECODE_AC
|
||||
} else {
|
||||
// Simple protocols
|
||||
// Some protocols have an address &/or command.
|
||||
// NOTE: It will ignore the atypical case when a message has been
|
||||
// decoded but the address & the command are both 0.
|
||||
if (results->address > 0 || results->command > 0) {
|
||||
output += "uint32_t address = 0x" +
|
||||
uint64ToString(results->address, 16) + ";\n";
|
||||
output += "uint32_t command = 0x" +
|
||||
uint64ToString(results->command, 16) + ";\n";
|
||||
}
|
||||
// Most protocols have data
|
||||
output += "uint64_t data = 0x" + uint64ToString(results->value, 16) +
|
||||
";\n";
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
#ifndef UNIT_TEST
|
||||
Serial.print(str);
|
||||
// Dump out the decode_results structure.
|
||||
//
|
||||
#ifdef ARDUINO
|
||||
String resultToTimingInfo(const decode_results *results) {
|
||||
String output = "";
|
||||
String value = "";
|
||||
#else
|
||||
std::string resultToTimingInfo(const decode_results *results) {
|
||||
std::string output = "";
|
||||
std::string value = "";
|
||||
#endif
|
||||
output += "Raw Timing[" + uint64ToString(results->rawlen - 1, 10) + "]:\n";
|
||||
|
||||
for (uint16_t i = 1; i < results->rawlen; i++) {
|
||||
if (i % 2 == 0)
|
||||
output += "-"; // even
|
||||
else
|
||||
output += " +"; // odd
|
||||
value = uint64ToString(results->rawbuf[i] * RAWTICK);
|
||||
// Space pad the value till it is at least 6 chars long.
|
||||
while (value.length() < 6)
|
||||
value = " " + value;
|
||||
output += value;
|
||||
if (i < results->rawlen - 1)
|
||||
output += ", "; // ',' not needed for last one
|
||||
if (!(i % 8)) output += "\n"; // Newline every 8 entries.
|
||||
}
|
||||
output += "\n";
|
||||
return output;
|
||||
}
|
||||
|
||||
// Dump out the decode_results structure.
|
||||
//
|
||||
#ifdef ARDUINO
|
||||
String resultToHumanReadableBasic(const decode_results *results) {
|
||||
String output = "";
|
||||
#else
|
||||
std::string resultToHumanReadableBasic(const decode_results *results) {
|
||||
std::string output = "";
|
||||
#endif
|
||||
// Show Encoding standard
|
||||
output += "Encoding : " +
|
||||
typeToString(results->decode_type, results->repeat) + "\n";
|
||||
|
||||
// Show Code & length
|
||||
output += "Code : ";
|
||||
if (hasACState(results->decode_type)) {
|
||||
#if DECODE_AC
|
||||
for (uint16_t i = 0; results->bits > i * 8; i++) {
|
||||
if (results->state[i] < 0x10) output += "0"; // Zero pad
|
||||
output += uint64ToString(results->state[i], 16);
|
||||
}
|
||||
#endif // DECODE_AC
|
||||
} else {
|
||||
output += uint64ToString(results->value, 16);
|
||||
}
|
||||
output += " (" + uint64ToString(results->bits) + " bits)\n";
|
||||
return output;
|
||||
}
|
||||
|
||||
uint8_t sumBytes(uint8_t *start, const uint16_t length, const uint8_t init) {
|
||||
uint8_t checksum = init;
|
||||
uint8_t *ptr;
|
||||
for (ptr = start; ptr - start < length; ptr++)
|
||||
checksum += *ptr;
|
||||
return checksum;
|
||||
}
|
||||
|
||||
uint64_t invertBits(const uint64_t data, const uint16_t nbits) {
|
||||
// No change if we are asked to invert no bits.
|
||||
if (nbits == 0) return data;
|
||||
uint64_t result = ~data;
|
||||
// If we are asked to invert all the bits or more than we have, it's simple.
|
||||
if (nbits >= sizeof(data) * 8) return result;
|
||||
// Mask off any unwanted bits and return the result.
|
||||
return (result & ((1ULL << nbits) - 1));
|
||||
}
|
||||
@@ -3,10 +3,37 @@
|
||||
|
||||
// Copyright 2017 David Conran
|
||||
|
||||
#ifndef UNIT_TEST
|
||||
#include <Arduino.h>
|
||||
#endif
|
||||
#define __STDC_LIMIT_MACROS
|
||||
#include <stdint.h>
|
||||
#ifndef ARDUINO
|
||||
#include <string>
|
||||
#endif
|
||||
#include "IRremoteESP8266.h"
|
||||
#include "IRrecv.h"
|
||||
|
||||
uint64_t reverseBits(uint64_t input, uint16_t nbits);
|
||||
void serialPrintUint64(uint64_t input, uint8_t base);
|
||||
#ifdef ARDUINO // Arduino's & C++'s string implementations can't co-exist.
|
||||
String uint64ToString(uint64_t input, uint8_t base = 10);
|
||||
String typeToString(const decode_type_t protocol,
|
||||
const bool isRepeat = false);
|
||||
void serialPrintUint64(uint64_t input, uint8_t base = 10);
|
||||
String resultToSourceCode(const decode_results *results);
|
||||
String resultToTimingInfo(const decode_results *results);
|
||||
String resultToHumanReadableBasic(const decode_results *results);
|
||||
#else
|
||||
std::string uint64ToString(uint64_t input, uint8_t base = 10);
|
||||
std::string typeToString(const decode_type_t protocol,
|
||||
const bool isRepeat = false);
|
||||
std::string resultToSourceCode(const decode_results *results);
|
||||
std::string resultToTimingInfo(const decode_results *results);
|
||||
std::string resultToHumanReadableBasic(const decode_results *results);
|
||||
#endif
|
||||
bool hasACState(const decode_type_t protocol);
|
||||
uint16_t getCorrectedRawLength(const decode_results *results);
|
||||
uint8_t sumBytes(uint8_t *start, const uint16_t length, const uint8_t init = 0);
|
||||
uint64_t invertBits(const uint64_t data, const uint16_t nbits);
|
||||
|
||||
#endif // IRUTILS_H_
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include "IRrecv.h"
|
||||
#include "IRsend.h"
|
||||
#include "IRtimer.h"
|
||||
|
||||
// AAA IIIII W W AAA
|
||||
// A A I W W A A
|
||||
@@ -22,7 +21,7 @@
|
||||
#define AIWA_RC_T501_POST_DATA 1ULL
|
||||
|
||||
#if SEND_AIWA_RC_T501
|
||||
// Send a Aiwa RC T501 formatted message.
|
||||
// Send an Aiwa RC T501 formatted message.
|
||||
//
|
||||
// Args:
|
||||
// data: The message to be sent.
|
||||
|
||||
@@ -11,39 +11,31 @@ Copyright 2017 Schmolders
|
||||
|
||||
// Constants
|
||||
// using SPACE modulation. MARK is always const 400u
|
||||
#define ARGO_PREAMBLE_1 6400U // Mark
|
||||
#define ARGO_PREAMBLE_2 3300U // Space
|
||||
#define ARGO_MARK 400U
|
||||
#define ARGO_HDR_MARK 6400U // Mark
|
||||
#define ARGO_HDR_SPACE 3300U // Space
|
||||
#define ARGO_BIT_MARK 400U
|
||||
#define ARGO_ONE_SPACE 2200U
|
||||
#define ARGO_ZERO_SPACE 900U
|
||||
|
||||
#if SEND_ARGO
|
||||
// Send a Argo A/C message.
|
||||
// Send an Argo A/C message.
|
||||
//
|
||||
// Args:
|
||||
// data: An array of ARGO_COMMAND_LENGTH bytes containing the IR command.
|
||||
//
|
||||
// Status: ALPHA / Untested.
|
||||
//
|
||||
// Overloading the IRSend Function
|
||||
|
||||
void IRsend::sendArgo(unsigned char data[], uint16_t nbytes, uint16_t repeat) {
|
||||
// Check if we have enough bytes to send a proper message.
|
||||
if (nbytes < ARGO_COMMAND_LENGTH) return;
|
||||
// Set IR carrier frequency
|
||||
enableIROut(38);
|
||||
for (uint16_t r = 0; r <= repeat; r++) {
|
||||
// Header
|
||||
// TODO(kaschmo): validate
|
||||
mark(ARGO_PREAMBLE_1);
|
||||
space(ARGO_PREAMBLE_2);
|
||||
// send data, defined in IRSend.cpp
|
||||
for (uint16_t i = 0; i < nbytes; i++)
|
||||
sendData(ARGO_MARK, ARGO_ONE_SPACE, ARGO_MARK,
|
||||
ARGO_ZERO_SPACE, data[i], 8, false);
|
||||
// send LSB first reverses the bit order in array for sending.
|
||||
}
|
||||
// TODO(kaschmo): validate
|
||||
sendGeneric(ARGO_HDR_MARK, ARGO_HDR_SPACE,
|
||||
ARGO_BIT_MARK, ARGO_ONE_SPACE,
|
||||
ARGO_BIT_MARK, ARGO_ZERO_SPACE,
|
||||
0, 0, // No Footer.
|
||||
data, nbytes, 38, false, repeat, 50);
|
||||
}
|
||||
#endif // SEND_ARGO
|
||||
|
||||
IRArgoAC::IRArgoAC(uint16_t pin) : _irsend(pin) {
|
||||
stateReset();
|
||||
@@ -53,11 +45,12 @@ void IRArgoAC::begin() {
|
||||
_irsend.begin();
|
||||
}
|
||||
|
||||
#if SEND_ARGO
|
||||
void IRArgoAC::send() {
|
||||
// Serial.println("Sending IR code"); // Only for Debug
|
||||
checksum(); // Create valid checksum before sending
|
||||
_irsend.sendArgo(argo);
|
||||
}
|
||||
#endif // SEND_ARGO
|
||||
|
||||
void IRArgoAC::checksum() {
|
||||
uint8_t sum = 2; // Corresponds to byte 11 being constant 0b01
|
||||
@@ -261,4 +254,3 @@ void IRArgoAC::setRoomTemp(uint8_t temp) {
|
||||
argo[3] += temp << 5; // Append to bit 5,6,7
|
||||
argo[4] += temp >> 3; // Remove lowest 3 bits and append in 0,1
|
||||
}
|
||||
#endif // SEND_ARGO
|
||||
@@ -57,12 +57,13 @@
|
||||
#define ARGO_FLAP_FULL 7U // 0b111
|
||||
|
||||
|
||||
#if SEND_ARGO
|
||||
class IRArgoAC {
|
||||
public:
|
||||
explicit IRArgoAC(uint16_t pin);
|
||||
|
||||
#if SEND_ARGO
|
||||
void send();
|
||||
#endif // SEND_ARGO
|
||||
void begin();
|
||||
void on();
|
||||
void off();
|
||||
@@ -119,6 +120,5 @@ class IRArgoAC {
|
||||
uint8_t max_mode; // on/off
|
||||
uint8_t ifeel_mode; // on/off
|
||||
};
|
||||
#endif // SEND_ARGO
|
||||
|
||||
#endif // IR_ARGO_H_
|
||||
@@ -0,0 +1,116 @@
|
||||
// Copyright 2018 David Conran
|
||||
|
||||
#include "IRrecv.h"
|
||||
#include "IRsend.h"
|
||||
#include "IRutils.h"
|
||||
|
||||
// CCCCC AAA RRRRRR RRRRRR IIIII EEEEEEE RRRRRR
|
||||
// CC C AAAAA RR RR RR RR III EE RR RR
|
||||
// CC AA AA RRRRRR RRRRRR III EEEEE RRRRRR
|
||||
// CC C AAAAAAA RR RR RR RR III EE RR RR
|
||||
// CCCCC AA AA RR RR RR RR IIIII EEEEEEE RR RR
|
||||
|
||||
// Suits Carrier/Surrey HVAC models:
|
||||
// 42QG5A55970 (remote)
|
||||
// 619EGX0090E0 / 619EGX0120E0 / 619EGX0180E0 / 619EGX0220E0 (indoor units)
|
||||
// 53NGK009/012 (inverter)
|
||||
|
||||
// Constants
|
||||
// Ref:
|
||||
// https://github.com/markszabo/IRremoteESP8266/issues/385
|
||||
#define CARRIER_AC_HDR_MARK 8532U
|
||||
#define CARRIER_AC_HDR_SPACE 4228U
|
||||
#define CARRIER_AC_BIT_MARK 628U
|
||||
#define CARRIER_AC_ONE_SPACE 1320U
|
||||
#define CARRIER_AC_ZERO_SPACE 532U
|
||||
#define CARRIER_AC_GAP 20000U
|
||||
|
||||
#if SEND_CARRIER_AC
|
||||
// Send a Carrier HVAC formatted message.
|
||||
//
|
||||
// Args:
|
||||
// data: The message to be sent.
|
||||
// nbits: The bit size of the message being sent. typically CARRIER_AC_BITS.
|
||||
// repeat: The number of times the message is to be repeated.
|
||||
//
|
||||
// Status: BETA / Appears to work on real devices.
|
||||
//
|
||||
void IRsend::sendCarrierAC(uint64_t data, uint16_t nbits, uint16_t repeat) {
|
||||
for (uint16_t r = 0; r <= repeat; r++) {
|
||||
uint64_t temp_data = data;
|
||||
// Carrier sends the data block three times. normal + inverted + normal.
|
||||
for (uint16_t i = 0; i < 3; i++) {
|
||||
sendGeneric(CARRIER_AC_HDR_MARK, CARRIER_AC_HDR_SPACE,
|
||||
CARRIER_AC_BIT_MARK, CARRIER_AC_ONE_SPACE,
|
||||
CARRIER_AC_BIT_MARK, CARRIER_AC_ZERO_SPACE,
|
||||
CARRIER_AC_BIT_MARK, CARRIER_AC_GAP,
|
||||
temp_data, nbits, 38, true, 0, 50);
|
||||
temp_data = invertBits(temp_data, nbits);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if DECODE_CARRIER_AC
|
||||
// Decode the supplied Carrier HVAC message.
|
||||
// Carrier HVAC messages contain only 32 bits, but it is sent three(3) times.
|
||||
// i.e. normal + inverted + normal
|
||||
// Args:
|
||||
// results: Ptr to the data to decode and where to store the decode result.
|
||||
// nbits: Nr. of bits to expect in the data portion.
|
||||
// Typically CARRIER_AC_BITS.
|
||||
// strict: Flag to indicate if we strictly adhere to the specification.
|
||||
// Returns:
|
||||
// boolean: True if it can decode it, false if it can't.
|
||||
//
|
||||
// Status: ALPHA / Untested.
|
||||
//
|
||||
bool IRrecv::decodeCarrierAC(decode_results *results, uint16_t nbits,
|
||||
bool strict) {
|
||||
if (results->rawlen < ((2 * nbits + HEADER + FOOTER) * 3) - 1)
|
||||
return false; // Can't possibly be a valid Carrier message.
|
||||
if (strict && nbits != CARRIER_AC_BITS)
|
||||
return false; // We expect Carrier to be 32 bits of message.
|
||||
|
||||
uint64_t data = 0;
|
||||
uint64_t prev_data = 0;
|
||||
uint16_t offset = OFFSET_START;
|
||||
|
||||
for (uint8_t i = 0; i < 3; i++) {
|
||||
prev_data = data;
|
||||
// Header
|
||||
if (!matchMark(results->rawbuf[offset++], CARRIER_AC_HDR_MARK))
|
||||
return false;
|
||||
if (!matchSpace(results->rawbuf[offset++], CARRIER_AC_HDR_SPACE))
|
||||
return false;
|
||||
// Data
|
||||
match_result_t data_result = matchData(&(results->rawbuf[offset]), nbits,
|
||||
CARRIER_AC_BIT_MARK,
|
||||
CARRIER_AC_ONE_SPACE,
|
||||
CARRIER_AC_BIT_MARK,
|
||||
CARRIER_AC_ZERO_SPACE);
|
||||
if (data_result.success == false) return false;
|
||||
data = data_result.data;
|
||||
offset += data_result.used;
|
||||
// Footer
|
||||
if (!matchMark(results->rawbuf[offset++], CARRIER_AC_BIT_MARK))
|
||||
return false;
|
||||
if (offset < results->rawlen &&
|
||||
!matchAtLeast(results->rawbuf[offset++], CARRIER_AC_GAP))
|
||||
return false;
|
||||
// Compliance.
|
||||
if (strict) {
|
||||
// Check if the data is an inverted copy of the previous data.
|
||||
if (i > 0 && prev_data != invertBits(data, nbits)) return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Success
|
||||
results->bits = nbits;
|
||||
results->value = data;
|
||||
results->decode_type = CARRIER_AC;
|
||||
results->address = data >> 16;
|
||||
results->command = data & 0xFFFF;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
#include "IRrecv.h"
|
||||
#include "IRsend.h"
|
||||
#include "IRtimer.h"
|
||||
#include "IRutils.h"
|
||||
|
||||
// CCCCC OOOOO OOOOO LL IIIII XX XX
|
||||
@@ -99,7 +98,7 @@ bool IRrecv::decodeCOOLIX(decode_results *results, uint16_t nbits,
|
||||
if (results->rawlen < 2 * 2 * nbits + HEADER + FOOTER - 1)
|
||||
return false; // Can't possibly be a valid COOLIX message.
|
||||
if (strict && nbits != COOLIX_BITS)
|
||||
return false; // Not strictly an COOLIX message.
|
||||
return false; // Not strictly a COOLIX message.
|
||||
if (nbits % 8 != 0) // nbits has to be a multiple of nr. of bits in a byte.
|
||||
return false;
|
||||
|
||||
|
||||
@@ -4,12 +4,18 @@ Read more at:
|
||||
http://harizanov.com/2012/02/control-daikin-air-conditioner-over-the-internet/
|
||||
|
||||
Copyright 2016 sillyfrog
|
||||
Copyright 2017 sillyfrog, crankyoldgit
|
||||
*/
|
||||
|
||||
#include "ir_Daikin.h"
|
||||
#include <algorithm>
|
||||
#ifndef ARDUINO
|
||||
#include <string>
|
||||
#endif
|
||||
#include "IRremoteESP8266.h"
|
||||
#include "IRutils.h"
|
||||
#include "IRrecv.h"
|
||||
#include "IRsend.h"
|
||||
|
||||
// DDDDD AAA IIIII KK KK IIIII NN NN
|
||||
// DD DD AAAAA III KK KK III NNN NN
|
||||
@@ -20,15 +26,20 @@ Copyright 2016 sillyfrog
|
||||
// Constants
|
||||
// Ref:
|
||||
// https://github.com/mharizanov/Daikin-AC-remote-control-over-the-Internet/tree/master/IRremote
|
||||
#define DAIKIN_HDR_MARK 3650U // DAIKIN_ZERO_MARK * 8
|
||||
#define DAIKIN_HDR_SPACE 1623U // DAIKIN_ZERO_MARK * 4
|
||||
#define DAIKIN_ONE_SPACE 1280U
|
||||
#define DAIKIN_ONE_MARK 428U
|
||||
#define DAIKIN_ZERO_MARK 428U
|
||||
#define DAIKIN_ZERO_SPACE 428U
|
||||
#define DAIKIN_GAP 29000U
|
||||
// http://rdlab.cdmt.vn/project-2013/daikin-ir-protocol
|
||||
|
||||
#if SEND_DAIKIN
|
||||
// Original header
|
||||
// static uint8_t header1[DAIKIN_HEADER1_LENGTH];
|
||||
// header1[0] = 0b00010001;
|
||||
// header1[1] = 0b11011010;
|
||||
// header1[2] = 0b00100111;
|
||||
// header1[3] = 0b00000000;
|
||||
// header1[4] = 0b11000101;
|
||||
// header1[5] = 0b00000000;
|
||||
// header1[6] = 0b00000000;
|
||||
// header1[7] = 0b11010111;
|
||||
|
||||
// Send a Daikin A/C message.
|
||||
//
|
||||
// Args:
|
||||
@@ -43,32 +54,36 @@ void IRsend::sendDaikin(unsigned char data[], uint16_t nbytes,
|
||||
uint16_t repeat) {
|
||||
if (nbytes < DAIKIN_COMMAND_LENGTH)
|
||||
return; // Not enough bytes to send a proper message.
|
||||
// Set IR carrier frequency
|
||||
enableIROut(38);
|
||||
for (uint16_t r = 0; r <= repeat; r++) {
|
||||
// Header #1
|
||||
mark(DAIKIN_HDR_MARK);
|
||||
space(DAIKIN_HDR_SPACE);
|
||||
// Data #1
|
||||
for (uint16_t i = 0; i < 8 && i < nbytes; i++)
|
||||
sendData(DAIKIN_ONE_MARK, DAIKIN_ONE_SPACE, DAIKIN_ZERO_MARK,
|
||||
DAIKIN_ZERO_SPACE, data[i], 8, false);
|
||||
// Footer #1
|
||||
mark(DAIKIN_ONE_MARK);
|
||||
space(DAIKIN_ZERO_SPACE + DAIKIN_GAP);
|
||||
|
||||
// Header #2
|
||||
mark(DAIKIN_HDR_MARK);
|
||||
space(DAIKIN_HDR_SPACE);
|
||||
for (uint16_t r = 0; r <= repeat; r++) {
|
||||
// Send the header, 0b00000
|
||||
sendGeneric(0, 0, // No header for the header
|
||||
DAIKIN_BIT_MARK, DAIKIN_ONE_SPACE,
|
||||
DAIKIN_BIT_MARK, DAIKIN_ZERO_SPACE,
|
||||
DAIKIN_BIT_MARK, DAIKIN_ZERO_SPACE + DAIKIN_GAP,
|
||||
(uint64_t) 0b00000, 5, 38, false, 0, 50);
|
||||
// Leading header
|
||||
// Do this as a constant to save RAM and keep in flash memory
|
||||
sendGeneric(DAIKIN_HDR_MARK, DAIKIN_HDR_SPACE,
|
||||
DAIKIN_BIT_MARK, DAIKIN_ONE_SPACE,
|
||||
DAIKIN_BIT_MARK, DAIKIN_ZERO_SPACE,
|
||||
DAIKIN_BIT_MARK, DAIKIN_ZERO_SPACE + DAIKIN_GAP,
|
||||
DAIKIN_FIRST_HEADER64, 64, 38, false, 0, 50);
|
||||
// Data #1
|
||||
sendGeneric(DAIKIN_HDR_MARK, DAIKIN_HDR_SPACE,
|
||||
DAIKIN_BIT_MARK, DAIKIN_ONE_SPACE,
|
||||
DAIKIN_BIT_MARK, DAIKIN_ZERO_SPACE,
|
||||
DAIKIN_BIT_MARK, DAIKIN_ZERO_SPACE + DAIKIN_GAP,
|
||||
data, 8, 38, false, 0, 50);
|
||||
// Data #2
|
||||
for (uint16_t i = 8; i < nbytes; i++)
|
||||
sendData(DAIKIN_ONE_MARK, DAIKIN_ONE_SPACE, DAIKIN_ZERO_MARK,
|
||||
DAIKIN_ZERO_SPACE, data[i], 8, false);
|
||||
// Footer #2
|
||||
mark(DAIKIN_ONE_MARK);
|
||||
space(DAIKIN_ZERO_SPACE + DAIKIN_GAP);
|
||||
sendGeneric(DAIKIN_HDR_MARK, DAIKIN_HDR_SPACE,
|
||||
DAIKIN_BIT_MARK, DAIKIN_ONE_SPACE,
|
||||
DAIKIN_BIT_MARK, DAIKIN_ZERO_SPACE,
|
||||
DAIKIN_BIT_MARK, DAIKIN_ZERO_SPACE + DAIKIN_GAP,
|
||||
data + 8, nbytes - 8, 38, false, 0, 50);
|
||||
}
|
||||
}
|
||||
#endif // SEND_DAIKIN
|
||||
|
||||
IRDaikinESP::IRDaikinESP(uint16_t pin) : _irsend(pin) {
|
||||
stateReset();
|
||||
@@ -78,42 +93,69 @@ void IRDaikinESP::begin() {
|
||||
_irsend.begin();
|
||||
}
|
||||
|
||||
#if SEND_DAIKIN
|
||||
void IRDaikinESP::send() {
|
||||
checksum();
|
||||
_irsend.sendDaikin(daikin);
|
||||
}
|
||||
#endif // SEND_DAIKIN
|
||||
|
||||
void IRDaikinESP::checksum() {
|
||||
// Calculate the checksum for a given data block.
|
||||
// Args:
|
||||
// block: Ptr to the start of the data block.
|
||||
// length: Nr. of bytes to checksum.
|
||||
// Returns:
|
||||
// A byte containing the calculated checksum.
|
||||
uint8_t IRDaikinESP::calcBlockChecksum(const uint8_t *block,
|
||||
const uint16_t length) {
|
||||
uint8_t sum = 0;
|
||||
uint8_t i;
|
||||
|
||||
for (i = 0; i <= 6; i++)
|
||||
sum += daikin[i];
|
||||
|
||||
daikin[7] = sum & 0xFF;
|
||||
sum = 0;
|
||||
for (i = 8; i <= 25; i++)
|
||||
sum += daikin[i];
|
||||
daikin[26] = sum & 0xFF;
|
||||
// Daikin checksum is just the addition of all the data bytes
|
||||
// in the block but capped to 8 bits.
|
||||
for (uint16_t i = 0; i < length; i++, block++)
|
||||
sum += *block;
|
||||
return sum & 0xFFU;
|
||||
}
|
||||
|
||||
// Verify the checksum is valid for a given state.
|
||||
// Args:
|
||||
// state: The array to verify the checksum of.
|
||||
// length: The size of the state.
|
||||
// Returns:
|
||||
// A boolean.
|
||||
bool IRDaikinESP::validChecksum(const uint8_t state[],
|
||||
const uint16_t length) {
|
||||
if (length < 8 || state[7] != calcBlockChecksum(state, 7)) return false;
|
||||
if (length < 10 ||
|
||||
state[length - 1] != calcBlockChecksum(state + 8, length - 9))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Calculate and set the checksum values for the internal state.
|
||||
void IRDaikinESP::checksum() {
|
||||
daikin[7] = calcBlockChecksum(daikin, 7);
|
||||
daikin[26] = calcBlockChecksum(daikin + 8, 17);
|
||||
}
|
||||
|
||||
void IRDaikinESP::stateReset() {
|
||||
for (uint8_t i = 4; i < DAIKIN_COMMAND_LENGTH; i++)
|
||||
for (uint8_t i = 0; i < DAIKIN_COMMAND_LENGTH; i++)
|
||||
daikin[i] = 0x0;
|
||||
|
||||
daikin[0] = 0x11;
|
||||
daikin[1] = 0xDA;
|
||||
daikin[2] = 0x27;
|
||||
daikin[3] = 0xF0;
|
||||
daikin[7] = 0x20;
|
||||
daikin[4] = 0x42;
|
||||
// daikin[7] is a checksum byte, it will be set by checksum().
|
||||
daikin[8] = 0x11;
|
||||
daikin[9] = 0xDA;
|
||||
daikin[10] = 0x27;
|
||||
daikin[13] = 0x41;
|
||||
daikin[13] = 0x49;
|
||||
daikin[14] = 0x1E;
|
||||
daikin[16] = 0xB0;
|
||||
daikin[19] = 0x06;
|
||||
daikin[20] = 0x60;
|
||||
daikin[23] = 0xC0;
|
||||
daikin[26] = 0xE3;
|
||||
// daikin[26] is a checksum byte, it will be set by checksum().
|
||||
checksum();
|
||||
}
|
||||
|
||||
@@ -122,16 +164,19 @@ uint8_t* IRDaikinESP::getRaw() {
|
||||
return daikin;
|
||||
}
|
||||
|
||||
void IRDaikinESP::setRaw(uint8_t new_code[]) {
|
||||
for (uint8_t i = 0; i < DAIKIN_COMMAND_LENGTH; i++)
|
||||
daikin[i] = new_code[i];
|
||||
}
|
||||
|
||||
void IRDaikinESP::on() {
|
||||
// state = ON;
|
||||
daikin[13] |= 0x01;
|
||||
checksum();
|
||||
setBit(DAIKIN_BYTE_POWER, DAIKIN_BIT_POWER);
|
||||
}
|
||||
|
||||
void IRDaikinESP::off() {
|
||||
// state = OFF;
|
||||
daikin[13] &= 0xFE;
|
||||
checksum();
|
||||
clearBit(DAIKIN_BYTE_POWER, DAIKIN_BIT_POWER);
|
||||
}
|
||||
|
||||
void IRDaikinESP::setPower(bool state) {
|
||||
@@ -141,40 +186,8 @@ void IRDaikinESP::setPower(bool state) {
|
||||
off();
|
||||
}
|
||||
|
||||
uint8_t IRDaikinESP::getPower() {
|
||||
return daikin[13] & 0x01;
|
||||
}
|
||||
|
||||
// DAIKIN_SILENT or DAIKIN_POWERFUL
|
||||
void IRDaikinESP::setAux(uint8_t aux) {
|
||||
daikin[21] = aux;
|
||||
checksum();
|
||||
}
|
||||
|
||||
uint8_t IRDaikinESP::getAux() {
|
||||
return daikin[21];
|
||||
}
|
||||
|
||||
void IRDaikinESP::setQuiet(bool state) {
|
||||
if (state)
|
||||
setAux(DAIKIN_SILENT);
|
||||
else
|
||||
setAux(0x0);
|
||||
}
|
||||
|
||||
bool IRDaikinESP::getQuiet() {
|
||||
return (getAux() == DAIKIN_SILENT);
|
||||
}
|
||||
|
||||
void IRDaikinESP::setPowerful(bool state) {
|
||||
if (state)
|
||||
setAux(DAIKIN_POWERFUL);
|
||||
else
|
||||
setAux(0x0);
|
||||
}
|
||||
|
||||
bool IRDaikinESP::getPowerful() {
|
||||
return (getAux() == DAIKIN_POWERFUL);
|
||||
bool IRDaikinESP::getPower() {
|
||||
return (getBit(DAIKIN_BYTE_POWER, DAIKIN_BIT_POWER) > 0);
|
||||
}
|
||||
|
||||
// Set the temp in deg C
|
||||
@@ -184,32 +197,30 @@ void IRDaikinESP::setTemp(uint8_t temp) {
|
||||
else if (temp > DAIKIN_MAX_TEMP)
|
||||
temp = DAIKIN_MAX_TEMP;
|
||||
daikin[14] = temp * 2;
|
||||
checksum();
|
||||
}
|
||||
|
||||
uint8_t IRDaikinESP::getTemp() {
|
||||
return daikin[14] / 2;
|
||||
}
|
||||
|
||||
// Set the speed of the fan, 0-5, 0 is auto, 1-5 is the speed
|
||||
// Set the speed of the fan, 1-5 or DAIKIN_FAN_AUTO or DAIKIN_FAN_QUIET
|
||||
void IRDaikinESP::setFan(uint8_t fan) {
|
||||
// Set the fan speed bits, leave low 4 bits alone
|
||||
uint8_t fanset;
|
||||
daikin[16] &= 0x0F;
|
||||
fan = std::min(fan, DAIKIN_FAN_MAX);
|
||||
if (fan == DAIKIN_FAN_AUTO)
|
||||
fanset = 0xA0;
|
||||
if (fan == DAIKIN_FAN_QUIET || fan == DAIKIN_FAN_AUTO)
|
||||
fanset = fan;
|
||||
else if (fan < DAIKIN_FAN_MIN || fan > DAIKIN_FAN_MAX)
|
||||
fanset = DAIKIN_FAN_AUTO;
|
||||
else
|
||||
fanset = 0x20 + (0x10 * fan);
|
||||
daikin[16] |= fanset;
|
||||
checksum();
|
||||
fanset = 2 + fan;
|
||||
daikin[16] &= 0x0F;
|
||||
daikin[16] |= (fanset << 4);
|
||||
}
|
||||
|
||||
uint8_t IRDaikinESP::getFan() {
|
||||
uint8_t fan = daikin[16] >> 4;
|
||||
fan -= 2;
|
||||
if (fan > DAIKIN_FAN_MAX)
|
||||
fan = DAIKIN_FAN_AUTO;
|
||||
if (fan != DAIKIN_FAN_QUIET && fan != DAIKIN_FAN_AUTO)
|
||||
fan -= 2;
|
||||
return fan;
|
||||
}
|
||||
|
||||
@@ -234,8 +245,9 @@ void IRDaikinESP::setMode(uint8_t mode) {
|
||||
default:
|
||||
mode = DAIKIN_AUTO;
|
||||
}
|
||||
daikin[13] = (mode << 4) | getPower();
|
||||
checksum();
|
||||
mode <<= 4;
|
||||
daikin[13] &= 0b10001111;
|
||||
daikin[13] |= mode;
|
||||
}
|
||||
|
||||
void IRDaikinESP::setSwingVertical(bool state) {
|
||||
@@ -243,7 +255,6 @@ void IRDaikinESP::setSwingVertical(bool state) {
|
||||
daikin[16] |= 0x0F;
|
||||
else
|
||||
daikin[16] &= 0xF0;
|
||||
checksum();
|
||||
}
|
||||
|
||||
bool IRDaikinESP::getSwingVertical() {
|
||||
@@ -255,93 +266,524 @@ void IRDaikinESP::setSwingHorizontal(bool state) {
|
||||
daikin[17] |= 0x0F;
|
||||
else
|
||||
daikin[17] &= 0xF0;
|
||||
checksum();
|
||||
}
|
||||
|
||||
bool IRDaikinESP::getSwingHorizontal() {
|
||||
return daikin[17] & 0x01;
|
||||
}
|
||||
#endif // SEND_DAIKIN
|
||||
|
||||
void IRDaikinESP::setQuiet(bool state) {
|
||||
if (state) {
|
||||
setBit(DAIKIN_BYTE_SILENT, DAIKIN_BIT_SILENT);
|
||||
// Powerful & Quiet mode being on are mutually exclusive.
|
||||
setPowerful(false);
|
||||
} else {
|
||||
clearBit(DAIKIN_BYTE_SILENT, DAIKIN_BIT_SILENT);
|
||||
}
|
||||
}
|
||||
|
||||
bool IRDaikinESP::getQuiet() {
|
||||
return (getBit(DAIKIN_BYTE_SILENT, DAIKIN_BIT_SILENT) > 0);
|
||||
}
|
||||
|
||||
void IRDaikinESP::setPowerful(bool state) {
|
||||
if (state) {
|
||||
setBit(DAIKIN_BYTE_POWERFUL, DAIKIN_BIT_POWERFUL);
|
||||
// Powerful, Quiet, & Econo mode being on are mutually exclusive.
|
||||
setQuiet(false);
|
||||
setEcono(false);
|
||||
} else {
|
||||
clearBit(DAIKIN_BYTE_POWERFUL, DAIKIN_BIT_POWERFUL);
|
||||
}
|
||||
}
|
||||
|
||||
bool IRDaikinESP::getPowerful() {
|
||||
return (getBit(DAIKIN_BYTE_POWERFUL, DAIKIN_BIT_POWERFUL) > 0);
|
||||
}
|
||||
|
||||
void IRDaikinESP::setSensor(bool state) {
|
||||
if (state)
|
||||
setBit(DAIKIN_BYTE_SENSOR, DAIKIN_BIT_SENSOR);
|
||||
else
|
||||
clearBit(DAIKIN_BYTE_SENSOR, DAIKIN_BIT_SENSOR);
|
||||
}
|
||||
|
||||
bool IRDaikinESP::getSensor() {
|
||||
return (getBit(DAIKIN_BYTE_SENSOR, DAIKIN_BIT_SENSOR) > 0);
|
||||
}
|
||||
|
||||
void IRDaikinESP::setEcono(bool state) {
|
||||
if (state) {
|
||||
setBit(DAIKIN_BYTE_ECONO, DAIKIN_BIT_ECONO);
|
||||
// Powerful & Econo mode being on are mutually exclusive.
|
||||
setPowerful(false);
|
||||
} else {
|
||||
clearBit(DAIKIN_BYTE_ECONO, DAIKIN_BIT_ECONO);
|
||||
}
|
||||
}
|
||||
|
||||
bool IRDaikinESP::getEcono() {
|
||||
return (getBit(DAIKIN_BYTE_ECONO, DAIKIN_BIT_ECONO) > 0);
|
||||
}
|
||||
|
||||
void IRDaikinESP::setEye(bool state) {
|
||||
if (state)
|
||||
setBit(DAIKIN_BYTE_EYE, DAIKIN_BIT_EYE);
|
||||
else
|
||||
clearBit(DAIKIN_BYTE_EYE, DAIKIN_BIT_EYE);
|
||||
}
|
||||
|
||||
bool IRDaikinESP::getEye() {
|
||||
return (getBit(DAIKIN_BYTE_EYE, DAIKIN_BIT_EYE) > 0);
|
||||
}
|
||||
|
||||
void IRDaikinESP::setMold(bool state) {
|
||||
if (state)
|
||||
setBit(DAIKIN_BYTE_MOLD, DAIKIN_BIT_MOLD);
|
||||
else
|
||||
clearBit(DAIKIN_BYTE_MOLD, DAIKIN_BIT_MOLD);
|
||||
}
|
||||
|
||||
bool IRDaikinESP::getMold() {
|
||||
return (getBit(DAIKIN_BYTE_MOLD, DAIKIN_BIT_MOLD) > 0);
|
||||
}
|
||||
|
||||
void IRDaikinESP::setBit(uint8_t byte, uint8_t bitmask) {
|
||||
daikin[byte] |= bitmask;
|
||||
}
|
||||
|
||||
void IRDaikinESP::clearBit(uint8_t byte, uint8_t bitmask) {
|
||||
bitmask = ~bitmask;
|
||||
daikin[byte] &= bitmask;
|
||||
}
|
||||
|
||||
uint8_t IRDaikinESP::getBit(uint8_t byte, uint8_t bitmask) {
|
||||
return daikin[byte] & bitmask;
|
||||
}
|
||||
|
||||
// starttime: Number of minutes after midnight, in 10 minutes increments
|
||||
void IRDaikinESP::enableOnTimer(uint16_t starttime) {
|
||||
setBit(DAIKIN_BYTE_ON_TIMER, DAIKIN_BIT_ON_TIMER);
|
||||
daikin[18] = (uint8_t) (starttime & 0x00FF);
|
||||
// only keep 4 bits
|
||||
daikin[19] &= 0xF0;
|
||||
daikin[19] |= (uint8_t) ((starttime >> 8) & 0x0F);
|
||||
}
|
||||
|
||||
void IRDaikinESP::disableOnTimer() {
|
||||
enableOnTimer(0x600);
|
||||
clearBit(DAIKIN_BYTE_ON_TIMER, DAIKIN_BIT_ON_TIMER);
|
||||
}
|
||||
|
||||
uint16_t IRDaikinESP::getOnTime() {
|
||||
uint16_t ret;
|
||||
ret = daikin[19] & 0x0F;
|
||||
ret = ret << 8;
|
||||
ret += daikin[18];
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool IRDaikinESP::getOnTimerEnabled() {
|
||||
return getBit(DAIKIN_BYTE_ON_TIMER, DAIKIN_BIT_ON_TIMER);
|
||||
}
|
||||
|
||||
// endtime: Number of minutes after midnight, in 10 minutes increments
|
||||
void IRDaikinESP::enableOffTimer(uint16_t endtime) {
|
||||
setBit(DAIKIN_BYTE_OFF_TIMER, DAIKIN_BIT_OFF_TIMER);
|
||||
daikin[20] = (uint8_t)((endtime >> 4) & 0xFF);
|
||||
daikin[19] &= 0x0F;
|
||||
daikin[19] |= (uint8_t) ((endtime & 0x000F) << 4);
|
||||
}
|
||||
|
||||
void IRDaikinESP::disableOffTimer() {
|
||||
enableOffTimer(0x600);
|
||||
clearBit(DAIKIN_BYTE_OFF_TIMER, DAIKIN_BIT_OFF_TIMER);
|
||||
}
|
||||
|
||||
uint16_t IRDaikinESP::getOffTime() {
|
||||
uint16_t ret, tmp;
|
||||
ret = daikin[20];
|
||||
ret <<= 4;
|
||||
tmp = daikin[19] & 0xF0;
|
||||
tmp >>= 4;
|
||||
ret += tmp;
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool IRDaikinESP::getOffTimerEnabled() {
|
||||
return getBit(DAIKIN_BYTE_OFF_TIMER, DAIKIN_BIT_OFF_TIMER);
|
||||
}
|
||||
|
||||
void IRDaikinESP::setCurrentTime(uint16_t numMins) {
|
||||
if (numMins > 24 * 60) numMins = 0; // If > 23:59, set to 00:00
|
||||
daikin[5] = (uint8_t) (numMins & 0x00FF);
|
||||
// only keep 4 bits
|
||||
daikin[6] &= 0xF0;
|
||||
daikin[6] |= (uint8_t) ((numMins >> 8) & 0x0F);
|
||||
}
|
||||
|
||||
uint16_t IRDaikinESP::getCurrentTime() {
|
||||
uint16_t ret;
|
||||
ret = daikin[6] & 0x0F;
|
||||
ret <<= 8;
|
||||
ret += daikin[5];
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef ARDUINO
|
||||
String IRDaikinESP::renderTime(uint16_t timemins) {
|
||||
String ret;
|
||||
#else // ARDUINO
|
||||
std::string IRDaikinESP::renderTime(uint16_t timemins) {
|
||||
std::string ret;
|
||||
#endif // ARDUINO
|
||||
uint16_t hours, mins;
|
||||
hours = timemins / 60;
|
||||
ret = uint64ToString(hours) + ":";
|
||||
mins = timemins - (hours * 60);
|
||||
if (mins < 10)
|
||||
ret += "0";
|
||||
ret += uint64ToString(mins);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Convert the internal state into a human readable string.
|
||||
#ifdef ARDUINO
|
||||
String IRDaikinESP::toString() {
|
||||
String result = "";
|
||||
#else // ARDUINO
|
||||
std::string IRDaikinESP::toString() {
|
||||
std::string result = "";
|
||||
#endif // ARDUINO
|
||||
result += "Power: ";
|
||||
if (getPower())
|
||||
result += "On";
|
||||
else
|
||||
result += "Off";
|
||||
result += ", Mode: " + uint64ToString(getMode());
|
||||
switch (getMode()) {
|
||||
case DAIKIN_AUTO:
|
||||
result += " (AUTO)";
|
||||
break;
|
||||
case DAIKIN_COOL:
|
||||
result += " (COOL)";
|
||||
break;
|
||||
case DAIKIN_HEAT:
|
||||
result += " (HEAT)";
|
||||
break;
|
||||
case DAIKIN_DRY:
|
||||
result += " (DRY)";
|
||||
break;
|
||||
case DAIKIN_FAN:
|
||||
result += " (FAN)";
|
||||
break;
|
||||
default:
|
||||
result += " (UNKNOWN)";
|
||||
}
|
||||
result += ", Temp: " + uint64ToString(getTemp()) + "C";
|
||||
result += ", Fan: " + uint64ToString(getFan());
|
||||
switch (getFan()) {
|
||||
case DAIKIN_FAN_AUTO:
|
||||
result += " (AUTO)";
|
||||
break;
|
||||
case DAIKIN_FAN_QUIET:
|
||||
result += " (QUIET)";
|
||||
break;
|
||||
case DAIKIN_FAN_MIN:
|
||||
result += " (MIN)";
|
||||
break;
|
||||
case DAIKIN_FAN_MAX:
|
||||
result += " (MAX)";
|
||||
break;
|
||||
}
|
||||
result += ", Powerful: ";
|
||||
if (getPowerful())
|
||||
result += "On";
|
||||
else
|
||||
result += "Off";
|
||||
result += ", Quiet: ";
|
||||
if (getQuiet())
|
||||
result += "On";
|
||||
else
|
||||
result += "Off";
|
||||
result += ", Sensor: ";
|
||||
if (getSensor())
|
||||
result += "On";
|
||||
else
|
||||
result += "Off";
|
||||
result += ", Eye: ";
|
||||
if (getEye())
|
||||
result += "On";
|
||||
else
|
||||
result += "Off";
|
||||
result += ", Mold: ";
|
||||
if (getMold())
|
||||
result += "On";
|
||||
else
|
||||
result += "Off";
|
||||
result += ", Swing (Horizontal): ";
|
||||
if (getSwingHorizontal())
|
||||
result += "On";
|
||||
else
|
||||
result += "Off";
|
||||
result += ", Swing (Vertical): ";
|
||||
if (getSwingVertical())
|
||||
result += "On";
|
||||
else
|
||||
result += "Off";
|
||||
result += ", Current Time: " + renderTime(getCurrentTime());
|
||||
result += ", On Time: ";
|
||||
if (getOnTimerEnabled())
|
||||
result += renderTime(getOnTime());
|
||||
else
|
||||
result += "Off";
|
||||
result += ", Off Time: ";
|
||||
if (getOffTimerEnabled())
|
||||
result += renderTime(getOffTime());
|
||||
else
|
||||
result += "Off";
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#if DAIKIN_DEBUG
|
||||
// Print what we have
|
||||
void IRDaikinESP::printState() {
|
||||
#ifdef ARDUINO
|
||||
String strbits;
|
||||
#else // ARDUINO
|
||||
std::string strbits;
|
||||
#endif // ARDUINO
|
||||
DPRINTLN("Raw Bits:");
|
||||
for (uint8_t i = 0; i < DAIKIN_COMMAND_LENGTH; i++) {
|
||||
strbits = uint64ToString(daikin[i], BIN);
|
||||
while (strbits.length() < 8)
|
||||
strbits = "0" + strbits;
|
||||
DPRINT(strbits);
|
||||
DPRINT(" ");
|
||||
}
|
||||
DPRINTLN("");
|
||||
DPRINTLN(toString());
|
||||
}
|
||||
#endif // DAIKIN_DEBUG
|
||||
|
||||
/*
|
||||
* Return most important bits to allow replay
|
||||
* layout is:
|
||||
* 0: Power
|
||||
* 1-3: Mode
|
||||
* 4-7: Fan speed/mode
|
||||
* 8-14: Target Temperature
|
||||
* 15: Econo
|
||||
* 16: Powerful
|
||||
* 17: Quiet
|
||||
* 18: Sensor
|
||||
* 19: Swing Vertical
|
||||
* 20-31: Current time (mins since midnight)
|
||||
* */
|
||||
uint32_t IRDaikinESP::getCommand() {
|
||||
uint32_t ret = 0;
|
||||
uint32_t tmp = 0;
|
||||
if (getPower())
|
||||
ret |= 0b00000000000000000000000000000001;
|
||||
tmp = getMode();
|
||||
tmp = tmp << 1;
|
||||
ret |= tmp;
|
||||
|
||||
tmp = getFan();
|
||||
tmp <<= 4;
|
||||
ret |= tmp;
|
||||
|
||||
tmp = getTemp();
|
||||
tmp <<= 8;
|
||||
ret |= tmp;
|
||||
|
||||
if (getEcono())
|
||||
ret |= 0b00000000000000001000000000000000;
|
||||
if (getPowerful())
|
||||
ret |= 0b00000000000000010000000000000000;
|
||||
if (getQuiet())
|
||||
ret |= 0b00000000000000100000000000000000;
|
||||
if (getSensor())
|
||||
ret |= 0b00000000000001000000000000000000;
|
||||
if (getSwingVertical())
|
||||
ret |= 0b00000000000010000000000000000000;
|
||||
ret |= (getCurrentTime() << 20);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void IRDaikinESP::setCommand(uint32_t value) {
|
||||
uint32_t tmp = 0;
|
||||
if (value & 0b00000000000000000000000000000001)
|
||||
setPower(true);
|
||||
tmp = value & 0b00000000000000000000000000001110;
|
||||
tmp >>= 1;
|
||||
setMode(tmp);
|
||||
|
||||
tmp = value & 0b00000000000000000000000011110000;
|
||||
tmp >>= 4;
|
||||
setFan(tmp);
|
||||
|
||||
tmp = value & 0b00000000000000000111111100000000;
|
||||
tmp >>= 8;
|
||||
setTemp(tmp);
|
||||
|
||||
if (value & 0b00000000000000001000000000000000)
|
||||
setEcono(true);
|
||||
if (value & 0b00000000000000010000000000000000)
|
||||
setPowerful(true);
|
||||
if (value & 0b00000000000000100000000000000000)
|
||||
setQuiet(true);
|
||||
if (value & 0b00000000000001000000000000000000)
|
||||
setSensor(true);
|
||||
if (value & 0b00000000000010000000000000000000)
|
||||
setSwingVertical(true);
|
||||
|
||||
value >>= 20;
|
||||
setCurrentTime(value);
|
||||
}
|
||||
|
||||
#if DECODE_DAIKIN
|
||||
// TODO(crankyoldgit): NOT WORKING. This needs to be finished.
|
||||
// Decode the supplied Daikin A/C message. (NOT WORKING - DO NOT USE)
|
||||
|
||||
void addbit(bool val, unsigned char data[]) {
|
||||
uint8_t curbit = data[DAIKIN_CURBIT];
|
||||
uint8_t curindex = data[DAIKIN_CURINDEX];
|
||||
if (val) {
|
||||
unsigned char bit = 1;
|
||||
bit = bit << curbit;
|
||||
data[curindex] |= bit;
|
||||
}
|
||||
curbit++;
|
||||
if (curbit == 8) {
|
||||
curbit = 0;
|
||||
curindex++;
|
||||
}
|
||||
data[DAIKIN_CURBIT] = curbit;
|
||||
data[DAIKIN_CURINDEX] = curindex;
|
||||
}
|
||||
|
||||
bool checkheader(decode_results *results, uint16_t* offset) {
|
||||
if (!IRrecv::matchMark(results->rawbuf[(*offset)++], DAIKIN_BIT_MARK,
|
||||
DAIKIN_TOLERANCE, DAIKIN_MARK_EXCESS))
|
||||
return false;
|
||||
if (!IRrecv::matchSpace(results->rawbuf[(*offset)++],
|
||||
DAIKIN_ZERO_SPACE + DAIKIN_GAP,
|
||||
DAIKIN_TOLERANCE, DAIKIN_MARK_EXCESS))
|
||||
return false;
|
||||
if (!IRrecv::matchMark(results->rawbuf[(*offset)++], DAIKIN_HDR_MARK,
|
||||
DAIKIN_TOLERANCE, DAIKIN_MARK_EXCESS))
|
||||
return false;
|
||||
if (!IRrecv::matchSpace(results->rawbuf[(*offset)++], DAIKIN_HDR_SPACE,
|
||||
DAIKIN_TOLERANCE, DAIKIN_MARK_EXCESS))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool readbits(decode_results *results, uint16_t *offset,
|
||||
unsigned char daikin_code[], uint16_t countbits) {
|
||||
for (uint16_t i = 0; i < countbits && *offset < results->rawlen - 1;
|
||||
i++, (*offset)++) {
|
||||
if (!IRrecv::matchMark(results->rawbuf[(*offset)++], DAIKIN_BIT_MARK,
|
||||
DAIKIN_TOLERANCE, DAIKIN_MARK_EXCESS))
|
||||
return false;
|
||||
if (IRrecv::matchSpace(results->rawbuf[*offset], DAIKIN_ONE_SPACE,
|
||||
DAIKIN_TOLERANCE, DAIKIN_MARK_EXCESS))
|
||||
addbit(1, daikin_code);
|
||||
else if (IRrecv::matchSpace(results->rawbuf[*offset], DAIKIN_ZERO_SPACE,
|
||||
DAIKIN_TOLERANCE, DAIKIN_MARK_EXCESS))
|
||||
addbit(0, daikin_code);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Decode the supplied Daikin A/C message.
|
||||
// Args:
|
||||
// results: Ptr to the data to decode and where to store the decode result.
|
||||
// nbits: Nr. of bits to expect in the data portion. Typically SAMSUNG_BITS.
|
||||
// nbits: Nr. of bits to expect in the data portion. (DAIKIN_RAW_BITS)
|
||||
// strict: Flag to indicate if we strictly adhere to the specification.
|
||||
// Returns:
|
||||
// boolean: True if it can decode it, false if it can't.
|
||||
//
|
||||
// Status: UNFINISHED / Completely not working, not even vaguely.
|
||||
// Status: BETA / Should be working.
|
||||
//
|
||||
// Notes:
|
||||
// If DAIKIN_DEBUG enabled, will print all the set options and values.
|
||||
//
|
||||
// Ref:
|
||||
// https://github.com/mharizanov/Daikin-AC-remote-control-over-the-Internet/tree/master/IRremote
|
||||
bool IRrecv::decodeDaikin(decode_results *results, uint16_t nbits,
|
||||
bool strict) {
|
||||
if (results->rawlen < 2 * nbits + HEADER + FOOTER)
|
||||
if (results->rawlen < DAIKIN_RAW_BITS)
|
||||
return false;
|
||||
|
||||
// Compliance
|
||||
if (strict && nbits != DAIKIN_BITS)
|
||||
if (strict && nbits != DAIKIN_RAW_BITS)
|
||||
return false;
|
||||
|
||||
uint32_t data = 0;
|
||||
uint16_t offset = OFFSET_START;
|
||||
unsigned char daikin_code[DAIKIN_COMMAND_LENGTH + 2];
|
||||
for (uint8_t i = 0; i < DAIKIN_COMMAND_LENGTH+2; i++)
|
||||
daikin_code[i] = 0;
|
||||
|
||||
// Header
|
||||
if (!matchMark(results->rawbuf[offset++], DAIKIN_HDR_MARK))
|
||||
return false;
|
||||
if (!matchSpace(results->rawbuf[offset++], DAIKIN_HDR_SPACE))
|
||||
// Header (#1)
|
||||
for (uint8_t i = 0; i < 10; i++) {
|
||||
if (!matchMark(results->rawbuf[offset++], DAIKIN_BIT_MARK))
|
||||
return false;
|
||||
}
|
||||
if (!checkheader(results, &offset)) return false;
|
||||
|
||||
// Data (#1)
|
||||
for (uint8_t i = 0; i < sizeof(data) * 8; i++, offset++) {
|
||||
if (!matchMark(results->rawbuf[offset++], DAIKIN_ONE_MARK))
|
||||
return false;
|
||||
if (matchSpace(results->rawbuf[offset], DAIKIN_ONE_SPACE))
|
||||
data = (data << 1) | 1; // 1
|
||||
else if (matchSpace(results->rawbuf[offset], DAIKIN_ZERO_SPACE))
|
||||
data <<= 1; // 0
|
||||
else
|
||||
return false;
|
||||
}
|
||||
if (!readbits(results, &offset, daikin_code, 8 * 8)) return false;
|
||||
|
||||
uint32_t number = data; // some number...
|
||||
uint32_t reversed = reverseBits(number, sizeof(number) * 8)
|
||||
// Ignore everything that has just been captured as it is not needed.
|
||||
// Some remotes may not send this portion, my remote did, but it's not
|
||||
// required.
|
||||
for (uint8_t i = 0; i < DAIKIN_COMMAND_LENGTH + 2; i++)
|
||||
daikin_code[i] = 0;
|
||||
|
||||
DPRINT("Code ");
|
||||
DPRINTLN(reversed, HEX);
|
||||
// Header (#2)
|
||||
if (!checkheader(results, &offset)) return false;
|
||||
|
||||
// Data (#2)
|
||||
for (uint8_t i = 0; i < sizeof(data) * 8; i++, offset++) {
|
||||
if (!matchMark(results->rawbuf[offset++], DAIKIN_ONE_MARK))
|
||||
return false;
|
||||
if (matchSpace(results->rawbuf[offset], DAIKIN_ONE_SPACE))
|
||||
data = (data << 1) | 1; // 1
|
||||
else if (matchSpace(results->rawbuf[offset], DAIKIN_ZERO_SPACE))
|
||||
data <<= 1; // 0
|
||||
else
|
||||
return false;
|
||||
}
|
||||
if (!readbits(results, &offset, daikin_code, 8 * 8)) return false;
|
||||
|
||||
number = data; // some number...
|
||||
reversed = reverseBits(number, sizeof(number) * 8)
|
||||
// Header (#3)
|
||||
if (!checkheader(results, &offset)) return false;
|
||||
|
||||
DPRINT("Code2 ");
|
||||
DPRINTLN(reversed, HEX);
|
||||
|
||||
if (!matchSpace(results->rawbuf[offset++], DAIKIN_GAP)) {
|
||||
DPRINTLN("no gap");
|
||||
// Data (#3), read up everything else
|
||||
if (!readbits(results, &offset, daikin_code, DAIKIN_BITS - (8 * 8)))
|
||||
return false;
|
||||
|
||||
// Footer
|
||||
if (!matchMark(results->rawbuf[offset++], DAIKIN_BIT_MARK))
|
||||
return false;
|
||||
if (offset < results->rawlen && !matchAtLeast(results->rawbuf[offset],
|
||||
DAIKIN_GAP))
|
||||
return false;
|
||||
|
||||
// Compliance
|
||||
if (strict) {
|
||||
if (!IRDaikinESP::validChecksum(daikin_code)) return false;
|
||||
}
|
||||
|
||||
// Success
|
||||
results->bits = DAIKIN_BITS;
|
||||
results->value = reversed;
|
||||
#if DAIKIN_DEBUG
|
||||
IRDaikinESP dako = IRDaikinESP(0);
|
||||
dako.setRaw(daikin_code);
|
||||
#ifdef ARDUINO
|
||||
yield();
|
||||
#endif // ARDUINO
|
||||
dako.printState();
|
||||
#endif // DAIKIN_DEBUG
|
||||
|
||||
// Copy across the bits to state
|
||||
for (uint8_t i = 0; i < DAIKIN_COMMAND_LENGTH; i++)
|
||||
results->state[i] = daikin_code[i];
|
||||
results->bits = DAIKIN_COMMAND_LENGTH * 8;
|
||||
results->decode_type = DAIKIN;
|
||||
results->address = 0;
|
||||
results->command = 0;
|
||||
return true;
|
||||
}
|
||||
#endif // DECODE_DAIKIN
|
||||
@@ -1,10 +1,20 @@
|
||||
/* Copyright 2016 sillyfrog */
|
||||
// Copyright 2016 sillyfrog
|
||||
// Copyright 2017 sillyfrog, crankyoldgit
|
||||
#ifndef IR_DAIKIN_H_
|
||||
#define IR_DAIKIN_H_
|
||||
|
||||
#ifndef UNIT_TEST
|
||||
#include <Arduino.h>
|
||||
#else
|
||||
#include <string>
|
||||
#endif
|
||||
#include "IRremoteESP8266.h"
|
||||
#include "IRrecv.h"
|
||||
#include "IRsend.h"
|
||||
|
||||
// Option to disable the additional Daikin debug info to conserve memory
|
||||
#define DAIKIN_DEBUG false
|
||||
|
||||
// DDDDD AAA IIIII KK KK IIIII NN NN
|
||||
// DD DD AAAAA III KK KK III NNN NN
|
||||
// DD DD AA AA III KKKK III NN N NN
|
||||
@@ -13,6 +23,9 @@
|
||||
|
||||
/*
|
||||
Daikin AC map
|
||||
byte 5=Current time, mins past midnight, low bits
|
||||
byte 6
|
||||
b0-b3=Current time, mins past midnight, high bits
|
||||
byte 7= checksum of the first part (and last byte before a 29ms pause)
|
||||
byte 13=mode
|
||||
b7 = 0
|
||||
@@ -23,22 +36,22 @@
|
||||
110 = FAN (temp not shown, but 25)
|
||||
000 = Fully Automatic (temp 25)
|
||||
010 = DRY (temp 0xc0 = 96 degrees c)
|
||||
b3 = 0
|
||||
b3 = 1
|
||||
b2 = OFF timer set
|
||||
b1 = ON timer set
|
||||
b0 = Air Conditioner ON
|
||||
byte 14=temp*2 (Temp should be between 18 - 32)
|
||||
byte 14=temp*2 (Temp should be between 10 - 32)
|
||||
byte 16=Fan
|
||||
FAN control
|
||||
b7+b6+b5+b4 = Fan speed
|
||||
Fan: b7+b6+b5+b4
|
||||
0×30 = 1 bar
|
||||
0×40 = 2 bar
|
||||
0×50 = 3 bar
|
||||
0×60 = 4 bar
|
||||
0×70 = 5 bar
|
||||
0xa0 = Auto
|
||||
0xb0 = Not auto, moon + tree
|
||||
0×3 = 1 bar
|
||||
0×4 = 2 bar
|
||||
0×5 = 3 bar
|
||||
0×6 = 4 bar
|
||||
0×7 = 5 bar
|
||||
0xa = Auto
|
||||
0xb = Quite
|
||||
b3+b2+b1+b0 = Swing control up/down
|
||||
Swing control up/down:
|
||||
0000 = Swing up/down off
|
||||
@@ -47,8 +60,18 @@
|
||||
Swing control left/right:
|
||||
0000 = Swing left/right off
|
||||
1111 = Swing left/right on
|
||||
byte 18=On timer mins past midnight, low bits
|
||||
byte 19
|
||||
b0-b3=On timer mins past midnight, high bits
|
||||
b4-b7=Off timer mins past midnight, low bits
|
||||
byte 20=Off timer mins past midnight, high bits
|
||||
byte 21=Aux -> Powerful (bit 1), Silent (bit 5)
|
||||
byte 24=Aux2 -> Intelligent eye on (bit 7)
|
||||
byte 24=Aux2
|
||||
b1: Sensor
|
||||
b2: Econo mode
|
||||
b7: Intelligent eye on
|
||||
byte 25=Aux3
|
||||
b1: Mold Proof
|
||||
byte 26= checksum of the second part
|
||||
*/
|
||||
|
||||
@@ -58,27 +81,65 @@
|
||||
#define DAIKIN_FAN 0b110
|
||||
#define DAIKIN_AUTO 0b000
|
||||
#define DAIKIN_DRY 0b010
|
||||
#define DAIKIN_POWERFUL 0b00000010
|
||||
#define DAIKIN_SILENT 0b00100000
|
||||
#define DAIKIN_MIN_TEMP 18U // Celsius
|
||||
#define DAIKIN_MIN_TEMP 10U // Celsius
|
||||
#define DAIKIN_MAX_TEMP 32U // Celsius
|
||||
#define DAIKIN_FAN_AUTO (uint8_t) 0U
|
||||
#define DAIKIN_FAN_MIN (uint8_t) 1U
|
||||
#define DAIKIN_FAN_MAX (uint8_t) 5U
|
||||
#define DAIKIN_FAN_AUTO (uint8_t) 0b1010
|
||||
#define DAIKIN_FAN_QUIET (uint8_t) 0b1011
|
||||
|
||||
#define DAIKIN_BYTE_POWER 13
|
||||
#define DAIKIN_BIT_POWER 0b00000001
|
||||
|
||||
#define DAIKIN_BYTE_POWERFUL 21
|
||||
#define DAIKIN_BIT_POWERFUL 0b00000001
|
||||
#define DAIKIN_BYTE_SILENT 21
|
||||
#define DAIKIN_BIT_SILENT 0b00100000
|
||||
|
||||
#define DAIKIN_BYTE_SENSOR 24
|
||||
#define DAIKIN_BIT_SENSOR 0b00000010
|
||||
#define DAIKIN_BYTE_ECONO 24
|
||||
#define DAIKIN_BIT_ECONO 0b00000100
|
||||
#define DAIKIN_BYTE_EYE 24
|
||||
#define DAIKIN_BIT_EYE 0b10000000
|
||||
#define DAIKIN_BYTE_MOLD 25
|
||||
#define DAIKIN_BIT_MOLD 0b00000010
|
||||
|
||||
#define DAIKIN_BYTE_OFF_TIMER 13
|
||||
#define DAIKIN_BIT_OFF_TIMER 0b00000100
|
||||
#define DAIKIN_BYTE_ON_TIMER 13
|
||||
#define DAIKIN_BIT_ON_TIMER 0b00000010
|
||||
|
||||
#define DAIKIN_CURBIT DAIKIN_COMMAND_LENGTH
|
||||
#define DAIKIN_CURINDEX (DAIKIN_COMMAND_LENGTH + 1)
|
||||
#define OFFSET_ERR 65432
|
||||
|
||||
#define DAIKIN_TOLERANCE 35
|
||||
#define DAIKIN_MARK_EXCESS MARK_EXCESS
|
||||
|
||||
#define DAIKIN_HDR_MARK 3650U // DAIKIN_BIT_MARK * 8
|
||||
#define DAIKIN_HDR_SPACE 1623U // DAIKIN_BIT_MARK * 4
|
||||
#define DAIKIN_BIT_MARK 428U
|
||||
#define DAIKIN_ZERO_SPACE 428U
|
||||
#define DAIKIN_ONE_SPACE 1280U
|
||||
#define DAIKIN_GAP 29000U
|
||||
|
||||
// Note bits in each octet swapped so can be sent as a single value
|
||||
#define DAIKIN_FIRST_HEADER64 \
|
||||
0b1101011100000000000000001100010100000000001001111101101000010001
|
||||
|
||||
#if SEND_DAIKIN
|
||||
class IRDaikinESP {
|
||||
public:
|
||||
explicit IRDaikinESP(uint16_t pin);
|
||||
|
||||
#if SEND_DAIKIN
|
||||
void send();
|
||||
#endif
|
||||
void begin();
|
||||
void on();
|
||||
void off();
|
||||
void setPower(bool state);
|
||||
uint8_t getPower();
|
||||
void setAux(uint8_t aux);
|
||||
uint8_t getAux();
|
||||
bool getPower();
|
||||
void setTemp(uint8_t temp);
|
||||
uint8_t getTemp();
|
||||
void setFan(uint8_t fan);
|
||||
@@ -93,15 +154,51 @@ class IRDaikinESP {
|
||||
void setQuiet(bool state);
|
||||
bool getPowerful();
|
||||
void setPowerful(bool state);
|
||||
void setSensor(bool state);
|
||||
bool getSensor();
|
||||
void setEcono(bool state);
|
||||
bool getEcono();
|
||||
void setEye(bool state);
|
||||
bool getEye();
|
||||
void setMold(bool state);
|
||||
bool getMold();
|
||||
void enableOnTimer(uint16_t starttime);
|
||||
void disableOnTimer();
|
||||
uint16_t getOnTime();
|
||||
bool getOnTimerEnabled();
|
||||
void enableOffTimer(uint16_t endtime);
|
||||
void disableOffTimer();
|
||||
uint16_t getOffTime();
|
||||
bool getOffTimerEnabled();
|
||||
void setCurrentTime(uint16_t time);
|
||||
uint16_t getCurrentTime();
|
||||
uint8_t* getRaw();
|
||||
void setRaw(uint8_t new_code[]);
|
||||
#if DAIKIN_DEBUG
|
||||
void printState();
|
||||
#endif // DAIKIN_DEBUG
|
||||
uint32_t getCommand();
|
||||
void setCommand(uint32_t value);
|
||||
static bool validChecksum(const uint8_t state[],
|
||||
const uint16_t length = DAIKIN_COMMAND_LENGTH);
|
||||
#ifdef ARDUINO
|
||||
String toString();
|
||||
static String renderTime(uint16_t timemins);
|
||||
#else
|
||||
std::string toString();
|
||||
static std::string renderTime(uint16_t timemins);
|
||||
#endif
|
||||
|
||||
private:
|
||||
// # of bytes per command
|
||||
uint8_t daikin[DAIKIN_COMMAND_LENGTH];
|
||||
void stateReset();
|
||||
static uint8_t calcBlockChecksum(const uint8_t *block, const uint16_t length);
|
||||
void checksum();
|
||||
void setBit(uint8_t byte, uint8_t bitmask);
|
||||
void clearBit(uint8_t byte, uint8_t bitmask);
|
||||
uint8_t getBit(uint8_t byte, uint8_t bitmask);
|
||||
IRsend _irsend;
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif // IR_DAIKIN_H_
|
||||
@@ -4,7 +4,6 @@
|
||||
#include <algorithm>
|
||||
#include "IRrecv.h"
|
||||
#include "IRsend.h"
|
||||
#include "IRtimer.h"
|
||||
#include "IRutils.h"
|
||||
|
||||
// DDDD EEEEE N N OOO N N
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
#include "IRrecv.h"
|
||||
#include "IRsend.h"
|
||||
#include "IRtimer.h"
|
||||
#include "IRutils.h"
|
||||
|
||||
// DDDD IIIII SSSS H H
|
||||
@@ -56,20 +55,16 @@
|
||||
// Ref:
|
||||
// http://www.hifi-remote.com/wiki/index.php?title=Dish
|
||||
void IRsend::sendDISH(uint64_t data, uint16_t nbits, uint16_t repeat) {
|
||||
// Set 57.6kHz IR carrier frequency, duty cycle is unknown.
|
||||
enableIROut(57600);
|
||||
// Header
|
||||
enableIROut(57600); // Set modulation freq. to 57.6kHz.
|
||||
// Header is only ever sent once.
|
||||
mark(DISH_HDR_MARK);
|
||||
space(DISH_HDR_SPACE);
|
||||
// We always send a command, even for repeat=0, hence '<= repeat'.
|
||||
for (uint16_t i = 0; i <= repeat; i++) {
|
||||
// Data
|
||||
sendData(DISH_BIT_MARK, DISH_ONE_SPACE, DISH_BIT_MARK, DISH_ZERO_SPACE,
|
||||
data, nbits, true);
|
||||
// Footer
|
||||
mark(DISH_BIT_MARK);
|
||||
space(DISH_RPT_SPACE);
|
||||
}
|
||||
|
||||
sendGeneric(0, 0, // No headers from here on in.
|
||||
DISH_BIT_MARK, DISH_ONE_SPACE,
|
||||
DISH_BIT_MARK, DISH_ZERO_SPACE,
|
||||
DISH_BIT_MARK, DISH_RPT_SPACE,
|
||||
data, nbits, 57600, true, repeat, 50);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -1,30 +1,39 @@
|
||||
// Copyright 2017 Jonny Graham
|
||||
// Copyright 2017 Jonny Graham, David Conran
|
||||
#include "ir_Fujitsu.h"
|
||||
#include <algorithm>
|
||||
#ifndef ARDUINO
|
||||
#include <string>
|
||||
#endif
|
||||
#include "IRsend.h"
|
||||
#include "IRutils.h"
|
||||
|
||||
|
||||
// Fujitsu A/C support added by Jonny Graham
|
||||
// Fujitsu A/C support added by Jonny Graham & David Conran
|
||||
|
||||
// Equipment it seems compatible with:
|
||||
// * Fujitsu ASYG30LFCA with remote AR-RAH2E
|
||||
// * Fujitsu AST9RSGCW with remote AR-DB1
|
||||
// * <Add models (A/C & remotes) you've gotten it working with here>
|
||||
|
||||
// Fujitsu A/C
|
||||
// Ref:
|
||||
// These values are based on averages of measurements
|
||||
#define FUJITSU_AC_HDR_MARK 3224U
|
||||
#define FUJITSU_AC_HDR_MARK 3324U
|
||||
#define FUJITSU_AC_HDR_SPACE 1574U
|
||||
#define FUJITSU_AC_BIT_MARK 448U
|
||||
#define FUJITSU_AC_ONE_SPACE 1182U
|
||||
#define FUJITSU_AC_ZERO_SPACE 367U
|
||||
#define FUJITSU_AC_TRL_MARK 448U
|
||||
#define FUJITSU_AC_TRL_SPACE 8100U
|
||||
#define FUJITSU_AC_ZERO_SPACE 390U
|
||||
#define FUJITSU_AC_MIN_GAP 8100U
|
||||
|
||||
#if SEND_FUJITSU_AC
|
||||
// Send a Fujitsu A/C message.
|
||||
//
|
||||
// Args:
|
||||
// data: An array of bytes containing the IR command.
|
||||
// nbytes: Nr. of bytes of data in the array. (typically either
|
||||
// FUJITSU_AC_STATE_LENGTH or FUJITSU_AC_STATE_LENGTH_SHORT)
|
||||
// nbytes: Nr. of bytes of data in the array. Typically one of:
|
||||
// FUJITSU_AC_STATE_LENGTH
|
||||
// FUJITSU_AC_STATE_LENGTH - 1
|
||||
// FUJITSU_AC_STATE_LENGTH_SHORT
|
||||
// FUJITSU_AC_STATE_LENGTH_SHORT - 1
|
||||
// repeat: Nr. of times the message is to be repeated.
|
||||
// (Default = FUJITSU_AC_MIN_REPEAT).
|
||||
//
|
||||
@@ -32,35 +41,36 @@
|
||||
//
|
||||
void IRsend::sendFujitsuAC(unsigned char data[], uint16_t nbytes,
|
||||
uint16_t repeat) {
|
||||
// Set IR carrier frequency
|
||||
enableIROut(38);
|
||||
for (uint16_t r = 0; r <= repeat; ++r) {
|
||||
// Header
|
||||
mark(FUJITSU_AC_HDR_MARK);
|
||||
space(FUJITSU_AC_HDR_SPACE);
|
||||
// Data
|
||||
for (uint16_t i = 0; i < nbytes; i++)
|
||||
sendData(FUJITSU_AC_BIT_MARK, FUJITSU_AC_ONE_SPACE,
|
||||
FUJITSU_AC_BIT_MARK, FUJITSU_AC_ZERO_SPACE,
|
||||
data[i], 8, false);
|
||||
// Footer
|
||||
mark(FUJITSU_AC_TRL_MARK);
|
||||
space(FUJITSU_AC_TRL_SPACE);
|
||||
}
|
||||
sendGeneric(FUJITSU_AC_HDR_MARK, FUJITSU_AC_HDR_SPACE,
|
||||
FUJITSU_AC_BIT_MARK, FUJITSU_AC_ONE_SPACE,
|
||||
FUJITSU_AC_BIT_MARK, FUJITSU_AC_ZERO_SPACE,
|
||||
FUJITSU_AC_BIT_MARK, FUJITSU_AC_MIN_GAP,
|
||||
data, nbytes, 38, false, repeat, 50);
|
||||
}
|
||||
#endif // SEND_FUJITSU_AC
|
||||
|
||||
// Code to emulate Fujitsu A/C IR remote control unit.
|
||||
|
||||
// Warning: Consider this very alpha code. Seems to work, but not validated.
|
||||
//
|
||||
// Equipment it seems compatible with:
|
||||
// * Fujitsu ASYG30LFCA with remote AR-RAH2E
|
||||
// * <Add models (A/C & remotes) you've gotten it working with here>
|
||||
// Initialise the object.
|
||||
IRFujitsuAC::IRFujitsuAC(uint16_t pin) : _irsend(pin) {
|
||||
IRFujitsuAC::IRFujitsuAC(uint16_t pin, fujitsu_ac_remote_model_t model)
|
||||
: _irsend(pin) {
|
||||
setModel(model);
|
||||
stateReset();
|
||||
}
|
||||
|
||||
void IRFujitsuAC::setModel(fujitsu_ac_remote_model_t model) {
|
||||
_model = model;
|
||||
switch (model) {
|
||||
case ARDB1:
|
||||
_state_length = FUJITSU_AC_STATE_LENGTH - 1;
|
||||
_state_length_short = FUJITSU_AC_STATE_LENGTH_SHORT - 1;
|
||||
break;
|
||||
default:
|
||||
_state_length = FUJITSU_AC_STATE_LENGTH;
|
||||
_state_length_short = FUJITSU_AC_STATE_LENGTH_SHORT;
|
||||
}
|
||||
}
|
||||
|
||||
// Reset the state of the remote to a known good state/sequence.
|
||||
void IRFujitsuAC::stateReset() {
|
||||
_temp = 24;
|
||||
@@ -68,29 +78,23 @@ void IRFujitsuAC::stateReset() {
|
||||
_mode = FUJITSU_AC_MODE_COOL;
|
||||
_swingMode = FUJITSU_AC_SWING_BOTH;
|
||||
_cmd = FUJITSU_AC_CMD_TURN_ON;
|
||||
buildState();
|
||||
}
|
||||
|
||||
// Configure the pin for output.
|
||||
void IRFujitsuAC::begin() {
|
||||
_irsend.begin();
|
||||
_irsend.begin();
|
||||
}
|
||||
|
||||
#if SEND_FUJITSU_AC
|
||||
// Send the current desired state to the IR LED.
|
||||
void IRFujitsuAC::send() {
|
||||
getRaw();
|
||||
uint8_t len = getCommandLength();
|
||||
_irsend.sendFujitsuAC(remote_state, len);
|
||||
_irsend.sendFujitsuAC(remote_state, getStateLength());
|
||||
}
|
||||
#endif // SEND_FUJITSU_AC
|
||||
|
||||
uint8_t IRFujitsuAC::getCommandLength() {
|
||||
if (remote_state[5] != 0xFE)
|
||||
return FUJITSU_AC_STATE_LENGTH_SHORT;
|
||||
else
|
||||
return FUJITSU_AC_STATE_LENGTH;
|
||||
}
|
||||
|
||||
// Return a pointer to the internal state date of the remote.
|
||||
uint8_t* IRFujitsuAC::getRaw() {
|
||||
void IRFujitsuAC::buildState() {
|
||||
remote_state[0] = 0x14;
|
||||
remote_state[1] = 0x63;
|
||||
remote_state[2] = 0x00;
|
||||
@@ -108,48 +112,130 @@ uint8_t* IRFujitsuAC::getRaw() {
|
||||
remote_state[5] = 0x6C;
|
||||
break;
|
||||
default:
|
||||
remote_state[5] = 0xFE;
|
||||
switch (_model) {
|
||||
case ARRAH2E:
|
||||
remote_state[5] = 0xFE;
|
||||
break;
|
||||
case ARDB1:
|
||||
remote_state[5] = 0xFC;
|
||||
break;
|
||||
}
|
||||
fullCmd = true;
|
||||
break;
|
||||
}
|
||||
if (fullCmd) {
|
||||
remote_state[6] = 0x09;
|
||||
remote_state[7] = 0x30;
|
||||
if (fullCmd) { // long codes
|
||||
uint8_t tempByte = _temp - FUJITSU_AC_MIN_TEMP;
|
||||
// Nr. of bytes in the message after this byte.
|
||||
remote_state[6] = _state_length - 7;
|
||||
|
||||
remote_state[7] = 0x30;
|
||||
remote_state[8] = (_cmd == FUJITSU_AC_CMD_TURN_ON) | (tempByte << 4);
|
||||
remote_state[9] = _mode | 0 << 4; // timer off
|
||||
remote_state[10] = _fanSpeed | _swingMode << 4;
|
||||
remote_state[11] = 0; // timerOff values
|
||||
remote_state[12] = 0; // timerOff/on values
|
||||
remote_state[12] = 0; // timerOff/On values
|
||||
remote_state[13] = 0; // timerOn values
|
||||
remote_state[14] = 0x20;
|
||||
// Checksum is the sum of the 8th to 16th bytes (ie remote_state[7]
|
||||
// thru remote_state[15]).
|
||||
// The checksum itself is stored in the 16th byte (ie remote_state[15]).
|
||||
// So we sum bytes 8th-15th...
|
||||
if (_model == ARRAH2E)
|
||||
remote_state[14] = 0x20;
|
||||
else
|
||||
remote_state[14] = 0x00;
|
||||
|
||||
uint8_t checksum = 0;
|
||||
for (uint8_t i = 7 ; i < 15; ++i) {
|
||||
checksum += remote_state[i];
|
||||
uint8_t checksum_complement = 0;
|
||||
if (_model == ARRAH2E) {
|
||||
checksum = sumBytes(remote_state + _state_length_short,
|
||||
_state_length - _state_length_short - 1);
|
||||
} else if (_model == ARDB1) {
|
||||
checksum = sumBytes(remote_state, _state_length - 1);
|
||||
checksum_complement = 0x9B;
|
||||
}
|
||||
// and then do 0 - sum and store it in 16th.
|
||||
remote_state[15] = 0 - checksum;
|
||||
} else {
|
||||
// For the short codes, byte 7 is the inverse of byte 6
|
||||
remote_state[6] = ~remote_state[5];
|
||||
for (uint8_t i = 7; i < FUJITSU_AC_STATE_LENGTH; ++i) {
|
||||
// and negate the checksum and store it in the last byte.
|
||||
remote_state[_state_length - 1] = checksum_complement - checksum;
|
||||
} else { // short codes
|
||||
if (_model == ARRAH2E)
|
||||
// The last byte is the inverse of penultimate byte
|
||||
remote_state[_state_length_short - 1] = ~remote_state[_state_length_short
|
||||
- 2];
|
||||
// Zero the rest of the state.
|
||||
for (uint8_t i = _state_length_short;
|
||||
i < FUJITSU_AC_STATE_LENGTH;
|
||||
i++)
|
||||
remote_state[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t IRFujitsuAC::getStateLength() {
|
||||
buildState(); // Force an update of the internal state.
|
||||
if ((_model == ARRAH2E && remote_state[5] != 0xFE) ||
|
||||
(_model == ARDB1 && remote_state[5] != 0xFC))
|
||||
return _state_length_short;
|
||||
else
|
||||
return _state_length;
|
||||
}
|
||||
|
||||
// Return a pointer to the internal state date of the remote.
|
||||
uint8_t* IRFujitsuAC::getRaw() {
|
||||
buildState();
|
||||
return remote_state;
|
||||
}
|
||||
|
||||
void IRFujitsuAC::buildFromState(const uint16_t length) {
|
||||
switch (length) {
|
||||
case FUJITSU_AC_STATE_LENGTH - 1:
|
||||
case FUJITSU_AC_STATE_LENGTH_SHORT - 1:
|
||||
setModel(ARDB1);
|
||||
break;
|
||||
default:
|
||||
setModel(ARRAH2E);
|
||||
}
|
||||
switch (remote_state[6]) {
|
||||
case 8:
|
||||
setModel(ARDB1);
|
||||
break;
|
||||
case 9:
|
||||
setModel(ARRAH2E);
|
||||
break;
|
||||
}
|
||||
setTemp((remote_state[8] >> 4) + FUJITSU_AC_MIN_TEMP);
|
||||
if (remote_state[8] & 0x1)
|
||||
setCmd(FUJITSU_AC_CMD_TURN_ON);
|
||||
else
|
||||
setCmd(FUJITSU_AC_CMD_STAY_ON);
|
||||
setMode(remote_state[9] & 0b111);
|
||||
setFanSpeed(remote_state[10] & 0b111);
|
||||
setSwing(remote_state[10] >> 4);
|
||||
switch (remote_state[5]) {
|
||||
case FUJITSU_AC_CMD_TURN_OFF:
|
||||
case FUJITSU_AC_CMD_STEP_HORIZ:
|
||||
case FUJITSU_AC_CMD_STEP_VERT:
|
||||
setCmd(remote_state[5]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool IRFujitsuAC::setRaw(const uint8_t newState[], const uint16_t length) {
|
||||
if (length > FUJITSU_AC_STATE_LENGTH) return false;
|
||||
for (uint16_t i = 0; i < FUJITSU_AC_STATE_LENGTH; i++) {
|
||||
if (i < length)
|
||||
remote_state[i] = newState[i];
|
||||
else
|
||||
remote_state[i] = 0;
|
||||
}
|
||||
buildFromState(length);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Set the requested power state of the A/C to off.
|
||||
void IRFujitsuAC::off() {
|
||||
_cmd = FUJITSU_AC_CMD_TURN_OFF;
|
||||
}
|
||||
|
||||
void IRFujitsuAC::stepHoriz() {
|
||||
_cmd = FUJITSU_AC_CMD_STEP_HORIZ;
|
||||
switch (_model) {
|
||||
case ARDB1: break; // This remote doesn't have a horizontal option.
|
||||
default:
|
||||
_cmd = FUJITSU_AC_CMD_STEP_HORIZ;
|
||||
}
|
||||
}
|
||||
|
||||
void IRFujitsuAC::stepVert() {
|
||||
@@ -162,20 +248,26 @@ void IRFujitsuAC::setCmd(uint8_t cmd) {
|
||||
case FUJITSU_AC_CMD_TURN_OFF:
|
||||
case FUJITSU_AC_CMD_TURN_ON:
|
||||
case FUJITSU_AC_CMD_STAY_ON:
|
||||
case FUJITSU_AC_CMD_STEP_HORIZ:
|
||||
case FUJITSU_AC_CMD_STEP_VERT:
|
||||
_cmd = cmd;
|
||||
break;
|
||||
case FUJITSU_AC_CMD_STEP_HORIZ:
|
||||
if (_model != ARDB1) // AR-DB1 remote doesn't have step horizontal.
|
||||
_cmd = cmd;
|
||||
default:
|
||||
cmd = FUJITSU_AC_CMD_STAY_ON;
|
||||
_cmd = FUJITSU_AC_CMD_STAY_ON;
|
||||
break;
|
||||
}
|
||||
_cmd = cmd;
|
||||
}
|
||||
|
||||
uint8_t IRFujitsuAC::getCmd() {
|
||||
return _cmd;
|
||||
}
|
||||
|
||||
bool IRFujitsuAC::getPower() {
|
||||
return _cmd != FUJITSU_AC_CMD_TURN_OFF;
|
||||
}
|
||||
|
||||
// Set the temp. in deg C
|
||||
void IRFujitsuAC::setTemp(uint8_t temp) {
|
||||
temp = std::max((uint8_t) FUJITSU_AC_MIN_TEMP, temp);
|
||||
@@ -209,8 +301,18 @@ uint8_t IRFujitsuAC::getMode() {
|
||||
}
|
||||
// Set the requested swing operation mode of the a/c unit.
|
||||
void IRFujitsuAC::setSwing(uint8_t swingMode) {
|
||||
if (swingMode > FUJITSU_AC_SWING_BOTH)
|
||||
swingMode = FUJITSU_AC_SWING_BOTH; // Set the mode to max if out of range
|
||||
switch (_model) {
|
||||
case ARDB1:
|
||||
// Set the mode to max if out of range
|
||||
if (swingMode > FUJITSU_AC_SWING_VERT)
|
||||
swingMode = FUJITSU_AC_SWING_VERT;
|
||||
break;
|
||||
case ARRAH2E:
|
||||
default:
|
||||
// Set the mode to max if out of range
|
||||
if (swingMode > FUJITSU_AC_SWING_BOTH)
|
||||
swingMode = FUJITSU_AC_SWING_BOTH;
|
||||
}
|
||||
_swingMode = swingMode;
|
||||
}
|
||||
|
||||
@@ -218,4 +320,228 @@ uint8_t IRFujitsuAC::getSwing() {
|
||||
return _swingMode;
|
||||
}
|
||||
|
||||
#endif
|
||||
bool IRFujitsuAC::validChecksum(uint8_t state[], uint16_t length) {
|
||||
uint8_t sum = 0;
|
||||
uint8_t sum_complement = 0;
|
||||
uint8_t checksum = 0;
|
||||
switch (length) {
|
||||
case FUJITSU_AC_STATE_LENGTH_SHORT: // ARRAH2E
|
||||
return state[length - 1] == (uint8_t) ~state[length - 2];
|
||||
case FUJITSU_AC_STATE_LENGTH - 1: // ARDB1
|
||||
sum = sumBytes(state, length - 1);
|
||||
sum_complement = 0x9B;
|
||||
checksum = state[length - 1];
|
||||
break;
|
||||
case FUJITSU_AC_STATE_LENGTH: // ARRAH2E
|
||||
sum = sumBytes(state + FUJITSU_AC_STATE_LENGTH_SHORT,
|
||||
length - 1 - FUJITSU_AC_STATE_LENGTH_SHORT);
|
||||
default: // Includes ARDB1 short.
|
||||
return true; // Assume the checksum is valid for other lengths.
|
||||
}
|
||||
return checksum == (uint8_t) (sum_complement - sum); // Does it match?
|
||||
}
|
||||
|
||||
// Convert the internal state into a human readable string.
|
||||
#ifdef ARDUINO
|
||||
String IRFujitsuAC::toString() {
|
||||
String result = "";
|
||||
#else
|
||||
std::string IRFujitsuAC::toString() {
|
||||
std::string result = "";
|
||||
#endif // ARDUINO
|
||||
result += "Power: ";
|
||||
if (getPower())
|
||||
result += "On";
|
||||
else
|
||||
result += "Off";
|
||||
result += ", Mode: " + uint64ToString(getMode());
|
||||
switch (getMode()) {
|
||||
case FUJITSU_AC_MODE_AUTO:
|
||||
result += " (AUTO)";
|
||||
break;
|
||||
case FUJITSU_AC_MODE_COOL:
|
||||
result += " (COOL)";
|
||||
break;
|
||||
case FUJITSU_AC_MODE_HEAT:
|
||||
result += " (HEAT)";
|
||||
break;
|
||||
case FUJITSU_AC_MODE_DRY:
|
||||
result += " (DRY)";
|
||||
break;
|
||||
case FUJITSU_AC_MODE_FAN:
|
||||
result += " (FAN)";
|
||||
break;
|
||||
default:
|
||||
result += " (UNKNOWN)";
|
||||
}
|
||||
result += ", Temp: " + uint64ToString(getTemp()) + "C";
|
||||
result += ", Fan: " + uint64ToString(getFanSpeed());
|
||||
switch (getFanSpeed()) {
|
||||
case FUJITSU_AC_FAN_AUTO:
|
||||
result += " (AUTO)";
|
||||
break;
|
||||
case FUJITSU_AC_FAN_HIGH:
|
||||
result += " (HIGH)";
|
||||
break;
|
||||
case FUJITSU_AC_FAN_MED:
|
||||
result += " (MED)";
|
||||
break;
|
||||
case FUJITSU_AC_FAN_LOW:
|
||||
result += " (LOW)";
|
||||
break;
|
||||
case FUJITSU_AC_FAN_QUIET:
|
||||
result += " (QUIET)";
|
||||
break;
|
||||
}
|
||||
result += ", Swing: ";
|
||||
switch (getSwing()) {
|
||||
case FUJITSU_AC_SWING_OFF:
|
||||
result += "Off";
|
||||
break;
|
||||
case FUJITSU_AC_SWING_VERT:
|
||||
result += "Vert";
|
||||
break;
|
||||
case FUJITSU_AC_SWING_HORIZ:
|
||||
result += "Horiz";
|
||||
break;
|
||||
case FUJITSU_AC_SWING_BOTH:
|
||||
result += "Vert + Horiz";
|
||||
break;
|
||||
default:
|
||||
result += "UNKNOWN";
|
||||
}
|
||||
result += ", Command: ";
|
||||
switch (getCmd()) {
|
||||
case FUJITSU_AC_CMD_STEP_HORIZ:
|
||||
result += "Step vane horizontally";
|
||||
break;
|
||||
case FUJITSU_AC_CMD_STEP_VERT:
|
||||
result += "Step vane vertically";
|
||||
break;
|
||||
default:
|
||||
result += "N/A";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#if DECODE_FUJITSU_AC
|
||||
// Decode a Fujitsu AC IR message if possible.
|
||||
// Places successful decode information in the results pointer.
|
||||
// Args:
|
||||
// results: Ptr to the data to decode and where to store the decode result.
|
||||
// nbits: The number of data bits to expect. Typically FUJITSU_AC_BITS.
|
||||
// strict: Flag to indicate if we strictly adhere to the specification.
|
||||
// Returns:
|
||||
// boolean: True if it can decode it, false if it can't.
|
||||
//
|
||||
// Status: ALPHA / Untested.
|
||||
//
|
||||
// Ref:
|
||||
//
|
||||
bool IRrecv::decodeFujitsuAC(decode_results *results, uint16_t nbits,
|
||||
bool strict) {
|
||||
uint16_t offset = OFFSET_START;
|
||||
uint16_t dataBitsSoFar = 0;
|
||||
|
||||
// Have we got enough data to successfully decode?
|
||||
if (results->rawlen < (2 * FUJITSU_AC_MIN_BITS) + HEADER + FOOTER - 1)
|
||||
return false; // Can't possibly be a valid message.
|
||||
|
||||
|
||||
// Compliance
|
||||
if (strict) {
|
||||
switch (nbits) {
|
||||
case FUJITSU_AC_BITS:
|
||||
case FUJITSU_AC_BITS - 8:
|
||||
case FUJITSU_AC_MIN_BITS:
|
||||
case FUJITSU_AC_MIN_BITS + 8:
|
||||
break;
|
||||
default:
|
||||
return false; // Must be called with the correct nr. of bits.
|
||||
}
|
||||
}
|
||||
|
||||
// Header
|
||||
if (!matchMark(results->rawbuf[offset++], FUJITSU_AC_HDR_MARK))
|
||||
return false;
|
||||
if (!matchSpace(results->rawbuf[offset++], FUJITSU_AC_HDR_SPACE))
|
||||
return false;
|
||||
|
||||
// Data (Fixed signature)
|
||||
match_result_t data_result = matchData(&(results->rawbuf[offset]),
|
||||
FUJITSU_AC_MIN_BITS - 8,
|
||||
FUJITSU_AC_BIT_MARK,
|
||||
FUJITSU_AC_ONE_SPACE,
|
||||
FUJITSU_AC_BIT_MARK,
|
||||
FUJITSU_AC_ZERO_SPACE);
|
||||
if (data_result.success == false) return false; // Fail
|
||||
if (reverseBits(data_result.data, FUJITSU_AC_MIN_BITS - 8) != 0x1010006314)
|
||||
return false; // Signature failed.
|
||||
dataBitsSoFar += FUJITSU_AC_MIN_BITS - 8;
|
||||
offset += data_result.used;
|
||||
results->state[0] = 0x14;
|
||||
results->state[1] = 0x63;
|
||||
results->state[2] = 0x00;
|
||||
results->state[3] = 0x10;
|
||||
results->state[4] = 0x10;
|
||||
|
||||
// Keep reading bytes until we either run out of message or state to fill.
|
||||
for (uint16_t i = 5;
|
||||
offset <= results->rawlen - 16 && i < FUJITSU_AC_STATE_LENGTH;
|
||||
i++, dataBitsSoFar += 8, offset += data_result.used) {
|
||||
data_result = matchData(&(results->rawbuf[offset]), 8,
|
||||
FUJITSU_AC_BIT_MARK,
|
||||
FUJITSU_AC_ONE_SPACE,
|
||||
FUJITSU_AC_BIT_MARK,
|
||||
FUJITSU_AC_ZERO_SPACE);
|
||||
if (data_result.success == false) break; // Fail
|
||||
results->state[i] = (uint8_t) reverseBits(data_result.data, 8);
|
||||
}
|
||||
|
||||
// Footer
|
||||
if (offset > results->rawlen ||
|
||||
!matchMark(results->rawbuf[offset++], FUJITSU_AC_BIT_MARK)) return false;
|
||||
// The space is optional if we are out of capture.
|
||||
if (offset < results->rawlen &&
|
||||
!matchAtLeast(results->rawbuf[offset], FUJITSU_AC_MIN_GAP)) return false;
|
||||
|
||||
// Compliance
|
||||
if (strict) {
|
||||
if (dataBitsSoFar != nbits) return false;
|
||||
}
|
||||
|
||||
results->decode_type = FUJITSU_AC;
|
||||
results->bits = dataBitsSoFar;
|
||||
|
||||
// Compliance
|
||||
switch (dataBitsSoFar) {
|
||||
case FUJITSU_AC_MIN_BITS:
|
||||
// Check if this values indicate that this should have been a long state
|
||||
// message.
|
||||
if (results->state[5] == 0xFC) return false;
|
||||
return true; // Success
|
||||
case FUJITSU_AC_MIN_BITS + 8:
|
||||
// Check if this values indicate that this should have been a long state
|
||||
// message.
|
||||
if (results->state[5] == 0xFE) return false;
|
||||
// The last byte needs to be the inverse of the penultimate byte.
|
||||
if (results->state[5] != (uint8_t) ~results->state[6]) return false;
|
||||
return true; // Success
|
||||
case FUJITSU_AC_BITS - 8:
|
||||
// Long messages of this size require this byte be correct.
|
||||
if (results->state[5] != 0xFC) return false;
|
||||
break;
|
||||
case FUJITSU_AC_BITS:
|
||||
// Long messages of this size require this byte be correct.
|
||||
if (results->state[5] != 0xFE) return false;
|
||||
break;
|
||||
default:
|
||||
return false; // Unexpected size.
|
||||
}
|
||||
if (!IRFujitsuAC::validChecksum(results->state, dataBitsSoFar / 8))
|
||||
return false;
|
||||
|
||||
// Success
|
||||
return true; // All good.
|
||||
}
|
||||
#endif // DECODE_FUJITSU_AC
|
||||
@@ -4,7 +4,13 @@
|
||||
|
||||
#define __STDC_LIMIT_MACROS
|
||||
#include <stdint.h>
|
||||
#ifdef ARDUINO
|
||||
#include <Arduino.h>
|
||||
#else
|
||||
#include <string>
|
||||
#endif
|
||||
#include "IRremoteESP8266.h"
|
||||
#include "IRrecv.h"
|
||||
#include "IRsend.h"
|
||||
|
||||
|
||||
@@ -38,16 +44,21 @@
|
||||
#define FUJITSU_AC_SWING_HORIZ 0x02U
|
||||
#define FUJITSU_AC_SWING_BOTH 0x03U
|
||||
|
||||
#define FUJITSU_AC_STATE_LENGTH 16
|
||||
#define FUJITSU_AC_STATE_LENGTH_SHORT 7
|
||||
|
||||
#if SEND_FUJITSU_AC
|
||||
enum fujitsu_ac_remote_model_t {
|
||||
ARRAH2E = 1,
|
||||
ARDB1,
|
||||
};
|
||||
|
||||
class IRFujitsuAC {
|
||||
public:
|
||||
explicit IRFujitsuAC(uint16_t pin);
|
||||
explicit IRFujitsuAC(uint16_t pin, fujitsu_ac_remote_model_t model = ARRAH2E);
|
||||
|
||||
void setModel(fujitsu_ac_remote_model_t model);
|
||||
void stateReset();
|
||||
#if SEND_FUJITSU_AC
|
||||
void send();
|
||||
#endif // SEND_FUJITSU_AC
|
||||
void begin();
|
||||
void off();
|
||||
void stepHoriz();
|
||||
@@ -63,18 +74,29 @@ class IRFujitsuAC {
|
||||
void setSwing(uint8_t mode);
|
||||
uint8_t getSwing();
|
||||
uint8_t* getRaw();
|
||||
bool setRaw(const uint8_t newState[], const uint16_t length);
|
||||
uint8_t getStateLength();
|
||||
static bool validChecksum(uint8_t *state, uint16_t length);
|
||||
bool getPower();
|
||||
#ifdef ARDUINO
|
||||
String toString();
|
||||
#else
|
||||
std::string toString();
|
||||
#endif
|
||||
|
||||
private:
|
||||
uint8_t remote_state[FUJITSU_AC_STATE_LENGTH];
|
||||
uint8_t getCommandLength();
|
||||
IRsend _irsend;
|
||||
uint8_t _temp;
|
||||
uint8_t _fanSpeed;
|
||||
uint8_t _mode;
|
||||
uint8_t _swingMode;
|
||||
uint8_t _cmd;
|
||||
fujitsu_ac_remote_model_t _model;
|
||||
uint8_t _state_length;
|
||||
uint8_t _state_length_short;
|
||||
void buildState();
|
||||
void buildFromState(const uint16_t length);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#endif // IR_FUJITSU_H_
|
||||
@@ -0,0 +1,119 @@
|
||||
// Copyright 2018 David Conran
|
||||
|
||||
#define __STDC_LIMIT_MACROS
|
||||
#include <stdint.h>
|
||||
#include <algorithm>
|
||||
#include "IRrecv.h"
|
||||
#include "IRsend.h"
|
||||
#include "IRutils.h"
|
||||
|
||||
// GGGG IIIII CCCCC AAA BBBBB LL EEEEEEE
|
||||
// GG GG III CC C AAAAA BB B LL EE
|
||||
// GG III CC AA AA BBBBBB LL EEEEE
|
||||
// GG GG ... III ... CC C AAAAAAA BB BB LL EE
|
||||
// GGGGGG ... IIIII ... CCCCC AA AA BBBBBB LLLLLLL EEEEEEE
|
||||
//
|
||||
// Ref:
|
||||
// https://github.com/cyborg5/IRLib2/blob/master/IRLibProtocols/IRLib_P09_GICable.h
|
||||
// https://github.com/markszabo/IRremoteESP8266/issues/447
|
||||
|
||||
// Constants
|
||||
#define GICABLE_HDR_MARK 9000U
|
||||
#define GICABLE_HDR_SPACE 4400U
|
||||
#define GICABLE_BIT_MARK 550U
|
||||
#define GICABLE_ONE_SPACE 4400U
|
||||
#define GICABLE_ZERO_SPACE 2200U
|
||||
#define GICABLE_RPT_SPACE 2200U
|
||||
#define GICABLE_MIN_COMMAND_LENGTH 99600U
|
||||
#define GICABLE_MIN_GAP (GICABLE_MIN_COMMAND_LENGTH - \
|
||||
(GICABLE_HDR_MARK + GICABLE_HDR_SPACE + \
|
||||
GICABLE_BITS * (GICABLE_BIT_MARK + GICABLE_ONE_SPACE) + GICABLE_BIT_MARK))
|
||||
|
||||
|
||||
#if SEND_GICABLE
|
||||
// Send a raw G.I. Cable formatted message.
|
||||
//
|
||||
// Args:
|
||||
// data: The message to be sent.
|
||||
// nbits: The number of bits of the message to be sent.
|
||||
// Typically GICABLE_BITS.
|
||||
// repeat: The number of times the command is to be repeated.
|
||||
//
|
||||
// Status: Alpha / Untested.
|
||||
//
|
||||
// Ref:
|
||||
void IRsend::sendGICable(uint64_t data, uint16_t nbits, uint16_t repeat) {
|
||||
sendGeneric(GICABLE_HDR_MARK, GICABLE_HDR_SPACE,
|
||||
GICABLE_BIT_MARK, GICABLE_ONE_SPACE,
|
||||
GICABLE_BIT_MARK, GICABLE_ZERO_SPACE,
|
||||
GICABLE_BIT_MARK, GICABLE_MIN_GAP, GICABLE_MIN_COMMAND_LENGTH,
|
||||
data, nbits, 39, true, 0, // Repeats are handled later.
|
||||
50);
|
||||
// Message repeat sequence.
|
||||
if (repeat)
|
||||
sendGeneric(GICABLE_HDR_MARK, GICABLE_RPT_SPACE,
|
||||
0, 0, 0, 0, // No actual data sent.
|
||||
GICABLE_BIT_MARK, GICABLE_MIN_GAP, GICABLE_MIN_COMMAND_LENGTH,
|
||||
0, 0, // No data to be sent.
|
||||
39, true, repeat - 1, 50);
|
||||
}
|
||||
#endif // SEND_GICABLE
|
||||
|
||||
#if DECODE_GICABLE
|
||||
// Decode the supplied G.I. Cable message.
|
||||
//
|
||||
// Args:
|
||||
// results: Ptr to the data to decode and where to store the decode result.
|
||||
// nbits: The number of data bits to expect. Typically GICABLE_BITS.
|
||||
// strict: Flag indicating if we should perform strict matching.
|
||||
// Returns:
|
||||
// boolean: True if it can decode it, false if it can't.
|
||||
//
|
||||
// Status: Alpha / Not tested against a real device.
|
||||
bool IRrecv::decodeGICable(decode_results *results, uint16_t nbits,
|
||||
bool strict) {
|
||||
if (results->rawlen < 2 * (nbits + HEADER + FOOTER) - 1)
|
||||
return false; // Can't possibly be a valid GICABLE message.
|
||||
if (strict && nbits != GICABLE_BITS)
|
||||
return false; // Not strictly an GICABLE message.
|
||||
|
||||
uint64_t data = 0;
|
||||
uint16_t offset = OFFSET_START;
|
||||
|
||||
// Header
|
||||
if (!matchMark(results->rawbuf[offset++], GICABLE_HDR_MARK)) return false;
|
||||
if (!matchSpace(results->rawbuf[offset++], GICABLE_HDR_SPACE)) return false;
|
||||
|
||||
// Data
|
||||
match_result_t data_result = matchData(&(results->rawbuf[offset]), nbits,
|
||||
GICABLE_BIT_MARK,
|
||||
GICABLE_ONE_SPACE,
|
||||
GICABLE_BIT_MARK,
|
||||
GICABLE_ZERO_SPACE);
|
||||
if (data_result.success == false) return false;
|
||||
data = data_result.data;
|
||||
offset += data_result.used;
|
||||
|
||||
// Footer
|
||||
if (!matchMark(results->rawbuf[offset++], GICABLE_BIT_MARK)) return false;
|
||||
if (offset < results->rawlen &&
|
||||
!matchAtLeast(results->rawbuf[offset++], GICABLE_MIN_GAP))
|
||||
return false;
|
||||
|
||||
// Compliance
|
||||
if (strict) {
|
||||
// We expect a repeat frame.
|
||||
if (!matchMark(results->rawbuf[offset++], GICABLE_HDR_MARK)) return false;
|
||||
if (!matchSpace(results->rawbuf[offset++], GICABLE_RPT_SPACE)) return false;
|
||||
if (!matchMark(results->rawbuf[offset++], GICABLE_BIT_MARK)) return false;
|
||||
}
|
||||
|
||||
// Success
|
||||
results->bits = nbits;
|
||||
results->value = data;
|
||||
results->decode_type = GICABLE;
|
||||
results->command = 0;
|
||||
results->address = 0;
|
||||
return true;
|
||||
}
|
||||
#endif // DECODE_GICABLE
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include "IRsend.h"
|
||||
#include "IRtimer.h"
|
||||
|
||||
// GGG L OOOO BBBB AA L CCCC AA CCCC H H EEEEEE
|
||||
// G G L O O B B AAAA L C C AAAA C C H H E
|
||||
|
||||
@@ -1,11 +1,22 @@
|
||||
// Copyright 2017 Ville Skyttä (scop)
|
||||
// Copyright 2017 David Conran
|
||||
// Copyright 2017, 2018 David Conran
|
||||
//
|
||||
// Gree protocol compatible heat pump carrying the "Ultimate" brand name.
|
||||
// Code to emulate Gree protocol compatible HVAC devices.
|
||||
// Should be compatible with:
|
||||
// * Heat pumps carrying the "Ultimate" brand name.
|
||||
// * EKOKAI air conditioners.
|
||||
//
|
||||
|
||||
#include "ir_Gree.h"
|
||||
#include <algorithm>
|
||||
#ifndef ARDUINO
|
||||
#include <string>
|
||||
#endif
|
||||
#include "IRremoteESP8266.h"
|
||||
#include "IRrecv.h"
|
||||
#include "IRsend.h"
|
||||
#include "IRutils.h"
|
||||
#include "ir_Kelvinator.h"
|
||||
|
||||
// GGGG RRRRRR EEEEEEE EEEEEEE
|
||||
// GG GG RR RR EE EE
|
||||
@@ -15,12 +26,14 @@
|
||||
|
||||
// Constants
|
||||
// Ref: https://github.com/ToniA/arduino-heatpumpir/blob/master/GreeHeatpumpIR.h
|
||||
#define GREE_HDR_MARK 9000U
|
||||
#define GREE_HDR_SPACE 4000U
|
||||
#define GREE_BIT_MARK 620U
|
||||
#define GREE_ONE_SPACE 1600U
|
||||
#define GREE_ZERO_SPACE 540U
|
||||
#define GREE_MSG_SPACE 19000U
|
||||
#define GREE_HDR_MARK 9000U
|
||||
#define GREE_HDR_SPACE 4000U
|
||||
#define GREE_BIT_MARK 620U
|
||||
#define GREE_ONE_SPACE 1600U
|
||||
#define GREE_ZERO_SPACE 540U
|
||||
#define GREE_MSG_SPACE 19000U
|
||||
#define GREE_BLOCK_FOOTER 0b010U
|
||||
#define GREE_BLOCK_FOOTER_BITS 3U
|
||||
|
||||
#if SEND_GREE
|
||||
// Send a Gree Heat Pump message.
|
||||
@@ -38,36 +51,26 @@ void IRsend::sendGree(unsigned char data[], uint16_t nbytes, uint16_t repeat) {
|
||||
if (nbytes < GREE_STATE_LENGTH)
|
||||
return; // Not enough bytes to send a proper message.
|
||||
|
||||
// Set IR carrier frequency
|
||||
enableIROut(38);
|
||||
|
||||
for (uint16_t r = 0; r <= repeat; r++) {
|
||||
// Header #1
|
||||
mark(GREE_HDR_MARK);
|
||||
space(GREE_HDR_SPACE);
|
||||
// Block #1
|
||||
sendGeneric(GREE_HDR_MARK, GREE_HDR_SPACE,
|
||||
GREE_BIT_MARK, GREE_ONE_SPACE,
|
||||
GREE_BIT_MARK, GREE_ZERO_SPACE,
|
||||
0, 0, // No Footer.
|
||||
data, 4, 38, false, 0, 50);
|
||||
// Footer #1
|
||||
sendGeneric(0, 0, // No Header
|
||||
GREE_BIT_MARK, GREE_ONE_SPACE,
|
||||
GREE_BIT_MARK, GREE_ZERO_SPACE,
|
||||
GREE_BIT_MARK, GREE_MSG_SPACE,
|
||||
0b010, 3, 38, true, 0, false);
|
||||
|
||||
// Data #1
|
||||
uint16_t i;
|
||||
for (i = 0; i < 4 && i < nbytes; i++)
|
||||
sendData(GREE_BIT_MARK, GREE_ONE_SPACE, GREE_BIT_MARK, GREE_ZERO_SPACE,
|
||||
data[i], 8, false);
|
||||
|
||||
// Footer #1 (010)
|
||||
sendData(GREE_BIT_MARK, GREE_ONE_SPACE, GREE_BIT_MARK, GREE_ZERO_SPACE,
|
||||
0b010, 3);
|
||||
|
||||
// Header #2
|
||||
mark(GREE_BIT_MARK);
|
||||
space(GREE_MSG_SPACE);
|
||||
|
||||
// Data #2
|
||||
for (; i < nbytes; i++)
|
||||
sendData(GREE_BIT_MARK, GREE_ONE_SPACE, GREE_BIT_MARK, GREE_ZERO_SPACE,
|
||||
data[i], 8, false);
|
||||
|
||||
// Footer #2
|
||||
mark(GREE_BIT_MARK);
|
||||
space(GREE_MSG_SPACE);
|
||||
// Block #2
|
||||
sendGeneric(0, 0, // No Header for Block #2
|
||||
GREE_BIT_MARK, GREE_ONE_SPACE,
|
||||
GREE_BIT_MARK, GREE_ZERO_SPACE,
|
||||
GREE_BIT_MARK, GREE_MSG_SPACE,
|
||||
data + 4, nbytes - 4, 38, false, 0, 50);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,3 +114,385 @@ void IRsend::sendGree(uint64_t data, uint16_t nbits, uint16_t repeat) {
|
||||
}
|
||||
}
|
||||
#endif // SEND_GREE
|
||||
|
||||
IRGreeAC::IRGreeAC(uint16_t pin) : _irsend(pin) {
|
||||
stateReset();
|
||||
}
|
||||
|
||||
void IRGreeAC::stateReset() {
|
||||
// This resets to a known-good state to Power Off, Fan Auto, Mode Auto, 25C.
|
||||
for (uint8_t i = 0; i < GREE_STATE_LENGTH; i++)
|
||||
remote_state[i] = 0x0;
|
||||
remote_state[1] = 0x09;
|
||||
remote_state[2] = 0x20;
|
||||
remote_state[3] = 0x50;
|
||||
remote_state[5] = 0x20;
|
||||
remote_state[7] = 0x50;
|
||||
}
|
||||
|
||||
void IRGreeAC::fixup() {
|
||||
checksum(); // Calculate the checksums
|
||||
}
|
||||
|
||||
void IRGreeAC::begin() {
|
||||
_irsend.begin();
|
||||
}
|
||||
|
||||
#if SEND_GREE
|
||||
void IRGreeAC::send() {
|
||||
fixup(); // Ensure correct settings before sending.
|
||||
_irsend.sendGree(remote_state);
|
||||
}
|
||||
#endif // SEND_GREE
|
||||
|
||||
uint8_t* IRGreeAC::getRaw() {
|
||||
fixup(); // Ensure correct settings before sending.
|
||||
return remote_state;
|
||||
}
|
||||
|
||||
void IRGreeAC::setRaw(uint8_t new_code[]) {
|
||||
for (uint8_t i = 0; i < GREE_STATE_LENGTH; i++) {
|
||||
remote_state[i] = new_code[i];
|
||||
}
|
||||
}
|
||||
|
||||
void IRGreeAC::checksum(const uint16_t length) {
|
||||
// Gree uses the same checksum alg. as Kelvinator's block checksum.
|
||||
uint8_t sum = IRKelvinatorAC::calcBlockChecksum(remote_state, length);
|
||||
remote_state[length - 1] = (sum << 4) | (remote_state[length - 1] & 0xFU);
|
||||
}
|
||||
|
||||
// Verify the checksum is valid for a given state.
|
||||
// Args:
|
||||
// state: The array to verify the checksum of.
|
||||
// length: The size of the state.
|
||||
// Returns:
|
||||
// A boolean.
|
||||
bool IRGreeAC::validChecksum(const uint8_t state[], const uint16_t length) {
|
||||
// Top 4 bits of the last byte in the state is the state's checksum.
|
||||
if (state[length - 1] >> 4 == IRKelvinatorAC::calcBlockChecksum(state,
|
||||
length))
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
void IRGreeAC::on() {
|
||||
remote_state[0] |= GREE_POWER1_MASK;
|
||||
remote_state[2] |= GREE_POWER2_MASK;
|
||||
}
|
||||
|
||||
void IRGreeAC::off() {
|
||||
remote_state[0] &= ~GREE_POWER1_MASK;
|
||||
remote_state[2] &= ~GREE_POWER2_MASK;
|
||||
}
|
||||
|
||||
void IRGreeAC::setPower(const bool state) {
|
||||
if (state)
|
||||
on();
|
||||
else
|
||||
off();
|
||||
}
|
||||
|
||||
bool IRGreeAC::getPower() {
|
||||
return (remote_state[0] & GREE_POWER1_MASK) &&
|
||||
(remote_state[2] & GREE_POWER2_MASK);
|
||||
}
|
||||
|
||||
// Set the temp. in deg C
|
||||
void IRGreeAC::setTemp(const uint8_t temp) {
|
||||
uint8_t new_temp = std::max((uint8_t) GREE_MIN_TEMP, temp);
|
||||
new_temp = std::min((uint8_t) GREE_MAX_TEMP, new_temp);
|
||||
if (getMode() == GREE_AUTO) new_temp = 25;
|
||||
remote_state[1] = (remote_state[1] & 0xF0U) | (new_temp - GREE_MIN_TEMP);
|
||||
}
|
||||
|
||||
// Return the set temp. in deg C
|
||||
uint8_t IRGreeAC::getTemp() {
|
||||
return ((remote_state[1] & 0xFU) + GREE_MIN_TEMP);
|
||||
}
|
||||
|
||||
// Set the speed of the fan, 0-3, 0 is auto, 1-3 is the speed
|
||||
void IRGreeAC::setFan(const uint8_t speed) {
|
||||
uint8_t fan = std::min((uint8_t) GREE_FAN_MAX, speed); // Bounds check
|
||||
|
||||
if (getMode() == GREE_DRY) fan = 1; // DRY mode is always locked to fan 1.
|
||||
// Set the basic fan values.
|
||||
remote_state[0] &= ~GREE_FAN_MASK;
|
||||
remote_state[0] |= (fan << 4);
|
||||
}
|
||||
|
||||
uint8_t IRGreeAC::getFan() {
|
||||
return ((remote_state[0] & GREE_FAN_MASK) >> 4);
|
||||
}
|
||||
|
||||
void IRGreeAC::setMode(const uint8_t new_mode) {
|
||||
uint8_t mode = new_mode;
|
||||
switch (mode) {
|
||||
case GREE_AUTO:
|
||||
// AUTO is locked to 25C
|
||||
setTemp(25);
|
||||
break;
|
||||
case GREE_DRY:
|
||||
// DRY always sets the fan to 1.
|
||||
setFan(1);
|
||||
break;
|
||||
case GREE_COOL:
|
||||
case GREE_FAN:
|
||||
case GREE_HEAT:
|
||||
break;
|
||||
default:
|
||||
// If we get an unexpected mode, default to AUTO.
|
||||
mode = GREE_AUTO;
|
||||
}
|
||||
remote_state[0] &= ~GREE_MODE_MASK;
|
||||
remote_state[0] |= mode;
|
||||
}
|
||||
|
||||
uint8_t IRGreeAC::getMode() {
|
||||
return (remote_state[0] & GREE_MODE_MASK);
|
||||
}
|
||||
|
||||
void IRGreeAC::setLight(const bool state) {
|
||||
remote_state[2] &= ~GREE_LIGHT_MASK;
|
||||
remote_state[2] |= (state << 5);
|
||||
}
|
||||
|
||||
bool IRGreeAC::getLight() {
|
||||
return remote_state[2] & GREE_LIGHT_MASK;
|
||||
}
|
||||
|
||||
void IRGreeAC::setXFan(const bool state) {
|
||||
remote_state[2] &= ~GREE_XFAN_MASK;
|
||||
remote_state[2] |= (state << 7);
|
||||
}
|
||||
|
||||
bool IRGreeAC::getXFan() {
|
||||
return remote_state[2] & GREE_XFAN_MASK;
|
||||
}
|
||||
|
||||
void IRGreeAC::setSleep(const bool state) {
|
||||
remote_state[0] &= ~GREE_SLEEP_MASK;
|
||||
remote_state[0] |= (state << 7);
|
||||
}
|
||||
|
||||
bool IRGreeAC::getSleep() {
|
||||
return remote_state[0] & GREE_SLEEP_MASK;
|
||||
}
|
||||
|
||||
void IRGreeAC::setTurbo(const bool state) {
|
||||
remote_state[2] &= ~GREE_TURBO_MASK;
|
||||
remote_state[2] |= (state << 4);
|
||||
}
|
||||
|
||||
bool IRGreeAC::getTurbo() {
|
||||
return remote_state[2] & GREE_TURBO_MASK;
|
||||
}
|
||||
|
||||
void IRGreeAC::setSwingVertical(const bool automatic, const uint8_t position) {
|
||||
remote_state[0] &= ~GREE_SWING_AUTO_MASK;
|
||||
remote_state[0] |= (automatic << 6);
|
||||
uint8_t new_position = position;
|
||||
if (!automatic) {
|
||||
switch (position) {
|
||||
case GREE_SWING_UP:
|
||||
case GREE_SWING_MIDDLE_UP:
|
||||
case GREE_SWING_MIDDLE:
|
||||
case GREE_SWING_MIDDLE_DOWN:
|
||||
case GREE_SWING_DOWN:
|
||||
break;
|
||||
default:
|
||||
new_position = GREE_SWING_LAST_POS;
|
||||
}
|
||||
} else {
|
||||
switch (position) {
|
||||
case GREE_SWING_AUTO:
|
||||
case GREE_SWING_DOWN_AUTO:
|
||||
case GREE_SWING_MIDDLE_AUTO:
|
||||
case GREE_SWING_UP_AUTO:
|
||||
break;
|
||||
default:
|
||||
new_position = GREE_SWING_AUTO;
|
||||
}
|
||||
}
|
||||
remote_state[4] &= ~GREE_SWING_POS_MASK;
|
||||
remote_state[4] |= new_position;
|
||||
}
|
||||
|
||||
bool IRGreeAC::getSwingVerticalAuto() {
|
||||
return remote_state[0] & GREE_SWING_AUTO_MASK;
|
||||
}
|
||||
|
||||
uint8_t IRGreeAC::getSwingVerticalPosition() {
|
||||
return remote_state[4] & GREE_SWING_POS_MASK;
|
||||
}
|
||||
|
||||
// Convert the internal state into a human readable string.
|
||||
#ifdef ARDUINO
|
||||
String IRGreeAC::toString() {
|
||||
String result = "";
|
||||
#else
|
||||
std::string IRGreeAC::toString() {
|
||||
std::string result = "";
|
||||
#endif // ARDUINO
|
||||
result += "Power: ";
|
||||
if (getPower())
|
||||
result += "On";
|
||||
else
|
||||
result += "Off";
|
||||
result += ", Mode: " + uint64ToString(getMode());
|
||||
switch (getMode()) {
|
||||
case GREE_AUTO:
|
||||
result += " (AUTO)";
|
||||
break;
|
||||
case GREE_COOL:
|
||||
result += " (COOL)";
|
||||
break;
|
||||
case GREE_HEAT:
|
||||
result += " (HEAT)";
|
||||
break;
|
||||
case GREE_DRY:
|
||||
result += " (DRY)";
|
||||
break;
|
||||
case GREE_FAN:
|
||||
result += " (FAN)";
|
||||
break;
|
||||
default:
|
||||
result += " (UNKNOWN)";
|
||||
}
|
||||
result += ", Temp: " + uint64ToString(getTemp()) + "C";
|
||||
result += ", Fan: " + uint64ToString(getFan());
|
||||
switch (getFan()) {
|
||||
case 0:
|
||||
result += " (AUTO)";
|
||||
break;
|
||||
case GREE_FAN_MAX:
|
||||
result += " (MAX)";
|
||||
break;
|
||||
}
|
||||
result += ", Turbo: ";
|
||||
if (getTurbo())
|
||||
result += "On";
|
||||
else
|
||||
result += "Off";
|
||||
result += ", XFan: ";
|
||||
if (getXFan())
|
||||
result += "On";
|
||||
else
|
||||
result += "Off";
|
||||
result += ", Light: ";
|
||||
if (getLight())
|
||||
result += "On";
|
||||
else
|
||||
result += "Off";
|
||||
result += ", Sleep: ";
|
||||
if (getSleep())
|
||||
result += "On";
|
||||
else
|
||||
result += "Off";
|
||||
result += ", Swing Vertical Mode: ";
|
||||
if (getSwingVerticalAuto())
|
||||
result += "Auto";
|
||||
else
|
||||
result += "Manual";
|
||||
result += ", Swing Vertical Pos: " +
|
||||
uint64ToString(getSwingVerticalPosition());
|
||||
switch (getSwingVerticalPosition()) {
|
||||
case GREE_SWING_LAST_POS:
|
||||
result += " (Last Pos)";
|
||||
break;
|
||||
case GREE_SWING_AUTO:
|
||||
result += " (Auto)";
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#if DECODE_GREE
|
||||
// Decode the supplied Gree message.
|
||||
//
|
||||
// Args:
|
||||
// results: Ptr to the data to decode and where to store the decode result.
|
||||
// nbits: The number of data bits to expect. Typically GREE_BITS.
|
||||
// strict: Flag indicating if we should perform strict matching.
|
||||
// Returns:
|
||||
// boolean: True if it can decode it, false if it can't.
|
||||
//
|
||||
// Status: ALPHA / Untested.
|
||||
bool IRrecv::decodeGree(decode_results *results, uint16_t nbits, bool strict) {
|
||||
if (results->rawlen < 2 * (nbits + GREE_BLOCK_FOOTER_BITS) +
|
||||
(HEADER + FOOTER + 1))
|
||||
return false; // Can't possibly be a valid Gree message.
|
||||
if (strict && nbits != GREE_BITS)
|
||||
return false; // Not strictly a Gree message.
|
||||
|
||||
uint32_t data;
|
||||
uint16_t offset = OFFSET_START;
|
||||
|
||||
// There are two blocks back-to-back in a full Gree IR message
|
||||
// sequence.
|
||||
int8_t state_pos = 0;
|
||||
match_result_t data_result;
|
||||
|
||||
// Header
|
||||
if (!matchMark(results->rawbuf[offset++], GREE_HDR_MARK)) return false;
|
||||
if (!matchSpace(results->rawbuf[offset++], GREE_HDR_SPACE)) return false;
|
||||
// Data Block #1 (32 bits)
|
||||
data_result = matchData(&(results->rawbuf[offset]), 32, GREE_BIT_MARK,
|
||||
GREE_ONE_SPACE, GREE_BIT_MARK, GREE_ZERO_SPACE);
|
||||
if (data_result.success == false) return false;
|
||||
data = data_result.data;
|
||||
offset += data_result.used;
|
||||
|
||||
// Record Data Block #1 in the state.
|
||||
for (int i = state_pos + 3; i >= state_pos; i--, data >>= 8)
|
||||
results->state[i] = reverseBits(data & 0xFF, 8);
|
||||
state_pos += 4;
|
||||
|
||||
// Block #1 footer (3 bits, B010)
|
||||
data_result = matchData(&(results->rawbuf[offset]), GREE_BLOCK_FOOTER_BITS,
|
||||
GREE_BIT_MARK, GREE_ONE_SPACE, GREE_BIT_MARK,
|
||||
GREE_ZERO_SPACE);
|
||||
if (data_result.success == false) return false;
|
||||
if (data_result.data != GREE_BLOCK_FOOTER) return false;
|
||||
offset += data_result.used;
|
||||
|
||||
// Inter-block gap.
|
||||
if (!matchMark(results->rawbuf[offset++], GREE_BIT_MARK)) return false;
|
||||
if (!matchSpace(results->rawbuf[offset++], GREE_MSG_SPACE)) return false;
|
||||
|
||||
// Data Block #2 (32 bits)
|
||||
data_result = matchData(&(results->rawbuf[offset]), 32, GREE_BIT_MARK,
|
||||
GREE_ONE_SPACE, GREE_BIT_MARK, GREE_ZERO_SPACE);
|
||||
if (data_result.success == false) return false;
|
||||
data = data_result.data;
|
||||
offset += data_result.used;
|
||||
|
||||
// Record Data Block #2 in the state.
|
||||
for (int i = state_pos + 3; i >= state_pos; i--, data >>= 8)
|
||||
results->state[i] = reverseBits(data & 0xFF, 8);
|
||||
state_pos += 4;
|
||||
|
||||
// Footer.
|
||||
if (!matchMark(results->rawbuf[offset++], GREE_BIT_MARK)) return false;
|
||||
if (offset <= results->rawlen &&
|
||||
!matchAtLeast(results->rawbuf[offset], GREE_MSG_SPACE))
|
||||
return false;
|
||||
|
||||
// Compliance
|
||||
if (strict) {
|
||||
// Correct size/length)
|
||||
if (state_pos != GREE_STATE_LENGTH) return false;
|
||||
// Verify the message's checksum is correct.
|
||||
if (!IRGreeAC::validChecksum(results->state)) return false;
|
||||
}
|
||||
|
||||
// Success
|
||||
results->decode_type = GREE;
|
||||
results->bits = state_pos * 8;
|
||||
// No need to record the state as we stored it as we decoded it.
|
||||
// As we use result->state, we don't record value, address, or command as it
|
||||
// is a union data type.
|
||||
return true;
|
||||
}
|
||||
#endif // DECODE_GREE
|
||||
@@ -0,0 +1,106 @@
|
||||
// Kelvinator A/C
|
||||
//
|
||||
// Copyright 2016 David Conran
|
||||
|
||||
#ifndef IR_GREE_H_
|
||||
#define IR_GREE_H_
|
||||
|
||||
#define __STDC_LIMIT_MACROS
|
||||
#include <stdint.h>
|
||||
#ifndef UNIT_TEST
|
||||
#include <Arduino.h>
|
||||
#else
|
||||
#include <string>
|
||||
#endif
|
||||
#include "IRremoteESP8266.h"
|
||||
#include "IRsend.h"
|
||||
|
||||
// GGGG RRRRRR EEEEEEE EEEEEEE
|
||||
// GG GG RR RR EE EE
|
||||
// GG RRRRRR EEEEE EEEEE
|
||||
// GG GG RR RR EE EE
|
||||
// GGGGGG RR RR EEEEEEE EEEEEEE
|
||||
|
||||
// Constants
|
||||
#define GREE_AUTO 0U
|
||||
#define GREE_COOL 1U
|
||||
#define GREE_DRY 2U
|
||||
#define GREE_FAN 3U
|
||||
#define GREE_HEAT 4U
|
||||
|
||||
#define GREE_POWER1_MASK 0b00001000U
|
||||
#define GREE_POWER2_MASK 0b01000000U
|
||||
#define GREE_MIN_TEMP 16U // Celsius
|
||||
#define GREE_MAX_TEMP 30U // Celsius
|
||||
#define GREE_FAN_MAX 3U
|
||||
#define GREE_FAN_MASK 0b00110000U
|
||||
#define GREE_MODE_MASK 0b00000111U
|
||||
#define GREE_TURBO_MASK 0b00010000U
|
||||
#define GREE_LIGHT_MASK 0b00100000U
|
||||
#define GREE_XFAN_MASK 0b10000000U
|
||||
#define GREE_SLEEP_MASK 0b10000000U
|
||||
|
||||
#define GREE_SWING_AUTO_MASK 0b01000000U
|
||||
#define GREE_SWING_POS_MASK 0b00001111U
|
||||
#define GREE_SWING_LAST_POS 0b00000000U
|
||||
#define GREE_SWING_AUTO 0b00000001U
|
||||
#define GREE_SWING_UP 0b00000010U
|
||||
#define GREE_SWING_MIDDLE_UP 0b00000011U
|
||||
#define GREE_SWING_MIDDLE 0b00000100U
|
||||
#define GREE_SWING_MIDDLE_DOWN 0b00000101U
|
||||
#define GREE_SWING_DOWN 0b00000110U
|
||||
#define GREE_SWING_DOWN_AUTO 0b00000111U
|
||||
#define GREE_SWING_MIDDLE_AUTO 0b00001001U
|
||||
#define GREE_SWING_UP_AUTO 0b00001011U
|
||||
|
||||
// Classes
|
||||
class IRGreeAC {
|
||||
public:
|
||||
explicit IRGreeAC(uint16_t pin);
|
||||
|
||||
void stateReset();
|
||||
#if SEND_GREE
|
||||
void send();
|
||||
#endif // SEND_GREE
|
||||
void begin();
|
||||
void on();
|
||||
void off();
|
||||
void setPower(const bool state);
|
||||
bool getPower();
|
||||
void setTemp(const uint8_t temp);
|
||||
uint8_t getTemp();
|
||||
void setFan(const uint8_t speed);
|
||||
uint8_t getFan();
|
||||
void setMode(const uint8_t new_mode);
|
||||
uint8_t getMode();
|
||||
void setLight(const bool state);
|
||||
bool getLight();
|
||||
void setXFan(const bool state);
|
||||
bool getXFan();
|
||||
void setSleep(const bool state);
|
||||
bool getSleep();
|
||||
void setTurbo(const bool state);
|
||||
bool getTurbo();
|
||||
void setSwingVertical(const bool automatic, const uint8_t position);
|
||||
bool getSwingVerticalAuto();
|
||||
uint8_t getSwingVerticalPosition();
|
||||
|
||||
uint8_t* getRaw();
|
||||
void setRaw(uint8_t new_code[]);
|
||||
static bool validChecksum(const uint8_t state[],
|
||||
const uint16_t length = GREE_STATE_LENGTH);
|
||||
#ifdef ARDUINO
|
||||
String toString();
|
||||
#else
|
||||
std::string toString();
|
||||
#endif
|
||||
|
||||
private:
|
||||
// The state of the IR remote in IR code form.
|
||||
uint8_t remote_state[GREE_STATE_LENGTH];
|
||||
void checksum(const uint16_t length = GREE_STATE_LENGTH);
|
||||
void fixup();
|
||||
IRsend _irsend;
|
||||
};
|
||||
|
||||
#endif // IR_GREE_H_
|
||||
@@ -0,0 +1,497 @@
|
||||
// Copyright 2018 crankyoldgit
|
||||
// The specifics of reverse engineering the protocol details by kuzin2006
|
||||
|
||||
#include "ir_Haier.h"
|
||||
#ifndef UNIT_TEST
|
||||
#include <Arduino.h>
|
||||
#else
|
||||
#include <string>
|
||||
#endif
|
||||
#include "IRremoteESP8266.h"
|
||||
#include "IRutils.h"
|
||||
|
||||
// HH HH AAA IIIII EEEEEEE RRRRRR
|
||||
// HH HH AAAAA III EE RR RR
|
||||
// HHHHHHH AA AA III EEEEE RRRRRR
|
||||
// HH HH AAAAAAA III EE RR RR
|
||||
// HH HH AA AA IIIII EEEEEEE RR RR
|
||||
|
||||
// Supported devices:
|
||||
// * Haier HSU07-HEA03 Remote control.
|
||||
|
||||
// Ref:
|
||||
// https://github.com/markszabo/IRremoteESP8266/issues/404
|
||||
// https://www.dropbox.com/s/mecyib3lhdxc8c6/IR%20data%20reverse%20engineering.xlsx?dl=0
|
||||
|
||||
// Constants
|
||||
#define HAIER_AC_HDR 3000U
|
||||
#define HAIER_AC_HDR_GAP 4300U
|
||||
|
||||
#define HAIER_AC_BIT_MARK 520U
|
||||
#define HAIER_AC_ONE_SPACE 1650U
|
||||
#define HAIER_AC_ZERO_SPACE 650U
|
||||
#define HAIER_AC_MIN_GAP 150000U // Completely made up value.
|
||||
|
||||
#if SEND_HAIER_AC
|
||||
// Send a Haier A/C message.
|
||||
//
|
||||
// Args:
|
||||
// data: An array of bytes containing the IR command.
|
||||
// nbytes: Nr. of bytes of data in the array. (>=HAIER_AC_STATE_LENGTH)
|
||||
// repeat: Nr. of times the message is to be repeated. (Default = 0).
|
||||
//
|
||||
// Status: Beta / Probably working.
|
||||
//
|
||||
void IRsend::sendHaierAC(unsigned char data[], uint16_t nbytes,
|
||||
uint16_t repeat) {
|
||||
if (nbytes < HAIER_AC_STATE_LENGTH)
|
||||
return;
|
||||
|
||||
for (uint16_t r = 0; r <= repeat; r++) {
|
||||
enableIROut(38000);
|
||||
mark(HAIER_AC_HDR);
|
||||
space(HAIER_AC_HDR);
|
||||
sendGeneric(HAIER_AC_HDR, HAIER_AC_HDR_GAP,
|
||||
HAIER_AC_BIT_MARK, HAIER_AC_ONE_SPACE,
|
||||
HAIER_AC_BIT_MARK, HAIER_AC_ZERO_SPACE,
|
||||
HAIER_AC_BIT_MARK, HAIER_AC_MIN_GAP,
|
||||
data, nbytes, 38, true, 0, // Repeats handled elsewhere
|
||||
50);
|
||||
}
|
||||
}
|
||||
#endif // SEND_HAIER_AC
|
||||
|
||||
IRHaierAC::IRHaierAC(uint16_t pin) : _irsend(pin) {
|
||||
stateReset();
|
||||
}
|
||||
|
||||
void IRHaierAC::begin() {
|
||||
_irsend.begin();
|
||||
}
|
||||
|
||||
#if SEND_HAIER_AC
|
||||
void IRHaierAC::send() {
|
||||
checksum();
|
||||
_irsend.sendHaierAC(remote_state);
|
||||
}
|
||||
#endif // SEND_HAIER_AC
|
||||
|
||||
void IRHaierAC::checksum() {
|
||||
remote_state[8] = sumBytes(remote_state, HAIER_AC_STATE_LENGTH - 1);
|
||||
}
|
||||
|
||||
bool IRHaierAC::validChecksum(uint8_t state[], const uint16_t length) {
|
||||
if (length < 2) return false; // 1 byte of data can't have a checksum.
|
||||
return (state[length - 1] == sumBytes(state, length - 1));
|
||||
}
|
||||
|
||||
void IRHaierAC::stateReset() {
|
||||
for (uint8_t i = 1; i < HAIER_AC_STATE_LENGTH; i++)
|
||||
remote_state[i] = 0x0;
|
||||
remote_state[0] = HAIER_AC_PREFIX;
|
||||
remote_state[2] = 0b00100000;
|
||||
|
||||
setTemp(HAIER_AC_DEF_TEMP);
|
||||
setFan(HAIER_AC_FAN_AUTO);
|
||||
setMode(HAIER_AC_AUTO);
|
||||
setCommand(HAIER_AC_CMD_ON);
|
||||
}
|
||||
|
||||
uint8_t* IRHaierAC::getRaw() {
|
||||
checksum();
|
||||
return remote_state;
|
||||
}
|
||||
|
||||
void IRHaierAC::setRaw(uint8_t new_code[]) {
|
||||
for (uint8_t i = 0; i < HAIER_AC_STATE_LENGTH; i++) {
|
||||
remote_state[i] = new_code[i];
|
||||
}
|
||||
}
|
||||
|
||||
void IRHaierAC::setCommand(uint8_t state) {
|
||||
remote_state[1] &= 0b11110000;
|
||||
switch (state) {
|
||||
case HAIER_AC_CMD_OFF:
|
||||
case HAIER_AC_CMD_ON:
|
||||
case HAIER_AC_CMD_MODE:
|
||||
case HAIER_AC_CMD_FAN:
|
||||
case HAIER_AC_CMD_TEMP_UP:
|
||||
case HAIER_AC_CMD_TEMP_DOWN:
|
||||
case HAIER_AC_CMD_SLEEP:
|
||||
case HAIER_AC_CMD_TIMER_SET:
|
||||
case HAIER_AC_CMD_TIMER_CANCEL:
|
||||
case HAIER_AC_CMD_HEALTH:
|
||||
case HAIER_AC_CMD_SWING:
|
||||
remote_state[1] |= (state & 0b00001111);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t IRHaierAC::getCommand() {
|
||||
return remote_state[1] & (0b00001111);
|
||||
}
|
||||
|
||||
void IRHaierAC::setFan(uint8_t speed) {
|
||||
uint8_t new_speed = HAIER_AC_FAN_AUTO;
|
||||
switch (speed) {
|
||||
case HAIER_AC_FAN_LOW:
|
||||
new_speed = 3;
|
||||
break;
|
||||
case HAIER_AC_FAN_MED:
|
||||
new_speed = 1;
|
||||
break;
|
||||
case HAIER_AC_FAN_HIGH:
|
||||
new_speed = 2;
|
||||
break;
|
||||
default:
|
||||
new_speed = HAIER_AC_FAN_AUTO; // Default to auto for anything else.
|
||||
}
|
||||
|
||||
if (speed != getFan()) setCommand(HAIER_AC_CMD_FAN);
|
||||
remote_state[5] &= 0b11111100;
|
||||
remote_state[5] |= new_speed;
|
||||
}
|
||||
|
||||
uint8_t IRHaierAC::getFan() {
|
||||
switch (remote_state[5] & 0b00000011) {
|
||||
case 1:
|
||||
return HAIER_AC_FAN_MED;
|
||||
case 2:
|
||||
return HAIER_AC_FAN_HIGH;
|
||||
case 3:
|
||||
return HAIER_AC_FAN_LOW;
|
||||
default:
|
||||
return HAIER_AC_FAN_AUTO;
|
||||
}
|
||||
}
|
||||
|
||||
void IRHaierAC::setMode(uint8_t mode) {
|
||||
uint8_t new_mode = mode;
|
||||
setCommand(HAIER_AC_CMD_MODE);
|
||||
if (mode > HAIER_AC_FAN) // If out of range, default to auto mode.
|
||||
new_mode = HAIER_AC_AUTO;
|
||||
remote_state[7] &= 0b00011111;
|
||||
remote_state[7] |= (new_mode << 5);
|
||||
}
|
||||
|
||||
uint8_t IRHaierAC::getMode() {
|
||||
return (remote_state[7] & 0b11100000) >> 5;
|
||||
}
|
||||
|
||||
void IRHaierAC::setTemp(const uint8_t celcius ) {
|
||||
uint8_t temp = celcius;
|
||||
if (temp < HAIER_AC_MIN_TEMP)
|
||||
temp = HAIER_AC_MIN_TEMP;
|
||||
else if (temp > HAIER_AC_MAX_TEMP)
|
||||
temp = HAIER_AC_MAX_TEMP;
|
||||
|
||||
uint8_t old_temp = getTemp();
|
||||
if (old_temp == temp) return;
|
||||
if (old_temp > temp)
|
||||
setCommand(HAIER_AC_CMD_TEMP_DOWN);
|
||||
else
|
||||
setCommand(HAIER_AC_CMD_TEMP_UP);
|
||||
|
||||
remote_state[1] &= 0b00001111; // Clear the previous temp.
|
||||
remote_state[1] |= ((temp - HAIER_AC_MIN_TEMP) << 4);
|
||||
}
|
||||
|
||||
uint8_t IRHaierAC::getTemp() {
|
||||
return ((remote_state[1] & 0b11110000) >> 4) + HAIER_AC_MIN_TEMP;
|
||||
}
|
||||
|
||||
void IRHaierAC::setHealth(bool state) {
|
||||
setCommand(HAIER_AC_CMD_HEALTH);
|
||||
remote_state[4] &= 0b11011111;
|
||||
remote_state[4] |= (state << 5);
|
||||
}
|
||||
|
||||
bool IRHaierAC::getHealth(void) {
|
||||
return remote_state[4] & (1 << 5);
|
||||
}
|
||||
|
||||
void IRHaierAC::setSleep(bool state) {
|
||||
setCommand(HAIER_AC_CMD_SLEEP);
|
||||
remote_state[7] &= 0b10111111;
|
||||
remote_state[7] |= (state << 6);
|
||||
}
|
||||
|
||||
bool IRHaierAC::getSleep(void) {
|
||||
return remote_state[7] & 0b01000000;
|
||||
}
|
||||
|
||||
uint16_t IRHaierAC::getTime(const uint8_t ptr[]) {
|
||||
return (ptr[0] & 0b00011111) * 60 + (ptr[1] & 0b00111111);
|
||||
}
|
||||
|
||||
int16_t IRHaierAC::getOnTimer() {
|
||||
if (remote_state[3] & 0b10000000) // Check if the timer is turned on.
|
||||
return getTime(remote_state + 6);
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
int16_t IRHaierAC::getOffTimer() {
|
||||
if (remote_state[3] & 0b01000000) // Check if the timer is turned on.
|
||||
return getTime(remote_state + 4);
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint16_t IRHaierAC::getCurrTime() {
|
||||
return getTime(remote_state + 2);
|
||||
}
|
||||
|
||||
void IRHaierAC::setTime(uint8_t ptr[], const uint16_t nr_mins) {
|
||||
uint16_t mins = nr_mins;
|
||||
if (nr_mins > HAIER_AC_MAX_TIME)
|
||||
mins = HAIER_AC_MAX_TIME;
|
||||
|
||||
// Hours
|
||||
ptr[0] &= 0b11100000;
|
||||
ptr[0] |= (mins / 60);
|
||||
// Minutes
|
||||
ptr[1] &= 0b11000000;
|
||||
ptr[1] |= (mins % 60);
|
||||
}
|
||||
|
||||
void IRHaierAC::setOnTimer(const uint16_t nr_mins) {
|
||||
setCommand(HAIER_AC_CMD_TIMER_SET);
|
||||
remote_state[3] |= 0b10000000;
|
||||
setTime(remote_state + 6, nr_mins);
|
||||
}
|
||||
|
||||
void IRHaierAC::setOffTimer(const uint16_t nr_mins) {
|
||||
setCommand(HAIER_AC_CMD_TIMER_SET);
|
||||
remote_state[3] |= 0b01000000;
|
||||
setTime(remote_state + 4, nr_mins);
|
||||
}
|
||||
|
||||
void IRHaierAC::cancelTimers() {
|
||||
setCommand(HAIER_AC_CMD_TIMER_CANCEL);
|
||||
remote_state[3] &= 0b00111111;
|
||||
}
|
||||
|
||||
void IRHaierAC::setCurrTime(const uint16_t nr_mins) {
|
||||
setTime(remote_state + 2, nr_mins);
|
||||
}
|
||||
|
||||
uint8_t IRHaierAC::getSwing() {
|
||||
return (remote_state[2] & 0b11000000) >> 6;
|
||||
}
|
||||
|
||||
void IRHaierAC::setSwing(const uint8_t state) {
|
||||
if (state == getSwing()) return; // Nothing to do.
|
||||
setCommand(HAIER_AC_CMD_SWING);
|
||||
switch (state) {
|
||||
case HAIER_AC_SWING_OFF:
|
||||
case HAIER_AC_SWING_UP:
|
||||
case HAIER_AC_SWING_DOWN:
|
||||
case HAIER_AC_SWING_CHG:
|
||||
remote_state[2] &= 0b00111111;
|
||||
remote_state[2] |= (state << 6);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert a Haier time into a human readable string.
|
||||
#ifdef ARDUINO
|
||||
String IRHaierAC::timeToString(const uint16_t nr_mins) {
|
||||
String result = "";
|
||||
#else
|
||||
std::string IRHaierAC::timeToString(const uint16_t nr_mins) {
|
||||
std::string result = "";
|
||||
#endif // ARDUINO
|
||||
|
||||
if (nr_mins / 24 < 10) result += "0"; // Zero pad.
|
||||
result += uint64ToString(nr_mins / 60);
|
||||
result += ":";
|
||||
if (nr_mins % 60 < 10) result += "0"; // Zero pad.
|
||||
result += uint64ToString(nr_mins % 60);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Convert the internal state into a human readable string.
|
||||
#ifdef ARDUINO
|
||||
String IRHaierAC::toString() {
|
||||
String result = "";
|
||||
#else
|
||||
std::string IRHaierAC::toString() {
|
||||
std::string result = "";
|
||||
#endif // ARDUINO
|
||||
uint8_t cmd = getCommand();
|
||||
result += "Command: " + uint64ToString(cmd) +" (";
|
||||
switch (cmd) {
|
||||
case HAIER_AC_CMD_OFF:
|
||||
result += "Off";
|
||||
break;
|
||||
case HAIER_AC_CMD_ON:
|
||||
result += "On";
|
||||
break;
|
||||
case HAIER_AC_CMD_MODE:
|
||||
result += "Mode";
|
||||
break;
|
||||
case HAIER_AC_CMD_FAN:
|
||||
result += "Fan";
|
||||
break;
|
||||
case HAIER_AC_CMD_TEMP_UP:
|
||||
result += "Temp Up";
|
||||
break;
|
||||
case HAIER_AC_CMD_TEMP_DOWN:
|
||||
result += "Temp Down";
|
||||
break;
|
||||
case HAIER_AC_CMD_SLEEP:
|
||||
result += "Sleep";
|
||||
break;
|
||||
case HAIER_AC_CMD_TIMER_SET:
|
||||
result += "Timer Set";
|
||||
break;
|
||||
case HAIER_AC_CMD_TIMER_CANCEL:
|
||||
result += "Timer Cancel";
|
||||
break;
|
||||
case HAIER_AC_CMD_HEALTH:
|
||||
result += "Health";
|
||||
break;
|
||||
case HAIER_AC_CMD_SWING:
|
||||
result += "Swing";
|
||||
break;
|
||||
default:
|
||||
result += "Unknown";
|
||||
}
|
||||
result += ")";
|
||||
result += ", Mode: " + uint64ToString(getMode());
|
||||
switch (getMode()) {
|
||||
case HAIER_AC_AUTO:
|
||||
result += " (AUTO)";
|
||||
break;
|
||||
case HAIER_AC_COOL:
|
||||
result += " (COOL)";
|
||||
break;
|
||||
case HAIER_AC_HEAT:
|
||||
result += " (HEAT)";
|
||||
break;
|
||||
case HAIER_AC_DRY:
|
||||
result += " (DRY)";
|
||||
break;
|
||||
case HAIER_AC_FAN:
|
||||
result += " (FAN)";
|
||||
break;
|
||||
default:
|
||||
result += " (UNKNOWN)";
|
||||
}
|
||||
result += ", Temp: " + uint64ToString(getTemp()) + "C";
|
||||
result += ", Fan: " + uint64ToString(getFan());
|
||||
switch (getFan()) {
|
||||
case HAIER_AC_FAN_AUTO:
|
||||
result += " (AUTO)";
|
||||
break;
|
||||
case HAIER_AC_FAN_HIGH:
|
||||
result += " (MAX)";
|
||||
break;
|
||||
}
|
||||
result += ", Swing: " + uint64ToString(getSwing()) + " (";
|
||||
switch (getSwing()) {
|
||||
case HAIER_AC_SWING_OFF:
|
||||
result += "Off";
|
||||
break;
|
||||
case HAIER_AC_SWING_UP:
|
||||
result += "Up";
|
||||
break;
|
||||
case HAIER_AC_SWING_DOWN:
|
||||
result += "Down";
|
||||
break;
|
||||
case HAIER_AC_SWING_CHG:
|
||||
result += "Chg";
|
||||
break;
|
||||
default:
|
||||
result += "Unknown";
|
||||
}
|
||||
result += ")";
|
||||
result += ", Sleep: ";
|
||||
if (getSleep())
|
||||
result += "On";
|
||||
else
|
||||
result += "Off";
|
||||
result += ", Health: ";
|
||||
if (getHealth())
|
||||
result += "On";
|
||||
else
|
||||
result += "Off";
|
||||
result += ", Current Time: " + timeToString(getCurrTime());
|
||||
result += ", On Timer: ";
|
||||
if (getOnTimer() >= 0)
|
||||
result += timeToString(getOnTimer());
|
||||
else
|
||||
result += "Off";
|
||||
result += ", Off Timer: ";
|
||||
if (getOffTimer() >= 0)
|
||||
result += timeToString(getOffTimer());
|
||||
else
|
||||
result += "Off";
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#if DECODE_HAIER_AC
|
||||
// Decode the supplied Haier message.
|
||||
//
|
||||
// Args:
|
||||
// results: Ptr to the data to decode and where to store the decode result.
|
||||
// nbits: The number of data bits to expect. Typically HAIER_AC_BITS.
|
||||
// strict: Flag indicating if we should perform strict matching.
|
||||
// Returns:
|
||||
// boolean: True if it can decode it, false if it can't.
|
||||
//
|
||||
// Status: BETA / Appears to be working.
|
||||
//
|
||||
bool IRrecv::decodeHaierAC(decode_results *results, uint16_t nbits,
|
||||
bool strict) {
|
||||
if (nbits % 8 != 0) // nbits has to be a multiple of nr. of bits in a byte.
|
||||
return false;
|
||||
|
||||
if (strict) {
|
||||
if (nbits != HAIER_AC_BITS)
|
||||
return false; // Not strictly a HAIER_AC message.
|
||||
}
|
||||
|
||||
if (results->rawlen < (2 * nbits + HEADER) + FOOTER - 1)
|
||||
return false; // Can't possibly be a valid HAIER_AC message.
|
||||
|
||||
uint16_t offset = OFFSET_START;
|
||||
|
||||
|
||||
// Header
|
||||
if (!matchMark(results->rawbuf[offset++], HAIER_AC_HDR)) return false;
|
||||
if (!matchSpace(results->rawbuf[offset++], HAIER_AC_HDR)) return false;
|
||||
if (!matchMark(results->rawbuf[offset++], HAIER_AC_HDR)) return false;
|
||||
if (!matchSpace(results->rawbuf[offset++], HAIER_AC_HDR_GAP)) return false;
|
||||
|
||||
// Data
|
||||
for (uint16_t i = 0; i < nbits / 8; i++) {
|
||||
match_result_t data_result = matchData(&(results->rawbuf[offset]), 8,
|
||||
HAIER_AC_BIT_MARK,
|
||||
HAIER_AC_ONE_SPACE,
|
||||
HAIER_AC_BIT_MARK,
|
||||
HAIER_AC_ZERO_SPACE);
|
||||
if (data_result.success == false) return false;
|
||||
offset += data_result.used;
|
||||
results->state[i] = (uint8_t) data_result.data;
|
||||
}
|
||||
|
||||
// Footer
|
||||
if (!matchMark(results->rawbuf[offset++], HAIER_AC_BIT_MARK)) return false;
|
||||
if (offset < results->rawlen &&
|
||||
!matchAtLeast(results->rawbuf[offset++], HAIER_AC_MIN_GAP))
|
||||
return false;
|
||||
|
||||
// Compliance
|
||||
if (strict) {
|
||||
if (results->state[0] != HAIER_AC_PREFIX) return false;
|
||||
if (!IRHaierAC::validChecksum(results->state, nbits / 8)) return false;
|
||||
}
|
||||
|
||||
// Success
|
||||
results->decode_type = HAIER_AC;
|
||||
results->bits = nbits;
|
||||
return true;
|
||||
}
|
||||
#endif // DECODE_HAIER_AC
|
||||
@@ -0,0 +1,126 @@
|
||||
// Copyright 2018 crankyoldgit
|
||||
// The specifics of reverse engineering the protocol details by kuzin2006
|
||||
|
||||
#ifndef IR_HAIER_H_
|
||||
#define IR_HAIER_H_
|
||||
|
||||
#ifndef UNIT_TEST
|
||||
#include <Arduino.h>
|
||||
#else
|
||||
#include <string>
|
||||
#endif
|
||||
#include "IRremoteESP8266.h"
|
||||
#include "IRsend.h"
|
||||
|
||||
// HH HH AAA IIIII EEEEEEE RRRRRR
|
||||
// HH HH AAAAA III EE RR RR
|
||||
// HHHHHHH AA AA III EEEEE RRRRRR
|
||||
// HH HH AAAAAAA III EE RR RR
|
||||
// HH HH AA AA IIIII EEEEEEE RR RR
|
||||
|
||||
// Ref:
|
||||
// https://github.com/markszabo/IRremoteESP8266/issues/404
|
||||
// https://www.dropbox.com/s/mecyib3lhdxc8c6/IR%20data%20reverse%20engineering.xlsx?dl=0
|
||||
|
||||
// Constants
|
||||
// Byte 0
|
||||
#define HAIER_AC_PREFIX 0b10100101
|
||||
|
||||
// Byte 1
|
||||
#define HAIER_AC_MIN_TEMP 16
|
||||
#define HAIER_AC_MAX_TEMP 30
|
||||
#define HAIER_AC_DEF_TEMP 25
|
||||
|
||||
#define HAIER_AC_CMD_OFF 0b00000000
|
||||
#define HAIER_AC_CMD_ON 0b00000001
|
||||
#define HAIER_AC_CMD_MODE 0b00000010
|
||||
#define HAIER_AC_CMD_FAN 0b00000011
|
||||
#define HAIER_AC_CMD_TEMP_UP 0b00000110
|
||||
#define HAIER_AC_CMD_TEMP_DOWN 0b00000111
|
||||
#define HAIER_AC_CMD_SLEEP 0b00001000
|
||||
#define HAIER_AC_CMD_TIMER_SET 0b00001001
|
||||
#define HAIER_AC_CMD_TIMER_CANCEL 0b00001010
|
||||
#define HAIER_AC_CMD_HEALTH 0b00001100
|
||||
#define HAIER_AC_CMD_SWING 0b00001101
|
||||
|
||||
// Byte 2
|
||||
#define HAIER_AC_SWING_OFF 0b00000000
|
||||
#define HAIER_AC_SWING_UP 0b00000001
|
||||
#define HAIER_AC_SWING_DOWN 0b00000010
|
||||
#define HAIER_AC_SWING_CHG 0b00000011
|
||||
|
||||
// Byte 6
|
||||
#define HAIER_AC_AUTO 0
|
||||
#define HAIER_AC_COOL 1
|
||||
#define HAIER_AC_DRY 2
|
||||
#define HAIER_AC_HEAT 3
|
||||
#define HAIER_AC_FAN 4
|
||||
|
||||
#define HAIER_AC_FAN_AUTO 0
|
||||
#define HAIER_AC_FAN_LOW 1
|
||||
#define HAIER_AC_FAN_MED 2
|
||||
#define HAIER_AC_FAN_HIGH 3
|
||||
|
||||
#define HAIER_AC_MAX_TIME (23 * 60 + 59)
|
||||
|
||||
|
||||
class IRHaierAC {
|
||||
public:
|
||||
explicit IRHaierAC(uint16_t pin);
|
||||
|
||||
#if SEND_HAIER_AC
|
||||
void send();
|
||||
#endif // SEND_HAIER_AC
|
||||
void begin();
|
||||
|
||||
void setCommand(const uint8_t command);
|
||||
uint8_t getCommand();
|
||||
|
||||
void setTemp(const uint8_t temp);
|
||||
uint8_t getTemp();
|
||||
|
||||
void setFan(const uint8_t speed);
|
||||
uint8_t getFan();
|
||||
|
||||
uint8_t getMode();
|
||||
void setMode(const uint8_t mode);
|
||||
|
||||
bool getSleep();
|
||||
void setSleep(const bool state);
|
||||
bool getHealth();
|
||||
void setHealth(const bool state);
|
||||
|
||||
int16_t getOnTimer();
|
||||
void setOnTimer(const uint16_t mins);
|
||||
int16_t getOffTimer();
|
||||
void setOffTimer(const uint16_t mins);
|
||||
void cancelTimers();
|
||||
|
||||
uint16_t getCurrTime();
|
||||
void setCurrTime(const uint16_t mins);
|
||||
|
||||
uint8_t getSwing();
|
||||
void setSwing(const uint8_t state);
|
||||
|
||||
uint8_t* getRaw();
|
||||
void setRaw(uint8_t new_code[]);
|
||||
static bool validChecksum(uint8_t state[],
|
||||
const uint16_t length = HAIER_AC_STATE_LENGTH);
|
||||
#ifdef ARDUINO
|
||||
String toString();
|
||||
static String timeToString(const uint16_t nr_mins);
|
||||
#else
|
||||
std::string toString();
|
||||
static std::string timeToString(const uint16_t nr_mins);
|
||||
#endif
|
||||
|
||||
private:
|
||||
uint8_t remote_state[HAIER_AC_STATE_LENGTH];
|
||||
void stateReset();
|
||||
void checksum();
|
||||
static uint16_t getTime(const uint8_t ptr[]);
|
||||
static void setTime(uint8_t ptr[], const uint16_t nr_mins);
|
||||
IRsend _irsend;
|
||||
};
|
||||
|
||||
#endif // IR_HAIER_H_
|
||||
@@ -0,0 +1,215 @@
|
||||
// Copyright 2018 David Conran
|
||||
//
|
||||
// Code to emulate Hitachi protocol compatible devices.
|
||||
// Should be compatible with:
|
||||
// * Hitachi RAS-35THA6 remote
|
||||
//
|
||||
|
||||
#include <algorithm>
|
||||
#ifndef ARDUINO
|
||||
#include <string>
|
||||
#endif
|
||||
#include "IRremoteESP8266.h"
|
||||
#include "IRrecv.h"
|
||||
#include "IRsend.h"
|
||||
#include "IRutils.h"
|
||||
|
||||
// HH HH IIIII TTTTTTT AAA CCCCC HH HH IIIII
|
||||
// HH HH III TTT AAAAA CC C HH HH III
|
||||
// HHHHHHH III TTT AA AA CC HHHHHHH III
|
||||
// HH HH III TTT AAAAAAA CC C HH HH III
|
||||
// HH HH IIIII TTT AA AA CCCCC HH HH IIIII
|
||||
|
||||
// Constants
|
||||
// Ref: https://github.com/markszabo/IRremoteESP8266/issues/417
|
||||
#define HITACHI_AC_HDR_MARK 3300U
|
||||
#define HITACHI_AC_HDR_SPACE 1700U
|
||||
#define HITACHI_AC1_HDR_MARK 3400U
|
||||
#define HITACHI_AC1_HDR_SPACE 3400U
|
||||
#define HITACHI_AC_BIT_MARK 400U
|
||||
#define HITACHI_AC_ONE_SPACE 1250U
|
||||
#define HITACHI_AC_ZERO_SPACE 500U
|
||||
#define HITACHI_AC_MIN_GAP 100000U // Completely made up value.
|
||||
|
||||
#if (SEND_HITACHI_AC || SEND_HITACHI_AC2)
|
||||
// Send a Hitachi A/C message.
|
||||
//
|
||||
// Args:
|
||||
// data: An array of bytes containing the IR command.
|
||||
// nbytes: Nr. of bytes of data in the array. (>=HITACHI_AC_STATE_LENGTH)
|
||||
// repeat: Nr. of times the message is to be repeated. (Default = 0).
|
||||
//
|
||||
// Status: ALPHA / Untested.
|
||||
//
|
||||
// Ref:
|
||||
// https://github.com/markszabo/IRremoteESP8266/issues/417
|
||||
void IRsend::sendHitachiAC(unsigned char data[], uint16_t nbytes,
|
||||
uint16_t repeat) {
|
||||
if (nbytes < HITACHI_AC_STATE_LENGTH)
|
||||
return; // Not enough bytes to send a proper message.
|
||||
sendGeneric(HITACHI_AC_HDR_MARK, HITACHI_AC_HDR_SPACE,
|
||||
HITACHI_AC_BIT_MARK, HITACHI_AC_ONE_SPACE,
|
||||
HITACHI_AC_BIT_MARK, HITACHI_AC_ZERO_SPACE,
|
||||
HITACHI_AC_BIT_MARK, HITACHI_AC_MIN_GAP,
|
||||
data, nbytes, 38, true, repeat, 50);
|
||||
}
|
||||
#endif // (SEND_HITACHI_AC || SEND_HITACHI_AC2)
|
||||
|
||||
#if SEND_HITACHI_AC1
|
||||
// Send a Hitachi A/C 13-byte message.
|
||||
//
|
||||
// For devices:
|
||||
// Hitachi A/C Series VI (Circa 2007) / Remote: LT0541-HTA
|
||||
//
|
||||
// Args:
|
||||
// data: An array of bytes containing the IR command.
|
||||
// nbytes: Nr. of bytes of data in the array. (>=HITACHI_AC1_STATE_LENGTH)
|
||||
// repeat: Nr. of times the message is to be repeated. (Default = 0).
|
||||
//
|
||||
// Status: BETA / Appears to work.
|
||||
//
|
||||
// Ref:
|
||||
// https://github.com/markszabo/IRremoteESP8266/issues/453
|
||||
// Basically the same as sendHitatchiAC() except different size and header.
|
||||
void IRsend::sendHitachiAC1(unsigned char data[], uint16_t nbytes,
|
||||
uint16_t repeat) {
|
||||
if (nbytes < HITACHI_AC1_STATE_LENGTH)
|
||||
return; // Not enough bytes to send a proper message.
|
||||
sendGeneric(HITACHI_AC1_HDR_MARK, HITACHI_AC1_HDR_SPACE,
|
||||
HITACHI_AC_BIT_MARK, HITACHI_AC_ONE_SPACE,
|
||||
HITACHI_AC_BIT_MARK, HITACHI_AC_ZERO_SPACE,
|
||||
HITACHI_AC_BIT_MARK, HITACHI_AC_MIN_GAP,
|
||||
data, nbytes, 38, true, repeat, 50);
|
||||
}
|
||||
#endif // SEND_HITACHI_AC1
|
||||
|
||||
#if SEND_HITACHI_AC2
|
||||
// Send a Hitachi A/C 53-byte message.
|
||||
//
|
||||
// For devices:
|
||||
// Hitachi A/C Series VI (Circa 2007) / Remote: LT0541-HTA
|
||||
//
|
||||
// Args:
|
||||
// data: An array of bytes containing the IR command.
|
||||
// nbytes: Nr. of bytes of data in the array. (>=HITACHI_AC2_STATE_LENGTH)
|
||||
// repeat: Nr. of times the message is to be repeated. (Default = 0).
|
||||
//
|
||||
// Status: BETA / Appears to work.
|
||||
//
|
||||
// Ref:
|
||||
// https://github.com/markszabo/IRremoteESP8266/issues/417
|
||||
// Basically the same as sendHitatchiAC() except different size.
|
||||
void IRsend::sendHitachiAC2(unsigned char data[], uint16_t nbytes,
|
||||
uint16_t repeat) {
|
||||
if (nbytes < HITACHI_AC2_STATE_LENGTH)
|
||||
return; // Not enough bytes to send a proper message.
|
||||
sendHitachiAC(data, nbytes, repeat);
|
||||
}
|
||||
#endif // SEND_HITACHI_AC2
|
||||
|
||||
#if (DECODE_HITACHI_AC || DECODE_HITACHI_AC1 || DECODE_HITACHI_AC2)
|
||||
// Decode the supplied Hitachi A/C message.
|
||||
//
|
||||
// Args:
|
||||
// results: Ptr to the data to decode and where to store the decode result.
|
||||
// nbits: The number of data bits to expect.
|
||||
// Typically HITACHI_AC_BITS, HITACHI_AC1_BITS, HITACHI_AC2_BITS
|
||||
// strict: Flag indicating if we should perform strict matching.
|
||||
// Returns:
|
||||
// boolean: True if it can decode it, false if it can't.
|
||||
//
|
||||
// Status: ALPHA / Untested.
|
||||
//
|
||||
// Supported devices:
|
||||
// Hitachi A/C Series VI (Circa 2007) / Remote: LT0541-HTA
|
||||
//
|
||||
// Ref:
|
||||
// https://github.com/markszabo/IRremoteESP8266/issues/417
|
||||
// https://github.com/markszabo/IRremoteESP8266/issues/453
|
||||
bool IRrecv::decodeHitachiAC(decode_results *results, uint16_t nbits,
|
||||
bool strict) {
|
||||
const uint8_t kTolerance = 30;
|
||||
if (results->rawlen < 2 * nbits + HEADER + FOOTER - 1)
|
||||
return false; // Can't possibly be a valid HitachiAC message.
|
||||
if (strict) {
|
||||
switch (nbits) {
|
||||
case HITACHI_AC_BITS:
|
||||
case HITACHI_AC1_BITS:
|
||||
case HITACHI_AC2_BITS:
|
||||
break; // Okay to continue.
|
||||
default:
|
||||
return false; // Not strictly a Hitachi message.
|
||||
}
|
||||
}
|
||||
uint16_t offset = OFFSET_START;
|
||||
uint16_t dataBitsSoFar = 0;
|
||||
match_result_t data_result;
|
||||
|
||||
// Header
|
||||
if (nbits == HITACHI_AC1_BITS) {
|
||||
if (!matchMark(results->rawbuf[offset++], HITACHI_AC1_HDR_MARK, kTolerance))
|
||||
return false;
|
||||
if (!matchSpace(results->rawbuf[offset++], HITACHI_AC1_HDR_SPACE,
|
||||
kTolerance))
|
||||
return false;
|
||||
} else { // Everything else.
|
||||
if (!matchMark(results->rawbuf[offset++], HITACHI_AC_HDR_MARK, kTolerance))
|
||||
return false;
|
||||
if (!matchSpace(results->rawbuf[offset++], HITACHI_AC_HDR_SPACE,
|
||||
kTolerance))
|
||||
return false;
|
||||
}
|
||||
// Data
|
||||
// Keep reading bytes until we either run out of message or state to fill.
|
||||
for (uint16_t i = 0;
|
||||
offset <= results->rawlen - 16 && i < nbits / 8;
|
||||
i++, dataBitsSoFar += 8, offset += data_result.used) {
|
||||
data_result = matchData(&(results->rawbuf[offset]), 8,
|
||||
HITACHI_AC_BIT_MARK,
|
||||
HITACHI_AC_ONE_SPACE,
|
||||
HITACHI_AC_BIT_MARK,
|
||||
HITACHI_AC_ZERO_SPACE,
|
||||
kTolerance);
|
||||
if (data_result.success == false) break; // Fail
|
||||
results->state[i] = (uint8_t) data_result.data;
|
||||
}
|
||||
|
||||
// Footer
|
||||
if (!matchMark(results->rawbuf[offset++], HITACHI_AC_BIT_MARK, kTolerance))
|
||||
return false;
|
||||
if (offset <= results->rawlen &&
|
||||
!matchAtLeast(results->rawbuf[offset], HITACHI_AC_MIN_GAP, kTolerance))
|
||||
return false;
|
||||
|
||||
// Compliance
|
||||
if (strict) {
|
||||
// Re-check we got the correct size/length due to the way we read the data.
|
||||
switch (dataBitsSoFar / 8) {
|
||||
case HITACHI_AC_STATE_LENGTH:
|
||||
case HITACHI_AC1_STATE_LENGTH:
|
||||
case HITACHI_AC2_STATE_LENGTH:
|
||||
break; // Continue
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Success
|
||||
switch (dataBitsSoFar) {
|
||||
case HITACHI_AC1_BITS:
|
||||
results->decode_type = HITACHI_AC1;
|
||||
break;
|
||||
case HITACHI_AC2_BITS:
|
||||
results->decode_type = HITACHI_AC2;
|
||||
break;
|
||||
case HITACHI_AC_BITS:
|
||||
default:
|
||||
results->decode_type = HITACHI_AC;
|
||||
}
|
||||
results->bits = dataBitsSoFar;
|
||||
// No need to record the state as we stored it as we decoded it.
|
||||
// As we use result->state, we don't record value, address, or command as it
|
||||
// is a union data type.
|
||||
return true;
|
||||
}
|
||||
#endif // (DECODE_HITACHI_AC || DECODE_HITACHI_AC1 || DECODE_HITACHI_AC2)
|
||||
@@ -62,13 +62,18 @@ void IRsend::sendJVC(uint64_t data, uint16_t nbits, uint16_t repeat) {
|
||||
|
||||
// We always send the data & footer at least once, hence '<= repeat'.
|
||||
for (uint16_t i = 0; i <= repeat; i++) {
|
||||
// Data
|
||||
sendData(JVC_BIT_MARK, JVC_ONE_SPACE, JVC_BIT_MARK, JVC_ZERO_SPACE,
|
||||
data, nbits, true);
|
||||
// Footer
|
||||
mark(JVC_BIT_MARK);
|
||||
sendGeneric(0, 0, // No Header
|
||||
JVC_BIT_MARK, JVC_ONE_SPACE,
|
||||
JVC_BIT_MARK, JVC_ZERO_SPACE,
|
||||
JVC_BIT_MARK, JVC_MIN_GAP,
|
||||
data, nbits, 38, true, 0, // Repeats are handles elsewhere.
|
||||
33);
|
||||
// Wait till the end of the repeat time window before we send another code.
|
||||
space(std::max(JVC_MIN_GAP, JVC_RPT_LENGTH - usecs.elapsed()));
|
||||
uint32_t elapsed = usecs.elapsed();
|
||||
// Avoid potential unsigned integer underflow.
|
||||
// e.g. when elapsed > JVC_RPT_LENGTH.
|
||||
if (elapsed < JVC_RPT_LENGTH)
|
||||
space(JVC_RPT_LENGTH - elapsed);
|
||||
usecs.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,12 @@
|
||||
|
||||
#include "ir_Kelvinator.h"
|
||||
#include <algorithm>
|
||||
#ifndef ARDUINO
|
||||
#include <string>
|
||||
#endif
|
||||
#include "IRrecv.h"
|
||||
#include "IRsend.h"
|
||||
#include "IRutils.h"
|
||||
|
||||
// KK KK EEEEEEE LL VV VV IIIII NN NN AAA TTTTTTT OOOOO RRRRRR
|
||||
// KK KK EE LL VV VV III NNN NN AAAAA TTT OO OO RR RR
|
||||
@@ -24,13 +30,21 @@
|
||||
// KK KK EEEEEEE LLLLLLL VVV IIIII NN NN AA AA TTT OOOO0 RR RR
|
||||
|
||||
// Constants
|
||||
#define KELVINATOR_HDR_MARK 8990U
|
||||
#define KELVINATOR_HDR_SPACE 4490U
|
||||
#define KELVINATOR_BIT_MARK 675U
|
||||
#define KELVINATOR_ONE_SPACE 1560U
|
||||
#define KELVINATOR_ZERO_SPACE 520U
|
||||
#define KELVINATOR_GAP_SPACE 19950U
|
||||
#define KELVINATOR_TICK 85U
|
||||
#define KELVINATOR_HDR_MARK_TICKS 106U
|
||||
#define KELVINATOR_HDR_MARK (KELVINATOR_HDR_MARK_TICKS * KELVINATOR_TICK)
|
||||
#define KELVINATOR_HDR_SPACE_TICKS 53U
|
||||
#define KELVINATOR_HDR_SPACE (KELVINATOR_HDR_SPACE_TICKS * KELVINATOR_TICK)
|
||||
#define KELVINATOR_BIT_MARK_TICKS 8U
|
||||
#define KELVINATOR_BIT_MARK (KELVINATOR_BIT_MARK_TICKS * KELVINATOR_TICK)
|
||||
#define KELVINATOR_ONE_SPACE_TICKS 18U
|
||||
#define KELVINATOR_ONE_SPACE (KELVINATOR_ONE_SPACE_TICKS * KELVINATOR_TICK)
|
||||
#define KELVINATOR_ZERO_SPACE_TICKS 6U
|
||||
#define KELVINATOR_ZERO_SPACE (KELVINATOR_ZERO_SPACE_TICKS * KELVINATOR_TICK)
|
||||
#define KELVINATOR_GAP_SPACE_TICKS 235U
|
||||
#define KELVINATOR_GAP_SPACE (KELVINATOR_GAP_SPACE_TICKS * KELVINATOR_TICK)
|
||||
#define KELVINATOR_CMD_FOOTER 2U
|
||||
#define KELVINATOR_CMD_FOOTER_BITS 3U
|
||||
|
||||
#define KELVINATOR_POWER 8U
|
||||
#define KELVINATOR_MODE_MASK 0xF8U
|
||||
@@ -69,59 +83,48 @@ void IRsend::sendKelvinator(unsigned char data[], uint16_t nbytes,
|
||||
if (nbytes < KELVINATOR_STATE_LENGTH)
|
||||
return; // Not enough bytes to send a proper message.
|
||||
|
||||
// Set IR carrier frequency
|
||||
enableIROut(38);
|
||||
|
||||
for (uint16_t r = 0; r <= repeat; r++) {
|
||||
// Header #1
|
||||
mark(KELVINATOR_HDR_MARK);
|
||||
space(KELVINATOR_HDR_SPACE);
|
||||
// Data (command)
|
||||
// Send the first command data (4 bytes)
|
||||
uint8_t i;
|
||||
for (i = 0; i < 4; i++)
|
||||
sendData(KELVINATOR_BIT_MARK, KELVINATOR_ONE_SPACE, KELVINATOR_BIT_MARK,
|
||||
KELVINATOR_ZERO_SPACE, data[i], 8, false);
|
||||
// Send Footer for the command data (3 bits (0b010))
|
||||
sendData(KELVINATOR_BIT_MARK, KELVINATOR_ONE_SPACE, KELVINATOR_BIT_MARK,
|
||||
KELVINATOR_ZERO_SPACE, KELVINATOR_CMD_FOOTER, 3, false);
|
||||
// Send an interdata gap.
|
||||
mark(KELVINATOR_BIT_MARK);
|
||||
space(KELVINATOR_GAP_SPACE);
|
||||
// Data (options)
|
||||
// Send the 1st option chunk of data (4 bytes).
|
||||
for (; i < 8; i++)
|
||||
sendData(KELVINATOR_BIT_MARK, KELVINATOR_ONE_SPACE, KELVINATOR_BIT_MARK,
|
||||
KELVINATOR_ZERO_SPACE, data[i], 8, false);
|
||||
// Send a double data gap to signify we are starting a new command sequence.
|
||||
mark(KELVINATOR_BIT_MARK);
|
||||
space(KELVINATOR_GAP_SPACE * 2);
|
||||
// Header #2
|
||||
mark(KELVINATOR_HDR_MARK);
|
||||
space(KELVINATOR_HDR_SPACE);
|
||||
// Data (command)
|
||||
// Send the 2nd command data (4 bytes).
|
||||
// Basically an almost identical repeat of the earlier command data.
|
||||
for (; i < 12; i++)
|
||||
sendData(KELVINATOR_BIT_MARK, KELVINATOR_ONE_SPACE, KELVINATOR_BIT_MARK,
|
||||
KELVINATOR_ZERO_SPACE, data[i], 8, false);
|
||||
// Send Footer for the command data (3 bits (B010))
|
||||
sendData(KELVINATOR_BIT_MARK, KELVINATOR_ONE_SPACE, KELVINATOR_BIT_MARK,
|
||||
KELVINATOR_ZERO_SPACE, KELVINATOR_CMD_FOOTER, 3, false);
|
||||
// Send an interdata gap.
|
||||
mark(KELVINATOR_BIT_MARK);
|
||||
space(KELVINATOR_GAP_SPACE);
|
||||
// Data (options)
|
||||
// Send the 2nd option chunk of data (4 bytes).
|
||||
// Unlike the commands, definitely not a repeat of the earlier option data.
|
||||
for (; i < KELVINATOR_STATE_LENGTH; i++)
|
||||
sendData(KELVINATOR_BIT_MARK, KELVINATOR_ONE_SPACE, KELVINATOR_BIT_MARK,
|
||||
KELVINATOR_ZERO_SPACE, data[i], 8, false);
|
||||
// Footer
|
||||
mark(KELVINATOR_BIT_MARK);
|
||||
space(KELVINATOR_GAP_SPACE * 2);
|
||||
// Command Block #1 (4 bytes)
|
||||
sendGeneric(KELVINATOR_HDR_MARK, KELVINATOR_HDR_SPACE,
|
||||
KELVINATOR_BIT_MARK, KELVINATOR_ONE_SPACE,
|
||||
KELVINATOR_BIT_MARK, KELVINATOR_ZERO_SPACE,
|
||||
0, 0, // No Footer yet.
|
||||
data, 4, 38, false, 0, 50);
|
||||
// Send Footer for the command block (3 bits (B010))
|
||||
sendGeneric(0, 0, // No Header
|
||||
KELVINATOR_BIT_MARK, KELVINATOR_ONE_SPACE,
|
||||
KELVINATOR_BIT_MARK, KELVINATOR_ZERO_SPACE,
|
||||
KELVINATOR_BIT_MARK, KELVINATOR_GAP_SPACE,
|
||||
KELVINATOR_CMD_FOOTER, KELVINATOR_CMD_FOOTER_BITS,
|
||||
38, false, 0, 50);
|
||||
// Data Block #1 (4 bytes)
|
||||
sendGeneric(0, 0, // No header
|
||||
KELVINATOR_BIT_MARK, KELVINATOR_ONE_SPACE,
|
||||
KELVINATOR_BIT_MARK, KELVINATOR_ZERO_SPACE,
|
||||
KELVINATOR_BIT_MARK, KELVINATOR_GAP_SPACE * 2,
|
||||
data + 4, 4, 38, false, 0, 50);
|
||||
// Command Block #2 (4 bytes)
|
||||
sendGeneric(KELVINATOR_HDR_MARK, KELVINATOR_HDR_SPACE,
|
||||
KELVINATOR_BIT_MARK, KELVINATOR_ONE_SPACE,
|
||||
KELVINATOR_BIT_MARK, KELVINATOR_ZERO_SPACE,
|
||||
0, 0, // No Footer yet.
|
||||
data + 8, 4, 38, false, 0, 50);
|
||||
// Send Footer for the command block (3 bits (B010))
|
||||
sendGeneric(0, 0, // No Header
|
||||
KELVINATOR_BIT_MARK, KELVINATOR_ONE_SPACE,
|
||||
KELVINATOR_BIT_MARK, KELVINATOR_ZERO_SPACE,
|
||||
KELVINATOR_BIT_MARK, KELVINATOR_GAP_SPACE,
|
||||
KELVINATOR_CMD_FOOTER, KELVINATOR_CMD_FOOTER_BITS,
|
||||
38, false, 0, 50);
|
||||
// Data Block #2 (4 bytes)
|
||||
sendGeneric(0, 0, // No header
|
||||
KELVINATOR_BIT_MARK, KELVINATOR_ONE_SPACE,
|
||||
KELVINATOR_BIT_MARK, KELVINATOR_ZERO_SPACE,
|
||||
KELVINATOR_BIT_MARK, KELVINATOR_GAP_SPACE * 2,
|
||||
data + 12, 4, 38, false, 0, 50);
|
||||
}
|
||||
}
|
||||
#endif // SEND_KELVINATOR
|
||||
|
||||
IRKelvinatorAC::IRKelvinatorAC(uint16_t pin) : _irsend(pin) {
|
||||
stateReset();
|
||||
@@ -145,34 +148,62 @@ void IRKelvinatorAC::fixup() {
|
||||
checksum(); // Calculate the checksums
|
||||
}
|
||||
|
||||
#if SEND_KELVINATOR
|
||||
void IRKelvinatorAC::send() {
|
||||
fixup(); // Ensure correct settings before sending.
|
||||
_irsend.sendKelvinator(remote_state);
|
||||
}
|
||||
#endif // SEND_KELVINATOR
|
||||
|
||||
uint8_t* IRKelvinatorAC::getRaw() {
|
||||
fixup(); // Ensure correct settings before sending.
|
||||
return remote_state;
|
||||
}
|
||||
|
||||
// Many Bothans died to bring us this information.
|
||||
void IRKelvinatorAC::checksum() {
|
||||
// For each command + options block.
|
||||
for (uint8_t offset = 0; offset < KELVINATOR_STATE_LENGTH; offset += 8) {
|
||||
uint8_t sum = KELVINATOR_CHECKSUM_START;
|
||||
// Sum the lower half of the first 4 bytes of this block.
|
||||
for (uint8_t i = 0; i < 4; i++)
|
||||
sum += (remote_state[i + offset] & 0xFU);
|
||||
void IRKelvinatorAC::setRaw(uint8_t new_code[]) {
|
||||
for (uint8_t i = 0; i < KELVINATOR_STATE_LENGTH; i++) {
|
||||
remote_state[i] = new_code[i];
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t IRKelvinatorAC::calcBlockChecksum(const uint8_t *block,
|
||||
const uint16_t length) {
|
||||
uint8_t sum = KELVINATOR_CHECKSUM_START;
|
||||
// Sum the lower half of the first 4 bytes of this block.
|
||||
for (uint8_t i = 0; i < 4 && i < length - 1; i++, block++)
|
||||
sum += (*block & 0x0FU);
|
||||
// then sum the upper half of the next 3 bytes.
|
||||
for (uint8_t i = 4; i < 7; i++)
|
||||
sum += (remote_state[i + offset] >> 4);
|
||||
// Trim it down to fit into the 4 bits allowed. i.e. Mod 16.
|
||||
sum &= 0xFU;
|
||||
// Place it into the IR code in the top half of the 8th & 16th byte.
|
||||
for (uint8_t i = 4; i < length - 1; i++, block++)
|
||||
sum += (*block >> 4);
|
||||
// Trim it down to fit into the 4 bits allowed. i.e. Mod 16.
|
||||
return sum & 0x0FU;
|
||||
}
|
||||
|
||||
// Many Bothans died to bring us this information.
|
||||
void IRKelvinatorAC::checksum(const uint16_t length) {
|
||||
// For each command + options block.
|
||||
for (uint16_t offset = 0; offset + 7 < length; offset += 8) {
|
||||
uint8_t sum = calcBlockChecksum(remote_state + offset);
|
||||
remote_state[7 + offset] = (sum << 4) | (remote_state[7 + offset] & 0xFU);
|
||||
}
|
||||
}
|
||||
|
||||
// Verify the checksum is valid for a given state.
|
||||
// Args:
|
||||
// state: The array to verify the checksum of.
|
||||
// length: The size of the state.
|
||||
// Returns:
|
||||
// A boolean.
|
||||
bool IRKelvinatorAC::validChecksum(const uint8_t state[],
|
||||
const uint16_t length) {
|
||||
for (uint16_t offset = 0; offset + 7 < length; offset += 8) {
|
||||
// Top 4 bits of the last byte in the block is the block's checksum.
|
||||
if (state[offset + 7] >> 4 != calcBlockChecksum(state + offset))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void IRKelvinatorAC::on() {
|
||||
remote_state[0] |= KELVINATOR_POWER;
|
||||
remote_state[8] = remote_state[0]; // Duplicate to the 2nd command chunk.
|
||||
@@ -326,4 +357,206 @@ void IRKelvinatorAC::setTurbo(bool state) {
|
||||
bool IRKelvinatorAC::getTurbo() {
|
||||
return ((remote_state[2] & KELVINATOR_TURBO) != 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Convert the internal state into a human readable string.
|
||||
#ifdef ARDUINO
|
||||
String IRKelvinatorAC::toString() {
|
||||
String result = "";
|
||||
#else
|
||||
std::string IRKelvinatorAC::toString() {
|
||||
std::string result = "";
|
||||
#endif // ARDUINO
|
||||
result += "Power: ";
|
||||
if (getPower())
|
||||
result += "On";
|
||||
else
|
||||
result += "Off";
|
||||
result += ", Mode: " + uint64ToString(getMode());
|
||||
switch (getMode()) {
|
||||
case KELVINATOR_AUTO:
|
||||
result += " (AUTO)";
|
||||
break;
|
||||
case KELVINATOR_COOL:
|
||||
result += " (COOL)";
|
||||
break;
|
||||
case KELVINATOR_HEAT:
|
||||
result += " (HEAT)";
|
||||
break;
|
||||
case KELVINATOR_DRY:
|
||||
result += " (DRY)";
|
||||
break;
|
||||
case KELVINATOR_FAN:
|
||||
result += " (FAN)";
|
||||
break;
|
||||
default:
|
||||
result += " (UNKNOWN)";
|
||||
}
|
||||
result += ", Temp: " + uint64ToString(getTemp()) + "C";
|
||||
result += ", Fan: " + uint64ToString(getFan());
|
||||
switch (getFan()) {
|
||||
case KELVINATOR_FAN_AUTO:
|
||||
result += " (AUTO)";
|
||||
break;
|
||||
case KELVINATOR_FAN_MAX:
|
||||
result += " (MAX)";
|
||||
break;
|
||||
}
|
||||
result += ", Turbo: ";
|
||||
if (getTurbo())
|
||||
result += "On";
|
||||
else
|
||||
result += "Off";
|
||||
result += ", Quiet: ";
|
||||
if (getQuiet())
|
||||
result += "On";
|
||||
else
|
||||
result += "Off";
|
||||
result += ", XFan: ";
|
||||
if (getXFan())
|
||||
result += "On";
|
||||
else
|
||||
result += "Off";
|
||||
result += ", IonFilter: ";
|
||||
if (getIonFilter())
|
||||
result += "On";
|
||||
else
|
||||
result += "Off";
|
||||
result += ", Light: ";
|
||||
if (getLight())
|
||||
result += "On";
|
||||
else
|
||||
result += "Off";
|
||||
result += ", Swing (Horizontal): ";
|
||||
if (getSwingHorizontal())
|
||||
result += "On";
|
||||
else
|
||||
result += "Off";
|
||||
result += ", Swing (Vertical): ";
|
||||
if (getSwingVertical())
|
||||
result += "On";
|
||||
else
|
||||
result += "Off";
|
||||
return result;
|
||||
}
|
||||
|
||||
#if DECODE_KELVINATOR
|
||||
// Decode the supplied Kelvinator message.
|
||||
//
|
||||
// Args:
|
||||
// results: Ptr to the data to decode and where to store the decode result.
|
||||
// nbits: The number of data bits to expect. Typically KELVINATOR_BITS.
|
||||
// strict: Flag indicating if we should perform strict matching.
|
||||
// Returns:
|
||||
// boolean: True if it can decode it, false if it can't.
|
||||
//
|
||||
// Status: ALPHA / Untested.
|
||||
bool IRrecv::decodeKelvinator(decode_results *results, uint16_t nbits,
|
||||
bool strict) {
|
||||
if (results->rawlen < 2 * (nbits + KELVINATOR_CMD_FOOTER_BITS) +
|
||||
(HEADER + FOOTER + 1) * 2 - 1)
|
||||
return false; // Can't possibly be a valid Kelvinator message.
|
||||
if (strict && nbits != KELVINATOR_BITS)
|
||||
return false; // Not strictly a Kelvinator message.
|
||||
|
||||
uint32_t data;
|
||||
uint16_t offset = OFFSET_START;
|
||||
|
||||
// There are two messages back-to-back in a full Kelvinator IR message
|
||||
// sequence.
|
||||
int8_t state_pos = 0;
|
||||
for (uint8_t s = 0; s < 2; s++) {
|
||||
match_result_t data_result;
|
||||
|
||||
// Header
|
||||
if (!matchMark(results->rawbuf[offset], KELVINATOR_HDR_MARK)) return false;
|
||||
// Calculate how long the lowest tick time is based on the header mark.
|
||||
uint32_t mark_tick = results->rawbuf[offset++] * RAWTICK /
|
||||
KELVINATOR_HDR_MARK_TICKS;
|
||||
if (!matchSpace(results->rawbuf[offset], KELVINATOR_HDR_SPACE))
|
||||
return false;
|
||||
// Calculate how long the common tick time is based on the header space.
|
||||
uint32_t space_tick = results->rawbuf[offset++] * RAWTICK /
|
||||
KELVINATOR_HDR_SPACE_TICKS;
|
||||
|
||||
// Data (Command) (32 bits)
|
||||
data_result = matchData(&(results->rawbuf[offset]), 32,
|
||||
KELVINATOR_BIT_MARK_TICKS * mark_tick,
|
||||
KELVINATOR_ONE_SPACE_TICKS * space_tick,
|
||||
KELVINATOR_BIT_MARK_TICKS * mark_tick,
|
||||
KELVINATOR_ZERO_SPACE_TICKS * space_tick);
|
||||
if (data_result.success == false) return false;
|
||||
data = data_result.data;
|
||||
offset += data_result.used;
|
||||
|
||||
// Record command data in the state.
|
||||
for (int i = state_pos + 3; i >= state_pos; i--, data >>= 8)
|
||||
results->state[i] = reverseBits(data & 0xFF, 8);
|
||||
state_pos += 4;
|
||||
|
||||
// Command data footer (3 bits, B010)
|
||||
data_result = matchData(&(results->rawbuf[offset]),
|
||||
KELVINATOR_CMD_FOOTER_BITS,
|
||||
KELVINATOR_BIT_MARK_TICKS * mark_tick,
|
||||
KELVINATOR_ONE_SPACE_TICKS * space_tick,
|
||||
KELVINATOR_BIT_MARK_TICKS * mark_tick,
|
||||
KELVINATOR_ZERO_SPACE_TICKS * space_tick);
|
||||
if (data_result.success == false) return false;
|
||||
if (data_result.data != KELVINATOR_CMD_FOOTER) return false;
|
||||
offset += data_result.used;
|
||||
|
||||
// Interdata gap.
|
||||
if (!matchMark(results->rawbuf[offset++],
|
||||
KELVINATOR_BIT_MARK_TICKS * mark_tick))
|
||||
return false;
|
||||
if (!matchSpace(results->rawbuf[offset++],
|
||||
KELVINATOR_GAP_SPACE_TICKS * space_tick))
|
||||
return false;
|
||||
|
||||
// Data (Options) (32 bits)
|
||||
data_result = matchData(&(results->rawbuf[offset]), 32,
|
||||
KELVINATOR_BIT_MARK_TICKS * mark_tick,
|
||||
KELVINATOR_ONE_SPACE_TICKS * space_tick,
|
||||
KELVINATOR_BIT_MARK_TICKS * mark_tick,
|
||||
KELVINATOR_ZERO_SPACE_TICKS * space_tick);
|
||||
if (data_result.success == false) return false;
|
||||
data = data_result.data;
|
||||
offset += data_result.used;
|
||||
|
||||
// Record option data in the state.
|
||||
for (int i = state_pos + 3; i >= state_pos; i--, data >>= 8)
|
||||
results->state[i] = reverseBits(data & 0xFF, 8);
|
||||
state_pos += 4;
|
||||
|
||||
// Inter-sequence gap. (Double length gap)
|
||||
if (!matchMark(results->rawbuf[offset++],
|
||||
KELVINATOR_BIT_MARK_TICKS * mark_tick))
|
||||
return false;
|
||||
if (s == 0) {
|
||||
if (!matchSpace(results->rawbuf[offset++],
|
||||
KELVINATOR_GAP_SPACE_TICKS * space_tick * 2))
|
||||
return false;
|
||||
} else {
|
||||
if (offset <= results->rawlen &&
|
||||
!matchAtLeast(results->rawbuf[offset],
|
||||
KELVINATOR_GAP_SPACE_TICKS * 2 * space_tick))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Compliance
|
||||
if (strict) {
|
||||
// Correct size/length)
|
||||
if (state_pos != KELVINATOR_STATE_LENGTH) return false;
|
||||
// Verify the message's checksum is correct.
|
||||
if (!IRKelvinatorAC::validChecksum(results->state)) return false;
|
||||
}
|
||||
|
||||
// Success
|
||||
results->decode_type = KELVINATOR;
|
||||
results->bits = state_pos * 8;
|
||||
// No need to record the state as we stored it as we decoded it.
|
||||
// As we use result->state, we don't record value, address, or command as it
|
||||
// is a union data type.
|
||||
return true;
|
||||
}
|
||||
#endif // DECODE_KELVINATOR
|
||||
@@ -7,6 +7,11 @@
|
||||
|
||||
#define __STDC_LIMIT_MACROS
|
||||
#include <stdint.h>
|
||||
#ifndef UNIT_TEST
|
||||
#include <Arduino.h>
|
||||
#else
|
||||
#include <string>
|
||||
#endif
|
||||
#include "IRremoteESP8266.h"
|
||||
#include "IRsend.h"
|
||||
|
||||
@@ -23,6 +28,7 @@
|
||||
#define KELVINATOR_FAN 3U
|
||||
#define KELVINATOR_HEAT 4U
|
||||
#define KELVINATOR_BASIC_FAN_MAX 3U
|
||||
#define KELVINATOR_FAN_AUTO 0U
|
||||
#define KELVINATOR_FAN_MAX 5U
|
||||
#define KELVINATOR_MIN_TEMP 16U // 16C
|
||||
#define KELVINATOR_MAX_TEMP 30U // 30C
|
||||
@@ -104,15 +110,15 @@
|
||||
b7-4 = checksum of the previous bytes (8-14)
|
||||
*/
|
||||
|
||||
#if SEND_KELVINATOR
|
||||
|
||||
// Classes
|
||||
class IRKelvinatorAC {
|
||||
public:
|
||||
explicit IRKelvinatorAC(uint16_t pin);
|
||||
|
||||
void stateReset();
|
||||
#if SEND_KELVINATOR
|
||||
void send();
|
||||
#endif // SEND_KELVINATOR
|
||||
void begin();
|
||||
void on();
|
||||
void off();
|
||||
@@ -139,14 +145,24 @@ class IRKelvinatorAC {
|
||||
void setTurbo(bool state);
|
||||
bool getTurbo();
|
||||
uint8_t* getRaw();
|
||||
void setRaw(uint8_t new_code[]);
|
||||
static uint8_t calcBlockChecksum(
|
||||
const uint8_t *block,
|
||||
const uint16_t length = KELVINATOR_STATE_LENGTH / 2);
|
||||
static bool validChecksum(const uint8_t state[],
|
||||
const uint16_t length = KELVINATOR_STATE_LENGTH);
|
||||
#ifdef ARDUINO
|
||||
String toString();
|
||||
#else
|
||||
std::string toString();
|
||||
#endif
|
||||
|
||||
private:
|
||||
// The state of the IR remote in IR code form.
|
||||
uint8_t remote_state[KELVINATOR_STATE_LENGTH];
|
||||
void checksum();
|
||||
void checksum(const uint16_t length = KELVINATOR_STATE_LENGTH);
|
||||
void fixup();
|
||||
IRsend _irsend;
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif // IR_KELVINATOR_H_
|
||||
@@ -6,7 +6,6 @@
|
||||
#include <algorithm>
|
||||
#include "IRrecv.h"
|
||||
#include "IRsend.h"
|
||||
#include "IRtimer.h"
|
||||
#include "IRutils.h"
|
||||
|
||||
// L GGGG
|
||||
@@ -69,11 +68,7 @@ uint8_t calcLGChecksum(uint16_t data) {
|
||||
// Notes:
|
||||
// LG has a separate message to indicate a repeat, like NEC does.
|
||||
void IRsend::sendLG(uint64_t data, uint16_t nbits, uint16_t repeat) {
|
||||
// Set IR carrier frequency
|
||||
enableIROut(38);
|
||||
|
||||
uint16_t repeatHeaderMark = 0;
|
||||
IRtimer usecTimer = IRtimer();
|
||||
|
||||
if (nbits >= LG32_BITS) {
|
||||
// LG 32bit protocol is near identical to Samsung except for repeats.
|
||||
@@ -83,29 +78,23 @@ void IRsend::sendLG(uint64_t data, uint16_t nbits, uint16_t repeat) {
|
||||
} else {
|
||||
// LG (28-bit) protocol.
|
||||
repeatHeaderMark = LG_HDR_MARK;
|
||||
// Header
|
||||
usecTimer.reset();
|
||||
mark(LG_HDR_MARK);
|
||||
space(LG_HDR_SPACE);
|
||||
// Data
|
||||
sendData(LG_BIT_MARK, LG_ONE_SPACE, LG_BIT_MARK, LG_ZERO_SPACE,
|
||||
data, nbits, true);
|
||||
// Footer
|
||||
mark(LG_BIT_MARK);
|
||||
space(std::max((uint32_t) (LG_MIN_MESSAGE_LENGTH - usecTimer.elapsed()),
|
||||
(uint32_t) LG_MIN_GAP));
|
||||
sendGeneric(LG_HDR_MARK, LG_HDR_SPACE,
|
||||
LG_BIT_MARK, LG_ONE_SPACE,
|
||||
LG_BIT_MARK, LG_ZERO_SPACE,
|
||||
LG_BIT_MARK,
|
||||
LG_MIN_GAP, LG_MIN_MESSAGE_LENGTH,
|
||||
data, nbits, 38, true, 0, // Repeats are handled later.
|
||||
50);
|
||||
}
|
||||
|
||||
// Repeat
|
||||
// Protocol has a mandatory repeat-specific code sent after every command.
|
||||
for (uint16_t i = 0; i < repeat; i++) {
|
||||
usecTimer.reset();
|
||||
mark(repeatHeaderMark);
|
||||
space(LG_RPT_SPACE);
|
||||
mark(LG_BIT_MARK);
|
||||
space(std::max((uint32_t) LG_MIN_MESSAGE_LENGTH - usecTimer.elapsed(),
|
||||
(uint32_t) LG_MIN_GAP));
|
||||
}
|
||||
if (repeat)
|
||||
sendGeneric(repeatHeaderMark, LG_RPT_SPACE,
|
||||
0, 0, 0, 0, // No data is sent.
|
||||
LG_BIT_MARK, LG_MIN_GAP, LG_MIN_MESSAGE_LENGTH,
|
||||
0, 0, // No data.
|
||||
38, true, repeat - 1, 50);
|
||||
}
|
||||
|
||||
// Construct a raw 28-bit LG message from the supplied address & command.
|
||||
@@ -150,8 +139,13 @@ uint32_t IRsend::encodeLG(uint16_t address, uint16_t command) {
|
||||
// Ref:
|
||||
// https://funembedded.wordpress.com/2014/11/08/ir-remote-control-for-lg-conditioner-using-stm32f302-mcu-on-mbed-platform/
|
||||
bool IRrecv::decodeLG(decode_results *results, uint16_t nbits, bool strict) {
|
||||
if (results->rawlen < 2 * nbits + HEADER + FOOTER - 1 && results->rawlen != 4)
|
||||
return false; // Can't possibly be a valid LG message.
|
||||
if (nbits >= LG32_BITS) {
|
||||
if (results->rawlen < 2 * nbits + 2 * (HEADER + FOOTER) - 1)
|
||||
return false; // Can't possibly be a valid LG32 message.
|
||||
} else {
|
||||
if (results->rawlen < 2 * nbits + HEADER + FOOTER - 1)
|
||||
return false; // Can't possibly be a valid LG message.
|
||||
}
|
||||
if (strict && nbits != LG_BITS && nbits != LG32_BITS)
|
||||
return false; // Doesn't comply with expected LG protocol.
|
||||
|
||||
@@ -195,15 +189,6 @@ bool IRrecv::decodeLG(decode_results *results, uint16_t nbits, bool strict) {
|
||||
if (nbits >= LG32_BITS) {
|
||||
// If we are expecting the LG 32-bit protocol, there is always
|
||||
// a repeat message. So, check for it.
|
||||
#ifndef UNIT_TEST
|
||||
if (!matchSpace(results->rawbuf[offset], LG_MIN_GAP_TICKS * s_tick))
|
||||
#else
|
||||
if (!(matchSpace(results->rawbuf[offset],
|
||||
LG_MIN_MESSAGE_LENGTH_TICKS * s_tick) ||
|
||||
matchSpace(results->rawbuf[offset], 65500) ||
|
||||
matchSpace(results->rawbuf[offset], LG_MIN_GAP_TICKS * s_tick)))
|
||||
#endif // UNIT_TEST
|
||||
return false;
|
||||
offset++;
|
||||
if (!matchMark(results->rawbuf[offset++], LG32_RPT_HDR_MARK_TICKS * m_tick))
|
||||
return false;
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
// Copyright 2017 David Conran
|
||||
|
||||
#include <algorithm>
|
||||
#include "IRrecv.h"
|
||||
#include "IRsend.h"
|
||||
#include "IRutils.h"
|
||||
|
||||
// LL AAA SSSSS EEEEEEE RRRRRR TTTTTTT AAA GGGG
|
||||
// LL AAAAA SS EE RR RR TTT AAAAA GG GG
|
||||
// LL AA AA SSSSS EEEEE RRRRRR TTT AA AA GG
|
||||
// LL AAAAAAA SS EE RR RR TTT AAAAAAA GG GG
|
||||
// LLLLLLL AA AA SSSSS EEEEEEE RR RR TTT AA AA GGGGGG
|
||||
|
||||
// Constants
|
||||
#define MIN_LASERTAG_SAMPLES 13U
|
||||
#define LASERTAG_TICK 333U
|
||||
#define LASERTAG_MIN_GAP 100000UL // Completely made up amount.
|
||||
#define LASERTAG_TOLERANCE 0U // Percentage error margin
|
||||
#define LASERTAG_EXCESS 0U // See MARK_EXCESS
|
||||
#define LASERTAG_DELTA 150U // Use instead of EXCESS and TOLERANCE.
|
||||
const int16_t kSPACE = 1;
|
||||
const int16_t kMARK = 0;
|
||||
|
||||
#if SEND_LASERTAG
|
||||
// Send a Lasertag packet.
|
||||
// This protocol is pretty much just raw Manchester encoding.
|
||||
//
|
||||
// Args:
|
||||
// data: The message you wish to send.
|
||||
// nbits: Bit size of the protocol you want to send.
|
||||
// repeat: Nr. of extra times the data will be sent.
|
||||
//
|
||||
// Status: STABLE / Working.
|
||||
//
|
||||
|
||||
void IRsend::sendLasertag(uint64_t data, uint16_t nbits, uint16_t repeat) {
|
||||
if (nbits > sizeof(data) * 8)
|
||||
return; // We can't send something that big.
|
||||
|
||||
// Set 36kHz IR carrier frequency & a 1/4 (25%) duty cycle.
|
||||
// NOTE: duty cycle is not confirmed. Just guessing based on RC5/6 protocols.
|
||||
enableIROut(36, 25);
|
||||
|
||||
for (uint16_t i = 0; i <= repeat; i++) {
|
||||
// Data
|
||||
for (uint64_t mask = 1ULL << (nbits - 1); mask; mask >>= 1)
|
||||
if (data & mask) { // 1
|
||||
space(LASERTAG_TICK); // 1 is space, then mark.
|
||||
mark(LASERTAG_TICK);
|
||||
} else { // 0
|
||||
mark(LASERTAG_TICK); // 0 is mark, then space.
|
||||
space(LASERTAG_TICK);
|
||||
}
|
||||
// Footer
|
||||
space(LASERTAG_MIN_GAP);
|
||||
}
|
||||
}
|
||||
#endif // SEND_LASERTAG
|
||||
|
||||
#if DECODE_LASERTAG
|
||||
// Decode the supplied Lasertag message.
|
||||
// This protocol is pretty much just raw Manchester encoding.
|
||||
//
|
||||
// Args:
|
||||
// results: Ptr to the data to decode and where to store the decode result.
|
||||
// nbits: The number of data bits to expect.
|
||||
// strict: Flag indicating if we should perform strict matching.
|
||||
// Returns:
|
||||
// boolean: True if it can decode it, false if it can't.
|
||||
//
|
||||
// Status: BETA / Appears to be working 90% of the time.
|
||||
//
|
||||
// Ref:
|
||||
// http://www.sbprojects.com/knowledge/ir/rc5.php
|
||||
// https://en.wikipedia.org/wiki/RC-5
|
||||
// https://en.wikipedia.org/wiki/Manchester_code
|
||||
bool IRrecv::decodeLasertag(decode_results *results, uint16_t nbits,
|
||||
bool strict) {
|
||||
if (results->rawlen < MIN_LASERTAG_SAMPLES) return false;
|
||||
|
||||
// Compliance
|
||||
if (strict && nbits != LASERTAG_BITS) return false;
|
||||
|
||||
uint16_t offset = OFFSET_START;
|
||||
uint16_t used = 0;
|
||||
uint64_t data = 0;
|
||||
uint16_t actual_bits = 0;
|
||||
|
||||
// No Header
|
||||
|
||||
// Data
|
||||
for (; offset <= results->rawlen; actual_bits++) {
|
||||
int16_t levelA = getRClevel(results, &offset, &used, LASERTAG_TICK,
|
||||
LASERTAG_TOLERANCE, LASERTAG_EXCESS,
|
||||
LASERTAG_DELTA);
|
||||
int16_t levelB = getRClevel(results, &offset, &used, LASERTAG_TICK,
|
||||
LASERTAG_TOLERANCE, LASERTAG_EXCESS,
|
||||
LASERTAG_DELTA);
|
||||
if (levelA == kSPACE && levelB == kMARK) {
|
||||
data = (data << 1) | 1; // 1
|
||||
} else {
|
||||
if (levelA == kMARK && levelB == kSPACE) {
|
||||
data <<= 1; // 0
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Footer (None)
|
||||
|
||||
// Compliance
|
||||
if (actual_bits < nbits) return false; // Less data than we expected.
|
||||
if (strict && actual_bits != LASERTAG_BITS) return false;
|
||||
|
||||
// Success
|
||||
results->decode_type = LASERTAG;
|
||||
results->value = data;
|
||||
results->address = data & 0xF; // Unit
|
||||
results->command = data >> 4; // Team
|
||||
results->repeat = false;
|
||||
results->bits = actual_bits;
|
||||
return true;
|
||||
}
|
||||
#endif // DECODE_LASERTAG
|
||||
@@ -0,0 +1,160 @@
|
||||
// Copyright 2013 mpflaga
|
||||
// Copyright 2015 kitlaan
|
||||
// Copyright 2017 Jason kendall, David Conran
|
||||
|
||||
#include "ir_Magiquest.h"
|
||||
#include <algorithm>
|
||||
#include "IRrecv.h"
|
||||
#include "IRsend.h"
|
||||
#include "IRutils.h"
|
||||
|
||||
#define IS_ZERO(m, s) (((m) * 100 / ((m) + (s))) <= MAGIQUEST_ZERO_RATIO)
|
||||
#define IS_ONE(m, s) (((m) * 100 / ((m) + (s))) >= MAGIQUEST_ONE_RATIO)
|
||||
|
||||
// Strips taken from:
|
||||
// https://github.com/kitlaan/Arduino-IRremote/blob/master/ir_Magiquest.cpp
|
||||
// and
|
||||
// https://github.com/mpflaga/Arduino-IRremote
|
||||
|
||||
// Source: https://github.com/mpflaga/Arduino-IRremote
|
||||
|
||||
#if SEND_MAGIQUEST
|
||||
// Send a MagiQuest formatted message.
|
||||
//
|
||||
// Args:
|
||||
// data: The contents of the message you want to send.
|
||||
// nbits: The bit size of the message being sent.
|
||||
// Typically MAGIQUEST_BITS.
|
||||
// repeat: The number of times you want the message to be repeated.
|
||||
//
|
||||
// Status: Alpha / Should be working.
|
||||
//
|
||||
void IRsend::sendMagiQuest(uint64_t data, uint16_t nbits, uint16_t repeat) {
|
||||
sendGeneric(0, 0, // No Headers - Technically it's included in the data.
|
||||
// i.e. 8 zeros.
|
||||
MAGIQUEST_MARK_ONE, MAGIQUEST_SPACE_ONE,
|
||||
MAGIQUEST_MARK_ZERO, MAGIQUEST_SPACE_ZERO,
|
||||
0, // No footer mark.
|
||||
MAGIQUEST_GAP,
|
||||
data, nbits, 36, true, repeat, 50);
|
||||
}
|
||||
|
||||
// Encode a MagiQuest wand_id, and a magnitude into a single 64bit value.
|
||||
// (Only 48 bits of real data + 8 leading zero bits)
|
||||
// This is suitable for calling sendMagiQuest() with.
|
||||
// e.g. sendMagiQuest(encodeMagiQuest(wand_id, magnitude));
|
||||
uint64_t IRsend::encodeMagiQuest(uint32_t wand_id, uint16_t magnitude) {
|
||||
uint64_t result = 0;
|
||||
result = wand_id;
|
||||
result <<= 16;
|
||||
result |= magnitude;
|
||||
// Shouldn't be needed, but ensure top 8/16 bit are zero.
|
||||
result &= 0xFFFFFFFFFFFFULL;
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Source: https://github.com/kitlaan/Arduino-IRremote/blob/master/ir_Magiquest.cpp
|
||||
|
||||
#if DECODE_MAGIQUEST
|
||||
// Decode the supplied MagiQuest message.
|
||||
// MagiQuest protocol appears to be a header of 8 'zero' bits, followed
|
||||
// by 32 bits of "wand ID" and finally 16 bits of "magnitude".
|
||||
// Even though we describe this protocol as 56 bits, it really only has
|
||||
// 48 bits of data that matter.
|
||||
//
|
||||
// In transmission order, 8 zeros + 32 wand_id + 16 magnitude.
|
||||
//
|
||||
// Args:
|
||||
// results: Ptr to the data to decode and where to store the decode result.
|
||||
// nbits: Nr. of bits to expect in the data portion, inc. the 8 bit header.
|
||||
// Typically MAGIQUEST_BITS.
|
||||
// strict: Flag to indicate if we strictly adhere to the specification.
|
||||
// Returns:
|
||||
// boolean: True if it can decode it, false if it can't.
|
||||
//
|
||||
// Status: Alpha / Should work.
|
||||
//
|
||||
// Ref:
|
||||
// https://github.com/kitlaan/Arduino-IRremote/blob/master/ir_Magiquest.cpp
|
||||
bool IRrecv::decodeMagiQuest(decode_results *results, uint16_t nbits,
|
||||
bool strict) {
|
||||
uint16_t bits = 0;
|
||||
uint64_t data = 0;
|
||||
uint16_t offset = OFFSET_START;
|
||||
|
||||
if (results->rawlen < (2 * MAGIQUEST_BITS)) {
|
||||
DPRINT("Not enough bits to be Magiquest - Rawlen: ");
|
||||
DPRINT(results->rawlen);
|
||||
DPRINT(" Expected: ");
|
||||
DPRINTLN((2 * MAGIQUEST_BITS));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Compliance
|
||||
if (strict && nbits != MAGIQUEST_BITS) return false;
|
||||
|
||||
// Of six wands as datapoints, so far they all start with 8 ZEROs.
|
||||
// For example, here is the data from two wands
|
||||
// 00000000 00100011 01001100 00100110 00000010 00000010 00010111
|
||||
// 00000000 00100000 10001000 00110001 00000010 00000010 10110100
|
||||
|
||||
// Decode the (MARK + SPACE) bits
|
||||
while (offset + 1 < results->rawlen && bits < nbits - 1) {
|
||||
uint16_t mark = results->rawbuf[offset];
|
||||
uint16_t space = results->rawbuf[offset + 1];
|
||||
if (!matchMark(mark + space, MAGIQUEST_TOTAL_USEC)) {
|
||||
DPRINT("Not enough time to be Magiquest - Mark: ");
|
||||
DPRINT(mark);
|
||||
DPRINT(" Space: ");
|
||||
DPRINT(space);
|
||||
DPRINT(" Total: ");
|
||||
DPRINT(mark+space);
|
||||
DPRINT("Expected: ");
|
||||
DPRINTLN(MAGIQUEST_TOTAL_USEC);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IS_ZERO(mark, space)) data = (data << 1) | 0;
|
||||
else if (IS_ONE( mark, space)) data = (data << 1) | 1;
|
||||
else return false;
|
||||
|
||||
bits++;
|
||||
offset += 2;
|
||||
|
||||
// Compliance
|
||||
// The first 8 bits of this protocol are supposed to all be 0.
|
||||
// Exit out early as it is never going to match.
|
||||
if (strict && bits == 8 && data != 0) return false;
|
||||
}
|
||||
|
||||
// Last bit is special as the protocol ends with a SPACE, not a MARK.
|
||||
// Grab the last MARK bit, assuming a good SPACE after it
|
||||
if (offset < results->rawlen) {
|
||||
uint16_t mark = results->rawbuf[offset];
|
||||
uint16_t space = (MAGIQUEST_TOTAL_USEC / RAWTICK) - mark;
|
||||
|
||||
if (IS_ZERO(mark, space)) data = (data << 1) | 0;
|
||||
else if (IS_ONE( mark, space)) data = (data << 1) | 1;
|
||||
else return false;
|
||||
|
||||
bits++;
|
||||
}
|
||||
|
||||
if (bits != nbits) return false;
|
||||
|
||||
if (strict) {
|
||||
// The top 8 bits of the 56 bits needs to be 0x00 to be valid.
|
||||
// i.e. bits 56 to 49 are all zero.
|
||||
if ((data >> (nbits - 8)) != 0) return false;
|
||||
}
|
||||
|
||||
// Success
|
||||
results->decode_type = MAGIQUEST;
|
||||
results->bits = bits;
|
||||
results->value = data;
|
||||
results->address = data >> 16; // Wand ID
|
||||
results->command = data & 0xFFFF; // Magnitude
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright 2013 mpflaga
|
||||
// Copyright 2015 kitlaan
|
||||
// Copyright 2017 Jason kendall, David Conran
|
||||
|
||||
#ifndef IR_MAGIQUEST_H_
|
||||
#define IR_MAGIQUEST_H_
|
||||
|
||||
#define __STDC_LIMIT_MACROS
|
||||
#include <stdint.h>
|
||||
#include "IRremoteESP8266.h"
|
||||
#include "IRsend.h"
|
||||
|
||||
// MagiQuest packet is both Wand ID and magnitude of swish and flick
|
||||
union magiquest {
|
||||
uint64_t llword;
|
||||
uint8_t byte[8];
|
||||
// uint16_t word[4];
|
||||
uint32_t lword[2];
|
||||
struct {
|
||||
uint16_t magnitude;
|
||||
uint32_t wand_id;
|
||||
uint8_t padding;
|
||||
uint8_t scrap;
|
||||
} cmd;
|
||||
};
|
||||
|
||||
#define MAGIQUEST_TOTAL_USEC 1150U
|
||||
#define MAGIQUEST_ZERO_RATIO 30U // usually <= ~25%
|
||||
#define MAGIQUEST_ONE_RATIO 38U // usually >= ~50%
|
||||
|
||||
#define MAGIQUEST_PERIOD 1150U
|
||||
#define MAGIQUEST_MARK_ZERO 280U
|
||||
#define MAGIQUEST_SPACE_ZERO 850U
|
||||
#define MAGIQUEST_MARK_ONE 580U
|
||||
#define MAGIQUEST_SPACE_ONE 600U
|
||||
#define MAGIQUEST_GAP 100000UL // A guess of the gap between messages
|
||||
#endif // IR_MAGIQUEST_H_
|
||||
@@ -0,0 +1,426 @@
|
||||
// Copyright 2017 bwze, crankyoldgit
|
||||
|
||||
#include "ir_Midea.h"
|
||||
#include <algorithm>
|
||||
#ifndef ARDUINO
|
||||
#include <string>
|
||||
#endif
|
||||
#include "IRrecv.h"
|
||||
#include "IRsend.h"
|
||||
#include "IRutils.h"
|
||||
|
||||
// MM MM IIIII DDDDD EEEEEEE AAA
|
||||
// MMM MMM III DD DD EE AAAAA
|
||||
// MM MM MM III DD DD EEEEE AA AA
|
||||
// MM MM III DD DD EE AAAAAAA
|
||||
// MM MM IIIII DDDDDD EEEEEEE AA AA
|
||||
|
||||
// Midea A/C added by (send) bwze/crankyoldgit & (decode) crankyoldgit
|
||||
//
|
||||
// Equipment it seems compatible with:
|
||||
// * Pioneer System Model RYBO12GMFILCAD (12K BTU)
|
||||
// * Pioneer System Model RUBO18GMFILCAD (18K BTU)
|
||||
// * <Add models (A/C & remotes) you've gotten it working with here>
|
||||
|
||||
// Ref:
|
||||
// https://docs.google.com/spreadsheets/d/1TZh4jWrx4h9zzpYUI9aYXMl1fYOiqu-xVuOOMqagxrs/edit?usp=sharing
|
||||
|
||||
// Constants
|
||||
#define MIDEA_TICK 80U
|
||||
#define MIDEA_BIT_MARK_TICKS 7U
|
||||
#define MIDEA_BIT_MARK (MIDEA_BIT_MARK_TICKS * MIDEA_TICK)
|
||||
#define MIDEA_ONE_SPACE_TICKS 21U
|
||||
#define MIDEA_ONE_SPACE (MIDEA_ONE_SPACE_TICKS * MIDEA_TICK)
|
||||
#define MIDEA_ZERO_SPACE_TICKS 7U
|
||||
#define MIDEA_ZERO_SPACE (MIDEA_ZERO_SPACE_TICKS * MIDEA_TICK)
|
||||
#define MIDEA_HDR_MARK_TICKS 56U
|
||||
#define MIDEA_HDR_MARK (MIDEA_HDR_MARK_TICKS * MIDEA_TICK)
|
||||
#define MIDEA_HDR_SPACE_TICKS 56U
|
||||
#define MIDEA_HDR_SPACE (MIDEA_HDR_SPACE_TICKS * MIDEA_TICK)
|
||||
#define MIDEA_MIN_GAP_TICKS (MIDEA_HDR_MARK_TICKS + MIDEA_ZERO_SPACE_TICKS \
|
||||
+ MIDEA_BIT_MARK_TICKS)
|
||||
#define MIDEA_MIN_GAP (MIDEA_MIN_GAP_TICKS * MIDEA_TICK)
|
||||
#define MIDEA_TOLERANCE 30U // Percent
|
||||
|
||||
#if SEND_MIDEA
|
||||
// Send a Midea message
|
||||
//
|
||||
// Args:
|
||||
// data: Contents of the message to be sent.
|
||||
// nbits: Nr. of bits of data to be sent. Typically MIDEA_BITS.
|
||||
// repeat: Nr. of additional times the message is to be sent.
|
||||
//
|
||||
// Status: Alpha / Needs testing against a real device.
|
||||
//
|
||||
void IRsend::sendMidea(uint64_t data, uint16_t nbits, uint16_t repeat) {
|
||||
if (nbits % 8 != 0)
|
||||
return; // nbits is required to be a multiple of 8.
|
||||
|
||||
// Set IR carrier frequency
|
||||
enableIROut(38);
|
||||
|
||||
for (uint16_t r = 0; r <= repeat; r++) {
|
||||
// The protcol sends the message, then follows up with an entirely
|
||||
// inverted payload.
|
||||
for (size_t inner_loop = 0; inner_loop < 2; inner_loop++) {
|
||||
// Header
|
||||
mark(MIDEA_HDR_MARK);
|
||||
space(MIDEA_HDR_SPACE);
|
||||
// Data
|
||||
// Break data into byte segments, starting at the Most Significant
|
||||
// Byte. Each byte then being sent normal, then followed inverted.
|
||||
for (uint16_t i = 8; i <= nbits; i += 8) {
|
||||
// Grab a bytes worth of data.
|
||||
uint8_t segment = (data >> (nbits - i)) & 0xFF;
|
||||
sendData(MIDEA_BIT_MARK, MIDEA_ONE_SPACE,
|
||||
MIDEA_BIT_MARK, MIDEA_ZERO_SPACE,
|
||||
segment, 8, true);
|
||||
}
|
||||
// Footer
|
||||
mark(MIDEA_BIT_MARK);
|
||||
space(MIDEA_MIN_GAP); // Pause before repeating
|
||||
|
||||
// Invert the data for the 2nd phase of the message.
|
||||
// As we get called twice in the inner loop, we will always revert
|
||||
// to the original 'data' state.
|
||||
data = ~data;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Code to emulate Midea A/C IR remote control unit.
|
||||
// Warning: Consider this very alpha code.
|
||||
|
||||
// Initialise the object.
|
||||
IRMideaAC::IRMideaAC(uint16_t pin) : _irsend(pin) {
|
||||
stateReset();
|
||||
}
|
||||
|
||||
// Reset the state of the remote to a known good state/sequence.
|
||||
void IRMideaAC::stateReset() {
|
||||
// Power On, Mode Auto, Fan Auto, Temp = 25C/77F
|
||||
remote_state = 0xA1826FFFFF62;
|
||||
}
|
||||
|
||||
// Configure the pin for output.
|
||||
void IRMideaAC::begin() {
|
||||
_irsend.begin();
|
||||
}
|
||||
|
||||
#if SEND_MIDEA
|
||||
// Send the current desired state to the IR LED.
|
||||
void IRMideaAC::send() {
|
||||
checksum(); // Ensure correct checksum before sending.
|
||||
_irsend.sendMidea(remote_state);
|
||||
}
|
||||
#endif // SEND_MIDEA
|
||||
|
||||
// Return a pointer to the internal state date of the remote.
|
||||
uint64_t IRMideaAC::getRaw() {
|
||||
checksum();
|
||||
return remote_state & MIDEA_AC_STATE_MASK;
|
||||
}
|
||||
|
||||
// Override the internal state with the new state.
|
||||
void IRMideaAC::setRaw(uint64_t newState) {
|
||||
remote_state = newState & MIDEA_AC_STATE_MASK;
|
||||
}
|
||||
|
||||
// Set the requested power state of the A/C to off.
|
||||
void IRMideaAC::on() {
|
||||
remote_state |= MIDEA_AC_POWER;
|
||||
}
|
||||
|
||||
// Set the requested power state of the A/C to off.
|
||||
void IRMideaAC::off() {
|
||||
remote_state &= (MIDEA_AC_STATE_MASK ^ MIDEA_AC_POWER);
|
||||
}
|
||||
|
||||
// Set the requested power state of the A/C.
|
||||
void IRMideaAC::setPower(const bool state) {
|
||||
if (state)
|
||||
on();
|
||||
else
|
||||
off();
|
||||
}
|
||||
|
||||
// Return the requested power state of the A/C.
|
||||
bool IRMideaAC::getPower() {
|
||||
return (remote_state & MIDEA_AC_POWER);
|
||||
}
|
||||
|
||||
// Set the temperature.
|
||||
// Args:
|
||||
// temp: Temp. in degrees.
|
||||
// useCelsius: Degree type to use. celsius (true) or fahrenheit (false)
|
||||
void IRMideaAC::setTemp(const uint8_t temp, const bool useCelsius) {
|
||||
uint8_t new_temp = temp;
|
||||
if (useCelsius) {
|
||||
new_temp = std::max((uint8_t) MIDEA_AC_MIN_TEMP_C, new_temp);
|
||||
new_temp = std::min((uint8_t) MIDEA_AC_MAX_TEMP_C, new_temp);
|
||||
new_temp = (uint8_t) ((new_temp * 1.8) + 32.5); // 0.5 so we rounding.
|
||||
}
|
||||
new_temp = std::max((uint8_t) MIDEA_AC_MIN_TEMP_F, new_temp);
|
||||
new_temp = std::min((uint8_t) MIDEA_AC_MAX_TEMP_F, new_temp);
|
||||
new_temp -= MIDEA_AC_MIN_TEMP_F;
|
||||
remote_state &= MIDEA_AC_TEMP_MASK;
|
||||
remote_state |= ((uint64_t) new_temp << 24);
|
||||
}
|
||||
|
||||
// Return the set temp.
|
||||
// Args:
|
||||
// useCelsius: Flag indicating if the results are in celsius or fahrenheit.
|
||||
// Returns:
|
||||
// A uint8_t containing the temperature.
|
||||
uint8_t IRMideaAC::getTemp(const bool useCelsius) {
|
||||
uint8_t temp = ((remote_state >> 24) & 0x1F) + MIDEA_AC_MIN_TEMP_F;
|
||||
if (useCelsius) {
|
||||
temp = (uint8_t) ((temp - 32) / 1.8);
|
||||
}
|
||||
return temp;
|
||||
}
|
||||
|
||||
// Set the speed of the fan,
|
||||
// 1-3 set the speed, 0 or anything else set it to auto.
|
||||
void IRMideaAC::setFan(const uint8_t fan) {
|
||||
uint64_t new_fan;
|
||||
switch (fan) {
|
||||
case MIDEA_AC_FAN_LOW:
|
||||
case MIDEA_AC_FAN_MED:
|
||||
case MIDEA_AC_FAN_HI:
|
||||
new_fan = fan;
|
||||
break;
|
||||
default:
|
||||
new_fan = MIDEA_AC_FAN_AUTO;
|
||||
}
|
||||
remote_state &= MIDEA_AC_FAN_MASK;
|
||||
remote_state |= (new_fan << 35);
|
||||
}
|
||||
|
||||
// Return the requested state of the unit's fan.
|
||||
uint8_t IRMideaAC::getFan() {
|
||||
return (remote_state >> 35) & 0b111;
|
||||
}
|
||||
|
||||
// Get the requested climate operation mode of the a/c unit.
|
||||
// Returns:
|
||||
// A uint8_t containing the A/C mode.
|
||||
uint8_t IRMideaAC::getMode() {
|
||||
return ((remote_state >> 32) & 0b111);
|
||||
}
|
||||
|
||||
// Set the requested climate operation mode of the a/c unit.
|
||||
void IRMideaAC::setMode(const uint8_t mode) {
|
||||
// If we get an unexpected mode, default to AUTO.
|
||||
uint64_t new_mode;
|
||||
switch (mode) {
|
||||
case MIDEA_AC_AUTO:
|
||||
case MIDEA_AC_COOL:
|
||||
case MIDEA_AC_HEAT:
|
||||
case MIDEA_AC_DRY:
|
||||
case MIDEA_AC_FAN:
|
||||
new_mode = mode;
|
||||
break;
|
||||
default:
|
||||
new_mode = MIDEA_AC_AUTO;
|
||||
}
|
||||
remote_state &= MIDEA_AC_MODE_MASK;
|
||||
remote_state |= (new_mode << 32);
|
||||
}
|
||||
|
||||
// Set the Sleep state of the A/C.
|
||||
void IRMideaAC::setSleep(const bool state) {
|
||||
if (state)
|
||||
remote_state |= MIDEA_AC_SLEEP;
|
||||
else
|
||||
remote_state &= (MIDEA_AC_STATE_MASK ^ MIDEA_AC_SLEEP);
|
||||
}
|
||||
|
||||
// Return the Sleep state of the A/C.
|
||||
bool IRMideaAC::getSleep() {
|
||||
return (remote_state & MIDEA_AC_SLEEP);
|
||||
}
|
||||
|
||||
// Calculate the checksum for a given array.
|
||||
// Args:
|
||||
// state: The state to calculate the checksum over.
|
||||
// Returns:
|
||||
// The 8 bit checksum value.
|
||||
uint8_t IRMideaAC::calcChecksum(const uint64_t state) {
|
||||
uint8_t sum = 0;
|
||||
uint64_t temp_state = state;
|
||||
|
||||
for (uint8_t i = 0; i < 5; i++) {
|
||||
temp_state >>= 8;
|
||||
sum += reverseBits((temp_state & 0xFF), 8);
|
||||
}
|
||||
sum = 256 - sum;
|
||||
return reverseBits(sum, 8);
|
||||
}
|
||||
|
||||
// Verify the checksum is valid for a given state.
|
||||
// Args:
|
||||
// state: The state to verify the checksum of.
|
||||
// Returns:
|
||||
// A boolean.
|
||||
bool IRMideaAC::validChecksum(const uint64_t state) {
|
||||
return ((state & 0xFF) == calcChecksum(state));
|
||||
}
|
||||
|
||||
// Calculate & set the checksum for the current internal state of the remote.
|
||||
void IRMideaAC::checksum() {
|
||||
// Stored the checksum value in the last byte.
|
||||
remote_state &= MIDEA_AC_CHECKSUM_MASK;
|
||||
remote_state |= calcChecksum(remote_state);
|
||||
}
|
||||
|
||||
// Convert the internal state into a human readable string.
|
||||
#ifdef ARDUINO
|
||||
String IRMideaAC::toString() {
|
||||
String result = "";
|
||||
#else
|
||||
std::string IRMideaAC::toString() {
|
||||
std::string result = "";
|
||||
#endif // ARDUINO
|
||||
result += "Power: ";
|
||||
if (getPower())
|
||||
result += "On";
|
||||
else
|
||||
result += "Off";
|
||||
result += ", Mode: " + uint64ToString(getMode());
|
||||
switch (getMode()) {
|
||||
case MIDEA_AC_AUTO:
|
||||
result += " (AUTO)";
|
||||
break;
|
||||
case MIDEA_AC_COOL:
|
||||
result += " (COOL)";
|
||||
break;
|
||||
case MIDEA_AC_HEAT:
|
||||
result += " (HEAT)";
|
||||
break;
|
||||
case MIDEA_AC_DRY:
|
||||
result += " (DRY)";
|
||||
break;
|
||||
case MIDEA_AC_FAN:
|
||||
result += " (FAN)";
|
||||
break;
|
||||
default:
|
||||
result += " (UNKNOWN)";
|
||||
}
|
||||
result += ", Temp: " + uint64ToString(getTemp(true)) + "C/" +
|
||||
uint64ToString(getTemp(false)) + "F";
|
||||
result += ", Fan: " + uint64ToString(getFan());
|
||||
switch (getFan()) {
|
||||
case MIDEA_AC_FAN_AUTO:
|
||||
result += " (AUTO)";
|
||||
break;
|
||||
case MIDEA_AC_FAN_LOW:
|
||||
result += " (LOW)";
|
||||
break;
|
||||
case MIDEA_AC_FAN_MED:
|
||||
result += " (MED)";
|
||||
break;
|
||||
case MIDEA_AC_FAN_HI:
|
||||
result += " (HI)";
|
||||
break;
|
||||
}
|
||||
result += ", Sleep: ";
|
||||
if (getSleep())
|
||||
result += "On";
|
||||
else
|
||||
result += "Off";
|
||||
return result;
|
||||
}
|
||||
|
||||
#if DECODE_MIDEA
|
||||
// Decode the supplied Midea message.
|
||||
//
|
||||
// Args:
|
||||
// results: Ptr to the data to decode and where to store the decode result.
|
||||
// nbits: The number of data bits to expect. Typically MIDEA_BITS.
|
||||
// strict: Flag indicating if we should perform strict matching.
|
||||
// Returns:
|
||||
// boolean: True if it can decode it, false if it can't.
|
||||
//
|
||||
// Status: Alpha / Needs testing against a real device.
|
||||
//
|
||||
bool IRrecv::decodeMidea(decode_results *results, uint16_t nbits,
|
||||
bool strict) {
|
||||
if (nbits % 8 != 0) // nbits has to be a multiple of nr. of bits in a byte.
|
||||
return false;
|
||||
|
||||
uint8_t min_nr_of_messages = 1;
|
||||
if (strict) {
|
||||
if (nbits != MIDEA_BITS)
|
||||
return false; // Not strictly a MIDEA message.
|
||||
min_nr_of_messages = 2;
|
||||
}
|
||||
|
||||
// The protocol sends the data normal + inverted, alternating on
|
||||
// each byte. Hence twice the number of expected data bits.
|
||||
if (results->rawlen < min_nr_of_messages * (2 * nbits + HEADER + FOOTER) - 1)
|
||||
return false; // Can't possibly be a valid MIDEA message.
|
||||
|
||||
uint64_t data = 0;
|
||||
uint64_t inverted = 0;
|
||||
uint16_t offset = OFFSET_START;
|
||||
|
||||
if (nbits > sizeof(data) * 8)
|
||||
return false; // We can't possibly capture a Midea packet that big.
|
||||
|
||||
for (uint8_t i = 0; i < min_nr_of_messages; i++) {
|
||||
// Header
|
||||
if (!matchMark(results->rawbuf[offset], MIDEA_HDR_MARK)) return false;
|
||||
// Calculate how long the common tick time is based on the header mark.
|
||||
uint32_t m_tick = results->rawbuf[offset++] * RAWTICK /
|
||||
MIDEA_HDR_MARK_TICKS;
|
||||
if (!matchSpace(results->rawbuf[offset], MIDEA_HDR_SPACE)) return false;
|
||||
// Calculate how long the common tick time is based on the header space.
|
||||
uint32_t s_tick = results->rawbuf[offset++] * RAWTICK /
|
||||
MIDEA_HDR_SPACE_TICKS;
|
||||
|
||||
// Data (Normal)
|
||||
match_result_t data_result = matchData(&(results->rawbuf[offset]), nbits,
|
||||
MIDEA_BIT_MARK_TICKS * m_tick,
|
||||
MIDEA_ONE_SPACE_TICKS * s_tick,
|
||||
MIDEA_BIT_MARK_TICKS * m_tick,
|
||||
MIDEA_ZERO_SPACE_TICKS * s_tick,
|
||||
MIDEA_TOLERANCE);
|
||||
if (data_result.success == false) return false;
|
||||
offset += data_result.used;
|
||||
if (i % 2 == 0)
|
||||
data = data_result.data;
|
||||
else
|
||||
inverted = data_result.data;
|
||||
|
||||
// Footer
|
||||
if (!matchMark(results->rawbuf[offset++], MIDEA_BIT_MARK_TICKS * m_tick,
|
||||
MIDEA_TOLERANCE))
|
||||
return false;
|
||||
if (offset < results->rawlen &&
|
||||
!matchAtLeast(results->rawbuf[offset++], MIDEA_MIN_GAP_TICKS * s_tick,
|
||||
MIDEA_TOLERANCE))
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Compliance
|
||||
if (strict) {
|
||||
// Protocol requires a second message with all the data bits inverted.
|
||||
// We should have checked we got a second message in the previous loop.
|
||||
// Just need to check it's value is an inverted copy of the first message.
|
||||
uint64_t mask = (1ULL << MIDEA_BITS) - 1;
|
||||
if ((data & mask) != ((inverted ^ mask) & mask)) return false;
|
||||
if (!IRMideaAC::validChecksum(data)) return false;
|
||||
}
|
||||
|
||||
// Success
|
||||
results->decode_type = MIDEA;
|
||||
results->bits = nbits;
|
||||
results->value = data;
|
||||
results->address = 0;
|
||||
results->command = 0;
|
||||
return true;
|
||||
}
|
||||
#endif // DECODE_MIDEA
|
||||
@@ -0,0 +1,87 @@
|
||||
// Copyright 2017 David Conran
|
||||
#ifndef IR_MIDEA_H_
|
||||
#define IR_MIDEA_H_
|
||||
|
||||
#define __STDC_LIMIT_MACROS
|
||||
#include <stdint.h>
|
||||
#ifdef ARDUINO
|
||||
#include <Arduino.h>
|
||||
#else
|
||||
#include <string>
|
||||
#endif
|
||||
#include "IRremoteESP8266.h"
|
||||
#include "IRsend.h"
|
||||
|
||||
// MM MM IIIII DDDDD EEEEEEE AAA
|
||||
// MMM MMM III DD DD EE AAAAA
|
||||
// MM MM MM III DD DD EEEEE AA AA
|
||||
// MM MM III DD DD EE AAAAAAA
|
||||
// MM MM IIIII DDDDDD EEEEEEE AA AA
|
||||
|
||||
// Midea added by crankyoldgit & bwze
|
||||
// Ref:
|
||||
// https://docs.google.com/spreadsheets/d/1TZh4jWrx4h9zzpYUI9aYXMl1fYOiqu-xVuOOMqagxrs/edit?usp=sharing
|
||||
|
||||
// Constants
|
||||
#define MIDEA_AC_COOL 0U // 0b000
|
||||
#define MIDEA_AC_DRY 1U // 0b001
|
||||
#define MIDEA_AC_AUTO 2U // 0b010
|
||||
#define MIDEA_AC_HEAT 3U // 0b011
|
||||
#define MIDEA_AC_FAN 4U // 0b100
|
||||
#define MIDEA_AC_POWER 1ULL << 39
|
||||
#define MIDEA_AC_SLEEP 1ULL << 38
|
||||
#define MIDEA_AC_FAN_AUTO 0U // 0b000
|
||||
#define MIDEA_AC_FAN_LOW 1U // 0b001
|
||||
#define MIDEA_AC_FAN_MED 2U // 0b010
|
||||
#define MIDEA_AC_FAN_HI 3U // 0b011
|
||||
#define MIDEA_AC_MIN_TEMP_F 62U // 62F
|
||||
#define MIDEA_AC_MAX_TEMP_F 86U // 86F
|
||||
#define MIDEA_AC_MIN_TEMP_C 16U // 16C
|
||||
#define MIDEA_AC_MAX_TEMP_C 30U // 30C
|
||||
#define MIDEA_AC_STATE_MASK 0x0000FFFFFFFFFFFFULL
|
||||
#define MIDEA_AC_TEMP_MASK 0x0000FFFFE0FFFFFFULL
|
||||
#define MIDEA_AC_FAN_MASK 0x0000FFC7FFFFFFFFULL
|
||||
#define MIDEA_AC_MODE_MASK 0x0000FFF8FFFFFFFFULL
|
||||
#define MIDEA_AC_CHECKSUM_MASK 0x0000FFFFFFFFFF00ULL
|
||||
|
||||
class IRMideaAC {
|
||||
public:
|
||||
explicit IRMideaAC(uint16_t pin);
|
||||
|
||||
void stateReset();
|
||||
#if SEND_MIDEA
|
||||
void send();
|
||||
#endif // SEND_MIDEA
|
||||
void begin();
|
||||
void on();
|
||||
void off();
|
||||
void setPower(const bool state);
|
||||
bool getPower();
|
||||
void setTemp(const uint8_t temp, const bool useCelsius = false);
|
||||
uint8_t getTemp(const bool useCelsius = false);
|
||||
void setFan(const uint8_t fan);
|
||||
uint8_t getFan();
|
||||
void setMode(const uint8_t mode);
|
||||
uint8_t getMode();
|
||||
void setRaw(uint64_t newState);
|
||||
uint64_t getRaw();
|
||||
static bool validChecksum(const uint64_t state);
|
||||
void setSleep(const bool state);
|
||||
bool getSleep();
|
||||
#ifdef ARDUINO
|
||||
String toString();
|
||||
#else
|
||||
std::string toString();
|
||||
#endif
|
||||
#ifndef UNIT_TEST
|
||||
|
||||
private:
|
||||
#endif
|
||||
uint64_t remote_state;
|
||||
void checksum();
|
||||
static uint8_t calcChecksum(const uint64_t state);
|
||||
IRsend _irsend;
|
||||
};
|
||||
|
||||
|
||||
#endif // IR_MIDEA_H_
|
||||
@@ -5,7 +5,6 @@
|
||||
#include <algorithm>
|
||||
#include "IRrecv.h"
|
||||
#include "IRsend.h"
|
||||
#include "IRtimer.h"
|
||||
#include "IRutils.h"
|
||||
|
||||
// MMMMM IIIII TTTTT SSSS U U BBBB IIIII SSSS H H IIIII
|
||||
@@ -40,6 +39,16 @@
|
||||
#define MITSUBISHI_MIN_GAP (MITSUBISHI_MIN_GAP_TICKS * \
|
||||
MITSUBISHI_TICK)
|
||||
|
||||
// Mitsubishi Projector (HC3000)
|
||||
// Ref:
|
||||
// https://github.com/markszabo/IRremoteESP8266/issues/441
|
||||
#define MITSUBISHI2_HDR_MARK 8400U
|
||||
#define MITSUBISHI2_HDR_SPACE (MITSUBISHI2_HDR_MARK / 2)
|
||||
#define MITSUBISHI2_BIT_MARK 560U
|
||||
#define MITSUBISHI2_ZERO_SPACE 520U
|
||||
#define MITSUBISHI2_ONE_SPACE (MITSUBISHI2_ZERO_SPACE * 3)
|
||||
#define MITSUBISHI2_MIN_GAP 28500U
|
||||
|
||||
// Mitsubishi A/C
|
||||
// Ref:
|
||||
// https://github.com/r45635/HVAC-IR-Control/blob/master/HVAC_ESP8266/HVAC_ESP8266.ino#L84
|
||||
@@ -67,24 +76,14 @@
|
||||
// https://github.com/marcosamarinho/IRremoteESP8266/blob/master/ir_Mitsubishi.cpp
|
||||
// GlobalCache's Control Tower's Mitsubishi TV data.
|
||||
void IRsend::sendMitsubishi(uint64_t data, uint16_t nbits, uint16_t repeat) {
|
||||
enableIROut(33); // Set IR carrier frequency
|
||||
IRtimer usecTimer = IRtimer();
|
||||
|
||||
for (uint16_t i = 0; i <= repeat; i++) {
|
||||
usecTimer.reset();
|
||||
// No header
|
||||
|
||||
// Data
|
||||
sendData(MITSUBISHI_BIT_MARK, MITSUBISHI_ONE_SPACE,
|
||||
MITSUBISHI_BIT_MARK, MITSUBISHI_ZERO_SPACE,
|
||||
data, nbits, true);
|
||||
// Footer
|
||||
mark(MITSUBISHI_BIT_MARK);
|
||||
space(std::max(MITSUBISHI_MIN_COMMAND_LENGTH - usecTimer.elapsed(),
|
||||
MITSUBISHI_MIN_GAP));
|
||||
}
|
||||
sendGeneric(0, 0, // No Header
|
||||
MITSUBISHI_BIT_MARK, MITSUBISHI_ONE_SPACE,
|
||||
MITSUBISHI_BIT_MARK, MITSUBISHI_ZERO_SPACE,
|
||||
MITSUBISHI_BIT_MARK, MITSUBISHI_MIN_GAP,
|
||||
MITSUBISHI_MIN_COMMAND_LENGTH,
|
||||
data, nbits, 33, true, repeat, 50);
|
||||
}
|
||||
#endif
|
||||
#endif // SEND_MITSUBISHI
|
||||
|
||||
#if DECODE_MITSUBISHI
|
||||
// Decode the supplied Mitsubishi message.
|
||||
@@ -152,7 +151,122 @@ bool IRrecv::decodeMitsubishi(decode_results *results, uint16_t nbits,
|
||||
results->command = 0;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#endif // DECODE_MITSUBISHI
|
||||
|
||||
#if SEND_MITSUBISHI2
|
||||
// Send a Mitsubishi2 message
|
||||
//
|
||||
// Args:
|
||||
// data: Contents of the message to be sent.
|
||||
// nbits: Nr. of bits of data to be sent. Typically MITSUBISHI_BITS.
|
||||
// repeat: Nr. of additional times the message is to be sent.
|
||||
//
|
||||
// Status: ALPHA / untested.
|
||||
//
|
||||
// Notes:
|
||||
// Based on a Mitsubishi HC3000 projector's remote.
|
||||
// This protocol appears to have a manditory in-protocol repeat.
|
||||
// That is in *addition* to the entire message needing to be sent twice
|
||||
// for the device to accept the command. That is separate from the repeat.
|
||||
// i.e. Allegedly, the real remote requires the "OFF" button pressed twice.
|
||||
// You will need to add a suitable gap yourself.
|
||||
// Ref:
|
||||
// https://github.com/markszabo/IRremoteESP8266/issues/441
|
||||
void IRsend::sendMitsubishi2(uint64_t data, uint16_t nbits, uint16_t repeat) {
|
||||
for (uint16_t i = 0; i <= repeat; i++) {
|
||||
// First half of the data.
|
||||
sendGeneric(MITSUBISHI2_HDR_MARK, MITSUBISHI2_HDR_SPACE,
|
||||
MITSUBISHI2_BIT_MARK, MITSUBISHI2_ONE_SPACE,
|
||||
MITSUBISHI2_BIT_MARK, MITSUBISHI2_ZERO_SPACE,
|
||||
MITSUBISHI2_BIT_MARK, MITSUBISHI2_HDR_SPACE,
|
||||
data >> (nbits / 2), nbits / 2, 33, true, 0, 50);
|
||||
// Second half of the data.
|
||||
sendGeneric(0, 0, // No header for the second data block
|
||||
MITSUBISHI2_BIT_MARK, MITSUBISHI2_ONE_SPACE,
|
||||
MITSUBISHI2_BIT_MARK, MITSUBISHI2_ZERO_SPACE,
|
||||
MITSUBISHI2_BIT_MARK, MITSUBISHI2_MIN_GAP,
|
||||
data & ((1 << (nbits / 2)) - 1), nbits / 2, 33, true, 0, 50);
|
||||
}
|
||||
}
|
||||
#endif // SEND_MITSUBISHI2
|
||||
|
||||
#if DECODE_MITSUBISHI2
|
||||
// Decode the supplied Mitsubishi2 message.
|
||||
//
|
||||
// Args:
|
||||
// results: Ptr to the data to decode and where to store the decode result.
|
||||
// nbits: Nr. of data bits to expect.
|
||||
// strict: Flag indicating if we should perform strict matching.
|
||||
// Returns:
|
||||
// boolean: True if it can decode it, false if it can't.
|
||||
//
|
||||
// Status: BETA / Works with simulated data.
|
||||
//
|
||||
// Notes:
|
||||
// Hardware supported:
|
||||
// * Mitsubishi HC3000 projector's remote.
|
||||
//
|
||||
// Ref:
|
||||
// https://github.com/markszabo/IRremoteESP8266/issues/441
|
||||
bool IRrecv::decodeMitsubishi2(decode_results *results, uint16_t nbits,
|
||||
bool strict) {
|
||||
if (results->rawlen < 2 * nbits + HEADER + (FOOTER * 2) - 1)
|
||||
return false; // Shorter than shortest possibly expected.
|
||||
if (strict && nbits != MITSUBISHI_BITS)
|
||||
return false; // Request is out of spec.
|
||||
|
||||
uint16_t offset = OFFSET_START;
|
||||
uint64_t data = 0;
|
||||
uint16_t actualBits = 0;
|
||||
|
||||
// Header
|
||||
if (!matchMark(results->rawbuf[offset++], MITSUBISHI2_HDR_MARK))
|
||||
return false;
|
||||
if (!matchSpace(results->rawbuf[offset++], MITSUBISHI2_HDR_SPACE))
|
||||
return false;
|
||||
for (uint8_t i = 1; i <= 2; i++) {
|
||||
// Data
|
||||
match_result_t data_result = matchData(&(results->rawbuf[offset]),
|
||||
nbits / 2,
|
||||
MITSUBISHI2_BIT_MARK,
|
||||
MITSUBISHI2_ONE_SPACE,
|
||||
MITSUBISHI2_BIT_MARK,
|
||||
MITSUBISHI2_ZERO_SPACE);
|
||||
if (data_result.success == false) return false;
|
||||
data <<= nbits / 2;
|
||||
data += data_result.data;
|
||||
offset += data_result.used;
|
||||
actualBits += data_result.used / 2;
|
||||
|
||||
// Footer
|
||||
if (!matchMark(results->rawbuf[offset++], MITSUBISHI2_BIT_MARK))
|
||||
return false;
|
||||
if (i % 2) { // Every odd data block, we expect a HDR space.
|
||||
if (!matchSpace(results->rawbuf[offset++], MITSUBISHI2_HDR_SPACE))
|
||||
return false;
|
||||
} else { // Every even data block, we expect Min Gap or end of the message.
|
||||
if (offset < results->rawlen &&
|
||||
!matchAtLeast(results->rawbuf[offset++], MITSUBISHI2_MIN_GAP))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Compliance
|
||||
if (actualBits < nbits)
|
||||
return false;
|
||||
if (strict && actualBits != nbits)
|
||||
return false; // Not as we expected.
|
||||
|
||||
// Success
|
||||
results->decode_type = MITSUBISHI2;
|
||||
results->bits = actualBits;
|
||||
results->value = data;
|
||||
results->address = data >> actualBits / 2;
|
||||
results->command = data & ((1 << (actualBits / 2)) - 1);
|
||||
return true;
|
||||
}
|
||||
#endif // DECODE_MITSUBISHI2
|
||||
|
||||
#if SEND_MITSUBISHI_AC
|
||||
// Send a Mitsubishi A/C message.
|
||||
@@ -170,23 +284,13 @@ void IRsend::sendMitsubishiAC(unsigned char data[], uint16_t nbytes,
|
||||
if (nbytes < MITSUBISHI_AC_STATE_LENGTH)
|
||||
return; // Not enough bytes to send a proper message.
|
||||
|
||||
// Set IR carrier frequency
|
||||
enableIROut(38);
|
||||
// Mitsubishi AC remote sends the packet twice.
|
||||
for (uint16_t r = 0; r <= repeat; r++) {
|
||||
// Header
|
||||
mark(MITSUBISHI_AC_HDR_MARK);
|
||||
space(MITSUBISHI_AC_HDR_SPACE);
|
||||
// Data
|
||||
for (uint16_t i = 0; i < nbytes; i++)
|
||||
sendData(MITSUBISHI_AC_BIT_MARK, MITSUBISHI_AC_ONE_SPACE,
|
||||
MITSUBISHI_AC_BIT_MARK, MITSUBISHI_AC_ZERO_SPACE,
|
||||
data[i], 8, false);
|
||||
// Footer
|
||||
mark(MITSUBISHI_AC_RPT_MARK);
|
||||
space(MITSUBISHI_AC_RPT_SPACE);
|
||||
}
|
||||
sendGeneric(MITSUBISHI_AC_HDR_MARK, MITSUBISHI_AC_HDR_SPACE,
|
||||
MITSUBISHI_AC_BIT_MARK, MITSUBISHI_AC_ONE_SPACE,
|
||||
MITSUBISHI_AC_BIT_MARK, MITSUBISHI_AC_ZERO_SPACE,
|
||||
MITSUBISHI_AC_RPT_MARK, MITSUBISHI_AC_RPT_SPACE,
|
||||
data, nbytes, 38, false, repeat, 50);
|
||||
}
|
||||
#endif // SEND_MITSUBISHI_AC
|
||||
|
||||
// Code to emulate Mitsubishi A/C IR remote control unit.
|
||||
// Inspired and derived from the work done at:
|
||||
@@ -232,11 +336,13 @@ void IRMitsubishiAC::begin() {
|
||||
_irsend.begin();
|
||||
}
|
||||
|
||||
#if SEND_MITSUBISHI_AC
|
||||
// Send the current desired state to the IR LED.
|
||||
void IRMitsubishiAC::send() {
|
||||
checksum(); // Ensure correct checksum before sending.
|
||||
_irsend.sendMitsubishiAC(remote_state);
|
||||
}
|
||||
#endif // SEND_MITSUBISHI_AC
|
||||
|
||||
// Return a pointer to the internal state date of the remote.
|
||||
uint8_t* IRMitsubishiAC::getRaw() {
|
||||
@@ -346,4 +452,3 @@ void IRMitsubishiAC::setVane(uint8_t mode) {
|
||||
uint8_t IRMitsubishiAC::getVane() {
|
||||
return ((remote_state[9] & 0b00111000) >> 3);
|
||||
}
|
||||
#endif
|
||||
@@ -32,13 +32,14 @@
|
||||
#define MITSUBISHI_AC_VANE_AUTO 0U
|
||||
#define MITSUBISHI_AC_VANE_AUTO_MOVE 7U
|
||||
|
||||
#if SEND_MITSUBISHI_AC
|
||||
class IRMitsubishiAC {
|
||||
public:
|
||||
explicit IRMitsubishiAC(uint16_t pin);
|
||||
|
||||
void stateReset();
|
||||
#if SEND_MITSUBISHI_AC
|
||||
void send();
|
||||
#endif // SEND_MITSUBISHI_AC
|
||||
void begin();
|
||||
void on();
|
||||
void off();
|
||||
@@ -60,6 +61,5 @@ class IRMitsubishiAC {
|
||||
IRsend _irsend;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#endif // IR_MITSUBISHI_H_
|
||||
@@ -6,7 +6,6 @@
|
||||
#include <algorithm>
|
||||
#include "IRrecv.h"
|
||||
#include "IRsend.h"
|
||||
#include "IRtimer.h"
|
||||
#include "IRutils.h"
|
||||
|
||||
// N N EEEEE CCCC
|
||||
@@ -57,29 +56,20 @@
|
||||
// Ref:
|
||||
// http://www.sbprojects.com/knowledge/ir/nec.php
|
||||
void IRsend::sendNEC(uint64_t data, uint16_t nbits, uint16_t repeat) {
|
||||
// Set 38kHz IR carrier frequency & a 1/3 (33%) duty cycle.
|
||||
enableIROut(38, 33);
|
||||
IRtimer usecs = IRtimer();
|
||||
// Header
|
||||
mark(NEC_HDR_MARK);
|
||||
space(NEC_HDR_SPACE);
|
||||
// Data
|
||||
sendData(NEC_BIT_MARK, NEC_ONE_SPACE, NEC_BIT_MARK, NEC_ZERO_SPACE,
|
||||
data, nbits, true);
|
||||
// Footer
|
||||
mark(NEC_BIT_MARK);
|
||||
// Gap to next command.
|
||||
space(std::max(NEC_MIN_GAP, NEC_MIN_COMMAND_LENGTH - usecs.elapsed()));
|
||||
|
||||
sendGeneric(NEC_HDR_MARK, NEC_HDR_SPACE,
|
||||
NEC_BIT_MARK, NEC_ONE_SPACE,
|
||||
NEC_BIT_MARK, NEC_ZERO_SPACE,
|
||||
NEC_BIT_MARK, NEC_MIN_GAP, NEC_MIN_COMMAND_LENGTH,
|
||||
data, nbits, 38, true, 0, // Repeats are handled later.
|
||||
33);
|
||||
// Optional command repeat sequence.
|
||||
for (uint16_t i = 0; i < repeat; i++) {
|
||||
usecs.reset();
|
||||
mark(NEC_HDR_MARK);
|
||||
space(NEC_RPT_SPACE);
|
||||
mark(NEC_BIT_MARK);
|
||||
// Gap till next command.
|
||||
space(std::max(NEC_MIN_GAP, NEC_MIN_COMMAND_LENGTH - usecs.elapsed()));
|
||||
}
|
||||
if (repeat)
|
||||
sendGeneric(NEC_HDR_MARK, NEC_RPT_SPACE,
|
||||
0, 0, 0, 0, // No actual data sent.
|
||||
NEC_BIT_MARK, NEC_MIN_GAP, NEC_MIN_COMMAND_LENGTH,
|
||||
0, 0, // No data to be sent.
|
||||
38, true, repeat - 1, // We've already sent a one message.
|
||||
33);
|
||||
}
|
||||
|
||||
// Calculate the raw NEC data based on address and command.
|
||||
@@ -176,7 +166,7 @@ bool IRrecv::decodeNEC(decode_results *results, uint16_t nbits, bool strict) {
|
||||
// Footer
|
||||
if (!matchMark(results->rawbuf[offset++], NEC_BIT_MARK_TICKS * mark_tick))
|
||||
return false;
|
||||
if (offset <= results->rawlen &&
|
||||
if (offset < results->rawlen &&
|
||||
!matchAtLeast(results->rawbuf[offset], NEC_MIN_GAP_TICKS * space_tick))
|
||||
return false;
|
||||
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
// Copyright 2009 Ken Shirriff
|
||||
// Copyright 2017 David Conran
|
||||
|
||||
#include <algorithm>
|
||||
#include "IRrecv.h"
|
||||
#include "IRsend.h"
|
||||
#include "IRutils.h"
|
||||
|
||||
// NN NN IIIII KK KK AAA IIIII
|
||||
// NNN NN III KK KK AAAAA III
|
||||
// NN N NN III KKKK AA AA III
|
||||
// NN NNN III KK KK AAAAAAA III
|
||||
// NN NN IIIII KK KK AA AA IIIII
|
||||
|
||||
// Constants
|
||||
// Ref:
|
||||
// https://github.com/markszabo/IRremoteESP8266/issues/309
|
||||
#define NIKAI_TICK 500U
|
||||
#define NIKAI_HDR_MARK_TICKS 8U
|
||||
#define NIKAI_HDR_MARK (NIKAI_HDR_MARK_TICKS * NIKAI_TICK)
|
||||
#define NIKAI_HDR_SPACE_TICKS 8U
|
||||
#define NIKAI_HDR_SPACE (NIKAI_HDR_SPACE_TICKS * NIKAI_TICK)
|
||||
#define NIKAI_BIT_MARK_TICKS 1U
|
||||
#define NIKAI_BIT_MARK (NIKAI_BIT_MARK_TICKS * NIKAI_TICK)
|
||||
#define NIKAI_ONE_SPACE_TICKS 2U
|
||||
#define NIKAI_ONE_SPACE (NIKAI_ONE_SPACE_TICKS * NIKAI_TICK)
|
||||
#define NIKAI_ZERO_SPACE_TICKS 4U
|
||||
#define NIKAI_ZERO_SPACE (NIKAI_ZERO_SPACE_TICKS * NIKAI_TICK)
|
||||
#define NIKAI_MIN_GAP_TICKS 17U
|
||||
#define NIKAI_MIN_GAP (NIKAI_MIN_GAP_TICKS * NIKAI_TICK)
|
||||
|
||||
|
||||
#if SEND_NIKAI
|
||||
// Send a Nikai TV formatted message.
|
||||
//
|
||||
// Args:
|
||||
// data: The message to be sent.
|
||||
// nbits: The bit size of the message being sent. typically NIKAI_BITS.
|
||||
// repeat: The number of times the message is to be repeated.
|
||||
//
|
||||
// Status: STABLE / Working.
|
||||
//
|
||||
// Ref: https://github.com/markszabo/IRremoteESP8266/issues/309
|
||||
void IRsend::sendNikai(uint64_t data, uint16_t nbits, uint16_t repeat) {
|
||||
sendGeneric(NIKAI_HDR_MARK, NIKAI_HDR_SPACE,
|
||||
NIKAI_BIT_MARK, NIKAI_ONE_SPACE,
|
||||
NIKAI_BIT_MARK, NIKAI_ZERO_SPACE,
|
||||
NIKAI_BIT_MARK, NIKAI_MIN_GAP,
|
||||
data, nbits, 38, true, repeat, 33);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if DECODE_NIKAI
|
||||
// Decode the supplied Nikai message.
|
||||
//
|
||||
// Args:
|
||||
// results: Ptr to the data to decode and where to store the decode result.
|
||||
// nbits: Nr. of bits to expect in the data portion.
|
||||
// Typically NIKAI_BITS.
|
||||
// strict: Flag to indicate if we strictly adhere to the specification.
|
||||
// Returns:
|
||||
// boolean: True if it can decode it, false if it can't.
|
||||
//
|
||||
// Status: STABLE / Working.
|
||||
//
|
||||
bool IRrecv::decodeNikai(decode_results *results, uint16_t nbits, bool strict) {
|
||||
if (results->rawlen < 2 * nbits + HEADER + FOOTER - 1)
|
||||
return false; // Can't possibly be a valid Nikai message.
|
||||
if (strict && nbits != NIKAI_BITS)
|
||||
return false; // We expect Nikai to be a certain sized message.
|
||||
|
||||
uint64_t data = 0;
|
||||
uint16_t offset = OFFSET_START;
|
||||
|
||||
// Header
|
||||
if (!matchMark(results->rawbuf[offset], NIKAI_HDR_MARK)) return false;
|
||||
// Calculate how long the common tick time is based on the header mark.
|
||||
uint32_t m_tick = results->rawbuf[offset++] * RAWTICK /
|
||||
NIKAI_HDR_MARK_TICKS;
|
||||
if (!matchSpace(results->rawbuf[offset], NIKAI_HDR_SPACE)) return false;
|
||||
// Calculate how long the common tick time is based on the header space.
|
||||
uint32_t s_tick = results->rawbuf[offset++] * RAWTICK /
|
||||
NIKAI_HDR_SPACE_TICKS;
|
||||
// Data
|
||||
match_result_t data_result = matchData(&(results->rawbuf[offset]), nbits,
|
||||
NIKAI_BIT_MARK_TICKS * m_tick,
|
||||
NIKAI_ONE_SPACE_TICKS * s_tick,
|
||||
NIKAI_BIT_MARK_TICKS * m_tick,
|
||||
NIKAI_ZERO_SPACE_TICKS * s_tick);
|
||||
if (data_result.success == false) return false;
|
||||
data = data_result.data;
|
||||
offset += data_result.used;
|
||||
// Footer
|
||||
if (!matchMark(results->rawbuf[offset++], NIKAI_BIT_MARK_TICKS * m_tick))
|
||||
return false;
|
||||
if (offset < results->rawlen &&
|
||||
!matchAtLeast(results->rawbuf[offset], NIKAI_MIN_GAP_TICKS * s_tick))
|
||||
return false;
|
||||
|
||||
// Compliance
|
||||
|
||||
// Success
|
||||
results->bits = nbits;
|
||||
results->value = data;
|
||||
results->decode_type = NIKAI;
|
||||
results->command = 0;
|
||||
results->address = 0;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
@@ -4,7 +4,6 @@
|
||||
#include <algorithm>
|
||||
#include "IRrecv.h"
|
||||
#include "IRsend.h"
|
||||
#include "IRtimer.h"
|
||||
#include "IRutils.h"
|
||||
|
||||
// PPPP AAA N N AAA SSSS OOO N N IIIII CCCC
|
||||
@@ -31,7 +30,7 @@
|
||||
#define PANASONIC_ONE_SPACE (PANASONIC_ONE_SPACE_TICKS * PANASONIC_TICK)
|
||||
#define PANASONIC_ZERO_SPACE_TICKS 1U
|
||||
#define PANASONIC_ZERO_SPACE (PANASONIC_ZERO_SPACE_TICKS * PANASONIC_TICK)
|
||||
#define PANASONIC_MIN_COMMAND_LENGTH_TICKS 300UL
|
||||
#define PANASONIC_MIN_COMMAND_LENGTH_TICKS 378UL
|
||||
#define PANASONIC_MIN_COMMAND_LENGTH (PANASONIC_MIN_COMMAND_LENGTH_TICKS * \
|
||||
PANASONIC_TICK)
|
||||
#define PANASONIC_END_GAP 5000U // See issue #245
|
||||
@@ -53,24 +52,12 @@
|
||||
// Note:
|
||||
// This protocol is a modified version of Kaseikyo.
|
||||
void IRsend::sendPanasonic64(uint64_t data, uint16_t nbits, uint16_t repeat) {
|
||||
enableIROut(36700U); // Set IR carrier frequency of 36.7kHz.
|
||||
IRtimer usecTimer = IRtimer();
|
||||
|
||||
for (uint16_t i = 0; i <= repeat; i++) {
|
||||
usecTimer.reset();
|
||||
// Header
|
||||
mark(PANASONIC_HDR_MARK);
|
||||
space(PANASONIC_HDR_SPACE);
|
||||
// Data
|
||||
sendData(PANASONIC_BIT_MARK, PANASONIC_ONE_SPACE,
|
||||
PANASONIC_BIT_MARK, PANASONIC_ZERO_SPACE,
|
||||
data, nbits, true);
|
||||
// Footer
|
||||
mark(PANASONIC_BIT_MARK);
|
||||
space(std::max((uint32_t) PANASONIC_MIN_COMMAND_LENGTH -
|
||||
usecTimer.elapsed(),
|
||||
PANASONIC_MIN_GAP));
|
||||
}
|
||||
sendGeneric(PANASONIC_HDR_MARK, PANASONIC_HDR_SPACE,
|
||||
PANASONIC_BIT_MARK, PANASONIC_ONE_SPACE,
|
||||
PANASONIC_BIT_MARK, PANASONIC_ZERO_SPACE,
|
||||
PANASONIC_BIT_MARK,
|
||||
PANASONIC_MIN_GAP, PANASONIC_MIN_COMMAND_LENGTH,
|
||||
data, nbits, 36700U, true, repeat, 50);
|
||||
}
|
||||
|
||||
// Send a Panasonic formatted message.
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include "IRsend.h"
|
||||
#include "IRtimer.h"
|
||||
|
||||
// PPPPPP tt
|
||||
// PP PP rr rr oooo nn nnn tt oooo
|
||||
|
||||
@@ -42,9 +42,8 @@
|
||||
#define RC6_36_TOGGLE_MASK 0x8000U // (The 16th bit)
|
||||
|
||||
// Common (getRClevel())
|
||||
#define MARK 0U
|
||||
#define SPACE 1U
|
||||
|
||||
const int16_t kMARK = 0;
|
||||
const int16_t kSPACE = 1;
|
||||
|
||||
#if SEND_RC5
|
||||
// Send a Philips RC-5/RC-5X packet.
|
||||
@@ -282,7 +281,7 @@ void IRsend::sendRC6(uint64_t data, uint16_t nbits, uint16_t repeat) {
|
||||
}
|
||||
#endif // SEND_RC6
|
||||
|
||||
#if (DECODE_RC5 || DECODE_RC6)
|
||||
#if (DECODE_RC5 || DECODE_RC6 || DECODE_LASERTAG)
|
||||
// Gets one undecoded level at a time from the raw buffer.
|
||||
// The RC5/6 decoding is easier if the data is broken into time intervals.
|
||||
// E.g. if the buffer has MARK for 2 time intervals and SPACE for 1,
|
||||
@@ -300,30 +299,39 @@ void IRsend::sendRC6(uint64_t data, uint16_t nbits, uint16_t repeat) {
|
||||
// Ref:
|
||||
// https://en.wikipedia.org/wiki/Manchester_code
|
||||
int16_t IRrecv::getRClevel(decode_results *results, uint16_t *offset,
|
||||
uint16_t *used, uint16_t bitTime) {
|
||||
if (*offset >= results->rawlen)
|
||||
return SPACE; // After end of recorded buffer, assume SPACE.
|
||||
uint16_t *used, uint16_t bitTime,
|
||||
uint8_t tolerance, int16_t excess, uint16_t delta) {
|
||||
DPRINT("DEBUG: getRClevel: offset = ");
|
||||
DPRINTLN(uint64ToString(*offset));
|
||||
if (*offset >= results->rawlen) {
|
||||
DPRINTLN("DEBUG: getRClevel: SPACE, past end of rawbuf");
|
||||
return kSPACE; // After end of recorded buffer, assume SPACE.
|
||||
}
|
||||
uint16_t width = results->rawbuf[*offset];
|
||||
// If the value of offset is odd, it's a MARK. Even, it's a SPACE.
|
||||
uint16_t val = ((*offset) % 2) ? MARK : SPACE;
|
||||
uint16_t val = ((*offset) % 2) ? kMARK : kSPACE;
|
||||
// Check to see if we have hit an inter-message gap (> 20ms).
|
||||
if (val == SPACE && width > 20000)
|
||||
return SPACE;
|
||||
int16_t correction = (val == MARK) ? MARK_EXCESS : -MARK_EXCESS;
|
||||
if (val == kSPACE && width > 20000 - delta) {
|
||||
DPRINTLN("DEBUG: getRClevel: SPACE, hit end of mesg gap.");
|
||||
return kSPACE;
|
||||
}
|
||||
int16_t correction = (val == kMARK) ? excess : -excess;
|
||||
|
||||
// Calculate the look-ahead for our current position in the buffer.
|
||||
uint16_t avail;
|
||||
// Note: We want to match in greedy order as the other way leads to
|
||||
// mismatches due to overlaps induced by the correction and tolerance
|
||||
// values.
|
||||
if (match(width, 3 * bitTime + correction))
|
||||
if (match(width, 3 * bitTime + correction, tolerance, delta)) {
|
||||
avail = 3;
|
||||
else if (match(width, 2 * bitTime + correction))
|
||||
} else if (match(width, 2 * bitTime + correction, tolerance, delta)) {
|
||||
avail = 2;
|
||||
else if (match(width, bitTime + correction))
|
||||
} else if (match(width, bitTime + correction, tolerance, delta)) {
|
||||
avail = 1;
|
||||
else
|
||||
} else {
|
||||
DPRINTLN("DEBUG: getRClevel: Unexpected width. Exiting.");
|
||||
return -1; // The width is not what we expected.
|
||||
}
|
||||
|
||||
(*used)++; // Count another one of the avail slots as used.
|
||||
if (*used >= avail) { // Are we out of look-ahead/avail slots?
|
||||
@@ -331,10 +339,15 @@ int16_t IRrecv::getRClevel(decode_results *results, uint16_t *offset,
|
||||
*used = 0;
|
||||
(*offset)++;
|
||||
}
|
||||
if (val == kMARK) {
|
||||
DPRINTLN("DEBUG: getRClevel: MARK");
|
||||
} else {
|
||||
DPRINTLN("DEBUG: getRClevel: SPACE");
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
#endif // (DECODE_RC5 || DECODE_RC6)
|
||||
#endif // (DECODE_RC5 || DECODE_RC6 || DECODE_LASERTAG)
|
||||
|
||||
#if DECODE_RC5
|
||||
// Decode the supplied RC-5/RC5X message.
|
||||
@@ -371,14 +384,14 @@ bool IRrecv::decodeRC5(decode_results *results, uint16_t nbits, bool strict) {
|
||||
|
||||
// Header
|
||||
// Get start bit #1.
|
||||
if (getRClevel(results, &offset, &used, RC5_T1) != MARK) return false;
|
||||
if (getRClevel(results, &offset, &used, RC5_T1) != kMARK) return false;
|
||||
// Get field/start bit #2 (inverted bit-7 of the command if RC-5X protocol)
|
||||
uint16_t actual_bits = 1;
|
||||
int16_t levelA = getRClevel(results, &offset, &used, RC5_T1);
|
||||
int16_t levelB = getRClevel(results, &offset, &used, RC5_T1);
|
||||
if (levelA == SPACE && levelB == MARK) { // Matched a 1.
|
||||
if (levelA == kSPACE && levelB == kMARK) { // Matched a 1.
|
||||
is_rc5x = false;
|
||||
} else if (levelA == MARK && levelB == SPACE) { // Matched a 0.
|
||||
} else if (levelA == kMARK && levelB == kSPACE) { // Matched a 0.
|
||||
if (nbits <= RC5_BITS) return false; // Field bit must be '1' for RC5.
|
||||
is_rc5x = true;
|
||||
data = 1;
|
||||
@@ -390,9 +403,9 @@ bool IRrecv::decodeRC5(decode_results *results, uint16_t nbits, bool strict) {
|
||||
for (; offset < results->rawlen; actual_bits++) {
|
||||
int16_t levelA = getRClevel(results, &offset, &used, RC5_T1);
|
||||
int16_t levelB = getRClevel(results, &offset, &used, RC5_T1);
|
||||
if (levelA == SPACE && levelB == MARK)
|
||||
if (levelA == kSPACE && levelB == kMARK)
|
||||
data = (data << 1) | 1; // 1
|
||||
else if (levelA == MARK && levelB == SPACE)
|
||||
else if (levelA == kMARK && levelB == kSPACE)
|
||||
data <<= 1; // 0
|
||||
else
|
||||
break;
|
||||
@@ -472,8 +485,8 @@ bool IRrecv::decodeRC6(decode_results *results, uint16_t nbits, bool strict) {
|
||||
uint16_t used = 0;
|
||||
|
||||
// Get the start bit. e.g. 1.
|
||||
if (getRClevel(results, &offset, &used, tick) != MARK) return false;
|
||||
if (getRClevel(results, &offset, &used, tick) != SPACE) return false;
|
||||
if (getRClevel(results, &offset, &used, tick) != kMARK) return false;
|
||||
if (getRClevel(results, &offset, &used, tick) != kSPACE) return false;
|
||||
|
||||
uint16_t actual_bits;
|
||||
uint64_t data = 0;
|
||||
@@ -491,9 +504,9 @@ bool IRrecv::decodeRC6(decode_results *results, uint16_t nbits, bool strict) {
|
||||
if (actual_bits == 3 &&
|
||||
levelB != getRClevel(results, &offset, &used, tick))
|
||||
return false;
|
||||
if (levelA == MARK && levelB == SPACE) // reversed compared to RC5
|
||||
if (levelA == kMARK && levelB == kSPACE) // reversed compared to RC5
|
||||
data = (data << 1) | 1; // 1
|
||||
else if (levelA == SPACE && levelB == MARK)
|
||||
else if (levelA == kSPACE && levelB == kMARK)
|
||||
data <<= 1; // 0
|
||||
else
|
||||
break;
|
||||
|
||||
@@ -107,7 +107,7 @@ bool IRrecv::decodeRCMM(decode_results *results, uint16_t nbits, bool strict) {
|
||||
return false; // Not enough entries to ever be RCMM.
|
||||
|
||||
// Calc the maximum size in bits, the message can be, or that we can accept.
|
||||
int16_t maxBitSize = std::min((uint16_t) results->rawlen - 4,
|
||||
int16_t maxBitSize = std::min((uint16_t) results->rawlen - 5,
|
||||
(uint16_t) sizeof(data) * 8);
|
||||
// Compliance
|
||||
if (strict) {
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include <algorithm>
|
||||
#include "IRrecv.h"
|
||||
#include "IRsend.h"
|
||||
#include "IRtimer.h"
|
||||
#include "IRutils.h"
|
||||
|
||||
// SSSS AAA MMM SSSS U U N N GGGG
|
||||
@@ -57,24 +56,12 @@
|
||||
//
|
||||
// Ref: http://elektrolab.wz.cz/katalog/samsung_protocol.pdf
|
||||
void IRsend::sendSAMSUNG(uint64_t data, uint16_t nbits, uint16_t repeat) {
|
||||
// Set 38kHz IR carrier frequency & a 1/3 (33%) duty cycle.
|
||||
enableIROut(38, 33);
|
||||
IRtimer usecTimer = IRtimer();
|
||||
// We always send a message, even for repeat=0, hence '<= repeat'.
|
||||
for (uint16_t i=0; i <= repeat; i++) {
|
||||
usecTimer.reset();
|
||||
// Header
|
||||
mark(SAMSUNG_HDR_MARK);
|
||||
space(SAMSUNG_HDR_SPACE);
|
||||
// Data
|
||||
sendData(SAMSUNG_BIT_MARK, SAMSUNG_ONE_SPACE, SAMSUNG_BIT_MARK,
|
||||
SAMSUNG_ZERO_SPACE, data, nbits, true);
|
||||
// Footer
|
||||
mark(SAMSUNG_BIT_MARK);
|
||||
space(std::max((uint32_t) SAMSUNG_MIN_GAP,
|
||||
(uint32_t) (SAMSUNG_MIN_MESSAGE_LENGTH -
|
||||
usecTimer.elapsed())));
|
||||
}
|
||||
sendGeneric(SAMSUNG_HDR_MARK, SAMSUNG_HDR_SPACE,
|
||||
SAMSUNG_BIT_MARK, SAMSUNG_ONE_SPACE,
|
||||
SAMSUNG_BIT_MARK, SAMSUNG_ZERO_SPACE,
|
||||
SAMSUNG_BIT_MARK,
|
||||
SAMSUNG_MIN_GAP, SAMSUNG_MIN_MESSAGE_LENGTH,
|
||||
data, nbits, 38, true, repeat, 33);
|
||||
}
|
||||
|
||||
// Construct a raw Samsung message from the supplied customer(address) &
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include <algorithm>
|
||||
#include "IRrecv.h"
|
||||
#include "IRsend.h"
|
||||
#include "IRtimer.h"
|
||||
|
||||
// SSSS AAA N N Y Y OOO
|
||||
// S A A NN N Y Y O O
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include <algorithm>
|
||||
#include "IRrecv.h"
|
||||
#include "IRsend.h"
|
||||
#include "IRtimer.h"
|
||||
#include "IRutils.h"
|
||||
|
||||
// SSSS H H AAA RRRR PPPP
|
||||
@@ -13,6 +12,11 @@
|
||||
// S H H A A R R P
|
||||
// SSSS H H A A R R P
|
||||
|
||||
// Equipment it seems compatible with:
|
||||
// * Sharp LC-52D62U
|
||||
// * <Add models (devices & remotes) you've gotten it working with here>
|
||||
//
|
||||
|
||||
// Constants
|
||||
// period time = 1/38000Hz = 26.316 microseconds.
|
||||
// Ref:
|
||||
@@ -56,25 +60,18 @@
|
||||
// http://www.mwftr.com/ucF08/LEC14%20PIC%20IR.pdf
|
||||
// http://www.hifi-remote.com/johnsfine/DecodeIR.html#Sharp
|
||||
void IRsend::sendSharpRaw(uint64_t data, uint16_t nbits, uint16_t repeat) {
|
||||
// Set 38kHz IR carrier frequency & a 1/3 (33%) duty cycle.
|
||||
enableIROut(38, 33);
|
||||
|
||||
for (uint16_t i = 0; i <= repeat; i++) {
|
||||
// Protocol demands that the data be sent twice; once normally,
|
||||
// then with all but the address bits inverted.
|
||||
// Note: Previously this used to be performed 3 times (normal, inverted,
|
||||
// normal), however all data points to that being incorrect.
|
||||
for (uint8_t n = 0; n < 2; n++) {
|
||||
// No Header
|
||||
|
||||
// Data
|
||||
sendData(SHARP_BIT_MARK, SHARP_ONE_SPACE,
|
||||
SHARP_BIT_MARK, SHARP_ZERO_SPACE,
|
||||
data, nbits, true);
|
||||
// Footer
|
||||
mark(SHARP_BIT_MARK);
|
||||
space(SHARP_GAP);
|
||||
|
||||
sendGeneric(0, 0, // No Header
|
||||
SHARP_BIT_MARK, SHARP_ONE_SPACE,
|
||||
SHARP_BIT_MARK, SHARP_ZERO_SPACE,
|
||||
SHARP_BIT_MARK, SHARP_GAP,
|
||||
data, nbits, 38, true, 0, // Repeats are handled already.
|
||||
33);
|
||||
// Invert the data per protocol. This is always called twice, so it's
|
||||
// retured to original upon exiting the inner loop.
|
||||
data ^= SHARP_TOGGLE_MASK;
|
||||
@@ -165,7 +162,7 @@ void IRsend::sendSharp(uint16_t address, uint16_t command, uint16_t nbits,
|
||||
// Returns:
|
||||
// boolean: True if it can decode it, false if it can't.
|
||||
//
|
||||
// Status: BETA / Should work okay.
|
||||
// Status: STABLE / Working fine.
|
||||
//
|
||||
// Note:
|
||||
// This procedure returns a value suitable for use in sendSharpRaw().
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include <algorithm>
|
||||
#include "IRrecv.h"
|
||||
#include "IRsend.h"
|
||||
#include "IRtimer.h"
|
||||
#include "IRutils.h"
|
||||
|
||||
// SSSS OOO N N Y Y
|
||||
@@ -51,24 +50,12 @@
|
||||
// Ref:
|
||||
// http://www.sbprojects.com/knowledge/ir/sirc.php
|
||||
void IRsend::sendSony(uint64_t data, uint16_t nbits, uint16_t repeat) {
|
||||
// Sony devices use a 40kHz IR carrier frequency & a 1/3 (33%) duty cycle.
|
||||
enableIROut(40, 33);
|
||||
IRtimer usecs = IRtimer();
|
||||
|
||||
for (uint16_t i = 0; i <= repeat; i++) { // Typically loop 3 or more times.
|
||||
usecs.reset();
|
||||
// Header
|
||||
mark(SONY_HDR_MARK);
|
||||
space(SONY_SPACE);
|
||||
// Data
|
||||
sendData(SONY_ONE_MARK, SONY_SPACE, SONY_ZERO_MARK, SONY_SPACE,
|
||||
data, nbits, true);
|
||||
// Footer
|
||||
// The Sony protocol requires us to wait 45ms from start of a code to the
|
||||
// start of the next one. A 10ms minimum gap is also required.
|
||||
space(std::max(SONY_MIN_GAP, SONY_RPT_LENGTH - usecs.elapsed()));
|
||||
}
|
||||
// A space() is always performed last, so no need to turn off the LED.
|
||||
sendGeneric(SONY_HDR_MARK, SONY_SPACE,
|
||||
SONY_ONE_MARK, SONY_SPACE,
|
||||
SONY_ZERO_MARK, SONY_SPACE,
|
||||
0, // No Footer mark.
|
||||
SONY_MIN_GAP, SONY_RPT_LENGTH,
|
||||
data, nbits, 40, true, repeat, 33);
|
||||
}
|
||||
|
||||
// Convert Sony/SIRC command, address, & extended bits into sendSony format.
|
||||
|
||||
@@ -0,0 +1,349 @@
|
||||
// Copyright 2017 David Conran
|
||||
|
||||
#include "ir_Toshiba.h"
|
||||
#include <algorithm>
|
||||
#ifndef ARDUINO
|
||||
#include <string>
|
||||
#endif
|
||||
#include "IRrecv.h"
|
||||
#include "IRsend.h"
|
||||
#include "IRutils.h"
|
||||
|
||||
// TTTTTTT OOOOO SSSSS HH HH IIIII BBBBB AAA
|
||||
// TTT OO OO SS HH HH III BB B AAAAA
|
||||
// TTT OO OO SSSSS HHHHHHH III BBBBBB AA AA
|
||||
// TTT OO OO SS HH HH III BB BB AAAAAAA
|
||||
// TTT OOOO0 SSSSS HH HH IIIII BBBBBB AA AA
|
||||
|
||||
// Toshiba A/C support added by David Conran
|
||||
//
|
||||
// Equipment it seems compatible with:
|
||||
// * Toshiba RAS-B13N3KV2 / Akita EVO II
|
||||
// * Toshiba RAS-B13N3KVP-E, RAS 18SKP-ES
|
||||
// * Toshiba WH-TA04NE, WC-L03SE
|
||||
// * <Add models (A/C & remotes) you've gotten it working with here>
|
||||
|
||||
// Constants
|
||||
|
||||
// Toshiba A/C
|
||||
// Ref:
|
||||
// https://github.com/r45635/HVAC-IR-Control/blob/master/HVAC_ESP8266/HVAC_ESP8266T.ino#L77
|
||||
#define TOSHIBA_AC_HDR_MARK 4400U
|
||||
#define TOSHIBA_AC_HDR_SPACE 4300U
|
||||
#define TOSHIBA_AC_BIT_MARK 543U
|
||||
#define TOSHIBA_AC_ONE_SPACE 1623U
|
||||
#define TOSHIBA_AC_ZERO_SPACE 472U
|
||||
#define TOSHIBA_AC_MIN_GAP 7048U
|
||||
|
||||
#if SEND_TOSHIBA_AC
|
||||
// Send a Toshiba A/C message.
|
||||
//
|
||||
// Args:
|
||||
// data: An array of bytes containing the IR command.
|
||||
// nbytes: Nr. of bytes of data in the array. (>=TOSHIBA_AC_STATE_LENGTH)
|
||||
// repeat: Nr. of times the message is to be repeated.
|
||||
// (Default = TOSHIBA_AC_MIN_REPEAT).
|
||||
//
|
||||
// Status: StABLE / Working.
|
||||
//
|
||||
void IRsend::sendToshibaAC(unsigned char data[], uint16_t nbytes,
|
||||
uint16_t repeat) {
|
||||
if (nbytes < TOSHIBA_AC_STATE_LENGTH)
|
||||
return; // Not enough bytes to send a proper message.
|
||||
sendGeneric(TOSHIBA_AC_HDR_MARK, TOSHIBA_AC_HDR_SPACE,
|
||||
TOSHIBA_AC_BIT_MARK, TOSHIBA_AC_ONE_SPACE,
|
||||
TOSHIBA_AC_BIT_MARK, TOSHIBA_AC_ZERO_SPACE,
|
||||
TOSHIBA_AC_BIT_MARK, TOSHIBA_AC_MIN_GAP,
|
||||
data, nbytes, 38, true, repeat, 50);
|
||||
}
|
||||
#endif // SEND_TOSHIBA_AC
|
||||
|
||||
// Code to emulate Toshiba A/C IR remote control unit.
|
||||
// Inspired and derived from the work done at:
|
||||
// https://github.com/r45635/HVAC-IR-Control
|
||||
//
|
||||
// Status: STABLE / Working.
|
||||
//
|
||||
// Initialise the object.
|
||||
IRToshibaAC::IRToshibaAC(uint16_t pin) : _irsend(pin) {
|
||||
stateReset();
|
||||
}
|
||||
|
||||
// Reset the state of the remote to a known good state/sequence.
|
||||
void IRToshibaAC::stateReset() {
|
||||
// The state of the IR remote in IR code form.
|
||||
// Known good state obtained from:
|
||||
// https://github.com/r45635/HVAC-IR-Control/blob/master/HVAC_ESP8266/HVAC_ESP8266T.ino#L103
|
||||
// Note: Can't use the following because it requires -std=c++11
|
||||
// uint8_t remote_state[TOSHIBA_AC_STATE_LENGTH] = {
|
||||
// 0xF2, 0x0D, 0x03, 0xFC, 0x01, 0x00, 0x00, 0x00, 0x00 };
|
||||
remote_state[0] = 0xF2;
|
||||
remote_state[1] = 0x0D;
|
||||
remote_state[2] = 0x03;
|
||||
remote_state[3] = 0xFC;
|
||||
remote_state[4] = 0x01;
|
||||
for (uint8_t i = 5; i < TOSHIBA_AC_STATE_LENGTH; i++)
|
||||
remote_state[i] = 0;
|
||||
mode_state = remote_state[6] & 0b00000011;
|
||||
checksum(); // Calculate the checksum
|
||||
}
|
||||
|
||||
// Configure the pin for output.
|
||||
void IRToshibaAC::begin() {
|
||||
_irsend.begin();
|
||||
}
|
||||
|
||||
#if SEND_TOSHIBA_AC
|
||||
// Send the current desired state to the IR LED.
|
||||
void IRToshibaAC::send() {
|
||||
checksum(); // Ensure correct checksum before sending.
|
||||
_irsend.sendToshibaAC(remote_state);
|
||||
}
|
||||
#endif // SEND_TOSHIBA_AC
|
||||
|
||||
// Return a pointer to the internal state date of the remote.
|
||||
uint8_t* IRToshibaAC::getRaw() {
|
||||
checksum();
|
||||
return remote_state;
|
||||
}
|
||||
|
||||
// Override the internal state with the new state.
|
||||
void IRToshibaAC::setRaw(uint8_t newState[]) {
|
||||
for (uint8_t i = 0; i < TOSHIBA_AC_STATE_LENGTH; i++) {
|
||||
remote_state[i] = newState[i];
|
||||
}
|
||||
mode_state = getMode(true);
|
||||
}
|
||||
|
||||
// Calculate the checksum for a given array.
|
||||
// Args:
|
||||
// state: The array to calculate the checksum over.
|
||||
// length: The size of the array.
|
||||
// Returns:
|
||||
// The 8 bit checksum value.
|
||||
uint8_t IRToshibaAC::calcChecksum(const uint8_t state[],
|
||||
const uint16_t length) {
|
||||
uint8_t checksum = 0;
|
||||
// Only calculate it for valid lengths.
|
||||
if (length > 1) {
|
||||
// Checksum is simple XOR of all bytes except the last one.
|
||||
for (uint8_t i = 0; i < length - 1; i++)
|
||||
checksum ^= state[i];
|
||||
}
|
||||
return checksum;
|
||||
}
|
||||
|
||||
// Verify the checksum is valid for a given state.
|
||||
// Args:
|
||||
// state: The array to verify the checksum of.
|
||||
// length: The size of the state.
|
||||
// Returns:
|
||||
// A boolean.
|
||||
bool IRToshibaAC::validChecksum(const uint8_t state[],
|
||||
const uint16_t length) {
|
||||
return (length > 1 && state[length - 1] == calcChecksum(state, length));
|
||||
}
|
||||
|
||||
// Calculate & set the checksum for the current internal state of the remote.
|
||||
void IRToshibaAC::checksum(const uint16_t length) {
|
||||
// Stored the checksum value in the last byte.
|
||||
if (length > 1)
|
||||
remote_state[length - 1] = calcChecksum(remote_state, length);
|
||||
}
|
||||
|
||||
// Set the requested power state of the A/C to off.
|
||||
void IRToshibaAC::on() {
|
||||
// state = ON;
|
||||
remote_state[6] &= ~TOSHIBA_AC_POWER;
|
||||
setMode(mode_state);
|
||||
}
|
||||
|
||||
// Set the requested power state of the A/C to off.
|
||||
void IRToshibaAC::off() {
|
||||
// state = OFF;
|
||||
remote_state[6] |= (TOSHIBA_AC_POWER | 0b00000011);
|
||||
}
|
||||
|
||||
// Set the requested power state of the A/C.
|
||||
void IRToshibaAC::setPower(bool state) {
|
||||
if (state)
|
||||
on();
|
||||
else
|
||||
off();
|
||||
}
|
||||
|
||||
// Return the requested power state of the A/C.
|
||||
bool IRToshibaAC::getPower() {
|
||||
return((remote_state[6] & TOSHIBA_AC_POWER) == 0);
|
||||
}
|
||||
|
||||
// Set the temp. in deg C
|
||||
void IRToshibaAC::setTemp(uint8_t temp) {
|
||||
temp = std::max((uint8_t) TOSHIBA_AC_MIN_TEMP, temp);
|
||||
temp = std::min((uint8_t) TOSHIBA_AC_MAX_TEMP, temp);
|
||||
remote_state[5] = (temp - TOSHIBA_AC_MIN_TEMP) << 4;
|
||||
}
|
||||
|
||||
// Return the set temp. in deg C
|
||||
uint8_t IRToshibaAC::getTemp() {
|
||||
return((remote_state[5] >> 4) + TOSHIBA_AC_MIN_TEMP);
|
||||
}
|
||||
|
||||
// Set the speed of the fan, 0-5.
|
||||
// 0 is auto, 1-5 is the speed, 5 is Max.
|
||||
void IRToshibaAC::setFan(uint8_t fan) {
|
||||
// Bounds check
|
||||
if (fan > TOSHIBA_AC_FAN_MAX)
|
||||
fan = TOSHIBA_AC_FAN_MAX; // Set the fan to maximum if out of range.
|
||||
if (fan > TOSHIBA_AC_FAN_AUTO) fan++;
|
||||
remote_state[6] &= 0b00011111; // Clear the previous fan state
|
||||
remote_state[6] |= (fan << 5);
|
||||
}
|
||||
|
||||
// Return the requested state of the unit's fan.
|
||||
uint8_t IRToshibaAC::getFan() {
|
||||
uint8_t fan = remote_state[6] >> 5;
|
||||
if (fan == TOSHIBA_AC_FAN_AUTO) return TOSHIBA_AC_FAN_AUTO;
|
||||
return --fan;
|
||||
}
|
||||
|
||||
// Get the requested climate operation mode of the a/c unit.
|
||||
// Args:
|
||||
// useRaw: Indicate to get the mode from the state array. (Default: false)
|
||||
// Returns:
|
||||
// A uint8_t containing the A/C mode.
|
||||
uint8_t IRToshibaAC::getMode(bool useRaw) {
|
||||
if (useRaw)
|
||||
return (remote_state[6] & 0b00000011);
|
||||
else
|
||||
return mode_state;
|
||||
}
|
||||
|
||||
// Set the requested climate operation mode of the a/c unit.
|
||||
void IRToshibaAC::setMode(uint8_t mode) {
|
||||
// If we get an unexpected mode, default to AUTO.
|
||||
switch (mode) {
|
||||
case TOSHIBA_AC_AUTO: break;
|
||||
case TOSHIBA_AC_COOL: break;
|
||||
case TOSHIBA_AC_DRY: break;
|
||||
case TOSHIBA_AC_HEAT: break;
|
||||
default: mode = TOSHIBA_AC_AUTO;
|
||||
}
|
||||
mode_state = mode;
|
||||
// Only adjust the remote_state if we have power set to on.
|
||||
if (getPower()) {
|
||||
remote_state[6] &= 0b11111100; // Clear the previous mode.
|
||||
remote_state[6] |= mode_state;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert the internal state into a human readable string.
|
||||
#ifdef ARDUINO
|
||||
String IRToshibaAC::toString() {
|
||||
String result = "";
|
||||
#else
|
||||
std::string IRToshibaAC::toString() {
|
||||
std::string result = "";
|
||||
#endif // ARDUINO
|
||||
result += "Power: ";
|
||||
if (getPower())
|
||||
result += "On";
|
||||
else
|
||||
result += "Off";
|
||||
result += ", Mode: " + uint64ToString(getMode());
|
||||
switch (getMode()) {
|
||||
case TOSHIBA_AC_AUTO:
|
||||
result += " (AUTO)";
|
||||
break;
|
||||
case TOSHIBA_AC_COOL:
|
||||
result += " (COOL)";
|
||||
break;
|
||||
case TOSHIBA_AC_HEAT:
|
||||
result += " (HEAT)";
|
||||
break;
|
||||
case TOSHIBA_AC_DRY:
|
||||
result += " (DRY)";
|
||||
break;
|
||||
default:
|
||||
result += " (UNKNOWN)";
|
||||
}
|
||||
result += ", Temp: " + uint64ToString(getTemp()) + "C";
|
||||
result += ", Fan: " + uint64ToString(getFan());
|
||||
switch (getFan()) {
|
||||
case TOSHIBA_AC_FAN_AUTO:
|
||||
result += " (AUTO)";
|
||||
break;
|
||||
case TOSHIBA_AC_FAN_MAX:
|
||||
result += " (MAX)";
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#if DECODE_TOSHIBA_AC
|
||||
// Decode a Toshiba AC IR message if possible.
|
||||
// Places successful decode information in the results pointer.
|
||||
// Args:
|
||||
// results: Ptr to the data to decode and where to store the decode result.
|
||||
// nbits: The number of data bits to expect. Typically TOSHIBA_AC_BITS.
|
||||
// strict: Flag to indicate if we strictly adhere to the specification.
|
||||
// Returns:
|
||||
// boolean: True if it can decode it, false if it can't.
|
||||
//
|
||||
// Status: STABLE / Working.
|
||||
//
|
||||
// Ref:
|
||||
//
|
||||
bool IRrecv::decodeToshibaAC(decode_results *results, uint16_t nbits,
|
||||
bool strict) {
|
||||
uint16_t offset = OFFSET_START;
|
||||
uint16_t dataBitsSoFar = 0;
|
||||
|
||||
// Have we got enough data to successfully decode?
|
||||
if (results->rawlen < TOSHIBA_AC_BITS + HEADER + FOOTER - 1)
|
||||
return false; // Can't possibly be a valid message.
|
||||
|
||||
|
||||
// Compliance
|
||||
if (strict && nbits != TOSHIBA_AC_BITS)
|
||||
return false; // Must be called with the correct nr. of bytes.
|
||||
|
||||
// Header
|
||||
if (!matchMark(results->rawbuf[offset++], TOSHIBA_AC_HDR_MARK))
|
||||
return false;
|
||||
if (!matchSpace(results->rawbuf[offset++], TOSHIBA_AC_HDR_SPACE))
|
||||
return false;
|
||||
|
||||
// Data
|
||||
for (uint8_t i = 0; i < TOSHIBA_AC_STATE_LENGTH; i++) {
|
||||
// Read a byte's worth of data.
|
||||
match_result_t data_result = matchData(&(results->rawbuf[offset]), 8,
|
||||
TOSHIBA_AC_BIT_MARK,
|
||||
TOSHIBA_AC_ONE_SPACE,
|
||||
TOSHIBA_AC_BIT_MARK,
|
||||
TOSHIBA_AC_ZERO_SPACE);
|
||||
if (data_result.success == false) return false; // Fail
|
||||
dataBitsSoFar += 8;
|
||||
results->state[i] = (uint8_t) data_result.data;
|
||||
offset += data_result.used;
|
||||
}
|
||||
|
||||
// Footer
|
||||
if (!matchMark(results->rawbuf[offset++], TOSHIBA_AC_BIT_MARK)) return false;
|
||||
if (!matchSpace(results->rawbuf[offset++], TOSHIBA_AC_MIN_GAP)) return false;
|
||||
|
||||
// Compliance
|
||||
if (strict) {
|
||||
// Check that the checksum of the message is correct.
|
||||
if (!IRToshibaAC::validChecksum(results->state)) return false;
|
||||
}
|
||||
|
||||
// Success
|
||||
results->decode_type = TOSHIBA_AC;
|
||||
results->bits = dataBitsSoFar;
|
||||
// No need to record the state as we stored it as we decoded it.
|
||||
// As we use result->state, we don't record value, address, or command as it
|
||||
// is a union data type.
|
||||
return true;
|
||||
}
|
||||
#endif // DECODE_TOSHIBA_AC
|
||||
@@ -0,0 +1,74 @@
|
||||
// Copyright 2017 David Conran
|
||||
#ifndef IR_TOSHIBA_H_
|
||||
#define IR_TOSHIBA_H_
|
||||
|
||||
#define __STDC_LIMIT_MACROS
|
||||
#include <stdint.h>
|
||||
#ifdef ARDUINO
|
||||
#include <Arduino.h>
|
||||
#else
|
||||
#include <string>
|
||||
#endif
|
||||
#include "IRremoteESP8266.h"
|
||||
#include "IRsend.h"
|
||||
|
||||
// TTTTTTT OOOOO SSSSS HH HH IIIII BBBBB AAA
|
||||
// TTT OO OO SS HH HH III BB B AAAAA
|
||||
// TTT OO OO SSSSS HHHHHHH III BBBBBB AA AA
|
||||
// TTT OO OO SS HH HH III BB BB AAAAAAA
|
||||
// TTT OOOO0 SSSSS HH HH IIIII BBBBBB AA AA
|
||||
|
||||
// Toshiba A/C support added by David Conran
|
||||
|
||||
// Constants
|
||||
#define TOSHIBA_AC_AUTO 0U
|
||||
#define TOSHIBA_AC_COOL 1U
|
||||
#define TOSHIBA_AC_DRY 2U
|
||||
#define TOSHIBA_AC_HEAT 3U
|
||||
#define TOSHIBA_AC_POWER 4U
|
||||
#define TOSHIBA_AC_FAN_AUTO 0U
|
||||
#define TOSHIBA_AC_FAN_MAX 5U
|
||||
#define TOSHIBA_AC_MIN_TEMP 17U // 17C
|
||||
#define TOSHIBA_AC_MAX_TEMP 30U // 30C
|
||||
|
||||
class IRToshibaAC {
|
||||
public:
|
||||
explicit IRToshibaAC(uint16_t pin);
|
||||
|
||||
void stateReset();
|
||||
#if SEND_TOSHIBA_AC
|
||||
void send();
|
||||
#endif // SEND_TOSHIBA_AC
|
||||
void begin();
|
||||
void on();
|
||||
void off();
|
||||
void setPower(bool state);
|
||||
bool getPower();
|
||||
void setTemp(uint8_t temp);
|
||||
uint8_t getTemp();
|
||||
void setFan(uint8_t fan);
|
||||
uint8_t getFan();
|
||||
void setMode(uint8_t mode);
|
||||
uint8_t getMode(bool useRaw = false);
|
||||
void setRaw(uint8_t newState[]);
|
||||
uint8_t* getRaw();
|
||||
static bool validChecksum(const uint8_t state[],
|
||||
const uint16_t length = TOSHIBA_AC_STATE_LENGTH);
|
||||
#ifdef ARDUINO
|
||||
String toString();
|
||||
#else
|
||||
std::string toString();
|
||||
#endif
|
||||
#ifndef UNIT_TEST
|
||||
|
||||
private:
|
||||
#endif
|
||||
uint8_t remote_state[TOSHIBA_AC_STATE_LENGTH];
|
||||
void checksum(const uint16_t length = TOSHIBA_AC_STATE_LENGTH);
|
||||
static uint8_t calcChecksum(const uint8_t state[],
|
||||
const uint16_t length = TOSHIBA_AC_STATE_LENGTH);
|
||||
uint8_t mode_state;
|
||||
IRsend _irsend;
|
||||
};
|
||||
|
||||
#endif // IR_TOSHIBA_H_
|
||||
@@ -21,25 +21,20 @@ void IRsend::sendTrotec(unsigned char data[], uint16_t nbytes,
|
||||
if (nbytes < TROTEC_COMMAND_LENGTH)
|
||||
return;
|
||||
|
||||
enableIROut(36);
|
||||
|
||||
for (uint16_t r = 0; r <= repeat; r++) {
|
||||
// Header
|
||||
mark(TROTEC_HDR_MARK);
|
||||
space(TROTEC_HDR_SPACE);
|
||||
|
||||
// Data
|
||||
for (uint16_t i = 0; i < nbytes; i++)
|
||||
sendData(TROTEC_ONE_MARK, TROTEC_ONE_SPACE, TROTEC_ZERO_MARK,
|
||||
TROTEC_ZERO_SPACE, data[i], 8, false);
|
||||
|
||||
// Footer
|
||||
mark(TROTEC_ONE_MARK);
|
||||
space(TROTEC_GAP);
|
||||
sendGeneric(TROTEC_HDR_MARK, TROTEC_HDR_SPACE,
|
||||
TROTEC_ONE_MARK, TROTEC_ONE_SPACE,
|
||||
TROTEC_ZERO_MARK, TROTEC_ZERO_SPACE,
|
||||
TROTEC_ONE_MARK, TROTEC_GAP,
|
||||
data, nbytes, 36, false, 0, // Repeats handled elsewhere
|
||||
50);
|
||||
// More footer
|
||||
enableIROut(36);
|
||||
mark(TROTEC_ONE_MARK);
|
||||
space(TROTEC_GAP_END);
|
||||
}
|
||||
}
|
||||
#endif // SEND_TROTEC
|
||||
|
||||
IRTrotecESP::IRTrotecESP(uint16_t pin) : _irsend(pin) {
|
||||
stateReset();
|
||||
@@ -49,10 +44,12 @@ void IRTrotecESP::begin() {
|
||||
_irsend.begin();
|
||||
}
|
||||
|
||||
#if SEND_TROTEC
|
||||
void IRTrotecESP::send() {
|
||||
checksum();
|
||||
_irsend.sendTrotec(trotec);
|
||||
}
|
||||
#endif // SEND_TROTEC
|
||||
|
||||
void IRTrotecESP::checksum() {
|
||||
uint8_t sum = 0;
|
||||
@@ -147,5 +144,3 @@ void IRTrotecESP::setTimer(uint8_t timer) {
|
||||
uint8_t IRTrotecESP::getTimer() {
|
||||
return trotec[6];
|
||||
}
|
||||
|
||||
#endif // SEND_TROTEC
|
||||
@@ -40,13 +40,14 @@
|
||||
#define TROTEC_MIN_TIMER 0
|
||||
#define TROTEC_MAX_TIMER 23
|
||||
|
||||
#if SEND_TROTEC
|
||||
|
||||
class IRTrotecESP {
|
||||
public:
|
||||
explicit IRTrotecESP(uint16_t pin);
|
||||
|
||||
#if SEND_TROTEC
|
||||
void send();
|
||||
#endif // SEND_TROTEC
|
||||
void begin();
|
||||
|
||||
void setPower(bool state);
|
||||
@@ -75,6 +76,5 @@ class IRTrotecESP {
|
||||
void checksum();
|
||||
IRsend _irsend;
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif // IR_TROTEC_H_
|
||||
@@ -4,7 +4,6 @@
|
||||
#include <algorithm>
|
||||
#include "IRrecv.h"
|
||||
#include "IRsend.h"
|
||||
#include "IRtimer.h"
|
||||
#include "IRutils.h"
|
||||
|
||||
// W W H H Y Y N N TTTTT EEEEE RRRRR
|
||||
@@ -54,22 +53,19 @@
|
||||
void IRsend::sendWhynter(uint64_t data, uint16_t nbits, uint16_t repeat) {
|
||||
// Set IR carrier frequency
|
||||
enableIROut(38);
|
||||
IRtimer usecTimer = IRtimer();
|
||||
|
||||
for (uint16_t i = 0; i <= repeat; i++) {
|
||||
usecTimer.reset();
|
||||
// Header
|
||||
// (Pre-)Header
|
||||
mark(WHYNTER_BIT_MARK);
|
||||
space(WHYNTER_ZERO_SPACE);
|
||||
mark(WHYNTER_HDR_MARK);
|
||||
space(WHYNTER_HDR_SPACE);
|
||||
// Data
|
||||
sendData(WHYNTER_BIT_MARK, WHYNTER_ONE_SPACE, WHYNTER_BIT_MARK,
|
||||
WHYNTER_ZERO_SPACE, data, nbits, true);
|
||||
// Footer
|
||||
mark(WHYNTER_BIT_MARK);
|
||||
space(std::max(WHYNTER_MIN_COMMAND_LENGTH - usecTimer.elapsed(),
|
||||
WHYNTER_MIN_GAP));
|
||||
sendGeneric(WHYNTER_HDR_MARK, WHYNTER_HDR_SPACE,
|
||||
WHYNTER_BIT_MARK, WHYNTER_ONE_SPACE,
|
||||
WHYNTER_BIT_MARK, WHYNTER_ZERO_SPACE,
|
||||
WHYNTER_BIT_MARK, WHYNTER_MIN_GAP,
|
||||
WHYNTER_MIN_COMMAND_LENGTH - (WHYNTER_BIT_MARK +
|
||||
WHYNTER_ZERO_SPACE),
|
||||
data, nbits, 38, true, 0, // Repeats are already handled.
|
||||
50);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in new issue
Block a user