Skip to content

Commit

Permalink
SamsungAc: Add sleep timer support.
Browse files Browse the repository at this point in the history
* Handle interactions between on, off, & sleep timers.
* Add `setSleepTimer()` & `getSleepTimer()`.
* Add sleep support to SamsungAc in `IRac`.
* Add & extend Unit tests accordingly.
* Change some parameters/variable names to better suiting names.
* Update supported devices.

Fixes #1277
  • Loading branch information
crankyoldgit committed Nov 6, 2021
1 parent d82a1d1 commit d860a19
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 27 deletions.
16 changes: 10 additions & 6 deletions src/IRac.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1847,18 +1847,21 @@ void IRac::panasonic32(IRPanasonicAc32 *ac,
/// @param[in] filter Turn on the (ion/pollen/etc) filter mode.
/// @param[in] clean Turn on the self-cleaning mode. e.g. Mould, dry filters etc
/// @param[in] beep Enable/Disable beeps when receiving IR messages.
/// @param[in] sleep Nr. of minutes for sleep mode. <= 0 is Off, > 0 is on.
/// @param[in] prevpower The power setting from the previous A/C state.
/// @param[in] forcepower Do we force send the special power message?
/// @param[in] prevsleep Nr. of minutes for sleep from the previous A/C state.
/// @param[in] forceextended Do we force sending the special extended message?
void IRac::samsung(IRSamsungAc *ac,
const bool on, const stdAc::opmode_t mode,
const float degrees,
const stdAc::fanspeed_t fan, const stdAc::swingv_t swingv,
const bool quiet, const bool turbo, const bool light,
const bool filter, const bool clean,
const bool beep, const bool prevpower,
const bool forcepower) {
const bool beep, const int16_t sleep,
const bool prevpower, const int16_t prevsleep,
const bool forceextended) {
ac->begin();
ac->stateReset(forcepower, prevpower);
ac->stateReset(forceextended || (sleep != prevsleep), prevpower);
ac->setPower(on);
ac->setMode(ac->convertMode(mode));
ac->setTemp(degrees);
Expand All @@ -1872,7 +1875,7 @@ void IRac::samsung(IRSamsungAc *ac,
ac->setIon(filter);
ac->setClean(clean);
ac->setBeep(beep);
// No Sleep setting available.
ac->setSleepTimer((sleep <= 0) ? 0 : sleep);
// No Clock setting available.
// Do setMode() again as it can affect fan speed.
ac->setMode(ac->convertMode(mode));
Expand Down Expand Up @@ -2602,6 +2605,7 @@ bool IRac::sendAc(const stdAc::state_t desired, const stdAc::state_t *prev) {
// Construct a pointer-safe previous power state incase prev is NULL/NULLPTR.
#if (SEND_HITACHI_AC1 || SEND_SAMSUNG_AC || SEND_SHARP_AC)
const bool prev_power = (prev != NULL) ? prev->power : !send.power;
const int16_t prev_sleep = (prev != NULL) ? prev->sleep : -1;
#endif // (SEND_HITACHI_AC1 || SEND_SAMSUNG_AC || SEND_SHARP_AC)
#if (SEND_LG || SEND_SHARP_AC)
const stdAc::swingv_t prev_swingv = (prev != NULL) ? prev->swingv
Expand Down Expand Up @@ -3000,7 +3004,7 @@ bool IRac::sendAc(const stdAc::state_t desired, const stdAc::state_t *prev) {
IRSamsungAc ac(_pin, _inverted, _modulation);
samsung(&ac, send.power, send.mode, degC, send.fanspeed, send.swingv,
send.quiet, send.turbo, send.light, send.filter, send.clean,
send.beep, prev_power);
send.beep, send.sleep, prev_power, prev_sleep);
break;
}
#endif // SEND_SAMSUNG_AC
Expand Down
5 changes: 3 additions & 2 deletions src/IRac.h
Original file line number Diff line number Diff line change
Expand Up @@ -409,8 +409,9 @@ void electra(IRElectraAc *ac,
const stdAc::fanspeed_t fan, const stdAc::swingv_t swingv,
const bool quiet, const bool turbo, const bool light,
const bool filter, const bool clean,
const bool beep, const bool prevpower = true,
const bool forcepower = true);
const bool beep, const int16_t sleep = -1,
const bool prevpower = true, const int16_t prevsleep = -1,
const bool forceextended = true);
#endif // SEND_SAMSUNG_AC
#if SEND_SANYO_AC
void sanyo(IRSanyoAc *ac,
Expand Down
59 changes: 48 additions & 11 deletions src/ir_Samsung.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -276,19 +276,21 @@ IRSamsungAc::IRSamsungAc(const uint16_t pin, const bool inverted,
}

/// Reset the internal state of the emulation.
/// @param[in] forcepower A flag indicating if force sending a special power
/// @param[in] extended A flag indicating if force sending a special extended
/// message with the first `send()` call.
/// @param[in] initialPower Set the initial power state. True, on. False, off.
void IRSamsungAc::stateReset(const bool forcepower, const bool initialPower) {
void IRSamsungAc::stateReset(const bool extended, const bool initialPower) {
static const uint8_t kReset[kSamsungAcExtendedStateLength] = {
0x02, 0x92, 0x0F, 0x00, 0x00, 0x00, 0xF0,
0x01, 0x02, 0xAE, 0x71, 0x00, 0x15, 0xF0};
std::memcpy(_.raw, kReset, kSamsungAcExtendedStateLength);
_forcepower = forcepower;
_forceextended = extended;
_lastsentpowerstate = initialPower;
setPower(initialPower);
_OnTimerEnable = false;
_OffTimerEnable = false;
_Sleep = false;
_lastSleep = false;
_OnTimer = _OffTimer = _lastOnTimer = _lastOffTimer = 0;
}

Expand Down Expand Up @@ -358,9 +360,10 @@ void IRSamsungAc::checksum(void) {
/// @note Use for most function/mode/settings changes to the unit.
/// i.e. When the device is already running.
void IRSamsungAc::send(const uint16_t repeat) {
// Do we need to send a the special (extended) message?
if (getPower() != _lastsentpowerstate || _forcepower ||
(_lastOnTimer != _OnTimer) || (_lastOffTimer != _OffTimer)) // We do.
// Do we need to send a special (extended) message?
if (getPower() != _lastsentpowerstate || _forceextended ||
(_lastOnTimer != _OnTimer) || (_lastOffTimer != _OffTimer) ||
(_Sleep != _lastSleep)) // We do.
sendExtended(repeat);
else // No, it's just a normal message.
_irsend.sendSamsungAC(getRaw(), kSamsungAcStateLength, repeat);
Expand All @@ -385,15 +388,15 @@ void IRSamsungAc::sendExtended(const uint16_t repeat) {
std::memcpy(_.raw + kSamsungAcSectionLength, extended_middle_section,
kSamsungAcSectionLength);
_setOnTimer();
_setOffTimer();
_setSleepTimer(); // This also sets any Off Timer if needed too.
// Send it.
_irsend.sendSamsungAC(getRaw(), kSamsungAcExtendedStateLength, repeat);
// Now revert it by copying the third section over the second section.
std::memcpy(_.raw + kSamsungAcSectionLength,
_.raw + 2 * kSamsungAcSectionLength,
kSamsungAcSectionLength);

_forcepower = false; // Power has now been sent, so clear the flag if set.
_forceextended = false; // It has now been sent, so clear the flag if set.
}

/// Send the special extended "On" message as the library can't seem to
Expand Down Expand Up @@ -440,6 +443,7 @@ void IRSamsungAc::setRaw(const uint8_t new_code[], const uint16_t length) {
if (length > kSamsungAcStateLength) {
_OnTimerEnable = _.OnTimerEnable;
_OffTimerEnable = _.OffTimerEnable;
_Sleep = _.Sleep;
_OnTimer = _getOnTimer();
_OffTimer = _getOffTimer();
for (uint8_t i = kSamsungAcStateLength; i < length; i++)
Expand Down Expand Up @@ -685,31 +689,63 @@ void IRSamsungAc::_setOffTimer(void) {
_.OffTimeHrs2 = hours >> 1;
}

// Set the current Sleep Timer value of the A/C into the raw extended state.
void IRSamsungAc::_setSleepTimer(void) {
_setOffTimer();
// The Sleep mode/timer should only be engaged if an off time has been set.
_.Sleep = _Sleep && _OffTimerEnable;
}

/// Get the On Timer setting of the A/C.
/// @return The Nr. of minutes the On Timer is set for.
uint16_t IRSamsungAc::getOnTimer(void) const { return _OnTimer; }

/// Get the Off Timer setting of the A/C.
/// @return The Nr. of minutes the Off Timer is set for.
uint16_t IRSamsungAc::getOffTimer(void) const { return _OffTimer; }
/// @note Sleep & Off Timer share the same timer.
uint16_t IRSamsungAc::getOffTimer(void) const {
return _Sleep ? 0 : _OffTimer;
}

/// Get the Sleep Timer setting of the A/C.
/// @return The Nr. of minutes the Off Timer is set for.
/// @note Sleep & Off Timer share the same timer.
uint16_t IRSamsungAc::getSleepTimer(void) const {
return _Sleep ? _OffTimer : 0;
}

#define TIMER_RESOLUTION(mins) \
(((std::min((mins), (uint16_t)(24 * 60))) / 10) * 10)

/// Set the On Timer value of the A/C.
/// @param[in] nr_of_mins The number of minutes the timer should be.
/// @note The timer time only has a resolution of 10 mins.
/// @note Setting the On Timer active will cancel the Sleep timer/setting.
void IRSamsungAc::setOnTimer(const uint16_t nr_of_mins) {
// Limit to one day, and round down to nearest 10 min increment.
_OnTimer = TIMER_RESOLUTION(nr_of_mins);
if (_OnTimer) _Sleep = false;
}

/// Set the Off Timer value of the A/C.
/// @param[in] nr_of_mins The number of minutes the timer should be.
/// @note The timer time only has a resolution of 10 mins.
/// @note Setting the Off Timer active will cancel the Sleep timer/setting.
void IRSamsungAc::setOffTimer(const uint16_t nr_of_mins) {
// Limit to one day, and round down to nearest 10 min increment.
_OffTimer = TIMER_RESOLUTION(nr_of_mins);
if (_OffTimer) _Sleep = false;
}

/// Set the Sleep Timer value of the A/C.
/// @param[in] nr_of_mins The number of minutes the timer should be.
/// @note The timer time only has a resolution of 10 mins.
/// @note Sleep timer acts as an Off timer, and cancels any On Timer.
void IRSamsungAc::setSleepTimer(const uint16_t nr_of_mins) {
// Limit to one day, and round down to nearest 10 min increment.
_OffTimer = TIMER_RESOLUTION(nr_of_mins);
if (_OffTimer) setOnTimer(0); // Clear the on timer if set.
_Sleep = _OffTimer > 0;
}

/// Convert a stdAc::opmode_t enum into its native mode.
Expand Down Expand Up @@ -784,10 +820,10 @@ stdAc::state_t IRSamsungAc::toCommon(void) const {
result.beep = _.Beep;
result.light = _.Display;
result.filter = _.Ion;
result.sleep = _Sleep ? getSleepTimer() : -1;
// Not supported.
result.swingh = stdAc::swingh_t::kOff;
result.econo = false;
result.sleep = -1;
result.clock = -1;
return result;
}
Expand Down Expand Up @@ -837,7 +873,8 @@ String IRSamsungAc::toString(void) const {
if (_OnTimerEnable)
result += addLabeledString(minsToString(_OnTimer), kOnTimerStr);
if (_OffTimerEnable)
result += addLabeledString(minsToString(_OffTimer), kOffTimerStr);
result += addLabeledString(minsToString(_OffTimer),
_Sleep ? kSleepTimerStr : kOffTimerStr);
return result;
}

Expand Down
12 changes: 9 additions & 3 deletions src/ir_Samsung.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@
// Brand: Samsung, Model: AH59-02692E Soundbar remote (SAMSUNG36)
// Brand: Samsung, Model: HW-J551 Soundbar (SAMSUNG36)
// Brand: Samsung, Model: AR09FSSDAWKNFA A/C (SAMSUNG_AC)
// Brand: Samsung, Model: AR09HSFSBWKN A/C (SAMSUNG_AC)
// Brand: Samsung, Model: AR12KSFPEWQNET A/C (SAMSUNG_AC)
// Brand: Samsung, Model: AR12HSSDBWKNEU A/C (SAMSUNG_AC)
// Brand: Samsung, Model: AR12NXCXAWKXEU A/C (SAMSUNG_AC)
// Brand: Samsung, Model: AR09HSFSBWKN A/C (SAMSUNG_AC)
// Brand: Samsung, Model: AR12TXEAAWKNEU A/C (SAMSUNG_AC)
// Brand: Samsung, Model: DB93-14195A remote (SAMSUNG_AC)

#ifndef IR_SAMSUNG_H_
Expand Down Expand Up @@ -186,7 +187,7 @@ class IRSamsungAc {
public:
explicit IRSamsungAc(const uint16_t pin, const bool inverted = false,
const bool use_modulation = true);
void stateReset(const bool forcepower = true, const bool initialPower = true);
void stateReset(const bool extended = true, const bool initialPower = true);
#if SEND_SAMSUNG_AC
void send(const uint16_t repeat = kSamsungAcDefaultRepeat);
void sendExtended(const uint16_t repeat = kSamsungAcDefaultRepeat);
Expand Down Expand Up @@ -229,6 +230,8 @@ class IRSamsungAc {
void setOnTimer(const uint16_t nr_of_mins);
uint16_t getOffTimer(void) const;
void setOffTimer(const uint16_t nr_of_mins);
uint16_t getSleepTimer(void) const;
void setSleepTimer(const uint16_t nr_of_mins);
uint8_t* getRaw(void);
void setRaw(const uint8_t new_code[],
const uint16_t length = kSamsungAcStateLength);
Expand All @@ -252,10 +255,12 @@ class IRSamsungAc {
/// @endcond
#endif // UNIT_TEST
SamsungProtocol _;
bool _forcepower; ///< Hack to know when we need to send a special power mesg
bool _forceextended; ///< Flag to know when we need to send an extended mesg.
bool _lastsentpowerstate;
bool _OnTimerEnable;
bool _OffTimerEnable;
bool _Sleep;
bool _lastSleep;
uint16_t _OnTimer;
uint16_t _OffTimer;
uint16_t _lastOnTimer;
Expand All @@ -265,6 +270,7 @@ class IRSamsungAc {
uint16_t _getOffTimer(void) const;
void _setOnTimer(void);
void _setOffTimer(void);
void _setSleepTimer(void);
};

#endif // IR_SAMSUNG_H_
42 changes: 38 additions & 4 deletions test/IRac_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1622,7 +1622,7 @@ TEST(TestIRac, Samsung) {
IRSamsungAc ac(kGpioUnused);
IRac irac(kGpioUnused);
IRrecv capture(kGpioUnused);
char expected[] =
const char expected[] =
"Power: On, Mode: 0 (Auto), Temp: 28C, Fan: 6 (Auto), Swing: On, "
"Beep: On, Clean: On, Quiet: On, Powerful: Off, Breeze: Off, "
"Light: On, Ion: Off";
Expand All @@ -1640,8 +1640,10 @@ TEST(TestIRac, Samsung) {
false, // Filter (Ion)
true, // Clean
true, // Beep
-1, // Sleep
true, // Previous power state
false); // with dopower Off
-1, // Previous Sleep
false); // Force Extended
ASSERT_EQ(expected, ac.toString());
ac._irsend.makeDecodeResult();
EXPECT_TRUE(capture.decode(&ac._irsend.capture));
Expand All @@ -1664,16 +1666,48 @@ TEST(TestIRac, Samsung) {
false, // Filter (Ion)
true, // Clean
true, // Beep
-1, // Sleep
true, // Previous power state
true); // with dopower On
-1, // Previous Sleep
true); // Force Extended
ASSERT_EQ(expected, ac.toString()); // Class should be in the desired mode.
ac._irsend.makeDecodeResult();
EXPECT_TRUE(capture.decode(&ac._irsend.capture));
ASSERT_EQ(SAMSUNG_AC, ac._irsend.capture.decode_type);
// We expect an extended state because of `dopower`.
// We expect an extended state because of `Force Extended`.
ASSERT_EQ(kSamsungAcExtendedBits, ac._irsend.capture.bits);
ASSERT_EQ(expected, IRAcUtils::resultAcToString(&ac._irsend.capture));
ASSERT_TRUE(IRAcUtils::decodeToState(&ac._irsend.capture, &r, &p));

ac._irsend.reset();
const char sleep[] =
"Power: On, Mode: 0 (Auto), Temp: 28C, Fan: 6 (Auto), Swing: On, "
"Beep: On, Clean: On, Quiet: On, Powerful: Off, Breeze: Off, "
"Light: On, Ion: Off, Sleep Timer: 08:00";
irac.samsung(&ac,
true, // Power
stdAc::opmode_t::kAuto, // Mode
28, // Celsius
stdAc::fanspeed_t::kMedium, // Fan speed
stdAc::swingv_t::kAuto, // Vertical swing
true, // Quiet
false, // Turbo
true, // Light (Display)
false, // Filter (Ion)
true, // Clean
true, // Beep
8 * 60, // Sleep
true, // Previous power state
-1, // Previous Sleep
false); // Force Extended
ASSERT_EQ(sleep, ac.toString());
ac._irsend.makeDecodeResult();
EXPECT_TRUE(capture.decode(&ac._irsend.capture));
ASSERT_EQ(SAMSUNG_AC, ac._irsend.capture.decode_type);
// We expect an extended state because of the change in `sleep`.
ASSERT_EQ(kSamsungAcExtendedBits, ac._irsend.capture.bits);
ASSERT_EQ(sleep, IRAcUtils::resultAcToString(&ac._irsend.capture));
ASSERT_TRUE(IRAcUtils::decodeToState(&ac._irsend.capture, &r, &p));
}

TEST(TestIRac, Sanyo) {
Expand Down
Loading

0 comments on commit d860a19

Please sign in to comment.