Skip to content

Commit

Permalink
Add Made With Magic (MWM) support (#557)
Browse files Browse the repository at this point in the history
* Adding Made With Magic (MWM) support
Adding support for Disney's Made With Magic IR protocol, formerly known as Glow With the Show.
* Make mark and space methods public in IRsendtest
[tools] Added command mode2_decode
MWM devices typically wait 30ms between messages, so increase
footer to that length.
* Enforce kStateSizeMax
* Implement unit tests for MWM
* Explain origin
* Update IRMQTTServer for MWM support.
* Anchor the googletest library to v1.8.x to solve c++11 issues.
  • Loading branch information
bwarden authored and crankyoldgit committed Oct 19, 2018
1 parent 47fab51 commit ec75c50
Show file tree
Hide file tree
Showing 16 changed files with 648 additions and 23 deletions.
1 change: 1 addition & 0 deletions .github/Contributors.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
- [Stu Fisher](https://github.com/stufisher/)
- [Jorge Cisneros](https://github.com/jorgecis/)
- [Denes Varga](https://github.com/denxhun/)
- [Brett T. Warden](https://github.com/bwarden/)

All contributors can be found on the [contributors site](https://github.com/markszabo/IRremoteESP8266/graphs/contributors).

Expand Down
1 change: 1 addition & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
[submodule "lib/googletest"]
path = lib/googletest
url = https://github.com/google/googletest.git
branch = v1.8.x
18 changes: 18 additions & 0 deletions examples/IRMQTTServer/IRMQTTServer.ino
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,7 @@ void handleRoot() {
"<option value='42'>Hitachi2 (53 bytes)</option>"
"<option selected='selected' value='18'>Kelvinator</option>" // Default
"<option value='20'>Mitsubishi</option>"
"<option value='52'>MWM</option>"
"<option value='46'>Samsung</option>"
"<option value='32'>Toshiba</option>"
"<option value='28'>Trotec</option>"
Expand Down Expand Up @@ -631,6 +632,17 @@ bool parseStringAndSendAirCon(const uint16_t irType, const String str) {
// Lastly, it should never exceed the maximum "extended" size.
stateSize = std::min(stateSize, kSamsungAcExtendedStateLength);
break;
case MWM:
// MWM has variable size states, so make a best guess
// which one we are being presented with based on the number of
// hexadecimal digits provided. i.e. Zero-pad if you need to to get
// the correct length/byte size.
stateSize = inputLength / 2; // Every two hex chars is a byte.
// Use at least the minimum size.
stateSize = std::max(stateSize, (uint16_t) 3);
// Cap the maximum size.
stateSize = std::min(stateSize, kStateSizeMax);
break;
default: // Not a protocol we expected. Abort.
debug("Unexpected AirCon protocol detected. Ignoring.");
return false;
Expand Down Expand Up @@ -750,6 +762,11 @@ bool parseStringAndSendAirCon(const uint16_t irType, const String str) {
case PANASONIC_AC:
irsend.sendPanasonicAC(reinterpret_cast<uint8_t *>(state));
break;
#endif
#if SEND_MWM_
case MWM:
irsend.sendMWM(reinterpret_cast<uint8_t *>(state), stateSize);
break;
#endif
default:
debug("Unexpected AirCon type in send request. Not sent.");
Expand Down Expand Up @@ -1342,6 +1359,7 @@ bool sendIRCode(int const ir_type, uint64_t const code, char const * code_str,
case SAMSUNG_AC: // 46
case ELECTRA_AC: // 48
case PANASONIC_AC: // 49
case MWM: // 52
success = parseStringAndSendAirCon(ir_type, code_str);
break;
#if SEND_DENON
Expand Down
2 changes: 1 addition & 1 deletion lib/googletest
Submodule googletest updated 335 files
5 changes: 5 additions & 0 deletions src/IRrecv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,11 @@ bool IRrecv::decode(decode_results *results, irparams_t *save) {
if (decodeLutron(results))
return true;
#endif
#if DECODE_MWM
DPRINTLN("Attempting MWM decode");
if (decodeMWM(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
18 changes: 13 additions & 5 deletions src/IRrecv.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,13 @@ const uint16_t kMaxTimeoutMs = kRawTick * (UINT16_MAX / MS_TO_USEC(1));
const uint32_t kFnvPrime32 = 16777619UL;
const uint32_t kFnvBasis32 = 2166136261UL;

#if DECODE_AC
// Hitachi AC is the current largest state size.
const uint16_t kStateSizeMax = kHitachiAc2StateLength;
#else
// Just define something
const uint16_t kStateSizeMax = 0;
#endif

// Types
// information for the interrupt handler
Expand Down Expand Up @@ -91,9 +96,7 @@ class decode_results {
uint32_t address; // Decoded device address.
uint32_t command; // Decoded command.
};
#if DECODE_AC // Only include state if we must. It's big.
uint8_t state[kStateSizeMax]; // Complex multi-byte A/C result.
#endif
uint8_t state[kStateSizeMax]; // Multi-byte results.
};
uint16_t bits; // Number of bits in decoded value
volatile uint16_t *rawbuf; // Raw intervals in .5 us ticks
Expand Down Expand Up @@ -181,10 +184,11 @@ class IRrecv {
uint16_t nbits = kMitsubishiACBits,
bool strict = false);
#endif
#if (DECODE_RC5 || DECODE_R6 || DECODE_LASERTAG)
#if (DECODE_RC5 || DECODE_R6 || DECODE_LASERTAG || DECODE_MWM)
int16_t getRClevel(decode_results *results, uint16_t *offset, uint16_t *used,
uint16_t bitTime, uint8_t tolerance = kTolerance,
int16_t excess = kMarkExcess, uint16_t delta = 0);
int16_t excess = kMarkExcess, uint16_t delta = 0,
uint8_t maxwidth = 3);
#endif
#if DECODE_RC5
bool decodeRC5(decode_results *results, uint16_t nbits = kRC5XBits,
Expand Down Expand Up @@ -324,6 +328,10 @@ class IRrecv {
bool decodePanasonicAC(decode_results *results,
uint16_t nbits = kPanasonicAcBits, bool strict = true);
#endif
#if DECODE_MWM
bool decodeMWM(decode_results *results, uint16_t nbits = 24,
bool strict = true);
#endif
};

#endif // IRRECV_H_
6 changes: 5 additions & 1 deletion src/IRremoteESP8266.h
Original file line number Diff line number Diff line change
Expand Up @@ -193,12 +193,15 @@
#define DECODE_PANASONIC_AC true
#define SEND_PANASONIC_AC true

#define DECODE_MWM true
#define SEND_MWM true

#if (DECODE_ARGO || DECODE_DAIKIN || DECODE_FUJITSU_AC || DECODE_GREE || \
DECODE_KELVINATOR || DECODE_MITSUBISHI_AC || DECODE_TOSHIBA_AC || \
DECODE_TROTEC || DECODE_HAIER_AC || DECODE_HITACHI_AC || \
DECODE_HITACHI_AC1 || DECODE_HITACHI_AC2 || DECODE_HAIER_AC_YRW02 || \
DECODE_WHIRLPOOL_AC || DECODE_SAMSUNG_AC || DECODE_ELECTRA_AC || \
DECODE_PANASONIC_AC)
DECODE_PANASONIC_AC || DECODE_MWM)
#define DECODE_AC true // We need some common infrastructure for decoding A/Cs.
#else
#define DECODE_AC false // We don't need that infrastructure.
Expand Down Expand Up @@ -269,6 +272,7 @@ enum decode_type_t {
PANASONIC_AC,
PIONEER,
LG2,
MWM,
};

// Message lengths & required repeat values
Expand Down
4 changes: 4 additions & 0 deletions src/IRsend.h
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,10 @@ void send(uint16_t type, uint64_t data, uint16_t nbits);
uint16_t nbytes = kPanasonicAcStateLength,
uint16_t repeat = kNoRepeat);
#endif
#if SEND_MWM
void sendMWM(unsigned char data[], uint16_t nbytes,
uint16_t repeat = kNoRepeat);
#endif

protected:
#ifdef UNIT_TEST
Expand Down
2 changes: 2 additions & 0 deletions src/IRutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ std::string typeToString(const decode_type_t protocol,
case MITSUBISHI: result = "MITSUBISHI"; break;
case MITSUBISHI2: result = "MITSUBISHI2"; break;
case MITSUBISHI_AC: result = "MITSUBISHI_AC"; break;
case MWM: result = "MWM"; break;
case NEC: result = "NEC"; break;
case NEC_LIKE: result = "NEC (non-strict)"; break;
case NIKAI: result = "NIKAI"; break;
Expand Down Expand Up @@ -169,6 +170,7 @@ bool hasACState(const decode_type_t protocol) {
case HITACHI_AC2:
case KELVINATOR:
case MITSUBISHI_AC:
case MWM:
case PANASONIC_AC:
case SAMSUNG_AC:
case TOSHIBA_AC:
Expand Down
205 changes: 205 additions & 0 deletions src/ir_MWM.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
// Copyright 2018 Brett T. Warden
// derived from ir_Lasertag.cpp, Copyright 2017 David Conran

#include <algorithm>
#include "IRrecv.h"
#include "IRsend.h"
#include "IRutils.h"

// MM MM WW WW MM MM
// MMM MMM WW WW MMM MMM
// MM M MM WW W WW MM M MM
// MM MM WWW WWW MM MM
// MM MM WW WW MM MM

// Constants
const uint16_t kMWMMinSamples = 6; // Msgs are >=3 bytes, bytes have >=2
// samples
const uint16_t kMWMTick = 417;
const uint32_t kMWMMinGap = 30000; // Typical observed delay b/w commands
const uint8_t kMWMTolerance = 0; // Percentage error margin.
const uint16_t kMWMExcess = 0; // See kMarkExcess.
const uint16_t kMWMDelta = 150; // Use instead of Excess and Tolerance.
const uint8_t kMWMMaxWidth = 9; // Maximum number of successive bits at a
// single level - worst case
const int16_t kSpace = 1;
const int16_t kMark = 0;

#if SEND_MWM
// Send a MWM packet.
// This protocol is 2400 bps serial, 1 start bit (mark), 1 stop bit (space), no
// parity
//
// Args:
// data: The message you wish to send.
// nbits: Bit size of the protocol you want to send.
// repeat: Nr. of extra times the data will be sent.
//
// Status: Implemented.
//
void IRsend::sendMWM(uint8_t data[], uint16_t nbytes, uint16_t repeat) {
if (nbytes < 3)
return; // Shortest possible message is 3 bytes

// Set 38kHz IR carrier frequency & a 1/4 (25%) duty cycle.
// NOTE: duty cycle is not confirmed. Just guessing based on RC5/6 protocols.
enableIROut(38, 25);

for (uint16_t r = 0; r <= repeat; r++) {
// Data
for (uint16_t i = 0; i < nbytes; i++) {
uint8_t byte = data[i];

// Start bit
mark(kMWMTick);

// LSB first, space=1
for (uint8_t mask = 0x1; mask; mask <<= 1) {
if (byte & mask) { // 1
space(kMWMTick);
} else { // 0
mark(kMWMTick);
}
}
// Stop bit
space(kMWMTick);
}
// Footer
space(kMWMMinGap);
}
}
#endif // SEND_MWM

#if DECODE_MWM
// Decode the supplied MWM message.
// This protocol is 2400 bps serial, 1 start bit (mark), 1 stop bit (space), no
// parity
//
// Args:
// results: Ptr to the data to decode and where to store the decode result.
// nbits: The number of data bits to expect.
// strict: Flag indicating if we should perform strict matching.
// Returns:
// boolean: True if it can decode it, false if it can't.
//
// Status: Implemented.
//
bool IRrecv::decodeMWM(decode_results *results, uint16_t nbits,
bool strict) {
DPRINTLN("DEBUG: decodeMWM");

// Compliance
if (results->rawlen < kMWMMinSamples) {
DPRINTLN("DEBUG: decodeMWM: too few samples");
return false;
}

uint16_t offset = kStartOffset;
uint16_t used = 0;
uint64_t data = 0;
uint16_t frame_bits = 0;
uint16_t data_bits = 0;

// No Header

// Data
uint8_t bits_per_frame = 10;
for (; offset < results->rawlen && results->bits < 8*kStateSizeMax;
frame_bits++) {
DPRINT("DEBUG: decodeMWM: offset = ");
DPRINTLN(uint64ToString(offset));
int16_t level = getRClevel(results, &offset, &used, kMWMTick,
kMWMTolerance, kMWMExcess,
kMWMDelta, kMWMMaxWidth);
if (level < 0) {
DPRINTLN("DEBUG: decodeMWM: getRClevel returned error");
break;
}
switch (frame_bits % bits_per_frame) {
case 0:
// Start bit
if (level != kMark) {
DPRINTLN("DEBUG: decodeMWM: framing error - invalid start bit");
goto done;
}
break;
case 9:
// Stop bit
if (level != kSpace) {
DPRINTLN("DEBUG: decodeMWM: framing error - invalid stop bit");
return false;
} else {
DPRINT("DEBUG: decodeMWM: data_bits = ");
DPRINTLN(data_bits);
DPRINT("DEBUG: decodeMWM: Finished byte: ");
DPRINTLN(data);
results->state[data_bits / 8 - 1] = data & 0xFF;
results->bits = data_bits;
data = 0;
}
break;
default:
// Data bits
DPRINT("DEBUG: decodeMWM: Storing bit: ");
DPRINTLN((level == kSpace));
// Transmission is LSB-first, space=1
data |= ((level == kSpace)) << 8;
data >>= 1;
data_bits++;
break;
}
}

done:
// Footer (None)

// Compliance
DPRINT("DEBUG: decodeMWM: frame_bits = ");
DPRINTLN(frame_bits);
DPRINT("DEBUG: decodeMWM: data_bits = ");
DPRINTLN(data_bits);
if (data_bits < nbits) {
DPRINT("DEBUG: decodeMWM: too few bits; expected ");
DPRINTLN(nbits);
return false; // Less data than we expected.
}

uint16_t payload_length = 0;
switch (results->state[0] & 0xf0) {
case 0x90:
case 0xf0:
// Normal commands
payload_length = results->state[0] & 0x0f;
DPRINT("DEBUG: decodeMWM: payload_length = ");
DPRINTLN(payload_length);
break;
default:
if (strict) {
// Show commands
if (results->state[0] != 0x55 && results->state[1] != 0xAA) {
return false;
}
}
break;
}
if (data_bits < (payload_length + 3) * 8) {
DPRINT("DEBUG: decodeMWM: too few bytes; expected ");
DPRINTLN((payload_length + 3));
return false;
}
if (strict) {
if (payload_length && (data_bits > (payload_length + 3) * 8)) {
DPRINT("DEBUG: decodeMWM: too many bytes; expected ");
DPRINTLN((payload_length + 3));
return false;
}
}

// Success
results->decode_type = MWM;
results->repeat = false;
return true;
}
#endif // DECODE_MWM

// vim: et:ts=2:sw=2
Loading

0 comments on commit ec75c50

Please sign in to comment.