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

Add detailed support for Airwell A/C protocol. #1204

Merged
merged 3 commits into from
Jun 25, 2020
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
58 changes: 57 additions & 1 deletion src/IRac.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "IRremoteESP8266.h"
#include "IRtext.h"
#include "IRutils.h"
#include "ir_Airwell.h"
#include "ir_Amcor.h"
#include "ir_Argo.h"
#include "ir_Carrier.h"
Expand Down Expand Up @@ -132,6 +133,9 @@ stdAc::state_t IRac::getStatePrev(void) { return _prev; }
/// @return true if the protocol is supported by this class, otherwise false.
bool IRac::isProtocolSupported(const decode_type_t protocol) {
switch (protocol) {
#if SEND_AIRWELL
case decode_type_t::AIRWELL:
#endif
#if SEND_AMCOR
case decode_type_t::AMCOR:
#endif
Expand Down Expand Up @@ -269,6 +273,34 @@ bool IRac::isProtocolSupported(const decode_type_t protocol) {
}
}

#if SEND_AIRWELL
/// Send an Airwell A/C message with the supplied settings.
/// @param[in, out] ac A Ptr to an IRAirwellAc object to use.
/// @param[in] on The power setting.
/// @param[in] mode The operation mode setting.
/// @param[in] degrees The temperature setting in degrees.
/// @param[in] fan The speed setting for the fan.
void IRac::airwell(IRAirwellAc *ac,
const bool on, const stdAc::opmode_t mode,
const float degrees, const stdAc::fanspeed_t fan) {
ac->begin();
ac->setPowerToggle(on);
ac->setMode(ac->convertMode(mode));
ac->setTemp(degrees);
ac->setFan(ac->convertFan(fan));
// No Swing setting available.
// No Quiet setting available.
// No Light setting available.
// No Filter setting available.
// No Turbo setting available.
// No Economy setting available.
// No Clean setting available.
// No Beep setting available.
// No Sleep setting available.
ac->send();
}
#endif // SEND_AIRWELL

#if SEND_AMCOR
/// Send an Amcor A/C message with the supplied settings.
/// @param[in, out] ac A Ptr to an IRAmcorAc object to use.
Expand Down Expand Up @@ -1892,6 +1924,7 @@ stdAc::state_t IRac::handleToggles(const stdAc::state_t desired,
else
result.swingv = stdAc::swingv_t::kOff; // No change, so no toggle.
break;
case decode_type_t::AIRWELL:
case decode_type_t::DAIKIN64:
case decode_type_t::WHIRLPOOL_AC:
result.power = desired.power ^ prev->power;
Expand Down Expand Up @@ -1959,6 +1992,14 @@ bool IRac::sendAc(const stdAc::state_t desired, const stdAc::state_t *prev) {
stdAc::state_t send = this->handleToggles(this->cleanState(desired), prev);
// Per vendor settings & setup.
switch (send.protocol) {
#if SEND_AIRWELL
case AIRWELL:
{
IRAirwellAc ac(_pin, _inverted, _modulation);
airwell(&ac, send.power, send.mode, degC, send.fanspeed);
break;
}
#endif // SEND_AIRWELL
#if SEND_AMCOR
case AMCOR:
{
Expand Down Expand Up @@ -2715,6 +2756,13 @@ namespace IRAcUtils {
/// An empty string if we can't.
String resultAcToString(const decode_results * const result) {
switch (result->decode_type) {
#if DECODE_AIRWELL
case decode_type_t::AIRWELL: {
IRAirwellAc ac(kGpioUnused);
ac.setRaw(result->value); // AIRWELL uses value instead of state.
return ac.toString();
}
#endif // DECODE_AIRWELL
#if DECODE_AMCOR
case decode_type_t::AMCOR: {
IRAmcorAc ac(0);
Expand All @@ -2724,7 +2772,7 @@ namespace IRAcUtils {
#endif // DECODE_AMCOR
#if DECODE_ARGO
case decode_type_t::ARGO: {
IRArgoAC ac(0);
IRArgoAC ac(kGpioUnused);
ac.setRaw(result->state);
return ac.toString();
}
Expand Down Expand Up @@ -3040,6 +3088,14 @@ namespace IRAcUtils {
) {
if (decode == NULL || result == NULL) return false; // Safety check.
switch (decode->decode_type) {
#if DECODE_AIRWELL
case decode_type_t::AIRWELL: {
IRAirwellAc ac(kGpioUnused);
ac.setRaw(decode->value); // Uses value instead of state.
*result = ac.toCommon();
break;
}
#endif // DECODE_AIRWELL
#if DECODE_AMCOR
case decode_type_t::AMCOR: {
IRAmcorAc ac(kGpioUnused);
Expand Down
6 changes: 6 additions & 0 deletions src/IRac.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <Arduino.h>
#endif
#include "IRremoteESP8266.h"
#include "ir_Airwell.h"
#include "ir_Amcor.h"
#include "ir_Argo.h"
#include "ir_Carrier.h"
Expand Down Expand Up @@ -98,6 +99,11 @@ class IRac {
bool _inverted; ///< IR LED is lit when GPIO is LOW (true) or HIGH (false)?
bool _modulation; ///< Is frequency modulation to be used?
stdAc::state_t _prev; ///< The state we expect the device to currently be in.
#if SEND_AIRWELL
void airwell(IRAirwellAc *ac,
const bool on, const stdAc::opmode_t mode, const float degrees,
const stdAc::fanspeed_t fan);
#endif // SEND_AIRWELL
#if SEND_AMCOR
void amcor(IRAmcorAc *ac,
const bool on, const stdAc::opmode_t mode, const float degrees,
Expand Down
217 changes: 211 additions & 6 deletions src/ir_Airwell.cpp
Original file line number Diff line number Diff line change
@@ -1,23 +1,28 @@
// Copyright 2020 David Conran

#include "ir_Airwell.h"
#include <algorithm>
#include "IRrecv.h"
#include "IRsend.h"
#include "IRtext.h"
#include "IRutils.h"

/// @file
/// @brief Airwell "Manchester code" based protocol.
/// Some other Airwell products use the COOLIX protocol.

// Supports:
// Brand: Airwell, Model: RC08W remote
// Brand: Airwell, Model: RC04 remote
// Brand: Airwell, Model: DLS 21 DCI R410 AW A/C

const uint8_t kAirwellOverhead = 4;
const uint16_t kAirwellHalfClockPeriod = 950; // uSeconds
const uint16_t kAirwellHdrMark = 3 * kAirwellHalfClockPeriod; // uSeconds
const uint16_t kAirwellHdrSpace = 3 * kAirwellHalfClockPeriod; // uSeconds
const uint16_t kAirwellFooterMark = 5 * kAirwellHalfClockPeriod; // uSeconds

using irutils::addBoolToString;
using irutils::addModeToString;
using irutils::addFanToString;
using irutils::addTempToString;
using irutils::setBit;
using irutils::setBits;

#if SEND_AIRWELL
/// Send an Airwell Manchester Code formatted message.
/// Status: BETA / Appears to be working.
Expand Down Expand Up @@ -74,3 +79,203 @@ bool IRrecv::decodeAirwell(decode_results *results, uint16_t offset,
return true;
}
#endif

/// Class constructor
/// @param[in] pin GPIO to be used when sending.
/// @param[in] inverted Is the output signal to be inverted?
/// @param[in] use_modulation Is frequency modulation to be used?
IRAirwellAc::IRAirwellAc(const uint16_t pin, const bool inverted,
const bool use_modulation)
: _irsend(pin, inverted, use_modulation) { stateReset(); }

/// Set up hardware to be able to send a message.
void IRAirwellAc::begin(void) { _irsend.begin(); }

/// Get the raw state of the object, suitable to be sent with the appropriate
/// IRsend object method.
/// @return A copy of the internal state.
uint64_t IRAirwellAc::getRaw(void) {
return remote_state;
}

/// Set the raw state of the object.
/// @param[in] state The raw state from the native IR message.
void IRAirwellAc::setRaw(const uint64_t state) {
remote_state = state;
}

#if SEND_AIRWELL
/// Send the current internal state as an IR message.
/// @param[in] repeat Nr. of times the message will be repeated.
void IRAirwellAc::send(const uint16_t repeat) {
_irsend.sendAirwell(getRaw(), kAirwellBits, repeat);
}
#endif // SEND_AIRWELL

/// Reset the internals of the object to a known good state.
void IRAirwellAc::stateReset(void) {
remote_state = kAirwellKnownGoodState;
}

/// Turn on/off the Power Airwell setting.
/// @param[in] on The desired setting state.
void IRAirwellAc::setPowerToggle(const bool on) {
setBit(&remote_state, kAirwellPowerToggleBit, on);
}

/// Get the power toggle setting from the internal state.
/// @return A boolean indicating the setting.
bool IRAirwellAc::getPowerToggle(void) {
return GETBIT64(remote_state, kAirwellPowerToggleBit);
}

/// Get the current operation mode setting.
/// @return The current operation mode.
uint8_t IRAirwellAc::getMode(void) {
return GETBITS64(remote_state, kAirwellModeOffset, kAirwellModeSize);
}

/// Set the desired operation mode.
/// @param[in] mode The desired operation mode.
void IRAirwellAc::setMode(const uint8_t mode) {
switch (mode) {
case kAirwellFan:
case kAirwellCool:
case kAirwellHeat:
case kAirwellDry:
case kAirwellAuto:
setBits(&remote_state, kAirwellModeOffset, kAirwellModeSize, mode);
break;
default:
setMode(kAirwellAuto);
}
setFan(getFan()); // Ensure the fan is at the correct speed for the new mode.
}

/// Convert a stdAc::opmode_t enum into its native mode.
/// @param[in] mode The enum to be converted.
/// @return The native equivilant of the enum.
uint8_t IRAirwellAc::convertMode(const stdAc::opmode_t mode) {
switch (mode) {
case stdAc::opmode_t::kCool: return kAirwellCool;
case stdAc::opmode_t::kHeat: return kAirwellHeat;
case stdAc::opmode_t::kDry: return kAirwellDry;
case stdAc::opmode_t::kFan: return kAirwellFan;
default: return kAirwellAuto;
}
}

/// Convert a native mode into its stdAc equivilant.
/// @param[in] mode The native setting to be converted.
/// @return The stdAc equivilant of the native setting.
stdAc::opmode_t IRAirwellAc::toCommonMode(const uint8_t mode) {
switch (mode) {
case kAirwellCool: return stdAc::opmode_t::kCool;
case kAirwellHeat: return stdAc::opmode_t::kHeat;
case kAirwellDry: return stdAc::opmode_t::kDry;
case kAirwellFan: return stdAc::opmode_t::kFan;
default: return stdAc::opmode_t::kAuto;
}
}

/// Set the speed of the fan.
/// @param[in] speed The desired setting.
/// @note The speed is locked to Low when in Dry mode.
void IRAirwellAc::setFan(const uint8_t speed) {
setBits(&remote_state, kAirwellFanOffset, kAirwellFanSize,
(getMode() == kAirwellDry) ? kAirwellFanLow
: std::min(speed, kAirwellFanAuto));
}

/// Get the current fan speed setting.
/// @return The current fan speed.
uint8_t IRAirwellAc::getFan(void) {
return GETBITS64(remote_state, kAirwellFanOffset, kAirwellFanSize);
}

/// Convert a stdAc::fanspeed_t enum into it's native speed.
/// @param[in] speed The enum to be converted.
/// @return The native equivilant of the enum.
uint8_t IRAirwellAc::convertFan(const stdAc::fanspeed_t speed) {
switch (speed) {
case stdAc::fanspeed_t::kMin:
case stdAc::fanspeed_t::kLow:
return kAirwellFanLow;
case stdAc::fanspeed_t::kMedium:
return kAirwellFanMedium;
case stdAc::fanspeed_t::kHigh:
case stdAc::fanspeed_t::kMax:
return kAirwellFanHigh;
default:
return kAirwellFanAuto;
}
}

/// Convert a native fan speed into its stdAc equivilant.
/// @param[in] speed The native setting to be converted.
/// @return The stdAc equivilant of the native setting.
stdAc::fanspeed_t IRAirwellAc::toCommonFanSpeed(const uint8_t speed) {
switch (speed) {
case kAirwellFanHigh: return stdAc::fanspeed_t::kMax;
case kAirwellFanMedium: return stdAc::fanspeed_t::kMedium;
case kAirwellFanLow: return stdAc::fanspeed_t::kMin;
default: return stdAc::fanspeed_t::kAuto;
}
}

/// Set the temperature.
/// @param[in] degrees The temperature in degrees celsius.
void IRAirwellAc::setTemp(const uint8_t degrees) {
uint8_t temp = std::max(kAirwellMinTemp, degrees);
temp = std::min(kAirwellMaxTemp, temp);
setBits(&remote_state, kAirwellTempOffset, kAirwellTempSize,
temp - kAirwellMinTemp + 1);
}

/// Get the current temperature setting.
/// @return Get current setting for temp. in degrees celsius.
uint8_t IRAirwellAc::getTemp(void) {
return GETBITS64(remote_state, kAirwellTempOffset,
kAirwellTempSize) + kAirwellMinTemp - 1;
}

/// Convert the current internal state into its stdAc::state_t equivilant.
/// @return The stdAc equivilant of the native settings.
stdAc::state_t IRAirwellAc::toCommon(void) {
stdAc::state_t result;
result.protocol = decode_type_t::AIRWELL;
result.power = getPowerToggle();
result.mode = toCommonMode(getMode());
result.celsius = true;
result.degrees = getTemp();
result.fanspeed = toCommonFanSpeed(getFan());
// Not supported.
result.model = -1;
result.turbo = false;
result.swingv = stdAc::swingv_t::kOff;
result.swingh = stdAc::swingh_t::kOff;
result.light = false;
result.filter = false;
result.econo = false;
result.quiet = false;
result.clean = false;
result.beep = false;
result.sleep = -1;
result.clock = -1;
return result;
}

/// Convert the current internal state into a human readable string.
/// @return A human readable string.
String IRAirwellAc::toString(void) {
String result = "";
result.reserve(70); // Reserve some heap for the string to reduce fragging.
result += addBoolToString(getPowerToggle(), kPowerToggleStr, false);
result += addModeToString(getMode(), kAirwellAuto, kAirwellCool,
kAirwellHeat, kAirwellDry, kAirwellFan);
result += addFanToString(getFan(), kAirwellFanHigh, kAirwellFanLow,
kAirwellFanAuto, kAirwellFanAuto,
kAirwellFanMedium);
result += addTempToString(getTemp());
return result;
}
Loading