publish libraries to avoid users to have to download them one by one
This commit is contained in:
1 parent
93c075e880
commit
f0546fab8c
396 files changed
+536928
No files matched your search
@@ -0,0 +1 @@
|
||||
filter=-build/include,+build/include_alpha,+build/include_order,+build/include_what_you_use
|
||||
@@ -0,0 +1,627 @@
|
||||
// Copyright 2009 Ken Shirriff
|
||||
// Copyright 2015 Mark Szabo
|
||||
// Copyright 2015 Sebastien Warin
|
||||
// Copyright 2017 David Conran
|
||||
|
||||
#include "IRrecv.h"
|
||||
#include <stddef.h>
|
||||
#ifndef UNIT_TEST
|
||||
extern "C" {
|
||||
#include <gpio.h>
|
||||
#include <user_interface.h>
|
||||
}
|
||||
#include <Arduino.h>
|
||||
#endif
|
||||
#include <algorithm>
|
||||
#include "IRremoteESP8266.h"
|
||||
|
||||
#ifdef UNIT_TEST
|
||||
#undef ICACHE_RAM_ATTR
|
||||
#define ICACHE_RAM_ATTR
|
||||
#endif
|
||||
// Updated by Sebastien Warin (http://sebastien.warin.fr) for receiving IR code
|
||||
// on ESP8266
|
||||
// Updated by markszabo (https://github.com/markszabo/IRremoteESP8266) for
|
||||
// sending IR code on ESP8266
|
||||
|
||||
// Globals
|
||||
#ifndef UNIT_TEST
|
||||
static ETSTimer timer;
|
||||
#endif
|
||||
volatile irparams_t irparams;
|
||||
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;
|
||||
os_intr_unlock();
|
||||
}
|
||||
|
||||
static void ICACHE_RAM_ATTR gpio_intr() {
|
||||
uint32_t now = system_get_time();
|
||||
uint32_t gpio_status = GPIO_REG_READ(GPIO_STATUS_ADDRESS);
|
||||
static uint32_t start = 0;
|
||||
|
||||
os_timer_disarm(&timer);
|
||||
GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, gpio_status);
|
||||
|
||||
// Grab a local copy of rawlen to reduce instructions used in IRAM.
|
||||
// This is an ugly premature optimisation code-wise, but we do everything we
|
||||
// can to save IRAM.
|
||||
// It seems referencing the value via the structure uses more instructions.
|
||||
// Less instructions means faster and less IRAM used.
|
||||
// N.B. It saves about 13 bytes of IRAM.
|
||||
uint16_t rawlen = irparams.rawlen;
|
||||
|
||||
if (rawlen >= irparams.bufsize) {
|
||||
irparams.overflow = true;
|
||||
irparams.rcvstate = STATE_STOP;
|
||||
}
|
||||
|
||||
if (irparams.rcvstate == STATE_STOP)
|
||||
return;
|
||||
|
||||
if (irparams.rcvstate == STATE_IDLE) {
|
||||
irparams.rcvstate = STATE_MARK;
|
||||
irparams.rawbuf[rawlen] = 1;
|
||||
} else {
|
||||
if (now < start)
|
||||
irparams.rawbuf[rawlen] = (UINT32_MAX - start + now) / RAWTICK;
|
||||
else
|
||||
irparams.rawbuf[rawlen] = (now - start) / RAWTICK;
|
||||
}
|
||||
irparams.rawlen++;
|
||||
|
||||
start = now;
|
||||
#define ONCE 0
|
||||
os_timer_arm(&timer, irparams.timeout, ONCE);
|
||||
}
|
||||
#endif // UNIT_TEST
|
||||
|
||||
// Start of IRrecv class -------------------
|
||||
|
||||
// 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)
|
||||
// timeout: Nr. of milli-Seconds of no signal before we stop capturing data.
|
||||
// (Default: TIMEOUT_MS)
|
||||
// save_buffer: Use a second (save) buffer to decode from. (Def: false)
|
||||
// Returns:
|
||||
// A IRrecv class object.
|
||||
IRrecv::IRrecv(uint16_t recvpin, uint16_t bufsize, uint8_t timeout,
|
||||
bool save_buffer) {
|
||||
irparams.recvpin = recvpin;
|
||||
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.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!");
|
||||
#ifndef UNIT_TEST
|
||||
ESP.restart(); // Mem alloc failure. Reboot.
|
||||
#endif
|
||||
}
|
||||
// If we have been asked to use a save buffer (for decoding), then create one.
|
||||
if (save_buffer) {
|
||||
irparams_save = new irparams_t;
|
||||
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!");
|
||||
#ifndef UNIT_TEST
|
||||
ESP.restart(); // Mem alloc failure. Reboot.
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
irparams_save = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// Class destructor
|
||||
IRrecv::~IRrecv(void) {
|
||||
delete [] irparams.rawbuf;
|
||||
if (irparams_save != NULL) {
|
||||
delete [] irparams_save->rawbuf;
|
||||
delete irparams_save;
|
||||
}
|
||||
}
|
||||
|
||||
// initialization
|
||||
void IRrecv::enableIRIn() {
|
||||
// initialize state machine variables
|
||||
resume();
|
||||
|
||||
#ifndef UNIT_TEST
|
||||
// Initialize timer
|
||||
os_timer_disarm(&timer);
|
||||
os_timer_setfn(&timer, reinterpret_cast<os_timer_func_t *>(read_timeout),
|
||||
NULL);
|
||||
|
||||
// Attach Interrupt
|
||||
attachInterrupt(irparams.recvpin, gpio_intr, CHANGE);
|
||||
#endif
|
||||
}
|
||||
|
||||
void IRrecv::disableIRIn() {
|
||||
#ifndef UNIT_TEST
|
||||
os_timer_disarm(&timer);
|
||||
detachInterrupt(irparams.recvpin);
|
||||
#endif
|
||||
}
|
||||
|
||||
void IRrecv::resume() {
|
||||
irparams.rcvstate = STATE_IDLE;
|
||||
irparams.rawlen = 0;
|
||||
irparams.overflow = false;
|
||||
}
|
||||
|
||||
// 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.
|
||||
//
|
||||
// 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)
|
||||
|
||||
// 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.
|
||||
// This isn't immediately obvious due to typecasting/different variable names.
|
||||
uint16_t *dst_rawbuf_ptr;
|
||||
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];
|
||||
|
||||
// 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];
|
||||
}
|
||||
|
||||
// Obtain the maximum number of entries possible in the capture buffer.
|
||||
// i.e. It's size.
|
||||
uint16_t IRrecv::getBufSize() {
|
||||
return irparams.bufsize;
|
||||
}
|
||||
|
||||
// Decodes the received IR message.
|
||||
// If the interrupt state is saved, we will immediately resume waiting
|
||||
// for the next IR message to avoid missing messages.
|
||||
// Note: There is a trade-off here. Saving the state means less time lost until
|
||||
// we can receiving the next message vs. using more RAM. Choose appropriately.
|
||||
//
|
||||
// Args:
|
||||
// results: A pointer to where the decoded IR message will be stored.
|
||||
// save: A pointer to an irparams_t instance in which to save
|
||||
// the interrupt's memory/state. NULL means don't save it.
|
||||
// Returns:
|
||||
// A boolean indicating if an IR message is ready or not.
|
||||
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;
|
||||
#endif
|
||||
|
||||
// Clear the entry we are currently pointing to when we got the timeout.
|
||||
// i.e. Stopped collecting IR data.
|
||||
// It's junk as we never wrote an entry to it and can only confuse decoding.
|
||||
// This is done here rather than logically the best place in read_timeout()
|
||||
// as it saves a few bytes of ICACHE_RAM as that routine is bound to an
|
||||
// interrupt. decode() is not stored in ICACHE_RAM.
|
||||
// Another better option would be to zero the entire irparams.rawbuf[] on
|
||||
// resume() but that is a much more expensive operation compare to this.
|
||||
irparams.rawbuf[irparams.rawlen] = 0;
|
||||
|
||||
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) {
|
||||
// We haven't been asked to copy it so use the existing memory.
|
||||
#ifndef UNIT_TEST
|
||||
results->rawbuf = irparams.rawbuf;
|
||||
results->rawlen = irparams.rawlen;
|
||||
results->overflow = irparams.overflow;
|
||||
#endif
|
||||
} else {
|
||||
copyIrParams(&irparams, save); // Duplicate the interrupt's memory.
|
||||
resume(); // It's now safe to rearm. The IR message won't be overridden.
|
||||
resumed = true;
|
||||
// Point the results at the saved copy.
|
||||
results->rawbuf = save->rawbuf;
|
||||
results->rawlen = save->rawlen;
|
||||
results->overflow = save->overflow;
|
||||
}
|
||||
|
||||
// Reset any previously partially processed results.
|
||||
results->decode_type = UNKNOWN;
|
||||
results->bits = 0;
|
||||
results->value = 0;
|
||||
results->address = 0;
|
||||
results->command = 0;
|
||||
results->repeat = false;
|
||||
|
||||
#if DECODE_AIWA_RC_T501
|
||||
DPRINTLN("Attempting Aiwa RC T501 decode");
|
||||
// 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;
|
||||
#endif
|
||||
#if DECODE_SANYO
|
||||
DPRINTLN("Attempting Sanyo LC7461 decode");
|
||||
// Try decodeSanyoLC7461() before decodeNEC() because the protocols are
|
||||
// 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;
|
||||
#endif
|
||||
#if DECODE_NEC
|
||||
DPRINTLN("Attempting NEC decode");
|
||||
if (decodeNEC(results))
|
||||
return true;
|
||||
#endif
|
||||
#if DECODE_SONY
|
||||
DPRINTLN("Attempting Sony decode");
|
||||
if (decodeSony(results))
|
||||
return true;
|
||||
#endif
|
||||
#if DECODE_MITSUBISHI
|
||||
DPRINTLN("Attempting Mitsubishi decode");
|
||||
if (decodeMitsubishi(results))
|
||||
return true;
|
||||
#endif
|
||||
#if DECODE_RC5
|
||||
DPRINTLN("Attempting RC5 decode");
|
||||
if (decodeRC5(results))
|
||||
return true;
|
||||
#endif
|
||||
#if DECODE_RC6
|
||||
DPRINTLN("Attempting RC6 decode");
|
||||
if (decodeRC6(results))
|
||||
return true;
|
||||
#endif
|
||||
#if DECODE_RCMM
|
||||
DPRINTLN("Attempting RC-MM decode");
|
||||
if (decodeRCMM(results))
|
||||
return true;
|
||||
#endif
|
||||
#if DECODE_DENON
|
||||
// Denon needs to precede Panasonic as it is a special case of Panasonic.
|
||||
#ifdef DEBUG
|
||||
DPRINTLN("Attempting Denon decode");
|
||||
#endif
|
||||
if (decodeDenon(results, DENON_48_BITS) ||
|
||||
decodeDenon(results, DENON_BITS) ||
|
||||
decodeDenon(results, DENON_LEGACY_BITS))
|
||||
return true;
|
||||
#endif
|
||||
#if DECODE_PANASONIC
|
||||
DPRINTLN("Attempting Panasonic decode");
|
||||
if (decodePanasonic(results))
|
||||
return true;
|
||||
#endif
|
||||
#if DECODE_LG
|
||||
DPRINTLN("Attempting LG (28-bit) decode");
|
||||
if (decodeLG(results, LG_BITS, true))
|
||||
return true;
|
||||
DPRINTLN("Attempting LG (32-bit) decode");
|
||||
// LG32 should be tried before Samsung
|
||||
if (decodeLG(results, LG32_BITS, true))
|
||||
return true;
|
||||
#endif
|
||||
#if DECODE_JVC
|
||||
DPRINTLN("Attempting JVC decode");
|
||||
if (decodeJVC(results))
|
||||
return true;
|
||||
#endif
|
||||
#if DECODE_SAMSUNG
|
||||
DPRINTLN("Attempting SAMSUNG decode");
|
||||
if (decodeSAMSUNG(results))
|
||||
return true;
|
||||
#endif
|
||||
#if DECODE_WHYNTER
|
||||
DPRINTLN("Attempting Whynter decode");
|
||||
if (decodeWhynter(results))
|
||||
return true;
|
||||
#endif
|
||||
#if DECODE_DISH
|
||||
DPRINTLN("Attempting DISH decode");
|
||||
if (decodeDISH(results))
|
||||
return true;
|
||||
#endif
|
||||
#if DECODE_SHARP
|
||||
DPRINTLN("Attempting Sharp decode");
|
||||
if (decodeSharp(results))
|
||||
return true;
|
||||
#endif
|
||||
#if DECODE_COOLIX
|
||||
DPRINTLN("Attempting Coolix decode");
|
||||
if (decodeCOOLIX(results))
|
||||
return true;
|
||||
#endif
|
||||
/* NOTE: Disabled due to poor quality.
|
||||
#if DECODE_SANYO
|
||||
// The Sanyo S866500B decoder is very poor quality & depricated.
|
||||
// *IF* you are going to enable it, do it near last to avoid false positive
|
||||
// matches.
|
||||
DPRINTLN("Attempting Sanyo SA8650B decode");
|
||||
if (decodeSanyo(results))
|
||||
return true;
|
||||
#endif
|
||||
*/
|
||||
#if DECODE_NEC
|
||||
// Some devices send NEC-like codes that don't follow the true NEC spec.
|
||||
// This should detect those. e.g. Apple TV remote etc.
|
||||
// This needs to be done after all other codes that use strict and some
|
||||
// other protocols that are NEC-like as well, as turning off strict may
|
||||
// cause this to match other valid protocols.
|
||||
DPRINTLN("Attempting NEC (non-stict) decode");
|
||||
if (decodeNEC(results, NEC_BITS, false)) {
|
||||
results->decode_type = NEC_LIKE;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
// decodeHash returns a hash on any input.
|
||||
// Thus, it needs to be last in the list.
|
||||
// If you add any decodes, add them before this.
|
||||
if (decodeHash(results)) {
|
||||
return true;
|
||||
}
|
||||
// Throw away and start over
|
||||
if (!resumed) // Check if we have already resumed.
|
||||
resume();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Calculate the lower bound of the nr. of ticks.
|
||||
//
|
||||
// Args:
|
||||
// usecs: Nr. of uSeconds.
|
||||
// tolerance: Percent as an integer. e.g. 10 is 10%
|
||||
// Returns:
|
||||
// Nr. of ticks.
|
||||
uint32_t IRrecv::ticksLow(uint32_t usecs, uint8_t tolerance) {
|
||||
// max() used to ensure the result can't drop below 0 before the cast.
|
||||
return((uint32_t) std::max((int32_t) (usecs * (1.0 - tolerance / 100.0)), 0));
|
||||
}
|
||||
|
||||
// Calculate the upper bound of the nr. of ticks.
|
||||
//
|
||||
// Args:
|
||||
// usecs: Nr. of uSeconds.
|
||||
// tolerance: Percent as an integer. e.g. 10 is 10%
|
||||
// Returns:
|
||||
// Nr. of ticks.
|
||||
uint32_t IRrecv::ticksHigh(uint32_t usecs, uint8_t tolerance) {
|
||||
return((uint32_t) (usecs * (1.0 + tolerance / 100.0)) + 1);
|
||||
}
|
||||
|
||||
// Check if we match a pulse(measured) with the desired within
|
||||
// +/-tolerance percent.
|
||||
//
|
||||
// Args:
|
||||
// measured: The recorded period of the signal pulse.
|
||||
// desired: The expected period (in useconds) we are matching against.
|
||||
// tolerance: A percentage expressed as an integer. e.g. 10 is 10%.
|
||||
//
|
||||
// Returns:
|
||||
// Boolean: true if it matches, false if it doesn't.
|
||||
bool IRrecv::match(uint32_t measured, uint32_t desired,
|
||||
uint8_t tolerance) {
|
||||
measured *= RAWTICK; // Convert to uSecs.
|
||||
DPRINT("Matching: ");
|
||||
DPRINT(ticksLow(desired, tolerance));
|
||||
DPRINT(" <= ");
|
||||
DPRINT(measured);
|
||||
DPRINT(" <= ");
|
||||
DPRINTLN(ticksHigh(desired, tolerance));
|
||||
return (measured >= ticksLow(desired, tolerance) &&
|
||||
measured <= ticksHigh(desired, tolerance));
|
||||
}
|
||||
|
||||
|
||||
// Check if we match a pulse(measured) of at least desired within
|
||||
// +/-tolerance percent.
|
||||
//
|
||||
// Args:
|
||||
// measured: The recorded period of the signal pulse.
|
||||
// desired: The expected period (in useconds) we are matching against.
|
||||
// tolerance: A percentage expressed as an integer. e.g. 10 is 10%.
|
||||
//
|
||||
// Returns:
|
||||
// Boolean: true if it matches, false if it doesn't.
|
||||
bool IRrecv::matchAtLeast(uint32_t measured, uint32_t desired,
|
||||
uint8_t tolerance) {
|
||||
measured *= RAWTICK; // Convert to uSecs.
|
||||
DPRINT("Matching ATLEAST ");
|
||||
DPRINT(measured);
|
||||
DPRINT(" vs ");
|
||||
DPRINT(desired);
|
||||
DPRINT(". Matching: ");
|
||||
DPRINT(measured);
|
||||
DPRINT(" >= ");
|
||||
DPRINT(ticksLow(std::min(desired, MS_TO_USEC(irparams.timeout)), tolerance));
|
||||
DPRINT(" [min(");
|
||||
DPRINT(ticksLow(desired, tolerance));
|
||||
DPRINT(", ");
|
||||
DPRINT(ticksLow(MS_TO_USEC(irparams.timeout), tolerance));
|
||||
DPRINTLN(")]");
|
||||
// We really should never get a value of 0, except as the last value
|
||||
// in the buffer. If that is the case, then assume infinity and return true.
|
||||
if (measured == 0) return true;
|
||||
return measured >= ticksLow(std::min(desired, MS_TO_USEC(irparams.timeout)),
|
||||
tolerance);
|
||||
}
|
||||
|
||||
// Check if we match a mark signal(measured) with the desired within
|
||||
// +/-tolerance percent, after an expected is excess is added.
|
||||
//
|
||||
// Args:
|
||||
// measured: The recorded period of the signal pulse.
|
||||
// desired: The expected period (in useconds) we are matching against.
|
||||
// tolerance: A percentage expressed as an integer. e.g. 10 is 10%.
|
||||
// excess: Nr. of useconds.
|
||||
//
|
||||
// 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) {
|
||||
DPRINT("Matching MARK ");
|
||||
DPRINT(measured * RAWTICK);
|
||||
DPRINT(" vs ");
|
||||
DPRINT(desired);
|
||||
DPRINT(" + ");
|
||||
DPRINT(excess);
|
||||
DPRINT(". ");
|
||||
return match(measured, desired + excess, tolerance);
|
||||
}
|
||||
|
||||
// Check if we match a space signal(measured) with the desired within
|
||||
// +/-tolerance percent, after an expected is excess is removed.
|
||||
//
|
||||
// Args:
|
||||
// measured: The recorded period of the signal pulse.
|
||||
// desired: The expected period (in useconds) we are matching against.
|
||||
// tolerance: A percentage expressed as an integer. e.g. 10 is 10%.
|
||||
// excess: Nr. of useconds.
|
||||
//
|
||||
// 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) {
|
||||
DPRINT("Matching SPACE ");
|
||||
DPRINT(measured * RAWTICK);
|
||||
DPRINT(" vs ");
|
||||
DPRINT(desired);
|
||||
DPRINT(" - ");
|
||||
DPRINT(excess);
|
||||
DPRINT(". ");
|
||||
return match(measured, desired - excess, tolerance);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
* hashdecode - decode an arbitrary IR code.
|
||||
* Instead of decoding using a standard encoding scheme
|
||||
* (e.g. Sony, NEC, RC5), the code is hashed to a 32-bit value.
|
||||
*
|
||||
* The algorithm: look at the sequence of MARK signals, and see if each one
|
||||
* is shorter (0), the same length (1), or longer (2) than the previous.
|
||||
* Do the same with the SPACE signals. Hash the resulting sequence of 0's,
|
||||
* 1's, and 2's to a 32-bit value. This will give a unique value for each
|
||||
* different code (probably), for most code systems.
|
||||
*
|
||||
* http://arcfn.com/2010/01/using-arbitrary-remotes-with-arduino.html
|
||||
*/
|
||||
|
||||
// Compare two tick values, returning 0 if newval is shorter,
|
||||
// 1 if newval is equal, and 2 if newval is longer
|
||||
// Use a tolerance of 20%
|
||||
int16_t IRrecv::compare(uint16_t oldval, uint16_t newval) {
|
||||
if (newval < oldval * 0.8)
|
||||
return 0;
|
||||
else if (oldval < newval * 0.8)
|
||||
return 2;
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Converts the raw code values into a 32-bit hash code.
|
||||
* Hopefully this code is unique for each button.
|
||||
* This isn't a "real" decoding, just an arbitrary value.
|
||||
*/
|
||||
bool IRrecv::decodeHash(decode_results *results) {
|
||||
// Require at least 6 samples to prevent triggering on noise
|
||||
if (results->rawlen < 6)
|
||||
return false;
|
||||
int32_t hash = FNV_BASIS_32;
|
||||
// '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
|
||||
// values.
|
||||
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;
|
||||
}
|
||||
results->value = hash & 0xFFFFFFFF;
|
||||
results->bits = results->rawlen / 2;
|
||||
results->address = 0;
|
||||
results->command = 0;
|
||||
results->decode_type = UNKNOWN;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Match & decode the typical data section of an IR message.
|
||||
// The data value constructed as the Most Significant Bit first.
|
||||
//
|
||||
// Args:
|
||||
// data_ptr: A pointer to where we are at in the capture buffer.
|
||||
// nbits: Nr. of data bits we expect.
|
||||
// onemark: Nr. of uSeconds in an expected mark signal for a '1' bit.
|
||||
// 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.
|
||||
// Returns:
|
||||
// A match_result_t structure containing the success (or not), the data value,
|
||||
// and how many buffer entries were used.
|
||||
match_result_t IRrecv::matchData(volatile uint16_t *data_ptr, uint16_t nbits,
|
||||
uint16_t onemark, uint32_t onespace,
|
||||
uint16_t zeromark, uint32_t zerospace) {
|
||||
match_result_t result;
|
||||
result.success = false;
|
||||
result.data = 0;
|
||||
if (onemark == zeromark) { // Is this space encoded data format?
|
||||
for (result.used = 0;
|
||||
result.used < nbits * 2;
|
||||
result.used += 2, data_ptr++) {
|
||||
if (!matchMark(*data_ptr, onemark))
|
||||
return result; // Fail
|
||||
data_ptr++;
|
||||
if (matchSpace(*data_ptr, onespace))
|
||||
result.data = (result.data << 1) | 1;
|
||||
else if (matchSpace(*data_ptr, zerospace))
|
||||
result.data <<= 1;
|
||||
else
|
||||
return result; // Fail
|
||||
}
|
||||
result.success = true;
|
||||
} else if (onespace == zerospace) { // Is this mark encoded data format?
|
||||
for (result.used = 0;
|
||||
result.used < nbits * 2;
|
||||
result.used += 2, data_ptr++) {
|
||||
if (matchMark(*data_ptr, onemark))
|
||||
result.data = (result.data << 1) | 1;
|
||||
else if (matchMark(*data_ptr, zeromark))
|
||||
result.data <<= 1;
|
||||
else
|
||||
return result; // Fail
|
||||
data_ptr++;
|
||||
if (!matchSpace(*data_ptr, onespace))
|
||||
return result; // Fail
|
||||
}
|
||||
result.success = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// End of IRrecv class -------------------
|
||||
@@ -0,0 +1,203 @@
|
||||
// Copyright 2009 Ken Shirriff
|
||||
// Copyright 2015 Mark Szabo
|
||||
// Copyright 2015 Sebastien Warin
|
||||
// Copyright 2017 David Conran
|
||||
|
||||
#ifndef IRRECV_H_
|
||||
#define IRRECV_H_
|
||||
|
||||
#ifndef UNIT_TEST
|
||||
#include <Arduino.h>
|
||||
#endif
|
||||
#include <stddef.h>
|
||||
#define __STDC_LIMIT_MACROS
|
||||
#include <stdint.h>
|
||||
#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.
|
||||
// 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
|
||||
// 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.
|
||||
// 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)
|
||||
// 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))
|
||||
|
||||
// Use FNV hash algorithm: http://isthe.com/chongo/tech/comp/fnv/#FNV-param
|
||||
#define FNV_PRIME_32 16777619UL
|
||||
#define FNV_BASIS_32 2166136261UL
|
||||
|
||||
// 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
|
||||
// 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.
|
||||
} irparams_t;
|
||||
|
||||
// results from a data match
|
||||
typedef struct {
|
||||
bool success; // Was the match successful?
|
||||
uint64_t data; // The data found.
|
||||
uint16_t used; // How many buffer positions were used.
|
||||
} match_result_t;
|
||||
|
||||
// Classes
|
||||
|
||||
// Results returned from the decoder
|
||||
class decode_results {
|
||||
public:
|
||||
decode_type_t decode_type; // NEC, SONY, RC5, UNKNOWN
|
||||
uint64_t value; // 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.
|
||||
bool overflow;
|
||||
bool repeat; // Is the result a repeat code?
|
||||
uint32_t address; // Decoded device address.
|
||||
uint32_t command; // Decoded command.
|
||||
};
|
||||
|
||||
// main class for receiving IR
|
||||
class IRrecv {
|
||||
public:
|
||||
explicit IRrecv(uint16_t recvpin, uint16_t bufsize = RAWBUF,
|
||||
uint8_t timeout = TIMEOUT_MS,
|
||||
bool save_buffer = false); // Constructor
|
||||
~IRrecv(); // Destructor
|
||||
bool decode(decode_results *results, irparams_t *save = NULL);
|
||||
void enableIRIn();
|
||||
void disableIRIn();
|
||||
void resume();
|
||||
uint16_t getBufSize();
|
||||
|
||||
#ifndef UNIT_TEST
|
||||
|
||||
private:
|
||||
#endif
|
||||
irparams_t *irparams_save;
|
||||
// These are called by decode
|
||||
void copyIrParams(volatile irparams_t *src, irparams_t *dst);
|
||||
int16_t compare(uint16_t oldval, uint16_t newval);
|
||||
uint32_t ticksLow(uint32_t usecs, uint8_t tolerance = TOLERANCE);
|
||||
uint32_t ticksHigh(uint32_t usecs, uint8_t tolerance = TOLERANCE);
|
||||
bool match(uint32_t measured, uint32_t desired,
|
||||
uint8_t tolerance = TOLERANCE);
|
||||
bool matchAtLeast(uint32_t measured, uint32_t desired,
|
||||
uint8_t tolerance = TOLERANCE);
|
||||
bool matchMark(uint32_t measured, uint32_t desired,
|
||||
uint8_t tolerance = TOLERANCE, int16_t excess = MARK_EXCESS);
|
||||
bool matchSpace(uint32_t measured, uint32_t desired,
|
||||
uint8_t tolerance = TOLERANCE, int16_t excess = MARK_EXCESS);
|
||||
match_result_t matchData(volatile uint16_t *data_ptr, uint16_t nbits,
|
||||
uint16_t onemark, uint32_t onespace,
|
||||
uint16_t zeromark, uint32_t zerospace);
|
||||
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 strict = true);
|
||||
#endif
|
||||
#if DECODE_SONY
|
||||
bool decodeSony(decode_results *results, uint16_t nbits = SONY_MIN_BITS,
|
||||
bool strict = false);
|
||||
#endif
|
||||
#if DECODE_SANYO
|
||||
// DISABLED due to poor quality.
|
||||
// bool decodeSanyo(decode_results *results,
|
||||
// uint16_t nbits = SANYO_SA8650B_BITS,
|
||||
// bool strict = false);
|
||||
bool decodeSanyoLC7461(decode_results *results,
|
||||
uint16_t nbits = SANYO_LC7461_BITS,
|
||||
bool strict = true);
|
||||
#endif
|
||||
#if DECODE_MITSUBISHI
|
||||
bool decodeMitsubishi(decode_results *results,
|
||||
uint16_t nbits = MITSUBISHI_BITS,
|
||||
bool strict = true);
|
||||
#endif
|
||||
#if (DECODE_RC5 || DECODE_R6)
|
||||
int16_t getRClevel(decode_results *results, uint16_t *offset, uint16_t *used,
|
||||
uint16_t bitTime);
|
||||
#endif
|
||||
#if DECODE_RC5
|
||||
bool decodeRC5(decode_results *results, uint16_t nbits = RC5X_BITS,
|
||||
bool strict = true);
|
||||
#endif
|
||||
#if DECODE_RC6
|
||||
bool decodeRC6(decode_results *results, uint16_t nbits = RC6_MODE0_BITS,
|
||||
bool strict = false);
|
||||
#endif
|
||||
#if DECODE_RCMM
|
||||
bool decodeRCMM(decode_results *results, uint16_t nbits = RCMM_BITS,
|
||||
bool strict = false);
|
||||
#endif
|
||||
#if (DECODE_PANASONIC || DECODE_DENON)
|
||||
bool decodePanasonic(decode_results *results, uint16_t nbits = PANASONIC_BITS,
|
||||
bool strict = false,
|
||||
uint32_t manufacturer = PANASONIC_MANUFACTURER);
|
||||
#endif
|
||||
#if DECODE_LG
|
||||
bool decodeLG(decode_results *results, uint16_t nbits = LG_BITS,
|
||||
bool strict = false);
|
||||
#endif
|
||||
#if DECODE_JVC
|
||||
bool decodeJVC(decode_results *results, uint16_t nbits = JVC_BITS,
|
||||
bool strict = true);
|
||||
#endif
|
||||
#if DECODE_SAMSUNG
|
||||
bool decodeSAMSUNG(decode_results *results, uint16_t nbits = SAMSUNG_BITS,
|
||||
bool strict = true);
|
||||
#endif
|
||||
#if DECODE_WHYNTER
|
||||
bool decodeWhynter(decode_results *results, uint16_t nbits = WHYNTER_BITS,
|
||||
bool strict = true);
|
||||
#endif
|
||||
#if DECODE_COOLIX
|
||||
bool decodeCOOLIX(decode_results *results, uint16_t nbits = COOLIX_BITS,
|
||||
bool strict = true);
|
||||
#endif
|
||||
#if DECODE_DENON
|
||||
bool decodeDenon(decode_results *results, uint16_t nbits = DENON_BITS,
|
||||
bool strict = true);
|
||||
#endif
|
||||
#if DECODE_DISH
|
||||
bool decodeDISH(decode_results *results, uint16_t nbits = DISH_BITS,
|
||||
bool strict = true);
|
||||
#endif
|
||||
#if (DECODE_SHARP || DECODE_DENON)
|
||||
bool decodeSharp(decode_results *results, uint16_t nbits = SHARP_BITS,
|
||||
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);
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // IRRECV_H_
|
||||
@@ -0,0 +1,243 @@
|
||||
/***************************************************
|
||||
* IRremote for ESP8266
|
||||
*
|
||||
* Based on the IRremote library for Arduino by Ken Shirriff
|
||||
* Version 0.11 August, 2009
|
||||
* Copyright 2009 Ken Shirriff
|
||||
* For details, see http://arcfn.com/2009/08/multi-protocol-infrared-remote-library.html
|
||||
*
|
||||
* Edited by Mitra to add new controller SANYO
|
||||
*
|
||||
* Interrupt code based on NECIRrcv by Joe Knapp
|
||||
* http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1210243556
|
||||
* Also influenced by http://zovirl.com/2008/11/12/building-a-universal-remote-with-an-arduino/
|
||||
*
|
||||
* JVC and Panasonic protocol added by Kristian Lauszus (Thanks to zenwheel and other people at the original blog post)
|
||||
* LG added by Darryl Smith (based on the JVC protocol)
|
||||
* Whynter A/C ARC-110WD added by Francesco Meschia
|
||||
* Coolix A/C / heatpump added by (send) bakrus & (decode) crankyoldgit
|
||||
* Denon: sendDenon, decodeDenon added by Massimiliano Pinto
|
||||
(from https://github.com/z3t0/Arduino-IRremote/blob/master/ir_Denon.cpp)
|
||||
* Kelvinator A/C and Sherwood added by crankyoldgit
|
||||
* Mitsubishi (TV) sending added by crankyoldgit
|
||||
* Pronto code sending added by crankyoldgit
|
||||
* Mitsubishi A/C added by crankyoldgit
|
||||
* (derived from https://github.com/r45635/HVAC-IR-Control)
|
||||
* DISH decode by marcosamarinho
|
||||
* Gree Heatpump sending added by Ville Skyttä (scop)
|
||||
* (derived from https://github.com/ToniA/arduino-heatpumpir/blob/master/GreeHeatpumpIR.cpp)
|
||||
* Updated by markszabo (https://github.com/markszabo/IRremoteESP8266) for sending IR code on ESP8266
|
||||
* Updated by Sebastien Warin (http://sebastien.warin.fr) for receiving IR code on ESP8266
|
||||
*
|
||||
* Updated by sillyfrog for Daikin, adopted from
|
||||
* (https://github.com/mharizanov/Daikin-AC-remote-control-over-the-Internet/)
|
||||
* Fujitsu A/C code added by jonnygraham
|
||||
* Trotec AC code by stufisher
|
||||
* GPL license, all text above must be included in any redistribution
|
||||
****************************************************/
|
||||
|
||||
#ifndef IRREMOTEESP8266_H_
|
||||
#define IRREMOTEESP8266_H_
|
||||
|
||||
#define __STDC_LIMIT_MACROS
|
||||
#include <stdint.h>
|
||||
#ifdef UNIT_TEST
|
||||
#include <iostream>
|
||||
#endif
|
||||
|
||||
// Library Version
|
||||
#define _IRREMOTEESP8266_VERSION_ "2.2.0"
|
||||
// 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!
|
||||
//
|
||||
#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_RC5 true
|
||||
#define SEND_RC5 true
|
||||
|
||||
#define DECODE_RC6 true
|
||||
#define SEND_RC6 true
|
||||
|
||||
#define DECODE_RCMM true
|
||||
#define SEND_RCMM true
|
||||
|
||||
#define DECODE_SONY true
|
||||
#define SEND_SONY true
|
||||
|
||||
#define DECODE_PANASONIC true
|
||||
#define SEND_PANASONIC true
|
||||
|
||||
#define DECODE_JVC true
|
||||
#define SEND_JVC true
|
||||
|
||||
#define DECODE_SAMSUNG true
|
||||
#define SEND_SAMSUNG true
|
||||
|
||||
#define DECODE_WHYNTER true
|
||||
#define SEND_WHYNTER true
|
||||
|
||||
#define DECODE_AIWA_RC_T501 true
|
||||
#define SEND_AIWA_RC_T501 true
|
||||
|
||||
#define DECODE_LG true
|
||||
#define SEND_LG true
|
||||
|
||||
#define DECODE_SANYO true
|
||||
#define SEND_SANYO true
|
||||
|
||||
#define DECODE_MITSUBISHI true
|
||||
#define SEND_MITSUBISHI true
|
||||
|
||||
#define DECODE_DISH true
|
||||
#define SEND_DISH true
|
||||
|
||||
#define DECODE_SHARP true
|
||||
#define SEND_SHARP true
|
||||
|
||||
#define DECODE_DENON true
|
||||
#define SEND_DENON true
|
||||
|
||||
#define DECODE_KELVINATOR false // Not written.
|
||||
#define SEND_KELVINATOR true
|
||||
|
||||
#define DECODE_MITSUBISHI_AC false // Not written.
|
||||
#define SEND_MITSUBISHI_AC true
|
||||
|
||||
#define DECODE_FUJITSU_AC false // Not written.
|
||||
#define SEND_FUJITSU_AC true
|
||||
|
||||
#define DECODE_DAIKIN false // Not finished.
|
||||
#define SEND_DAIKIN true
|
||||
|
||||
#define DECODE_COOLIX true
|
||||
#define SEND_COOLIX true
|
||||
|
||||
#define DECODE_GLOBALCACHE false // Not written.
|
||||
#define SEND_GLOBALCACHE true
|
||||
|
||||
#define DECODE_GREE false // Not written.
|
||||
#define SEND_GREE true
|
||||
|
||||
#define DECODE_PRONTO false // Not written.
|
||||
#define SEND_PRONTO true
|
||||
|
||||
#define DECODE_ARGO false // Not written.
|
||||
#define SEND_ARGO true
|
||||
|
||||
#define DECODE_TROTEC false // Not implemented.
|
||||
#define SEND_TROTEC true
|
||||
|
||||
/*
|
||||
* Always add to the end of the list and should never remove entries
|
||||
* or change order. Projects may save the type number for later usage
|
||||
* so numbering should always stay the same.
|
||||
*/
|
||||
enum decode_type_t {
|
||||
UNKNOWN = -1,
|
||||
UNUSED = 0,
|
||||
RC5,
|
||||
RC6,
|
||||
NEC,
|
||||
SONY,
|
||||
PANASONIC,
|
||||
JVC,
|
||||
SAMSUNG,
|
||||
WHYNTER,
|
||||
AIWA_RC_T501,
|
||||
LG,
|
||||
SANYO,
|
||||
MITSUBISHI,
|
||||
DISH,
|
||||
SHARP,
|
||||
COOLIX,
|
||||
DAIKIN,
|
||||
DENON,
|
||||
KELVINATOR,
|
||||
SHERWOOD,
|
||||
MITSUBISHI_AC,
|
||||
RCMM,
|
||||
SANYO_LC7461,
|
||||
RC5X,
|
||||
GREE,
|
||||
PRONTO,
|
||||
NEC_LIKE,
|
||||
ARGO,
|
||||
TROTEC
|
||||
};
|
||||
|
||||
// Message lengths & required repeat values
|
||||
#define AIWA_RC_T501_BITS 15U
|
||||
#define AIWA_RC_T501_MIN_REPEAT 1U
|
||||
#define COOLIX_BITS 24U
|
||||
#define DAIKIN_BITS 99U
|
||||
#define DAIKIN_COMMAND_LENGTH 27U
|
||||
#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 GREE_STATE_LENGTH 8U
|
||||
#define GREE_BITS (GREE_STATE_LENGTH * 8)
|
||||
#define JVC_BITS 16U
|
||||
#define KELVINATOR_STATE_LENGTH 16U
|
||||
#define LG_BITS 28U
|
||||
#define LG32_BITS 32U
|
||||
#define MITSUBISHI_BITS 16U
|
||||
// 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 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 TROTEC_COMMAND_LENGTH 9U
|
||||
#define WHYNTER_BITS 32U
|
||||
#define ARGO_COMMAND_LENGTH 12U
|
||||
|
||||
// Turn on Debugging information by uncommenting the following line.
|
||||
// #define DEBUG 1
|
||||
|
||||
#ifdef DEBUG
|
||||
#ifdef UNIT_TEST
|
||||
#define DPRINT(x) do { std::cout << x; } while (0)
|
||||
#define DPRINTLN(x) do { std::cout << x << std::endl; } while (0)
|
||||
#endif // UNIT_TEST
|
||||
#ifdef ARDUINO
|
||||
#define DPRINT(x) do { Serial.print(x); } while (0)
|
||||
#define DPRINTLN(x) do { Serial.println(x); } while (0)
|
||||
#endif // ARDUINO
|
||||
#else // DEBUG
|
||||
#define DPRINT(x)
|
||||
#define DPRINTLN(x)
|
||||
#endif // DEBUG
|
||||
|
||||
#endif // IRREMOTEESP8266_H_
|
||||
@@ -0,0 +1,327 @@
|
||||
// Copyright 2009 Ken Shirriff
|
||||
// Copyright 2015 Mark Szabo
|
||||
// Copyright 2017 David Conran
|
||||
|
||||
#include "IRsend.h"
|
||||
#ifndef UNIT_TEST
|
||||
#include <Arduino.h>
|
||||
#else
|
||||
#define __STDC_LIMIT_MACROS
|
||||
#include <stdint.h>
|
||||
#endif
|
||||
#include <algorithm>
|
||||
#ifdef UNIT_TEST
|
||||
#include <cmath>
|
||||
#endif
|
||||
#include "IRtimer.h"
|
||||
|
||||
// Originally from https://github.com/shirriff/Arduino-IRremote/
|
||||
// Updated by markszabo (https://github.com/markszabo/IRremoteESP8266) for
|
||||
// sending IR code on ESP8266
|
||||
|
||||
// IRsend ----------------------------------------------------------------------
|
||||
// Create an IRsend object.
|
||||
//
|
||||
// Args:
|
||||
// IRsendPin: Which GPIO pin to use when sending an IR command.
|
||||
// inverted: *DANGER* Optional flag to invert the output. (default = false)
|
||||
// e.g. LED is illuminated when GPIO is LOW rather than HIGH.
|
||||
// Setting this to something other than the default could
|
||||
// easily destroy your IR LED if you are overdriving it.
|
||||
// Unless you *REALLY* know what you are doing, don't change this.
|
||||
// Returns:
|
||||
// An IRsend object.
|
||||
IRsend::IRsend(uint16_t IRsendPin, bool inverted) : IRpin(IRsendPin),
|
||||
periodOffset(PERIOD_OFFSET) {
|
||||
if (inverted) {
|
||||
outputOn = LOW;
|
||||
outputOff = HIGH;
|
||||
} else {
|
||||
outputOn = HIGH;
|
||||
outputOff = LOW;
|
||||
}
|
||||
}
|
||||
|
||||
// Enable the pin for output.
|
||||
void IRsend::begin() {
|
||||
#ifndef UNIT_TEST
|
||||
pinMode(IRpin, OUTPUT);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Turn off the IR LED.
|
||||
void IRsend::ledOff() {
|
||||
#ifndef UNIT_TEST
|
||||
digitalWrite(IRpin, outputOff);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Calculate the period for a given frequency. (T = 1/f)
|
||||
//
|
||||
// Args:
|
||||
// freq: Frequency in Hz.
|
||||
// use_offset: Should we use the calculated offset or not?
|
||||
// Returns:
|
||||
// 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).
|
||||
// Apply the offset and ensure we don't result in a <= 0 value.
|
||||
if (use_offset)
|
||||
return std::max((uint32_t) 1, period + periodOffset);
|
||||
else
|
||||
return std::max((uint32_t) 1, period);
|
||||
}
|
||||
|
||||
// Set the output frequency modulation and duty cycle.
|
||||
//
|
||||
// Args:
|
||||
// freq: The freq we want to modulate at. Assumes < 1000 means kHz else Hz.
|
||||
// duty: Percentage duty cycle of the LED. e.g. 25 = 25% = 1/4 on, 3/4 off.
|
||||
//
|
||||
// Note:
|
||||
// Integer timing functions & math mean we can't do fractions of
|
||||
// microseconds timing. Thus minor changes to the freq & duty values may have
|
||||
// limited effect. You've been warned.
|
||||
void IRsend::enableIROut(uint32_t freq, uint8_t duty) {
|
||||
// Can't have more than 100% duty cycle.
|
||||
duty = std::min(duty, (uint8_t) 100);
|
||||
if (freq < 1000) // Were we given kHz? Supports the old call usage.
|
||||
freq *= 1000;
|
||||
uint32_t period = calcUSecPeriod(freq);
|
||||
// Nr. of uSeconds the LED will be on per pulse.
|
||||
onTimePeriod = (period * duty) / 100;
|
||||
// Nr. of uSeconds the LED will be off per pulse.
|
||||
offTimePeriod = period - onTimePeriod;
|
||||
}
|
||||
|
||||
// Modulate the IR LED for the given period (usec) and at the duty cycle set.
|
||||
//
|
||||
// Args:
|
||||
// usec: The period of time to modulate the IR LED for, in microseconds.
|
||||
// Returns:
|
||||
// Nr. of pulses actually sent.
|
||||
//
|
||||
// Note:
|
||||
// The ESP8266 has no good way to do hardware PWM, so we have to do it all
|
||||
// in software. There is a horrible kludge/brilliant hack to use the second
|
||||
// serial TX line to do fairly accurate hardware PWM, but it is only
|
||||
// available on a single specific GPIO and only available on some modules.
|
||||
// e.g. It's not available on the ESP-01 module.
|
||||
// Hence, for greater compatibility & choice, we don't use that method.
|
||||
// Ref:
|
||||
// https://www.analysir.com/blog/2017/01/29/updated-esp8266-nodemcu-backdoor-upwm-hack-for-ir-signals/
|
||||
uint16_t IRsend::mark(uint16_t usec) {
|
||||
uint16_t counter = 0;
|
||||
IRtimer usecTimer = IRtimer();
|
||||
// Cache the time taken so far. This saves us calling time, and we can be
|
||||
// assured that we can't have odd math problems. i.e. unsigned under/overflow.
|
||||
uint32_t elapsed = usecTimer.elapsed();
|
||||
|
||||
while (elapsed < usec) { // Loop until we've met/exceeded our required time.
|
||||
#ifndef UNIT_TEST
|
||||
digitalWrite(IRpin, outputOn); // Turn the LED on.
|
||||
// Calculate how long we should pulse on for.
|
||||
// e.g. Are we to close to the end of our requested mark time (usec)?
|
||||
delayMicroseconds(std::min((uint32_t) onTimePeriod, usec - elapsed));
|
||||
digitalWrite(IRpin, outputOff); // Turn the LED off.
|
||||
#endif
|
||||
counter++;
|
||||
if (elapsed + onTimePeriod >= usec)
|
||||
return counter; // LED is now off & we've passed our allotted time.
|
||||
// Wait for the lesser of the rest of the duty cycle, or the time remaining.
|
||||
#ifndef UNIT_TEST
|
||||
delayMicroseconds(std::min(usec - elapsed - onTimePeriod,
|
||||
(uint32_t) offTimePeriod));
|
||||
#endif
|
||||
elapsed = usecTimer.elapsed(); // Update & recache the actual elapsed time.
|
||||
}
|
||||
return counter;
|
||||
}
|
||||
|
||||
// Turn the pin (LED) off for a given time.
|
||||
// Sends an IR space for the specified number of microseconds.
|
||||
// A space is no output, so the PWM output is disabled.
|
||||
//
|
||||
// Args:
|
||||
// time: Time in microseconds (us).
|
||||
void IRsend::space(uint32_t time) {
|
||||
ledOff();
|
||||
if (time == 0) return;
|
||||
#ifndef UNIT_TEST
|
||||
// delayMicroseconds is only accurate to 16383us.
|
||||
// Ref: https://www.arduino.cc/en/Reference/delayMicroseconds
|
||||
if (time <= 16383) {
|
||||
delayMicroseconds(time);
|
||||
} else {
|
||||
// Invoke a delay(), where possible, to avoid triggering the WDT.
|
||||
delay(time / 1000UL); // Delay for as many whole milliseconds as we can.
|
||||
// Delay the remaining sub-millisecond.
|
||||
delayMicroseconds(static_cast<uint16_t>(time % 1000UL));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Calculate & set any offsets to account for execution times.
|
||||
//
|
||||
// Args:
|
||||
// hz: The frequency to calibrate at >= 1000Hz. Default is 38000Hz.
|
||||
//
|
||||
// Status: ALPHA / Untested.
|
||||
//
|
||||
// NOTE:
|
||||
// This will generate an 65535us mark() IR LED signal.
|
||||
// This only needs to be called once, if at all.
|
||||
void IRsend::calibrate(uint16_t hz) {
|
||||
if (hz < 1000) // Were we given kHz? Supports the old call usage.
|
||||
hz *= 1000;
|
||||
periodOffset = 0; // Turn off any existing offset while we calibrate.
|
||||
enableIROut(hz);
|
||||
IRtimer usecTimer = IRtimer(); // Start a timer *just* before we do the call.
|
||||
uint16_t pulses = mark(UINT16_MAX); // Generate a PWM of 65,535 us. (Max.)
|
||||
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);
|
||||
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.
|
||||
// e.g. 65535.0us / 26us = 2520.5769
|
||||
// This should have caused approx 2520 loops through the main loop in mark().
|
||||
// The average over that many interations should give us a reasonable
|
||||
// approximation at what offset we need to use to account for instruction
|
||||
// execution times.
|
||||
//
|
||||
// Calculate the actual period from the actual time & the actual pulses
|
||||
// generated.
|
||||
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);
|
||||
}
|
||||
|
||||
// Generic method for sending data that is common to most protocols.
|
||||
// Will send leading or trailing 0's if the nbits is larger than the number
|
||||
// of bits in data.
|
||||
//
|
||||
// Args:
|
||||
// onemark: Nr. of usecs for the led to be pulsed for a '1' bit.
|
||||
// onespace: Nr. of usecs for the led to be fully off for a '1' bit.
|
||||
// zeromark: Nr. of usecs for the led to be pulsed for a '0' bit.
|
||||
// zerospace: Nr. of usecs for the led to be fully off for a '0' bit.
|
||||
// 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) {
|
||||
if (nbits == 0) // If we are asked to send nothing, just return.
|
||||
return;
|
||||
if (MSBfirst) { // Send the MSB first.
|
||||
// Send 0's until we get down to a bit size we can actually manage.
|
||||
while (nbits > sizeof(data) * 8) {
|
||||
mark(zeromark);
|
||||
space(zerospace);
|
||||
nbits--;
|
||||
}
|
||||
// Send the supplied data.
|
||||
for (uint64_t mask = 1ULL << (nbits - 1); mask; mask >>= 1)
|
||||
if (data & mask) { // Send a 1
|
||||
mark(onemark);
|
||||
space(onespace);
|
||||
} else { // Send a 0
|
||||
mark(zeromark);
|
||||
space(zerospace);
|
||||
}
|
||||
} else { // Send the Least Significant Bit (LSB) first / MSB last.
|
||||
for (uint16_t bit = 0; bit < nbits; bit++, data >>= 1)
|
||||
if (data & 1) { // Send a 1
|
||||
mark(onemark);
|
||||
space(onespace);
|
||||
} else { // Send a 0
|
||||
mark(zeromark);
|
||||
space(zerospace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Send a raw IRremote message.
|
||||
//
|
||||
// Args:
|
||||
// buf: An array of uint16_t's that has microseconds elements.
|
||||
// len: Nr. of elements in the buf[] array.
|
||||
// hz: Frequency to send the message at. (kHz < 1000; Hz >= 1000)
|
||||
//
|
||||
// Status: STABLE / Known working.
|
||||
//
|
||||
// Notes:
|
||||
// Even elements are Mark times (On), Odd elements are Space times (Off).
|
||||
//
|
||||
// Ref:
|
||||
// examples/IRrecvDumpV2/IRrecvDumpV2.ino
|
||||
void IRsend::sendRaw(uint16_t buf[], uint16_t len, uint16_t hz) {
|
||||
// Set IR carrier frequency
|
||||
enableIROut(hz);
|
||||
for (uint16_t i = 0; i < len; i++) {
|
||||
if (i & 1) { // Odd bit.
|
||||
space(buf[i]);
|
||||
} else { // Even bit.
|
||||
mark(buf[i]);
|
||||
}
|
||||
}
|
||||
ledOff(); // We potentially have ended with a mark(), so turn of the LED.
|
||||
}
|
||||
|
||||
#ifndef UNIT_TEST
|
||||
void IRsend::send(uint16_t type, uint64_t data, uint16_t nbits) {
|
||||
switch (type) {
|
||||
#if SEND_NEC
|
||||
case NEC: sendNEC(data, nbits); break;
|
||||
#endif
|
||||
#if SEND_SONY
|
||||
case SONY: sendSony(data, nbits); break;
|
||||
#endif
|
||||
#if SEND_RC5
|
||||
case RC5: sendRC5(data, nbits); break;
|
||||
#endif
|
||||
#if SEND_RC6
|
||||
case RC6: sendRC6(data, nbits); break;
|
||||
#endif
|
||||
#if SEND_DISH
|
||||
case DISH: sendDISH(data, nbits); break;
|
||||
#endif
|
||||
#if SEND_JVC
|
||||
case JVC: sendJVC(data, nbits); break;
|
||||
#endif
|
||||
#if SEND_SAMSUNG
|
||||
case SAMSUNG: sendSAMSUNG(data, nbits); break;
|
||||
#endif
|
||||
#if SEND_LG
|
||||
case LG: sendLG(data, nbits); break;
|
||||
#endif
|
||||
#if SEND_WHYNTER
|
||||
case WHYNTER: sendWhynter(data, nbits); break;
|
||||
#endif
|
||||
#if SEND_COOLIX
|
||||
case COOLIX: sendCOOLIX(data, nbits); break;
|
||||
#endif
|
||||
#if SEND_DENON
|
||||
case DENON: sendDenon(data, nbits); break;
|
||||
#endif
|
||||
#if SEND_SHERWOOD
|
||||
case SHERWOOD: sendSherwood(data, nbits); break;
|
||||
#endif
|
||||
#if SEND_RCMM
|
||||
case RCMM: sendRCMM(data, nbits); break;
|
||||
#endif
|
||||
#if SEND_MITSUBISHI
|
||||
case MITSUBISHI: sendMitsubishi(data, nbits); break;
|
||||
#endif
|
||||
#if SEND_SHARP
|
||||
case SHARP: sendSharpRaw(data, nbits); break;
|
||||
#endif
|
||||
#if SEND_AIWA_RC_T501
|
||||
case AIWA_RC_T501: sendAiwaRCT501(data, nbits); break;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,206 @@
|
||||
// Copyright 2009 Ken Shirriff
|
||||
// Copyright 2015 Mark Szabo
|
||||
// Copyright 2017 David Conran
|
||||
#ifndef IRSEND_H_
|
||||
#define IRSEND_H_
|
||||
|
||||
#define __STDC_LIMIT_MACROS
|
||||
#include <stdint.h>
|
||||
#include "IRremoteESP8266.h"
|
||||
|
||||
// Originally from https://github.com/shirriff/Arduino-IRremote/
|
||||
// Updated by markszabo (https://github.com/markszabo/IRremoteESP8266) for
|
||||
// sending IR code on ESP8266
|
||||
|
||||
#if TEST || UNIT_TEST
|
||||
#define VIRTUAL virtual
|
||||
#else
|
||||
#define VIRTUAL
|
||||
#endif
|
||||
|
||||
// Constants
|
||||
// Offset (in microseconds) to use in Period time calculations to account for
|
||||
// code excution time in producing the software PWM signal.
|
||||
// Value determined in https://github.com/markszabo/IRremoteESP8266/issues/62
|
||||
#define PERIOD_OFFSET -3
|
||||
#define DUTY_DEFAULT 50
|
||||
|
||||
// Classes
|
||||
class IRsend {
|
||||
public:
|
||||
explicit IRsend(uint16_t IRsendPin, bool inverted = false);
|
||||
void begin();
|
||||
void enableIROut(uint32_t freq, uint8_t duty = DUTY_DEFAULT);
|
||||
VIRTUAL uint16_t mark(uint16_t usec);
|
||||
VIRTUAL void space(uint32_t usec);
|
||||
void calibrate(uint16_t hz = 38000U);
|
||||
void sendRaw(uint16_t buf[], uint16_t len, uint16_t hz);
|
||||
void sendData(uint16_t onemark, uint32_t onespace, uint16_t zeromark,
|
||||
uint32_t zerospace, uint64_t data, uint16_t nbits,
|
||||
bool MSBfirst = true);
|
||||
void send(uint16_t type, uint64_t data, uint16_t nbits);
|
||||
#if (SEND_NEC || SEND_SHERWOOD || SEND_AIWA_RC_T501 || SEND_SANYO)
|
||||
void sendNEC(uint64_t data, uint16_t nbits = NEC_BITS, uint16_t repeat = 0);
|
||||
uint32_t encodeNEC(uint16_t address, uint16_t command);
|
||||
#endif
|
||||
#if SEND_SONY
|
||||
// sendSony() should typically be called with repeat=2 as Sony devices
|
||||
// expect the code to be sent at least 3 times. (code + 2 repeats = 3 codes)
|
||||
// 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);
|
||||
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);
|
||||
#endif
|
||||
#if SEND_SAMSUNG
|
||||
void sendSAMSUNG(uint64_t data, uint16_t nbits = SAMSUNG_BITS,
|
||||
uint16_t repeat = 0);
|
||||
uint32_t encodeSAMSUNG(uint8_t customer, uint8_t command);
|
||||
#endif
|
||||
#if SEND_LG
|
||||
void sendLG(uint64_t data, uint16_t nbits = LG_BITS, uint16_t repeat = 0);
|
||||
uint32_t encodeLG(uint16_t address, uint16_t command);
|
||||
#endif
|
||||
#if (SEND_SHARP || SEND_DENON)
|
||||
uint32_t encodeSharp(uint16_t address, uint16_t command,
|
||||
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);
|
||||
#endif
|
||||
#if SEND_JVC
|
||||
void sendJVC(uint64_t data, uint16_t nbits = JVC_BITS, uint16_t repeat = 0);
|
||||
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);
|
||||
#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);
|
||||
#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);
|
||||
#endif
|
||||
#if (SEND_PANASONIC || SEND_DENON)
|
||||
void sendPanasonic64(uint64_t data, uint16_t nbits = PANASONIC_BITS,
|
||||
uint16_t repeat = 0);
|
||||
void sendPanasonic(uint16_t address, uint32_t data,
|
||||
uint16_t nbits = PANASONIC_BITS, uint16_t repeat = 0);
|
||||
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);
|
||||
uint16_t encodeRC5(uint8_t address, uint8_t command,
|
||||
bool key_released = false);
|
||||
uint16_t encodeRC5X(uint8_t address, uint8_t command,
|
||||
bool key_released = false);
|
||||
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);
|
||||
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);
|
||||
#endif
|
||||
#if SEND_RCMM
|
||||
void sendRCMM(uint64_t data, uint16_t nbits = RCMM_BITS, uint16_t repeat = 0);
|
||||
#endif
|
||||
#if SEND_COOLIX
|
||||
void sendCOOLIX(uint64_t data, uint16_t nbits = COOLIX_BITS,
|
||||
uint16_t repeat = 0);
|
||||
#endif
|
||||
#if SEND_WHYNTER
|
||||
void sendWhynter(uint64_t data, uint16_t nbits = WHYNTER_BITS,
|
||||
uint16_t repeat = 0);
|
||||
#endif
|
||||
#if SEND_MITSUBISHI
|
||||
void sendMitsubishi(uint64_t data, uint16_t nbits = MITSUBISHI_BITS,
|
||||
uint16_t repeat = MITSUBISHI_MIN_REPEAT);
|
||||
#endif
|
||||
#if SEND_MITSUBISHI_AC
|
||||
void sendMitsubishiAC(unsigned char data[],
|
||||
uint16_t nbytes = MITSUBISHI_AC_STATE_LENGTH,
|
||||
uint16_t repeat = MITSUBISHI_AC_MIN_REPEAT);
|
||||
#endif
|
||||
#if SEND_FUJITSU_AC
|
||||
void sendFujitsuAC(unsigned char data[],
|
||||
uint16_t nbytes,
|
||||
uint16_t repeat = FUJITSU_AC_MIN_REPEAT);
|
||||
#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);
|
||||
#endif
|
||||
#if SEND_DAIKIN
|
||||
void sendDaikin(unsigned char data[],
|
||||
uint16_t nbytes = DAIKIN_COMMAND_LENGTH,
|
||||
uint16_t repeat = 0);
|
||||
#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);
|
||||
#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);
|
||||
#endif
|
||||
#if SEND_PRONTO
|
||||
void sendPronto(uint16_t data[], uint16_t len, uint16_t repeat = 0);
|
||||
#endif
|
||||
#if SEND_ARGO
|
||||
void sendArgo(unsigned char data[],
|
||||
uint16_t nbytes = ARGO_COMMAND_LENGTH,
|
||||
uint16_t repeat = 0);
|
||||
#endif
|
||||
|
||||
#if SEND_TROTEC
|
||||
void sendTrotec(unsigned char data[],
|
||||
uint16_t nbytes = TROTEC_COMMAND_LENGTH,
|
||||
uint16_t repeat = 0);
|
||||
#endif
|
||||
|
||||
protected:
|
||||
#ifdef UNIT_TEST
|
||||
#ifndef HIGH
|
||||
#define HIGH 0x1
|
||||
#endif
|
||||
#ifndef LOW
|
||||
#define LOW 0x0
|
||||
#endif
|
||||
#endif // UNIT_TEST
|
||||
uint8_t outputOn;
|
||||
uint8_t outputOff;
|
||||
|
||||
private:
|
||||
uint16_t onTimePeriod;
|
||||
uint16_t offTimePeriod;
|
||||
uint16_t IRpin;
|
||||
int8_t periodOffset;
|
||||
void ledOff();
|
||||
uint32_t calcUSecPeriod(uint32_t hz, bool use_offset = true);
|
||||
};
|
||||
|
||||
#endif // IRSEND_H_
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2017 David Conran
|
||||
|
||||
#include "IRtimer.h"
|
||||
#ifndef UNIT_TEST
|
||||
#include <Arduino.h>
|
||||
#endif
|
||||
|
||||
// This class performs a simple time in useconds since instantiated.
|
||||
// Handles when the system timer wraps around (once).
|
||||
|
||||
IRtimer::IRtimer() {
|
||||
reset();
|
||||
}
|
||||
|
||||
void IRtimer::reset() {
|
||||
#ifndef UNIT_TEST
|
||||
start = micros();
|
||||
#else
|
||||
start = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
uint32_t IRtimer::elapsed() {
|
||||
#ifndef UNIT_TEST
|
||||
uint32_t now = micros();
|
||||
#else
|
||||
uint32_t now = 0;
|
||||
#endif
|
||||
if (start <= now) // Check if the system timer has wrapped.
|
||||
return now - start; // No wrap.
|
||||
else
|
||||
return UINT32_MAX - start + now; // Has wrapped.
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright 2017 David Conran
|
||||
|
||||
#ifndef IRTIMER_H_
|
||||
#define IRTIMER_H_
|
||||
|
||||
#define __STDC_LIMIT_MACROS
|
||||
#include <stdint.h>
|
||||
|
||||
// Classes
|
||||
class IRtimer {
|
||||
public:
|
||||
IRtimer();
|
||||
void reset();
|
||||
uint32_t elapsed();
|
||||
|
||||
private:
|
||||
uint32_t start;
|
||||
};
|
||||
|
||||
#endif // IRTIMER_H_
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright 2017 David Conran
|
||||
|
||||
#include "IRutils.h"
|
||||
#ifndef UNIT_TEST
|
||||
#include <Arduino.h>
|
||||
#endif
|
||||
#define __STDC_LIMIT_MACROS
|
||||
#include <stdint.h>
|
||||
#include <algorithm>
|
||||
#include "IRrecv.h"
|
||||
|
||||
// Reverse the order of the requested least significant nr. of bits.
|
||||
// Args:
|
||||
// input: Bit pattern/integer to reverse.
|
||||
// nbits: Nr. of bits to reverse.
|
||||
// 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.
|
||||
// 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));
|
||||
uint64_t output = 0;
|
||||
for (uint16_t i = 0; i < nbits; i++) {
|
||||
output <<= 1;
|
||||
output |= (input & 1);
|
||||
input >>= 1;
|
||||
}
|
||||
// Merge any remaining unreversed bits back to the top of the reversed bits.
|
||||
return (input << nbits) | output;
|
||||
}
|
||||
|
||||
// Print a uint64_t/unsigned long long to the Serial port
|
||||
// Serial.print() can't handle printing long longs. (uint64_t)
|
||||
//
|
||||
// Args:
|
||||
// input: The value to print
|
||||
// base: The output base.
|
||||
// Note: Based on Arduino's Print::printNumber()
|
||||
void serialPrintUint64(uint64_t input, uint8_t base) {
|
||||
char buf[8 * sizeof(input) + 1]; // Assumes 8-bit chars plus zero byte.
|
||||
char *str = &buf[sizeof(buf) - 1];
|
||||
|
||||
*str = '\0';
|
||||
|
||||
// prevent crash if called with base == 1
|
||||
if (base < 2) base = 10;
|
||||
|
||||
do {
|
||||
char c = input % base;
|
||||
input /= base;
|
||||
|
||||
*--str = c < 10 ? c + '0' : c + 'A' - 10;
|
||||
} while (input);
|
||||
|
||||
#ifndef UNIT_TEST
|
||||
Serial.print(str);
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#ifndef IRUTILS_H_
|
||||
#define IRUTILS_H_
|
||||
|
||||
// Copyright 2017 David Conran
|
||||
|
||||
#define __STDC_LIMIT_MACROS
|
||||
#include <stdint.h>
|
||||
|
||||
uint64_t reverseBits(uint64_t input, uint16_t nbits);
|
||||
void serialPrintUint64(uint64_t input, uint8_t base);
|
||||
|
||||
#endif // IRUTILS_H_
|
||||
@@ -0,0 +1,118 @@
|
||||
// Copyright 2017 David Conran
|
||||
|
||||
#include "IRrecv.h"
|
||||
#include "IRsend.h"
|
||||
#include "IRtimer.h"
|
||||
|
||||
// AAA IIIII W W AAA
|
||||
// A A I W W A A
|
||||
// AAAAA I W W W AAAAA
|
||||
// A A I W W W A A
|
||||
// A A IIIII WWW A A
|
||||
|
||||
// Based off the RC-T501 RCU
|
||||
// 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
|
||||
// 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
|
||||
|
||||
#if SEND_AIWA_RC_T501
|
||||
// Send a Aiwa RC T501 formatted message.
|
||||
//
|
||||
// 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)
|
||||
// repeat: The number of times the command is to be repeated.
|
||||
//
|
||||
// Status: BETA / Should work.
|
||||
//
|
||||
// Ref:
|
||||
// http://lirc.sourceforge.net/remotes/aiwa/RC-T501
|
||||
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;
|
||||
if (nbits > sizeof(new_data) * 8)
|
||||
return; // We are overflowing. Abort, and don't send.
|
||||
sendNEC(new_data, nbits, repeat);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if DECODE_AIWA_RC_T501
|
||||
// Decode the supplied Aiwa RC T501 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 AIWA_RC_T501_BITS.
|
||||
// strict: Flag indicating if we should perform strict matching.
|
||||
// Returns:
|
||||
// boolean: True if it can decode it, false if it can't.
|
||||
//
|
||||
// Status: BETA / Should work.
|
||||
//
|
||||
// Notes:
|
||||
// Aiwa RC T501 appears to be a 42 bit variant of the NEC1 protocol.
|
||||
// However, we historically (original Arduino IRremote project) treats it as
|
||||
// a 15 bit (data) protocol. So, we expect nbits to typically be 15, and we
|
||||
// will remove the prefix and postfix from the raw data, and use that as
|
||||
// the result.
|
||||
//
|
||||
// Ref:
|
||||
// http://www.sbprojects.com/knowledge/ir/nec.php
|
||||
bool IRrecv::decodeAiwaRCT501(decode_results *results, uint16_t nbits,
|
||||
bool strict) {
|
||||
// Compliance
|
||||
if (strict && nbits != AIWA_RC_T501_BITS)
|
||||
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;
|
||||
uint64_t new_data;
|
||||
if (expected_nbits > sizeof(new_data) * 8)
|
||||
return false; // We can't possibly match something that big.
|
||||
// Decode it as a much bigger (non-standard) NEC message, so we have to turn
|
||||
// off strict mode checking for NEC.
|
||||
if (!decodeNEC(results, expected_nbits, false))
|
||||
return false; // The NEC decode had a problem, so we should too.
|
||||
uint16_t actual_bits = results->bits;
|
||||
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)
|
||||
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;
|
||||
|
||||
// Extract out our likely new value and put it back in the results.
|
||||
actual_bits -= AIWA_RC_T501_PRE_BITS;
|
||||
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.
|
||||
return false;
|
||||
|
||||
// Compliance
|
||||
if (strict && results->bits != expected_nbits)
|
||||
return false;
|
||||
|
||||
// Success
|
||||
results->decode_type = AIWA_RC_T501;
|
||||
results->bits = actual_bits;
|
||||
results->address = 0;
|
||||
results->command = 0;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,264 @@
|
||||
/*
|
||||
Node MCU/ESP8266 Sketch to emulate Argo Ulisse 13 DCI remote
|
||||
Controls Argo Ulisse 13 DCI A/C
|
||||
Copyright 2017 Schmolders
|
||||
*/
|
||||
|
||||
#include "ir_Argo.h"
|
||||
#include <algorithm>
|
||||
#include "IRremoteESP8266.h"
|
||||
#include "IRutils.h"
|
||||
|
||||
// Constants
|
||||
// using SPACE modulation. MARK is always const 400u
|
||||
#define ARGO_PREAMBLE_1 6400U // Mark
|
||||
#define ARGO_PREAMBLE_2 3300U // Space
|
||||
#define ARGO_MARK 400U
|
||||
#define ARGO_ONE_SPACE 2200U
|
||||
#define ARGO_ZERO_SPACE 900U
|
||||
|
||||
#if SEND_ARGO
|
||||
// Send a Argo A/C message.
|
||||
//
|
||||
// Args:
|
||||
// data: An array of ARGO_COMMAND_LENGTH bytes containing the IR command.
|
||||
//
|
||||
// Status: ALPHA / Untested.
|
||||
//
|
||||
// Overloading the IRSend Function
|
||||
|
||||
void IRsend::sendArgo(unsigned char data[], uint16_t nbytes, uint16_t repeat) {
|
||||
// Check if we have enough bytes to send a proper message.
|
||||
if (nbytes < ARGO_COMMAND_LENGTH) return;
|
||||
// Set IR carrier frequency
|
||||
enableIROut(38);
|
||||
for (uint16_t r = 0; r <= repeat; r++) {
|
||||
// Header
|
||||
// TODO(kaschmo): validate
|
||||
mark(ARGO_PREAMBLE_1);
|
||||
space(ARGO_PREAMBLE_2);
|
||||
// send data, defined in IRSend.cpp
|
||||
for (uint16_t i = 0; i < nbytes; i++)
|
||||
sendData(ARGO_MARK, ARGO_ONE_SPACE, ARGO_MARK,
|
||||
ARGO_ZERO_SPACE, data[i], 8, false);
|
||||
// send LSB first reverses the bit order in array for sending.
|
||||
}
|
||||
}
|
||||
|
||||
IRArgoAC::IRArgoAC(uint16_t pin) : _irsend(pin) {
|
||||
stateReset();
|
||||
}
|
||||
|
||||
void IRArgoAC::begin() {
|
||||
_irsend.begin();
|
||||
}
|
||||
|
||||
void IRArgoAC::send() {
|
||||
// Serial.println("Sending IR code"); // Only for Debug
|
||||
checksum(); // Create valid checksum before sending
|
||||
_irsend.sendArgo(argo);
|
||||
}
|
||||
|
||||
void IRArgoAC::checksum() {
|
||||
uint8_t sum = 2; // Corresponds to byte 11 being constant 0b01
|
||||
uint8_t i;
|
||||
|
||||
// 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];
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
void IRArgoAC::stateReset() {
|
||||
for (uint8_t i = 0; i < ARGO_COMMAND_LENGTH; i++)
|
||||
argo[i] = 0x0;
|
||||
|
||||
// Argo Message. Store MSB left.
|
||||
// Default message:
|
||||
argo[0] = 0b10101100; // LSB first (as sent) 0b00110101; //const preamble
|
||||
argo[1] = 0b11110101; // LSB first: 0b10101111; //const preamble
|
||||
// Keep payload 2-9 at zero
|
||||
argo[10] = 0b00000010; // Const 01, checksum 6bit
|
||||
argo[11] = 0b00000000; // Checksum 2bit
|
||||
|
||||
this->off();
|
||||
this->setTemp(20);
|
||||
this->setRoomTemp(25);
|
||||
this->setCoolMode(ARGO_COOL_AUTO);
|
||||
this->setFan(ARGO_FAN_AUTO);
|
||||
}
|
||||
|
||||
uint8_t* IRArgoAC::getRaw() {
|
||||
checksum(); // Ensure correct bit array before returning
|
||||
return argo;
|
||||
}
|
||||
|
||||
void IRArgoAC::on() {
|
||||
// state = ON;
|
||||
ac_state = 1;
|
||||
// Bit 5 of byte 9 is on/off
|
||||
// in MSB first
|
||||
argo[9] = argo[9] | 0b00100000; // Set ON/OFF bit to 1
|
||||
}
|
||||
|
||||
void IRArgoAC::off() {
|
||||
// state = OFF;
|
||||
ac_state = 0;
|
||||
// in MSB first
|
||||
// bit 5 of byte 9 to off
|
||||
argo[9] = argo[9] & 0b11011111; // Set on/off bit to 0
|
||||
}
|
||||
|
||||
void IRArgoAC::setPower(bool state) {
|
||||
if (state)
|
||||
on();
|
||||
else
|
||||
off();
|
||||
}
|
||||
|
||||
uint8_t IRArgoAC::getPower() {
|
||||
return ac_state;
|
||||
}
|
||||
|
||||
void IRArgoAC::setMax(bool state) {
|
||||
max_mode = state;
|
||||
if (max_mode)
|
||||
argo[9] |= 0b00001000;
|
||||
else
|
||||
argo[9] &= 0b11110111;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
// Store in attributes
|
||||
set_temp = temp;
|
||||
// offset 4 degrees. "If I want 12 degrees, I need to send 8"
|
||||
temp -= 4;
|
||||
// Settemp = Bit 6,7 of byte 2, and bit 0-2 of byte 3
|
||||
// mask out bits
|
||||
// argo[13] & 0x00000100; // mask out ON/OFF Bit
|
||||
argo[2] &= 0b00111111;
|
||||
argo[3] &= 0b11111000;
|
||||
|
||||
argo[2] += temp << 6; // append to bit 6,7
|
||||
argo[3] += temp >> 2; // remove lowest to bits and append in 0-2
|
||||
}
|
||||
|
||||
uint8_t IRArgoAC::getTemp() {
|
||||
return set_temp;
|
||||
}
|
||||
|
||||
// Set the speed of the fan
|
||||
void IRArgoAC::setFan(uint8_t fan) {
|
||||
// Set the fan speed bits, leave low 4 bits alone
|
||||
fan_mode = fan;
|
||||
// Mask out bits
|
||||
argo[3] &= 0b11100111;
|
||||
// Set fan mode at bit positions
|
||||
argo[3] += fan << 3;
|
||||
}
|
||||
|
||||
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::getMode() {
|
||||
// return cooling 0, heating 1
|
||||
return ac_mode;
|
||||
}
|
||||
|
||||
void IRArgoAC::setCoolMode(uint8_t mode) {
|
||||
ac_mode = 0; // Set ac mode to cooling
|
||||
cool_mode = mode;
|
||||
// Mask out bits, also leave bit 5 on 0 for cooling
|
||||
argo[2] &= 0b11000111;
|
||||
|
||||
// Set cool mode at bit positions
|
||||
argo[2] += mode << 3;
|
||||
}
|
||||
|
||||
uint8_t IRArgoAC::getCoolMode() {
|
||||
return cool_mode;
|
||||
}
|
||||
|
||||
void IRArgoAC::setHeatMode(uint8_t mode) {
|
||||
ac_mode = 1; // Set ac mode to heating
|
||||
heat_mode = mode;
|
||||
// Mask out bits
|
||||
argo[2] &= 0b11000111;
|
||||
// Set heating bit
|
||||
argo[2] |= 0b00100000;
|
||||
// Set cool mode at bit positions
|
||||
argo[2] += mode << 3;
|
||||
}
|
||||
|
||||
uint8_t IRArgoAC::getHeatMode() {
|
||||
return heat_mode;
|
||||
}
|
||||
|
||||
void IRArgoAC::setNight(bool state) {
|
||||
night_mode = state;
|
||||
if (night_mode)
|
||||
// Set bit at night position: bit 2
|
||||
argo[9] |= 0b00000100;
|
||||
else
|
||||
argo[9] &= 0b11111011;
|
||||
}
|
||||
|
||||
bool IRArgoAC::getNight() {
|
||||
return night_mode;
|
||||
}
|
||||
|
||||
void IRArgoAC::setiFeel(bool state) {
|
||||
ifeel_mode = state;
|
||||
if (ifeel_mode)
|
||||
// Set bit at iFeel position: bit 7
|
||||
argo[9] |= 0b10000000;
|
||||
else
|
||||
argo[9] &= 0b01111111;
|
||||
}
|
||||
|
||||
bool IRArgoAC::getiFeel() {
|
||||
return ifeel_mode;
|
||||
}
|
||||
|
||||
void IRArgoAC::setTime() {
|
||||
// TODO(kaschmo): use function call from checksum to set time first
|
||||
}
|
||||
|
||||
void IRArgoAC::setRoomTemp(uint8_t temp) {
|
||||
temp -= 4;
|
||||
// Mask out bits
|
||||
argo[3] &= 0b00011111;
|
||||
argo[4] &= 0b11111100;
|
||||
|
||||
argo[3] += temp << 5; // Append to bit 5,6,7
|
||||
argo[4] += temp >> 3; // Remove lowest 3 bits and append in 0,1
|
||||
}
|
||||
#endif // SEND_ARGO
|
||||
@@ -0,0 +1,124 @@
|
||||
/* Copyright 2017 Schmolders
|
||||
// Adds support for Argo Ulisse 13 DCI Mobile Split ACs.
|
||||
*/
|
||||
#ifndef IR_ARGO_H_
|
||||
#define IR_ARGO_H_
|
||||
|
||||
#include "IRremoteESP8266.h"
|
||||
#include "IRsend.h"
|
||||
|
||||
// ARGO Ulisse DCI
|
||||
|
||||
/*
|
||||
Protocol Description:
|
||||
All in LSB first as it is sent. argo message array will be stored MSB first!
|
||||
do LSB-MSB conversion in sendData
|
||||
Byte 0: const 0 0 1 1 0 1 0 1
|
||||
Byte 1: const 1 0 1 0 1 1 1 1
|
||||
Byte 2: 0 0 0, 3bit Cool/Heat Mode, 2bit start SetTemp LSB first
|
||||
Byte 3: 3bit End SetTemp, 2bit Fan Mode, 3bit RoomTemp LSB first
|
||||
Byte 4: 2bit RoomTemp, 3bit Flap Mode, 3bit OnTimer
|
||||
Byte 5: 8bit OnTimer
|
||||
Byte 6: 8Bit OffTimer
|
||||
Byte 7: 3bit OffTimer, 5bit Time
|
||||
Byte 8: 6bit Time, 1bit Timer On/Off, 1bit Timer Program
|
||||
Byte 9: 1bit Timer Program, 1bit Timer 1h, 1 bit Night Mode, 1bit Max Mode, 1bit Filter, 1bit on/off, 1bit const 0, 1bit iFeel
|
||||
Byte 10: 2bit const 0 1, 6bit Checksum
|
||||
Byte 11: 2bit Checksum
|
||||
*/
|
||||
|
||||
// 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
|
||||
|
||||
#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
|
||||
|
||||
|
||||
#if SEND_ARGO
|
||||
class IRArgoAC {
|
||||
public:
|
||||
explicit IRArgoAC(uint16_t pin);
|
||||
|
||||
void send();
|
||||
void begin();
|
||||
void on();
|
||||
void off();
|
||||
|
||||
void setPower(bool state);
|
||||
uint8_t getPower();
|
||||
|
||||
void setTemp(uint8_t temp);
|
||||
uint8_t getTemp();
|
||||
|
||||
void setFan(uint8_t fan);
|
||||
uint8_t getFan();
|
||||
|
||||
void setFlap(uint8_t flap);
|
||||
uint8_t getFlap();
|
||||
|
||||
void setCoolMode(uint8_t mode);
|
||||
uint8_t getCoolMode();
|
||||
|
||||
void setHeatMode(uint8_t mode);
|
||||
uint8_t getHeatMode();
|
||||
uint8_t getMode();
|
||||
|
||||
void setMax(bool state);
|
||||
bool getMax();
|
||||
|
||||
void setNight(bool state);
|
||||
bool getNight();
|
||||
|
||||
void setiFeel(bool state);
|
||||
bool getiFeel();
|
||||
|
||||
void setTime();
|
||||
void setRoomTemp(uint8_t temp);
|
||||
|
||||
uint8_t* getRaw();
|
||||
|
||||
private:
|
||||
// # of bytes per command
|
||||
uint8_t argo[ARGO_COMMAND_LENGTH]; // Defined in IRremoteESP8266.h
|
||||
void stateReset();
|
||||
void checksum();
|
||||
IRsend _irsend; // instance of the IR send class
|
||||
|
||||
// Attributes
|
||||
uint8_t set_temp;
|
||||
uint8_t fan_mode;
|
||||
uint8_t flap_mode;
|
||||
uint8_t ac_state;
|
||||
uint8_t ac_mode; // heat 1, cool 0
|
||||
uint8_t heat_mode;
|
||||
uint8_t cool_mode;
|
||||
uint8_t night_mode; // on/off
|
||||
uint8_t max_mode; // on/off
|
||||
uint8_t ifeel_mode; // on/off
|
||||
};
|
||||
#endif // SEND_ARGO
|
||||
|
||||
#endif // IR_ARGO_H_
|
||||
@@ -0,0 +1,167 @@
|
||||
// Copyright bakrus
|
||||
// Copyright 2017 David Conran
|
||||
|
||||
#include "IRrecv.h"
|
||||
#include "IRsend.h"
|
||||
#include "IRtimer.h"
|
||||
#include "IRutils.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
|
||||
|
||||
// Coolix A/C / heatpump added by (send) bakrus & (decode) crankyoldgit
|
||||
|
||||
// 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)
|
||||
|
||||
#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.
|
||||
// repeat: Nr. of additional times the message is to be sent.
|
||||
//
|
||||
// Status: BETA / Probably works.
|
||||
//
|
||||
// Ref:
|
||||
// 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.
|
||||
|
||||
// Set IR carrier frequency
|
||||
enableIROut(38);
|
||||
|
||||
for (uint16_t r = 0; r <= repeat; r++) {
|
||||
// Header
|
||||
mark(COOLIX_HDR_MARK);
|
||||
space(COOLIX_HDR_SPACE);
|
||||
|
||||
// Data
|
||||
// Break data into byte segments, starting at the Most Significant
|
||||
// Byte. Each byte then being sent normal, then followed inverted.
|
||||
for (uint16_t i = 8; i <= nbits; i += 8) {
|
||||
// Grab a bytes worth of data.
|
||||
uint8_t segment = (data >> (nbits - i)) & 0xFF;
|
||||
// Normal
|
||||
sendData(COOLIX_BIT_MARK, COOLIX_ONE_SPACE,
|
||||
COOLIX_BIT_MARK, COOLIX_ZERO_SPACE,
|
||||
segment, 8, true);
|
||||
// Inverted.
|
||||
sendData(COOLIX_BIT_MARK, COOLIX_ONE_SPACE,
|
||||
COOLIX_BIT_MARK, COOLIX_ZERO_SPACE,
|
||||
segment ^ 0xFF, 8, true);
|
||||
}
|
||||
|
||||
// Footer
|
||||
mark(COOLIX_BIT_MARK);
|
||||
space(COOLIX_MIN_GAP); // Pause before repeating
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#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.
|
||||
// strict: Flag indicating if we should perform strict matching.
|
||||
// Returns:
|
||||
// boolean: True if it can decode it, false if it can't.
|
||||
//
|
||||
// Status: BETA / Probably working.
|
||||
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)
|
||||
return false; // Can't possibly be a valid COOLIX message.
|
||||
if (strict && nbits != COOLIX_BITS)
|
||||
return false; // Not strictly an 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;
|
||||
|
||||
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;
|
||||
// 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;
|
||||
// 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;
|
||||
|
||||
// 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))
|
||||
return false;
|
||||
if (matchSpace(results->rawbuf[offset], COOLIX_ONE_SPACE_TICKS * 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)) {
|
||||
if (flip)
|
||||
inverted <<= 1;
|
||||
else
|
||||
data <<= 1;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Footer
|
||||
if (!matchMark(results->rawbuf[offset++], COOLIX_BIT_MARK_TICKS * m_tick))
|
||||
return false;
|
||||
if (offset < results->rawlen &&
|
||||
!matchAtLeast(results->rawbuf[offset], COOLIX_MIN_GAP_TICKS * 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;
|
||||
}
|
||||
|
||||
// Success
|
||||
results->decode_type = COOLIX;
|
||||
results->bits = nbits;
|
||||
results->value = orig;
|
||||
results->address = 0;
|
||||
results->command = 0;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,347 @@
|
||||
/*
|
||||
An Arduino sketch to emulate IR Daikin ARC433** remote control unit
|
||||
Read more at:
|
||||
http://harizanov.com/2012/02/control-daikin-air-conditioner-over-the-internet/
|
||||
|
||||
Copyright 2016 sillyfrog
|
||||
*/
|
||||
|
||||
#include "ir_Daikin.h"
|
||||
#include <algorithm>
|
||||
#include "IRremoteESP8266.h"
|
||||
#include "IRutils.h"
|
||||
|
||||
// DDDDD AAA IIIII KK KK IIIII NN NN
|
||||
// DD DD AAAAA III KK KK III NNN NN
|
||||
// DD DD AA AA III KKKK III NN N NN
|
||||
// DD DD AAAAAAA III KK KK III NN NNN
|
||||
// DDDDDD AA AA IIIII KK KK IIIII NN NN
|
||||
|
||||
// Constants
|
||||
// Ref:
|
||||
// https://github.com/mharizanov/Daikin-AC-remote-control-over-the-Internet/tree/master/IRremote
|
||||
#define DAIKIN_HDR_MARK 3650U // DAIKIN_ZERO_MARK * 8
|
||||
#define DAIKIN_HDR_SPACE 1623U // DAIKIN_ZERO_MARK * 4
|
||||
#define DAIKIN_ONE_SPACE 1280U
|
||||
#define DAIKIN_ONE_MARK 428U
|
||||
#define DAIKIN_ZERO_MARK 428U
|
||||
#define DAIKIN_ZERO_SPACE 428U
|
||||
#define DAIKIN_GAP 29000U
|
||||
|
||||
#if SEND_DAIKIN
|
||||
// Send a Daikin A/C message.
|
||||
//
|
||||
// Args:
|
||||
// data: An array of DAIKIN_COMMAND_LENGTH bytes containing the IR command.
|
||||
//
|
||||
// Status: STABLE
|
||||
//
|
||||
// Ref:
|
||||
// IRDaikinESP.cpp
|
||||
// https://github.com/mharizanov/Daikin-AC-remote-control-over-the-Internet/tree/master/IRremote
|
||||
void IRsend::sendDaikin(unsigned char data[], uint16_t nbytes,
|
||||
uint16_t repeat) {
|
||||
if (nbytes < DAIKIN_COMMAND_LENGTH)
|
||||
return; // Not enough bytes to send a proper message.
|
||||
// Set IR carrier frequency
|
||||
enableIROut(38);
|
||||
for (uint16_t r = 0; r <= repeat; r++) {
|
||||
// Header #1
|
||||
mark(DAIKIN_HDR_MARK);
|
||||
space(DAIKIN_HDR_SPACE);
|
||||
// Data #1
|
||||
for (uint16_t i = 0; i < 8 && i < nbytes; i++)
|
||||
sendData(DAIKIN_ONE_MARK, DAIKIN_ONE_SPACE, DAIKIN_ZERO_MARK,
|
||||
DAIKIN_ZERO_SPACE, data[i], 8, false);
|
||||
// Footer #1
|
||||
mark(DAIKIN_ONE_MARK);
|
||||
space(DAIKIN_ZERO_SPACE + DAIKIN_GAP);
|
||||
|
||||
// Header #2
|
||||
mark(DAIKIN_HDR_MARK);
|
||||
space(DAIKIN_HDR_SPACE);
|
||||
// Data #2
|
||||
for (uint16_t i = 8; i < nbytes; i++)
|
||||
sendData(DAIKIN_ONE_MARK, DAIKIN_ONE_SPACE, DAIKIN_ZERO_MARK,
|
||||
DAIKIN_ZERO_SPACE, data[i], 8, false);
|
||||
// Footer #2
|
||||
mark(DAIKIN_ONE_MARK);
|
||||
space(DAIKIN_ZERO_SPACE + DAIKIN_GAP);
|
||||
}
|
||||
}
|
||||
|
||||
IRDaikinESP::IRDaikinESP(uint16_t pin) : _irsend(pin) {
|
||||
stateReset();
|
||||
}
|
||||
|
||||
void IRDaikinESP::begin() {
|
||||
_irsend.begin();
|
||||
}
|
||||
|
||||
void IRDaikinESP::send() {
|
||||
_irsend.sendDaikin(daikin);
|
||||
}
|
||||
|
||||
void IRDaikinESP::checksum() {
|
||||
uint8_t sum = 0;
|
||||
uint8_t i;
|
||||
|
||||
for (i = 0; i <= 6; i++)
|
||||
sum += daikin[i];
|
||||
|
||||
daikin[7] = sum & 0xFF;
|
||||
sum = 0;
|
||||
for (i = 8; i <= 25; i++)
|
||||
sum += daikin[i];
|
||||
daikin[26] = sum & 0xFF;
|
||||
}
|
||||
|
||||
|
||||
void IRDaikinESP::stateReset() {
|
||||
for (uint8_t i = 4; i < DAIKIN_COMMAND_LENGTH; i++)
|
||||
daikin[i] = 0x0;
|
||||
|
||||
daikin[0] = 0x11;
|
||||
daikin[1] = 0xDA;
|
||||
daikin[2] = 0x27;
|
||||
daikin[3] = 0xF0;
|
||||
daikin[7] = 0x20;
|
||||
daikin[8] = 0x11;
|
||||
daikin[9] = 0xDA;
|
||||
daikin[10] = 0x27;
|
||||
daikin[13] = 0x41;
|
||||
daikin[14] = 0x1E;
|
||||
daikin[16] = 0xB0;
|
||||
daikin[23] = 0xC0;
|
||||
daikin[26] = 0xE3;
|
||||
checksum();
|
||||
}
|
||||
|
||||
uint8_t* IRDaikinESP::getRaw() {
|
||||
checksum(); // Ensure correct settings before sending.
|
||||
return daikin;
|
||||
}
|
||||
|
||||
void IRDaikinESP::on() {
|
||||
// state = ON;
|
||||
daikin[13] |= 0x01;
|
||||
checksum();
|
||||
}
|
||||
|
||||
void IRDaikinESP::off() {
|
||||
// state = OFF;
|
||||
daikin[13] &= 0xFE;
|
||||
checksum();
|
||||
}
|
||||
|
||||
void IRDaikinESP::setPower(bool state) {
|
||||
if (state)
|
||||
on();
|
||||
else
|
||||
off();
|
||||
}
|
||||
|
||||
uint8_t IRDaikinESP::getPower() {
|
||||
return daikin[13] & 0x01;
|
||||
}
|
||||
|
||||
// DAIKIN_SILENT or DAIKIN_POWERFUL
|
||||
void IRDaikinESP::setAux(uint8_t aux) {
|
||||
daikin[21] = aux;
|
||||
checksum();
|
||||
}
|
||||
|
||||
uint8_t IRDaikinESP::getAux() {
|
||||
return daikin[21];
|
||||
}
|
||||
|
||||
void IRDaikinESP::setQuiet(bool state) {
|
||||
if (state)
|
||||
setAux(DAIKIN_SILENT);
|
||||
else
|
||||
setAux(0x0);
|
||||
}
|
||||
|
||||
bool IRDaikinESP::getQuiet() {
|
||||
return (getAux() == DAIKIN_SILENT);
|
||||
}
|
||||
|
||||
void IRDaikinESP::setPowerful(bool state) {
|
||||
if (state)
|
||||
setAux(DAIKIN_POWERFUL);
|
||||
else
|
||||
setAux(0x0);
|
||||
}
|
||||
|
||||
bool IRDaikinESP::getPowerful() {
|
||||
return (getAux() == DAIKIN_POWERFUL);
|
||||
}
|
||||
|
||||
// Set the temp in deg C
|
||||
void IRDaikinESP::setTemp(uint8_t temp) {
|
||||
if (temp < DAIKIN_MIN_TEMP)
|
||||
temp = DAIKIN_MIN_TEMP;
|
||||
else if (temp > DAIKIN_MAX_TEMP)
|
||||
temp = DAIKIN_MAX_TEMP;
|
||||
daikin[14] = temp * 2;
|
||||
checksum();
|
||||
}
|
||||
|
||||
uint8_t IRDaikinESP::getTemp() {
|
||||
return daikin[14] / 2;
|
||||
}
|
||||
|
||||
// Set the speed of the fan, 0-5, 0 is auto, 1-5 is the speed
|
||||
void IRDaikinESP::setFan(uint8_t fan) {
|
||||
// Set the fan speed bits, leave low 4 bits alone
|
||||
uint8_t fanset;
|
||||
daikin[16] &= 0x0F;
|
||||
fan = std::min(fan, DAIKIN_FAN_MAX);
|
||||
if (fan == DAIKIN_FAN_AUTO)
|
||||
fanset = 0xA0;
|
||||
else
|
||||
fanset = 0x20 + (0x10 * fan);
|
||||
daikin[16] |= fanset;
|
||||
checksum();
|
||||
}
|
||||
|
||||
uint8_t IRDaikinESP::getFan() {
|
||||
uint8_t fan = daikin[16] >> 4;
|
||||
fan -= 2;
|
||||
if (fan > DAIKIN_FAN_MAX)
|
||||
fan = DAIKIN_FAN_AUTO;
|
||||
return fan;
|
||||
}
|
||||
|
||||
uint8_t IRDaikinESP::getMode() {
|
||||
/*
|
||||
DAIKIN_COOL
|
||||
DAIKIN_HEAT
|
||||
DAIKIN_FAN
|
||||
DAIKIN_AUTO
|
||||
DAIKIN_DRY
|
||||
*/
|
||||
return daikin[13] >> 4;
|
||||
}
|
||||
|
||||
void IRDaikinESP::setMode(uint8_t mode) {
|
||||
switch (mode) {
|
||||
case DAIKIN_COOL:
|
||||
case DAIKIN_HEAT:
|
||||
case DAIKIN_FAN:
|
||||
case DAIKIN_DRY:
|
||||
break;
|
||||
default:
|
||||
mode = DAIKIN_AUTO;
|
||||
}
|
||||
daikin[13] = (mode << 4) | getPower();
|
||||
checksum();
|
||||
}
|
||||
|
||||
void IRDaikinESP::setSwingVertical(bool state) {
|
||||
if (state)
|
||||
daikin[16] |= 0x0F;
|
||||
else
|
||||
daikin[16] &= 0xF0;
|
||||
checksum();
|
||||
}
|
||||
|
||||
bool IRDaikinESP::getSwingVertical() {
|
||||
return daikin[16] & 0x01;
|
||||
}
|
||||
|
||||
void IRDaikinESP::setSwingHorizontal(bool state) {
|
||||
if (state)
|
||||
daikin[17] |= 0x0F;
|
||||
else
|
||||
daikin[17] &= 0xF0;
|
||||
checksum();
|
||||
}
|
||||
|
||||
bool IRDaikinESP::getSwingHorizontal() {
|
||||
return daikin[17] & 0x01;
|
||||
}
|
||||
#endif // SEND_DAIKIN
|
||||
|
||||
#if DECODE_DAIKIN
|
||||
// TODO(crankyoldgit): NOT WORKING. This needs to be finished.
|
||||
// Decode the supplied Daikin A/C message. (NOT WORKING - DO NOT USE)
|
||||
// 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.
|
||||
// strict: Flag to indicate if we strictly adhere to the specification.
|
||||
// Returns:
|
||||
// boolean: True if it can decode it, false if it can't.
|
||||
//
|
||||
// Status: UNFINISHED / Completely not working, not even vaguely.
|
||||
//
|
||||
// Ref:
|
||||
// https://github.com/mharizanov/Daikin-AC-remote-control-over-the-Internet/tree/master/IRremote
|
||||
bool IRrecv::decodeDaikin(decode_results *results, uint16_t nbits,
|
||||
bool strict) {
|
||||
if (results->rawlen < 2 * nbits + HEADER + FOOTER)
|
||||
return false;
|
||||
|
||||
// Compliance
|
||||
if (strict && nbits != DAIKIN_BITS)
|
||||
return false;
|
||||
|
||||
uint32_t data = 0;
|
||||
uint16_t offset = OFFSET_START;
|
||||
|
||||
// Header
|
||||
if (!matchMark(results->rawbuf[offset++], DAIKIN_HDR_MARK))
|
||||
return false;
|
||||
if (!matchSpace(results->rawbuf[offset++], DAIKIN_HDR_SPACE))
|
||||
return false;
|
||||
|
||||
// Data (#1)
|
||||
for (uint8_t i = 0; i < sizeof(data) * 8; i++, offset++) {
|
||||
if (!matchMark(results->rawbuf[offset++], DAIKIN_ONE_MARK))
|
||||
return false;
|
||||
if (matchSpace(results->rawbuf[offset], DAIKIN_ONE_SPACE))
|
||||
data = (data << 1) | 1; // 1
|
||||
else if (matchSpace(results->rawbuf[offset], DAIKIN_ZERO_SPACE))
|
||||
data <<= 1; // 0
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t number = data; // some number...
|
||||
uint32_t reversed = reverseBits(number, sizeof(number) * 8)
|
||||
|
||||
DPRINT("Code ");
|
||||
DPRINTLN(reversed, HEX);
|
||||
|
||||
// Data (#2)
|
||||
for (uint8_t i = 0; i < sizeof(data) * 8; i++, offset++) {
|
||||
if (!matchMark(results->rawbuf[offset++], DAIKIN_ONE_MARK))
|
||||
return false;
|
||||
if (matchSpace(results->rawbuf[offset], DAIKIN_ONE_SPACE))
|
||||
data = (data << 1) | 1; // 1
|
||||
else if (matchSpace(results->rawbuf[offset], DAIKIN_ZERO_SPACE))
|
||||
data <<= 1; // 0
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
number = data; // some number...
|
||||
reversed = reverseBits(number, sizeof(number) * 8)
|
||||
|
||||
DPRINT("Code2 ");
|
||||
DPRINTLN(reversed, HEX);
|
||||
|
||||
if (!matchSpace(results->rawbuf[offset++], DAIKIN_GAP)) {
|
||||
DPRINTLN("no gap");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Success
|
||||
results->bits = DAIKIN_BITS;
|
||||
results->value = reversed;
|
||||
results->decode_type = DAIKIN;
|
||||
results->address = 0;
|
||||
results->command = 0;
|
||||
return true;
|
||||
}
|
||||
#endif // DECODE_DAIKIN
|
||||
@@ -0,0 +1,107 @@
|
||||
/* Copyright 2016 sillyfrog */
|
||||
#ifndef IR_DAIKIN_H_
|
||||
#define IR_DAIKIN_H_
|
||||
|
||||
#include "IRremoteESP8266.h"
|
||||
#include "IRsend.h"
|
||||
|
||||
// DDDDD AAA IIIII KK KK IIIII NN NN
|
||||
// DD DD AAAAA III KK KK III NNN NN
|
||||
// DD DD AA AA III KKKK III NN N NN
|
||||
// DD DD AAAAAAA III KK KK III NN NNN
|
||||
// DDDDDD AA AA IIIII KK KK IIIII NN NN
|
||||
|
||||
/*
|
||||
Daikin AC map
|
||||
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 = 0
|
||||
b2 = OFF timer set
|
||||
b1 = ON timer set
|
||||
b0 = Air Conditioner ON
|
||||
byte 14=temp*2 (Temp should be between 18 - 32)
|
||||
byte 16=Fan
|
||||
FAN control
|
||||
b7+b6+b5+b4 = Fan speed
|
||||
Fan: b7+b6+b5+b4
|
||||
0×30 = 1 bar
|
||||
0×40 = 2 bar
|
||||
0×50 = 3 bar
|
||||
0×60 = 4 bar
|
||||
0×70 = 5 bar
|
||||
0xa0 = Auto
|
||||
0xb0 = Not auto, moon + tree
|
||||
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 21=Aux -> Powerful (bit 1), Silent (bit 5)
|
||||
byte 24=Aux2 -> Intelligent eye on (bit 7)
|
||||
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_POWERFUL 0b00000010
|
||||
#define DAIKIN_SILENT 0b00100000
|
||||
#define DAIKIN_MIN_TEMP 18U // Celsius
|
||||
#define DAIKIN_MAX_TEMP 32U // Celsius
|
||||
#define DAIKIN_FAN_AUTO (uint8_t) 0U
|
||||
#define DAIKIN_FAN_MIN (uint8_t) 1U
|
||||
#define DAIKIN_FAN_MAX (uint8_t) 5U
|
||||
|
||||
#if SEND_DAIKIN
|
||||
class IRDaikinESP {
|
||||
public:
|
||||
explicit IRDaikinESP(uint16_t pin);
|
||||
|
||||
void send();
|
||||
void begin();
|
||||
void on();
|
||||
void off();
|
||||
void setPower(bool state);
|
||||
uint8_t getPower();
|
||||
void setAux(uint8_t aux);
|
||||
uint8_t getAux();
|
||||
void setTemp(uint8_t temp);
|
||||
uint8_t getTemp();
|
||||
void setFan(uint8_t fan);
|
||||
uint8_t getFan();
|
||||
uint8_t getMode();
|
||||
void setMode(uint8_t mode);
|
||||
void setSwingVertical(bool state);
|
||||
bool getSwingVertical();
|
||||
void setSwingHorizontal(bool state);
|
||||
bool getSwingHorizontal();
|
||||
bool getQuiet();
|
||||
void setQuiet(bool state);
|
||||
bool getPowerful();
|
||||
void setPowerful(bool state);
|
||||
uint8_t* getRaw();
|
||||
|
||||
private:
|
||||
// # of bytes per command
|
||||
uint8_t daikin[DAIKIN_COMMAND_LENGTH];
|
||||
void stateReset();
|
||||
void checksum();
|
||||
IRsend _irsend;
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif // IR_DAIKIN_H_
|
||||
@@ -0,0 +1,153 @@
|
||||
// Copyright 2016 Massimiliano Pinto
|
||||
// Copyright 2017 David Conran
|
||||
|
||||
#include <algorithm>
|
||||
#include "IRrecv.h"
|
||||
#include "IRsend.h"
|
||||
#include "IRtimer.h"
|
||||
#include "IRutils.h"
|
||||
|
||||
// DDDD EEEEE N N OOO N N
|
||||
// D D E NN N O O NN N
|
||||
// D D EEE N N N O O N N N
|
||||
// D D E N NN O O N NN
|
||||
// DDDD EEEEE N N OOO N N
|
||||
|
||||
// Original Denon support added by https://github.com/csBlueChip
|
||||
// Ported over by Massimiliano Pinto
|
||||
|
||||
// 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
|
||||
|
||||
#if SEND_DENON
|
||||
// Send a Denon message
|
||||
//
|
||||
// Args:
|
||||
// data: Contents of the message to be sent.
|
||||
// nbits: Nr. of bits of data to be sent. Typically DENON_BITS.
|
||||
// repeat: Nr. of additional times the message is to be sent.
|
||||
//
|
||||
// Status: BETA / Should be working.
|
||||
//
|
||||
// Notes:
|
||||
// Some Denon devices use a Kaseikyo/Panasonic 48-bit format
|
||||
// Others use the Sharp protocol.
|
||||
// Ref:
|
||||
// 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?
|
||||
sendPanasonic64(data, nbits, repeat);
|
||||
else if (nbits == DENON_LEGACY_BITS)
|
||||
// Support legacy (broken) calls of sendDenon().
|
||||
sendSharpRaw(data & (~0x2000ULL), nbits + 1, repeat);
|
||||
else
|
||||
sendSharpRaw(data, nbits, repeat);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if DECODE_DENON
|
||||
// Decode a Denon message.
|
||||
//
|
||||
// Args:
|
||||
// results: Ptr to the data to decode and where to store the decode result.
|
||||
// nbits: Expected nr. of data bits. (Typically DENON_BITS)
|
||||
// Returns:
|
||||
// boolean: True if it can decode it, false if it can't.
|
||||
//
|
||||
// Status: BETA / Should work fine.
|
||||
//
|
||||
// Ref:
|
||||
// https://github.com/z3t0/Arduino-IRremote/blob/master/ir_Denon.cpp
|
||||
bool IRrecv::decodeDenon(decode_results *results, uint16_t nbits, bool strict) {
|
||||
// Compliance
|
||||
if (strict) {
|
||||
switch (nbits) {
|
||||
case DENON_BITS:
|
||||
case DENON_48_BITS:
|
||||
case DENON_LEGACY_BITS:
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Denon uses the Sharp & Panasonic(Kaseikyo) protocol for some
|
||||
// devices, so check for those first.
|
||||
// It is not exactly like Sharp's protocols, but close enough.
|
||||
// e.g. The expansion bit is not set for Denon vs. set for Sharp.
|
||||
// Ditto for Panasonic, it's the same except for a different
|
||||
// manufacturer code.
|
||||
|
||||
if (!decodeSharp(results, nbits, true, false) &&
|
||||
!decodePanasonic(results, nbits, true, DENON_MANUFACTURER)) {
|
||||
// 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;
|
||||
|
||||
uint64_t data = 0;
|
||||
uint16_t offset = OFFSET_START;
|
||||
|
||||
// Header
|
||||
if (!matchMark(results->rawbuf[offset], DENON_HDR_MARK)) return false;
|
||||
// Calculate how long the common tick time is based on the header mark.
|
||||
uint32_t m_tick = results->rawbuf[offset++] * RAWTICK /
|
||||
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;
|
||||
|
||||
// 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);
|
||||
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))
|
||||
return false;
|
||||
|
||||
// Success
|
||||
results->bits = nbits;
|
||||
results->value = data;
|
||||
results->address = 0;
|
||||
results->command = 0;
|
||||
} // Legacy decode.
|
||||
|
||||
// Compliance
|
||||
if (strict && nbits != results->bits) return false;
|
||||
|
||||
// Success
|
||||
results->decode_type = DENON;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,144 @@
|
||||
// Copyright Todd Treece
|
||||
// Copyright 2017 David Conran
|
||||
|
||||
#include "IRrecv.h"
|
||||
#include "IRsend.h"
|
||||
#include "IRtimer.h"
|
||||
#include "IRutils.h"
|
||||
|
||||
// DDDD IIIII SSSS H H
|
||||
// D D I S H H
|
||||
// D D I SSS HHHHH
|
||||
// D D I S H H
|
||||
// DDDD IIIII SSSS H H
|
||||
|
||||
// DISH support originally by Todd Treece
|
||||
// http://unionbridge.org/design/ircommand
|
||||
|
||||
// Constants
|
||||
// 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)
|
||||
|
||||
#if SEND_DISH
|
||||
// Send an IR command to a DISH NETWORK device.
|
||||
//
|
||||
// Args:
|
||||
// data: The contents of the command you want to send.
|
||||
// nbits: The bit size of the command being sent.
|
||||
// repeat: The number of times you want the command to be repeated.
|
||||
//
|
||||
// Status: BETA / Previously working.
|
||||
//
|
||||
// Note:
|
||||
// Dishplayer is a different protocol.
|
||||
// Typically a DISH device needs to get a command a total of at least 4
|
||||
// times to accept it. e.g. repeat=3
|
||||
//
|
||||
// Here is the LIRC file I found that seems to match the remote codes from the
|
||||
// oscilloscope:
|
||||
// DISH NETWORK (echostar 301):
|
||||
// http://lirc.sourceforge.net/remotes/echostar/301_501_3100_5100_58xx_59xx
|
||||
//
|
||||
// Ref:
|
||||
// http://www.hifi-remote.com/wiki/index.php?title=Dish
|
||||
void IRsend::sendDISH(uint64_t data, uint16_t nbits, uint16_t repeat) {
|
||||
// Set 57.6kHz IR carrier frequency, duty cycle is unknown.
|
||||
enableIROut(57600);
|
||||
// Header
|
||||
mark(DISH_HDR_MARK);
|
||||
space(DISH_HDR_SPACE);
|
||||
// We always send a command, even for repeat=0, hence '<= repeat'.
|
||||
for (uint16_t i = 0; i <= repeat; i++) {
|
||||
// Data
|
||||
sendData(DISH_BIT_MARK, DISH_ONE_SPACE, DISH_BIT_MARK, DISH_ZERO_SPACE,
|
||||
data, nbits, true);
|
||||
// Footer
|
||||
mark(DISH_BIT_MARK);
|
||||
space(DISH_RPT_SPACE);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if DECODE_DISH
|
||||
// Decode the supplied DISH NETWORK message.
|
||||
//
|
||||
// Args:
|
||||
// results: Ptr to the data to decode and where to store the decode result.
|
||||
// nbits: Nr. of bits to expect in the data portion. Typically DISH_BITS.
|
||||
// strict: Flag to indicate if we strictly adhere to the specification.
|
||||
// Returns:
|
||||
// boolean: True if it can decode it, false if it can't.
|
||||
//
|
||||
// Status: ALPHA (untested and unconfirmed.)
|
||||
//
|
||||
// Note:
|
||||
// Dishplayer is a different protocol.
|
||||
// Typically a DISH device needs to get a command a total of at least 4
|
||||
// times to accept it.
|
||||
// Ref:
|
||||
// http://www.hifi-remote.com/wiki/index.php?title=Dish
|
||||
// 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)
|
||||
return false; // Not enough entries to be valid.
|
||||
if (strict && nbits != DISH_BITS)
|
||||
return false; // Not strictly compliant.
|
||||
|
||||
uint64_t data = 0;
|
||||
uint16_t offset = OFFSET_START;
|
||||
|
||||
// Header
|
||||
if (!match(results->rawbuf[offset], DISH_HDR_MARK)) return false;
|
||||
// Calculate how long the common tick time is based on the header mark.
|
||||
uint32_t m_tick = results->rawbuf[offset++] * RAWTICK / DISH_HDR_MARK_TICKS;
|
||||
if (!matchSpace(results->rawbuf[offset], DISH_HDR_SPACE)) return false;
|
||||
// Calculate how long the common tick time is based on the header space.
|
||||
uint32_t s_tick = results->rawbuf[offset++] * RAWTICK / DISH_HDR_SPACE_TICKS;
|
||||
|
||||
// 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);
|
||||
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))
|
||||
return false;
|
||||
|
||||
// Compliance
|
||||
if (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))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Success
|
||||
results->decode_type = DISH;
|
||||
results->bits = nbits;
|
||||
results->value = data;
|
||||
results->address = 0;
|
||||
results->command = 0;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,221 @@
|
||||
// Copyright 2017 Jonny Graham
|
||||
#include "ir_Fujitsu.h"
|
||||
#include <algorithm>
|
||||
#include "IRsend.h"
|
||||
|
||||
|
||||
// Fujitsu A/C support added by Jonny Graham
|
||||
|
||||
|
||||
// Fujitsu A/C
|
||||
// Ref:
|
||||
// These values are based on averages of measurements
|
||||
#define FUJITSU_AC_HDR_MARK 3224U
|
||||
#define FUJITSU_AC_HDR_SPACE 1574U
|
||||
#define FUJITSU_AC_BIT_MARK 448U
|
||||
#define FUJITSU_AC_ONE_SPACE 1182U
|
||||
#define FUJITSU_AC_ZERO_SPACE 367U
|
||||
#define FUJITSU_AC_TRL_MARK 448U
|
||||
#define FUJITSU_AC_TRL_SPACE 8100U
|
||||
|
||||
#if SEND_FUJITSU_AC
|
||||
// Send a Fujitsu A/C message.
|
||||
//
|
||||
// Args:
|
||||
// data: An array of bytes containing the IR command.
|
||||
// nbytes: Nr. of bytes of data in the array. (typically either
|
||||
// FUJITSU_AC_STATE_LENGTH or FUJITSU_AC_STATE_LENGTH_SHORT)
|
||||
// repeat: Nr. of times the message is to be repeated.
|
||||
// (Default = FUJITSU_AC_MIN_REPEAT).
|
||||
//
|
||||
// Status: BETA / Appears to be working.
|
||||
//
|
||||
void IRsend::sendFujitsuAC(unsigned char data[], uint16_t nbytes,
|
||||
uint16_t repeat) {
|
||||
// Set IR carrier frequency
|
||||
enableIROut(38);
|
||||
for (uint16_t r = 0; r <= repeat; ++r) {
|
||||
// Header
|
||||
mark(FUJITSU_AC_HDR_MARK);
|
||||
space(FUJITSU_AC_HDR_SPACE);
|
||||
// Data
|
||||
for (uint16_t i = 0; i < nbytes; i++)
|
||||
sendData(FUJITSU_AC_BIT_MARK, FUJITSU_AC_ONE_SPACE,
|
||||
FUJITSU_AC_BIT_MARK, FUJITSU_AC_ZERO_SPACE,
|
||||
data[i], 8, false);
|
||||
// Footer
|
||||
mark(FUJITSU_AC_TRL_MARK);
|
||||
space(FUJITSU_AC_TRL_SPACE);
|
||||
}
|
||||
}
|
||||
|
||||
// Code to emulate Fujitsu A/C IR remote control unit.
|
||||
|
||||
// Warning: Consider this very alpha code. Seems to work, but not validated.
|
||||
//
|
||||
// Equipment it seems compatible with:
|
||||
// * Fujitsu ASYG30LFCA with remote AR-RAH2E
|
||||
// * <Add models (A/C & remotes) you've gotten it working with here>
|
||||
// Initialise the object.
|
||||
IRFujitsuAC::IRFujitsuAC(uint16_t pin) : _irsend(pin) {
|
||||
stateReset();
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Configure the pin for output.
|
||||
void IRFujitsuAC::begin() {
|
||||
_irsend.begin();
|
||||
}
|
||||
|
||||
// Send the current desired state to the IR LED.
|
||||
void IRFujitsuAC::send() {
|
||||
getRaw();
|
||||
uint8_t len = getCommandLength();
|
||||
_irsend.sendFujitsuAC(remote_state, len);
|
||||
}
|
||||
|
||||
uint8_t IRFujitsuAC::getCommandLength() {
|
||||
if (remote_state[5] != 0xFE)
|
||||
return FUJITSU_AC_STATE_LENGTH_SHORT;
|
||||
else
|
||||
return FUJITSU_AC_STATE_LENGTH;
|
||||
}
|
||||
|
||||
// Return a pointer to the internal state date of the remote.
|
||||
uint8_t* IRFujitsuAC::getRaw() {
|
||||
remote_state[0] = 0x14;
|
||||
remote_state[1] = 0x63;
|
||||
remote_state[2] = 0x00;
|
||||
remote_state[3] = 0x10;
|
||||
remote_state[4] = 0x10;
|
||||
bool fullCmd = false;
|
||||
switch (_cmd) {
|
||||
case FUJITSU_AC_CMD_TURN_OFF:
|
||||
remote_state[5] = 0x02;
|
||||
break;
|
||||
case FUJITSU_AC_CMD_STEP_HORIZ:
|
||||
remote_state[5] = 0x79;
|
||||
break;
|
||||
case FUJITSU_AC_CMD_STEP_VERT:
|
||||
remote_state[5] = 0x6C;
|
||||
break;
|
||||
default:
|
||||
remote_state[5] = 0xFE;
|
||||
fullCmd = true;
|
||||
break;
|
||||
}
|
||||
if (fullCmd) {
|
||||
remote_state[6] = 0x09;
|
||||
remote_state[7] = 0x30;
|
||||
uint8_t tempByte = _temp - FUJITSU_AC_MIN_TEMP;
|
||||
remote_state[8] = (_cmd == FUJITSU_AC_CMD_TURN_ON) | (tempByte << 4);
|
||||
remote_state[9] = _mode | 0 << 4; // timer off
|
||||
remote_state[10] = _fanSpeed | _swingMode << 4;
|
||||
remote_state[11] = 0; // timerOff values
|
||||
remote_state[12] = 0; // timerOff/on values
|
||||
remote_state[13] = 0; // timerOn values
|
||||
remote_state[14] = 0x20;
|
||||
// Checksum is the sum of the 8th to 16th bytes (ie remote_state[7]
|
||||
// thru remote_state[15]).
|
||||
// The checksum itself is stored in the 16th byte (ie remote_state[15]).
|
||||
// So we sum bytes 8th-15th...
|
||||
uint8_t checksum = 0;
|
||||
for (uint8_t i = 7 ; i < 15; ++i) {
|
||||
checksum += remote_state[i];
|
||||
}
|
||||
// and then do 0 - sum and store it in 16th.
|
||||
remote_state[15] = 0 - checksum;
|
||||
} else {
|
||||
// For the short codes, byte 7 is the inverse of byte 6
|
||||
remote_state[6] = ~remote_state[5];
|
||||
for (uint8_t i = 7; i < FUJITSU_AC_STATE_LENGTH; ++i) {
|
||||
remote_state[i] = 0;
|
||||
}
|
||||
}
|
||||
return remote_state;
|
||||
}
|
||||
|
||||
// Set the requested power state of the A/C to off.
|
||||
void IRFujitsuAC::off() {
|
||||
_cmd = FUJITSU_AC_CMD_TURN_OFF;
|
||||
}
|
||||
|
||||
void IRFujitsuAC::stepHoriz() {
|
||||
_cmd = FUJITSU_AC_CMD_STEP_HORIZ;
|
||||
}
|
||||
|
||||
void IRFujitsuAC::stepVert() {
|
||||
_cmd = FUJITSU_AC_CMD_STEP_VERT;
|
||||
}
|
||||
|
||||
// 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_HORIZ:
|
||||
case FUJITSU_AC_CMD_STEP_VERT:
|
||||
break;
|
||||
default:
|
||||
cmd = FUJITSU_AC_CMD_STAY_ON;
|
||||
break;
|
||||
}
|
||||
_cmd = cmd;
|
||||
}
|
||||
|
||||
uint8_t IRFujitsuAC::getCmd() {
|
||||
return _cmd;
|
||||
}
|
||||
|
||||
// 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 = 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.
|
||||
_fanSpeed = 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.
|
||||
_mode = mode;
|
||||
}
|
||||
|
||||
uint8_t IRFujitsuAC::getMode() {
|
||||
return _mode;
|
||||
}
|
||||
// Set the requested swing operation mode of the a/c unit.
|
||||
void IRFujitsuAC::setSwing(uint8_t swingMode) {
|
||||
if (swingMode > FUJITSU_AC_SWING_BOTH)
|
||||
swingMode = FUJITSU_AC_SWING_BOTH; // Set the mode to max if out of range
|
||||
_swingMode = swingMode;
|
||||
}
|
||||
|
||||
uint8_t IRFujitsuAC::getSwing() {
|
||||
return _swingMode;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright 2017 Jonny Graham
|
||||
#ifndef IR_FUJITSU_H_
|
||||
#define IR_FUJITSU_H_
|
||||
|
||||
#define __STDC_LIMIT_MACROS
|
||||
#include <stdint.h>
|
||||
#include "IRremoteESP8266.h"
|
||||
#include "IRsend.h"
|
||||
|
||||
|
||||
// FUJITSU A/C support added by Jonny Graham
|
||||
|
||||
// Constants
|
||||
|
||||
#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
|
||||
|
||||
#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
|
||||
|
||||
#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
|
||||
|
||||
#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
|
||||
|
||||
#define FUJITSU_AC_STATE_LENGTH 16
|
||||
#define FUJITSU_AC_STATE_LENGTH_SHORT 7
|
||||
|
||||
#if SEND_FUJITSU_AC
|
||||
class IRFujitsuAC {
|
||||
public:
|
||||
explicit IRFujitsuAC(uint16_t pin);
|
||||
|
||||
void stateReset();
|
||||
void send();
|
||||
void begin();
|
||||
void off();
|
||||
void stepHoriz();
|
||||
void stepVert();
|
||||
void setCmd(uint8_t cmd);
|
||||
uint8_t getCmd();
|
||||
void setTemp(uint8_t temp);
|
||||
uint8_t getTemp();
|
||||
void setFanSpeed(uint8_t fan);
|
||||
uint8_t getFanSpeed();
|
||||
void setMode(uint8_t mode);
|
||||
uint8_t getMode();
|
||||
void setSwing(uint8_t mode);
|
||||
uint8_t getSwing();
|
||||
uint8_t* getRaw();
|
||||
|
||||
private:
|
||||
uint8_t remote_state[FUJITSU_AC_STATE_LENGTH];
|
||||
uint8_t getCommandLength();
|
||||
IRsend _irsend;
|
||||
uint8_t _temp;
|
||||
uint8_t _fanSpeed;
|
||||
uint8_t _mode;
|
||||
uint8_t _swingMode;
|
||||
uint8_t _cmd;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#endif // IR_FUJITSU_H_
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright 2016 Hisham Khalifa
|
||||
// Copyright 2017 David Conran
|
||||
|
||||
#include <algorithm>
|
||||
#include "IRsend.h"
|
||||
#include "IRtimer.h"
|
||||
|
||||
// GGG L OOOO BBBB AA L CCCC AA CCCC H H EEEEEE
|
||||
// G G L O O B B AAAA L C C AAAA C C H H E
|
||||
// G L O O BBBBB A A L C A A C HHHHHH EEEE
|
||||
// G GG L O O B BB AAAAAA L C C AAAAAA C C H H E
|
||||
// GGGGG LLLLLL OOOO BBBBB A A LLLLLL CCCC A A CCCC H H EEEEEE
|
||||
|
||||
// Global Cache IR format sender originally added by Hisham Khalifa
|
||||
// (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
|
||||
|
||||
#if SEND_GLOBALCACHE
|
||||
// Send a shortened GlobalCache (GC) IRdb/control tower formatted message.
|
||||
//
|
||||
// Args:
|
||||
// buf: An array of uint16_t containing the shortened GlobalCache data.
|
||||
// len: Nr. of entries in the buf[] array.
|
||||
//
|
||||
// Status: STABLE / Known working.
|
||||
//
|
||||
// Note:
|
||||
// Global Cache format without the emitter ID or request ID.
|
||||
// Starts at the frequency (Hertz), followed by nr. of times to emit (count),
|
||||
// then the offset for repeats (where a repeat will start from),
|
||||
// then the rest of entries are the actual IR message as units of periodic
|
||||
// time.
|
||||
// e.g. sendir,1:1,1,38000,1,1,9,70,9,30,9,... -> 38000,1,1,9,70,9,30,9,...
|
||||
// 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.
|
||||
enableIROut(hz);
|
||||
uint32_t periodic_time = calcUSecPeriod(hz, false);
|
||||
uint8_t emits = std::min(buf[GLOBALCACHE_RPT_INDEX],
|
||||
(uint16_t) GLOBALCACHE_MAX_REPEAT);
|
||||
// Repeat
|
||||
for (uint8_t repeat = 0; repeat < emits; repeat++) {
|
||||
// First time through, start at the beginning (GLOBALCACHE_START_INDEX),
|
||||
// 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;
|
||||
// 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);
|
||||
// These codes start at an odd index (not even as with sendRaw).
|
||||
if (offset & 1) // Odd bit.
|
||||
mark(microseconds);
|
||||
else // Even bit.
|
||||
space(microseconds);
|
||||
}
|
||||
}
|
||||
// It's possible that we've ended on a mark(), thus ensure the LED is off.
|
||||
ledOff();
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,113 @@
|
||||
// Copyright 2017 Ville Skyttä (scop)
|
||||
// Copyright 2017 David Conran
|
||||
//
|
||||
// Gree protocol compatible heat pump carrying the "Ultimate" brand name.
|
||||
//
|
||||
|
||||
#include "IRremoteESP8266.h"
|
||||
#include "IRsend.h"
|
||||
|
||||
// GGGG RRRRRR EEEEEEE EEEEEEE
|
||||
// GG GG RR RR EE EE
|
||||
// GG RRRRRR EEEEE EEEEE
|
||||
// GG GG RR RR EE EE
|
||||
// GGGGGG RR RR EEEEEEE EEEEEEE
|
||||
|
||||
// Constants
|
||||
// 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
|
||||
|
||||
#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)
|
||||
// repeat: Nr. of times the message is to be repeated. (Default = 0).
|
||||
//
|
||||
// Status: ALPHA / Untested.
|
||||
//
|
||||
// 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)
|
||||
return; // Not enough bytes to send a proper message.
|
||||
|
||||
// Set IR carrier frequency
|
||||
enableIROut(38);
|
||||
|
||||
for (uint16_t r = 0; r <= repeat; r++) {
|
||||
// Header #1
|
||||
mark(GREE_HDR_MARK);
|
||||
space(GREE_HDR_SPACE);
|
||||
|
||||
// Data #1
|
||||
uint16_t i;
|
||||
for (i = 0; i < 4 && i < nbytes; i++)
|
||||
sendData(GREE_BIT_MARK, GREE_ONE_SPACE, GREE_BIT_MARK, GREE_ZERO_SPACE,
|
||||
data[i], 8, false);
|
||||
|
||||
// Footer #1 (010)
|
||||
sendData(GREE_BIT_MARK, GREE_ONE_SPACE, GREE_BIT_MARK, GREE_ZERO_SPACE,
|
||||
0b010, 3);
|
||||
|
||||
// Header #2
|
||||
mark(GREE_BIT_MARK);
|
||||
space(GREE_MSG_SPACE);
|
||||
|
||||
// Data #2
|
||||
for (; i < nbytes; i++)
|
||||
sendData(GREE_BIT_MARK, GREE_ONE_SPACE, GREE_BIT_MARK, GREE_ZERO_SPACE,
|
||||
data[i], 8, false);
|
||||
|
||||
// Footer #2
|
||||
mark(GREE_BIT_MARK);
|
||||
space(GREE_MSG_SPACE);
|
||||
}
|
||||
}
|
||||
|
||||
// Send a Gree Heat Pump message.
|
||||
//
|
||||
// Args:
|
||||
// data: The raw message to be sent.
|
||||
// nbits: Nr. of bits of data in the message. (Default is GREE_BITS)
|
||||
// repeat: Nr. of times the message is to be repeated. (Default = 0).
|
||||
//
|
||||
// Status: ALPHA / Untested.
|
||||
//
|
||||
// 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)
|
||||
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);
|
||||
|
||||
// Data
|
||||
for (int16_t i = 8; i <= nbits; i += 8) {
|
||||
sendData(GREE_BIT_MARK, GREE_ONE_SPACE, GREE_BIT_MARK, GREE_ZERO_SPACE,
|
||||
(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,
|
||||
0b010, 3);
|
||||
mark(GREE_BIT_MARK);
|
||||
space(GREE_MSG_SPACE);
|
||||
}
|
||||
}
|
||||
// Footer
|
||||
mark(GREE_BIT_MARK);
|
||||
space(GREE_MSG_SPACE);
|
||||
}
|
||||
}
|
||||
#endif // SEND_GREE
|
||||
@@ -0,0 +1,165 @@
|
||||
// Copyright 2015 Kristian Lauszus
|
||||
// Copyright 2017 David Conran
|
||||
|
||||
#include <algorithm>
|
||||
#include "IRrecv.h"
|
||||
#include "IRsend.h"
|
||||
#include "IRtimer.h"
|
||||
#include "IRutils.h"
|
||||
|
||||
// JJJJJ V V CCCC
|
||||
// J V V C
|
||||
// J V V C
|
||||
// J J V V C
|
||||
// J V CCCC
|
||||
|
||||
// JVC originally added by Kristian Lauszus
|
||||
// (Thanks to zenwheel and other people at the original blog post)
|
||||
|
||||
// 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)
|
||||
|
||||
#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)
|
||||
// repeat: The number of times you want the command to be repeated.
|
||||
//
|
||||
// Status: STABLE.
|
||||
//
|
||||
// Ref:
|
||||
// http://www.sbprojects.com/knowledge/ir/jvc.php
|
||||
void IRsend::sendJVC(uint64_t data, uint16_t nbits, uint16_t repeat) {
|
||||
// Set 38kHz IR carrier frequency & a 1/3 (33%) duty cycle.
|
||||
enableIROut(38, 33);
|
||||
|
||||
IRtimer usecs = IRtimer();
|
||||
// Header
|
||||
// Only sent for the first message.
|
||||
mark(JVC_HDR_MARK);
|
||||
space(JVC_HDR_SPACE);
|
||||
|
||||
// We always send the data & footer at least once, hence '<= repeat'.
|
||||
for (uint16_t i = 0; i <= repeat; i++) {
|
||||
// Data
|
||||
sendData(JVC_BIT_MARK, JVC_ONE_SPACE, JVC_BIT_MARK, JVC_ZERO_SPACE,
|
||||
data, nbits, true);
|
||||
// Footer
|
||||
mark(JVC_BIT_MARK);
|
||||
// Wait till the end of the repeat time window before we send another code.
|
||||
space(std::max(JVC_MIN_GAP, JVC_RPT_LENGTH - usecs.elapsed()));
|
||||
usecs.reset();
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the raw JVC data based on address and command.
|
||||
//
|
||||
// Args:
|
||||
// address: An 8-bit address value.
|
||||
// command: An 8-bit command value.
|
||||
// Returns:
|
||||
// A raw JVC message.
|
||||
//
|
||||
// Status: BETA / Should work fine.
|
||||
//
|
||||
// Ref:
|
||||
// http://www.sbprojects.com/knowledge/ir/jvc.php
|
||||
uint16_t IRsend::encodeJVC(uint8_t address, uint8_t command) {
|
||||
return reverseBits((command << 8) | address, 16);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if DECODE_JVC
|
||||
// Decode the supplied JVC message.
|
||||
//
|
||||
// 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.
|
||||
// strict: Flag indicating if we should perform strict matching.
|
||||
// Returns:
|
||||
// boolean: True if it can decode it, false if it can't.
|
||||
//
|
||||
// Status: STABLE
|
||||
//
|
||||
// Note:
|
||||
// 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)
|
||||
return false; // Must be called with the correct nr. of bits.
|
||||
if (results->rawlen < 2 * nbits + FOOTER - 1)
|
||||
return false; // Can't possibly be a valid JVC message.
|
||||
|
||||
uint64_t data = 0;
|
||||
uint16_t offset = OFFSET_START;
|
||||
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)) {
|
||||
isRepeat = false;
|
||||
m_tick = results->rawbuf[offset++] * RAWTICK / JVC_HDR_MARK_TICKS;
|
||||
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;
|
||||
} 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;
|
||||
}
|
||||
|
||||
// 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);
|
||||
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))
|
||||
return false;
|
||||
if (offset < results->rawlen &&
|
||||
!matchAtLeast(results->rawbuf[offset], JVC_MIN_GAP_TICKS * s_tick))
|
||||
return false;
|
||||
|
||||
// Success
|
||||
results->decode_type = JVC;
|
||||
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->command = reverseBits(data & 0xFF, 8); // The last 8 bits sent.
|
||||
results->repeat = isRepeat;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,329 @@
|
||||
// Copyright 2016 David Conran
|
||||
//
|
||||
// Code to emulate IR Kelvinator YALIF remote control unit, which should control
|
||||
// at least the following Kelvinator A/C units:
|
||||
// KSV26CRC, KSV26HRC, KSV35CRC, KSV35HRC, KSV53HRC, KSV62HRC, KSV70CRC,
|
||||
// KSV70HRC, KSV80HRC.
|
||||
//
|
||||
// Note:
|
||||
// * Unsupported:
|
||||
// - All Sleep modes.
|
||||
// - All Timer modes.
|
||||
// - "I Feel" button & mode.
|
||||
// - Energy Saving mode.
|
||||
// - Low Heat mode.
|
||||
// - Fahrenheit.
|
||||
|
||||
#include "ir_Kelvinator.h"
|
||||
#include <algorithm>
|
||||
|
||||
// KK KK EEEEEEE LL VV VV IIIII NN NN AAA TTTTTTT OOOOO RRRRRR
|
||||
// KK KK EE LL VV VV III NNN NN AAAAA TTT OO OO RR RR
|
||||
// KKKK EEEEE LL VV VV III NN N NN AA AA TTT OO OO RRRRRR
|
||||
// KK KK EE LL VV VV III NN NNN AAAAAAA TTT OO OO RR RR
|
||||
// KK KK EEEEEEE LLLLLLL VVV IIIII NN NN AA AA TTT OOOO0 RR RR
|
||||
|
||||
// Constants
|
||||
#define KELVINATOR_HDR_MARK 8990U
|
||||
#define KELVINATOR_HDR_SPACE 4490U
|
||||
#define KELVINATOR_BIT_MARK 675U
|
||||
#define KELVINATOR_ONE_SPACE 1560U
|
||||
#define KELVINATOR_ZERO_SPACE 520U
|
||||
#define KELVINATOR_GAP_SPACE 19950U
|
||||
#define KELVINATOR_CMD_FOOTER 2U
|
||||
|
||||
#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)
|
||||
|
||||
#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)
|
||||
// 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)
|
||||
return; // Not enough bytes to send a proper message.
|
||||
|
||||
// Set IR carrier frequency
|
||||
enableIROut(38);
|
||||
|
||||
for (uint16_t r = 0; r <= repeat; r++) {
|
||||
// Header #1
|
||||
mark(KELVINATOR_HDR_MARK);
|
||||
space(KELVINATOR_HDR_SPACE);
|
||||
// Data (command)
|
||||
// Send the first command data (4 bytes)
|
||||
uint8_t i;
|
||||
for (i = 0; i < 4; i++)
|
||||
sendData(KELVINATOR_BIT_MARK, KELVINATOR_ONE_SPACE, KELVINATOR_BIT_MARK,
|
||||
KELVINATOR_ZERO_SPACE, data[i], 8, false);
|
||||
// Send Footer for the command data (3 bits (0b010))
|
||||
sendData(KELVINATOR_BIT_MARK, KELVINATOR_ONE_SPACE, KELVINATOR_BIT_MARK,
|
||||
KELVINATOR_ZERO_SPACE, KELVINATOR_CMD_FOOTER, 3, false);
|
||||
// Send an interdata gap.
|
||||
mark(KELVINATOR_BIT_MARK);
|
||||
space(KELVINATOR_GAP_SPACE);
|
||||
// Data (options)
|
||||
// Send the 1st option chunk of data (4 bytes).
|
||||
for (; i < 8; i++)
|
||||
sendData(KELVINATOR_BIT_MARK, KELVINATOR_ONE_SPACE, KELVINATOR_BIT_MARK,
|
||||
KELVINATOR_ZERO_SPACE, data[i], 8, false);
|
||||
// Send a double data gap to signify we are starting a new command sequence.
|
||||
mark(KELVINATOR_BIT_MARK);
|
||||
space(KELVINATOR_GAP_SPACE * 2);
|
||||
// Header #2
|
||||
mark(KELVINATOR_HDR_MARK);
|
||||
space(KELVINATOR_HDR_SPACE);
|
||||
// Data (command)
|
||||
// Send the 2nd command data (4 bytes).
|
||||
// Basically an almost identical repeat of the earlier command data.
|
||||
for (; i < 12; i++)
|
||||
sendData(KELVINATOR_BIT_MARK, KELVINATOR_ONE_SPACE, KELVINATOR_BIT_MARK,
|
||||
KELVINATOR_ZERO_SPACE, data[i], 8, false);
|
||||
// Send Footer for the command data (3 bits (B010))
|
||||
sendData(KELVINATOR_BIT_MARK, KELVINATOR_ONE_SPACE, KELVINATOR_BIT_MARK,
|
||||
KELVINATOR_ZERO_SPACE, KELVINATOR_CMD_FOOTER, 3, false);
|
||||
// Send an interdata gap.
|
||||
mark(KELVINATOR_BIT_MARK);
|
||||
space(KELVINATOR_GAP_SPACE);
|
||||
// Data (options)
|
||||
// Send the 2nd option chunk of data (4 bytes).
|
||||
// Unlike the commands, definitely not a repeat of the earlier option data.
|
||||
for (; i < KELVINATOR_STATE_LENGTH; i++)
|
||||
sendData(KELVINATOR_BIT_MARK, KELVINATOR_ONE_SPACE, KELVINATOR_BIT_MARK,
|
||||
KELVINATOR_ZERO_SPACE, data[i], 8, false);
|
||||
// Footer
|
||||
mark(KELVINATOR_BIT_MARK);
|
||||
space(KELVINATOR_GAP_SPACE * 2);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
remote_state[3] = 0x50;
|
||||
remote_state[11] = 0x70;
|
||||
}
|
||||
|
||||
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)
|
||||
setXFan(false);
|
||||
checksum(); // Calculate the checksums
|
||||
}
|
||||
|
||||
void IRKelvinatorAC::send() {
|
||||
fixup(); // Ensure correct settings before sending.
|
||||
_irsend.sendKelvinator(remote_state);
|
||||
}
|
||||
|
||||
uint8_t* IRKelvinatorAC::getRaw() {
|
||||
fixup(); // Ensure correct settings before sending.
|
||||
return remote_state;
|
||||
}
|
||||
|
||||
// Many Bothans died to bring us this information.
|
||||
void IRKelvinatorAC::checksum() {
|
||||
// For each command + options block.
|
||||
for (uint8_t offset = 0; offset < KELVINATOR_STATE_LENGTH; offset += 8) {
|
||||
uint8_t sum = KELVINATOR_CHECKSUM_START;
|
||||
// Sum the lower half of the first 4 bytes of this block.
|
||||
for (uint8_t i = 0; i < 4; i++)
|
||||
sum += (remote_state[i + offset] & 0xFU);
|
||||
// then sum the upper half of the next 3 bytes.
|
||||
for (uint8_t i = 4; i < 7; i++)
|
||||
sum += (remote_state[i + offset] >> 4);
|
||||
// Trim it down to fit into the 4 bits allowed. i.e. Mod 16.
|
||||
sum &= 0xFU;
|
||||
// Place it into the IR code in the top half of the 8th & 16th byte.
|
||||
remote_state[7 + offset] = (sum << 4) | (remote_state[7 + offset] & 0xFU);
|
||||
}
|
||||
}
|
||||
|
||||
void IRKelvinatorAC::on() {
|
||||
remote_state[0] |= KELVINATOR_POWER;
|
||||
remote_state[8] = remote_state[0]; // Duplicate to the 2nd command chunk.
|
||||
}
|
||||
|
||||
void IRKelvinatorAC::off() {
|
||||
remote_state[0] &= ~KELVINATOR_POWER;
|
||||
remote_state[8] = remote_state[0]; // Duplicate to the 2nd command chunk.
|
||||
}
|
||||
|
||||
void IRKelvinatorAC::setPower(bool state) {
|
||||
if (state)
|
||||
on();
|
||||
else
|
||||
off();
|
||||
}
|
||||
|
||||
bool IRKelvinatorAC::getPower() {
|
||||
return ((remote_state[0] & KELVINATOR_POWER) != 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);
|
||||
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);
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
// 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);
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
uint8_t IRKelvinatorAC::getMode() {
|
||||
return (remote_state[0] & ~KELVINATOR_MODE_MASK);
|
||||
}
|
||||
|
||||
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;
|
||||
remote_state[8] = remote_state[0]; // Duplicate to the 2nd command chunk.
|
||||
if (mode == KELVINATOR_AUTO || KELVINATOR_DRY)
|
||||
// When the remote is set to Auto or Dry, it defaults to 25C and doesn't
|
||||
// show it.
|
||||
setTemp(KELVINATOR_AUTO_TEMP);
|
||||
}
|
||||
|
||||
void IRKelvinatorAC::setSwingVertical(bool state) {
|
||||
if (state) {
|
||||
remote_state[0] |= KELVINATOR_VENT_SWING;
|
||||
remote_state[4] |= KELVINATOR_VENT_SWING_V;
|
||||
} else {
|
||||
remote_state[4] &= ~KELVINATOR_VENT_SWING_V;
|
||||
if (!getSwingHorizontal())
|
||||
remote_state[0] &= ~KELVINATOR_VENT_SWING;
|
||||
}
|
||||
remote_state[8] = remote_state[0]; // Duplicate to the 2nd command chunk.
|
||||
}
|
||||
|
||||
bool IRKelvinatorAC::getSwingVertical() {
|
||||
return ((remote_state[4] & KELVINATOR_VENT_SWING_V) != 0);
|
||||
}
|
||||
|
||||
void IRKelvinatorAC::setSwingHorizontal(bool state) {
|
||||
if (state) {
|
||||
remote_state[0] |= KELVINATOR_VENT_SWING;
|
||||
remote_state[4] |= KELVINATOR_VENT_SWING_H;
|
||||
} else {
|
||||
remote_state[4] &= ~KELVINATOR_VENT_SWING_H;
|
||||
if (!getSwingVertical())
|
||||
remote_state[0] &= ~KELVINATOR_VENT_SWING;
|
||||
}
|
||||
remote_state[8] = remote_state[0]; // Duplicate to the 2nd command chunk.
|
||||
}
|
||||
|
||||
bool IRKelvinatorAC::getSwingHorizontal() {
|
||||
return ((remote_state[4] & KELVINATOR_VENT_SWING_H) != 0);
|
||||
}
|
||||
|
||||
void IRKelvinatorAC::setQuiet(bool state) {
|
||||
remote_state[12] &= ~KELVINATOR_QUIET;
|
||||
remote_state[12] |= (state << KELVINATOR_QUIET_OFFSET);
|
||||
}
|
||||
|
||||
bool IRKelvinatorAC::getQuiet() {
|
||||
return ((remote_state[12] & KELVINATOR_QUIET) != 0);
|
||||
}
|
||||
|
||||
void IRKelvinatorAC::setIonFilter(bool state) {
|
||||
remote_state[2] &= ~KELVINATOR_ION_FILTER;
|
||||
remote_state[2] |= (state << KELVINATOR_ION_FILTER_OFFSET);
|
||||
remote_state[10] = remote_state[2]; // Duplicate to the 2nd command chunk.
|
||||
}
|
||||
|
||||
bool IRKelvinatorAC::getIonFilter() {
|
||||
return ((remote_state[2] & KELVINATOR_ION_FILTER) != 0);
|
||||
}
|
||||
|
||||
void IRKelvinatorAC::setLight(bool state) {
|
||||
remote_state[2] &= ~KELVINATOR_LIGHT;
|
||||
remote_state[2] |= (state << KELVINATOR_LIGHT_OFFSET);
|
||||
remote_state[10] = remote_state[2]; // Duplicate to the 2nd command chunk.
|
||||
}
|
||||
|
||||
bool IRKelvinatorAC::getLight() {
|
||||
return ((remote_state[2] & KELVINATOR_LIGHT) != 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[10] = remote_state[2]; // Duplicate to the 2nd command chunk.
|
||||
}
|
||||
|
||||
bool IRKelvinatorAC::getXFan() {
|
||||
return ((remote_state[2] & KELVINATOR_XFAN) != 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[10] = remote_state[2]; // Duplicate to the 2nd command chunk.
|
||||
}
|
||||
|
||||
bool IRKelvinatorAC::getTurbo() {
|
||||
return ((remote_state[2] & KELVINATOR_TURBO) != 0);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,152 @@
|
||||
// Kelvinator A/C
|
||||
//
|
||||
// Copyright 2016 David Conran
|
||||
|
||||
#ifndef IR_KELVINATOR_H_
|
||||
#define IR_KELVINATOR_H_
|
||||
|
||||
#define __STDC_LIMIT_MACROS
|
||||
#include <stdint.h>
|
||||
#include "IRremoteESP8266.h"
|
||||
#include "IRsend.h"
|
||||
|
||||
// KK KK EEEEEEE LL VV VV IIIII NN NN AAA TTTTTTT OOOOO RRRRRR
|
||||
// KK KK EE LL VV VV III NNN NN AAAAA TTT OO OO RR RR
|
||||
// KKKK EEEEE LL VV VV III NN N NN AA AA TTT OO OO RRRRRR
|
||||
// KK KK EE LL VV VV III NN NNN AAAAAAA TTT OO OO RR RR
|
||||
// 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_MAX 5U
|
||||
#define KELVINATOR_MIN_TEMP 16U // 16C
|
||||
#define KELVINATOR_MAX_TEMP 30U // 30C
|
||||
#define KELVINATOR_AUTO_TEMP 25U // 25C
|
||||
|
||||
/*
|
||||
Kelvinator AC map
|
||||
|
||||
(header mark and space)
|
||||
byte 0 = Basic Modes
|
||||
b2-0 = Modes
|
||||
Modes:
|
||||
000 = Auto (temp = 25C)
|
||||
001 = Cool
|
||||
010 = Dry (temp = 25C, but not shown)
|
||||
011 = Fan
|
||||
100 = Heat
|
||||
b3 = Power Status (1 = On, 0 = Off)
|
||||
b5-4 = Fan (Basic modes)
|
||||
Fan:
|
||||
00 = Auto
|
||||
01 = Fan 1
|
||||
10 = Fan 2
|
||||
11 = Fan 3 or higher (See byte 14)
|
||||
b6 = Vent swing (1 = On, 0 = Off) (See byte 4)
|
||||
b7 = Sleep Modes 1 & 3 (1 = On, 0 = Off)
|
||||
byte 1 = Temperature
|
||||
b3-0: Degrees C.
|
||||
0000 (0) = 16C
|
||||
0001 (1) = 17C
|
||||
0010 (2) = 18C
|
||||
...
|
||||
1101 (13) = 29C
|
||||
1110 (14) = 30C
|
||||
byte 2 = Extras
|
||||
b3-0 = UNKNOWN, typically 0.
|
||||
b4 = Turbo Fan (1 = On, 0 = Off)
|
||||
b5 = Light (Display) (1 = On, 0 = Off)
|
||||
b6 = Ion Filter (1 = On, 0 = Off)
|
||||
b7 = X-Fan (Fan runs for a while after power off) (1 = On, 0 = Off)
|
||||
byte 3 = Section Indicator
|
||||
b3-0 = Unused (Typically 0)
|
||||
b5-4 = Unknown (possibly timer related) (Typically 0b01)
|
||||
b7-6 = End of command block (B01)
|
||||
(B010 marker and a gap of 20ms)
|
||||
byte 4 = Extended options
|
||||
b0 = Swing Vent Vertical (1 = On, 0 = Off)
|
||||
b4 = Swing Vent Horizontal (1 = On, 0 = Off)
|
||||
byte 5-6 = Timer related. Typically 0 except when timer in use.
|
||||
byte 7 = checksum
|
||||
b3-0 = Unknown (Used in Timer mode)
|
||||
b7-4 = checksum of the previous bytes (0-6)
|
||||
(gap of 40ms)
|
||||
(header mark and space)
|
||||
byte 8 = Repeat of byte 0
|
||||
byte 9 = Repeat of byte 1
|
||||
byte 10 = Repeat of byte 2
|
||||
byte 11 = Section Indicator
|
||||
b3-0 = Unused (Typically 0)
|
||||
b5-4 = Unknown (possibly timer related) (Typically 0b11)
|
||||
b7-6 = End of command block (B01)
|
||||
(B010 marker and a gap of 20ms)
|
||||
byte 12 = Extended options
|
||||
b0 = Sleep mode 2 (1 = On, 0=Off)
|
||||
b6-1 = Unknown (Used in Sleep Mode 3, Typically 0b000000)
|
||||
b7 = Quiet Mode (1 = On, 0=Off)
|
||||
byte 13 = Unknown (Sleep Mode 3 related, Typically 0x00)
|
||||
byte 14 = Fan control
|
||||
b3-0 = Unknown (Sleep Mode 3 related, Typically 0b0000)
|
||||
b6-4 = Fan speed
|
||||
0b000 (0) = Automatic
|
||||
0b001 (1) = Fan 1
|
||||
0b010 (2) = Fan 2
|
||||
0b011 (3) = Fan 3
|
||||
0b100 (4) = Fan 4
|
||||
0b101 (5) = Fan 5
|
||||
byte 15 = checksum
|
||||
b3-0 = Unknown (Typically 0b0000)
|
||||
b7-4 = checksum of the previous bytes (8-14)
|
||||
*/
|
||||
|
||||
#if SEND_KELVINATOR
|
||||
|
||||
// Classes
|
||||
class IRKelvinatorAC {
|
||||
public:
|
||||
explicit IRKelvinatorAC(uint16_t pin);
|
||||
|
||||
void stateReset();
|
||||
void send();
|
||||
void begin();
|
||||
void on();
|
||||
void off();
|
||||
void setPower(bool state);
|
||||
bool getPower();
|
||||
void setTemp(uint8_t temp);
|
||||
uint8_t getTemp();
|
||||
void setFan(uint8_t fan);
|
||||
uint8_t getFan();
|
||||
void setMode(uint8_t mode);
|
||||
uint8_t getMode();
|
||||
void setSwingVertical(bool state);
|
||||
bool getSwingVertical();
|
||||
void setSwingHorizontal(bool state);
|
||||
bool getSwingHorizontal();
|
||||
void setQuiet(bool state);
|
||||
bool getQuiet();
|
||||
void setIonFilter(bool state);
|
||||
bool getIonFilter();
|
||||
void setLight(bool state);
|
||||
bool getLight();
|
||||
void setXFan(bool state);
|
||||
bool getXFan();
|
||||
void setTurbo(bool state);
|
||||
bool getTurbo();
|
||||
uint8_t* getRaw();
|
||||
|
||||
private:
|
||||
// The state of the IR remote in IR code form.
|
||||
uint8_t remote_state[KELVINATOR_STATE_LENGTH];
|
||||
void checksum();
|
||||
void fixup();
|
||||
IRsend _irsend;
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif // IR_KELVINATOR_H_
|
||||
@@ -0,0 +1,233 @@
|
||||
// Copyright 2015 Darryl Smith
|
||||
// Copyright 2015 cheaplin
|
||||
// Copyright 2017 David Conran
|
||||
|
||||
#include "ir_LG.h"
|
||||
#include <algorithm>
|
||||
#include "IRrecv.h"
|
||||
#include "IRsend.h"
|
||||
#include "IRtimer.h"
|
||||
#include "IRutils.h"
|
||||
|
||||
// L GGGG
|
||||
// L G
|
||||
// L G GG
|
||||
// L G G
|
||||
// LLLLL GGG
|
||||
|
||||
// LG decode originally added by Darryl Smith (based on the JVC protocol)
|
||||
// LG send originally added by https://github.com/chaeplin
|
||||
|
||||
// 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)
|
||||
|
||||
#if (SEND_LG || DECODE_LG)
|
||||
// Calculate the rolling 4-bit wide checksum over all of the data.
|
||||
// Args:
|
||||
// data: The value to be checksum'ed.
|
||||
// Returns:
|
||||
// A 4-bit checksum.
|
||||
uint8_t calcLGChecksum(uint16_t data) {
|
||||
return(((data >> 12) + ((data >> 8) & 0xF) + ((data >> 4) & 0xF) +
|
||||
(data & 0xF)) & 0xF);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if SEND_LG
|
||||
// Send an LG formatted message.
|
||||
//
|
||||
// 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.
|
||||
// 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.
|
||||
void IRsend::sendLG(uint64_t data, uint16_t nbits, uint16_t repeat) {
|
||||
// Set IR carrier frequency
|
||||
enableIROut(38);
|
||||
|
||||
uint16_t repeatHeaderMark = 0;
|
||||
IRtimer usecTimer = IRtimer();
|
||||
|
||||
if (nbits >= LG32_BITS) {
|
||||
// LG 32bit protocol is near identical to Samsung except for repeats.
|
||||
sendSAMSUNG(data, nbits, 0); // Send it as a single Samsung message.
|
||||
repeatHeaderMark = LG32_RPT_HDR_MARK;
|
||||
repeat++;
|
||||
} else {
|
||||
// LG (28-bit) protocol.
|
||||
repeatHeaderMark = LG_HDR_MARK;
|
||||
// Header
|
||||
usecTimer.reset();
|
||||
mark(LG_HDR_MARK);
|
||||
space(LG_HDR_SPACE);
|
||||
// Data
|
||||
sendData(LG_BIT_MARK, LG_ONE_SPACE, LG_BIT_MARK, LG_ZERO_SPACE,
|
||||
data, nbits, true);
|
||||
// Footer
|
||||
mark(LG_BIT_MARK);
|
||||
space(std::max((uint32_t) (LG_MIN_MESSAGE_LENGTH - usecTimer.elapsed()),
|
||||
(uint32_t) LG_MIN_GAP));
|
||||
}
|
||||
|
||||
// Repeat
|
||||
// Protocol has a mandatory repeat-specific code sent after every command.
|
||||
for (uint16_t i = 0; i < repeat; i++) {
|
||||
usecTimer.reset();
|
||||
mark(repeatHeaderMark);
|
||||
space(LG_RPT_SPACE);
|
||||
mark(LG_BIT_MARK);
|
||||
space(std::max((uint32_t) LG_MIN_MESSAGE_LENGTH - usecTimer.elapsed(),
|
||||
(uint32_t) LG_MIN_GAP));
|
||||
}
|
||||
}
|
||||
|
||||
// Construct a raw 28-bit LG message from the supplied address & command.
|
||||
//
|
||||
// Args:
|
||||
// address: The address code.
|
||||
// command: The command code.
|
||||
// Returns:
|
||||
// A raw 28-bit LG message suitable for sendLG().
|
||||
//
|
||||
// Status: BETA / Should work.
|
||||
//
|
||||
// Notes:
|
||||
// e.g. Sequence of bits = address + command + checksum.
|
||||
uint32_t IRsend::encodeLG(uint16_t address, uint16_t command) {
|
||||
return ((address << 20) | (command << 4) | calcLGChecksum(command));
|
||||
}
|
||||
#endif
|
||||
|
||||
#if DECODE_LG
|
||||
// Decode the supplied LG message.
|
||||
// LG protocol has a repeat code which is 4 items long.
|
||||
// Even though the protocol has 28/32 bits of data, only 24/28 bits are
|
||||
// distinct.
|
||||
// In transmission order, the 28/32 bits are constructed as follows:
|
||||
// 8/12 bits of address + 16 bits of command + 4 bits of checksum.
|
||||
//
|
||||
// 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.
|
||||
// strict: Flag to indicate if we strictly adhere to the specification.
|
||||
// Returns:
|
||||
// boolean: True if it can decode it, false if it can't.
|
||||
//
|
||||
// Status: BETA / Should work.
|
||||
//
|
||||
// Note:
|
||||
// LG 32bit protocol appears near identical to the Samsung protocol.
|
||||
// They possibly differ on how they repeat and initial HDR mark.
|
||||
|
||||
// Ref:
|
||||
// https://funembedded.wordpress.com/2014/11/08/ir-remote-control-for-lg-conditioner-using-stm32f302-mcu-on-mbed-platform/
|
||||
bool IRrecv::decodeLG(decode_results *results, uint16_t nbits, bool strict) {
|
||||
if (results->rawlen < 2 * nbits + HEADER + FOOTER - 1 && results->rawlen != 4)
|
||||
return false; // Can't possibly be a valid LG message.
|
||||
if (strict && nbits != LG_BITS && nbits != LG32_BITS)
|
||||
return false; // Doesn't comply with expected LG protocol.
|
||||
|
||||
uint64_t data = 0;
|
||||
uint16_t offset = OFFSET_START;
|
||||
|
||||
// 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;
|
||||
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;
|
||||
|
||||
// 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);
|
||||
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))
|
||||
return false;
|
||||
if (offset < results->rawlen &&
|
||||
!matchAtLeast(results->rawbuf[offset], LG_MIN_GAP_TICKS * s_tick))
|
||||
return false;
|
||||
|
||||
// Repeat
|
||||
if (nbits >= LG32_BITS) {
|
||||
// If we are expecting the LG 32-bit protocol, there is always
|
||||
// a repeat message. So, check for it.
|
||||
#ifndef UNIT_TEST
|
||||
if (!matchSpace(results->rawbuf[offset], LG_MIN_GAP_TICKS * s_tick))
|
||||
#else
|
||||
if (!(matchSpace(results->rawbuf[offset],
|
||||
LG_MIN_MESSAGE_LENGTH_TICKS * s_tick) ||
|
||||
matchSpace(results->rawbuf[offset], 65500) ||
|
||||
matchSpace(results->rawbuf[offset], LG_MIN_GAP_TICKS * s_tick)))
|
||||
#endif // UNIT_TEST
|
||||
return false;
|
||||
offset++;
|
||||
if (!matchMark(results->rawbuf[offset++], LG32_RPT_HDR_MARK_TICKS * m_tick))
|
||||
return false;
|
||||
if (!matchSpace(results->rawbuf[offset++], LG_RPT_SPACE_TICKS * s_tick))
|
||||
return false;
|
||||
if (!matchMark(results->rawbuf[offset++], LG_BIT_MARK_TICKS * m_tick))
|
||||
return false;
|
||||
if (offset < results->rawlen &&
|
||||
!matchAtLeast(results->rawbuf[offset], LG_MIN_GAP_TICKS * s_tick))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Compliance
|
||||
uint16_t command = (data >> 4) & 0xFFFF; // The 16 bits before the checksum.
|
||||
|
||||
if (strict && (data & 0xF) != calcLGChecksum(command))
|
||||
return false; // The last 4 bits sent are the expected checksum.
|
||||
|
||||
// Success
|
||||
results->decode_type = LG;
|
||||
results->bits = nbits;
|
||||
results->value = data;
|
||||
results->command = command;
|
||||
results->address = data >> 20; // The bits before the command.
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright 2017 David Conran
|
||||
|
||||
#ifndef IR_LG_H_
|
||||
#define IR_LG_H_
|
||||
|
||||
// L GGGG
|
||||
// L G
|
||||
// L G GG
|
||||
// L G G
|
||||
// LLLLL GGG
|
||||
|
||||
#define __STDC_LIMIT_MACROS
|
||||
#include <stdint.h>
|
||||
|
||||
uint8_t calcLGChecksum(uint16_t data);
|
||||
|
||||
#endif // IR_LG_H_
|
||||
@@ -0,0 +1,349 @@
|
||||
// Copyright 2009 Ken Shirriff
|
||||
// Copyright 2017 David Conran
|
||||
|
||||
#include "ir_Mitsubishi.h"
|
||||
#include <algorithm>
|
||||
#include "IRrecv.h"
|
||||
#include "IRsend.h"
|
||||
#include "IRtimer.h"
|
||||
#include "IRutils.h"
|
||||
|
||||
// MMMMM IIIII TTTTT SSSS U U BBBB IIIII SSSS H H IIIII
|
||||
// M M M I T S U U B B I S H H I
|
||||
// M M M I T SSS U U BBBB I SSS HHHHH I
|
||||
// M M I T S U U B B I S H H I
|
||||
// M M IIIII T SSSS UUU BBBBB IIIII SSSS H H IIIII
|
||||
|
||||
// Mitsubishi (TV) decoding added from https://github.com/z3t0/Arduino-IRremote
|
||||
// Mitsubishi (TV) sending & Mitsubishi A/C support added by David Conran
|
||||
|
||||
// Constants
|
||||
// Mitsubishi TV
|
||||
// period time is 1/33000Hz = 30.303 uSeconds (T)
|
||||
// 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)
|
||||
|
||||
// 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
|
||||
|
||||
#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.
|
||||
// repeat: Nr. of additional times the message is to be sent.
|
||||
//
|
||||
// Status: ALPHA / untested.
|
||||
//
|
||||
// Notes:
|
||||
// This protocol appears to have no header.
|
||||
// Ref:
|
||||
// https://github.com/marcosamarinho/IRremoteESP8266/blob/master/ir_Mitsubishi.cpp
|
||||
// GlobalCache's Control Tower's Mitsubishi TV data.
|
||||
void IRsend::sendMitsubishi(uint64_t data, uint16_t nbits, uint16_t repeat) {
|
||||
enableIROut(33); // Set IR carrier frequency
|
||||
IRtimer usecTimer = IRtimer();
|
||||
|
||||
for (uint16_t i = 0; i <= repeat; i++) {
|
||||
usecTimer.reset();
|
||||
// No header
|
||||
|
||||
// Data
|
||||
sendData(MITSUBISHI_BIT_MARK, MITSUBISHI_ONE_SPACE,
|
||||
MITSUBISHI_BIT_MARK, MITSUBISHI_ZERO_SPACE,
|
||||
data, nbits, true);
|
||||
// Footer
|
||||
mark(MITSUBISHI_BIT_MARK);
|
||||
space(std::max(MITSUBISHI_MIN_COMMAND_LENGTH - usecTimer.elapsed(),
|
||||
MITSUBISHI_MIN_GAP));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if DECODE_MITSUBISHI
|
||||
// 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: BETA / previously working.
|
||||
//
|
||||
// Notes:
|
||||
// This protocol appears to have no header.
|
||||
//
|
||||
// Ref:
|
||||
// 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)
|
||||
return false; // Shorter than shortest possibly expected.
|
||||
if (strict && nbits != MITSUBISHI_BITS)
|
||||
return false; // Request is out of spec.
|
||||
|
||||
uint16_t offset = OFFSET_START;
|
||||
uint64_t data = 0;
|
||||
|
||||
// No Header
|
||||
// But try to auto-calibrate off the initial mark signal.
|
||||
if (!matchMark(results->rawbuf[offset], MITSUBISHI_BIT_MARK, 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;
|
||||
|
||||
// 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);
|
||||
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 (offset < results->rawlen &&
|
||||
!matchAtLeast(results->rawbuf[offset], MITSUBISHI_MIN_GAP_TICKS * tick))
|
||||
return false;
|
||||
|
||||
// Compliance
|
||||
if (actualBits < nbits)
|
||||
return false;
|
||||
if (strict && actualBits != nbits)
|
||||
return false; // Not as we expected.
|
||||
|
||||
// Success
|
||||
results->decode_type = MITSUBISHI;
|
||||
results->bits = actualBits;
|
||||
results->value = data;
|
||||
results->address = 0;
|
||||
results->command = 0;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if SEND_MITSUBISHI_AC
|
||||
// Send a Mitsubishi A/C message.
|
||||
//
|
||||
// Args:
|
||||
// data: An array of bytes containing the IR command.
|
||||
// nbytes: Nr. of bytes of data in the array. (>=MITSUBISHI_AC_STATE_LENGTH)
|
||||
// repeat: Nr. of times the message is to be repeated.
|
||||
// (Default = MITSUBISHI_AC_MIN_REPEAT).
|
||||
//
|
||||
// Status: BETA / Appears to be working.
|
||||
//
|
||||
void IRsend::sendMitsubishiAC(unsigned char data[], uint16_t nbytes,
|
||||
uint16_t repeat) {
|
||||
if (nbytes < MITSUBISHI_AC_STATE_LENGTH)
|
||||
return; // Not enough bytes to send a proper message.
|
||||
|
||||
// Set IR carrier frequency
|
||||
enableIROut(38);
|
||||
// Mitsubishi AC remote sends the packet twice.
|
||||
for (uint16_t r = 0; r <= repeat; r++) {
|
||||
// Header
|
||||
mark(MITSUBISHI_AC_HDR_MARK);
|
||||
space(MITSUBISHI_AC_HDR_SPACE);
|
||||
// Data
|
||||
for (uint16_t i = 0; i < nbytes; i++)
|
||||
sendData(MITSUBISHI_AC_BIT_MARK, MITSUBISHI_AC_ONE_SPACE,
|
||||
MITSUBISHI_AC_BIT_MARK, MITSUBISHI_AC_ZERO_SPACE,
|
||||
data[i], 8, false);
|
||||
// Footer
|
||||
mark(MITSUBISHI_AC_RPT_MARK);
|
||||
space(MITSUBISHI_AC_RPT_SPACE);
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
//
|
||||
// Warning: Consider this very alpha code. Seems to work, but not validated.
|
||||
//
|
||||
// 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();
|
||||
}
|
||||
|
||||
// Reset the state of the remote to a known good state/sequence.
|
||||
void IRMitsubishiAC::stateReset() {
|
||||
// The state of the IR remote in IR code form.
|
||||
// Known good state obtained from:
|
||||
// https://github.com/r45635/HVAC-IR-Control/blob/master/HVAC_ESP8266/HVAC_ESP8266.ino#L108
|
||||
// Note: Can't use the following because it requires -std=c++11
|
||||
// uint8_t known_good_state[MITSUBISHI_AC_STATE_LENGTH] = {
|
||||
// 0x23, 0xCB, 0x26, 0x01, 0x00, 0x20, 0x08, 0x06, 0x30, 0x45, 0x67, 0x00,
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F};
|
||||
remote_state[0] = 0x23;
|
||||
remote_state[1] = 0xCB;
|
||||
remote_state[2] = 0x26;
|
||||
remote_state[3] = 0x01;
|
||||
remote_state[4] = 0x00;
|
||||
remote_state[5] = 0x20;
|
||||
remote_state[6] = 0x08;
|
||||
remote_state[7] = 0x06;
|
||||
remote_state[8] = 0x30;
|
||||
remote_state[9] = 0x45;
|
||||
remote_state[10] = 0x67;
|
||||
for (uint8_t i = 11; i < MITSUBISHI_AC_STATE_LENGTH - 1; i++)
|
||||
remote_state[i] = 0;
|
||||
remote_state[MITSUBISHI_AC_STATE_LENGTH - 1] = 0x1F;
|
||||
checksum(); // Calculate the checksum
|
||||
}
|
||||
|
||||
// Configure the pin for output.
|
||||
void IRMitsubishiAC::begin() {
|
||||
_irsend.begin();
|
||||
}
|
||||
|
||||
// Send the current desired state to the IR LED.
|
||||
void IRMitsubishiAC::send() {
|
||||
checksum(); // Ensure correct checksum before sending.
|
||||
_irsend.sendMitsubishiAC(remote_state);
|
||||
}
|
||||
|
||||
// Return a pointer to the internal state date of the remote.
|
||||
uint8_t* IRMitsubishiAC::getRaw() {
|
||||
checksum();
|
||||
return remote_state;
|
||||
}
|
||||
|
||||
// Calculate the checksum for the current internal state of the remote.
|
||||
void IRMitsubishiAC::checksum() {
|
||||
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;
|
||||
}
|
||||
|
||||
// Set the requested power state of the A/C to off.
|
||||
void IRMitsubishiAC::on() {
|
||||
// state = ON;
|
||||
remote_state[5] |= MITSUBISHI_AC_POWER;
|
||||
}
|
||||
|
||||
// Set the requested power state of the A/C to off.
|
||||
void IRMitsubishiAC::off() {
|
||||
// state = OFF;
|
||||
remote_state[5] &= ~MITSUBISHI_AC_POWER;
|
||||
}
|
||||
|
||||
// Set the requested power state of the A/C.
|
||||
void IRMitsubishiAC::setPower(bool state) {
|
||||
if (state)
|
||||
on();
|
||||
else
|
||||
off();
|
||||
}
|
||||
|
||||
// Return the requested power state of the A/C.
|
||||
bool IRMitsubishiAC::getPower() {
|
||||
return((remote_state[5] & MITSUBISHI_AC_POWER) != 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;
|
||||
}
|
||||
|
||||
// Return the set temp. in deg C
|
||||
uint8_t IRMitsubishiAC::getTemp() {
|
||||
return(remote_state[7] + MITSUBISHI_AC_MIN_TEMP);
|
||||
}
|
||||
|
||||
// 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.
|
||||
remote_state[9] = 0b10000000 | (remote_state[9] & 0b01111000);
|
||||
return;
|
||||
} else if (fan >= MITSUBISHI_AC_FAN_MAX) {
|
||||
fan--; // There is no spoon^H^H^Heed 5 (max), pretend it doesn't exist.
|
||||
}
|
||||
remote_state[9] &= 0b01111000; // Clear the previous state
|
||||
remote_state[9] |= 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;
|
||||
return fan;
|
||||
}
|
||||
|
||||
// Return the requested climate operation mode of the a/c unit.
|
||||
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;
|
||||
}
|
||||
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 |= 0b1000;
|
||||
mode <<= 3;
|
||||
remote_state[9] &= 0b11000111; // Clear the previous setting.
|
||||
remote_state[9] |= mode;
|
||||
}
|
||||
|
||||
// Return the requested vane operation mode of the a/c unit.
|
||||
uint8_t IRMitsubishiAC::getVane() {
|
||||
return ((remote_state[9] & 0b00111000) >> 3);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright 2009 Ken Shirriff
|
||||
// Copyright 2017 David Conran
|
||||
#ifndef IR_MITSUBISHI_H_
|
||||
#define IR_MITSUBISHI_H_
|
||||
|
||||
#define __STDC_LIMIT_MACROS
|
||||
#include <stdint.h>
|
||||
#include "IRremoteESP8266.h"
|
||||
#include "IRsend.h"
|
||||
|
||||
// MMMMM IIIII TTTTT SSSS U U BBBB IIIII SSSS H H IIIII
|
||||
// M M M I T S U U B B I S H H I
|
||||
// M M M I T SSS U U BBBB I SSS HHHHH I
|
||||
// M M I T S U U B B I S H H I
|
||||
// M M IIIII T SSSS UUU BBBBB IIIII SSSS H H IIIII
|
||||
|
||||
// Mitsubishi (TV) decoding added from https://github.com/z3t0/Arduino-IRremote
|
||||
// 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
|
||||
|
||||
#if SEND_MITSUBISHI_AC
|
||||
class IRMitsubishiAC {
|
||||
public:
|
||||
explicit IRMitsubishiAC(uint16_t pin);
|
||||
|
||||
void stateReset();
|
||||
void send();
|
||||
void begin();
|
||||
void on();
|
||||
void off();
|
||||
void setPower(bool state);
|
||||
bool getPower();
|
||||
void setTemp(uint8_t temp);
|
||||
uint8_t getTemp();
|
||||
void setFan(uint8_t fan);
|
||||
uint8_t getFan();
|
||||
void setMode(uint8_t mode);
|
||||
uint8_t getMode();
|
||||
void setVane(uint8_t mode);
|
||||
uint8_t getVane();
|
||||
uint8_t* getRaw();
|
||||
|
||||
private:
|
||||
uint8_t remote_state[MITSUBISHI_AC_STATE_LENGTH];
|
||||
void checksum();
|
||||
IRsend _irsend;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#endif // IR_MITSUBISHI_H_
|
||||
@@ -0,0 +1,210 @@
|
||||
// Copyright 2009 Ken Shirriff
|
||||
// Copyright 2017 David Conran
|
||||
|
||||
#define __STDC_LIMIT_MACROS
|
||||
#include <stdint.h>
|
||||
#include <algorithm>
|
||||
#include "IRrecv.h"
|
||||
#include "IRsend.h"
|
||||
#include "IRtimer.h"
|
||||
#include "IRutils.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
|
||||
#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)
|
||||
// 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.
|
||||
// repeat: The number of times the command is to be repeated.
|
||||
//
|
||||
// Status: STABLE / Known working.
|
||||
//
|
||||
// Ref:
|
||||
// http://www.sbprojects.com/knowledge/ir/nec.php
|
||||
void IRsend::sendNEC(uint64_t data, uint16_t nbits, uint16_t repeat) {
|
||||
// Set 38kHz IR carrier frequency & a 1/3 (33%) duty cycle.
|
||||
enableIROut(38, 33);
|
||||
IRtimer usecs = IRtimer();
|
||||
// Header
|
||||
mark(NEC_HDR_MARK);
|
||||
space(NEC_HDR_SPACE);
|
||||
// Data
|
||||
sendData(NEC_BIT_MARK, NEC_ONE_SPACE, NEC_BIT_MARK, NEC_ZERO_SPACE,
|
||||
data, nbits, true);
|
||||
// Footer
|
||||
mark(NEC_BIT_MARK);
|
||||
// Gap to next command.
|
||||
space(std::max(NEC_MIN_GAP, NEC_MIN_COMMAND_LENGTH - usecs.elapsed()));
|
||||
|
||||
// Optional command repeat sequence.
|
||||
for (uint16_t i = 0; i < repeat; i++) {
|
||||
usecs.reset();
|
||||
mark(NEC_HDR_MARK);
|
||||
space(NEC_RPT_SPACE);
|
||||
mark(NEC_BIT_MARK);
|
||||
// Gap till next command.
|
||||
space(std::max(NEC_MIN_GAP, NEC_MIN_COMMAND_LENGTH - usecs.elapsed()));
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the raw NEC data based on address and command.
|
||||
// Args:
|
||||
// address: An address value.
|
||||
// command: An 8-bit command value.
|
||||
// Returns:
|
||||
// A raw 32-bit NEC message.
|
||||
//
|
||||
// Status: BETA / Expected to work.
|
||||
//
|
||||
// Ref:
|
||||
// http://www.sbprojects.com/knowledge/ir/nec.php
|
||||
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?
|
||||
address = reverseBits(address, 16);
|
||||
return ((address << 16) + command); // Extended.
|
||||
} else {
|
||||
address = reverseBits(address, 8);
|
||||
return (address << 24) + ((address ^ 0xFF) << 16) + command; // Normal.
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if (DECODE_NEC || DECODE_SHERWOOD || DECODE_AIWA_RC_T501 || DECODE_SANYO)
|
||||
// Decode the supplied NEC 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 NEC_BITS.
|
||||
// strict: Flag indicating if we should perform strict matching.
|
||||
// Returns:
|
||||
// boolean: True if it can decode it, false if it can't.
|
||||
//
|
||||
// Status: STABLE / Known good.
|
||||
//
|
||||
// Notes:
|
||||
// NEC protocol has three varients/forms.
|
||||
// Normal: a 8 bit address & a 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.
|
||||
// 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)
|
||||
return false; // Can't possibly be a valid NEC message.
|
||||
if (strict && nbits != NEC_BITS)
|
||||
return false; // Not strictly an NEC message.
|
||||
|
||||
uint64_t data = 0;
|
||||
uint16_t offset = OFFSET_START;
|
||||
|
||||
// Header
|
||||
if (!matchMark(results->rawbuf[offset], NEC_HDR_MARK)) return false;
|
||||
// Calculate how long the lowest tick time is based on the header mark.
|
||||
uint32_t mark_tick = results->rawbuf[offset++] * RAWTICK /
|
||||
NEC_HDR_MARK_TICKS;
|
||||
// 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;
|
||||
results->decode_type = NEC;
|
||||
results->bits = 0;
|
||||
results->address = 0;
|
||||
results->command = 0;
|
||||
results->repeat = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Header (cont.)
|
||||
if (!matchSpace(results->rawbuf[offset], NEC_HDR_SPACE)) return false;
|
||||
// Calculate how long the common tick time is based on the header space.
|
||||
uint32_t space_tick = results->rawbuf[offset++] * RAWTICK /
|
||||
NEC_HDR_SPACE_TICKS;
|
||||
// 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);
|
||||
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 (offset <= results->rawlen &&
|
||||
!matchAtLeast(results->rawbuf[offset], NEC_MIN_GAP_TICKS * space_tick))
|
||||
return false;
|
||||
|
||||
// Compliance
|
||||
// Calculate command and optionally enforce integrity checking.
|
||||
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.
|
||||
command = 0; // The command value isn't valid, so default to zero.
|
||||
}
|
||||
|
||||
// Success
|
||||
results->bits = nbits;
|
||||
results->value = data;
|
||||
results->decode_type = NEC;
|
||||
// NEC command and address are technically in LSB first order so the
|
||||
// final versions have to be reversed.
|
||||
results->command = reverseBits(command, 8);
|
||||
// Normal NEC protocol has an 8 bit address sent, followed by it inverted.
|
||||
uint8_t address = (data & 0xFF000000) >> 24;
|
||||
uint8_t address_inverted = (data & 0x00FF0000) >> 16;
|
||||
if (address == (address_inverted ^ 0xFF))
|
||||
// Inverted, so it is normal NEC protocol.
|
||||
results->address = reverseBits(address, 8);
|
||||
else // Not inverted, so must be Extended NEC protocol, thus 16 bit address.
|
||||
results->address = reverseBits((data >> 16) & UINT16_MAX, 16);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,196 @@
|
||||
// Copyright 2015 Kristian Lauszus
|
||||
// Copyright 2017 David Conran
|
||||
|
||||
#include <algorithm>
|
||||
#include "IRrecv.h"
|
||||
#include "IRsend.h"
|
||||
#include "IRtimer.h"
|
||||
#include "IRutils.h"
|
||||
|
||||
// PPPP AAA N N AAA SSSS OOO N N IIIII CCCC
|
||||
// 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 protocol originally added by Kristian Lauszus from:
|
||||
// https://github.com/z3t0/Arduino-IRremote
|
||||
// (Thanks to zenwheel and other people at the original blog post)
|
||||
|
||||
// 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 300UL
|
||||
#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))
|
||||
#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).
|
||||
// repeat: The number of times the command is to be repeated.
|
||||
//
|
||||
// Status: BETA / Should be working.
|
||||
//
|
||||
// Note:
|
||||
// This protocol is a modified version of Kaseikyo.
|
||||
void IRsend::sendPanasonic64(uint64_t data, uint16_t nbits, uint16_t repeat) {
|
||||
enableIROut(36700U); // Set IR carrier frequency of 36.7kHz.
|
||||
IRtimer usecTimer = IRtimer();
|
||||
|
||||
for (uint16_t i = 0; i <= repeat; i++) {
|
||||
usecTimer.reset();
|
||||
// Header
|
||||
mark(PANASONIC_HDR_MARK);
|
||||
space(PANASONIC_HDR_SPACE);
|
||||
// Data
|
||||
sendData(PANASONIC_BIT_MARK, PANASONIC_ONE_SPACE,
|
||||
PANASONIC_BIT_MARK, PANASONIC_ZERO_SPACE,
|
||||
data, nbits, true);
|
||||
// Footer
|
||||
mark(PANASONIC_BIT_MARK);
|
||||
space(std::max((uint32_t) PANASONIC_MIN_COMMAND_LENGTH -
|
||||
usecTimer.elapsed(),
|
||||
PANASONIC_MIN_GAP));
|
||||
}
|
||||
}
|
||||
|
||||
// Send a Panasonic formatted message.
|
||||
//
|
||||
// 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).
|
||||
// repeat: The number of times the command is to be repeated.
|
||||
//
|
||||
// Status: STABLE.
|
||||
//
|
||||
// Note:
|
||||
// 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);
|
||||
}
|
||||
|
||||
// Calculate the raw Panasonic data based on device, subdevice, & function.
|
||||
//
|
||||
// Args:
|
||||
// manufacturer: A 16-bit manufacturer code. e.g. 0x4004 is Panasonic.
|
||||
// device: An 8-bit code.
|
||||
// subdevice: An 8-bit code.
|
||||
// function: An 8-bit code.
|
||||
// Returns:
|
||||
// A raw uint64_t Panasonic message.
|
||||
//
|
||||
// Status: BETA / Should be working..
|
||||
//
|
||||
// Note:
|
||||
// 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) {
|
||||
uint8_t checksum = device ^ subdevice ^ function;
|
||||
return (((uint64_t) manufacturer << 32) |
|
||||
((uint64_t) device << 24) |
|
||||
((uint64_t) subdevice << 16) |
|
||||
((uint64_t) function << 8) |
|
||||
checksum);
|
||||
}
|
||||
#endif // (SEND_PANASONIC || SEND_DENON)
|
||||
|
||||
#if (DECODE_PANASONIC || DECODE_DENON)
|
||||
// Decode the supplied Panasonic message.
|
||||
//
|
||||
// Args:
|
||||
// results: Ptr to the data to decode and where to store the decode result.
|
||||
// nbits: Nr. of data bits to expect.
|
||||
// strict: Flag indicating if we should perform strict matching.
|
||||
// Returns:
|
||||
// boolean: True if it can decode it, false if it can't.
|
||||
//
|
||||
// Status: BETA / Should be working.
|
||||
// Note:
|
||||
// Panasonic 48-bit protocol is a modified version of Kaseikyo.
|
||||
// Ref:
|
||||
// http://www.remotecentral.com/cgi-bin/mboard/rc-pronto/thread.cgi?26152
|
||||
// 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)
|
||||
return false; // Not enough entries to be a Panasonic message.
|
||||
if (strict && nbits != PANASONIC_BITS)
|
||||
return false; // Request is out of spec.
|
||||
|
||||
uint64_t data = 0;
|
||||
uint16_t offset = OFFSET_START;
|
||||
|
||||
// Header
|
||||
if (!matchMark(results->rawbuf[offset], PANASONIC_HDR_MARK)) return false;
|
||||
// Calculate how long the common tick time is based on the header mark.
|
||||
uint32_t m_tick = results->rawbuf[offset++] * RAWTICK /
|
||||
PANASONIC_HDR_MARK_TICKS;
|
||||
if (!matchSpace(results->rawbuf[offset], PANASONIC_HDR_SPACE)) return false;
|
||||
// Calculate how long the common tick time is based on the header space.
|
||||
uint32_t s_tick = results->rawbuf[offset++] * RAWTICK /
|
||||
PANASONIC_HDR_SPACE_TICKS;
|
||||
|
||||
// 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);
|
||||
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))
|
||||
return false;
|
||||
if (offset < results->rawlen &&
|
||||
!matchAtLeast(results->rawbuf[offset], PANASONIC_END_GAP))
|
||||
return false;
|
||||
|
||||
// Compliance
|
||||
uint32_t address = data >> 32;
|
||||
uint32_t command = data & 0xFFFFFFFF;
|
||||
if (strict) {
|
||||
if (address != manufacturer) // Verify the Manufacturer code.
|
||||
return false;
|
||||
// Verify the checksum.
|
||||
uint8_t checksumOrig = data & 0xFF;
|
||||
uint8_t checksumCalc = ((data >> 24) ^ (data >> 16) ^ (data >> 8)) & 0xFF;
|
||||
if (checksumOrig != checksumCalc)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Success
|
||||
results->value = data;
|
||||
results->address = address;
|
||||
results->command = command;
|
||||
results->decode_type = PANASONIC;
|
||||
results->bits = nbits;
|
||||
return true;
|
||||
}
|
||||
#endif // (DECODE_PANASONIC || DECODE_DENON)
|
||||
@@ -0,0 +1,109 @@
|
||||
// Copyright 2017 David Conran
|
||||
|
||||
#include <algorithm>
|
||||
#include "IRsend.h"
|
||||
#include "IRtimer.h"
|
||||
|
||||
// PPPPPP tt
|
||||
// PP PP rr rr oooo nn nnn tt oooo
|
||||
// PPPPPP rrr r oo oo nnn nn tttt oo oo
|
||||
// PP rr oo oo nn nn tt oo oo
|
||||
// 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
|
||||
|
||||
#if SEND_PRONTO
|
||||
// Send a Pronto Code formatted message.
|
||||
//
|
||||
// Args:
|
||||
// data: An array of uint16_t containing the pronto codes.
|
||||
// len: Nr. of entries in the data[] array.
|
||||
// repeat: Nr. of times to repeat the message.
|
||||
//
|
||||
// Status: ALPHA / Not tested in the real world.
|
||||
//
|
||||
// Note:
|
||||
// Pronto codes are typically represented in hexadecimal.
|
||||
// You will need to convert the code to an array of integers, and calculate
|
||||
// it's length.
|
||||
// e.g.
|
||||
// A Sony 20 bit DVD remote command.
|
||||
// "0000 0067 0000 0015 0060 0018 0018 0018 0030 0018 0030 0018 0030 0018
|
||||
// 0018 0018 0030 0018 0018 0018 0018 0018 0030 0018 0018 0018 0030 0018
|
||||
// 0030 0018 0030 0018 0018 0018 0018 0018 0030 0018 0018 0018 0018 0018
|
||||
// 0030 0018 0018 03f6"
|
||||
//
|
||||
// converts to:
|
||||
//
|
||||
// uint16_t prontoCode[46] = {
|
||||
// 0x0000, 0x0067, 0x0000, 0x0015,
|
||||
// 0x0060, 0x0018, 0x0018, 0x0018, 0x0030, 0x0018, 0x0030, 0x0018,
|
||||
// 0x0030, 0x0018, 0x0018, 0x0018, 0x0030, 0x0018, 0x0018, 0x0018,
|
||||
// 0x0018, 0x0018, 0x0030, 0x0018, 0x0018, 0x0018, 0x0030, 0x0018,
|
||||
// 0x0030, 0x0018, 0x0030, 0x0018, 0x0018, 0x0018, 0x0018, 0x0018,
|
||||
// 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);
|
||||
//
|
||||
// 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;
|
||||
|
||||
// We only know how to deal with 'raw' pronto codes types. Reject all others.
|
||||
if (data[PRONTO_TYPE_OFFSET] != 0) return;
|
||||
|
||||
// Pronto frequency is in Hz.
|
||||
uint16_t hz = (uint16_t) (1000000U / (data[PRONTO_FREQ_OFFSET] *
|
||||
PRONTO_FREQ_FACTOR));
|
||||
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;
|
||||
// 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;
|
||||
|
||||
uint32_t periodic_time = calcUSecPeriod(hz, false);
|
||||
|
||||
// Normal (1st sequence) case.
|
||||
// Is there a first (normal) sequence to send?
|
||||
if (seq_1_len > 0) {
|
||||
// Check we have enough data to send the complete first sequence.
|
||||
if (seq_1_len + seq_1_start > len) return;
|
||||
// Send the contents of the 1st sequence.
|
||||
for (uint16_t i = seq_1_start; i < seq_1_start + seq_1_len; i += 2) {
|
||||
mark(data[i] * periodic_time);
|
||||
space(data[i + 1] * periodic_time);
|
||||
}
|
||||
} else {
|
||||
// There was no first sequence to send, it is implied that we have to send
|
||||
// the 2nd/repeat sequence an additional time. i.e. At least once.
|
||||
repeat++;
|
||||
}
|
||||
|
||||
// Repeat (2nd sequence) case.
|
||||
// Is there a second (repeat) sequence to be sent?
|
||||
if (seq_2_len > 0) {
|
||||
// Check we have enough data to send the complete second sequence.
|
||||
if (seq_2_len + seq_2_start > len) return;
|
||||
|
||||
// Send the contents of the 2nd sequence.
|
||||
for (uint16_t r = 0; r < repeat; r++)
|
||||
for (uint16_t i = seq_2_start; i < seq_2_start + seq_2_len; i += 2) {
|
||||
mark(data[i] * periodic_time);
|
||||
space(data[i + 1] * periodic_time);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,514 @@
|
||||
// Copyright 2009 Ken Shirriff
|
||||
// Copyright 2017 David Conran
|
||||
|
||||
#include <algorithm>
|
||||
#include "IRrecv.h"
|
||||
#include "IRsend.h"
|
||||
#include "IRtimer.h"
|
||||
#include "IRutils.h"
|
||||
|
||||
// RRRRRR CCCCC 555555 XX XX RRRRRR CCCCC 666
|
||||
// RR RR CC C 55 XX XX RR RR CC C 66
|
||||
// RRRRRR CC _____ 555555 XXXX RRRRRR CC _____ 666666
|
||||
// RR RR CC C 5555 XX XX RR RR CC C 66 66
|
||||
// RR RR CCCCC 555555 XX XX RR RR CCCCC 66666
|
||||
|
||||
// RC-5 & RC-6 support added from https://github.com/z3t0/Arduino-IRremote
|
||||
// RC-5X support added by David Conran
|
||||
|
||||
// Constants
|
||||
// RC-5/RC-5X
|
||||
// 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)
|
||||
// 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)
|
||||
|
||||
// Common (getRClevel())
|
||||
#define MARK 0U
|
||||
#define SPACE 1U
|
||||
|
||||
|
||||
#if SEND_RC5
|
||||
// Send a Philips RC-5/RC-5X packet.
|
||||
//
|
||||
// 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: RC-5 (stable), RC-5X (alpha)
|
||||
//
|
||||
// Note:
|
||||
// Caller needs to take care of flipping the toggle bit.
|
||||
// That bit differentiates between key press & key release.
|
||||
// For RC-5 it is the MSB of the data.
|
||||
// For RC-5X it is the 2nd MSB of the data.
|
||||
// Ref:
|
||||
// http://www.sbprojects.com/knowledge/ir/rc5.php
|
||||
// https://en.wikipedia.org/wiki/RC-5
|
||||
// https://en.wikipedia.org/wiki/Manchester_code
|
||||
// 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.
|
||||
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?
|
||||
// field bit is the inverted MSB of RC-5X data.
|
||||
field_bit = ((data >> (nbits - 1)) ^ 1) & 1;
|
||||
nbits--;
|
||||
}
|
||||
|
||||
IRtimer usecTimer = IRtimer();
|
||||
for (uint16_t i = 0; i <= repeat; i++) {
|
||||
usecTimer.reset();
|
||||
|
||||
// Header
|
||||
// First start bit (0x1). space, then mark.
|
||||
if (skipSpace)
|
||||
skipSpace = false; // First time through, we assume the leading space().
|
||||
else
|
||||
space(RC5_T1);
|
||||
mark(RC5_T1);
|
||||
// Field/Second start bit.
|
||||
if (field_bit) { // Send a 1. Normal for RC-5.
|
||||
space(RC5_T1);
|
||||
mark(RC5_T1);
|
||||
} else { // Send a 0. Special case for RC-5X. Means 7th command bit is 1.
|
||||
mark(RC5_T1);
|
||||
space(RC5_T1);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
// Footer
|
||||
space(std::max(RC5_MIN_GAP, RC5_MIN_COMMAND_LENGTH - usecTimer.elapsed()));
|
||||
}
|
||||
}
|
||||
|
||||
// Encode a Philips RC-5 data message.
|
||||
//
|
||||
// Args:
|
||||
// address: The 5-bit address value for the message.
|
||||
// command: The 6-bit command value for the message.
|
||||
// key_released: Boolean flag indicating if the remote key has been released.
|
||||
//
|
||||
// Returns:
|
||||
// A data message suitable for use in sendRC5().
|
||||
//
|
||||
// Status: Beta / Should be working.
|
||||
//
|
||||
// Ref:
|
||||
// http://www.sbprojects.com/knowledge/ir/rc5.php
|
||||
// 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) |
|
||||
(command & 0x3F);
|
||||
}
|
||||
|
||||
// Encode a Philips RC-5X data message.
|
||||
//
|
||||
// Args:
|
||||
// address: The 5-bit address value for the message.
|
||||
// command: The 7-bit command value for the message.
|
||||
// key_released: Boolean flag indicating if the remote key has been released.
|
||||
//
|
||||
// Returns:
|
||||
// A data message suitable for use in sendRC5().
|
||||
//
|
||||
// Status: Beta / Should be working.
|
||||
//
|
||||
// Ref:
|
||||
// http://www.sbprojects.com/knowledge/ir/rc5.php
|
||||
// https://en.wikipedia.org/wiki/RC-5
|
||||
uint16_t IRsend::encodeRC5X(uint8_t address, uint8_t command,
|
||||
bool key_released) {
|
||||
// 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);
|
||||
}
|
||||
|
||||
// Flip the toggle bit of a Philips RC-5/RC-5X data message.
|
||||
// Used to indicate a change of remote button's state.
|
||||
//
|
||||
// Args:
|
||||
// data: The existing RC-5/RC-5X message.
|
||||
//
|
||||
// Returns:
|
||||
// A data message suitable for use in sendRC5() with the toggle bit flipped.
|
||||
//
|
||||
// Status: STABLE.
|
||||
//
|
||||
// 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;
|
||||
}
|
||||
#endif // SEND_RC5
|
||||
|
||||
#if SEND_RC6
|
||||
// Flip the toggle bit of a Philips RC-6 data message.
|
||||
// Used to indicate a change of remote button's state.
|
||||
// For RC-6 (20-bits), it is the 17th least significant bit.
|
||||
// for RC-6 (36-bits/Xbox-360), it is the 16th least significant bit.
|
||||
//
|
||||
// Args:
|
||||
// data: The existing RC-6 message.
|
||||
// nbits: Nr. of bits in the RC-6 protocol.
|
||||
//
|
||||
// Returns:
|
||||
// A data message suitable for use in sendRC6() with the toggle bit flipped.
|
||||
//
|
||||
// Status: BETA / Should work fine.
|
||||
//
|
||||
// Ref:
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Encode a Philips RC-6 data message.
|
||||
//
|
||||
// Args:
|
||||
// address: The address (aka. control) value for the message.
|
||||
// Includes the field/mode/toggle bits.
|
||||
// command: The 8-bit command value for the message. (aka. information)
|
||||
// mode: Which protocol to use. Defined by nr. of bits in the protocol.
|
||||
//
|
||||
// Returns:
|
||||
// A data message suitable for use in sendRC6().
|
||||
//
|
||||
// Status: Beta / Should be working.
|
||||
//
|
||||
// Ref:
|
||||
// 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) {
|
||||
switch (mode) {
|
||||
case RC6_MODE0_BITS:
|
||||
return ((address & 0xFFF) << 8) | (command & 0xFF);
|
||||
case RC6_36_BITS:
|
||||
return ((uint64_t) (address & 0xFFFFFFF) << 8) | (command & 0xFF);
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Send a Philips RC-6 packet.
|
||||
// Note: Caller needs to take care of flipping the toggle bit (The 4th Most
|
||||
// Significant Bit). That bit differentiates between key press & key release.
|
||||
//
|
||||
// Args:
|
||||
// data: The message you wish to send.
|
||||
// nbits: Bit size of the protocol you want to send.
|
||||
// repeat: Nr. of extra times the data will be sent.
|
||||
//
|
||||
// Status: Stable.
|
||||
//
|
||||
// Ref:
|
||||
// http://www.sbprojects.com/knowledge/ir/rc6.php
|
||||
// http://www.righto.com/2010/12/64-bit-rc6-codes-arduino-and-xbox.html
|
||||
// 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;
|
||||
// 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);
|
||||
// Start bit.
|
||||
mark(RC6_TICK); // mark, then space == 0x1.
|
||||
space(RC6_TICK);
|
||||
// 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
|
||||
else
|
||||
bitTime = RC6_TICK; // Normal bit
|
||||
if (data & mask) { // 1
|
||||
mark(bitTime);
|
||||
space(bitTime);
|
||||
} else { // 0
|
||||
space(bitTime);
|
||||
mark(bitTime);
|
||||
}
|
||||
}
|
||||
// Footer
|
||||
space(RC6_RPT_LENGTH);
|
||||
}
|
||||
}
|
||||
#endif // SEND_RC6
|
||||
|
||||
#if (DECODE_RC5 || DECODE_RC6)
|
||||
// Gets one undecoded level at a time from the raw buffer.
|
||||
// The RC5/6 decoding is easier if the data is broken into time intervals.
|
||||
// E.g. if the buffer has MARK for 2 time intervals and SPACE for 1,
|
||||
// successive calls to getRClevel will return MARK, MARK, SPACE.
|
||||
// offset and used are updated to keep track of the current position.
|
||||
//
|
||||
// Args:
|
||||
// results: Ptr to the data to decode and where to store the decode result.
|
||||
// offset: Ptr to the currect offset to the rawbuf.
|
||||
// used: Ptr to the current used counter.
|
||||
// bitTime: Time interval of single bit in microseconds.
|
||||
// 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) {
|
||||
if (*offset >= results->rawlen)
|
||||
return SPACE; // After end of recorded buffer, assume SPACE.
|
||||
uint16_t width = results->rawbuf[*offset];
|
||||
// If the value of offset is odd, it's a MARK. Even, it's a SPACE.
|
||||
uint16_t val = ((*offset) % 2) ? MARK : SPACE;
|
||||
// Check to see if we have hit an inter-message gap (> 20ms).
|
||||
if (val == SPACE && width > 20000)
|
||||
return SPACE;
|
||||
int16_t correction = (val == MARK) ? MARK_EXCESS : -MARK_EXCESS;
|
||||
|
||||
// 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))
|
||||
avail = 3;
|
||||
else if (match(width, 2 * bitTime + correction))
|
||||
avail = 2;
|
||||
else if (match(width, bitTime + correction))
|
||||
avail = 1;
|
||||
else
|
||||
return -1; // The width is not what we expected.
|
||||
|
||||
(*used)++; // Count another one of the avail slots as used.
|
||||
if (*used >= avail) { // Are we out of look-ahead/avail slots?
|
||||
// Yes, so reset the used counter, and move the offset ahead.
|
||||
*used = 0;
|
||||
(*offset)++;
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
#endif // (DECODE_RC5 || DECODE_RC6)
|
||||
|
||||
#if DECODE_RC5
|
||||
// Decode the supplied RC-5/RC5X message.
|
||||
//
|
||||
// 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: RC-5 (stable), RC-5X (alpha)
|
||||
//
|
||||
// Note:
|
||||
// The 'toggle' bit is included as the 6th (MSB) address bit, the MSB of data,
|
||||
// & in the count of bits decoded.
|
||||
// Ref:
|
||||
// http://www.sbprojects.com/knowledge/ir/rc5.php
|
||||
// https://en.wikipedia.org/wiki/RC-5
|
||||
// https://en.wikipedia.org/wiki/Manchester_code
|
||||
// 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;
|
||||
|
||||
// Compliance
|
||||
if (strict && nbits != RC5_BITS && nbits != RC5X_BITS)
|
||||
return false; // It's neither RC-5 or RC-5X.
|
||||
|
||||
uint16_t offset = OFFSET_START;
|
||||
uint16_t used = 0;
|
||||
bool is_rc5x = false;
|
||||
uint64_t data = 0;
|
||||
|
||||
// Header
|
||||
// Get start bit #1.
|
||||
if (getRClevel(results, &offset, &used, RC5_T1) != MARK) return false;
|
||||
// Get field/start bit #2 (inverted bit-7 of the command if RC-5X protocol)
|
||||
uint16_t actual_bits = 1;
|
||||
int16_t levelA = getRClevel(results, &offset, &used, RC5_T1);
|
||||
int16_t levelB = getRClevel(results, &offset, &used, RC5_T1);
|
||||
if (levelA == SPACE && levelB == MARK) { // Matched a 1.
|
||||
is_rc5x = false;
|
||||
} else if (levelA == MARK && levelB == SPACE) { // Matched a 0.
|
||||
if (nbits <= RC5_BITS) return false; // Field bit must be '1' for RC5.
|
||||
is_rc5x = true;
|
||||
data = 1;
|
||||
} else {
|
||||
return false; // Not what we expected.
|
||||
}
|
||||
|
||||
// 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 == SPACE && levelB == MARK)
|
||||
data = (data << 1) | 1; // 1
|
||||
else if (levelA == MARK && levelB == SPACE)
|
||||
data <<= 1; // 0
|
||||
else
|
||||
break;
|
||||
}
|
||||
// Footer (None)
|
||||
|
||||
// Compliance
|
||||
if (actual_bits < nbits) return false; // Less data than we expected.
|
||||
if (strict && actual_bits != RC5_BITS &&
|
||||
actual_bits != RC5X_BITS) return false;
|
||||
|
||||
// Success
|
||||
results->value = data;
|
||||
results->address = (data >> 6) & 0x1F;
|
||||
results->command = data & 0x3F;
|
||||
results->repeat = false;
|
||||
if (is_rc5x) {
|
||||
results->decode_type = RC5X;
|
||||
results->command |= ((uint32_t) is_rc5x) << 6;
|
||||
} else {
|
||||
results->decode_type = RC5;
|
||||
actual_bits--; // RC5 doesn't count the field bit as data.
|
||||
}
|
||||
results->bits = actual_bits;
|
||||
return true;
|
||||
}
|
||||
#endif // DECODE_RC5
|
||||
|
||||
#if DECODE_RC6
|
||||
// Decode the supplied RC6 message.
|
||||
//
|
||||
// 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: Stable.
|
||||
//
|
||||
// Ref:
|
||||
// http://www.sbprojects.com/knowledge/ir/rc6.php
|
||||
// https://en.wikipedia.org/wiki/Manchester_code
|
||||
// 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.
|
||||
return false; // Smaller than absolute smallest possible RC6 message.
|
||||
|
||||
if (strict) { // Compliance
|
||||
// Unlike typical protocols, the ability to have mark+space, and space+mark
|
||||
// as data bits means it is possible to only have nbits of entries for the
|
||||
// data portion, rather than the typically required 2 * nbits.
|
||||
// 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)
|
||||
return false; // Don't have enough entries/samples to be valid.
|
||||
switch (nbits) {
|
||||
case RC6_MODE0_BITS:
|
||||
case RC6_36_BITS:
|
||||
break;
|
||||
default:
|
||||
return false; // Asking for the wrong number of bits.
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t offset = OFFSET_START;
|
||||
|
||||
// Header
|
||||
if (!matchMark(results->rawbuf[offset], RC6_HDR_MARK)) 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))
|
||||
return false;
|
||||
|
||||
uint16_t used = 0;
|
||||
|
||||
// Get the start bit. e.g. 1.
|
||||
if (getRClevel(results, &offset, &used, tick) != MARK) return false;
|
||||
if (getRClevel(results, &offset, &used, tick) != SPACE) return false;
|
||||
|
||||
uint16_t actual_bits;
|
||||
uint64_t data = 0;
|
||||
|
||||
// Data (Warning: Here be dragons^Wpointers!!)
|
||||
for (actual_bits = 0; offset < results->rawlen; actual_bits++) {
|
||||
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))
|
||||
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))
|
||||
return false;
|
||||
if (levelA == MARK && levelB == SPACE) // reversed compared to RC5
|
||||
data = (data << 1) | 1; // 1
|
||||
else if (levelA == SPACE && levelB == MARK)
|
||||
data <<= 1; // 0
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
// More compliance
|
||||
if (strict && actual_bits != nbits)
|
||||
return false; // Actual nr. of bits didn't match expected.
|
||||
|
||||
// Success
|
||||
results->decode_type = RC6;
|
||||
results->bits = actual_bits;
|
||||
results->value = data;
|
||||
results->address = data >> 8;
|
||||
results->command = data & 0xFF;
|
||||
return true;
|
||||
}
|
||||
#endif // DECODE_RC6
|
||||
@@ -0,0 +1,174 @@
|
||||
// Copyright 2017 David Conran
|
||||
|
||||
#include <algorithm>
|
||||
#include "IRrecv.h"
|
||||
#include "IRsend.h"
|
||||
#include "IRtimer.h"
|
||||
#include "IRutils.h"
|
||||
|
||||
// RRRRRR CCCCC MM MM MM MM
|
||||
// RR RR CC C MMM MMM MMM MMM
|
||||
// RRRRRR CC _____ MM MM MM MM MM MM
|
||||
// RR RR CC C MM MM MM MM
|
||||
// RR RR CCCCC MM MM MM MM
|
||||
|
||||
// Send & decode support for RC-MM added by David Conran
|
||||
|
||||
// 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
|
||||
// Use a tolerance of +/-10% when matching some data spaces.
|
||||
#define RCMM_TOLERANCE 10U
|
||||
#define RCMM_EXCESS 50U
|
||||
|
||||
#if SEND_RCMM
|
||||
// Send a Philips RC-MM packet.
|
||||
//
|
||||
// Args:
|
||||
// data: The data we want to send. MSB first.
|
||||
// nbits: The number of bits of data to send. (Typically 12, 24, or 32[Nokia])
|
||||
// repeat: The nr. of times the message should be sent.
|
||||
//
|
||||
// Status: BETA / Should be working.
|
||||
//
|
||||
// Ref:
|
||||
// http://www.sbprojects.com/knowledge/ir/rcmm.php
|
||||
void IRsend::sendRCMM(uint64_t data, uint16_t nbits, uint16_t repeat) {
|
||||
// Set 36kHz IR carrier frequency & a 1/3 (33%) duty cycle.
|
||||
enableIROut(36, 33);
|
||||
IRtimer usecs = IRtimer();
|
||||
|
||||
for (uint16_t r = 0; r <= repeat; r++) {
|
||||
usecs.reset();
|
||||
// Header
|
||||
mark(RCMM_HDR_MARK);
|
||||
space(RCMM_HDR_SPACE);
|
||||
// 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);
|
||||
// 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;
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if DECODE_RCMM
|
||||
// Decode a Philips RC-MM packet (between 12 & 32 bits) if possible.
|
||||
// Places successful decode information in the results pointer.
|
||||
// Args:
|
||||
// results: Ptr to the data to decode and where to store the decode result.
|
||||
// nbits: Nr. of bits to expect in the data portion. Typically RCMM_BITS.
|
||||
// strict: Flag to indicate if we strictly adhere to the specification.
|
||||
// Returns:
|
||||
// boolean: True if it can decode it, false if it can't.
|
||||
//
|
||||
// Status: BETA / Should be working.
|
||||
//
|
||||
// Ref:
|
||||
// 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;
|
||||
|
||||
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 - 4,
|
||||
(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 < 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;
|
||||
// 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;
|
||||
// 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;
|
||||
|
||||
// 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))
|
||||
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))
|
||||
data += 0;
|
||||
else if (match(results->rawbuf[offset], RCMM_BIT_SPACE_1_TICKS * s_tick,
|
||||
TOLERANCE))
|
||||
data += 1;
|
||||
else if (match(results->rawbuf[offset], RCMM_BIT_SPACE_2_TICKS * s_tick,
|
||||
RCMM_TOLERANCE))
|
||||
data += 2;
|
||||
else if (match(results->rawbuf[offset], RCMM_BIT_SPACE_3_TICKS * s_tick,
|
||||
RCMM_TOLERANCE))
|
||||
data += 3;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
// Footer decode
|
||||
if (!match(results->rawbuf[offset++], RCMM_BIT_MARK_TICKS * m_tick))
|
||||
return false;
|
||||
if (offset < results->rawlen &&
|
||||
!matchAtLeast(results->rawbuf[offset], RCMM_MIN_GAP_TICKS * s_tick))
|
||||
return false;
|
||||
|
||||
// Compliance
|
||||
if (strict && actualBits != nbits)
|
||||
return false;
|
||||
|
||||
// Success
|
||||
results->value = data;
|
||||
results->decode_type = RCMM;
|
||||
results->bits = actualBits;
|
||||
results->address = 0;
|
||||
results->command = 0;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,175 @@
|
||||
// Copyright 2009 Ken Shirriff
|
||||
// Copyright 2017 David Conran
|
||||
|
||||
#include <algorithm>
|
||||
#include "IRrecv.h"
|
||||
#include "IRsend.h"
|
||||
#include "IRtimer.h"
|
||||
#include "IRutils.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
|
||||
|
||||
// Samsung originally added from https://github.com/shirriff/Arduino-IRremote/
|
||||
|
||||
// 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)
|
||||
|
||||
|
||||
|
||||
#if SEND_SAMSUNG
|
||||
// Send a Samsung formatted message.
|
||||
// Samsung has a separate message to indicate a repeat, like NEC does.
|
||||
// TODO(crankyoldgit): Confirm that is actually how Samsung sends a repeat.
|
||||
// The refdoc doesn't indicate it is true.
|
||||
//
|
||||
// Args:
|
||||
// data: The message to be sent.
|
||||
// nbits: The bit size of the message being sent. typically SAMSUNG_BITS.
|
||||
// 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) {
|
||||
// Set 38kHz IR carrier frequency & a 1/3 (33%) duty cycle.
|
||||
enableIROut(38, 33);
|
||||
IRtimer usecTimer = IRtimer();
|
||||
// We always send a message, even for repeat=0, hence '<= repeat'.
|
||||
for (uint16_t i=0; i <= repeat; i++) {
|
||||
usecTimer.reset();
|
||||
// Header
|
||||
mark(SAMSUNG_HDR_MARK);
|
||||
space(SAMSUNG_HDR_SPACE);
|
||||
// Data
|
||||
sendData(SAMSUNG_BIT_MARK, SAMSUNG_ONE_SPACE, SAMSUNG_BIT_MARK,
|
||||
SAMSUNG_ZERO_SPACE, data, nbits, true);
|
||||
// Footer
|
||||
mark(SAMSUNG_BIT_MARK);
|
||||
space(std::max((uint32_t) SAMSUNG_MIN_GAP,
|
||||
(uint32_t) (SAMSUNG_MIN_MESSAGE_LENGTH -
|
||||
usecTimer.elapsed())));
|
||||
}
|
||||
}
|
||||
|
||||
// Construct a raw Samsung message from the supplied customer(address) &
|
||||
// command.
|
||||
//
|
||||
// Args:
|
||||
// customer: The customer code. (aka. Address)
|
||||
// command: The command code.
|
||||
// Returns:
|
||||
// A raw 32-bit Samsung message suitable for sendSAMSUNG().
|
||||
//
|
||||
// Status: BETA / Should be working.
|
||||
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));
|
||||
}
|
||||
#endif
|
||||
|
||||
#if DECODE_SAMSUNG
|
||||
// Decode the supplied Samsung message.
|
||||
// Samsung messages whilst 32 bits in size, only contain 16 bits of distinct
|
||||
// data. e.g. In transmition order:
|
||||
// customer_byte + customer_byte(same) + address_byte + invert(address_byte)
|
||||
//
|
||||
// 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.
|
||||
// strict: Flag to indicate if we strictly adhere to the specification.
|
||||
// Returns:
|
||||
// boolean: True if it can decode it, false if it can't.
|
||||
//
|
||||
// Status: STABLE
|
||||
//
|
||||
// Note:
|
||||
// LG 32bit protocol appears near identical to the Samsung protocol.
|
||||
// They differ on their compliance criteria and how they repeat.
|
||||
// Ref:
|
||||
// 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)
|
||||
return false; // Can't possibly be a valid Samsung message.
|
||||
if (strict && nbits != SAMSUNG_BITS)
|
||||
return false; // We expect Samsung to be 32 bits of message.
|
||||
|
||||
uint64_t data = 0;
|
||||
uint16_t offset = OFFSET_START;
|
||||
|
||||
// Header
|
||||
if (!matchMark(results->rawbuf[offset], SAMSUNG_HDR_MARK)) return false;
|
||||
// Calculate how long the common tick time is based on the header mark.
|
||||
uint32_t m_tick = results->rawbuf[offset++] * RAWTICK /
|
||||
SAMSUNG_HDR_MARK_TICKS;
|
||||
if (!matchSpace(results->rawbuf[offset], SAMSUNG_HDR_SPACE)) return false;
|
||||
// Calculate how long the common tick time is based on the header space.
|
||||
uint32_t s_tick = results->rawbuf[offset++] * RAWTICK /
|
||||
SAMSUNG_HDR_SPACE_TICKS;
|
||||
// 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);
|
||||
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))
|
||||
return false;
|
||||
if (offset < results->rawlen &&
|
||||
!matchAtLeast(results->rawbuf[offset], SAMSUNG_MIN_GAP_TICKS * s_tick))
|
||||
return false;
|
||||
|
||||
// Compliance
|
||||
|
||||
// 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;
|
||||
// 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;
|
||||
|
||||
// Success
|
||||
results->bits = nbits;
|
||||
results->value = data;
|
||||
results->decode_type = SAMSUNG;
|
||||
// command & address need to be reversed as they are transmitted LSB first,
|
||||
results->command = reverseBits(command, sizeof(command) * 8);
|
||||
results->address = reverseBits(address, sizeof(address) * 8);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,239 @@
|
||||
// Copyright 2009 Ken Shirriff
|
||||
// Copyright 2016 marcosamarinho
|
||||
// Copyright 2017 David Conran
|
||||
|
||||
#include <algorithm>
|
||||
#include "IRrecv.h"
|
||||
#include "IRsend.h"
|
||||
#include "IRtimer.h"
|
||||
|
||||
// SSSS AAA N N Y Y OOO
|
||||
// S A A NN N Y Y O O
|
||||
// SSS AAAAA N N N Y O O
|
||||
// S A A N NN Y O O
|
||||
// SSSS A A N N Y OOO
|
||||
|
||||
// Sanyo SA 8650B originally added from:
|
||||
// https://github.com/shirriff/Arduino-IRremote/
|
||||
// Sanyo LC7461 support originally by marcosamarinho
|
||||
|
||||
// Constants
|
||||
// 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
|
||||
// usually see 713 - not using ticks as get number wrapround
|
||||
#define SANYO_SA8650B_DOUBLE_SPACE_USECS 800U
|
||||
#define SANYO_SA8650B_RPT_LENGTH 45000U
|
||||
// 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)
|
||||
|
||||
#if SEND_SANYO
|
||||
// Construct a Sanyo LC7461 message.
|
||||
//
|
||||
// Args:
|
||||
// address: The 13 bit value of the address(Custom) portion of the protocol.
|
||||
// command: The 8 bit value of the command(Key) portion of the protocol.
|
||||
// Returns:
|
||||
// An uint64_t with the encoded raw 42 bit Sanyo LC7461 data value.
|
||||
//
|
||||
// Notes:
|
||||
// This protocol uses the NEC protocol timings. However, data is
|
||||
// formatted as : address(13 bits), !address, command(8 bits), !command.
|
||||
// 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;
|
||||
|
||||
uint64_t data = address;
|
||||
address ^= SANYO_LC7461_ADDRESS_MASK; // Invert the 13 LSBs.
|
||||
// Append the now inverted address.
|
||||
data = (data << SANYO_LC7461_ADDRESS_BITS) | address;
|
||||
// Append the command.
|
||||
data = (data << SANYO_LC7461_COMMAND_BITS) | command;
|
||||
command ^= SANYO_LC7461_COMMAND_MASK; // Invert the command.
|
||||
// Append the now inverted command.
|
||||
data = (data << SANYO_LC7461_COMMAND_BITS) | command;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
// Send a Sanyo LC7461 message.
|
||||
//
|
||||
// Args:
|
||||
// data: The contents of the command you want to send.
|
||||
// nbits: The bit size of the command being sent.
|
||||
// repeat: The number of times you want the command to be repeated.
|
||||
//
|
||||
// Status: BETA / Probably works.
|
||||
//
|
||||
// Notes:
|
||||
// Based on @marcosamarinho's work.
|
||||
// This protocol uses the NEC protocol timings. However, data is
|
||||
// formatted as : address(13 bits), !address, command (8 bits), !command.
|
||||
// According with LIRC, this protocol is used on Sanyo, Aiwa and Chinon
|
||||
// Information for this protocol is available at the Sanyo LC7461 datasheet.
|
||||
// Repeats are performed similar to the NEC method of sending a special
|
||||
// repeat message, rather than duplicating the entire message.
|
||||
// Ref:
|
||||
// https://github.com/marcosamarinho/IRremoteESP8266/blob/master/ir_Sanyo.cpp
|
||||
// http://pdf.datasheetcatalog.com/datasheet/sanyo/LC7461.pdf
|
||||
void IRsend::sendSanyoLC7461(uint64_t data, uint16_t nbits, uint16_t repeat) {
|
||||
// This protocol appears to be another 42-bit variant of the NEC protcol.
|
||||
sendNEC(data, nbits, repeat);
|
||||
}
|
||||
#endif // SEND_SANYO
|
||||
|
||||
#if DECODE_SANYO
|
||||
// Decode the supplied SANYO LC7461 message.
|
||||
//
|
||||
// Args:
|
||||
// results: Ptr to the data to decode and where to store the decode result.
|
||||
// nbits: Nr. of data bits to expect.
|
||||
// strict: Flag indicating if we should perform strict matching.
|
||||
// Returns:
|
||||
// boolean: True if it can decode it, false if it can't.
|
||||
//
|
||||
// Status: BETA / Probably works.
|
||||
//
|
||||
// Notes:
|
||||
// Based on @marcosamarinho's work.
|
||||
// This protocol uses the NEC protocol. However, data is
|
||||
// formatted as : address(13 bits), !address, command (8 bits), !command.
|
||||
// According with LIRC, this protocol is used on Sanyo, Aiwa and Chinon
|
||||
// Information for this protocol is available at the Sanyo LC7461 datasheet.
|
||||
// Ref:
|
||||
// http://slydiman.narod.ru/scr/kb/sanyo.htm
|
||||
// https://github.com/marcosamarinho/IRremoteESP8266/blob/master/ir_Sanyo.cpp
|
||||
// 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)
|
||||
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);
|
||||
// Bits 9 to 16.
|
||||
uint8_t command = (results->value >> SANYO_LC7461_COMMAND_BITS) &
|
||||
SANYO_LC7461_COMMAND_MASK;
|
||||
// Compliance
|
||||
if (strict) {
|
||||
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;
|
||||
// Bits 1-8.
|
||||
uint8_t inverted_command = results->value & SANYO_LC7461_COMMAND_MASK;
|
||||
if ((address ^ SANYO_LC7461_ADDRESS_MASK) != inverted_address)
|
||||
return false; // Address integrity check failed.
|
||||
if ((command ^ SANYO_LC7461_COMMAND_MASK) != inverted_command)
|
||||
return false; // Command integrity check failed.
|
||||
}
|
||||
|
||||
// Success
|
||||
results->decode_type = SANYO_LC7461;
|
||||
results->address = address;
|
||||
results->command = command;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* NOTE: Disabled due to poor quality.
|
||||
// Decode the supplied Sanyo SA 8650B 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: Depricated.
|
||||
//
|
||||
// NOTE: This decoder looks like rubbish. Only keeping it for compatibility
|
||||
// with the Arduino IRremote library. Seriously, don't trust it.
|
||||
// If someone has a device that this is supposed to be for, please log an
|
||||
// Issue on github with a rawData dump please. We should probably remove
|
||||
// it. We think this is a Sanyo decoder - serial = SA 8650B
|
||||
// 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)
|
||||
return false; // Shorter than shortest possible.
|
||||
if (strict && nbits != SANYO_SA8650B_BITS)
|
||||
return false; // Doesn't match the spec.
|
||||
|
||||
uint16_t offset = 0;
|
||||
|
||||
// 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) {
|
||||
results->bits = 0;
|
||||
results->value = REPEAT;
|
||||
results->decode_type = SANYO;
|
||||
results->address = 0;
|
||||
results->command = 0;
|
||||
results->repeat = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Header
|
||||
if (!matchMark(results->rawbuf[offset++], SANYO_SA8650B_HDR_MARK))
|
||||
return false;
|
||||
// NOTE: These next two lines look very wrong. Treat as suspect.
|
||||
if (!matchMark(results->rawbuf[offset++], SANYO_SA8650B_HDR_MARK))
|
||||
return false;
|
||||
// Data
|
||||
uint64_t data = 0;
|
||||
while (offset + 1 < results->rawlen) {
|
||||
if (!matchSpace(results->rawbuf[offset], SANYO_SA8650B_HDR_SPACE))
|
||||
break;
|
||||
offset++;
|
||||
if (matchMark(results->rawbuf[offset], SANYO_SA8650B_ONE_MARK))
|
||||
data = (data << 1) | 1; // 1
|
||||
else if (matchMark(results->rawbuf[offset], SANYO_SA8650B_ZERO_MARK))
|
||||
data <<= 1; // 0
|
||||
else
|
||||
return false;
|
||||
offset++;
|
||||
}
|
||||
|
||||
if (strict && SANYO_SA8650B_BITS > (offset - 1U) / 2U)
|
||||
return false;
|
||||
|
||||
// Success
|
||||
results->bits = (offset - 1) / 2;
|
||||
results->decode_type = SANYO;
|
||||
results->value = data;
|
||||
results->address = 0;
|
||||
results->command = 0;
|
||||
return true;
|
||||
}
|
||||
*/
|
||||
#endif // DECODE_SANYO
|
||||
@@ -0,0 +1,278 @@
|
||||
// Copyright 2009 Ken Shirriff
|
||||
// Copyright 2017 David Conran
|
||||
|
||||
#include <algorithm>
|
||||
#include "IRrecv.h"
|
||||
#include "IRsend.h"
|
||||
#include "IRtimer.h"
|
||||
#include "IRutils.h"
|
||||
|
||||
// SSSS H H AAA RRRR PPPP
|
||||
// S H H A A R R P P
|
||||
// SSS HHHHH AAAAA RRRR PPPP
|
||||
// S H H A A R R P
|
||||
// SSSS H H A A R R P
|
||||
|
||||
// Constants
|
||||
// period time = 1/38000Hz = 26.316 microseconds.
|
||||
// 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)
|
||||
|
||||
// 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)
|
||||
|
||||
#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.
|
||||
// repeat: Nr. of additional times the message is to be sent.
|
||||
//
|
||||
// Status: BETA / Previously working fine.
|
||||
//
|
||||
// Notes:
|
||||
// This procedure handles the inversion of bits required per protocol.
|
||||
// The protocol spec says to send the LSB first, but legacy code & usage
|
||||
// has us sending the MSB first. Grrrr. Normal invocation of encodeSharp()
|
||||
// handles this for you, assuming you are using the correct/standard values.
|
||||
// e.g. sendSharpRaw(encodeSharp(address, command));
|
||||
//
|
||||
// Ref:
|
||||
// http://www.sbprojects.com/knowledge/ir/sharp.htm
|
||||
// http://lirc.sourceforge.net/remotes/sharp/GA538WJSA
|
||||
// http://www.mwftr.com/ucF08/LEC14%20PIC%20IR.pdf
|
||||
// http://www.hifi-remote.com/johnsfine/DecodeIR.html#Sharp
|
||||
void IRsend::sendSharpRaw(uint64_t data, uint16_t nbits, uint16_t repeat) {
|
||||
// Set 38kHz IR carrier frequency & a 1/3 (33%) duty cycle.
|
||||
enableIROut(38, 33);
|
||||
|
||||
for (uint16_t i = 0; i <= repeat; i++) {
|
||||
// Protocol demands that the data be sent twice; once normally,
|
||||
// then with all but the address bits inverted.
|
||||
// Note: Previously this used to be performed 3 times (normal, inverted,
|
||||
// normal), however all data points to that being incorrect.
|
||||
for (uint8_t n = 0; n < 2; n++) {
|
||||
// No Header
|
||||
|
||||
// Data
|
||||
sendData(SHARP_BIT_MARK, SHARP_ONE_SPACE,
|
||||
SHARP_BIT_MARK, SHARP_ZERO_SPACE,
|
||||
data, nbits, true);
|
||||
// Footer
|
||||
mark(SHARP_BIT_MARK);
|
||||
space(SHARP_GAP);
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Encode a (raw) Sharp message from it's components.
|
||||
//
|
||||
// Args:
|
||||
// address: The value of the address to be sent.
|
||||
// command: The value of the address to be sent. (8 bits)
|
||||
// expansion: The value of the expansion bit to use. (0 or 1, typically 1)
|
||||
// check: The value of the check bit to use. (0 or 1, typically 0)
|
||||
// MSBfirst: Flag indicating MSB first or LSB first order. (Default: false)
|
||||
// Returns:
|
||||
// An uint32_t containing the raw Sharp message for sendSharpRaw().
|
||||
//
|
||||
// Status: BETA / Should work okay.
|
||||
//
|
||||
// Notes:
|
||||
// Assumes the standard Sharp bit sizes.
|
||||
// Historically sendSharp() sends address & command in
|
||||
// MSB first order. This is actually incorrect. It should be sent in LSB
|
||||
// order. The behaviour of sendSharp() hasn't been changed to maintain
|
||||
// backward compatibility.
|
||||
//
|
||||
// Ref:
|
||||
// http://www.sbprojects.com/knowledge/ir/sharp.htm
|
||||
// http://lirc.sourceforge.net/remotes/sharp/GA538WJSA
|
||||
// http://www.mwftr.com/ucF08/LEC14%20PIC%20IR.pdf
|
||||
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);
|
||||
expansion &= 1;
|
||||
check &= 1;
|
||||
|
||||
if (!MSBfirst) { // Correct bit order if needed.
|
||||
address = reverseBits(address, SHARP_ADDRESS_BITS);
|
||||
command = reverseBits(command, SHARP_COMMAND_BITS);
|
||||
}
|
||||
// Concatinate all the bits.
|
||||
return (address << (SHARP_COMMAND_BITS + 2)) | (command << 2) |
|
||||
(expansion << 1) | check;
|
||||
}
|
||||
|
||||
// Send a Sharp message
|
||||
//
|
||||
// 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.
|
||||
// repeat: Nr. of additional times the message is to be sent.
|
||||
//
|
||||
// Status: DEPRICATED / Previously working fine.
|
||||
//
|
||||
// Notes:
|
||||
// This procedure has a non-standard invocation style compared to similar
|
||||
// sendProtocol() routines. This is due to legacy, compatibility, & historic
|
||||
// reasons. Normally the calling syntax version is like sendSharpRaw().
|
||||
// This procedure transmits the address & command in MSB first order, which is
|
||||
// incorrect. This behaviour is left as-is to maintain backward
|
||||
// compatibility with legacy code.
|
||||
// In short, you should use sendSharpRaw(), encodeSharp(), and the correct
|
||||
// values of address & command instead of using this, & the wrong values.
|
||||
//
|
||||
// Ref:
|
||||
// http://www.sbprojects.com/knowledge/ir/sharp.htm
|
||||
// http://lirc.sourceforge.net/remotes/sharp/GA538WJSA
|
||||
// http://www.mwftr.com/ucF08/LEC14%20PIC%20IR.pdf
|
||||
void IRsend::sendSharp(uint16_t address, uint16_t command, uint16_t nbits,
|
||||
uint16_t repeat) {
|
||||
sendSharpRaw(encodeSharp(address, command, 1, 0, true), nbits, repeat);
|
||||
}
|
||||
#endif // (SEND_SHARP || SEND_DENON)
|
||||
|
||||
#if (DECODE_SHARP || DECODE_DENON)
|
||||
// Decode the supplied Sharp message.
|
||||
//
|
||||
// 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.
|
||||
// strict: Flag indicating if we should perform strict matching.
|
||||
// expansion: Should we expect the expansion bit to be set. Default is true.
|
||||
// Returns:
|
||||
// boolean: True if it can decode it, false if it can't.
|
||||
//
|
||||
// Status: BETA / Should work okay.
|
||||
//
|
||||
// Note:
|
||||
// This procedure returns a value suitable for use in sendSharpRaw().
|
||||
// TODO(crankyoldgit): Need to ensure capture of the inverted message as it can
|
||||
// be missed due to the interrupt timeout used to detect an end of message.
|
||||
// Several compliance checks are disabled until that is resolved.
|
||||
// Ref:
|
||||
// http://www.sbprojects.com/knowledge/ir/sharp.php
|
||||
// http://www.mwftr.com/ucF08/LEC14%20PIC%20IR.pdf
|
||||
// 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)
|
||||
return false; // Not enough entries to be a Sharp message.
|
||||
// Compliance
|
||||
if (strict) {
|
||||
if (nbits != SHARP_BITS)
|
||||
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;
|
||||
#endif
|
||||
}
|
||||
|
||||
uint64_t data = 0;
|
||||
uint16_t offset = OFFSET_START;
|
||||
|
||||
// No header
|
||||
// But try to auto-calibrate off the initial mark signal.
|
||||
if (!matchMark(results->rawbuf[offset], SHARP_BIT_MARK, 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;
|
||||
// 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))
|
||||
return false;
|
||||
if (matchSpace(results->rawbuf[offset], SHARP_ONE_SPACE_TICKS * tick))
|
||||
data = (data << 1) | 1; // 1
|
||||
else if (matchSpace(results->rawbuf[offset], SHARP_ZERO_SPACE_TICKS * tick))
|
||||
data <<= 1; // 0
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
// Footer
|
||||
if (!match(results->rawbuf[offset++], SHARP_BIT_MARK_TICKS * tick))
|
||||
return false;
|
||||
if (offset < results->rawlen &&
|
||||
!matchAtLeast(results->rawbuf[offset], SHARP_GAP_TICKS * tick))
|
||||
return false;
|
||||
|
||||
// Compliance
|
||||
if (strict) {
|
||||
// Check the state of the expansion bit is what we expect.
|
||||
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
|
||||
#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))
|
||||
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))
|
||||
return false;
|
||||
if (matchSpace(results->rawbuf[offset], SHARP_ONE_SPACE_TICKS * tick))
|
||||
second_data = (second_data << 1) | 1; // 1
|
||||
else if (matchSpace(results->rawbuf[offset],
|
||||
SHARP_ZERO_SPACE_TICKS * tick))
|
||||
second_data <<= 1; // 0
|
||||
else
|
||||
return false;
|
||||
}
|
||||
// Footer
|
||||
if (!match(results->rawbuf[offset++], SHARP_BIT_MARK_TICKS * tick))
|
||||
return false;
|
||||
if (offset < results->rawlen &&
|
||||
!matchAtLeast(results->rawbuf[offset], SHARP_GAP_TICKS * tick))
|
||||
return false;
|
||||
|
||||
// Check that second_data has been inverted correctly.
|
||||
if (data != (second_data ^ SHARP_TOGGLE_MASK))
|
||||
return false;
|
||||
#endif // UNIT_TEST
|
||||
}
|
||||
|
||||
// Success
|
||||
results->decode_type = SHARP;
|
||||
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);
|
||||
return true;
|
||||
}
|
||||
#endif // (DECODE_SHARP || DECODE_DENON)
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright 2017 David Conran
|
||||
|
||||
#include <algorithm>
|
||||
#include "IRsend.h"
|
||||
|
||||
// SSSSS HH HH EEEEEEE RRRRRR WW WW OOOOO OOOOO DDDDD
|
||||
// SS HH HH EE RR RR WW WW OO OO OO OO DD DD
|
||||
// SSSSS HHHHHHH EEEEE RRRRRR WW W WW OO OO OO OO DD DD
|
||||
// SS HH HH EE RR RR WW WWW WW OO OO OO OO DD DD
|
||||
// SSSSS HH HH EEEEEEE RR RR WW WW OOOO0 OOOO0 DDDDDD
|
||||
|
||||
#if SEND_SHERWOOD
|
||||
// Send an IR command to a Sherwood device.
|
||||
//
|
||||
// Args:
|
||||
// data: The contents of the command you want to send.
|
||||
// nbits: The bit size of the command being sent. (SHERWOOD_BITS)
|
||||
// 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));
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,197 @@
|
||||
// Copyright 2009 Ken Shirriff
|
||||
// Copyright 2016 marcosamarinho
|
||||
// Copyright 2017 David Conran
|
||||
|
||||
#include <algorithm>
|
||||
#include "IRrecv.h"
|
||||
#include "IRsend.h"
|
||||
#include "IRtimer.h"
|
||||
#include "IRutils.h"
|
||||
|
||||
// SSSS OOO N N Y Y
|
||||
// S O O NN N Y Y
|
||||
// SSS O O N N N Y
|
||||
// S O O N NN Y
|
||||
// SSSS OOO N N Y
|
||||
|
||||
// Sony originally added from https://github.com/shirriff/Arduino-IRremote/
|
||||
// Updates from marcosamarinho
|
||||
|
||||
// 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)
|
||||
|
||||
#if SEND_SONY
|
||||
// Send a Sony/SIRC(Serial Infra-Red Control) message.
|
||||
//
|
||||
// Args:
|
||||
// data: message to be sent.
|
||||
// nbits: Nr. of bits of the message to be sent.
|
||||
// repeat: Nr. of additional times the message is to be sent. (Default: 2)
|
||||
//
|
||||
// Status: STABLE / Known working.
|
||||
//
|
||||
// Notes:
|
||||
// sendSony() should typically be called with repeat=2 as Sony devices
|
||||
// expect the message to be sent at least 3 times.
|
||||
//
|
||||
// Ref:
|
||||
// http://www.sbprojects.com/knowledge/ir/sirc.php
|
||||
void IRsend::sendSony(uint64_t data, uint16_t nbits, uint16_t repeat) {
|
||||
// Sony devices use a 40kHz IR carrier frequency & a 1/3 (33%) duty cycle.
|
||||
enableIROut(40, 33);
|
||||
IRtimer usecs = IRtimer();
|
||||
|
||||
for (uint16_t i = 0; i <= repeat; i++) { // Typically loop 3 or more times.
|
||||
usecs.reset();
|
||||
// Header
|
||||
mark(SONY_HDR_MARK);
|
||||
space(SONY_SPACE);
|
||||
// Data
|
||||
sendData(SONY_ONE_MARK, SONY_SPACE, SONY_ZERO_MARK, SONY_SPACE,
|
||||
data, nbits, true);
|
||||
// Footer
|
||||
// The Sony protocol requires us to wait 45ms from start of a code to the
|
||||
// start of the next one. A 10ms minimum gap is also required.
|
||||
space(std::max(SONY_MIN_GAP, SONY_RPT_LENGTH - usecs.elapsed()));
|
||||
}
|
||||
// A space() is always performed last, so no need to turn off the LED.
|
||||
}
|
||||
|
||||
// Convert Sony/SIRC command, address, & extended bits into sendSony format.
|
||||
// Args:
|
||||
// nbits: Sony protocol bit size.
|
||||
// command: Sony command bits.
|
||||
// address: Sony address bits.
|
||||
// extended: Sony extended bits.
|
||||
// Returns:
|
||||
// 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 result = 0;
|
||||
switch (nbits) {
|
||||
case 12: // 5 address bits.
|
||||
result = address & 0x1F;
|
||||
break;
|
||||
case 15: // 8 address bits.
|
||||
result = address & 0xFF;
|
||||
break;
|
||||
case 20: // 5 address bits, 8 extended bits.
|
||||
result = address & 0x1F;
|
||||
result |= (extended & 0xFF) << 5;
|
||||
break;
|
||||
default:
|
||||
return 0; // This is not an expected Sony bit size/protocol.
|
||||
}
|
||||
result = (result << 7) | (command & 0x7F); // All sizes have 7 command bits.
|
||||
return reverseBits(result, nbits); // sendSony uses reverse ordered bits.
|
||||
}
|
||||
#endif
|
||||
|
||||
#if DECODE_SONY
|
||||
// Decode the supplied Sony/SIRC message.
|
||||
//
|
||||
// Args:
|
||||
// results: Ptr to the data to decode and where to store the decode result.
|
||||
// nbits: The number of data bits to expect.
|
||||
// strict: Flag indicating if we should perform strict matching.
|
||||
// Returns:
|
||||
// boolean: True if it can decode it, false if it can't.
|
||||
//
|
||||
// Status: BETA / Should be working. strict mode is ALPHA / Untested.
|
||||
//
|
||||
// Notes:
|
||||
// SONY protocol, SIRC (Serial Infra-Red Control) can be 12,15,20 bits long.
|
||||
// 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)
|
||||
return false; // Message is smaller than we expected.
|
||||
|
||||
// Compliance
|
||||
if (strict) {
|
||||
switch (nbits) { // Check we've been called with a correct bit size.
|
||||
case 12:
|
||||
case 15:
|
||||
case 20:
|
||||
break;
|
||||
default:
|
||||
return false; // The request doesn't strictly match the protocol defn.
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t data = 0;
|
||||
uint16_t offset = OFFSET_START;
|
||||
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;
|
||||
// Calculate how long the common tick time is based on the header mark.
|
||||
uint32_t tick = results->rawbuf[offset++] * RAWTICK / SONY_HDR_MARK_TICKS;
|
||||
|
||||
// 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))
|
||||
break; // Found a repeat space.
|
||||
timeSoFar += results->rawbuf[offset] * RAWTICK;
|
||||
if (!matchSpace(results->rawbuf[offset++], SONY_SPACE_TICKS * tick))
|
||||
return false;
|
||||
timeSoFar += results->rawbuf[offset] * RAWTICK;
|
||||
if (matchMark(results->rawbuf[offset], SONY_ONE_MARK_TICKS * tick))
|
||||
data = (data << 1) | 1;
|
||||
else if (matchMark(results->rawbuf[offset], SONY_ZERO_MARK_TICKS * tick))
|
||||
data <<= 1;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
// No Footer for Sony.
|
||||
|
||||
// Compliance
|
||||
if (strict && actualBits != nbits)
|
||||
return false; // We got the wrong number of bits.
|
||||
|
||||
// Success
|
||||
results->bits = actualBits;
|
||||
results->value = data;
|
||||
results->decode_type = SONY;
|
||||
// Message comes in LSB first. Convert ot MSB first.
|
||||
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.
|
||||
results->command = data & 0x7F; // Bits 0-6
|
||||
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
|
||||
break;
|
||||
default: // Shouldn't happen, but just in case.
|
||||
results->address = 0;
|
||||
results->command = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,151 @@
|
||||
// Copyright 2017 stufisher
|
||||
|
||||
#include "ir_Trotec.h"
|
||||
#include "IRremoteESP8266.h"
|
||||
#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
|
||||
|
||||
#if SEND_TROTEC
|
||||
|
||||
void IRsend::sendTrotec(unsigned char data[], uint16_t nbytes,
|
||||
uint16_t repeat) {
|
||||
if (nbytes < TROTEC_COMMAND_LENGTH)
|
||||
return;
|
||||
|
||||
enableIROut(36);
|
||||
|
||||
for (uint16_t r = 0; r <= repeat; r++) {
|
||||
// Header
|
||||
mark(TROTEC_HDR_MARK);
|
||||
space(TROTEC_HDR_SPACE);
|
||||
|
||||
// Data
|
||||
for (uint16_t i = 0; i < nbytes; i++)
|
||||
sendData(TROTEC_ONE_MARK, TROTEC_ONE_SPACE, TROTEC_ZERO_MARK,
|
||||
TROTEC_ZERO_SPACE, data[i], 8, false);
|
||||
|
||||
// Footer
|
||||
mark(TROTEC_ONE_MARK);
|
||||
space(TROTEC_GAP);
|
||||
mark(TROTEC_ONE_MARK);
|
||||
space(TROTEC_GAP_END);
|
||||
}
|
||||
}
|
||||
|
||||
IRTrotecESP::IRTrotecESP(uint16_t pin) : _irsend(pin) {
|
||||
stateReset();
|
||||
}
|
||||
|
||||
void IRTrotecESP::begin() {
|
||||
_irsend.begin();
|
||||
}
|
||||
|
||||
void IRTrotecESP::send() {
|
||||
checksum();
|
||||
_irsend.sendTrotec(trotec);
|
||||
}
|
||||
|
||||
void IRTrotecESP::checksum() {
|
||||
uint8_t sum = 0;
|
||||
uint8_t i;
|
||||
|
||||
for (i = 2; i < 8; i++) sum += trotec[i];
|
||||
|
||||
trotec[8] = sum & 0xFF;
|
||||
}
|
||||
|
||||
void IRTrotecESP::stateReset() {
|
||||
for (uint8_t i = 2; i < TROTEC_COMMAND_LENGTH; i++)
|
||||
trotec[i] = 0x0;
|
||||
|
||||
trotec[0] = TROTEC_INTRO1;
|
||||
trotec[1] = TROTEC_INTRO2;
|
||||
|
||||
setPower(false);
|
||||
setTemp(TROTEC_DEF_TEMP);
|
||||
setSpeed(TROTEC_FAN_MED);
|
||||
setMode(TROTEC_AUTO);
|
||||
}
|
||||
|
||||
uint8_t* IRTrotecESP::getRaw() {
|
||||
checksum();
|
||||
return trotec;
|
||||
}
|
||||
|
||||
void IRTrotecESP::setPower(bool state) {
|
||||
if (state)
|
||||
trotec[2] |= (TROTEC_ON << 3);
|
||||
else
|
||||
trotec[2] &= ~(TROTEC_ON << 3);
|
||||
}
|
||||
|
||||
uint8_t IRTrotecESP::getPower() {
|
||||
return trotec[2] & (TROTEC_ON << 3);
|
||||
}
|
||||
|
||||
void IRTrotecESP::setSpeed(uint8_t speed) {
|
||||
trotec[2] = (trotec[2] & 0xcf) | (speed << 4);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
trotec[3] = (trotec[3] & 0x80) | (temp - TROTEC_MIN_TEMP);
|
||||
}
|
||||
|
||||
uint8_t IRTrotecESP::getTemp() {
|
||||
return trotec[3] & 0x7f;
|
||||
}
|
||||
|
||||
void IRTrotecESP::setSleep(bool sleep) {
|
||||
if (sleep)
|
||||
trotec[3] |= (TROTEC_SLEEP_ON << 7);
|
||||
else
|
||||
trotec[3] &= ~(TROTEC_SLEEP_ON << 7);
|
||||
}
|
||||
|
||||
bool IRTrotecESP::getSleep(void) {
|
||||
return trotec[3] & (TROTEC_SLEEP_ON << 7);
|
||||
}
|
||||
|
||||
void IRTrotecESP::setTimer(uint8_t timer) {
|
||||
if (timer > TROTEC_MAX_TIMER) timer = TROTEC_MAX_TIMER;
|
||||
|
||||
if (timer) {
|
||||
trotec[5] |= (TROTEC_TIMER_ON << 6);
|
||||
trotec[6] = timer;
|
||||
} else {
|
||||
trotec[5] &= ~(TROTEC_TIMER_ON << 6);
|
||||
trotec[6] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t IRTrotecESP::getTimer() {
|
||||
return trotec[6];
|
||||
}
|
||||
|
||||
#endif // SEND_TROTEC
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright 2017 stufisher
|
||||
|
||||
#ifndef IR_TROTEC_H_
|
||||
#define IR_TROTEC_H_
|
||||
|
||||
#include "IRremoteESP8266.h"
|
||||
#include "IRsend.h"
|
||||
|
||||
// Constants
|
||||
// Byte 0
|
||||
#define TROTEC_INTRO1 0x12
|
||||
|
||||
// Byte 1
|
||||
#define TROTEC_INTRO2 0x34
|
||||
|
||||
// Byte 2
|
||||
#define TROTEC_AUTO 0
|
||||
#define TROTEC_COOL 1
|
||||
#define TROTEC_DRY 2
|
||||
#define TROTEC_FAN 3
|
||||
|
||||
#define TROTEC_ON 1
|
||||
#define TROTEC_OFF 0
|
||||
|
||||
#define TROTEC_FAN_LOW 1
|
||||
#define TROTEC_FAN_MED 2
|
||||
#define TROTEC_FAN_HIGH 3
|
||||
|
||||
// Byte 3
|
||||
#define TROTEC_MIN_TEMP 18
|
||||
#define TROTEC_MAX_TEMP 32
|
||||
#define TROTEC_DEF_TEMP 25
|
||||
|
||||
#define TROTEC_SLEEP_ON 1
|
||||
|
||||
// Byte 5
|
||||
#define TROTEC_TIMER_ON 1
|
||||
|
||||
// Byte 6
|
||||
#define TROTEC_MIN_TIMER 0
|
||||
#define TROTEC_MAX_TIMER 23
|
||||
|
||||
#if SEND_TROTEC
|
||||
|
||||
class IRTrotecESP {
|
||||
public:
|
||||
explicit IRTrotecESP(uint16_t pin);
|
||||
|
||||
void send();
|
||||
void begin();
|
||||
|
||||
void setPower(bool state);
|
||||
uint8_t getPower();
|
||||
|
||||
void setTemp(uint8_t temp);
|
||||
uint8_t getTemp();
|
||||
|
||||
void setSpeed(uint8_t fan);
|
||||
uint8_t getSpeed();
|
||||
|
||||
uint8_t getMode();
|
||||
void setMode(uint8_t mode);
|
||||
|
||||
bool getSleep();
|
||||
void setSleep(bool sleep);
|
||||
|
||||
uint8_t getTimer();
|
||||
void setTimer(uint8_t timer);
|
||||
|
||||
uint8_t* getRaw();
|
||||
|
||||
private:
|
||||
uint8_t trotec[TROTEC_COMMAND_LENGTH];
|
||||
void stateReset();
|
||||
void checksum();
|
||||
IRsend _irsend;
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif // IR_TROTEC_H_
|
||||
@@ -0,0 +1,144 @@
|
||||
// Copyright 2009 Ken Shirriff
|
||||
// Copyright 2017 David Conran
|
||||
|
||||
#include <algorithm>
|
||||
#include "IRrecv.h"
|
||||
#include "IRsend.h"
|
||||
#include "IRtimer.h"
|
||||
#include "IRutils.h"
|
||||
|
||||
// W W H H Y Y N N TTTTT EEEEE RRRRR
|
||||
// W W H H Y Y NN N T E R R
|
||||
// W W W HHHHH Y N N N T EEE RRRR
|
||||
// W W W H H Y N NN T E R R
|
||||
// WWW H H Y N N T EEEEE R R
|
||||
|
||||
// Whynter A/C ARC-110WD added by Francesco Meschia
|
||||
// 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)
|
||||
|
||||
#if SEND_WHYNTER
|
||||
// Send a Whynter message.
|
||||
//
|
||||
// Args:
|
||||
// data: message to be sent.
|
||||
// nbits: Nr. of bits of the message to be sent.
|
||||
// repeat: Nr. of additional times the message is to be sent.
|
||||
//
|
||||
// Status: STABLE
|
||||
//
|
||||
// Ref:
|
||||
// https://github.com/z3t0/Arduino-IRremote/blob/master/ir_Whynter.cpp
|
||||
void IRsend::sendWhynter(uint64_t data, uint16_t nbits, uint16_t repeat) {
|
||||
// Set IR carrier frequency
|
||||
enableIROut(38);
|
||||
IRtimer usecTimer = IRtimer();
|
||||
|
||||
for (uint16_t i = 0; i <= repeat; i++) {
|
||||
usecTimer.reset();
|
||||
// Header
|
||||
mark(WHYNTER_BIT_MARK);
|
||||
space(WHYNTER_ZERO_SPACE);
|
||||
mark(WHYNTER_HDR_MARK);
|
||||
space(WHYNTER_HDR_SPACE);
|
||||
// Data
|
||||
sendData(WHYNTER_BIT_MARK, WHYNTER_ONE_SPACE, WHYNTER_BIT_MARK,
|
||||
WHYNTER_ZERO_SPACE, data, nbits, true);
|
||||
// Footer
|
||||
mark(WHYNTER_BIT_MARK);
|
||||
space(std::max(WHYNTER_MIN_COMMAND_LENGTH - usecTimer.elapsed(),
|
||||
WHYNTER_MIN_GAP));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if DECODE_WHYNTER
|
||||
// Decode the supplied Whynter message.
|
||||
//
|
||||
// Args:
|
||||
// results: Ptr to the data to decode and where to store the decode result.
|
||||
// nbits: Nr. of data bits to expect.
|
||||
// strict: Flag indicating if we should perform strict matching.
|
||||
// Returns:
|
||||
// boolean: True if it can decode it, false if it can't.
|
||||
//
|
||||
// Status: BETA Strict mode is ALPHA.
|
||||
//
|
||||
// Ref:
|
||||
// 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.
|
||||
|
||||
// Compliance
|
||||
if (strict && nbits != WHYNTER_BITS)
|
||||
return false; // Incorrect nr. of bits per spec.
|
||||
|
||||
uint16_t offset = OFFSET_START;
|
||||
|
||||
// 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;
|
||||
// Main header mark and space
|
||||
if (!matchMark(results->rawbuf[offset], WHYNTER_HDR_MARK)) return false;
|
||||
// Calculate how long the common tick time is based on the header mark.
|
||||
uint32_t m_tick = results->rawbuf[offset++] * RAWTICK /
|
||||
WHYNTER_HDR_MARK_TICKS;
|
||||
if (!matchSpace(results->rawbuf[offset], WHYNTER_HDR_SPACE)) return false;
|
||||
// Calculate how long the common tick time is based on the header space.
|
||||
uint32_t s_tick = results->rawbuf[offset++] * RAWTICK /
|
||||
WHYNTER_HDR_SPACE_TICKS;
|
||||
|
||||
// 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);
|
||||
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))
|
||||
return false;
|
||||
if (offset < results->rawlen &&
|
||||
!matchAtLeast(results->rawbuf[offset], WHYNTER_MIN_GAP_TICKS * s_tick))
|
||||
return false;
|
||||
|
||||
// Success
|
||||
results->decode_type = WHYNTER;
|
||||
results->bits = nbits;
|
||||
results->value = data;
|
||||
results->address = 0;
|
||||
results->command = 0;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
Reference in new issue
Block a user