Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SamsungAc] Add support for On, Off, & Sleep Timers #1662

Merged
merged 3 commits into from
Nov 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
168 changes: 143 additions & 25 deletions src/ir_Samsung.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -275,17 +276,22 @@ 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;
}

/// Set up hardware to be able to send a message.
Expand Down Expand Up @@ -351,28 +357,26 @@ 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 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);
}

/// 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
Expand All @@ -383,13 +387,16 @@ void IRSamsungAc::sendExtended(const uint16_t repeat, const bool calcchecksum) {
kSamsungAcSectionLength);
std::memcpy(_.raw + kSamsungAcSectionLength, extended_middle_section,
kSamsungAcSectionLength);
if (calcchecksum) checksum();
_setOnTimer();
_setSleepTimer(); // This also sets any Off Timer if needed too.
// 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);

_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 @@ -434,6 +441,11 @@ 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;
_Sleep = _.Sleep;
_OnTimer = _getOnTimer();
_OffTimer = _getOffTimer();
for (uint8_t i = kSamsungAcStateLength; i < length; i++)
_.raw[i - kSamsungAcSectionLength] = _.raw[i];
}
Expand Down Expand Up @@ -580,15 +592,15 @@ 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);
}

/// Set the Powerful (Turbo) setting of the A/C.
/// @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);
Expand All @@ -603,7 +615,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());
}

Expand All @@ -612,7 +624,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);
Expand All @@ -635,6 +647,107 @@ 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;
}

// 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.
/// @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.
/// @param[in] mode The enum to be converted.
/// @return The native equivalent of the enum.
Expand Down Expand Up @@ -707,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 @@ -757,6 +870,11 @@ 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),
_Sleep ? kSleepTimerStr : kOffTimerStr);
return result;
}

Expand Down
Loading