Skip to content

Commit dbc8e86

Browse files
authored
TCL112AC: Add support for quiet/mute setting. (#1529)
* Changes required to handle quiet setting, which is sent in a separate special message. * Modify checksum alg. for these special messages. * Add `IRac` support for quiet for TCL112 * Add & update unit tests. * Update supported models * General code style cleanups. Fixes #1528
1 parent 2d4659c commit dbc8e86

File tree

6 files changed

+322
-133
lines changed

6 files changed

+322
-133
lines changed

src/IRac.cpp

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1861,6 +1861,7 @@ void IRac::sharp(IRSharpAc *ac, const sharp_ac_remote_model_t model,
18611861
/// @param[in] fan The speed setting for the fan.
18621862
/// @param[in] swingv The vertical swing setting.
18631863
/// @param[in] swingh The horizontal swing setting.
1864+
/// @param[in] quiet Run the device in quiet/silent mode.
18641865
/// @param[in] turbo Run the device in turbo/powerful mode.
18651866
/// @param[in] light Turn on the LED/Display mode.
18661867
/// @param[in] econo Run the device in economical mode.
@@ -1869,16 +1870,16 @@ void IRac::tcl112(IRTcl112Ac *ac,
18691870
const bool on, const stdAc::opmode_t mode,
18701871
const float degrees, const stdAc::fanspeed_t fan,
18711872
const stdAc::swingv_t swingv, const stdAc::swingh_t swingh,
1872-
const bool turbo, const bool light, const bool econo,
1873-
const bool filter) {
1873+
const bool quiet, const bool turbo, const bool light,
1874+
const bool econo, const bool filter) {
18741875
ac->begin();
18751876
ac->setPower(on);
18761877
ac->setMode(ac->convertMode(mode));
18771878
ac->setTemp(degrees);
18781879
ac->setFan(ac->convertFan(fan));
18791880
ac->setSwingVertical(swingv != stdAc::swingv_t::kOff);
18801881
ac->setSwingHorizontal(swingh != stdAc::swingh_t::kOff);
1881-
// No Quiet setting available.
1882+
ac->setQuiet(quiet);
18821883
ac->setTurbo(turbo);
18831884
ac->setLight(light);
18841885
ac->setEcono(econo);
@@ -2771,7 +2772,8 @@ bool IRac::sendAc(const stdAc::state_t desired, const stdAc::state_t *prev) {
27712772
{
27722773
IRTcl112Ac ac(_pin, _inverted, _modulation);
27732774
tcl112(&ac, send.power, send.mode, degC, send.fanspeed, send.swingv,
2774-
send.swingh, send.turbo, send.light, send.econo, send.filter);
2775+
send.swingh, send.quiet, send.turbo, send.light, send.econo,
2776+
send.filter);
27752777
break;
27762778
}
27772779
#endif // SEND_TCL112AC
@@ -3957,7 +3959,7 @@ namespace IRAcUtils {
39573959
case decode_type_t::TCL112AC: {
39583960
IRTcl112Ac ac(kGpioUnused);
39593961
ac.setRaw(decode->state);
3960-
*result = ac.toCommon();
3962+
*result = ac.toCommon(prev);
39613963
break;
39623964
}
39633965
#endif // DECODE_TCL112AC

src/IRac.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -405,8 +405,8 @@ void electra(IRElectraAc *ac,
405405
const bool on, const stdAc::opmode_t mode, const float degrees,
406406
const stdAc::fanspeed_t fan,
407407
const stdAc::swingv_t swingv, const stdAc::swingh_t swingh,
408-
const bool turbo, const bool light, const bool econo,
409-
const bool filter);
408+
const bool quiet, const bool turbo, const bool light,
409+
const bool econo, const bool filter);
410410
#endif // SEND_TCL112AC
411411
#if SEND_TECHNIBEL_AC
412412
void technibel(IRTechnibelAc *ac,

src/ir_Tcl.cpp

Lines changed: 104 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2019 David Conran
1+
// Copyright 2019, 2021 David Conran
22

33
/// @file
44
/// @brief Support for TCL protocols.
@@ -53,6 +53,26 @@ void IRTcl112Ac::begin(void) { _irsend.begin(); }
5353
/// Send the current internal state as an IR message.
5454
/// @param[in] repeat Nr. of times the message will be repeated.
5555
void IRTcl112Ac::send(const uint16_t repeat) {
56+
uint8_t save[kTcl112AcStateLength];
57+
// Do we need to send the special "quiet" message?
58+
if (_quiet != _quiet_prev) {
59+
// Backup the current state.
60+
std::memcpy(save, _.raw, kTcl112AcStateLength);
61+
const uint8_t quiet_off[kTcl112AcStateLength] = {
62+
0x23, 0xCB, 0x26, 0x02, 0x00, 0x40, 0x00,
63+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65};
64+
// Use a known good quiet/mute off/type 2 state for the time being.
65+
// Ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/1528#issuecomment-876989044
66+
setRaw(quiet_off);
67+
setQuiet(_quiet);
68+
// Send it.
69+
_irsend.sendTcl112Ac(getRaw(), kTcl112AcStateLength, repeat);
70+
// Now it's been sent, update the quiet previous state.
71+
_quiet_prev = _quiet;
72+
// Restore the old state.
73+
setRaw(save);
74+
}
75+
// Send the normal (type 1) state.
5676
_irsend.sendTcl112Ac(getRaw(), kTcl112AcStateLength, repeat);
5777
}
5878
#endif // SEND_TCL112AC
@@ -62,10 +82,15 @@ void IRTcl112Ac::send(const uint16_t repeat) {
6282
/// @param[in] length The length/size of the array.
6383
/// @return The calculated checksum value.
6484
uint8_t IRTcl112Ac::calcChecksum(uint8_t state[], const uint16_t length) {
65-
if (length)
66-
return sumBytes(state, length - 1);
67-
else
85+
if (length) {
86+
if (length > 4 && state[3] == 0x02) { // Special nessage?
87+
return sumBytes(state, length - 1, 0xF); // Checksum needs an offset.
88+
} else {
89+
return sumBytes(state, length - 1);
90+
}
91+
} else {
6892
return 0;
93+
}
6994
}
7095

7196
/// Calculate & set the checksum for the current internal state of the remote.
@@ -91,6 +116,9 @@ void IRTcl112Ac::stateReset(void) {
91116
0x23, 0xCB, 0x26, 0x01, 0x00, 0x24, 0x03, 0x07, 0x40, 0x00, 0x00, 0x00,
92117
0x00, 0x03};
93118
std::memcpy(_.raw, reset, kTcl112AcStateLength);
119+
_quiet = false;
120+
_quiet_prev = false;
121+
_quiet_explictly_set = false;
94122
}
95123

96124
/// Get a PTR to the internal state/code for this protocol.
@@ -115,21 +143,15 @@ void IRTcl112Ac::off(void) { setPower(false); }
115143

116144
/// Change the power setting.
117145
/// @param[in] on true, the setting is on. false, the setting is off.
118-
void IRTcl112Ac::setPower(const bool on) {
119-
_.Power = on;
120-
}
146+
void IRTcl112Ac::setPower(const bool on) { _.Power = on; }
121147

122148
/// Get the value of the current power setting.
123149
/// @return true, the setting is on. false, the setting is off.
124-
bool IRTcl112Ac::getPower(void) const {
125-
return _.Power;
126-
}
150+
bool IRTcl112Ac::getPower(void) const { return _.Power; }
127151

128152
/// Get the operating mode setting of the A/C.
129153
/// @return The current operating mode setting.
130-
uint8_t IRTcl112Ac::getMode(void) const {
131-
return _.Mode;
132-
}
154+
uint8_t IRTcl112Ac::getMode(void) const { return _.Mode; }
133155

134156
/// Set the operating mode of the A/C.
135157
/// @param[in] mode The desired operating mode.
@@ -193,57 +215,39 @@ void IRTcl112Ac::setFan(const uint8_t speed) {
193215

194216
/// Get the current fan speed setting.
195217
/// @return The current fan speed/mode.
196-
uint8_t IRTcl112Ac::getFan(void) const {
197-
return _.Fan;
198-
}
218+
uint8_t IRTcl112Ac::getFan(void) const { return _.Fan; }
199219

200220
/// Set the economy setting of the A/C.
201221
/// @param[in] on true, the setting is on. false, the setting is off.
202-
void IRTcl112Ac::setEcono(const bool on) {
203-
_.Econo = on;
204-
}
222+
void IRTcl112Ac::setEcono(const bool on) { _.Econo = on; }
205223

206224
/// Get the economy setting of the A/C.
207225
/// @return true, the setting is on. false, the setting is off.
208-
bool IRTcl112Ac::getEcono(void) const {
209-
return _.Econo;
210-
}
226+
bool IRTcl112Ac::getEcono(void) const { return _.Econo; }
211227

212228
/// Set the Health (Filter) setting of the A/C.
213229
/// @param[in] on true, the setting is on. false, the setting is off.
214-
void IRTcl112Ac::setHealth(const bool on) {
215-
_.Health = on;
216-
}
230+
void IRTcl112Ac::setHealth(const bool on) { _.Health = on; }
217231

218232
/// Get the Health (Filter) setting of the A/C.
219233
/// @return true, the setting is on. false, the setting is off.
220-
bool IRTcl112Ac::getHealth(void) const {
221-
return _.Health;
222-
}
234+
bool IRTcl112Ac::getHealth(void) const { return _.Health; }
223235

224236
/// Set the Light (LED/Display) setting of the A/C.
225237
/// @param[in] on true, the setting is on. false, the setting is off.
226-
void IRTcl112Ac::setLight(const bool on) {
227-
_.Light = !on; // Cleared when on.
228-
}
238+
void IRTcl112Ac::setLight(const bool on) { _.Light = !on; } // Cleared when on.
229239

230240
/// Get the Light (LED/Display) setting of the A/C.
231241
/// @return true, the setting is on. false, the setting is off.
232-
bool IRTcl112Ac::getLight(void) const {
233-
return !_.Light;
234-
}
242+
bool IRTcl112Ac::getLight(void) const { return !_.Light; }
235243

236244
/// Set the horizontal swing setting of the A/C.
237245
/// @param[in] on true, the setting is on. false, the setting is off.
238-
void IRTcl112Ac::setSwingHorizontal(const bool on) {
239-
_.SwingH = on;
240-
}
246+
void IRTcl112Ac::setSwingHorizontal(const bool on) { _.SwingH = on; }
241247

242248
/// Get the horizontal swing setting of the A/C.
243249
/// @return true, the setting is on. false, the setting is off.
244-
bool IRTcl112Ac::getSwingHorizontal(void) const {
245-
return _.SwingH;
246-
}
250+
bool IRTcl112Ac::getSwingHorizontal(void) const { return _.SwingH; }
247251

248252
/// Set the vertical swing setting of the A/C.
249253
/// @param[in] on true, the setting is on. false, the setting is off.
@@ -253,9 +257,7 @@ void IRTcl112Ac::setSwingVertical(const bool on) {
253257

254258
/// Get the vertical swing setting of the A/C.
255259
/// @return true, the setting is on. false, the setting is off.
256-
bool IRTcl112Ac::getSwingVertical(void) const {
257-
return _.SwingV;
258-
}
260+
bool IRTcl112Ac::getSwingVertical(void) const { return _.SwingV; }
259261

260262
/// Set the Turbo setting of the A/C.
261263
/// @param[in] on true, the setting is on. false, the setting is off.
@@ -269,8 +271,24 @@ void IRTcl112Ac::setTurbo(const bool on) {
269271

270272
/// Get the Turbo setting of the A/C.
271273
/// @return true, the setting is on. false, the setting is off.
272-
bool IRTcl112Ac::getTurbo(void) const {
273-
return _.Turbo;
274+
bool IRTcl112Ac::getTurbo(void) const { return _.Turbo; }
275+
276+
/// Set the Quiet setting of the A/C.
277+
/// @param[in] on true, the setting is on. false, the setting is off.
278+
void IRTcl112Ac::setQuiet(const bool on) {
279+
_quiet_explictly_set = true;
280+
_quiet = on;
281+
if (_.MsgType == kTcl112AcSpecial) _.Quiet = on;
282+
}
283+
284+
/// Get the Quiet setting of the A/C.
285+
/// @param[in] def The default value to use if we are not sure.
286+
/// @return true, the setting is on. false, the setting is off.
287+
bool IRTcl112Ac::getQuiet(const bool def) const {
288+
if (_.MsgType == kTcl112AcSpecial)
289+
return _.Quiet;
290+
else
291+
return _quiet_explictly_set ? _quiet : def;
274292
}
275293

276294
/// Convert a stdAc::opmode_t enum into its native mode.
@@ -326,26 +344,30 @@ stdAc::fanspeed_t IRTcl112Ac::toCommonFanSpeed(const uint8_t spd) {
326344
}
327345

328346
/// Convert the current internal state into its stdAc::state_t equivalent.
347+
/// @param[in] prev Ptr to the previous state if required.
329348
/// @return The stdAc equivalent of the native settings.
330-
stdAc::state_t IRTcl112Ac::toCommon(void) const {
349+
stdAc::state_t IRTcl112Ac::toCommon(const stdAc::state_t *prev) const {
331350
stdAc::state_t result;
351+
// Start with the previous state if given it.
352+
if (prev != NULL) result = *prev;
332353
result.protocol = decode_type_t::TCL112AC;
333354
result.model = -1; // Not supported.
334-
result.power = _.Power;
335-
result.mode = toCommonMode(_.Mode);
336-
result.celsius = true;
337-
result.degrees = getTemp();
338-
result.fanspeed = toCommonFanSpeed(_.Fan);
339-
result.swingv = _.SwingV ? stdAc::swingv_t::kAuto :
340-
stdAc::swingv_t::kOff;
341-
result.swingh = _.SwingH ? stdAc::swingh_t::kAuto :
342-
stdAc::swingh_t::kOff;
343-
result.turbo = _.Turbo;
344-
result.light = getLight();
345-
result.filter = _.Health;
346-
result.econo = _.Econo;
355+
result.quiet = getQuiet(result.quiet);
356+
// The rest only get updated if it is a "normal" message.
357+
if (_.MsgType == kTcl112AcNormal) {
358+
result.power = _.Power;
359+
result.mode = toCommonMode(_.Mode);
360+
result.celsius = true;
361+
result.degrees = getTemp();
362+
result.fanspeed = toCommonFanSpeed(_.Fan);
363+
result.swingv = _.SwingV ? stdAc::swingv_t::kAuto : stdAc::swingv_t::kOff;
364+
result.swingh = _.SwingH ? stdAc::swingh_t::kAuto : stdAc::swingh_t::kOff;
365+
result.turbo = _.Turbo;
366+
result.filter = _.Health;
367+
result.econo = _.Econo;
368+
result.light = getLight();
369+
}
347370
// Not supported.
348-
result.quiet = false;
349371
result.clean = false;
350372
result.beep = false;
351373
result.sleep = -1;
@@ -357,19 +379,28 @@ stdAc::state_t IRTcl112Ac::toCommon(void) const {
357379
/// @return A human readable string.
358380
String IRTcl112Ac::toString(void) const {
359381
String result = "";
360-
result.reserve(140); // Reserve some heap for the string to reduce fragging.
361-
result += addBoolToString(_.Power, kPowerStr, false);
362-
result += addModeToString(_.Mode, kTcl112AcAuto, kTcl112AcCool,
363-
kTcl112AcHeat, kTcl112AcDry, kTcl112AcFan);
364-
result += addTempFloatToString(getTemp());
365-
result += addFanToString(_.Fan, kTcl112AcFanHigh, kTcl112AcFanLow,
366-
kTcl112AcFanAuto, kTcl112AcFanAuto, kTcl112AcFanMed);
367-
result += addBoolToString(_.Econo, kEconoStr);
368-
result += addBoolToString(_.Health, kHealthStr);
369-
result += addBoolToString(getLight(), kLightStr);
370-
result += addBoolToString(_.Turbo, kTurboStr);
371-
result += addBoolToString(_.SwingH, kSwingHStr);
372-
result += addBoolToString(_.SwingV, kSwingVStr);
382+
result.reserve(150); // Reserve some heap for the string to reduce fragging.
383+
result += addIntToString(_.MsgType, D_STR_TYPE, false);
384+
switch (_.MsgType) {
385+
case kTcl112AcNormal:
386+
result += addBoolToString(_.Power, kPowerStr);
387+
result += addModeToString(_.Mode, kTcl112AcAuto, kTcl112AcCool,
388+
kTcl112AcHeat, kTcl112AcDry, kTcl112AcFan);
389+
result += addTempFloatToString(getTemp());
390+
result += addFanToString(_.Fan, kTcl112AcFanHigh, kTcl112AcFanLow,
391+
kTcl112AcFanAuto, kTcl112AcFanAuto,
392+
kTcl112AcFanMed);
393+
result += addBoolToString(_.Econo, kEconoStr);
394+
result += addBoolToString(_.Health, kHealthStr);
395+
result += addBoolToString(_.Turbo, kTurboStr);
396+
result += addBoolToString(_.SwingH, kSwingHStr);
397+
result += addBoolToString(_.SwingV, kSwingVStr);
398+
result += addBoolToString(getLight(), kLightStr);
399+
break;
400+
case kTcl112AcSpecial:
401+
result += addBoolToString(_.Quiet, kQuietStr);
402+
break;
403+
}
373404
return result;
374405
}
375406

0 commit comments

Comments
 (0)