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:
1 parent
0180a0dbd5
commit
f0305236db
149 files changed
+20957
-7850
No files matched your search
+159
-144
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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_
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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_
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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_
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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_
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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_
|
||||
@@ -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
|
||||
|
||||
@@ -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_
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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_
|
||||
@@ -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.
|
||||
}
|
||||
|
||||
|
||||
@@ -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_
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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]; }
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user