Skip to content

Commit 673d38e

Browse files
authored
Experimental basic support for Kelon 168 bit / 21 byte protocol. (#1747)
* Add `sendKelon168()` & `decodeKelon168()` * Add & extend unit test coverage. * Code style cleanup. For #1745 Ref #1744
1 parent a3e2c81 commit 673d38e

File tree

11 files changed

+265
-45
lines changed

11 files changed

+265
-45
lines changed

src/IRrecv.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1050,8 +1050,12 @@ bool IRrecv::decode(decode_results *results, irparams_t *save,
10501050
DPRINTLN("Attempting Teknopoint decode");
10511051
if (decodeTeknopoint(results, offset)) return true;
10521052
#endif // DECODE_TEKNOPOINT
1053+
#if DECODE_KELON168
1054+
DPRINTLN("Attempting Kelon 168-bit decode");
1055+
if (decodeKelon168(results, offset)) return true;
1056+
#endif // DECODE_KELON168
10531057
#if DECODE_KELON
1054-
DPRINTLN("Attempting Kelon decode");
1058+
DPRINTLN("Attempting Kelon 48-bit decode");
10551059
if (decodeKelon(results, offset)) return true;
10561060
#endif // DECODE_KELON
10571061
#if DECODE_SANYO_AC88

src/IRrecv.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -773,6 +773,11 @@ class IRrecv {
773773
bool decodeKelon(decode_results *results, uint16_t offset = kStartOffset,
774774
const uint16_t nbits = kKelonBits, const bool strict = true);
775775
#endif // DECODE_KELON
776+
#if DECODE_KELON168
777+
bool decodeKelon168(decode_results *results, uint16_t offset = kStartOffset,
778+
const uint16_t nbits = kKelon168Bits,
779+
const bool strict = true);
780+
#endif // DECODE_KELON168
776781
#if DECODE_BOSE
777782
bool decodeBose(decode_results *results, uint16_t offset = kStartOffset,
778783
const uint16_t nbits = kBoseBits, const bool strict = true);

src/IRremoteESP8266.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -847,6 +847,13 @@
847847
#define SEND_AIRTON _IR_ENABLE_DEFAULT_
848848
#endif // SEND_AIRTON
849849

850+
#ifndef DECODE_KELON168
851+
#define DECODE_KELON168 _IR_ENABLE_DEFAULT_
852+
#endif // DECODE_KELON168
853+
#ifndef SEND_KELON168
854+
#define SEND_KELON168 _IR_ENABLE_DEFAULT_
855+
#endif // SEND_KELON168
856+
850857
#if (DECODE_ARGO || DECODE_DAIKIN || DECODE_FUJITSU_AC || DECODE_GREE || \
851858
DECODE_KELVINATOR || DECODE_MITSUBISHI_AC || DECODE_TOSHIBA_AC || \
852859
DECODE_TROTEC || DECODE_HAIER_AC || DECODE_HITACHI_AC || \
@@ -862,6 +869,7 @@
862869
DECODE_VOLTAS || DECODE_MIRAGE || DECODE_HAIER_AC176 || \
863870
DECODE_TEKNOPOINT || DECODE_KELON || DECODE_TROTEC_3550 || \
864871
DECODE_SANYO_AC88 || DECODE_RHOSS || DECODE_HITACHI_AC264 || \
872+
DECODE_KELON168 || \
865873
false)
866874
// Add any DECODE to the above if it uses result->state (see kStateSizeMax)
867875
// you might also want to add the protocol to hasACState function
@@ -1013,8 +1021,9 @@ enum decode_type_t {
10131021
AIRTON,
10141022
COOLIX48, // 110
10151023
HITACHI_AC264,
1024+
KELON168,
10161025
// Add new entries before this one, and update it to point to the last entry.
1017-
kLastDecodeType = HITACHI_AC264,
1026+
kLastDecodeType = KELON168,
10181027
};
10191028

10201029
// Message lengths & required repeat values
@@ -1134,6 +1143,8 @@ const uint16_t kInaxBits = 24;
11341143
const uint16_t kInaxMinRepeat = kSingleRepeat;
11351144
const uint16_t kJvcBits = 16;
11361145
const uint16_t kKelonBits = 48;
1146+
const uint16_t kKelon168StateLength = 21;
1147+
const uint16_t kKelon168Bits = kKelon168StateLength * 8;
11371148
const uint16_t kKelvinatorStateLength = 16;
11381149
const uint16_t kKelvinatorBits = kKelvinatorStateLength * 8;
11391150
const uint16_t kKelvinatorDefaultRepeat = kNoRepeat;

src/IRsend.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -724,6 +724,8 @@ uint16_t IRsend::defaultBits(const decode_type_t protocol) {
724724
return kHitachiAc344Bits;
725725
case HITACHI_AC424:
726726
return kHitachiAc424Bits;
727+
case KELON168:
728+
return kKelon168Bits;
727729
case KELVINATOR:
728730
return kKelvinatorBits;
729731
case MILESTAG2:
@@ -1227,6 +1229,11 @@ bool IRsend::send(const decode_type_t type, const uint8_t *state,
12271229
sendHitachiAc424(state, nbytes);
12281230
break;
12291231
#endif // SEND_HITACHI_AC424
1232+
#if SEND_KELON168
1233+
case KELON168:
1234+
sendKelon168(state, nbytes);
1235+
break;
1236+
#endif // SEND_KELON168
12301237
#if SEND_KELVINATOR
12311238
case KELVINATOR:
12321239
sendKelvinator(state, nbytes);

src/IRsend.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -760,6 +760,11 @@ class IRsend {
760760
void sendKelon(const uint64_t data, const uint16_t nbits = kKelonBits,
761761
const uint16_t repeat = kNoRepeat);
762762
#endif // SEND_KELON
763+
#if SEND_KELON168
764+
void sendKelon168(const unsigned char data[],
765+
const uint16_t nbytes = kKelon168StateLength,
766+
const uint16_t repeat = kNoRepeat);
767+
#endif // SEND_KELON168
763768
#if SEND_BOSE
764769
void sendBose(const uint64_t data, const uint16_t nbits = kBoseBits,
765770
const uint16_t repeat = kNoRepeat);

src/IRtext.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,7 @@ IRTEXT_CONST_BLOB_DECL(kAllProtocolNamesStr) {
392392
D_STR_AIRTON "\x0"
393393
D_STR_COOLIX48 "\x0"
394394
D_STR_HITACHI_AC264 "\x0"
395+
D_STR_KELON168 "\x0"
395396
///< New protocol strings should be added just above this line.
396397
"\x0" ///< This string requires double null termination.
397398
};

src/IRutils.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ bool hasACState(const decode_type_t protocol) {
193193
case HITACHI_AC264:
194194
case HITACHI_AC344:
195195
case HITACHI_AC424:
196+
case KELON168:
196197
case KELVINATOR:
197198
case MIRAGE:
198199
case MITSUBISHI136:

src/ir_Kelon.cpp

Lines changed: 113 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
// Copyright 2021 Davide Depau
2+
// Copyright 2022 David Conran
23

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

1415
#include <algorithm>
16+
#include <cassert>
1517

1618
#include "ir_Kelon.h"
1719

@@ -39,8 +41,13 @@ const uint16_t kKelonZeroSpace = 600;
3941
const uint32_t kKelonGap = 2 * kDefaultMessageGap;
4042
const uint16_t kKelonFreq = 38000;
4143

44+
const uint32_t kKelon168FooterSpace = 8000;
45+
const uint16_t kKelon168Section1Size = 6;
46+
const uint16_t kKelon168Section2Size = 8;
47+
const uint16_t kKelon168Section3Size = 7;
48+
4249
#if SEND_KELON
43-
/// Send a Kelon message.
50+
/// Send a Kelon 48-bit message.
4451
/// Status: STABLE / Working.
4552
/// @param[in] data The data to be transmitted.
4653
/// @param[in] nbits Nr. of bits of data to be sent.
@@ -52,12 +59,12 @@ void IRsend::sendKelon(const uint64_t data, const uint16_t nbits,
5259
kKelonBitMark, kKelonZeroSpace,
5360
kKelonBitMark, kKelonGap,
5461
data, nbits, kKelonFreq, false, // LSB First.
55-
repeat, 50);
62+
repeat, kDutyDefault);
5663
}
5764
#endif // SEND_KELON
5865

5966
#if DECODE_KELON
60-
/// Decode the supplied Kelon message.
67+
/// Decode the supplied Kelon 48-bit message.
6168
/// Status: STABLE / Working.
6269
/// @param[in,out] results Ptr to the data to decode & where to store the result
6370
/// @param[in] offset The starting index to use when attempting to decode the
@@ -440,3 +447,105 @@ String IRKelonAc::toString() const {
440447
result += addBoolToString(true, kSwingVToggleStr);
441448
return result;
442449
}
450+
451+
#if SEND_KELON168
452+
/// Send a Kelon 168 bit / 21 byte message.
453+
/// Status: BETA / Probably works.
454+
/// @param[in] data The data to be transmitted.
455+
/// @param[in] nbytes Nr. of bytes of data to be sent.
456+
/// @param[in] repeat The number of times the command is to be repeated.
457+
void IRsend::sendKelon168(const uint8_t data[], const uint16_t nbytes,
458+
const uint16_t repeat) {
459+
assert(kKelon168StateLength == kKelon168Section1Size + kKelon168Section2Size +
460+
kKelon168Section3Size);
461+
// Enough bytes to send a proper message?
462+
if (nbytes < kKelon168StateLength) return;
463+
464+
for (uint16_t r = 0; r <= repeat; r++) {
465+
// Section #1 (48 bits)
466+
sendGeneric(kKelonHdrMark, kKelonHdrSpace,
467+
kKelonBitMark, kKelonOneSpace,
468+
kKelonBitMark, kKelonZeroSpace,
469+
kKelonBitMark, kKelon168FooterSpace,
470+
data, kKelon168Section1Size, kKelonFreq, false, // LSB First.
471+
0, // No repeats here
472+
kDutyDefault);
473+
// Section #2 (64 bits)
474+
sendGeneric(0, 0,
475+
kKelonBitMark, kKelonOneSpace,
476+
kKelonBitMark, kKelonZeroSpace,
477+
kKelonBitMark, kKelon168FooterSpace,
478+
data + kKelon168Section1Size, kKelon168Section2Size,
479+
kKelonFreq, false, // LSB First.
480+
0, // No repeats here
481+
kDutyDefault);
482+
// Section #3 (56 bits)
483+
sendGeneric(0, 0,
484+
kKelonBitMark, kKelonOneSpace,
485+
kKelonBitMark, kKelonZeroSpace,
486+
kKelonBitMark, kKelonGap,
487+
data + kKelon168Section1Size + kKelon168Section2Size,
488+
nbytes - (kKelon168Section1Size + kKelon168Section2Size),
489+
kKelonFreq, false, // LSB First.
490+
0, // No repeats here
491+
kDutyDefault);
492+
}
493+
}
494+
#endif // SEND_KELON168
495+
496+
#if DECODE_KELON168
497+
/// Decode the supplied Kelon 168 bit / 21 byte message.
498+
/// Status: BETA / Probably Working.
499+
/// @param[in,out] results Ptr to the data to decode & where to store the result
500+
/// @param[in] offset The starting index to use when attempting to decode the
501+
/// raw data. Typically/Defaults to kStartOffset.
502+
/// @param[in] nbits The number of data bits to expect.
503+
/// @param[in] strict Flag indicating if we should perform strict matching.
504+
/// @return True if it can decode it, false if it can't.
505+
bool IRrecv::decodeKelon168(decode_results *results, uint16_t offset,
506+
const uint16_t nbits, const bool strict) {
507+
if (strict && nbits != kKelon168Bits) return false;
508+
if (results->rawlen <= 2 * nbits + kHeader + kFooter * 2 - 1 + offset)
509+
return false; // Can't possibly be a valid Kelon 168 bit message.
510+
511+
uint16_t used = 0;
512+
513+
used = matchGeneric(results->rawbuf + offset, results->state,
514+
results->rawlen - offset, kKelon168Section1Size * 8,
515+
kKelonHdrMark, kKelonHdrSpace,
516+
kKelonBitMark, kKelonOneSpace,
517+
kKelonBitMark, kKelonZeroSpace,
518+
kKelonBitMark, kKelon168FooterSpace,
519+
false, _tolerance, 0, false);
520+
if (!used) return false; // Failed to match.
521+
offset += used;
522+
523+
used = matchGeneric(results->rawbuf + offset,
524+
results->state + kKelon168Section1Size,
525+
results->rawlen - offset, kKelon168Section2Size * 8,
526+
0, 0,
527+
kKelonBitMark, kKelonOneSpace,
528+
kKelonBitMark, kKelonZeroSpace,
529+
kKelonBitMark, kKelon168FooterSpace,
530+
false, _tolerance, 0, false);
531+
if (!used) return false; // Failed to match.
532+
offset += used;
533+
534+
used = matchGeneric(results->rawbuf + offset,
535+
results->state + (kKelon168Section1Size +
536+
kKelon168Section2Size),
537+
results->rawlen - offset,
538+
nbits - (kKelon168Section1Size +
539+
kKelon168Section2Size) * 8,
540+
0, 0,
541+
kKelonBitMark, kKelonOneSpace,
542+
kKelonBitMark, kKelonZeroSpace,
543+
kKelonBitMark, kKelonGap,
544+
true, _tolerance, 0, false);
545+
if (!used) return false; // Failed to match.
546+
547+
results->decode_type = decode_type_t::KELON168;
548+
results->bits = nbits;
549+
return true;
550+
}
551+
#endif // DECODE_KELON168

src/ir_Kelon.h

Lines changed: 11 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,23 @@
22

33
/// @file
44
/// @brief Support for Kelan AC protocol.
5-
/// Both sending and decoding should be functional for models of series KELON
6-
/// ON/OFF 9000-12000.
5+
/// @note Both sending and decoding should be functional for models of series
6+
/// KELON ON/OFF 9000-12000.
77
/// All features of the standard remote are implemented.
88
///
99
/// @note Unsupported:
1010
/// - Explicit on/off due to AC unit limitations
1111
/// - Explicit swing position due to AC unit limitations
1212
/// - Fahrenheit.
13+
///
14+
/// For KELON168:
15+
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1745
16+
1317
// Supports:
14-
// Brand: Kelon, Model: ON/OFF 9000-12000
18+
// Brand: Kelon, Model: ON/OFF 9000-12000 (KELON)
19+
// Brand: Kelon, Model: DG11R2-01 remote (KELON168)
20+
// Brand: Kelon, Model: AST-09UW4RVETG00A A/C (KELON168)
21+
// Brand: Hisense, Model: AST-09UW4RVETG00A A/C (KELON168)
1522

1623
#ifndef IR_KELON_H_
1724
#define IR_KELON_H_
@@ -71,84 +78,50 @@ class IRKelonAc {
7178
public:
7279
explicit IRKelonAc(uint16_t pin, bool inverted = false,
7380
bool use_modulation = true);
74-
7581
void stateReset(void);
76-
7782
#if SEND_KELON
78-
7983
void send(const uint16_t repeat = kNoRepeat);
80-
8184
/// Run the calibration to calculate uSec timing offsets for this platform.
8285
/// @return The uSec timing offset needed per modulation of the IR Led.
8386
/// @note This will produce a 65ms IR signal pulse at 38kHz.
8487
/// Only ever needs to be run once per object instantiation, if at all.
8588
int8_t calibrate(void) { return _irsend.calibrate(); }
86-
8789
/// Since the AC does not support actually setting the power state to a known
8890
/// value, this utility allow ensuring the AC is on or off by exploiting
8991
/// the fact that the AC, according to the user manual, will always turn on
9092
/// when setting it to "smart" or "super" mode.
9193
void ensurePower(const bool on);
92-
93-
#endif
94+
#endif // SEND_KELON
9495

9596

9697
void begin(void);
97-
9898
void setTogglePower(const bool toggle);
99-
10099
bool getTogglePower(void) const;
101-
102100
void setTemp(const uint8_t degrees);
103-
104101
uint8_t getTemp(void) const;
105-
106102
void setFan(const uint8_t speed);
107-
108103
uint8_t getFan(void) const;
109-
110104
void setDryGrade(const int8_t grade);
111-
112105
int8_t getDryGrade(void) const;
113-
114106
void setMode(const uint8_t mode);
115-
116107
uint8_t getMode(void) const;
117-
118108
void setToggleSwingVertical(const bool toggle);
119-
120109
bool getToggleSwingVertical(void) const;
121-
122110
void setSleep(const bool on);
123-
124111
bool getSleep(void) const;
125-
126112
void setSupercool(const bool on);
127-
128113
bool getSupercool(void) const;
129-
130114
void setTimer(const uint16_t mins);
131-
132115
uint16_t getTimer(void) const;
133-
134116
void setTimerEnabled(const bool on);
135-
136117
bool getTimerEnabled(void) const;
137-
138118
uint64_t getRaw(void) const;
139-
140119
void setRaw(const uint64_t new_code);
141-
142120
static uint8_t convertMode(const stdAc::opmode_t mode);
143-
144121
static uint8_t convertFan(const stdAc::fanspeed_t fan);
145-
146122
static stdAc::opmode_t toCommonMode(const uint8_t mode);
147-
148123
static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed);
149-
150124
stdAc::state_t toCommon(const stdAc::state_t *prev = nullptr) const;
151-
152125
String toString(void) const;
153126

154127
private:
@@ -166,5 +139,4 @@ class IRKelonAc {
166139
uint8_t _previousTemp = kKelonMinTemp;
167140
uint8_t _previousFan = kKelonFanAuto;
168141
};
169-
170142
#endif // IR_KELON_H_

src/locale/defaults.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -826,6 +826,9 @@ D_STR_INDIRECT " " D_STR_MODE
826826
#ifndef D_STR_KELON
827827
#define D_STR_KELON "KELON"
828828
#endif // D_STR_KELON
829+
#ifndef D_STR_KELON168
830+
#define D_STR_KELON168 D_STR_KELON "168"
831+
#endif // D_STR_KELON168
829832
#ifndef D_STR_KELVINATOR
830833
#define D_STR_KELVINATOR "KELVINATOR"
831834
#endif // D_STR_KELVINATOR

0 commit comments

Comments
 (0)