Skip to content

Commit

Permalink
Experimental basic support for Kelon 168 bit / 21 byte protocol.
Browse files Browse the repository at this point in the history
* Add `sendKelon168()` & `decodeKelon168()`
* Add & extend unit test coverage.
* Code style cleanup.

Fixes #1745
Ref #1744
  • Loading branch information
crankyoldgit committed Jan 28, 2022
1 parent f8e5662 commit b5771a6
Show file tree
Hide file tree
Showing 11 changed files with 265 additions and 45 deletions.
6 changes: 5 additions & 1 deletion src/IRrecv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1050,8 +1050,12 @@ bool IRrecv::decode(decode_results *results, irparams_t *save,
DPRINTLN("Attempting Teknopoint decode");
if (decodeTeknopoint(results, offset)) return true;
#endif // DECODE_TEKNOPOINT
#if DECODE_KELON168
DPRINTLN("Attempting Kelon 168-bit decode");
if (decodeKelon168(results, offset)) return true;
#endif // DECODE_KELON168
#if DECODE_KELON
DPRINTLN("Attempting Kelon decode");
DPRINTLN("Attempting Kelon 48-bit decode");
if (decodeKelon(results, offset)) return true;
#endif // DECODE_KELON
#if DECODE_SANYO_AC88
Expand Down
5 changes: 5 additions & 0 deletions src/IRrecv.h
Original file line number Diff line number Diff line change
Expand Up @@ -773,6 +773,11 @@ class IRrecv {
bool decodeKelon(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kKelonBits, const bool strict = true);
#endif // DECODE_KELON
#if DECODE_KELON168
bool decodeKelon168(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kKelon168Bits,
const bool strict = true);
#endif // DECODE_KELON168
#if DECODE_BOSE
bool decodeBose(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kBoseBits, const bool strict = true);
Expand Down
13 changes: 12 additions & 1 deletion src/IRremoteESP8266.h
Original file line number Diff line number Diff line change
Expand Up @@ -847,6 +847,13 @@
#define SEND_AIRTON _IR_ENABLE_DEFAULT_
#endif // SEND_AIRTON

#ifndef DECODE_KELON168
#define DECODE_KELON168 _IR_ENABLE_DEFAULT_
#endif // DECODE_KELON168
#ifndef SEND_KELON168
#define SEND_KELON168 _IR_ENABLE_DEFAULT_
#endif // SEND_KELON168

#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 All @@ -862,6 +869,7 @@
DECODE_VOLTAS || DECODE_MIRAGE || DECODE_HAIER_AC176 || \
DECODE_TEKNOPOINT || DECODE_KELON || DECODE_TROTEC_3550 || \
DECODE_SANYO_AC88 || DECODE_RHOSS || DECODE_HITACHI_AC264 || \
DECODE_KELON168 || \
false)
// Add any DECODE to the above if it uses result->state (see kStateSizeMax)
// you might also want to add the protocol to hasACState function
Expand Down Expand Up @@ -1013,8 +1021,9 @@ enum decode_type_t {
AIRTON,
COOLIX48, // 110
HITACHI_AC264,
KELON168,
// Add new entries before this one, and update it to point to the last entry.
kLastDecodeType = HITACHI_AC264,
kLastDecodeType = KELON168,
};

// Message lengths & required repeat values
Expand Down Expand Up @@ -1134,6 +1143,8 @@ const uint16_t kInaxBits = 24;
const uint16_t kInaxMinRepeat = kSingleRepeat;
const uint16_t kJvcBits = 16;
const uint16_t kKelonBits = 48;
const uint16_t kKelon168StateLength = 21;
const uint16_t kKelon168Bits = kKelon168StateLength * 8;
const uint16_t kKelvinatorStateLength = 16;
const uint16_t kKelvinatorBits = kKelvinatorStateLength * 8;
const uint16_t kKelvinatorDefaultRepeat = kNoRepeat;
Expand Down
7 changes: 7 additions & 0 deletions src/IRsend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -724,6 +724,8 @@ uint16_t IRsend::defaultBits(const decode_type_t protocol) {
return kHitachiAc344Bits;
case HITACHI_AC424:
return kHitachiAc424Bits;
case KELON168:
return kKelon168Bits;
case KELVINATOR:
return kKelvinatorBits;
case MILESTAG2:
Expand Down Expand Up @@ -1227,6 +1229,11 @@ bool IRsend::send(const decode_type_t type, const uint8_t *state,
sendHitachiAc424(state, nbytes);
break;
#endif // SEND_HITACHI_AC424
#if SEND_KELON168
case KELON168:
sendKelon168(state, nbytes);
break;
#endif // SEND_KELON168
#if SEND_KELVINATOR
case KELVINATOR:
sendKelvinator(state, nbytes);
Expand Down
5 changes: 5 additions & 0 deletions src/IRsend.h
Original file line number Diff line number Diff line change
Expand Up @@ -760,6 +760,11 @@ class IRsend {
void sendKelon(const uint64_t data, const uint16_t nbits = kKelonBits,
const uint16_t repeat = kNoRepeat);
#endif // SEND_KELON
#if SEND_KELON168
void sendKelon168(const unsigned char data[],
const uint16_t nbytes = kKelon168StateLength,
const uint16_t repeat = kNoRepeat);
#endif // SEND_KELON168
#if SEND_BOSE
void sendBose(const uint64_t data, const uint16_t nbits = kBoseBits,
const uint16_t repeat = kNoRepeat);
Expand Down
1 change: 1 addition & 0 deletions src/IRtext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,7 @@ IRTEXT_CONST_BLOB_DECL(kAllProtocolNamesStr) {
D_STR_AIRTON "\x0"
D_STR_COOLIX48 "\x0"
D_STR_HITACHI_AC264 "\x0"
D_STR_KELON168 "\x0"
///< New protocol strings should be added just above this line.
"\x0" ///< This string requires double null termination.
};
Expand Down
1 change: 1 addition & 0 deletions src/IRutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ bool hasACState(const decode_type_t protocol) {
case HITACHI_AC264:
case HITACHI_AC344:
case HITACHI_AC424:
case KELON168:
case KELVINATOR:
case MIRAGE:
case MITSUBISHI136:
Expand Down
117 changes: 113 additions & 4 deletions src/ir_Kelon.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// Copyright 2021 Davide Depau
// Copyright 2022 David Conran

/// @file
/// @brief Support for Kelan AC protocol.
/// @brief Support for Kelon AC protocols.
/// Both sending and decoding should be functional for models of series
/// KELON ON/OFF 9000-12000.
/// All features of the standard remote are implemented.
Expand All @@ -12,6 +13,7 @@
/// - Fahrenheit.

#include <algorithm>
#include <cassert>

#include "ir_Kelon.h"

Expand Down Expand Up @@ -39,8 +41,13 @@ const uint16_t kKelonZeroSpace = 600;
const uint32_t kKelonGap = 2 * kDefaultMessageGap;
const uint16_t kKelonFreq = 38000;

const uint32_t kKelon168FooterSpace = 8000;
const uint16_t kKelon168Section1Size = 6;
const uint16_t kKelon168Section2Size = 8;
const uint16_t kKelon168Section3Size = 7;

#if SEND_KELON
/// Send a Kelon message.
/// Send a Kelon 48-bit message.
/// Status: STABLE / Working.
/// @param[in] data The data to be transmitted.
/// @param[in] nbits Nr. of bits of data to be sent.
Expand All @@ -52,12 +59,12 @@ void IRsend::sendKelon(const uint64_t data, const uint16_t nbits,
kKelonBitMark, kKelonZeroSpace,
kKelonBitMark, kKelonGap,
data, nbits, kKelonFreq, false, // LSB First.
repeat, 50);
repeat, kDutyDefault);
}
#endif // SEND_KELON

#if DECODE_KELON
/// Decode the supplied Kelon message.
/// Decode the supplied Kelon 48-bit message.
/// Status: STABLE / Working.
/// @param[in,out] results Ptr to the data to decode & where to store the result
/// @param[in] offset The starting index to use when attempting to decode the
Expand Down Expand Up @@ -440,3 +447,105 @@ String IRKelonAc::toString() const {
result += addBoolToString(true, kSwingVToggleStr);
return result;
}

#if SEND_KELON168
/// Send a Kelon 168 bit / 21 byte message.
/// Status: BETA / Probably works.
/// @param[in] data The data to be transmitted.
/// @param[in] nbytes Nr. of bytes of data to be sent.
/// @param[in] repeat The number of times the command is to be repeated.
void IRsend::sendKelon168(const uint8_t data[], const uint16_t nbytes,
const uint16_t repeat) {
assert(kKelon168StateLength == kKelon168Section1Size + kKelon168Section2Size +
kKelon168Section3Size);
// Enough bytes to send a proper message?
if (nbytes < kKelon168StateLength) return;

for (uint16_t r = 0; r <= repeat; r++) {
// Section #1 (48 bits)
sendGeneric(kKelonHdrMark, kKelonHdrSpace,
kKelonBitMark, kKelonOneSpace,
kKelonBitMark, kKelonZeroSpace,
kKelonBitMark, kKelon168FooterSpace,
data, kKelon168Section1Size, kKelonFreq, false, // LSB First.
0, // No repeats here
kDutyDefault);
// Section #2 (64 bits)
sendGeneric(0, 0,
kKelonBitMark, kKelonOneSpace,
kKelonBitMark, kKelonZeroSpace,
kKelonBitMark, kKelon168FooterSpace,
data + kKelon168Section1Size, kKelon168Section2Size,
kKelonFreq, false, // LSB First.
0, // No repeats here
kDutyDefault);
// Section #3 (56 bits)
sendGeneric(0, 0,
kKelonBitMark, kKelonOneSpace,
kKelonBitMark, kKelonZeroSpace,
kKelonBitMark, kKelonGap,
data + kKelon168Section1Size + kKelon168Section2Size,
nbytes - (kKelon168Section1Size + kKelon168Section2Size),
kKelonFreq, false, // LSB First.
0, // No repeats here
kDutyDefault);
}
}
#endif // SEND_KELON168

#if DECODE_KELON168
/// Decode the supplied Kelon 168 bit / 21 byte message.
/// Status: BETA / Probably Working.
/// @param[in,out] results Ptr to the data to decode & where to store the result
/// @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 True if it can decode it, false if it can't.
bool IRrecv::decodeKelon168(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
if (strict && nbits != kKelon168Bits) return false;
if (results->rawlen <= 2 * nbits + kHeader + kFooter * 2 - 1 + offset)
return false; // Can't possibly be a valid Kelon 168 bit message.

uint16_t used = 0;

used = matchGeneric(results->rawbuf + offset, results->state,
results->rawlen - offset, kKelon168Section1Size * 8,
kKelonHdrMark, kKelonHdrSpace,
kKelonBitMark, kKelonOneSpace,
kKelonBitMark, kKelonZeroSpace,
kKelonBitMark, kKelon168FooterSpace,
false, _tolerance, 0, false);
if (!used) return false; // Failed to match.
offset += used;

used = matchGeneric(results->rawbuf + offset,
results->state + kKelon168Section1Size,
results->rawlen - offset, kKelon168Section2Size * 8,
0, 0,
kKelonBitMark, kKelonOneSpace,
kKelonBitMark, kKelonZeroSpace,
kKelonBitMark, kKelon168FooterSpace,
false, _tolerance, 0, false);
if (!used) return false; // Failed to match.
offset += used;

used = matchGeneric(results->rawbuf + offset,
results->state + (kKelon168Section1Size +
kKelon168Section2Size),
results->rawlen - offset,
nbits - (kKelon168Section1Size +
kKelon168Section2Size) * 8,
0, 0,
kKelonBitMark, kKelonOneSpace,
kKelonBitMark, kKelonZeroSpace,
kKelonBitMark, kKelonGap,
true, _tolerance, 0, false);
if (!used) return false; // Failed to match.

results->decode_type = decode_type_t::KELON168;
results->bits = nbits;
return true;
}
#endif // DECODE_KELON168
50 changes: 11 additions & 39 deletions src/ir_Kelon.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,23 @@

/// @file
/// @brief Support for Kelan AC protocol.
/// Both sending and decoding should be functional for models of series KELON
/// ON/OFF 9000-12000.
/// @note Both sending and decoding should be functional for models of series
/// KELON ON/OFF 9000-12000.
/// All features of the standard remote are implemented.
///
/// @note Unsupported:
/// - Explicit on/off due to AC unit limitations
/// - Explicit swing position due to AC unit limitations
/// - Fahrenheit.
///
/// For KELON168:
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1745

// Supports:
// Brand: Kelon, Model: ON/OFF 9000-12000
// Brand: Kelon, Model: ON/OFF 9000-12000 (KELON)
// Brand: Kelon, Model: DG11R2-01 remote (KELON168)
// Brand: Kelon, Model: AST-09UW4RVETG00A A/C (KELON168)
// Brand: Hisense, Model: AST-09UW4RVETG00A A/C (KELON168)

#ifndef IR_KELON_H_
#define IR_KELON_H_
Expand Down Expand Up @@ -71,84 +78,50 @@ class IRKelonAc {
public:
explicit IRKelonAc(uint16_t pin, bool inverted = false,
bool use_modulation = true);

void stateReset(void);

#if SEND_KELON

void send(const uint16_t repeat = kNoRepeat);

/// Run the calibration to calculate uSec timing offsets for this platform.
/// @return The uSec timing offset needed per modulation of the IR Led.
/// @note This will produce a 65ms IR signal pulse at 38kHz.
/// Only ever needs to be run once per object instantiation, if at all.
int8_t calibrate(void) { return _irsend.calibrate(); }

/// Since the AC does not support actually setting the power state to a known
/// value, this utility allow ensuring the AC is on or off by exploiting
/// the fact that the AC, according to the user manual, will always turn on
/// when setting it to "smart" or "super" mode.
void ensurePower(const bool on);

#endif
#endif // SEND_KELON


void begin(void);

void setTogglePower(const bool toggle);

bool getTogglePower(void) const;

void setTemp(const uint8_t degrees);

uint8_t getTemp(void) const;

void setFan(const uint8_t speed);

uint8_t getFan(void) const;

void setDryGrade(const int8_t grade);

int8_t getDryGrade(void) const;

void setMode(const uint8_t mode);

uint8_t getMode(void) const;

void setToggleSwingVertical(const bool toggle);

bool getToggleSwingVertical(void) const;

void setSleep(const bool on);

bool getSleep(void) const;

void setSupercool(const bool on);

bool getSupercool(void) const;

void setTimer(const uint16_t mins);

uint16_t getTimer(void) const;

void setTimerEnabled(const bool on);

bool getTimerEnabled(void) const;

uint64_t getRaw(void) const;

void setRaw(const uint64_t new_code);

static uint8_t convertMode(const stdAc::opmode_t mode);

static uint8_t convertFan(const stdAc::fanspeed_t fan);

static stdAc::opmode_t toCommonMode(const uint8_t mode);

static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed);

stdAc::state_t toCommon(const stdAc::state_t *prev = nullptr) const;

String toString(void) const;

private:
Expand All @@ -166,5 +139,4 @@ class IRKelonAc {
uint8_t _previousTemp = kKelonMinTemp;
uint8_t _previousFan = kKelonFanAuto;
};

#endif // IR_KELON_H_
3 changes: 3 additions & 0 deletions src/locale/defaults.h
Original file line number Diff line number Diff line change
Expand Up @@ -826,6 +826,9 @@ D_STR_INDIRECT " " D_STR_MODE
#ifndef D_STR_KELON
#define D_STR_KELON "KELON"
#endif // D_STR_KELON
#ifndef D_STR_KELON168
#define D_STR_KELON168 D_STR_KELON "168"
#endif // D_STR_KELON168
#ifndef D_STR_KELVINATOR
#define D_STR_KELVINATOR "KELVINATOR"
#endif // D_STR_KELVINATOR
Expand Down
Loading

0 comments on commit b5771a6

Please sign in to comment.