Update IRRemoteESP8266 version to 2.5.5

factorize IR send signal function between the 2 types of receiving
Add new IRRemoteESP8266 protocols
replace pins on mega according schema
remove unecessaries spaces
This commit is contained in:
Florian committed 2019-02-08 16:50:37 +01:00
1 parent 0180a0dbd5
commit f0305236db
149 files changed
+20957 -7850

No files matched your search

+159 -144
View File
@@ -7,13 +7,14 @@
#include <stddef.h>
#ifndef UNIT_TEST
extern "C" {
#include <gpio.h>
#include <user_interface.h>
#include <gpio.h>
#include <user_interface.h>
}
#include <Arduino.h>
#endif
#include <algorithm>
#include "IRremoteESP8266.h"
#include "IRutils.h"
#ifdef UNIT_TEST
#undef ICACHE_RAM_ATTR
@@ -34,8 +35,7 @@ irparams_t *irparams_save; // A copy of the interrupt state while decoding.
#ifndef UNIT_TEST
static void ICACHE_RAM_ATTR read_timeout(void *arg __attribute__((unused))) {
os_intr_lock();
if (irparams.rawlen)
irparams.rcvstate = STATE_STOP;
if (irparams.rawlen) irparams.rcvstate = kStopState;
os_intr_unlock();
}
@@ -57,25 +57,24 @@ static void ICACHE_RAM_ATTR gpio_intr() {
if (rawlen >= irparams.bufsize) {
irparams.overflow = true;
irparams.rcvstate = STATE_STOP;
irparams.rcvstate = kStopState;
}
if (irparams.rcvstate == STATE_STOP)
return;
if (irparams.rcvstate == kStopState) return;
if (irparams.rcvstate == STATE_IDLE) {
irparams.rcvstate = STATE_MARK;
if (irparams.rcvstate == kIdleState) {
irparams.rcvstate = kMarkState;
irparams.rawbuf[rawlen] = 1;
} else {
if (now < start)
irparams.rawbuf[rawlen] = (UINT32_MAX - start + now) / RAWTICK;
irparams.rawbuf[rawlen] = (UINT32_MAX - start + now) / kRawTick;
else
irparams.rawbuf[rawlen] = (now - start) / RAWTICK;
irparams.rawbuf[rawlen] = (now - start) / kRawTick;
}
irparams.rawlen++;
start = now;
#define ONCE 0
#define ONCE 0
os_timer_arm(&timer, irparams.timeout, ONCE);
}
#endif // UNIT_TEST
@@ -85,9 +84,9 @@ static void ICACHE_RAM_ATTR gpio_intr() {
// Class constructor
// Args:
// recvpin: GPIO pin the IR receiver module's data pin is connected to.
// bufsize: Nr. of entries to have in the capture buffer. (Default: RAWBUF)
// bufsize: Nr. of entries to have in the capture buffer. (Default: kRawBuf)
// timeout: Nr. of milli-Seconds of no signal before we stop capturing data.
// (Default: TIMEOUT_MS)
// (Default: kTimeoutMs)
// save_buffer: Use a second (save) buffer to decode from. (Def: false)
// Returns:
// An IRrecv class object.
@@ -97,11 +96,12 @@ IRrecv::IRrecv(uint16_t recvpin, uint16_t bufsize, uint8_t timeout,
irparams.bufsize = bufsize;
// Ensure we are going to be able to store all possible values in the
// capture buffer.
irparams.timeout = std::min(timeout, (uint8_t) MAX_TIMEOUT_MS);
irparams.timeout = std::min(timeout, (uint8_t)kMaxTimeoutMs);
irparams.rawbuf = new uint16_t[bufsize];
if (irparams.rawbuf == NULL) {
DPRINTLN("Could not allocate memory for the primary IR buffer.\n"
"Try a smaller size for CAPTURE_BUFFER_SIZE.\nRebooting!");
DPRINTLN(
"Could not allocate memory for the primary IR buffer.\n"
"Try a smaller size for CAPTURE_BUFFER_SIZE.\nRebooting!");
#ifndef UNIT_TEST
ESP.restart(); // Mem alloc failure. Reboot.
#endif
@@ -112,8 +112,9 @@ IRrecv::IRrecv(uint16_t recvpin, uint16_t bufsize, uint8_t timeout,
irparams_save->rawbuf = new uint16_t[bufsize];
// Check we allocated the memory successfully.
if (irparams_save->rawbuf == NULL) {
DPRINTLN("Could not allocate memory for the second IR buffer.\n"
"Try a smaller size for CAPTURE_BUFFER_SIZE.\nRebooting!");
DPRINTLN(
"Could not allocate memory for the second IR buffer.\n"
"Try a smaller size for CAPTURE_BUFFER_SIZE.\nRebooting!");
#ifndef UNIT_TEST
ESP.restart(); // Mem alloc failure. Reboot.
#endif
@@ -122,15 +123,15 @@ IRrecv::IRrecv(uint16_t recvpin, uint16_t bufsize, uint8_t timeout,
irparams_save = NULL;
}
#if DECODE_HASH
unknown_threshold = UNKNOWN_THRESHOLD;
unknown_threshold = kUnknownThreshold;
#endif // DECODE_HASH
}
// Class destructor
IRrecv::~IRrecv(void) {
delete [] irparams.rawbuf;
delete[] irparams.rawbuf;
if (irparams_save != NULL) {
delete [] irparams_save->rawbuf;
delete[] irparams_save->rawbuf;
delete irparams_save;
}
}
@@ -159,7 +160,7 @@ void IRrecv::disableIRIn() {
}
void IRrecv::resume() {
irparams.rcvstate = STATE_IDLE;
irparams.rcvstate = kIdleState;
irparams.rawlen = 0;
irparams.overflow = false;
}
@@ -167,15 +168,15 @@ void IRrecv::resume() {
// Make a copy of the interrupt state & buffer data.
// Needed because irparams is marked as volatile, thus memcpy() isn't allowed.
// Only call this when you know the interrupt handlers won't modify anything.
// i.e. In STATE_STOP.
// i.e. In kStopState.
//
// Args:
// src: Pointer to an irparams_t structure to copy from.
// dst: Pointer to an irparams_t structure to copy to.
void IRrecv::copyIrParams(volatile irparams_t *src, irparams_t *dst) {
// Typecast src and dst addresses to (char *)
char *csrc = (char *) src; // NOLINT(readability/casting)
char *cdst = (char *) dst; // NOLINT(readability/casting)
char *csrc = (char *)src; // NOLINT(readability/casting)
char *cdst = (char *)dst; // NOLINT(readability/casting)
// Save the pointer to the destination's rawbuf so we don't lose it as
// the for-loop/copy after this will overwrite it with src's rawbuf pointer.
@@ -184,22 +185,18 @@ void IRrecv::copyIrParams(volatile irparams_t *src, irparams_t *dst) {
dst_rawbuf_ptr = dst->rawbuf;
// Copy contents of src[] to dst[]
for (uint16_t i = 0; i < sizeof(irparams_t); i++)
cdst[i] = csrc[i];
for (uint16_t i = 0; i < sizeof(irparams_t); i++) cdst[i] = csrc[i];
// Restore the buffer pointer
dst->rawbuf = dst_rawbuf_ptr;
// Copy the rawbuf
for (uint16_t i = 0; i < dst->bufsize; i++)
dst->rawbuf[i] = src->rawbuf[i];
for (uint16_t i = 0; i < dst->bufsize; i++) dst->rawbuf[i] = src->rawbuf[i];
}
// Obtain the maximum number of entries possible in the capture buffer.
// i.e. It's size.
uint16_t IRrecv::getBufSize() {
return irparams.bufsize;
}
uint16_t IRrecv::getBufSize() { return irparams.bufsize; }
#if DECODE_HASH
// Set the minimum length we will consider for reporting UNKNOWN message types.
@@ -223,8 +220,7 @@ void IRrecv::setUnknownThreshold(uint16_t length) {
bool IRrecv::decode(decode_results *results, irparams_t *save) {
// Proceed only if an IR message been received.
#ifndef UNIT_TEST
if (irparams.rcvstate != STATE_STOP)
return false;
if (irparams.rcvstate != kStopState) return false;
#endif
// Clear the entry we are currently pointing to when we got the timeout.
@@ -240,8 +236,7 @@ bool IRrecv::decode(decode_results *results, irparams_t *save) {
bool resumed = false; // Flag indicating if we have resumed.
// If we were requested to use a save buffer previously, do so.
if (save == NULL)
save = irparams_save;
if (save == NULL) save = irparams_save;
if (save == NULL) {
// We haven't been asked to copy it so use the existing memory.
@@ -273,8 +268,7 @@ bool IRrecv::decode(decode_results *results, irparams_t *save) {
// Try decodeAiwaRCT501() before decodeSanyoLC7461() & decodeNEC()
// because the protocols are similar. This protocol is more specific than
// those ones, so should got before them.
if (decodeAiwaRCT501(results))
return true;
if (decodeAiwaRCT501(results)) return true;
#endif
#if DECODE_SANYO
DPRINTLN("Attempting Sanyo LC7461 decode");
@@ -282,8 +276,7 @@ bool IRrecv::decode(decode_results *results, irparams_t *save) {
// similar in timings & structure, but the Sanyo one is much longer than the
// NEC protocol (42 vs 32 bits) so this one should be tried first to try to
// reduce false detection as a NEC packet.
if (decodeSanyoLC7461(results))
return true;
if (decodeSanyoLC7461(results)) return true;
#endif
#if DECODE_CARRIER_AC
DPRINTLN("Attempting Carrier AC decode");
@@ -291,141 +284,131 @@ bool IRrecv::decode(decode_results *results, irparams_t *save) {
// 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;
if (decodeCarrierAC(results)) return true;
#endif
#if DECODE_PIONEER
DPRINTLN("Attempting Pioneer decode");
// Try decodePioneer() before decodeNEC() because the protocols are
// similar in timings & structure, but the Pioneer one is much longer than the
// NEC protocol (2x32 bits vs 1x32 bits) so this one should be tried first to
// try to reduce false detection as a NEC packet.
if (decodePioneer(results)) return true;
#endif
#if DECODE_NEC
DPRINTLN("Attempting NEC decode");
if (decodeNEC(results))
return true;
if (decodeNEC(results)) return true;
#endif
#if DECODE_SONY
DPRINTLN("Attempting Sony decode");
if (decodeSony(results))
return true;
if (decodeSony(results)) return true;
#endif
#if DECODE_MITSUBISHI
DPRINTLN("Attempting Mitsubishi decode");
if (decodeMitsubishi(results))
return true;
if (decodeMitsubishi(results)) return true;
#endif
#if DECODE_MITSUBISHI_AC
DPRINTLN("Attempting Mitsubishi AC decode");
if (decodeMitsubishiAC(results)) return true;
#endif
#if DECODE_MITSUBISHI2
DPRINTLN("Attempting Mitsubishi2 decode");
if (decodeMitsubishi2(results))
return true;
if (decodeMitsubishi2(results)) return true;
#endif
#if DECODE_RC5
DPRINTLN("Attempting RC5 decode");
if (decodeRC5(results))
return true;
if (decodeRC5(results)) return true;
#endif
#if DECODE_RC6
DPRINTLN("Attempting RC6 decode");
if (decodeRC6(results))
return true;
if (decodeRC6(results)) return true;
#endif
#if DECODE_RCMM
DPRINTLN("Attempting RC-MM decode");
if (decodeRCMM(results))
return true;
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;
if (decodeFujitsuAC(results)) return true;
#endif
#if DECODE_DENON
// Denon needs to precede Panasonic as it is a special case of Panasonic.
DPRINTLN("Attempting Denon decode");
if (decodeDenon(results, DENON_48_BITS) ||
decodeDenon(results, DENON_BITS) ||
decodeDenon(results, DENON_LEGACY_BITS))
if (decodeDenon(results, DENON_48_BITS) || decodeDenon(results, DENON_BITS) ||
decodeDenon(results, kDenonLegacyBits))
return true;
#endif
#if DECODE_PANASONIC
DPRINTLN("Attempting Panasonic decode");
if (decodePanasonic(results))
return true;
if (decodePanasonic(results)) return true;
#endif
#if DECODE_LG
DPRINTLN("Attempting LG (28-bit) decode");
if (decodeLG(results, LG_BITS, true))
return true;
if (decodeLG(results, kLgBits, true)) return true;
DPRINTLN("Attempting LG (32-bit) decode");
// LG32 should be tried before Samsung
if (decodeLG(results, LG32_BITS, true))
return true;
if (decodeLG(results, kLg32Bits, 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;
if (decodeGICable(results)) return true;
#endif
#if DECODE_JVC
DPRINTLN("Attempting JVC decode");
if (decodeJVC(results))
return true;
if (decodeJVC(results)) return true;
#endif
#if DECODE_SAMSUNG
DPRINTLN("Attempting SAMSUNG decode");
if (decodeSAMSUNG(results))
return true;
if (decodeSAMSUNG(results)) return true;
#endif
#if DECODE_WHYNTER
DPRINTLN("Attempting Whynter decode");
if (decodeWhynter(results))
return true;
if (decodeWhynter(results)) return true;
#endif
#if DECODE_DISH
DPRINTLN("Attempting DISH decode");
if (decodeDISH(results))
return true;
if (decodeDISH(results)) return true;
#endif
#if DECODE_SHARP
DPRINTLN("Attempting Sharp decode");
if (decodeSharp(results))
return true;
if (decodeSharp(results)) return true;
#endif
#if DECODE_COOLIX
DPRINTLN("Attempting Coolix decode");
if (decodeCOOLIX(results))
return true;
if (decodeCOOLIX(results)) return true;
#endif
#if DECODE_NIKAI
DPRINTLN("Attempting Nikai decode");
if (decodeNikai(results))
return true;
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().
// 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;
if (decodeKelvinator(results)) return true;
#endif
#if DECODE_DAIKIN
DPRINTLN("Attempting Daikin decode");
if (decodeDaikin(results))
return true;
if (decodeDaikin(results)) return true;
#endif
#if DECODE_DAIKIN2
DPRINTLN("Attempting Daikin2 decode");
if (decodeDaikin2(results)) return true;
#endif
#if DECODE_TOSHIBA_AC
DPRINTLN("Attempting Toshiba AC decode");
if (decodeToshibaAC(results))
return true;
if (decodeToshibaAC(results)) return true;
#endif
#if DECODE_MIDEA
DPRINTLN("Attempting Midea decode");
if (decodeMidea(results))
return true;
if (decodeMidea(results)) return true;
#endif
#if DECODE_MAGIQUEST
DPRINTLN("Attempting Magiquest decode");
if (decodeMagiQuest(results))
return true;
if (decodeMagiQuest(results)) return true;
#endif
/* NOTE: Disabled due to poor quality.
#if DECODE_SANYO
@@ -444,43 +427,75 @@ bool IRrecv::decode(decode_results *results, irparams_t *save) {
// other protocols that are NEC-like as well, as turning off strict may
// cause this to match other valid protocols.
DPRINTLN("Attempting NEC (non-strict) decode");
if (decodeNEC(results, NEC_BITS, false)) {
if (decodeNEC(results, kNECBits, false)) {
results->decode_type = NEC_LIKE;
return true;
}
#endif
#if DECODE_LASERTAG
DPRINTLN("Attempting Lasertag decode");
if (decodeLasertag(results))
return true;
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;
if (decodeGree(results)) return true;
#endif
#if DECODE_HAIER_AC
DPRINTLN("Attempting Haier AC decode");
if (decodeHaierAC(results))
return true;
if (decodeHaierAC(results)) return true;
#endif
#if DECODE_HAIER_AC_YRW02
DPRINTLN("Attempting Haier AC YR-W02 decode");
if (decodeHaierACYRW02(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;
if (decodeHitachiAC(results, kHitachiAc2Bits)) return true;
#endif
#if DECODE_HITACHI_AC
DPRINTLN("Attempting Hitachi AC decode");
if (decodeHitachiAC(results, HITACHI_AC_BITS))
return true;
if (decodeHitachiAC(results, kHitachiAcBits)) return true;
#endif
#if DECODE_HITACHI_AC1
DPRINTLN("Attempting Hitachi AC1 decode");
if (decodeHitachiAC(results, HITACHI_AC1_BITS))
return true;
if (decodeHitachiAC(results, kHitachiAc1Bits)) return true;
#endif
#if DECODE_WHIRLPOOL_AC
DPRINTLN("Attempting Whirlpool AC decode");
if (decodeWhirlpoolAC(results)) return true;
#endif
#if DECODE_SAMSUNG_AC
DPRINTLN("Attempting Samsung AC (extended) decode");
// Check the extended size first, as it should fail fast due to longer length.
if (decodeSamsungAC(results, kSamsungAcExtendedBits, false)) return true;
// Now check for the more common length.
DPRINTLN("Attempting Samsung AC decode");
if (decodeSamsungAC(results, kSamsungAcBits)) return true;
#endif
#if DECODE_ELECTRA_AC
DPRINTLN("Attempting Electra AC decode");
if (decodeElectraAC(results)) return true;
#endif
#if DECODE_PANASONIC_AC
DPRINTLN("Attempting Panasonic AC decode");
if (decodePanasonicAC(results)) return true;
DPRINTLN("Attempting Panasonic AC short decode");
if (decodePanasonicAC(results, kPanasonicAcShortBits)) return true;
#endif
#if DECODE_LUTRON
DPRINTLN("Attempting Lutron decode");
if (decodeLutron(results)) return true;
#endif
#if DECODE_MWM
DPRINTLN("Attempting MWM decode");
if (decodeMWM(results)) return true;
#endif
#if DECODE_VESTEL_AC
DPRINTLN("Attempting Vestel AC decode");
if (decodeVestelAC(results)) return true;
#endif
#if DECODE_HASH
// decodeHash returns a hash on any input.
@@ -506,8 +521,8 @@ bool IRrecv::decode(decode_results *results, irparams_t *save) {
// Nr. of ticks.
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) - delta), 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.
@@ -519,7 +534,7 @@ uint32_t IRrecv::ticksLow(uint32_t usecs, uint8_t tolerance, uint16_t delta) {
// Returns:
// Nr. of ticks.
uint32_t IRrecv::ticksHigh(uint32_t usecs, uint8_t tolerance, uint16_t delta) {
return((uint32_t) (usecs * (1.0 + tolerance / 100.0)) + 1 + delta);
return ((uint32_t)(usecs * (1.0 + tolerance / 100.0)) + 1 + delta);
}
// Check if we match a pulse(measured) with the desired within
@@ -533,9 +548,9 @@ uint32_t IRrecv::ticksHigh(uint32_t usecs, uint8_t tolerance, uint16_t delta) {
//
// Returns:
// Boolean: true if it matches, false if it doesn't.
bool IRrecv::match(uint32_t measured, uint32_t desired,
uint8_t tolerance, uint16_t delta) {
measured *= RAWTICK; // Convert to uSecs.
bool IRrecv::match(uint32_t measured, uint32_t desired, uint8_t tolerance,
uint16_t delta) {
measured *= kRawTick; // Convert to uSecs.
DPRINT("Matching: ");
DPRINT(ticksLow(desired, tolerance, delta));
DPRINT(" <= ");
@@ -546,7 +561,6 @@ bool IRrecv::match(uint32_t measured, uint32_t desired,
measured <= ticksHigh(desired, tolerance, delta));
}
// Check if we match a pulse(measured) of at least desired within
// tolerance percent and/or a fixed delta margin.
//
@@ -561,7 +575,7 @@ bool IRrecv::match(uint32_t measured, uint32_t desired,
// Boolean: true if it matches, false if it doesn't.
bool IRrecv::matchAtLeast(uint32_t measured, uint32_t desired,
uint8_t tolerance, uint16_t delta) {
measured *= RAWTICK; // Convert to uSecs.
measured *= kRawTick; // Convert to uSecs.
DPRINT("Matching ATLEAST ");
DPRINT(measured);
DPRINT(" vs ");
@@ -594,10 +608,10 @@ bool IRrecv::matchAtLeast(uint32_t measured, uint32_t desired,
//
// Returns:
// Boolean: true if it matches, false if it doesn't.
bool IRrecv::matchMark(uint32_t measured, uint32_t desired,
uint8_t tolerance, int16_t excess) {
bool IRrecv::matchMark(uint32_t measured, uint32_t desired, uint8_t tolerance,
int16_t excess) {
DPRINT("Matching MARK ");
DPRINT(measured * RAWTICK);
DPRINT(measured * kRawTick);
DPRINT(" vs ");
DPRINT(desired);
DPRINT(" + ");
@@ -617,10 +631,10 @@ bool IRrecv::matchMark(uint32_t measured, uint32_t desired,
//
// Returns:
// Boolean: true if it matches, false if it doesn't.
bool IRrecv::matchSpace(uint32_t measured, uint32_t desired,
uint8_t tolerance, int16_t excess) {
bool IRrecv::matchSpace(uint32_t measured, uint32_t desired, uint8_t tolerance,
int16_t excess) {
DPRINT("Matching SPACE ");
DPRINT(measured * RAWTICK);
DPRINT(measured * kRawTick);
DPRINT(" vs ");
DPRINT(desired);
DPRINT(" - ");
@@ -662,9 +676,8 @@ int16_t IRrecv::compare(uint16_t oldval, uint16_t newval) {
*/
bool IRrecv::decodeHash(decode_results *results) {
// Require at least some samples to prevent triggering on noise
if (results->rawlen < unknown_threshold)
return false;
int32_t hash = FNV_BASIS_32;
if (results->rawlen < unknown_threshold) return false;
int32_t hash = kFnvBasis32;
// 'rawlen - 2' to avoid the look ahead from going out of bounds.
// Should probably be -3 to avoid comparing the trailing space entry,
// however it is left this way for compatibility with previously captured
@@ -672,7 +685,7 @@ bool IRrecv::decodeHash(decode_results *results) {
for (uint16_t i = 1; i < results->rawlen - 2; i++) {
int16_t value = compare(results->rawbuf[i], results->rawbuf[i + 2]);
// Add value into the hash
hash = (hash * FNV_PRIME_32) ^ value;
hash = (hash * kFnvPrime32) ^ value;
}
results->value = hash & 0xFFFFFFFF;
results->bits = results->rawlen / 2;
@@ -684,7 +697,8 @@ bool IRrecv::decodeHash(decode_results *results) {
#endif // DECODE_HASH
// Match & decode the typical data section of an IR message.
// The data value constructed as the Most Significant Bit first.
// The data value is stored in the least significant bits reguardless of the
// bit ordering requested.
//
// Args:
// data_ptr: A pointer to where we are at in the capture buffer.
@@ -693,34 +707,35 @@ 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.
// tolerance: Percentage error margin to allow. (Def: kTolerance)
// excess: Nr. of useconds. (Def: kMarkExcess)
// MSBfirst: Bit order to save the data in. (Def: true)
// 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,
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 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, const int16_t excess, const bool MSBfirst) {
match_result_t result;
result.success = false; // Fail by default.
result.data = 0;
for (result.used = 0;
result.used < nbits * 2;
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))
if (matchMark(*data_ptr, onemark, tolerance, excess) &&
matchSpace(*(data_ptr + 1), onespace, tolerance, excess)) {
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
} else if (matchMark(*data_ptr, zeromark, tolerance, excess) &&
matchSpace(*(data_ptr + 1), zerospace, tolerance, excess)) {
result.data <<= 1; // The bit is a '0'.
} else {
if (!MSBfirst) result.data = reverseBits(result.data, result.used / 2);
return result; // It's neither, so fail.
}
}
result.success = true;
if (!MSBfirst) result.data = reverseBits(result.data, nbits);
return result;
}
+146 -96
View File
@@ -15,59 +15,68 @@
#include "IRremoteESP8266.h"
// Constants
#define HEADER 2U // Usual nr. of header entries.
#define FOOTER 2U // Usual nr. of footer (stop bits) entries.
#define OFFSET_START 1U // Usual rawbuf entry to start processing from.
#define MS_TO_USEC(x) (x * 1000U) // Convert milli-Seconds to micro-Seconds.
const uint16_t kHeader = 2; // Usual nr. of header entries.
const uint16_t kFooter = 2; // Usual nr. of footer (stop bits) entries.
const uint16_t kStartOffset = 1; // Usual rawbuf entry to start from.
#define MS_TO_USEC(x) (x * 1000U) // Convert milli-Seconds to micro-Seconds.
// Marks tend to be 100us too long, and spaces 100us too short
// when received due to sensor lag.
#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.
const uint16_t kMarkExcess = 50;
const uint16_t kRawBuf = 100; // Default length of raw capture buffer
const uint64_t kRepeat = UINT64_MAX;
// Default min size of reported UNKNOWN messages.
const uint16_t kUnknownThreshold = 6;
// receiver states
#define STATE_IDLE 2U
#define STATE_MARK 3U
#define STATE_SPACE 4U
#define STATE_STOP 5U
#define TOLERANCE 25U // default percent tolerance in measurements
#define RAWTICK 2U // Capture tick to uSec factor.
const uint8_t kIdleState = 2;
const uint8_t kMarkState = 3;
const uint8_t kSpaceState = 4;
const uint8_t kStopState = 5;
const uint8_t kTolerance = 25; // default percent tolerance in measurements.
const uint16_t kRawTick = 2; // Capture tick to uSec factor.
#define RAWTICK kRawTick // Deprecated. For legacy user code support only.
// How long (ms) before we give up wait for more data?
// Don't exceed MAX_TIMEOUT_MS without a good reason.
// That is the capture buffers maximum value size. (UINT16_MAX / RAWTICK)
// Don't exceed kMaxTimeoutMs without a good reason.
// That is the capture buffers maximum value size. (UINT16_MAX / kRawTick)
// Typically messages/protocols tend to repeat around the 100ms timeframe,
// thus we should timeout before that to give us some time to try to decode
// before we need to start capturing a possible new message.
// Typically 15ms suits most applications. However, some protocols demand a
// higher value. e.g. 90ms for XMP-1 and some aircon units.
#define TIMEOUT_MS 15U // In MilliSeconds.
#define MAX_TIMEOUT_MS (RAWTICK * UINT16_MAX / MS_TO_USEC(1))
const uint8_t kTimeoutMs = 15; // In MilliSeconds.
#define TIMEOUT_MS kTimeoutMs // For legacy documentation.
const uint16_t kMaxTimeoutMs = kRawTick * (UINT16_MAX / MS_TO_USEC(1));
// Use FNV hash algorithm: http://isthe.com/chongo/tech/comp/fnv/#FNV-param
#define FNV_PRIME_32 16777619UL
#define FNV_BASIS_32 2166136261UL
const uint32_t kFnvPrime32 = 16777619UL;
const uint32_t kFnvBasis32 = 2166136261UL;
#if DECODE_AC
// Hitachi AC is the current largest state size.
#define STATE_SIZE_MAX HITACHI_AC2_STATE_LENGTH
const uint16_t kStateSizeMax = kHitachiAc2StateLength;
#else
// Just define something
const uint16_t kStateSizeMax = 0;
#endif
// Types
// information for the interrupt handler
typedef struct {
uint8_t recvpin; // pin for IR data from detector
uint8_t rcvstate; // state machine
uint16_t timer; // state timer, counts 50uS ticks.
uint16_t bufsize; // max. nr. of entries in the capture buffer.
uint16_t *rawbuf; // raw data
uint8_t recvpin; // pin for IR data from detector
uint8_t rcvstate; // state machine
uint16_t timer; // state timer, counts 50uS ticks.
uint16_t bufsize; // max. nr. of entries in the capture buffer.
uint16_t *rawbuf; // raw data
// uint16_t is used for rawlen as it saves 3 bytes of iram in the interrupt
// handler. Don't ask why, I don't know. It just does.
uint16_t rawlen; // counter of entries in rawbuf.
uint8_t overflow; // Buffer overflow indicator.
uint8_t timeout; // Nr. of milliSeconds before we give up.
uint16_t rawlen; // counter of entries in rawbuf.
uint8_t overflow; // Buffer overflow indicator.
uint8_t timeout; // Nr. of milliSeconds before we give up.
} irparams_t;
// results from a data match
typedef struct {
bool success; // Was the match successful?
bool success; // Was the match successful?
uint64_t data; // The data found.
uint16_t used; // How many buffer positions were used.
} match_result_t;
@@ -83,17 +92,15 @@ class decode_results {
// structure to save us a handful of valuable bytes of memory.
union {
struct {
uint64_t value; // Decoded value
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
uint8_t state[kStateSizeMax]; // Multi-byte results.
};
uint16_t bits; // Number of bits in decoded value
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.
uint16_t rawlen; // Number of records in rawbuf.
bool overflow;
bool repeat; // Is the result a repeat code?
};
@@ -101,10 +108,10 @@ class decode_results {
// main class for receiving IR
class IRrecv {
public:
explicit IRrecv(uint16_t recvpin, uint16_t bufsize = RAWBUF,
uint8_t timeout = TIMEOUT_MS,
explicit IRrecv(uint16_t recvpin, uint16_t bufsize = kRawBuf,
uint8_t timeout = kTimeoutMs,
bool save_buffer = false); // Constructor
~IRrecv(); // Destructor
~IRrecv(); // Destructor
bool decode(decode_results *results, irparams_t *save = NULL);
void enableIRIn();
void disableIRIn();
@@ -114,13 +121,13 @@ class IRrecv {
void setUnknownThreshold(uint16_t length);
#endif
static bool match(uint32_t measured, uint32_t desired,
uint8_t tolerance = TOLERANCE, uint16_t delta = 0);
uint8_t tolerance = kTolerance, uint16_t delta = 0);
static bool matchMark(uint32_t measured, uint32_t desired,
uint8_t tolerance = TOLERANCE,
int16_t excess = MARK_EXCESS);
uint8_t tolerance = kTolerance,
int16_t excess = kMarkExcess);
static bool matchSpace(uint32_t measured, uint32_t desired,
uint8_t tolerance = TOLERANCE,
int16_t excess = MARK_EXCESS);
uint8_t tolerance = kTolerance,
int16_t excess = kMarkExcess);
#ifndef UNIT_TEST
private:
@@ -132,84 +139,93 @@ class IRrecv {
// These are called by decode
void copyIrParams(volatile irparams_t *src, irparams_t *dst);
int16_t compare(uint16_t oldval, uint16_t newval);
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,
static uint32_t ticksLow(uint32_t usecs, uint8_t tolerance = kTolerance,
uint16_t delta = 0);
static uint32_t ticksHigh(uint32_t usecs, uint8_t tolerance = kTolerance,
uint16_t delta = 0);
bool matchAtLeast(uint32_t measured, uint32_t desired,
uint8_t tolerance = TOLERANCE, uint16_t delta = 0);
uint8_t tolerance = kTolerance, 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);
const uint8_t tolerance = kTolerance,
const int16_t excess = kMarkExcess,
const bool MSBfirst = true);
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,
bool decodeNEC(decode_results *results, uint16_t nbits = kNECBits,
bool strict = true);
#endif
#if DECODE_SONY
bool decodeSony(decode_results *results, uint16_t nbits = SONY_MIN_BITS,
bool decodeSony(decode_results *results, uint16_t nbits = kSonyMinBits,
bool strict = false);
#endif
#if DECODE_SANYO
// DISABLED due to poor quality.
// bool decodeSanyo(decode_results *results,
// uint16_t nbits = SANYO_SA8650B_BITS,
// uint16_t nbits = kSanyoSA8650BBits,
// bool strict = false);
bool decodeSanyoLC7461(decode_results *results,
uint16_t nbits = SANYO_LC7461_BITS,
bool strict = true);
uint16_t nbits = kSanyoLC7461Bits, bool strict = true);
#endif
#if DECODE_MITSUBISHI
bool decodeMitsubishi(decode_results *results,
uint16_t nbits = MITSUBISHI_BITS,
bool strict = true);
uint16_t nbits = kMitsubishiBits, bool strict = true);
#endif
#if DECODE_MITSUBISHI2
bool decodeMitsubishi2(decode_results *results,
uint16_t nbits = MITSUBISHI_BITS,
bool strict = true);
uint16_t nbits = kMitsubishiBits, bool strict = true);
#endif
#if (DECODE_RC5 || DECODE_R6 || DECODE_LASERTAG)
#if DECODE_MITSUBISHI_AC
bool decodeMitsubishiAC(decode_results *results,
uint16_t nbits = kMitsubishiACBits,
bool strict = false);
#endif
#if (DECODE_RC5 || DECODE_R6 || DECODE_LASERTAG || DECODE_MWM)
int16_t getRClevel(decode_results *results, uint16_t *offset, uint16_t *used,
uint16_t bitTime, uint8_t tolerance = TOLERANCE,
int16_t excess = MARK_EXCESS, uint16_t delta = 0);
uint16_t bitTime, uint8_t tolerance = kTolerance,
int16_t excess = kMarkExcess, uint16_t delta = 0,
uint8_t maxwidth = 3);
#endif
#if DECODE_RC5
bool decodeRC5(decode_results *results, uint16_t nbits = RC5X_BITS,
bool decodeRC5(decode_results *results, uint16_t nbits = kRC5XBits,
bool strict = true);
#endif
#if DECODE_RC6
bool decodeRC6(decode_results *results, uint16_t nbits = RC6_MODE0_BITS,
bool decodeRC6(decode_results *results, uint16_t nbits = kRC6Mode0Bits,
bool strict = false);
#endif
#if DECODE_RCMM
bool decodeRCMM(decode_results *results, uint16_t nbits = RCMM_BITS,
bool decodeRCMM(decode_results *results, uint16_t nbits = kRCMMBits,
bool strict = false);
#endif
#if (DECODE_PANASONIC || DECODE_DENON)
bool decodePanasonic(decode_results *results, uint16_t nbits = PANASONIC_BITS,
bool decodePanasonic(decode_results *results, uint16_t nbits = kPanasonicBits,
bool strict = false,
uint32_t manufacturer = PANASONIC_MANUFACTURER);
uint32_t manufacturer = kPanasonicManufacturer);
#endif
#if DECODE_LG
bool decodeLG(decode_results *results, uint16_t nbits = LG_BITS,
bool decodeLG(decode_results *results, uint16_t nbits = kLgBits,
bool strict = false);
#endif
#if DECODE_JVC
bool decodeJVC(decode_results *results, uint16_t nbits = JVC_BITS,
bool decodeJVC(decode_results *results, uint16_t nbits = kJvcBits,
bool strict = true);
#endif
#if DECODE_SAMSUNG
bool decodeSAMSUNG(decode_results *results, uint16_t nbits = SAMSUNG_BITS,
bool decodeSAMSUNG(decode_results *results, uint16_t nbits = kSamsungBits,
bool strict = true);
#endif
#if DECODE_SAMSUNG_AC
bool decodeSamsungAC(decode_results *results, uint16_t nbits = kSamsungAcBits,
bool strict = true);
#endif
#if DECODE_WHYNTER
bool decodeWhynter(decode_results *results, uint16_t nbits = WHYNTER_BITS,
bool decodeWhynter(decode_results *results, uint16_t nbits = kWhynterBits,
bool strict = true);
#endif
#if DECODE_COOLIX
bool decodeCOOLIX(decode_results *results, uint16_t nbits = COOLIX_BITS,
bool decodeCOOLIX(decode_results *results, uint16_t nbits = kCoolixBits,
bool strict = true);
#endif
#if DECODE_DENON
@@ -217,77 +233,111 @@ class IRrecv {
bool strict = true);
#endif
#if DECODE_DISH
bool decodeDISH(decode_results *results, uint16_t nbits = DISH_BITS,
bool decodeDISH(decode_results *results, uint16_t nbits = kDishBits,
bool strict = true);
#endif
#if (DECODE_SHARP || DECODE_DENON)
bool decodeSharp(decode_results *results, uint16_t nbits = SHARP_BITS,
bool decodeSharp(decode_results *results, uint16_t nbits = kSharpBits,
bool strict = true, bool expansion = true);
#endif
#if DECODE_AIWA_RC_T501
bool decodeAiwaRCT501(decode_results *results,
uint16_t nbits = AIWA_RC_T501_BITS, bool strict = true);
uint16_t nbits = kAiwaRcT501Bits, bool strict = true);
#endif
#if DECODE_NIKAI
bool decodeNikai(decode_results *results, uint16_t nbits = NIKAI_BITS,
bool decodeNikai(decode_results *results, uint16_t nbits = kNikaiBits,
bool strict = true);
#endif
#if DECODE_MAGIQUEST
bool decodeMagiQuest(decode_results *results, uint16_t nbits = MAGIQUEST_BITS,
bool decodeMagiQuest(decode_results *results, uint16_t nbits = kMagiquestBits,
bool strict = true);
#endif
#if DECODE_KELVINATOR
bool decodeKelvinator(decode_results *results,
uint16_t nbits = KELVINATOR_BITS,
bool strict = true);
uint16_t nbits = kKelvinatorBits, bool strict = true);
#endif
#if DECODE_DAIKIN
bool decodeDaikin(decode_results *results, uint16_t nbits = DAIKIN_RAW_BITS,
bool decodeDaikin(decode_results *results, uint16_t nbits = kDaikinRawBits,
bool strict = true);
#endif
#if DECODE_DAIKIN2
bool decodeDaikin2(decode_results *results, uint16_t nbits = kDaikin2Bits,
bool strict = true);
#endif
#if DECODE_TOSHIBA_AC
bool decodeToshibaAC(decode_results *results,
uint16_t nbytes = TOSHIBA_AC_BITS,
bool strict = true);
uint16_t nbytes = kToshibaACBits, bool strict = true);
#endif
#if DECODE_MIDEA
bool decodeMidea(decode_results *results, uint16_t nbits = MIDEA_BITS,
bool decodeMidea(decode_results *results, uint16_t nbits = kMideaBits,
bool strict = true);
#endif
#if DECODE_FUJITSU_AC
bool decodeFujitsuAC(decode_results *results,
uint16_t nbits = FUJITSU_AC_BITS,
bool decodeFujitsuAC(decode_results *results, uint16_t nbits = kFujitsuAcBits,
bool strict = false);
#endif
#if DECODE_LASERTAG
bool decodeLasertag(decode_results *results, uint16_t nbits = LASERTAG_BITS,
bool decodeLasertag(decode_results *results, uint16_t nbits = kLasertagBits,
bool strict = true);
#endif
#if DECODE_CARRIER_AC
bool decodeCarrierAC(decode_results *results,
uint16_t nbits = CARRIER_AC_BITS,
bool decodeCarrierAC(decode_results *results, uint16_t nbits = kCarrierAcBits,
bool strict = true);
#endif
#if DECODE_GREE
bool decodeGree(decode_results *results,
uint16_t nbits = GREE_BITS, bool strict = true);
bool decodeGree(decode_results *results, uint16_t nbits = kGreeBits,
bool strict = true);
#endif
#if DECODE_HAIER_AC
bool decodeHaierAC(decode_results *results,
uint16_t nbits = HAIER_AC_BITS, bool strict = true);
#if (DECODE_HAIER_AC | DECODE_HAIER_AC_YRW02)
bool decodeHaierAC(decode_results *results, uint16_t nbits = kHaierACBits,
bool strict = true);
#endif
#if DECODE_HAIER_AC_YRW02
bool decodeHaierACYRW02(decode_results *results,
uint16_t nbits = kHaierACYRW02Bits,
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);
bool decodeHitachiAC(decode_results *results, uint16_t nbits = kHitachiAcBits,
bool strict = true);
#endif
#if DECODE_HITACHI_AC1
bool decodeHitachiAC1(decode_results *results,
uint16_t nbits = HITACHI_AC1_BITS, bool strict = true);
uint16_t nbits = kHitachiAc1Bits, bool strict = true);
#endif
#if DECODE_GICABLE
bool decodeGICable(decode_results *results, uint16_t nbits = GICABLE_BITS,
bool decodeGICable(decode_results *results, uint16_t nbits = kGicableBits,
bool strict = true);
#endif
#if DECODE_WHIRLPOOL_AC
bool decodeWhirlpoolAC(decode_results *results,
uint16_t nbits = kWhirlpoolAcBits, bool strict = true);
#endif
#if DECODE_LUTRON
bool decodeLutron(decode_results *results, uint16_t nbits = kLutronBits,
bool strict = true);
#endif
#if DECODE_ELECTRA_AC
bool decodeElectraAC(decode_results *results, uint16_t nbits = kElectraAcBits,
bool strict = true);
#endif
#if DECODE_PANASONIC_AC
bool decodePanasonicAC(decode_results *results,
uint16_t nbits = kPanasonicAcBits, bool strict = true);
#endif
#if DECODE_PIONEER
bool decodePioneer(decode_results *results,
const uint16_t nbits = kPioneerBits,
const bool strict = true);
#endif
#if DECODE_MWM
bool decodeMWM(decode_results *results, uint16_t nbits = 24,
bool strict = true);
#endif
#if DECODE_VESTEL_AC
bool decodeVestelAC(decode_results *results, uint16_t nbits = kVestelACBits,
bool strict = true);
#endif
};
#endif // IRRECV_H_
+304 -169
View File
@@ -33,7 +33,8 @@
* (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
* Carrier & Haier AC code by crankyoldgit
* Vestel AC code by Erdem U. Altınyurt
*
* GPL license, all text above must be included in any redistribution
****************************************************/
@@ -48,137 +49,169 @@
#endif
// Library Version
#define _IRREMOTEESP8266_VERSION_ "2.4.2"
#define _IRREMOTEESP8266_VERSION_ "2.5.5"
// 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 DECODE_HASH true // Semi-unique code for unknown messages
#define SEND_RAW true
#define SEND_RAW true
#define DECODE_NEC true
#define SEND_NEC true
#define DECODE_NEC true
#define SEND_NEC true
#define DECODE_SHERWOOD true // Doesn't exist. Actually is DECODE_NEC
#define SEND_SHERWOOD true
#define DECODE_SHERWOOD true // Doesn't exist. Actually is DECODE_NEC
#define SEND_SHERWOOD true
#define DECODE_RC5 true
#define SEND_RC5 true
#define DECODE_RC5 true
#define SEND_RC5 true
#define DECODE_RC6 true
#define SEND_RC6 true
#define DECODE_RC6 true
#define SEND_RC6 true
#define DECODE_RCMM true
#define SEND_RCMM true
#define DECODE_RCMM true
#define SEND_RCMM true
#define DECODE_SONY true
#define SEND_SONY true
#define DECODE_SONY true
#define SEND_SONY true
#define DECODE_PANASONIC true
#define SEND_PANASONIC true
#define DECODE_PANASONIC true
#define SEND_PANASONIC true
#define DECODE_JVC true
#define SEND_JVC true
#define DECODE_JVC true
#define SEND_JVC true
#define DECODE_SAMSUNG true
#define SEND_SAMSUNG true
#define DECODE_SAMSUNG true
#define SEND_SAMSUNG true
#define DECODE_WHYNTER true
#define SEND_WHYNTER true
#define DECODE_SAMSUNG_AC true
#define SEND_SAMSUNG_AC true
#define DECODE_AIWA_RC_T501 true
#define SEND_AIWA_RC_T501 true
#define DECODE_WHYNTER true
#define SEND_WHYNTER true
#define DECODE_LG true
#define SEND_LG true
#define DECODE_AIWA_RC_T501 true
#define SEND_AIWA_RC_T501 true
#define DECODE_SANYO true
#define SEND_SANYO true
#define DECODE_LG true
#define SEND_LG true
#define DECODE_MITSUBISHI true
#define SEND_MITSUBISHI true
#define DECODE_SANYO true
#define SEND_SANYO true
#define DECODE_MITSUBISHI2 true
#define SEND_MITSUBISHI2 true
#define DECODE_MITSUBISHI true
#define SEND_MITSUBISHI true
#define DECODE_DISH true
#define SEND_DISH true
#define DECODE_MITSUBISHI2 true
#define SEND_MITSUBISHI2 true
#define DECODE_SHARP true
#define SEND_SHARP true
#define DECODE_DISH true
#define SEND_DISH true
#define DECODE_DENON true
#define SEND_DENON true
#define DECODE_SHARP true
#define SEND_SHARP true
#define DECODE_KELVINATOR true
#define SEND_KELVINATOR true
#define DECODE_DENON true
#define SEND_DENON true
#define DECODE_MITSUBISHI_AC false // Not written.
#define SEND_MITSUBISHI_AC true
#define DECODE_KELVINATOR true
#define SEND_KELVINATOR true
#define DECODE_FUJITSU_AC true
#define SEND_FUJITSU_AC true
#define DECODE_MITSUBISHI_AC true // Beta.
#define SEND_MITSUBISHI_AC true
#define DECODE_DAIKIN true
#define SEND_DAIKIN true
#define DECODE_FUJITSU_AC true
#define SEND_FUJITSU_AC true
#define DECODE_COOLIX true
#define SEND_COOLIX true
#define DECODE_DAIKIN true
#define SEND_DAIKIN true
#define DECODE_GLOBALCACHE false // Not written.
#define SEND_GLOBALCACHE true
#define DECODE_COOLIX true
#define SEND_COOLIX true
#define DECODE_GREE true
#define SEND_GREE true
#define DECODE_GLOBALCACHE false // Not written.
#define SEND_GLOBALCACHE true
#define DECODE_PRONTO false // Not written.
#define SEND_PRONTO true
#define DECODE_GREE true
#define SEND_GREE true
#define DECODE_ARGO false // Not written.
#define SEND_ARGO true
#define DECODE_PRONTO false // Not written.
#define SEND_PRONTO true
#define DECODE_TROTEC false // Not implemented.
#define SEND_TROTEC true
#define DECODE_ARGO false // Not written.
#define SEND_ARGO true
#define DECODE_NIKAI true
#define SEND_NIKAI true
#define DECODE_TROTEC false // Not implemented.
#define SEND_TROTEC true
#define DECODE_TOSHIBA_AC true
#define SEND_TOSHIBA_AC true
#define DECODE_NIKAI true
#define SEND_NIKAI true
#define DECODE_MAGIQUEST true
#define SEND_MAGIQUEST true
#define DECODE_TOSHIBA_AC true
#define SEND_TOSHIBA_AC true
#define DECODE_MIDEA true
#define SEND_MIDEA true
#define DECODE_MAGIQUEST true
#define SEND_MAGIQUEST true
#define DECODE_LASERTAG true
#define SEND_LASERTAG true
#define DECODE_MIDEA true
#define SEND_MIDEA true
#define DECODE_CARRIER_AC true
#define SEND_CARRIER_AC true
#define DECODE_LASERTAG true
#define SEND_LASERTAG true
#define DECODE_HAIER_AC true
#define SEND_HAIER_AC true
#define DECODE_CARRIER_AC true
#define SEND_CARRIER_AC true
#define DECODE_HITACHI_AC true
#define SEND_HITACHI_AC true
#define DECODE_HAIER_AC true
#define SEND_HAIER_AC true
#define DECODE_HITACHI_AC1 true
#define SEND_HITACHI_AC1 true
#define DECODE_HITACHI_AC true
#define SEND_HITACHI_AC true
#define DECODE_HITACHI_AC2 true
#define SEND_HITACHI_AC2 true
#define DECODE_HITACHI_AC1 true
#define SEND_HITACHI_AC1 true
#define DECODE_GICABLE true
#define SEND_GICABLE true
#define DECODE_HITACHI_AC2 true
#define SEND_HITACHI_AC2 true
#define DECODE_GICABLE true
#define SEND_GICABLE true
#define DECODE_HAIER_AC_YRW02 true
#define SEND_HAIER_AC_YRW02 true
#define DECODE_WHIRLPOOL_AC true
#define SEND_WHIRLPOOL_AC true
#define DECODE_LUTRON true
#define SEND_LUTRON true
#define DECODE_ELECTRA_AC true
#define SEND_ELECTRA_AC true
#define DECODE_PANASONIC_AC true
#define SEND_PANASONIC_AC true
#define DECODE_MWM true
#define SEND_MWM true
#define DECODE_PIONEER true
#define SEND_PIONEER true
#define DECODE_DAIKIN2 true
#define SEND_DAIKIN2 true
#define DECODE_VESTEL_AC true
#define SEND_VESTEL_AC 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)
DECODE_HITACHI_AC1 || DECODE_HITACHI_AC2 || DECODE_HAIER_AC_YRW02 || \
DECODE_WHIRLPOOL_AC || DECODE_SAMSUNG_AC || DECODE_ELECTRA_AC || \
DECODE_PANASONIC_AC || DECODE_MWM || DECODE_DAIKIN2 || DECODE_VESTEL_AC )
#define DECODE_AC true // We need some common infrastructure for decoding A/Cs.
#else
#define DECODE_AC false // We don't need that infrastructure.
@@ -202,127 +235,229 @@ enum decode_type_t {
RC6,
NEC,
SONY,
PANASONIC,
PANASONIC, // (5)
JVC,
SAMSUNG,
WHYNTER,
AIWA_RC_T501,
LG,
LG, // (10)
SANYO,
MITSUBISHI,
DISH,
SHARP,
COOLIX,
COOLIX, // (15)
DAIKIN,
DENON,
KELVINATOR,
SHERWOOD,
MITSUBISHI_AC,
MITSUBISHI_AC, // (20)
RCMM,
SANYO_LC7461,
RC5X,
GREE,
PRONTO, // Technically not a protocol, but an encoding.
PRONTO, // Technically not a protocol, but an encoding. (25)
NEC_LIKE,
ARGO,
TROTEC,
NIKAI,
RAW, // Technically not a protocol, but an encoding.
RAW, // Technically not a protocol, but an encoding. (30)
GLOBALCACHE, // Technically not a protocol, but an encoding.
TOSHIBA_AC,
FUJITSU_AC,
MIDEA,
MAGIQUEST,
MAGIQUEST, // (35)
LASERTAG,
CARRIER_AC,
HAIER_AC,
MITSUBISHI2,
HITACHI_AC,
HITACHI_AC, // (40)
HITACHI_AC1,
HITACHI_AC2,
GICABLE
GICABLE,
HAIER_AC_YRW02,
WHIRLPOOL_AC, // (45)
SAMSUNG_AC,
LUTRON,
ELECTRA_AC,
PANASONIC_AC,
PIONEER, // 50
LG2,
MWM,
DAIKIN2,
VESTEL_AC,
};
// Message lengths & required repeat values
#define AIWA_RC_T501_BITS 15U
#define AIWA_RC_T501_MIN_REPEAT 1U
#define COOLIX_BITS 24U
#define CARRIER_AC_BITS 32U
#define CARRIER_AC_MIN_REPEAT 0U
const uint16_t kNoRepeat = 0;
const uint16_t kSingleRepeat = 1;
const uint16_t kAiwaRcT501Bits = 15;
const uint16_t kAiwaRcT501MinRepeats = kSingleRepeat;
const uint16_t kArgoStateLength = 12;
const uint16_t kArgoDefaultRepeat = kNoRepeat;
const uint16_t kCoolixBits = 24;
const uint16_t kCoolixDefaultRepeat = kNoRepeat;
const uint16_t kCarrierAcBits = 32;
const uint16_t kCarrierAcMinRepeat = kNoRepeat;
// 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
const uint16_t kDaikinRawBits = 583;
const uint16_t kDaikinStateLength = 27;
const uint16_t kDaikinBits = kDaikinStateLength * 8;
const uint16_t kDaikinDefaultRepeat = kNoRepeat;
const uint16_t kDaikin2StateLength = 39;
const uint16_t kDaikin2Bits = kDaikin2StateLength * 8;
const uint16_t kDaikin2DefaultRepeat = kNoRepeat;
const uint16_t kDenonBits = 15;
const uint16_t kDenonLegacyBits = 14;
const uint16_t kDishBits = 16;
const uint16_t kDishMinRepeat = 3;
const uint16_t kElectraAcStateLength = 13;
const uint16_t kElectraAcBits = kElectraAcStateLength * 8;
const uint16_t kFujitsuAcMinRepeat = kNoRepeat;
const uint16_t kFujitsuAcStateLength = 16;
const uint16_t kFujitsuAcStateLengthShort = 7;
const uint16_t kFujitsuAcBits = kFujitsuAcStateLength * 8;
const uint16_t kFujitsuAcMinBits = (kFujitsuAcStateLengthShort - 1) * 8;
const uint16_t kGicableBits = 16;
const uint16_t kGicableMinRepeat = kSingleRepeat;
const uint16_t kGreeStateLength = 8;
const uint16_t kGreeBits = kGreeStateLength * 8;
const uint16_t kGreeDefaultRepeat = kNoRepeat;
const uint16_t kHaierACStateLength = 9;
const uint16_t kHaierACBits = kHaierACStateLength * 8;
const uint16_t kHaierAcDefaultRepeat = kNoRepeat;
const uint16_t kHaierACYRW02StateLength = 14;
const uint16_t kHaierACYRW02Bits = kHaierACYRW02StateLength * 8;
const uint16_t kHaierAcYrw02DefaultRepeat = kNoRepeat;
const uint16_t kHitachiAcStateLength = 28;
const uint16_t kHitachiAcBits = kHitachiAcStateLength * 8;
const uint16_t kHitachiAcDefaultRepeat = kNoRepeat;
const uint16_t kHitachiAc1StateLength = 13;
const uint16_t kHitachiAc1Bits = kHitachiAc1StateLength * 8;
const uint16_t kHitachiAc2StateLength = 53;
const uint16_t kHitachiAc2Bits = kHitachiAc2StateLength * 8;
const uint16_t kJvcBits = 16;
const uint16_t kKelvinatorStateLength = 16;
const uint16_t kKelvinatorBits = kKelvinatorStateLength * 8;
const uint16_t kKelvinatorDefaultRepeat = kNoRepeat;
const uint16_t kLasertagBits = 13;
const uint16_t kLasertagMinRepeat = kNoRepeat;
const uint16_t kLgBits = 28;
const uint16_t kLg32Bits = 32;
const uint16_t kLutronBits = 35;
const uint16_t kMagiquestBits = 56;
const uint16_t kMideaBits = 48;
const uint16_t kMideaMinRepeat = kNoRepeat;
const uint16_t kMitsubishiBits = 16;
// TODO(anyone): Verify that the Mitsubishi repeat is really needed.
#define MITSUBISHI_MIN_REPEAT 1U // Based on marcosamarinho's code.
#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
#define PRONTO_MIN_LENGTH 6U
#define RC5_RAW_BITS 14U
#define RC5_BITS RC5_RAW_BITS - 2U
#define RC5X_BITS RC5_RAW_BITS - 1U
#define RC6_MODE0_BITS 20U // Excludes the 'start' bit.
#define RC6_36_BITS 36U // Excludes the 'start' bit.
#define RCMM_BITS 24U
#define SAMSUNG_BITS 32U
#define SANYO_SA8650B_BITS 12U
#define SANYO_LC7461_ADDRESS_BITS 13U
#define SANYO_LC7461_COMMAND_BITS 8U
#define SANYO_LC7461_BITS ((SANYO_LC7461_ADDRESS_BITS + \
SANYO_LC7461_COMMAND_BITS) * 2)
#define SHARP_ADDRESS_BITS 5U
#define SHARP_COMMAND_BITS 8U
#define SHARP_BITS (SHARP_ADDRESS_BITS + SHARP_COMMAND_BITS + 2) // 15U
#define SHERWOOD_BITS NEC_BITS
#define SHERWOOD_MIN_REPEAT 1U
#define SONY_12_BITS 12U
#define SONY_15_BITS 15U
#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
// Based on marcosamarinho's code.
const uint16_t kMitsubishiMinRepeat = kSingleRepeat;
const uint16_t kMitsubishiACStateLength = 18;
const uint16_t kMitsubishiACBits = kMitsubishiACStateLength * 8;
const uint16_t kMitsubishiACMinRepeat = kSingleRepeat;
const uint16_t kNikaiBits = 24;
const uint16_t kNECBits = 32;
const uint16_t kPanasonicBits = 48;
const uint32_t kPanasonicManufacturer = 0x4004;
const uint16_t kPanasonicAcStateLength = 27;
const uint16_t kPanasonicAcStateShortLength = 16;
const uint16_t kPanasonicAcBits = kPanasonicAcStateLength * 8;
const uint16_t kPanasonicAcShortBits = kPanasonicAcStateShortLength * 8;
const uint16_t kPanasonicAcDefaultRepeat = kNoRepeat;
const uint16_t kPioneerBits = 64;
const uint16_t kProntoMinLength = 6;
const uint16_t kRC5RawBits = 14;
const uint16_t kRC5Bits = kRC5RawBits - 2;
const uint16_t kRC5XBits = kRC5RawBits - 1;
const uint16_t kRC6Mode0Bits = 20; // Excludes the 'start' bit.
const uint16_t kRC6_36Bits = 36; // Excludes the 'start' bit.
const uint16_t kRCMMBits = 24;
const uint16_t kSamsungBits = 32;
const uint16_t kSamsungAcStateLength = 14;
const uint16_t kSamsungAcBits = kSamsungAcStateLength * 8;
const uint16_t kSamsungAcExtendedStateLength = 21;
const uint16_t kSamsungAcExtendedBits = kSamsungAcExtendedStateLength * 8;
const uint16_t kSamsungAcDefaultRepeat = kNoRepeat;
const uint16_t kSanyoSA8650BBits = 12;
const uint16_t kSanyoLC7461AddressBits = 13;
const uint16_t kSanyoLC7461CommandBits = 8;
const uint16_t kSanyoLC7461Bits = (kSanyoLC7461AddressBits +
kSanyoLC7461CommandBits) * 2;
const uint8_t kSharpAddressBits = 5;
const uint8_t kSharpCommandBits = 8;
const uint16_t kSharpBits = kSharpAddressBits + kSharpCommandBits + 2; // 15
const uint8_t kSherwoodBits = kNECBits;
const uint16_t kSherwoodMinRepeat = kSingleRepeat;
const uint16_t kSony12Bits = 12;
const uint16_t kSony15Bits = 15;
const uint16_t kSony20Bits = 20;
const uint16_t kSonyMinBits = 12;
const uint16_t kSonyMinRepeat = 2;
const uint16_t kToshibaACStateLength = 9;
const uint16_t kToshibaACBits = kToshibaACStateLength * 8;
const uint16_t kToshibaACMinRepeat = kSingleRepeat;
const uint16_t kTrotecStateLength = 9;
const uint16_t kTrotecDefaultRepeat = kNoRepeat;
const uint16_t kWhirlpoolAcStateLength = 21;
const uint16_t kWhirlpoolAcBits = kWhirlpoolAcStateLength * 8;
const uint16_t kWhirlpoolAcDefaultRepeat = kNoRepeat;
const uint16_t kWhynterBits = 32;
const uint8_t kVestelACBits = 56;
// Legacy defines. (Deprecated)
#define AIWA_RC_T501_BITS kAiwaRcT501Bits
#define ARGO_COMMAND_LENGTH kArgoStateLength
#define COOLIX_BITS kCoolixBits
#define CARRIER_AC_BITS kCarrierAcBits
#define DAIKIN_COMMAND_LENGTH kDaikinStateLength
#define DENON_BITS kDenonBits
#define DENON_48_BITS kPanasonicBits
#define DENON_LEGACY_BITS kDenonLegacyBits
#define DISH_BITS kDishBits
#define FUJITSU_AC_MIN_REPEAT kFujitsuAcMinRepeat
#define FUJITSU_AC_STATE_LENGTH kFujitsuAcStateLength
#define FUJITSU_AC_STATE_LENGTH_SHORT kFujitsuAcStateLengthShort
#define FUJITSU_AC_BITS kFujitsuAcBits
#define FUJITSU_AC_MIN_BITS kFujitsuAcMinBits
#define GICABLE_BITS kGicableBits
#define GREE_STATE_LENGTH kGreeStateLength
#define HAIER_AC_STATE_LENGTH kHaierACStateLength
#define HAIER_AC_YRW02_STATE_LENGTH kHaierACYRW02StateLength
#define HITACHI_AC_STATE_LENGTH kHitachiAcStateLength
#define HITACHI_AC_BITS kHitachiAcBits
#define HITACHI_AC1_STATE_LENGTH kHitachiAc1StateLength
#define HITACHI_AC1_BITS kHitachiAc1Bits
#define HITACHI_AC2_STATE_LENGTH kHitachiAc2StateLength
#define HITACHI_AC2_BITS kHitachiAc2Bits
#define JVC_BITS kJvcBits
#define KELVINATOR_STATE_LENGTH kKelvinatorStateLength
#define LASERTAG_BITS kLasertagBits
#define LG_BITS kLgBits
#define LG32_BITS kLg32Bits
#define MAGIQUEST_BITS kMagiquestBits
#define MIDEA_BITS kMideaBits
#define MITSUBISHI_BITS kMitsubishiBits
#define MITSUBISHI_AC_STATE_LENGTH kMitsubishiACStateLength
#define NEC_BITS kNECBits
#define NIKAI_BITS kNikaiBits
#define PANASONIC_BITS kPanasonicBits
#define RC5_BITS kRC5Bits
#define RC5X_BITS kRC5XBits
#define RC6_MODE0_BITS kRC6Mode0Bits
#define RC6_36_BITS kRC6_36Bits
#define RCMM_BITS kRCMMBits
#define SANYO_LC7461_BITS kSanyoLC7461Bits
#define SAMSUNG_BITS kSamsungBits
#define SANYO_SA8650B_BITS kSanyoSA8650BBits
#define SHARP_BITS kSharpBits
#define SHERWOOD_BITS kSherwoodBits
#define SONY_12_BITS kSony12Bits
#define SONY_15_BITS kSony15Bits
#define SONY_20_BITS kSony20Bits
#define TOSHIBA_AC_STATE_LENGTH kToshibaACStateLength
#define TROTEC_COMMAND_LENGTH kTrotecStateLength
#define WHYNTER_BITS kWhynterBits
// Turn on Debugging information by uncommenting the following line.
// #define DEBUG 1
+108 -55
View File
@@ -34,9 +34,8 @@
// to change the duty cycle etc.
// Returns:
// An IRsend object.
IRsend::IRsend(uint16_t IRsendPin, bool inverted,
bool use_modulation) : IRpin(IRsendPin),
periodOffset(PERIOD_OFFSET) {
IRsend::IRsend(uint16_t IRsendPin, bool inverted, bool use_modulation)
: IRpin(IRsendPin), periodOffset(kPeriodOffset) {
if (inverted) {
outputOn = LOW;
outputOff = HIGH;
@@ -46,9 +45,9 @@ IRsend::IRsend(uint16_t IRsendPin, bool inverted,
}
modulation = use_modulation;
if (modulation)
_dutycycle = DUTY_DEFAULT;
_dutycycle = kDutyDefault;
else
_dutycycle = DUTY_MAX;
_dutycycle = kDutyMax;
}
// Enable the pin for output.
@@ -82,12 +81,13 @@ void IRsend::ledOn() {
// nr. of uSeconds.
uint32_t IRsend::calcUSecPeriod(uint32_t hz, bool use_offset) {
if (hz == 0) hz = 1; // Avoid Zero hz. Divide by Zero is nasty.
uint32_t period = (1000000UL + hz/2) / hz; // The equiv of round(1000000/hz).
uint32_t period =
(1000000UL + hz / 2) / hz; // The equiv of round(1000000/hz).
// Apply the offset and ensure we don't result in a <= 0 value.
if (use_offset)
return std::max((uint32_t) 1, period + periodOffset);
return std::max((uint32_t)1, period + periodOffset);
else
return std::max((uint32_t) 1, period);
return std::max((uint32_t)1, period);
}
// Set the output frequency modulation and duty cycle.
@@ -104,15 +104,15 @@ uint32_t IRsend::calcUSecPeriod(uint32_t hz, bool use_offset) {
void IRsend::enableIROut(uint32_t freq, uint8_t duty) {
// Set the duty cycle to use if we want freq. modulation.
if (modulation) {
_dutycycle = std::min(duty, (uint8_t) DUTY_MAX);
_dutycycle = std::min(duty, kDutyMax);
} else {
_dutycycle = DUTY_MAX;
_dutycycle = kDutyMax;
}
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 * _dutycycle) / DUTY_MAX;
onTimePeriod = (period * _dutycycle) / kDutyMax;
// Nr. of uSeconds the LED will be off per pulse.
offTimePeriod = period - onTimePeriod;
}
@@ -124,7 +124,7 @@ void IRsend::enableIROut(uint32_t freq, uint8_t duty) {
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) {
if (usec <= kMaxAccurateUsecDelay) {
#ifndef UNIT_TEST
delayMicroseconds(usec);
#endif
@@ -146,9 +146,9 @@ void IRsend::_delayMicroseconds(uint32_t usec) {
// 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)
for (; usec > kMaxAccurateUsecDelay; usec -= kMaxAccurateUsecDelay)
#ifndef UNIT_TEST
delayMicroseconds(MAX_ACCURATE_USEC_DELAY);
delayMicroseconds(kMaxAccurateUsecDelay);
delayMicroseconds(static_cast<uint16_t>(usec));
#endif // UNIT_TEST
}
@@ -190,14 +190,14 @@ uint16_t IRsend::mark(uint16_t usec) {
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));
_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.
_delayMicroseconds(std::min(usec - elapsed - onTimePeriod,
(uint32_t) offTimePeriod));
_delayMicroseconds(
std::min(usec - elapsed - onTimePeriod, (uint32_t)offTimePeriod));
elapsed = usecTimer.elapsed(); // Update & recache the actual elapsed time.
}
return counter;
@@ -238,7 +238,7 @@ int8_t IRsend::calibrate(uint16_t hz) {
uint32_t timeTaken = usecTimer.elapsed(); // Record the time it took.
// While it shouldn't be necessary, assume at least 1 pulse, to avoid a
// divide by 0 situation.
pulses = std::max(pulses, (uint16_t) 1U);
pulses = std::max(pulses, (uint16_t)1U);
uint32_t calcPeriod = calcUSecPeriod(hz); // e.g. @38kHz it should be 26us.
// Assuming 38kHz for the example calculations:
// In a 65535us pulse, we should have 2520.5769 pulses @ 26us periods.
@@ -250,9 +250,9 @@ int8_t IRsend::calibrate(uint16_t hz) {
//
// Calculate the actual period from the actual time & the actual pulses
// generated.
double_t actualPeriod = (double_t) timeTaken / (double_t) pulses;
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);
periodOffset = (int8_t)((double_t)calcPeriod - actualPeriod);
return periodOffset;
}
@@ -268,9 +268,9 @@ int8_t IRsend::calibrate(uint16_t hz) {
// data: The data to be transmitted.
// nbits: Nr. of bits of data to be sent.
// MSBfirst: Flag for bit transmission order. Defaults to MSB->LSB order.
void IRsend::sendData(uint16_t onemark, uint32_t onespace,
uint16_t zeromark, uint32_t zerospace,
uint64_t data, uint16_t nbits, bool MSBfirst) {
void IRsend::sendData(uint16_t onemark, uint32_t onespace, uint16_t zeromark,
uint32_t zerospace, uint64_t data, uint16_t nbits,
bool MSBfirst) {
if (nbits == 0) // If we are asked to send nothing, just return.
return;
if (MSBfirst) { // Send the MSB first.
@@ -281,7 +281,7 @@ void IRsend::sendData(uint16_t onemark, uint32_t onespace,
nbits--;
}
// Send the supplied data.
for (uint64_t mask = 1ULL << (nbits - 1); mask; mask >>= 1)
for (uint64_t mask = 1ULL << (nbits - 1); mask; mask >>= 1)
if (data & mask) { // Send a 1
mark(onemark);
space(onespace);
@@ -376,10 +376,10 @@ 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) {
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();
@@ -389,14 +389,14 @@ void IRsend::sendGeneric(const uint16_t headermark, const uint32_t headerspace,
usecs.reset();
// Header
if (headermark) mark(headermark);
if (headerspace) space(headerspace);
if (headermark) mark(headermark);
if (headerspace) space(headerspace);
// Data
sendData(onemark, onespace, zeromark, zerospace, data, nbits, MSBfirst);
// Footer
if (footermark) mark(footermark);
if (footermark) mark(footermark);
uint32_t elapsed = usecs.elapsed();
// Avoid potential unsigned integer underflow. e.g. when mesgtime is 0.
if (elapsed >= mesgtime)
@@ -445,16 +445,16 @@ void IRsend::sendGeneric(const uint16_t headermark, const uint32_t headerspace,
// 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);
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);
sendData(onemark, onespace, zeromark, zerospace, *(dataptr + i), 8,
MSBfirst);
// Footer
if (footermark) mark(footermark);
if (footermark) mark(footermark);
space(gap);
}
}
@@ -492,61 +492,114 @@ void IRsend::sendRaw(uint16_t buf[], uint16_t len, uint16_t hz) {
void IRsend::send(uint16_t type, uint64_t data, uint16_t nbits) {
switch (type) {
#if SEND_NEC
case NEC: sendNEC(data, nbits); break;
case NEC:
sendNEC(data, nbits);
break;
#endif
#if SEND_SONY
case SONY: sendSony(data, nbits); break;
case SONY:
sendSony(data, nbits);
break;
#endif
#if SEND_RC5
case RC5: sendRC5(data, nbits); break;
case RC5:
sendRC5(data, nbits);
break;
#endif
#if SEND_RC6
case RC6: sendRC6(data, nbits); break;
case RC6:
sendRC6(data, nbits);
break;
#endif
#if SEND_DISH
case DISH: sendDISH(data, nbits); break;
case DISH:
sendDISH(data, nbits);
break;
#endif
#if SEND_JVC
case JVC: sendJVC(data, nbits); break;
case JVC:
sendJVC(data, nbits);
break;
#endif
#if SEND_SAMSUNG
case SAMSUNG: sendSAMSUNG(data, nbits); break;
case SAMSUNG:
sendSAMSUNG(data, nbits);
break;
#endif
#if SEND_LG
case LG: sendLG(data, nbits); break;
case LG:
sendLG(data, nbits);
break;
#endif
#if SEND_LG
case LG2:
sendLG2(data, nbits);
break;
#endif
#if SEND_WHYNTER
case WHYNTER: sendWhynter(data, nbits); break;
case WHYNTER:
sendWhynter(data, nbits);
break;
#endif
#if SEND_COOLIX
case COOLIX: sendCOOLIX(data, nbits); break;
case COOLIX:
sendCOOLIX(data, nbits);
break;
#endif
#if SEND_DENON
case DENON: sendDenon(data, nbits); break;
case DENON:
sendDenon(data, nbits);
break;
#endif
#if SEND_SHERWOOD
case SHERWOOD: sendSherwood(data, nbits); break;
case SHERWOOD:
sendSherwood(data, nbits);
break;
#endif
#if SEND_RCMM
case RCMM: sendRCMM(data, nbits); break;
case RCMM:
sendRCMM(data, nbits);
break;
#endif
#if SEND_MITSUBISHI
case MITSUBISHI: sendMitsubishi(data, nbits); break;
case MITSUBISHI:
sendMitsubishi(data, nbits);
break;
#endif
#if SEND_MITSUBISHI2
case MITSUBISHI2: sendMitsubishi2(data, nbits); break;
case MITSUBISHI2:
sendMitsubishi2(data, nbits);
break;
#endif
#if SEND_SHARP
case SHARP: sendSharpRaw(data, nbits); break;
case SHARP:
sendSharpRaw(data, nbits);
break;
#endif
#if SEND_AIWA_RC_T501
case AIWA_RC_T501: sendAiwaRCT501(data, nbits); break;
case AIWA_RC_T501:
sendAiwaRCT501(data, nbits);
break;
#endif
#if SEND_MIDEA
case MIDEA: sendMidea(data, nbits); break;
case MIDEA:
sendMidea(data, nbits);
break;
#endif
#if SEND_GICABLE
case GICABLE: sendGICable(data, nbits); break;
case GICABLE:
sendGICable(data, nbits);
break;
#endif
#if SEND_PIONEER
case PIONEER:
sendPioneer(data, nbits);
break;
#endif
#if SEND_VESTEL_AC
case VESTEL_AC:
sendVestelAC(data, nbits);
break;
#endif
}
}
+140 -91
View File
@@ -22,12 +22,12 @@
// Offset (in microseconds) to use in Period time calculations to account for
// code excution time in producing the software PWM signal.
// 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
const int8_t kPeriodOffset = -5;
const uint8_t kDutyDefault = 50; // Percentage
const uint8_t kDutyMax = 100; // Percentage
// delayMicroseconds() is only accurate to 16383us.
// Ref: https://www.arduino.cc/en/Reference/delayMicroseconds
#define MAX_ACCURATE_USEC_DELAY 16383U
const uint16_t kMaxAccurateUsecDelay = 16383;
// Classes
class IRsend {
@@ -35,7 +35,7 @@ class IRsend {
explicit IRsend(uint16_t IRsendPin, bool inverted = false,
bool use_modulation = true);
void begin();
void enableIROut(uint32_t freq, uint8_t duty = DUTY_DEFAULT);
void enableIROut(uint32_t freq, uint8_t duty = kDutyDefault);
VIRTUAL void _delayMicroseconds(uint32_t usec);
VIRTUAL uint16_t mark(uint16_t usec);
VIRTUAL void space(uint32_t usec);
@@ -55,10 +55,10 @@ class IRsend {
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);
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,
@@ -66,9 +66,10 @@ class IRsend {
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);
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);
void sendNEC(uint64_t data, uint16_t nbits = kNECBits,
uint16_t repeat = kNoRepeat);
uint32_t encodeNEC(uint16_t address, uint16_t command);
#endif
#if SEND_SONY
@@ -77,22 +78,30 @@ void send(uint16_t type, uint64_t data, uint16_t nbits);
// Legacy use of this procedure was to only send a single code so call it with
// repeat=0 for backward compatibility. As of v2.0 it defaults to sending
// a Sony command that will be accepted be a device.
void sendSony(uint64_t data, uint16_t nbits = SONY_20_BITS,
uint16_t repeat = SONY_MIN_REPEAT);
void sendSony(uint64_t data, uint16_t nbits = kSony20Bits,
uint16_t repeat = kSonyMinRepeat);
uint32_t encodeSony(uint16_t nbits, uint16_t command, uint16_t address,
uint16_t extended = 0);
#endif
#if SEND_SHERWOOD
void sendSherwood(uint64_t data, uint16_t nbits = SHERWOOD_BITS,
uint16_t repeat = SHERWOOD_MIN_REPEAT);
void sendSherwood(uint64_t data, uint16_t nbits = kSherwoodBits,
uint16_t repeat = kSherwoodMinRepeat);
#endif
#if SEND_SAMSUNG
void sendSAMSUNG(uint64_t data, uint16_t nbits = SAMSUNG_BITS,
uint16_t repeat = 0);
void sendSAMSUNG(uint64_t data, uint16_t nbits = kSamsungBits,
uint16_t repeat = kNoRepeat);
uint32_t encodeSAMSUNG(uint8_t customer, uint8_t command);
#endif
#if SEND_SAMSUNG_AC
void sendSamsungAC(unsigned char data[],
uint16_t nbytes = kSamsungAcStateLength,
uint16_t repeat = kSamsungAcDefaultRepeat);
#endif
#if SEND_LG
void sendLG(uint64_t data, uint16_t nbits = LG_BITS, uint16_t repeat = 0);
void sendLG(uint64_t data, uint16_t nbits = kLgBits,
uint16_t repeat = kNoRepeat);
void sendLG2(uint64_t data, uint16_t nbits = kLgBits,
uint16_t repeat = kNoRepeat);
uint32_t encodeLG(uint16_t address, uint16_t command);
#endif
#if (SEND_SHARP || SEND_DENON)
@@ -100,41 +109,44 @@ void send(uint16_t type, uint64_t data, uint16_t nbits);
uint16_t expansion = 1, uint16_t check = 0,
bool MSBfirst = false);
void sendSharp(uint16_t address, uint16_t command,
uint16_t nbits = SHARP_BITS, uint16_t repeat = 0);
void sendSharpRaw(uint64_t data, uint16_t nbits = SHARP_BITS,
uint16_t repeat = 0);
uint16_t nbits = kSharpBits, uint16_t repeat = kNoRepeat);
void sendSharpRaw(uint64_t data, uint16_t nbits = kSharpBits,
uint16_t repeat = kNoRepeat);
#endif
#if SEND_JVC
void sendJVC(uint64_t data, uint16_t nbits = JVC_BITS, uint16_t repeat = 0);
void sendJVC(uint64_t data, uint16_t nbits = kJvcBits,
uint16_t repeat = kNoRepeat);
uint16_t encodeJVC(uint8_t address, uint8_t command);
#endif
#if SEND_DENON
void sendDenon(uint64_t data, uint16_t nbits = DENON_BITS,
uint16_t repeat = 0);
uint16_t repeat = kNoRepeat);
#endif
#if SEND_SANYO
uint64_t encodeSanyoLC7461(uint16_t address, uint8_t command);
void sendSanyoLC7461(uint64_t data, uint16_t nbits = SANYO_LC7461_BITS,
uint16_t repeat = 0);
void sendSanyoLC7461(uint64_t data, uint16_t nbits = kSanyoLC7461Bits,
uint16_t repeat = kNoRepeat);
#endif
#if SEND_DISH
// sendDISH() should typically be called with repeat=3 as DISH devices
// expect the code to be sent at least 4 times. (code + 3 repeats = 4 codes)
// Legacy use of this procedure was only to send a single code
// so use repeat=0 for backward compatibility.
void sendDISH(uint64_t data, uint16_t nbits = DISH_BITS,
uint16_t repeat = DISH_MIN_REPEAT);
void sendDISH(uint64_t data, uint16_t nbits = kDishBits,
uint16_t repeat = kDishMinRepeat);
#endif
#if (SEND_PANASONIC || SEND_DENON)
void sendPanasonic64(uint64_t data, uint16_t nbits = PANASONIC_BITS,
uint16_t repeat = 0);
void sendPanasonic64(uint64_t data, uint16_t nbits = kPanasonicBits,
uint16_t repeat = kNoRepeat);
void sendPanasonic(uint16_t address, uint32_t data,
uint16_t nbits = PANASONIC_BITS, uint16_t repeat = 0);
uint16_t nbits = kPanasonicBits,
uint16_t repeat = kNoRepeat);
uint64_t encodePanasonic(uint16_t manufacturer, uint8_t device,
uint8_t subdevice, uint8_t function);
#endif
#if SEND_RC5
void sendRC5(uint64_t data, uint16_t nbits = RC5X_BITS, uint16_t repeat = 0);
void sendRC5(uint64_t data, uint16_t nbits = kRC5XBits,
uint16_t repeat = kNoRepeat);
uint16_t encodeRC5(uint8_t address, uint8_t command,
bool key_released = false);
uint16_t encodeRC5X(uint8_t address, uint8_t command,
@@ -142,126 +154,163 @@ void send(uint16_t type, uint64_t data, uint16_t nbits);
uint64_t toggleRC5(uint64_t data);
#endif
#if SEND_RC6
void sendRC6(uint64_t data, uint16_t nbits = RC6_MODE0_BITS,
uint16_t repeat = 0);
void sendRC6(uint64_t data, uint16_t nbits = kRC6Mode0Bits,
uint16_t repeat = kNoRepeat);
uint64_t encodeRC6(uint32_t address, uint8_t command,
uint16_t mode = RC6_MODE0_BITS);
uint64_t toggleRC6(uint64_t data, uint16_t nbits = RC6_MODE0_BITS);
uint16_t mode = kRC6Mode0Bits);
uint64_t toggleRC6(uint64_t data, uint16_t nbits = kRC6Mode0Bits);
#endif
#if SEND_RCMM
void sendRCMM(uint64_t data, uint16_t nbits = RCMM_BITS, uint16_t repeat = 0);
void sendRCMM(uint64_t data, uint16_t nbits = kRCMMBits,
uint16_t repeat = kNoRepeat);
#endif
#if SEND_COOLIX
void sendCOOLIX(uint64_t data, uint16_t nbits = COOLIX_BITS,
uint16_t repeat = 0);
void sendCOOLIX(uint64_t data, uint16_t nbits = kCoolixBits,
uint16_t repeat = kCoolixDefaultRepeat);
#endif
#if SEND_WHYNTER
void sendWhynter(uint64_t data, uint16_t nbits = WHYNTER_BITS,
uint16_t repeat = 0);
void sendWhynter(uint64_t data, uint16_t nbits = kWhynterBits,
uint16_t repeat = kNoRepeat);
#endif
#if SEND_MITSUBISHI
void sendMitsubishi(uint64_t data, uint16_t nbits = MITSUBISHI_BITS,
uint16_t repeat = MITSUBISHI_MIN_REPEAT);
void sendMitsubishi(uint64_t data, uint16_t nbits = kMitsubishiBits,
uint16_t repeat = kMitsubishiMinRepeat);
#endif
#if SEND_MITSUBISHI2
void sendMitsubishi2(uint64_t data, uint16_t nbits = MITSUBISHI_BITS,
uint16_t repeat = MITSUBISHI_MIN_REPEAT);
void sendMitsubishi2(uint64_t data, uint16_t nbits = kMitsubishiBits,
uint16_t repeat = kMitsubishiMinRepeat);
#endif
#if SEND_MITSUBISHI_AC
void sendMitsubishiAC(unsigned char data[],
uint16_t nbytes = MITSUBISHI_AC_STATE_LENGTH,
uint16_t repeat = MITSUBISHI_AC_MIN_REPEAT);
uint16_t nbytes = kMitsubishiACStateLength,
uint16_t repeat = kMitsubishiACMinRepeat);
#endif
#if SEND_FUJITSU_AC
void sendFujitsuAC(unsigned char data[],
uint16_t nbytes,
uint16_t repeat = FUJITSU_AC_MIN_REPEAT);
void sendFujitsuAC(unsigned char data[], uint16_t nbytes,
uint16_t repeat = kFujitsuAcMinRepeat);
#endif
#if SEND_GLOBALCACHE
void sendGC(uint16_t buf[], uint16_t len);
#endif
#if SEND_KELVINATOR
void sendKelvinator(unsigned char data[],
uint16_t nbytes = KELVINATOR_STATE_LENGTH,
uint16_t repeat = 0);
uint16_t nbytes = kKelvinatorStateLength,
uint16_t repeat = kKelvinatorDefaultRepeat);
#endif
#if SEND_DAIKIN
void sendDaikin(unsigned char data[],
uint16_t nbytes = DAIKIN_COMMAND_LENGTH,
uint16_t repeat = 0);
void sendDaikinGapHeader();
void sendDaikin(unsigned char data[], uint16_t nbytes = kDaikinStateLength,
uint16_t repeat = kDaikinDefaultRepeat);
#endif
#if SEND_DAIKIN2
void sendDaikin2(unsigned char data[], uint16_t nbytes = kDaikin2StateLength,
uint16_t repeat = kDaikin2DefaultRepeat);
#endif
#if SEND_AIWA_RC_T501
void sendAiwaRCT501(uint64_t data, uint16_t nbits = AIWA_RC_T501_BITS,
uint16_t repeat = AIWA_RC_T501_MIN_REPEAT);
void sendAiwaRCT501(uint64_t data, uint16_t nbits = kAiwaRcT501Bits,
uint16_t repeat = kAiwaRcT501MinRepeats);
#endif
#if SEND_GREE
void sendGree(uint64_t data, uint16_t nbits = GREE_BITS, uint16_t repeat = 0);
void sendGree(uint8_t data[], uint16_t nbytes = GREE_STATE_LENGTH,
uint16_t repeat = 0);
void sendGree(uint64_t data, uint16_t nbits = kGreeBits,
uint16_t repeat = kGreeDefaultRepeat);
void sendGree(uint8_t data[], uint16_t nbytes = kGreeStateLength,
uint16_t repeat = kGreeDefaultRepeat);
#endif
#if SEND_PRONTO
void sendPronto(uint16_t data[], uint16_t len, uint16_t repeat = 0);
void sendPronto(uint16_t data[], uint16_t len, uint16_t repeat = kNoRepeat);
#endif
#if SEND_ARGO
void sendArgo(unsigned char data[],
uint16_t nbytes = ARGO_COMMAND_LENGTH,
uint16_t repeat = 0);
void sendArgo(unsigned char data[], uint16_t nbytes = kArgoStateLength,
uint16_t repeat = kArgoDefaultRepeat);
#endif
#if SEND_TROTEC
void sendTrotec(unsigned char data[],
uint16_t nbytes = TROTEC_COMMAND_LENGTH,
uint16_t repeat = 0);
void sendTrotec(unsigned char data[], uint16_t nbytes = kTrotecStateLength,
uint16_t repeat = kTrotecDefaultRepeat);
#endif
#if SEND_NIKAI
void sendNikai(uint64_t data, uint16_t nbits = NIKAI_BITS,
uint16_t repeat = 0);
void sendNikai(uint64_t data, uint16_t nbits = kNikaiBits,
uint16_t repeat = kNoRepeat);
#endif
#if SEND_TOSHIBA_AC
void sendToshibaAC(unsigned char data[],
uint16_t nbytes = TOSHIBA_AC_STATE_LENGTH,
uint16_t repeat = TOSHIBA_AC_MIN_REPEAT);
uint16_t nbytes = kToshibaACStateLength,
uint16_t repeat = kToshibaACMinRepeat);
#endif
#if SEND_MIDEA
void sendMidea(uint64_t data, uint16_t nbits = MIDEA_BITS,
uint16_t repeat = MIDEA_MIN_REPEAT);
void sendMidea(uint64_t data, uint16_t nbits = kMideaBits,
uint16_t repeat = kMideaMinRepeat);
#endif
#if SEND_MAGIQUEST
void sendMagiQuest(uint64_t data, uint16_t nbits = MAGIQUEST_BITS,
uint16_t repeat = 0);
void sendMagiQuest(uint64_t data, uint16_t nbits = kMagiquestBits,
uint16_t repeat = kNoRepeat);
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);
void sendLasertag(uint64_t data, uint16_t nbits = kLasertagBits,
uint16_t repeat = kLasertagMinRepeat);
#endif
#if SEND_CARRIER_AC
void sendCarrierAC(uint64_t data, uint16_t nbits = CARRIER_AC_BITS,
uint16_t repeat = CARRIER_AC_MIN_REPEAT);
void sendCarrierAC(uint64_t data, uint16_t nbits = kCarrierAcBits,
uint16_t repeat = kCarrierAcMinRepeat);
#endif
#if SEND_HAIER_AC
void sendHaierAC(unsigned char data[],
uint16_t nbytes = HAIER_AC_STATE_LENGTH,
uint16_t repeat = 0);
#if (SEND_HAIER_AC || SEND_HAIER_AC_YRW02)
void sendHaierAC(unsigned char data[], uint16_t nbytes = kHaierACStateLength,
uint16_t repeat = kHaierAcDefaultRepeat);
#endif
#if SEND_HAIER_AC_YRW02
void sendHaierACYRW02(unsigned char data[],
uint16_t nbytes = kHaierACYRW02StateLength,
uint16_t repeat = kHaierAcYrw02DefaultRepeat);
#endif
#if SEND_HITACHI_AC
void sendHitachiAC(unsigned char data[],
uint16_t nbytes = HITACHI_AC_STATE_LENGTH,
uint16_t repeat = 0);
uint16_t nbytes = kHitachiAcStateLength,
uint16_t repeat = kHitachiAcDefaultRepeat);
#endif
#if SEND_HITACHI_AC1
void sendHitachiAC1(unsigned char data[],
uint16_t nbytes = HITACHI_AC1_STATE_LENGTH,
uint16_t repeat = 0);
uint16_t nbytes = kHitachiAc1StateLength,
uint16_t repeat = kNoRepeat);
#endif
#if SEND_HITACHI_AC2
void sendHitachiAC2(unsigned char data[],
uint16_t nbytes = HITACHI_AC2_STATE_LENGTH,
uint16_t repeat = 0);
uint16_t nbytes = kHitachiAc2StateLength,
uint16_t repeat = kNoRepeat);
#endif
#if SEND_GICABLE
void sendGICable(uint64_t data, uint16_t nbits = GICABLE_BITS,
uint16_t repeat = GICABLE_MIN_REPEAT);
void sendGICable(uint64_t data, uint16_t nbits = kGicableBits,
uint16_t repeat = kGicableMinRepeat);
#endif
#if SEND_WHIRLPOOL_AC
void sendWhirlpoolAC(unsigned char data[],
uint16_t nbytes = kWhirlpoolAcStateLength,
uint16_t repeat = kWhirlpoolAcDefaultRepeat);
#endif
#if SEND_LUTRON
void sendLutron(uint64_t data, uint16_t nbits = kLutronBits,
uint16_t repeat = kNoRepeat);
#endif
#if SEND_ELECTRA_AC
void sendElectraAC(unsigned char data[],
uint16_t nbytes = kElectraAcStateLength,
uint16_t repeat = kNoRepeat);
#endif
#if SEND_PANASONIC_AC
void sendPanasonicAC(unsigned char data[],
uint16_t nbytes = kPanasonicAcStateLength,
uint16_t repeat = kPanasonicAcDefaultRepeat);
#endif
#if SEND_PIONEER
void sendPioneer(const uint64_t data, const uint16_t nbits = kPioneerBits,
const uint16_t repeat = kNoRepeat);
uint64_t encodePioneer(uint16_t address, uint16_t command);
#endif
#if SEND_MWM
void sendMWM(unsigned char data[], uint16_t nbytes,
uint16_t repeat = kNoRepeat);
#endif
#if SEND_VESTEL_AC
void sendVestelAC(const uint64_t data, const uint16_t nbits = kVestelACBits,
const uint16_t repeat = kNoRepeat);
#endif
protected:
+3 -7
View File
@@ -13,9 +13,7 @@ extern uint32_t _IRtimer_unittest_now;
// This class performs a simple time in useconds since instantiated.
// Handles when the system timer wraps around (once).
IRtimer::IRtimer() {
reset();
}
IRtimer::IRtimer() { reset(); }
void IRtimer::reset() {
#ifndef UNIT_TEST
@@ -31,7 +29,7 @@ uint32_t IRtimer::elapsed() {
#else
uint32_t now = _IRtimer_unittest_now;
#endif
if (start <= now) // Check if the system timer has wrapped.
if (start <= now) // Check if the system timer has wrapped.
return now - start; // No wrap.
else
return UINT32_MAX - start + now; // Has wrapped.
@@ -39,7 +37,5 @@ uint32_t IRtimer::elapsed() {
// Only used in unit testing.
#ifdef UNIT_TEST
void IRtimer::add(uint32_t usecs) {
_IRtimer_unittest_now += usecs;
}
void IRtimer::add(uint32_t usecs) { _IRtimer_unittest_now += usecs; }
#endif // UNIT_TEST
+273 -86
View File
@@ -21,10 +21,9 @@
// Returns:
// The reversed bit pattern.
uint64_t reverseBits(uint64_t input, uint16_t nbits) {
if (nbits <= 1)
return input; // Reversing <= 1 bits makes no change at all.
if (nbits <= 1) return input; // Reversing <= 1 bits makes no change at all.
// Cap the nr. of bits to rotate to the max nr. of bits in the input.
nbits = std::min(nbits, (uint16_t) (sizeof(input) * 8));
nbits = std::min(nbits, (uint16_t)(sizeof(input) * 8));
uint64_t output = 0;
for (uint16_t i = 0; i < nbits; i++) {
output <<= 1;
@@ -62,7 +61,7 @@ std::string uint64ToString(uint64_t input, uint8_t base) {
input /= base;
if (c < 10)
c +='0';
c += '0';
else
c += 'A' - 10;
result = c + result;
@@ -92,57 +91,179 @@ void serialPrintUint64(uint64_t input, uint8_t base) {
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 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;
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 DAIKIN2:
result = "DAIKIN2";
break;
case DENON:
result = "DENON";
break;
case DISH:
result = "DISH";
break;
case ELECTRA_AC:
result = "ELECTRA_AC";
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 HAIER_AC_YRW02:
result = "HAIER_AC_YRW02";
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 LG2:
result = "LG2";
break;
case LASERTAG:
result = "LASERTAG";
break;
case LUTRON:
result = "LUTRON";
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 MWM:
result = "MWM";
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 PANASONIC_AC:
result = "PANASONIC_AC";
break;
case PIONEER:
result = "PIONEER";
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 SAMSUNG_AC:
result = "SAMSUNG_AC";
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 VESTEL_AC:
result = "VESTEL_AC";
break;
case WHIRLPOOL_AC:
result = "WHIRLPOOL_AC";
break;
case WHYNTER:
result = "WHYNTER";
break;
}
if (isRepeat) result += " (Repeat)";
return result;
@@ -152,15 +273,22 @@ std::string typeToString(const decode_type_t protocol,
bool hasACState(const decode_type_t protocol) {
switch (protocol) {
case DAIKIN:
case DAIKIN2:
case ELECTRA_AC:
case FUJITSU_AC:
case GREE:
case HAIER_AC:
case HAIER_AC_YRW02:
case HITACHI_AC:
case HITACHI_AC1:
case HITACHI_AC2:
case KELVINATOR:
case MITSUBISHI_AC:
case MWM:
case PANASONIC_AC:
case SAMSUNG_AC:
case TOSHIBA_AC:
case WHIRLPOOL_AC:
return true;
default:
return false;
@@ -176,7 +304,7 @@ bool hasACState(const decode_type_t protocol) {
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;
uint32_t usecs = results->rawbuf[i] * kRawTick;
// Add two extra entries for multiple larger than UINT16_MAX it is.
extended_length += (usecs / (UINT16_MAX + 1)) * 2;
}
@@ -196,14 +324,13 @@ std::string resultToSourceCode(const decode_results *results) {
output += "uint16_t "; // variable type
output += "rawData["; // array name
output += uint64ToString(getCorrectedRawLength(results), 10);
// array size
output += "] = {"; // Start declaration
// array size
output += "] = {"; // Start declaration
// Dump data
for (uint16_t i = 1; i < results->rawlen; i++) {
uint32_t usecs;
for (usecs = results->rawbuf[i] * RAWTICK;
usecs > UINT16_MAX;
for (usecs = results->rawbuf[i] * kRawTick; usecs > UINT16_MAX;
usecs -= UINT16_MAX) {
output += uint64ToString(UINT16_MAX);
if (i % 2)
@@ -213,12 +340,12 @@ std::string resultToSourceCode(const decode_results *results) {
}
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.
output += ", "; // ',' not needed on the last one
if (i % 2 == 0) output += " "; // Extra if it was even.
}
// End declaration
output +="};";
output += "};";
// Comment
output += " // " + typeToString(results->decode_type, results->repeat);
@@ -235,10 +362,9 @@ std::string resultToSourceCode(const decode_results *results) {
output += "uint8_t state[" + uint64ToString(nbytes) + "] = {";
for (uint16_t i = 0; i < nbytes; i++) {
output += "0x";
if (results->state[i] < 0x10) output += "0";
if (results->state[i] < 0x10) output += "0";
output += uint64ToString(results->state[i], 16);
if (i < nbytes - 1)
output += ", ";
if (i < nbytes - 1) output += ", ";
}
output += "};\n";
#endif // DECODE_AC
@@ -254,8 +380,8 @@ std::string resultToSourceCode(const decode_results *results) {
uint64ToString(results->command, 16) + ";\n";
}
// Most protocols have data
output += "uint64_t data = 0x" + uint64ToString(results->value, 16) +
";\n";
output +=
"uint64_t data = 0x" + uint64ToString(results->value, 16) + ";\n";
}
}
return output;
@@ -279,19 +405,39 @@ std::string resultToTimingInfo(const decode_results *results) {
output += "-"; // even
else
output += " +"; // odd
value = uint64ToString(results->rawbuf[i] * RAWTICK);
value = uint64ToString(results->rawbuf[i] * kRawTick);
// Space pad the value till it is at least 6 chars long.
while (value.length() < 6)
value = " " + value;
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.
if (i < results->rawlen - 1) output += ", "; // ',' not needed for last one
if (!(i % 8)) output += "\n"; // Newline every 8 entries.
}
output += "\n";
return output;
}
// Convert the decode_results structure's value/state to simple hexadecimal.
//
#ifdef ARDUINO
String resultToHexidecimal(const decode_results *result) {
String output = "";
#else
std::string resultToHexidecimal(const decode_results *result) {
std::string output = "";
#endif
if (hasACState(result->decode_type)) {
#if DECODE_AC
for (uint16_t i = 0; result->bits > i * 8; i++) {
if (result->state[i] < 0x10) output += "0"; // Zero pad
output += uint64ToString(result->state[i], 16);
}
#endif // DECODE_AC
} else {
output += uint64ToString(result->value, 16);
}
return output;
}
// Dump out the decode_results structure.
//
#ifdef ARDUINO
@@ -302,21 +448,13 @@ std::string resultToHumanReadableBasic(const decode_results *results) {
std::string output = "";
#endif
// Show Encoding standard
output += "Encoding : " +
typeToString(results->decode_type, results->repeat) + "\n";
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 += resultToHexidecimal(results);
output += " (" + uint64ToString(results->bits) + " bits)\n";
return output;
}
@@ -324,17 +462,66 @@ std::string resultToHumanReadableBasic(const decode_results *results) {
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;
for (ptr = start; ptr - start < length; ptr++) checksum += *ptr;
return checksum;
}
uint8_t xorBytes(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;
}
// Count the number of bits of a certain type.
// Args:
// start: Ptr to the start of data to count bits in.
// length: How many bytes to count.
// ones: Count the binary 1 bits. False for counting the 0 bits.
// init: Start the counting from this value.
// Returns:
// Nr. of bits found.
uint16_t countBits(const uint8_t *start, const uint16_t length, const bool ones,
const uint16_t init) {
uint16_t count = init;
for (uint16_t offset = 0; offset < length; offset++)
for (uint8_t currentbyte = *(start + offset);
currentbyte;
currentbyte >>= 1)
if (currentbyte & 1) count++;
if (ones || length == 0)
return count;
else
return (length * 8) - count;
}
// Count the number of bits of a certain type.
// Args:
// data: The value you want bits counted for, starting from the LSB.
// length: How many bits to count.
// ones: Count the binary 1 bits. False for counting the 0 bits.
// init: Start the counting from this value.
// Returns:
// Nr. of bits found.
uint16_t countBits(const uint64_t data, const uint8_t length, const bool ones,
const uint16_t init) {
uint16_t count = init;
uint8_t bitsSoFar = length;
for (uint64_t remainder = data; remainder && bitsSoFar;
remainder >>= 1, bitsSoFar--)
if (remainder & 1) count++;
if (ones || length == 0)
return count;
else
return length - count;
}
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;
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;
if (nbits >= sizeof(data) * 8) return result;
// Mask off any unwanted bits and return the result.
return (result & ((1ULL << nbits) - 1));
}
+7
View File
@@ -23,6 +23,7 @@ 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);
String resultToHexidecimal(const decode_results *result);
#else
std::string uint64ToString(uint64_t input, uint8_t base = 10);
std::string typeToString(const decode_type_t protocol,
@@ -30,10 +31,16 @@ std::string typeToString(const decode_type_t protocol,
std::string resultToSourceCode(const decode_results *results);
std::string resultToTimingInfo(const decode_results *results);
std::string resultToHumanReadableBasic(const decode_results *results);
std::string resultToHexidecimal(const decode_results *result);
#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);
uint8_t xorBytes(uint8_t *start, const uint16_t length, const uint8_t init = 0);
uint16_t countBits(const uint8_t *start, const uint16_t length,
const bool ones = true, const uint16_t init = 0);
uint16_t countBits(const uint64_t data, const uint8_t length,
const bool ones = true, const uint16_t init = 0);
uint64_t invertBits(const uint64_t data, const uint16_t nbits);
#endif // IRUTILS_H_
+17 -20
View File
@@ -13,12 +13,12 @@
// Added by David Conran. (Inspired by IRremoteESP8266's implementation:
// https://github.com/z3t0/Arduino-IRremote)
#define AIWA_RC_T501_PRE_BITS 26U
#define AIWA_RC_T501_POST_BITS 1U
const uint16_t kAiwaRcT501PreBits = 26;
const uint16_t kAiwaRcT501PostBits = 1;
// NOTE: These are the compliment (inverted) of lirc values as
// lirc uses a '0' for a mark, and a '1' for a space.
#define AIWA_RC_T501_PRE_DATA 0x1D8113FULL // 26-bits
#define AIWA_RC_T501_POST_DATA 1ULL
const uint64_t kAiwaRcT501PreData = 0x1D8113FULL; // 26-bits
const uint64_t kAiwaRcT501PostData = 1ULL;
#if SEND_AIWA_RC_T501
// Send an Aiwa RC T501 formatted message.
@@ -26,7 +26,7 @@
// Args:
// data: The message to be sent.
// nbits: The number of bits of the message to be sent.
// Typically AIWA_RC_T501_BITS. Max is 37 = (64 - 27)
// Typically kAiwaRcT501Bits. Max is 37 = (64 - 27)
// repeat: The number of times the command is to be repeated.
//
// Status: BETA / Should work.
@@ -37,10 +37,9 @@ void IRsend::sendAiwaRCT501(uint64_t data, uint16_t nbits, uint16_t repeat) {
// Appears to be an extended NEC1 protocol. i.e. 42 bits instead of 32 bits.
// So use sendNEC instead, however the twist is it has a fixed 26 bit
// prefix, and a fixed postfix bit.
uint64_t new_data = (
(AIWA_RC_T501_PRE_DATA << (nbits + AIWA_RC_T501_POST_BITS)) |
(data << AIWA_RC_T501_POST_BITS) | AIWA_RC_T501_POST_DATA);
nbits += AIWA_RC_T501_PRE_BITS + AIWA_RC_T501_POST_BITS;
uint64_t new_data = ((kAiwaRcT501PreData << (nbits + kAiwaRcT501PostBits)) |
(data << kAiwaRcT501PostBits) | kAiwaRcT501PostData);
nbits += kAiwaRcT501PreBits + kAiwaRcT501PostBits;
if (nbits > sizeof(new_data) * 8)
return; // We are overflowing. Abort, and don't send.
sendNEC(new_data, nbits, repeat);
@@ -52,7 +51,7 @@ void IRsend::sendAiwaRCT501(uint64_t data, uint16_t nbits, uint16_t repeat) {
//
// Args:
// results: Ptr to the data to decode and where to store the decode result.
// nbits: The number of data bits to expect. Typically AIWA_RC_T501_BITS.
// nbits: The number of data bits to expect. Typically kAiwaRcT501Bits.
// strict: Flag indicating if we should perform strict matching.
// Returns:
// boolean: True if it can decode it, false if it can't.
@@ -71,12 +70,11 @@ void IRsend::sendAiwaRCT501(uint64_t data, uint16_t nbits, uint16_t repeat) {
bool IRrecv::decodeAiwaRCT501(decode_results *results, uint16_t nbits,
bool strict) {
// Compliance
if (strict && nbits != AIWA_RC_T501_BITS)
if (strict && nbits != kAiwaRcT501Bits)
return false; // Doesn't match our protocol defn.
// Add on the pre & post bits to our requested bit length.
uint16_t expected_nbits = nbits + AIWA_RC_T501_PRE_BITS +
AIWA_RC_T501_POST_BITS;
uint16_t expected_nbits = nbits + kAiwaRcT501PreBits + kAiwaRcT501PostBits;
uint64_t new_data;
if (expected_nbits > sizeof(new_data) * 8)
return false; // We can't possibly match something that big.
@@ -88,24 +86,23 @@ bool IRrecv::decodeAiwaRCT501(decode_results *results, uint16_t nbits,
new_data = results->value;
if (actual_bits < expected_nbits)
return false; // The data we caught was undersized. Throw it back.
if ((new_data & 0x1ULL) != AIWA_RC_T501_POST_DATA)
if ((new_data & 0x1ULL) != kAiwaRcT501PostData)
return false; // The post data doesn't match, so it can't be this protocol.
// Trim off the post data bit.
new_data >>= AIWA_RC_T501_POST_BITS;
actual_bits -= AIWA_RC_T501_POST_BITS;
new_data >>= kAiwaRcT501PostBits;
actual_bits -= kAiwaRcT501PostBits;
// Extract out our likely new value and put it back in the results.
actual_bits -= AIWA_RC_T501_PRE_BITS;
actual_bits -= kAiwaRcT501PreBits;
results->value = new_data & ((1ULL << actual_bits) - 1);
// Check the prefix data matches.
new_data >>= actual_bits; // Trim off the new data to expose the prefix.
if (new_data != AIWA_RC_T501_PRE_DATA) // Check the prefix.
if (new_data != kAiwaRcT501PreData) // Check the prefix.
return false;
// Compliance
if (strict && results->bits != expected_nbits)
return false;
if (strict && results->bits != expected_nbits) return false;
// Success
results->decode_type = AIWA_RC_T501;
+32 -58
View File
@@ -11,44 +11,38 @@ Copyright 2017 Schmolders
// Constants
// using SPACE modulation. MARK is always const 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
const uint16_t kArgoHdrMark = 6400;
const uint16_t kArgoHdrSpace = 3300;
const uint16_t kArgoBitMark = 400;
const uint16_t kArgoOneSpace = 2200;
const uint16_t kArgoZeroSpace = 900;
#if SEND_ARGO
// Send an Argo A/C message.
//
// Args:
// data: An array of ARGO_COMMAND_LENGTH bytes containing the IR command.
// data: An array of kArgoStateLength bytes containing the IR command.
//
// Status: ALPHA / Untested.
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;
if (nbytes < kArgoStateLength) return;
// 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);
sendGeneric(kArgoHdrMark, kArgoHdrSpace, kArgoBitMark, kArgoOneSpace,
kArgoBitMark, kArgoZeroSpace, 0, 0, // No Footer.
data, nbytes, 38, false, repeat, kDutyDefault);
}
#endif // SEND_ARGO
IRArgoAC::IRArgoAC(uint16_t pin) : _irsend(pin) {
stateReset();
}
IRArgoAC::IRArgoAC(uint16_t pin) : _irsend(pin) { stateReset(); }
void IRArgoAC::begin() {
_irsend.begin();
}
void IRArgoAC::begin() { _irsend.begin(); }
#if SEND_ARGO
void IRArgoAC::send() {
void IRArgoAC::send(const uint16_t repeat) {
checksum(); // Create valid checksum before sending
_irsend.sendArgo(argo);
_irsend.sendArgo(argo, kArgoStateLength, repeat);
}
#endif // SEND_ARGO
@@ -58,20 +52,18 @@ void IRArgoAC::checksum() {
// Only add up bytes to 9. byte 10 is 0b01 constant anyway.
// Assume that argo array is MSB first (left)
for (i = 0; i < 10; i++)
sum += argo[i];
for (i = 0; i < 10; i++) sum += argo[i];
sum = sum % 256; // modulo 256
// Append sum to end of array
// Set const part of checksum bit 10
argo[10] = 0b00000010;
argo[10] += sum << 2; // Shift up 2 bits and append to byte 10
argo[11] = sum >> 6; // Shift down 6 bits and add in two LSBs of bit 11
argo[11] = sum >> 6; // Shift down 6 bits and add in two LSBs of bit 11
}
void IRArgoAC::stateReset() {
for (uint8_t i = 0; i < ARGO_COMMAND_LENGTH; i++)
argo[i] = 0x0;
for (uint8_t i = 0; i < kArgoStateLength; i++) argo[i] = 0x0;
// Argo Message. Store MSB left.
// Default message:
@@ -84,8 +76,8 @@ void IRArgoAC::stateReset() {
this->off();
this->setTemp(20);
this->setRoomTemp(25);
this->setCoolMode(ARGO_COOL_AUTO);
this->setFan(ARGO_FAN_AUTO);
this->setCoolMode(kArgoCoolAuto);
this->setFan(kArgoFanAuto);
}
uint8_t* IRArgoAC::getRaw() {
@@ -116,9 +108,7 @@ void IRArgoAC::setPower(bool state) {
off();
}
uint8_t IRArgoAC::getPower() {
return ac_state;
}
uint8_t IRArgoAC::getPower() { return ac_state; }
void IRArgoAC::setMax(bool state) {
max_mode = state;
@@ -128,17 +118,15 @@ void IRArgoAC::setMax(bool state) {
argo[9] &= 0b11110111;
}
bool IRArgoAC::getMax() {
return max_mode;
}
bool IRArgoAC::getMax() { return max_mode; }
// Set the temp in deg C
// Sending 0 equals +4
void IRArgoAC::setTemp(uint8_t temp) {
if (temp < ARGO_MIN_TEMP)
temp = ARGO_MIN_TEMP;
else if (temp > ARGO_MAX_TEMP)
temp = ARGO_MAX_TEMP;
if (temp < kArgoMinTemp)
temp = kArgoMinTemp;
else if (temp > kArgoMaxTemp)
temp = kArgoMaxTemp;
// Store in attributes
set_temp = temp;
@@ -154,9 +142,7 @@ void IRArgoAC::setTemp(uint8_t temp) {
argo[3] += temp >> 2; // remove lowest to bits and append in 0-2
}
uint8_t IRArgoAC::getTemp() {
return set_temp;
}
uint8_t IRArgoAC::getTemp() { return set_temp; }
// Set the speed of the fan
void IRArgoAC::setFan(uint8_t fan) {
@@ -168,18 +154,14 @@ void IRArgoAC::setFan(uint8_t fan) {
argo[3] += fan << 3;
}
uint8_t IRArgoAC::getFan() {
return fan_mode;
}
uint8_t IRArgoAC::getFan() { return fan_mode; }
void IRArgoAC::setFlap(uint8_t flap) {
flap_mode = flap;
// TODO(kaschmo): set correct bits for flap mode
}
uint8_t IRArgoAC::getFlap() {
return flap_mode;
}
uint8_t IRArgoAC::getFlap() { return flap_mode; }
uint8_t IRArgoAC::getMode() {
// return cooling 0, heating 1
@@ -196,9 +178,7 @@ void IRArgoAC::setCoolMode(uint8_t mode) {
argo[2] += mode << 3;
}
uint8_t IRArgoAC::getCoolMode() {
return cool_mode;
}
uint8_t IRArgoAC::getCoolMode() { return cool_mode; }
void IRArgoAC::setHeatMode(uint8_t mode) {
ac_mode = 1; // Set ac mode to heating
@@ -211,9 +191,7 @@ void IRArgoAC::setHeatMode(uint8_t mode) {
argo[2] += mode << 3;
}
uint8_t IRArgoAC::getHeatMode() {
return heat_mode;
}
uint8_t IRArgoAC::getHeatMode() { return heat_mode; }
void IRArgoAC::setNight(bool state) {
night_mode = state;
@@ -224,9 +202,7 @@ void IRArgoAC::setNight(bool state) {
argo[9] &= 0b11111011;
}
bool IRArgoAC::getNight() {
return night_mode;
}
bool IRArgoAC::getNight() { return night_mode; }
void IRArgoAC::setiFeel(bool state) {
ifeel_mode = state;
@@ -237,9 +213,7 @@ void IRArgoAC::setiFeel(bool state) {
argo[9] &= 0b01111111;
}
bool IRArgoAC::getiFeel() {
return ifeel_mode;
}
bool IRArgoAC::getiFeel() { return ifeel_mode; }
void IRArgoAC::setTime() {
// TODO(kaschmo): use function call from checksum to set time first
+45 -27
View File
@@ -29,32 +29,50 @@
// Constants. Store MSB left.
#define ARGO_COOL_ON 0U // 0b000
#define ARGO_COOL_OFF 3U // 0b110
#define ARGO_COOL_AUTO 2U // 0b010
#define ARGO_COOl_HUM 1U // 0b100
const uint8_t kArgoCoolOn = 0; // 0b000
const uint8_t kArgoCoolOff = 3; // 0b110
const uint8_t kArgoCoolAuto = 2; // 0b010
const uint8_t kArgoCoolHum = 1; // 0b100
const uint8_t kArgoHeatOn = 0; // 0b001
const uint8_t kArgoHeatAuto = 1; // 0b101
const uint8_t kArgoHeatBlink = 2; // 0b011 // ??no idea what mode that is
const uint8_t kArgoMinTemp = 10; // Celsius offset +4
const uint8_t kArgoMaxTemp = 32; // Celsius
const uint8_t kArgoFanAuto = 0; // 0b00
const uint8_t kArgoFan3 = 3; // 0b11
const uint8_t kArgoFan2 = 2; // 0b01
const uint8_t kArgoFan1 = 1; // 0b10
const uint8_t kArgoFlapAuto = 0; // 0b000
const uint8_t kArgoFlap1 = 1; // 0b100
const uint8_t kArgoFlap2 = 2; // 0b010
const uint8_t kArgoFlap3 = 3; // 0b110
const uint8_t kArgoFlap4 = 4; // 0b001
const uint8_t kArgoFlap5 = 5; // 0b101
const uint8_t kArgoFlap6 = 6; // 0b011
const uint8_t kArgoFlapFull = 7; // 0b111
#define ARGO_HEAT_ON 0U // 0b001
#define ARGO_HEAT_AUTO 1U // 0b101
#define ARGO_HEAT_BLINK 2U // 0b011 // ??no idea what mode that is
#define ARGO_MIN_TEMP 10U // Celsius offset +4
#define ARGO_MAX_TEMP 32U // Celsius
#define ARGO_FAN_AUTO 0U // 0b00
#define ARGO_FAN_3 3U // 0b11
#define ARGO_FAN_2 2U // 0b01
#define ARGO_FAN_1 1U // 0b10
#define ARGO_FLAP_AUTO 0U // 0b000
#define ARGO_FLAP_1 1U // 0b100
#define ARGO_FLAP_2 2U // 0b010
#define ARGO_FLAP_3 3U // 0b110
#define ARGO_FLAP_4 4U // 0b001
#define ARGO_FLAP_5 5U // 0b101
#define ARGO_FLAP_6 6U // 0b011
#define ARGO_FLAP_FULL 7U // 0b111
// Legacy defines. (Deperecated)
#define ARGO_COOL_ON kArgoCoolOn
#define ARGO_COOL_OFF kArgoCoolOff
#define ARGO_COOL_AUTO kArgoCoolAuto
#define ARGO_COOl_HUM kArgoCoolHum
#define ARGO_HEAT_ON kArgoHeatOn
#define ARGO_HEAT_AUTO kArgoHeatAuto
#define ARGO_HEAT_BLINK kArgoHeatBlink
#define ARGO_MIN_TEMP kArgoMinTemp
#define ARGO_MAX_TEMP kArgoMaxTemp
#define ARGO_FAN_AUTO kArgoFanAuto
#define ARGO_FAN_3 kArgoFan3
#define ARGO_FAN_2 kArgoFan2
#define ARGO_FAN_1 kArgoFan1
#define ARGO_FLAP_AUTO kArgoFlapAuto
#define ARGO_FLAP_1 kArgoFlap1
#define ARGO_FLAP_2 kArgoFlap2
#define ARGO_FLAP_3 kArgoFlap3
#define ARGO_FLAP_4 kArgoFlap4
#define ARGO_FLAP_5 kArgoFlap5
#define ARGO_FLAP_6 kArgoFlap6
#define ARGO_FLAP_FULL kArgoFlapFull
class IRArgoAC {
@@ -62,7 +80,7 @@ class IRArgoAC {
explicit IRArgoAC(uint16_t pin);
#if SEND_ARGO
void send();
void send(const uint16_t repeat = kArgoDefaultRepeat);
#endif // SEND_ARGO
void begin();
void on();
@@ -103,7 +121,7 @@ class IRArgoAC {
private:
// # of bytes per command
uint8_t argo[ARGO_COMMAND_LENGTH]; // Defined in IRremoteESP8266.h
uint8_t argo[kArgoStateLength]; // Defined in IRremoteESP8266.h
void stateReset();
void checksum();
IRsend _irsend; // instance of the IR send class
+24 -29
View File
@@ -18,19 +18,19 @@
// 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
const uint16_t kCarrierAcHdrMark = 8532;
const uint16_t kCarrierAcHdrSpace = 4228;
const uint16_t kCarrierAcBitMark = 628;
const uint16_t kCarrierAcOneSpace = 1320;
const uint16_t kCarrierAcZeroSpace = 532;
const uint16_t kCarrierAcGap = 20000;
#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.
// nbits: The bit size of the message being sent. typically kCarrierAcBits.
// repeat: The number of times the message is to be repeated.
//
// Status: BETA / Appears to work on real devices.
@@ -40,11 +40,10 @@ void IRsend::sendCarrierAC(uint64_t data, uint16_t nbits, uint16_t repeat) {
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);
sendGeneric(kCarrierAcHdrMark, kCarrierAcHdrSpace, kCarrierAcBitMark,
kCarrierAcOneSpace, kCarrierAcBitMark, kCarrierAcZeroSpace,
kCarrierAcBitMark, kCarrierAcGap, temp_data, nbits, 38, true,
0, kDutyDefault);
temp_data = invertBits(temp_data, nbits);
}
}
@@ -58,7 +57,7 @@ void IRsend::sendCarrierAC(uint64_t data, uint16_t nbits, uint16_t repeat) {
// 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.
// Typically kCarrierAcBits.
// strict: Flag to indicate if we strictly adhere to the specification.
// Returns:
// boolean: True if it can decode it, false if it can't.
@@ -66,42 +65,38 @@ void IRsend::sendCarrierAC(uint64_t data, uint16_t nbits, uint16_t repeat) {
// Status: ALPHA / Untested.
//
bool IRrecv::decodeCarrierAC(decode_results *results, uint16_t nbits,
bool strict) {
if (results->rawlen < ((2 * nbits + HEADER + FOOTER) * 3) - 1)
bool strict) {
if (results->rawlen < ((2 * nbits + kHeader + kFooter) * 3) - 1)
return false; // Can't possibly be a valid Carrier message.
if (strict && nbits != CARRIER_AC_BITS)
if (strict && nbits != kCarrierAcBits)
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;
uint16_t offset = kStartOffset;
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))
if (!matchMark(results->rawbuf[offset++], kCarrierAcHdrMark)) return false;
if (!matchSpace(results->rawbuf[offset++], kCarrierAcHdrSpace))
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);
match_result_t data_result =
matchData(&(results->rawbuf[offset]), nbits, kCarrierAcBitMark,
kCarrierAcOneSpace, kCarrierAcBitMark, kCarrierAcZeroSpace);
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 (!matchMark(results->rawbuf[offset++], kCarrierAcBitMark)) return false;
if (offset < results->rawlen &&
!matchAtLeast(results->rawbuf[offset++], CARRIER_AC_GAP))
!matchAtLeast(results->rawbuf[offset++], kCarrierAcGap))
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;
if (i > 0 && prev_data != invertBits(data, nbits)) return false;
}
}
+307 -44
View File
@@ -1,6 +1,11 @@
// Copyright bakrus
// Copyright 2017 David Conran
#include "ir_Coolix.h"
#include <algorithm>
#ifndef ARDUINO
#include <string>
#endif
#include "IRrecv.h"
#include "IRsend.h"
#include "IRutils.h"
@@ -12,32 +17,36 @@
// CCCCC OOOO0 OOOO0 LLLLLLL IIIII XX XX
// Coolix A/C / heatpump added by (send) bakrus & (decode) crankyoldgit
//
// Supports:
// RG57K7(B)/BGEF remote control for Beko BINR 070/071 split-type aircon.
// Ref:
// https://github.com/markszabo/IRremoteESP8266/issues/484
// Constants
// Pulse parms are *50-100 for the Mark and *50+100 for the space
// First MARK is the one after the long gap
// pulse parameters in usec
#define COOLIX_TICK 560U // Approximately 21 cycles at 38kHz
#define COOLIX_BIT_MARK_TICKS 1U
#define COOLIX_BIT_MARK (COOLIX_BIT_MARK_TICKS * COOLIX_TICK)
#define COOLIX_ONE_SPACE_TICKS 3U
#define COOLIX_ONE_SPACE (COOLIX_ONE_SPACE_TICKS * COOLIX_TICK)
#define COOLIX_ZERO_SPACE_TICKS 1U
#define COOLIX_ZERO_SPACE (COOLIX_ZERO_SPACE_TICKS * COOLIX_TICK)
#define COOLIX_HDR_MARK_TICKS 8U
#define COOLIX_HDR_MARK (COOLIX_HDR_MARK_TICKS * COOLIX_TICK)
#define COOLIX_HDR_SPACE_TICKS 8U
#define COOLIX_HDR_SPACE (COOLIX_HDR_SPACE_TICKS * COOLIX_TICK)
#define COOLIX_MIN_GAP_TICKS (COOLIX_HDR_MARK_TICKS + \
COOLIX_ZERO_SPACE_TICKS)
#define COOLIX_MIN_GAP (COOLIX_MIN_GAP_TICKS * COOLIX_TICK)
const uint16_t kCoolixTick = 560; // Approximately 21 cycles at 38kHz
const uint16_t kCoolixBitMarkTicks = 1;
const uint16_t kCoolixBitMark = kCoolixBitMarkTicks * kCoolixTick;
const uint16_t kCoolixOneSpaceTicks = 3;
const uint16_t kCoolixOneSpace = kCoolixOneSpaceTicks * kCoolixTick;
const uint16_t kCoolixZeroSpaceTicks = 1;
const uint16_t kCoolixZeroSpace = kCoolixZeroSpaceTicks * kCoolixTick;
const uint16_t kCoolixHdrMarkTicks = 8;
const uint16_t kCoolixHdrMark = kCoolixHdrMarkTicks * kCoolixTick;
const uint16_t kCoolixHdrSpaceTicks = 8;
const uint16_t kCoolixHdrSpace = kCoolixHdrSpaceTicks * kCoolixTick;
const uint16_t kCoolixMinGapTicks = kCoolixHdrMarkTicks + kCoolixZeroSpaceTicks;
const uint16_t kCoolixMinGap = kCoolixMinGapTicks * kCoolixTick;
#if SEND_COOLIX
// Send a Coolix message
//
// Args:
// data: Contents of the message to be sent.
// nbits: Nr. of bits of data to be sent. Typically COOLIX_BITS.
// nbits: Nr. of bits of data to be sent. Typically kCoolixBits.
// repeat: Nr. of additional times the message is to be sent.
//
// Status: BETA / Probably works.
@@ -46,16 +55,15 @@
// https://github.com/z3t0/Arduino-IRremote/blob/master/ir_COOLIX.cpp
// TODO(anyone): Verify repeat functionality against a real unit.
void IRsend::sendCOOLIX(uint64_t data, uint16_t nbits, uint16_t repeat) {
if (nbits % 8 != 0)
return; // nbits is required to be a multiple of 8.
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++) {
// Header
mark(COOLIX_HDR_MARK);
space(COOLIX_HDR_SPACE);
mark(kCoolixHdrMark);
space(kCoolixHdrSpace);
// Data
// Break data into byte segments, starting at the Most Significant
@@ -64,28 +72,285 @@ void IRsend::sendCOOLIX(uint64_t data, uint16_t nbits, uint16_t repeat) {
// Grab a bytes worth of data.
uint8_t segment = (data >> (nbits - i)) & 0xFF;
// Normal
sendData(COOLIX_BIT_MARK, COOLIX_ONE_SPACE,
COOLIX_BIT_MARK, COOLIX_ZERO_SPACE,
segment, 8, true);
sendData(kCoolixBitMark, kCoolixOneSpace, kCoolixBitMark,
kCoolixZeroSpace, segment, 8, true);
// Inverted.
sendData(COOLIX_BIT_MARK, COOLIX_ONE_SPACE,
COOLIX_BIT_MARK, COOLIX_ZERO_SPACE,
segment ^ 0xFF, 8, true);
sendData(kCoolixBitMark, kCoolixOneSpace, kCoolixBitMark,
kCoolixZeroSpace, segment ^ 0xFF, 8, true);
}
// Footer
mark(COOLIX_BIT_MARK);
space(COOLIX_MIN_GAP); // Pause before repeating
mark(kCoolixBitMark);
space(kCoolixMinGap); // Pause before repeating
}
}
#endif
// IRCoolixAC class
// Supports:
// RG57K7(B)/BGEF remote control for Beko BINR 070/071 split-type aircon.
// Ref:
// https://github.com/markszabo/IRremoteESP8266/issues/484
IRCoolixAC::IRCoolixAC(uint16_t pin) : _irsend(pin) { stateReset(); }
void IRCoolixAC::stateReset() { remote_state = kCoolixDefaultState; }
void IRCoolixAC::begin() { _irsend.begin(); }
#if SEND_COOLIX
void IRCoolixAC::send(const uint16_t repeat) {
_irsend.sendCOOLIX(remote_state, kCoolixBits, repeat);
}
#endif // SEND_COOLIX
uint32_t IRCoolixAC::getRaw() { return remote_state; }
void IRCoolixAC::setRaw(const uint32_t new_code) { remote_state = new_code; }
void IRCoolixAC::setTempRaw(const uint8_t code) {
remote_state &= ~kCoolixTempMask; // Clear the old temp.
remote_state |= (code << 4);
}
uint8_t IRCoolixAC::getTempRaw() {
return (remote_state & kCoolixTempMask) >> 4;
}
void IRCoolixAC::setTemp(const uint8_t desired) {
// Range check.
uint8_t temp = std::min(desired, kCoolixTempMax);
temp = std::max(temp, kCoolixTempMin);
setTempRaw(kCoolixTempMap[temp - kCoolixTempMin]);
}
uint8_t IRCoolixAC::getTemp() {
uint8_t code = getTempRaw();
uint8_t i;
for (i = 0; i < kCoolixTempRange; i++)
if (kCoolixTempMap[i] == code) return kCoolixTempMin + i;
return kCoolixUnknown; // Not a temp we expected.
}
void IRCoolixAC::setSensorTempRaw(const uint8_t code) {
remote_state &= ~kCoolixSensorTempMask; // Clear previous sensor temp.
remote_state |= ((code & 0xF) << 8);
}
void IRCoolixAC::setSensorTemp(const uint8_t desired) {
uint8_t temp = desired;
temp = std::min(temp, kCoolixSensorTempMax);
temp = std::max(temp, kCoolixSensorTempMin);
setSensorTempRaw(temp - kCoolixSensorTempMin);
setZoneFollow(true); // Setting a Sensor temp means you want to Zone Follow.
}
uint8_t IRCoolixAC::getSensorTemp() {
return ((remote_state & kCoolixSensorTempMask) >> 8) + kCoolixSensorTempMin;
}
bool IRCoolixAC::getPower() {
// There is only an off state. Everything else is "on".
return remote_state != kCoolixOff;
}
void IRCoolixAC::setPower(const bool power) {
if (!power) remote_state = kCoolixOff;
// There really is no distinct "on" setting, so do nothing.
}
bool IRCoolixAC::getSwing() { return remote_state == kCoolixSwing; }
void IRCoolixAC::setSwing() {
// Assumes that repeated sending "swing" toggles the action on the device.
remote_state = kCoolixSwing;
}
bool IRCoolixAC::getSleep() { return remote_state == kCoolixSleep; }
void IRCoolixAC::setSleep() { remote_state = kCoolixSleep; }
bool IRCoolixAC::getTurbo() { return remote_state == kCoolixTurbo; }
void IRCoolixAC::setTurbo() {
// Assumes that repeated sending "turbo" toggles the action on the device.
remote_state = kCoolixTurbo;
}
bool IRCoolixAC::getLed() { return remote_state == kCoolixLed; }
void IRCoolixAC::setLed() {
// Assumes that repeated sending "Led" toggles the action on the device.
remote_state = kCoolixLed;
}
bool IRCoolixAC::getClean() { return remote_state == kCoolixClean; }
void IRCoolixAC::setClean() { remote_state = kCoolixClean; }
bool IRCoolixAC::getZoneFollow() {
return remote_state & kCoolixZoneFollowMask;
}
// Internal use only.
void IRCoolixAC::setZoneFollow(bool state) {
if (state) {
remote_state |= kCoolixZoneFollowMask;
} else {
remote_state &= ~kCoolixZoneFollowMask;
}
}
void IRCoolixAC::clearSensorTemp() {
setZoneFollow(false);
setSensorTempRaw(kCoolixSensorTempIgnoreCode);
}
void IRCoolixAC::setMode(const uint8_t mode) {
uint32_t actualmode = mode;
// Fan mode is a special case of Dry.
if (mode == kCoolixFan) actualmode = kCoolixDry;
switch (actualmode) {
case kCoolixCool:
case kCoolixAuto:
case kCoolixHeat:
case kCoolixDry:
remote_state = (remote_state & ~kCoolixModeMask) | (actualmode << 2);
// Force the temp into a known-good state.
setTemp(getTemp());
}
if (mode == kCoolixFan) setTempRaw(kCoolixFanTempCode);
}
uint8_t IRCoolixAC::getMode() {
uint8_t mode = (remote_state & kCoolixModeMask) >> 2;
if (mode == kCoolixDry)
if (getTempRaw() == kCoolixFanTempCode) return kCoolixFan;
return mode;
}
uint8_t IRCoolixAC::getFan() { return (remote_state & kCoolixFanMask) >> 13; }
void IRCoolixAC::setFan(const uint8_t speed) {
uint8_t newspeed = speed;
switch (speed) {
case kCoolixFanMin:
case kCoolixFanMed:
case kCoolixFanMax:
case kCoolixFanAuto:
case kCoolixFanAuto0:
case kCoolixFanZoneFollow:
case kCoolixFanFixed:
break;
default: // Unknown speed requested.
newspeed = kCoolixFanAuto;
}
remote_state &= ~kCoolixFanMask;
remote_state |= ((newspeed << 13) & kCoolixFanMask);
}
// Convert the internal state into a human readable string.
#ifdef ARDUINO
String IRCoolixAC::toString() {
String result = "";
#else
std::string IRCoolixAC::toString() {
std::string result = "";
#endif // ARDUINO
result += "Power: ";
if (getPower()) {
result += "On";
} else {
result += "Off";
return result; // If it's off, there is no other info.
}
result += ", Fan: " + uint64ToString(getFan());
switch (getFan()) {
case kCoolixFanAuto:
result += " (AUTO)";
break;
case kCoolixFanAuto0:
result += " (AUTO0)";
break;
case kCoolixFanMax:
result += " (MAX)";
break;
case kCoolixFanMin:
result += " (MIN)";
break;
case kCoolixFanMed:
result += " (MED)";
break;
case kCoolixFanZoneFollow:
result += " (ZONEFOLLOW)";
break;
case kCoolixFanFixed:
result += " (FIXED)";
break;
default:
result += " (UNKNOWN)";
}
// Special modes.
if (getSwing()) {
result += ", Swing: Toggle";
return result;
}
if (getSleep()) {
result += ", Sleep: Toggle";
return result;
}
if (getTurbo()) {
result += ", Turbo: Toggle";
return result;
}
if (getLed()) {
result += ", Led: Toggle";
return result;
}
if (getClean()) {
result += ", Mode: Self clean";
return result;
}
result += ", Mode: " + uint64ToString(getMode());
switch (getMode()) {
case kCoolixAuto:
result += " (AUTO)";
break;
case kCoolixCool:
result += " (COOL)";
break;
case kCoolixHeat:
result += " (HEAT)";
break;
case kCoolixDry:
result += " (DRY)";
break;
case kCoolixFan:
result += " (FAN)";
break;
default:
result += " (UNKNOWN)";
}
if (getMode() != kCoolixFan) // Fan mode doesn't have a temperature.
result += ", Temp: " + uint64ToString(getTemp()) + "C";
result += ", Zone Follow: ";
if (getZoneFollow())
result += "On";
else
result += "Off";
result += ", Sensor Temp: ";
if (getSensorTemp() > kCoolixSensorTempMax)
result += "Ignored";
else
result += uint64ToString(getSensorTemp()) + "C";
return result;
}
#if DECODE_COOLIX
// Decode the supplied Coolix 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 COOLIX_BITS.
// nbits: The number of data bits to expect. Typically kCoolixBits.
// strict: Flag indicating if we should perform strict matching.
// Returns:
// boolean: True if it can decode it, false if it can't.
@@ -95,42 +360,41 @@ bool IRrecv::decodeCOOLIX(decode_results *results, uint16_t nbits,
bool strict) {
// The protocol sends the data normal + inverted, alternating on
// each byte. Hence twice the number of expected data bits.
if (results->rawlen < 2 * 2 * nbits + HEADER + FOOTER - 1)
if (results->rawlen < 2 * 2 * nbits + kHeader + kFooter - 1)
return false; // Can't possibly be a valid COOLIX message.
if (strict && nbits != COOLIX_BITS)
return false; // Not strictly a COOLIX message.
if (strict && nbits != kCoolixBits)
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;
uint64_t data = 0;
uint64_t inverted = 0;
uint16_t offset = OFFSET_START;
uint16_t offset = kStartOffset;
if (nbits > sizeof(data) * 8)
return false; // We can't possibly capture a Coolix packet that big.
// Header
if (!matchMark(results->rawbuf[offset], COOLIX_HDR_MARK)) return false;
if (!matchMark(results->rawbuf[offset], kCoolixHdrMark)) return false;
// Calculate how long the common tick time is based on the header mark.
uint32_t m_tick = results->rawbuf[offset++] * RAWTICK / COOLIX_HDR_MARK_TICKS;
if (!matchSpace(results->rawbuf[offset], COOLIX_HDR_SPACE)) return false;
uint32_t m_tick = results->rawbuf[offset++] * kRawTick / kCoolixHdrMarkTicks;
if (!matchSpace(results->rawbuf[offset], kCoolixHdrSpace)) return false;
// Calculate how long the common tick time is based on the header space.
uint32_t s_tick = results->rawbuf[offset++] * RAWTICK /
COOLIX_HDR_SPACE_TICKS;
uint32_t s_tick = results->rawbuf[offset++] * kRawTick / kCoolixHdrSpaceTicks;
// Data
// Twice as many bits as there are normal plus inverted bits.
for (uint16_t i = 0; i < nbits * 2; i++, offset++) {
bool flip = (i / 8) % 2;
if (!matchMark(results->rawbuf[offset++], COOLIX_BIT_MARK_TICKS * m_tick))
if (!matchMark(results->rawbuf[offset++], kCoolixBitMarkTicks * m_tick))
return false;
if (matchSpace(results->rawbuf[offset], COOLIX_ONE_SPACE_TICKS * s_tick)) {
if (matchSpace(results->rawbuf[offset], kCoolixOneSpaceTicks * s_tick)) {
if (flip)
inverted = (inverted << 1) | 1;
else
data = (data << 1) | 1;
} else if (matchSpace(results->rawbuf[offset],
COOLIX_ZERO_SPACE_TICKS * s_tick)) {
kCoolixZeroSpaceTicks * s_tick)) {
if (flip)
inverted <<= 1;
else
@@ -141,18 +405,17 @@ bool IRrecv::decodeCOOLIX(decode_results *results, uint16_t nbits,
}
// Footer
if (!matchMark(results->rawbuf[offset++], COOLIX_BIT_MARK_TICKS * m_tick))
if (!matchMark(results->rawbuf[offset++], kCoolixBitMarkTicks * m_tick))
return false;
if (offset < results->rawlen &&
!matchAtLeast(results->rawbuf[offset], COOLIX_MIN_GAP_TICKS * s_tick))
!matchAtLeast(results->rawbuf[offset], kCoolixMinGapTicks * s_tick))
return false;
// Compliance
uint64_t orig = data; // Save a copy of the data.
if (strict) {
for (uint16_t i = 0; i < nbits; i += 8, data >>= 8, inverted >>= 8)
if ((data & 0xFF) != ((inverted & 0xFF) ^ 0xFF))
return false;
if ((data & 0xFF) != ((inverted & 0xFF) ^ 0xFF)) return false;
}
// Success
+140
View File
@@ -0,0 +1,140 @@
// Coolix A/C
//
// Copyright 2018 David Conran
#ifndef IR_COOLIX_H_
#define IR_COOLIX_H_
#define __STDC_LIMIT_MACROS
#include <stdint.h>
#ifndef UNIT_TEST
#include <Arduino.h>
#else
#include <string>
#endif
#include "IRremoteESP8266.h"
#include "IRsend.h"
// CCCCC OOOOO OOOOO LL IIIII XX XX
// CC C OO OO OO OO LL III XX XX
// CC OO OO OO OO LL III XXXX
// CC C OO OO OO OO LL III XX XX
// CCCCC OOOO0 OOOO0 LLLLLLL IIIII XX XX
// Supports:
// RG57K7(B)/BGEF remote control for Beko BINR 070/071 split-type aircon.
// Ref:
// https://github.com/markszabo/IRremoteESP8266/issues/484
// Kudos:
// Hamper: For the breakdown and mapping of the bit values.
// Constants
// Modes
const uint8_t kCoolixCool = 0b00;
const uint8_t kCoolixDry = 0b01;
const uint8_t kCoolixAuto = 0b10;
const uint8_t kCoolixHeat = 0b11;
const uint8_t kCoolixFan = 4; // Synthetic.
const uint32_t kCoolixModeMask = 0b000000000000000000001100; // 0xC
const uint32_t kCoolixZoneFollowMask = 0b000010000000000000000000; // 0x80000
// Fan Control
const uint8_t kCoolixFanMin = 0b100;
const uint8_t kCoolixFanMed = 0b010;
const uint8_t kCoolixFanMax = 0b001;
const uint8_t kCoolixFanAuto = 0b101;
const uint8_t kCoolixFanAuto0 = 0b000;
const uint8_t kCoolixFanZoneFollow = 0b110;
const uint8_t kCoolixFanFixed = 0b111;
const uint32_t kCoolixFanMask = 0b000000001110000000000000; // 0x00E000
// Temperature
const uint8_t kCoolixTempMin = 17; // Celsius
const uint8_t kCoolixTempMax = 30; // Celsius
const uint8_t kCoolixTempRange = kCoolixTempMax - kCoolixTempMin + 1;
const uint8_t kCoolixFanTempCode = 0b1110; // Part of Fan Mode.
const uint32_t kCoolixTempMask = 0b11110000;
const uint8_t kCoolixTempMap[kCoolixTempRange] = {
0b0000, // 17C
0b0001, // 18c
0b0011, // 19C
0b0010, // 20C
0b0110, // 21C
0b0111, // 22C
0b0101, // 23C
0b0100, // 24C
0b1100, // 25C
0b1101, // 26C
0b1001, // 27C
0b1000, // 28C
0b1010, // 29C
0b1011 // 30C
};
const uint8_t kCoolixSensorTempMin = 16; // Celsius
const uint8_t kCoolixSensorTempMax = 30; // Celsius
const uint8_t kCoolixSensorTempIgnoreCode = 0b1111;
const uint32_t kCoolixSensorTempMask = 0b000000000000111100000000; // 0xF00
// Fixed states/messages.
const uint8_t kCoolixPrefix = 0b1011; // 0xB
const uint8_t kCoolixUnknown = 0xFF;
const uint32_t kCoolixOff = 0b101100100111101111100000; // 0xB27BE0
const uint32_t kCoolixSwing = 0b101100100110101111100000; // 0xB26BE0
const uint32_t kCoolixSleep = 0b101100101110000000000011; // 0xB2E003
const uint32_t kCoolixTurbo = 0b101101011111010110100010; // 0xB5F5A2
const uint32_t kCoolixLed = 0b101101011111010110100101; // 0xB5F5A5
const uint32_t kCoolixClean = 0b101101011111010110101010; // 0xB5F5AA
// On, 25C, Mode: Auto, Fan: Auto, Zone Follow: Off, Sensor Temp: Ignore.
const uint32_t kCoolixDefaultState = 0b101100101011111111001000; // 0xB2BFC8
// Classes
class IRCoolixAC {
public:
explicit IRCoolixAC(uint16_t pin);
void stateReset();
#if SEND_COOLIX
void send(const uint16_t repeat = kCoolixDefaultRepeat);
#endif // SEND_COOLIX
void begin();
void on();
void off();
void setPower(const bool state);
bool getPower();
void setTemp(const uint8_t temp);
uint8_t getTemp();
void setSensorTemp(const uint8_t desired);
uint8_t getSensorTemp();
void clearSensorTemp();
void setFan(const uint8_t fan);
uint8_t getFan();
void setMode(const uint8_t mode);
uint8_t getMode();
void setSwing();
bool getSwing();
void setSleep();
bool getSleep();
void setTurbo();
bool getTurbo();
void setLed();
bool getLed();
void setClean();
bool getClean();
bool getZoneFollow();
uint32_t getRaw();
void setRaw(const uint32_t new_code);
#ifdef ARDUINO
String toString();
#else
std::string toString();
#endif
private:
// The state of the IR remote in IR code form.
uint32_t remote_state;
IRsend _irsend;
void setTempRaw(const uint8_t code);
uint8_t getTempRaw();
void setSensorTempRaw(const uint8_t code);
void setZoneFollow(const bool state);
};
#endif // IR_COOLIX_H_
File diff suppressed because it is too large. Load diff
+230 -100
View File
@@ -1,5 +1,6 @@
// Copyright 2016 sillyfrog
// Copyright 2017 sillyfrog, crankyoldgit
// Copyright 2018-2019 crankyoldgit
#ifndef IR_DAIKIN_H_
#define IR_DAIKIN_H_
@@ -8,8 +9,8 @@
#else
#include <string>
#endif
#include "IRremoteESP8266.h"
#include "IRrecv.h"
#include "IRremoteESP8266.h"
#include "IRsend.h"
// Option to disable the additional Daikin debug info to conserve memory
@@ -22,118 +23,161 @@
// DDDDDD AA AA IIIII KK KK IIIII NN NN
/*
Daikin AC map
byte 5=Current time, mins past midnight, low bits
byte 6
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
b6+b5+b4 = Mode
Modes: b6+b5+b4
011 = Cool
100 = Heat (temp 23)
110 = FAN (temp not shown, but 25)
000 = Fully Automatic (temp 25)
010 = DRY (temp 0xc0 = 96 degrees c)
b3 = 1
b2 = OFF timer set
b1 = ON timer set
b0 = Air Conditioner ON
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×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
1111 = Swing up/down on
byte 17
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
byte 7= checksum of the first part (and last byte before a 29ms pause)
byte 13=mode
b7 = 0
b6+b5+b4 = Mode
Modes: b6+b5+b4
011 = Cool
100 = Heat (temp 23)
110 = FAN (temp not shown, but 25)
000 = Fully Automatic (temp 25)
010 = DRY (temp 0xc0 = 96 degrees c)
b3 = 1
b2 = OFF timer set
b1 = ON timer set
b0 = Air Conditioner ON
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×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
1111 = Swing up/down on
byte 17
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
byte 20=Off timer mins past midnight, high bits
byte 21=Aux -> Powerful (bit 1), Silent (bit 5)
byte 24=Aux2
b1: Sensor
b2: Econo mode
b7: Intelligent eye on
byte 25=Aux3
byte 25=Aux3
b1: Mold Proof
byte 26= checksum of the second part
byte 26= checksum of the second part
*/
// Constants
#define DAIKIN_COOL 0b011
#define DAIKIN_HEAT 0b100
#define DAIKIN_FAN 0b110
#define DAIKIN_AUTO 0b000
#define DAIKIN_DRY 0b010
#define DAIKIN_MIN_TEMP 10U // Celsius
#define DAIKIN_MAX_TEMP 32U // Celsius
#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
const uint8_t kDaikinAuto = 0b000;
const uint8_t kDaikinDry = 0b010;
const uint8_t kDaikinCool = 0b011;
const uint8_t kDaikinHeat = 0b100;
const uint8_t kDaikinFan = 0b110;
const uint8_t kDaikinMinTemp = 10; // Celsius
const uint8_t kDaikinMaxTemp = 32; // Celsius
const uint8_t kDaikinFanMin = 1;
const uint8_t kDaikinFanMax = 5;
const uint8_t kDaikinFanAuto = 0b1010;
const uint8_t kDaikinFanQuiet = 0b1011;
const uint8_t kDaikinBytePower = 13;
const uint8_t kDaikinBitPower = 0b00000001;
const uint8_t kDaikinBytePowerful = 21;
const uint8_t kDaikinBitPowerful = 0b00000001;
const uint8_t kDaikinByteSilent = 21;
const uint8_t kDaikinBitSilent = 0b00100000;
const uint8_t kDaikinByteSensor = 24;
const uint8_t kDaikinBitSensor = 0b00000010;
const uint8_t kDaikinByteEcono = 24;
const uint8_t kDaikinBitEcono = 0b00000100;
const uint8_t kDaikinByteEye = 24;
const uint8_t kDaikinBitEye = 0b10000000;
const uint8_t kDaikinByteMold = 25;
const uint8_t kDaikinBitMold = 0b00000010;
const uint8_t kDaikinByteOffTimer = 13;
const uint8_t kDaikinBitOffTimer = 0b00000100;
const uint8_t kDaikinByteOnTimer = 13;
const uint8_t kDaikinBitOnTimer = 0b00000010;
const uint16_t kDaikinUnusedTime = 0x600;
const uint8_t kDaikinBeepQuiet = 1;
const uint8_t kDaikinBeepLoud = 2;
const uint8_t kDaikinBeepOff = 3;
const uint8_t kDaikinLightBright = 1;
const uint8_t kDaikinLightDim = 2;
const uint8_t kDaikinLightOff = 3;
const uint8_t kDaikinCurBit = kDaikinStateLength;
const uint8_t kDaikinCurIndex = kDaikinStateLength + 1;
const uint8_t kDaikinTolerance = 35;
const uint16_t kDaikinMarkExcess = kMarkExcess;
const uint16_t kDaikinHdrMark = 3650; // kDaikinBitMark * 8
const uint16_t kDaikinHdrSpace = 1623; // kDaikinBitMark * 4
const uint16_t kDaikinBitMark = 428;
const uint16_t kDaikinZeroSpace = 428;
const uint16_t kDaikinOneSpace = 1280;
const uint16_t kDaikinGap = 29000;
// Note bits in each octet swapped so can be sent as a single value
#define DAIKIN_FIRST_HEADER64 \
0b1101011100000000000000001100010100000000001001111101101000010001
const uint64_t kDaikinFirstHeader64 =
0b1101011100000000000000001100010100000000001001111101101000010001;
// Another variant of the protocol for the Daikin ARC477A1 remote.
const uint16_t kDaikin2LeaderMark = 10024;
const uint16_t kDaikin2LeaderSpace = 25180;
const uint16_t kDaikin2Gap = kDaikin2LeaderMark + kDaikin2LeaderSpace;
const uint16_t kDaikin2HdrMark = 3500;
const uint16_t kDaikin2HdrSpace = 1728;
const uint16_t kDaikin2BitMark = 418;
const uint16_t kDaikin2OneSpace = 1315;
const uint16_t kDaikin2ZeroSpace = 451;
const uint16_t kDaikin2Sections = 2;
const uint16_t kDaikin2Section1Length = 20;
const uint16_t kDaikin2Section2Length = 19;
const uint8_t kDaikin2BitSleepTimer = 0b00100000;
const uint8_t kDaikin2BitPurify = 0b00010000;
const uint8_t kDaikin2BitEye = 0b00000010;
const uint8_t kDaikin2BitEyeAuto = 0b10000000;
const uint8_t kDaikin2BitMold = 0b00001000;
const uint8_t kDaikin2BitClean = 0b00100000;
const uint8_t kDaikin2BitFreshAir = 0b00000001;
const uint8_t kDaikin2BitFreshAirHigh = 0b10000000;
const uint8_t kDaikin2LightMask = 0b00110000;
const uint8_t kDaikin2BeepMask = 0b11000000;
const uint8_t kDaikin2SwingVHigh = 0x1;
const uint8_t kDaikin2SwingVLow = 0x6;
const uint8_t kDaikin2SwingVBreeze = 0xC;
const uint8_t kDaikin2SwingVCirculate = 0xD;
const uint8_t kDaikin2SwingVAuto = 0xE;
const uint8_t kDaikin2SwingHAuto = 0xBE;
const uint8_t kDaikin2SwingHSwing = 0xBF;
// Legacy defines.
#define DAIKIN_COOL kDaikinCool
#define DAIKIN_HEAT kDaikinHeat
#define DAIKIN_FAN kDaikinFan
#define DAIKIN_AUTO kDaikinAuto
#define DAIKIN_DRY kDaikinDry
#define DAIKIN_MIN_TEMP kDaikinMinTemp
#define DAIKIN_MAX_TEMP kDaikinMaxTemp
#define DAIKIN_FAN_MIN kDaikinFanMin
#define DAIKIN_FAN_MAX kDaikinFanMax
#define DAIKIN_FAN_AUTO kDaikinFanAuto
#define DAIKIN_FAN_QUIET kDaikinFanQuiet
class IRDaikinESP {
public:
explicit IRDaikinESP(uint16_t pin);
#if SEND_DAIKIN
void send();
void send(const uint16_t repeat = kDaikinDefaultRepeat);
#endif
void begin();
void on();
@@ -179,8 +223,8 @@ class IRDaikinESP {
#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);
static bool validChecksum(uint8_t state[],
const uint16_t length = kDaikinStateLength);
#ifdef ARDUINO
String toString();
static String renderTime(uint16_t timemins);
@@ -191,9 +235,8 @@ class IRDaikinESP {
private:
// # of bytes per command
uint8_t daikin[DAIKIN_COMMAND_LENGTH];
uint8_t daikin[kDaikinStateLength];
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);
@@ -201,4 +244,91 @@ class IRDaikinESP {
IRsend _irsend;
};
// Class to emulate a Daikin ARC477A1 remote.
class IRDaikin2 {
public:
explicit IRDaikin2(uint16_t pin);
#if SEND_DAIKIN2
void send(const uint16_t repeat = kDaikin2DefaultRepeat);
#endif
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 fan);
uint8_t getFan();
uint8_t getMode();
void setMode(const uint8_t mode);
void setSwingVertical(const uint8_t position);
uint8_t getSwingVertical();
void setSwingHorizontal(const uint8_t position);
uint8_t getSwingHorizontal();
bool getQuiet();
void setQuiet(const bool on);
bool getPowerful();
void setPowerful(const bool on);
void setSensor(const bool on);
bool getSensor();
void setEcono(const bool on);
bool getEcono();
void setEye(const bool on);
bool getEye();
void setEyeAuto(const bool on);
bool getEyeAuto();
void setPurify(const bool on);
bool getPurify();
void setMold(const bool on);
bool getMold();
void enableOnTimer(const uint16_t starttime);
void disableOnTimer();
uint16_t getOnTime();
bool getOnTimerEnabled();
void enableSleepTimer(const uint16_t sleeptime);
void disableSleepTimer();
uint16_t getSleepTime();
bool getSleepTimerEnabled();
void enableOffTimer(const uint16_t endtime);
void disableOffTimer();
uint16_t getOffTime();
bool getOffTimerEnabled();
void setCurrentTime(const uint16_t time);
uint16_t getCurrentTime();
void setBeep(const uint8_t beep);
uint8_t getBeep();
void setLight(const uint8_t light);
uint8_t getLight();
void setClean(const bool on);
bool getClean();
void setFreshAir(const bool on);
bool getFreshAir();
void setFreshAirHigh(const bool on);
bool getFreshAirHigh();
uint8_t* getRaw();
void setRaw(const uint8_t new_code[]);
uint32_t getCommand();
void setCommand(uint32_t value);
static bool validChecksum(uint8_t state[],
const uint16_t length = kDaikin2StateLength);
#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 remote_state[kDaikin2StateLength];
void stateReset();
void checksum();
void clearOnTimerFlag();
void clearSleepTimerFlag();
IRsend _irsend;
};
#endif // IR_DAIKIN_H_
+36 -40
View File
@@ -18,25 +18,25 @@
// Constants
// Ref:
// https://github.com/z3t0/Arduino-IRremote/blob/master/ir_Denon.cpp
#define DENON_TICK 263U
#define DENON_HDR_MARK_TICKS 1U
#define DENON_HDR_MARK (DENON_HDR_MARK_TICKS * DENON_TICK)
#define DENON_HDR_SPACE_TICKS 3U
#define DENON_HDR_SPACE (DENON_HDR_SPACE_TICKS * DENON_TICK)
#define DENON_BIT_MARK_TICKS 1U
#define DENON_BIT_MARK (DENON_BIT_MARK_TICKS * DENON_TICK)
#define DENON_ONE_SPACE_TICKS 7U
#define DENON_ONE_SPACE (DENON_ONE_SPACE_TICKS * DENON_TICK)
#define DENON_ZERO_SPACE_TICKS 3U
#define DENON_ZERO_SPACE (DENON_ZERO_SPACE_TICKS * DENON_TICK)
#define DENON_MIN_COMMAND_LENGTH_TICKS 510U
#define DENON_MIN_COMMAND_LENGTH (DENON_MIN_COMMAND_LENGTH_TICKS * DENON_TICK)
#define DENON_MIN_GAP_TICKS (DENON_MIN_COMMAND_LENGTH_TICKS - \
(DENON_HDR_MARK_TICKS + DENON_HDR_SPACE_TICKS + \
DENON_BITS * (DENON_BIT_MARK_TICKS + DENON_ONE_SPACE_TICKS) + \
DENON_BIT_MARK_TICKS))
#define DENON_MIN_GAP (DENON_MIN_GAP_TICKS * DENON_TICK)
#define DENON_MANUFACTURER 0x2A4CULL
const uint16_t kDenonTick = 263;
const uint16_t kDenonHdrMarkTicks = 1;
const uint16_t kDenonHdrMark = kDenonHdrMarkTicks * kDenonTick;
const uint16_t kDenonHdrSpaceTicks = 3;
const uint16_t kDenonHdrSpace = kDenonHdrSpaceTicks * kDenonTick;
const uint16_t kDenonBitMarkTicks = 1;
const uint16_t kDenonBitMark = kDenonBitMarkTicks * kDenonTick;
const uint16_t kDenonOneSpaceTicks = 7;
const uint16_t kDenonOneSpace = kDenonOneSpaceTicks * kDenonTick;
const uint16_t kDenonZeroSpaceTicks = 3;
const uint16_t kDenonZeroSpace = kDenonZeroSpaceTicks * kDenonTick;
const uint16_t kDenonMinCommandLengthTicks = 510;
const uint16_t kDenonMinGapTicks =
kDenonMinCommandLengthTicks -
(kDenonHdrMarkTicks + kDenonHdrSpaceTicks +
kDenonBits * (kDenonBitMarkTicks + kDenonOneSpaceTicks) +
kDenonBitMarkTicks);
const uint32_t kDenonMinGap = kDenonMinGapTicks * kDenonTick;
const uint64_t kDenonManufacturer = 0x2A4CULL;
#if SEND_DENON
// Send a Denon message
@@ -55,9 +55,9 @@
// https://github.com/z3t0/Arduino-IRremote/blob/master/ir_Denon.cpp
// http://assets.denon.com/documentmaster/us/denon%20master%20ir%20hex.xls
void IRsend::sendDenon(uint64_t data, uint16_t nbits, uint16_t repeat) {
if (nbits >= PANASONIC_BITS) // Is this really Panasonic?
if (nbits >= kPanasonicBits) // Is this really Panasonic?
sendPanasonic64(data, nbits, repeat);
else if (nbits == DENON_LEGACY_BITS)
else if (nbits == kDenonLegacyBits)
// Support legacy (broken) calls of sendDenon().
sendSharpRaw(data & (~0x2000ULL), nbits + 1, repeat);
else
@@ -84,7 +84,7 @@ bool IRrecv::decodeDenon(decode_results *results, uint16_t nbits, bool strict) {
switch (nbits) {
case DENON_BITS:
case DENON_48_BITS:
case DENON_LEGACY_BITS:
case kDenonLegacyBits:
break;
default:
return false;
@@ -99,40 +99,36 @@ bool IRrecv::decodeDenon(decode_results *results, uint16_t nbits, bool strict) {
// manufacturer code.
if (!decodeSharp(results, nbits, true, false) &&
!decodePanasonic(results, nbits, true, DENON_MANUFACTURER)) {
!decodePanasonic(results, nbits, true, kDenonManufacturer)) {
// We couldn't decode it as expected, so try the old legacy method.
// NOTE: I don't think this following protocol actually exists.
// Looks like a partial version of the Sharp protocol.
// Check we have enough data
if (results->rawlen < 2 * nbits + HEADER + FOOTER - 1)
return false;
if (strict && nbits != DENON_LEGACY_BITS)
return false;
if (results->rawlen < 2 * nbits + kHeader + kFooter - 1) return false;
if (strict && nbits != kDenonLegacyBits) return false;
uint64_t data = 0;
uint16_t offset = OFFSET_START;
uint16_t offset = kStartOffset;
// Header
if (!matchMark(results->rawbuf[offset], DENON_HDR_MARK)) return false;
if (!matchMark(results->rawbuf[offset], kDenonHdrMark)) return false;
// Calculate how long the common tick time is based on the header mark.
uint32_t m_tick = results->rawbuf[offset++] * RAWTICK /
DENON_HDR_MARK_TICKS;
if (!matchSpace(results->rawbuf[offset], DENON_HDR_SPACE)) return false;
uint32_t s_tick = results->rawbuf[offset++] * RAWTICK /
DENON_HDR_SPACE_TICKS;
uint32_t m_tick = results->rawbuf[offset++] * kRawTick / kDenonHdrMarkTicks;
if (!matchSpace(results->rawbuf[offset], kDenonHdrSpace)) return false;
uint32_t s_tick =
results->rawbuf[offset++] * kRawTick / kDenonHdrSpaceTicks;
// Data
match_result_t data_result = matchData(&(results->rawbuf[offset]), nbits,
DENON_BIT_MARK_TICKS * m_tick,
DENON_ONE_SPACE_TICKS * s_tick,
DENON_BIT_MARK_TICKS * m_tick,
DENON_ZERO_SPACE_TICKS * s_tick);
match_result_t data_result =
matchData(&(results->rawbuf[offset]), nbits,
kDenonBitMarkTicks * m_tick, kDenonOneSpaceTicks * s_tick,
kDenonBitMarkTicks * m_tick, kDenonZeroSpaceTicks * s_tick);
if (data_result.success == false) return false;
data = data_result.data;
offset += data_result.used;
// Footer
if (!matchMark(results->rawbuf[offset++], DENON_BIT_MARK_TICKS * m_tick))
if (!matchMark(results->rawbuf[offset++], kDenonBitMarkTicks * m_tick))
return false;
// Success
+32 -35
View File
@@ -18,19 +18,19 @@
// Ref:
// https://github.com/marcosamarinho/IRremoteESP8266/blob/master/ir_Dish.cpp
// http://www.hifi-remote.com/wiki/index.php?title=Dish
#define DISH_TICK 100U
#define DISH_HDR_MARK_TICKS 4U
#define DISH_HDR_MARK (DISH_HDR_MARK_TICKS * DISH_TICK)
#define DISH_HDR_SPACE_TICKS 61U
#define DISH_HDR_SPACE (DISH_HDR_SPACE_TICKS * DISH_TICK)
#define DISH_BIT_MARK_TICKS 4U
#define DISH_BIT_MARK (DISH_BIT_MARK_TICKS * DISH_TICK)
#define DISH_ONE_SPACE_TICKS 17U
#define DISH_ONE_SPACE (DISH_ONE_SPACE_TICKS * DISH_TICK)
#define DISH_ZERO_SPACE_TICKS 28U
#define DISH_ZERO_SPACE (DISH_ZERO_SPACE_TICKS * DISH_TICK)
#define DISH_RPT_SPACE_TICKS DISH_HDR_SPACE_TICKS
#define DISH_RPT_SPACE (DISH_RPT_SPACE_TICKS * DISH_TICK)
const uint16_t kDishTick = 100;
const uint16_t kDishHdrMarkTicks = 4;
const uint16_t kDishHdrMark = kDishHdrMarkTicks * kDishTick;
const uint16_t kDishHdrSpaceTicks = 61;
const uint16_t kDishHdrSpace = kDishHdrSpaceTicks * kDishTick;
const uint16_t kDishBitMarkTicks = 4;
const uint16_t kDishBitMark = kDishBitMarkTicks * kDishTick;
const uint16_t kDishOneSpaceTicks = 17;
const uint16_t kDishOneSpace = kDishOneSpaceTicks * kDishTick;
const uint16_t kDishZeroSpaceTicks = 28;
const uint16_t kDishZeroSpace = kDishZeroSpaceTicks * kDishTick;
const uint16_t kDishRptSpaceTicks = kDishHdrSpaceTicks;
const uint16_t kDishRptSpace = kDishRptSpaceTicks * kDishTick;
#if SEND_DISH
// Send an IR command to a DISH NETWORK device.
@@ -57,14 +57,13 @@
void IRsend::sendDISH(uint64_t data, uint16_t nbits, uint16_t repeat) {
enableIROut(57600); // Set modulation freq. to 57.6kHz.
// Header is only ever sent once.
mark(DISH_HDR_MARK);
space(DISH_HDR_SPACE);
mark(kDishHdrMark);
space(kDishHdrSpace);
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);
kDishBitMark, kDishOneSpace, kDishBitMark, kDishZeroSpace,
kDishBitMark, kDishRptSpace, data, nbits, 57600, true, repeat,
50);
}
#endif
@@ -73,7 +72,7 @@ void IRsend::sendDISH(uint64_t data, uint16_t nbits, uint16_t repeat) {
//
// 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 DISH_BITS.
// nbits: Nr. of bits to expect in the data portion. Typically kDishBits.
// strict: Flag to indicate if we strictly adhere to the specification.
// Returns:
// boolean: True if it can decode it, false if it can't.
@@ -89,34 +88,32 @@ void IRsend::sendDISH(uint64_t data, uint16_t nbits, uint16_t repeat) {
// http://lirc.sourceforge.net/remotes/echostar/301_501_3100_5100_58xx_59xx
// https://github.com/marcosamarinho/IRremoteESP8266/blob/master/ir_Dish.cpp
bool IRrecv::decodeDISH(decode_results *results, uint16_t nbits, bool strict) {
if (results->rawlen < 2 * nbits + HEADER + FOOTER - 1)
if (results->rawlen < 2 * nbits + kHeader + kFooter - 1)
return false; // Not enough entries to be valid.
if (strict && nbits != DISH_BITS)
return false; // Not strictly compliant.
if (strict && nbits != kDishBits) return false; // Not strictly compliant.
uint64_t data = 0;
uint16_t offset = OFFSET_START;
uint16_t offset = kStartOffset;
// Header
if (!match(results->rawbuf[offset], DISH_HDR_MARK)) return false;
if (!match(results->rawbuf[offset], kDishHdrMark)) return false;
// Calculate how long the common tick time is based on the header mark.
uint32_t m_tick = results->rawbuf[offset++] * RAWTICK / DISH_HDR_MARK_TICKS;
if (!matchSpace(results->rawbuf[offset], DISH_HDR_SPACE)) return false;
uint32_t m_tick = results->rawbuf[offset++] * kRawTick / kDishHdrMarkTicks;
if (!matchSpace(results->rawbuf[offset], kDishHdrSpace)) return false;
// Calculate how long the common tick time is based on the header space.
uint32_t s_tick = results->rawbuf[offset++] * RAWTICK / DISH_HDR_SPACE_TICKS;
uint32_t s_tick = results->rawbuf[offset++] * kRawTick / kDishHdrSpaceTicks;
// Data
match_result_t data_result = matchData(&(results->rawbuf[offset]), nbits,
DISH_BIT_MARK_TICKS * m_tick,
DISH_ONE_SPACE_TICKS * s_tick,
DISH_BIT_MARK_TICKS * m_tick,
DISH_ZERO_SPACE_TICKS * s_tick);
match_result_t data_result =
matchData(&(results->rawbuf[offset]), nbits, kDishBitMarkTicks * m_tick,
kDishOneSpaceTicks * s_tick, kDishBitMarkTicks * m_tick,
kDishZeroSpaceTicks * s_tick);
if (data_result.success == false) return false;
data = data_result.data;
offset += data_result.used;
// Footer
if (!matchMark(results->rawbuf[offset++], DISH_BIT_MARK_TICKS * m_tick))
if (!matchMark(results->rawbuf[offset++], kDishBitMarkTicks * m_tick))
return false;
// Compliance
@@ -124,7 +121,7 @@ bool IRrecv::decodeDISH(decode_results *results, uint16_t nbits, bool strict) {
// The DISH protocol calls for a repeated message, so strictly speaking
// there should be a code following this. Only require it if we are set to
// strict matching.
if (!matchSpace(results->rawbuf[offset], DISH_RPT_SPACE_TICKS * s_tick))
if (!matchSpace(results->rawbuf[offset], kDishRptSpaceTicks * s_tick))
return false;
}
+112
View File
@@ -0,0 +1,112 @@
// Copyright 2018 David Conran
#include "IRrecv.h"
#include "IRsend.h"
#include "IRutils.h"
// EEEEEEE LL EEEEEEE CCCCC TTTTTTT RRRRRR AAA
// EE LL EE CC C TTT RR RR AAAAA
// EEEEE LL EEEEE CC TTT RRRRRR AA AA
// EE LL EE CC C TTT RR RR AAAAAAA
// EEEEEEE LLLLLLL EEEEEEE CCCCC TTT RR RR AA AA
// Electra A/C added by crankyoldgit
//
// Equipment it seems compatible with:
// * <Add models (A/C & remotes) you've gotten it working with here>
// Ref:
// https://github.com/markszabo/IRremoteESP8266/issues/527
// Constants
const uint16_t kElectraAcHdrMark = 9166;
const uint16_t kElectraAcBitMark = 646;
const uint16_t kElectraAcHdrSpace = 4470;
const uint16_t kElectraAcOneSpace = 1647;
const uint16_t kElectraAcZeroSpace = 547;
const uint32_t kElectraAcMessageGap = 100000; // Completely made-up guess.
#if SEND_ELECTRA_AC
// Send a Electra message
//
// Args:
// data: Contents of the message to be sent. (Guessing MSBF order)
// nbits: Nr. of bits of data to be sent. Typically kElectraAcBits.
// repeat: Nr. of additional times the message is to be sent.
//
// Status: Alpha / Needs testing against a real device.
//
void IRsend::sendElectraAC(uint8_t data[], uint16_t nbytes, uint16_t repeat) {
for (uint16_t r = 0; r <= repeat; r++)
sendGeneric(kElectraAcHdrMark, kElectraAcHdrSpace, kElectraAcBitMark,
kElectraAcOneSpace, kElectraAcBitMark, kElectraAcZeroSpace,
kElectraAcBitMark, kElectraAcMessageGap, data, nbytes,
38000, // Complete guess of the modulation frequency.
true, 0, 50);
}
#endif
#if DECODE_ELECTRA_AC
// Decode the supplied Electra 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 kElectraAcBits.
// 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::decodeElectraAC(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 != kElectraAcBits)
return false; // Not strictly a ELECTRA_AC message.
}
// The protocol sends the data normal + inverted, alternating on
// each byte. Hence twice the number of expected data bits.
if (results->rawlen < 2 * nbits + kHeader + kFooter - 1)
return false; // Can't possibly be a valid ELECTRA_AC message.
uint16_t offset = kStartOffset;
// Message Header
if (!matchMark(results->rawbuf[offset++], kElectraAcHdrMark)) return false;
if (!matchSpace(results->rawbuf[offset++], kElectraAcHdrSpace)) return false;
// Data Section
match_result_t data_result;
uint16_t dataBitsSoFar = 0;
// Keep reading bytes until we either run out of section 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, kElectraAcBitMark,
kElectraAcOneSpace, kElectraAcBitMark,
kElectraAcZeroSpace, kTolerance, 0, true);
if (data_result.success == false) return false; // Fail
results->state[i] = data_result.data;
}
// Message Footer
if (!matchMark(results->rawbuf[offset++], kElectraAcBitMark)) return false;
if (offset <= results->rawlen &&
!matchAtLeast(results->rawbuf[offset++], kElectraAcMessageGap))
return false;
// Compliance
if (strict && dataBitsSoFar != nbits) return false;
// Success
results->decode_type = ELECTRA_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_ELECTRA_AC
+135 -163
View File
@@ -7,7 +7,6 @@
#include "IRsend.h"
#include "IRutils.h"
// Fujitsu A/C support added by Jonny Graham & David Conran
// Equipment it seems compatible with:
@@ -17,12 +16,12 @@
// Ref:
// These values are based on averages of measurements
#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 390U
#define FUJITSU_AC_MIN_GAP 8100U
const uint16_t kFujitsuAcHdrMark = 3324;
const uint16_t kFujitsuAcHdrSpace = 1574;
const uint16_t kFujitsuAcBitMark = 448;
const uint16_t kFujitsuAcOneSpace = 1182;
const uint16_t kFujitsuAcZeroSpace = 390;
const uint16_t kFujitsuAcMinGap = 8100;
#if SEND_FUJITSU_AC
// Send a Fujitsu A/C message.
@@ -30,22 +29,21 @@
// Args:
// data: An array of bytes containing the IR command.
// 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
// kFujitsuAcStateLength
// kFujitsuAcStateLength - 1
// kFujitsuAcStateLengthShort
// kFujitsuAcStateLengthShort - 1
// repeat: Nr. of times the message is to be repeated.
// (Default = FUJITSU_AC_MIN_REPEAT).
// (Default = kFujitsuAcMinRepeat).
//
// Status: BETA / Appears to be working.
//
void IRsend::sendFujitsuAC(unsigned char data[], uint16_t nbytes,
uint16_t repeat) {
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);
sendGeneric(kFujitsuAcHdrMark, kFujitsuAcHdrSpace, kFujitsuAcBitMark,
kFujitsuAcOneSpace, kFujitsuAcBitMark, kFujitsuAcZeroSpace,
kFujitsuAcBitMark, kFujitsuAcMinGap, data, nbytes, 38, false,
repeat, 50);
}
#endif // SEND_FUJITSU_AC
@@ -62,35 +60,33 @@ 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;
_state_length = kFujitsuAcStateLength - 1;
_state_length_short = kFujitsuAcStateLengthShort - 1;
break;
default:
_state_length = FUJITSU_AC_STATE_LENGTH;
_state_length_short = FUJITSU_AC_STATE_LENGTH_SHORT;
_state_length = kFujitsuAcStateLength;
_state_length_short = kFujitsuAcStateLengthShort;
}
}
// Reset the state of the remote to a known good state/sequence.
void IRFujitsuAC::stateReset() {
_temp = 24;
_fanSpeed = FUJITSU_AC_FAN_HIGH;
_mode = FUJITSU_AC_MODE_COOL;
_swingMode = FUJITSU_AC_SWING_BOTH;
_cmd = FUJITSU_AC_CMD_TURN_ON;
_fanSpeed = kFujitsuAcFanHigh;
_mode = kFujitsuAcModeCool;
_swingMode = kFujitsuAcSwingBoth;
_cmd = kFujitsuAcCmdTurnOn;
buildState();
}
// Configure the pin for output.
void IRFujitsuAC::begin() {
_irsend.begin();
}
void IRFujitsuAC::begin() { _irsend.begin(); }
#if SEND_FUJITSU_AC
// Send the current desired state to the IR LED.
void IRFujitsuAC::send() {
void IRFujitsuAC::send(const uint16_t repeat) {
getRaw();
_irsend.sendFujitsuAC(remote_state, getStateLength());
_irsend.sendFujitsuAC(remote_state, getStateLength(), repeat);
}
#endif // SEND_FUJITSU_AC
@@ -102,13 +98,13 @@ void IRFujitsuAC::buildState() {
remote_state[4] = 0x10;
bool fullCmd = false;
switch (_cmd) {
case FUJITSU_AC_CMD_TURN_OFF:
case kFujitsuAcCmdTurnOff:
remote_state[5] = 0x02;
break;
case FUJITSU_AC_CMD_STEP_HORIZ:
case kFujitsuAcCmdStepHoriz:
remote_state[5] = 0x79;
break;
case FUJITSU_AC_CMD_STEP_VERT:
case kFujitsuAcCmdStepVert:
remote_state[5] = 0x6C;
break;
default:
@@ -124,12 +120,12 @@ void IRFujitsuAC::buildState() {
break;
}
if (fullCmd) { // long codes
uint8_t tempByte = _temp - FUJITSU_AC_MIN_TEMP;
uint8_t tempByte = _temp - kFujitsuAcMinTemp;
// 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[8] = (_cmd == kFujitsuAcCmdTurnOn) | (tempByte << 4);
remote_state[9] = _mode | 0 << 4; // timer off
remote_state[10] = _fanSpeed | _swingMode << 4;
remote_state[11] = 0; // timerOff values
@@ -154,12 +150,10 @@ void IRFujitsuAC::buildState() {
} 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];
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++)
for (uint8_t i = _state_length_short; i < kFujitsuAcStateLength; i++)
remote_state[i] = 0;
}
}
@@ -181,8 +175,8 @@ uint8_t* IRFujitsuAC::getRaw() {
void IRFujitsuAC::buildFromState(const uint16_t length) {
switch (length) {
case FUJITSU_AC_STATE_LENGTH - 1:
case FUJITSU_AC_STATE_LENGTH_SHORT - 1:
case kFujitsuAcStateLength - 1:
case kFujitsuAcStateLengthShort - 1:
setModel(ARDB1);
break;
default:
@@ -196,26 +190,26 @@ void IRFujitsuAC::buildFromState(const uint16_t length) {
setModel(ARRAH2E);
break;
}
setTemp((remote_state[8] >> 4) + FUJITSU_AC_MIN_TEMP);
setTemp((remote_state[8] >> 4) + kFujitsuAcMinTemp);
if (remote_state[8] & 0x1)
setCmd(FUJITSU_AC_CMD_TURN_ON);
setCmd(kFujitsuAcCmdTurnOn);
else
setCmd(FUJITSU_AC_CMD_STAY_ON);
setCmd(kFujitsuAcCmdStayOn);
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:
case kFujitsuAcCmdTurnOff:
case kFujitsuAcCmdStepHoriz:
case kFujitsuAcCmdStepVert:
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 (length > kFujitsuAcStateLength) return false;
for (uint16_t i = 0; i < kFujitsuAcStateLength; i++) {
if (i < length)
remote_state[i] = newState[i];
else
@@ -226,119 +220,103 @@ bool IRFujitsuAC::setRaw(const uint8_t newState[], const uint16_t length) {
}
// Set the requested power state of the A/C to off.
void IRFujitsuAC::off() {
_cmd = FUJITSU_AC_CMD_TURN_OFF;
}
void IRFujitsuAC::off() { _cmd = kFujitsuAcCmdTurnOff; }
void IRFujitsuAC::stepHoriz() {
switch (_model) {
case ARDB1: break; // This remote doesn't have a horizontal option.
case ARDB1:
break; // This remote doesn't have a horizontal option.
default:
_cmd = FUJITSU_AC_CMD_STEP_HORIZ;
_cmd = kFujitsuAcCmdStepHoriz;
}
}
void IRFujitsuAC::stepVert() {
_cmd = FUJITSU_AC_CMD_STEP_VERT;
}
void IRFujitsuAC::stepVert() { _cmd = kFujitsuAcCmdStepVert; }
// Set the requested command of the A/C.
void IRFujitsuAC::setCmd(uint8_t cmd) {
switch (cmd) {
case FUJITSU_AC_CMD_TURN_OFF:
case FUJITSU_AC_CMD_TURN_ON:
case FUJITSU_AC_CMD_STAY_ON:
case FUJITSU_AC_CMD_STEP_VERT:
case kFujitsuAcCmdTurnOff:
case kFujitsuAcCmdTurnOn:
case kFujitsuAcCmdStayOn:
case kFujitsuAcCmdStepVert:
_cmd = cmd;
break;
case FUJITSU_AC_CMD_STEP_HORIZ:
case kFujitsuAcCmdStepHoriz:
if (_model != ARDB1) // AR-DB1 remote doesn't have step horizontal.
_cmd = cmd;
// FALLTHRU
default:
_cmd = FUJITSU_AC_CMD_STAY_ON;
_cmd = kFujitsuAcCmdStayOn;
break;
}
}
uint8_t IRFujitsuAC::getCmd() {
return _cmd;
}
uint8_t IRFujitsuAC::getCmd() { return _cmd; }
bool IRFujitsuAC::getPower() {
return _cmd != FUJITSU_AC_CMD_TURN_OFF;
}
bool IRFujitsuAC::getPower() { return _cmd != kFujitsuAcCmdTurnOff; }
// Set the temp. in deg C
void IRFujitsuAC::setTemp(uint8_t temp) {
temp = std::max((uint8_t) FUJITSU_AC_MIN_TEMP, temp);
temp = std::min((uint8_t) FUJITSU_AC_MAX_TEMP, temp);
temp = std::max((uint8_t)kFujitsuAcMinTemp, temp);
temp = std::min((uint8_t)kFujitsuAcMaxTemp, temp);
_temp = temp;
}
uint8_t IRFujitsuAC::getTemp() {
return _temp;
}
uint8_t IRFujitsuAC::getTemp() { return _temp; }
// Set the speed of the fan
void IRFujitsuAC::setFanSpeed(uint8_t fanSpeed) {
if (fanSpeed > FUJITSU_AC_FAN_QUIET)
fanSpeed = FUJITSU_AC_FAN_HIGH; // Set the fan to maximum if out of range.
if (fanSpeed > kFujitsuAcFanQuiet)
fanSpeed = kFujitsuAcFanHigh; // Set the fan to maximum if out of range.
_fanSpeed = fanSpeed;
}
uint8_t IRFujitsuAC::getFanSpeed() {
return _fanSpeed;
}
uint8_t IRFujitsuAC::getFanSpeed() { return _fanSpeed; }
// Set the requested climate operation mode of the a/c unit.
void IRFujitsuAC::setMode(uint8_t mode) {
if (mode > FUJITSU_AC_MODE_HEAT)
mode = FUJITSU_AC_MODE_HEAT; // Set the mode to maximum if out of range.
if (mode > kFujitsuAcModeHeat)
mode = kFujitsuAcModeHeat; // Set the mode to maximum if out of range.
_mode = mode;
}
uint8_t IRFujitsuAC::getMode() {
return _mode;
}
uint8_t IRFujitsuAC::getMode() { return _mode; }
// Set the requested swing operation mode of the a/c unit.
void IRFujitsuAC::setSwing(uint8_t swingMode) {
switch (_model) {
case ARDB1:
// Set the mode to max if out of range
if (swingMode > FUJITSU_AC_SWING_VERT)
swingMode = FUJITSU_AC_SWING_VERT;
if (swingMode > kFujitsuAcSwingVert) swingMode = kFujitsuAcSwingVert;
break;
case ARRAH2E:
default:
// Set the mode to max if out of range
if (swingMode > FUJITSU_AC_SWING_BOTH)
swingMode = FUJITSU_AC_SWING_BOTH;
if (swingMode > kFujitsuAcSwingBoth) swingMode = kFujitsuAcSwingBoth;
}
_swingMode = swingMode;
}
uint8_t IRFujitsuAC::getSwing() {
return _swingMode;
}
uint8_t IRFujitsuAC::getSwing() { return _swingMode; }
bool IRFujitsuAC::validChecksum(uint8_t state[], uint16_t length) {
uint8_t sum = 0;
uint8_t sum_complement = 0;
uint8_t checksum = 0;
uint8_t checksum = state[length - 1];
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
case kFujitsuAcStateLengthShort: // ARRAH2E
return state[length - 1] == (uint8_t)~state[length - 2];
case kFujitsuAcStateLength - 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.
case kFujitsuAcStateLength: // ARRAH2E
sum = sumBytes(state + kFujitsuAcStateLengthShort,
length - 1 - kFujitsuAcStateLengthShort);
break;
default: // Includes ARDB1 short.
return true; // Assume the checksum is valid for other lengths.
}
return checksum == (uint8_t) (sum_complement - sum); // Does it match?
return checksum == (uint8_t)(sum_complement - sum); // Does it match?
}
// Convert the internal state into a human readable string.
@@ -356,19 +334,19 @@ std::string IRFujitsuAC::toString() {
result += "Off";
result += ", Mode: " + uint64ToString(getMode());
switch (getMode()) {
case FUJITSU_AC_MODE_AUTO:
case kFujitsuAcModeAuto:
result += " (AUTO)";
break;
case FUJITSU_AC_MODE_COOL:
case kFujitsuAcModeCool:
result += " (COOL)";
break;
case FUJITSU_AC_MODE_HEAT:
case kFujitsuAcModeHeat:
result += " (HEAT)";
break;
case FUJITSU_AC_MODE_DRY:
case kFujitsuAcModeDry:
result += " (DRY)";
break;
case FUJITSU_AC_MODE_FAN:
case kFujitsuAcModeFan:
result += " (FAN)";
break;
default:
@@ -377,34 +355,34 @@ std::string IRFujitsuAC::toString() {
result += ", Temp: " + uint64ToString(getTemp()) + "C";
result += ", Fan: " + uint64ToString(getFanSpeed());
switch (getFanSpeed()) {
case FUJITSU_AC_FAN_AUTO:
case kFujitsuAcFanAuto:
result += " (AUTO)";
break;
case FUJITSU_AC_FAN_HIGH:
case kFujitsuAcFanHigh:
result += " (HIGH)";
break;
case FUJITSU_AC_FAN_MED:
case kFujitsuAcFanMed:
result += " (MED)";
break;
case FUJITSU_AC_FAN_LOW:
case kFujitsuAcFanLow:
result += " (LOW)";
break;
case FUJITSU_AC_FAN_QUIET:
case kFujitsuAcFanQuiet:
result += " (QUIET)";
break;
}
result += ", Swing: ";
switch (getSwing()) {
case FUJITSU_AC_SWING_OFF:
case kFujitsuAcSwingOff:
result += "Off";
break;
case FUJITSU_AC_SWING_VERT:
case kFujitsuAcSwingVert:
result += "Vert";
break;
case FUJITSU_AC_SWING_HORIZ:
case kFujitsuAcSwingHoriz:
result += "Horiz";
break;
case FUJITSU_AC_SWING_BOTH:
case kFujitsuAcSwingBoth:
result += "Vert + Horiz";
break;
default:
@@ -412,14 +390,14 @@ std::string IRFujitsuAC::toString() {
}
result += ", Command: ";
switch (getCmd()) {
case FUJITSU_AC_CMD_STEP_HORIZ:
case kFujitsuAcCmdStepHoriz:
result += "Step vane horizontally";
break;
case FUJITSU_AC_CMD_STEP_VERT:
case kFujitsuAcCmdStepVert:
result += "Step vane vertically";
break;
default:
result += "N/A";
result += "N/A";
}
return result;
}
@@ -429,7 +407,7 @@ std::string IRFujitsuAC::toString() {
// 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.
// nbits: The number of data bits to expect. Typically kFujitsuAcBits.
// strict: Flag to indicate if we strictly adhere to the specification.
// Returns:
// boolean: True if it can decode it, false if it can't.
@@ -438,23 +416,22 @@ std::string IRFujitsuAC::toString() {
//
// Ref:
//
bool IRrecv::decodeFujitsuAC(decode_results *results, uint16_t nbits,
bool IRrecv::decodeFujitsuAC(decode_results* results, uint16_t nbits,
bool strict) {
uint16_t offset = OFFSET_START;
uint16_t offset = kStartOffset;
uint16_t dataBitsSoFar = 0;
// Have we got enough data to successfully decode?
if (results->rawlen < (2 * FUJITSU_AC_MIN_BITS) + HEADER + FOOTER - 1)
if (results->rawlen < (2 * kFujitsuAcMinBits) + kHeader + kFooter - 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:
case kFujitsuAcBits:
case kFujitsuAcBits - 8:
case kFujitsuAcMinBits:
case kFujitsuAcMinBits + 8:
break;
default:
return false; // Must be called with the correct nr. of bits.
@@ -462,22 +439,17 @@ bool IRrecv::decodeFujitsuAC(decode_results *results, uint16_t nbits,
}
// Header
if (!matchMark(results->rawbuf[offset++], FUJITSU_AC_HDR_MARK))
return false;
if (!matchSpace(results->rawbuf[offset++], FUJITSU_AC_HDR_SPACE))
return false;
if (!matchMark(results->rawbuf[offset++], kFujitsuAcHdrMark)) return false;
if (!matchSpace(results->rawbuf[offset++], kFujitsuAcHdrSpace)) 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;
match_result_t data_result =
matchData(&(results->rawbuf[offset]), kFujitsuAcMinBits - 8,
kFujitsuAcBitMark, kFujitsuAcOneSpace, kFujitsuAcBitMark,
kFujitsuAcZeroSpace, kTolerance, kMarkExcess, false);
if (data_result.success == false) return false; // Fail
if (data_result.data != 0x1010006314) return false; // Signature failed.
dataBitsSoFar += kFujitsuAcMinBits - 8;
offset += data_result.used;
results->state[0] = 0x14;
results->state[1] = 0x63;
@@ -487,27 +459,27 @@ bool IRrecv::decodeFujitsuAC(decode_results *results, uint16_t nbits,
// 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);
offset <= results->rawlen - 16 && i < kFujitsuAcStateLength;
i++, dataBitsSoFar += 8, offset += data_result.used) {
data_result = matchData(
&(results->rawbuf[offset]), 8, kFujitsuAcBitMark, kFujitsuAcOneSpace,
kFujitsuAcBitMark, kFujitsuAcZeroSpace, kTolerance, kMarkExcess, false);
if (data_result.success == false) break; // Fail
results->state[i] = data_result.data;
}
// Footer
if (offset > results->rawlen ||
!matchMark(results->rawbuf[offset++], FUJITSU_AC_BIT_MARK)) return false;
!matchMark(results->rawbuf[offset++], kFujitsuAcBitMark))
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;
!matchAtLeast(results->rawbuf[offset], kFujitsuAcMinGap))
return false;
// Compliance
if (strict) {
if (dataBitsSoFar != nbits) return false;
if (dataBitsSoFar != nbits) return false;
}
results->decode_type = FUJITSU_AC;
@@ -515,25 +487,25 @@ bool IRrecv::decodeFujitsuAC(decode_results *results, uint16_t nbits,
// Compliance
switch (dataBitsSoFar) {
case FUJITSU_AC_MIN_BITS:
case kFujitsuAcMinBits:
// Check if this values indicate that this should have been a long state
// message.
if (results->state[5] == 0xFC) return false;
if (results->state[5] == 0xFC) return false;
return true; // Success
case FUJITSU_AC_MIN_BITS + 8:
case kFujitsuAcMinBits + 8:
// Check if this values indicate that this should have been a long state
// message.
if (results->state[5] == 0xFE) return false;
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;
if (results->state[5] != (uint8_t)~results->state[6]) return false;
return true; // Success
case FUJITSU_AC_BITS - 8:
case kFujitsuAcBits - 8:
// Long messages of this size require this byte be correct.
if (results->state[5] != 0xFC) return false;
if (results->state[5] != 0xFC) return false;
break;
case FUJITSU_AC_BITS:
case kFujitsuAcBits:
// Long messages of this size require this byte be correct.
if (results->state[5] != 0xFE) return false;
if (results->state[5] != 0xFE) return false;
break;
default:
return false; // Unexpected size.
+53 -32
View File
@@ -1,4 +1,5 @@
// Copyright 2017 Jonny Graham
// Copyright 2018 David Conran
#ifndef IR_FUJITSU_H_
#define IR_FUJITSU_H_
@@ -9,41 +10,61 @@
#else
#include <string>
#endif
#include "IRremoteESP8266.h"
#include "IRrecv.h"
#include "IRremoteESP8266.h"
#include "IRsend.h"
// FUJITSU A/C support added by Jonny Graham
// Constants
const uint8_t kFujitsuAcModeAuto = 0x00;
const uint8_t kFujitsuAcModeCool = 0x01;
const uint8_t kFujitsuAcModeDry = 0x02;
const uint8_t kFujitsuAcModeFan = 0x03;
const uint8_t kFujitsuAcModeHeat = 0x04;
#define FUJITSU_AC_MODE_AUTO 0x00U
#define FUJITSU_AC_MODE_COOL 0x01U
#define FUJITSU_AC_MODE_DRY 0x02U
#define FUJITSU_AC_MODE_FAN 0x03U
#define FUJITSU_AC_MODE_HEAT 0x04U
const uint8_t kFujitsuAcCmdStayOn = 0x00;
const uint8_t kFujitsuAcCmdTurnOn = 0x01;
const uint8_t kFujitsuAcCmdTurnOff = 0x02;
const uint8_t kFujitsuAcCmdStepHoriz = 0x79;
const uint8_t kFujitsuAcCmdStepVert = 0x6C;
#define FUJITSU_AC_CMD_STAY_ON 0x00U
#define FUJITSU_AC_CMD_TURN_ON 0x01U
#define FUJITSU_AC_CMD_TURN_OFF 0x02U
#define FUJITSU_AC_CMD_STEP_HORIZ 0x79U
#define FUJITSU_AC_CMD_STEP_VERT 0x6CU
const uint8_t kFujitsuAcFanAuto = 0x00;
const uint8_t kFujitsuAcFanHigh = 0x01;
const uint8_t kFujitsuAcFanMed = 0x02;
const uint8_t kFujitsuAcFanLow = 0x03;
const uint8_t kFujitsuAcFanQuiet = 0x04;
#define FUJITSU_AC_FAN_AUTO 0x00U
#define FUJITSU_AC_FAN_HIGH 0x01U
#define FUJITSU_AC_FAN_MED 0x02U
#define FUJITSU_AC_FAN_LOW 0x03U
#define FUJITSU_AC_FAN_QUIET 0x04U
const uint8_t kFujitsuAcMinTemp = 16; // 16C
const uint8_t kFujitsuAcMaxTemp = 30; // 30C
#define FUJITSU_AC_MIN_TEMP 16U // 16C
#define FUJITSU_AC_MAX_TEMP 30U // 30C
#define FUJITSU_AC_SWING_OFF 0x00U
#define FUJITSU_AC_SWING_VERT 0x01U
#define FUJITSU_AC_SWING_HORIZ 0x02U
#define FUJITSU_AC_SWING_BOTH 0x03U
const uint8_t kFujitsuAcSwingOff = 0x00;
const uint8_t kFujitsuAcSwingVert = 0x01;
const uint8_t kFujitsuAcSwingHoriz = 0x02;
const uint8_t kFujitsuAcSwingBoth = 0x03;
// Legacy defines.
#define FUJITSU_AC_MODE_AUTO kFujitsuAcModeAuto
#define FUJITSU_AC_MODE_COOL kFujitsuAcModeCool
#define FUJITSU_AC_MODE_DRY kFujitsuAcModeDry
#define FUJITSU_AC_MODE_FAN kFujitsuAcModeFan
#define FUJITSU_AC_MODE_HEAT kFujitsuAcModeHeat
#define FUJITSU_AC_CMD_STAY_ON kFujitsuAcCmdStayOn
#define FUJITSU_AC_CMD_TURN_ON kFujitsuAcCmdTurnOn
#define FUJITSU_AC_CMD_TURN_OFF kFujitsuAcCmdTurnOff
#define FUJITSU_AC_CMD_STEP_HORIZ kFujitsuAcCmdStepHoriz
#define FUJITSU_AC_CMD_STEP_VERT kFujitsuAcCmdStepVert
#define FUJITSU_AC_FAN_AUTO kFujitsuAcFanAuto
#define FUJITSU_AC_FAN_HIGH kFujitsuAcFanHigh
#define FUJITSU_AC_FAN_MED kFujitsuAcFanMed
#define FUJITSU_AC_FAN_LOW kFujitsuAcFanLow
#define FUJITSU_AC_FAN_QUIET kFujitsuAcFanQuiet
#define FUJITSU_AC_MIN_TEMP kFujitsuAcMinTemp
#define FUJITSU_AC_MAX_TEMP kFujitsuAcMaxTemp
#define FUJITSU_AC_SWING_OFF kFujitsuAcSwingOff
#define FUJITSU_AC_SWING_VERT kFujitsuAcSwingVert
#define FUJITSU_AC_SWING_HORIZ kFujitsuAcSwingHoriz
#define FUJITSU_AC_SWING_BOTH kFujitsuAcSwingBoth
enum fujitsu_ac_remote_model_t {
ARRAH2E = 1,
@@ -57,7 +78,7 @@ class IRFujitsuAC {
void setModel(fujitsu_ac_remote_model_t model);
void stateReset();
#if SEND_FUJITSU_AC
void send();
void send(const uint16_t repeat = kFujitsuAcMinRepeat);
#endif // SEND_FUJITSU_AC
void begin();
void off();
@@ -76,16 +97,16 @@ class IRFujitsuAC {
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);
static bool validChecksum(uint8_t* state, uint16_t length);
bool getPower();
#ifdef ARDUINO
String toString();
#else
std::string toString();
#endif
#ifdef ARDUINO
String toString();
#else
std::string toString();
#endif
private:
uint8_t remote_state[FUJITSU_AC_STATE_LENGTH];
uint8_t remote_state[kFujitsuAcStateLength];
IRsend _irsend;
uint8_t _temp;
uint8_t _fanSpeed;
+34 -37
View File
@@ -18,17 +18,17 @@
// 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))
const uint16_t kGicableHdrMark = 9000;
const uint16_t kGicableHdrSpace = 4400;
const uint16_t kGicableBitMark = 550;
const uint16_t kGicableOneSpace = 4400;
const uint16_t kGicableZeroSpace = 2200;
const uint16_t kGicableRptSpace = 2200;
const uint32_t kGicableMinCommandLength = 99600;
const uint32_t kGicableMinGap =
kGicableMinCommandLength -
(kGicableHdrMark + kGicableHdrSpace +
kGicableBits * (kGicableBitMark + kGicableOneSpace) + kGicableBitMark);
#if SEND_GICABLE
// Send a raw G.I. Cable formatted message.
@@ -36,25 +36,24 @@
// Args:
// data: The message to be sent.
// nbits: The number of bits of the message to be sent.
// Typically GICABLE_BITS.
// Typically kGicableBits.
// 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.
sendGeneric(kGicableHdrMark, kGicableHdrSpace, kGicableBitMark,
kGicableOneSpace, kGicableBitMark, kGicableZeroSpace,
kGicableBitMark, kGicableMinGap, kGicableMinCommandLength, 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.
sendGeneric(kGicableHdrMark, kGicableRptSpace, 0, 0, 0,
0, // No actual data sent.
kGicableBitMark, kGicableMinGap, kGicableMinCommandLength, 0,
0, // No data to be sent.
39, true, repeat - 1, 50);
}
#endif // SEND_GICABLE
@@ -64,7 +63,7 @@ void IRsend::sendGICable(uint64_t data, uint16_t nbits, uint16_t repeat) {
//
// 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.
// nbits: The number of data bits to expect. Typically kGicableBits.
// strict: Flag indicating if we should perform strict matching.
// Returns:
// boolean: True if it can decode it, false if it can't.
@@ -72,40 +71,38 @@ void IRsend::sendGICable(uint64_t data, uint16_t nbits, uint16_t repeat) {
// 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)
if (results->rawlen < 2 * (nbits + kHeader + kFooter) - 1)
return false; // Can't possibly be a valid GICABLE message.
if (strict && nbits != GICABLE_BITS)
if (strict && nbits != kGicableBits)
return false; // Not strictly an GICABLE message.
uint64_t data = 0;
uint16_t offset = OFFSET_START;
uint16_t offset = kStartOffset;
// Header
if (!matchMark(results->rawbuf[offset++], GICABLE_HDR_MARK)) return false;
if (!matchSpace(results->rawbuf[offset++], GICABLE_HDR_SPACE)) return false;
if (!matchMark(results->rawbuf[offset++], kGicableHdrMark)) return false;
if (!matchSpace(results->rawbuf[offset++], kGicableHdrSpace)) 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);
match_result_t data_result =
matchData(&(results->rawbuf[offset]), nbits, kGicableBitMark,
kGicableOneSpace, kGicableBitMark, kGicableZeroSpace);
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 (!matchMark(results->rawbuf[offset++], kGicableBitMark)) return false;
if (offset < results->rawlen &&
!matchAtLeast(results->rawbuf[offset++], GICABLE_MIN_GAP))
!matchAtLeast(results->rawbuf[offset++], kGicableMinGap))
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;
if (!matchMark(results->rawbuf[offset++], kGicableHdrMark)) return false;
if (!matchSpace(results->rawbuf[offset++], kGicableRptSpace)) return false;
if (!matchMark(results->rawbuf[offset++], kGicableBitMark)) return false;
}
// Success
+15 -16
View File
@@ -14,12 +14,12 @@
// (http://www.hishamkhalifa.com)
// Constants
#define GLOBALCACHE_MAX_REPEAT 50U
#define GLOBALCACHE_MIN_USEC 80U
#define GLOBALCACHE_FREQ_INDEX 0U
#define GLOBALCACHE_RPT_INDEX GLOBALCACHE_FREQ_INDEX + 1U
#define GLOBALCACHE_RPT_START_INDEX GLOBALCACHE_RPT_INDEX + 1U
#define GLOBALCACHE_START_INDEX GLOBALCACHE_RPT_START_INDEX + 1U
const uint16_t kGlobalCacheMaxRepeat = 50;
const uint32_t kGlobalCacheMinUsec = 80;
const uint8_t kGlobalCacheFreqIndex = 0;
const uint8_t kGlobalCacheRptIndex = kGlobalCacheFreqIndex + 1;
const uint8_t kGlobalCacheRptStartIndex = kGlobalCacheRptIndex + 1;
const uint8_t kGlobalCacheStartIndex = kGlobalCacheRptStartIndex + 1;
#if SEND_GLOBALCACHE
// Send a shortened GlobalCache (GC) IRdb/control tower formatted message.
@@ -40,24 +40,23 @@
// Ref:
// https://irdb.globalcache.com/Home/Database
void IRsend::sendGC(uint16_t buf[], uint16_t len) {
uint16_t hz = buf[GLOBALCACHE_FREQ_INDEX]; // GC frequency is in Hz.
uint16_t hz = buf[kGlobalCacheFreqIndex]; // GC frequency is in Hz.
enableIROut(hz);
uint32_t periodic_time = calcUSecPeriod(hz, false);
uint8_t emits = std::min(buf[GLOBALCACHE_RPT_INDEX],
(uint16_t) GLOBALCACHE_MAX_REPEAT);
uint8_t emits =
std::min(buf[kGlobalCacheRptIndex], (uint16_t)kGlobalCacheMaxRepeat);
// Repeat
for (uint8_t repeat = 0; repeat < emits; repeat++) {
// First time through, start at the beginning (GLOBALCACHE_START_INDEX),
// First time through, start at the beginning (kGlobalCacheStartIndex),
// otherwise for repeats, we start a specified offset from that.
uint16_t offset = GLOBALCACHE_START_INDEX;
if (repeat)
offset += buf[GLOBALCACHE_RPT_START_INDEX] - 1;
uint16_t offset = kGlobalCacheStartIndex;
if (repeat) offset += buf[kGlobalCacheRptStartIndex] - 1;
// Data
for (; offset < len; offset++) {
// Convert periodic units to microseconds.
// Minimum is GLOBALCACHE_MIN_USEC for actual GC units.
uint32_t microseconds = std::max(buf[offset] * periodic_time,
GLOBALCACHE_MIN_USEC);
// Minimum is kGlobalCacheMinUsec for actual GC units.
uint32_t microseconds =
std::max(buf[offset] * periodic_time, kGlobalCacheMinUsec);
// These codes start at an odd index (not even as with sendRaw).
if (offset & 1) // Odd bit.
mark(microseconds);
+121 -141
View File
@@ -12,8 +12,8 @@
#ifndef ARDUINO
#include <string>
#endif
#include "IRremoteESP8266.h"
#include "IRrecv.h"
#include "IRremoteESP8266.h"
#include "IRsend.h"
#include "IRutils.h"
#include "ir_Kelvinator.h"
@@ -26,21 +26,21 @@
// 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_BLOCK_FOOTER 0b010U
#define GREE_BLOCK_FOOTER_BITS 3U
const uint16_t kGreeHdrMark = 9000;
const uint16_t kGreeHdrSpace = 4000;
const uint16_t kGreeBitMark = 620;
const uint16_t kGreeOneSpace = 1600;
const uint16_t kGreeZeroSpace = 540;
const uint16_t kGreeMsgSpace = 19000;
const uint8_t kGreeBlockFooter = 0b010;
const uint8_t kGreeBlockFooterBits = 3;
#if SEND_GREE
// Send a Gree Heat Pump message.
//
// Args:
// data: An array of bytes containing the IR command.
// nbytes: Nr. of bytes of data in the array. (>=GREE_STATE_LENGTH)
// nbytes: Nr. of bytes of data in the array. (>=kGreeStateLength)
// repeat: Nr. of times the message is to be repeated. (Default = 0).
//
// Status: ALPHA / Untested.
@@ -48,29 +48,24 @@
// Ref:
// https://github.com/ToniA/arduino-heatpumpir/blob/master/GreeHeatpumpIR.cpp
void IRsend::sendGree(unsigned char data[], uint16_t nbytes, uint16_t repeat) {
if (nbytes < GREE_STATE_LENGTH)
if (nbytes < kGreeStateLength)
return; // Not enough bytes to send a proper message.
for (uint16_t r = 0; r <= repeat; r++) {
// 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.
sendGeneric(kGreeHdrMark, kGreeHdrSpace, kGreeBitMark, kGreeOneSpace,
kGreeBitMark, kGreeZeroSpace, 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);
kGreeBitMark, kGreeOneSpace, kGreeBitMark, kGreeZeroSpace,
kGreeBitMark, kGreeMsgSpace, 0b010, 3, 38, true, 0, false);
// 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);
kGreeBitMark, kGreeOneSpace, kGreeBitMark, kGreeZeroSpace,
kGreeBitMark, kGreeMsgSpace, data + 4, nbytes - 4, 38, false, 0,
50);
}
}
@@ -78,7 +73,7 @@ void IRsend::sendGree(unsigned char data[], uint16_t nbytes, uint16_t repeat) {
//
// Args:
// data: The raw message to be sent.
// nbits: Nr. of bits of data in the message. (Default is GREE_BITS)
// nbits: Nr. of bits of data in the message. (Default is kGreeBits)
// repeat: Nr. of times the message is to be repeated. (Default = 0).
//
// Status: ALPHA / Untested.
@@ -86,43 +81,40 @@ void IRsend::sendGree(unsigned char data[], uint16_t nbytes, uint16_t repeat) {
// Ref:
// https://github.com/ToniA/arduino-heatpumpir/blob/master/GreeHeatpumpIR.cpp
void IRsend::sendGree(uint64_t data, uint16_t nbits, uint16_t repeat) {
if (nbits != GREE_BITS)
if (nbits != kGreeBits)
return; // Wrong nr. of bits to send a proper message.
// Set IR carrier frequency
enableIROut(38);
for (uint16_t r = 0; r <= repeat; r++) {
// Header
mark(GREE_HDR_MARK);
space(GREE_HDR_SPACE);
mark(kGreeHdrMark);
space(kGreeHdrSpace);
// Data
for (int16_t i = 8; i <= nbits; i += 8) {
sendData(GREE_BIT_MARK, GREE_ONE_SPACE, GREE_BIT_MARK, GREE_ZERO_SPACE,
sendData(kGreeBitMark, kGreeOneSpace, kGreeBitMark, kGreeZeroSpace,
(data >> (nbits - i)) & 0xFF, 8, false);
if (i == nbits / 2) {
// Send the mid-message Footer.
sendData(GREE_BIT_MARK, GREE_ONE_SPACE, GREE_BIT_MARK, GREE_ZERO_SPACE,
sendData(kGreeBitMark, kGreeOneSpace, kGreeBitMark, kGreeZeroSpace,
0b010, 3);
mark(GREE_BIT_MARK);
space(GREE_MSG_SPACE);
mark(kGreeBitMark);
space(kGreeMsgSpace);
}
}
// Footer
mark(GREE_BIT_MARK);
space(GREE_MSG_SPACE);
mark(kGreeBitMark);
space(kGreeMsgSpace);
}
}
#endif // SEND_GREE
IRGreeAC::IRGreeAC(uint16_t pin) : _irsend(pin) {
stateReset();
}
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;
for (uint8_t i = 0; i < kGreeStateLength; i++) remote_state[i] = 0x0;
remote_state[1] = 0x09;
remote_state[2] = 0x20;
remote_state[3] = 0x50;
@@ -134,24 +126,22 @@ void IRGreeAC::fixup() {
checksum(); // Calculate the checksums
}
void IRGreeAC::begin() {
_irsend.begin();
}
void IRGreeAC::begin() { _irsend.begin(); }
#if SEND_GREE
void IRGreeAC::send() {
fixup(); // Ensure correct settings before sending.
_irsend.sendGree(remote_state);
void IRGreeAC::send(const uint16_t repeat) {
fixup(); // Ensure correct settings before sending.
_irsend.sendGree(remote_state, kGreeStateLength, repeat);
}
#endif // SEND_GREE
uint8_t* IRGreeAC::getRaw() {
fixup(); // Ensure correct settings before sending.
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++) {
for (uint8_t i = 0; i < kGreeStateLength; i++) {
remote_state[i] = new_code[i];
}
}
@@ -170,21 +160,21 @@ void IRGreeAC::checksum(const uint16_t length) {
// 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))
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;
remote_state[0] |= kGreePower1Mask;
remote_state[2] |= kGreePower2Mask;
}
void IRGreeAC::off() {
remote_state[0] &= ~GREE_POWER1_MASK;
remote_state[2] &= ~GREE_POWER2_MASK;
remote_state[0] &= ~kGreePower1Mask;
remote_state[2] &= ~kGreePower2Mask;
}
void IRGreeAC::setPower(const bool state) {
@@ -195,136 +185,124 @@ void IRGreeAC::setPower(const bool state) {
}
bool IRGreeAC::getPower() {
return (remote_state[0] & GREE_POWER1_MASK) &&
(remote_state[2] & GREE_POWER2_MASK);
return (remote_state[0] & kGreePower1Mask) &&
(remote_state[2] & kGreePower2Mask);
}
// 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);
uint8_t new_temp = std::max((uint8_t)kGreeMinTemp, temp);
new_temp = std::min((uint8_t)kGreeMaxTemp, new_temp);
if (getMode() == kGreeAuto) new_temp = 25;
remote_state[1] = (remote_state[1] & 0xF0U) | (new_temp - kGreeMinTemp);
}
// Return the set temp. in deg C
uint8_t IRGreeAC::getTemp() {
return ((remote_state[1] & 0xFU) + GREE_MIN_TEMP);
return ((remote_state[1] & 0xFU) + kGreeMinTemp);
}
// 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
uint8_t fan = std::min((uint8_t)kGreeFanMax, speed); // Bounds check
if (getMode() == GREE_DRY) fan = 1; // DRY mode is always locked to fan 1.
if (getMode() == kGreeDry) fan = 1; // DRY mode is always locked to fan 1.
// Set the basic fan values.
remote_state[0] &= ~GREE_FAN_MASK;
remote_state[0] &= ~kGreeFanMask;
remote_state[0] |= (fan << 4);
}
uint8_t IRGreeAC::getFan() {
return ((remote_state[0] & GREE_FAN_MASK) >> 4);
}
uint8_t IRGreeAC::getFan() { return ((remote_state[0] & kGreeFanMask) >> 4); }
void IRGreeAC::setMode(const uint8_t new_mode) {
uint8_t mode = new_mode;
switch (mode) {
case GREE_AUTO:
case kGreeAuto:
// AUTO is locked to 25C
setTemp(25);
break;
case GREE_DRY:
case kGreeDry:
// DRY always sets the fan to 1.
setFan(1);
break;
case GREE_COOL:
case GREE_FAN:
case GREE_HEAT:
case kGreeCool:
case kGreeFan:
case kGreeHeat:
break;
default:
// If we get an unexpected mode, default to AUTO.
mode = GREE_AUTO;
mode = kGreeAuto;
}
remote_state[0] &= ~GREE_MODE_MASK;
remote_state[0] &= ~kGreeModeMask;
remote_state[0] |= mode;
}
uint8_t IRGreeAC::getMode() {
return (remote_state[0] & GREE_MODE_MASK);
}
uint8_t IRGreeAC::getMode() { return (remote_state[0] & kGreeModeMask); }
void IRGreeAC::setLight(const bool state) {
remote_state[2] &= ~GREE_LIGHT_MASK;
remote_state[2] &= ~kGreeLightMask;
remote_state[2] |= (state << 5);
}
bool IRGreeAC::getLight() {
return remote_state[2] & GREE_LIGHT_MASK;
}
bool IRGreeAC::getLight() { return remote_state[2] & kGreeLightMask; }
void IRGreeAC::setXFan(const bool state) {
remote_state[2] &= ~GREE_XFAN_MASK;
remote_state[2] &= ~kGreeXfanMask;
remote_state[2] |= (state << 7);
}
bool IRGreeAC::getXFan() {
return remote_state[2] & GREE_XFAN_MASK;
}
bool IRGreeAC::getXFan() { return remote_state[2] & kGreeXfanMask; }
void IRGreeAC::setSleep(const bool state) {
remote_state[0] &= ~GREE_SLEEP_MASK;
remote_state[0] &= ~kGreeSleepMask;
remote_state[0] |= (state << 7);
}
bool IRGreeAC::getSleep() {
return remote_state[0] & GREE_SLEEP_MASK;
}
bool IRGreeAC::getSleep() { return remote_state[0] & kGreeSleepMask; }
void IRGreeAC::setTurbo(const bool state) {
remote_state[2] &= ~GREE_TURBO_MASK;
remote_state[2] &= ~kGreeTurboMask;
remote_state[2] |= (state << 4);
}
bool IRGreeAC::getTurbo() {
return remote_state[2] & GREE_TURBO_MASK;
}
bool IRGreeAC::getTurbo() { return remote_state[2] & kGreeTurboMask; }
void IRGreeAC::setSwingVertical(const bool automatic, const uint8_t position) {
remote_state[0] &= ~GREE_SWING_AUTO_MASK;
remote_state[0] &= ~kGreeSwingAutoMask;
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:
case kGreeSwingUp:
case kGreeSwingMiddleUp:
case kGreeSwingMiddle:
case kGreeSwingMiddleDown:
case kGreeSwingDown:
break;
default:
new_position = GREE_SWING_LAST_POS;
new_position = kGreeSwingLastPos;
}
} else {
switch (position) {
case GREE_SWING_AUTO:
case GREE_SWING_DOWN_AUTO:
case GREE_SWING_MIDDLE_AUTO:
case GREE_SWING_UP_AUTO:
case kGreeSwingAuto:
case kGreeSwingDownAuto:
case kGreeSwingMiddleAuto:
case kGreeSwingUpAuto:
break;
default:
new_position = GREE_SWING_AUTO;
new_position = kGreeSwingAuto;
}
}
remote_state[4] &= ~GREE_SWING_POS_MASK;
remote_state[4] &= ~kGreeSwingPosMask;
remote_state[4] |= new_position;
}
bool IRGreeAC::getSwingVerticalAuto() {
return remote_state[0] & GREE_SWING_AUTO_MASK;
return remote_state[0] & kGreeSwingAutoMask;
}
uint8_t IRGreeAC::getSwingVerticalPosition() {
return remote_state[4] & GREE_SWING_POS_MASK;
return remote_state[4] & kGreeSwingPosMask;
}
// Convert the internal state into a human readable string.
@@ -342,19 +320,19 @@ std::string IRGreeAC::toString() {
result += "Off";
result += ", Mode: " + uint64ToString(getMode());
switch (getMode()) {
case GREE_AUTO:
case kGreeAuto:
result += " (AUTO)";
break;
case GREE_COOL:
case kGreeCool:
result += " (COOL)";
break;
case GREE_HEAT:
case kGreeHeat:
result += " (HEAT)";
break;
case GREE_DRY:
case kGreeDry:
result += " (DRY)";
break;
case GREE_FAN:
case kGreeFan:
result += " (FAN)";
break;
default:
@@ -366,7 +344,7 @@ std::string IRGreeAC::toString() {
case 0:
result += " (AUTO)";
break;
case GREE_FAN_MAX:
case kGreeFanMax:
result += " (MAX)";
break;
}
@@ -395,13 +373,13 @@ std::string IRGreeAC::toString() {
result += "Auto";
else
result += "Manual";
result += ", Swing Vertical Pos: " +
uint64ToString(getSwingVerticalPosition());
result +=
", Swing Vertical Pos: " + uint64ToString(getSwingVerticalPosition());
switch (getSwingVerticalPosition()) {
case GREE_SWING_LAST_POS:
case kGreeSwingLastPos:
result += " (Last Pos)";
break;
case GREE_SWING_AUTO:
case kGreeSwingAuto:
result += " (Auto)";
break;
}
@@ -413,21 +391,21 @@ std::string IRGreeAC::toString() {
//
// 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.
// nbits: The number of data bits to expect. Typically kGreeBits.
// 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))
bool IRrecv::decodeGree(decode_results* results, uint16_t nbits, bool strict) {
if (results->rawlen <
2 * (nbits + kGreeBlockFooterBits) + (kHeader + kFooter + 1))
return false; // Can't possibly be a valid Gree message.
if (strict && nbits != GREE_BITS)
if (strict && nbits != kGreeBits)
return false; // Not strictly a Gree message.
uint32_t data;
uint16_t offset = OFFSET_START;
uint16_t offset = kStartOffset;
// There are two blocks back-to-back in a full Gree IR message
// sequence.
@@ -435,54 +413,56 @@ bool IRrecv::decodeGree(decode_results *results, uint16_t nbits, bool strict) {
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;
if (!matchMark(results->rawbuf[offset++], kGreeHdrMark)) return false;
if (!matchSpace(results->rawbuf[offset++], kGreeHdrSpace)) 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);
data_result =
matchData(&(results->rawbuf[offset]), 32, kGreeBitMark, kGreeOneSpace,
kGreeBitMark, kGreeZeroSpace, kTolerance, kMarkExcess, false);
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);
for (uint16_t i = 0; i < 4; i++, data >>= 8)
results->state[state_pos + i] = data & 0xFF;
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);
data_result = matchData(&(results->rawbuf[offset]), kGreeBlockFooterBits,
kGreeBitMark, kGreeOneSpace, kGreeBitMark,
kGreeZeroSpace, kTolerance, kMarkExcess, false);
if (data_result.success == false) return false;
if (data_result.data != GREE_BLOCK_FOOTER) return false;
if (data_result.data != kGreeBlockFooter) 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;
if (!matchMark(results->rawbuf[offset++], kGreeBitMark)) return false;
if (!matchSpace(results->rawbuf[offset++], kGreeMsgSpace)) 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);
data_result =
matchData(&(results->rawbuf[offset]), 32, kGreeBitMark, kGreeOneSpace,
kGreeBitMark, kGreeZeroSpace, kTolerance, kMarkExcess, false);
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);
for (uint16_t i = 0; i < 4; i++, data >>= 8)
results->state[state_pos + i] = data & 0xFF;
state_pos += 4;
// Footer.
if (!matchMark(results->rawbuf[offset++], GREE_BIT_MARK)) return false;
if (!matchMark(results->rawbuf[offset++], kGreeBitMark)) return false;
if (offset <= results->rawlen &&
!matchAtLeast(results->rawbuf[offset], GREE_MSG_SPACE))
!matchAtLeast(results->rawbuf[offset], kGreeMsgSpace))
return false;
// Compliance
if (strict) {
// Correct size/length)
if (state_pos != GREE_STATE_LENGTH) return false;
if (state_pos != kGreeStateLength) return false;
// Verify the message's checksum is correct.
if (!IRGreeAC::validChecksum(results->state)) return false;
}
+56 -32
View File
@@ -22,36 +22,60 @@
// 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
const uint8_t kGreeAuto = 0;
const uint8_t kGreeCool = 1;
const uint8_t kGreeDry = 2;
const uint8_t kGreeFan = 3;
const uint8_t kGreeHeat = 4;
#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
// Byte 0
const uint8_t kGreeModeMask = 0b00000111;
const uint8_t kGreePower1Mask = 0b00001000;
const uint8_t kGreeFanMask = 0b00110000;
const uint8_t kGreeSwingAutoMask = 0b01000000;
const uint8_t kGreeSleepMask = 0b10000000;
// Byte 2
const uint8_t kGreeTurboMask = 0b00010000;
const uint8_t kGreeLightMask = 0b00100000;
const uint8_t kGreePower2Mask = 0b01000000;
const uint8_t kGreeXfanMask = 0b10000000;
// Byte 4
const uint8_t kGreeSwingPosMask = 0b00001111;
#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
const uint8_t kGreeMinTemp = 16; // Celsius
const uint8_t kGreeMaxTemp = 30; // Celsius
const uint8_t kGreeFanMax = 3;
const uint8_t kGreeSwingLastPos = 0b00000000;
const uint8_t kGreeSwingAuto = 0b00000001;
const uint8_t kGreeSwingUp = 0b00000010;
const uint8_t kGreeSwingMiddleUp = 0b00000011;
const uint8_t kGreeSwingMiddle = 0b00000100;
const uint8_t kGreeSwingMiddleDown = 0b00000101;
const uint8_t kGreeSwingDown = 0b00000110;
const uint8_t kGreeSwingDownAuto = 0b00000111;
const uint8_t kGreeSwingMiddleAuto = 0b00001001;
const uint8_t kGreeSwingUpAuto = 0b00001011;
// Legacy defines.
#define GREE_AUTO kGreeAuto
#define GREE_COOL kGreeCool
#define GREE_DRY kGreeDry
#define GREE_FAN kGreeFan
#define GREE_HEAT kGreeHeat
#define GREE_MIN_TEMP kGreeMinTemp
#define GREE_MAX_TEMP kGreeMaxTemp
#define GREE_FAN_MAX kGreeFanMax
#define GREE_SWING_LAST_POS kGreeSwingLastPos
#define GREE_SWING_AUTO kGreeSwingAuto
#define GREE_SWING_UP kGreeSwingUp
#define GREE_SWING_MIDDLE_UP kGreeSwingMiddleUp
#define GREE_SWING_MIDDLE kGreeSwingMiddle
#define GREE_SWING_MIDDLE_DOWN kGreeSwingMiddleDown
#define GREE_SWING_DOWN kGreeSwingDown
#define GREE_SWING_DOWN_AUTO kGreeSwingDownAuto
#define GREE_SWING_MIDDLE_AUTO kGreeSwingMiddleAuto
#define GREE_SWING_UP_AUTO kGreeSwingUpAuto
// Classes
class IRGreeAC {
@@ -60,7 +84,7 @@ class IRGreeAC {
void stateReset();
#if SEND_GREE
void send();
void send(const uint16_t repeat = kGreeDefaultRepeat);
#endif // SEND_GREE
void begin();
void on();
@@ -88,7 +112,7 @@ class IRGreeAC {
uint8_t* getRaw();
void setRaw(uint8_t new_code[]);
static bool validChecksum(const uint8_t state[],
const uint16_t length = GREE_STATE_LENGTH);
const uint16_t length = kGreeStateLength);
#ifdef ARDUINO
String toString();
#else
@@ -97,8 +121,8 @@ class IRGreeAC {
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);
uint8_t remote_state[kGreeStateLength];
void checksum(const uint16_t length = kGreeStateLength);
void fixup();
IRsend _irsend;
};
+521 -153
View File
@@ -1,5 +1,7 @@
// Copyright 2018 crankyoldgit
// The specifics of reverse engineering the protocol details by kuzin2006
// The specifics of reverse engineering the protocols details:
// * HSU07-HEA03 by kuzin2006.
// * YR-W02/HSU-09HMC203 by non7top.
#include "ir_Haier.h"
#ifndef UNIT_TEST
@@ -18,83 +20,96 @@
// Supported devices:
// * Haier HSU07-HEA03 Remote control.
// * Haier YR-W02 Remote control
// * Haier HSU-09HMC203 A/C unit.
// Ref:
// https://github.com/markszabo/IRremoteESP8266/issues/404
// https://www.dropbox.com/s/mecyib3lhdxc8c6/IR%20data%20reverse%20engineering.xlsx?dl=0
// https://github.com/markszabo/IRremoteESP8266/issues/485
// https://www.dropbox.com/sh/w0bt7egp0fjger5/AADRFV6Wg4wZskJVdFvzb8Z0a?dl=0&preview=haer2.ods
// Constants
#define HAIER_AC_HDR 3000U
#define HAIER_AC_HDR_GAP 4300U
const uint16_t kHaierAcHdr = 3000;
const uint16_t kHaierAcHdrGap = 4300;
const uint16_t kHaierAcBitMark = 520;
const uint16_t kHaierAcOneSpace = 1650;
const uint16_t kHaierAcZeroSpace = 650;
const uint32_t kHaierAcMinGap = 150000; // Completely made up value.
#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.
#if (SEND_HAIER_AC || SEND_HAIER_AC_YRW02)
// Send a Haier A/C message. (HSU07-HEA03 remote)
//
// Args:
// data: An array of bytes containing the IR command.
// nbytes: Nr. of bytes of data in the array. (>=HAIER_AC_STATE_LENGTH)
// nbytes: Nr. of bytes of data in the array. (>=kHaierACStateLength)
// 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;
if (nbytes < kHaierACStateLength) 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
mark(kHaierAcHdr);
space(kHaierAcHdr);
sendGeneric(kHaierAcHdr, kHaierAcHdrGap, kHaierAcBitMark, kHaierAcOneSpace,
kHaierAcBitMark, kHaierAcZeroSpace, kHaierAcBitMark,
kHaierAcMinGap, data, nbytes, 38, true,
0, // Repeats handled elsewhere
50);
}
}
#endif // SEND_HAIER_AC
#endif // (SEND_HAIER_AC || SEND_HAIER_AC_YRW02)
IRHaierAC::IRHaierAC(uint16_t pin) : _irsend(pin) {
stateReset();
#if SEND_HAIER_AC_YRW02
// Send a Haier YR-W02 remote A/C message.
//
// Args:
// data: An array of bytes containing the IR command.
// nbytes: Nr. of bytes of data in the array. (>=kHaierACYRW02StateLength)
// repeat: Nr. of times the message is to be repeated. (Default = 0).
//
// Status: Alpha / Untested on a real device.
//
void IRsend::sendHaierACYRW02(unsigned char data[], uint16_t nbytes,
uint16_t repeat) {
if (nbytes >= kHaierACYRW02StateLength) sendHaierAC(data, nbytes, repeat);
}
#endif // SEND_HAIER_AC_YRW02
void IRHaierAC::begin() {
_irsend.begin();
}
// Class for emulating a Haier HSU07-HEA03 remote
IRHaierAC::IRHaierAC(uint16_t pin) : _irsend(pin) { stateReset(); }
void IRHaierAC::begin() { _irsend.begin(); }
#if SEND_HAIER_AC
void IRHaierAC::send() {
void IRHaierAC::send(const uint16_t repeat) {
checksum();
_irsend.sendHaierAC(remote_state);
_irsend.sendHaierAC(remote_state, kHaierACStateLength, repeat);
}
#endif // SEND_HAIER_AC
void IRHaierAC::checksum() {
remote_state[8] = sumBytes(remote_state, HAIER_AC_STATE_LENGTH - 1);
remote_state[8] = sumBytes(remote_state, kHaierACStateLength - 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.
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;
for (uint8_t i = 1; i < kHaierACStateLength; i++) remote_state[i] = 0x0;
remote_state[0] = kHaierAcPrefix;
remote_state[2] = 0b00100000;
setTemp(HAIER_AC_DEF_TEMP);
setFan(HAIER_AC_FAN_AUTO);
setMode(HAIER_AC_AUTO);
setCommand(HAIER_AC_CMD_ON);
setTemp(kHaierAcDefTemp);
setFan(kHaierAcFanAuto);
setMode(kHaierAcAuto);
setCommand(kHaierAcCmdOn);
}
uint8_t* IRHaierAC::getRaw() {
@@ -103,7 +118,7 @@ uint8_t* IRHaierAC::getRaw() {
}
void IRHaierAC::setRaw(uint8_t new_code[]) {
for (uint8_t i = 0; i < HAIER_AC_STATE_LENGTH; i++) {
for (uint8_t i = 0; i < kHaierACStateLength; i++) {
remote_state[i] = new_code[i];
}
}
@@ -111,42 +126,40 @@ void IRHaierAC::setRaw(uint8_t new_code[]) {
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:
case kHaierAcCmdOff:
case kHaierAcCmdOn:
case kHaierAcCmdMode:
case kHaierAcCmdFan:
case kHaierAcCmdTempUp:
case kHaierAcCmdTempDown:
case kHaierAcCmdSleep:
case kHaierAcCmdTimerSet:
case kHaierAcCmdTimerCancel:
case kHaierAcCmdHealth:
case kHaierAcCmdSwing:
remote_state[1] |= (state & 0b00001111);
}
}
uint8_t IRHaierAC::getCommand() {
return remote_state[1] & (0b00001111);
}
uint8_t IRHaierAC::getCommand() { return remote_state[1] & (0b00001111); }
void IRHaierAC::setFan(uint8_t speed) {
uint8_t new_speed = HAIER_AC_FAN_AUTO;
uint8_t new_speed = kHaierAcFanAuto;
switch (speed) {
case HAIER_AC_FAN_LOW:
case kHaierAcFanLow:
new_speed = 3;
break;
case HAIER_AC_FAN_MED:
case kHaierAcFanMed:
new_speed = 1;
break;
case HAIER_AC_FAN_HIGH:
case kHaierAcFanHigh:
new_speed = 2;
break;
default:
new_speed = HAIER_AC_FAN_AUTO; // Default to auto for anything else.
new_speed = kHaierAcFanAuto; // Default to auto for anything else.
}
if (speed != getFan()) setCommand(HAIER_AC_CMD_FAN);
if (speed != getFan()) setCommand(kHaierAcCmdFan);
remote_state[5] &= 0b11111100;
remote_state[5] |= new_speed;
}
@@ -154,70 +167,64 @@ void IRHaierAC::setFan(uint8_t speed) {
uint8_t IRHaierAC::getFan() {
switch (remote_state[5] & 0b00000011) {
case 1:
return HAIER_AC_FAN_MED;
return kHaierAcFanMed;
case 2:
return HAIER_AC_FAN_HIGH;
return kHaierAcFanHigh;
case 3:
return HAIER_AC_FAN_LOW;
return kHaierAcFanLow;
default:
return HAIER_AC_FAN_AUTO;
return kHaierAcFanAuto;
}
}
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;
setCommand(kHaierAcCmdMode);
if (mode > kHaierAcFan) // If out of range, default to auto mode.
new_mode = kHaierAcAuto;
remote_state[7] &= 0b00011111;
remote_state[7] |= (new_mode << 5);
}
uint8_t IRHaierAC::getMode() {
return (remote_state[7] & 0b11100000) >> 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;
void IRHaierAC::setTemp(const uint8_t celsius) {
uint8_t temp = celsius;
if (temp < kHaierAcMinTemp)
temp = kHaierAcMinTemp;
else if (temp > kHaierAcMaxTemp)
temp = kHaierAcMaxTemp;
uint8_t old_temp = getTemp();
if (old_temp == temp) return;
if (old_temp == temp) return;
if (old_temp > temp)
setCommand(HAIER_AC_CMD_TEMP_DOWN);
setCommand(kHaierAcCmdTempDown);
else
setCommand(HAIER_AC_CMD_TEMP_UP);
setCommand(kHaierAcCmdTempUp);
remote_state[1] &= 0b00001111; // Clear the previous temp.
remote_state[1] |= ((temp - HAIER_AC_MIN_TEMP) << 4);
remote_state[1] |= ((temp - kHaierAcMinTemp) << 4);
}
uint8_t IRHaierAC::getTemp() {
return ((remote_state[1] & 0b11110000) >> 4) + HAIER_AC_MIN_TEMP;
return ((remote_state[1] & 0b11110000) >> 4) + kHaierAcMinTemp;
}
void IRHaierAC::setHealth(bool state) {
setCommand(HAIER_AC_CMD_HEALTH);
setCommand(kHaierAcCmdHealth);
remote_state[4] &= 0b11011111;
remote_state[4] |= (state << 5);
}
bool IRHaierAC::getHealth(void) {
return remote_state[4] & (1 << 5);
}
bool IRHaierAC::getHealth(void) { return remote_state[4] & (1 << 5); }
void IRHaierAC::setSleep(bool state) {
setCommand(HAIER_AC_CMD_SLEEP);
setCommand(kHaierAcCmdSleep);
remote_state[7] &= 0b10111111;
remote_state[7] |= (state << 6);
}
bool IRHaierAC::getSleep(void) {
return remote_state[7] & 0b01000000;
}
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);
@@ -237,16 +244,13 @@ int16_t IRHaierAC::getOffTimer() {
return -1;
}
uint16_t IRHaierAC::getCurrTime() {
return getTime(remote_state + 2);
}
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;
if (nr_mins > kHaierAcMaxTime) mins = kHaierAcMaxTime;
// Hours
// Hours
ptr[0] &= 0b11100000;
ptr[0] |= (mins / 60);
// Minutes
@@ -255,19 +259,19 @@ void IRHaierAC::setTime(uint8_t ptr[], const uint16_t nr_mins) {
}
void IRHaierAC::setOnTimer(const uint16_t nr_mins) {
setCommand(HAIER_AC_CMD_TIMER_SET);
setCommand(kHaierAcCmdTimerSet);
remote_state[3] |= 0b10000000;
setTime(remote_state + 6, nr_mins);
}
void IRHaierAC::setOffTimer(const uint16_t nr_mins) {
setCommand(HAIER_AC_CMD_TIMER_SET);
setCommand(kHaierAcCmdTimerSet);
remote_state[3] |= 0b01000000;
setTime(remote_state + 4, nr_mins);
}
void IRHaierAC::cancelTimers() {
setCommand(HAIER_AC_CMD_TIMER_CANCEL);
setCommand(kHaierAcCmdTimerCancel);
remote_state[3] &= 0b00111111;
}
@@ -275,18 +279,16 @@ void IRHaierAC::setCurrTime(const uint16_t nr_mins) {
setTime(remote_state + 2, nr_mins);
}
uint8_t IRHaierAC::getSwing() {
return (remote_state[2] & 0b11000000) >> 6;
}
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);
if (state == getSwing()) return; // Nothing to do.
setCommand(kHaierAcCmdSwing);
switch (state) {
case HAIER_AC_SWING_OFF:
case HAIER_AC_SWING_UP:
case HAIER_AC_SWING_DOWN:
case HAIER_AC_SWING_CHG:
case kHaierAcSwingOff:
case kHaierAcSwingUp:
case kHaierAcSwingDown:
case kHaierAcSwingChg:
remote_state[2] &= 0b00111111;
remote_state[2] |= (state << 6);
break;
@@ -302,10 +304,10 @@ std::string IRHaierAC::timeToString(const uint16_t nr_mins) {
std::string result = "";
#endif // ARDUINO
if (nr_mins / 24 < 10) result += "0"; // Zero pad.
if (nr_mins / 24 < 10) result += "0"; // Zero pad.
result += uint64ToString(nr_mins / 60);
result += ":";
if (nr_mins % 60 < 10) result += "0"; // Zero pad.
if (nr_mins % 60 < 10) result += "0"; // Zero pad.
result += uint64ToString(nr_mins % 60);
return result;
}
@@ -319,39 +321,39 @@ std::string IRHaierAC::toString() {
std::string result = "";
#endif // ARDUINO
uint8_t cmd = getCommand();
result += "Command: " + uint64ToString(cmd) +" (";
result += "Command: " + uint64ToString(cmd) + " (";
switch (cmd) {
case HAIER_AC_CMD_OFF:
case kHaierAcCmdOff:
result += "Off";
break;
case HAIER_AC_CMD_ON:
case kHaierAcCmdOn:
result += "On";
break;
case HAIER_AC_CMD_MODE:
case kHaierAcCmdMode:
result += "Mode";
break;
case HAIER_AC_CMD_FAN:
case kHaierAcCmdFan:
result += "Fan";
break;
case HAIER_AC_CMD_TEMP_UP:
case kHaierAcCmdTempUp:
result += "Temp Up";
break;
case HAIER_AC_CMD_TEMP_DOWN:
case kHaierAcCmdTempDown:
result += "Temp Down";
break;
case HAIER_AC_CMD_SLEEP:
case kHaierAcCmdSleep:
result += "Sleep";
break;
case HAIER_AC_CMD_TIMER_SET:
case kHaierAcCmdTimerSet:
result += "Timer Set";
break;
case HAIER_AC_CMD_TIMER_CANCEL:
case kHaierAcCmdTimerCancel:
result += "Timer Cancel";
break;
case HAIER_AC_CMD_HEALTH:
case kHaierAcCmdHealth:
result += "Health";
break;
case HAIER_AC_CMD_SWING:
case kHaierAcCmdSwing:
result += "Swing";
break;
default:
@@ -360,19 +362,19 @@ std::string IRHaierAC::toString() {
result += ")";
result += ", Mode: " + uint64ToString(getMode());
switch (getMode()) {
case HAIER_AC_AUTO:
case kHaierAcAuto:
result += " (AUTO)";
break;
case HAIER_AC_COOL:
case kHaierAcCool:
result += " (COOL)";
break;
case HAIER_AC_HEAT:
case kHaierAcHeat:
result += " (HEAT)";
break;
case HAIER_AC_DRY:
case kHaierAcDry:
result += " (DRY)";
break;
case HAIER_AC_FAN:
case kHaierAcFan:
result += " (FAN)";
break;
default:
@@ -381,25 +383,25 @@ std::string IRHaierAC::toString() {
result += ", Temp: " + uint64ToString(getTemp()) + "C";
result += ", Fan: " + uint64ToString(getFan());
switch (getFan()) {
case HAIER_AC_FAN_AUTO:
case kHaierAcFanAuto:
result += " (AUTO)";
break;
case HAIER_AC_FAN_HIGH:
case kHaierAcFanHigh:
result += " (MAX)";
break;
}
result += ", Swing: " + uint64ToString(getSwing()) + " (";
switch (getSwing()) {
case HAIER_AC_SWING_OFF:
case kHaierAcSwingOff:
result += "Off";
break;
case HAIER_AC_SWING_UP:
case kHaierAcSwingUp:
result += "Up";
break;
case HAIER_AC_SWING_DOWN:
case kHaierAcSwingDown:
result += "Down";
break;
case HAIER_AC_SWING_CHG:
case kHaierAcSwingChg:
result += "Chg";
break;
default:
@@ -430,63 +432,394 @@ std::string IRHaierAC::toString() {
return result;
}
// End of IRHaierAC class.
#if DECODE_HAIER_AC
// Decode the supplied Haier message.
// Class for emulating a Haier YRW02 remote
IRHaierACYRW02::IRHaierACYRW02(uint16_t pin) : _irsend(pin) { stateReset(); }
void IRHaierACYRW02::begin() { _irsend.begin(); }
#if SEND_HAIER_AC_YRW02
void IRHaierACYRW02::send(const uint16_t repeat) {
checksum();
_irsend.sendHaierACYRW02(remote_state, kHaierACYRW02StateLength, repeat);
}
#endif // SEND_HAIER_AC_YRW02
void IRHaierACYRW02::checksum() {
remote_state[kHaierACYRW02StateLength - 1] =
sumBytes(remote_state, kHaierACYRW02StateLength - 1);
}
bool IRHaierACYRW02::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 IRHaierACYRW02::stateReset() {
for (uint8_t i = 1; i < kHaierACYRW02StateLength; i++) remote_state[i] = 0x0;
remote_state[0] = kHaierAcYrw02Prefix;
setTemp(kHaierAcDefTemp);
setHealth(true);
setTurbo(kHaierAcYrw02TurboOff);
setSleep(false);
setFan(kHaierAcYrw02FanAuto);
setSwing(kHaierAcYrw02SwingOff);
setMode(kHaierAcYrw02Auto);
setPower(true);
}
uint8_t* IRHaierACYRW02::getRaw() {
checksum();
return remote_state;
}
void IRHaierACYRW02::setRaw(uint8_t new_code[]) {
for (uint8_t i = 0; i < kHaierACYRW02StateLength; i++) {
remote_state[i] = new_code[i];
}
}
void IRHaierACYRW02::setButton(uint8_t button) {
switch (button) {
case kHaierAcYrw02ButtonTempUp:
case kHaierAcYrw02ButtonTempDown:
case kHaierAcYrw02ButtonSwing:
case kHaierAcYrw02ButtonFan:
case kHaierAcYrw02ButtonPower:
case kHaierAcYrw02ButtonMode:
case kHaierAcYrw02ButtonHealth:
case kHaierAcYrw02ButtonTurbo:
case kHaierAcYrw02ButtonSleep:
remote_state[12] &= 0b11110000;
remote_state[12] |= (button & 0b00001111);
}
}
uint8_t IRHaierACYRW02::getButton() { return remote_state[12] & (0b00001111); }
void IRHaierACYRW02::setMode(uint8_t mode) {
uint8_t new_mode = mode;
setButton(kHaierAcYrw02ButtonMode);
switch (mode) {
case kHaierAcYrw02Auto:
case kHaierAcYrw02Cool:
case kHaierAcYrw02Dry:
case kHaierAcYrw02Heat:
case kHaierAcYrw02Fan:
break;
default: // If unexpected, default to auto mode.
new_mode = kHaierAcYrw02Auto;
}
remote_state[7] &= 0b0001111;
remote_state[7] |= (new_mode << 4);
}
uint8_t IRHaierACYRW02::getMode() { return remote_state[7] >> 4; }
void IRHaierACYRW02::setTemp(const uint8_t celcius) {
uint8_t temp = celcius;
if (temp < kHaierAcMinTemp)
temp = kHaierAcMinTemp;
else if (temp > kHaierAcMaxTemp)
temp = kHaierAcMaxTemp;
uint8_t old_temp = getTemp();
if (old_temp == temp) return;
if (old_temp > temp)
setButton(kHaierAcYrw02ButtonTempDown);
else
setButton(kHaierAcYrw02ButtonTempUp);
remote_state[1] &= 0b00001111; // Clear the previous temp.
remote_state[1] |= ((temp - kHaierAcMinTemp) << 4);
}
uint8_t IRHaierACYRW02::getTemp() {
return ((remote_state[1] & 0b11110000) >> 4) + kHaierAcMinTemp;
}
void IRHaierACYRW02::setHealth(bool state) {
setButton(kHaierAcYrw02ButtonHealth);
remote_state[3] &= 0b11111101;
remote_state[3] |= (state << 1);
}
bool IRHaierACYRW02::getHealth(void) { return remote_state[3] & 0b00000010; }
bool IRHaierACYRW02::getPower() { return remote_state[4] & kHaierAcYrw02Power; }
void IRHaierACYRW02::setPower(bool state) {
setButton(kHaierAcYrw02ButtonPower);
if (state)
remote_state[4] |= kHaierAcYrw02Power;
else
remote_state[4] &= ~kHaierAcYrw02Power;
}
void IRHaierACYRW02::on() { setPower(true); }
void IRHaierACYRW02::off() { setPower(false); }
bool IRHaierACYRW02::getSleep() { return remote_state[8] & kHaierAcYrw02Sleep; }
void IRHaierACYRW02::setSleep(bool state) {
setButton(kHaierAcYrw02ButtonSleep);
if (state)
remote_state[8] |= kHaierAcYrw02Sleep;
else
remote_state[8] &= ~kHaierAcYrw02Sleep;
}
uint8_t IRHaierACYRW02::getTurbo() { return remote_state[6] >> 6; }
void IRHaierACYRW02::setTurbo(uint8_t speed) {
switch (speed) {
case kHaierAcYrw02TurboOff:
case kHaierAcYrw02TurboLow:
case kHaierAcYrw02TurboHigh:
remote_state[6] &= 0b00111111;
remote_state[6] |= (speed << 6);
setButton(kHaierAcYrw02ButtonTurbo);
}
}
uint8_t IRHaierACYRW02::getFan() { return remote_state[5] >> 4; }
void IRHaierACYRW02::setFan(uint8_t speed) {
switch (speed) {
case kHaierAcYrw02FanLow:
case kHaierAcYrw02FanMed:
case kHaierAcYrw02FanHigh:
case kHaierAcYrw02FanAuto:
remote_state[5] &= 0b00001111;
remote_state[5] |= (speed << 4);
setButton(kHaierAcYrw02ButtonFan);
}
}
uint8_t IRHaierACYRW02::getSwing() { return remote_state[1] & 0b00001111; }
void IRHaierACYRW02::setSwing(uint8_t state) {
uint8_t newstate = state;
switch (state) {
case kHaierAcYrw02SwingOff:
case kHaierAcYrw02SwingAuto:
case kHaierAcYrw02SwingTop:
case kHaierAcYrw02SwingMiddle:
case kHaierAcYrw02SwingBottom:
case kHaierAcYrw02SwingDown:
setButton(kHaierAcYrw02ButtonSwing);
break;
default:
return; // Unexpected value so don't do anything.
}
// Heat mode has no MIDDLE setting, use BOTTOM instead.
if (state == kHaierAcYrw02SwingMiddle && getMode() == kHaierAcYrw02Heat)
newstate = kHaierAcYrw02SwingBottom;
// BOTTOM is only allowed if we are in Heat mode, otherwise MIDDLE.
if (state == kHaierAcYrw02SwingBottom && getMode() != kHaierAcYrw02Heat)
newstate = kHaierAcYrw02SwingMiddle;
remote_state[1] &= 0b11110000;
remote_state[1] |= newstate;
}
// Convert the internal state into a human readable string.
#ifdef ARDUINO
String IRHaierACYRW02::toString() {
String result = "";
#else
std::string IRHaierACYRW02::toString() {
std::string result = "";
#endif // ARDUINO
result += "Power: ";
if (getPower())
result += "On";
else
result += "Off";
uint8_t cmd = getButton();
result += ", Button: " + uint64ToString(cmd) + " (";
switch (cmd) {
case kHaierAcYrw02ButtonPower:
result += "Power";
break;
case kHaierAcYrw02ButtonMode:
result += "Mode";
break;
case kHaierAcYrw02ButtonFan:
result += "Fan";
break;
case kHaierAcYrw02ButtonTempUp:
result += "Temp Up";
break;
case kHaierAcYrw02ButtonTempDown:
result += "Temp Down";
break;
case kHaierAcYrw02ButtonSleep:
result += "Sleep";
break;
case kHaierAcYrw02ButtonHealth:
result += "Health";
break;
case kHaierAcYrw02ButtonSwing:
result += "Swing";
break;
case kHaierAcYrw02ButtonTurbo:
result += "Turbo";
break;
default:
result += "Unknown";
}
result += ")";
result += ", Mode: " + uint64ToString(getMode());
switch (getMode()) {
case kHaierAcYrw02Auto:
result += " (Auto)";
break;
case kHaierAcYrw02Cool:
result += " (Cool)";
break;
case kHaierAcYrw02Heat:
result += " (Heat)";
break;
case kHaierAcYrw02Dry:
result += " (Dry)";
break;
case kHaierAcYrw02Fan:
result += " (Fan)";
break;
default:
result += " (UNKNOWN)";
}
result += ", Temp: " + uint64ToString(getTemp()) + "C";
result += ", Fan: " + uint64ToString(getFan());
switch (getFan()) {
case kHaierAcYrw02FanAuto:
result += " (Auto)";
break;
case kHaierAcYrw02FanHigh:
result += " (High)";
break;
case kHaierAcYrw02FanLow:
result += " (Low)";
break;
case kHaierAcYrw02FanMed:
result += " (Med)";
break;
default:
result += " (Unknown)";
}
result += ", Turbo: " + uint64ToString(getTurbo()) + " (";
switch (getTurbo()) {
case kHaierAcYrw02TurboOff:
result += "Off";
break;
case kHaierAcYrw02TurboLow:
result += "Low";
break;
case kHaierAcYrw02TurboHigh:
result += "High";
break;
default:
result += "Unknown";
}
result += ")";
result += ", Swing: " + uint64ToString(getSwing()) + " (";
switch (getSwing()) {
case kHaierAcYrw02SwingOff:
result += "Off";
break;
case kHaierAcYrw02SwingAuto:
result += "Auto";
break;
case kHaierAcYrw02SwingBottom:
result += "Bottom";
break;
case kHaierAcYrw02SwingDown:
result += "Down";
break;
case kHaierAcYrw02SwingTop:
result += "Top";
break;
case kHaierAcYrw02SwingMiddle:
result += "Middle";
break;
default:
result += "Unknown";
}
result += ")";
result += ", Sleep: ";
if (getSleep())
result += "On";
else
result += "Off";
result += ", Health: ";
if (getHealth())
result += "On";
else
result += "Off";
return result;
}
// End of IRHaierACYRW02 class.
#if (DECODE_HAIER_AC || DECODE_HAIER_AC_YRW02)
// Decode the supplied Haier HSU07-HEA03 remote 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.
// nbits: The number of data bits to expect. Typically kHaierACBits.
// 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 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)
if (nbits != kHaierACBits)
return false; // Not strictly a HAIER_AC message.
}
if (results->rawlen < (2 * nbits + HEADER) + FOOTER - 1)
if (results->rawlen < (2 * nbits + kHeader) + kFooter - 1)
return false; // Can't possibly be a valid HAIER_AC message.
uint16_t offset = OFFSET_START;
uint16_t offset = kStartOffset;
// 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;
if (!matchMark(results->rawbuf[offset++], kHaierAcHdr)) return false;
if (!matchSpace(results->rawbuf[offset++], kHaierAcHdr)) return false;
if (!matchMark(results->rawbuf[offset++], kHaierAcHdr)) return false;
if (!matchSpace(results->rawbuf[offset++], kHaierAcHdrGap)) 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);
match_result_t data_result =
matchData(&(results->rawbuf[offset]), 8, kHaierAcBitMark,
kHaierAcOneSpace, kHaierAcBitMark, kHaierAcZeroSpace);
if (data_result.success == false) return false;
offset += data_result.used;
results->state[i] = (uint8_t) data_result.data;
results->state[i] = (uint8_t)data_result.data;
}
// Footer
if (!matchMark(results->rawbuf[offset++], HAIER_AC_BIT_MARK)) return false;
if (!matchMark(results->rawbuf[offset++], kHaierAcBitMark)) return false;
if (offset < results->rawlen &&
!matchAtLeast(results->rawbuf[offset++], HAIER_AC_MIN_GAP))
!matchAtLeast(results->rawbuf[offset++], kHaierAcMinGap))
return false;
// Compliance
if (strict) {
if (results->state[0] != HAIER_AC_PREFIX) return false;
if (!IRHaierAC::validChecksum(results->state, nbits / 8)) return false;
if (results->state[0] != kHaierAcPrefix) return false;
if (!IRHaierAC::validChecksum(results->state, nbits / 8)) return false;
}
// Success
@@ -494,4 +827,39 @@ bool IRrecv::decodeHaierAC(decode_results *results, uint16_t nbits,
results->bits = nbits;
return true;
}
#endif // DECODE_HAIER_AC
#endif // (DECODE_HAIER_AC || DECODE_HAIER_AC_YRW02)
#if DECODE_HAIER_AC_YRW02
// Decode the supplied Haier YR-W02 remote 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 kHaierACYRW02Bits.
// 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::decodeHaierACYRW02(decode_results* results, uint16_t nbits,
bool strict) {
if (strict) {
if (nbits != kHaierACYRW02Bits)
return false; // Not strictly a HAIER_AC_YRW02 message.
}
// The protocol is almost exactly the same as HAIER_AC
if (!decodeHaierAC(results, nbits, false)) return false;
// Compliance
if (strict) {
if (results->state[0] != kHaierAcYrw02Prefix) return false;
if (!IRHaierACYRW02::validChecksum(results->state, nbits / 8)) return false;
}
// Success
// It looks correct, but we haven't check the checksum etc.
results->decode_type = HAIER_AC_YRW02;
return true;
}
#endif // DECODE_HAIER_AC_YRW02
+211 -40
View File
@@ -21,55 +21,172 @@
// Ref:
// https://github.com/markszabo/IRremoteESP8266/issues/404
// https://www.dropbox.com/s/mecyib3lhdxc8c6/IR%20data%20reverse%20engineering.xlsx?dl=0
// https://github.com/markszabo/IRremoteESP8266/issues/485
// https://www.dropbox.com/sh/w0bt7egp0fjger5/AADRFV6Wg4wZskJVdFvzb8Z0a?dl=0&preview=haer2.ods
// Constants
// Haier HSU07-HEA03 remote
// Byte 0
#define HAIER_AC_PREFIX 0b10100101
const uint8_t kHaierAcPrefix = 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
const uint8_t kHaierAcMinTemp = 16;
const uint8_t kHaierAcDefTemp = 25;
const uint8_t kHaierAcMaxTemp = 30;
const uint8_t kHaierAcCmdOff = 0b00000000;
const uint8_t kHaierAcCmdOn = 0b00000001;
const uint8_t kHaierAcCmdMode = 0b00000010;
const uint8_t kHaierAcCmdFan = 0b00000011;
const uint8_t kHaierAcCmdTempUp = 0b00000110;
const uint8_t kHaierAcCmdTempDown = 0b00000111;
const uint8_t kHaierAcCmdSleep = 0b00001000;
const uint8_t kHaierAcCmdTimerSet = 0b00001001;
const uint8_t kHaierAcCmdTimerCancel = 0b00001010;
const uint8_t kHaierAcCmdHealth = 0b00001100;
const uint8_t kHaierAcCmdSwing = 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
const uint8_t kHaierAcSwingOff = 0b00000000;
const uint8_t kHaierAcSwingUp = 0b00000001;
const uint8_t kHaierAcSwingDown = 0b00000010;
const uint8_t kHaierAcSwingChg = 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
const uint8_t kHaierAcAuto = 0;
const uint8_t kHaierAcCool = 1;
const uint8_t kHaierAcDry = 2;
const uint8_t kHaierAcHeat = 3;
const uint8_t kHaierAcFan = 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
const uint8_t kHaierAcFanAuto = 0;
const uint8_t kHaierAcFanLow = 1;
const uint8_t kHaierAcFanMed = 2;
const uint8_t kHaierAcFanHigh = 3;
#define HAIER_AC_MAX_TIME (23 * 60 + 59)
const uint16_t kHaierAcMaxTime = (23 * 60) + 59;
// Legacy Haier AC defines.
#define HAIER_AC_MIN_TEMP kHaierAcMinTemp
#define HAIER_AC_DEF_TEMP kHaierAcDefTemp
#define HAIER_AC_MAX_TEMP kHaierAcMaxTemp
#define HAIER_AC_CMD_OFF kHaierAcCmdOff
#define HAIER_AC_CMD_ON kHaierAcCmdOn
#define HAIER_AC_CMD_MODE kHaierAcCmdMode
#define HAIER_AC_CMD_FAN kHaierAcCmdFan
#define HAIER_AC_CMD_TEMP_UP kHaierAcCmdTempUp
#define HAIER_AC_CMD_TEMP_DOWN kHaierAcCmdTempDown
#define HAIER_AC_CMD_SLEEP kHaierAcCmdSleep
#define HAIER_AC_CMD_TIMER_SET kHaierAcCmdTimerSet
#define HAIER_AC_CMD_TIMER_CANCEL kHaierAcCmdTimerCancel
#define HAIER_AC_CMD_HEALTH kHaierAcCmdHealth
#define HAIER_AC_CMD_SWING kHaierAcCmdSwing
#define HAIER_AC_SWING_OFF kHaierAcSwingOff
#define HAIER_AC_SWING_UP kHaierAcSwingUp
#define HAIER_AC_SWING_DOWN kHaierAcSwingDown
#define HAIER_AC_SWING_CHG kHaierAcSwingChg
#define HAIER_AC_AUTO kHaierAcAuto
#define HAIER_AC_COOL kHaierAcCool
#define HAIER_AC_DRY kHaierAcDry
#define HAIER_AC_HEAT kHaierAcHeat
#define HAIER_AC_FAN kHaierAcFan
#define HAIER_AC_FAN_AUTO kHaierAcFanAuto
#define HAIER_AC_FAN_LOW kHaierAcFanLow
#define HAIER_AC_FAN_MED kHaierAcFanMed
#define HAIER_AC_FAN_HIGH kHaierAcFanHigh
// Haier YRW02 remote
// Byte 0
const uint8_t kHaierAcYrw02Prefix = 0xA6;
// Byte 1
// Bits 0-3
// 0x0 = 16DegC, ... 0xE = 30DegC
// Bits 4-7 - Swing
const uint8_t kHaierAcYrw02SwingOff = 0x0;
const uint8_t kHaierAcYrw02SwingTop = 0x1;
const uint8_t kHaierAcYrw02SwingMiddle = 0x2; // Not available in heat mode.
const uint8_t kHaierAcYrw02SwingBottom = 0x3; // Only available in heat mode.
const uint8_t kHaierAcYrw02SwingDown = 0xA;
const uint8_t kHaierAcYrw02SwingAuto = 0xC; // Airflow
// Byte 3
// Bit 7 - Health mode
// Byte 4
const uint8_t kHaierAcYrw02Power = 0b01000000;
// Byte 5
// Bits 0-3
const uint8_t kHaierAcYrw02FanHigh = 0x2;
const uint8_t kHaierAcYrw02FanMed = 0x4;
const uint8_t kHaierAcYrw02FanLow = 0x6;
const uint8_t kHaierAcYrw02FanAuto = 0xA;
// Byte 6
// Bits 0-1
const uint8_t kHaierAcYrw02TurboOff = 0x0;
const uint8_t kHaierAcYrw02TurboHigh = 0x1;
const uint8_t kHaierAcYrw02TurboLow = 0x2;
// Byte 7
// Bits 0-3
const uint8_t kHaierAcYrw02Auto = 0x0;
const uint8_t kHaierAcYrw02Cool = 0x2;
const uint8_t kHaierAcYrw02Dry = 0x4;
const uint8_t kHaierAcYrw02Heat = 0x8;
const uint8_t kHaierAcYrw02Fan = 0xC;
// Byte 8
const uint8_t kHaierAcYrw02Sleep = 0b10000000;
// Byte 12
// Bits 4-7
const uint8_t kHaierAcYrw02ButtonTempUp = 0x0;
const uint8_t kHaierAcYrw02ButtonTempDown = 0x1;
const uint8_t kHaierAcYrw02ButtonSwing = 0x2;
const uint8_t kHaierAcYrw02ButtonFan = 0x4;
const uint8_t kHaierAcYrw02ButtonPower = 0x5;
const uint8_t kHaierAcYrw02ButtonMode = 0x6;
const uint8_t kHaierAcYrw02ButtonHealth = 0x7;
const uint8_t kHaierAcYrw02ButtonTurbo = 0x8;
const uint8_t kHaierAcYrw02ButtonSleep = 0xB;
// Legacy Haier YRW02 remote defines.
#define HAIER_AC_YRW02_SWING_OFF kHaierAcYrw02SwingOff
#define HAIER_AC_YRW02_SWING_TOP kHaierAcYrw02SwingTop
#define HAIER_AC_YRW02_SWING_MIDDLE kHaierAcYrw02SwingMiddle
#define HAIER_AC_YRW02_SWING_BOTTOM kHaierAcYrw02SwingBottom
#define HAIER_AC_YRW02_SWING_DOWN kHaierAcYrw02SwingDown
#define HAIER_AC_YRW02_SWING_AUTO kHaierAcYrw02SwingAuto
#define HAIER_AC_YRW02_FAN_HIGH kHaierAcYrw02FanHigh
#define HAIER_AC_YRW02_FAN_MED kHaierAcYrw02FanMed
#define HAIER_AC_YRW02_FAN_LOW kHaierAcYrw02FanLow
#define HAIER_AC_YRW02_FAN_AUTO kHaierAcYrw02FanAuto
#define HAIER_AC_YRW02_TURBO_OFF kHaierAcYrw02TurboOff
#define HAIER_AC_YRW02_TURBO_HIGH kHaierAcYrw02TurboHigh
#define HAIER_AC_YRW02_TURBO_LOW kHaierAcYrw02TurboLow
#define HAIER_AC_YRW02_AUTO kHaierAcYrw02Auto
#define HAIER_AC_YRW02_COOL kHaierAcYrw02Cool
#define HAIER_AC_YRW02_DRY kHaierAcYrw02Dry
#define HAIER_AC_YRW02_HEAT kHaierAcYrw02Heat
#define HAIER_AC_YRW02_FAN kHaierAcYrw02Fan
#define HAIER_AC_YRW02_BUTTON_TEMP_UP kHaierAcYrw02ButtonTempUp
#define HAIER_AC_YRW02_BUTTON_TEMP_DOWN kHaierAcYrw02ButtonTempDown
#define HAIER_AC_YRW02_BUTTON_SWING kHaierAcYrw02ButtonSwing
#define HAIER_AC_YRW02_BUTTON_FAN kHaierAcYrw02ButtonFan
#define HAIER_AC_YRW02_BUTTON_POWER kHaierAcYrw02ButtonPower
#define HAIER_AC_YRW02_BUTTON_MODE kHaierAcYrw02ButtonMode
#define HAIER_AC_YRW02_BUTTON_HEALTH kHaierAcYrw02ButtonHealth
#define HAIER_AC_YRW02_BUTTON_TURBO kHaierAcYrw02ButtonTurbo
#define HAIER_AC_YRW02_BUTTON_SLEEP kHaierAcYrw02ButtonSleep
class IRHaierAC {
public:
explicit IRHaierAC(uint16_t pin);
#if SEND_HAIER_AC
void send();
void send(const uint16_t repeat = kHaierAcDefaultRepeat);
#endif // SEND_HAIER_AC
void begin();
@@ -105,17 +222,17 @@ class IRHaierAC {
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
const uint16_t length = kHaierACStateLength);
#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];
uint8_t remote_state[kHaierACStateLength];
void stateReset();
void checksum();
static uint16_t getTime(const uint8_t ptr[]);
@@ -123,4 +240,58 @@ class IRHaierAC {
IRsend _irsend;
};
class IRHaierACYRW02 {
public:
explicit IRHaierACYRW02(uint16_t pin);
#if SEND_HAIER_AC_YRW02
void send(const uint16_t repeat = kHaierAcYrw02DefaultRepeat);
#endif // SEND_HAIER_AC_YRW02
void begin();
void setButton(const uint8_t button);
uint8_t getButton();
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 getPower();
void setPower(const bool state);
void on();
void off();
bool getSleep();
void setSleep(const bool state);
bool getHealth();
void setHealth(const bool state);
uint8_t getTurbo();
void setTurbo(const uint8_t speed);
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 = kHaierACYRW02StateLength);
#ifdef ARDUINO
String toString();
#else
std::string toString();
#endif
private:
uint8_t remote_state[kHaierACYRW02StateLength];
void stateReset();
void checksum();
IRsend _irsend;
};
#endif // IR_HAIER_H_
+266 -57
View File
@@ -5,12 +5,13 @@
// * Hitachi RAS-35THA6 remote
//
#include "ir_Hitachi.h"
#include <algorithm>
#ifndef ARDUINO
#include <string>
#endif
#include "IRremoteESP8266.h"
#include "IRrecv.h"
#include "IRremoteESP8266.h"
#include "IRsend.h"
#include "IRutils.h"
@@ -22,21 +23,21 @@
// 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.
const uint16_t kHitachiAcHdrMark = 3300;
const uint16_t kHitachiAcHdrSpace = 1700;
const uint16_t kHitachiAc1HdrMark = 3400;
const uint16_t kHitachiAc1HdrSpace = 3400;
const uint16_t kHitachiAcBitMark = 400;
const uint16_t kHitachiAcOneSpace = 1250;
const uint16_t kHitachiAcZeroSpace = 500;
const uint32_t kHitachiAcMinGap = 100000; // 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)
// nbytes: Nr. of bytes of data in the array. (>=kHitachiAcStateLength)
// repeat: Nr. of times the message is to be repeated. (Default = 0).
//
// Status: ALPHA / Untested.
@@ -45,13 +46,12 @@
// 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)
if (nbytes < kHitachiAcStateLength)
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);
sendGeneric(kHitachiAcHdrMark, kHitachiAcHdrSpace, kHitachiAcBitMark,
kHitachiAcOneSpace, kHitachiAcBitMark, kHitachiAcZeroSpace,
kHitachiAcBitMark, kHitachiAcMinGap, data, nbytes, 38, true,
repeat, 50);
}
#endif // (SEND_HITACHI_AC || SEND_HITACHI_AC2)
@@ -63,7 +63,7 @@ void IRsend::sendHitachiAC(unsigned char data[], uint16_t nbytes,
//
// Args:
// data: An array of bytes containing the IR command.
// nbytes: Nr. of bytes of data in the array. (>=HITACHI_AC1_STATE_LENGTH)
// nbytes: Nr. of bytes of data in the array. (>=kHitachiAc1StateLength)
// repeat: Nr. of times the message is to be repeated. (Default = 0).
//
// Status: BETA / Appears to work.
@@ -73,13 +73,12 @@ void IRsend::sendHitachiAC(unsigned char data[], uint16_t nbytes,
// 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)
if (nbytes < kHitachiAc1StateLength)
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);
sendGeneric(kHitachiAc1HdrMark, kHitachiAc1HdrSpace, kHitachiAcBitMark,
kHitachiAcOneSpace, kHitachiAcBitMark, kHitachiAcZeroSpace,
kHitachiAcBitMark, kHitachiAcMinGap, data, nbytes, 38, true,
repeat, 50);
}
#endif // SEND_HITACHI_AC1
@@ -91,7 +90,7 @@ void IRsend::sendHitachiAC1(unsigned char data[], uint16_t nbytes,
//
// Args:
// data: An array of bytes containing the IR command.
// nbytes: Nr. of bytes of data in the array. (>=HITACHI_AC2_STATE_LENGTH)
// nbytes: Nr. of bytes of data in the array. (>=kHitachiAc2StateLength)
// repeat: Nr. of times the message is to be repeated. (Default = 0).
//
// Status: BETA / Appears to work.
@@ -101,19 +100,232 @@ void IRsend::sendHitachiAC1(unsigned char data[], uint16_t nbytes,
// 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)
if (nbytes < kHitachiAc2StateLength)
return; // Not enough bytes to send a proper message.
sendHitachiAC(data, nbytes, repeat);
}
#endif // SEND_HITACHI_AC2
// Class for handling the remote control oh a Hitachi 28 byte A/C message.
// Inspired by:
// https://github.com/ToniA/arduino-heatpumpir/blob/master/HitachiHeatpumpIR.cpp
IRHitachiAc::IRHitachiAc(uint16_t pin) : _irsend(pin) { stateReset(); }
void IRHitachiAc::stateReset() {
remote_state[0] = 0x80;
remote_state[1] = 0x08;
remote_state[2] = 0x0C;
remote_state[3] = 0x02;
remote_state[4] = 0xFD;
remote_state[5] = 0x80;
remote_state[6] = 0x7F;
remote_state[7] = 0x88;
remote_state[8] = 0x48;
remote_state[9] = 0x10;
for (uint8_t i = 10; i < kHitachiAcStateLength; i++) remote_state[i] = 0x00;
remote_state[14] = 0x60;
remote_state[15] = 0x60;
remote_state[24] = 0x80;
setTemp(23);
}
void IRHitachiAc::begin() { _irsend.begin(); }
uint8_t IRHitachiAc::calcChecksum(const uint8_t state[],
const uint16_t length) {
int8_t sum = 62;
for (uint16_t i = 0; i < length - 1; i++) sum -= reverseBits(state[i], 8);
return reverseBits((uint8_t)sum, 8);
}
void IRHitachiAc::checksum(const uint16_t length) {
remote_state[length - 1] = calcChecksum(remote_state, length);
}
bool IRHitachiAc::validChecksum(const uint8_t state[], const uint16_t length) {
if (length < 2) return true; // Assume true for lengths that are too short.
return (state[length - 1] == calcChecksum(state, length));
}
uint8_t *IRHitachiAc::getRaw() {
checksum();
return remote_state;
}
void IRHitachiAc::setRaw(const uint8_t new_code[], const uint16_t length) {
for (uint8_t i = 0; i < length && i < kHitachiAcStateLength; i++)
remote_state[i] = new_code[i];
}
#if SEND_HITACHI_AC
void IRHitachiAc::send(const uint16_t repeat) {
checksum();
_irsend.sendHitachiAC(remote_state, kHitachiAcStateLength, repeat);
}
#endif // SEND_HITACHI_AC
bool IRHitachiAc::getPower() { return (remote_state[17] & 0x01); }
void IRHitachiAc::setPower(const bool on) {
if (on)
remote_state[17] |= 0x01;
else
remote_state[17] &= 0xFE;
}
void IRHitachiAc::on() { setPower(true); }
void IRHitachiAc::off() { setPower(false); }
uint8_t IRHitachiAc::getMode() { return reverseBits(remote_state[10], 8); }
void IRHitachiAc::setMode(const uint8_t mode) {
uint8_t newmode = mode;
switch (mode) {
case kHitachiAcFan:
// Fan mode sets a special temp.
setTemp(64);
break;
case kHitachiAcAuto:
case kHitachiAcHeat:
case kHitachiAcCool:
case kHitachiAcDry:
break;
default:
newmode = kHitachiAcAuto;
}
remote_state[10] = reverseBits(newmode, 8);
if (mode != kHitachiAcFan) setTemp(_previoustemp);
setFan(getFan()); // Reset the fan speed after the mode change.
}
uint8_t IRHitachiAc::getTemp() { return reverseBits(remote_state[11], 8) >> 1; }
void IRHitachiAc::setTemp(const uint8_t celsius) {
uint8_t temp;
if (celsius != 64) _previoustemp = celsius;
switch (celsius) {
case 64:
temp = celsius;
break;
default:
temp = std::min(celsius, kHitachiAcMaxTemp);
temp = std::max(temp, kHitachiAcMinTemp);
}
remote_state[11] = reverseBits(temp << 1, 8);
if (temp == kHitachiAcMinTemp)
remote_state[9] = 0x90;
else
remote_state[9] = 0x10;
}
uint8_t IRHitachiAc::getFan() { return reverseBits(remote_state[13], 8); }
void IRHitachiAc::setFan(const uint8_t speed) {
uint8_t fanmin = kHitachiAcFanAuto;
uint8_t fanmax = kHitachiAcFanHigh;
switch (getMode()) {
case kHitachiAcDry: // Only 2 x low speeds in Dry mode.
fanmin = kHitachiAcFanLow;
fanmax = kHitachiAcFanLow + 1;
break;
case kHitachiAcFan:
fanmin = kHitachiAcFanLow; // No Auto in Fan mode.
break;
}
uint8_t newspeed = std::max(speed, fanmin);
newspeed = std::min(newspeed, fanmax);
remote_state[13] = reverseBits(newspeed, 8);
}
bool IRHitachiAc::getSwingVertical() { return remote_state[14] & 0x80; }
void IRHitachiAc::setSwingVertical(const bool on) {
if (on)
remote_state[14] |= 0x80;
else
remote_state[14] &= 0x7F;
}
bool IRHitachiAc::getSwingHorizontal() { return remote_state[15] & 0x80; }
void IRHitachiAc::setSwingHorizontal(const bool on) {
if (on)
remote_state[15] |= 0x80;
else
remote_state[15] &= 0x7F;
}
// Convert the internal state into a human readable string.
#ifdef ARDUINO
String IRHitachiAc::toString() {
String result = "";
#else
std::string IRHitachiAc::toString() {
std::string result = "";
#endif // ARDUINO
result += "Power: ";
if (getPower())
result += "On";
else
result += "Off";
result += ", Mode: " + uint64ToString(getMode());
switch (getMode()) {
case kHitachiAcAuto:
result += " (AUTO)";
break;
case kHitachiAcCool:
result += " (COOL)";
break;
case kHitachiAcHeat:
result += " (HEAT)";
break;
case kHitachiAcDry:
result += " (DRY)";
break;
case kHitachiAcFan:
result += " (FAN)";
break;
default:
result += " (UNKNOWN)";
}
result += ", Temp: " + uint64ToString(getTemp()) + "C";
result += ", Fan: " + uint64ToString(getFan());
switch (getFan()) {
case kHitachiAcFanAuto:
result += " (AUTO)";
break;
case kHitachiAcFanLow:
result += " (LOW)";
break;
case kHitachiAcFanHigh:
result += " (HIGH)";
break;
default:
result += " (UNKNOWN)";
break;
}
result += ", Swing (Vertical): ";
if (getSwingVertical())
result += "On";
else
result += "Off";
result += ", Swing (Horizontal): ";
if (getSwingHorizontal())
result += "On";
else
result += "Off";
return result;
}
#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
// Typically kHitachiAcBits, kHitachiAc1Bits, kHitachiAc2Bits
// strict: Flag indicating if we should perform strict matching.
// Returns:
// boolean: True if it can decode it, false if it can't.
@@ -129,80 +341,77 @@ void IRsend::sendHitachiAC2(unsigned char data[], uint16_t nbytes,
bool IRrecv::decodeHitachiAC(decode_results *results, uint16_t nbits,
bool strict) {
const uint8_t kTolerance = 30;
if (results->rawlen < 2 * nbits + HEADER + FOOTER - 1)
if (results->rawlen < 2 * nbits + kHeader + kFooter - 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:
case kHitachiAcBits:
case kHitachiAc1Bits:
case kHitachiAc2Bits:
break; // Okay to continue.
default:
return false; // Not strictly a Hitachi message.
}
}
uint16_t offset = OFFSET_START;
uint16_t offset = kStartOffset;
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))
if (nbits == kHitachiAc1Bits) {
if (!matchMark(results->rawbuf[offset++], kHitachiAc1HdrMark, kTolerance))
return false;
if (!matchSpace(results->rawbuf[offset++], HITACHI_AC1_HDR_SPACE,
kTolerance))
if (!matchSpace(results->rawbuf[offset++], kHitachiAc1HdrSpace, kTolerance))
return false;
} else { // Everything else.
if (!matchMark(results->rawbuf[offset++], HITACHI_AC_HDR_MARK, kTolerance))
if (!matchMark(results->rawbuf[offset++], kHitachiAcHdrMark, kTolerance))
return false;
if (!matchSpace(results->rawbuf[offset++], HITACHI_AC_HDR_SPACE,
kTolerance))
if (!matchSpace(results->rawbuf[offset++], kHitachiAcHdrSpace, 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;
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, kHitachiAcBitMark,
kHitachiAcOneSpace, kHitachiAcBitMark,
kHitachiAcZeroSpace, 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))
if (!matchMark(results->rawbuf[offset++], kHitachiAcBitMark, kTolerance))
return false;
if (offset <= results->rawlen &&
!matchAtLeast(results->rawbuf[offset], HITACHI_AC_MIN_GAP, kTolerance))
!matchAtLeast(results->rawbuf[offset], kHitachiAcMinGap, 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:
case kHitachiAcStateLength:
case kHitachiAc1StateLength:
case kHitachiAc2StateLength:
break; // Continue
default:
return false;
}
if (dataBitsSoFar / 8 == kHitachiAcStateLength &&
!IRHitachiAc::validChecksum(results->state, kHitachiAcStateLength))
return false;
}
// Success
switch (dataBitsSoFar) {
case HITACHI_AC1_BITS:
case kHitachiAc1Bits:
results->decode_type = HITACHI_AC1;
break;
case HITACHI_AC2_BITS:
case kHitachiAc2Bits:
results->decode_type = HITACHI_AC2;
break;
case HITACHI_AC_BITS:
case kHitachiAcBits:
default:
results->decode_type = HITACHI_AC;
}
+76
View File
@@ -0,0 +1,76 @@
// Hitachi A/C
//
// Copyright 2018 David Conran
#ifndef IR_HITACHI_H_
#define IR_HITACHI_H_
#define __STDC_LIMIT_MACROS
#include <stdint.h>
#ifndef UNIT_TEST
#include <Arduino.h>
#else
#include <string>
#endif
#include "IRremoteESP8266.h"
#include "IRsend.h"
// Constants
const uint8_t kHitachiAcAuto = 2;
const uint8_t kHitachiAcHeat = 3;
const uint8_t kHitachiAcCool = 4;
const uint8_t kHitachiAcDry = 5;
const uint8_t kHitachiAcFan = 0xC;
const uint8_t kHitachiAcFanAuto = 1;
const uint8_t kHitachiAcFanLow = 2;
const uint8_t kHitachiAcFanHigh = 5;
const uint8_t kHitachiAcMinTemp = 16; // 16C
const uint8_t kHitachiAcMaxTemp = 32; // 32C
const uint8_t kHitachiAcAutoTemp = 23; // 23C
// Classes
class IRHitachiAc {
public:
explicit IRHitachiAc(uint16_t pin);
void stateReset();
#if SEND_HITACHI_AC
void send(const uint16_t repeat = kHitachiAcDefaultRepeat);
#endif // SEND_HITACHI_AC
void begin();
void on();
void off();
void setPower(const bool on);
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 mode);
uint8_t getMode();
void setSwingVertical(const bool on);
bool getSwingVertical();
void setSwingHorizontal(const bool on);
bool getSwingHorizontal();
uint8_t* getRaw();
void setRaw(const uint8_t new_code[],
const uint16_t length = kHitachiAcStateLength);
static bool validChecksum(const uint8_t state[],
const uint16_t length = kHitachiAcStateLength);
static uint8_t calcChecksum(const uint8_t state[],
const uint16_t length = kHitachiAcStateLength);
#ifdef ARDUINO
String toString();
#else
std::string toString();
#endif
private:
// The state of the IR remote in IR code form.
uint8_t remote_state[kHitachiAcStateLength];
void checksum(const uint16_t length = kHitachiAcStateLength);
IRsend _irsend;
uint8_t _previoustemp;
};
#endif // IR_HITACHI_H_
+44 -48
View File
@@ -19,31 +19,31 @@
// Constants
// Ref:
// http://www.sbprojects.com/knowledge/ir/jvc.php
#define JVC_TICK 75U
#define JVC_HDR_MARK_TICKS 112U
#define JVC_HDR_MARK (JVC_HDR_MARK_TICKS * JVC_TICK)
#define JVC_HDR_SPACE_TICKS 56U
#define JVC_HDR_SPACE (JVC_HDR_SPACE_TICKS * JVC_TICK)
#define JVC_BIT_MARK_TICKS 7U
#define JVC_BIT_MARK (JVC_BIT_MARK_TICKS * JVC_TICK)
#define JVC_ONE_SPACE_TICKS 23U
#define JVC_ONE_SPACE (JVC_ONE_SPACE_TICKS * JVC_TICK)
#define JVC_ZERO_SPACE_TICKS 7U
#define JVC_ZERO_SPACE (JVC_ZERO_SPACE_TICKS * JVC_TICK)
#define JVC_RPT_LENGTH_TICKS 800U
#define JVC_RPT_LENGTH (JVC_RPT_LENGTH_TICKS * JVC_TICK)
#define JVC_MIN_GAP_TICKS (JVC_RPT_LENGTH_TICKS - \
(JVC_HDR_MARK_TICKS + JVC_HDR_SPACE_TICKS + \
JVC_BITS * (JVC_BIT_MARK_TICKS + JVC_ONE_SPACE_TICKS) + \
JVC_BIT_MARK_TICKS))
#define JVC_MIN_GAP (JVC_MIN_GAP_TICKS * JVC_TICK)
const uint16_t kJvcTick = 75;
const uint16_t kJvcHdrMarkTicks = 112;
const uint16_t kJvcHdrMark = kJvcHdrMarkTicks * kJvcTick;
const uint16_t kJvcHdrSpaceTicks = 56;
const uint16_t kJvcHdrSpace = kJvcHdrSpaceTicks * kJvcTick;
const uint16_t kJvcBitMarkTicks = 7;
const uint16_t kJvcBitMark = kJvcBitMarkTicks * kJvcTick;
const uint16_t kJvcOneSpaceTicks = 23;
const uint16_t kJvcOneSpace = kJvcOneSpaceTicks * kJvcTick;
const uint16_t kJvcZeroSpaceTicks = 7;
const uint16_t kJvcZeroSpace = kJvcZeroSpaceTicks * kJvcTick;
const uint16_t kJvcRptLengthTicks = 800;
const uint16_t kJvcRptLength = kJvcRptLengthTicks * kJvcTick;
const uint16_t kJvcMinGapTicks =
kJvcRptLengthTicks -
(kJvcHdrMarkTicks + kJvcHdrSpaceTicks +
kJvcBits * (kJvcBitMarkTicks + kJvcOneSpaceTicks) + kJvcBitMarkTicks);
const uint16_t kJvcMinGap = kJvcMinGapTicks * kJvcTick;
#if SEND_JVC
// Send a JVC message.
//
// Args:
// data: The contents of the command you want to send.
// nbits: The bit size of the command being sent. (JVC_BITS)
// nbits: The bit size of the command being sent. (kJvcBits)
// repeat: The number of times you want the command to be repeated.
//
// Status: STABLE.
@@ -57,23 +57,21 @@ void IRsend::sendJVC(uint64_t data, uint16_t nbits, uint16_t repeat) {
IRtimer usecs = IRtimer();
// Header
// Only sent for the first message.
mark(JVC_HDR_MARK);
space(JVC_HDR_SPACE);
mark(kJvcHdrMark);
space(kJvcHdrSpace);
// We always send the data & footer at least once, hence '<= repeat'.
for (uint16_t i = 0; i <= repeat; i++) {
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.
kJvcBitMark, kJvcOneSpace, kJvcBitMark, kJvcZeroSpace,
kJvcBitMark, kJvcMinGap, data, nbits, 38, true,
0, // Repeats are handles elsewhere.
33);
// Wait till the end of the repeat time window before we send another code.
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);
// e.g. when elapsed > kJvcRptLength.
if (elapsed < kJvcRptLength) space(kJvcRptLength - elapsed);
usecs.reset();
}
}
@@ -100,7 +98,7 @@ uint16_t IRsend::encodeJVC(uint8_t address, uint8_t command) {
//
// Args:
// results: Ptr to the data to decode and where to store the decode result.
// nbits: Nr. of bits of data to expect. Typically JVC_BITS.
// nbits: Nr. of bits of data to expect. Typically kJvcBits.
// strict: Flag indicating if we should perform strict matching.
// Returns:
// boolean: True if it can decode it, false if it can't.
@@ -111,50 +109,48 @@ uint16_t IRsend::encodeJVC(uint8_t address, uint8_t command) {
// JVC repeat codes don't have a header.
// Ref:
// http://www.sbprojects.com/knowledge/ir/jvc.php
bool IRrecv::decodeJVC(decode_results *results, uint16_t nbits, bool strict) {
if (strict && nbits != JVC_BITS)
bool IRrecv::decodeJVC(decode_results *results, uint16_t nbits, bool strict) {
if (strict && nbits != kJvcBits)
return false; // Must be called with the correct nr. of bits.
if (results->rawlen < 2 * nbits + FOOTER - 1)
if (results->rawlen < 2 * nbits + kFooter - 1)
return false; // Can't possibly be a valid JVC message.
uint64_t data = 0;
uint16_t offset = OFFSET_START;
uint16_t offset = kStartOffset;
bool isRepeat = true;
uint32_t m_tick;
uint32_t s_tick;
// Header
// (Optional as repeat codes don't have the header)
if (matchMark(results->rawbuf[offset], JVC_HDR_MARK)) {
if (matchMark(results->rawbuf[offset], kJvcHdrMark)) {
isRepeat = false;
m_tick = results->rawbuf[offset++] * RAWTICK / JVC_HDR_MARK_TICKS;
m_tick = results->rawbuf[offset++] * kRawTick / kJvcHdrMarkTicks;
if (results->rawlen < 2 * nbits + 4)
return false; // Can't possibly be a valid JVC message with a header.
if (!matchSpace(results->rawbuf[offset], JVC_HDR_SPACE))
return false;
s_tick = results->rawbuf[offset++] * RAWTICK / JVC_HDR_SPACE_TICKS;
if (!matchSpace(results->rawbuf[offset], kJvcHdrSpace)) return false;
s_tick = results->rawbuf[offset++] * kRawTick / kJvcHdrSpaceTicks;
} else {
// We can't easily auto-calibrate as there is no header, so assume
// the default tick time.
m_tick = JVC_TICK;
s_tick = JVC_TICK;
m_tick = kJvcTick;
s_tick = kJvcTick;
}
// Data
match_result_t data_result = matchData(&(results->rawbuf[offset]), nbits,
JVC_BIT_MARK_TICKS * m_tick,
JVC_ONE_SPACE_TICKS * s_tick,
JVC_BIT_MARK_TICKS * m_tick,
JVC_ZERO_SPACE_TICKS * s_tick);
match_result_t data_result =
matchData(&(results->rawbuf[offset]), nbits, kJvcBitMarkTicks * m_tick,
kJvcOneSpaceTicks * s_tick, kJvcBitMarkTicks * m_tick,
kJvcZeroSpaceTicks * s_tick);
if (data_result.success == false) return false;
data = data_result.data;
offset += data_result.used;
// Footer
if (!matchMark(results->rawbuf[offset++], JVC_BIT_MARK_TICKS * m_tick))
if (!matchMark(results->rawbuf[offset++], kJvcBitMarkTicks * m_tick))
return false;
if (offset < results->rawlen &&
!matchAtLeast(results->rawbuf[offset], JVC_MIN_GAP_TICKS * s_tick))
!matchAtLeast(results->rawbuf[offset], kJvcMinGapTicks * s_tick))
return false;
// Success
@@ -162,7 +158,7 @@ bool IRrecv::decodeJVC(decode_results *results, uint16_t nbits, bool strict) {
results->bits = nbits;
results->value = data;
// command & address are transmitted LSB first, so we need to reverse them.
results->address = reverseBits(data >> 8, 8); // The first 8 bits sent.
results->address = reverseBits(data >> 8, 8); // The first 8 bits sent.
results->command = reverseBits(data & 0xFF, 8); // The last 8 bits sent.
results->repeat = isRepeat;
return true;
+162 -174
View File
@@ -30,151 +30,142 @@
// KK KK EEEEEEE LLLLLLL VVV IIIII NN NN AA AA TTT OOOO0 RR RR
// Constants
#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
#define KELVINATOR_FAN_OFFSET 4U
#define KELVINATOR_BASIC_FAN_MASK uint8_t(0xFFU ^ (3U << KELVINATOR_FAN_OFFSET))
#define KELVINATOR_FAN_MASK uint8_t(0xFFU ^ (7U << KELVINATOR_FAN_OFFSET))
#define KELVINATOR_CHECKSUM_START 10U
#define KELVINATOR_VENT_SWING_OFFSET 6U
#define KELVINATOR_VENT_SWING uint8_t(1U << KELVINATOR_VENT_SWING_OFFSET)
#define KELVINATOR_VENT_SWING_V uint8_t(1U)
#define KELVINATOR_VENT_SWING_H uint8_t(1U << 4)
#define KELVINATOR_SLEEP_1_AND_3 uint8_t(1U << 7)
#define KELVINATOR_QUIET_OFFSET 7U
#define KELVINATOR_QUIET uint8_t(1U << KELVINATOR_QUIET_OFFSET)
#define KELVINATOR_ION_FILTER_OFFSET 6U
#define KELVINATOR_ION_FILTER uint8_t(1U << KELVINATOR_ION_FILTER_OFFSET)
#define KELVINATOR_LIGHT_OFFSET 5U
#define KELVINATOR_LIGHT uint8_t(1U << KELVINATOR_LIGHT_OFFSET)
#define KELVINATOR_XFAN_OFFSET 7U
#define KELVINATOR_XFAN uint8_t(1U << KELVINATOR_XFAN_OFFSET)
#define KELVINATOR_TURBO_OFFSET 4U
#define KELVINATOR_TURBO uint8_t(1U << KELVINATOR_TURBO_OFFSET)
const uint16_t kKelvinatorTick = 85;
const uint16_t kKelvinatorHdrMarkTicks = 106;
const uint16_t kKelvinatorHdrMark = kKelvinatorHdrMarkTicks * kKelvinatorTick;
const uint16_t kKelvinatorHdrSpaceTicks = 53;
const uint16_t kKelvinatorHdrSpace = kKelvinatorHdrSpaceTicks * kKelvinatorTick;
const uint16_t kKelvinatorBitMarkTicks = 8;
const uint16_t kKelvinatorBitMark = kKelvinatorBitMarkTicks * kKelvinatorTick;
const uint16_t kKelvinatorOneSpaceTicks = 18;
const uint16_t kKelvinatorOneSpace = kKelvinatorOneSpaceTicks * kKelvinatorTick;
const uint16_t kKelvinatorZeroSpaceTicks = 6;
const uint16_t kKelvinatorZeroSpace =
kKelvinatorZeroSpaceTicks * kKelvinatorTick;
const uint16_t kKelvinatorGapSpaceTicks = 235;
const uint16_t kKelvinatorGapSpace = kKelvinatorGapSpaceTicks * kKelvinatorTick;
const uint8_t kKelvinatorCmdFooter = 2;
const uint8_t kKelvinatorCmdFooterBits = 3;
const uint8_t kKelvinatorPower = 8;
const uint8_t kKelvinatorModeMask = 0xF8;
const uint8_t kKelvinatorFanOffset = 4;
const uint8_t kKelvinatorBasicFanMask = 0xFF ^ (3U << kKelvinatorFanOffset);
const uint8_t kKelvinatorFanMask = 0xFF ^ (7U << kKelvinatorFanOffset);
const uint8_t kKelvinatorChecksumStart = 10;
const uint8_t kKelvinatorVentSwingOffset = 6;
const uint8_t kKelvinatorVentSwing = 1 << kKelvinatorVentSwingOffset;
const uint8_t kKelvinatorVentSwingV = 1;
const uint8_t kKelvinatorVentSwingH = 1 << 4;
const uint8_t kKelvinatorSleep1And3 = 1 << 7;
const uint8_t kKelvinatorQuietOffset = 7;
const uint8_t kKelvinatorQuiet = 1 << kKelvinatorQuietOffset;
const uint8_t kKelvinatorIonFilterOffset = 6;
const uint8_t kKelvinatorIonFilter = 1 << kKelvinatorIonFilterOffset;
const uint8_t kKelvinatorLightOffset = 5;
const uint8_t kKelvinatorLight = 1 << kKelvinatorLightOffset;
const uint8_t kKelvinatorXfanOffset = 7;
const uint8_t kKelvinatorXfan = 1 << kKelvinatorXfanOffset;
const uint8_t kKelvinatorTurboOffset = 4;
const uint8_t kKelvinatorTurbo = 1 << kKelvinatorTurboOffset;
#if SEND_KELVINATOR
// Send a Kelvinator A/C message.
//
// Args:
// data: An array of bytes containing the IR command.
// nbytes: Nr. of bytes of data in the array. (>=KELVINATOR_STATE_LENGTH)
// nbytes: Nr. of bytes of data in the array. (>=kKelvinatorStateLength)
// repeat: Nr. of times the message is to be repeated. (Default = 0).
//
// Status: STABLE / Known working.
//
void IRsend::sendKelvinator(unsigned char data[], uint16_t nbytes,
uint16_t repeat) {
if (nbytes < KELVINATOR_STATE_LENGTH)
if (nbytes < kKelvinatorStateLength)
return; // Not enough bytes to send a proper message.
for (uint16_t r = 0; r <= repeat; r++) {
// Command Block #1 (4 bytes)
sendGeneric(KELVINATOR_HDR_MARK, KELVINATOR_HDR_SPACE,
KELVINATOR_BIT_MARK, KELVINATOR_ONE_SPACE,
KELVINATOR_BIT_MARK, KELVINATOR_ZERO_SPACE,
sendGeneric(kKelvinatorHdrMark, kKelvinatorHdrSpace, kKelvinatorBitMark,
kKelvinatorOneSpace, kKelvinatorBitMark, kKelvinatorZeroSpace,
0, 0, // No Footer yet.
data, 4, 38, false, 0, 50);
// Send Footer for the command block (3 bits (B010))
// 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);
kKelvinatorBitMark, kKelvinatorOneSpace, kKelvinatorBitMark,
kKelvinatorZeroSpace, kKelvinatorBitMark, kKelvinatorGapSpace,
kKelvinatorCmdFooter, kKelvinatorCmdFooterBits, 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);
kKelvinatorBitMark, kKelvinatorOneSpace, kKelvinatorBitMark,
kKelvinatorZeroSpace, kKelvinatorBitMark,
kKelvinatorGapSpace * 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,
sendGeneric(kKelvinatorHdrMark, kKelvinatorHdrSpace, kKelvinatorBitMark,
kKelvinatorOneSpace, kKelvinatorBitMark, kKelvinatorZeroSpace,
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);
kKelvinatorBitMark, kKelvinatorOneSpace, kKelvinatorBitMark,
kKelvinatorZeroSpace, kKelvinatorBitMark, kKelvinatorGapSpace,
kKelvinatorCmdFooter, kKelvinatorCmdFooterBits, 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);
kKelvinatorBitMark, kKelvinatorOneSpace, kKelvinatorBitMark,
kKelvinatorZeroSpace, kKelvinatorBitMark,
kKelvinatorGapSpace * 2, data + 12, 4, 38, false, 0, 50);
}
}
#endif // SEND_KELVINATOR
IRKelvinatorAC::IRKelvinatorAC(uint16_t pin) : _irsend(pin) {
stateReset();
}
IRKelvinatorAC::IRKelvinatorAC(uint16_t pin) : _irsend(pin) { stateReset(); }
void IRKelvinatorAC::stateReset() {
for (uint8_t i = 0; i < KELVINATOR_STATE_LENGTH; i++)
remote_state[i] = 0x0;
for (uint8_t i = 0; i < kKelvinatorStateLength; i++) remote_state[i] = 0x0;
remote_state[3] = 0x50;
remote_state[11] = 0x70;
}
void IRKelvinatorAC::begin() {
_irsend.begin();
}
void IRKelvinatorAC::begin() { _irsend.begin(); }
void IRKelvinatorAC::fixup() {
// X-Fan mode is only valid in COOL or DRY modes.
if (getMode() != KELVINATOR_COOL && getMode() != KELVINATOR_DRY)
if (getMode() != kKelvinatorCool && getMode() != kKelvinatorDry)
setXFan(false);
checksum(); // Calculate the checksums
}
#if SEND_KELVINATOR
void IRKelvinatorAC::send() {
fixup(); // Ensure correct settings before sending.
_irsend.sendKelvinator(remote_state);
void IRKelvinatorAC::send(const uint16_t repeat) {
fixup(); // Ensure correct settings before sending.
_irsend.sendKelvinator(remote_state, kKelvinatorStateLength, repeat);
}
#endif // SEND_KELVINATOR
uint8_t* IRKelvinatorAC::getRaw() {
fixup(); // Ensure correct settings before sending.
uint8_t *IRKelvinatorAC::getRaw() {
fixup(); // Ensure correct settings before sending.
return remote_state;
}
void IRKelvinatorAC::setRaw(uint8_t new_code[]) {
for (uint8_t i = 0; i < KELVINATOR_STATE_LENGTH; i++) {
for (uint8_t i = 0; i < kKelvinatorStateLength; 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;
uint8_t sum = kKelvinatorChecksumStart;
// 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 < length - 1; i++, block++)
sum += (*block >> 4);
// then sum the upper half of the next 3 bytes.
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;
}
@@ -205,12 +196,12 @@ bool IRKelvinatorAC::validChecksum(const uint8_t state[],
}
void IRKelvinatorAC::on() {
remote_state[0] |= KELVINATOR_POWER;
remote_state[0] |= kKelvinatorPower;
remote_state[8] = remote_state[0]; // Duplicate to the 2nd command chunk.
}
void IRKelvinatorAC::off() {
remote_state[0] &= ~KELVINATOR_POWER;
remote_state[0] &= ~kKelvinatorPower;
remote_state[8] = remote_state[0]; // Duplicate to the 2nd command chunk.
}
@@ -222,140 +213,138 @@ void IRKelvinatorAC::setPower(bool state) {
}
bool IRKelvinatorAC::getPower() {
return ((remote_state[0] & KELVINATOR_POWER) != 0);
return ((remote_state[0] & kKelvinatorPower) != 0);
}
// Set the temp. in deg C
void IRKelvinatorAC::setTemp(uint8_t temp) {
temp = std::max((uint8_t) KELVINATOR_MIN_TEMP, temp);
temp = std::min((uint8_t) KELVINATOR_MAX_TEMP, temp);
remote_state[1] = (remote_state[1] & 0xF0U) | (temp - KELVINATOR_MIN_TEMP);
temp = std::max(kKelvinatorMinTemp, temp);
temp = std::min(kKelvinatorMaxTemp, temp);
remote_state[1] = (remote_state[1] & 0xF0U) | (temp - kKelvinatorMinTemp);
remote_state[9] = remote_state[1]; // Duplicate to the 2nd command chunk.
}
// Return the set temp. in deg C
uint8_t IRKelvinatorAC::getTemp() {
return ((remote_state[1] & 0xFU) + KELVINATOR_MIN_TEMP);
return ((remote_state[1] & 0xFU) + kKelvinatorMinTemp);
}
// Set the speed of the fan, 0-5, 0 is auto, 1-5 is the speed
void IRKelvinatorAC::setFan(uint8_t fan) {
fan = std::min((uint8_t) KELVINATOR_FAN_MAX, fan); // Bounds check
fan = std::min(kKelvinatorFanMax, fan); // Bounds check
// Only change things if we need to.
if (fan != getFan()) {
// Set the basic fan values.
uint8_t fan_basic = std::min((uint8_t) KELVINATOR_BASIC_FAN_MAX, fan);
remote_state[0] = (remote_state[0] & KELVINATOR_BASIC_FAN_MASK) |
(fan_basic << KELVINATOR_FAN_OFFSET);
uint8_t fan_basic = std::min(kKelvinatorBasicFanMax, fan);
remote_state[0] = (remote_state[0] & kKelvinatorBasicFanMask) |
(fan_basic << kKelvinatorFanOffset);
remote_state[8] = remote_state[0]; // Duplicate to the 2nd command chunk.
// Set the advanced(?) fan value.
remote_state[14] = (remote_state[14] & KELVINATOR_FAN_MASK) |
(fan << KELVINATOR_FAN_OFFSET);
remote_state[14] =
(remote_state[14] & kKelvinatorFanMask) | (fan << kKelvinatorFanOffset);
setTurbo(false); // Turbo mode is turned off if we change the fan settings.
}
}
uint8_t IRKelvinatorAC::getFan() {
return ((remote_state[14] & ~KELVINATOR_FAN_MASK) >> KELVINATOR_FAN_OFFSET);
return ((remote_state[14] & ~kKelvinatorFanMask) >> kKelvinatorFanOffset);
}
uint8_t IRKelvinatorAC::getMode() {
return (remote_state[0] & ~KELVINATOR_MODE_MASK);
return (remote_state[0] & ~kKelvinatorModeMask);
}
void IRKelvinatorAC::setMode(uint8_t mode) {
// If we get an unexpected mode, default to AUTO.
if (mode > KELVINATOR_HEAT) mode = KELVINATOR_AUTO;
remote_state[0] = (remote_state[0] & KELVINATOR_MODE_MASK) | mode;
if (mode > kKelvinatorHeat) mode = kKelvinatorAuto;
remote_state[0] = (remote_state[0] & kKelvinatorModeMask) | mode;
remote_state[8] = remote_state[0]; // Duplicate to the 2nd command chunk.
if (mode == KELVINATOR_AUTO || KELVINATOR_DRY)
if (mode == kKelvinatorAuto || kKelvinatorDry)
// When the remote is set to Auto or Dry, it defaults to 25C and doesn't
// show it.
setTemp(KELVINATOR_AUTO_TEMP);
setTemp(kKelvinatorAutoTemp);
}
void IRKelvinatorAC::setSwingVertical(bool state) {
if (state) {
remote_state[0] |= KELVINATOR_VENT_SWING;
remote_state[4] |= KELVINATOR_VENT_SWING_V;
remote_state[0] |= kKelvinatorVentSwing;
remote_state[4] |= kKelvinatorVentSwingV;
} else {
remote_state[4] &= ~KELVINATOR_VENT_SWING_V;
if (!getSwingHorizontal())
remote_state[0] &= ~KELVINATOR_VENT_SWING;
remote_state[4] &= ~kKelvinatorVentSwingV;
if (!getSwingHorizontal()) remote_state[0] &= ~kKelvinatorVentSwing;
}
remote_state[8] = remote_state[0]; // Duplicate to the 2nd command chunk.
}
bool IRKelvinatorAC::getSwingVertical() {
return ((remote_state[4] & KELVINATOR_VENT_SWING_V) != 0);
return ((remote_state[4] & kKelvinatorVentSwingV) != 0);
}
void IRKelvinatorAC::setSwingHorizontal(bool state) {
if (state) {
remote_state[0] |= KELVINATOR_VENT_SWING;
remote_state[4] |= KELVINATOR_VENT_SWING_H;
remote_state[0] |= kKelvinatorVentSwing;
remote_state[4] |= kKelvinatorVentSwingH;
} else {
remote_state[4] &= ~KELVINATOR_VENT_SWING_H;
if (!getSwingVertical())
remote_state[0] &= ~KELVINATOR_VENT_SWING;
remote_state[4] &= ~kKelvinatorVentSwingH;
if (!getSwingVertical()) remote_state[0] &= ~kKelvinatorVentSwing;
}
remote_state[8] = remote_state[0]; // Duplicate to the 2nd command chunk.
}
bool IRKelvinatorAC::getSwingHorizontal() {
return ((remote_state[4] & KELVINATOR_VENT_SWING_H) != 0);
return ((remote_state[4] & kKelvinatorVentSwingH) != 0);
}
void IRKelvinatorAC::setQuiet(bool state) {
remote_state[12] &= ~KELVINATOR_QUIET;
remote_state[12] |= (state << KELVINATOR_QUIET_OFFSET);
remote_state[12] &= ~kKelvinatorQuiet;
remote_state[12] |= (state << kKelvinatorQuietOffset);
}
bool IRKelvinatorAC::getQuiet() {
return ((remote_state[12] & KELVINATOR_QUIET) != 0);
return ((remote_state[12] & kKelvinatorQuiet) != 0);
}
void IRKelvinatorAC::setIonFilter(bool state) {
remote_state[2] &= ~KELVINATOR_ION_FILTER;
remote_state[2] |= (state << KELVINATOR_ION_FILTER_OFFSET);
remote_state[2] &= ~kKelvinatorIonFilter;
remote_state[2] |= (state << kKelvinatorIonFilterOffset);
remote_state[10] = remote_state[2]; // Duplicate to the 2nd command chunk.
}
bool IRKelvinatorAC::getIonFilter() {
return ((remote_state[2] & KELVINATOR_ION_FILTER) != 0);
return ((remote_state[2] & kKelvinatorIonFilter) != 0);
}
void IRKelvinatorAC::setLight(bool state) {
remote_state[2] &= ~KELVINATOR_LIGHT;
remote_state[2] |= (state << KELVINATOR_LIGHT_OFFSET);
remote_state[2] &= ~kKelvinatorLight;
remote_state[2] |= (state << kKelvinatorLightOffset);
remote_state[10] = remote_state[2]; // Duplicate to the 2nd command chunk.
}
bool IRKelvinatorAC::getLight() {
return ((remote_state[2] & KELVINATOR_LIGHT) != 0);
return ((remote_state[2] & kKelvinatorLight) != 0);
}
// Note: XFan mode is only valid in Cool or Dry mode.
void IRKelvinatorAC::setXFan(bool state) {
remote_state[2] &= ~KELVINATOR_XFAN;
remote_state[2] |= (state << KELVINATOR_XFAN_OFFSET);
remote_state[2] &= ~kKelvinatorXfan;
remote_state[2] |= (state << kKelvinatorXfanOffset);
remote_state[10] = remote_state[2]; // Duplicate to the 2nd command chunk.
}
bool IRKelvinatorAC::getXFan() {
return ((remote_state[2] & KELVINATOR_XFAN) != 0);
return ((remote_state[2] & kKelvinatorXfan) != 0);
}
// Note: Turbo mode is turned off if the fan speed is changed.
void IRKelvinatorAC::setTurbo(bool state) {
remote_state[2] &= ~KELVINATOR_TURBO;
remote_state[2] |= (state << KELVINATOR_TURBO_OFFSET);
remote_state[2] &= ~kKelvinatorTurbo;
remote_state[2] |= (state << kKelvinatorTurboOffset);
remote_state[10] = remote_state[2]; // Duplicate to the 2nd command chunk.
}
bool IRKelvinatorAC::getTurbo() {
return ((remote_state[2] & KELVINATOR_TURBO) != 0);
return ((remote_state[2] & kKelvinatorTurbo) != 0);
}
// Convert the internal state into a human readable string.
@@ -373,19 +362,19 @@ std::string IRKelvinatorAC::toString() {
result += "Off";
result += ", Mode: " + uint64ToString(getMode());
switch (getMode()) {
case KELVINATOR_AUTO:
case kKelvinatorAuto:
result += " (AUTO)";
break;
case KELVINATOR_COOL:
case kKelvinatorCool:
result += " (COOL)";
break;
case KELVINATOR_HEAT:
case kKelvinatorHeat:
result += " (HEAT)";
break;
case KELVINATOR_DRY:
case kKelvinatorDry:
result += " (DRY)";
break;
case KELVINATOR_FAN:
case kKelvinatorFan:
result += " (FAN)";
break;
default:
@@ -394,10 +383,10 @@ std::string IRKelvinatorAC::toString() {
result += ", Temp: " + uint64ToString(getTemp()) + "C";
result += ", Fan: " + uint64ToString(getFan());
switch (getFan()) {
case KELVINATOR_FAN_AUTO:
case kKelvinatorFanAuto:
result += " (AUTO)";
break;
case KELVINATOR_FAN_MAX:
case kKelvinatorFanMax:
result += " (MAX)";
break;
}
@@ -444,7 +433,7 @@ std::string IRKelvinatorAC::toString() {
//
// 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.
// nbits: The number of data bits to expect. Typically kKelvinatorBits.
// strict: Flag indicating if we should perform strict matching.
// Returns:
// boolean: True if it can decode it, false if it can't.
@@ -452,14 +441,14 @@ std::string IRKelvinatorAC::toString() {
// 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)
if (results->rawlen <
2 * (nbits + kKelvinatorCmdFooterBits) + (kHeader + kFooter + 1) * 2 - 1)
return false; // Can't possibly be a valid Kelvinator message.
if (strict && nbits != KELVINATOR_BITS)
if (strict && nbits != kKelvinatorBits)
return false; // Not strictly a Kelvinator message.
uint32_t data;
uint16_t offset = OFFSET_START;
uint16_t offset = kStartOffset;
// There are two messages back-to-back in a full Kelvinator IR message
// sequence.
@@ -468,77 +457,76 @@ bool IRrecv::decodeKelvinator(decode_results *results, uint16_t nbits,
match_result_t data_result;
// Header
if (!matchMark(results->rawbuf[offset], KELVINATOR_HDR_MARK)) return false;
if (!matchMark(results->rawbuf[offset], kKelvinatorHdrMark)) 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;
uint32_t mark_tick =
results->rawbuf[offset++] * kRawTick / kKelvinatorHdrMarkTicks;
if (!matchSpace(results->rawbuf[offset], kKelvinatorHdrSpace)) 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;
uint32_t space_tick =
results->rawbuf[offset++] * kRawTick / kKelvinatorHdrSpaceTicks;
// 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);
data_result = matchData(
&(results->rawbuf[offset]), 32, kKelvinatorBitMarkTicks * mark_tick,
kKelvinatorOneSpaceTicks * space_tick,
kKelvinatorBitMarkTicks * mark_tick,
kKelvinatorZeroSpaceTicks * space_tick, kTolerance, kMarkExcess, false);
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);
for (uint16_t i = 0; i < 4; i++, data >>= 8)
results->state[state_pos + i] = data & 0xFF;
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);
data_result = matchData(
&(results->rawbuf[offset]), kKelvinatorCmdFooterBits,
kKelvinatorBitMarkTicks * mark_tick,
kKelvinatorOneSpaceTicks * space_tick,
kKelvinatorBitMarkTicks * mark_tick,
kKelvinatorZeroSpaceTicks * space_tick, kTolerance, kMarkExcess, false);
if (data_result.success == false) return false;
if (data_result.data != KELVINATOR_CMD_FOOTER) return false;
if (data_result.data != kKelvinatorCmdFooter) return false;
offset += data_result.used;
// Interdata gap.
if (!matchMark(results->rawbuf[offset++],
KELVINATOR_BIT_MARK_TICKS * mark_tick))
kKelvinatorBitMarkTicks * mark_tick))
return false;
if (!matchSpace(results->rawbuf[offset++],
KELVINATOR_GAP_SPACE_TICKS * space_tick))
kKelvinatorGapSpaceTicks * 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);
data_result = matchData(
&(results->rawbuf[offset]), 32, kKelvinatorBitMarkTicks * mark_tick,
kKelvinatorOneSpaceTicks * space_tick,
kKelvinatorBitMarkTicks * mark_tick,
kKelvinatorZeroSpaceTicks * space_tick, kTolerance, kMarkExcess, false);
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);
for (uint16_t i = 0; i < 4; i++, data >>= 8)
results->state[state_pos + i] = data & 0xFF;
state_pos += 4;
// Inter-sequence gap. (Double length gap)
if (!matchMark(results->rawbuf[offset++],
KELVINATOR_BIT_MARK_TICKS * mark_tick))
kKelvinatorBitMarkTicks * mark_tick))
return false;
if (s == 0) {
if (!matchSpace(results->rawbuf[offset++],
KELVINATOR_GAP_SPACE_TICKS * space_tick * 2))
kKelvinatorGapSpaceTicks * space_tick * 2))
return false;
} else {
if (offset <= results->rawlen &&
!matchAtLeast(results->rawbuf[offset],
KELVINATOR_GAP_SPACE_TICKS * 2 * space_tick))
kKelvinatorGapSpaceTicks * 2 * space_tick))
return false;
}
}
@@ -546,7 +534,7 @@ bool IRrecv::decodeKelvinator(decode_results *results, uint16_t nbits,
// Compliance
if (strict) {
// Correct size/length)
if (state_pos != KELVINATOR_STATE_LENGTH) return false;
if (state_pos != kKelvinatorStateLength) return false;
// Verify the message's checksum is correct.
if (!IRKelvinatorAC::validChecksum(results->state)) return false;
}
+30 -18
View File
@@ -22,20 +22,33 @@
// KK KK EEEEEEE LLLLLLL VVV IIIII NN NN AA AA TTT OOOO0 RR RR
// Constants
#define KELVINATOR_AUTO 0U
#define KELVINATOR_COOL 1U
#define KELVINATOR_DRY 2U
#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
#define KELVINATOR_AUTO_TEMP 25U // 25C
const uint8_t kKelvinatorAuto = 0;
const uint8_t kKelvinatorCool = 1;
const uint8_t kKelvinatorDry = 2;
const uint8_t kKelvinatorFan = 3;
const uint8_t kKelvinatorHeat = 4;
const uint8_t kKelvinatorBasicFanMax = 3;
const uint8_t kKelvinatorFanAuto = 0;
const uint8_t kKelvinatorFanMax = 5;
const uint8_t kKelvinatorMinTemp = 16; // 16C
const uint8_t kKelvinatorMaxTemp = 30; // 30C
const uint8_t kKelvinatorAutoTemp = 25; // 25C
// Legacy defines (Deprecated)
#define KELVINATOR_MIN_TEMP kKelvinatorMinTemp
#define KELVINATOR_MAX_TEMP kKelvinatorMaxTemp
#define KELVINATOR_HEAT kKelvinatorHeat
#define KELVINATOR_FAN_MAX kKelvinatorFanMax
#define KELVINATOR_FAN_AUTO kKelvinatorFanAuto
#define KELVINATOR_FAN kKelvinatorFan
#define KELVINATOR_DRY kKelvinatorDry
#define KELVINATOR_COOL kKelvinatorCool
#define KELVINATOR_BASIC_FAN_MAX kKelvinatorBasicFanMax
#define KELVINATOR_AUTO_TEMP kKelvinatorAutoTemp
#define KELVINATOR_AUTO kKelvinatorAuto
/*
Kelvinator AC map
Kelvinator AC map
(header mark and space)
byte 0 = Basic Modes
@@ -117,7 +130,7 @@ class IRKelvinatorAC {
void stateReset();
#if SEND_KELVINATOR
void send();
void send(const uint16_t repeat = kKelvinatorDefaultRepeat);
#endif // SEND_KELVINATOR
void begin();
void on();
@@ -147,10 +160,9 @@ class IRKelvinatorAC {
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);
const uint8_t* block, const uint16_t length = kKelvinatorStateLength / 2);
static bool validChecksum(const uint8_t state[],
const uint16_t length = KELVINATOR_STATE_LENGTH);
const uint16_t length = kKelvinatorStateLength);
#ifdef ARDUINO
String toString();
#else
@@ -159,8 +171,8 @@ class IRKelvinatorAC {
private:
// The state of the IR remote in IR code form.
uint8_t remote_state[KELVINATOR_STATE_LENGTH];
void checksum(const uint16_t length = KELVINATOR_STATE_LENGTH);
uint8_t remote_state[kKelvinatorStateLength];
void checksum(const uint16_t length = kKelvinatorStateLength);
void fixup();
IRsend _irsend;
};
+145 -73
View File
@@ -1,6 +1,6 @@
// Copyright 2015 Darryl Smith
// Copyright 2015 cheaplin
// Copyright 2017 David Conran
// Copyright 2017, 2018 David Conran
#include "ir_LG.h"
#include <algorithm>
@@ -16,31 +16,44 @@
// LG decode originally added by Darryl Smith (based on the JVC protocol)
// LG send originally added by https://github.com/chaeplin
//
// Known supported devices:
// IR Remotes:
// 6711A20083V
// AKB74395308
// Constants
#define LG_TICK 50U
#define LG_HDR_MARK_TICKS 160U
#define LG_HDR_MARK (LG_HDR_MARK_TICKS * LG_TICK)
#define LG_HDR_SPACE_TICKS 80U
#define LG_HDR_SPACE (LG_HDR_SPACE_TICKS * LG_TICK)
#define LG_BIT_MARK_TICKS 11U
#define LG_BIT_MARK (LG_BIT_MARK_TICKS * LG_TICK)
#define LG_ONE_SPACE_TICKS 32U
#define LG_ONE_SPACE (LG_ONE_SPACE_TICKS * LG_TICK)
#define LG_ZERO_SPACE_TICKS 11U
#define LG_ZERO_SPACE (LG_ZERO_SPACE_TICKS * LG_TICK)
#define LG_RPT_SPACE_TICKS 45U
#define LG_RPT_SPACE (LG_RPT_SPACE_TICKS * LG_TICK)
#define LG_MIN_GAP_TICKS 795U
#define LG_MIN_GAP (LG_MIN_GAP_TICKS * LG_TICK)
#define LG_MIN_MESSAGE_LENGTH_TICKS 2161U
#define LG_MIN_MESSAGE_LENGTH (LG_MIN_MESSAGE_LENGTH_TICKS * LG_TICK)
#define LG32_HDR_MARK_TICKS 90U
#define LG32_HDR_MARK (LG32_HDR_MARK_TICKS * LG_TICK)
#define LG32_HDR_SPACE_TICKS 89U
#define LG32_HDR_SPACE (LG32_HDR_SPACE_TICKS * LG_TICK)
#define LG32_RPT_HDR_MARK_TICKS 179U
#define LG32_RPT_HDR_MARK (LG32_RPT_HDR_MARK_TICKS * LG_TICK)
const uint16_t kLgTick = 50;
const uint16_t kLgHdrMarkTicks = 170;
const uint16_t kLgHdrMark = kLgHdrMarkTicks * kLgTick; // 8500
const uint16_t kLgHdrSpaceTicks = 85;
const uint16_t kLgHdrSpace = kLgHdrSpaceTicks * kLgTick; // 4250
const uint16_t kLgBitMarkTicks = 11;
const uint16_t kLgBitMark = kLgBitMarkTicks * kLgTick; // 550
const uint16_t kLgOneSpaceTicks = 32;
const uint16_t kLgOneSpace = kLgOneSpaceTicks * kLgTick; // 1600
const uint16_t kLgZeroSpaceTicks = 11;
const uint16_t kLgZeroSpace = kLgZeroSpaceTicks * kLgTick; // 550
const uint16_t kLgRptSpaceTicks = 45;
const uint16_t kLgRptSpace = kLgRptSpaceTicks * kLgTick; // 2250
const uint16_t kLgMinGapTicks = 795;
const uint16_t kLgMinGap = kLgMinGapTicks * kLgTick; // 39750
const uint16_t kLgMinMessageLengthTicks = 2161;
const uint32_t kLgMinMessageLength = kLgMinMessageLengthTicks * kLgTick;
const uint16_t kLg32HdrMarkTicks = 90;
const uint16_t kLg32HdrMark = kLg32HdrMarkTicks * kLgTick; // 4500
const uint16_t kLg32HdrSpaceTicks = 89;
const uint16_t kLg32HdrSpace = kLg32HdrSpaceTicks * kLgTick; // 4450
const uint16_t kLg32RptHdrMarkTicks = 179;
const uint16_t kLg32RptHdrMark = kLg32RptHdrMarkTicks * kLgTick; // 8950
const uint16_t kLg2HdrMarkTicks = 64;
const uint16_t kLg2HdrMark = kLg2HdrMarkTicks * kLgTick; // 3200
const uint16_t kLg2HdrSpaceTicks = 197;
const uint16_t kLg2HdrSpace = kLg2HdrSpaceTicks * kLgTick; // 9850
const uint16_t kLg2BitMarkTicks = 10;
const uint16_t kLg2BitMark = kLg2BitMarkTicks * kLgTick; // 500
#if (SEND_LG || DECODE_LG)
// Calculate the rolling 4-bit wide checksum over all of the data.
@@ -49,8 +62,9 @@
// Returns:
// A 4-bit checksum.
uint8_t calcLGChecksum(uint16_t data) {
return(((data >> 12) + ((data >> 8) & 0xF) + ((data >> 4) & 0xF) +
(data & 0xF)) & 0xF);
return (((data >> 12) + ((data >> 8) & 0xF) + ((data >> 4) & 0xF) +
(data & 0xF)) &
0xF);
}
#endif
@@ -60,50 +74,83 @@ uint8_t calcLGChecksum(uint16_t data) {
// Args:
// data: The contents of the message you want to send.
// nbits: The bit size of the message being sent.
// Typically LG_BITS or LG32_BITS.
// Typically kLgBits or kLg32Bits.
// repeat: The number of times you want the message to be repeated.
//
// Status: Beta / Should be working.
//
// Notes:
// LG has a separate message to indicate a repeat, like NEC does.
// Supports:
// IR Remote models: 6711A20083V
void IRsend::sendLG(uint64_t data, uint16_t nbits, uint16_t repeat) {
uint16_t repeatHeaderMark = 0;
if (nbits >= LG32_BITS) {
if (nbits >= kLg32Bits) {
// LG 32bit protocol is near identical to Samsung except for repeats.
sendSAMSUNG(data, nbits, 0); // Send it as a single Samsung message.
repeatHeaderMark = LG32_RPT_HDR_MARK;
repeatHeaderMark = kLg32RptHdrMark;
repeat++;
} else {
// LG (28-bit) protocol.
repeatHeaderMark = LG_HDR_MARK;
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.
repeatHeaderMark = kLgHdrMark;
sendGeneric(kLgHdrMark, kLgHdrSpace, kLgBitMark, kLgOneSpace, kLgBitMark,
kLgZeroSpace, kLgBitMark, kLgMinGap, kLgMinMessageLength, data,
nbits, 38, true, 0, // Repeats are handled later.
50);
}
// Repeat
// Protocol has a mandatory repeat-specific code sent after every command.
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.
sendGeneric(repeatHeaderMark, kLgRptSpace, 0, 0, 0, 0, // No data is sent.
kLgBitMark, kLgMinGap, kLgMinMessageLength, 0, 0, // No data.
38, true, repeat - 1, 50);
}
// Construct a raw 28-bit LG message from the supplied address & command.
// Send an LG Variant-2 formatted message.
//
// Args:
// data: The contents of the message you want to send.
// nbits: The bit size of the message being sent.
// Typically kLgBits or kLg32Bits.
// repeat: The number of times you want the message to be repeated.
//
// Status: Beta / Should be working.
//
// Notes:
// LG has a separate message to indicate a repeat, like NEC does.
// Supports:
// IR Remote models: AKB74395308
void IRsend::sendLG2(uint64_t data, uint16_t nbits, uint16_t repeat) {
if (nbits >= kLg32Bits) {
// Let the original routine handle it.
sendLG(data, nbits, repeat); // Send it as a single Samsung message.
return;
}
// LGv2 (28-bit) protocol.
sendGeneric(kLg2HdrMark, kLg2HdrSpace, kLgBitMark, kLgOneSpace, kLgBitMark,
kLgZeroSpace, kLgBitMark, kLgMinGap, kLgMinMessageLength, data,
nbits, 38, true, 0, // Repeats are handled later.
50);
// TODO(crackn): Verify the details of what repeat messages look like.
// Repeat
// Protocol has a mandatory repeat-specific code sent after every command.
if (repeat)
sendGeneric(kLg2HdrMark, kLgRptSpace, 0, 0, 0, 0, // No data is sent.
kLgBitMark, kLgMinGap, kLgMinMessageLength, 0, 0, // No data.
38, true, repeat - 1, 50);
}
// Construct a raw 28-bit LG message code from the supplied address & command.
//
// Args:
// address: The address code.
// command: The command code.
// Returns:
// A raw 28-bit LG message suitable for sendLG().
// A raw 28-bit LG message code suitable for sendLG() etc.
//
// Status: BETA / Should work.
//
@@ -125,7 +172,7 @@ uint32_t IRsend::encodeLG(uint16_t address, uint16_t command) {
// 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 LG_BITS or LG32_BITS.
// Typically kLgBits or kLg32Bits.
// strict: Flag to indicate if we strictly adhere to the specification.
// Returns:
// boolean: True if it can decode it, false if it can't.
@@ -135,69 +182,91 @@ uint32_t IRsend::encodeLG(uint16_t address, uint16_t command) {
// Note:
// LG 32bit protocol appears near identical to the Samsung protocol.
// They possibly differ on how they repeat and initial HDR mark.
//
// Supports:
// IR Remote models: 6711A20083V, AKB74395308
// 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 (nbits >= LG32_BITS) {
if (results->rawlen < 2 * nbits + 2 * (HEADER + FOOTER) - 1)
if (nbits >= kLg32Bits) {
if (results->rawlen < 2 * nbits + 2 * (kHeader + kFooter) - 1)
return false; // Can't possibly be a valid LG32 message.
} else {
if (results->rawlen < 2 * nbits + HEADER + FOOTER - 1)
if (results->rawlen < 2 * nbits + kHeader + kFooter - 1)
return false; // Can't possibly be a valid LG message.
}
if (strict && nbits != LG_BITS && nbits != LG32_BITS)
if (strict && nbits != kLgBits && nbits != kLg32Bits)
return false; // Doesn't comply with expected LG protocol.
uint64_t data = 0;
uint16_t offset = OFFSET_START;
uint16_t offset = kStartOffset;
bool isLg2 = false;
// Header
if (!matchMark(results->rawbuf[offset], LG_HDR_MARK) &&
!matchMark(results->rawbuf[offset], LG32_HDR_MARK)) return false;
uint32_t m_tick;
if (matchMark(results->rawbuf[offset], LG_HDR_MARK))
m_tick = results->rawbuf[offset++] * RAWTICK / LG_HDR_MARK_TICKS;
else
m_tick = results->rawbuf[offset++] * RAWTICK / LG32_HDR_MARK_TICKS;
if (!matchSpace(results->rawbuf[offset], LG_HDR_SPACE) &&
!matchSpace(results->rawbuf[offset], LG32_HDR_SPACE)) return false;
if (matchMark(results->rawbuf[offset], kLgHdrMark)) {
m_tick = results->rawbuf[offset++] * kRawTick / kLgHdrMarkTicks;
} else if (matchMark(results->rawbuf[offset], kLg2HdrMark)) {
m_tick = results->rawbuf[offset++] * kRawTick / kLg2HdrMarkTicks;
isLg2 = true;
} else if (matchMark(results->rawbuf[offset], kLg32HdrMark)) {
m_tick = results->rawbuf[offset++] * kRawTick / kLg32HdrMarkTicks;
} else {
return false;
}
uint32_t s_tick;
if (matchSpace(results->rawbuf[offset], LG_HDR_SPACE))
s_tick = results->rawbuf[offset++] * RAWTICK / LG_HDR_SPACE_TICKS;
else
s_tick = results->rawbuf[offset++] * RAWTICK / LG32_HDR_SPACE_TICKS;
if (isLg2) {
if (matchSpace(results->rawbuf[offset], kLg2HdrSpace))
s_tick = results->rawbuf[offset++] * kRawTick / kLg2HdrSpaceTicks;
else
return false;
} else {
if (matchSpace(results->rawbuf[offset], kLgHdrSpace))
s_tick = results->rawbuf[offset++] * kRawTick / kLgHdrSpaceTicks;
else if (matchSpace(results->rawbuf[offset], kLg2HdrSpace))
s_tick = results->rawbuf[offset++] * kRawTick / kLg32HdrSpaceTicks;
else
return false;
}
// Set up the expected tick sizes based on variant.
uint16_t bitmarkticks;
if (isLg2) {
bitmarkticks = kLg2BitMarkTicks;
} else {
bitmarkticks = kLgBitMarkTicks;
}
// Data
match_result_t data_result = matchData(&(results->rawbuf[offset]), nbits,
LG_BIT_MARK_TICKS * m_tick,
LG_ONE_SPACE_TICKS * s_tick,
LG_BIT_MARK_TICKS * m_tick,
LG_ZERO_SPACE_TICKS * s_tick);
match_result_t data_result =
matchData(&(results->rawbuf[offset]), nbits, bitmarkticks * m_tick,
kLgOneSpaceTicks * s_tick, bitmarkticks * m_tick,
kLgZeroSpaceTicks * s_tick, kTolerance, 0);
if (data_result.success == false) return false;
data = data_result.data;
offset += data_result.used;
// Footer
if (!matchMark(results->rawbuf[offset++], LG_BIT_MARK_TICKS * m_tick))
if (!matchMark(results->rawbuf[offset++], bitmarkticks * m_tick))
return false;
if (offset < results->rawlen &&
!matchAtLeast(results->rawbuf[offset], LG_MIN_GAP_TICKS * s_tick))
!matchAtLeast(results->rawbuf[offset], kLgMinGapTicks * s_tick))
return false;
// Repeat
if (nbits >= LG32_BITS) {
if (nbits >= kLg32Bits) {
// If we are expecting the LG 32-bit protocol, there is always
// a repeat message. So, check for it.
offset++;
if (!matchMark(results->rawbuf[offset++], LG32_RPT_HDR_MARK_TICKS * m_tick))
if (!matchMark(results->rawbuf[offset++], kLg32RptHdrMarkTicks * m_tick))
return false;
if (!matchSpace(results->rawbuf[offset++], LG_RPT_SPACE_TICKS * s_tick))
if (!matchSpace(results->rawbuf[offset++], kLgRptSpaceTicks * s_tick))
return false;
if (!matchMark(results->rawbuf[offset++], LG_BIT_MARK_TICKS * m_tick))
if (!matchMark(results->rawbuf[offset++], bitmarkticks * m_tick))
return false;
if (offset < results->rawlen &&
!matchAtLeast(results->rawbuf[offset], LG_MIN_GAP_TICKS * s_tick))
!matchAtLeast(results->rawbuf[offset], kLgMinGapTicks * s_tick))
return false;
}
@@ -208,7 +277,10 @@ bool IRrecv::decodeLG(decode_results *results, uint16_t nbits, bool strict) {
return false; // The last 4 bits sent are the expected checksum.
// Success
results->decode_type = LG;
if (isLg2)
results->decode_type = LG2;
else
results->decode_type = LG;
results->bits = nbits;
results->value = data;
results->command = command;
+29 -31
View File
@@ -12,14 +12,14 @@
// 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;
const uint16_t kLasertagMinSamples = 13;
const uint16_t kLasertagTick = 333;
const uint32_t kLasertagMinGap = 100000; // Completely made up amount.
const uint8_t kLasertagTolerance = 0; // Percentage error margin.
const uint16_t kLasertagExcess = 0; // See kMarkExcess.
const uint16_t kLasertagDelta = 150; // Use instead of Excess and Tolerance.
const int16_t kSpace = 1;
const int16_t kMark = 0;
#if SEND_LASERTAG
// Send a Lasertag packet.
@@ -32,10 +32,8 @@ const int16_t kMARK = 0;
//
// 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.
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.
@@ -44,15 +42,15 @@ void IRsend::sendLasertag(uint64_t data, uint16_t nbits, uint16_t repeat) {
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);
if (data & mask) { // 1
space(kLasertagTick); // 1 is space, then mark.
mark(kLasertagTick);
} else { // 0
mark(kLasertagTick); // 0 is mark, then space.
space(kLasertagTick);
}
// Footer
space(LASERTAG_MIN_GAP);
space(kLasertagMinGap);
}
}
#endif // SEND_LASERTAG
@@ -76,12 +74,12 @@ void IRsend::sendLasertag(uint64_t data, uint16_t nbits, uint16_t repeat) {
// 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;
if (results->rawlen < kLasertagMinSamples) return false;
// Compliance
if (strict && nbits != LASERTAG_BITS) return false;
if (strict && nbits != kLasertagBits) return false;
uint16_t offset = OFFSET_START;
uint16_t offset = kStartOffset;
uint16_t used = 0;
uint64_t data = 0;
uint16_t actual_bits = 0;
@@ -90,19 +88,19 @@ bool IRrecv::decodeLasertag(decode_results *results, uint16_t nbits,
// 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) {
int16_t levelA =
getRClevel(results, &offset, &used, kLasertagTick, kLasertagTolerance,
kLasertagExcess, kLasertagDelta);
int16_t levelB =
getRClevel(results, &offset, &used, kLasertagTick, kLasertagTolerance,
kLasertagExcess, kLasertagDelta);
if (levelA == kSpace && levelB == kMark) {
data = (data << 1) | 1; // 1
} else {
if (levelA == kMARK && levelB == kSPACE) {
if (levelA == kMark && levelB == kSpace) {
data <<= 1; // 0
} else {
break;
break;
}
}
}
@@ -110,7 +108,7 @@ bool IRrecv::decodeLasertag(decode_results *results, uint16_t nbits,
// Compliance
if (actual_bits < nbits) return false; // Less data than we expected.
if (strict && actual_bits != LASERTAG_BITS) return false;
if (strict && actual_bits != kLasertagBits) return false;
// Success
results->decode_type = LASERTAG;
+156
View File
@@ -0,0 +1,156 @@
// Copyright 2018 David Conran
#define __STDC_LIMIT_MACROS
#include <stdint.h>
#include <algorithm>
#include "IRrecv.h"
#include "IRsend.h"
#include "IRutils.h"
// LL UU UU TTTTTTT RRRRRR OOOOO NN NN
// LL UU UU TTT RR RR OO OO NNN NN
// LL UU UU TTT RRRRRR OO OO NN N NN
// LL UU UU TTT RR RR OO OO NN NNN
// LLLLLLL UUUUU TTT RR RR OOOO0 NN NN
// Notes:
// The Lutron protocol uses a sort of Run Length encoding to encode
// its data. There is no header or footer per-se.
// As a mark is the first data we will notice, we always assume the First
// bit of the technically 36-bit protocol is '1'. So it is assumed, and thus
// we only care about the 35 bits of data.
// Constants
// Ref:
// https://github.com/markszabo/IRremoteESP8266/issues/515
const uint16_t kLutronTick = 2288;
const uint32_t kLutronGap = 150000; // Completely made up value.
const uint16_t kLutronDelta = 400; // +/- 300 usecs.
#if SEND_LUTRON
// Send a Lutron formatted message.
//
// Args:
// data: The message to be sent.
// nbits: The number of bits of the message to be sent. Typically kLutronBits
// repeat: The number of times the command is to be repeated.
//
// Status: Stable / Appears to be working for real devices.
// Notes:
// Protocol is really 36 bits long, but the first bit is always a 1.
// So, assume the 1 and only have a normal payload of 35 bits.
//
// Ref:
// https://github.com/markszabo/IRremoteESP8266/issues/515
void IRsend::sendLutron(uint64_t data, uint16_t nbits, uint16_t repeat) {
enableIROut(40000, 40); // 40Khz & 40% dutycycle.
for (uint16_t r = 0; r <= repeat; r++) {
mark(kLutronTick); // 1st bit is always '1'.
// Send the supplied data in MSB First order.
for (uint64_t mask = 1ULL << (nbits - 1); mask; mask >>= 1)
if (data & mask)
mark(kLutronTick); // Send a 1
else
space(kLutronTick); // Send a 0
space(kLutronGap); // Inter-message gap.
}
}
#endif // SEND_LUTRON
#if DECODE_LUTRON
// Decode the supplied Lutron 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 kLutronBits.
// 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.
//
// Notes:
//
// Ref:
// https://github.com/markszabo/IRremoteESP8266/issues/515
bool IRrecv::decodeLutron(decode_results *results, uint16_t nbits,
bool strict) {
// Technically the smallest number of entries for the smallest message is '1'.
// i.e. All the bits set to 1, would produce a single huge mark signal.
// So no minimum length check is required.
if (strict && nbits != kLutronBits)
return false; // Not strictly an Lutron message.
uint64_t data = 0;
int16_t bitsSoFar = -1;
if (nbits > sizeof(data) * 8) return false; // To large to store the data.
for (uint16_t offset = kStartOffset;
bitsSoFar < nbits && offset < results->rawlen; offset++) {
uint16_t entry = results->rawbuf[offset];
// It has to be large enough to qualify as a bit.
if (!matchAtLeast(entry, kLutronTick, 0, kLutronDelta)) {
DPRINTLN("Entry too small. Aborting.");
return false;
}
// Keep reading bits of the same value until we run out.
while (entry != 0 && matchAtLeast(entry, kLutronTick, 0, kLutronDelta)) {
bitsSoFar++;
DPRINT("Bit: ");
DPRINT(bitsSoFar);
if (offset % 2) { // Is Odd?
data = (data << 1) + 1; // Append a '1'.
DPRINTLN(" is a 1.");
} else { // Is it Even?
data <<= 1; // Append a '0'.
DPRINTLN(" is a 0.");
if (bitsSoFar == nbits && matchAtLeast(entry, kLutronGap))
break; // We've likely reached the end of a message.
}
// Remove a bit length from the current entry.
entry = std::max(entry, (uint16_t)(kLutronTick / kRawTick)) -
kLutronTick / kRawTick;
}
if (offset % 2 && !match(entry, kLutronDelta, 0, kLutronDelta)) {
DPRINT("offset = ");
DPRINTLN(offset);
DPRINT("rawlen = ");
DPRINTLN(results->rawlen);
DPRINT("entry = ");
DPRINTLN(entry);
DPRINTLN("Odd Entry has too much left over. Aborting.");
return false; // Too much left over to be a good value. Reject it.
}
if (offset % 2 == 0 && offset <= results->rawlen - 1 &&
!matchAtLeast(entry, kLutronDelta, 0, kLutronDelta)) {
DPRINT("offset = ");
DPRINTLN(offset);
DPRINT("rawlen = ");
DPRINTLN(results->rawlen);
DPRINT("entry = ");
DPRINTLN(entry);
DPRINTLN("Entry has too much left over. Aborting.");
return false; // Too much left over to be a good value. Reject it.
}
}
// We got too many bits.
if (bitsSoFar > nbits || bitsSoFar < 0) {
DPRINTLN("Wrong number of bits found. Aborting.");
return false;
}
// If we got less bits than we were expecting, we need to pad with zeros
// until we get the correct number of bits.
if (bitsSoFar < nbits) data <<= (nbits - bitsSoFar);
// Success
DPRINTLN("Lutron Success!");
results->decode_type = LUTRON;
results->bits = bitsSoFar;
results->value = data ^ (1ULL << nbits); // Mask off the initial '1'.
results->address = 0;
results->command = 0;
return true;
}
#endif // DECODE_LUTRON
+202
View File
@@ -0,0 +1,202 @@
// Copyright 2018 Brett T. Warden
// derived from ir_Lasertag.cpp, Copyright 2017 David Conran
#include <algorithm>
#include "IRrecv.h"
#include "IRsend.h"
#include "IRutils.h"
// MM MM WW WW MM MM
// MMM MMM WW WW MMM MMM
// MM M MM WW W WW MM M MM
// MM MM WWW WWW MM MM
// MM MM WW WW MM MM
// Constants
const uint16_t kMWMMinSamples = 6; // Msgs are >=3 bytes, bytes have >=2
// samples
const uint16_t kMWMTick = 417;
const uint32_t kMWMMinGap = 30000; // Typical observed delay b/w commands
const uint8_t kMWMTolerance = 0; // Percentage error margin.
const uint16_t kMWMExcess = 0; // See kMarkExcess.
const uint16_t kMWMDelta = 150; // Use instead of Excess and Tolerance.
const uint8_t kMWMMaxWidth = 9; // Maximum number of successive bits at a
// single level - worst case
const int16_t kSpace = 1;
const int16_t kMark = 0;
#if SEND_MWM
// Send a MWM packet.
// This protocol is 2400 bps serial, 1 start bit (mark), 1 stop bit (space), no
// parity
//
// 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: Implemented.
//
void IRsend::sendMWM(uint8_t data[], uint16_t nbytes, uint16_t repeat) {
if (nbytes < 3) return; // Shortest possible message is 3 bytes
// Set 38kHz IR carrier frequency & a 1/4 (25%) duty cycle.
// NOTE: duty cycle is not confirmed. Just guessing based on RC5/6 protocols.
enableIROut(38, 25);
for (uint16_t r = 0; r <= repeat; r++) {
// Data
for (uint16_t i = 0; i < nbytes; i++) {
uint8_t byte = data[i];
// Start bit
mark(kMWMTick);
// LSB first, space=1
for (uint8_t mask = 0x1; mask; mask <<= 1) {
if (byte & mask) { // 1
space(kMWMTick);
} else { // 0
mark(kMWMTick);
}
}
// Stop bit
space(kMWMTick);
}
// Footer
space(kMWMMinGap);
}
}
#endif // SEND_MWM
#if DECODE_MWM
// Decode the supplied MWM message.
// This protocol is 2400 bps serial, 1 start bit (mark), 1 stop bit (space), no
// parity
//
// 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: Implemented.
//
bool IRrecv::decodeMWM(decode_results *results, uint16_t nbits, bool strict) {
DPRINTLN("DEBUG: decodeMWM");
// Compliance
if (results->rawlen < kMWMMinSamples) {
DPRINTLN("DEBUG: decodeMWM: too few samples");
return false;
}
uint16_t offset = kStartOffset;
uint16_t used = 0;
uint64_t data = 0;
uint16_t frame_bits = 0;
uint16_t data_bits = 0;
// No Header
// Data
uint8_t bits_per_frame = 10;
for (; offset < results->rawlen && results->bits < 8 * kStateSizeMax;
frame_bits++) {
DPRINT("DEBUG: decodeMWM: offset = ");
DPRINTLN(uint64ToString(offset));
int16_t level = getRClevel(results, &offset, &used, kMWMTick, kMWMTolerance,
kMWMExcess, kMWMDelta, kMWMMaxWidth);
if (level < 0) {
DPRINTLN("DEBUG: decodeMWM: getRClevel returned error");
break;
}
switch (frame_bits % bits_per_frame) {
case 0:
// Start bit
if (level != kMark) {
DPRINTLN("DEBUG: decodeMWM: framing error - invalid start bit");
goto done;
}
break;
case 9:
// Stop bit
if (level != kSpace) {
DPRINTLN("DEBUG: decodeMWM: framing error - invalid stop bit");
return false;
} else {
DPRINT("DEBUG: decodeMWM: data_bits = ");
DPRINTLN(data_bits);
DPRINT("DEBUG: decodeMWM: Finished byte: ");
DPRINTLN(data);
results->state[data_bits / 8 - 1] = data & 0xFF;
results->bits = data_bits;
data = 0;
}
break;
default:
// Data bits
DPRINT("DEBUG: decodeMWM: Storing bit: ");
DPRINTLN((level == kSpace));
// Transmission is LSB-first, space=1
data |= ((level == kSpace)) << 8;
data >>= 1;
data_bits++;
break;
}
}
done:
// Footer (None)
// Compliance
DPRINT("DEBUG: decodeMWM: frame_bits = ");
DPRINTLN(frame_bits);
DPRINT("DEBUG: decodeMWM: data_bits = ");
DPRINTLN(data_bits);
if (data_bits < nbits) {
DPRINT("DEBUG: decodeMWM: too few bits; expected ");
DPRINTLN(nbits);
return false; // Less data than we expected.
}
uint16_t payload_length = 0;
switch (results->state[0] & 0xf0) {
case 0x90:
case 0xf0:
// Normal commands
payload_length = results->state[0] & 0x0f;
DPRINT("DEBUG: decodeMWM: payload_length = ");
DPRINTLN(payload_length);
break;
default:
if (strict) {
// Show commands
if (results->state[0] != 0x55 && results->state[1] != 0xAA) {
return false;
}
}
break;
}
if (data_bits < (payload_length + 3) * 8) {
DPRINT("DEBUG: decodeMWM: too few bytes; expected ");
DPRINTLN((payload_length + 3));
return false;
}
if (strict) {
if (payload_length && (data_bits > (payload_length + 3) * 8)) {
DPRINT("DEBUG: decodeMWM: too many bytes; expected ");
DPRINTLN((payload_length + 3));
return false;
}
}
// Success
results->decode_type = MWM;
results->repeat = false;
return true;
}
#endif // DECODE_MWM
// vim: et:ts=2:sw=2
+35 -29
View File
@@ -8,8 +8,8 @@
#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)
#define IS_ZERO(m, s) (((m)*100 / ((m) + (s))) <= kMagiQuestZeroRatio)
#define IS_ONE(m, s) (((m)*100 / ((m) + (s))) >= kMagiQuestOneRatio)
// Strips taken from:
// https://github.com/kitlaan/Arduino-IRremote/blob/master/ir_Magiquest.cpp
@@ -24,7 +24,7 @@
// Args:
// data: The contents of the message you want to send.
// nbits: The bit size of the message being sent.
// Typically MAGIQUEST_BITS.
// Typically kMagiquestBits.
// repeat: The number of times you want the message to be repeated.
//
// Status: Alpha / Should be working.
@@ -32,11 +32,10 @@
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,
kMagiQuestMarkOne, kMagiQuestSpaceOne, kMagiQuestMarkZero,
kMagiQuestSpaceZero,
0, // No footer mark.
MAGIQUEST_GAP,
data, nbits, 36, true, repeat, 50);
kMagiQuestGap, data, nbits, 36, true, repeat, 50);
}
// Encode a MagiQuest wand_id, and a magnitude into a single 64bit value.
@@ -54,7 +53,8 @@ uint64_t IRsend::encodeMagiQuest(uint32_t wand_id, uint16_t magnitude) {
}
#endif
// Source: https://github.com/kitlaan/Arduino-IRremote/blob/master/ir_Magiquest.cpp
// Source:
// https://github.com/kitlaan/Arduino-IRremote/blob/master/ir_Magiquest.cpp
#if DECODE_MAGIQUEST
// Decode the supplied MagiQuest message.
@@ -68,7 +68,7 @@ uint64_t IRsend::encodeMagiQuest(uint32_t wand_id, uint16_t 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.
// Typically kMagiquestBits.
// strict: Flag to indicate if we strictly adhere to the specification.
// Returns:
// boolean: True if it can decode it, false if it can't.
@@ -81,18 +81,18 @@ bool IRrecv::decodeMagiQuest(decode_results *results, uint16_t nbits,
bool strict) {
uint16_t bits = 0;
uint64_t data = 0;
uint16_t offset = OFFSET_START;
uint16_t offset = kStartOffset;
if (results->rawlen < (2 * MAGIQUEST_BITS)) {
if (results->rawlen < (2 * kMagiquestBits)) {
DPRINT("Not enough bits to be Magiquest - Rawlen: ");
DPRINT(results->rawlen);
DPRINT(" Expected: ");
DPRINTLN((2 * MAGIQUEST_BITS));
DPRINTLN((2 * kMagiquestBits));
return false;
}
// Compliance
if (strict && nbits != MAGIQUEST_BITS) return false;
if (strict && nbits != kMagiquestBits) return false;
// Of six wands as datapoints, so far they all start with 8 ZEROs.
// For example, here is the data from two wands
@@ -101,23 +101,26 @@ bool IRrecv::decodeMagiQuest(decode_results *results, uint16_t nbits,
// Decode the (MARK + SPACE) bits
while (offset + 1 < results->rawlen && bits < nbits - 1) {
uint16_t mark = results->rawbuf[offset];
uint16_t mark = results->rawbuf[offset];
uint16_t space = results->rawbuf[offset + 1];
if (!matchMark(mark + space, MAGIQUEST_TOTAL_USEC)) {
if (!matchMark(mark + space, kMagiQuestTotalUsec)) {
DPRINT("Not enough time to be Magiquest - Mark: ");
DPRINT(mark);
DPRINT(" Space: ");
DPRINT(space);
DPRINT(" Total: ");
DPRINT(mark+space);
DPRINT(mark + space);
DPRINT("Expected: ");
DPRINTLN(MAGIQUEST_TOTAL_USEC);
DPRINTLN(kMagiQuestTotalUsec);
return false;
}
if (IS_ZERO(mark, space)) data = (data << 1) | 0;
else if (IS_ONE( mark, space)) data = (data << 1) | 1;
else 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;
@@ -125,35 +128,38 @@ bool IRrecv::decodeMagiQuest(decode_results *results, uint16_t nbits,
// 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;
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;
uint16_t mark = results->rawbuf[offset];
uint16_t space = (kMagiQuestTotalUsec / kRawTick) - mark;
if (IS_ZERO(mark, space)) data = (data << 1) | 0;
else if (IS_ONE( mark, space)) data = (data << 1) | 1;
else 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++;
}
if (bits != nbits) return false;
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;
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->address = data >> 16; // Wand ID
results->command = data & 0xFFFF; // Magnitude
return true;
}
+14 -16
View File
@@ -13,25 +13,23 @@
// 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];
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;
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_
const uint16_t kMagiQuestTotalUsec = 1150;
const uint8_t kMagiQuestZeroRatio = 30; // usually <= ~25%
const uint8_t kMagiQuestOneRatio = 38; // usually >= ~50%
const uint16_t kMagiQuestMarkZero = 280;
const uint16_t kMagiQuestSpaceZero = 850;
const uint16_t kMagiQuestMarkOne = 580;
const uint16_t kMagiQuestSpaceOne = 600;
const uint32_t kMagiQuestGap = 100000; // A guess of the gap between messages
#endif // IR_MAGIQUEST_H_
+94 -117
View File
@@ -26,35 +26,34 @@
// 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
const uint16_t kMideaTick = 80;
const uint16_t kMideaBitMarkTicks = 7;
const uint16_t kMideaBitMark = kMideaBitMarkTicks * kMideaTick;
const uint16_t kMideaOneSpaceTicks = 21;
const uint16_t kMideaOneSpace = kMideaOneSpaceTicks * kMideaTick;
const uint16_t kMideaZeroSpaceTicks = 7;
const uint16_t kMideaZeroSpace = kMideaZeroSpaceTicks * kMideaTick;
const uint16_t kMideaHdrMarkTicks = 56;
const uint16_t kMideaHdrMark = kMideaHdrMarkTicks * kMideaTick;
const uint16_t kMideaHdrSpaceTicks = 56;
const uint16_t kMideaHdrSpace = kMideaHdrSpaceTicks * kMideaTick;
const uint16_t kMideaMinGapTicks =
kMideaHdrMarkTicks + kMideaZeroSpaceTicks + kMideaBitMarkTicks;
const uint16_t kMideaMinGap = kMideaMinGapTicks * kMideaTick;
const uint8_t kMideaTolerance = 30; // 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.
// nbits: Nr. of bits of data to be sent. Typically kMideaBits.
// 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.
if (nbits % 8 != 0) return; // nbits is required to be a multiple of 8.
// Set IR carrier frequency
enableIROut(38);
@@ -64,21 +63,20 @@ void IRsend::sendMidea(uint64_t data, uint16_t nbits, uint16_t repeat) {
// inverted payload.
for (size_t inner_loop = 0; inner_loop < 2; inner_loop++) {
// Header
mark(MIDEA_HDR_MARK);
space(MIDEA_HDR_SPACE);
mark(kMideaHdrMark);
space(kMideaHdrSpace);
// 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,
sendData(kMideaBitMark, kMideaOneSpace, kMideaBitMark, kMideaZeroSpace,
segment, 8, true);
}
// Footer
mark(MIDEA_BIT_MARK);
space(MIDEA_MIN_GAP); // Pause before repeating
mark(kMideaBitMark);
space(kMideaMinGap); // 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
@@ -93,9 +91,7 @@ void IRsend::sendMidea(uint64_t data, uint16_t nbits, uint16_t repeat) {
// Warning: Consider this very alpha code.
// Initialise the object.
IRMideaAC::IRMideaAC(uint16_t pin) : _irsend(pin) {
stateReset();
}
IRMideaAC::IRMideaAC(uint16_t pin) : _irsend(pin) { stateReset(); }
// Reset the state of the remote to a known good state/sequence.
void IRMideaAC::stateReset() {
@@ -104,38 +100,32 @@ void IRMideaAC::stateReset() {
}
// Configure the pin for output.
void IRMideaAC::begin() {
_irsend.begin();
}
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);
void IRMideaAC::send(const uint16_t repeat) {
checksum(); // Ensure correct checksum before sending.
_irsend.sendMidea(remote_state, kMideaBits, repeat);
}
#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;
return remote_state & kMideaACStateMask;
}
// Override the internal state with the new state.
void IRMideaAC::setRaw(uint64_t newState) {
remote_state = newState & MIDEA_AC_STATE_MASK;
remote_state = newState & kMideaACStateMask;
}
// Set the requested power state of the A/C to off.
void IRMideaAC::on() {
remote_state |= MIDEA_AC_POWER;
}
void IRMideaAC::on() { remote_state |= kMideaACPower; }
// Set the requested power state of the A/C to off.
void IRMideaAC::off() {
remote_state &= (MIDEA_AC_STATE_MASK ^ MIDEA_AC_POWER);
}
void IRMideaAC::off() { remote_state &= (kMideaACStateMask ^ kMideaACPower); }
// Set the requested power state of the A/C.
void IRMideaAC::setPower(const bool state) {
@@ -146,37 +136,35 @@ void IRMideaAC::setPower(const bool state) {
}
// Return the requested power state of the A/C.
bool IRMideaAC::getPower() {
return (remote_state & MIDEA_AC_POWER);
}
bool IRMideaAC::getPower() { return (remote_state & kMideaACPower); }
// Set the temperature.
// Args:
// temp: Temp. in degrees.
// useCelsius: Degree type to use. celsius (true) or fahrenheit (false)
// 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(kMideaACMinTempC, new_temp);
new_temp = std::min(kMideaACMaxTempC, 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);
new_temp = std::max(kMideaACMinTempF, new_temp);
new_temp = std::min(kMideaACMaxTempF, new_temp);
new_temp -= kMideaACMinTempF;
remote_state &= kMideaACTempMask;
remote_state |= ((uint64_t)new_temp << 24);
}
// Return the set temp.
// Args:
// useCelsius: Flag indicating if the results are in celsius or fahrenheit.
// 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;
uint8_t temp = ((remote_state >> 24) & 0x1F) + kMideaACMinTempF;
if (useCelsius) {
temp = (uint8_t) ((temp - 32) / 1.8);
temp = (uint8_t)((temp - 32) / 1.8);
}
return temp;
}
@@ -186,61 +174,55 @@ uint8_t IRMideaAC::getTemp(const bool useCelsius) {
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:
case kMideaACFanLow:
case kMideaACFanMed:
case kMideaACFanHigh:
new_fan = fan;
break;
default:
new_fan = MIDEA_AC_FAN_AUTO;
new_fan = kMideaACFanAuto;
}
remote_state &= MIDEA_AC_FAN_MASK;
remote_state &= kMideaACFanMask;
remote_state |= (new_fan << 35);
}
// Return the requested state of the unit's fan.
uint8_t IRMideaAC::getFan() {
return (remote_state >> 35) & 0b111;
}
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);
}
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:
case kMideaACAuto:
case kMideaACCool:
case kMideaACHeat:
case kMideaACDry:
case kMideaACFan:
new_mode = mode;
break;
default:
new_mode = MIDEA_AC_AUTO;
new_mode = kMideaACAuto;
}
remote_state &= MIDEA_AC_MODE_MASK;
remote_state &= kMideaACModeMask;
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;
remote_state |= kMideaACSleep;
else
remote_state &= (MIDEA_AC_STATE_MASK ^ MIDEA_AC_SLEEP);
remote_state &= (kMideaACStateMask ^ kMideaACSleep);
}
// Return the Sleep state of the A/C.
bool IRMideaAC::getSleep() {
return (remote_state & MIDEA_AC_SLEEP);
}
bool IRMideaAC::getSleep() { return (remote_state & kMideaACSleep); }
// Calculate the checksum for a given array.
// Args:
@@ -271,7 +253,7 @@ bool IRMideaAC::validChecksum(const uint64_t 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 &= kMideaACChecksumMask;
remote_state |= calcChecksum(remote_state);
}
@@ -290,38 +272,38 @@ std::string IRMideaAC::toString() {
result += "Off";
result += ", Mode: " + uint64ToString(getMode());
switch (getMode()) {
case MIDEA_AC_AUTO:
case kMideaACAuto:
result += " (AUTO)";
break;
case MIDEA_AC_COOL:
case kMideaACCool:
result += " (COOL)";
break;
case MIDEA_AC_HEAT:
case kMideaACHeat:
result += " (HEAT)";
break;
case MIDEA_AC_DRY:
case kMideaACDry:
result += " (DRY)";
break;
case MIDEA_AC_FAN:
case kMideaACFan:
result += " (FAN)";
break;
default:
result += " (UNKNOWN)";
}
result += ", Temp: " + uint64ToString(getTemp(true)) + "C/" +
uint64ToString(getTemp(false)) + "F";
uint64ToString(getTemp(false)) + "F";
result += ", Fan: " + uint64ToString(getFan());
switch (getFan()) {
case MIDEA_AC_FAN_AUTO:
case kMideaACFanAuto:
result += " (AUTO)";
break;
case MIDEA_AC_FAN_LOW:
case kMideaACFanLow:
result += " (LOW)";
break;
case MIDEA_AC_FAN_MED:
case kMideaACFanMed:
result += " (MED)";
break;
case MIDEA_AC_FAN_HI:
case kMideaACFanHigh:
result += " (HI)";
break;
}
@@ -338,55 +320,51 @@ std::string IRMideaAC::toString() {
//
// 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.
// nbits: The number of data bits to expect. Typically kMideaBits.
// 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) {
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.
if (nbits != kMideaBits) 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)
if (results->rawlen <
min_nr_of_messages * (2 * nbits + kHeader + kFooter) - 1)
return false; // Can't possibly be a valid MIDEA message.
uint64_t data = 0;
uint64_t inverted = 0;
uint16_t offset = OFFSET_START;
uint16_t offset = kStartOffset;
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;
if (!matchMark(results->rawbuf[offset], kMideaHdrMark)) 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;
uint32_t m_tick = results->rawbuf[offset++] * kRawTick / kMideaHdrMarkTicks;
if (!matchSpace(results->rawbuf[offset], kMideaHdrSpace)) 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;
uint32_t s_tick =
results->rawbuf[offset++] * kRawTick / kMideaHdrSpaceTicks;
// 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);
match_result_t data_result = matchData(
&(results->rawbuf[offset]), nbits, kMideaBitMarkTicks * m_tick,
kMideaOneSpaceTicks * s_tick, kMideaBitMarkTicks * m_tick,
kMideaZeroSpaceTicks * s_tick, kMideaTolerance);
if (data_result.success == false) return false;
offset += data_result.used;
if (i % 2 == 0)
@@ -395,24 +373,23 @@ bool IRrecv::decodeMidea(decode_results *results, uint16_t nbits,
inverted = data_result.data;
// Footer
if (!matchMark(results->rawbuf[offset++], MIDEA_BIT_MARK_TICKS * m_tick,
MIDEA_TOLERANCE))
if (!matchMark(results->rawbuf[offset++], kMideaBitMarkTicks * m_tick,
kMideaTolerance))
return false;
if (offset < results->rawlen &&
!matchAtLeast(results->rawbuf[offset++], MIDEA_MIN_GAP_TICKS * s_tick,
MIDEA_TOLERANCE))
!matchAtLeast(results->rawbuf[offset++], kMideaMinGapTicks * s_tick,
kMideaTolerance))
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;
uint64_t mask = (1ULL << kMideaBits) - 1;
if ((data & mask) != ((inverted ^ mask) & mask)) return false;
if (!IRMideaAC::validChecksum(data)) return false;
}
// Success
+38 -22
View File
@@ -23,26 +23,43 @@
// 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
const uint8_t kMideaACCool = 0; // 0b000
const uint8_t kMideaACDry = 1; // 0b001
const uint8_t kMideaACAuto = 2; // 0b010
const uint8_t kMideaACHeat = 3; // 0b011
const uint8_t kMideaACFan = 4; // 0b100
const uint8_t kMideaACFanAuto = 0; // 0b000
const uint8_t kMideaACFanLow = 1; // 0b001
const uint8_t kMideaACFanMed = 2; // 0b010
const uint8_t kMideaACFanHigh = 3; // 0b011
const uint64_t kMideaACPower = 1ULL << 39;
const uint64_t kMideaACSleep = 1ULL << 38;
const uint8_t kMideaACMinTempF = 62; // Fahrenheit
const uint8_t kMideaACMaxTempF = 86; // Fahrenheit
const uint8_t kMideaACMinTempC = 16; // Celsius
const uint8_t kMideaACMaxTempC = 30; // Celsius
const uint64_t kMideaACStateMask = 0x0000FFFFFFFFFFFF;
const uint64_t kMideaACTempMask = 0x0000FFFFE0FFFFFF;
const uint64_t kMideaACFanMask = 0x0000FFC7FFFFFFFF;
const uint64_t kMideaACModeMask = 0x0000FFF8FFFFFFFF;
const uint64_t kMideaACChecksumMask = 0x0000FFFFFFFFFF00;
// Legacy defines. (Deprecated)
#define MIDEA_AC_COOL kMideaACCool
#define MIDEA_AC_DRY kMideaACDry
#define MIDEA_AC_AUTO kMideaACAuto
#define MIDEA_AC_HEAT kMideaACHeat
#define MIDEA_AC_FAN kMideaACFan
#define MIDEA_AC_FAN_AUTO kMideaACFanAuto
#define MIDEA_AC_FAN_LOW kMideaACFanLow
#define MIDEA_AC_FAN_MED kMideaACFanMed
#define MIDEA_AC_FAN_HI kMideaACFanHigh
#define MIDEA_AC_POWER kMideaACPower
#define MIDEA_AC_SLEEP kMideaACSleep
#define MIDEA_AC_MIN_TEMP_F kMideaACMinTempF
#define MIDEA_AC_MAX_TEMP_F kMideaACMaxTempF
#define MIDEA_AC_MIN_TEMP_C kMideaACMinTempC
#define MIDEA_AC_MAX_TEMP_C kMideaACMaxTempC
class IRMideaAC {
public:
@@ -50,7 +67,7 @@ class IRMideaAC {
void stateReset();
#if SEND_MIDEA
void send();
void send(const uint16_t repeat = kMideaMinRepeat);
#endif // SEND_MIDEA
void begin();
void on();
@@ -83,5 +100,4 @@ class IRMideaAC {
IRsend _irsend;
};
#endif // IR_MIDEA_H_
+392 -132
View File
@@ -1,8 +1,12 @@
// Copyright 2009 Ken Shirriff
// Copyright 2017 David Conran
// Copyright 2017-2018 David Conran
// Copyright 2018 Denes Varga
#include "ir_Mitsubishi.h"
#include <algorithm>
#ifndef ARDUINO
#include <string>
#endif
#include "IRrecv.h"
#include "IRsend.h"
#include "IRutils.h"
@@ -22,50 +26,49 @@
// Ref:
// GlobalCache's Control Tower's Mitsubishi TV data.
// https://github.com/marcosamarinho/IRremoteESP8266/blob/master/ir_Mitsubishi.cpp
#define MITSUBISHI_TICK 30U
#define MITSUBISHI_BIT_MARK_TICKS 10U
#define MITSUBISHI_BIT_MARK (MITSUBISHI_BIT_MARK_TICKS * \
MITSUBISHI_TICK)
#define MITSUBISHI_ONE_SPACE_TICKS 70U
#define MITSUBISHI_ONE_SPACE (MITSUBISHI_ONE_SPACE_TICKS * \
MITSUBISHI_TICK)
#define MITSUBISHI_ZERO_SPACE_TICKS 30U
#define MITSUBISHI_ZERO_SPACE (MITSUBISHI_ZERO_SPACE_TICKS * \
MITSUBISHI_TICK)
#define MITSUBISHI_MIN_COMMAND_LENGTH_TICKS 1786U
#define MITSUBISHI_MIN_COMMAND_LENGTH (MITSUBISHI_MIN_COMMAND_LENGTH_TICKS * \
MITSUBISHI_TICK)
#define MITSUBISHI_MIN_GAP_TICKS 936U
#define MITSUBISHI_MIN_GAP (MITSUBISHI_MIN_GAP_TICKS * \
MITSUBISHI_TICK)
const uint16_t kMitsubishiTick = 30;
const uint16_t kMitsubishiBitMarkTicks = 10;
const uint16_t kMitsubishiBitMark = kMitsubishiBitMarkTicks * kMitsubishiTick;
const uint16_t kMitsubishiOneSpaceTicks = 70;
const uint16_t kMitsubishiOneSpace = kMitsubishiOneSpaceTicks * kMitsubishiTick;
const uint16_t kMitsubishiZeroSpaceTicks = 30;
const uint16_t kMitsubishiZeroSpace =
kMitsubishiZeroSpaceTicks * kMitsubishiTick;
const uint16_t kMitsubishiMinCommandLengthTicks = 1786;
const uint16_t kMitsubishiMinCommandLength =
kMitsubishiMinCommandLengthTicks * kMitsubishiTick;
const uint16_t kMitsubishiMinGapTicks = 936;
const uint16_t kMitsubishiMinGap = kMitsubishiMinGapTicks * kMitsubishiTick;
// 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
const uint16_t kMitsubishi2HdrMark = 8400;
const uint16_t kMitsubishi2HdrSpace = kMitsubishi2HdrMark / 2;
const uint16_t kMitsubishi2BitMark = 560;
const uint16_t kMitsubishi2ZeroSpace = 520;
const uint16_t kMitsubishi2OneSpace = kMitsubishi2ZeroSpace * 3;
const uint16_t kMitsubishi2MinGap = 28500;
// Mitsubishi A/C
// Ref:
// https://github.com/r45635/HVAC-IR-Control/blob/master/HVAC_ESP8266/HVAC_ESP8266.ino#L84
#define MITSUBISHI_AC_HDR_MARK 3400U
#define MITSUBISHI_AC_HDR_SPACE 1750U
#define MITSUBISHI_AC_BIT_MARK 450U
#define MITSUBISHI_AC_ONE_SPACE 1300U
#define MITSUBISHI_AC_ZERO_SPACE 420U
#define MITSUBISHI_AC_RPT_MARK 440U
#define MITSUBISHI_AC_RPT_SPACE 17100UL
const uint16_t kMitsubishiAcHdrMark = 3400;
const uint16_t kMitsubishiAcHdrSpace = 1750;
const uint16_t kMitsubishiAcBitMark = 450;
const uint16_t kMitsubishiAcOneSpace = 1300;
const uint16_t kMitsubishiAcZeroSpace = 420;
const uint16_t kMitsubishiAcRptMark = 440;
const uint16_t kMitsubishiAcRptSpace = 17100;
#if SEND_MITSUBISHI
// Send a Mitsubishi message
//
// Args:
// data: Contents of the message to be sent.
// nbits: Nr. of bits of data to be sent. Typically MITSUBISHI_BITS.
// nbits: Nr. of bits of data to be sent. Typically kMitsubishiBits.
// repeat: Nr. of additional times the message is to be sent.
//
// Status: ALPHA / untested.
@@ -77,13 +80,11 @@
// GlobalCache's Control Tower's Mitsubishi TV data.
void IRsend::sendMitsubishi(uint64_t data, uint16_t nbits, uint16_t repeat) {
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);
kMitsubishiBitMark, kMitsubishiOneSpace, kMitsubishiBitMark,
kMitsubishiZeroSpace, kMitsubishiBitMark, kMitsubishiMinGap,
kMitsubishiMinCommandLength, data, nbits, 33, true, repeat, 50);
}
#endif // SEND_MITSUBISHI
#endif // SEND_MITSUBISHI
#if DECODE_MITSUBISHI
// Decode the supplied Mitsubishi message.
@@ -104,44 +105,40 @@ void IRsend::sendMitsubishi(uint64_t data, uint16_t nbits, uint16_t repeat) {
// GlobalCache's Control Tower's Mitsubishi TV data.
bool IRrecv::decodeMitsubishi(decode_results *results, uint16_t nbits,
bool strict) {
if (results->rawlen < 2 * nbits + FOOTER - 1)
if (results->rawlen < 2 * nbits + kFooter - 1)
return false; // Shorter than shortest possibly expected.
if (strict && nbits != MITSUBISHI_BITS)
if (strict && nbits != kMitsubishiBits)
return false; // Request is out of spec.
uint16_t offset = OFFSET_START;
uint16_t offset = kStartOffset;
uint64_t data = 0;
// No Header
// But try to auto-calibrate off the initial mark signal.
if (!matchMark(results->rawbuf[offset], MITSUBISHI_BIT_MARK, 30))
return false;
if (!matchMark(results->rawbuf[offset], kMitsubishiBitMark, 30)) return false;
// Calculate how long the common tick time is based on the initial mark.
uint32_t tick = results->rawbuf[offset] * RAWTICK / MITSUBISHI_BIT_MARK_TICKS;
uint32_t tick = results->rawbuf[offset] * kRawTick / kMitsubishiBitMarkTicks;
// Data
match_result_t data_result = matchData(&(results->rawbuf[offset]), nbits,
MITSUBISHI_BIT_MARK_TICKS * tick,
MITSUBISHI_ONE_SPACE_TICKS * tick,
MITSUBISHI_BIT_MARK_TICKS * tick,
MITSUBISHI_ZERO_SPACE_TICKS * tick);
match_result_t data_result = matchData(
&(results->rawbuf[offset]), nbits, kMitsubishiBitMarkTicks * tick,
kMitsubishiOneSpaceTicks * tick, kMitsubishiBitMarkTicks * tick,
kMitsubishiZeroSpaceTicks * tick);
if (data_result.success == false) return false;
data = data_result.data;
offset += data_result.used;
uint16_t actualBits = data_result.used / 2;
// Footer
if (!matchMark(results->rawbuf[offset++], MITSUBISHI_BIT_MARK_TICKS * tick,
30)) return false;
if (!matchMark(results->rawbuf[offset++], kMitsubishiBitMarkTicks * tick, 30))
return false;
if (offset < results->rawlen &&
!matchAtLeast(results->rawbuf[offset], MITSUBISHI_MIN_GAP_TICKS * tick))
!matchAtLeast(results->rawbuf[offset], kMitsubishiMinGapTicks * tick))
return false;
// Compliance
if (actualBits < nbits)
return false;
if (strict && actualBits != nbits)
return false; // Not as we expected.
if (actualBits < nbits) return false;
if (strict && actualBits != nbits) return false; // Not as we expected.
// Success
results->decode_type = MITSUBISHI;
@@ -158,7 +155,7 @@ bool IRrecv::decodeMitsubishi(decode_results *results, uint16_t nbits,
//
// Args:
// data: Contents of the message to be sent.
// nbits: Nr. of bits of data to be sent. Typically MITSUBISHI_BITS.
// nbits: Nr. of bits of data to be sent. Typically kMitsubishiBits.
// repeat: Nr. of additional times the message is to be sent.
//
// Status: ALPHA / untested.
@@ -175,17 +172,16 @@ bool IRrecv::decodeMitsubishi(decode_results *results, uint16_t nbits,
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);
sendGeneric(kMitsubishi2HdrMark, kMitsubishi2HdrSpace, kMitsubishi2BitMark,
kMitsubishi2OneSpace, kMitsubishi2BitMark,
kMitsubishi2ZeroSpace, kMitsubishi2BitMark,
kMitsubishi2HdrSpace, 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);
kMitsubishi2BitMark, kMitsubishi2OneSpace, kMitsubishi2BitMark,
kMitsubishi2ZeroSpace, kMitsubishi2BitMark, kMitsubishi2MinGap,
data & ((1 << (nbits / 2)) - 1), nbits / 2, 33, true, 0, 50);
}
}
#endif // SEND_MITSUBISHI2
@@ -210,28 +206,24 @@ void IRsend::sendMitsubishi2(uint64_t data, uint16_t nbits, uint16_t repeat) {
// 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)
if (results->rawlen < 2 * nbits + kHeader + (kFooter * 2) - 1)
return false; // Shorter than shortest possibly expected.
if (strict && nbits != MITSUBISHI_BITS)
if (strict && nbits != kMitsubishiBits)
return false; // Request is out of spec.
uint16_t offset = OFFSET_START;
uint16_t offset = kStartOffset;
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))
if (!matchMark(results->rawbuf[offset++], kMitsubishi2HdrMark)) return false;
if (!matchSpace(results->rawbuf[offset++], kMitsubishi2HdrSpace))
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);
match_result_t data_result = matchData(
&(results->rawbuf[offset]), nbits / 2, kMitsubishi2BitMark,
kMitsubishi2OneSpace, kMitsubishi2BitMark, kMitsubishi2ZeroSpace);
if (data_result.success == false) return false;
data <<= nbits / 2;
data += data_result.data;
@@ -239,24 +231,21 @@ bool IRrecv::decodeMitsubishi2(decode_results *results, uint16_t nbits,
actualBits += data_result.used / 2;
// Footer
if (!matchMark(results->rawbuf[offset++], MITSUBISHI2_BIT_MARK))
if (!matchMark(results->rawbuf[offset++], kMitsubishi2BitMark))
return false;
if (i % 2) { // Every odd data block, we expect a HDR space.
if (!matchSpace(results->rawbuf[offset++], MITSUBISHI2_HDR_SPACE))
if (!matchSpace(results->rawbuf[offset++], kMitsubishi2HdrSpace))
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))
!matchAtLeast(results->rawbuf[offset++], kMitsubishi2MinGap))
return false;
}
}
// Compliance
if (actualBits < nbits)
return false;
if (strict && actualBits != nbits)
return false; // Not as we expected.
if (actualBits < nbits) return false;
if (strict && actualBits != nbits) return false; // Not as we expected.
// Success
results->decode_type = MITSUBISHI2;
@@ -273,25 +262,155 @@ bool IRrecv::decodeMitsubishi2(decode_results *results, uint16_t nbits,
//
// Args:
// data: An array of bytes containing the IR command.
// nbytes: Nr. of bytes of data in the array. (>=MITSUBISHI_AC_STATE_LENGTH)
// nbytes: Nr. of bytes of data in the array. (>=kMitsubishiACStateLength)
// repeat: Nr. of times the message is to be repeated.
// (Default = MITSUBISHI_AC_MIN_REPEAT).
// (Default = kMitsubishiACMinRepeat).
//
// Status: BETA / Appears to be working.
//
void IRsend::sendMitsubishiAC(unsigned char data[], uint16_t nbytes,
uint16_t repeat) {
if (nbytes < MITSUBISHI_AC_STATE_LENGTH)
if (nbytes < kMitsubishiACStateLength)
return; // Not enough bytes to send a proper message.
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);
sendGeneric(kMitsubishiAcHdrMark, kMitsubishiAcHdrSpace, kMitsubishiAcBitMark,
kMitsubishiAcOneSpace, kMitsubishiAcBitMark,
kMitsubishiAcZeroSpace, kMitsubishiAcRptMark,
kMitsubishiAcRptSpace, data, nbytes, 38, false, repeat, 50);
}
#endif // SEND_MITSUBISHI_AC
#if DECODE_MITSUBISHI_AC
// Decode the supplied Mitsubishi 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: ALPHA / Under development
//
// Ref:
// https://www.analysir.com/blog/2015/01/06/reverse-engineering-mitsubishi-ac-infrared-protocol/
bool IRrecv::decodeMitsubishiAC(decode_results *results, uint16_t nbits,
bool strict) {
if (results->rawlen < ((kMitsubishiACBits * 2) + 2)) {
DPRINTLN("Shorter than shortest possibly expected.");
return false; // Shorter than shortest possibly expected.
}
if (strict && nbits != kMitsubishiACBits) {
DPRINTLN("Request is out of spec.");
return false; // Request is out of spec.
}
uint16_t offset = kStartOffset;
for (uint8_t i = 0; i < kMitsubishiACStateLength; i++) {
results->state[i] = 0;
}
bool failure = false;
uint8_t rep = 0;
do {
failure = false;
// Header:
// Somtime happens that junk signals arrives before the real message
bool headerFound = false;
while (!headerFound &&
offset < (results->rawlen - (kMitsubishiACBits * 2 + 2))) {
headerFound =
matchMark(results->rawbuf[offset++], kMitsubishiAcHdrMark) &&
matchSpace(results->rawbuf[offset++], kMitsubishiAcHdrSpace);
}
if (!headerFound) {
DPRINTLN("Header mark not found.");
failure = true;
}
// Decode byte-by-byte:
match_result_t data_result;
for (uint8_t i = 0; i < kMitsubishiACStateLength && !failure; i++) {
results->state[i] = 0;
data_result =
matchData(&(results->rawbuf[offset]), 8, kMitsubishiAcBitMark,
kMitsubishiAcOneSpace, kMitsubishiAcBitMark,
kMitsubishiAcZeroSpace, kTolerance, kMarkExcess, false);
if (data_result.success == false) {
failure = true;
DPRINT("Byte decode failed at #");
DPRINTLN((uint16_t)i);
} else {
results->state[i] = data_result.data;
offset += data_result.used;
DPRINT((uint16_t)results->state[i]);
DPRINT(",");
}
DPRINTLN("");
}
// HEADER validation:
if (failure || results->state[0] != 0x23 || results->state[1] != 0xCB ||
results->state[2] != 0x26 || results->state[3] != 0x01 ||
results->state[4] != 0x00) {
DPRINTLN("Header mismatch.");
failure = true;
} else {
// DATA part:
// FOOTER checksum:
if (IRMitsubishiAC::calculateChecksum(results->state) !=
results->state[kMitsubishiACStateLength - 1]) {
DPRINTLN("Checksum error.");
failure = true;
}
}
if (rep != kMitsubishiACMinRepeat && failure) {
bool repeatMarkFound = false;
while (!repeatMarkFound &&
offset < (results->rawlen - (kMitsubishiACBits * 2 + 4))) {
repeatMarkFound =
matchMark(results->rawbuf[offset++], kMitsubishiAcRptMark) &&
matchSpace(results->rawbuf[offset++], kMitsubishiAcRptSpace);
}
if (!repeatMarkFound) {
DPRINTLN("First attempt failure and repeat mark not found.");
return false;
}
}
rep++;
// Check if the repeat is correct if we need strict decode:
if (strict && !failure) {
DPRINTLN("Strict repeat check enabled.");
// Repeat mark and space:
if (!matchMark(results->rawbuf[offset++], kMitsubishiAcRptMark) ||
!matchSpace(results->rawbuf[offset++], kMitsubishiAcRptSpace)) {
DPRINTLN("Repeat mark error.");
return false;
}
// Header mark and space:
if (!matchMark(results->rawbuf[offset++], kMitsubishiAcHdrMark) ||
!matchSpace(results->rawbuf[offset++], kMitsubishiAcHdrSpace)) {
DPRINTLN("Repeat header error.");
return false;
}
// Payload:
for (uint8_t i = 0; i < kMitsubishiACStateLength; i++) {
data_result =
matchData(&(results->rawbuf[offset]), 8, kMitsubishiAcBitMark,
kMitsubishiAcOneSpace, kMitsubishiAcBitMark,
kMitsubishiAcZeroSpace, kTolerance, kMarkExcess, false);
if (data_result.success == false ||
data_result.data != results->state[i]) {
DPRINTLN("Repeat payload error.");
return false;
}
offset += data_result.used;
}
} // strict repeat check
} while (failure && rep <= kMitsubishiACMinRepeat);
results->decode_type = MITSUBISHI_AC;
results->bits = kMitsubishiACStateLength * 8;
return true;
}
#endif // DECODE_MITSUBISHI_AC
// Code to emulate Mitsubishi A/C IR remote control unit.
// Inspired and derived from the work done at:
// https://github.com/r45635/HVAC-IR-Control
@@ -301,9 +420,7 @@ void IRsend::sendMitsubishiAC(unsigned char data[], uint16_t nbytes,
// Equipment it seems compatible with:
// * <Add models (A/C & remotes) you've gotten it working with here>
// Initialise the object.
IRMitsubishiAC::IRMitsubishiAC(uint16_t pin) : _irsend(pin) {
stateReset();
}
IRMitsubishiAC::IRMitsubishiAC(uint16_t pin) : _irsend(pin) { stateReset(); }
// Reset the state of the remote to a known good state/sequence.
void IRMitsubishiAC::stateReset() {
@@ -311,7 +428,7 @@ void IRMitsubishiAC::stateReset() {
// Known good state obtained from:
// https://github.com/r45635/HVAC-IR-Control/blob/master/HVAC_ESP8266/HVAC_ESP8266.ino#L108
// Note: Can't use the following because it requires -std=c++11
// uint8_t known_good_state[MITSUBISHI_AC_STATE_LENGTH] = {
// uint8_t known_good_state[kMitsubishiACStateLength] = {
// 0x23, 0xCB, 0x26, 0x01, 0x00, 0x20, 0x08, 0x06, 0x30, 0x45, 0x67, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F};
remote_state[0] = 0x23;
@@ -325,51 +442,59 @@ void IRMitsubishiAC::stateReset() {
remote_state[8] = 0x30;
remote_state[9] = 0x45;
remote_state[10] = 0x67;
for (uint8_t i = 11; i < MITSUBISHI_AC_STATE_LENGTH - 1; i++)
for (uint8_t i = 11; i < kMitsubishiACStateLength - 1; i++)
remote_state[i] = 0;
remote_state[MITSUBISHI_AC_STATE_LENGTH - 1] = 0x1F;
remote_state[kMitsubishiACStateLength - 1] = 0x1F;
checksum(); // Calculate the checksum
}
// Configure the pin for output.
void IRMitsubishiAC::begin() {
_irsend.begin();
}
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);
void IRMitsubishiAC::send(const uint16_t repeat) {
checksum(); // Ensure correct checksum before sending.
_irsend.sendMitsubishiAC(remote_state, kMitsubishiACStateLength, repeat);
}
#endif // SEND_MITSUBISHI_AC
// Return a pointer to the internal state date of the remote.
uint8_t* IRMitsubishiAC::getRaw() {
uint8_t *IRMitsubishiAC::getRaw() {
checksum();
return remote_state;
}
void IRMitsubishiAC::setRaw(uint8_t *data) {
for (uint8_t i = 0; i < (kMitsubishiACStateLength - 1); i++) {
remote_state[i] = data[i];
}
checksum();
}
// Calculate the checksum for the current internal state of the remote.
void IRMitsubishiAC::checksum() {
remote_state[17] = calculateChecksum(remote_state);
}
uint8_t IRMitsubishiAC::calculateChecksum(uint8_t *data) {
uint8_t sum = 0;
// Checksum is simple addition of all previous bytes stored
// as a 8 bit value.
for (uint8_t i = 0; i < 17; i++)
sum += remote_state[i];
remote_state[17] = sum & 0xFFU;
// as an 8 bit value.
for (uint8_t i = 0; i < 17; i++) sum += data[i];
return sum & 0xFFU;
}
// Set the requested power state of the A/C to off.
void IRMitsubishiAC::on() {
// state = ON;
remote_state[5] |= MITSUBISHI_AC_POWER;
remote_state[5] |= kMitsubishiAcPower;
}
// Set the requested power state of the A/C to off.
void IRMitsubishiAC::off() {
// state = OFF;
remote_state[5] &= ~MITSUBISHI_AC_POWER;
remote_state[5] &= ~kMitsubishiAcPower;
}
// Set the requested power state of the A/C.
@@ -382,31 +507,31 @@ void IRMitsubishiAC::setPower(bool state) {
// Return the requested power state of the A/C.
bool IRMitsubishiAC::getPower() {
return((remote_state[5] & MITSUBISHI_AC_POWER) != 0);
return ((remote_state[5] & kMitsubishiAcPower) != 0);
}
// Set the temp. in deg C
void IRMitsubishiAC::setTemp(uint8_t temp) {
temp = std::max((uint8_t) MITSUBISHI_AC_MIN_TEMP, temp);
temp = std::min((uint8_t) MITSUBISHI_AC_MAX_TEMP, temp);
remote_state[7] = temp - MITSUBISHI_AC_MIN_TEMP;
temp = std::max((uint8_t)kMitsubishiAcMinTemp, temp);
temp = std::min((uint8_t)kMitsubishiAcMaxTemp, temp);
remote_state[7] = temp - kMitsubishiAcMinTemp;
}
// Return the set temp. in deg C
uint8_t IRMitsubishiAC::getTemp() {
return(remote_state[7] + MITSUBISHI_AC_MIN_TEMP);
return (remote_state[7] + kMitsubishiAcMinTemp);
}
// Set the speed of the fan, 0-6.
// 0 is auto, 1-5 is the speed, 6 is silent.
void IRMitsubishiAC::setFan(uint8_t fan) {
// Bounds check
if (fan > MITSUBISHI_AC_FAN_SILENT)
fan = MITSUBISHI_AC_FAN_MAX; // Set the fan to maximum if out of range.
if (fan == MITSUBISHI_AC_FAN_AUTO) { // Automatic is a special case.
if (fan > kMitsubishiAcFanSilent)
fan = kMitsubishiAcFanMax; // Set the fan to maximum if out of range.
if (fan == kMitsubishiAcFanAuto) { // Automatic is a special case.
remote_state[9] = 0b10000000 | (remote_state[9] & 0b01111000);
return;
} else if (fan >= MITSUBISHI_AC_FAN_MAX) {
} else if (fan >= kMitsubishiAcFanMax) {
fan--; // There is no spoon^H^H^Heed 5 (max), pretend it doesn't exist.
}
remote_state[9] &= 0b01111000; // Clear the previous state
@@ -416,32 +541,39 @@ void IRMitsubishiAC::setFan(uint8_t fan) {
// Return the requested state of the unit's fan.
uint8_t IRMitsubishiAC::getFan() {
uint8_t fan = remote_state[9] & 0b111;
if (fan == MITSUBISHI_AC_FAN_MAX)
return MITSUBISHI_AC_FAN_SILENT;
if (fan == kMitsubishiAcFanMax) return kMitsubishiAcFanSilent;
return fan;
}
// Return the requested climate operation mode of the a/c unit.
uint8_t IRMitsubishiAC::getMode() {
return(remote_state[6]);
}
uint8_t IRMitsubishiAC::getMode() { return (remote_state[6]); }
// Set the requested climate operation mode of the a/c unit.
void IRMitsubishiAC::setMode(uint8_t mode) {
// If we get an unexpected mode, default to AUTO.
switch (mode) {
case MITSUBISHI_AC_AUTO: break;
case MITSUBISHI_AC_COOL: break;
case MITSUBISHI_AC_DRY: break;
case MITSUBISHI_AC_HEAT: break;
default: mode = MITSUBISHI_AC_AUTO;
case kMitsubishiAcAuto:
remote_state[8] = 0b00110000;
break;
case kMitsubishiAcCool:
remote_state[8] = 0b00110110;
break;
case kMitsubishiAcDry:
remote_state[8] = 0b00110010;
break;
case kMitsubishiAcHeat:
remote_state[8] = 0b00110000;
break;
default:
mode = kMitsubishiAcAuto;
remote_state[8] = 0b00110000;
}
remote_state[6] = mode;
}
// Set the requested vane operation mode of the a/c unit.
void IRMitsubishiAC::setVane(uint8_t mode) {
mode = std::min(mode, (uint8_t) 0b111); // bounds check
mode = std::min(mode, (uint8_t)0b111); // bounds check
mode |= 0b1000;
mode <<= 3;
remote_state[9] &= 0b11000111; // Clear the previous setting.
@@ -452,3 +584,131 @@ void IRMitsubishiAC::setVane(uint8_t mode) {
uint8_t IRMitsubishiAC::getVane() {
return ((remote_state[9] & 0b00111000) >> 3);
}
// Return the clock setting of the message. 1=1/6 hour. e.g. 4pm = 48
uint8_t IRMitsubishiAC::getClock() { return remote_state[10]; }
// Set the current time. 1 = 1/6 hour. e.g. 6am = 36.
void IRMitsubishiAC::setClock(uint8_t clock) { remote_state[10] = clock; }
// Return the desired start time. 1 = 1/6 hour. e.g. 1am = 6
uint8_t IRMitsubishiAC::getStartClock() { return remote_state[12]; }
// Set the desired start tiem of the AC. 1 = 1/6 hour. e.g. 8pm = 120
void IRMitsubishiAC::setStartClock(uint8_t clock) { remote_state[12] = clock; }
// Return the desired stop time of the AC. 1 = 1/6 hour. e.g 10pm = 132
uint8_t IRMitsubishiAC::getStopClock() { return remote_state[11]; }
// Set the desired stop time of the AC. 1 = 1/6 hour. e.g 10pm = 132
void IRMitsubishiAC::setStopClock(uint8_t clock) { remote_state[11] = clock; }
// Return the timer setting. Possible values: kMitsubishiAcNoTimer,
// kMitsubishiAcStartTimer, kMitsubishiAcStopTimer,
// kMitsubishiAcStartStopTimer
uint8_t IRMitsubishiAC::getTimer() { return remote_state[13] & 0b111; }
// Set the timer setting. Possible values: kMitsubishiAcNoTimer,
// kMitsubishiAcStartTimer, kMitsubishiAcStopTimer,
// kMitsubishiAcStartStopTimer
void IRMitsubishiAC::setTimer(uint8_t timer) {
remote_state[13] = timer & 0b111;
}
#ifdef ARDUINO
String IRMitsubishiAC::timeToString(uint64_t time) {
String result = "";
#else
std::string IRMitsubishiAC::timeToString(uint64_t time) {
std::string result = "";
#endif // ARDUINO
if (time / 6 < 10) result += "0";
result += uint64ToString(time / 6);
result += ":";
if (time * 10 % 60 < 10) result += "0";
result += uint64ToString(time * 10 % 60);
return result;
}
// Convert the internal state into a human readable string.
#ifdef ARDUINO
String IRMitsubishiAC::toString() {
String result = "";
#else
std::string IRMitsubishiAC::toString() {
std::string result = "";
#endif // ARDUINO
result += "Power: ";
if (getPower())
result += "On";
else
result += "Off";
switch (getMode()) {
case MITSUBISHI_AC_AUTO:
result += " (AUTO)";
break;
case MITSUBISHI_AC_COOL:
result += " (COOL)";
break;
case MITSUBISHI_AC_DRY:
result += " (DRY)";
break;
case MITSUBISHI_AC_HEAT:
result += " (HEAT)";
break;
default:
result += " (UNKNOWN)";
}
result += ", Temp: " + uint64ToString(getTemp()) + "C";
result += ", FAN: ";
switch (getFan()) {
case MITSUBISHI_AC_FAN_AUTO:
result += "AUTO";
break;
case MITSUBISHI_AC_FAN_MAX:
result += "MAX";
break;
case MITSUBISHI_AC_FAN_SILENT:
result += "SILENT";
break;
default:
result += uint64ToString(getFan());
}
result += ", VANE: ";
switch (getVane()) {
case MITSUBISHI_AC_VANE_AUTO:
result += "AUTO";
break;
case MITSUBISHI_AC_VANE_AUTO_MOVE:
result += "AUTO MOVE";
break;
default:
result += uint64ToString(getVane());
}
result += ", Time: ";
result += timeToString(getClock());
result += ", On timer: ";
result += timeToString(getStartClock());
result += ", Off timer: ";
result += timeToString(getStopClock());
result += ", Timer: ";
switch (getTimer()) {
case kMitsubishiAcNoTimer:
result += "-";
break;
case kMitsubishiAcStartTimer:
result += "Start";
break;
case kMitsubishiAcStopTimer:
result += "Stop";
break;
case kMitsubishiAcStartStopTimer:
result += "Start+Stop";
break;
default:
result += "? (";
result += getTimer();
result += ")\n";
}
return result;
}
+60 -16
View File
@@ -5,6 +5,11 @@
#define __STDC_LIMIT_MACROS
#include <stdint.h>
#ifndef UNIT_TEST
#include <Arduino.h>
#else
#include <string>
#endif
#include "IRremoteESP8266.h"
#include "IRsend.h"
@@ -18,27 +23,48 @@
// Mitsubishi (TV) sending & Mitsubishi A/C support added by David Conran
// Constants
#define MITSUBISHI_AC_AUTO 0x20U
#define MITSUBISHI_AC_COOL 0x18U
#define MITSUBISHI_AC_DRY 0x10U
#define MITSUBISHI_AC_HEAT 0x08U
#define MITSUBISHI_AC_POWER 0x20U
#define MITSUBISHI_AC_FAN_AUTO 0U
#define MITSUBISHI_AC_FAN_MAX 5U
#define MITSUBISHI_AC_FAN_REAL_MAX 4U
#define MITSUBISHI_AC_FAN_SILENT 6U
#define MITSUBISHI_AC_MIN_TEMP 16U // 16C
#define MITSUBISHI_AC_MAX_TEMP 31U // 31C
#define MITSUBISHI_AC_VANE_AUTO 0U
#define MITSUBISHI_AC_VANE_AUTO_MOVE 7U
const uint8_t kMitsubishiAcAuto = 0x20;
const uint8_t kMitsubishiAcCool = 0x18;
const uint8_t kMitsubishiAcDry = 0x10;
const uint8_t kMitsubishiAcHeat = 0x08;
const uint8_t kMitsubishiAcPower = 0x20;
const uint8_t kMitsubishiAcFanAuto = 0;
const uint8_t kMitsubishiAcFanMax = 5;
const uint8_t kMitsubishiAcFanRealMax = 4;
const uint8_t kMitsubishiAcFanSilent = 6;
const uint8_t kMitsubishiAcMinTemp = 16; // 16C
const uint8_t kMitsubishiAcMaxTemp = 31; // 31C
const uint8_t kMitsubishiAcVaneAuto = 0;
const uint8_t kMitsubishiAcVaneAutoMove = 7;
const uint8_t kMitsubishiAcNoTimer = 0;
const uint8_t kMitsubishiAcStartTimer = 5;
const uint8_t kMitsubishiAcStopTimer = 3;
const uint8_t kMitsubishiAcStartStopTimer = 7;
// Legacy defines (Deprecated)
#define MITSUBISHI_AC_VANE_AUTO_MOVE kMitsubishiAcVaneAutoMove
#define MITSUBISHI_AC_VANE_AUTO kMitsubishiAcVaneAuto
#define MITSUBISHI_AC_POWER kMitsubishiAcPower
#define MITSUBISHI_AC_MIN_TEMP kMitsubishiAcMinTemp
#define MITSUBISHI_AC_MAX_TEMP kMitsubishiAcMaxTemp
#define MITSUBISHI_AC_HEAT kMitsubishiAcHeat
#define MITSUBISHI_AC_FAN_SILENT kMitsubishiAcFanSilent
#define MITSUBISHI_AC_FAN_REAL_MAX kMitsubishiAcFanRealMax
#define MITSUBISHI_AC_FAN_MAX kMitsubishiAcFanMax
#define MITSUBISHI_AC_FAN_AUTO kMitsubishiAcFanAuto
#define MITSUBISHI_AC_DRY kMitsubishiAcDry
#define MITSUBISHI_AC_COOL kMitsubishiAcCool
#define MITSUBISHI_AC_AUTO kMitsubishiAcAuto
class IRMitsubishiAC {
public:
explicit IRMitsubishiAC(uint16_t pin);
static uint8_t calculateChecksum(uint8_t* data);
void stateReset();
#if SEND_MITSUBISHI_AC
void send();
void send(const uint16_t repeat = kMitsubishiACMinRepeat);
#endif // SEND_MITSUBISHI_AC
void begin();
void on();
@@ -54,12 +80,30 @@ class IRMitsubishiAC {
void setVane(uint8_t mode);
uint8_t getVane();
uint8_t* getRaw();
void setRaw(uint8_t* data);
uint8_t getClock();
void setClock(uint8_t clock);
uint8_t getStartClock();
void setStartClock(uint8_t clock);
uint8_t getStopClock();
void setStopClock(uint8_t clock);
uint8_t getTimer();
void setTimer(uint8_t timer);
#ifdef ARDUINO
String toString();
#else
std::string toString();
#endif
private:
uint8_t remote_state[MITSUBISHI_AC_STATE_LENGTH];
#ifdef ARDUINO
String timeToString(uint64_t time);
#else
std::string timeToString(uint64_t time);
#endif
uint8_t remote_state[kMitsubishiACStateLength];
void checksum();
IRsend _irsend;
};
#endif // IR_MITSUBISHI_H_
+36 -67
View File
@@ -2,6 +2,7 @@
// Copyright 2017 David Conran
#define __STDC_LIMIT_MACROS
#include "ir_NEC.h"
#include <stdint.h>
#include <algorithm>
#include "IRrecv.h"
@@ -16,39 +17,13 @@
// NEC originally added from https://github.com/shirriff/Arduino-IRremote/
// Constants
// Ref:
// http://www.sbprojects.com/knowledge/ir/nec.php
#define NEC_TICK 560U
#define NEC_HDR_MARK_TICKS 16U
#define NEC_HDR_MARK (NEC_HDR_MARK_TICKS * NEC_TICK)
#define NEC_HDR_SPACE_TICKS 8U
#define NEC_HDR_SPACE (NEC_HDR_SPACE_TICKS * NEC_TICK)
#define NEC_BIT_MARK_TICKS 1U
#define NEC_BIT_MARK (NEC_BIT_MARK_TICKS * NEC_TICK)
#define NEC_ONE_SPACE_TICKS 3U
#define NEC_ONE_SPACE (NEC_TICK * NEC_ONE_SPACE_TICKS)
#define NEC_ZERO_SPACE_TICKS 1U
#define NEC_ZERO_SPACE (NEC_TICK * NEC_ZERO_SPACE_TICKS)
#define NEC_RPT_SPACE_TICKS 4U
#define NEC_RPT_SPACE (NEC_RPT_SPACE_TICKS * NEC_TICK)
#define NEC_RPT_LENGTH 4U
#define NEC_MIN_COMMAND_LENGTH_TICKS 193U
#define NEC_MIN_COMMAND_LENGTH (NEC_MIN_COMMAND_LENGTH_TICKS * NEC_TICK)
#define NEC_MIN_GAP (NEC_MIN_COMMAND_LENGTH - \
(NEC_HDR_MARK + NEC_HDR_SPACE + NEC_BITS * (NEC_BIT_MARK + NEC_ONE_SPACE) \
+ NEC_BIT_MARK))
#define NEC_MIN_GAP_TICKS (NEC_MIN_COMMAND_LENGTH_TICKS - \
(NEC_HDR_MARK_TICKS + NEC_HDR_SPACE_TICKS + \
NEC_BITS * (NEC_BIT_MARK_TICKS + NEC_ONE_SPACE_TICKS) + \
NEC_BIT_MARK_TICKS))
#if (SEND_NEC || SEND_SHERWOOD || SEND_AIWA_RC_T501 || SEND_SANYO)
#if (SEND_NEC || SEND_SHERWOOD || SEND_AIWA_RC_T501 || SEND_SANYO || \
SEND_PIONEER)
// Send a raw NEC(Renesas) formatted message.
//
// Args:
// data: The message to be sent.
// nbits: The number of bits of the message to be sent. Typically NEC_BITS.
// nbits: The number of bits of the message to be sent. Typically kNECBits.
// repeat: The number of times the command is to be repeated.
//
// Status: STABLE / Known working.
@@ -56,18 +31,15 @@
// Ref:
// http://www.sbprojects.com/knowledge/ir/nec.php
void IRsend::sendNEC(uint64_t data, uint16_t nbits, uint16_t repeat) {
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,
sendGeneric(kNecHdrMark, kNecHdrSpace, kNecBitMark, kNecOneSpace, kNecBitMark,
kNecZeroSpace, kNecBitMark, kNecMinGap, kNecMinCommandLength,
data, nbits, 38, true, 0, // Repeats are handled later.
33);
// Optional command repeat sequence.
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.
sendGeneric(kNecHdrMark, kNecRptSpace, 0, 0, 0, 0, // No actual data sent.
kNecBitMark, kNecMinGap, kNecMinCommandLength, 0,
0, // No data to be sent.
38, true, repeat - 1, // We've already sent a one message.
33);
}
@@ -87,8 +59,8 @@ uint32_t IRsend::encodeNEC(uint16_t address, uint16_t command) {
command &= 0xFF; // We only want the least significant byte of command.
// sendNEC() sends MSB first, but protocol says this is LSB first.
command = reverseBits(command, 8);
command = (command << 8) + (command ^ 0xFF); // Calculate the new command.
if (address > 0xFF) { // Is it Extended NEC?
command = (command << 8) + (command ^ 0xFF); // Calculate the new command.
if (address > 0xFF) { // Is it Extended NEC?
address = reverseBits(address, 16);
return ((address << 16) + command); // Extended.
} else {
@@ -103,7 +75,7 @@ uint32_t IRsend::encodeNEC(uint16_t address, uint16_t command) {
//
// Args:
// results: Ptr to the data to decode and where to store the decode result.
// nbits: The number of data bits to expect. Typically NEC_BITS.
// nbits: The number of data bits to expect. Typically kNECBits.
// strict: Flag indicating if we should perform strict matching.
// Returns:
// boolean: True if it can decode it, false if it can't.
@@ -112,34 +84,33 @@ uint32_t IRsend::encodeNEC(uint16_t address, uint16_t command) {
//
// Notes:
// NEC protocol has three varients/forms.
// Normal: a 8 bit address & a 8 bit command in 32 bit data form.
// Normal: an 8 bit address & an 8 bit command in 32 bit data form.
// i.e. address + inverted(address) + command + inverted(command)
// Extended: a 16 bit address & a 8 bit command in 32 bit data form.
// Extended: a 16 bit address & an 8 bit command in 32 bit data form.
// i.e. address + command + inverted(command)
// Repeat: a 0-bit code. i.e. No data bits. Just the header + footer.
//
// Ref:
// http://www.sbprojects.com/knowledge/ir/nec.php
bool IRrecv::decodeNEC(decode_results *results, uint16_t nbits, bool strict) {
if (results->rawlen < 2 * nbits + HEADER + FOOTER - 1 &&
results->rawlen != NEC_RPT_LENGTH)
if (results->rawlen < 2 * nbits + kHeader + kFooter - 1 &&
results->rawlen != kNecRptLength)
return false; // Can't possibly be a valid NEC message.
if (strict && nbits != NEC_BITS)
if (strict && nbits != kNECBits)
return false; // Not strictly an NEC message.
uint64_t data = 0;
uint16_t offset = OFFSET_START;
uint16_t offset = kStartOffset;
// Header
if (!matchMark(results->rawbuf[offset], NEC_HDR_MARK)) return false;
if (!matchMark(results->rawbuf[offset], kNecHdrMark)) return false;
// Calculate how long the lowest tick time is based on the header mark.
uint32_t mark_tick = results->rawbuf[offset++] * RAWTICK /
NEC_HDR_MARK_TICKS;
uint32_t mark_tick = results->rawbuf[offset++] * kRawTick / kNecHdrMarkTicks;
// Check if it is a repeat code.
if (results->rawlen == NEC_RPT_LENGTH &&
matchSpace(results->rawbuf[offset], NEC_RPT_SPACE) &&
matchMark(results->rawbuf[offset + 1], NEC_BIT_MARK_TICKS * mark_tick)) {
results->value = REPEAT;
if (results->rawlen == kNecRptLength &&
matchSpace(results->rawbuf[offset], kNecRptSpace) &&
matchMark(results->rawbuf[offset + 1], kNecBitMarkTicks * mark_tick)) {
results->value = kRepeat;
results->decode_type = NEC;
results->bits = 0;
results->address = 0;
@@ -149,34 +120,32 @@ bool IRrecv::decodeNEC(decode_results *results, uint16_t nbits, bool strict) {
}
// Header (cont.)
if (!matchSpace(results->rawbuf[offset], NEC_HDR_SPACE)) return false;
if (!matchSpace(results->rawbuf[offset], kNecHdrSpace)) return false;
// Calculate how long the common tick time is based on the header space.
uint32_t space_tick = results->rawbuf[offset++] * RAWTICK /
NEC_HDR_SPACE_TICKS;
uint32_t space_tick =
results->rawbuf[offset++] * kRawTick / kNecHdrSpaceTicks;
// Data
match_result_t data_result = matchData(&(results->rawbuf[offset]), nbits,
NEC_BIT_MARK_TICKS * mark_tick,
NEC_ONE_SPACE_TICKS * space_tick,
NEC_BIT_MARK_TICKS * mark_tick,
NEC_ZERO_SPACE_TICKS * space_tick);
match_result_t data_result =
matchData(&(results->rawbuf[offset]), nbits, kNecBitMarkTicks * mark_tick,
kNecOneSpaceTicks * space_tick, kNecBitMarkTicks * mark_tick,
kNecZeroSpaceTicks * space_tick);
if (data_result.success == false) return false;
data = data_result.data;
offset += data_result.used;
// Footer
if (!matchMark(results->rawbuf[offset++], NEC_BIT_MARK_TICKS * mark_tick))
return false;
if (!matchMark(results->rawbuf[offset++], kNecBitMarkTicks * mark_tick))
return false;
if (offset < results->rawlen &&
!matchAtLeast(results->rawbuf[offset], NEC_MIN_GAP_TICKS * space_tick))
!matchAtLeast(results->rawbuf[offset], kNecMinGapTicks * space_tick))
return false;
// Compliance
// Calculate command and optionally enforce integrity checking.
uint8_t command = (data & 0xFF00) >> 8;
uint8_t command = (data & 0xFF00) >> 8;
// Command is sent twice, once as plain and then inverted.
if ((command ^ 0xFF) != (data & 0xFF)) {
if (strict)
return false; // Command integrity failed.
if (strict) return false; // Command integrity failed.
command = 0; // The command value isn't valid, so default to zero.
}
+46
View File
@@ -0,0 +1,46 @@
// Copyright 2009 Ken Shirriff
// Copyright 2017, 2018 David Conran
#ifndef IR_NEC_H_
#define IR_NEC_H_
#include <stdint.h>
#include "IRremoteESP8266.h"
// N N EEEEE CCCC
// NN N E C
// N N N EEE C
// N NN E C
// N N EEEEE CCCC
// NEC originally added from https://github.com/shirriff/Arduino-IRremote/
// Constants
// Ref:
// http://www.sbprojects.com/knowledge/ir/nec.php
const uint16_t kNecTick = 560;
const uint16_t kNecHdrMarkTicks = 16;
const uint16_t kNecHdrMark = kNecHdrMarkTicks * kNecTick;
const uint16_t kNecHdrSpaceTicks = 8;
const uint16_t kNecHdrSpace = kNecHdrSpaceTicks * kNecTick;
const uint16_t kNecBitMarkTicks = 1;
const uint16_t kNecBitMark = kNecBitMarkTicks * kNecTick;
const uint16_t kNecOneSpaceTicks = 3;
const uint16_t kNecOneSpace = kNecOneSpaceTicks * kNecTick;
const uint16_t kNecZeroSpaceTicks = 1;
const uint16_t kNecZeroSpace = kNecZeroSpaceTicks * kNecTick;
const uint16_t kNecRptSpaceTicks = 4;
const uint16_t kNecRptSpace = kNecRptSpaceTicks * kNecTick;
const uint16_t kNecRptLength = 4;
const uint16_t kNecMinCommandLengthTicks = 193;
const uint32_t kNecMinCommandLength = kNecMinCommandLengthTicks * kNecTick;
const uint32_t kNecMinGap =
kNecMinCommandLength -
(kNecHdrMark + kNecHdrSpace + kNECBits * (kNecBitMark + kNecOneSpace) +
kNecBitMark);
const uint16_t kNecMinGapTicks =
kNecMinCommandLengthTicks -
(kNecHdrMarkTicks + kNecHdrSpaceTicks +
kNECBits * (kNecBitMarkTicks + kNecOneSpaceTicks) + kNecBitMarkTicks);
#endif // IR_NEC_H_
+31 -37
View File
@@ -15,38 +15,35 @@
// 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)
const uint16_t kNikaiTick = 500;
const uint16_t kNikaiHdrMarkTicks = 8;
const uint16_t kNikaiHdrMark = kNikaiHdrMarkTicks * kNikaiTick;
const uint16_t kNikaiHdrSpaceTicks = 8;
const uint16_t kNikaiHdrSpace = kNikaiHdrSpaceTicks * kNikaiTick;
const uint16_t kNikaiBitMarkTicks = 1;
const uint16_t kNikaiBitMark = kNikaiBitMarkTicks * kNikaiTick;
const uint16_t kNikaiOneSpaceTicks = 2;
const uint16_t kNikaiOneSpace = kNikaiOneSpaceTicks * kNikaiTick;
const uint16_t kNikaiZeroSpaceTicks = 4;
const uint16_t kNikaiZeroSpace = kNikaiZeroSpaceTicks * kNikaiTick;
const uint16_t kNikaiMinGapTicks = 17;
const uint16_t kNikaiMinGap = kNikaiMinGapTicks * kNikaiTick;
#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.
// nbits: The bit size of the message being sent. typically kNikaiBits.
// 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);
sendGeneric(kNikaiHdrMark, kNikaiHdrSpace, kNikaiBitMark, kNikaiOneSpace,
kNikaiBitMark, kNikaiZeroSpace, kNikaiBitMark, kNikaiMinGap, data,
nbits, 38, true, repeat, 33);
}
#endif
@@ -56,7 +53,7 @@ void IRsend::sendNikai(uint64_t data, uint16_t nbits, uint16_t repeat) {
// 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.
// Typically kNikaiBits.
// strict: Flag to indicate if we strictly adhere to the specification.
// Returns:
// boolean: True if it can decode it, false if it can't.
@@ -64,37 +61,34 @@ void IRsend::sendNikai(uint64_t data, uint16_t nbits, uint16_t repeat) {
// Status: STABLE / Working.
//
bool IRrecv::decodeNikai(decode_results *results, uint16_t nbits, bool strict) {
if (results->rawlen < 2 * nbits + HEADER + FOOTER - 1)
if (results->rawlen < 2 * nbits + kHeader + kFooter - 1)
return false; // Can't possibly be a valid Nikai message.
if (strict && nbits != NIKAI_BITS)
if (strict && nbits != kNikaiBits)
return false; // We expect Nikai to be a certain sized message.
uint64_t data = 0;
uint16_t offset = OFFSET_START;
uint16_t offset = kStartOffset;
// Header
if (!matchMark(results->rawbuf[offset], NIKAI_HDR_MARK)) return false;
if (!matchMark(results->rawbuf[offset], kNikaiHdrMark)) 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;
uint32_t m_tick = results->rawbuf[offset++] * kRawTick / kNikaiHdrMarkTicks;
if (!matchSpace(results->rawbuf[offset], kNikaiHdrSpace)) 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;
uint32_t s_tick = results->rawbuf[offset++] * kRawTick / kNikaiHdrSpaceTicks;
// 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);
match_result_t data_result =
matchData(&(results->rawbuf[offset]), nbits, kNikaiBitMarkTicks * m_tick,
kNikaiOneSpaceTicks * s_tick, kNikaiBitMarkTicks * m_tick,
kNikaiZeroSpaceTicks * 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))
if (!matchMark(results->rawbuf[offset++], kNikaiBitMarkTicks * m_tick))
return false;
if (offset < results->rawlen &&
!matchAtLeast(results->rawbuf[offset], NIKAI_MIN_GAP_TICKS * s_tick))
!matchAtLeast(results->rawbuf[offset], kNikaiMinGapTicks * s_tick))
return false;
// Compliance
+737 -57
View File
@@ -1,7 +1,11 @@
// Copyright 2015 Kristian Lauszus
// Copyright 2017 David Conran
// Copyright 2017, 2018 David Conran
#include "ir_Panasonic.h"
#include <algorithm>
#ifndef ARDUINO
#include <string>
#endif
#include "IRrecv.h"
#include "IRsend.h"
#include "IRutils.h"
@@ -15,36 +19,58 @@
// Panasonic protocol originally added by Kristian Lauszus from:
// https://github.com/z3t0/Arduino-IRremote
// (Thanks to zenwheel and other people at the original blog post)
//
// Panasonic A/C support add by crankyoldgit but heavily influenced by:
// https://github.com/ToniA/ESPEasy/blob/HeatpumpIR/lib/HeatpumpIR/PanasonicHeatpumpIR.cpp
// Panasonic A/C Clock & Timer support:
// Reverse Engineering by MikkelTb
// Code by crankyoldgit
// Panasonic A/C models supported:
// A/C Series/models:
// JKE, LKE, DKE, CKP, & NKE series. (In theory)
// CS-YW9MKD (confirmed)
// CS-ME14CKPG / CS-ME12CKPG / CS-ME10CKPG
// A/C Remotes:
// A75C3747 (confirmed)
// A75C3704
// A75C2311 (CKP)
// Constants
// Ref:
// http://www.remotecentral.com/cgi-bin/mboard/rc-pronto/thread.cgi?26152
#define PANASONIC_TICK 432U
#define PANASONIC_HDR_MARK_TICKS 8U
#define PANASONIC_HDR_MARK (PANASONIC_HDR_MARK_TICKS * PANASONIC_TICK)
#define PANASONIC_HDR_SPACE_TICKS 4U
#define PANASONIC_HDR_SPACE (PANASONIC_HDR_SPACE_TICKS * PANASONIC_TICK)
#define PANASONIC_BIT_MARK_TICKS 1U
#define PANASONIC_BIT_MARK (PANASONIC_BIT_MARK_TICKS * PANASONIC_TICK)
#define PANASONIC_ONE_SPACE_TICKS 3U
#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 378UL
#define PANASONIC_MIN_COMMAND_LENGTH (PANASONIC_MIN_COMMAND_LENGTH_TICKS * \
PANASONIC_TICK)
#define PANASONIC_END_GAP 5000U // See issue #245
#define PANASONIC_MIN_GAP_TICKS (PANASONIC_MIN_COMMAND_LENGTH_TICKS - \
(PANASONIC_HDR_MARK_TICKS + PANASONIC_HDR_SPACE_TICKS + \
PANASONIC_BITS * (PANASONIC_BIT_MARK_TICKS + PANASONIC_ONE_SPACE_TICKS) + \
PANASONIC_BIT_MARK_TICKS))
#define PANASONIC_MIN_GAP ((uint32_t)(PANASONIC_MIN_GAP_TICKS * PANASONIC_TICK))
const uint16_t kPanasonicTick = 432;
const uint16_t kPanasonicHdrMarkTicks = 8;
const uint16_t kPanasonicHdrMark = kPanasonicHdrMarkTicks * kPanasonicTick;
const uint16_t kPanasonicHdrSpaceTicks = 4;
const uint16_t kPanasonicHdrSpace = kPanasonicHdrSpaceTicks * kPanasonicTick;
const uint16_t kPanasonicBitMarkTicks = 1;
const uint16_t kPanasonicBitMark = kPanasonicBitMarkTicks * kPanasonicTick;
const uint16_t kPanasonicOneSpaceTicks = 3;
const uint16_t kPanasonicOneSpace = kPanasonicOneSpaceTicks * kPanasonicTick;
const uint16_t kPanasonicZeroSpaceTicks = 1;
const uint16_t kPanasonicZeroSpace = kPanasonicZeroSpaceTicks * kPanasonicTick;
const uint16_t kPanasonicMinCommandLengthTicks = 378;
const uint32_t kPanasonicMinCommandLength =
kPanasonicMinCommandLengthTicks * kPanasonicTick;
const uint16_t kPanasonicEndGap = 5000; // See issue #245
const uint16_t kPanasonicMinGapTicks =
kPanasonicMinCommandLengthTicks -
(kPanasonicHdrMarkTicks + kPanasonicHdrSpaceTicks +
kPanasonicBits * (kPanasonicBitMarkTicks + kPanasonicOneSpaceTicks) +
kPanasonicBitMarkTicks);
const uint32_t kPanasonicMinGap = kPanasonicMinGapTicks * kPanasonicTick;
const uint16_t kPanasonicAcSectionGap = 10000;
const uint16_t kPanasonicAcSection1Length = 8;
const uint32_t kPanasonicAcMessageGap = 100000; // A complete guess.
#if (SEND_PANASONIC || SEND_DENON)
// Send a Panasonic formatted message.
//
// Args:
// data: The message to be sent.
// nbits: The number of bits of the message to be sent. (PANASONIC_BITS).
// nbits: The number of bits of the message to be sent. (kPanasonicBits).
// repeat: The number of times the command is to be repeated.
//
// Status: BETA / Should be working.
@@ -52,12 +78,10 @@
// Note:
// This protocol is a modified version of Kaseikyo.
void IRsend::sendPanasonic64(uint64_t data, uint16_t nbits, uint16_t repeat) {
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);
sendGeneric(kPanasonicHdrMark, kPanasonicHdrSpace, kPanasonicBitMark,
kPanasonicOneSpace, kPanasonicBitMark, kPanasonicZeroSpace,
kPanasonicBitMark, kPanasonicMinGap, kPanasonicMinCommandLength,
data, nbits, kPanasonicFreq, true, repeat, 50);
}
// Send a Panasonic formatted message.
@@ -65,7 +89,7 @@ void IRsend::sendPanasonic64(uint64_t data, uint16_t nbits, uint16_t repeat) {
// Args:
// address: The manufacturer code.
// data: The data portion to be sent.
// nbits: The number of bits of the message to be sent. (PANASONIC_BITS).
// nbits: The number of bits of the message to be sent. (kPanasonicBits).
// repeat: The number of times the command is to be repeated.
//
// Status: STABLE.
@@ -74,7 +98,7 @@ void IRsend::sendPanasonic64(uint64_t data, uint16_t nbits, uint16_t repeat) {
// This protocol is a modified version of Kaseikyo.
void IRsend::sendPanasonic(uint16_t address, uint32_t data, uint16_t nbits,
uint16_t repeat) {
sendPanasonic64(((uint64_t) address << 32) | (uint64_t) data, nbits, repeat);
sendPanasonic64(((uint64_t)address << 32) | (uint64_t)data, nbits, repeat);
}
// Calculate the raw Panasonic data based on device, subdevice, & function.
@@ -93,16 +117,11 @@ void IRsend::sendPanasonic(uint16_t address, uint32_t data, uint16_t nbits,
// Panasonic 48-bit protocol is a modified version of Kaseikyo.
// Ref:
// http://www.remotecentral.com/cgi-bin/mboard/rc-pronto/thread.cgi?2615
uint64_t IRsend::encodePanasonic(uint16_t manufacturer,
uint8_t device,
uint8_t subdevice,
uint8_t function) {
uint64_t IRsend::encodePanasonic(uint16_t manufacturer, uint8_t device,
uint8_t subdevice, uint8_t function) {
uint8_t checksum = device ^ subdevice ^ function;
return (((uint64_t) manufacturer << 32) |
((uint64_t) device << 24) |
((uint64_t) subdevice << 16) |
((uint64_t) function << 8) |
checksum);
return (((uint64_t)manufacturer << 32) | ((uint64_t)device << 24) |
((uint64_t)subdevice << 16) | ((uint64_t)function << 8) | checksum);
}
#endif // (SEND_PANASONIC || SEND_DENON)
@@ -124,39 +143,38 @@ uint64_t IRsend::encodePanasonic(uint16_t manufacturer,
// http://www.hifi-remote.com/wiki/index.php?title=Panasonic
bool IRrecv::decodePanasonic(decode_results *results, uint16_t nbits,
bool strict, uint32_t manufacturer) {
if (results->rawlen < 2 * nbits + HEADER + FOOTER - 1)
if (results->rawlen < 2 * nbits + kHeader + kFooter - 1)
return false; // Not enough entries to be a Panasonic message.
if (strict && nbits != PANASONIC_BITS)
if (strict && nbits != kPanasonicBits)
return false; // Request is out of spec.
uint64_t data = 0;
uint16_t offset = OFFSET_START;
uint16_t offset = kStartOffset;
// Header
if (!matchMark(results->rawbuf[offset], PANASONIC_HDR_MARK)) return false;
if (!matchMark(results->rawbuf[offset], kPanasonicHdrMark)) return false;
// Calculate how long the common tick time is based on the header mark.
uint32_t m_tick = results->rawbuf[offset++] * RAWTICK /
PANASONIC_HDR_MARK_TICKS;
if (!matchSpace(results->rawbuf[offset], PANASONIC_HDR_SPACE)) return false;
uint32_t m_tick =
results->rawbuf[offset++] * kRawTick / kPanasonicHdrMarkTicks;
if (!matchSpace(results->rawbuf[offset], kPanasonicHdrSpace)) return false;
// Calculate how long the common tick time is based on the header space.
uint32_t s_tick = results->rawbuf[offset++] * RAWTICK /
PANASONIC_HDR_SPACE_TICKS;
uint32_t s_tick =
results->rawbuf[offset++] * kRawTick / kPanasonicHdrSpaceTicks;
// Data
match_result_t data_result = matchData(&(results->rawbuf[offset]), nbits,
PANASONIC_BIT_MARK_TICKS * m_tick,
PANASONIC_ONE_SPACE_TICKS * s_tick,
PANASONIC_BIT_MARK_TICKS * m_tick,
PANASONIC_ZERO_SPACE_TICKS * s_tick);
match_result_t data_result = matchData(
&(results->rawbuf[offset]), nbits, kPanasonicBitMarkTicks * m_tick,
kPanasonicOneSpaceTicks * s_tick, kPanasonicBitMarkTicks * m_tick,
kPanasonicZeroSpaceTicks * s_tick);
if (data_result.success == false) return false;
data = data_result.data;
offset += data_result.used;
// Footer
if (!match(results->rawbuf[offset++], PANASONIC_BIT_MARK_TICKS * m_tick))
if (!match(results->rawbuf[offset++], kPanasonicBitMarkTicks * m_tick))
return false;
if (offset < results->rawlen &&
!matchAtLeast(results->rawbuf[offset], PANASONIC_END_GAP))
!matchAtLeast(results->rawbuf[offset], kPanasonicEndGap))
return false;
// Compliance
@@ -168,8 +186,7 @@ bool IRrecv::decodePanasonic(decode_results *results, uint16_t nbits,
// Verify the checksum.
uint8_t checksumOrig = data & 0xFF;
uint8_t checksumCalc = ((data >> 24) ^ (data >> 16) ^ (data >> 8)) & 0xFF;
if (checksumOrig != checksumCalc)
return false;
if (checksumOrig != checksumCalc) return false;
}
// Success
@@ -181,3 +198,666 @@ bool IRrecv::decodePanasonic(decode_results *results, uint16_t nbits,
return true;
}
#endif // (DECODE_PANASONIC || DECODE_DENON)
#if SEND_PANASONIC_AC
// Send a Panasonic A/C message.
//
// Args:
// data: Contents of the message to be sent. (Guessing MSBF order)
// nbits: Nr. of bits of data to be sent. Typically kPanasonicAcBits.
// repeat: Nr. of additional times the message is to be sent.
//
// Status: Beta / Appears to work with real device(s).
//:
// Panasonic A/C models supported:
// A/C Series/models:
// JKE, LKE, DKE, & NKE series.
// CS-YW9MKD
// A/C Remotes:
// A75C3747
// A75C3704
//
void IRsend::sendPanasonicAC(uint8_t data[], uint16_t nbytes, uint16_t repeat) {
if (nbytes < kPanasonicAcSection1Length) return;
for (uint16_t r = 0; r <= repeat; r++) {
// First section. (8 bytes)
sendGeneric(kPanasonicHdrMark, kPanasonicHdrSpace, kPanasonicBitMark,
kPanasonicOneSpace, kPanasonicBitMark, kPanasonicZeroSpace,
kPanasonicBitMark, kPanasonicAcSectionGap, data,
kPanasonicAcSection1Length, kPanasonicFreq, false, 0, 50);
// First section. (The rest of the data bytes)
sendGeneric(kPanasonicHdrMark, kPanasonicHdrSpace, kPanasonicBitMark,
kPanasonicOneSpace, kPanasonicBitMark, kPanasonicZeroSpace,
kPanasonicBitMark, kPanasonicAcMessageGap,
data + kPanasonicAcSection1Length,
nbytes - kPanasonicAcSection1Length, kPanasonicFreq, false, 0,
50);
}
}
#endif // SEND_PANASONIC_AC
IRPanasonicAc::IRPanasonicAc(uint16_t pin) : _irsend(pin) { stateReset(); }
void IRPanasonicAc::stateReset() {
for (uint8_t i = 0; i < kPanasonicAcStateLength; i++)
remote_state[i] = kPanasonicKnownGoodState[i];
_temp = 25; // An initial saved desired temp. Completely made up.
_swingh = kPanasonicAcSwingHMiddle; // A similar made up value for H Swing.
}
void IRPanasonicAc::begin() { _irsend.begin(); }
// 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 IRPanasonicAc::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, kPanasonicAcChecksumInit));
}
uint8_t IRPanasonicAc::calcChecksum(uint8_t state[], const uint16_t length) {
return sumBytes(state, length - 1, kPanasonicAcChecksumInit);
}
void IRPanasonicAc::fixChecksum(const uint16_t length) {
remote_state[length - 1] = calcChecksum(remote_state, length);
}
#if SEND_PANASONIC_AC
void IRPanasonicAc::send(const uint16_t repeat) {
fixChecksum();
_irsend.sendPanasonicAC(remote_state, kPanasonicAcStateLength, repeat);
}
#endif // SEND_PANASONIC_AC
void IRPanasonicAc::setModel(const panasonic_ac_remote_model_t model) {
switch (model) {
case kPanasonicDke:
case kPanasonicJke:
case kPanasonicLke:
case kPanasonicNke:
case kPanasonicCkp:
break;
default: // Only proceed if we know what to do.
return;
}
// clear & set the various bits and bytes.
remote_state[13] &= 0xF0;
remote_state[17] = 0x00;
remote_state[21] &= 0b11101111;
remote_state[23] = 0x81;
remote_state[25] = 0x00;
switch (model) {
case kPanasonicLke:
remote_state[13] |= 0x02;
remote_state[17] = 0x06;
break;
case kPanasonicDke:
remote_state[23] = 0x01;
remote_state[25] = 0x06;
// Has to be done last as setSwingHorizontal has model check built-in
setSwingHorizontal(_swingh);
break;
case kPanasonicNke:
remote_state[17] = 0x06;
break;
case kPanasonicJke:
break;
case kPanasonicCkp:
remote_state[21] |= 0x10;
remote_state[23] = 0x01;
default:
break;
}
}
panasonic_ac_remote_model_t IRPanasonicAc::getModel() {
if (remote_state[17] == 0x00) {
if ((remote_state[21] & 0x10) && (remote_state[23] & 0x01))
return kPanasonicCkp;
if (remote_state[23] & 0x80) return kPanasonicJke;
}
if (remote_state[17] == 0x06 && (remote_state[13] & 0x0F) == 0x02)
return kPanasonicLke;
if (remote_state[23] == 0x01) return kPanasonicDke;
if (remote_state[17] == 0x06) return kPanasonicNke;
return kPanasonicUnknown;
}
uint8_t *IRPanasonicAc::getRaw() {
fixChecksum();
return remote_state;
}
void IRPanasonicAc::setRaw(const uint8_t state[]) {
for (uint8_t i = 0; i < kPanasonicAcStateLength; i++) {
remote_state[i] = state[i];
}
}
// Control the power state of the A/C unit.
//
// For CKP models, the remote has no memory of the power state the A/C unit
// should be in. For those models setting this on/true will toggle the power
// state of the Panasonic A/C unit with the next meessage.
// e.g. If the A/C unit is already on, setPower(true) will turn it off.
// If the A/C unit is already off, setPower(true) will turn it on.
// setPower(false) will leave the A/C power state as it was.
//
// For all other models, setPower(true) should set the internal state to
// turn it on, and setPower(false) should turn it off.
void IRPanasonicAc::setPower(const bool state) {
if (state)
on();
else
off();
}
// Return the A/C power state of the remote.
// Except for CKP models, where it returns if the power state will be toggled
// on the A/C unit when the next message is sent.
bool IRPanasonicAc::getPower() {
return (remote_state[13] & kPanasonicAcPower) == kPanasonicAcPower;
}
void IRPanasonicAc::on() { remote_state[13] |= kPanasonicAcPower; }
void IRPanasonicAc::off() { remote_state[13] &= ~kPanasonicAcPower; }
uint8_t IRPanasonicAc::getMode() { return remote_state[13] >> 4; }
void IRPanasonicAc::setMode(const uint8_t desired) {
uint8_t mode = kPanasonicAcAuto; // Default to Auto mode.
switch (desired) {
case kPanasonicAcFan:
// Allegedly Fan mode has a temperature of 27.
setTemp(kPanasonicAcFanModeTemp, false);
mode = desired;
break;
case kPanasonicAcAuto:
case kPanasonicAcCool:
case kPanasonicAcHeat:
case kPanasonicAcDry:
mode = desired;
// Set the temp to the saved temp, just incase our previous mode was Fan.
setTemp(_temp);
break;
}
remote_state[13] &= 0x0F; // Clear the previous mode bits.
remote_state[13] |= mode << 4;
}
uint8_t IRPanasonicAc::getTemp() { return remote_state[14] >> 1; }
// Set the desitred temperature in Celcius.
// Args:
// celsius: The temperature to set the A/C unit to.
// remember: A boolean flag for the class to remember the temperature.
//
// Automatically safely limits the temp to the operating range supported.
void IRPanasonicAc::setTemp(const uint8_t celsius, const bool remember) {
uint8_t temperature;
temperature = std::max(celsius, kPanasonicAcMinTemp);
temperature = std::min(temperature, kPanasonicAcMaxTemp);
remote_state[14] = temperature << 1;
if (remember) _temp = temperature;
}
uint8_t IRPanasonicAc::getSwingVertical() { return remote_state[16] & 0x0F; }
void IRPanasonicAc::setSwingVertical(const uint8_t desired_elevation) {
uint8_t elevation = desired_elevation;
if (elevation != kPanasonicAcSwingVAuto) {
elevation = std::max(elevation, kPanasonicAcSwingVUp);
elevation = std::min(elevation, kPanasonicAcSwingVDown);
}
remote_state[16] &= 0xF0;
remote_state[16] |= elevation;
}
uint8_t IRPanasonicAc::getSwingHorizontal() { return remote_state[17]; }
void IRPanasonicAc::setSwingHorizontal(const uint8_t desired_direction) {
switch (desired_direction) {
case kPanasonicAcSwingHAuto:
case kPanasonicAcSwingHMiddle:
case kPanasonicAcSwingHFullLeft:
case kPanasonicAcSwingHLeft:
case kPanasonicAcSwingHRight:
case kPanasonicAcSwingHFullRight:
break;
default: // Ignore anything that isn't valid.
return;
}
_swingh = desired_direction; // Store the direction for later.
uint8_t direction = desired_direction;
switch (getModel()) {
case kPanasonicDke:
break;
case kPanasonicNke:
case kPanasonicLke:
direction = kPanasonicAcSwingHMiddle;
break;
default: // Ignore everything else.
return;
}
remote_state[17] = direction;
}
void IRPanasonicAc::setFan(const uint8_t speed) {
if (speed <= kPanasonicAcFanMax || speed == kPanasonicAcFanAuto)
remote_state[16] =
(remote_state[16] & 0x0F) | ((speed + kPanasonicAcFanOffset) << 4);
}
uint8_t IRPanasonicAc::getFan() {
return (remote_state[16] >> 4) - kPanasonicAcFanOffset;
}
bool IRPanasonicAc::getQuiet() {
if (getModel() == kPanasonicCkp)
return remote_state[21] & kPanasonicAcQuietCkp;
else
return remote_state[21] & kPanasonicAcQuiet;
}
void IRPanasonicAc::setQuiet(const bool state) {
uint8_t quiet;
if (getModel() == kPanasonicCkp)
quiet = kPanasonicAcQuietCkp;
else
quiet = kPanasonicAcQuiet;
if (state) {
setPowerful(false); // Powerful is mutually exclusive.
remote_state[21] |= quiet;
} else {
remote_state[21] &= ~quiet;
}
}
bool IRPanasonicAc::getPowerful() {
if (getModel() == kPanasonicCkp)
return remote_state[21] & kPanasonicAcPowerfulCkp;
else
return remote_state[21] & kPanasonicAcPowerful;
}
void IRPanasonicAc::setPowerful(const bool state) {
uint8_t powerful;
if (getModel() == kPanasonicCkp)
powerful = kPanasonicAcPowerfulCkp;
else
powerful = kPanasonicAcPowerful;
if (state) {
setQuiet(false); // Quiet is mutually exclusive.
remote_state[21] |= powerful;
} else {
remote_state[21] &= ~powerful;
}
}
uint16_t IRPanasonicAc::encodeTime(const uint8_t hours, const uint8_t mins) {
return std::min(hours, (uint8_t)23) * 60 + std::min(mins, (uint8_t)59);
}
uint16_t IRPanasonicAc::getClock() {
uint16_t result = ((remote_state[25] & 0b00000111) << 8) + remote_state[24];
if (result == kPanasonicAcTimeSpecial) return 0;
return result;
}
void IRPanasonicAc::setClock(const uint16_t mins_since_midnight) {
uint16_t corrected = std::min(mins_since_midnight, kPanasonicAcTimeMax);
if (mins_since_midnight == kPanasonicAcTimeSpecial)
corrected = kPanasonicAcTimeSpecial;
remote_state[24] = corrected & 0xFF;
remote_state[25] &= 0b11111000;
remote_state[25] |= (corrected >> 8);
}
uint16_t IRPanasonicAc::getOnTimer() {
uint16_t result = ((remote_state[19] & 0b00000111) << 8) + remote_state[18];
if (result == kPanasonicAcTimeSpecial) return 0;
return result;
}
void IRPanasonicAc::setOnTimer(const uint16_t mins_since_midnight,
const bool enable) {
// Ensure it's on a 10 minute boundary and no overflow.
uint16_t corrected = std::min(mins_since_midnight, kPanasonicAcTimeMax);
corrected -= corrected % 10;
if (mins_since_midnight == kPanasonicAcTimeSpecial)
corrected = kPanasonicAcTimeSpecial;
if (enable)
remote_state[13] |= kPanasonicAcOnTimer; // Set the Ontimer flag.
else
remote_state[13] &= ~kPanasonicAcOnTimer; // Clear the Ontimer flag.
// Store the time.
remote_state[18] = corrected & 0xFF;
remote_state[19] &= 0b11111000;
remote_state[19] |= (corrected >> 8);
}
void IRPanasonicAc::cancelOnTimer() { setOnTimer(0, false); }
bool IRPanasonicAc::isOnTimerEnabled() {
return remote_state[13] & kPanasonicAcOnTimer;
}
uint16_t IRPanasonicAc::getOffTimer() {
uint16_t result =
((remote_state[20] & 0b01111111) << 4) + (remote_state[19] >> 4);
if (result == kPanasonicAcTimeSpecial) return 0;
return result;
}
void IRPanasonicAc::setOffTimer(const uint16_t mins_since_midnight,
const bool enable) {
// Ensure its on a 10 minute boundary and no overflow.
uint16_t corrected = std::min(mins_since_midnight, kPanasonicAcTimeMax);
corrected -= corrected % 10;
if (mins_since_midnight == kPanasonicAcTimeSpecial)
corrected = kPanasonicAcTimeSpecial;
if (enable)
remote_state[13] |= kPanasonicAcOffTimer; // Set the OffTimer flag.
else
remote_state[13] &= ~kPanasonicAcOffTimer; // Clear the OffTimer flag.
// Store the time.
remote_state[19] &= 0b00001111;
remote_state[19] |= (corrected & 0b00001111) << 4;
remote_state[20] &= 0b10000000;
remote_state[20] |= corrected >> 4;
}
void IRPanasonicAc::cancelOffTimer() { setOffTimer(0, false); }
bool IRPanasonicAc::isOffTimerEnabled() {
return remote_state[13] & kPanasonicAcOffTimer;
}
#ifdef ARDUINO
String IRPanasonicAc::timeToString(const uint16_t mins_since_midnight) {
String result = "";
#else
std::string IRPanasonicAc::timeToString(const uint16_t mins_since_midnight) {
std::string result = "";
#endif // ARDUINO
result += uint64ToString(mins_since_midnight / 60) + ":";
uint8_t mins = mins_since_midnight % 60;
if (mins < 10) result += "0"; // Zero pad the minutes.
return result + uint64ToString(mins);
}
// Convert the internal state into a human readable string.
#ifdef ARDUINO
String IRPanasonicAc::toString() {
String result = "";
#else
std::string IRPanasonicAc::toString() {
std::string result = "";
#endif // ARDUINO
result += "Model: " + uint64ToString(getModel());
switch (getModel()) {
case kPanasonicDke:
result += " (DKE)";
break;
case kPanasonicJke:
result += " (JKE)";
break;
case kPanasonicNke:
result += " (NKE)";
break;
case kPanasonicLke:
result += " (LKE)";
break;
case kPanasonicCkp:
result += " (CKP)";
break;
default:
result += " (UNKNOWN)";
}
result += ", Power: ";
if (getPower())
result += "On";
else
result += "Off";
result += ", Mode: " + uint64ToString(getMode());
switch (getMode()) {
case kPanasonicAcAuto:
result += " (AUTO)";
break;
case kPanasonicAcCool:
result += " (COOL)";
break;
case kPanasonicAcHeat:
result += " (HEAT)";
break;
case kPanasonicAcDry:
result += " (DRY)";
break;
case kPanasonicAcFan:
result += " (FAN)";
break;
default:
result += " (UNKNOWN)";
}
result += ", Temp: " + uint64ToString(getTemp()) + "C";
result += ", Fan: " + uint64ToString(getFan());
switch (getFan()) {
case kPanasonicAcFanAuto:
result += " (AUTO)";
break;
case kPanasonicAcFanMax:
result += " (MAX)";
break;
case kPanasonicAcFanMin:
result += " (MIN)";
break;
default:
result += " (UNKNOWN)";
break;
}
result += ", Swing (Vertical): " + uint64ToString(getSwingVertical());
switch (getSwingVertical()) {
case kPanasonicAcSwingVAuto:
result += " (AUTO)";
break;
case kPanasonicAcSwingVUp:
result += " (Full Up)";
break;
case kPanasonicAcSwingVDown:
result += " (Full Down)";
break;
case 2:
case 3:
case 4:
break;
default:
result += " (UNKNOWN)";
break;
}
switch (getModel()) {
case kPanasonicJke:
case kPanasonicCkp:
break; // No Horizontal Swing support.
default:
result += ", Swing (Horizontal): " + uint64ToString(getSwingHorizontal());
switch (getSwingHorizontal()) {
case kPanasonicAcSwingHAuto:
result += " (AUTO)";
break;
case kPanasonicAcSwingHFullLeft:
result += " (Full Left)";
break;
case kPanasonicAcSwingHLeft:
result += " (Left)";
break;
case kPanasonicAcSwingHMiddle:
result += " (Middle)";
break;
case kPanasonicAcSwingHFullRight:
result += " (Full Right)";
break;
case kPanasonicAcSwingHRight:
result += " (Right)";
break;
default:
result += " (UNKNOWN)";
break;
}
}
result += ", Quiet: ";
if (getQuiet())
result += "On";
else
result += "Off";
result += ", Powerful: ";
if (getPowerful())
result += "On";
else
result += "Off";
result += ", Clock: " + timeToString(getClock());
result += ", On Timer: ";
if (isOnTimerEnabled())
result += timeToString(getOnTimer());
else
result += "Off";
result += ", Off Timer: ";
if (isOffTimerEnabled())
result += timeToString(getOffTimer());
else
result += "Off";
return result;
}
#if DECODE_PANASONIC_AC
// Decode the supplied Panasonic AC 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 kPanasonicAcBits.
// 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 work with real device(s).
//
// Panasonic A/C models supported:
// A/C Series/models:
// JKE, LKE, DKE, & NKE series.
// CS-YW9MKD
// A/C Remotes:
// A75C3747 (Confirmed)
// A75C3704
bool IRrecv::decodePanasonicAC(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 != kPanasonicAcBits && nbits != kPanasonicAcShortBits)
return false; // Not strictly a PANASONIC_AC message.
}
if (results->rawlen <
min_nr_of_messages * (2 * nbits + kHeader + kFooter) - 1)
return false; // Can't possibly be a valid PANASONIC_AC message.
uint16_t dataBitsSoFar = 0;
uint16_t offset = kStartOffset;
match_result_t data_result;
// Header
if (!matchMark(results->rawbuf[offset], kPanasonicHdrMark,
kPanasonicAcTolerance, kPanasonicAcExcess))
return false;
// Calculate how long the common tick time is based on the header mark.
uint32_t m_tick =
results->rawbuf[offset++] * kRawTick / kPanasonicHdrMarkTicks;
if (!matchSpace(results->rawbuf[offset], kPanasonicHdrSpace,
kPanasonicAcTolerance, kPanasonicAcExcess))
return false;
// Calculate how long the common tick time is based on the header space.
uint32_t s_tick =
results->rawbuf[offset++] * kRawTick / kPanasonicHdrSpaceTicks;
uint16_t i = 0;
// Data (Section #1)
// Keep reading bytes until we either run out of section or state to fill.
for (; offset <= results->rawlen - 16 && i < kPanasonicAcSection1Length;
i++, dataBitsSoFar += 8, offset += data_result.used) {
data_result = matchData(
&(results->rawbuf[offset]), 8, kPanasonicBitMarkTicks * m_tick,
kPanasonicOneSpaceTicks * s_tick, kPanasonicBitMarkTicks * m_tick,
kPanasonicZeroSpaceTicks * s_tick, kPanasonicAcTolerance,
kPanasonicAcExcess, false);
if (data_result.success == false) {
DPRINT("DEBUG: offset = ");
DPRINTLN(offset + data_result.used);
return false; // Fail
}
results->state[i] = data_result.data;
}
// Section footer.
if (!matchMark(results->rawbuf[offset++], kPanasonicBitMarkTicks * m_tick,
kPanasonicAcTolerance, kPanasonicAcExcess))
return false;
if (!matchSpace(results->rawbuf[offset++], kPanasonicAcSectionGap,
kPanasonicAcTolerance, kPanasonicAcExcess))
return false;
// Header.
if (!matchMark(results->rawbuf[offset++], kPanasonicHdrMarkTicks * m_tick,
kPanasonicAcTolerance, kPanasonicAcExcess))
return false;
if (!matchSpace(results->rawbuf[offset++], kPanasonicHdrSpaceTicks * s_tick,
kPanasonicAcTolerance, kPanasonicAcExcess))
return false;
// Data (Section #2)
// Keep reading bytes until we either run out of data.
for (; offset <= results->rawlen - 16 && i < nbits / 8;
i++, dataBitsSoFar += 8, offset += data_result.used) {
data_result = matchData(
&(results->rawbuf[offset]), 8, kPanasonicBitMarkTicks * m_tick,
kPanasonicOneSpaceTicks * s_tick, kPanasonicBitMarkTicks * m_tick,
kPanasonicZeroSpaceTicks * s_tick, kPanasonicAcTolerance,
kPanasonicAcExcess, false);
if (data_result.success == false) {
DPRINT("DEBUG: offset = ");
DPRINTLN(offset + data_result.used);
return false; // Fail
}
results->state[i] = data_result.data;
}
// Message Footer.
if (!matchMark(results->rawbuf[offset++], kPanasonicBitMarkTicks * m_tick,
kPanasonicAcTolerance, kPanasonicAcExcess))
return false;
if (offset <= results->rawlen &&
!matchAtLeast(results->rawbuf[offset++], kPanasonicAcMessageGap))
return false;
// Compliance
if (strict) {
// Check the signatures of the section blocks. They start with 0x02& 0x20.
if (results->state[0] != 0x02 || results->state[1] != 0x20 ||
results->state[8] != 0x02 || results->state[9] != 0x20)
return false;
if (!IRPanasonicAc::validChecksum(results->state, nbits / 8)) return false;
}
// Success
results->decode_type = PANASONIC_AC;
results->bits = nbits;
return true;
}
#endif // DECODE_PANASONIC_AC
+145
View File
@@ -0,0 +1,145 @@
// Copyright 2018 David Conran
#ifndef IR_PANASONIC_H_
#define IR_PANASONIC_H_
#define __STDC_LIMIT_MACROS
#include <stdint.h>
#ifdef ARDUINO
#include <Arduino.h>
#else
#include <string>
#endif
#include "IRremoteESP8266.h"
#include "IRsend.h"
// PPPP AAA N N AAA SSSS OOO N N IIIII CCCC
// P P A A NN N A A S O O NN N I C
// PPPP AAAAA N N N AAAAA SSS O O N N N I C
// P A A N NN A A S O O N NN I C
// P A A N N A A SSSS OOO N N IIIII CCCC
// Panasonic A/C support heavily influenced by:
// https://github.com/ToniA/ESPEasy/blob/HeatpumpIR/lib/HeatpumpIR/PanasonicHeatpumpIR.cpp
// Constants
const uint16_t kPanasonicFreq = 36700;
const uint16_t kPanasonicAcExcess = 0;
// Much higher than usual. See issue #540.
const uint16_t kPanasonicAcTolerance = 40;
const uint8_t kPanasonicAcAuto = 0; // 0b0000
const uint8_t kPanasonicAcDry = 2; // 0b0010
const uint8_t kPanasonicAcCool = 3; // 0b0011
const uint8_t kPanasonicAcHeat = 4; // 0b0010
const uint8_t kPanasonicAcFan = 6; // 0b0110
const uint8_t kPanasonicAcFanMin = 0;
const uint8_t kPanasonicAcFanMax = 4;
const uint8_t kPanasonicAcFanAuto = 7;
const uint8_t kPanasonicAcFanOffset = 3;
const uint8_t kPanasonicAcPower = 1; // 0b1
const uint8_t kPanasonicAcMinTemp = 16; // Celsius
const uint8_t kPanasonicAcMaxTemp = 30; // Celsius
const uint8_t kPanasonicAcFanModeTemp = 27; // Celsius
const uint8_t kPanasonicAcQuiet = 1; // 0b1
const uint8_t kPanasonicAcPowerful = 0x20; // 0b100000
// CKP models have Powerful and Quiet bits swapped.
const uint8_t kPanasonicAcQuietCkp = 0x20; // 0b100000
const uint8_t kPanasonicAcPowerfulCkp = 1; // 0b1
const uint8_t kPanasonicAcSwingVAuto = 0xF;
const uint8_t kPanasonicAcSwingVUp = 0x1;
const uint8_t kPanasonicAcSwingVDown = 0x5;
const uint8_t kPanasonicAcSwingHAuto = 0xD;
const uint8_t kPanasonicAcSwingHMiddle = 0x6;
const uint8_t kPanasonicAcSwingHFullLeft = 0x9;
const uint8_t kPanasonicAcSwingHLeft = 0xA;
const uint8_t kPanasonicAcSwingHRight = 0xB;
const uint8_t kPanasonicAcSwingHFullRight = 0xC;
const uint8_t kPanasonicAcChecksumInit = 0xF4;
const uint8_t kPanasonicAcOnTimer = 0b00000010;
const uint8_t kPanasonicAcOffTimer = 0b00000100;
const uint16_t kPanasonicAcTimeMax = 23 * 60 + 59; // Mins since midnight.
const uint16_t kPanasonicAcTimeSpecial = 0x600;
const uint8_t kPanasonicKnownGoodState[kPanasonicAcStateLength] = {
0x02, 0x20, 0xE0, 0x04, 0x00, 0x00, 0x00, 0x06, 0x02,
0x20, 0xE0, 0x04, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
0x00, 0x0E, 0xE0, 0x00, 0x00, 0x81, 0x00, 0x00, 0x00};
enum panasonic_ac_remote_model_t {
kPanasonicUnknown = 0,
kPanasonicLke = 1,
kPanasonicNke = 2,
kPanasonicDke = 3,
kPanasonicJke = 4,
kPanasonicCkp = 5,
};
class IRPanasonicAc {
public:
explicit IRPanasonicAc(uint16_t pin);
void stateReset();
#if SEND_PANASONIC
void send(const uint16_t repeat = kPanasonicAcDefaultRepeat);
#endif // SEND_PANASONIC
void begin();
void on();
void off();
void setPower(const bool state);
bool getPower();
void setTemp(const uint8_t temp, const bool remember = true);
uint8_t getTemp();
void setFan(const uint8_t fan);
uint8_t getFan();
void setMode(const uint8_t mode);
uint8_t getMode();
void setRaw(const uint8_t state[]);
uint8_t *getRaw();
static bool validChecksum(uint8_t *state,
const uint16_t length = kPanasonicAcStateLength);
static uint8_t calcChecksum(uint8_t *state,
const uint16_t length = kPanasonicAcStateLength);
void setQuiet(const bool state);
bool getQuiet();
void setPowerful(const bool state);
bool getPowerful();
void setModel(const panasonic_ac_remote_model_t model);
panasonic_ac_remote_model_t getModel();
void setSwingVertical(const uint8_t elevation);
uint8_t getSwingVertical();
void setSwingHorizontal(const uint8_t direction);
uint8_t getSwingHorizontal();
static uint16_t encodeTime(const uint8_t hours, const uint8_t mins);
uint16_t getClock();
void setClock(const uint16_t mins_since_midnight);
uint16_t getOnTimer();
void setOnTimer(const uint16_t mins_since_midnight, const bool enable = true);
void cancelOnTimer();
bool isOnTimerEnabled();
uint16_t getOffTimer();
void setOffTimer(const uint16_t mins_since_midnight,
const bool enable = true);
void cancelOffTimer();
bool isOffTimerEnabled();
#ifdef ARDUINO
String toString();
static String timeToString(const uint16_t mins_since_midnight);
#else
std::string toString();
static std::string timeToString(const uint16_t mins_since_midnight);
#endif
#ifndef UNIT_TEST
private:
#endif
uint8_t remote_state[kPanasonicAcStateLength];
uint8_t _swingh;
uint8_t _temp;
void fixChecksum(const uint16_t length = kPanasonicAcStateLength);
static uint8_t calcChecksum(const uint8_t *state,
const uint16_t length = kPanasonicAcStateLength);
IRsend _irsend;
};
#endif // IR_PANASONIC_H_
+143
View File
@@ -0,0 +1,143 @@
// Copyright 2009 Ken Shirriff
// Copyright 2017, 2018 David Conran
// Copyright 2018 Kamil Palczewski
#define __STDC_LIMIT_MACROS
#include <stdint.h>
#include <algorithm>
#include "IRrecv.h"
#include "IRsend.h"
#include "IRutils.h"
#include "ir_NEC.h"
// PPPP III OOO N N EEEE EEEE RRRR
// P P I O O NN N E E R R
// PPPP I O O N N N EEE EEE RRRR
// P I O O N NN E E R R
// P III OOO N N EEEE EEEE R RR
// Ref:
// http://adrian-kingston.com/IRFormatPioneer.htm
#if SEND_PIONEER
// Send a raw Pioneer formatted message.
//
// Args:
// data: The message to be sent.
// nbits: The number of bits of the message to be sent.
// Typically kPioneerBits.
// repeat: The number of times the command is to be repeated.
//
// Status: BETA / Expected to be working.
//
// Ref:
// http://adrian-kingston.com/IRFormatPioneer.htm
void IRsend::sendPioneer(const uint64_t data, const uint16_t nbits,
const uint16_t repeat) {
// If nbits is to big, or is odd, abort.
if (nbits > sizeof(data) * 8 || nbits % 2 == 1) return;
// send 1st part of the code
sendNEC(data >> (nbits / 2), nbits / 2, 0);
// send 2nd part of the code
sendNEC(data & (((uint64_t)1 << (nbits / 2)) - 1), nbits / 2, repeat);
}
// Calculate the raw Pioneer data code based on two NEC sub-codes
// Args:
// address A 16-bit "published" NEC value.
// command: A 16-bit "published" NEC value.
// Returns:
// A raw 64-bit Pioneer message code.
//
// Status: BETA / Expected to work.
//
// Note:
// Address & Command can be take from a decode result OR from the spreadsheets
// located at:
// https://www.pioneerelectronics.com/PUSA/Support/Home-Entertainment-Custom-Install/IR+Codes/A+V+Receivers
// where the first part is considered the address,
// and the second the command.
// e.g.
// "A556+AF20" is an Address of 0xA556 & a Command of 0xAF20.
uint64_t IRsend::encodePioneer(const uint16_t address, const uint16_t command) {
return (((uint64_t)encodeNEC(address >> 8, address & 0xFF)) << 32) |
encodeNEC(command >> 8, command & 0xFF);
}
#endif // SEND_PIONEER
#if DECODE_PIONEER
// Decode the supplied Pioneer 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 kPioneerBits.
// strict: Flag indicating if we should perform strict matching.
// Returns:
// boolean: True if it can decode it, false if it can't.
//
// Status: BETA / Should be working. (Self decodes & real examples)
//
bool IRrecv::decodePioneer(decode_results *results, const uint16_t nbits,
const bool strict) {
if (results->rawlen < 2 * (nbits + kHeader + kFooter) - 1)
return false; // Can't possibly be a valid Pioneer message.
if (strict && nbits != kPioneerBits)
return false; // Not strictly an Pioneer message.
uint64_t data = 0;
uint16_t offset = kStartOffset;
for (uint16_t section = 0; section < 2; section++) {
// Header
if (!matchMark(results->rawbuf[offset], kNecHdrMark)) return false;
// Calculate how long the lowest tick time is based on the header mark.
uint32_t mark_tick =
results->rawbuf[offset++] * kRawTick / kNecHdrMarkTicks;
if (!matchSpace(results->rawbuf[offset], kNecHdrSpace)) return false;
// Calculate how long the common tick time is based on the header space.
uint32_t space_tick =
results->rawbuf[offset++] * kRawTick / kNecHdrSpaceTicks;
//
// Data
match_result_t data_result = matchData(
&(results->rawbuf[offset]), nbits / 2, kNecBitMarkTicks * mark_tick,
kNecOneSpaceTicks * space_tick, kNecBitMarkTicks * mark_tick,
kNecZeroSpaceTicks * space_tick);
if (data_result.success == false) return false;
uint8_t command = data_result.data >> 8;
uint8_t command_inverted = data_result.data;
uint8_t address = data_result.data >> 24;
uint8_t address_inverted = data_result.data >> 16;
// Compliance
if (strict) {
if (command != (command_inverted ^ 0xFF))
return false; // Command integrity failed.
if (address != (address_inverted ^ 0xFF))
return false; // Address integrity failed.
}
data = (data << (nbits / 2)) + data_result.data;
offset += data_result.used;
// NEC-like commands and addresses are technically in LSB first order so the
// final versions have to be reversed.
uint16_t code = reverseBits((command << 8) + address, 16);
if (section)
results->command = code;
else
results->address = code;
// Footer
if (!matchMark(results->rawbuf[offset++], kNecBitMarkTicks * mark_tick))
return false;
if (offset < results->rawlen &&
!matchAtLeast(results->rawbuf[offset++], kNecMinGapTicks * space_tick))
return false;
}
// Success
results->bits = nbits;
results->value = data;
results->decode_type = PIONEER;
return true;
}
#endif // DECODE_PIONEER
+15 -15
View File
@@ -10,12 +10,12 @@
// PP rr oooo nn nn tttt oooo
// Constants
#define PRONTO_FREQ_FACTOR 0.241246
#define PRONTO_TYPE_OFFSET 0U
#define PRONTO_FREQ_OFFSET 1U
#define PRONTO_SEQ_1_LEN_OFFSET 2U
#define PRONTO_SEQ_2_LEN_OFFSET 3U
#define PRONTO_DATA_OFFSET 4U
const float kProntoFreqFactor = 0.241246;
const uint16_t kProntoTypeOffset = 0;
const uint16_t kProntoFreqOffset = 1;
const uint16_t kProntoSeq1LenOffset = 2;
const uint16_t kProntoSeq2LenOffset = 3;
const uint16_t kProntoDataOffset = 4;
#if SEND_PRONTO
// Send a Pronto Code formatted message.
@@ -49,29 +49,29 @@
// 0x0030, 0x0018, 0x0018, 0x0018, 0x0018, 0x0018, 0x0030, 0x0018,
// 0x0018, 0x03f6};
// // Send the Pronto(Sony) code. Repeat twice as Sony's require that.
// sendPronto(prontoCode, 46, SONY_MIN_REPEAT);
// sendPronto(prontoCode, 46, kSonyMinRepeat);
//
// Ref:
// http://www.etcwiki.org/wiki/Pronto_Infrared_Format
// http://www.remotecentral.com/features/irdisp2.htm
void IRsend::sendPronto(uint16_t data[], uint16_t len, uint16_t repeat) {
// Check we have enough data to work out what to send.
if (len < PRONTO_MIN_LENGTH) return;
if (len < kProntoMinLength) return;
// We only know how to deal with 'raw' pronto codes types. Reject all others.
if (data[PRONTO_TYPE_OFFSET] != 0) return;
if (data[kProntoTypeOffset] != 0) return;
// Pronto frequency is in Hz.
uint16_t hz = (uint16_t) (1000000U / (data[PRONTO_FREQ_OFFSET] *
PRONTO_FREQ_FACTOR));
uint16_t hz =
(uint16_t)(1000000U / (data[kProntoFreqOffset] * kProntoFreqFactor));
enableIROut(hz);
// Grab the length of the two sequences.
uint16_t seq_1_len = data[PRONTO_SEQ_1_LEN_OFFSET] * 2;
uint16_t seq_2_len = data[PRONTO_SEQ_2_LEN_OFFSET] * 2;
uint16_t seq_1_len = data[kProntoSeq1LenOffset] * 2;
uint16_t seq_2_len = data[kProntoSeq2LenOffset] * 2;
// Calculate where each sequence starts in the buffer.
uint16_t seq_1_start = PRONTO_DATA_OFFSET;
uint16_t seq_2_start = PRONTO_DATA_OFFSET + seq_1_len;
uint16_t seq_1_start = kProntoDataOffset;
uint16_t seq_2_start = kProntoDataOffset + seq_1_len;
uint32_t periodic_time = calcUSecPeriod(hz, false);
+104 -107
View File
@@ -21,29 +21,31 @@
// Ref:
// https://en.wikipedia.org/wiki/RC-5
// http://www.sbprojects.com/knowledge/ir/rc5.php
#define MIN_RC5_SAMPLES 11U
#define MIN_RC6_SAMPLES 1U
#define RC5_T1 889U
#define RC5_MIN_COMMAND_LENGTH 113778UL
#define RC5_MIN_GAP (RC5_MIN_COMMAND_LENGTH - RC5_RAW_BITS * (2 * RC5_T1))
#define RC5_TOGGLE_MASK 0x800U // (The 12th bit)
const uint16_t kRc5T1 = 889;
const uint32_t kRc5MinCommandLength = 113778;
const uint32_t kRc5MinGap = kRc5MinCommandLength - kRC5RawBits * (2 * kRc5T1);
const uint16_t kRc5ToggleMask = 0x800; // The 12th bit.
const uint16_t kRc5SamplesMin = 11;
// RC-6
// Ref:
// https://en.wikipedia.org/wiki/RC-6
// http://www.pcbheaven.com/userpages/The_Philips_RC6_Protocol/
#define RC6_TICK 444U
#define RC6_HDR_MARK_TICKS 6U
#define RC6_HDR_MARK (RC6_HDR_MARK_TICKS * RC6_TICK)
#define RC6_HDR_SPACE_TICKS 2U
#define RC6_HDR_SPACE (RC6_HDR_SPACE_TICKS * RC6_TICK)
#define RC6_RPT_LENGTH_TICKS 187U
#define RC6_RPT_LENGTH (RC6_RPT_LENGTH_TICKS * RC6_TICK)
#define RC6_TOGGLE_MASK 0x10000UL // (The 17th bit)
#define RC6_36_TOGGLE_MASK 0x8000U // (The 16th bit)
const uint16_t kRc6Tick = 444;
const uint16_t kRc6HdrMarkTicks = 6;
const uint16_t kRc6HdrMark = kRc6HdrMarkTicks * kRc6Tick;
const uint16_t kRc6HdrSpaceTicks = 2;
const uint16_t kRc6HdrSpace = kRc6HdrSpaceTicks * kRc6Tick;
const uint16_t kRc6RptLengthTicks = 187;
const uint32_t kRc6RptLength = kRc6RptLengthTicks * kRc6Tick;
const uint32_t kRc6ToggleMask = 0x10000UL; // The 17th bit.
const uint16_t kRc6_36ToggleMask = 0x8000; // The 16th bit.
// Common (getRClevel())
const int16_t kMARK = 0;
const int16_t kSPACE = 1;
const int16_t kMark = 0;
const int16_t kSpace = 1;
#if SEND_RC5
// Send a Philips RC-5/RC-5X packet.
@@ -67,14 +69,13 @@ const int16_t kSPACE = 1;
// TODO(anyone):
// Testing of the RC-5X components.
void IRsend::sendRC5(uint64_t data, uint16_t nbits, uint16_t repeat) {
if (nbits > sizeof(data) * 8)
return; // We can't send something that big.
if (nbits > sizeof(data) * 8) return; // We can't send something that big.
bool skipSpace = true;
bool field_bit = true;
// Set 36kHz IR carrier frequency & a 1/4 (25%) duty cycle.
enableIROut(36, 25);
if (nbits >= RC5X_BITS) { // Is this a RC-5X message?
if (nbits >= kRC5XBits) { // Is this a RC-5X message?
// field bit is the inverted MSB of RC-5X data.
field_bit = ((data >> (nbits - 1)) ^ 1) & 1;
nbits--;
@@ -89,28 +90,28 @@ void IRsend::sendRC5(uint64_t data, uint16_t nbits, uint16_t repeat) {
if (skipSpace)
skipSpace = false; // First time through, we assume the leading space().
else
space(RC5_T1);
mark(RC5_T1);
space(kRc5T1);
mark(kRc5T1);
// Field/Second start bit.
if (field_bit) { // Send a 1. Normal for RC-5.
space(RC5_T1);
mark(RC5_T1);
space(kRc5T1);
mark(kRc5T1);
} else { // Send a 0. Special case for RC-5X. Means 7th command bit is 1.
mark(RC5_T1);
space(RC5_T1);
mark(kRc5T1);
space(kRc5T1);
}
// Data
for (uint64_t mask = 1ULL << (nbits - 1); mask; mask >>= 1)
if (data & mask) { // 1
space(RC5_T1); // 1 is space, then mark.
mark(RC5_T1);
} else { // 0
mark(RC5_T1); // 0 is mark, then space.
space(RC5_T1);
space(kRc5T1); // 1 is space, then mark.
mark(kRc5T1);
} else { // 0
mark(kRc5T1); // 0 is mark, then space.
space(kRc5T1);
}
// Footer
space(std::max(RC5_MIN_GAP, RC5_MIN_COMMAND_LENGTH - usecTimer.elapsed()));
space(std::max(kRc5MinGap, kRc5MinCommandLength - usecTimer.elapsed()));
}
}
@@ -131,8 +132,7 @@ void IRsend::sendRC5(uint64_t data, uint16_t nbits, uint16_t repeat) {
// https://en.wikipedia.org/wiki/RC-5
uint16_t IRsend::encodeRC5(uint8_t address, uint8_t command,
bool key_released) {
return (key_released << (RC5_BITS - 1)) |
((address & 0x1f) << 6) |
return (key_released << (kRC5Bits - 1)) | ((address & 0x1f) << 6) |
(command & 0x3F);
}
@@ -156,8 +156,8 @@ uint16_t IRsend::encodeRC5X(uint8_t address, uint8_t command,
// The 2nd start/field bit (MSB of the return value) is the value of the 7th
// command bit.
bool s2 = (command >> 6) & 1;
return ((uint16_t) s2 << (RC5X_BITS - 1)) |
encodeRC5(address, command, key_released);
return ((uint16_t)s2 << (kRC5XBits - 1)) |
encodeRC5(address, command, key_released);
}
// Flip the toggle bit of a Philips RC-5/RC-5X data message.
@@ -174,9 +174,7 @@ uint16_t IRsend::encodeRC5X(uint8_t address, uint8_t command,
// Ref:
// http://www.sbprojects.com/knowledge/ir/rc5.php
// https://en.wikipedia.org/wiki/RC-5
uint64_t IRsend::toggleRC5(uint64_t data) {
return data ^ RC5_TOGGLE_MASK;
}
uint64_t IRsend::toggleRC5(uint64_t data) { return data ^ kRc5ToggleMask; }
#endif // SEND_RC5
#if SEND_RC6
@@ -198,9 +196,8 @@ uint64_t IRsend::toggleRC5(uint64_t data) {
// http://www.sbprojects.com/knowledge/ir/rc6.php
// http://www.righto.com/2010/12/64-bit-rc6-codes-arduino-and-xbox.html
uint64_t IRsend::toggleRC6(uint64_t data, uint16_t nbits) {
if (nbits == RC6_36_BITS)
return data ^ RC6_36_TOGGLE_MASK;
return data ^ RC6_TOGGLE_MASK;
if (nbits == kRC6_36Bits) return data ^ kRc6_36ToggleMask;
return data ^ kRc6ToggleMask;
}
// Encode a Philips RC-6 data message.
@@ -220,13 +217,12 @@ uint64_t IRsend::toggleRC6(uint64_t data, uint16_t nbits) {
// http://www.sbprojects.com/knowledge/ir/rc6.php
// http://www.righto.com/2010/12/64-bit-rc6-codes-arduino-and-xbox.html
// http://www.pcbheaven.com/userpages/The_Philips_RC6_Protocol/
uint64_t IRsend::encodeRC6(uint32_t address, uint8_t command,
uint16_t mode) {
uint64_t IRsend::encodeRC6(uint32_t address, uint8_t command, uint16_t mode) {
switch (mode) {
case RC6_MODE0_BITS:
case kRC6Mode0Bits:
return ((address & 0xFFF) << 8) | (command & 0xFF);
case RC6_36_BITS:
return ((uint64_t) (address & 0xFFFFFFF) << 8) | (command & 0xFF);
case kRC6_36Bits:
return ((uint64_t)(address & 0xFFFFFFF) << 8) | (command & 0xFF);
default:
return 0;
}
@@ -249,25 +245,24 @@ uint64_t IRsend::encodeRC6(uint32_t address, uint8_t command,
// https://en.wikipedia.org/wiki/Manchester_code
void IRsend::sendRC6(uint64_t data, uint16_t nbits, uint16_t repeat) {
// Check we can send the number of bits requested.
if (nbits > sizeof(data) * 8)
return;
if (nbits > sizeof(data) * 8) return;
// Set 36kHz IR carrier frequency & a 1/3 (33%) duty cycle.
enableIROut(36, 33);
for (uint16_t r = 0; r <= repeat; r++) {
// Header
mark(RC6_HDR_MARK);
space(RC6_HDR_SPACE);
mark(kRc6HdrMark);
space(kRc6HdrSpace);
// Start bit.
mark(RC6_TICK); // mark, then space == 0x1.
space(RC6_TICK);
mark(kRc6Tick); // mark, then space == 0x1.
space(kRc6Tick);
// Data
uint16_t bitTime;
for (uint64_t i = 1, mask = 1ULL << (nbits - 1); mask; i++, mask >>= 1) {
if (i == 4) // The fourth bit we send is a "double width trailer bit".
bitTime = 2 * RC6_TICK; // double-wide trailer bit
bitTime = 2 * kRc6Tick; // double-wide trailer bit
else
bitTime = RC6_TICK; // Normal bit
if (data & mask) { // 1
bitTime = kRc6Tick; // Normal bit
if (data & mask) { // 1
mark(bitTime);
space(bitTime);
} else { // 0
@@ -276,7 +271,7 @@ void IRsend::sendRC6(uint64_t data, uint16_t nbits, uint16_t repeat) {
}
}
// Footer
space(RC6_RPT_LENGTH);
space(kRc6RptLength);
}
}
#endif // SEND_RC6
@@ -293,53 +288,57 @@ void IRsend::sendRC6(uint64_t data, uint16_t nbits, uint16_t repeat) {
// offset: Ptr to the currect offset to the rawbuf.
// used: Ptr to the current used counter.
// bitTime: Time interval of single bit in microseconds.
// maxwidth: Maximum number of successive levels to find in a single level
// (default 3)
// Returns:
// int: MARK, SPACE, or -1 for error (The measured time interval is not a
// multiple of t1.)
// Ref:
// https://en.wikipedia.org/wiki/Manchester_code
int16_t IRrecv::getRClevel(decode_results *results, uint16_t *offset,
uint16_t *used, uint16_t bitTime,
uint8_t tolerance, int16_t excess, uint16_t delta) {
int16_t IRrecv::getRClevel(decode_results *results, uint16_t *offset,
uint16_t *used, uint16_t bitTime, uint8_t tolerance,
int16_t excess, uint16_t delta, uint8_t maxwidth) {
DPRINT("DEBUG: getRClevel: offset = ");
DPRINTLN(uint64ToString(*offset));
DPRINT("DEBUG: getRClevel: rawlen = ");
DPRINTLN(uint64ToString(results->rawlen));
if (*offset >= results->rawlen) {
DPRINTLN("DEBUG: getRClevel: SPACE, past end of rawbuf");
return kSPACE; // After end of recorded buffer, assume SPACE.
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) ? kMARK : kSPACE;
uint16_t val = ((*offset) % 2) ? kMark : kSpace;
// Check to see if we have hit an inter-message gap (> 20ms).
if (val == kSPACE && width > 20000 - delta) {
if (val == kSpace &&
(width > 20000 - delta || width > maxwidth * bitTime + delta)) {
DPRINTLN("DEBUG: getRClevel: SPACE, hit end of mesg gap.");
return kSPACE;
return kSpace;
}
int16_t correction = (val == kMARK) ? excess : -excess;
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, tolerance, delta)) {
avail = 3;
} else if (match(width, 2 * bitTime + correction, tolerance, delta)) {
avail = 2;
} else if (match(width, bitTime + correction, tolerance, delta)) {
avail = 1;
} else {
for (avail = maxwidth; avail > 0; avail--) {
if (match(width, avail * bitTime + correction, tolerance, delta)) {
break;
}
}
if (!avail) {
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.
(*used)++; // Count another one of the avail slots as used.
if (*used >= avail) { // Are we out of look-ahead/avail slots?
// Yes, so reset the used counter, and move the offset ahead.
*used = 0;
(*offset)++;
}
if (val == kMARK) {
if (val == kMark) {
DPRINTLN("DEBUG: getRClevel: MARK");
} else {
DPRINTLN("DEBUG: getRClevel: SPACE");
@@ -371,28 +370,28 @@ int16_t IRrecv::getRClevel(decode_results *results, uint16_t *offset,
// TODO(anyone):
// Serious testing of the RC-5X and strict aspects needs to be done.
bool IRrecv::decodeRC5(decode_results *results, uint16_t nbits, bool strict) {
if (results->rawlen < MIN_RC5_SAMPLES + HEADER - 1) return false;
if (results->rawlen < kRc5SamplesMin + kHeader - 1) return false;
// Compliance
if (strict && nbits != RC5_BITS && nbits != RC5X_BITS)
if (strict && nbits != kRC5Bits && nbits != kRC5XBits)
return false; // It's neither RC-5 or RC-5X.
uint16_t offset = OFFSET_START;
uint16_t offset = kStartOffset;
uint16_t used = 0;
bool is_rc5x = false;
uint64_t data = 0;
// Header
// Get start bit #1.
if (getRClevel(results, &offset, &used, RC5_T1) != kMARK) return false;
if (getRClevel(results, &offset, &used, kRc5T1) != 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 == kSPACE && levelB == kMARK) { // Matched a 1.
int16_t levelA = getRClevel(results, &offset, &used, kRc5T1);
int16_t levelB = getRClevel(results, &offset, &used, kRc5T1);
if (levelA == kSpace && levelB == kMark) { // Matched a 1.
is_rc5x = false;
} else if (levelA == kMARK && levelB == kSPACE) { // Matched a 0.
if (nbits <= RC5_BITS) return false; // Field bit must be '1' for RC5.
} else if (levelA == kMark && levelB == kSpace) { // Matched a 0.
if (nbits <= kRC5Bits) return false; // Field bit must be '1' for RC5.
is_rc5x = true;
data = 1;
} else {
@@ -401,11 +400,11 @@ bool IRrecv::decodeRC5(decode_results *results, uint16_t nbits, bool strict) {
// Data
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 == kSPACE && levelB == kMARK)
int16_t levelA = getRClevel(results, &offset, &used, kRc5T1);
int16_t levelB = getRClevel(results, &offset, &used, kRc5T1);
if (levelA == kSpace && levelB == kMark)
data = (data << 1) | 1; // 1
else if (levelA == kMARK && levelB == kSPACE)
else if (levelA == kMark && levelB == kSpace)
data <<= 1; // 0
else
break;
@@ -414,8 +413,8 @@ bool IRrecv::decodeRC5(decode_results *results, uint16_t nbits, bool strict) {
// Compliance
if (actual_bits < nbits) return false; // Less data than we expected.
if (strict && actual_bits != RC5_BITS &&
actual_bits != RC5X_BITS) return false;
if (strict && actual_bits != kRC5Bits && actual_bits != kRC5XBits)
return false;
// Success
results->value = data;
@@ -424,7 +423,7 @@ bool IRrecv::decodeRC5(decode_results *results, uint16_t nbits, bool strict) {
results->repeat = false;
if (is_rc5x) {
results->decode_type = RC5X;
results->command |= ((uint32_t) is_rc5x) << 6;
results->command |= ((uint32_t)is_rc5x) << 6;
} else {
results->decode_type = RC5;
actual_bits--; // RC5 doesn't count the field bit as data.
@@ -452,7 +451,7 @@ bool IRrecv::decodeRC5(decode_results *results, uint16_t nbits, bool strict) {
// TODO(anyone):
// Testing of the strict compliance aspects.
bool IRrecv::decodeRC6(decode_results *results, uint16_t nbits, bool strict) {
if (results->rawlen < HEADER + 2 + 4) // Up to the double-wide T bit.
if (results->rawlen < kHeader + 2 + 4) // Up to the double-wide T bit.
return false; // Smaller than absolute smallest possible RC6 message.
if (strict) { // Compliance
@@ -462,31 +461,31 @@ bool IRrecv::decodeRC6(decode_results *results, uint16_t nbits, bool strict) {
// Also due to potential melding with the start bit, we can only count
// the start bit as 1, instead of a more typical 2 value. The header still
// remains as normal.
if (results->rawlen < nbits + HEADER + 1)
if (results->rawlen < nbits + kHeader + 1)
return false; // Don't have enough entries/samples to be valid.
switch (nbits) {
case RC6_MODE0_BITS:
case RC6_36_BITS:
case kRC6Mode0Bits:
case kRC6_36Bits:
break;
default:
return false; // Asking for the wrong number of bits.
}
}
uint16_t offset = OFFSET_START;
uint16_t offset = kStartOffset;
// Header
if (!matchMark(results->rawbuf[offset], RC6_HDR_MARK)) return false;
if (!matchMark(results->rawbuf[offset], kRc6HdrMark)) return false;
// Calculate how long the common tick time is based on the header mark.
uint32_t tick = results->rawbuf[offset++] * RAWTICK / RC6_HDR_MARK_TICKS;
if (!matchSpace(results->rawbuf[offset++], RC6_HDR_SPACE_TICKS * tick))
uint32_t tick = results->rawbuf[offset++] * kRawTick / kRc6HdrMarkTicks;
if (!matchSpace(results->rawbuf[offset++], kRc6HdrSpaceTicks * tick))
return false;
uint16_t used = 0;
// Get the start bit. e.g. 1.
if (getRClevel(results, &offset, &used, tick) != kMARK) return false;
if (getRClevel(results, &offset, &used, tick) != kSPACE) 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;
@@ -496,17 +495,15 @@ bool IRrecv::decodeRC6(decode_results *results, uint16_t nbits, bool strict) {
int16_t levelA, levelB; // Next two levels
levelA = getRClevel(results, &offset, &used, tick);
// T bit is double wide; make sure second half matches
if (actual_bits == 3 &&
levelA != getRClevel(results, &offset, &used, tick))
if (actual_bits == 3 && levelA != getRClevel(results, &offset, &used, tick))
return false;
levelB = getRClevel(results, &offset, &used, tick);
// T bit is double wide; make sure second half matches
if (actual_bits == 3 &&
levelB != getRClevel(results, &offset, &used, tick))
if (actual_bits == 3 && levelB != getRClevel(results, &offset, &used, tick))
return false;
if (levelA == kMARK && levelB == kSPACE) // reversed compared to RC5
data = (data << 1) | 1; // 1
else if (levelA == kSPACE && levelB == kMARK)
if (levelA == kMark && levelB == kSpace) // reversed compared to RC5
data = (data << 1) | 1; // 1
else if (levelA == kSpace && levelB == kMark)
data <<= 1; // 0
else
break;
+61 -55
View File
@@ -17,28 +17,28 @@
// Constants
// Ref:
// http://www.sbprojects.com/knowledge/ir/rcmm.php
#define RCMM_TICK 28U // Technically it would be 27.777*
#define RCMM_HDR_MARK_TICKS 15U
#define RCMM_HDR_MARK 416U
#define RCMM_HDR_SPACE_TICKS 10U
#define RCMM_HDR_SPACE 277U
#define RCMM_BIT_MARK_TICKS 6U
#define RCMM_BIT_MARK 166U
#define RCMM_BIT_SPACE_0_TICKS 10U
#define RCMM_BIT_SPACE_0 277U
#define RCMM_BIT_SPACE_1_TICKS 16U
#define RCMM_BIT_SPACE_1 444U
#define RCMM_BIT_SPACE_2_TICKS 22U
#define RCMM_BIT_SPACE_2 611U
#define RCMM_BIT_SPACE_3_TICKS 28U
#define RCMM_BIT_SPACE_3 777U
#define RCMM_RPT_LENGTH_TICKS 992U
#define RCMM_RPT_LENGTH 27778U
#define RCMM_MIN_GAP_TICKS 120U
#define RCMM_MIN_GAP 3360U
const uint16_t kRcmmTick = 28; // Technically it would be 27.777*
const uint16_t kRcmmHdrMarkTicks = 15;
const uint16_t kRcmmHdrMark = 416;
const uint16_t kRcmmHdrSpaceTicks = 10;
const uint16_t kRcmmHdrSpace = 277;
const uint16_t kRcmmBitMarkTicks = 6;
const uint16_t kRcmmBitMark = 166;
const uint16_t kRcmmBitSpace0Ticks = 10;
const uint16_t kRcmmBitSpace0 = 277;
const uint16_t kRcmmBitSpace1Ticks = 16;
const uint16_t kRcmmBitSpace1 = 444;
const uint16_t kRcmmBitSpace2Ticks = 22;
const uint16_t kRcmmBitSpace2 = 611;
const uint16_t kRcmmBitSpace3Ticks = 28;
const uint16_t kRcmmBitSpace3 = 777;
const uint16_t kRcmmRptLengthTicks = 992;
const uint32_t kRcmmRptLength = 27778;
const uint16_t kRcmmMinGapTicks = 120;
const uint32_t kRcmmMinGap = 3360;
// Use a tolerance of +/-10% when matching some data spaces.
#define RCMM_TOLERANCE 10U
#define RCMM_EXCESS 50U
const uint8_t kRcmmTolerance = 10;
const uint16_t kRcmmExcess = 50;
#if SEND_RCMM
// Send a Philips RC-MM packet.
@@ -60,27 +60,35 @@ void IRsend::sendRCMM(uint64_t data, uint16_t nbits, uint16_t repeat) {
for (uint16_t r = 0; r <= repeat; r++) {
usecs.reset();
// Header
mark(RCMM_HDR_MARK);
space(RCMM_HDR_SPACE);
mark(kRcmmHdrMark);
space(kRcmmHdrSpace);
// Data
uint64_t mask = 0b11ULL << (nbits - 2);
// RC-MM sends data 2 bits at a time.
for (int32_t i = nbits; i > 0; i -= 2) {
mark(RCMM_BIT_MARK);
mark(kRcmmBitMark);
// Grab the next Most Significant Bits to send.
switch ((data & mask) >> (i - 2)) {
case 0b00: space(RCMM_BIT_SPACE_0); break;
case 0b01: space(RCMM_BIT_SPACE_1); break;
case 0b10: space(RCMM_BIT_SPACE_2); break;
case 0b11: space(RCMM_BIT_SPACE_3); break;
case 0b00:
space(kRcmmBitSpace0);
break;
case 0b01:
space(kRcmmBitSpace1);
break;
case 0b10:
space(kRcmmBitSpace2);
break;
case 0b11:
space(kRcmmBitSpace3);
break;
}
mask >>= 2;
}
// Footer
mark(RCMM_BIT_MARK);
// Protocol requires us to wait at least RCMM_RPT_LENGTH usecs from the
// start or RCMM_MIN_GAP usecs.
space(std::max(RCMM_RPT_LENGTH - usecs.elapsed(), RCMM_MIN_GAP));
mark(kRcmmBitMark);
// Protocol requires us to wait at least kRcmmRptLength usecs from the
// start or kRcmmMinGap usecs.
space(std::max(kRcmmRptLength - usecs.elapsed(), kRcmmMinGap));
}
}
#endif
@@ -90,7 +98,7 @@ void IRsend::sendRCMM(uint64_t data, uint16_t nbits, uint16_t repeat) {
// Places successful decode information in the results pointer.
// 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 RCMM_BITS.
// nbits: Nr. of bits to expect in the data portion. Typically kRCMMBits.
// strict: Flag to indicate if we strictly adhere to the specification.
// Returns:
// boolean: True if it can decode it, false if it can't.
@@ -101,67 +109,65 @@ void IRsend::sendRCMM(uint64_t data, uint16_t nbits, uint16_t repeat) {
// http://www.sbprojects.com/knowledge/ir/rcmm.php
bool IRrecv::decodeRCMM(decode_results *results, uint16_t nbits, bool strict) {
uint64_t data = 0;
uint16_t offset = OFFSET_START;
uint16_t offset = kStartOffset;
if (results->rawlen <= 4)
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 - 5,
(uint16_t) sizeof(data) * 8);
int16_t maxBitSize =
std::min((uint16_t)results->rawlen - 5, (uint16_t)sizeof(data) * 8);
// Compliance
if (strict) {
// Technically the spec says bit sizes should be 12 xor 24. however
// 32 bits has been seen from a device. We are going to assume
// 12 <= bits <= 32 is the 'required' bit length for the spec.
if (maxBitSize < 12 || maxBitSize > 32)
return false;
if (maxBitSize < 12 || maxBitSize > 32) return false;
if (maxBitSize < nbits)
return false; // Short cut, we can never reach the expected nr. of bits.
}
// Header decode
if (!matchMark(results->rawbuf[offset], RCMM_HDR_MARK)) return false;
if (!matchMark(results->rawbuf[offset], kRcmmHdrMark)) return false;
// Calculate how long the common tick time is based on the header mark.
uint32_t m_tick = results->rawbuf[offset++] * RAWTICK / RCMM_HDR_MARK_TICKS;
if (!matchSpace(results->rawbuf[offset], RCMM_HDR_SPACE)) return false;
uint32_t m_tick = results->rawbuf[offset++] * kRawTick / kRcmmHdrMarkTicks;
if (!matchSpace(results->rawbuf[offset], kRcmmHdrSpace)) return false;
// Calculate how long the common tick time is based on the header space.
uint32_t s_tick = results->rawbuf[offset++] * RAWTICK / RCMM_HDR_SPACE_TICKS;
uint32_t s_tick = results->rawbuf[offset++] * kRawTick / kRcmmHdrSpaceTicks;
// Data decode
// RC-MM has two bits of data per mark/space pair.
uint16_t actualBits;
for (actualBits = 0; actualBits < maxBitSize; actualBits += 2, offset++) {
if (!match(results->rawbuf[offset++], RCMM_BIT_MARK_TICKS * m_tick))
if (!match(results->rawbuf[offset++], kRcmmBitMarkTicks * m_tick))
return false;
data <<= 2;
// Use non-default tolerance & excess for matching some of the spaces as the
// defaults are too generous and causes mis-matches in some cases.
if (match(results->rawbuf[offset], RCMM_BIT_SPACE_0_TICKS * s_tick,
TOLERANCE))
if (match(results->rawbuf[offset], kRcmmBitSpace0Ticks * s_tick,
kTolerance))
data += 0;
else if (match(results->rawbuf[offset], RCMM_BIT_SPACE_1_TICKS * s_tick,
TOLERANCE))
else if (match(results->rawbuf[offset], kRcmmBitSpace1Ticks * s_tick,
kTolerance))
data += 1;
else if (match(results->rawbuf[offset], RCMM_BIT_SPACE_2_TICKS * s_tick,
RCMM_TOLERANCE))
else if (match(results->rawbuf[offset], kRcmmBitSpace2Ticks * s_tick,
kRcmmTolerance))
data += 2;
else if (match(results->rawbuf[offset], RCMM_BIT_SPACE_3_TICKS * s_tick,
RCMM_TOLERANCE))
else if (match(results->rawbuf[offset], kRcmmBitSpace3Ticks * s_tick,
kRcmmTolerance))
data += 3;
else
return false;
}
// Footer decode
if (!match(results->rawbuf[offset++], RCMM_BIT_MARK_TICKS * m_tick))
if (!match(results->rawbuf[offset++], kRcmmBitMarkTicks * m_tick))
return false;
if (offset < results->rawlen &&
!matchAtLeast(results->rawbuf[offset], RCMM_MIN_GAP_TICKS * s_tick))
!matchAtLeast(results->rawbuf[offset], kRcmmMinGapTicks * s_tick))
return false;
// Compliance
if (strict && actualBits != nbits)
return false;
if (strict && actualBits != nbits) return false;
// Success
results->value = data;
+491 -52
View File
@@ -1,7 +1,11 @@
// Copyright 2009 Ken Shirriff
// Copyright 2017 David Conran
#include "ir_Samsung.h"
#include <algorithm>
#ifndef ARDUINO
#include <string>
#endif
#include "IRrecv.h"
#include "IRsend.h"
#include "IRutils.h"
@@ -17,29 +21,38 @@
// Constants
// Ref:
// http://elektrolab.wz.cz/katalog/samsung_protocol.pdf
#define SAMSUNG_TICK 560U
#define SAMSUNG_HDR_MARK_TICKS 8U
#define SAMSUNG_HDR_MARK (SAMSUNG_HDR_MARK_TICKS * SAMSUNG_TICK)
#define SAMSUNG_HDR_SPACE_TICKS 8U
#define SAMSUNG_HDR_SPACE (SAMSUNG_HDR_SPACE_TICKS * SAMSUNG_TICK)
#define SAMSUNG_BIT_MARK_TICKS 1U
#define SAMSUNG_BIT_MARK (SAMSUNG_BIT_MARK_TICKS * SAMSUNG_TICK)
#define SAMSUNG_ONE_SPACE_TICKS 3U
#define SAMSUNG_ONE_SPACE (SAMSUNG_ONE_SPACE_TICKS * SAMSUNG_TICK)
#define SAMSUNG_ZERO_SPACE_TICKS 1U
#define SAMSUNG_ZERO_SPACE (SAMSUNG_ZERO_SPACE_TICKS * SAMSUNG_TICK)
#define SAMSUNG_RPT_SPACE_TICKS 4U
#define SAMSUNG_RPT_SPACE (SAMSUNG_RPT_SPACE_TICKS * SAMSUNG_TICK)
#define SAMSUNG_MIN_MESSAGE_LENGTH_TICKS 193U
#define SAMSUNG_MIN_MESSAGE_LENGTH (SAMSUNG_MIN_MESSAGE_LENGTH_TICKS * \
SAMSUNG_TICK)
#define SAMSUNG_MIN_GAP_TICKS (SAMSUNG_MIN_MESSAGE_LENGTH_TICKS - \
(SAMSUNG_HDR_MARK_TICKS + SAMSUNG_HDR_SPACE_TICKS + \
SAMSUNG_BITS * (SAMSUNG_BIT_MARK_TICKS + SAMSUNG_ONE_SPACE_TICKS) + \
SAMSUNG_BIT_MARK_TICKS))
#define SAMSUNG_MIN_GAP (SAMSUNG_MIN_GAP_TICKS * SAMSUNG_TICK)
const uint16_t kSamsungTick = 560;
const uint16_t kSamsungHdrMarkTicks = 8;
const uint16_t kSamsungHdrMark = kSamsungHdrMarkTicks * kSamsungTick;
const uint16_t kSamsungHdrSpaceTicks = 8;
const uint16_t kSamsungHdrSpace = kSamsungHdrSpaceTicks * kSamsungTick;
const uint16_t kSamsungBitMarkTicks = 1;
const uint16_t kSamsungBitMark = kSamsungBitMarkTicks * kSamsungTick;
const uint16_t kSamsungOneSpaceTicks = 3;
const uint16_t kSamsungOneSpace = kSamsungOneSpaceTicks * kSamsungTick;
const uint16_t kSamsungZeroSpaceTicks = 1;
const uint16_t kSamsungZeroSpace = kSamsungZeroSpaceTicks * kSamsungTick;
const uint16_t kSamsungRptSpaceTicks = 4;
const uint16_t kSamsungRptSpace = kSamsungRptSpaceTicks * kSamsungTick;
const uint16_t kSamsungMinMessageLengthTicks = 193;
const uint32_t kSamsungMinMessageLength =
kSamsungMinMessageLengthTicks * kSamsungTick;
const uint16_t kSamsungMinGapTicks =
kSamsungMinMessageLengthTicks -
(kSamsungHdrMarkTicks + kSamsungHdrSpaceTicks +
kSamsungBits * (kSamsungBitMarkTicks + kSamsungOneSpaceTicks) +
kSamsungBitMarkTicks);
const uint32_t kSamsungMinGap = kSamsungMinGapTicks * kSamsungTick;
const uint16_t kSamsungAcHdrMark = 690;
const uint16_t kSamsungAcHdrSpace = 17844;
const uint8_t kSamsungAcSections = 2;
const uint16_t kSamsungAcSectionMark = 3086;
const uint16_t kSamsungAcSectionSpace = 8864;
const uint16_t kSamsungAcSectionGap = 2886;
const uint16_t kSamsungAcBitMark = 586;
const uint16_t kSamsungAcOneSpace = 1432;
const uint16_t kSamsungAcZeroSpace = 436;
#if SEND_SAMSUNG
// Send a Samsung formatted message.
@@ -49,19 +62,17 @@
//
// Args:
// data: The message to be sent.
// nbits: The bit size of the message being sent. typically SAMSUNG_BITS.
// nbits: The bit size of the message being sent. typically kSamsungBits.
// repeat: The number of times the message is to be repeated.
//
// Status: BETA / Should be working.
//
// Ref: http://elektrolab.wz.cz/katalog/samsung_protocol.pdf
void IRsend::sendSAMSUNG(uint64_t data, uint16_t nbits, uint16_t repeat) {
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);
sendGeneric(kSamsungHdrMark, kSamsungHdrSpace, kSamsungBitMark,
kSamsungOneSpace, kSamsungBitMark, kSamsungZeroSpace,
kSamsungBitMark, kSamsungMinGap, kSamsungMinMessageLength, data,
nbits, 38, true, repeat, 33);
}
// Construct a raw Samsung message from the supplied customer(address) &
@@ -77,8 +88,8 @@ void IRsend::sendSAMSUNG(uint64_t data, uint16_t nbits, uint16_t repeat) {
uint32_t IRsend::encodeSAMSUNG(uint8_t customer, uint8_t command) {
customer = reverseBits(customer, sizeof(customer) * 8);
command = reverseBits(command, sizeof(command) * 8);
return((command ^ 0xFF) | (command << 8) |
(customer << 16) | (customer << 24));
return ((command ^ 0xFF) | (command << 8) | (customer << 16) |
(customer << 24));
}
#endif
@@ -90,7 +101,7 @@ uint32_t IRsend::encodeSAMSUNG(uint8_t customer, uint8_t command) {
//
// 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. Typically kSamsungBits.
// strict: Flag to indicate if we strictly adhere to the specification.
// Returns:
// boolean: True if it can decode it, false if it can't.
@@ -104,37 +115,35 @@ uint32_t IRsend::encodeSAMSUNG(uint8_t customer, uint8_t command) {
// http://elektrolab.wz.cz/katalog/samsung_protocol.pdf
bool IRrecv::decodeSAMSUNG(decode_results *results, uint16_t nbits,
bool strict) {
if (results->rawlen < 2 * nbits + HEADER + FOOTER - 1)
if (results->rawlen < 2 * nbits + kHeader + kFooter - 1)
return false; // Can't possibly be a valid Samsung message.
if (strict && nbits != SAMSUNG_BITS)
if (strict && nbits != kSamsungBits)
return false; // We expect Samsung to be 32 bits of message.
uint64_t data = 0;
uint16_t offset = OFFSET_START;
uint16_t offset = kStartOffset;
// Header
if (!matchMark(results->rawbuf[offset], SAMSUNG_HDR_MARK)) return false;
if (!matchMark(results->rawbuf[offset], kSamsungHdrMark)) return false;
// Calculate how long the common tick time is based on the header mark.
uint32_t m_tick = results->rawbuf[offset++] * RAWTICK /
SAMSUNG_HDR_MARK_TICKS;
if (!matchSpace(results->rawbuf[offset], SAMSUNG_HDR_SPACE)) return false;
uint32_t m_tick = results->rawbuf[offset++] * kRawTick / kSamsungHdrMarkTicks;
if (!matchSpace(results->rawbuf[offset], kSamsungHdrSpace)) return false;
// Calculate how long the common tick time is based on the header space.
uint32_t s_tick = results->rawbuf[offset++] * RAWTICK /
SAMSUNG_HDR_SPACE_TICKS;
uint32_t s_tick =
results->rawbuf[offset++] * kRawTick / kSamsungHdrSpaceTicks;
// Data
match_result_t data_result = matchData(&(results->rawbuf[offset]), nbits,
SAMSUNG_BIT_MARK_TICKS * m_tick,
SAMSUNG_ONE_SPACE_TICKS * s_tick,
SAMSUNG_BIT_MARK_TICKS * m_tick,
SAMSUNG_ZERO_SPACE_TICKS * s_tick);
match_result_t data_result =
matchData(&(results->rawbuf[offset]), nbits,
kSamsungBitMarkTicks * m_tick, kSamsungOneSpaceTicks * s_tick,
kSamsungBitMarkTicks * m_tick, kSamsungZeroSpaceTicks * s_tick);
if (data_result.success == false) return false;
data = data_result.data;
offset += data_result.used;
// Footer
if (!matchMark(results->rawbuf[offset++], SAMSUNG_BIT_MARK_TICKS * m_tick))
if (!matchMark(results->rawbuf[offset++], kSamsungBitMarkTicks * m_tick))
return false;
if (offset < results->rawlen &&
!matchAtLeast(results->rawbuf[offset], SAMSUNG_MIN_GAP_TICKS * s_tick))
!matchAtLeast(results->rawbuf[offset], kSamsungMinGapTicks * s_tick))
return false;
// Compliance
@@ -142,13 +151,11 @@ bool IRrecv::decodeSAMSUNG(decode_results *results, uint16_t nbits,
// According to the spec, the customer (address) code is the first 8
// transmitted bits. It's then repeated. Check for that.
uint8_t address = data >> 24;
if (strict && address != ((data >> 16) & 0xFF))
return false;
if (strict && address != ((data >> 16) & 0xFF)) return false;
// Spec says the command code is the 3rd block of transmitted 8-bits,
// followed by the inverted command code.
uint8_t command = (data & 0xFF00) >> 8;
if (strict && command != ((data & 0xFF) ^ 0xFF))
return false;
if (strict && command != ((data & 0xFF) ^ 0xFF)) return false;
// Success
results->bits = nbits;
@@ -160,3 +167,435 @@ bool IRrecv::decodeSAMSUNG(decode_results *results, uint16_t nbits,
return true;
}
#endif
#if SEND_SAMSUNG_AC
// Send a Samsung A/C message.
//
// Args:
// data: An array of bytes containing the IR command.
// nbytes: Nr. of bytes of data in the array. (>=kSamsungAcStateLength)
// repeat: Nr. of times the message is to be repeated. (Default = 0).
//
// Status: ALPHA / Untested.
//
// Ref:
// https://github.com/markszabo/IRremoteESP8266/issues/505
void IRsend::sendSamsungAC(uint8_t data[], uint16_t nbytes, uint16_t repeat) {
if (nbytes < kSamsungAcStateLength && nbytes % kSamsungACSectionLength)
return; // Not an appropriate number of bytes to send a proper message.
enableIROut(38);
for (uint16_t r = 0; r <= repeat; r++) {
// Header
mark(kSamsungAcHdrMark);
space(kSamsungAcHdrSpace);
// Send in 7 byte sections.
for (uint16_t offset = 0; offset < nbytes;
offset += kSamsungACSectionLength) {
sendGeneric(kSamsungAcSectionMark, kSamsungAcSectionSpace,
kSamsungAcBitMark, kSamsungAcOneSpace, kSamsungAcBitMark,
kSamsungAcZeroSpace, kSamsungAcBitMark, kSamsungAcSectionGap,
data + offset, kSamsungACSectionLength, // 7 bytes == 56 bits
38000, false, 0, 50); // Send in LSBF order
}
// Complete made up guess at inter-message gap.
space(100000 - kSamsungAcSectionGap);
}
}
#endif // SEND_SAMSUNG_AC
IRSamsungAc::IRSamsungAc(uint16_t pin) : _irsend(pin) { stateReset(); }
void IRSamsungAc::stateReset() {
for (uint8_t i = 0; i < kSamsungAcExtendedStateLength; i++)
remote_state[i] = 0x0;
remote_state[0] = 0x02;
remote_state[1] = 0x92;
remote_state[2] = 0x0F;
remote_state[6] = 0xF0;
remote_state[7] = 0x01;
remote_state[8] = 0x02;
remote_state[9] = 0xAE;
remote_state[10] = 0x71;
remote_state[12] = 0x15;
remote_state[13] = 0xF0;
}
void IRSamsungAc::begin() { _irsend.begin(); }
uint8_t IRSamsungAc::calcChecksum(const uint8_t state[],
const uint16_t length) {
uint8_t sum = 0;
// Safety check so we don't go outside the array.
if (length < 7) return 255;
// Shamelessly inspired by:
// https://github.com/adafruit/Raw-IR-decoder-for-Arduino/pull/3/files
// Count most of the '1' bits after the checksum location.
sum += countBits(state[length - 7], 8);
sum -= countBits(state[length - 6] & 0xF, 8);
sum += countBits(state[length - 5] & 0b11111110, 8);
sum += countBits(state + length - 4, 3);
return (28 - sum) & 0xF;
}
bool IRSamsungAc::validChecksum(const uint8_t state[], const uint16_t length) {
if (length < kSamsungAcStateLength)
return true; // No checksum to compare with. Assume okay.
uint8_t offset = 0;
if (length >= kSamsungAcExtendedStateLength) offset = 7;
return ((state[length - 6] >> 4) == calcChecksum(state, length) &&
(state[length - (13 + offset)] >> 4) == calcChecksum(state, length -
(7 + offset)));
}
// Update the checksum for the internal state.
void IRSamsungAc::checksum(uint16_t length) {
if (length < 13) return;
remote_state[length - 6] &= 0x0F;
remote_state[length - 6] |= (calcChecksum(remote_state, length) << 4);
remote_state[length - 13] &= 0x0F;
remote_state[length - 13] |= (calcChecksum(remote_state, length - 7) << 4);
}
#if SEND_SAMSUNG_AC
// Use for most function/mode/settings changes to the unit.
// i.e. When the device is already running.
void IRSamsungAc::send(const uint16_t repeat, const bool calcchecksum) {
if (calcchecksum) checksum();
_irsend.sendSamsungAC(remote_state, kSamsungAcStateLength, repeat);
}
#endif // SEND_SAMSUNG_AC
#if SEND_SAMSUNG_AC
// Use this for when you need to power on/off the device.
// Samsung A/C requires an extended length message when you want to
// change the power operating mode of the A/C unit.
void IRSamsungAc::sendExtended(const uint16_t repeat, const bool calcchecksum) {
if (calcchecksum) checksum();
uint8_t extended_state[kSamsungAcExtendedStateLength] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0xD2, 0x0F, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
// Copy/convert the internal state to an extended state.
for (uint16_t i = 0; i < kSamsungACSectionLength; i++)
extended_state[i] = remote_state[i];
for (uint16_t i = kSamsungACSectionLength; i < kSamsungAcStateLength; i++)
extended_state[i + kSamsungACSectionLength] = remote_state[i];
// extended_state[8] seems special. This is a guess on how to calculate it.
extended_state[8] = (extended_state[1] & 0x9F) | 0x40;
// Send it.
_irsend.sendSamsungAC(extended_state, kSamsungAcExtendedStateLength, repeat);
}
#endif // SEND_SAMSUNG_AC
uint8_t *IRSamsungAc::getRaw() {
checksum();
return remote_state;
}
void IRSamsungAc::setRaw(const uint8_t new_code[], const uint16_t length) {
for (uint8_t i = 0; i < length && i < kSamsungAcExtendedStateLength; i++) {
remote_state[i] = new_code[i];
}
// Shrink the extended state into a normal state.
if (length > kSamsungAcStateLength) {
for (uint8_t i = kSamsungAcStateLength; i < length; i++)
remote_state[i - kSamsungACSectionLength] = remote_state[i];
}
}
void IRSamsungAc::on() {
remote_state[1] &= ~kSamsungAcPowerMask1;
remote_state[6] |= kSamsungAcPowerMask2;
}
void IRSamsungAc::off() {
remote_state[1] |= kSamsungAcPowerMask1;
remote_state[6] &= ~kSamsungAcPowerMask2;
}
void IRSamsungAc::setPower(const bool state) {
if (state)
on();
else
off();
}
bool IRSamsungAc::getPower() {
return ((remote_state[6] & kSamsungAcPowerMask2) != 0) &&
((remote_state[1] & kSamsungAcPowerMask1) == 0);
}
// Set the temp. in deg C
void IRSamsungAc::setTemp(const uint8_t temp) {
uint8_t newtemp = std::max(kSamsungAcMinTemp, temp);
newtemp = std::min(kSamsungAcMaxTemp, newtemp);
remote_state[11] = (remote_state[11] & ~kSamsungAcTempMask) |
((newtemp - kSamsungAcMinTemp) << 4);
}
// Return the set temp. in deg C
uint8_t IRSamsungAc::getTemp() {
return ((remote_state[11] & kSamsungAcTempMask) >> 4) + kSamsungAcMinTemp;
}
void IRSamsungAc::setMode(const uint8_t mode) {
// If we get an unexpected mode, default to AUTO.
uint8_t newmode = mode;
if (newmode > kSamsungAcHeat) newmode = kSamsungAcAuto;
remote_state[12] = (remote_state[12] & ~kSamsungAcModeMask) | (newmode << 4);
// Auto mode has a special fan setting valid only in auto mode.
if (newmode == kSamsungAcAuto) {
setFan(kSamsungAcFanAuto2);
} else {
if (getFan() == kSamsungAcFanAuto2) // Non-Auto can't have this fan setting
setFan(kSamsungAcFanAuto); // Default to something safe.
}
}
uint8_t IRSamsungAc::getMode() {
return (remote_state[12] & kSamsungAcModeMask) >> 4;
}
void IRSamsungAc::setFan(const uint8_t speed) {
switch (speed) {
case kSamsungAcFanAuto:
case kSamsungAcFanLow:
case kSamsungAcFanMed:
case kSamsungAcFanHigh:
case kSamsungAcFanTurbo:
if (getMode() == kSamsungAcAuto) return; // Not valid in Auto mode.
break;
case kSamsungAcFanAuto2: // Special fan setting for when in Auto mode.
if (getMode() != kSamsungAcAuto) return;
break;
default:
return;
}
remote_state[12] = (remote_state[12] & ~kSamsungAcFanMask) | (speed << 1);
}
uint8_t IRSamsungAc::getFan() {
return ((remote_state[12] & kSamsungAcFanMask) >> 1);
}
bool IRSamsungAc::getSwing() {
// TODO(Hollako): Explain why sometimes the LSB of remote_state[9] is a 1.
// e.g. 0xAE or 0XAF for swing move.
return ((remote_state[9] & kSamsungAcSwingMask) >> 4) == kSamsungAcSwingMove;
}
void IRSamsungAc::setSwing(const bool state) {
// TODO(Hollako): Explain why sometimes the LSB of remote_state[9] is a 1.
// e.g. 0xAE or 0XAF for swing move.
remote_state[9] &= ~kSamsungAcSwingMask; // Clear the previous swing state.
if (state)
remote_state[9] |= (kSamsungAcSwingMove << 4);
else
remote_state[9] |= (kSamsungAcSwingStop << 4);
}
bool IRSamsungAc::getBeep() { return remote_state[13] & kSamsungAcBeepMask; }
void IRSamsungAc::setBeep(const bool state) {
if (state)
remote_state[13] |= kSamsungAcBeepMask;
else
remote_state[13] &= ~kSamsungAcBeepMask;
}
bool IRSamsungAc::getClean() {
return (remote_state[10] & kSamsungAcCleanMask10) &&
(remote_state[11] & kSamsungAcCleanMask11);
}
void IRSamsungAc::setClean(const bool state) {
if (state) {
remote_state[10] |= kSamsungAcCleanMask10;
remote_state[11] |= kSamsungAcCleanMask11;
} else {
remote_state[10] &= ~kSamsungAcCleanMask10;
remote_state[11] &= ~kSamsungAcCleanMask11;
}
}
// Very unsure this is correct.
bool IRSamsungAc::getQuiet() {
return remote_state[11] & kSamsungAcQuietMask11;
}
// Very unsure this is correct.
void IRSamsungAc::setQuiet(const bool state) {
if (state) {
remote_state[11] |= kSamsungAcQuietMask11;
setFan(kSamsungAcFanAuto); // Quiet mode seems to set fan speed to auto.
} else {
remote_state[11] &= ~kSamsungAcQuietMask11;
}
}
// Convert the internal state into a human readable string.
#ifdef ARDUINO
String IRSamsungAc::toString() {
String result = "";
#else
std::string IRSamsungAc::toString() {
std::string result = "";
#endif // ARDUINO
result += "Power: ";
if (getPower())
result += "On";
else
result += "Off";
result += ", Mode: " + uint64ToString(getMode());
switch (getMode()) {
case kSamsungAcAuto:
result += " (AUTO)";
break;
case kSamsungAcCool:
result += " (COOL)";
break;
case kSamsungAcHeat:
result += " (HEAT)";
break;
case kSamsungAcDry:
result += " (DRY)";
break;
case kSamsungAcFan:
result += " (FAN)";
break;
default:
result += " (UNKNOWN)";
}
result += ", Temp: " + uint64ToString(getTemp()) + "C";
result += ", Fan: " + uint64ToString(getFan());
switch (getFan()) {
case kSamsungAcFanAuto:
case kSamsungAcFanAuto2:
result += " (AUTO)";
break;
case kSamsungAcFanLow:
result += " (LOW)";
break;
case kSamsungAcFanMed:
result += " (MED)";
break;
case kSamsungAcFanHigh:
result += " (HIGH)";
break;
case kSamsungAcFanTurbo:
result += " (TURBO)";
break;
default:
result += " (UNKNOWN)";
break;
}
result += ", Swing: ";
if (getSwing())
result += "On";
else
result += "Off";
result += ", Beep: ";
if (getBeep())
result += "On";
else
result += "Off";
result += ", Clean: ";
if (getBeep())
result += "On";
else
result += "Off";
result += ", Quiet: ";
if (getQuiet())
result += "On";
else
result += "Off";
return result;
}
#if DECODE_SAMSUNG_AC
// Decode the supplied Samsung 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 kSamsungAcBits
// 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 mostly work.
//
// Ref:
// https://github.com/markszabo/IRremoteESP8266/issues/505
bool IRrecv::decodeSamsungAC(decode_results *results, uint16_t nbits,
bool strict) {
if (results->rawlen < 2 * nbits + kHeader * 3 + kFooter * 2 - 1)
return false; // Can't possibly be a valid Samsung A/C message.
if (nbits != kSamsungAcBits && nbits != kSamsungAcExtendedBits) return false;
uint16_t offset = kStartOffset;
uint16_t dataBitsSoFar = 0;
match_result_t data_result;
// Message Header
if (!matchMark(results->rawbuf[offset++], kSamsungAcBitMark)) return false;
if (!matchSpace(results->rawbuf[offset++], kSamsungAcHdrSpace)) return false;
// Section(s)
for (uint16_t pos = kSamsungACSectionLength, i = 0; pos <= nbits / 8;
pos += kSamsungACSectionLength) {
uint64_t sectiondata = 0;
// Section Header
if (!matchMark(results->rawbuf[offset++], kSamsungAcSectionMark))
return false;
if (!matchSpace(results->rawbuf[offset++], kSamsungAcSectionSpace))
return false;
// Section Data
// Keep reading bytes until we either run out of section or state to fill.
for (; offset <= results->rawlen - 16 && i < pos;
i++, dataBitsSoFar += 8, offset += data_result.used) {
data_result = matchData(&(results->rawbuf[offset]), 8, kSamsungAcBitMark,
kSamsungAcOneSpace, kSamsungAcBitMark,
kSamsungAcZeroSpace, kTolerance, 0, false);
if (data_result.success == false) {
DPRINT("DEBUG: offset = ");
DPRINTLN(offset + data_result.used);
return false; // Fail
}
results->state[i] = data_result.data;
sectiondata = (sectiondata << 8) + data_result.data;
}
DPRINTLN("DEBUG: sectiondata = 0x" + uint64ToString(sectiondata, 16));
// Section Footer
if (!matchMark(results->rawbuf[offset++], kSamsungAcBitMark)) return false;
if (pos < nbits / 8) { // Inter-section gap.
if (!matchSpace(results->rawbuf[offset++], kSamsungAcSectionGap))
return false;
} else { // Last section / End of message gap.
if (offset <= results->rawlen &&
!matchAtLeast(results->rawbuf[offset++], kSamsungAcSectionGap))
return false;
}
}
// Compliance
// Re-check we got the correct size/length due to the way we read the data.
if (dataBitsSoFar != nbits) return false;
// Is the signature correct?
DPRINTLN("DEBUG: Checking signature.");
if (results->state[0] != 0x02 || results->state[2] != 0x0F) return false;
if (strict) {
// Is the checksum valid?
if (!IRSamsungAc::validChecksum(results->state, nbits / 8)) {
DPRINTLN("DEBUG: Checksum failed!");
return false;
}
}
// Success
results->decode_type = SAMSUNG_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_SAMSUNG_AC
+109
View File
@@ -0,0 +1,109 @@
// Samsung A/C
//
// Copyright 2018 David Conran
#ifndef IR_SAMSUNG_H_
#define IR_SAMSUNG_H_
#define __STDC_LIMIT_MACROS
#include <stdint.h>
#ifndef UNIT_TEST
#include <Arduino.h>
#else
#include <string>
#endif
#include "IRremoteESP8266.h"
#include "IRsend.h"
// SSSS AAA MMM SSSS U U N N GGGG
// S A A M M M S U U NN N G
// SSS AAAAA M M M SSS U U N N N G GG
// S A A M M S U U N NN G G
// SSSS A A M M SSSS UUU N N GGG
// Ref:
// https://github.com/markszabo/IRremoteESP8266/issues/505
// Constants
const uint8_t kSamsungAcAuto = 0;
const uint8_t kSamsungAcCool = 1;
const uint8_t kSamsungAcDry = 2;
const uint8_t kSamsungAcFan = 3;
const uint8_t kSamsungAcHeat = 4;
const uint8_t kSamsungAcModeMask = 0x70;
const uint8_t kSamsungAcFanAuto = 0;
const uint8_t kSamsungAcFanLow = 2;
const uint8_t kSamsungAcFanMed = 4;
const uint8_t kSamsungAcFanHigh = 5;
const uint8_t kSamsungAcFanAuto2 = 6;
const uint8_t kSamsungAcFanTurbo = 7;
const uint8_t kSamsungAcMinTemp = 16; // 16C
const uint8_t kSamsungAcMaxTemp = 30; // 30C
const uint8_t kSamsungAcAutoTemp = 25; // 25C
const uint8_t kSamsungAcTempMask = 0xF0;
const uint8_t kSamsungAcPowerMask1 = 0x20;
const uint8_t kSamsungAcPowerMask2 = 0x30;
const uint8_t kSamsungAcFanMask = 0x0E;
const uint8_t kSamsungAcSwingMask = 0x70;
const uint8_t kSamsungAcSwingMove = 0b010;
const uint8_t kSamsungAcSwingStop = 0b111;
const uint8_t kSamsungAcBeepMask = 0x02;
const uint8_t kSamsungAcCleanMask10 = 0x80;
const uint8_t kSamsungAcCleanMask11 = 0x02;
const uint8_t kSamsungAcQuietMask11 = 0x01;
const uint16_t kSamsungACSectionLength = 7;
const uint64_t kSamsungAcPowerSection = 0x1D20F00000000;
// Classes
class IRSamsungAc {
public:
explicit IRSamsungAc(uint16_t pin);
void stateReset();
#if SEND_SAMSUNG_AC
void send(const uint16_t repeat = kSamsungAcDefaultRepeat,
const bool calcchecksum = true);
void sendExtended(const uint16_t repeat = kSamsungAcDefaultRepeat,
const bool calcchecksum = true);
#endif // SEND_SAMSUNG_AC
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 mode);
uint8_t getMode();
void setSwing(const bool state);
bool getSwing();
void setBeep(const bool state);
bool getBeep();
void setClean(const bool state);
bool getClean();
void setQuiet(const bool state);
bool getQuiet();
uint8_t* getRaw();
void setRaw(const uint8_t new_code[],
const uint16_t length = kSamsungAcStateLength);
static bool validChecksum(const uint8_t state[],
const uint16_t length = kSamsungAcStateLength);
static uint8_t calcChecksum(const uint8_t state[],
const uint16_t length = kSamsungAcStateLength);
#ifdef ARDUINO
String toString();
#else
std::string toString();
#endif
private:
// The state of the IR remote in IR code form.
uint8_t remote_state[kSamsungAcExtendedStateLength];
void checksum(const uint16_t length = kSamsungAcStateLength);
IRsend _irsend;
};
#endif // IR_SAMSUNG_H_
+51 -48
View File
@@ -20,31 +20,35 @@
// Sanyo SA 8650B
// Ref:
// https://github.com/z3t0/Arduino-IRremote/blob/master/ir_Sanyo.cpp
#define SANYO_SA8650B_HDR_MARK 3500U // seen range 3500
#define SANYO_SA8650B_HDR_SPACE 950U // seen 950
#define SANYO_SA8650B_ONE_MARK 2400U // seen 2400
#define SANYO_SA8650B_ZERO_MARK 700U // seen 700
const uint16_t kSanyoSa8650bHdrMark = 3500; // seen range 3500
const uint16_t kSanyoSa8650bHdrSpace = 950; // seen 950
const uint16_t kSanyoSa8650bOneMark = 2400; // seen 2400
const uint16_t kSanyoSa8650bZeroMark = 700; // seen 700
// usually see 713 - not using ticks as get number wrapround
#define SANYO_SA8650B_DOUBLE_SPACE_USECS 800U
#define SANYO_SA8650B_RPT_LENGTH 45000U
const uint16_t kSanyoSa8650bDoubleSpaceUsecs = 800;
const uint16_t kSanyoSa8650bRptLength = 45000;
// Sanyo LC7461
// Ref:
// https://github.com/marcosamarinho/IRremoteESP8266/blob/master/ir_Sanyo.cpp
// http://slydiman.narod.ru/scr/kb/sanyo.htm
// http://pdf.datasheetcatalog.com/datasheet/sanyo/LC7461.pdf
#define SANYO_LC7461_ADDRESS_MASK ((1 << SANYO_LC7461_ADDRESS_BITS) - 1)
#define SANYO_LC7461_COMMAND_MASK ((1 << SANYO_LC7461_COMMAND_BITS) - 1)
#define SANYO_LC7461_HDR_MARK 9000U
#define SANYO_LC7461_HDR_SPACE 4500U
#define SANYO_LC7461_BIT_MARK 560U // 1T
#define SANYO_LC7461_ONE_SPACE 1690U // 3T
#define SANYO_LC7461_ZERO_SPACE 560U // 1T
#define SANYO_LC7461_MIN_COMMAND_LENGTH 108000UL
#define SANYO_LC7461_MIN_GAP SANYO_LC7461_MIN_COMMAND_LENGTH - \
(SANYO_LC7461_HDR_MARK + SANYO_LC7461_HDR_SPACE + SANYO_LC7461_BITS * \
(SANYO_LC7461_BIT_MARK + (SANYO_LC7461_ONE_SPACE + \
SANYO_LC7461_ZERO_SPACE) / 2) \
+ SANYO_LC7461_BIT_MARK)
const uint16_t kSanyoLc7461AddressMask = (1 << kSanyoLC7461AddressBits) - 1;
const uint16_t kSanyoLc7461CommandMask = (1 << kSanyoLC7461CommandBits) - 1;
const uint16_t kSanyoLc7461HdrMark = 9000;
const uint16_t kSanyoLc7461HdrSpace = 4500;
const uint16_t kSanyoLc7461BitMark = 560; // 1T
const uint16_t kSanyoLc7461OneSpace = 1690; // 3T
const uint16_t kSanyoLc7461ZeroSpace = 560; // 1T
const uint32_t kSanyoLc7461MinCommandLength = 108000;
const uint16_t kSanyoLc7461MinGap =
kSanyoLc7461MinCommandLength -
(kSanyoLc7461HdrMark + kSanyoLc7461HdrSpace +
kSanyoLC7461Bits * (kSanyoLc7461BitMark +
(kSanyoLc7461OneSpace + kSanyoLc7461ZeroSpace) / 2) +
kSanyoLc7461BitMark);
#if SEND_SANYO
// Construct a Sanyo LC7461 message.
@@ -61,18 +65,18 @@
// According with LIRC, this protocol is used on Sanyo, Aiwa and Chinon
uint64_t IRsend::encodeSanyoLC7461(uint16_t address, uint8_t command) {
// Mask our input values to ensure the correct bit sizes.
address &= SANYO_LC7461_ADDRESS_MASK;
command &= SANYO_LC7461_COMMAND_MASK;
address &= kSanyoLc7461AddressMask;
command &= kSanyoLc7461CommandMask;
uint64_t data = address;
address ^= SANYO_LC7461_ADDRESS_MASK; // Invert the 13 LSBs.
address ^= kSanyoLc7461AddressMask; // Invert the 13 LSBs.
// Append the now inverted address.
data = (data << SANYO_LC7461_ADDRESS_BITS) | address;
data = (data << kSanyoLC7461AddressBits) | address;
// Append the command.
data = (data << SANYO_LC7461_COMMAND_BITS) | command;
command ^= SANYO_LC7461_COMMAND_MASK; // Invert the command.
data = (data << kSanyoLC7461CommandBits) | command;
command ^= kSanyoLc7461CommandMask; // Invert the command.
// Append the now inverted command.
data = (data << SANYO_LC7461_COMMAND_BITS) | command;
data = (data << kSanyoLC7461CommandBits) | command;
return data;
}
@@ -127,31 +131,30 @@ void IRsend::sendSanyoLC7461(uint64_t data, uint16_t nbits, uint16_t repeat) {
// http://pdf.datasheetcatalog.com/datasheet/sanyo/LC7461.pdf
bool IRrecv::decodeSanyoLC7461(decode_results *results, uint16_t nbits,
bool strict) {
if (strict && nbits != SANYO_LC7461_BITS)
if (strict && nbits != kSanyoLC7461Bits)
return false; // Not strictly in spec.
// This protocol is basically a 42-bit variant of the NEC protocol.
if (!decodeNEC(results, nbits, false))
return false; // Didn't match a NEC format (without strict)
// Bits 30 to 42+.
uint16_t address = results->value >> (SANYO_LC7461_BITS -
SANYO_LC7461_ADDRESS_BITS);
uint16_t address =
results->value >> (kSanyoLC7461Bits - kSanyoLC7461AddressBits);
// Bits 9 to 16.
uint8_t command = (results->value >> SANYO_LC7461_COMMAND_BITS) &
SANYO_LC7461_COMMAND_MASK;
uint8_t command =
(results->value >> kSanyoLC7461CommandBits) & kSanyoLc7461CommandMask;
// Compliance
if (strict) {
if (results->bits != nbits)
return false;
if (results->bits != nbits) return false;
// Bits 17 to 29.
uint16_t inverted_address =
(results->value >> (SANYO_LC7461_COMMAND_BITS * 2)) &
SANYO_LC7461_ADDRESS_MASK;
(results->value >> (kSanyoLC7461CommandBits * 2)) &
kSanyoLc7461AddressMask;
// Bits 1-8.
uint8_t inverted_command = results->value & SANYO_LC7461_COMMAND_MASK;
if ((address ^ SANYO_LC7461_ADDRESS_MASK) != inverted_address)
uint8_t inverted_command = results->value & kSanyoLc7461CommandMask;
if ((address ^ kSanyoLc7461AddressMask) != inverted_address)
return false; // Address integrity check failed.
if ((command ^ SANYO_LC7461_COMMAND_MASK) != inverted_command)
if ((command ^ kSanyoLc7461CommandMask) != inverted_command)
return false; // Command integrity check failed.
}
@@ -182,9 +185,9 @@ bool IRrecv::decodeSanyoLC7461(decode_results *results, uint16_t nbits,
// Ref:
// https://github.com/z3t0/Arduino-IRremote/blob/master/ir_Sanyo.cpp
bool IRrecv::decodeSanyo(decode_results *results, uint16_t nbits, bool strict) {
if (results->rawlen < 2 * nbits + HEADER - 1)
if (results->rawlen < 2 * nbits + kHeader - 1)
return false; // Shorter than shortest possible.
if (strict && nbits != SANYO_SA8650B_BITS)
if (strict && nbits != kSanyoSA8650BBits)
return false; // Doesn't match the spec.
uint16_t offset = 0;
@@ -192,9 +195,9 @@ bool IRrecv::decodeSanyo(decode_results *results, uint16_t nbits, bool strict) {
// TODO(crankyoldgit): This repeat code looks like garbage, it should never
// match or if it does, it won't be reliable. We should probably just
// remove it.
if (results->rawbuf[offset++] < SANYO_SA8650B_DOUBLE_SPACE_USECS) {
if (results->rawbuf[offset++] < kSanyoSa8650bDoubleSpaceUsecs) {
results->bits = 0;
results->value = REPEAT;
results->value = kRepeat;
results->decode_type = SANYO;
results->address = 0;
results->command = 0;
@@ -203,27 +206,27 @@ bool IRrecv::decodeSanyo(decode_results *results, uint16_t nbits, bool strict) {
}
// Header
if (!matchMark(results->rawbuf[offset++], SANYO_SA8650B_HDR_MARK))
if (!matchMark(results->rawbuf[offset++], kSanyoSa8650bHdrMark))
return false;
// NOTE: These next two lines look very wrong. Treat as suspect.
if (!matchMark(results->rawbuf[offset++], SANYO_SA8650B_HDR_MARK))
if (!matchMark(results->rawbuf[offset++], kSanyoSa8650bHdrMark))
return false;
// Data
uint64_t data = 0;
while (offset + 1 < results->rawlen) {
if (!matchSpace(results->rawbuf[offset], SANYO_SA8650B_HDR_SPACE))
if (!matchSpace(results->rawbuf[offset], kSanyoSa8650bHdrSpace))
break;
offset++;
if (matchMark(results->rawbuf[offset], SANYO_SA8650B_ONE_MARK))
if (matchMark(results->rawbuf[offset], kSanyoSa8650bOneMark))
data = (data << 1) | 1; // 1
else if (matchMark(results->rawbuf[offset], SANYO_SA8650B_ZERO_MARK))
else if (matchMark(results->rawbuf[offset], kSanyoSa8650bZeroMark))
data <<= 1; // 0
else
return false;
offset++;
}
if (strict && SANYO_SA8650B_BITS > (offset - 1U) / 2U)
if (strict && kSanyoSA8650BBits > (offset - 1U) / 2U)
return false;
// Success
+51 -59
View File
@@ -22,27 +22,27 @@
// Ref:
// GlobalCache's IR Control Tower data.
// http://www.sbprojects.com/knowledge/ir/sharp.php
#define SHARP_TICK 26U
#define SHARP_BIT_MARK_TICKS 10U
#define SHARP_BIT_MARK (SHARP_BIT_MARK_TICKS * SHARP_TICK)
#define SHARP_ONE_SPACE_TICKS 70U
#define SHARP_ONE_SPACE (SHARP_ONE_SPACE_TICKS * SHARP_TICK)
#define SHARP_ZERO_SPACE_TICKS 30U
#define SHARP_ZERO_SPACE (SHARP_ZERO_SPACE_TICKS * SHARP_TICK)
#define SHARP_GAP_TICKS 1677U
#define SHARP_GAP (SHARP_GAP_TICKS * SHARP_TICK)
const uint16_t kSharpTick = 26;
const uint16_t kSharpBitMarkTicks = 10;
const uint16_t kSharpBitMark = kSharpBitMarkTicks * kSharpTick;
const uint16_t kSharpOneSpaceTicks = 70;
const uint16_t kSharpOneSpace = kSharpOneSpaceTicks * kSharpTick;
const uint16_t kSharpZeroSpaceTicks = 30;
const uint16_t kSharpZeroSpace = kSharpZeroSpaceTicks * kSharpTick;
const uint16_t kSharpGapTicks = 1677;
const uint16_t kSharpGap = kSharpGapTicks * kSharpTick;
// Address(5) + Command(8) + Expansion(1) + Check(1)
#define SHARP_TOGGLE_MASK ((1 << (SHARP_BITS - SHARP_ADDRESS_BITS)) - 1)
#define SHARP_ADDRESS_MASK ((1 << SHARP_ADDRESS_BITS) - 1)
#define SHARP_COMMAND_MASK ((1 << SHARP_COMMAND_BITS) - 1)
const uint64_t kSharpToggleMask =
((uint64_t)1 << (kSharpBits - kSharpAddressBits)) - 1;
const uint64_t kSharpAddressMask = ((uint64_t)1 << kSharpAddressBits) - 1;
const uint64_t kSharpCommandMask = ((uint64_t)1 << kSharpCommandBits) - 1;
#if (SEND_SHARP || SEND_DENON)
// Send a (raw) Sharp message
//
// Args:
// data: Contents of the message to be sent.
// nbits: Nr. of bits of data to be sent. Typically SHARP_BITS.
// nbits: Nr. of bits of data to be sent. Typically kSharpBits.
// repeat: Nr. of additional times the message is to be sent.
//
// Status: BETA / Previously working fine.
@@ -67,14 +67,13 @@ void IRsend::sendSharpRaw(uint64_t data, uint16_t nbits, uint16_t repeat) {
// normal), however all data points to that being incorrect.
for (uint8_t n = 0; n < 2; n++) {
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.
kSharpBitMark, kSharpOneSpace, kSharpBitMark, kSharpZeroSpace,
kSharpBitMark, kSharpGap, 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;
data ^= kSharpToggleMask;
}
}
}
@@ -107,17 +106,17 @@ uint32_t IRsend::encodeSharp(uint16_t address, uint16_t command,
uint16_t expansion, uint16_t check,
bool MSBfirst) {
// Mask any unexpected bits.
address &= ((1 << SHARP_ADDRESS_BITS) - 1);
command &= ((1 << SHARP_COMMAND_BITS) - 1);
address &= ((1 << kSharpAddressBits) - 1);
command &= ((1 << kSharpCommandBits) - 1);
expansion &= 1;
check &= 1;
if (!MSBfirst) { // Correct bit order if needed.
address = reverseBits(address, SHARP_ADDRESS_BITS);
command = reverseBits(command, SHARP_COMMAND_BITS);
address = reverseBits(address, kSharpAddressBits);
command = reverseBits(command, kSharpCommandBits);
}
// Concatinate all the bits.
return (address << (SHARP_COMMAND_BITS + 2)) | (command << 2) |
return (address << (kSharpCommandBits + 2)) | (command << 2) |
(expansion << 1) | check;
}
@@ -126,7 +125,7 @@ uint32_t IRsend::encodeSharp(uint16_t address, uint16_t command,
// Args:
// address: Address value to be sent.
// command: Command value to be sent.
// nbits: Nr. of bits of data to be sent. Typically SHARP_BITS.
// nbits: Nr. of bits of data to be sent. Typically kSharpBits.
// repeat: Nr. of additional times the message is to be sent.
//
// Status: DEPRICATED / Previously working fine.
@@ -156,7 +155,7 @@ void IRsend::sendSharp(uint16_t address, uint16_t command, uint16_t nbits,
//
// Args:
// results: Ptr to the data to decode and where to store the decode result.
// nbits: Nr. of data bits to expect. Typically SHARP_BITS.
// nbits: Nr. of data bits to expect. Typically kSharpBits.
// strict: Flag indicating if we should perform strict matching.
// expansion: Should we expect the expansion bit to be set. Default is true.
// Returns:
@@ -175,90 +174,83 @@ void IRsend::sendSharp(uint16_t address, uint16_t command, uint16_t nbits,
// http://www.hifi-remote.com/johnsfine/DecodeIR.html#Sharp
bool IRrecv::decodeSharp(decode_results *results, uint16_t nbits, bool strict,
bool expansion) {
if (results->rawlen < 2 * nbits + FOOTER - 1)
if (results->rawlen < 2 * nbits + kFooter - 1)
return false; // Not enough entries to be a Sharp message.
// Compliance
if (strict) {
if (nbits != SHARP_BITS)
return false; // Request is out of spec.
if (nbits != kSharpBits) return false; // Request is out of spec.
// DISABLED - See TODO
#ifdef UNIT_TEST
// An in spec message has the data sent normally, then inverted. So we
// expect twice as many entries than to just get the results.
if (results->rawlen < 2 * (2 * nbits + FOOTER))
return false;
if (results->rawlen < 2 * (2 * nbits + kFooter)) return false;
#endif
}
uint64_t data = 0;
uint16_t offset = OFFSET_START;
uint16_t offset = kStartOffset;
// No header
// But try to auto-calibrate off the initial mark signal.
if (!matchMark(results->rawbuf[offset], SHARP_BIT_MARK, 35)) return false;
if (!matchMark(results->rawbuf[offset], kSharpBitMark, 35)) return false;
// Calculate how long the common tick time is based on the header mark.
uint32_t tick = results->rawbuf[offset] * RAWTICK / SHARP_BIT_MARK_TICKS;
uint32_t tick = results->rawbuf[offset] * kRawTick / kSharpBitMarkTicks;
// Data
for (uint16_t i = 0; i < nbits; i++, offset++) {
// Use a higher tolerance value for SHARP_BIT_MARK as it is quite small.
if (!matchMark(results->rawbuf[offset++], SHARP_BIT_MARK_TICKS * tick, 35))
// Use a higher tolerance value for kSharpBitMark as it is quite small.
if (!matchMark(results->rawbuf[offset++], kSharpBitMarkTicks * tick, 35))
return false;
if (matchSpace(results->rawbuf[offset], SHARP_ONE_SPACE_TICKS * tick))
if (matchSpace(results->rawbuf[offset], kSharpOneSpaceTicks * tick))
data = (data << 1) | 1; // 1
else if (matchSpace(results->rawbuf[offset], SHARP_ZERO_SPACE_TICKS * tick))
else if (matchSpace(results->rawbuf[offset], kSharpZeroSpaceTicks * tick))
data <<= 1; // 0
else
return false;
}
// Footer
if (!match(results->rawbuf[offset++], SHARP_BIT_MARK_TICKS * tick))
if (!match(results->rawbuf[offset++], kSharpBitMarkTicks * tick))
return false;
if (offset < results->rawlen &&
!matchAtLeast(results->rawbuf[offset], SHARP_GAP_TICKS * tick))
!matchAtLeast(results->rawbuf[offset], kSharpGapTicks * tick))
return false;
// Compliance
if (strict) {
// Check the state of the expansion bit is what we expect.
if ((data & 0b10) >> 1 != expansion)
return false;
if ((data & 0b10) >> 1 != expansion) return false;
// The check bit should be cleared in a normal message.
if (data & 0b1)
return false;
// DISABLED - See TODO
if (data & 0b1) return false;
// DISABLED - See TODO
#ifdef UNIT_TEST
// Grab the second copy of the data (i.e. inverted)
// Header
// i.e. The inter-data/command repeat gap.
if (!matchSpace(results->rawbuf[offset++], SHARP_GAP_TICKS * tick))
if (!matchSpace(results->rawbuf[offset++], kSharpGapTicks * tick))
return false;
// Data
uint64_t second_data = 0;
for (uint16_t i = 0; i < nbits; i++, offset++) {
// Use a higher tolerance value for SHARP_BIT_MARK as it is quite small.
if (!matchMark(results->rawbuf[offset++], SHARP_BIT_MARK_TICKS * tick,
35))
// Use a higher tolerance value for kSharpBitMark as it is quite small.
if (!matchMark(results->rawbuf[offset++], kSharpBitMarkTicks * tick, 35))
return false;
if (matchSpace(results->rawbuf[offset], SHARP_ONE_SPACE_TICKS * tick))
if (matchSpace(results->rawbuf[offset], kSharpOneSpaceTicks * tick))
second_data = (second_data << 1) | 1; // 1
else if (matchSpace(results->rawbuf[offset],
SHARP_ZERO_SPACE_TICKS * tick))
else if (matchSpace(results->rawbuf[offset], kSharpZeroSpaceTicks * tick))
second_data <<= 1; // 0
else
return false;
}
// Footer
if (!match(results->rawbuf[offset++], SHARP_BIT_MARK_TICKS * tick))
if (!match(results->rawbuf[offset++], kSharpBitMarkTicks * tick))
return false;
if (offset < results->rawlen &&
!matchAtLeast(results->rawbuf[offset], SHARP_GAP_TICKS * tick))
!matchAtLeast(results->rawbuf[offset], kSharpGapTicks * tick))
return false;
// Check that second_data has been inverted correctly.
if (data != (second_data ^ SHARP_TOGGLE_MASK))
return false;
if (data != (second_data ^ kSharpToggleMask)) return false;
#endif // UNIT_TEST
}
@@ -267,9 +259,9 @@ bool IRrecv::decodeSharp(decode_results *results, uint16_t nbits, bool strict,
results->bits = nbits;
results->value = data;
// Address & command are actually transmitted in LSB first order.
results->address = reverseBits(data, nbits) & SHARP_ADDRESS_MASK;
results->command = reverseBits((data >> 2) & SHARP_COMMAND_MASK,
SHARP_COMMAND_BITS);
results->address = reverseBits(data, nbits) & kSharpAddressMask;
results->command =
reverseBits((data >> 2) & kSharpCommandMask, kSharpCommandBits);
return true;
}
#endif // (DECODE_SHARP || DECODE_DENON)
+4 -5
View File
@@ -14,16 +14,15 @@
//
// Args:
// data: The contents of the command you want to send.
// nbits: The bit size of the command being sent. (SHERWOOD_BITS)
// nbits: The bit size of the command being sent. (kSherwoodBits)
// repeat: The nr. of times you want the command to be repeated. (Default: 1)
//
// Status: STABLE / Known working.
//
// Note:
// Sherwood remote codes appear to be NEC codes with a manditory repeat code.
// i.e. repeat should be >= SHERWOOD_MIN_REPEAT (1).
void IRsend::sendSherwood(uint64_t data, uint16_t nbits,
uint16_t repeat) {
sendNEC(data, nbits, std::max((uint16_t) SHERWOOD_MIN_REPEAT, repeat));
// i.e. repeat should be >= kSherwoodMinRepeat (1).
void IRsend::sendSherwood(uint64_t data, uint16_t nbits, uint16_t repeat) {
sendNEC(data, nbits, std::max((uint16_t)kSherwoodMinRepeat, repeat));
}
#endif
+36 -39
View File
@@ -19,19 +19,19 @@
// Constants
// Ref:
// http://www.sbprojects.com/knowledge/ir/sirc.php
#define SONY_TICK 200U
#define SONY_HDR_MARK_TICKS 12U
#define SONY_HDR_MARK (SONY_HDR_MARK_TICKS * SONY_TICK)
#define SONY_SPACE_TICKS 3U
#define SONY_SPACE (SONY_SPACE_TICKS * SONY_TICK)
#define SONY_ONE_MARK_TICKS 6U
#define SONY_ONE_MARK (SONY_ONE_MARK_TICKS * SONY_TICK)
#define SONY_ZERO_MARK_TICKS 3U
#define SONY_ZERO_MARK (SONY_ZERO_MARK_TICKS * SONY_TICK)
#define SONY_RPT_LENGTH_TICKS 225U
#define SONY_RPT_LENGTH (SONY_RPT_LENGTH_TICKS * SONY_TICK)
#define SONY_MIN_GAP_TICKS 50U
#define SONY_MIN_GAP (SONY_MIN_GAP_TICKS * SONY_TICK)
const uint16_t kSonyTick = 200;
const uint16_t kSonyHdrMarkTicks = 12;
const uint16_t kSonyHdrMark = kSonyHdrMarkTicks * kSonyTick;
const uint16_t kSonySpaceTicks = 3;
const uint16_t kSonySpace = kSonySpaceTicks * kSonyTick;
const uint16_t kSonyOneMarkTicks = 6;
const uint16_t kSonyOneMark = kSonyOneMarkTicks * kSonyTick;
const uint16_t kSonyZeroMarkTicks = 3;
const uint16_t kSonyZeroMark = kSonyZeroMarkTicks * kSonyTick;
const uint16_t kSonyRptLengthTicks = 225;
const uint16_t kSonyRptLength = kSonyRptLengthTicks * kSonyTick;
const uint16_t kSonyMinGapTicks = 50;
const uint16_t kSonyMinGap = kSonyMinGapTicks * kSonyTick;
#if SEND_SONY
// Send a Sony/SIRC(Serial Infra-Red Control) message.
@@ -50,12 +50,10 @@
// Ref:
// http://www.sbprojects.com/knowledge/ir/sirc.php
void IRsend::sendSony(uint64_t data, uint16_t nbits, uint16_t repeat) {
sendGeneric(SONY_HDR_MARK, SONY_SPACE,
SONY_ONE_MARK, SONY_SPACE,
SONY_ZERO_MARK, SONY_SPACE,
sendGeneric(kSonyHdrMark, kSonySpace, kSonyOneMark, kSonySpace, kSonyZeroMark,
kSonySpace,
0, // No Footer mark.
SONY_MIN_GAP, SONY_RPT_LENGTH,
data, nbits, 40, true, repeat, 33);
kSonyMinGap, kSonyRptLength, data, nbits, 40, true, repeat, 33);
}
// Convert Sony/SIRC command, address, & extended bits into sendSony format.
@@ -68,8 +66,8 @@ void IRsend::sendSony(uint64_t data, uint16_t nbits, uint16_t repeat) {
// A sendSony compatible data message.
//
// Status: BETA / Should be working.
uint32_t IRsend::encodeSony(uint16_t nbits, uint16_t command,
uint16_t address, uint16_t extended) {
uint32_t IRsend::encodeSony(uint16_t nbits, uint16_t command, uint16_t address,
uint16_t extended) {
uint32_t result = 0;
switch (nbits) {
case 12: // 5 address bits.
@@ -107,7 +105,7 @@ uint32_t IRsend::encodeSony(uint16_t nbits, uint16_t command,
// Ref:
// http://www.sbprojects.com/knowledge/ir/sirc.php
bool IRrecv::decodeSony(decode_results *results, uint16_t nbits, bool strict) {
if (results->rawlen < 2 * nbits + HEADER - 1)
if (results->rawlen < 2 * nbits + kHeader - 1)
return false; // Message is smaller than we expected.
// Compliance
@@ -123,31 +121,30 @@ bool IRrecv::decodeSony(decode_results *results, uint16_t nbits, bool strict) {
}
uint64_t data = 0;
uint16_t offset = OFFSET_START;
uint16_t offset = kStartOffset;
uint16_t actualBits;
uint32_t timeSoFar = 0; // Time in uSecs of the message length.
// Header
timeSoFar += results->rawbuf[offset] * RAWTICK;
if (!matchMark(results->rawbuf[offset], SONY_HDR_MARK))
return false;
timeSoFar += results->rawbuf[offset] * kRawTick;
if (!matchMark(results->rawbuf[offset], kSonyHdrMark)) return false;
// Calculate how long the common tick time is based on the header mark.
uint32_t tick = results->rawbuf[offset++] * RAWTICK / SONY_HDR_MARK_TICKS;
uint32_t tick = results->rawbuf[offset++] * kRawTick / kSonyHdrMarkTicks;
// Data
for (actualBits = 0; offset < results->rawlen - 1; actualBits++, offset++) {
// The gap after a Sony packet for a repeat should be SONY_MIN_GAP or
// (SONY_RPT_LENGTH - timeSoFar) according to the spec.
if (matchSpace(results->rawbuf[offset], SONY_MIN_GAP_TICKS * tick) ||
matchAtLeast(results->rawbuf[offset], SONY_RPT_LENGTH - timeSoFar))
// The gap after a Sony packet for a repeat should be kSonyMinGap or
// (kSonyRptLength - timeSoFar) according to the spec.
if (matchSpace(results->rawbuf[offset], kSonyMinGapTicks * tick) ||
matchAtLeast(results->rawbuf[offset], kSonyRptLength - timeSoFar))
break; // Found a repeat space.
timeSoFar += results->rawbuf[offset] * RAWTICK;
if (!matchSpace(results->rawbuf[offset++], SONY_SPACE_TICKS * tick))
timeSoFar += results->rawbuf[offset] * kRawTick;
if (!matchSpace(results->rawbuf[offset++], kSonySpaceTicks * tick))
return false;
timeSoFar += results->rawbuf[offset] * RAWTICK;
if (matchMark(results->rawbuf[offset], SONY_ONE_MARK_TICKS * tick))
timeSoFar += results->rawbuf[offset] * kRawTick;
if (matchMark(results->rawbuf[offset], kSonyOneMarkTicks * tick))
data = (data << 1) | 1;
else if (matchMark(results->rawbuf[offset], SONY_ZERO_MARK_TICKS * tick))
else if (matchMark(results->rawbuf[offset], kSonyZeroMarkTicks * tick))
data <<= 1;
else
return false;
@@ -166,14 +163,14 @@ bool IRrecv::decodeSony(decode_results *results, uint16_t nbits, bool strict) {
data = reverseBits(data, actualBits);
// Decode the address & command from raw decode value.
switch (actualBits) {
case 12: // 7 command bits, 5 address bits.
case 15: // 7 command bits, 8 address bits.
case 12: // 7 command bits, 5 address bits.
case 15: // 7 command bits, 8 address bits.
results->command = data & 0x7F; // Bits 0-6
results->address = data >> 7; // Bits 7-14
results->address = data >> 7; // Bits 7-14
break;
case 20: // 7 command bits, 5 address bits, 8 extended (command) bits.
results->command = (data & 0x7F) + ((data >> 12) << 7); // Bits 0-6,12-19
results->address = (data >> 7) & 0x1F; // Bits 7-11
results->address = (data >> 7) & 0x1F; // Bits 7-11
break;
default: // Shouldn't happen, but just in case.
results->address = 0;
+66 -75
View File
@@ -28,33 +28,32 @@
// 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
const uint16_t kToshibaAcHdrMark = 4400;
const uint16_t kToshibaAcHdrSpace = 4300;
const uint16_t kToshibaAcBitMark = 543;
const uint16_t kToshibaAcOneSpace = 1623;
const uint16_t kToshibaAcZeroSpace = 472;
const uint16_t kToshibaAcMinGap = 7048;
#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)
// nbytes: Nr. of bytes of data in the array. (>=kToshibaACStateLength)
// repeat: Nr. of times the message is to be repeated.
// (Default = TOSHIBA_AC_MIN_REPEAT).
// (Default = kToshibaACMinRepeat).
//
// Status: StABLE / Working.
//
void IRsend::sendToshibaAC(unsigned char data[], uint16_t nbytes,
uint16_t repeat) {
if (nbytes < TOSHIBA_AC_STATE_LENGTH)
uint16_t repeat) {
if (nbytes < kToshibaACStateLength)
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);
sendGeneric(kToshibaAcHdrMark, kToshibaAcHdrSpace, kToshibaAcBitMark,
kToshibaAcOneSpace, kToshibaAcBitMark, kToshibaAcZeroSpace,
kToshibaAcBitMark, kToshibaAcMinGap, data, nbytes, 38, true,
repeat, 50);
}
#endif // SEND_TOSHIBA_AC
@@ -65,9 +64,7 @@ void IRsend::sendToshibaAC(unsigned char data[], uint16_t nbytes,
// Status: STABLE / Working.
//
// Initialise the object.
IRToshibaAC::IRToshibaAC(uint16_t pin) : _irsend(pin) {
stateReset();
}
IRToshibaAC::IRToshibaAC(uint16_t pin) : _irsend(pin) { stateReset(); }
// Reset the state of the remote to a known good state/sequence.
void IRToshibaAC::stateReset() {
@@ -75,29 +72,26 @@ void IRToshibaAC::stateReset() {
// 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] = {
// uint8_t remote_state[kToshibaACStateLength] = {
// 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;
for (uint8_t i = 5; i < kToshibaACStateLength; 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();
}
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);
void IRToshibaAC::send(const uint16_t repeat) {
checksum(); // Ensure correct checksum before sending.
_irsend.sendToshibaAC(remote_state, kToshibaACStateLength, repeat);
}
#endif // SEND_TOSHIBA_AC
@@ -109,7 +103,7 @@ uint8_t* IRToshibaAC::getRaw() {
// 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++) {
for (uint8_t i = 0; i < kToshibaACStateLength; i++) {
remote_state[i] = newState[i];
}
mode_state = getMode(true);
@@ -127,8 +121,7 @@ uint8_t IRToshibaAC::calcChecksum(const uint8_t state[],
// 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];
for (uint8_t i = 0; i < length - 1; i++) checksum ^= state[i];
}
return checksum;
}
@@ -139,29 +132,27 @@ uint8_t IRToshibaAC::calcChecksum(const uint8_t state[],
// length: The size of the state.
// Returns:
// A boolean.
bool IRToshibaAC::validChecksum(const uint8_t state[],
const uint16_t length) {
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);
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;
remote_state[6] &= ~kToshibaAcPower;
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);
remote_state[6] |= (kToshibaAcPower | 0b00000011);
}
// Set the requested power state of the A/C.
@@ -174,28 +165,28 @@ void IRToshibaAC::setPower(bool state) {
// Return the requested power state of the A/C.
bool IRToshibaAC::getPower() {
return((remote_state[6] & TOSHIBA_AC_POWER) == 0);
return ((remote_state[6] & kToshibaAcPower) == 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;
temp = std::max((uint8_t)kToshibaAcMinTemp, temp);
temp = std::min((uint8_t)kToshibaAcMaxTemp, temp);
remote_state[5] = (temp - kToshibaAcMinTemp) << 4;
}
// Return the set temp. in deg C
uint8_t IRToshibaAC::getTemp() {
return((remote_state[5] >> 4) + TOSHIBA_AC_MIN_TEMP);
return ((remote_state[5] >> 4) + kToshibaAcMinTemp);
}
// 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++;
if (fan > kToshibaAcFanMax)
fan = kToshibaAcFanMax; // Set the fan to maximum if out of range.
if (fan > kToshibaAcFanAuto) fan++;
remote_state[6] &= 0b00011111; // Clear the previous fan state
remote_state[6] |= (fan << 5);
}
@@ -203,7 +194,7 @@ void IRToshibaAC::setFan(uint8_t fan) {
// 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;
if (fan == kToshibaAcFanAuto) return kToshibaAcFanAuto;
return --fan;
}
@@ -223,11 +214,16 @@ uint8_t IRToshibaAC::getMode(bool useRaw) {
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;
case kToshibaAcAuto:
break;
case kToshibaAcCool:
break;
case kToshibaAcDry:
break;
case kToshibaAcHeat:
break;
default:
mode = kToshibaAcAuto;
}
mode_state = mode;
// Only adjust the remote_state if we have power set to on.
@@ -252,16 +248,16 @@ std::string IRToshibaAC::toString() {
result += "Off";
result += ", Mode: " + uint64ToString(getMode());
switch (getMode()) {
case TOSHIBA_AC_AUTO:
case kToshibaAcAuto:
result += " (AUTO)";
break;
case TOSHIBA_AC_COOL:
case kToshibaAcCool:
result += " (COOL)";
break;
case TOSHIBA_AC_HEAT:
case kToshibaAcHeat:
result += " (HEAT)";
break;
case TOSHIBA_AC_DRY:
case kToshibaAcDry:
result += " (DRY)";
break;
default:
@@ -270,10 +266,10 @@ std::string IRToshibaAC::toString() {
result += ", Temp: " + uint64ToString(getTemp()) + "C";
result += ", Fan: " + uint64ToString(getFan());
switch (getFan()) {
case TOSHIBA_AC_FAN_AUTO:
case kToshibaAcFanAuto:
result += " (AUTO)";
break;
case TOSHIBA_AC_FAN_MAX:
case kToshibaAcFanMax:
result += " (MAX)";
break;
}
@@ -285,7 +281,7 @@ std::string IRToshibaAC::toString() {
// 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.
// nbits: The number of data bits to expect. Typically kToshibaACBits.
// strict: Flag to indicate if we strictly adhere to the specification.
// Returns:
// boolean: True if it can decode it, false if it can't.
@@ -294,43 +290,38 @@ std::string IRToshibaAC::toString() {
//
// Ref:
//
bool IRrecv::decodeToshibaAC(decode_results *results, uint16_t nbits,
bool IRrecv::decodeToshibaAC(decode_results* results, uint16_t nbits,
bool strict) {
uint16_t offset = OFFSET_START;
uint16_t offset = kStartOffset;
uint16_t dataBitsSoFar = 0;
// Have we got enough data to successfully decode?
if (results->rawlen < TOSHIBA_AC_BITS + HEADER + FOOTER - 1)
if (results->rawlen < kToshibaACBits + kHeader + kFooter - 1)
return false; // Can't possibly be a valid message.
// Compliance
if (strict && nbits != TOSHIBA_AC_BITS)
if (strict && nbits != kToshibaACBits)
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;
if (!matchMark(results->rawbuf[offset++], kToshibaAcHdrMark)) return false;
if (!matchSpace(results->rawbuf[offset++], kToshibaAcHdrSpace)) return false;
// Data
for (uint8_t i = 0; i < TOSHIBA_AC_STATE_LENGTH; i++) {
for (uint8_t i = 0; i < kToshibaACStateLength; 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);
match_result_t data_result =
matchData(&(results->rawbuf[offset]), 8, kToshibaAcBitMark,
kToshibaAcOneSpace, kToshibaAcBitMark, kToshibaAcZeroSpace);
if (data_result.success == false) return false; // Fail
dataBitsSoFar += 8;
results->state[i] = (uint8_t) data_result.data;
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;
if (!matchMark(results->rawbuf[offset++], kToshibaAcBitMark)) return false;
if (!matchSpace(results->rawbuf[offset++], kToshibaAcMinGap)) return false;
// Compliance
if (strict) {
+25 -14
View File
@@ -21,15 +21,26 @@
// 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
const uint8_t kToshibaAcAuto = 0;
const uint8_t kToshibaAcCool = 1;
const uint8_t kToshibaAcDry = 2;
const uint8_t kToshibaAcHeat = 3;
const uint8_t kToshibaAcPower = 4;
const uint8_t kToshibaAcFanAuto = 0;
const uint8_t kToshibaAcFanMax = 5;
const uint8_t kToshibaAcMinTemp = 17; // 17C
const uint8_t kToshibaAcMaxTemp = 30; // 30C
// Legacy defines. (Deperecated)
#define TOSHIBA_AC_AUTO kToshibaAcAuto
#define TOSHIBA_AC_COOL kToshibaAcCool
#define TOSHIBA_AC_DRY kToshibaAcDry
#define TOSHIBA_AC_HEAT kToshibaAcHeat
#define TOSHIBA_AC_POWER kToshibaAcPower
#define TOSHIBA_AC_FAN_AUTO kToshibaAcFanAuto
#define TOSHIBA_AC_FAN_MAX kToshibaAcFanMax
#define TOSHIBA_AC_MIN_TEMP kToshibaAcMinTemp
#define TOSHIBA_AC_MAX_TEMP kToshibaAcMaxTemp
class IRToshibaAC {
public:
@@ -37,7 +48,7 @@ class IRToshibaAC {
void stateReset();
#if SEND_TOSHIBA_AC
void send();
void send(const uint16_t repeat = kToshibaACMinRepeat);
#endif // SEND_TOSHIBA_AC
void begin();
void on();
@@ -53,7 +64,7 @@ class IRToshibaAC {
void setRaw(uint8_t newState[]);
uint8_t* getRaw();
static bool validChecksum(const uint8_t state[],
const uint16_t length = TOSHIBA_AC_STATE_LENGTH);
const uint16_t length = kToshibaACStateLength);
#ifdef ARDUINO
String toString();
#else
@@ -63,10 +74,10 @@ class IRToshibaAC {
private:
#endif
uint8_t remote_state[TOSHIBA_AC_STATE_LENGTH];
void checksum(const uint16_t length = TOSHIBA_AC_STATE_LENGTH);
uint8_t remote_state[kToshibaACStateLength];
void checksum(const uint16_t length = kToshibaACStateLength);
static uint8_t calcChecksum(const uint8_t state[],
const uint16_t length = TOSHIBA_AC_STATE_LENGTH);
const uint16_t length = kToshibaACStateLength);
uint8_t mode_state;
IRsend _irsend;
};
+43 -62
View File
@@ -5,49 +5,43 @@
#include "IRutils.h"
// Constants
#define TROTEC_HDR_MARK 5952U
#define TROTEC_HDR_SPACE 7364U
#define TROTEC_ONE_MARK 592U
#define TROTEC_ONE_SPACE 1560U
#define TROTEC_ZERO_MARK 592U
#define TROTEC_ZERO_SPACE 592U
#define TROTEC_GAP 6184U
#define TROTEC_GAP_END 1500U // made up value
const uint16_t kTrotecHdrMark = 5952;
const uint16_t kTrotecHdrSpace = 7364;
const uint16_t kTrotecOneMark = 592;
const uint16_t kTrotecOneSpace = 1560;
const uint16_t kTrotecZeroMark = 592;
const uint16_t kTrotecZeroSpace = 592;
const uint16_t kTrotecGap = 6184;
const uint16_t kTrotecGapEnd = 1500; // made up value
#if SEND_TROTEC
void IRsend::sendTrotec(unsigned char data[], uint16_t nbytes,
uint16_t repeat) {
if (nbytes < TROTEC_COMMAND_LENGTH)
return;
if (nbytes < kTrotecStateLength) return;
for (uint16_t r = 0; r <= repeat; r++) {
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
sendGeneric(kTrotecHdrMark, kTrotecHdrSpace, kTrotecOneMark,
kTrotecOneSpace, kTrotecZeroMark, kTrotecZeroSpace,
kTrotecOneMark, kTrotecGap, data, nbytes, 36, false,
0, // Repeats handled elsewhere
50);
// More footer
enableIROut(36);
mark(TROTEC_ONE_MARK);
space(TROTEC_GAP_END);
mark(kTrotecOneMark);
space(kTrotecGapEnd);
}
}
#endif // SEND_TROTEC
IRTrotecESP::IRTrotecESP(uint16_t pin) : _irsend(pin) {
stateReset();
}
IRTrotecESP::IRTrotecESP(uint16_t pin) : _irsend(pin) { stateReset(); }
void IRTrotecESP::begin() {
_irsend.begin();
}
void IRTrotecESP::begin() { _irsend.begin(); }
#if SEND_TROTEC
void IRTrotecESP::send() {
void IRTrotecESP::send(const uint16_t repeat) {
checksum();
_irsend.sendTrotec(trotec);
_irsend.sendTrotec(trotec, kTrotecStateLength, repeat);
}
#endif // SEND_TROTEC
@@ -61,16 +55,15 @@ void IRTrotecESP::checksum() {
}
void IRTrotecESP::stateReset() {
for (uint8_t i = 2; i < TROTEC_COMMAND_LENGTH; i++)
trotec[i] = 0x0;
for (uint8_t i = 2; i < kTrotecStateLength; i++) trotec[i] = 0x0;
trotec[0] = TROTEC_INTRO1;
trotec[1] = TROTEC_INTRO2;
trotec[0] = kTrotecIntro1;
trotec[1] = kTrotecIntro2;
setPower(false);
setTemp(TROTEC_DEF_TEMP);
setSpeed(TROTEC_FAN_MED);
setMode(TROTEC_AUTO);
setTemp(kTrotecDefTemp);
setSpeed(kTrotecFanMed);
setMode(kTrotecAuto);
}
uint8_t* IRTrotecESP::getRaw() {
@@ -80,67 +73,55 @@ uint8_t* IRTrotecESP::getRaw() {
void IRTrotecESP::setPower(bool state) {
if (state)
trotec[2] |= (TROTEC_ON << 3);
trotec[2] |= (kTrotecOn << 3);
else
trotec[2] &= ~(TROTEC_ON << 3);
trotec[2] &= ~(kTrotecOn << 3);
}
uint8_t IRTrotecESP::getPower() {
return trotec[2] & (TROTEC_ON << 3);
}
uint8_t IRTrotecESP::getPower() { return trotec[2] & (kTrotecOn << 3); }
void IRTrotecESP::setSpeed(uint8_t speed) {
trotec[2] = (trotec[2] & 0xcf) | (speed << 4);
}
uint8_t IRTrotecESP::getSpeed() {
return trotec[2] & 0x30;
}
uint8_t IRTrotecESP::getSpeed() { return trotec[2] & 0x30; }
void IRTrotecESP::setMode(uint8_t mode) {
trotec[2] = (trotec[2] & 0xfc) | mode;
}
uint8_t IRTrotecESP::getMode() {
return trotec[2] & 0x03;
}
uint8_t IRTrotecESP::getMode() { return trotec[2] & 0x03; }
void IRTrotecESP::setTemp(uint8_t temp) {
if (temp < TROTEC_MIN_TEMP)
temp = TROTEC_MIN_TEMP;
else if (temp > TROTEC_MAX_TEMP)
temp = TROTEC_MAX_TEMP;
if (temp < kTrotecMinTemp)
temp = kTrotecMinTemp;
else if (temp > kTrotecMaxTemp)
temp = kTrotecMaxTemp;
trotec[3] = (trotec[3] & 0x80) | (temp - TROTEC_MIN_TEMP);
trotec[3] = (trotec[3] & 0x80) | (temp - kTrotecMinTemp);
}
uint8_t IRTrotecESP::getTemp() {
return trotec[3] & 0x7f;
}
uint8_t IRTrotecESP::getTemp() { return trotec[3] & 0x7f; }
void IRTrotecESP::setSleep(bool sleep) {
if (sleep)
trotec[3] |= (TROTEC_SLEEP_ON << 7);
trotec[3] |= (kTrotecSleepOn << 7);
else
trotec[3] &= ~(TROTEC_SLEEP_ON << 7);
trotec[3] &= ~(kTrotecSleepOn << 7);
}
bool IRTrotecESP::getSleep(void) {
return trotec[3] & (TROTEC_SLEEP_ON << 7);
}
bool IRTrotecESP::getSleep(void) { return trotec[3] & (kTrotecSleepOn << 7); }
void IRTrotecESP::setTimer(uint8_t timer) {
if (timer > TROTEC_MAX_TIMER) timer = TROTEC_MAX_TIMER;
if (timer > kTrotecMaxTimer) timer = kTrotecMaxTimer;
if (timer) {
trotec[5] |= (TROTEC_TIMER_ON << 6);
trotec[5] |= (kTrotecTimerOn << 6);
trotec[6] = timer;
} else {
trotec[5] &= ~(TROTEC_TIMER_ON << 6);
trotec[5] &= ~(kTrotecTimerOn << 6);
trotec[6] = 0;
}
}
uint8_t IRTrotecESP::getTimer() {
return trotec[6];
}
uint8_t IRTrotecESP::getTimer() { return trotec[6]; }
+32 -20
View File
@@ -8,45 +8,57 @@
// Constants
// Byte 0
#define TROTEC_INTRO1 0x12
const uint8_t kTrotecIntro1 = 0x12;
// Byte 1
#define TROTEC_INTRO2 0x34
const uint8_t kTrotecIntro2 = 0x34;
// Byte 2
#define TROTEC_AUTO 0
#define TROTEC_COOL 1
#define TROTEC_DRY 2
#define TROTEC_FAN 3
const uint8_t kTrotecAuto = 0;
const uint8_t kTrotecCool = 1;
const uint8_t kTrotecDry = 2;
const uint8_t kTrotecFan = 3;
#define TROTEC_ON 1
#define TROTEC_OFF 0
const uint8_t kTrotecOn = 1;
const uint8_t kTrotecOff = 0;
#define TROTEC_FAN_LOW 1
#define TROTEC_FAN_MED 2
#define TROTEC_FAN_HIGH 3
const uint8_t kTrotecFanLow = 1;
const uint8_t kTrotecFanMed = 2;
const uint8_t kTrotecFanHigh = 3;
// Byte 3
#define TROTEC_MIN_TEMP 18
#define TROTEC_MAX_TEMP 32
#define TROTEC_DEF_TEMP 25
const uint8_t kTrotecMinTemp = 18;
const uint8_t kTrotecDefTemp = 25;
const uint8_t kTrotecMaxTemp = 32;
#define TROTEC_SLEEP_ON 1
const uint8_t kTrotecSleepOn = 1;
// Byte 5
#define TROTEC_TIMER_ON 1
const uint8_t kTrotecTimerOn = 1;
// Byte 6
#define TROTEC_MIN_TIMER 0
#define TROTEC_MAX_TIMER 23
const uint8_t kTrotecMinTimer = 0;
const uint8_t kTrotecMaxTimer = 23;
// Legacy defines. (Deperecated)
#define TROTEC_AUTO kTrotecAuto
#define TROTEC_COOL kTrotecCool
#define TROTEC_DRY kTrotecDry
#define TROTEC_FAN kTrotecFan
#define TROTEC_FAN_LOW kTrotecFanLow
#define TROTEC_FAN_MED kTrotecFanMed
#define TROTEC_FAN_HIGH kTrotecFanHigh
#define TROTEC_MIN_TEMP kTrotecMinTemp
#define TROTEC_MAX_TEMP kTrotecMaxTemp
#define TROTEC_MIN_TIMER kTrotecMinTimer
#define TROTEC_MAX_TIMER kTrotecMaxTimer
class IRTrotecESP {
public:
explicit IRTrotecESP(uint16_t pin);
#if SEND_TROTEC
void send();
void send(const uint16_t repeat = kTrotecDefaultRepeat);
#endif // SEND_TROTEC
void begin();
@@ -71,7 +83,7 @@ class IRTrotecESP {
uint8_t* getRaw();
private:
uint8_t trotec[TROTEC_COMMAND_LENGTH];
uint8_t trotec[kTrotecStateLength];
void stateReset();
void checksum();
IRsend _irsend;
+481
View File
@@ -0,0 +1,481 @@
// Copyright 2018 Erdem U. Altinyurt
#include "ir_Vestel.h"
#include <algorithm>
#ifndef UNIT_TEST
#include <Arduino.h>
#else
#include <string>
#endif
#include "IRrecv.h"
#include "IRremoteESP8266.h"
#include "IRsend.h"
#include "IRutils.h"
#include "ir_Haier.h"
// VV VV EEEEEEE SSSSS TTTTTTTT EEEEEEE LL
// VV VV EE S TT EE LL
// VV VV EEEEE SSSS TT EEEEE LL
// VV VV EE S TT EE LL
// VVV EEEEEEE SSSSS TT EEEEEEE LLLLLLL
// Vestel added by Erdem U. Altinyurt
// Equipment it seems compatible with:
// * Vestel AC Model BIOX CXP-9 (9K BTU)
// * <Add models (A/C & remotes) you've gotten it working with here>
// Ref:
// None. Totally reverse engineered.
#if SEND_VESTEL_AC
// Send a Vestel message
//
// Args:
// data: Contents of the message to be sent.
// nbits: Nr. of bits of data to be sent. Typically kVestelBits.
//
// Status: STABLE / Working.
//
void IRsend::sendVestelAC(const uint64_t data, const uint16_t nbits,
const uint16_t repeat) {
if (nbits % 8 != 0) return; // nbits is required to be a multiple of 8.
sendGeneric(kVestelACHdrMark, kVestelACHdrSpace, // Header
kVestelACBitMark, kVestelACOneSpace, // Data
kVestelACBitMark, kVestelACZeroSpace, // Data
kVestelACBitMark, 100000, // Footer + repeat gap
data, nbits, 38, false, repeat, 50);
}
#endif
// Code to emulate Vestel A/C IR remote control unit.
// Initialise the object.
IRVestelAC::IRVestelAC(uint16_t pin) : _irsend(pin) { stateReset(); }
// Reset the state of the remote to a known good state/sequence.
void IRVestelAC::stateReset() {
// Power On, Mode Auto, Fan Auto, Temp = 25C/77F
remote_state.rawCode = 0x0F00D9001FEF201LL;
remote_time_state.rawCode = 0x00;
use_time_state = false;
}
// Configure the pin for output.
void IRVestelAC::begin() {
_irsend.begin();
stateReset();
}
#if SEND_VESTEL_AC
// Send the current desired state to the IR LED.
void IRVestelAC::send() {
checksum(); // Ensure correct checksum before sending.
uint64_t code_to_send;
if (use_time_state)
code_to_send = remote_time_state.rawCode;
else
code_to_send = remote_state.rawCode;
_irsend.sendVestelAC(code_to_send);
}
#endif // SEND_VESTEL_AC
// Return the internal state date of the remote.
uint64_t IRVestelAC::getRaw() {
checksum();
if (use_time_state) return remote_time_state.rawCode;
return remote_state.rawCode;
}
// Override the internal state with the new state.
void IRVestelAC::setRaw(uint8_t* newState) {
uint64_t upState = 0;
for (int i = 0; i < 7; i++)
upState |= static_cast<uint64_t>(newState[i]) << (i * 8);
remote_state.rawCode = upState;
remote_time_state.rawCode = upState;
}
void IRVestelAC::setRaw(const uint64_t newState) {
remote_state.rawCode = newState;
remote_time_state.rawCode = newState;
}
// Set the requested power state of the A/C to on.
void IRVestelAC::on() { setPower(true); }
// Set the requested power state of the A/C to off.
void IRVestelAC::off() { setPower(false); }
// Set the requested power state of the A/C.
void IRVestelAC::setPower(const bool state) {
if (state)
remote_state.power = 0xF;
else
remote_state.power = 0xC;
use_time_state = false;
}
// Return the requested power state of the A/C.
bool IRVestelAC::getPower() { return (remote_state.power == 0xF); }
// Set the temperature in Celsius degrees.
void IRVestelAC::setTemp(const uint8_t temp) {
uint8_t new_temp = temp;
new_temp = std::max(kVestelACMinTempC, new_temp);
// new_temp = std::max(kVestelACMinTempH, new_temp); Check MODE
new_temp = std::min(kVestelACMaxTemp, new_temp);
remote_state.temp = new_temp - 16;
use_time_state = false;
}
// Return the set temperature.
uint8_t IRVestelAC::getTemp(void) { return remote_state.temp + 16; }
// Set the speed of the fan,
// 1-3 set the fan speed, 0 or anything else set it to auto.
void IRVestelAC::setFan(const uint8_t fan) {
switch (fan) {
case kVestelACFanLow:
case kVestelACFanMed:
case kVestelACFanHigh:
case kVestelACFanAutoCool:
case kVestelACFanAutoHot:
remote_state.fan = fan;
break;
default:
remote_state.fan = kVestelACFanAuto;
}
use_time_state = false;
}
// Return the requested state of the unit's fan.
uint8_t IRVestelAC::getFan() { return remote_state.fan; }
// Get the requested climate operation mode of the a/c unit.
// Returns:
// A uint8_t containing the A/C mode.
uint8_t IRVestelAC::getMode() { return remote_state.mode; }
// Set the requested climate operation mode of the a/c unit.
void IRVestelAC::setMode(const uint8_t mode) {
// If we get an unexpected mode, default to AUTO.
switch (mode) {
case kVestelACAuto:
case kVestelACCool:
case kVestelACHeat:
case kVestelACDry:
case kVestelACFan:
remote_state.mode = mode;
break;
default:
remote_state.mode = kVestelACAuto;
}
use_time_state = false;
}
// Set Auto mode of AC.
void IRVestelAC::setAuto(const int8_t autoLevel) {
if (autoLevel < -2 || autoLevel > 2) return;
setMode(kVestelACAuto);
setFan((autoLevel < 0 ? kVestelACFanAutoCool : kVestelACFanAutoHot));
if (autoLevel == 2)
setTemp(30);
else if (autoLevel == 1)
setTemp(31);
else if (autoLevel == 0)
setTemp(25);
else if (autoLevel == -1)
setTemp(16);
else if (autoLevel == -2)
setTemp(17);
}
// Set Timer option of AC.
// Valid time arguments are 0, 0.5, 1, 2, 3 and 5 hours (in min). 0 disables the
// timer.
void IRVestelAC::setTimer(const uint16_t minutes) {
remote_time_state.t_turnOnMinute = 0;
remote_time_state.t_turnOnHour = 0;
remote_time_state.t_turnOffHour = minutes / 60;
remote_time_state.t_turnOffMinute = (minutes % 60) / 10;
remote_time_state.t_off_active = 0;
if (minutes) {
remote_time_state.t_timer_mode = 1;
// Yes. t_on_active instead of t_off_active.
remote_time_state.t_on_active = 1;
} else { // Turn off the timer.
remote_time_state.t_timer_mode = 0;
remote_time_state.t_on_active = 0;
}
use_time_state = true;
}
uint16_t IRVestelAC::getTimer(void) { return getOffTimer(); }
// Set the AC's internal clock
void IRVestelAC::setTime(const uint16_t minutes) {
remote_time_state.t_hour = minutes / 60;
remote_time_state.t_minute = minutes % 60;
use_time_state = true;
}
uint16_t IRVestelAC::getTime(void) {
return remote_time_state.t_hour * 60 + remote_time_state.t_minute;
}
// Set AC's wake up time. Takes time in minute.
void IRVestelAC::setOnTimer(const uint16_t minutes) {
remote_time_state.t_turnOnHour = minutes / 60;
// Register is 3 bit and can only hold minute/10
remote_time_state.t_turnOnMinute = (minutes % 60) / 10;
if (minutes)
remote_time_state.t_on_active = 1; // activation
else
remote_time_state.t_on_active = 0; // deactivate
remote_time_state.t_timer_mode = 0;
use_time_state = true;
}
uint16_t IRVestelAC::getOnTimer(void) {
return remote_time_state.t_turnOnHour * 60 +
remote_time_state.t_turnOnMinute * 10;
}
// Set AC's turn off time. Takes time in minute.
void IRVestelAC::setOffTimer(const uint16_t minutes) {
remote_time_state.t_turnOffHour = minutes / 60;
// Register is 3 bit and can only hold minute/10
remote_time_state.t_turnOffMinute = (minutes % 60) / 10;
if (minutes)
remote_time_state.t_off_active = 1; // activation
else
remote_time_state.t_off_active = 0; // deactivate
remote_time_state.t_timer_mode = 0;
use_time_state = true;
}
uint16_t IRVestelAC::getOffTimer(void) {
return remote_time_state.t_turnOffHour * 60 +
remote_time_state.t_turnOffMinute * 10;
}
// Set the Sleep state of the A/C.
void IRVestelAC::setSleep(const bool state) {
remote_state.turbo_sleep_normal = state ? kVestelACSleep : kVestelACNormal;
use_time_state = false;
}
// Return the Sleep state of the A/C.
bool IRVestelAC::getSleep() {
return remote_state.turbo_sleep_normal == kVestelACSleep;
}
// Set the Turbo state of the A/C.
void IRVestelAC::setTurbo(const bool state) {
remote_state.turbo_sleep_normal = state ? kVestelACTurbo : kVestelACNormal;
use_time_state = false;
}
// Return the Turbo state of the A/C.
bool IRVestelAC::getTurbo() {
return remote_state.turbo_sleep_normal == kVestelACTurbo;
}
// Set the Ion state of the A/C.
void IRVestelAC::setIon(const bool state) {
remote_state.ion = state ? kVestelACIon : 0;
use_time_state = false;
}
// Return the Ion state of the A/C.
bool IRVestelAC::getIon() { return remote_state.ion == kVestelACIon; }
// Set the Swing Roaming state of the A/C.
void IRVestelAC::setSwing(const bool state) {
remote_state.swing = state ? kVestelACSwing : 0xF;
use_time_state = false;
}
// Return the Swing Roaming state of the A/C.
bool IRVestelAC::getSwing() { return remote_state.swing == kVestelACSwing; }
// Calculate the checksum for a given array.
// Args:
// state: The state to calculate the checksum over.
// Returns:
// The 8 bit checksum value.
uint8_t IRVestelAC::calcChecksum(const uint64_t state) {
// Just counts the set bits +1 on stream and take inverse after mask
uint8_t sum = 0;
uint64_t temp_state = state & kVestelACCRCMask;
for (; temp_state; temp_state >>= 1)
if (temp_state & 1) sum++;
sum += 2;
sum = 0xff - sum;
return sum;
}
// Verify the checksum is valid for a given state.
// Args:
// state: The state to verify the checksum of.
// Returns:
// A boolean.
bool IRVestelAC::validChecksum(const uint64_t state) {
return (((state >> 12) & 0xFF) == calcChecksum(state));
}
// Calculate & set the checksum for the current internal state of the remote.
void IRVestelAC::checksum() {
// Stored the checksum value in the last byte.
remote_state.CRC = calcChecksum(remote_state.rawCode);
remote_time_state.CRC = calcChecksum(remote_time_state.rawCode);
}
bool IRVestelAC::isTimeCommand() {
return (remote_state.power == 0x00 || use_time_state);
}
// Convert the internal state into a human readable string.
#ifdef ARDUINO
String IRVestelAC::toString() {
String result = "";
#else
std::string IRVestelAC::toString() {
std::string result = "";
#endif // ARDUINO
if (isTimeCommand()) {
result += "Time: " + IRHaierAC::timeToString(getTime());
result += ", Timer: ";
result += remote_time_state.t_timer_mode
? IRHaierAC::timeToString(getTimer())
: "Off";
result += ", On Timer: ";
result += (remote_time_state.t_on_active && !remote_time_state.t_timer_mode)
? IRHaierAC::timeToString(getOnTimer())
: "Off";
result += ", Off Timer: ";
result += remote_time_state.t_off_active
? IRHaierAC::timeToString(getOffTimer())
: "Off";
return result;
}
// Not a time command, it's a normal command.
result += "Power: ";
result += (getPower() ? "On" : "Off");
result += ", Mode: " + uint64ToString(getMode());
switch (getMode()) {
case kVestelACAuto:
result += " (AUTO)";
break;
case kVestelACCool:
result += " (COOL)";
break;
case kVestelACHeat:
result += " (HEAT)";
break;
case kVestelACDry:
result += " (DRY)";
break;
case kVestelACFan:
result += " (FAN)";
break;
default:
result += " (UNKNOWN)";
}
result += ", Temp: " + uint64ToString(getTemp()) + "C";
result += ", Fan: " + uint64ToString(getFan());
switch (getFan()) {
case kVestelACFanAuto:
result += " (AUTO)";
break;
case kVestelACFanLow:
result += " (LOW)";
break;
case kVestelACFanMed:
result += " (MEDIUM)";
break;
case kVestelACFanHigh:
result += " (HIGH)";
break;
case kVestelACFanAutoCool:
result += " (AUTO COOL)";
break;
case kVestelACFanAutoHot:
result += " (AUTO HOT)";
break;
default:
result += " (UNKNOWN)";
}
result += ", Sleep: ";
result += (getSleep() ? "On" : "Off");
result += ", Turbo: ";
result += (getTurbo() ? "On" : "Off");
result += ", Ion: ";
result += (getIon() ? "On" : "Off");
result += ", Swing: ";
result += (getSwing() ? "On" : "Off");
return result;
}
#if DECODE_VESTEL_AC
// Decode the supplied Vestel 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 kVestelBits.
// 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::decodeVestelAC(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 != kVestelACBits)
return false; // Not strictly a Vestel AC message.
uint64_t data = 0;
uint16_t offset = kStartOffset;
if (nbits > sizeof(data) * 8)
return false; // We can't possibly capture a Vestel packet that big.
// Header
if (!matchMark(results->rawbuf[offset++], kVestelACHdrMark)) return false;
if (!matchSpace(results->rawbuf[offset++], kVestelACHdrSpace)) return false;
// Data (Normal)
match_result_t data_result =
matchData(&(results->rawbuf[offset]), nbits, kVestelACBitMark,
kVestelACOneSpace, kVestelACBitMark, kVestelACZeroSpace,
kVestelACTolerance, kMarkExcess, false);
if (data_result.success == false) return false;
offset += data_result.used;
data = data_result.data;
// Footer
if (!matchMark(results->rawbuf[offset++], kVestelACBitMark)) return false;
// Compliance
if (strict)
if (!IRVestelAC::validChecksum(data_result.data)) return false;
// Success
results->decode_type = VESTEL_AC;
results->bits = nbits;
results->value = data;
results->address = 0;
results->command = 0;
return true;
}
#endif // DECODE_VESTEL_AC
+160
View File
@@ -0,0 +1,160 @@
// Copyright 2018 Erdem U. Altinyurt
// (Used Midea.h as template)
#ifndef IR_VESTEL_H_
#define IR_VESTEL_H_
#define __STDC_LIMIT_MACROS
#include <stdint.h>
#ifdef ARDUINO
#include <Arduino.h>
#else
#include <string>
#endif
#include "IRremoteESP8266.h"
#include "IRsend.h"
// VV VV EEEEEEE SSSSS TTTTTTTT EEEEEEE LL
// VV VV EE S TT EE LL
// VV VV EEEEE SSSS TT EEEEE LL
// VV VV EE S TT EE LL
// VVV EEEEEEE SSSSS TT EEEEEEE LLLLLLL
// Vestel added by Erdem U. Altinyurt
// Constants
const uint16_t kVestelACHdrMark = 3110;
const uint16_t kVestelACHdrSpace = 9066;
const uint16_t kVestelACBitMark = 520;
const uint16_t kVestelACOneSpace = 1535;
const uint16_t kVestelACZeroSpace = 480;
const uint16_t kVestelACTolerance = 30;
const uint8_t kVestelACMinTempH = 16;
const uint8_t kVestelACMinTempC = 18;
const uint8_t kVestelACMaxTemp = 30;
const uint64_t kVestelACCRCMask = 0xFFFFFFFFFFF00000;
const uint8_t kVestelACAuto = 0;
const uint8_t kVestelACCool = 1;
const uint8_t kVestelACDry = 2;
const uint8_t kVestelACFan = 3;
const uint8_t kVestelACHeat = 4;
const uint8_t kVestelACFanAuto = 1;
const uint8_t kVestelACFanLow = 5;
const uint8_t kVestelACFanMed = 9;
const uint8_t kVestelACFanHigh = 0xB;
const uint8_t kVestelACFanAutoCool = 0xC;
const uint8_t kVestelACFanAutoHot = 0xD;
const uint8_t kVestelACNormal = 1;
const uint8_t kVestelACSleep = 3;
const uint8_t kVestelACTurbo = 7;
const uint8_t kVestelACIon = 4;
const uint8_t kVestelACSwing = 0xA;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpacked-bitfield-compat"
union VestelACState {
// Remotes Command Stack
struct {
uint16_t footer : 12; // 0x201 footer
uint8_t CRC : 8;
uint8_t swing : 4; // auto 0xA, stop 0xF
uint8_t turbo_sleep_normal : 4; // normal 0x1, sleep 0x3, turbo 0x7
uint8_t zero : 8; // 0x00
uint8_t temp : 4; // temp-16 degree Celcius
uint8_t fan : 4; // auto 0x1, low 0x5, mid 0x9, high 0xB, 0xD auto hot, 0xC
// auto cool
uint8_t mode : 4; // auto 0x0, cold 0x1, dry 0x2, fan 0x3, hot 0x4
uint8_t ion : 4; // on 0x4, off 0x0
uint8_t power : 4; // on 0xF, off 0xC
uint8_t not_used : 8;
} __attribute__((packed)); // avoids padding.
uint64_t rawCode;
// Remotes Timer Stack for programming AC unit for turn of after some time or
// self wakeup - turn off at defined time. There are no timer functions
// implemented.
// Here is the stack only for decoding and reverse enginering purposes.
struct {
uint16_t t_footer : 12; // 0x201 footer
uint8_t t_CRC : 8;
// timer regs has 0x18 hour mask & 0x3=Minute mask minutes are divided to 10
// format. Need to multiply with 10 Examples: 0x01= xx:10 and 0x08 = 01:00,
// 0x03= 00:30 0x08=01:00 0x10=02:00 0x44=08:40
uint8_t t_turnOffMinute : 3; // off minute/10
uint8_t t_turnOffHour : 5;
uint8_t t_turnOnMinute : 3; // on minute/10
uint8_t t_turnOnHour : 5;
uint8_t t_hour : 5; // actual time hour
uint8_t t_on_active : 1;
uint8_t t_off_active : 1;
uint8_t t_timer_mode : 1; // For timer operation, also need t_on_active bit
uint8_t t_minute : 8; // actual time minute
uint16_t t_not_used : 16; // 00
} __attribute__((packed)); // avoids padding.
};
#pragma GCC diagnostic pop
class IRVestelAC {
public:
explicit IRVestelAC(uint16_t pin);
void stateReset();
#if SEND_VESTEL_AC
void send();
#endif // SEND_VESTEL_AC
void begin(void);
void on(void);
void off(void);
void setPower(const bool state);
bool getPower();
void setAuto(const int8_t autoLevel);
void setTimer(const uint16_t minutes);
uint16_t getTimer(void);
void setTime(const uint16_t minutes);
uint16_t getTime(void);
void setOnTimer(const uint16_t minutes);
uint16_t getOnTimer(void);
void setOffTimer(const uint16_t minutes);
uint16_t getOffTimer(void);
void setTemp(const uint8_t temp);
uint8_t getTemp(void);
void setFan(const uint8_t fan);
uint8_t getFan(void);
void setMode(const uint8_t mode);
uint8_t getMode(void);
void setRaw(uint8_t* newState);
void setRaw(const uint64_t newState);
uint64_t getRaw(void);
static bool validChecksum(const uint64_t state);
void setSwing(const bool state);
bool getSwing(void);
void setSleep(const bool state);
bool getSleep(void);
void setTurbo(const bool state);
bool getTurbo(void);
void setIon(const bool state);
bool getIon(void);
bool isTimeCommand(void);
#ifdef ARDUINO
String toString();
#else
std::string toString();
#endif
#ifndef UNIT_TEST
// private:
#endif
VestelACState remote_state;
VestelACState remote_time_state;
bool use_time_state = false;
void checksum();
static uint8_t calcChecksum(const uint64_t state);
IRsend _irsend;
};
#endif // IR_VESTEL_H_
+634
View File
@@ -0,0 +1,634 @@
// Copyright 2018 David Conran
//
// Code to emulate Whirlpool protocol compatible devices.
// Should be compatible with:
// * SPIS409L, SPIS412L, SPIW409L, SPIW412L, SPIW418L
// Remotes:
// * DG11J1-3A / DG11J1-04
// * DG11J1-91
//
// Note: Smart, iFeel, AroundU, PowerSave, & Silent modes are unsupported.
// Advanced 6thSense, Dehumidify, & Sleep modes are not supported.
// FYI:
// Dim == !Light
// Jet == Super == Turbo
//
#include "ir_Whirlpool.h"
#include <algorithm>
#ifndef ARDUINO
#include <string>
#endif
#include "IRrecv.h"
#include "IRremoteESP8266.h"
#include "IRsend.h"
#include "IRutils.h"
// WW WW HH HH IIIII RRRRRR LL PPPPPP OOOOO OOOOO LL
// WW WW HH HH III RR RR LL PP PP OO OO OO OO LL
// WW W WW HHHHHHH III RRRRRR LL PPPPPP OO OO OO OO LL
// WW WWW WW HH HH III RR RR LL PP OO OO OO OO LL
// WW WW HH HH IIIII RR RR LLLLLLL PP OOOO0 OOOO0 LLLLLLL
// Constants
// Ref: https://github.com/markszabo/IRremoteESP8266/issues/509
const uint16_t kWhirlpoolAcHdrMark = 8950;
const uint16_t kWhirlpoolAcHdrSpace = 4484;
const uint16_t kWhirlpoolAcBitMark = 597;
const uint16_t kWhirlpoolAcOneSpace = 1649;
const uint16_t kWhirlpoolAcZeroSpace = 533;
const uint16_t kWhirlpoolAcGap = 7920;
const uint32_t kWhirlpoolAcMinGap = 100000; // Completely made up value.
const uint8_t kWhirlpoolAcSections = 3;
#if SEND_WHIRLPOOL_AC
// Send a Whirlpool A/C message.
//
// Args:
// data: An array of bytes containing the IR command.
// nbytes: Nr. of bytes of data in the array. (>=kWhirlpoolAcStateLength)
// repeat: Nr. of times the message is to be repeated. (Default = 0).
//
// Status: ALPHA / Untested.
//
// Ref:
// https://github.com/markszabo/IRremoteESP8266/issues/509
void IRsend::sendWhirlpoolAC(unsigned char data[], uint16_t nbytes,
uint16_t repeat) {
if (nbytes < kWhirlpoolAcStateLength)
return; // Not enough bytes to send a proper message.
for (uint16_t r = 0; r <= repeat; r++) {
// Section 1
sendGeneric(kWhirlpoolAcHdrMark, kWhirlpoolAcHdrSpace, kWhirlpoolAcBitMark,
kWhirlpoolAcOneSpace, kWhirlpoolAcBitMark,
kWhirlpoolAcZeroSpace, kWhirlpoolAcBitMark, kWhirlpoolAcGap,
data, 6, // 6 bytes == 48 bits
38000, // Complete guess of the modulation frequency.
false, 0, 50);
// Section 2
sendGeneric(0, 0, kWhirlpoolAcBitMark, kWhirlpoolAcOneSpace,
kWhirlpoolAcBitMark, kWhirlpoolAcZeroSpace, kWhirlpoolAcBitMark,
kWhirlpoolAcGap, data + 6, 8, // 8 bytes == 64 bits
38000, // Complete guess of the modulation frequency.
false, 0, 50);
// Section 3
sendGeneric(0, 0, kWhirlpoolAcBitMark, kWhirlpoolAcOneSpace,
kWhirlpoolAcBitMark, kWhirlpoolAcZeroSpace, kWhirlpoolAcBitMark,
kWhirlpoolAcMinGap, data + 14, 7, // 7 bytes == 56 bits
38000, // Complete guess of the modulation frequency.
false, 0, 50);
}
}
#endif // SEND_WHIRLPOOL_AC
// Class for emulating a Whirlpool A/C remote.
// Decoding help from:
// @redmusicxd, @josh929800, @raducostea
IRWhirlpoolAc::IRWhirlpoolAc(uint16_t pin) : _irsend(pin) { stateReset(); }
void IRWhirlpoolAc::stateReset() {
for (uint8_t i = 2; i < kWhirlpoolAcStateLength; i++) remote_state[i] = 0x0;
remote_state[0] = 0x83;
remote_state[1] = 0x06;
remote_state[6] = 0x80;
_setTemp(kWhirlpoolAcAutoTemp); // Default to a sane value.
}
void IRWhirlpoolAc::begin() { _irsend.begin(); }
bool IRWhirlpoolAc::validChecksum(uint8_t state[], const uint16_t length) {
if (length > kWhirlpoolAcChecksumByte1 &&
state[kWhirlpoolAcChecksumByte1] !=
xorBytes(state + 2, kWhirlpoolAcChecksumByte1 - 1 - 2)) {
DPRINTLN("DEBUG: First Whirlpool AC checksum failed.");
return false;
}
if (length > kWhirlpoolAcChecksumByte2 &&
state[kWhirlpoolAcChecksumByte2] !=
xorBytes(state + kWhirlpoolAcChecksumByte1 + 1,
kWhirlpoolAcChecksumByte2 - kWhirlpoolAcChecksumByte1 - 1)) {
DPRINTLN("DEBUG: Second Whirlpool AC checksum failed.");
return false;
}
// State is too short to have a checksum or everything checked out.
return true;
}
// Update the checksum for the internal state.
void IRWhirlpoolAc::checksum(uint16_t length) {
if (length >= kWhirlpoolAcChecksumByte1)
remote_state[kWhirlpoolAcChecksumByte1] =
xorBytes(remote_state + 2, kWhirlpoolAcChecksumByte1 - 1 - 2);
if (length >= kWhirlpoolAcChecksumByte2)
remote_state[kWhirlpoolAcChecksumByte2] =
xorBytes(remote_state + kWhirlpoolAcChecksumByte1 + 1,
kWhirlpoolAcChecksumByte2 - kWhirlpoolAcChecksumByte1 - 1);
}
#if SEND_WHIRLPOOL_AC
void IRWhirlpoolAc::send(const uint16_t repeat, const bool calcchecksum) {
if (calcchecksum) checksum();
_irsend.sendWhirlpoolAC(remote_state, kWhirlpoolAcStateLength, repeat);
}
#endif // SEND_WHIRLPOOL_AC
uint8_t *IRWhirlpoolAc::getRaw(const bool calcchecksum) {
if (calcchecksum) checksum();
return remote_state;
}
void IRWhirlpoolAc::setRaw(const uint8_t new_code[], const uint16_t length) {
for (uint8_t i = 0; i < length && i < kWhirlpoolAcStateLength; i++)
remote_state[i] = new_code[i];
}
whirlpool_ac_remote_model_t IRWhirlpoolAc::getModel() {
if (remote_state[kWhirlpoolAcAltTempPos] & kWhirlpoolAcAltTempMask)
return DG11J191;
else
return DG11J13A;
}
void IRWhirlpoolAc::setModel(const whirlpool_ac_remote_model_t model) {
switch (model) {
case DG11J191:
remote_state[kWhirlpoolAcAltTempPos] |= kWhirlpoolAcAltTempMask;
break;
case DG11J13A:
// FALL THRU
default:
remote_state[kWhirlpoolAcAltTempPos] &= ~kWhirlpoolAcAltTempMask;
}
_setTemp(_desiredtemp); // Different models have different temp values.
}
// Return the temp. offset in deg C for the current model.
int8_t IRWhirlpoolAc::getTempOffset() {
switch (getModel()) {
case DG11J191:
return -2;
break;
default:
return 0;
}
}
// Set the temp. in deg C
void IRWhirlpoolAc::_setTemp(const uint8_t temp, const bool remember) {
if (remember) _desiredtemp = temp;
int8_t offset = getTempOffset(); // Cache the min temp for the model.
uint8_t newtemp = std::max((uint8_t)(kWhirlpoolAcMinTemp + offset), temp);
newtemp = std::min((uint8_t)(kWhirlpoolAcMaxTemp + offset), newtemp);
remote_state[kWhirlpoolAcTempPos] =
(remote_state[kWhirlpoolAcTempPos] & ~kWhirlpoolAcTempMask) |
((newtemp - (kWhirlpoolAcMinTemp + offset)) << 4);
}
// Set the temp. in deg C
void IRWhirlpoolAc::setTemp(const uint8_t temp) {
_setTemp(temp);
setSuper(false); // Changing temp cancels Super/Jet mode.
setCommand(kWhirlpoolAcCommandTemp);
}
// Return the set temp. in deg C
uint8_t IRWhirlpoolAc::getTemp() {
return ((remote_state[kWhirlpoolAcTempPos] & kWhirlpoolAcTempMask) >> 4) +
+ kWhirlpoolAcMinTemp + getTempOffset();
}
void IRWhirlpoolAc::_setMode(const uint8_t mode) {
switch (mode) {
case kWhirlpoolAcAuto:
setFan(kWhirlpoolAcFanAuto);
_setTemp(kWhirlpoolAcAutoTemp, false);
setSleep(false); // Cancel sleep mode when in auto/6thsense mode.
// FALL THRU
case kWhirlpoolAcHeat:
case kWhirlpoolAcCool:
case kWhirlpoolAcDry:
case kWhirlpoolAcFan:
remote_state[kWhirlpoolAcModePos] &= ~kWhirlpoolAcModeMask;
remote_state[kWhirlpoolAcModePos] |= mode;
setCommand(kWhirlpoolAcCommandMode);
break;
default:
return;
}
if (mode == kWhirlpoolAcAuto) setCommand(kWhirlpoolAcCommand6thSense);
}
void IRWhirlpoolAc::setMode(const uint8_t mode) {
setSuper(false); // Changing mode cancels Super/Jet mode.
_setMode(mode);
}
uint8_t IRWhirlpoolAc::getMode() {
return remote_state[kWhirlpoolAcModePos] & kWhirlpoolAcModeMask;
}
void IRWhirlpoolAc::setFan(const uint8_t speed) {
switch (speed) {
case kWhirlpoolAcFanAuto:
case kWhirlpoolAcFanLow:
case kWhirlpoolAcFanMedium:
case kWhirlpoolAcFanHigh:
remote_state[kWhirlpoolAcFanPos] =
(remote_state[kWhirlpoolAcFanPos] & ~kWhirlpoolAcFanMask) | speed;
setSuper(false); // Changing fan speed cancels Super/Jet mode.
setCommand(kWhirlpoolAcCommandFanSpeed);
break;
}
}
uint8_t IRWhirlpoolAc::getFan() {
return remote_state[kWhirlpoolAcFanPos] & kWhirlpoolAcFanMask;
}
void IRWhirlpoolAc::setSwing(const bool on) {
if (on) {
remote_state[kWhirlpoolAcFanPos] |= kWhirlpoolAcSwing1Mask;
remote_state[kWhirlpoolAcOffTimerPos] |= kWhirlpoolAcSwing2Mask;
} else {
remote_state[kWhirlpoolAcFanPos] &= ~kWhirlpoolAcSwing1Mask;
remote_state[kWhirlpoolAcOffTimerPos] &= ~kWhirlpoolAcSwing2Mask;
}
setCommand(kWhirlpoolAcCommandSwing);
}
bool IRWhirlpoolAc::getSwing() {
return (remote_state[kWhirlpoolAcFanPos] & kWhirlpoolAcSwing1Mask) &&
(remote_state[kWhirlpoolAcOffTimerPos] & kWhirlpoolAcSwing2Mask);
}
void IRWhirlpoolAc::setLight(const bool on) {
if (on)
remote_state[kWhirlpoolAcClockPos] &= ~kWhirlpoolAcLightMask;
else
remote_state[kWhirlpoolAcClockPos] |= kWhirlpoolAcLightMask;
}
bool IRWhirlpoolAc::getLight() {
return !(remote_state[kWhirlpoolAcClockPos] & kWhirlpoolAcLightMask);
}
void IRWhirlpoolAc::setTime(const uint16_t pos,
const uint16_t minspastmidnight) {
// Hours
remote_state[pos] &= ~kWhirlpoolAcHourMask;
remote_state[pos] |= (minspastmidnight / 60) % 24;
// Minutes
remote_state[pos + 1] &= ~kWhirlpoolAcMinuteMask;
remote_state[pos + 1] |= minspastmidnight % 60;
}
uint16_t IRWhirlpoolAc::getTime(const uint16_t pos) {
return (remote_state[pos] & kWhirlpoolAcHourMask) * 60 +
(remote_state[pos + 1] & kWhirlpoolAcMinuteMask);
}
bool IRWhirlpoolAc::isTimerEnabled(const uint16_t pos) {
return remote_state[pos - 1] & kWhirlpoolAcTimerEnableMask;
}
void IRWhirlpoolAc::enableTimer(const uint16_t pos, const bool state) {
if (state)
remote_state[pos - 1] |= kWhirlpoolAcTimerEnableMask;
else
remote_state[pos - 1] &= ~kWhirlpoolAcTimerEnableMask;
}
void IRWhirlpoolAc::setClock(const uint16_t minspastmidnight) {
setTime(kWhirlpoolAcClockPos, minspastmidnight);
}
uint16_t IRWhirlpoolAc::getClock() { return getTime(kWhirlpoolAcClockPos); }
void IRWhirlpoolAc::setOffTimer(const uint16_t minspastmidnight) {
setTime(kWhirlpoolAcOffTimerPos, minspastmidnight);
}
uint16_t IRWhirlpoolAc::getOffTimer() {
return getTime(kWhirlpoolAcOffTimerPos);
}
bool IRWhirlpoolAc::isOffTimerEnabled() {
return isTimerEnabled(kWhirlpoolAcOffTimerPos);
}
void IRWhirlpoolAc::enableOffTimer(const bool state) {
enableTimer(kWhirlpoolAcOffTimerPos, state);
setCommand(kWhirlpoolAcCommandOffTimer);
}
void IRWhirlpoolAc::setOnTimer(const uint16_t minspastmidnight) {
setTime(kWhirlpoolAcOnTimerPos, minspastmidnight);
}
uint16_t IRWhirlpoolAc::getOnTimer() { return getTime(kWhirlpoolAcOnTimerPos); }
bool IRWhirlpoolAc::isOnTimerEnabled() {
return isTimerEnabled(kWhirlpoolAcOnTimerPos);
}
void IRWhirlpoolAc::enableOnTimer(const bool state) {
enableTimer(kWhirlpoolAcOnTimerPos, state);
setCommand(kWhirlpoolAcCommandOnTimer);
}
void IRWhirlpoolAc::setPowerToggle(const bool on) {
if (on)
remote_state[kWhirlpoolAcPowerTogglePos] |= kWhirlpoolAcPowerToggleMask;
else
remote_state[kWhirlpoolAcPowerTogglePos] &= ~kWhirlpoolAcPowerToggleMask;
setSuper(false); // Changing power cancels Super/Jet mode.
setCommand(kWhirlpoolAcCommandPower);
}
bool IRWhirlpoolAc::getPowerToggle() {
return remote_state[kWhirlpoolAcPowerTogglePos] & kWhirlpoolAcPowerToggleMask;
}
uint8_t IRWhirlpoolAc::getCommand() {
return remote_state[kWhirlpoolAcCommandPos];
}
void IRWhirlpoolAc::setSleep(const bool on) {
if (on) {
remote_state[kWhirlpoolAcSleepPos] |= kWhirlpoolAcSleepMask;
setFan(kWhirlpoolAcFanLow);
} else {
remote_state[kWhirlpoolAcSleepPos] &= ~kWhirlpoolAcSleepMask;
}
setCommand(kWhirlpoolAcCommandSleep);
}
bool IRWhirlpoolAc::getSleep() {
return remote_state[kWhirlpoolAcSleepPos] & kWhirlpoolAcSleepMask;
}
// AKA Jet/Turbo mode.
void IRWhirlpoolAc::setSuper(const bool on) {
if (on) {
setFan(kWhirlpoolAcFanHigh);
switch (getMode()) {
case kWhirlpoolAcHeat:
setTemp(kWhirlpoolAcMaxTemp + getTempOffset());
break;
case kWhirlpoolAcCool:
default:
setTemp(kWhirlpoolAcMinTemp + getTempOffset());
setMode(kWhirlpoolAcCool);
break;
}
remote_state[kWhirlpoolAcSuperPos] |= kWhirlpoolAcSuperMask;
} else {
remote_state[kWhirlpoolAcSuperPos] &= ~kWhirlpoolAcSuperMask;
}
setCommand(kWhirlpoolAcCommandSuper);
}
bool IRWhirlpoolAc::getSuper() {
return remote_state[kWhirlpoolAcSuperPos] & kWhirlpoolAcSuperMask;
}
void IRWhirlpoolAc::setCommand(const uint8_t code) {
remote_state[kWhirlpoolAcCommandPos] = code;
}
#ifdef ARDUINO
String IRWhirlpoolAc::timeToString(const uint16_t minspastmidnight) {
String result = "";
#else
std::string IRWhirlpoolAc::timeToString(const uint16_t minspastmidnight) {
std::string result = "";
#endif // ARDUINO
uint8_t hours = minspastmidnight / 60;
if (hours < 10) result += "0";
result += uint64ToString(hours);
result += ":";
uint8_t mins = minspastmidnight % 60;
if (mins < 10) result += "0";
result += uint64ToString(mins);
return result;
}
// Convert the internal state into a human readable string.
#ifdef ARDUINO
String IRWhirlpoolAc::toString() {
String result = "";
#else
std::string IRWhirlpoolAc::toString() {
std::string result = "";
#endif // ARDUINO
result += "Model: " + uint64ToString(getModel());
switch (getModel()) {
case DG11J191:
result += " (DG11J191)";
break;
case DG11J13A:
result += " (DG11J13A)";
break;
default:
result += " (UNKNOWN)";
}
result += ", Power toggle: ";
if (getPowerToggle())
result += "On";
else
result += "Off";
result += ", Mode: " + uint64ToString(getMode());
switch (getMode()) {
case kWhirlpoolAcHeat:
result += " (HEAT)";
break;
case kWhirlpoolAcAuto:
result += " (AUTO)";
break;
case kWhirlpoolAcCool:
result += " (COOL)";
break;
case kWhirlpoolAcDry:
result += " (DRY)";
break;
case kWhirlpoolAcFan:
result += " (FAN)";
break;
default:
result += " (UNKNOWN)";
}
result += ", Temp: " + uint64ToString(getTemp()) + "C";
result += ", Fan: " + uint64ToString(getFan());
switch (getFan()) {
case kWhirlpoolAcFanAuto:
result += " (AUTO)";
break;
case kWhirlpoolAcFanHigh:
result += " (HIGH)";
break;
case kWhirlpoolAcFanMedium:
result += " (MEDIUM)";
break;
case kWhirlpoolAcFanLow:
result += " (LOW)";
break;
default:
result += " (UNKNOWN)";
break;
}
result += ", Swing: ";
if (getSwing())
result += "On";
else
result += "Off";
result += ", Light: ";
if (getLight())
result += "On";
else
result += "Off";
result += ", Clock: ";
result += timeToString(getClock());
result += ", On Timer: ";
if (isOnTimerEnabled())
result += timeToString(getOnTimer());
else
result += "Off";
result += ", Off Timer: ";
if (isOffTimerEnabled())
result += timeToString(getOffTimer());
else
result += "Off";
result += ", Sleep: ";
if (getSleep())
result += "On";
else
result += "Off";
result += ", Super: ";
if (getSuper())
result += "On";
else
result += "Off";
result += ", Command: " + uint64ToString(getCommand());
switch (getCommand()) {
case kWhirlpoolAcCommandLight:
result += " (LIGHT)";
break;
case kWhirlpoolAcCommandPower:
result += " (POWER)";
break;
case kWhirlpoolAcCommandTemp:
result += " (TEMP)";
break;
case kWhirlpoolAcCommandSleep:
result += " (SLEEP)";
break;
case kWhirlpoolAcCommandSuper:
result += " (SUPER)";
break;
case kWhirlpoolAcCommandOnTimer:
result += " (ONTIMER)";
break;
case kWhirlpoolAcCommandMode:
result += " (MODE)";
break;
case kWhirlpoolAcCommandSwing:
result += " (SWING)";
break;
case kWhirlpoolAcCommandIFeel:
result += " (IFEEL)";
break;
case kWhirlpoolAcCommandFanSpeed:
result += " (FANSPEED)";
break;
case kWhirlpoolAcCommand6thSense:
result += " (6THSENSE)";
break;
case kWhirlpoolAcCommandOffTimer:
result += " (OFFTIMER)";
break;
default:
result += " (UNKNOWN)";
break;
}
return result;
}
#if DECODE_WHIRLPOOL_AC
// Decode the supplied Whirlpool 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 kWhirlpoolAcBits
// strict: Flag indicating if we should perform strict matching.
// Returns:
// boolean: True if it can decode it, false if it can't.
//
// Status: STABLE / Working as intended.
//
//
// Ref:
// https://github.com/markszabo/IRremoteESP8266/issues/509
bool IRrecv::decodeWhirlpoolAC(decode_results *results, uint16_t nbits,
bool strict) {
if (results->rawlen < 2 * nbits + 4 + kHeader + kFooter - 1)
return false; // Can't possibly be a valid Whirlpool A/C message.
if (strict) {
if (nbits != kWhirlpoolAcBits) return false;
}
uint16_t offset = kStartOffset;
uint16_t dataBitsSoFar = 0;
uint16_t i = 0;
match_result_t data_result;
uint8_t sectionSize[kWhirlpoolAcSections] = {6, 8, 7};
// Header
if (!matchMark(results->rawbuf[offset++], kWhirlpoolAcHdrMark)) return false;
if (!matchSpace(results->rawbuf[offset++], kWhirlpoolAcHdrSpace))
return false;
// Data Section
// Keep reading bytes until we either run out of section or state to fill.
for (uint8_t section = 0, pos = 0; section < kWhirlpoolAcSections;
section++) {
pos += sectionSize[section];
for (; offset <= results->rawlen - 16 && i < pos;
i++, dataBitsSoFar += 8, offset += data_result.used) {
data_result =
matchData(&(results->rawbuf[offset]), 8, kWhirlpoolAcBitMark,
kWhirlpoolAcOneSpace, kWhirlpoolAcBitMark,
kWhirlpoolAcZeroSpace, kTolerance, kMarkExcess, false);
if (data_result.success == false) break; // Fail
// Data is in LSB order. We need to reverse it.
results->state[i] = (uint8_t)data_result.data;
}
// Section Footer
if (!matchMark(results->rawbuf[offset++], kWhirlpoolAcBitMark))
return false;
if (section < kWhirlpoolAcSections - 1) { // Inter-section gaps.
if (!matchSpace(results->rawbuf[offset++], kWhirlpoolAcGap)) return false;
} else { // Last section / End of message gap.
if (offset <= results->rawlen &&
!matchAtLeast(results->rawbuf[offset++], kWhirlpoolAcGap))
return false;
}
}
// Compliance
if (strict) {
// Re-check we got the correct size/length due to the way we read the data.
if (dataBitsSoFar != kWhirlpoolAcBits) return false;
if (!IRWhirlpoolAc::validChecksum(results->state, dataBitsSoFar / 8))
return false;
}
// Success
results->decode_type = WHIRLPOOL_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 // WHIRLPOOL_AC
+161
View File
@@ -0,0 +1,161 @@
// Whirlpool A/C
//
// Copyright 2018 David Conran
#ifndef IR_WHIRLPOOL_H_
#define IR_WHIRLPOOL_H_
#define __STDC_LIMIT_MACROS
#include <stdint.h>
#ifndef UNIT_TEST
#include <Arduino.h>
#else
#include <string>
#endif
#include "IRremoteESP8266.h"
#include "IRsend.h"
// WW WW HH HH IIIII RRRRRR LL PPPPPP OOOOO OOOOO LL
// WW WW HH HH III RR RR LL PP PP OO OO OO OO LL
// WW W WW HHHHHHH III RRRRRR LL PPPPPP OO OO OO OO LL
// WW WWW WW HH HH III RR RR LL PP OO OO OO OO LL
// WW WW HH HH IIIII RR RR LLLLLLL PP OOOO0 OOOO0 LLLLLLL
// Ref:
// https://github.com/markszabo/IRremoteESP8266/issues/509
// Constants
const uint8_t kWhirlpoolAcChecksumByte1 = 13;
const uint8_t kWhirlpoolAcChecksumByte2 = kWhirlpoolAcStateLength - 1;
const uint8_t kWhirlpoolAcHeat = 0;
const uint8_t kWhirlpoolAcAuto = 1;
const uint8_t kWhirlpoolAcCool = 2;
const uint8_t kWhirlpoolAcDry = 3;
const uint8_t kWhirlpoolAcFan = 4;
const uint8_t kWhirlpoolAcModeMask = 0b00000111;
const uint8_t kWhirlpoolAcModePos = 3;
const uint8_t kWhirlpoolAcFanAuto = 0;
const uint8_t kWhirlpoolAcFanHigh = 1;
const uint8_t kWhirlpoolAcFanMedium = 2;
const uint8_t kWhirlpoolAcFanLow = 3;
const uint8_t kWhirlpoolAcFanMask = 0b00000011;
const uint8_t kWhirlpoolAcFanPos = 2;
const uint8_t kWhirlpoolAcMinTemp = 18; // 18C (DG11J1-3A), 16C (DG11J1-91)
const uint8_t kWhirlpoolAcMaxTemp = 32; // 32C (DG11J1-3A), 30C (DG11J1-91)
const uint8_t kWhirlpoolAcAutoTemp = 23; // 23C
const uint8_t kWhirlpoolAcTempMask = 0b11110000;
const uint8_t kWhirlpoolAcTempPos = 3;
const uint8_t kWhirlpoolAcSwing1Mask = 0b10000000;
const uint8_t kWhirlpoolAcSwing2Mask = 0b01000000;
const uint8_t kWhirlpoolAcLightMask = 0b00100000;
const uint8_t kWhirlpoolAcPowerToggleMask = 0b00000100;
const uint8_t kWhirlpoolAcPowerTogglePos = 2;
const uint8_t kWhirlpoolAcSleepMask = 0b00001000;
const uint8_t kWhirlpoolAcSleepPos = 2;
const uint8_t kWhirlpoolAcSuperMask = 0b10010000;
const uint8_t kWhirlpoolAcSuperPos = 5;
const uint8_t kWhirlpoolAcHourMask = 0b00011111;
const uint8_t kWhirlpoolAcMinuteMask = 0b00111111;
const uint8_t kWhirlpoolAcTimerEnableMask = 0b10000000;
const uint8_t kWhirlpoolAcClockPos = 6;
const uint8_t kWhirlpoolAcOffTimerPos = 8;
const uint8_t kWhirlpoolAcOnTimerPos = 10;
const uint8_t kWhirlpoolAcCommandPos = 15;
const uint8_t kWhirlpoolAcCommandLight = 0x00;
const uint8_t kWhirlpoolAcCommandPower = 0x01;
const uint8_t kWhirlpoolAcCommandTemp = 0x02;
const uint8_t kWhirlpoolAcCommandSleep = 0x03;
const uint8_t kWhirlpoolAcCommandSuper = 0x04;
const uint8_t kWhirlpoolAcCommandOnTimer = 0x05;
const uint8_t kWhirlpoolAcCommandMode = 0x06;
const uint8_t kWhirlpoolAcCommandSwing = 0x07;
const uint8_t kWhirlpoolAcCommandIFeel = 0x0D;
const uint8_t kWhirlpoolAcCommandFanSpeed = 0x11;
const uint8_t kWhirlpoolAcCommand6thSense = 0x17;
const uint8_t kWhirlpoolAcCommandOffTimer = 0x1D;
const uint8_t kWhirlpoolAcAltTempMask = 0b00001000;
const uint8_t kWhirlpoolAcAltTempPos = 18;
enum whirlpool_ac_remote_model_t {
DG11J13A = 1, // DG11J1-04 too
DG11J191,
};
// Classes
class IRWhirlpoolAc {
public:
explicit IRWhirlpoolAc(uint16_t pin);
void stateReset();
#if SEND_WHIRLPOOL_AC
void send(const uint16_t repeat = kWhirlpoolAcDefaultRepeat,
const bool calcchecksum = true);
#endif // SEND_WHIRLPOOL_AC
void begin();
void on();
void off();
void setPowerToggle(const bool on);
bool getPowerToggle();
void setSleep(const bool on);
bool getSleep();
void setSuper(const bool on);
bool getSuper();
void setTemp(const uint8_t temp);
uint8_t getTemp();
void setFan(const uint8_t speed);
uint8_t getFan();
void setMode(const uint8_t mode);
uint8_t getMode();
void setSwing(const bool on);
bool getSwing();
void setLight(const bool on);
bool getLight();
uint16_t getClock();
void setClock(const uint16_t minspastmidnight);
uint16_t getOnTimer();
void setOnTimer(const uint16_t minspastmidnight);
void enableOnTimer(const bool state);
bool isOnTimerEnabled();
uint16_t getOffTimer();
void setOffTimer(const uint16_t minspastmidnight);
void enableOffTimer(const bool state);
bool isOffTimerEnabled();
void setCommand(const uint8_t code);
uint8_t getCommand();
whirlpool_ac_remote_model_t getModel();
void setModel(const whirlpool_ac_remote_model_t model);
uint8_t* getRaw(const bool calcchecksum = true);
void setRaw(const uint8_t new_code[],
const uint16_t length = kWhirlpoolAcStateLength);
static bool validChecksum(uint8_t state[],
const uint16_t length = kWhirlpoolAcStateLength);
#ifdef ARDUINO
String toString();
#else
std::string toString();
#endif
#ifndef UNIT_TEST
private:
#endif
// The state of the IR remote in IR code form.
uint8_t remote_state[kWhirlpoolAcStateLength];
IRsend _irsend;
uint8_t _desiredtemp;
void checksum(const uint16_t length = kWhirlpoolAcStateLength);
uint16_t getTime(const uint16_t pos);
void setTime(const uint16_t pos, const uint16_t minspastmidnight);
bool isTimerEnabled(const uint16_t pos);
void enableTimer(const uint16_t pos, const bool state);
void _setTemp(const uint8_t temp, const bool remember = true);
void _setMode(const uint8_t mode);
int8_t getTempOffset();
#ifdef ARDUINO
String timeToString(uint16_t minspastmidnight);
#else
std::string timeToString(uint16_t minspastmidnight);
#endif
};
#endif // IR_WHIRLPOOL_H_
+45 -50
View File
@@ -16,27 +16,26 @@
// Whynter originally added from https://github.com/shirriff/Arduino-IRremote/
// Constants
#define WHYNTER_TICK 50U
#define WHYNTER_HDR_MARK_TICKS 57U
#define WHYNTER_HDR_MARK (WHYNTER_HDR_MARK_TICKS * WHYNTER_TICK)
#define WHYNTER_HDR_SPACE_TICKS 57U
#define WHYNTER_HDR_SPACE (WHYNTER_HDR_SPACE_TICKS * \
WHYNTER_TICK)
#define WHYNTER_BIT_MARK_TICKS 15U
#define WHYNTER_BIT_MARK (WHYNTER_BIT_MARK_TICKS * WHYNTER_TICK)
#define WHYNTER_ONE_SPACE_TICKS 43U
#define WHYNTER_ONE_SPACE (WHYNTER_ONE_SPACE_TICKS * \
WHYNTER_TICK)
#define WHYNTER_ZERO_SPACE_TICKS 15U
#define WHYNTER_ZERO_SPACE (WHYNTER_ZERO_SPACE_TICKS * \
WHYNTER_TICK)
#define WHYNTER_MIN_COMMAND_LENGTH_TICKS 2160U // Completely made up value.
#define WHYNTER_MIN_COMMAND_LENGTH (WHYNTER_MIN_COMMAND_LENGTH_TICKS * \
WHYNTER_TICK)
#define WHYNTER_MIN_GAP_TICKS (WHYNTER_MIN_COMMAND_LENGTH_TICKS - \
(2 * (WHYNTER_BIT_MARK_TICKS + WHYNTER_ZERO_SPACE_TICKS) + \
WHYNTER_BITS * (WHYNTER_BIT_MARK_TICKS + WHYNTER_ONE_SPACE_TICKS)))
#define WHYNTER_MIN_GAP (WHYNTER_MIN_GAP_TICKS * WHYNTER_TICK)
const uint16_t kWhynterTick = 50;
const uint16_t kWhynterHdrMarkTicks = 57;
const uint16_t kWhynterHdrMark = kWhynterHdrMarkTicks * kWhynterTick;
const uint16_t kWhynterHdrSpaceTicks = 57;
const uint16_t kWhynterHdrSpace = kWhynterHdrSpaceTicks * kWhynterTick;
const uint16_t kWhynterBitMarkTicks = 15;
const uint16_t kWhynterBitMark = kWhynterBitMarkTicks * kWhynterTick;
const uint16_t kWhynterOneSpaceTicks = 43;
const uint16_t kWhynterOneSpace = kWhynterOneSpaceTicks * kWhynterTick;
const uint16_t kWhynterZeroSpaceTicks = 15;
const uint16_t kWhynterZeroSpace = kWhynterZeroSpaceTicks * kWhynterTick;
const uint16_t kWhynterMinCommandLengthTicks = 2160; // Totally made up value.
const uint32_t kWhynterMinCommandLength =
kWhynterMinCommandLengthTicks * kWhynterTick;
const uint16_t kWhynterMinGapTicks =
kWhynterMinCommandLengthTicks -
(2 * (kWhynterBitMarkTicks + kWhynterZeroSpaceTicks) +
kWhynterBits * (kWhynterBitMarkTicks + kWhynterOneSpaceTicks));
const uint16_t kWhynterMinGap = kWhynterMinGapTicks * kWhynterTick;
#if SEND_WHYNTER
// Send a Whynter message.
@@ -56,16 +55,14 @@ void IRsend::sendWhynter(uint64_t data, uint16_t nbits, uint16_t repeat) {
for (uint16_t i = 0; i <= repeat; i++) {
// (Pre-)Header
mark(WHYNTER_BIT_MARK);
space(WHYNTER_ZERO_SPACE);
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);
mark(kWhynterBitMark);
space(kWhynterZeroSpace);
sendGeneric(
kWhynterHdrMark, kWhynterHdrSpace, kWhynterBitMark, kWhynterOneSpace,
kWhynterBitMark, kWhynterZeroSpace, kWhynterBitMark, kWhynterMinGap,
kWhynterMinCommandLength - (kWhynterBitMark + kWhynterZeroSpace), data,
nbits, 38, true, 0, // Repeats are already handled.
50);
}
}
#endif
@@ -86,47 +83,45 @@ void IRsend::sendWhynter(uint64_t data, uint16_t nbits, uint16_t repeat) {
// https://github.com/z3t0/Arduino-IRremote/blob/master/ir_Whynter.cpp
bool IRrecv::decodeWhynter(decode_results *results, uint16_t nbits,
bool strict) {
if (results->rawlen < 2 * nbits + 2 * HEADER + FOOTER - 1)
return false; // We don't have enough entries to possibly match.
if (results->rawlen < 2 * nbits + 2 * kHeader + kFooter - 1)
return false; // We don't have enough entries to possibly match.
// Compliance
if (strict && nbits != WHYNTER_BITS)
if (strict && nbits != kWhynterBits)
return false; // Incorrect nr. of bits per spec.
uint16_t offset = OFFSET_START;
uint16_t offset = kStartOffset;
// Header
// Sequence begins with a bit mark and a zero space.
// These are typically small, so we'll prefer to do the calibration
// on the much larger header mark & space that are next.
if (!matchMark(results->rawbuf[offset++], WHYNTER_BIT_MARK)) return false;
if (!matchSpace(results->rawbuf[offset++], WHYNTER_ZERO_SPACE)) return false;
if (!matchMark(results->rawbuf[offset++], kWhynterBitMark)) return false;
if (!matchSpace(results->rawbuf[offset++], kWhynterZeroSpace)) return false;
// Main header mark and space
if (!matchMark(results->rawbuf[offset], WHYNTER_HDR_MARK)) return false;
if (!matchMark(results->rawbuf[offset], kWhynterHdrMark)) return false;
// Calculate how long the common tick time is based on the header mark.
uint32_t m_tick = results->rawbuf[offset++] * RAWTICK /
WHYNTER_HDR_MARK_TICKS;
if (!matchSpace(results->rawbuf[offset], WHYNTER_HDR_SPACE)) return false;
uint32_t m_tick = results->rawbuf[offset++] * kRawTick / kWhynterHdrMarkTicks;
if (!matchSpace(results->rawbuf[offset], kWhynterHdrSpace)) return false;
// Calculate how long the common tick time is based on the header space.
uint32_t s_tick = results->rawbuf[offset++] * RAWTICK /
WHYNTER_HDR_SPACE_TICKS;
uint32_t s_tick =
results->rawbuf[offset++] * kRawTick / kWhynterHdrSpaceTicks;
// Data
uint64_t data = 0;
match_result_t data_result = matchData(&(results->rawbuf[offset]), nbits,
WHYNTER_BIT_MARK_TICKS * m_tick,
WHYNTER_ONE_SPACE_TICKS * s_tick,
WHYNTER_BIT_MARK_TICKS * m_tick,
WHYNTER_ZERO_SPACE_TICKS * s_tick);
match_result_t data_result =
matchData(&(results->rawbuf[offset]), nbits,
kWhynterBitMarkTicks * m_tick, kWhynterOneSpaceTicks * s_tick,
kWhynterBitMarkTicks * m_tick, kWhynterZeroSpaceTicks * s_tick);
if (data_result.success == false) return false;
data = data_result.data;
offset += data_result.used;
// Footer
if (!matchMark(results->rawbuf[offset++], WHYNTER_BIT_MARK_TICKS * m_tick))
if (!matchMark(results->rawbuf[offset++], kWhynterBitMarkTicks * m_tick))
return false;
if (offset < results->rawlen &&
!matchAtLeast(results->rawbuf[offset], WHYNTER_MIN_GAP_TICKS * s_tick))
!matchAtLeast(results->rawbuf[offset], kWhynterMinGapTicks * s_tick))
return false;
// Success