Skip to content

Commit

Permalink
Add support for Metz protocol. (#1242)
Browse files Browse the repository at this point in the history
* Add `sendMetz()` & `decodeMetz()`.
* Add `encodeMetz()` to handle creation of valid Metz codes from Address & Command bits (inc toggle bit)
* Unit test coverage based on supplied data.

Note: Protocol is a simple non-A/C one for controlling TVs etc.

Fixes #1241
  • Loading branch information
crankyoldgit authored Aug 21, 2020
1 parent dcefcad commit 2c6f814
Show file tree
Hide file tree
Showing 9 changed files with 236 additions and 3 deletions.
4 changes: 4 additions & 0 deletions src/IRrecv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -864,6 +864,10 @@ bool IRrecv::decode(decode_results *results, irparams_t *save,
DPRINTLN("Attempting Voltas decode");
if (decodeVoltas(results)) return true;
#endif // DECODE_VOLTAS
#if DECODE_METZ
DPRINTLN("Attempting Metz decode");
if (decodeMetz(results, offset)) return true;
#endif // DECODE_METZ
// Typically new protocols are added above this line.
}
#if DECODE_HASH
Expand Down
5 changes: 5 additions & 0 deletions src/IRrecv.h
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,11 @@ bool decodeZepeal(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kZepealBits,
const bool strict = true);
#endif // DECODE_ZEPEAL
#if DECODE_METZ
bool decodeMetz(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kMetzBits,
const bool strict = true);
#endif // DECODE_METZ
};

#endif // IRRECV_H_
12 changes: 11 additions & 1 deletion src/IRremoteESP8266.h
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,13 @@
#define SEND_VOLTAS _IR_ENABLE_DEFAULT_
#endif // SEND_VOLTAS

#ifndef DECODE_METZ
#define DECODE_METZ _IR_ENABLE_DEFAULT_
#endif // DECODE_METZ
#ifndef SEND_METZ
#define SEND_METZ _IR_ENABLE_DEFAULT_
#endif // SEND_METZ

#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 @@ -819,8 +826,9 @@ enum decode_type_t {
ZEPEAL,
SANYO_AC,
VOLTAS, // 90
METZ,
// Add new entries before this one, and update it to point to the last entry.
kLastDecodeType = VOLTAS,
kLastDecodeType = METZ,
};

// Message lengths & required repeat values
Expand Down Expand Up @@ -936,6 +944,8 @@ const uint16_t kLg32Bits = 32;
const uint16_t kLgDefaultRepeat = kNoRepeat;
const uint16_t kLutronBits = 35;
const uint16_t kMagiquestBits = 56;
const uint16_t kMetzBits = 19;
const uint16_t kMetzMinRepeat = kNoRepeat;
const uint16_t kMideaBits = 48;
const uint16_t kMideaMinRepeat = kNoRepeat;
const uint16_t kMidea24Bits = 24;
Expand Down
9 changes: 8 additions & 1 deletion src/IRsend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,8 @@ uint16_t IRsend::defaultBits(const decode_type_t protocol) {
case MITSUBISHI2:
case ZEPEAL:
return 16;
case METZ:
return 19;
case RC6:
case SONY:
case SONY_38K:
Expand Down Expand Up @@ -987,7 +989,12 @@ bool IRsend::send(const decode_type_t type, const uint64_t data,
case ZEPEAL:
sendZepeal(data, nbits, min_repeat);
break;
#endif
#endif // SEND_ZEPEAL
#if SEND_METZ
case METZ:
sendMetz(data, nbits, min_repeat);
break;
#endif // SEND_METZ
default:
return false;
}
Expand Down
9 changes: 8 additions & 1 deletion src/IRsend.h
Original file line number Diff line number Diff line change
Expand Up @@ -634,12 +634,19 @@ class IRsend {
void sendZepeal(const uint64_t data,
const uint16_t nbits = kZepealBits,
const uint16_t repeat = kZepealMinRepeat);
#endif
#endif // SEND_ZEPEAL
#if SEND_VOLTAS
void sendVoltas(const unsigned char data[],
const uint16_t nbytes = kVoltasStateLength,
const uint16_t repeat = kNoRepeat);
#endif // SEND_VOLTAS
#if SEND_METZ
void sendMetz(const uint64_t data,
const uint16_t nbits = kMetzBits,
const uint16_t repeat = kMetzMinRepeat);
static uint32_t encodeMetz(const uint8_t address, const uint8_t command,
const bool toggle = false);
#endif // SEND_METZ

protected:
#ifdef UNIT_TEST
Expand Down
1 change: 1 addition & 0 deletions src/IRtext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -267,5 +267,6 @@ const PROGMEM char *kAllProtocolNamesStr =
D_STR_ZEPEAL "\x0"
D_STR_SANYO_AC "\x0"
D_STR_VOLTAS "\x0"
D_STR_METZ "\x0"
///< New protocol strings should be added just above this line.
"\x0"; ///< This string requires double null termination.
101 changes: 101 additions & 0 deletions src/ir_Metz.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// Copyright 2020 David Conran (crankyoldgit)
/// @file
/// @brief Support for Metz protocol
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1241

// Supports:
// Brand: Metz, Model: RM16 remote
// Brand: Metz, Model: RM17 remote
// Brand: Metz, Model: RM19 remote
// Brand: Metz, Model: CH610 TV

#include "IRrecv.h"
#include "IRsend.h"
#include "IRutils.h"

// Constants.
const uint16_t kMetzHdrMark = 880; ///< uSeconds.
const uint16_t kMetzHdrSpace = 2336; ///< uSeconds.
const uint16_t kMetzBitMark = 473; ///< uSeconds.
const uint16_t kMetzOneSpace = 1640; ///< uSeconds.
const uint16_t kMetzZeroSpace = 940; ///< uSeconds.
const uint16_t kMetzFreq = 38000; ///< Hz.
const uint8_t kMetzAddressBits = 3;
const uint8_t kMetzCommandBits = 6;

#if SEND_METZ
/// Send a Metz formatted message.
/// Status: Beta / Needs testing against a real device.
/// @param[in] data containing the IR command.
/// @param[in] nbits Nr. of bits to send. usually kMetzBits
/// @param[in] repeat Nr. of times the message is to be repeated.
void IRsend::sendMetz(const uint64_t data, const uint16_t nbits,
const uint16_t repeat) {
sendGeneric(kMetzHdrMark, kMetzHdrSpace, // Header
kMetzBitMark, kMetzOneSpace, // Data
kMetzBitMark, kMetzZeroSpace,
kMetzBitMark, kDefaultMessageGap, // Footer.
data, nbits, // Payload
kMetzFreq, true, repeat, kDutyDefault);
}

/// Encode a Metz address, command, and toggle bits into a code suitable
/// for use with sendMetz().
/// @param[in] address A 3-bit address value.
/// @param[in] command A 6-bit command value.
/// @param[in] toggle Should the toggle bit be set in the result?
/// @return A 19-bit value suitable for use with `sendMetz()`.
uint32_t IRsend::encodeMetz(const uint8_t address, const uint8_t command,
const bool toggle) {
return toggle << (2 * (kMetzAddressBits + kMetzCommandBits)) |
(address & 0x7) << (2 * kMetzCommandBits + kMetzAddressBits) |
(~address & 0x7) << (2 * kMetzCommandBits) |
(command & 0x3F) << kMetzCommandBits |
(~command & 0x3F);
}
#endif // SEND_METZ

#if DECODE_METZ
/// Decode the supplied Metz message.
/// Status: BETA / Probably works.
/// @param[in,out] results Ptr to the data to decode & where to store the decode
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// @param[in] strict Flag indicating if we should perform strict matching.
/// @return A boolean. True if it can decode it, false if it can't.
bool IRrecv::decodeMetz(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
if (strict && nbits != kMetzBits) return false;

uint64_t data = 0;

// Match Header + Data + Footer
if (!matchGeneric(results->rawbuf + offset, &data,
results->rawlen - offset, nbits,
kMetzHdrMark, kMetzHdrSpace, // Header
kMetzBitMark, kMetzOneSpace, // Data
kMetzBitMark, kMetzZeroSpace,
kMetzBitMark, kDefaultMessageGap, // Footer
true, _tolerance, 0, true)) return false;

uint16_t command = GETBITS64(data, kMetzCommandBits, kMetzCommandBits);
uint16_t address = GETBITS64(data, 2 * kMetzCommandBits + kMetzAddressBits,
kMetzAddressBits);
// Compliance
if (strict) {
if (command != invertBits(GETBITS64(data, 0, kMetzCommandBits),
kMetzCommandBits) ||
address != invertBits(GETBITS64(data, 2 * kMetzCommandBits,
kMetzAddressBits),
kMetzAddressBits)) return false;
}
// Success
results->decode_type = decode_type_t::METZ;
results->bits = nbits;
results->value = data;
results->address = address;
results->command = command;
return true;
}
#endif // DECODE_METZ
3 changes: 3 additions & 0 deletions src/locale/defaults.h
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,9 @@
#ifndef D_STR_MAGIQUEST
#define D_STR_MAGIQUEST "MAGIQUEST"
#endif // D_STR_MAGIQUEST
#ifndef D_STR_METZ
#define D_STR_METZ "METZ"
#endif // D_STR_METZ
#ifndef D_STR_MIDEA
#define D_STR_MIDEA "MIDEA"
#endif // D_STR_MIDEA
Expand Down
95 changes: 95 additions & 0 deletions test/ir_Metz_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Copyright 2020 crankyoldgit

#include "IRac.h"
#include "IRrecv.h"
#include "IRrecv_test.h"
#include "IRsend.h"
#include "IRsend_test.h"
#include "gtest/gtest.h"

// Tests for decodeMetz().

TEST(TestDecodeMetz, RealExample) {
IRsendTest irsend(kGpioUnused);
IRrecv irrecv(kGpioUnused);
uint16_t rawbuf[41] = {
// Header 1 0 1
880, 2336, 462, 1656, 460, 952, 486, 1630,
// 1 1 0 0
462, 1654, 460, 1660, 462, 948, 488, 902,
// 1 0 1 0
486, 1656, 486, 926, 486, 1630, 486, 924,
// 1 0 0 1
488, 1630, 460, 954, 460, 950, 512, 1606,
// 0 1 0 1
460, 978, 436, 1656, 486, 926, 486, 1630,
458};
irsend.begin();
irsend.reset();
irsend.sendRaw(rawbuf, 41, 38);
irsend.makeDecodeResult();

ASSERT_TRUE(irrecv.decode(&irsend.capture));
ASSERT_EQ(decode_type_t::METZ, irsend.capture.decode_type);
ASSERT_EQ(kMetzBits, irsend.capture.bits);
EXPECT_EQ(0x5CA95, irsend.capture.value);
EXPECT_EQ(0x2A, irsend.capture.command);
EXPECT_EQ(0x3, irsend.capture.address);
}

TEST(TestDecodeMetz, SyntheticExample) {
IRsendTest irsend(kGpioUnused);
IRrecv irrecv(kGpioUnused);
irsend.begin();
irsend.reset();
irsend.sendMetz(0x5CA95);
irsend.makeDecodeResult();

ASSERT_TRUE(irrecv.decode(&irsend.capture));
EXPECT_EQ(decode_type_t::METZ, irsend.capture.decode_type);
EXPECT_EQ(kMetzBits, irsend.capture.bits);
EXPECT_EQ(0x5CA95, irsend.capture.value);
EXPECT_EQ(0x2A, irsend.capture.command);
EXPECT_EQ(0x3, irsend.capture.address);

EXPECT_EQ(
"f38000d50"
// 880 2336 462 1656 460 952 486 1630 462 1654 460 1660 462 948 488 902
"m880s2336m473s1640m473s940m473s1640m473s1640m473s1640m473s940m473s940"
// 486 1656 486 926 486 1630 486 924 488 1630 460 954 460 950 512 1606
"m473s1640m473s940m473s1640m473s940m473s1640m473s940m473s940m473s1640"
// 460 978 436 1656 486 926 486 1630
"m473s940m473s1640m473s940m473s1640"
// 458
"m473s100000",
irsend.outputStr());
}

TEST(TestEncodeMetz, BasicTests) {
EXPECT_EQ(0x703F, IRsend::encodeMetz(0, 0));
EXPECT_EQ(0x703F, IRsend::encodeMetz(0, 0, false));
EXPECT_EQ(0x4703F, IRsend::encodeMetz(0, 0, true));

EXPECT_EQ(0xE0BD, IRsend::encodeMetz(1, 2, false));
EXPECT_EQ(0x4E0BD, IRsend::encodeMetz(1, 2, true));

EXPECT_EQ(0x38FC0, IRsend::encodeMetz(0b111, 0b111111, false));
EXPECT_EQ(0x78FC0, IRsend::encodeMetz(0b111, 0b111111, true));

// Out of range.
EXPECT_EQ(0x38FC0, IRsend::encodeMetz(255, 255, false));
EXPECT_EQ(0x78FC0, IRsend::encodeMetz(255, 255, true));
}

TEST(TestEncodeMetz, RealExamples) {
EXPECT_EQ(0x5CA95, IRsend::encodeMetz(0x3, 0x2A, true));
}

TEST(TestUtils, Housekeeping) {
ASSERT_EQ("METZ", typeToString(decode_type_t::METZ));
ASSERT_EQ(decode_type_t::METZ, strToDecodeType("METZ"));
ASSERT_FALSE(hasACState(decode_type_t::METZ));
ASSERT_FALSE(IRac::isProtocolSupported(decode_type_t::METZ));
ASSERT_EQ(kMetzBits, IRsend::defaultBits(decode_type_t::METZ));
ASSERT_EQ(kMetzMinRepeat, IRsend::minRepeats(decode_type_t::METZ));
}

0 comments on commit 2c6f814

Please sign in to comment.