Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Experimental support for Lutron protocol. #516

Merged
merged 3 commits into from
Sep 15, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions examples/IRMQTTServer/IRMQTTServer.ino
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ const uint32_t kMqttReconnectTime = 5000; // Delay(ms) between reconnect tries.
#define argBits "bits"
#define argRepeat "repeats"

#define _MY_VERSION_ "v0.5.4"
#define _MY_VERSION_ "v0.5.5"

#if IR_LED != 1 // Disable debug output if the LED is on the TX (D1) pin.
#undef DEBUG
Expand Down Expand Up @@ -320,10 +320,11 @@ void handleRoot() {
"<option value='15'>Coolix</option>"
"<option value='17'>Denon</option>"
"<option value='13'>Dish</option>"
"<option value='41'>GICable</option>"
"<option value='43'>GICable</option>"
"<option value='6'>JVC</option>"
"<option value='10'>LG</option>"
"<option value='36'>Lasertag</option>"
"<option value='47'>Lutron</option>"
"<option value='35'>MagiQuest</option>"
"<option value='34'>Midea</option>"
"<option value='12'>Mitsubishi</option>"
Expand Down Expand Up @@ -359,6 +360,7 @@ void handleRoot() {
"<option value='24'>24</option>"
"<option value='28'>28</option>"
"<option value='32'>32</option>"
"<option value='35'>35</option>"
"<option value='36'>36</option>"
"<option value='48'>48</option>"
"<option value='56'>56</option>"
Expand Down Expand Up @@ -1271,6 +1273,13 @@ void sendIRCode(int const ir_type, uint64_t const code, char const * code_str,
repeat = std::max(repeat, kGicableMinRepeat);
irsend.sendGICable(code, bits, repeat);
break;
#endif
#if SEND_LUTRON
case LUTRON: // 47
if (bits == 0)
bits = kLutronBits;
irsend.sendLutron(code, bits, repeat);
break;
#endif
}
sendReqCounter++;
Expand Down
5 changes: 5 additions & 0 deletions src/IRrecv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,11 @@ bool IRrecv::decode(decode_results *results, irparams_t *save) {
if (decodeSamsungAC(results))
return true;
#endif
#if DECODE_LUTRON
DPRINTLN("Attempting Lutron decode");
if (decodeLutron(results))
return true;
#endif
#if DECODE_HASH
// decodeHash returns a hash on any input.
// Thus, it needs to be last in the list.
Expand Down
4 changes: 4 additions & 0 deletions src/IRrecv.h
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,10 @@ class IRrecv {
bool decodeWhirlpoolAC(decode_results *results,
uint16_t nbits = kWhirlpoolAcBits, bool strict = true);
#endif
#if DECODE_LUTRON
bool decodeLutron(decode_results *results, uint16_t nbits = kLutronBits,
bool strict = true);
#endif
};

#endif // IRRECV_H_
7 changes: 6 additions & 1 deletion src/IRremoteESP8266.h
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,9 @@
#define DECODE_WHIRLPOOL_AC true
#define SEND_WHIRLPOOL_AC true

#define DECODE_LUTRON true
#define SEND_LUTRON true

#if (DECODE_ARGO || DECODE_DAIKIN || DECODE_FUJITSU_AC || DECODE_GREE || \
DECODE_KELVINATOR || DECODE_MITSUBISHI_AC || DECODE_TOSHIBA_AC || \
DECODE_TROTEC || DECODE_HAIER_AC || DECODE_HITACHI_AC || \
Expand Down Expand Up @@ -253,7 +256,8 @@ enum decode_type_t {
GICABLE,
HAIER_AC_YRW02,
WHIRLPOOL_AC,
SAMSUNG_AC
SAMSUNG_AC,
LUTRON
};

// Message lengths & required repeat values
Expand Down Expand Up @@ -300,6 +304,7 @@ const uint16_t kLasertagBits = 13;
const uint16_t kLasertagMinRepeat = kNoRepeat;
const uint16_t kLgBits = 28;
const uint16_t kLg32Bits = 32;
const uint16_t kLutronBits = 35;
const uint16_t kMagiquestBits = 56;
const uint16_t kMideaBits = 48;
const uint16_t kMideaMinRepeat = kNoRepeat;
Expand Down
4 changes: 4 additions & 0 deletions src/IRsend.h
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,10 @@ void send(uint16_t type, uint64_t data, uint16_t nbits);
uint16_t nbytes = kWhirlpoolAcStateLength,
uint16_t repeat = kNoRepeat);
#endif
#if SEND_LUTRON
void sendLutron(uint64_t data, uint16_t nbits = kLutronBits,
uint16_t repeat = kNoRepeat);
#endif

protected:
#ifdef UNIT_TEST
Expand Down
1 change: 1 addition & 0 deletions src/IRutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ std::string typeToString(const decode_type_t protocol,
case KELVINATOR: result = "KELVINATOR"; break;
case LG: result = "LG"; break;
case LASERTAG: result = "LASERTAG"; break;
case LUTRON: result = "LUTRON"; break;
case MAGIQUEST: result = "MAGIQUEST"; break;
case MIDEA: result = "MIDEA"; break;
case MITSUBISHI: result = "MITSUBISHI"; break;
Expand Down
159 changes: 159 additions & 0 deletions src/ir_Lutron.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
// Copyright 2018 David Conran

#define __STDC_LIMIT_MACROS
#include <stdint.h>
#include <algorithm>
#include "IRrecv.h"
#include "IRsend.h"
#include "IRutils.h"

// LL UU UU TTTTTTT RRRRRR OOOOO NN NN
// LL UU UU TTT RR RR OO OO NNN NN
// LL UU UU TTT RRRRRR OO OO NN N NN
// LL UU UU TTT RR RR OO OO NN NNN
// LLLLLLL UUUUU TTT RR RR OOOO0 NN NN

// Notes:
// The Lutron protocol uses a sort of Run Length encoding to encode
// its data. There is no header or footer per-se.
// As a mark is the first data we will notice, we always assume the First
// bit of the technically 36-bit protocol is '1'. So it is assumed, and thus
// we only care about the 35 bits of data.

// Constants
// Ref:
// https://github.com/markszabo/IRremoteESP8266/issues/515
const uint16_t kLutronTick = 2288;
const uint32_t kLutronGap = 150000; // Completely made up value.
const uint16_t kLutronDelta = 400; // +/- 300 usecs.



#if SEND_LUTRON
// Send a Lutron formatted message.
//
// Args:
// data: The message to be sent.
// nbits: The number of bits of the message to be sent. Typically kLutronBits
// repeat: The number of times the command is to be repeated.
//
// Status: ALPHA / Untested.

// Notes:
// Protocol is really 36 bits long, but the first bit is always a 1.
// So, assume the 1 and only have a normal payload of 35 bits.
//
// Ref:
// https://github.com/markszabo/IRremoteESP8266/issues/515
void IRsend::sendLutron(uint64_t data, uint16_t nbits, uint16_t repeat) {
enableIROut(40000, 40); // 40Khz & 40% dutycycle.
for (uint16_t r = 0; r <= repeat; r++) {
mark(kLutronTick); // 1st bit is always '1'.
// Send the supplied data in MSB First order.
for (uint64_t mask = 1ULL << (nbits - 1); mask; mask >>= 1)
if (data & mask)
mark(kLutronTick); // Send a 1
else
space(kLutronTick); // Send a 0
space(kLutronGap); // Inter-message gap.
}
}
#endif // SEND_LUTRON

#if DECODE_LUTRON
// Decode the supplied Lutron message.
//
// Args:
// results: Ptr to the data to decode and where to store the decode result.
// nbits: The number of data bits to expect. Typically kLutronBits.
// strict: Flag indicating if we should perform strict matching.
// Returns:
// boolean: True if it can decode it, false if it can't.
//
// Status: ALPHA / Untested.
//
// Notes:
//
// Ref:
// https://github.com/markszabo/IRremoteESP8266/issues/515
bool IRrecv::decodeLutron(decode_results *results, uint16_t nbits,
bool strict) {
// Technically the smallest number of entries for the smallest message is '1'.
// i.e. All the bits set to 1, would produce a single huge mark signal.
// So no minimum length check is required.
if (strict && nbits != kLutronBits)
return false; // Not strictly an Lutron message.

uint64_t data = 0;
int16_t bitsSoFar = -1;

if (nbits > sizeof(data) * 8) return false; // To large to store the data.
for (uint16_t offset = kStartOffset;
bitsSoFar < nbits && offset < results->rawlen;
offset++) {
uint16_t entry = results->rawbuf[offset];
// It has to be large enough to qualify as a bit.
if (!matchAtLeast(entry, kLutronTick, 0, kLutronDelta)) {
DPRINTLN("Entry too small. Aborting.");
return false;
}
// Keep reading bits of the same value until we run out.
while (entry != 0 && matchAtLeast(entry, kLutronTick, 0, kLutronDelta)) {
bitsSoFar++;
DPRINT("Bit: ");
DPRINT(bitsSoFar);
if (offset % 2) { // Is Odd?
data = (data << 1) + 1; // Append a '1'.
DPRINTLN(" is a 1.");
} else { // Is it Even?
data <<= 1; // Append a '0'.
DPRINTLN(" is a 0.");
if (bitsSoFar == nbits && matchAtLeast(entry, kLutronGap))
break; // We've likely reached the end of a message.
}
// Remove a bit length from the current entry.
entry = std::max(entry, (uint16_t) (kLutronTick / kRawTick)) -
kLutronTick / kRawTick;
}
if (offset % 2 && !match(entry, kLutronDelta, 0, kLutronDelta)) {
DPRINT("offset = ");
DPRINTLN(offset);
DPRINT("rawlen = ");
DPRINTLN(results->rawlen);
DPRINT("entry = ");
DPRINTLN(entry);
DPRINTLN("Odd Entry has too much left over. Aborting.");
return false; // Too much left over to be a good value. Reject it.
}
if (offset % 2 == 0 && offset <= results->rawlen -1 &&
!matchAtLeast(entry, kLutronDelta, 0, kLutronDelta)) {
DPRINT("offset = ");
DPRINTLN(offset);
DPRINT("rawlen = ");
DPRINTLN(results->rawlen);
DPRINT("entry = ");
DPRINTLN(entry);
DPRINTLN("Entry has too much left over. Aborting.");
return false; // Too much left over to be a good value. Reject it.
}
}

// We got too many bits.
if (bitsSoFar > nbits || bitsSoFar < 0) {
DPRINTLN("Wrong number of bits found. Aborting.");
return false;
}
// If we got less bits than we were expecting, we need to pad with zeros
// until we get the correct number of bits.
if (bitsSoFar < nbits) data <<= (nbits - bitsSoFar);

// Success
DPRINTLN("Lutron Success!");
results->decode_type = LUTRON;
results->bits = bitsSoFar;
results->value = data ^ (1ULL << nbits); // Mask off the initial '1'.
results->address = 0;
results->command = 0;
return true;
}
#endif // DECODE_LUTRON
14 changes: 12 additions & 2 deletions test/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ TESTS = IRutils_test IRsend_test ir_NEC_test ir_GlobalCache_test \
ir_Gree_test IRrecv_test ir_Pronto_test ir_Fujitsu_test ir_Nikai_test \
ir_Toshiba_test ir_Midea_test ir_Magiquest_test ir_Lasertag_test \
ir_Carrier_test ir_Haier_test ir_Hitachi_test ir_GICable_test \
ir_Whirlpool_test
ir_Whirlpool_test ir_Lutron_test

# All Google Test headers. Usually you shouldn't change this
# definition.
Expand Down Expand Up @@ -78,7 +78,7 @@ PROTOCOLS = ir_NEC.o ir_Sony.o ir_Samsung.o ir_JVC.o ir_RCMM.o ir_RC5_RC6.o \
ir_Panasonic.o ir_Whynter.o ir_Coolix.o ir_Aiwa.o ir_Sherwood.o \
ir_Kelvinator.o ir_Daikin.o ir_Gree.o ir_Pronto.o ir_Nikai.o ir_Toshiba.o \
ir_Midea.o ir_Magiquest.o ir_Lasertag.o ir_Carrier.o ir_Haier.o \
ir_Hitachi.o ir_GICable.o ir_Whirlpool.o
ir_Hitachi.o ir_GICable.o ir_Whirlpool.o ir_Lutron.o

# Common object files
COMMON_OBJ = IRutils.o IRtimer.o IRsend.o IRrecv.o ir_GlobalCache.o \
Expand Down Expand Up @@ -437,3 +437,13 @@ ir_Whirlpool_test.o : ir_Whirlpool_test.cpp $(COMMON_TEST_DEPS) $(GTEST_HEADERS)

ir_Whirlpool_test : $(COMMON_OBJ) ir_Whirlpool_test.o
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -lpthread $^ -o $@

ir_Lutron.o : $(USER_DIR)/ir_Lutron.cpp $(COMMON_DEPS) $(GTEST_HEADERS)
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $(USER_DIR)/ir_Lutron.cpp

ir_Lutron_test.o : ir_Lutron_test.cpp $(COMMON_TEST_DEPS) $(GTEST_HEADERS)
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -I$(USER_DIR) -c ir_Lutron_test.cpp

ir_Lutron_test : $(COMMON_OBJ) ir_Lutron_test.o
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -lpthread $^ -o $@

Loading