From d82a1d1e019680d1955c56d71619bfb8173a5ed1 Mon Sep 17 00:00:00 2001 From: crankyoldgit Date: Sat, 6 Nov 2021 08:28:14 +1000 Subject: [PATCH 1/3] [SamsungAc] Add support for On & Off Timers * Merge Powerful & Breeze bit settings. * Add basics for setting and decoding On & Offtimers in extended messages. * Unit testing for timer functions. * Remove optional checksum calcs from `send()` & `sendExtended()` since checksums now work fine. * Improve logic for when an Extended Message needs to be sent. TODO: * Add Sleep timer/function * Tests for timer with `send()`/`sendExtended()` For #1277 --- src/ir_Samsung.cpp | 123 ++++++++++++++++++++++++++++++++------- src/ir_Samsung.h | 99 ++++++++++++++++++------------- test/ir_Samsung_test.cpp | 120 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 281 insertions(+), 61 deletions(-) diff --git a/src/ir_Samsung.cpp b/src/ir_Samsung.cpp index 65a2d567d..e74ee1b7f 100644 --- a/src/ir_Samsung.cpp +++ b/src/ir_Samsung.cpp @@ -68,6 +68,7 @@ using irutils::addIntToString; using irutils::addLabeledString; using irutils::addModeToString; using irutils::addTempToString; +using irutils::minsToString; #if SEND_SAMSUNG /// Send a 32-bit Samsung formatted message. @@ -286,6 +287,9 @@ void IRSamsungAc::stateReset(const bool forcepower, const bool initialPower) { _forcepower = forcepower; _lastsentpowerstate = initialPower; setPower(initialPower); + _OnTimerEnable = false; + _OffTimerEnable = false; + _OnTimer = _OffTimer = _lastOnTimer = _lastOffTimer = 0; } /// Set up hardware to be able to send a message. @@ -351,28 +355,25 @@ void IRSamsungAc::checksum(void) { #if SEND_SAMSUNG_AC /// Send the current internal state as an IR message. /// @param[in] repeat Nr. of times the message will be repeated. -/// @param[in] calcchecksum Do we update the checksum before sending? /// @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, const bool calcchecksum) { - // Do we need to send a the special power on/off message? i.e. An Extended Msg - if (getPower() != _lastsentpowerstate || _forcepower) { // We do. - sendExtended(repeat, calcchecksum); - _forcepower = false; // It has now been sent, so clear the flag if set. - } else { // No, it's just a normal message. - if (calcchecksum) checksum(); - _irsend.sendSamsungAC(_.raw, kSamsungAcStateLength, repeat); - } +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. + sendExtended(repeat); + else // No, it's just a normal message. + _irsend.sendSamsungAC(getRaw(), kSamsungAcStateLength, repeat); } /// Send the extended current internal state as an IR message. /// @param[in] repeat Nr. of times the message will be repeated. -/// @param[in] calcchecksum Do we update the checksum before sending? -/// @note Use this for when you need to power on/off the device. -/// Samsung A/C requires an extended length message when you want to -/// change the power operating mode of the A/C unit. -void IRSamsungAc::sendExtended(const uint16_t repeat, const bool calcchecksum) { +/// @note Samsung A/C requires an extended length message when you want to +/// change the power operating mode, Timers, or Sleep setting of the A/C unit. +void IRSamsungAc::sendExtended(const uint16_t repeat) { _lastsentpowerstate = getPower(); // Remember the last power state sent. + _lastOnTimer = _OnTimer; + _lastOffTimer = _OffTimer; static const uint8_t extended_middle_section[kSamsungAcSectionLength] = { 0x01, 0xD2, 0x0F, 0x00, 0x00, 0x00, 0x00}; // Copy/convert the internal state to an extended state by @@ -383,13 +384,16 @@ void IRSamsungAc::sendExtended(const uint16_t repeat, const bool calcchecksum) { kSamsungAcSectionLength); std::memcpy(_.raw + kSamsungAcSectionLength, extended_middle_section, kSamsungAcSectionLength); - if (calcchecksum) checksum(); + _setOnTimer(); + _setOffTimer(); // Send it. - _irsend.sendSamsungAC(_.raw, kSamsungAcExtendedStateLength, repeat); + _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. } /// Send the special extended "On" message as the library can't seem to @@ -434,6 +438,10 @@ void IRSamsungAc::setRaw(const uint8_t new_code[], const uint16_t length) { kSamsungAcExtendedStateLength)); // Shrink the extended state into a normal state. if (length > kSamsungAcStateLength) { + _OnTimerEnable = _.OnTimerEnable; + _OffTimerEnable = _.OffTimerEnable; + _OnTimer = _getOnTimer(); + _OffTimer = _getOffTimer(); for (uint8_t i = kSamsungAcStateLength; i < length; i++) _.raw[i - kSamsungAcSectionLength] = _.raw[i]; } @@ -580,7 +588,7 @@ void IRSamsungAc::setQuiet(const bool on) { /// Get the Powerful (Turbo) setting of the A/C. /// @return true, the setting is on. false, the setting is off. bool IRSamsungAc::getPowerful(void) const { - return (_.Powerful == kSamsungAcPowerfulOn) && + return (_.FanSpecial == kSamsungAcPowerfulOn) && (_.Fan == kSamsungAcFanTurbo); } @@ -588,7 +596,7 @@ bool IRSamsungAc::getPowerful(void) const { /// @param[in] on true, the setting is on. false, the setting is off. void IRSamsungAc::setPowerful(const bool on) { uint8_t off_value = getBreeze() ? kSamsungAcBreezeOn : 0b000; - _.Powerful = (on ? kSamsungAcPowerfulOn : off_value); + _.FanSpecial = (on ? kSamsungAcPowerfulOn : off_value); if (on) { // Powerful mode sets fan speed to Turbo. setFan(kSamsungAcFanTurbo); @@ -603,7 +611,7 @@ void IRSamsungAc::setPowerful(const bool on) { /// @return true, the setting is on. false, the setting is off. /// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1062 bool IRSamsungAc::getBreeze(void) const { - return (_.Breeze == kSamsungAcBreezeOn) && + return (_.FanSpecial == kSamsungAcBreezeOn) && (_.Fan == kSamsungAcFanAuto && !getSwing()); } @@ -612,7 +620,7 @@ bool IRSamsungAc::getBreeze(void) const { /// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1062 void IRSamsungAc::setBreeze(const bool on) { const uint8_t off_value = getPowerful() ? kSamsungAcPowerfulOn : 0b000; - _.Breeze = (on ? kSamsungAcBreezeOn : off_value); + _.FanSpecial = (on ? kSamsungAcBreezeOn : off_value); if (on) { setFan(kSamsungAcFanAuto); setSwing(false); @@ -635,6 +643,75 @@ bool IRSamsungAc::getIon(void) const { return _.Ion; } /// @param[in] on true, the setting is on. false, the setting is off. void IRSamsungAc::setIon(const bool on) { _.Ion = on; } +/// Get the On Timer setting of the A/C from a raw extended state. +/// @return The Nr. of minutes the On Timer is set for. +uint16_t IRSamsungAc::_getOnTimer(void) const { + if (_.OnTimeDay) return 24 * 60; + return (_.OnTimeHrs2 * 2 + _.OnTimeHrs1) * 60 + _.OnTimeMins * 10; +} + +/// Set the current On Timer value of the A/C into the raw extended state. +void IRSamsungAc::_setOnTimer(void) { + _.OnTimerEnable = _OnTimerEnable = (_OnTimer > 0); + _.OnTimeDay = (_OnTimer >= 24 * 60); + if (_.OnTimeDay) { + _.OnTimeHrs2 = _.OnTimeHrs1 = _.OnTimeMins = 0; + return; + } + _.OnTimeMins = (_OnTimer % 60) / 10; + const uint8_t hours = _OnTimer / 60; + _.OnTimeHrs1 = hours & 0b1; + _.OnTimeHrs2 = hours >> 1; +} + +/// Get the Off Timer setting of the A/C from a raw extended state. +/// @return The Nr. of minutes the Off Timer is set for. +uint16_t IRSamsungAc::_getOffTimer(void) const { + if (_.OffTimeDay) return 24 * 60; + return (_.OffTimeHrs2 * 2 + _.OffTimeHrs1) * 60 + _.OffTimeMins * 10; +} + +/// Set the current Off Timer value of the A/C into the raw extended state. +void IRSamsungAc::_setOffTimer(void) { + _.OffTimerEnable = _OffTimerEnable = (_OffTimer > 0); + _.OffTimeDay = (_OffTimer >= 24 * 60); + if (_.OffTimeDay) { + _.OffTimeHrs2 = _.OffTimeHrs1 = _.OffTimeMins = 0; + return; + } + _.OffTimeMins = (_OffTimer % 60) / 10; + const uint8_t hours = _OffTimer / 60; + _.OffTimeHrs1 = hours & 0b1; + _.OffTimeHrs2 = hours >> 1; +} + +/// 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; } + +#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. +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); +} + +/// 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. +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); +} + /// Convert a stdAc::opmode_t enum into its native mode. /// @param[in] mode The enum to be converted. /// @return The native equivalent of the enum. @@ -757,6 +834,10 @@ String IRSamsungAc::toString(void) const { result += addBoolToString(getBreeze(), kBreezeStr); result += addBoolToString(_.Display, kLightStr); result += addBoolToString(_.Ion, kIonStr); + if (_OnTimerEnable) + result += addLabeledString(minsToString(_OnTimer), kOnTimerStr); + if (_OffTimerEnable) + result += addLabeledString(minsToString(_OffTimer), kOffTimerStr); return result; } diff --git a/src/ir_Samsung.h b/src/ir_Samsung.h index c9870f511..34ce9cccd 100644 --- a/src/ir_Samsung.h +++ b/src/ir_Samsung.h @@ -41,7 +41,7 @@ /// Native representation of a Samsung A/C message. union SamsungProtocol{ uint8_t raw[kSamsungAcExtendedStateLength]; ///< State in code form. - struct { + struct { // Standard message map // Byte 0 uint8_t :8; // Byte 1 @@ -72,11 +72,11 @@ union SamsungProtocol{ uint8_t Swing :3; uint8_t :1; // Byte 10 - uint8_t :1; - uint8_t Powerful :3; - uint8_t Display :1; - uint8_t :2; - uint8_t Clean10 :1; + uint8_t :1; + uint8_t FanSpecial :3; // Powerful, Breeze/WindFree + uint8_t Display :1; + uint8_t :2; + uint8_t Clean10 :1; // Byte 11 uint8_t Ion :1; uint8_t Clean11 :1; @@ -94,60 +94,67 @@ union SamsungProtocol{ uint8_t Power2 :2; uint8_t :2; }; - struct { + struct { // Extended message map // 1st Section // Byte 0 - uint8_t :8; + uint8_t :8; // Byte 1 - uint8_t :4; - uint8_t Sum1Lower :4; + uint8_t :4; + uint8_t Sum1Lower :4; // Byte 2 - uint8_t Sum1Upper :4; - uint8_t :4; + uint8_t Sum1Upper :4; + uint8_t :4; // Byte 3 - uint8_t :8; + uint8_t :8; // Byte 4 - uint8_t :8; + uint8_t :8; // Byte 5 - uint8_t :8; + uint8_t :8; // Byte 6 - uint8_t :8; + uint8_t :8; // 2nd Section // Byte 7 - uint8_t :8; + uint8_t :8; // Byte 8 - uint8_t :4; - uint8_t Sum2Lower :4; + uint8_t :4; + uint8_t Sum2Lower :4; // Byte 9 - uint8_t Sum2Upper :4; - uint8_t :4; + uint8_t Sum2Upper :4; + uint8_t OffTimeMins :3; // In units of 10's of mins + uint8_t OffTimeHrs1 :1; // LSB of the number of hours. // Byte 10 - uint8_t :1; - uint8_t Breeze :3; // WindFree - uint8_t :4; + uint8_t OffTimeHrs2 :4; // MSBs of the number of hours. + uint8_t OnTimeMins :3; // In units of 10's of mins + uint8_t OnTimeHrs1 :1; // LSB of the number of hours. // Byte 11 - uint8_t :8; + uint8_t OnTimeHrs2 :4; // MSBs of the number of hours. + uint8_t :4; // Byte 12 - uint8_t :8; + uint8_t OffTimeDay :1; + uint8_t OnTimerEnable :1; + uint8_t OffTimerEnable :1; + uint8_t Sleep :1; + uint8_t OnTimeDay :1; + uint8_t :3; // Byte 13 - uint8_t :8; + uint8_t :8; // 3rd Section // Byte 14 - uint8_t :8; + uint8_t :8; // Byte 15 - uint8_t :4; - uint8_t Sum3Lower :4; + uint8_t :4; + uint8_t Sum3Lower :4; // Byte 16 - uint8_t Sum3Upper :4; - uint8_t :4; + uint8_t Sum3Upper :4; + uint8_t :4; // Byte 17 - uint8_t :8; + uint8_t :8; // Byte 18 - uint8_t :8; + uint8_t :8; // Byte 19 - uint8_t :8; + uint8_t :8; // Byte 20 - uint8_t :8; + uint8_t :8; }; }; @@ -181,10 +188,8 @@ class IRSamsungAc { const bool use_modulation = true); void stateReset(const bool forcepower = true, const bool initialPower = true); #if SEND_SAMSUNG_AC - void send(const uint16_t repeat = kSamsungAcDefaultRepeat, - const bool calcchecksum = true); - void sendExtended(const uint16_t repeat = kSamsungAcDefaultRepeat, - const bool calcchecksum = true); + void send(const uint16_t repeat = kSamsungAcDefaultRepeat); + void sendExtended(const uint16_t repeat = kSamsungAcDefaultRepeat); void sendOn(const uint16_t repeat = kSamsungAcDefaultRepeat); void sendOff(const uint16_t repeat = kSamsungAcDefaultRepeat); /// Run the calibration to calculate uSec timing offsets for this platform. @@ -220,6 +225,10 @@ class IRSamsungAc { bool getDisplay(void) const; void setIon(const bool on); bool getIon(void) const; + uint16_t getOnTimer(void) const; + void setOnTimer(const uint16_t nr_of_mins); + uint16_t getOffTimer(void) const; + void setOffTimer(const uint16_t nr_of_mins); uint8_t* getRaw(void); void setRaw(const uint8_t new_code[], const uint16_t length = kSamsungAcStateLength); @@ -245,7 +254,17 @@ class IRSamsungAc { SamsungProtocol _; bool _forcepower; ///< Hack to know when we need to send a special power mesg bool _lastsentpowerstate; + bool _OnTimerEnable; + bool _OffTimerEnable; + uint16_t _OnTimer; + uint16_t _OffTimer; + uint16_t _lastOnTimer; + uint16_t _lastOffTimer; void checksum(void); + uint16_t _getOnTimer(void) const; + uint16_t _getOffTimer(void) const; + void _setOnTimer(void); + void _setOffTimer(void); }; #endif // IR_SAMSUNG_H_ diff --git a/test/ir_Samsung_test.cpp b/test/ir_Samsung_test.cpp index acff7b9d0..e3abfb749 100644 --- a/test/ir_Samsung_test.cpp +++ b/test/ir_Samsung_test.cpp @@ -1804,3 +1804,123 @@ TEST(TestIRSamsungAcClass, Issue1648) { EXPECT_EQ(expectedText, IRAcUtils::resultAcToString(&ac._irsend.capture)); ac._irsend.reset(); } + +TEST(TestIRSamsungAcClass, Timers) { + IRSamsungAc ac(kGpioUnused); + ac.begin(); + + // https://github.com/crankyoldgit/IRremoteESP8266/issues/1277#issuecomment-961836703 + const uint8_t on_timer_30m[kSamsungAcExtendedStateLength] = { + 0x02, 0xB2, 0x0F, 0x00, 0x00, 0x00, 0xC0, + 0x01, 0xA2, 0x0F, 0x30, 0x00, 0x02, 0x00, + 0x01, 0x02, 0xFF, 0x71, 0x40, 0x11, 0xC0}; + const uint8_t off_timer_1h_on_timer_10m[kSamsungAcExtendedStateLength] = { + 0x02, 0xB2, 0x0F, 0x00, 0x00, 0x00, 0xC0, + 0x01, 0x92, 0x8F, 0x10, 0x00, 0x06, 0x00, + 0x01, 0x02, 0xFF, 0x71, 0x40, 0x11, 0xC0}; + const uint8_t off_timer_1h_on_timer_0m[kSamsungAcExtendedStateLength] = { + 0x02, 0xB2, 0x0F, 0x00, 0x00, 0x00, 0xC0, + 0x01, 0xA2, 0x8F, 0x00, 0x00, 0x06, 0x00, + 0x01, 0x02, 0xFF, 0x71, 0x40, 0x11, 0xC0}; + + ac.setRaw(on_timer_30m, kSamsungAcExtendedStateLength); + EXPECT_EQ( + "Power: Off, Mode: 1 (Cool), Temp: 20C, Fan: 0 (Auto), Swing: Off, " + "Beep: Off, Clean: Off, Quiet: Off, Powerful: Off, Breeze: Off, " + "Light: On, Ion: Off, On Timer: 00:30", + ac.toString()); + ac.setRaw(off_timer_1h_on_timer_10m, kSamsungAcExtendedStateLength); + EXPECT_EQ( + "Power: Off, Mode: 1 (Cool), Temp: 20C, Fan: 0 (Auto), Swing: Off, " + "Beep: Off, Clean: Off, Quiet: Off, Powerful: Off, Breeze: Off, " + "Light: On, Ion: Off, On Timer: 00:10, Off Timer: 01:00", + ac.toString()); + ac.setRaw(off_timer_1h_on_timer_0m, kSamsungAcExtendedStateLength); + EXPECT_EQ( + "Power: Off, Mode: 1 (Cool), Temp: 20C, Fan: 0 (Auto), Swing: Off, " + "Beep: Off, Clean: Off, Quiet: Off, Powerful: Off, Breeze: Off, " + "Light: On, Ion: Off, On Timer: 00:00, Off Timer: 01:00", + ac.toString()); + + // https://cryptpad.fr/sheet/#/2/sheet/view/r9k8pmELYEjLyC71cD7EsThEYgKGLJygREZ5pVfNkS8/ + // Row 155 + const uint8_t off_timer_11h_on_timer_6h[kSamsungAcExtendedStateLength] = { + 0x02, 0xB2, 0x0F, 0x00, 0x00, 0x00, 0xC0, + 0x01, 0x62, 0x8F, 0x05, 0x03, 0x06, 0x00, + 0x01, 0x02, 0xFF, 0x71, 0x40, 0x11, 0xC0}; + ac.setRaw(off_timer_11h_on_timer_6h, kSamsungAcExtendedStateLength); + EXPECT_EQ( + "Power: Off, Mode: 1 (Cool), Temp: 20C, Fan: 0 (Auto), Swing: Off, " + "Beep: Off, Clean: Off, Quiet: Off, Powerful: Off, Breeze: Off, " + "Light: On, Ion: Off, On Timer: 06:00, Off Timer: 11:00", + ac.toString()); + + ac.stateReset(false); + EXPECT_EQ(0, ac.getOnTimer()); + EXPECT_EQ(0, ac.getOffTimer()); + + ac.setOnTimer(0); + EXPECT_EQ(0, ac.getOnTimer()); + EXPECT_EQ(0, ac.getOffTimer()); + + ac.setOffTimer(0); + EXPECT_EQ(0, ac.getOnTimer()); + EXPECT_EQ(0, ac.getOffTimer()); + + // On Timer only + ac.setOnTimer(30); + EXPECT_EQ(30, ac.getOnTimer()); + EXPECT_EQ(0, ac.getOffTimer()); + + ac.setOnTimer(90); // 1h30m + EXPECT_EQ(90, ac.getOnTimer()); + EXPECT_EQ(0, ac.getOffTimer()); + + ac.setOnTimer(85); // 1h25m -> 1h20m + EXPECT_EQ(80, ac.getOnTimer()); + EXPECT_EQ(0, ac.getOffTimer()); + + ac.setOnTimer(23 * 60 + 59); // 23:59 + EXPECT_EQ(23 * 60 + 50, ac.getOnTimer()); // 23:50 + EXPECT_EQ(0, ac.getOffTimer()); + + ac.setOnTimer(24 * 60 + 30); // 24:30 + EXPECT_EQ(24 * 60, ac.getOnTimer()); // 24:00 (Max) + EXPECT_EQ(0, ac.getOffTimer()); + + // Off Timer only + ac.setOnTimer(0); + ac.setOffTimer(0); + EXPECT_EQ(0, ac.getOnTimer()); + EXPECT_EQ(0, ac.getOffTimer()); + + ac.setOffTimer(30); + EXPECT_EQ(30, ac.getOffTimer()); + EXPECT_EQ(0, ac.getOnTimer()); + + ac.setOffTimer(90); // 1h30m + EXPECT_EQ(90, ac.getOffTimer()); + EXPECT_EQ(0, ac.getOnTimer()); + + ac.setOffTimer(85); // 1h25m -> 1h20m + EXPECT_EQ(80, ac.getOffTimer()); + EXPECT_EQ(0, ac.getOnTimer()); + + ac.setOffTimer(23 * 60 + 59); // 23:59 + EXPECT_EQ(23 * 60 + 50, ac.getOffTimer()); // 23:50 + EXPECT_EQ(0, ac.getOnTimer()); + + ac.setOffTimer(24 * 60 + 30); // 24:30 + EXPECT_EQ(24 * 60, ac.getOffTimer()); // 24:00 (Max) + EXPECT_EQ(0, ac.getOnTimer()); + + // Both Timers + ac.setOnTimer(24 * 60); // 24:00 + EXPECT_EQ(24 * 60, ac.getOnTimer()); // 24:00 (Max) + EXPECT_EQ(24 * 60, ac.getOffTimer()); // 24:00 (Max) + + ac.setOnTimer(1 * 60 + 30); // 1:30 + ac.setOffTimer(11 * 60); // 11:00 + EXPECT_EQ(1 * 60 + 30, ac.getOnTimer()); + EXPECT_EQ(11 * 60, ac.getOffTimer()); +} From d860a19ed50653aba13161f37d1b340be134401b Mon Sep 17 00:00:00 2001 From: crankyoldgit Date: Sat, 6 Nov 2021 22:35:20 +1000 Subject: [PATCH 2/3] SamsungAc: Add sleep timer support. * 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 --- src/IRac.cpp | 16 +++++++---- src/IRac.h | 5 ++-- src/ir_Samsung.cpp | 59 ++++++++++++++++++++++++++++++++-------- src/ir_Samsung.h | 12 ++++++-- test/IRac_test.cpp | 42 +++++++++++++++++++++++++--- test/ir_Samsung_test.cpp | 54 +++++++++++++++++++++++++++++++++++- 6 files changed, 161 insertions(+), 27 deletions(-) diff --git a/src/IRac.cpp b/src/IRac.cpp index 78bf294c3..19bbcbb8b 100644 --- a/src/IRac.cpp +++ b/src/IRac.cpp @@ -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); @@ -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)); @@ -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 @@ -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 diff --git a/src/IRac.h b/src/IRac.h index 2696e3809..a6ef06c90 100644 --- a/src/IRac.h +++ b/src/IRac.h @@ -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, diff --git a/src/ir_Samsung.cpp b/src/ir_Samsung.cpp index e74ee1b7f..370077841 100644 --- a/src/ir_Samsung.cpp +++ b/src/ir_Samsung.cpp @@ -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; } @@ -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); @@ -385,7 +388,7 @@ 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. @@ -393,7 +396,7 @@ void IRSamsungAc::sendExtended(const uint16_t repeat) { _.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 @@ -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++) @@ -685,13 +689,30 @@ 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) @@ -699,17 +720,32 @@ uint16_t IRSamsungAc::getOffTimer(void) const { return _OffTimer; } /// 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. @@ -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; } @@ -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; } diff --git a/src/ir_Samsung.h b/src/ir_Samsung.h index 34ce9cccd..c5b2e1282 100644 --- a/src/ir_Samsung.h +++ b/src/ir_Samsung.h @@ -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_ @@ -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); @@ -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); @@ -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; @@ -265,6 +270,7 @@ class IRSamsungAc { uint16_t _getOffTimer(void) const; void _setOnTimer(void); void _setOffTimer(void); + void _setSleepTimer(void); }; #endif // IR_SAMSUNG_H_ diff --git a/test/IRac_test.cpp b/test/IRac_test.cpp index 2b74d6970..8f783a048 100644 --- a/test/IRac_test.cpp +++ b/test/IRac_test.cpp @@ -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"; @@ -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)); @@ -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) { diff --git a/test/ir_Samsung_test.cpp b/test/ir_Samsung_test.cpp index e3abfb749..f110be3a4 100644 --- a/test/ir_Samsung_test.cpp +++ b/test/ir_Samsung_test.cpp @@ -1732,7 +1732,7 @@ TEST(TestIRSamsungAcClass, Issue1648) { EXPECT_EQ(onText, IRAcUtils::resultAcToString(&ac._irsend.capture)); ac._irsend.reset(); EXPECT_TRUE(ac._lastsentpowerstate); - EXPECT_FALSE(ac._forcepower); + EXPECT_FALSE(ac._forceextended); ac.off(); // User code ac.send(); // User code @@ -1924,3 +1924,55 @@ TEST(TestIRSamsungAcClass, Timers) { EXPECT_EQ(1 * 60 + 30, ac.getOnTimer()); EXPECT_EQ(11 * 60, ac.getOffTimer()); } + +TEST(TestIRSamsungAcClass, Sleep) { + IRSamsungAc ac(kGpioUnused); + ac.begin(); + + // https://cryptpad.fr/sheet/#/2/sheet/view/r9k8pmELYEjLyC71cD7EsThEYgKGLJygREZ5pVfNkS8/ + const uint8_t sleep_8h[kSamsungAcExtendedStateLength] = { + 0x02, 0x82, 0x0F, 0x00, 0x00, 0x10, 0xF0, + 0x01, 0xA2, 0x0F, 0x04, 0x00, 0x0C, 0x00, + 0x01, 0xE2, 0xFE, 0x71, 0x40, 0x11, 0xF0}; + ac.setRaw(sleep_8h, kSamsungAcExtendedStateLength); + EXPECT_EQ(8 * 60, ac.getSleepTimer()); + EXPECT_EQ(0, ac.getOffTimer()); + EXPECT_EQ(0, ac.getOnTimer()); + EXPECT_EQ( + "Power: On, Mode: 1 (Cool), Temp: 20C, Fan: 0 (Auto), Swing: Off, " + "Beep: Off, Clean: Off, Quiet: Off, Powerful: Off, Breeze: Off, " + "Light: On, Ion: Off, Sleep Timer: 08:00", + ac.toString()); + + ac.stateReset(false); + EXPECT_EQ(0, ac.getSleepTimer()); + EXPECT_EQ(0, ac.getOnTimer()); + EXPECT_EQ(0, ac.getOffTimer()); + + ac.setOnTimer(60); + ac.setOffTimer(90); + EXPECT_EQ(0, ac.getSleepTimer()); + EXPECT_EQ(60, ac.getOnTimer()); + EXPECT_EQ(90, ac.getOffTimer()); + + ac.setSleepTimer(120); + EXPECT_EQ(120, ac.getSleepTimer()); + EXPECT_EQ(0, ac.getOnTimer()); + EXPECT_EQ(0, ac.getOffTimer()); + + ac.setSleepTimer(24 * 60 + 31); // 24h31m + EXPECT_EQ(24 * 60, ac.getSleepTimer()); // 24h (Max) + + ac.setSleepTimer(35); // 45m + EXPECT_EQ(30, ac.getSleepTimer()); // 30m (Only stored in 10m increments). + + ac.setOnTimer(60); // Seting an On Timer should clear the sleep setting. + EXPECT_EQ(0, ac.getSleepTimer()); + EXPECT_EQ(60, ac.getOnTimer()); + + ac.setSleepTimer(120); + ac.setOffTimer(90); // Setting an Off Timer will clear the sleep setting. + EXPECT_EQ(0, ac.getSleepTimer()); + EXPECT_EQ(0, ac.getOnTimer()); + EXPECT_EQ(90, ac.getOffTimer()); +} From 654f5a4ddd4dd9d12d70afe36f8224b41a8dd1b9 Mon Sep 17 00:00:00 2001 From: crankyoldgit Date: Sat, 6 Nov 2021 22:49:21 +1000 Subject: [PATCH 3/3] Update supported remote device and docs. --- src/ir_Samsung.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ir_Samsung.h b/src/ir_Samsung.h index c5b2e1282..4f28c585d 100644 --- a/src/ir_Samsung.h +++ b/src/ir_Samsung.h @@ -7,6 +7,7 @@ /// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1062 /// @see http://elektrolab.wz.cz/katalog/samsung_protocol.pdf /// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1538 (Checksum) +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1277 (Timers) // Supports: // Brand: Samsung, Model: UA55H6300 TV (SAMSUNG) @@ -24,6 +25,7 @@ // Brand: Samsung, Model: AR12NXCXAWKXEU A/C (SAMSUNG_AC) // Brand: Samsung, Model: AR12TXEAAWKNEU A/C (SAMSUNG_AC) // Brand: Samsung, Model: DB93-14195A remote (SAMSUNG_AC) +// Brand: Samsung, Model: DB96-24901C remote (SAMSUNG_AC) #ifndef IR_SAMSUNG_H_ #define IR_SAMSUNG_H_