diff --git a/Doxyfile b/Doxyfile
index 8714b0476..1c1617947 100644
--- a/Doxyfile
+++ b/Doxyfile
@@ -10,7 +10,7 @@ EXTRACT_ALL = YES
EXTRACT_PRIVATE = YES
EXTRACT_LOCAL_CLASSES = NO
GENERATE_LATEX = NO
-ENABLE_PREPROCESSING = NO
+ENABLE_PREPROCESSING = YES
QUIET = YES
WARN_NO_PARAMDOC = YES
WARN_AS_ERROR = YES
diff --git a/examples/IRMQTTServer/IRMQTTServer.h b/examples/IRMQTTServer/IRMQTTServer.h
index 716aab60b..e69b891e4 100644
--- a/examples/IRMQTTServer/IRMQTTServer.h
+++ b/examples/IRMQTTServer/IRMQTTServer.h
@@ -253,6 +253,9 @@ const uint16_t kMinUnknownSize = 2 * 10;
#define KEY_JSON "json"
#define KEY_RESEND "resend"
#define KEY_VCC "vcc"
+#define KEY_COMMAND "command"
+#define KEY_ROOMTEMP "roomtemp"
+#define KEY_IFEEL "ifeel"
// HTML arguments we will parse for IR code information.
#define KEY_TYPE "type" // KEY_PROTOCOL is also checked too.
@@ -358,7 +361,8 @@ static const char kClimateTopics[] PROGMEM =
"(" KEY_PROTOCOL "|" KEY_MODEL "|" KEY_POWER "|" KEY_MODE "|" KEY_TEMP "|"
KEY_FANSPEED "|" KEY_SWINGV "|" KEY_SWINGH "|" KEY_QUIET "|"
KEY_TURBO "|" KEY_LIGHT "|" KEY_BEEP "|" KEY_ECONO "|" KEY_SLEEP "|"
- KEY_FILTER "|" KEY_CLEAN "|" KEY_CELSIUS "|" KEY_RESEND
+ KEY_FILTER "|" KEY_CLEAN "|" KEY_CELSIUS "|" KEY_RESEND "|" KEY_COMMAND "|"
+ "|" KEY_ROOMTEMP "|" KEY_IFEEL
#if MQTT_CLIMATE_JSON
"|" KEY_JSON
#endif // MQTT_CLIMATE_JSON
@@ -367,6 +371,7 @@ static const char* const kMqttTopics[] = {
KEY_PROTOCOL, KEY_MODEL, KEY_POWER, KEY_MODE, KEY_TEMP, KEY_FANSPEED,
KEY_SWINGV, KEY_SWINGH, KEY_QUIET, KEY_TURBO, KEY_LIGHT, KEY_BEEP,
KEY_ECONO, KEY_SLEEP, KEY_FILTER, KEY_CLEAN, KEY_CELSIUS, KEY_RESEND,
+ KEY_COMMAND, KEY_ROOMTEMP, KEY_IFEEL
KEY_JSON}; // KEY_JSON needs to be the last one.
diff --git a/examples/IRMQTTServer/IRMQTTServer.ino b/examples/IRMQTTServer/IRMQTTServer.ino
index 6701ef4bf..bf9b31596 100644
--- a/examples/IRMQTTServer/IRMQTTServer.ino
+++ b/examples/IRMQTTServer/IRMQTTServer.ino
@@ -984,6 +984,18 @@ String htmlSelectModel(const String name, const int16_t def) {
return html;
}
+String htmlSelectCommandType(const String name, const stdAc::ac_command_t def) {
+ String html = F("");
+ return html;
+}
+
String htmlSelectUint(const String name, const uint16_t max,
const uint16_t def) {
String html = F(""
+ "
" D_STR_SENSORTEMP " | "
+ " |
"
"" D_STR_FAN " | ") +
htmlSelectFanspeed(KEY_FANSPEED, climate[chan]->next.fanspeed) +
F(" |
"
@@ -1138,6 +1158,9 @@ void handleAirCon(void) {
"" D_STR_QUIET " | ") +
htmlSelectBool(KEY_QUIET, climate[chan]->next.quiet) +
F(" |
"
+ "" D_STR_IFEEL " | ") +
+ htmlSelectBool(KEY_IFEEL, climate[chan]->next.iFeel) +
+ F(" |
"
"" D_STR_TURBO " | ") +
htmlSelectBool(KEY_TURBO, climate[chan]->next.turbo) +
F(" |
"
@@ -2976,6 +2999,7 @@ void sendJsonState(const stdAc::state_t state, const String topic,
DynamicJsonDocument json(kJsonAcStateMaxSize);
json[KEY_PROTOCOL] = typeToString(state.protocol);
json[KEY_MODEL] = state.model;
+ json[KEY_COMMAND] = IRac::commandToString(state.command);
json[KEY_POWER] = IRac::boolToString(state.power);
json[KEY_MODE] = IRac::opmodeToString(state.mode, ha_mode);
// Home Assistant wants mode to be off if power is also off & vice-versa.
@@ -2985,10 +3009,12 @@ void sendJsonState(const stdAc::state_t state, const String topic,
}
json[KEY_CELSIUS] = IRac::boolToString(state.celsius);
json[KEY_TEMP] = state.degrees;
+ json[KEY_ROOMTEMP] = state.roomTemp;
json[KEY_FANSPEED] = IRac::fanspeedToString(state.fanspeed);
json[KEY_SWINGV] = IRac::swingvToString(state.swingv);
json[KEY_SWINGH] = IRac::swinghToString(state.swingh);
json[KEY_QUIET] = IRac::boolToString(state.quiet);
+ json[KEY_IFEEL] = IRac::boolToString(state.iFeel);
json[KEY_TURBO] = IRac::boolToString(state.turbo);
json[KEY_ECONO] = IRac::boolToString(state.econo);
json[KEY_LIGHT] = IRac::boolToString(state.light);
@@ -3026,6 +3052,10 @@ stdAc::state_t jsonToState(const stdAc::state_t current, const char *str) {
result.model = IRac::strToModel(json[KEY_MODEL].as());
else if (validJsonInt(json, KEY_MODEL))
result.model = json[KEY_MODEL];
+ if (validJsonStr(json, KEY_COMMAND))
+ result.command = IRac::strToCommand(json[KEY_COMMAND].as());
+ else if (validJsonInt(json, KEY_COMMAND))
+ result.command = json[KEY_COMMAND];
if (validJsonStr(json, KEY_MODE))
result.mode = IRac::strToOpmode(json[KEY_MODE]);
if (validJsonStr(json, KEY_FANSPEED))
@@ -3036,10 +3066,14 @@ stdAc::state_t jsonToState(const stdAc::state_t current, const char *str) {
result.swingh = IRac::strToSwingH(json[KEY_SWINGH]);
if (json.containsKey(KEY_TEMP))
result.degrees = json[KEY_TEMP];
+ if (json.containsKey(KEY_ROOMTEMP))
+ result.roomTemp = json[KEY_ROOMTEMP];
if (validJsonInt(json, KEY_SLEEP))
result.sleep = json[KEY_SLEEP];
if (validJsonStr(json, KEY_POWER))
result.power = IRac::strToBool(json[KEY_POWER]);
+ if (validJsonStr(json, KEY_IFEEL))
+ result.iFeel = IRac::strToBool(json[KEY_IFEEL]);
if (validJsonStr(json, KEY_QUIET))
result.quiet = IRac::strToBool(json[KEY_QUIET]);
if (validJsonStr(json, KEY_TURBO))
@@ -3071,6 +3105,8 @@ void updateClimate(stdAc::state_t *state, const String str,
state->protocol = strToDecodeType(payload.c_str());
} else if (str.equals(prefix + F(KEY_MODEL))) {
state->model = IRac::strToModel(payload.c_str());
+ } else if (str.equals(prefix + F(KEY_COMMAND))) {
+ state->command = IRac::strToCommandType(payload.c_str());
} else if (str.equals(prefix + F(KEY_POWER))) {
state->power = IRac::strToBool(payload.c_str());
#if MQTT_CLIMATE_HA_MODE
@@ -3085,12 +3121,16 @@ void updateClimate(stdAc::state_t *state, const String str,
#endif // MQTT_CLIMATE_HA_MODE
} else if (str.equals(prefix + F(KEY_TEMP))) {
state->degrees = payload.toFloat();
+ } else if (str.equals(prefix + F(KEY_ROOMTEMP))) {
+ state->roomTemperature = payload.toFloat();
} else if (str.equals(prefix + F(KEY_FANSPEED))) {
state->fanspeed = IRac::strToFanspeed(payload.c_str());
} else if (str.equals(prefix + F(KEY_SWINGV))) {
state->swingv = IRac::strToSwingV(payload.c_str());
} else if (str.equals(prefix + F(KEY_SWINGH))) {
state->swingh = IRac::strToSwingH(payload.c_str());
+ } else if (str.equals(prefix + F(KEY_IFEEL))) {
+ state->iFeel = IRac::strToBool(payload.c_str());
} else if (str.equals(prefix + F(KEY_QUIET))) {
state->quiet = IRac::strToBool(payload.c_str());
} else if (str.equals(prefix + F(KEY_TURBO))) {
@@ -3128,6 +3168,11 @@ bool sendClimate(const String topic_prefix, const bool retain,
diff = true;
success &= sendInt(topic_prefix + KEY_MODEL, next.model, retain);
}
+ if (prev.command != next.command || forceMQTT) {
+ String command_str = IRac::commandTypeToString(next.command);
+ diff = true;
+ success &= sendString(topic_prefix + KEY_COMMAND, command_str, retain);
+ }
#ifdef MQTT_CLIMATE_HA_MODE
String mode_str = IRac::opmodeToString(next.mode, MQTT_CLIMATE_HA_MODE);
#else // MQTT_CLIMATE_HA_MODE
@@ -3161,6 +3206,11 @@ bool sendClimate(const String topic_prefix, const bool retain,
diff = true;
success &= sendBool(topic_prefix + KEY_CELSIUS, next.celsius, retain);
}
+ if (prev.roomTemperature != next.roomTemperature || forceMQTT) {
+ diff = true;
+ success &= sendFloat(topic_prefix + KEY_ROOMTEMP, next.roomTemperature,
+ retain);
+ }
if (prev.fanspeed != next.fanspeed || forceMQTT) {
diff = true;
success &= sendString(topic_prefix + KEY_FANSPEED,
@@ -3176,6 +3226,10 @@ bool sendClimate(const String topic_prefix, const bool retain,
success &= sendString(topic_prefix + KEY_SWINGH,
IRac::swinghToString(next.swingh), retain);
}
+ if (prev.iFeel != next.iFeel || forceMQTT) {
+ diff = true;
+ success &= sendBool(topic_prefix + KEY_IFEEL, next.iFeel, retain);
+ }
if (prev.quiet != next.quiet || forceMQTT) {
diff = true;
success &= sendBool(topic_prefix + KEY_QUIET, next.quiet, retain);
diff --git a/src/IRac.cpp b/src/IRac.cpp
index 97fe05916..644a97072 100644
--- a/src/IRac.cpp
+++ b/src/IRac.cpp
@@ -64,6 +64,34 @@
#endif // ESP8266
#endif // STRCASECMP
+#ifndef UNIT_TEST
+#define OUTPUT_DECODE_RESULTS_FOR_UT(ac)
+#else
+/// If compiling for UT *and* a test receiver @c IRrecv is provided via the
+/// @c _utReceived param, this injects an "output" gadget @c _lastDecodeResults
+/// into the @c IRAc::sendAc method, so that the UT code may parse the "sent"
+/// value and drive further assertions
+///
+/// @note The @c decode_results "returned" is a shallow copy (empty rawbuf),
+/// mostly b/c the class does not have a custom/deep copy c-tor
+/// and defining it would be an overkill for this purpose
+/// @note For future maintainers: If @c IRAc class is ever refactored to use
+/// polymorphism (static or dynamic)... this macro should be removed
+/// and replaced with proper GMock injection.
+#define OUTPUT_DECODE_RESULTS_FOR_UT(ac) \
+ { \
+ if (_utReceiver) { \
+ _lastDecodeResults = nullptr; \
+ (ac)._irsend.makeDecodeResult(); \
+ if (_utReceiver->decode(&(ac)._irsend.capture)) { \
+ _lastDecodeResults = std::unique_ptr( \
+ new decode_results((ac)._irsend.capture)); \
+ _lastDecodeResults->rawbuf = nullptr; \
+ } \
+ } \
+ }
+#endif // UNIT_TEST
+
/// Class constructor
/// @param[in] pin Gpio pin to use when transmitting IR messages.
/// @param[in] inverted true, gpio output defaults to high. false, to low.
@@ -464,6 +492,109 @@ void IRac::argo(IRArgoAC *ac,
ac->setNight(sleep >= 0); // Convert to a boolean.
ac->send();
}
+
+/// Send an Argo A/C WREM-3 AC **control** message with the supplied settings.
+/// @param[in, out] ac A Ptr to an IRArgoAC_WREM3 object to use.
+/// @param[in] on The power setting.
+/// @param[in] mode The operation mode setting.
+/// @param[in] degrees The set temperature setting in degrees Celsius.
+/// @param[in] roomTemp The room (iFeel) temperature setting in degrees Celsius.
+/// @param[in] fan The speed setting for the fan.
+/// @param[in] swingv The vertical swing setting.
+/// @param[in] iFeel Whether to enable iFeel mode on the A/C unit.
+/// @param[in] night Enable night mode (raises temp by +1*C after 1h).
+/// @param[in] econo Enable eco mode (limits power consumed).
+/// @param[in] turbo Run the device in turbo/powerful mode.
+/// @param[in] filter Enable filter mode
+/// @param[in] light Enable device display/LEDs
+void IRac::argoWrem3_ACCommand(IRArgoAC_WREM3 *ac, const bool on,
+ const stdAc::opmode_t mode, const float degrees, const float roomTemp,
+ const stdAc::fanspeed_t fan, const stdAc::swingv_t swingv, const bool iFeel,
+ const bool night, const bool econo, const bool turbo, const bool filter,
+ const bool light) {
+ ac->begin();
+ ac->setMessageType(argoIrMessageType_t::AC_CONTROL);
+ ac->setPower(on);
+ ac->setMode(ac->convertMode(mode));
+ ac->setTemp(degrees);
+ ac->setRoomTemp(roomTemp);
+ ac->setFan(ac->convertFan(fan));
+ ac->setFlap(ac->convertSwingV(swingv));
+ ac->setiFeel(iFeel);
+ ac->setNight(night);
+ ac->setEco(econo);
+ ac->setMax(turbo);
+ ac->setFilter(filter);
+ ac->setLight(light);
+ // No Clean setting available.
+ // No Beep setting available - always beeps in this mode :)
+ ac->send();
+}
+
+/// Send an Argo A/C WREM-3 iFeel (room temp) silent (no beep) report.
+/// @param[in, out] ac A Ptr to an IRArgoAC_WREM3 object to use.
+/// @param[in] roomTemp The room (iFeel) temperature setting in degrees Celsius.
+void IRac::argoWrem3_iFeelReport(IRArgoAC_WREM3 *ac, const float roomTemp) {
+ ac->begin();
+ ac->setMessageType(argoIrMessageType_t::IFEEL_TEMP_REPORT);
+ ac->setRoomTemp(roomTemp);
+ ac->send();
+}
+
+/// Send an Argo A/C WREM-3 Config command.
+/// @param[in, out] ac A Ptr to an IRArgoAC_WREM3 object to use.
+/// @param[in] param The parameter ID.
+/// @param[in] value The parameter value.
+/// @param[in] safe If true, will only allow setting the below parameters
+/// in order to avoid accidentally setting a restricted
+/// vendor-specific param and breaking the A/C device
+/// @note Known parameters (P, where xx is the @c param)
+/// P05 - Temperature Scale (0-Celsius, 1-Fahrenheit)
+/// P06 - Transmission channel (0..3)
+/// P12 - ECO mode power input limit (30..99, default: 75)
+void IRac::argoWrem3_ConfigSet(IRArgoAC_WREM3 *ac, const uint8_t param,
+ const uint8_t value, bool safe /*= true*/) {
+ if (safe) {
+ switch (param) {
+ case 5: // temp. scale (note this is likely excess as not transmitted)
+ if (value > 1) { return; /* invalid */ }
+ break;
+ case 6: // channel (note this is likely excess as not transmitted)
+ if (value > 3) { return; /* invalid */ }
+ break;
+ case 12: // eco power limit
+ if (value < 30 || value > 99) { return; /* invalid */ }
+ break;
+ default:
+ return; /* invalid */
+ }
+ }
+ ac->begin();
+ ac->setMessageType(argoIrMessageType_t::CONFIG_PARAM_SET);
+ ac->setConfigEntry(param, value);
+ ac->send();
+}
+
+/// Send an Argo A/C WREM-3 Delay timer command.
+/// @param[in, out] ac A Ptr to an IRArgoAC_WREM3 object to use.
+/// @param[in] on Whether the unit is currently on. The timer, upon elapse
+/// will toggle this state
+/// @param[in] currentTime currentTime in minutes, starting from 00:00
+/// @note For timer mode, this value is not really used much so can be zero.
+/// @param[in] delayMinutes Number of minutes after which the @c on state should
+/// be toggled
+/// @note Schedule timers are not exposed via this interface
+void IRac::argoWrem3_SetTimer(IRArgoAC_WREM3 *ac, bool on,
+ const uint16_t currentTime, const uint16_t delayMinutes) {
+ ac->begin();
+ ac->setMessageType(argoIrMessageType_t::TIMER_COMMAND);
+ ac->setPower(on);
+ ac->setTimerType(argoTimerType_t::DELAY_TIMER);
+ ac->setCurrentTimeMinutes(currentTime);
+ // Note: Day of week is not set (no need)
+ ac->setDelayTimerMinutes(delayMinutes);
+ ac->send();
+}
#endif // SEND_ARGO
#if SEND_BOSCH144
@@ -2654,6 +2785,7 @@ stdAc::state_t IRac::cleanState(const stdAc::state_t state) {
// A hack for Home Assistant, it appears to need/want an Off opmode.
// So enforce the power is off if the mode is also off.
if (state.mode == stdAc::opmode_t::kOff) result.power = false;
+ if (state.roomTemperature == -1.0) result.roomTemperature = state.degrees;
return result;
}
@@ -2850,9 +2982,36 @@ bool IRac::sendAc(const stdAc::state_t desired, const stdAc::state_t *prev) {
#if SEND_ARGO
case ARGO:
{
- IRArgoAC ac(_pin, _inverted, _modulation);
- argo(&ac, send.power, send.mode, degC, send.fanspeed, send.swingv,
- send.turbo, send.sleep);
+ if (send.model == argo_ac_remote_model_t::SAC_WREM3) {
+ IRArgoAC_WREM3 ac(_pin, _inverted, _modulation);
+ switch (send.command) {
+ case stdAc::ac_command_t::kTemperatureReport:
+ argoWrem3_iFeelReport(&ac, send.roomTemperature);
+ break;
+ case stdAc::ac_command_t::kConfigCommand:
+ /// @warning: this is ABUSING current **common** parameters:
+ /// @c clock and @c sleep as config key and value
+ /// Hence, value pre-validation is performed (safe-mode)
+ /// to avoid accidental device misconfiguration
+ argoWrem3_ConfigSet(&ac, send.clock, send.sleep, true);
+ break;
+ case stdAc::ac_command_t::kTimerCommand:
+ argoWrem3_SetTimer(&ac, send.power, send.clock, send.sleep);
+ break;
+ case stdAc::ac_command_t::kControlCommand:
+ default:
+ argoWrem3_ACCommand(&ac, send.power, send.mode, send.degrees,
+ send.roomTemperature, send.fanspeed, send.swingv, send.iFeel,
+ send.quiet, send.econo, send.turbo, send.filter, send.light);
+ break;
+ }
+ OUTPUT_DECODE_RESULTS_FOR_UT(ac);
+ } else {
+ IRArgoAC ac(_pin, _inverted, _modulation);
+ argo(&ac, send.power, send.mode, degC, send.fanspeed, send.swingv,
+ send.turbo, send.sleep);
+ OUTPUT_DECODE_RESULTS_FOR_UT(ac);
+ }
break;
}
#endif // SEND_ARGO
@@ -3421,7 +3580,9 @@ bool IRac::cmpStates(const stdAc::state_t a, const stdAc::state_t b) {
a.fanspeed != b.fanspeed || a.swingv != b.swingv ||
a.swingh != b.swingh || a.quiet != b.quiet || a.turbo != b.turbo ||
a.econo != b.econo || a.light != b.light || a.filter != b.filter ||
- a.clean != b.clean || a.beep != b.beep || a.sleep != b.sleep;
+ a.clean != b.clean || a.beep != b.beep || a.sleep != b.sleep ||
+ a.mode != b.mode || a.roomTemperature != b.roomTemperature ||
+ a.iFeel != b.iFeel;
}
/// Check if the internal state has changed from what was previously sent.
@@ -3429,6 +3590,26 @@ bool IRac::cmpStates(const stdAc::state_t a, const stdAc::state_t b) {
/// @return True if it has changed, False if not.
bool IRac::hasStateChanged(void) { return cmpStates(next, _prev); }
+/// Convert the supplied str into the appropriate enum.
+/// @param[in] str A Ptr to a C-style string to be converted.
+/// @param[in] def The enum to return if no conversion was possible.
+/// @return The equivalent enum.
+stdAc::ac_command_t IRac::strToCommandType(const char *str,
+ const stdAc::ac_command_t def) {
+ if (!STRCASECMP(str, kControlCommandStr))
+ return stdAc::ac_command_t::kControlCommand;
+ else if (!STRCASECMP(str, kTemperatureReportStr) ||
+ !STRCASECMP(str, kIFeelStr))
+ return stdAc::ac_command_t::kTemperatureReport;
+ else if (!STRCASECMP(str, kTimerCommandStr) ||
+ !STRCASECMP(str, kTimerStr))
+ return stdAc::ac_command_t::kTimerCommand;
+ else if (!STRCASECMP(str, kConfigCommandStr))
+ return stdAc::ac_command_t::kConfigCommand;
+ else
+ return def;
+}
+
/// Convert the supplied str into the appropriate enum.
/// @param[in] str A Ptr to a C-style string to be converted.
/// @param[in] def The enum to return if no conversion was possible.
@@ -3492,6 +3673,8 @@ stdAc::fanspeed_t IRac::strToFanspeed(const char *str,
!STRCASECMP(str, kMaximumStr) ||
!STRCASECMP(str, kHighestStr))
return stdAc::fanspeed_t::kMax;
+ else if (!STRCASECMP(str, kMedHighStr))
+ return stdAc::fanspeed_t::kMediumHigh;
else
return def;
}
@@ -3666,6 +3849,11 @@ int16_t IRac::strToModel(const char *str, const int16_t def) {
return whirlpool_ac_remote_model_t::DG11J13A;
} else if (!STRCASECMP(str, kDg11j191Str)) {
return whirlpool_ac_remote_model_t::DG11J191;
+ // Argo A/C models
+ } else if (!STRCASECMP(str, kArgoWrem2Str)) {
+ return argo_ac_remote_model_t::SAC_WREM2;
+ } else if (!STRCASECMP(str, kArgoWrem3Str)) {
+ return argo_ac_remote_model_t::SAC_WREM3;
} else {
int16_t number = atoi(str);
if (number > 0)
@@ -3701,6 +3889,20 @@ String IRac::boolToString(const bool value) {
return value ? kOnStr : kOffStr;
}
+/// Convert the supplied operation mode into the appropriate String.
+/// @param[in] mode The enum to be converted.
+/// @param[in] ha A flag to indicate we want GoogleHome/HomeAssistant output.
+/// @return The equivalent String for the locale.
+String IRac::commandTypeToString(const stdAc::ac_command_t cmdType) {
+ switch (cmdType) {
+ case stdAc::ac_command_t::kControlCommand: return kControlCommandStr;
+ case stdAc::ac_command_t::kTemperatureReport: return kTemperatureReportStr;
+ case stdAc::ac_command_t::kTimerCommand: return kTimerCommandStr;
+ case stdAc::ac_command_t::kConfigCommand: return kConfigCommandStr;
+ default: return kUnknownStr;
+ }
+}
+
/// Convert the supplied operation mode into the appropriate String.
/// @param[in] mode The enum to be converted.
/// @param[in] ha A flag to indicate we want GoogleHome/HomeAssistant output.
@@ -3737,14 +3939,15 @@ String IRac::fanspeedToString(const stdAc::fanspeed_t speed) {
/// @return The equivalent String for the locale.
String IRac::swingvToString(const stdAc::swingv_t swingv) {
switch (swingv) {
- case stdAc::swingv_t::kOff: return kOffStr;
- case stdAc::swingv_t::kAuto: return kAutoStr;
- case stdAc::swingv_t::kHighest: return kHighestStr;
- case stdAc::swingv_t::kHigh: return kHighStr;
- case stdAc::swingv_t::kMiddle: return kMiddleStr;
- case stdAc::swingv_t::kLow: return kLowStr;
- case stdAc::swingv_t::kLowest: return kLowestStr;
- default: return kUnknownStr;
+ case stdAc::swingv_t::kOff: return kOffStr;
+ case stdAc::swingv_t::kAuto: return kAutoStr;
+ case stdAc::swingv_t::kHighest: return kHighestStr;
+ case stdAc::swingv_t::kHigh: return kHighStr;
+ case stdAc::swingv_t::kMiddle: return kMiddleStr;
+ case stdAc::swingv_t::kUpperMiddle: return kUpperMiddleStr;
+ case stdAc::swingv_t::kLow: return kLowStr;
+ case stdAc::swingv_t::kLowest: return kLowestStr;
+ default: return kUnknownStr;
}
}
@@ -3796,6 +3999,12 @@ namespace IRAcUtils {
#endif // DECODE_AMCOR
#if DECODE_ARGO
case decode_type_t::ARGO: {
+ if (IRArgoAC_WREM3::isValidWrem3Message(result->state, result->bits,
+ true)) {
+ IRArgoAC_WREM3 ac(kGpioUnused);
+ ac.setRaw(result->state, result->bits / 8);
+ return ac.toString();
+ }
IRArgoAC ac(kGpioUnused);
ac.setRaw(result->state, result->bits / 8);
return ac.toString();
@@ -4258,15 +4467,23 @@ namespace IRAcUtils {
#endif // DECODE_AMCOR
#if DECODE_ARGO
case decode_type_t::ARGO: {
- IRArgoAC ac(kGpioUnused);
const uint16_t length = decode->bits / 8;
- switch (length) {
- case kArgoStateLength:
- ac.setRaw(decode->state, length);
- *result = ac.toCommon();
- break;
- default:
- return false;
+ if (IRArgoAC_WREM3::isValidWrem3Message(decode->state,
+ decode->bits, true)) {
+ IRArgoAC_WREM3 ac(kGpioUnused);
+ ac.setRaw(decode->state, length);
+ *result = ac.toCommon();
+ } else {
+ IRArgoAC ac(kGpioUnused);
+ switch (length) {
+ case kArgoStateLength:
+ case kArgoShortStateLength:
+ ac.setRaw(decode->state, length);
+ *result = ac.toCommon();
+ break;
+ default:
+ return false;
+ }
}
break;
}
diff --git a/src/IRac.h b/src/IRac.h
index 9193a531d..9a0ca3e53 100644
--- a/src/IRac.h
+++ b/src/IRac.h
@@ -5,6 +5,8 @@
#ifndef UNIT_TEST
#include
+#else
+#include
#endif
#include "IRremoteESP8266.h"
#include "ir_Airton.h"
@@ -84,6 +86,8 @@ class IRac {
static bool cmpStates(const stdAc::state_t a, const stdAc::state_t b);
static bool strToBool(const char *str, const bool def = false);
static int16_t strToModel(const char *str, const int16_t def = -1);
+ static stdAc::ac_command_t strToCommandType(const char *str,
+ const stdAc::ac_command_t def = stdAc::ac_command_t::kControlCommand);
static stdAc::opmode_t strToOpmode(
const char *str, const stdAc::opmode_t def = stdAc::opmode_t::kAuto);
static stdAc::fanspeed_t strToFanspeed(
@@ -94,6 +98,7 @@ class IRac {
static stdAc::swingh_t strToSwingH(
const char *str, const stdAc::swingh_t def = stdAc::swingh_t::kOff);
static String boolToString(const bool value);
+ static String commandTypeToString(const stdAc::ac_command_t cmdType);
static String opmodeToString(const stdAc::opmode_t mode,
const bool ha = false);
static String fanspeedToString(const stdAc::fanspeed_t speed);
@@ -103,10 +108,17 @@ class IRac {
stdAc::state_t getStatePrev(void);
bool hasStateChanged(void);
stdAc::state_t next; ///< The state we want the device to be in after we send
-#ifndef UNIT_TEST
+#ifdef UNIT_TEST
+ /// @cond IGNORE
+ /// UT-specific
+ /// See @c OUTPUT_DECODE_RESULTS_FOR_UT macro description in IRac.cpp
+ std::shared_ptr _utReceiver = nullptr;
+ std::unique_ptr _lastDecodeResults = nullptr;
+ /// @endcond
+#else
private:
-#endif
+#endif // UNIT_TEST
uint16_t _pin; ///< The GPIO to use to transmit messages from.
bool _inverted; ///< IR LED is lit when GPIO is LOW (true) or HIGH (false)?
bool _modulation; ///< Is frequency modulation to be used?
@@ -134,6 +146,16 @@ class IRac {
const bool on, const stdAc::opmode_t mode, const float degrees,
const stdAc::fanspeed_t fan, const stdAc::swingv_t swingv,
const bool turbo, const int16_t sleep = -1);
+ void argoWrem3_ACCommand(IRArgoAC_WREM3 *ac,
+ const bool on, const stdAc::opmode_t mode, const float degrees,
+ const float roomTemp, const stdAc::fanspeed_t fan,
+ const stdAc::swingv_t swingv, const bool iFeel, const bool night,
+ const bool econo, const bool turbo, const bool filter, const bool light);
+ void argoWrem3_iFeelReport(IRArgoAC_WREM3 *ac, const float roomTemp);
+ void argoWrem3_ConfigSet(IRArgoAC_WREM3 *ac, const uint8_t param,
+ const uint8_t value, bool safe = true);
+ void argoWrem3_SetTimer(IRArgoAC_WREM3 *ac, bool on,
+ const uint16_t currentTime, const uint16_t delayMinutes);
#endif // SEND_ARGO
#if SEND_BOSCH144
void bosch144(IRBosch144AC *ac,
diff --git a/src/IRrecv.cpp b/src/IRrecv.cpp
index ef5d46396..d21c2a0a4 100644
--- a/src/IRrecv.cpp
+++ b/src/IRrecv.cpp
@@ -943,8 +943,20 @@ bool IRrecv::decode(decode_results *results, irparams_t *save,
return true;
#endif
#if DECODE_ARGO
- DPRINTLN("Attempting Argo decode");
- if (decodeArgo(results, offset) ||
+ DPRINTLN("Attempting Argo WREM3 decode (AC Control)");
+ if (decodeArgoWREM3(results, offset, kArgo3AcControlStateLength * 8, true))
+ return true;
+ DPRINTLN("Attempting Argo WREM3 decode (iFeel report)");
+ if (decodeArgoWREM3(results, offset, kArgo3iFeelReportStateLength * 8, true))
+ return true;
+ DPRINTLN("Attempting Argo WREM3 decode (Config)");
+ if (decodeArgoWREM3(results, offset, kArgo3ConfigStateLength * 8, true))
+ return true;
+ DPRINTLN("Attempting Argo WREM3 decode (Timer)");
+ if (decodeArgoWREM3(results, offset, kArgo3TimerStateLength * 8, true))
+ return true;
+ DPRINTLN("Attempting Argo WREM2 decode");
+ if (decodeArgo(results, offset, kArgoBits) ||
decodeArgo(results, offset, kArgoShortBits, false)) return true;
#endif // DECODE_ARGO
#if DECODE_SHARP_AC
diff --git a/src/IRrecv.h b/src/IRrecv.h
index 903fd2b07..2fc751dff 100644
--- a/src/IRrecv.h
+++ b/src/IRrecv.h
@@ -294,6 +294,9 @@ class IRrecv {
#if DECODE_ARGO
bool decodeArgo(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kArgoBits, const bool strict = true);
+ bool decodeArgoWREM3(decode_results *results, uint16_t offset = kStartOffset,
+ const uint16_t nbits = kArgo3AcControlStateLength * 8,
+ const bool strict = true);
#endif // DECODE_ARGO
#if DECODE_ARRIS
bool decodeArris(decode_results *results, uint16_t offset = kStartOffset,
diff --git a/src/IRremoteESP8266.h b/src/IRremoteESP8266.h
index 2484ed9c9..aee9b831a 100644
--- a/src/IRremoteESP8266.h
+++ b/src/IRremoteESP8266.h
@@ -1134,6 +1134,10 @@ const uint16_t kArgoStateLength = 12;
const uint16_t kArgoShortStateLength = 4;
const uint16_t kArgoBits = kArgoStateLength * 8;
const uint16_t kArgoShortBits = kArgoShortStateLength * 8;
+const uint16_t kArgo3AcControlStateLength = 6; // Bytes
+const uint16_t kArgo3iFeelReportStateLength = 2; // Bytes
+const uint16_t kArgo3TimerStateLength = 9; // Bytes
+const uint16_t kArgo3ConfigStateLength = 4; // Bytes
const uint16_t kArgoDefaultRepeat = kNoRepeat;
const uint16_t kArrisBits = 32;
const uint16_t kBosch144StateLength = 18;
diff --git a/src/IRsend.h b/src/IRsend.h
index 547253cf6..9b512cfb5 100644
--- a/src/IRsend.h
+++ b/src/IRsend.h
@@ -56,14 +56,15 @@ enum class opmode_t {
/// Common A/C settings for Fan Speeds.
enum class fanspeed_t {
- kAuto = 0,
- kMin = 1,
- kLow = 2,
- kMedium = 3,
- kHigh = 4,
- kMax = 5,
+ kAuto = 0,
+ kMin = 1,
+ kLow = 2,
+ kMedium = 3,
+ kHigh = 4,
+ kMax = 5,
+ kMediumHigh = 6,
// Add new entries before this one, and update it to point to the last entry
- kLastFanspeedEnum = kMax,
+ kLastFanspeedEnum = kMediumHigh,
};
/// Common A/C settings for Vertical Swing.
@@ -75,8 +76,21 @@ enum class swingv_t {
kMiddle = 3,
kLow = 4,
kLowest = 5,
+ kUpperMiddle = 6,
// Add new entries before this one, and update it to point to the last entry
- kLastSwingvEnum = kLowest,
+ kLastSwingvEnum = kUpperMiddle,
+};
+
+/// @brief Tyoe of A/C command (if the remote uses different codes for each)
+/// @note Most remotes support only a single command or aggregate multiple
+/// into one (e.g. control+timer). Use @c kControlCommand in such case
+enum class ac_command_t {
+ kControlCommand = 0,
+ kTemperatureReport = 1,
+ kTimerCommand = 2,
+ kConfigCommand = 3,
+ // Add new entries before this one, and update it to point to the last entry
+ kLastAcCommandEnum = kConfigCommand,
};
/// Common A/C settings for Horizontal Swing.
@@ -113,6 +127,9 @@ struct state_t {
bool beep = false;
int16_t sleep = -1; // `-1` means off.
int16_t clock = -1; // `-1` means not set.
+ bool iFeel = false;
+ float roomTemperature = -1; // `-1` means not set.
+ stdAc::ac_command_t command = stdAc::ac_command_t::kControlCommand;
};
}; // namespace stdAc
@@ -202,6 +219,11 @@ enum lg_ac_remote_model_t {
LG6711A20083V, // (5) Same as GE6711AR2853M, but only SwingV toggle.
};
+/// Argo A/C model numbers
+enum argo_ac_remote_model_t {
+ SAC_WREM2 = 1, // (1) ARGO WREM2 remote (default)
+ SAC_WREM3 // (2) ARGO WREM3 remote (touch buttons), bit-len vary by cmd
+};
// Classes
@@ -528,9 +550,13 @@ class IRsend {
#endif
#if SEND_ARGO
void sendArgo(const unsigned char data[],
+ const uint16_t nbytes = kArgoStateLength,
+ const uint16_t repeat = kArgoDefaultRepeat,
+ bool sendFooter = false);
+ void sendArgoWREM3(const unsigned char data[],
const uint16_t nbytes = kArgoStateLength,
const uint16_t repeat = kArgoDefaultRepeat);
-#endif
+#endif // SEND_ARGO
#if SEND_TROTEC
void sendTrotec(const unsigned char data[],
const uint16_t nbytes = kTrotecStateLength,
diff --git a/src/IRtext.cpp b/src/IRtext.cpp
index 14d3d1496..1f10e4b17 100644
--- a/src/IRtext.cpp
+++ b/src/IRtext.cpp
@@ -68,6 +68,8 @@ IRTEXT_CONST_STRING(kOffTimerStr, D_STR_OFFTIMER); ///< "Off Timer"
IRTEXT_CONST_STRING(kTimerModeStr, D_STR_TIMERMODE); ///< "Timer Mode"
IRTEXT_CONST_STRING(kClockStr, D_STR_CLOCK); ///< "Clock"
IRTEXT_CONST_STRING(kCommandStr, D_STR_COMMAND); ///< "Command"
+IRTEXT_CONST_STRING(kConfigCommandStr, D_STR_AC_COMMAND); ///< "A/C Config"
+IRTEXT_CONST_STRING(kControlCommandStr, D_STR_AC_CONTROL); ///< "A/C Control"
IRTEXT_CONST_STRING(kXFanStr, D_STR_XFAN); ///< "XFan"
IRTEXT_CONST_STRING(kHealthStr, D_STR_HEALTH); ///< "Health"
IRTEXT_CONST_STRING(kModelStr, D_STR_MODEL); ///< "Model"
@@ -123,6 +125,7 @@ IRTEXT_CONST_STRING(kOutsideStr, D_STR_OUTSIDE); ///< "Outside"
IRTEXT_CONST_STRING(kLoudStr, D_STR_LOUD); ///< "Loud"
IRTEXT_CONST_STRING(kLowerStr, D_STR_LOWER); ///< "Lower"
IRTEXT_CONST_STRING(kUpperStr, D_STR_UPPER); ///< "Upper"
+IRTEXT_CONST_STRING(kUpperMiddleStr, D_STR_UPPER_MIDDLE); ///< "UpperMiddle"
IRTEXT_CONST_STRING(kBreezeStr, D_STR_BREEZE); ///< "Breeze"
IRTEXT_CONST_STRING(kCirculateStr, D_STR_CIRCULATE); ///< "Circulate"
IRTEXT_CONST_STRING(kCeilingStr, D_STR_CEILING); ///< "Ceiling"
@@ -158,6 +161,7 @@ IRTEXT_CONST_STRING(kRecycleStr, D_STR_RECYCLE); ///< "Recycle"
IRTEXT_CONST_STRING(kMaxStr, D_STR_MAX); ///< "Max"
IRTEXT_CONST_STRING(kMaximumStr, D_STR_MAXIMUM); ///< "Maximum"
+IRTEXT_CONST_STRING(kMedHighStr, D_STR_MED_HIGH); ///< "Med-high"
IRTEXT_CONST_STRING(kMinStr, D_STR_MIN); ///< "Min"
IRTEXT_CONST_STRING(kMinimumStr, D_STR_MINIMUM); ///< "Minimum"
IRTEXT_CONST_STRING(kMedStr, D_STR_MED); ///< "Med"
@@ -205,6 +209,15 @@ IRTEXT_CONST_STRING(kSwingVModeStr, D_STR_SWINGVMODE); ///< "Swing(V) Mode"
IRTEXT_CONST_STRING(kSwingVToggleStr, D_STR_SWINGVTOGGLE); ///<
///< "Swing(V) Toggle"
IRTEXT_CONST_STRING(kTurboToggleStr, D_STR_TURBOTOGGLE); ///< "Turbo Toggle"
+IRTEXT_CONST_STRING(kTemperatureReportStr, D_STR_AC_TEMP_REPORT); ///<
+///< "A/C Temp Report"
+IRTEXT_CONST_STRING(kTimerCommandStr, D_STR_AC_TIMER); ///< "A/C Set Timer"
+IRTEXT_CONST_STRING(kScheduleStr, D_STR_SCHEDULE); ///< "Schedule"
+IRTEXT_CONST_STRING(kChStr, D_STR_CH); ///< "CH#"
+IRTEXT_CONST_STRING(kTimerActiveDaysStr, D_STR_TIMER_ACTIVE_DAYS);
+///< "TimerActiveDays"
+IRTEXT_CONST_STRING(kKeyStr, D_STR_KEY); ///< "Key"
+IRTEXT_CONST_STRING(kValueStr, D_STR_VALUE); ///< "Value"
// Separators & Punctuation
const char kTimeSep = D_CHR_TIME_SEP; ///< ':'
@@ -281,6 +294,8 @@ IRTEXT_CONST_STRING(k122lzfStr, D_STR_122LZF); ///< "122LZF"
IRTEXT_CONST_STRING(kDg11j13aStr, D_STR_DG11J13A); ///< "DG11J13A"
IRTEXT_CONST_STRING(kDg11j104Str, D_STR_DG11J104); ///< "DG11J104"
IRTEXT_CONST_STRING(kDg11j191Str, D_STR_DG11J191); ///< "DG11J191"
+IRTEXT_CONST_STRING(kArgoWrem2Str, D_STR_ARGO_WREM2); ///< "WREM3"
+IRTEXT_CONST_STRING(kArgoWrem3Str, D_STR_ARGO_WREM3); ///< "WREM3"
#define D_STR_UNSUPPORTED "?" // Unsupported protocols will be showing as
// a question mark, check for length > 1
diff --git a/src/IRtext.h b/src/IRtext.h
index 7bd4fbed3..73164f229 100644
--- a/src/IRtext.h
+++ b/src/IRtext.h
@@ -39,6 +39,8 @@ extern IRTEXT_CONST_PTR(kAkb73757604Str);
extern IRTEXT_CONST_PTR(kAkb74955603Str);
extern IRTEXT_CONST_PTR(kAkb75215403Str);
extern IRTEXT_CONST_PTR(kArdb1Str);
+extern IRTEXT_CONST_PTR(kArgoWrem2Str);
+extern IRTEXT_CONST_PTR(kArgoWrem3Str);
extern IRTEXT_CONST_PTR(kArjw2Str);
extern IRTEXT_CONST_PTR(kArrah2eStr);
extern IRTEXT_CONST_PTR(kArreb1eStr);
@@ -57,6 +59,7 @@ extern IRTEXT_CONST_PTR(kCelsiusFahrenheitStr);
extern IRTEXT_CONST_PTR(kCelsiusStr);
extern IRTEXT_CONST_PTR(kCentreStr);
extern IRTEXT_CONST_PTR(kChangeStr);
+extern IRTEXT_CONST_PTR(kChStr);
extern IRTEXT_CONST_PTR(kCirculateStr);
extern IRTEXT_CONST_PTR(kCkpStr);
extern IRTEXT_CONST_PTR(kCleanStr);
@@ -66,6 +69,8 @@ extern IRTEXT_CONST_PTR(kColonSpaceStr);
extern IRTEXT_CONST_PTR(kComfortStr);
extern IRTEXT_CONST_PTR(kCommaSpaceStr);
extern IRTEXT_CONST_PTR(kCommandStr);
+extern IRTEXT_CONST_PTR(kConfigCommandStr);
+extern IRTEXT_CONST_PTR(kControlCommandStr);
extern IRTEXT_CONST_PTR(kCoolStr);
extern IRTEXT_CONST_PTR(kCoolingStr);
extern IRTEXT_CONST_PTR(kDashStr);
@@ -116,6 +121,7 @@ extern IRTEXT_CONST_PTR(kIndirectStr);
extern IRTEXT_CONST_PTR(kInsideStr);
extern IRTEXT_CONST_PTR(kIonStr);
extern IRTEXT_CONST_PTR(kJkeStr);
+extern IRTEXT_CONST_PTR(kKeyStr);
extern IRTEXT_CONST_PTR(kKkg29ac1Str);
extern IRTEXT_CONST_PTR(kKkg9ac1Str);
extern IRTEXT_CONST_PTR(kLastStr);
@@ -139,6 +145,7 @@ extern IRTEXT_CONST_PTR(kMaxRightNoSpaceStr);
extern IRTEXT_CONST_PTR(kMaxRightStr);
extern IRTEXT_CONST_PTR(kMaxStr);
extern IRTEXT_CONST_PTR(kMaximumStr);
+extern IRTEXT_CONST_PTR(kMedHighStr);
extern IRTEXT_CONST_PTR(kMedStr);
extern IRTEXT_CONST_PTR(kMediumStr);
extern IRTEXT_CONST_PTR(kMidStr);
@@ -188,6 +195,7 @@ extern IRTEXT_CONST_PTR(kRlt0541htaaStr);
extern IRTEXT_CONST_PTR(kRlt0541htabStr);
extern IRTEXT_CONST_PTR(kRoomStr);
extern IRTEXT_CONST_PTR(kSaveStr);
+extern IRTEXT_CONST_PTR(kScheduleStr);
extern IRTEXT_CONST_PTR(kSecondStr);
extern IRTEXT_CONST_PTR(kSecondsStr);
extern IRTEXT_CONST_PTR(kSensorStr);
@@ -209,11 +217,14 @@ extern IRTEXT_CONST_PTR(kSwingVModeStr);
extern IRTEXT_CONST_PTR(kSwingVStr);
extern IRTEXT_CONST_PTR(kSwingVToggleStr);
extern IRTEXT_CONST_PTR(kTac09chsdStr);
+extern IRTEXT_CONST_PTR(kTemperatureReportStr);
extern IRTEXT_CONST_PTR(kTempDownStr);
extern IRTEXT_CONST_PTR(kTempStr);
extern IRTEXT_CONST_PTR(kTempUpStr);
extern IRTEXT_CONST_PTR(kThreeLetterDayOfWeekStr);
+extern IRTEXT_CONST_PTR(kTimerActiveDaysStr);
extern IRTEXT_CONST_PTR(kTimerModeStr);
+extern IRTEXT_CONST_PTR(kTimerCommandStr);
extern IRTEXT_CONST_PTR(kTimerStr);
extern IRTEXT_CONST_PTR(kToggleStr);
extern IRTEXT_CONST_PTR(kTopStr);
@@ -224,6 +235,8 @@ extern IRTEXT_CONST_PTR(kTypeStr);
extern IRTEXT_CONST_PTR(kUnknownStr);
extern IRTEXT_CONST_PTR(kUpStr);
extern IRTEXT_CONST_PTR(kUpperStr);
+extern IRTEXT_CONST_PTR(kUpperMiddleStr);
+extern IRTEXT_CONST_PTR(kValueStr);
extern IRTEXT_CONST_PTR(kV9014557AStr);
extern IRTEXT_CONST_PTR(kV9014557BStr);
extern IRTEXT_CONST_PTR(kVaneStr);
diff --git a/src/IRutils.cpp b/src/IRutils.cpp
index 7c59228c0..49746ac4a 100644
--- a/src/IRutils.cpp
+++ b/src/IRutils.cpp
@@ -695,6 +695,13 @@ namespace irutils {
default: return kUnknownStr;
}
break;
+ case decode_type_t::ARGO:
+ switch (model) {
+ case argo_ac_remote_model_t::SAC_WREM2: return kArgoWrem2Str;
+ case argo_ac_remote_model_t::SAC_WREM3: return kArgoWrem3Str;
+ default: return kUnknownStr;
+ }
+ break;
default: return kUnknownStr;
}
}
@@ -722,10 +729,12 @@ namespace irutils {
/// @param[in] celsius Is the temp Celsius or Fahrenheit.
/// true is C, false is F
/// @param[in] precomma Should the output string start with ", " or not?
+ /// @param[in] isRoomTemp Is the value a room (ambient) temperature or target?
/// @return The resulting String.
String addTempToString(const uint16_t degrees, const bool celsius,
- const bool precomma) {
- String result = addIntToString(degrees, kTempStr, precomma);
+ const bool precomma, const bool isRoomTemp) {
+ String result = addIntToString(degrees, (isRoomTemp)? kRoomStr : kTempStr,
+ precomma);
result += celsius ? 'C' : 'F';
return result;
}
@@ -736,12 +745,14 @@ namespace irutils {
/// @param[in] celsius Is the temp Celsius or Fahrenheit.
/// true is C, false is F
/// @param[in] precomma Should the output string start with ", " or not?
+ /// @param[in] isRoomTemp Is the value a room (ambient) temperature or target?
/// @return The resulting String.
String addTempFloatToString(const float degrees, const bool celsius,
- const bool precomma) {
+ const bool precomma, const bool isRoomTemp) {
String result = "";
result.reserve(14); // Assuming ", Temp: XXX.5F" is the largest.
- result += addIntToString(degrees, kTempStr, precomma);
+ result += addIntToString(degrees, isRoomTemp? kRoomStr : kTempStr,
+ precomma);
// Is it a half degree?
if (((uint16_t)(2 * degrees)) & 1) result += F(".5");
result += celsius ? 'C' : 'F';
@@ -788,43 +799,57 @@ namespace irutils {
result.reserve(19); // ", Day: N (UNKNOWN)"
result += addIntToString(day_of_week, kDayStr, precomma);
result += kSpaceLBraceStr;
+ result += dayToString(day_of_week, offset);
+ return result + ')';
+ }
+
+ /// Create a String of the 3-letter day of the week from a numerical day of
+ /// the week. e.g. "Mon"
+ /// @param[in] day_of_week A numerical version of the sequential day of the
+ /// week. e.g. Sunday = 1, Monday = 2, ..., Saturday = 7
+ /// @param[in] offset Days to offset by.
+ /// e.g. For different day starting the week.
+ /// @return The resulting String.
+ String dayToString(const uint8_t day_of_week, const int8_t offset) {
if ((uint8_t)(day_of_week + offset) < 7)
#if UNIT_TEST
- result += String(kThreeLetterDayOfWeekStr).substr(
- (day_of_week + offset) * 3, 3);
+ return String(kThreeLetterDayOfWeekStr).substr(
+ (day_of_week + offset) * 3, 3);
#else // UNIT_TEST
- result += String(kThreeLetterDayOfWeekStr).substring(
- (day_of_week + offset) * 3, (day_of_week + offset) * 3 + 3);
+ return String(kThreeLetterDayOfWeekStr).substring(
+ (day_of_week + offset) * 3, (day_of_week + offset) * 3 + 3);
#endif // UNIT_TEST
else
- result += kUnknownStr;
- return result + ')';
+ return kUnknownStr;
}
/// Create a String of human output for the given fan speed.
/// e.g. "Fan: 0 (Auto)"
/// @param[in] speed The numeric speed of the fan to display.
- /// @param[in] high The numeric value for High speed.
+ /// @param[in] high The numeric value for High speed. (second highest)
/// @param[in] low The numeric value for Low speed.
/// @param[in] automatic The numeric value for Auto speed.
/// @param[in] quiet The numeric value for Quiet speed.
/// @param[in] medium The numeric value for Medium speed.
/// @param[in] maximum The numeric value for Highest speed. (if > high)
+ /// @param[in] medium_high The numeric value for third-highest speed.
+ /// (if > medium)
/// @return The resulting String.
String addFanToString(const uint8_t speed, const uint8_t high,
const uint8_t low, const uint8_t automatic,
const uint8_t quiet, const uint8_t medium,
- const uint8_t maximum) {
+ const uint8_t maximum, const uint8_t medium_high) {
String result = "";
result.reserve(21); // ", Fan: NNN (UNKNOWN)"
result += addIntToString(speed, kFanStr);
result += kSpaceLBraceStr;
- if (speed == high) result += kHighStr;
- else if (speed == low) result += kLowStr;
- else if (speed == automatic) result += kAutoStr;
- else if (speed == quiet) result += kQuietStr;
- else if (speed == medium) result += kMediumStr;
- else if (speed == maximum) result += kMaximumStr;
+ if (speed == high) result += kHighStr;
+ else if (speed == low) result += kLowStr;
+ else if (speed == automatic) result += kAutoStr;
+ else if (speed == quiet) result += kQuietStr;
+ else if (speed == medium) result += kMediumStr;
+ else if (speed == maximum) result += kMaximumStr;
+ else if (speed == medium_high) result += kMedHighStr;
else
result += kUnknownStr;
return result + ')';
diff --git a/src/IRutils.h b/src/IRutils.h
index a5dcde043..5d5bb24da 100644
--- a/src/IRutils.h
+++ b/src/IRutils.h
@@ -60,16 +60,19 @@ namespace irutils {
String addLabeledString(const String value, const String label,
const bool precomma = true);
String addTempToString(const uint16_t degrees, const bool celsius = true,
- const bool precomma = true);
+ const bool precomma = true,
+ const bool isRoomTemp = false);
String addTempFloatToString(const float degrees, const bool celsius = true,
- const bool precomma = true);
+ const bool precomma = true,
+ const bool isRoomTemp = false);
String addModeToString(const uint8_t mode, const uint8_t automatic,
const uint8_t cool, const uint8_t heat,
const uint8_t dry, const uint8_t fan);
String addFanToString(const uint8_t speed, const uint8_t high,
const uint8_t low, const uint8_t automatic,
const uint8_t quiet, const uint8_t medium,
- const uint8_t maximum = 0xFF);
+ const uint8_t maximum = 0xFF,
+ const uint8_t medium_high = 0xFE);
String addSwingHToString(const uint8_t position, const uint8_t automatic,
const uint8_t maxleft, const uint8_t left,
const uint8_t middle,
@@ -87,6 +90,7 @@ namespace irutils {
const uint8_t breeze, const uint8_t circulate);
String addDayToString(const uint8_t day_of_week, const int8_t offset = 0,
const bool precomma = true);
+ String dayToString(const uint8_t day_of_week, const int8_t offset = 0);
String htmlEscape(const String unescaped);
String msToString(uint32_t const msecs);
String minsToString(const uint16_t mins);
diff --git a/src/ir_Argo.cpp b/src/ir_Argo.cpp
index 885406faf..8e8eff208 100644
--- a/src/ir_Argo.cpp
+++ b/src/ir_Argo.cpp
@@ -1,11 +1,16 @@
// Copyright 2017 Schmolders
// Copyright 2019 crankyoldgit
+// Copyright 2022 Mateusz Bronk (mbronk)
/// @file
/// @brief Argo A/C protocol.
-/// Controls an Argo Ulisse 13 DCI A/C
+
+/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1859
+/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1912
+
#include "ir_Argo.h"
#include
+#include
#include
#ifndef UNIT_TEST
#include
@@ -22,222 +27,858 @@ const uint16_t kArgoBitMark = 400;
const uint16_t kArgoOneSpace = 2200;
const uint16_t kArgoZeroSpace = 900;
const uint32_t kArgoGap = kDefaultMessageGap; // Made up value. Complete guess.
-
const uint8_t kArgoSensorCheck = 52; // Part of the sensor message check calc.
const uint8_t kArgoSensorFixed = 0b011;
+const uint8_t kArgoWrem3Preamble = 0b1011;
+const uint8_t kArgoWrem3Postfix_Timer = 0b1;
+const uint8_t kArgoWrem3Postfix_ACControl = 0b110000;
using irutils::addBoolToString;
using irutils::addIntToString;
using irutils::addLabeledString;
using irutils::addModeToString;
using irutils::addTempToString;
+using irutils::addFanToString;
+using irutils::addSwingVToString;
+using irutils::minsToString;
+using irutils::addDayToString;
+using irutils::addModelToString;
#if SEND_ARGO
/// Send a Argo A/C formatted message.
-/// Status: BETA / Probably works.
+/// Status: [WREM-2] BETA / Probably works.
+/// [WREM-3] Confirmed working w/ Argo 13 ECO (WREM-3)
+/// @note The "no footer" part needs re-checking for validity but retained for
+/// backwards compatibility.
+/// Consider using @c sendFooter=true code for WREM-2 as well
/// @param[in] data The message to be sent.
/// @param[in] nbytes The number of bytes of message to be sent.
/// @param[in] repeat The number of times the command is to be repeated.
+/// @param[in] sendFooter Whether to send footer and add a final gap.
+/// *REQUIRED* for WREM-3, UNKNOWN for WREM-2 (used to be
+/// disabled in previous impl., hence retained)
+/// @note Consider removing this param (default to true) if WREM-2 works w/ it
void IRsend::sendArgo(const unsigned char data[], const uint16_t nbytes,
- const uint16_t repeat) {
- // TODO(kaschmo): validate
+ const uint16_t repeat, bool sendFooter /*= false*/) {
+ if (nbytes < std::min({kArgo3AcControlStateLength,
+ kArgo3ConfigStateLength,
+ kArgo3iFeelReportStateLength,
+ kArgo3TimerStateLength,
+ kArgoStateLength,
+ kArgoShortStateLength})) {
+ return; // Not enough bytes to send a proper message.
+ }
+
+ const uint16_t _footermark = (sendFooter)? kArgoBitMark : 0;
+ const uint32_t _gap = (sendFooter)? kArgoGap : 0;
+
sendGeneric(kArgoHdrMark, kArgoHdrSpace, kArgoBitMark, kArgoOneSpace,
- kArgoBitMark, kArgoZeroSpace, 0, 0, // No Footer.
- data, nbytes, 38, false, repeat, kDutyDefault);
+ kArgoBitMark, kArgoZeroSpace,
+ _footermark, _gap,
+ data, nbytes, kArgoFrequency, false, repeat, kDutyDefault);
+}
+
+
+/// Send a Argo A/C formatted message.
+/// Status: Confirmed working w/ Argo 13 ECO (WREM-3)
+/// @param[in] data The message to be sent.
+/// @param[in] nbytes The number of bytes of message to be sent.
+/// @param[in] repeat The number of times the command is to be repeated.
+void IRsend::sendArgoWREM3(const unsigned char data[], const uint16_t nbytes,
+ const uint16_t repeat) {
+ sendArgo(data, nbytes, repeat, true);
}
#endif // SEND_ARGO
+
/// 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?
-IRArgoAC::IRArgoAC(const uint16_t pin, const bool inverted,
+template
+IRArgoACBase::IRArgoACBase(const uint16_t pin, const bool inverted,
const bool use_modulation)
: _irsend(pin, inverted, use_modulation) { stateReset(); }
+
+/// 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?
+IRArgoAC::IRArgoAC(const uint16_t pin, const bool inverted,
+ const bool use_modulation)
+ : IRArgoACBase(pin, inverted, use_modulation) { }
+
+
+/// 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?
+IRArgoAC_WREM3::IRArgoAC_WREM3(const uint16_t pin, const bool inverted,
+ const bool use_modulation)
+ : IRArgoACBase(pin, inverted, use_modulation) {}
+
/// Set up hardware to be able to send a message.
-void IRArgoAC::begin(void) { _irsend.begin(); }
+template
+void IRArgoACBase::begin(void) { _irsend.begin(); }
-#if SEND_ARGO
-/// Send the current internal state as an IR message.
-/// @param[in] repeat Nr. of times the message will be repeated.
-void IRArgoAC::send(const uint16_t repeat) {
- _irsend.sendArgo(getRaw(), kArgoStateLength, repeat);
+
+/// @brief Get byte length of raw WREM-2 message based on IR cmd type
+/// @note This is a full specialization for @c ArgoProtocol type and while
+/// it semantically belongs to @c IrArgoAC class impl., it has *not*
+/// been pushed there, to avoid having to use a virtual function
+/// @param type The type of IR command
+/// @note Not all types are supported. AC_CONTROL and TIMER are the same cmd
+/// @return Byte length of state command
+template<>
+uint16_t IRArgoACBase::getStateLengthForIrMsgType(
+ argoIrMessageType_t type) {
+ switch (type) {
+ case argoIrMessageType_t::AC_CONTROL:
+ case argoIrMessageType_t::TIMER_COMMAND:
+ return kArgoStateLength;
+ case argoIrMessageType_t::IFEEL_TEMP_REPORT:
+ return kArgoShortStateLength;
+ case argoIrMessageType_t::CONFIG_PARAM_SET:
+ default:
+ return 0; // Not supported by WREM-2
+ }
}
-/// Send current room temperature for the iFeel feature as a silent IR
-/// message (no acknowledgement from the device).
-/// @param[in] degrees The temperature in degrees celsius.
-/// @param[in] repeat Nr. of times the message will be repeated.
-void IRArgoAC::sendSensorTemp(const uint8_t degrees, const uint16_t repeat) {
- const uint8_t temp = std::max(std::min(degrees, kArgoMaxRoomTemp),
- kArgoTempDelta) - kArgoTempDelta;
- const uint8_t check = kArgoSensorCheck + temp;
- ArgoProtocol data;
- _stateReset(&data);
- data.SensorT = temp;
- data.CheckHi = check >> 5;
- data.CheckLo = check;
- data.Fixed = kArgoSensorFixed;
- _checksum(&data);
- _irsend.sendArgo(data.raw, kArgoStateLength, repeat);
+/// @brief Get byte length of raw WREM-3 message based on IR cmd type
+/// @note This is a full specialization for @c ArgoProtocolWREM3 type and while
+/// it semantically belongs to @c IrArgoAC_WREM3 class impl., it has *not*
+/// been pushed there, to avoid having to use a virtual function
+/// @param type The type of IR command
+/// @return Byte length of state command
+template<>
+uint16_t IRArgoACBase::getStateLengthForIrMsgType(
+ argoIrMessageType_t type) {
+ switch (type) {
+ case argoIrMessageType_t::AC_CONTROL:
+ return kArgo3AcControlStateLength;
+ case argoIrMessageType_t::IFEEL_TEMP_REPORT:
+ return kArgo3iFeelReportStateLength;
+ case argoIrMessageType_t::TIMER_COMMAND:
+ return kArgo3TimerStateLength;
+ case argoIrMessageType_t::CONFIG_PARAM_SET:
+ return kArgo3ConfigStateLength;
+ default:
+ return 0;
+ }
}
-#endif // SEND_ARGO
-/// Verify the checksum is valid for a given state.
-/// @param[in] state The array to verify the checksum of.
+
+/// @brief Get message type from raw WREM-2 data
+/// @param _ 1st param ignored: WREM-2 does not caryy type in payload, allegedly
+/// @param length Message length: used for *heuristic* detection of message type
+/// @return IR message type
+/// @note This is a full specialization for @c ArgoProtocol type and while
+/// it semantically belongs to @c IrArgoAC class impl., it has *not*
+/// been pushed there, to avoid having to use a virtual function
+template<>
+argoIrMessageType_t IRArgoACBase::getMessageType(
+ const uint8_t[], const uint16_t length) {
+ if (length == kArgoShortStateLength) {
+ return argoIrMessageType_t::IFEEL_TEMP_REPORT;
+ }
+ return argoIrMessageType_t::AC_CONTROL;
+}
+
+
+/// @brief Get message type from raw WREM-3 data
+/// @param state The raw IR data
+/// @param length Length of @c state (in byte)
+/// @return IR message type
+/// @note This is a full specialization for @c ArgoProtocolWREM3 type and while
+/// it semantically belongs to @c IrArgoAC_WREM3 class impl., it has *not*
+/// been pushed there, to avoid having to use a virtual function
+template<>
+argoIrMessageType_t IRArgoACBase::getMessageType(
+ const uint8_t state[], const uint16_t length) {
+ if (length < 1) {
+ return static_cast(-1);
+ }
+ return static_cast(state[0] >> 6);
+}
+
+/// @brief Get message type from raw WREM-3 data
+/// @param raw Raw data
+/// @return IR message type
+argoIrMessageType_t IRArgoAC_WREM3::getMessageType(
+ const ArgoProtocolWREM3& raw) {
+ return static_cast(raw.IrCommandType);
+}
+
+
+/// @brief Get actual raw state byte length for the current state
+/// @param _ 1st param ignored: WREM-2 does not caryy type in payload, allegedly
+/// @param messageType Type of message the state is carrying
+/// @return Actual length of state (in bytes)
+/// @note This is a full specialization for @c ArgoProtocol type and while
+/// it semantically belongs to @c IrArgoAC class impl., it has *not*
+/// been pushed there, to avoid having to use a virtual function
+template<>
+uint16_t IRArgoACBase::getRawByteLength(const ArgoProtocol&,
+ argoIrMessageType_t messageType) {
+ if (messageType == argoIrMessageType_t::IFEEL_TEMP_REPORT) {
+ return kArgoShortStateLength;
+ }
+ return kArgoStateLength;
+}
+
+
+/// @brief Get actual raw state byte length for the current state
+/// @param raw The raw state
+/// @param _ 2nd param ignored (1st byte of @c raw is sufficient to get len)
+/// @return Actual length of state (in bytes)
+/// @note This is a full specialization for @c ArgoProtocolWREM3 type and while
+/// it semantically belongs to @c IrArgoAC_WREM3 class impl., it has *not*
+/// been pushed there, to avoid having to use a virtual function
+template<>
+uint16_t IRArgoACBase::getRawByteLength(
+ const ArgoProtocolWREM3& raw, argoIrMessageType_t) {
+ return IRArgoAC_WREM3::getStateLengthForIrMsgType(
+ IRArgoAC_WREM3::getMessageType(raw));
+}
+
+
+/// @brief Get actual raw state byte length for the current state
+/// @return Actual length of state (in bytes)
+template
+uint16_t IRArgoACBase::getRawByteLength() const {
+ return getRawByteLength(_, _messageType);
+}
+
+
+/// Calculate the checksum for a given state (WREM-2).
+/// @note This is a full specialization for @c ArgoProtocol type and while
+/// it semantically belongs to @c IrArgoAC class impl., it has *not*
+/// been pushed there, to avoid having to use a virtual function
+/// @warning This does NOT calculate 'short' (iFeel) message checksums
+/// @param[in] state The array to calculate the checksum for.
/// @param[in] length The size of the state.
-/// @return A boolean indicating if it's checksum is valid.
-uint8_t IRArgoAC::calcChecksum(const uint8_t state[], const uint16_t length) {
+/// @return The 8-bit calculated result.
+template<>
+uint8_t IRArgoACBase::calcChecksum(const uint8_t state[],
+ const uint16_t length) {
// Corresponds to byte 11 being constant 0b01
// Only add up bytes to 9. byte 10 is 0b01 constant anyway.
// Assume that argo array is MSB first (left)
return sumBytes(state, length - 2, 2);
}
-/// Verify the checksum is valid for a given state.
-/// @param[in] state The array to verify the checksum of.
+
+/// Calculate the checksum for a given state (WREM-3).
+/// @note This is a full specialization for @c ArgoProtocolWREM3 type and while
+/// it semantically belongs to @c IrArgoAC_WREM3 class impl., it has *not*
+/// been pushed there, to avoid having to use a virtual function
+/// @param[in] state The array to calculate the checksum for.
/// @param[in] length The size of the state.
-/// @return A boolean indicating if it's checksum is valid.
-bool IRArgoAC::validChecksum(const uint8_t state[], const uint16_t length) {
- return ((state[length - 2] >> 2) + (state[length - 1] << 6)) ==
- IRArgoAC::calcChecksum(state, length);
+/// @return The 8-bit calculated result.
+template<>
+uint8_t IRArgoACBase::calcChecksum(const uint8_t state[],
+ const uint16_t length) {
+ if (length < 1) {
+ return -1; // Nothing to compute on
+ }
+
+ auto payloadSizeBits = (length - 1) * 8; // Last byte carries checksum
+
+ auto msgType = getMessageType(state, length);
+ if (msgType == argoIrMessageType_t::IFEEL_TEMP_REPORT) {
+ payloadSizeBits += 5; // For WREM3::iFeel the checksum is 3-bit
+ } else if (msgType == argoIrMessageType_t::TIMER_COMMAND) {
+ payloadSizeBits += 3; // For WREM3::Timer the checksum is 5-bit
+ } // Otherwise: full 8-bit checksum
+
+ uint8_t checksum = sumBytes(state, payloadSizeBits / 8, 0);
+
+ // Add stray bits from last byte to the checksum (if any)
+ const uint8_t maskPayload = 0xFF >> (8 - (payloadSizeBits % 8));
+ checksum += (state[length-1] & maskPayload);
+
+ const uint8_t maskChecksum = 0xFF >> (payloadSizeBits % 8);
+ return checksum & maskChecksum;
}
-/// Update the checksum for a given state.
+
+/// Update the checksum for a given state (WREM2).
+/// @note This is a full specialization for @c ArgoProtocol type and while
+/// it semantically belongs to @c IrArgoAC class impl., it has *not*
+/// been pushed there, to avoid having to use a virtual function
+/// @warning This impl does not support short message format (iFeel)
/// @param[in,out] state Pointer to a binary representation of the A/C state.
-void IRArgoAC::_checksum(ArgoProtocol *state) {
- uint8_t sum = IRArgoAC::calcChecksum(state->raw, kArgoStateLength);
+template<>
+void IRArgoACBase::_checksum(ArgoProtocol *state) {
+ uint8_t sum = calcChecksum(state->raw, kArgoStateLength);
// Append sum to end of array
// Set const part of checksum bit 10
state->Post = kArgoPost;
state->Sum = sum;
}
+
+/// @brief Update the checksum for a given state (WREM3).
+/// @note This is a full specialization for @c ArgoProtocolWREM3 type and while
+/// it semantically belongs to @c IrArgoAC_WREM3 class impl., it has *not*
+/// been pushed there, to avoid having to use a virtual function
+/// @param[in,out] state Pointer to a binary representation of the A/C state.
+template<>
+void IRArgoACBase::_checksum(ArgoProtocolWREM3 *state) {
+ auto msgType = IRArgoAC_WREM3::getMessageType(*state);
+
+ uint8_t sum = calcChecksum(state->raw, getRawByteLength(*state));
+ switch (msgType) {
+ case argoIrMessageType_t::IFEEL_TEMP_REPORT:
+ state->CheckHi = sum;
+ break;
+ case argoIrMessageType_t::TIMER_COMMAND:
+ state->timer.Checksum = sum;
+ break;
+ case argoIrMessageType_t::CONFIG_PARAM_SET:
+ state->config.Checksum = sum;
+ break;
+ case argoIrMessageType_t::AC_CONTROL:
+ default:
+ state->Sum = sum;
+ break;
+ }
+}
+
+
/// Update the checksum for the internal state.
-void IRArgoAC::checksum(void) { _checksum(&_); }
+template
+void IRArgoACBase::checksum(void) { _checksum(&_); }
+
/// Reset the given state to a known good state.
+/// @note This is a full specialization for @c ArgoProtocol type and while
+/// it semantically belongs to @c IrArgoAC class impl., it has *not*
+/// been pushed there, to avoid having to use a virtual function
/// @param[in,out] state Pointer to a binary representation of the A/C state.
-void IRArgoAC::_stateReset(ArgoProtocol *state) {
+/// @param _ 2nd param unused (always resets to AC_CONTROL state)
+template<>
+void IRArgoACBase::_stateReset(ArgoProtocol *state,
+ argoIrMessageType_t) {
for (uint8_t i = 2; i < kArgoStateLength; i++) state->raw[i] = 0x0;
state->Pre1 = kArgoPreamble1; // LSB first (as sent) 0b00110101;
- state->Pre2 = kArgoPreamble2; // LSB first: 0b10101111; //const preamble
+ state->Pre2 = kArgoPreamble2; // LSB first: 0b10101111;
state->Post = kArgoPost;
}
-/// Reset the internals of the object to a known good state.
-void IRArgoAC::stateReset(void) {
- _stateReset(&_);
- off();
- setTemp(20);
- setRoomTemp(25);
- setMode(kArgoAuto);
- setFan(kArgoFanAuto);
- _length = kArgoStateLength;
+
+/// Reset the given state to a known good state
+/// @note This is a full specialization for @c ArgoProtocolWREM3 type and while
+/// it semantically belongs to @c IrArgoAC_WREM3 class impl., it has *not*
+/// been pushed there, to avoid having to use a virtual function
+/// @param[in,out] state Pointer to a binary representation of the A/C state.
+/// @param messageType Type of message to reset the state for
+template<>
+void IRArgoACBase::_stateReset(ArgoProtocolWREM3 *state,
+ argoIrMessageType_t messageType) {
+ for (uint8_t i = 1; i < sizeof(state->raw) / sizeof(state->raw[0]); i++) {
+ state->raw[i] = 0x0;
+ }
+ state->Pre1 = kArgoWrem3Preamble; // LSB first (as sent) 0b00110101;
+ state->IrChannel = 0;
+ state->IrCommandType = static_cast(messageType);
+
+ if (messageType == argoIrMessageType_t::TIMER_COMMAND) {
+ state->timer.Post1 = kArgoWrem3Postfix_Timer; // 0b1
+ } else if (messageType == argoIrMessageType_t::AC_CONTROL) {
+ state->Post1 = kArgoWrem3Postfix_ACControl; // 0b110000
+ }
+}
+
+
+/// @brief Reset the internals of the object to a known good state.
+/// @param messageType Type of message to reset the state for
+template
+void IRArgoACBase::stateReset(argoIrMessageType_t messageType) {
+ _stateReset(&_, messageType);
+ if (messageType == argoIrMessageType_t::AC_CONTROL) {
+ off();
+ setTemp(20);
+ setRoomTemp(25);
+ setMode(argoMode_t::AUTO);
+ setFan(argoFan_t::FAN_AUTO);
+ }
+ _messageType = messageType;
+ _length = getStateLengthForIrMsgType(_messageType);
}
+
+/// @brief Retrieve the checksum value from transmitted state
+/// @note This is a full specialization for @c ArgoProtocol type and while
+/// it semantically belongs to @c IrArgoAC class impl., it has *not*
+/// been pushed there, to avoid having to use a virtual function
+/// @param[in] state Raw state
+/// @param length Length of @c state in bytes
+/// @return Checksum value (8-bit)
+template<>
+uint8_t IRArgoACBase::getChecksum(const uint8_t state[],
+ const uint16_t length) {
+ if (length < 1) {
+ return -1;
+ }
+ return (state[length - 2] >> 2) + (state[length - 1] << 6);
+}
+
+
+/// @brief Retrieve the checksum value from transmitted state
+/// @note This is a full specialization for @c ArgoProtocolWREM3 type and while
+/// it semantically belongs to @c IrArgoAC_WREM3 class impl., it has *not*
+/// been pushed there, to avoid having to use a virtual function
+/// @param[in] state Raw state
+/// @param length Length of @c state in bytes
+/// @return Checksum value (up to 8-bit)
+template<>
+uint8_t IRArgoACBase::getChecksum(const uint8_t state[],
+ const uint16_t length) {
+ if (length < 1) {
+ return -1;
+ }
+ auto msgType = getMessageType(state, length);
+ if (msgType == argoIrMessageType_t::IFEEL_TEMP_REPORT) {
+ return (state[length - 1] & 0b11100000) >> 5;
+ }
+ if (msgType == argoIrMessageType_t::TIMER_COMMAND) {
+ return state[length - 1] >> 3;
+ }
+ return (state[length - 1]);
+}
+
+
+/// Verify the checksum is valid for a given state.
+/// @param[in] state The array to verify the checksum of.
+/// @param[in] length The size of the state.
+/// @return A boolean indicating if it's checksum is valid.
+template
+bool IRArgoACBase::validChecksum(const uint8_t state[],
+ const uint16_t length) {
+ return (getChecksum(state, length) == calcChecksum(state, length));
+}
+
+
+#if SEND_ARGO
+/// Send the current internal state as an IR message.
+/// @param[in] repeat Nr. of times the message will be repeated.
+template
+void IRArgoACBase::send(const uint16_t repeat) {
+ _irsend.sendArgo(getRaw(), getRawByteLength(), repeat);
+}
+
+/// Send the current internal state as an IR message.
+/// @note This is a full specialization for @c ArgoProtocolWREM3 type and while
+/// it semantically belongs to @c IrArgoAC_WREM3 class impl., it has *not*
+/// been pushed there, to avoid having to use a virtual function
+/// @param[in] repeat Nr. of times the message will be repeated.
+template<>
+void IRArgoACBase::send(const uint16_t repeat) {
+ _irsend.sendArgoWREM3(getRaw(), getRawByteLength(), repeat);
+}
+
+
+/// Send current room temperature for the iFeel feature as a silent IR
+/// message (no acknowledgement from the device) (WREM2)
+/// @param[in] degrees The temperature in degrees celsius.
+/// @param[in] repeat Nr. of times the message will be repeated.
+void IRArgoAC::sendSensorTemp(const uint8_t degrees, const uint16_t repeat) {
+ const uint8_t temp = std::max(std::min(degrees, kArgoMaxRoomTemp),
+ kArgoTempDelta) - kArgoTempDelta;
+ const uint8_t check = kArgoSensorCheck + temp;
+
+ ArgoProtocol data;
+ _stateReset(&data, argoIrMessageType_t::IFEEL_TEMP_REPORT);
+ data.SensorT = temp;
+ data.CheckHi = check >> 5;
+ data.CheckLo = check;
+ data.Fixed = kArgoSensorFixed;
+ _checksum(&data);
+ auto msgLen = getRawByteLength(data, argoIrMessageType_t::IFEEL_TEMP_REPORT);
+
+ _irsend.sendArgo(data.raw, msgLen, repeat);
+}
+
+/// Send current room temperature for the iFeel feature as a silent IR
+/// message (no acknowledgement from the device) (WREM3)
+/// @param[in] degrees The temperature in degrees celsius.
+/// @param[in] repeat Nr. of times the message will be repeated.
+void IRArgoAC_WREM3::sendSensorTemp(const uint8_t degrees,
+ const uint16_t repeat) {
+ const uint8_t temp = std::max(std::min(degrees, kArgoMaxRoomTemp),
+ kArgoTempDelta) - kArgoTempDelta;
+ ArgoProtocolWREM3 data = {};
+ _stateReset(&data, argoIrMessageType_t::IFEEL_TEMP_REPORT);
+ data.SensorT = temp;
+ _checksum(&data);
+ auto msgLen = getRawByteLength(data, argoIrMessageType_t::IFEEL_TEMP_REPORT);
+ _irsend.sendArgoWREM3(data.raw, msgLen, repeat);
+}
+#endif
+
+
/// Get the raw state of the object, suitable to be sent with the appropriate
/// IRsend object method.
/// @return A PTR to the internal state.
-uint8_t* IRArgoAC::getRaw(void) {
+template
+uint8_t* IRArgoACBase::getRaw(void) {
checksum(); // Ensure correct bit array before returning
return _.raw;
}
+
/// Set the raw state of the object.
/// @param[in] state The raw state from the native IR message.
/// @param[in] length The length of raw state in bytes.
-void IRArgoAC::setRaw(const uint8_t state[], const uint16_t length) {
+template
+void IRArgoACBase::setRaw(const uint8_t state[], const uint16_t length) {
std::memcpy(_.raw, state, length);
+ _messageType = getMessageType(state, length);
_length = length;
}
/// Set the internal state to have the power on.
-void IRArgoAC::on(void) { setPower(true); }
+template
+void IRArgoACBase::on(void) { setPower(true); }
/// Set the internal state to have the power off.
-void IRArgoAC::off(void) { setPower(false); }
+template
+void IRArgoACBase::off(void) { setPower(false); }
/// Set the internal state to have the desired power.
/// @param[in] on The desired power state.
-void IRArgoAC::setPower(const bool on) {
+/// @note This is a full specialization for @c ArgoProtocol type and while
+/// it semantically belongs to @c IrArgoAC class impl., it has *not*
+/// been pushed there, to avoid having to use a virtual function
+template<>
+void IRArgoACBase::setPower(const bool on) {
_.Power = on;
}
+/// @brief Set the internal state to have the desired power.
+/// @note This is a full specialization for @c ArgoProtocolWREM3 type and while
+/// it semantically belongs to @c IrArgoAC_WREM3 class impl., it has *not*
+/// been pushed there, to avoid having to use a virtual function
+/// @param[in] on The desired power state.
+template<>
+void IRArgoACBase::setPower(const bool on) {
+ if (_messageType == argoIrMessageType_t::TIMER_COMMAND) {
+ _.timer.IsOn = on;
+ } else {
+ _.Power = on;
+ }
+}
+
+/// Get the power setting from the internal state.
+/// @note This is a full specialization for @c ArgoProtocol type and while
+/// it semantically belongs to @c IrArgoAC class impl., it has *not*
+/// been pushed there, to avoid having to use a virtual function
+/// @return A boolean indicating the power setting.
+template<>
+bool IRArgoACBase::getPower(void) const { return _.Power; }
+
/// Get the power setting from the internal state.
+/// @note This is a full specialization for @c ArgoProtocolWREM3 type and while
+/// it semantically belongs to @c IrArgoAC_WREM3 class impl., it has *not*
+/// been pushed there, to avoid having to use a virtual function
/// @return A boolean indicating the power setting.
-bool IRArgoAC::getPower(void) const { return _.Power; }
+template<>
+bool IRArgoACBase::getPower(void) const {
+ if (_messageType == argoIrMessageType_t::TIMER_COMMAND) {
+ return _.timer.IsOn;
+ }
+ return _.Power;
+}
/// Control the current Max setting. (i.e. Turbo)
/// @param[in] on The desired setting.
-void IRArgoAC::setMax(const bool on) {
+template
+void IRArgoACBase::setMax(const bool on) {
_.Max = on;
}
/// Is the Max (i.e. Turbo) setting on?
/// @return The current value.
-bool IRArgoAC::getMax(void) const { return _.Max; }
+template
+bool IRArgoACBase::getMax(void) const { return _.Max; }
/// Set the temperature.
/// @param[in] degrees The temperature in degrees celsius.
/// @note Sending 0 equals +4
-void IRArgoAC::setTemp(const uint8_t degrees) {
+template
+void IRArgoACBase::setTemp(const uint8_t degrees) {
uint8_t temp = std::max(kArgoMinTemp, degrees);
// delta 4 degrees. "If I want 12 degrees, I need to send 8"
temp = std::min(kArgoMaxTemp, temp) - kArgoTempDelta;
// mask out bits
- // argo[13] & 0x00000100; // mask out ON/OFF Bit
_.Temp = temp;
}
/// Get the current temperature setting.
/// @return The current setting for temp. in degrees celsius.
-uint8_t IRArgoAC::getTemp(void) const {
+template
+uint8_t IRArgoACBase::getTemp(void) const {
return _.Temp + kArgoTempDelta;
}
-/// Get the current sensor temperature setting.
+/// Get the current sensor temperature setting (iFeel report).
+/// @note This is different from @c getRoomTemp() as it retrieves iFeel report
+/// explicitly (won't take AC command reported temperature)
+/// ? Does it have any use ?
/// @return The current setting for the sensor. in degrees celsius.
-uint8_t IRArgoAC::getSensorTemp(void) const {
- return (_length == kArgoShortStateLength) ? _.SensorT + kArgoTempDelta : 0;
+template
+uint8_t IRArgoACBase::getSensorTemp(void) const {
+ return (_messageType == argoIrMessageType_t::IFEEL_TEMP_REPORT) ?
+ _.SensorT + kArgoTempDelta : 0;
+}
+
+
+/// @brief Get the current fan mode setting as a strongly typed value (WREM2).
+/// @note This is a full specialization for @c ArgoProtocol type and while
+/// it semantically belongs to @c IrArgoAC class impl., it has *not*
+/// been pushed there, to avoid having to use a virtual function
+/// @return The current fan mode.
+template<>
+argoFan_t IRArgoACBase::getFanEx(void) const {
+ switch (_.Fan) {
+ case kArgoFan3:
+ return argoFan_t::FAN_HIGHEST;
+ case kArgoFan2:
+ return argoFan_t::FAN_MEDIUM;
+ case kArgoFan1:
+ return argoFan_t::FAN_LOWEST;
+ case kArgoFanAuto:
+ return argoFan_t::FAN_AUTO;
+ default:
+ return static_cast(_.Fan);
+ }
+}
+
+/// @brief Get the current fan mode setting as a strongly typed value (WREM3).
+/// @note This is a full specialization for @c ArgoProtocolWREM3 type and while
+/// it semantically belongs to @c IrArgoAC_WREM3 class impl., it has *not*
+/// been pushed there, to avoid having to use a virtual function
+/// @return The current fan mode.
+template<>
+argoFan_t IRArgoACBase::getFanEx(void) const {
+ return static_cast(_.Fan);
+}
+
+/// Set the desired fan mode (WREM2).
+/// @note This is a full specialization for @c ArgoProtocol type and while
+/// it semantically belongs to @c IrArgoAC class impl., it has *not*
+/// been pushed there, to avoid having to use a virtual function
+/// @param[in] fan The desired fan speed.
+/// @note Only a subset of fan speeds are supported (1|2|3|Auto)
+template<>
+void IRArgoACBase::setFan(argoFan_t fan) {
+ switch (fan) {
+ case argoFan_t::FAN_AUTO:
+ _.Fan = kArgoFanAuto;
+ break;
+ case argoFan_t::FAN_HIGHEST:
+ case argoFan_t::FAN_HIGH:
+ _.Fan = kArgoFan3;
+ break;
+ case argoFan_t::FAN_MEDIUM:
+ case argoFan_t::FAN_LOW:
+ _.Fan = kArgoFan2;
+ break;
+ case argoFan_t::FAN_LOWER:
+ case argoFan_t::FAN_LOWEST:
+ _.Fan = kArgoFan1;
+ break;
+ default:
+ auto raw_value = static_cast(fan); // 2-bit value, per def.
+ if ((raw_value & 0b11) == raw_value) {
+ // Outside of known value range, but matches field length
+ // Let's assume the caller knows what they're doing and pass it through
+ _.Fan = raw_value;
+ } else {
+ _.Fan = kArgoFanAuto;
+ }
+ break;
+ }
+}
+
+/// Set the desired fan mode (WREM3).
+/// @note This is a full specialization for @c ArgoProtocolWREM3 type and while
+/// it semantically belongs to @c IrArgoAC_WREM3 class impl., it has *not*
+/// been pushed there, to avoid having to use a virtual function
+/// @param[in] mode The desired fan speed.
+template<>
+void IRArgoACBase::setFan(argoFan_t fan) {
+ switch (fan) {
+ case argoFan_t::FAN_AUTO:
+ case argoFan_t::FAN_HIGHEST:
+ case argoFan_t::FAN_HIGH:
+ case argoFan_t::FAN_MEDIUM:
+ case argoFan_t::FAN_LOW:
+ case argoFan_t::FAN_LOWER:
+ case argoFan_t::FAN_LOWEST:
+ _.Fan = static_cast(fan);
+ break;
+ default:
+ _.Fan = static_cast(argoFan_t::FAN_AUTO);
+ break;
+ }
}
/// Set the speed of the fan.
+/// @deprecated
/// @param[in] fan The desired setting.
void IRArgoAC::setFan(const uint8_t fan) {
_.Fan = std::min(fan, kArgoFan3);
}
/// Get the current fan speed setting.
+/// @deprecated
/// @return The current fan speed.
uint8_t IRArgoAC::getFan(void) const {
return _.Fan;
}
-/// Set the flap position. i.e. Swing.
+/// @brief Get Flap (VSwing) value as a strongly-typed value
+/// @note This @c getFlapEx() method has been introduced to be able to retain
+/// old implementation of @c getFlap() for @c IRArgoAc which used uint8_t
+/// @return Flap setting
+template
+argoFlap_t IRArgoACBase::getFlapEx(void) const {
+ return static_cast(_.Flap);
+}
+
+/// Set the desired flap mode
+/// @param[in] mode The desired flap mode.
+template
+void IRArgoACBase::setFlap(argoFlap_t flap) {
+ auto raw_value = static_cast(flap);
+ if ((raw_value & 0b111) == raw_value) {
+ // Outside of known value range, but matches field length
+ // Let's assume the caller knows what they're doing and pass it through
+ _.Flap = raw_value;
+ } else {
+ _.Flap = static_cast(argoFlap_t::FLAP_AUTO);
+ }
+}
+
+/// Set the flap position. i.e. Swing. (WREM2)
/// @warning Not yet working!
+/// @deprecated
/// @param[in] flap The desired setting.
void IRArgoAC::setFlap(const uint8_t flap) {
+ setFlap(static_cast(flap));
flap_mode = flap;
// TODO(kaschmo): set correct bits for flap mode
}
-/// Get the flap position. i.e. Swing.
+/// Get the flap position. i.e. Swing. (WREM2)
/// @warning Not yet working!
+/// @deprecated
/// @return The current flap setting.
uint8_t IRArgoAC::getFlap(void) const { return flap_mode; }
/// Get the current operation mode setting.
+/// @note This is a full specialization for @c ArgoProtocol type and while
+/// it semantically belongs to @c IrArgoAC class impl., it has *not*
+/// been pushed there, to avoid having to use a virtual function
+/// @return The current operation mode.
+/// @note This @c getModeEx() method has been introduced to be able to retain
+/// old implementation of @c getMode() for @c IRArgoAc which used uint8_t
+template<>
+argoMode_t IRArgoACBase::getModeEx(void) const {
+ switch (_.Mode) {
+ case kArgoCool:
+ return argoMode_t::COOL;
+ case kArgoDry:
+ return argoMode_t::DRY;
+ case kArgoAuto:
+ return argoMode_t::AUTO;
+ case kArgoHeat:
+ return argoMode_t::HEAT;
+ case kArgoOff: // Modelling "FAN" as "OFF", for the lack of better constant
+ return argoMode_t::FAN;
+ case kArgoHeatAuto:
+ default:
+ return static_cast(_.Mode);
+ }
+}
+
+/// Get the current operation mode setting.
+/// @note This is a full specialization for @c ArgoProtocolWREM3 type and while
+/// it semantically belongs to @c IrArgoAC_WREM3 class impl., it has *not*
+/// been pushed there, to avoid having to use a virtual function.
/// @return The current operation mode.
-uint8_t IRArgoAC::getMode(void) const {
- return _.Mode;
+/// @note This @c getModeEx() method has been introduced to be able to retain
+/// old implementation of @c getMode() for @c IRArgoAc which used uint8_t
+template<>
+argoMode_t IRArgoACBase::getModeEx(void) const {
+ return static_cast(_.Mode);
}
/// Set the desired operation mode.
/// @param[in] mode The desired operation mode.
-void IRArgoAC::setMode(const uint8_t mode) {
+/// @note This is a full specialization for @c ArgoProtocol type and while
+/// it semantically belongs to @c IrArgoAC class impl., it has *not*
+/// been pushed there, to avoid having to use a virtual function
+template<>
+void IRArgoACBase::setMode(argoMode_t mode) {
+ switch (mode) {
+ case argoMode_t::COOL:
+ _.Mode = static_cast(kArgoCool);
+ break;
+ case argoMode_t::DRY:
+ _.Mode = static_cast(kArgoDry);
+ break;
+ case argoMode_t::HEAT:
+ _.Mode = static_cast(kArgoHeat);
+ break;
+ case argoMode_t::FAN:
+ _.Mode = static_cast(kArgoOff);
+ break;
+ case argoMode_t::AUTO:
+ _.Mode = static_cast(kArgoAuto);
+ break;
+ default:
+ auto raw_value = static_cast(mode);
+ if ((raw_value & 0b111) == raw_value) {
+ // Outside of known value range, but matches field length
+ // Let's assume the caller knows what they're doing and pass it through
+ _.Mode = raw_value;
+ } else {
+ _.Mode = static_cast(kArgoAuto);;
+ }
+ break;
+ }
+}
+
+/// @brief Set the desired operation mode.
+/// @note This is a full specialization for @c ArgoProtocolWREM3 type and while
+/// it semantically belongs to @c IrArgoAC_WREM3 class impl., it has *not*
+/// been pushed there, to avoid having to use a virtual function
+/// @param[in] mode The desired operation mode.
+template<>
+void IRArgoACBase::setMode(argoMode_t mode) {
+ switch (mode) {
+ case argoMode_t::COOL:
+ case argoMode_t::DRY:
+ case argoMode_t::HEAT:
+ case argoMode_t::FAN:
+ case argoMode_t::AUTO:
+ _.Mode = static_cast(mode);
+ break;
+ default:
+ _.Mode = static_cast(argoMode_t::AUTO);
+ break;
+ }
+}
+
+/// @brief Set the desired operation mode.
+/// @deprecated
+/// @param mode The desired operation mode.
+void IRArgoAC::setMode(uint8_t mode) {
switch (mode) {
case kArgoCool:
case kArgoDry:
@@ -246,118 +887,306 @@ void IRArgoAC::setMode(const uint8_t mode) {
case kArgoHeat:
case kArgoHeatAuto:
_.Mode = mode;
- return;
+ break;
default:
_.Mode = kArgoAuto;
+ break;
}
}
+/// @brief Get the current operation mode
+/// @deprecated
+/// @return The current operation mode
+uint8_t IRArgoAC::getMode() const { return _.Mode;}
+
+argoFan_t IRArgoAC_WREM3::getFan(void) const { return getFanEx(); }
+argoFlap_t IRArgoAC_WREM3::getFlap(void) const { return getFlapEx(); }
+argoMode_t IRArgoAC_WREM3::getMode(void) const { return getModeEx(); }
+
/// Turn on/off the Night mode. i.e. Sleep.
/// @param[in] on The desired setting.
-void IRArgoAC::setNight(const bool on) {
- _.Night = on;
-}
+template
+void IRArgoACBase::setNight(const bool on) { _.Night = on; }
/// Get the status of Night mode. i.e. Sleep.
/// @return true if on, false if off.
-bool IRArgoAC::getNight(void) const { return _.Night; }
+template
+bool IRArgoACBase::getNight(void) const { return _.Night; }
-/// Turn on/off the iFeel mode.
+/// @brief Turn on/off the Economy mode (lowered power mode)
/// @param[in] on The desired setting.
-void IRArgoAC::setiFeel(const bool on) {
- _.iFeel = on;
+void IRArgoAC_WREM3::setEco(const bool on) { _.Eco = on; }
+
+/// @brief Get the status of Economy function
+/// @return true if on, false if off.
+bool IRArgoAC_WREM3::getEco(void) const { return _.Eco; }
+
+/// @brief Turn on/off the Filter mode (not supported by Argo Ulisse)
+/// @param[in] on The desired setting.
+void IRArgoAC_WREM3::setFilter(const bool on) { _.Filter = on; }
+
+/// @brief Get status of the filter function
+/// @return true if on, false if off.
+bool IRArgoAC_WREM3::getFilter(void) const { return _.Filter; }
+
+/// @brief Turn on/off the device Lights (LED)
+/// @param[in] on The desired setting.
+void IRArgoAC_WREM3::setLight(const bool on) { _.Light = on; }
+
+/// @brief Get status of device lights
+/// @return true if on, false if off.
+bool IRArgoAC_WREM3::getLight(void) const { return _.Light; }
+
+/// @brief Set the IR channel on which to communicate
+/// @param[in] channel The desired IR channel.
+void IRArgoAC_WREM3::setChannel(const uint8_t channel) {
+ _.IrChannel = std::min(channel, kArgoMaxChannel);
}
+/// @brief Get the currently set transmission channel
+/// @return Channel number
+uint8_t IRArgoAC_WREM3::getChannel(void) const { return _.IrChannel;}
+
+/// @brief Set the config data to send
+/// Valid only for @c argoIrMessageType_t::CONFIG_PARAM_SET message
+/// @param paramId The param ID
+/// @param value The value of the parameter
+void IRArgoAC_WREM3::setConfigEntry(const uint8_t paramId,
+ const uint8_t value) {
+ _.config.Key = paramId;
+ _.config.Value = value;
+}
+
+/// @brief Get the config entry previously set
+/// @return Key->value pair (paramID: value)
+std::pair IRArgoAC_WREM3::getConfigEntry(void) const {
+ return std::make_pair(_.config.Key, _.config.Value);
+}
+
+/// Turn on/off the iFeel mode.
+/// @param[in] on The desired setting.
+template
+void IRArgoACBase::setiFeel(const bool on) { _.iFeel = on; }
+
/// Get the status of iFeel mode.
/// @return true if on, false if off.
-bool IRArgoAC::getiFeel(void) const { return _.iFeel; }
+template
+bool IRArgoACBase::getiFeel(void) const { return _.iFeel; }
-/// Set the time for the A/C
-/// @warning Not yet working!
-void IRArgoAC::setTime(void) {
- // TODO(kaschmo): use function call from checksum to set time first
+/// @brief Set the message type of the next command (setting this resets state)
+/// @param msgType The message type to set
+template
+void IRArgoACBase::setMessageType(const argoIrMessageType_t msgType) {
+ stateReset(msgType);
+}
+
+/// @brief Get the message type
+/// @return Message type currently set
+template
+argoIrMessageType_t IRArgoACBase::getMessageType(void) const {
+ return _messageType;
}
/// Set the value for the current room temperature.
+/// @note Depending on message type - this will set `sensor` or `roomTemp` value
/// @param[in] degrees The temperature in degrees celsius.
-void IRArgoAC::setRoomTemp(const uint8_t degrees) {
+template
+void IRArgoACBase::setRoomTemp(const uint8_t degrees) {
uint8_t temp = std::min(degrees, kArgoMaxRoomTemp);
temp = std::max(temp, kArgoTempDelta) - kArgoTempDelta;
- _.RoomTemp = temp;
+ if (getMessageType() == argoIrMessageType_t::IFEEL_TEMP_REPORT) {
+ _.SensorT = temp;
+ } else {
+ _.RoomTemp = temp;
+ }
}
/// Get the currently stored value for the room temperature setting.
+/// @note Depending on message type - this will get `sensor` or `roomTemp` value
/// @return The current setting for the room temp. in degrees celsius.
-uint8_t IRArgoAC::getRoomTemp(void) const {
+template
+uint8_t IRArgoACBase::getRoomTemp(void) const {
+ if (getMessageType() == argoIrMessageType_t::IFEEL_TEMP_REPORT) {
+ return _.SensorT + kArgoTempDelta;
+ }
return _.RoomTemp + kArgoTempDelta;
}
+/// @brief Convert a stdAc::ac_command_t enum into its native message type.
+/// @param command The enum to be converted.
+/// @return The native equivalent of the enum.
+template
+argoIrMessageType_t IRArgoACBase::convertCommand(
+ const stdAc::ac_command_t command) {
+ switch (command) {
+ case stdAc::ac_command_t::kTemperatureReport:
+ return argoIrMessageType_t::IFEEL_TEMP_REPORT;
+ case stdAc::ac_command_t::kTimerCommand:
+ return argoIrMessageType_t::TIMER_COMMAND;
+ case stdAc::ac_command_t::kConfigCommand:
+ return argoIrMessageType_t::CONFIG_PARAM_SET;
+ case stdAc::ac_command_t::kControlCommand:
+ default:
+ return argoIrMessageType_t::AC_CONTROL;
+ }
+}
+
/// 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.
-uint8_t IRArgoAC::convertMode(const stdAc::opmode_t mode) {
+template
+argoMode_t IRArgoACBase::convertMode(const stdAc::opmode_t mode) {
switch (mode) {
case stdAc::opmode_t::kCool:
- return kArgoCool;
+ return argoMode_t::COOL;
case stdAc::opmode_t::kHeat:
- return kArgoHeat;
+ return argoMode_t::HEAT;
case stdAc::opmode_t::kDry:
- return kArgoDry;
- case stdAc::opmode_t::kOff:
- return kArgoOff;
- // No fan mode.
- default:
- return kArgoAuto;
+ return argoMode_t::DRY;
+ case stdAc::opmode_t::kFan:
+ return argoMode_t::FAN;
+ case stdAc::opmode_t::kAuto:
+ default: // No off mode.
+ return argoMode_t::AUTO;
}
}
/// Convert a stdAc::fanspeed_t enum into it's native speed.
/// @param[in] speed The enum to be converted.
/// @return The native equivalent of the enum.
-uint8_t IRArgoAC::convertFan(const stdAc::fanspeed_t speed) {
+template
+argoFan_t IRArgoACBase::convertFan(const stdAc::fanspeed_t speed) {
switch (speed) {
case stdAc::fanspeed_t::kMin:
+ return argoFan_t::FAN_LOWEST;
case stdAc::fanspeed_t::kLow:
- return kArgoFan1;
+ return argoFan_t::FAN_LOWER;
case stdAc::fanspeed_t::kMedium:
- return kArgoFan2;
+ return argoFan_t::FAN_LOW;
+ case stdAc::fanspeed_t::kMediumHigh:
+ return argoFan_t::FAN_MEDIUM;
case stdAc::fanspeed_t::kHigh:
+ return argoFan_t::FAN_HIGH;
case stdAc::fanspeed_t::kMax:
- return kArgoFan3;
+ return argoFan_t::FAN_HIGHEST;
default:
- return kArgoFanAuto;
+ return argoFan_t::FAN_AUTO;
}
}
/// Convert a stdAc::swingv_t enum into it's native setting.
/// @param[in] position The enum to be converted.
/// @return The native equivalent of the enum.
-uint8_t IRArgoAC::convertSwingV(const stdAc::swingv_t position) {
+template
+argoFlap_t IRArgoACBase::convertSwingV(const stdAc::swingv_t position) {
switch (position) {
case stdAc::swingv_t::kHighest:
- return kArgoFlapFull;
+ return argoFlap_t::FLAP_1;
case stdAc::swingv_t::kHigh:
- return kArgoFlap5;
+ return argoFlap_t::FLAP_2;
+ case stdAc::swingv_t::kUpperMiddle:
+ return argoFlap_t::FLAP_3;
case stdAc::swingv_t::kMiddle:
- return kArgoFlap4;
+ return argoFlap_t::FLAP_4;
case stdAc::swingv_t::kLow:
- return kArgoFlap3;
+ return argoFlap_t::FLAP_5;
case stdAc::swingv_t::kLowest:
- return kArgoFlap1;
+ return argoFlap_t::FLAP_6;
+ case stdAc::swingv_t::kOff: // This is abusing the semantics quite a bit
+ return argoFlap_t::FLAP_FULL;
+ case stdAc::swingv_t::kAuto:
default:
- return kArgoFlapAuto;
+ return argoFlap_t::FLAP_AUTO;
+ }
+}
+
+
+/// Convert a native flap mode into its stdAc equivalent (WREM2).
+/// @note This is a full specialization for @c ArgoProtocol type and while
+/// it semantically belongs to @c IrArgoAC class impl., it has *not*
+/// been pushed there, to avoid having to use a virtual function
+/// @param[in] position The native setting to be converted.
+/// @return The stdAc equivalent of the native setting.
+template<>
+stdAc::swingv_t IRArgoACBase::toCommonSwingV(
+ const uint8_t position) {
+ switch (position) {
+ case kArgoFlapFull:
+ return stdAc::swingv_t::kHighest;
+ case kArgoFlap5:
+ return stdAc::swingv_t::kHigh;
+ case kArgoFlap4:
+ return stdAc::swingv_t::kMiddle;
+ case kArgoFlap3:
+ return stdAc::swingv_t::kLow;
+ case kArgoFlap1:
+ return stdAc::swingv_t::kLowest;
+ default:
+ return stdAc::swingv_t::kAuto;
+ }
+}
+
+/// Convert a native flap mode into its stdAc equivalent (WREM3).
+/// @note This is a full specialization for @c ArgoProtocolWREM3 type and while
+/// it semantically belongs to @c IrArgoAC_WREM3 class impl., it has *not*
+/// been pushed there, to avoid having to use a virtual function
+/// @param[in] position The native setting to be converted.
+/// @return The stdAc equivalent of the native setting.
+template<>
+stdAc::swingv_t IRArgoACBase::toCommonSwingV(
+ const uint8_t position) {
+ switch (static_cast(position)) {
+ case argoFlap_t::FLAP_FULL:
+ return stdAc::swingv_t::kOff;
+ case argoFlap_t::FLAP_6:
+ return stdAc::swingv_t::kHighest;
+ case argoFlap_t::FLAP_5:
+ return stdAc::swingv_t::kHigh;
+ case argoFlap_t::FLAP_4:
+ return stdAc::swingv_t::kUpperMiddle;
+ case argoFlap_t::FLAP_3:
+ return stdAc::swingv_t::kMiddle;
+ case argoFlap_t::FLAP_2:
+ return stdAc::swingv_t::kLow;
+ case argoFlap_t::FLAP_1:
+ return stdAc::swingv_t::kLowest;
+ case argoFlap_t::FLAP_AUTO:
+ default:
+ return stdAc::swingv_t::kAuto;
+ }
+}
+
+/// Convert a native message type into its stdAc equivalent.
+/// @param[in] mode The native setting to be converted.
+/// @return The stdAc equivalent of the native setting.
+template
+stdAc::ac_command_t IRArgoACBase::toCommonCommand(
+ const argoIrMessageType_t command) {
+ switch (command) {
+ case argoIrMessageType_t::AC_CONTROL:
+ return stdAc::ac_command_t::kControlCommand;
+ case argoIrMessageType_t::IFEEL_TEMP_REPORT:
+ return stdAc::ac_command_t::kTemperatureReport;
+ case argoIrMessageType_t::TIMER_COMMAND:
+ return stdAc::ac_command_t::kTimerCommand;
+ case argoIrMessageType_t::CONFIG_PARAM_SET:
+ return stdAc::ac_command_t::kConfigCommand;
+ default:
+ return stdAc::ac_command_t::kControlCommand;
}
}
/// Convert a native mode into its stdAc equivalent.
/// @param[in] mode The native setting to be converted.
/// @return The stdAc equivalent of the native setting.
-stdAc::opmode_t IRArgoAC::toCommonMode(const uint8_t mode) {
+template
+stdAc::opmode_t IRArgoACBase::toCommonMode(const argoMode_t mode) {
switch (mode) {
- case kArgoCool: return stdAc::opmode_t::kCool;
- case kArgoHeat: return stdAc::opmode_t::kHeat;
- case kArgoDry: return stdAc::opmode_t::kDry;
- // No fan mode.
+ case argoMode_t::COOL: return stdAc::opmode_t::kCool;
+ case argoMode_t::DRY : return stdAc::opmode_t::kDry;
+ case argoMode_t::FAN : return stdAc::opmode_t::kFan;
+ case argoMode_t::HEAT : return stdAc::opmode_t::kHeat;
+ case argoMode_t::AUTO : return stdAc::opmode_t::kAuto;
default: return stdAc::opmode_t::kAuto;
}
}
@@ -365,11 +1194,16 @@ stdAc::opmode_t IRArgoAC::toCommonMode(const uint8_t mode) {
/// Convert a native fan speed into its stdAc equivalent.
/// @param[in] speed The native setting to be converted.
/// @return The stdAc equivalent of the native setting.
-stdAc::fanspeed_t IRArgoAC::toCommonFanSpeed(const uint8_t speed) {
+template
+stdAc::fanspeed_t IRArgoACBase::toCommonFanSpeed(const argoFan_t speed) {
switch (speed) {
- case kArgoFan3: return stdAc::fanspeed_t::kMax;
- case kArgoFan2: return stdAc::fanspeed_t::kMedium;
- case kArgoFan1: return stdAc::fanspeed_t::kMin;
+ case argoFan_t::FAN_AUTO: return stdAc::fanspeed_t::kAuto;
+ case argoFan_t::FAN_HIGHEST: return stdAc::fanspeed_t::kMax;
+ case argoFan_t::FAN_HIGH: return stdAc::fanspeed_t::kHigh;
+ case argoFan_t::FAN_MEDIUM: return stdAc::fanspeed_t::kMediumHigh;
+ case argoFan_t::FAN_LOW: return stdAc::fanspeed_t::kMedium;
+ case argoFan_t::FAN_LOWER: return stdAc::fanspeed_t::kLow;
+ case argoFan_t::FAN_LOWEST: return stdAc::fanspeed_t::kMin;
default: return stdAc::fanspeed_t::kAuto;
}
}
@@ -379,15 +1213,18 @@ stdAc::fanspeed_t IRArgoAC::toCommonFanSpeed(const uint8_t speed) {
stdAc::state_t IRArgoAC::toCommon(void) const {
stdAc::state_t result{};
result.protocol = decode_type_t::ARGO;
+ result.model = argo_ac_remote_model_t::SAC_WREM2;
+ result.command = toCommonCommand(_messageType);
result.power = _.Power;
- result.mode = toCommonMode(_.Mode);
+ result.mode = toCommonMode(getModeEx());
result.celsius = true;
result.degrees = getTemp();
- result.fanspeed = toCommonFanSpeed(_.Fan);
+ result.fanspeed = toCommonFanSpeed(getFanEx());
result.turbo = _.Max;
result.sleep = _.Night ? 0 : -1;
+ result.iFeel = getiFeel();
+ result.roomTemperature = getRoomTemp();
// Not supported.
- result.model = -1; // Not supported.
result.swingv = stdAc::swingv_t::kOff;
result.swingh = stdAc::swingh_t::kOff;
result.light = false;
@@ -400,16 +1237,68 @@ stdAc::state_t IRArgoAC::toCommon(void) const {
return result;
}
-/// Convert the current internal state into a human readable string.
+/// Convert the current internal state into its stdAc::state_t equivalent.
+/// @return The stdAc equivalent of the native settings.
+stdAc::state_t IRArgoAC_WREM3::toCommon(void) const {
+ stdAc::state_t result{};
+ result.protocol = decode_type_t::ARGO;
+ result.model = argo_ac_remote_model_t::SAC_WREM3;
+ result.command = toCommonCommand(_messageType);
+ result.power = getPower();
+ result.mode = toCommonMode(getModeEx());
+ result.celsius = true;
+ result.degrees = getTemp();
+ result.fanspeed = toCommonFanSpeed(getFanEx());
+ result.turbo = _.Max;
+ result.swingv = toCommonSwingV(_.Flap);
+ result.light = getLight();
+ result.filter = getFilter();
+ result.econo = getEco();
+ result.quiet = getNight();
+ result.beep = (_messageType != argoIrMessageType_t::IFEEL_TEMP_REPORT);
+
+ result.clock = -1;
+ result.sleep = _.Night ? 0 : -1;
+ if (_messageType == argoIrMessageType_t::TIMER_COMMAND) {
+ result.clock = getCurrentTimeMinutes();
+ result.sleep = getDelayTimerMinutes();
+ }
+ result.iFeel = getiFeel();
+ result.roomTemperature = getRoomTemp();
+
+ // Not supported.
+ result.swingh = stdAc::swingh_t::kOff;
+ result.clean = false;
+
+ return result;
+}
+
+
+namespace {
+ /// @brief Short-hand for casting enum to its underlying storage type
+ /// @tparam E The type of enum
+ /// @param e Enum value
+ /// @return Type of underlying value
+ template
+ constexpr typename std::underlying_type::type to_underlying(E e) noexcept {
+ return static_cast::type>(e);
+ }
+}
+
+/// Convert the current internal state into a human readable string (WREM2).
/// @return A human readable string.
String IRArgoAC::toString(void) const {
String result = "";
- result.reserve(100); // Reserve some heap for the string to reduce fragging.
- if (_length == kArgoShortStateLength) {
- result += addIntToString(getSensorTemp(), kSensorTempStr, false);
+ result.reserve(118); // Reserve some heap for the string to reduce fragging.
+ // E.g.: Model: 1 (WREM2), Power: On, Mode: 0 (Cool), Fan: 0 (Auto),
+ // Temp: 20C, Room Temp: 21C, Max: On, IFeel: On, Night: On
+ result += addModelToString(decode_type_t::ARGO,
+ argo_ac_remote_model_t::SAC_WREM2, false);
+ if (_messageType == argoIrMessageType_t::IFEEL_TEMP_REPORT) {
+ result += addIntToString(getSensorTemp(), kSensorTempStr);
result += 'C';
} else {
- result += addBoolToString(_.Power, kPowerStr, false);
+ result += addBoolToString(_.Power, kPowerStr);
result += addIntToString(_.Mode, kModeStr);
result += kSpaceLBraceStr;
switch (_.Mode) {
@@ -468,8 +1357,364 @@ String IRArgoAC::toString(void) const {
return result;
}
+/// @brief Set current clock (as minutes, counted from 0:00)
+/// E.g. 13:38 becomes 818 (13*60+38)
+/// @param currentTimeMinutes Current time (in minutes)
+void IRArgoAC_WREM3::setCurrentTimeMinutes(uint16_t currentTimeMinutes) {
+ uint16_t time = std::min(currentTimeMinutes, static_cast(23*60+59));
+ _.timer.CurrentTimeHi = (time >> 4);
+ _.timer.CurrentTimeLo = (time & 0b1111);
+}
+
+/// @brief Retrieve current time
+/// @return Current time as minutes from 0:00
+uint16_t IRArgoAC_WREM3::getCurrentTimeMinutes(void) const {
+ return (_.timer.CurrentTimeHi << 4) + _.timer.CurrentTimeLo;
+}
+
+/// @brief Set current day of week
+/// @param dayOfWeek Current day of week
+void IRArgoAC_WREM3::setCurrentDayOfWeek(argoWeekday dayOfWeek) {
+ auto day = std::min(to_underlying(dayOfWeek),
+ to_underlying(argoWeekday::SATURDAY));
+ _.timer.CurrentWeekdayHi = (day >> 1);
+ _.timer.CurrentWeekdayLo = (day & 0b1);
+}
+
+/// @brief Get current day of week
+/// @return Current day of week
+argoWeekday IRArgoAC_WREM3::getCurrentDayOfWeek(void) const {
+ return static_cast((_.timer.CurrentWeekdayHi << 1) +
+ _.timer.CurrentWeekdayLo);
+}
+
+/// @brief Set timer type
+/// @param timerType Timer type to use OFF | DELAY | SCHEDULE<1|2|3>
+/// @note 2 timer types supported: delay | schedule timer
+/// - @c DELAY_TIMER requires setting @c setDelayTimerMinutes
+/// and @c setCurrentTimeMinutes and (optionally) @c setCurrentDayOfWeek
+/// - @c SCHEDULE_TIMER requires setting:
+/// @c setScheduleTimerStartMinutes
+/// @c setScheduleTimerStopMinutes
+/// @c setScheduleTimerActiveDays
+/// as well as current time *and* day
+/// @c setCurrentTimeMinutes and @c setCurrentDayOfWeek
+void IRArgoAC_WREM3::setTimerType(const argoTimerType_t timerType) {
+ if (timerType > argoTimerType_t::SCHEDULE_TIMER_3) {
+ _.timer.TimerType = to_underlying(argoTimerType_t::NO_TIMER);
+ } else {
+ _.timer.TimerType = to_underlying(timerType);
+ }
+}
+
+/// @brief Get currently set timer type
+/// @return Timer type
+argoTimerType_t IRArgoAC_WREM3::getTimerType(void) const {
+ return static_cast(_.timer.TimerType);
+}
+
+/// @brief Set delay timer delay in minutes (10-minute increments only)
+/// Max is 1190 (19h50m)
+/// @note The delay timer also accepts current device state: set by @c setPower
+/// @param delayMinutes Delay minutes
+void IRArgoAC_WREM3::setDelayTimerMinutes(const uint16_t delayMinutes) {
+ const uint16_t DELAY_TIMER_MAX = 19*60+50;
+ uint16_t time = std::min(delayMinutes, DELAY_TIMER_MAX);
+
+ // only full 10 minute increments are allowed
+ time = static_cast((time / 10.0) + 0.5) * 10;
+
+ _.timer.DelayTimeHi = (time >> 6);
+ _.timer.DelayTimeLo = (time & 0b111111);
+}
+
+/// @brief Get current delay timer value
+/// @return Delay timer value (in minutes)
+uint16_t IRArgoAC_WREM3::getDelayTimerMinutes(void) const {
+ return (_.timer.DelayTimeHi << 6) + _.timer.DelayTimeLo;
+}
+
+/// @brief Set schedule timer on time (time when the device should turn on)
+/// (10-minute increments only)
+/// @param startTimeMinutes Time when the device should turn itself on
+/// expressed as # of minutes counted from 0:00
+/// The value is in 10-minute increments (rounded)
+/// E.g. 13:38 becomes 820 (13:40 in minutes)
+void IRArgoAC_WREM3::setScheduleTimerStartMinutes(
+ const uint16_t startTimeMinutes) {
+ const uint16_t SCHEDULE_TIMER_MAX = 23*60+50;
+ uint16_t time = std::min(startTimeMinutes, SCHEDULE_TIMER_MAX);
+
+ // only full 10 minute increments are allowed
+ time = static_cast((time / 10.0) + 0.5) * 10;
+
+ _.timer.TimerStartHi = (time >> 3);
+ _.timer.TimerStartLo = (time & 0b111);
+}
+
+/// @brief Get schedule timer ON time
+/// @return Schedule on time (as # of minutes from 0:00)
+uint16_t IRArgoAC_WREM3::getScheduleTimerStartMinutes(void) const {
+ return (_.timer.TimerStartHi << 3) + _.timer.TimerStartLo;
+}
+
+/// @brief Set schedule timer off time (time when the device should turn off)
+/// (10-minute increments only)
+/// @param stopTimeMinutes Time when the device should turn itself off
+/// expressed as # of minutes counted from 0:00
+/// The value is in 10-minute increments (rounded)
+/// E.g. 13:38 becomes 820 (13:40 in minutes)
+void IRArgoAC_WREM3::setScheduleTimerStopMinutes(
+ const uint16_t stopTimeMinutes) {
+ const uint16_t SCHEDULE_TIMER_MAX = 23*60+50;
+ uint16_t time = std::min(stopTimeMinutes, SCHEDULE_TIMER_MAX);
+
+ // only full 10 minute increments are allowed
+ time = static_cast((time / 10.0) + 0.5) * 10;
+
+ _.timer.TimerEndHi = (time >> 8);
+ _.timer.TimerEndLo = (time & 0b11111111);
+}
+
+/// @brief Get schedule timer OFF time
+/// @return Schedule off time (as # of minutes from 0:00)
+uint16_t IRArgoAC_WREM3::getScheduleTimerStopMinutes(void) const {
+ return (_.timer.TimerEndHi << 8) + _.timer.TimerEndLo;
+}
+
+/// @brief Get the days when shedule timer shall be active (as bitmap)
+/// @return Days when schedule timer is active, as raw bitmap type
+/// where bit[0] is Sunday, bit[1] -> Monday, ...
+uint8_t IRArgoAC_WREM3::getTimerActiveDaysBitmap(void) const {
+ return (_.timer.TimerActiveDaysHi << 5) + _.timer.TimerActiveDaysLo;
+}
+
+/// @brief Set the days when the schedule timer shall be active
+/// @param days A set of days when the timer shall run
+void IRArgoAC_WREM3::setScheduleTimerActiveDays(
+ const std::set& days) {
+ uint8_t daysBitmap = 0;
+ for (const auto& day : days) {
+ daysBitmap |= (0b1 << to_underlying(day));
+ }
+ _.timer.TimerActiveDaysHi = (daysBitmap >> 5);
+ _.timer.TimerActiveDaysLo = (daysBitmap & 0b11111);
+}
+
+/// @brief Get the days when shedule timer shall be active (as set)
+/// @return Days when the schedule timer runs
+std::set IRArgoAC_WREM3::getScheduleTimerActiveDays(void) const {
+ std::set result = {};
+ uint8_t daysBitmap = getTimerActiveDaysBitmap();
+ for (uint8_t i = to_underlying(argoWeekday::SUNDAY);
+ i <= to_underlying(argoWeekday::SATURDAY);
+ ++i) {
+ if (((daysBitmap >> i) & 0b1) == 0b1) {
+ result.insert(static_cast(i));
+ }
+ }
+ return result;
+}
+
+/// @brief Get device model
+/// @return Device model
+argo_ac_remote_model_t IRArgoAC_WREM3::getModel() const {
+ return argo_ac_remote_model_t::SAC_WREM3;
+}
+
+namespace {
+ /// @brief Helper function to convert timer active days to a string
+ /// @param days The active days bitmap
+ /// @return `|`-delimited representation of active days (3-letter day repr.)
+ String dayBitmaskToString(std::set days) {
+ String result = "";
+ result.reserve(29); // [Sun|Mon|Tue|Wed|Thu|Fri|Sat]
+
+ for (const auto& day : days) {
+ if (result.length() > 0) {
+ result += "|";
+ }
+ result += irutils::dayToString(to_underlying(day));
+ }
+ return result;
+ }
+
+ /// @brief Get string representation of a timer type
+ /// @param timerType Timer type to represent
+ /// @return String representation
+ String timerTypeToString(argoTimerType_t timerType) {
+ String result = "";
+ result.reserve(13); // "2 (Schedule1)"
+ result += uint64ToString(to_underlying(timerType));
+ result += kSpaceLBraceStr;
+
+ switch (timerType) {
+ case argoTimerType_t::NO_TIMER:
+ result += kOffStr;
+ break;
+ case argoTimerType_t::DELAY_TIMER:
+ result += kSleepTimerStr;
+ break;
+ case argoTimerType_t::SCHEDULE_TIMER_1:
+ result += kScheduleStr;
+ result += '1';
+ break;
+ case argoTimerType_t::SCHEDULE_TIMER_2:
+ result += kScheduleStr;
+ result += '2';
+ break;
+ case argoTimerType_t::SCHEDULE_TIMER_3:
+ result += kScheduleStr;
+ result += '3';
+ break;
+ default:
+ result += kUnknownStr;
+ break;
+ }
+ return result + ')';
+ }
+
+ String channelToString(uint8_t channel) {
+ String result = "";
+ result.reserve(6); // "[CH#4]"
+ result += "[";
+ result += kChStr;
+ result += uint64ToString(channel);
+ result += "]";
+ return result;
+ }
+
+ String commandTypeToString(argoIrMessageType_t type, uint8_t channel) {
+ String result = "";
+ result.reserve(19); // "Sensor Temp[CH#0]: "
+ switch (type) {
+ case argoIrMessageType_t::AC_CONTROL:
+ result += kCommandStr;
+ break;
+ case argoIrMessageType_t::IFEEL_TEMP_REPORT:
+ result += kSensorTempStr;
+ break;
+ case argoIrMessageType_t::TIMER_COMMAND:
+ result += kTimerStr;
+ break;
+ case argoIrMessageType_t::CONFIG_PARAM_SET:
+ result += kConfigCommandStr;
+ break;
+ default:
+ result += kUnknownStr;
+ }
+ result += channelToString(channel);
+ result += kColonSpaceStr;
+ return result;
+ }
+} // namespace
+
+/// Convert the current internal state into a human readable string (WREM3).
+/// @return A human readable string.
+String IRArgoAC_WREM3::toString(void) const {
+ String result = "";
+ result.reserve(190); // Reserve some heap for the string to reduce fragging.
+ // E.g.: Command[CH#0]: Model: 2 (WREM3), Power: On, Mode: 1 (Cool),
+ // Temp: 22C, Room: 26C, Fan: 0 (Auto), Swing(V): 7 (Breeze),
+ // IFeel: Off, Night: Off, Econo: Off, Max: Off, Filter: Off, Light: On
+ // Temp: 20C, Room Temp: 21C, Max: On, IFeel: On, Night: On
+
+ auto commandType = this->getMessageType();
+ argo_ac_remote_model_t model = getModel();
+
+ result += commandTypeToString(commandType, getChannel());
+ result += addModelToString(decode_type_t::ARGO, model, false);
+
+ switch (commandType) {
+ case argoIrMessageType_t::IFEEL_TEMP_REPORT:
+ result += addTempToString(getSensorTemp(), true, true, true);
+ break;
+
+ case argoIrMessageType_t::AC_CONTROL:
+ result += addBoolToString(getPower(), kPowerStr);
+ result += addModeToString(to_underlying(getModeEx()),
+ to_underlying(argoMode_t::AUTO),
+ to_underlying(argoMode_t::COOL),
+ to_underlying(argoMode_t::HEAT),
+ to_underlying(argoMode_t::DRY),
+ to_underlying(argoMode_t::FAN));
+ result += addTempToString(getTemp());
+ result += addTempToString(getRoomTemp(), true, true, true);
+ result += addFanToString(to_underlying(getFanEx()),
+ to_underlying(argoFan_t::FAN_HIGH),
+ to_underlying(argoFan_t::FAN_LOWER),
+ to_underlying(argoFan_t::FAN_AUTO),
+ to_underlying(argoFan_t::FAN_LOWEST),
+ to_underlying(argoFan_t::FAN_LOW),
+ to_underlying(argoFan_t::FAN_HIGHEST),
+ to_underlying(argoFan_t::FAN_MEDIUM));
+ result += addSwingVToString(to_underlying(getFlapEx()),
+ to_underlying(argoFlap_t::FLAP_AUTO),
+ to_underlying(argoFlap_t::FLAP_1),
+ to_underlying(argoFlap_t::FLAP_2),
+ to_underlying(argoFlap_t::FLAP_3),
+ to_underlying(argoFlap_t::FLAP_4), -1,
+ to_underlying(argoFlap_t::FLAP_5),
+ to_underlying(argoFlap_t::FLAP_6), -1, -1,
+ to_underlying(argoFlap_t::FLAP_FULL), -1);
+ result += addBoolToString(getiFeel(), kIFeelStr);
+ result += addBoolToString(getNight(), kNightStr);
+ result += addBoolToString(getEco(), kEconoStr);
+ result += addBoolToString(getMax(), kMaxStr); // Turbo
+ result += addBoolToString(getFilter(), kFilterStr);
+ result += addBoolToString(getLight(), kLightStr);
+ break;
+
+ case argoIrMessageType_t::TIMER_COMMAND:
+ result += addBoolToString(_.timer.IsOn, kPowerStr);
+ result += addLabeledString(timerTypeToString(getTimerType()),
+ kTimerModeStr);
+ result += addLabeledString(minsToString(getCurrentTimeMinutes()),
+ kClockStr);
+ result += addDayToString(to_underlying(getCurrentDayOfWeek()));
+ switch (getTimerType()) {
+ case argoTimerType_t::NO_TIMER:
+ result += addLabeledString(kOffStr, kTimerStr);
+ break;
+ case argoTimerType_t::DELAY_TIMER:
+ result += addLabeledString(minsToString(getDelayTimerMinutes()),
+ kTimerStr);
+ break;
+ default:
+ result += addLabeledString(minsToString(getScheduleTimerStartMinutes()),
+ kOnTimerStr);
+ result += addLabeledString(minsToString(getScheduleTimerStopMinutes()),
+ kOffTimerStr);
+ result += addLabeledString(dayBitmaskToString(
+ getScheduleTimerActiveDays()), kTimerActiveDaysStr);
+ break;
+ }
+ break;
+
+ case argoIrMessageType_t::CONFIG_PARAM_SET:
+ result += addIntToString(_.config.Key, kKeyStr);
+ result += addIntToString(_.config.Value, kValueStr);
+ break;
+ }
+
+ return result;
+}
+
+/// @brief Check if raw ARGO state starts with valid WREM3 preamble
+/// @param state The state bytes
+/// @param length Length of state in bytes
+/// @return True if state starts wiht valid WREM3 preamble, False otherwise
+bool IRArgoAC_WREM3::hasValidPreamble(const uint8_t state[],
+ const uint16_t length) {
+ if (length < 1) {
+ return false;
+ }
+ auto preamble = state[0] & 0x0F;
+ return preamble == kArgoWrem3Preamble;
+}
+
#if DECODE_ARGO
-/// Decode the supplied Argo message.
+/// Decode the supplied Argo message (WREM2).
/// Status: BETA / Probably works.
/// @param[in,out] results Ptr to the data to decode & where to store the decode
/// result.
@@ -496,7 +1741,9 @@ bool IRrecv::decodeArgo(decode_results *results, uint16_t offset,
// Compliance
// Verify we got a valid checksum.
- if (strict && !IRArgoAC::validChecksum(results->state)) return false;
+ if (strict && !IRArgoAC::validChecksum(results->state, kArgoStateLength)) {
+ return false;
+ }
// Success
results->decode_type = decode_type_t::ARGO;
results->bits = nbits;
@@ -505,4 +1752,98 @@ bool IRrecv::decodeArgo(decode_results *results, uint16_t offset,
// is a union data type.
return true;
}
+
+/// Decode the supplied Argo message (WREM3).
+/// Status: Confirmed working w/ Argo 13 ECO (WREM-3)
+/// @param[in,out] results Ptr to the data to decode & where to store the decode
+/// result.
+/// @param[in] offset The starting index to use when attempting to decode the
+/// raw data. Typically/Defaults to kStartOffset.
+/// @param[in] nbits The number of data bits to expect.
+/// @param[in] strict Flag indicating if we should perform strict matching.
+/// @return A boolean. True if it can decode it, false if it can't.
+/// @note This decoder is separate from @c decodeArgo to maintain backwards
+/// compatibility. Contrary to WREM2, this expects a footer and gap!
+bool IRrecv::decodeArgoWREM3(decode_results *results, uint16_t offset,
+ const uint16_t nbits,
+ const bool strict) {
+ if (strict
+ && nbits != kArgo3AcControlStateLength * 8
+ && nbits != kArgo3ConfigStateLength * 8
+ && nbits != kArgo3iFeelReportStateLength * 8
+ && nbits != kArgo3TimerStateLength * 8) {
+ return false;
+ }
+
+ const uint16_t kArgoOverhead = 3;
+ uint16_t bytesRead = matchGeneric(results->rawbuf + offset, results->state,
+ results->rawlen - offset, nbits,
+ kArgoHdrMark, kArgoHdrSpace,
+ kArgoBitMark, kArgoOneSpace,
+ kArgoBitMark, kArgoZeroSpace,
+ kArgoBitMark, kArgoGap, // difference vs decodeArgo
+ true, _tolerance, kArgoOverhead,
+ false);
+ if (!bytesRead ||
+ !IRArgoAC_WREM3::isValidWrem3Message(results->state, nbits, strict)) {
+ return false;
+ }
+
+ // Success: Matched ARGO protocol and WREM3-model
+ // Note that unfortunately decode_type does not allow to persist model...
+ // so we will be re-detecting it later :)
+ results->decode_type = decode_type_t::ARGO;
+ results->bits = nbits;
+ // No need to record the state as we stored it as we decoded it.
+ // As we use result->state, we don't record value, address, or command as it
+ // is a union data type.
+ return true;
+}
+
+/// @brief Detects if an ARGO protocol message is a WREM-3 sub-type (model)
+/// @param state The raw IR decore state
+/// @param nbits The length of @c state **IN BITS**
+/// @param strict Whether to perform strict matching (incl. checksum)
+/// @return True if the message is a WREM-3 one
+bool IRArgoAC_WREM3::isValidWrem3Message(const uint8_t state[],
+ const uint16_t nbits, bool strict) {
+ auto stateLength = std::min(static_cast(std::ceil(nbits / 8.0)),
+ kStateSizeMax);
+
+ if (strict && !IRArgoAC_WREM3::hasValidPreamble(state, stateLength)) {
+ return false;
+ }
+
+ auto messageType = IRArgoACBase::getMessageType(state,
+ stateLength);
+
+ switch (messageType) {
+ case argoIrMessageType_t::AC_CONTROL :
+ if (nbits != kArgo3AcControlStateLength * 8) { return false; }
+ break;
+ case argoIrMessageType_t::CONFIG_PARAM_SET:
+ if (nbits != kArgo3ConfigStateLength * 8) { return false; }
+ break;
+ case argoIrMessageType_t::TIMER_COMMAND:
+ if (nbits != kArgo3TimerStateLength * 8) { return false; }
+ break;
+ case argoIrMessageType_t::IFEEL_TEMP_REPORT:
+ if (nbits != kArgo3iFeelReportStateLength * 8) { return false; }
+ break;
+ default:
+ return false;
+ }
+
+ // Compliance: Verify we got a valid checksum.
+ if (strict && !IRArgoAC_WREM3::validChecksum(state, stateLength)) {
+ return false;
+ }
+ return true;
+}
+
#endif // DECODE_ARGO
+
+
+// force template instantiation
+template class IRArgoACBase;
+template class IRArgoACBase;
diff --git a/src/ir_Argo.h b/src/ir_Argo.h
index e0ead8e26..e0d1decce 100644
--- a/src/ir_Argo.h
+++ b/src/ir_Argo.h
@@ -1,14 +1,18 @@
// Copyright 2017 Schmolders
// Copyright 2022 crankyoldgit
+// Copyright 2022 Mateusz Bronk (mbronk)
/// @file
/// @brief Support for Argo Ulisse 13 DCI Mobile Split ACs.
// Supports:
-// Brand: Argo, Model: Ulisse 13 DCI Mobile Split A/C
+// Brand: Argo, Model: Ulisse 13 DCI Mobile Split A/C [WREM2 remote]
+// Brand: Argo, Model: Ulisse Eco Mobile Split A/C (Wifi) [WREM3 remote]
#ifndef IR_ARGO_H_
#define IR_ARGO_H_
+#include
+#include
#ifndef UNIT_TEST
#include
#endif
@@ -21,7 +25,7 @@
// ARGO Ulisse DCI
-/// Native representation of a Argo A/C message.
+/// Native representation of a Argo A/C message for WREM-2 remote.
union ArgoProtocol {
uint8_t raw[kArgoStateLength]; ///< The state in native IR code form
struct {
@@ -74,15 +78,127 @@ union ArgoProtocol {
};
};
-// Constants. Store MSB left.
+/// Native representation of A/C IR message for WREM-3 remote
+/// @note The remote sends 4 different IR command types, varying in length
+/// and methods of checksum calculation
+/// - [0b00] Regular A/C command (change operation mode) - 6-byte
+/// - [0b01] iFeel Temperature report - 2-byte
+/// - [0b10] Timer command - 9-byte
+/// - [0b11] Config command - 4-byte
+/// @note The 1st 2 structures are unnamed for compat. with @c ArgoProtocol
+/// 1st byte definition is a header common across all commands though
+union ArgoProtocolWREM3 {
+ uint8_t raw[kArgoStateLength]; ///< The state in native IR code form
+ struct {
+ // Byte 0 (same definition across the union)
+ uint8_t Pre1 :4; /// Preamble: 0b1011 @ref kArgoWrem3Preamble
+ uint8_t IrChannel :2; /// 0..3 range
+ uint8_t IrCommandType :2; /// @ref argoIrMessageType_t
+ // Byte 1
+ uint8_t RoomTemp :5; // in Celsius, range: 4..35 (offset by -4[*C])
+ uint8_t Mode :3; /// @ref argoMode_t
+ // Byte 2
+ uint8_t Temp :5; // in Celsius, range: 10..32 (offset by -4[*C])
+ uint8_t Fan :3; /// @ref argoFan_t
+ // Byte3
+ uint8_t Flap :3; /// SwingV @ref argoFlap_t
+ uint8_t Power :1;
+ uint8_t iFeel :1;
+ uint8_t Night :1;
+ uint8_t Eco :1;
+ uint8_t Max :1; ///< a.k.a. Turbo
+ // Byte4
+ uint8_t Filter :1;
+ uint8_t Light :1;
+ uint8_t Post1 :6; /// Unknown, always 0b110000 (TempScale?)
+ // Byte5
+ uint8_t Sum :8; /// Checksum
+ };
+ struct {
+ // Byte 0 (same definition across the union)
+ uint8_t :8; // {Pre1 | IrChannel | IrCommandType}
+ // Byte 1
+ uint8_t SensorT :5; // in Celsius, range: 4..35 (offset by -4[*C])
+ uint8_t CheckHi :3; // Checksum (short)
+ };
+ struct Timer {
+ // Byte 0 (same definition across the union)
+ uint8_t : 8; // {Pre1 | IrChannel | IrCommandType}
+ // Byte 1
+ uint8_t IsOn : 1;
+ uint8_t TimerType : 3;
+ uint8_t CurrentTimeLo : 4;
+ // Byte 2
+ uint8_t CurrentTimeHi : 7;
+ uint8_t CurrentWeekdayLo : 1;
+ // Byte 3
+ uint8_t CurrentWeekdayHi : 2;
+ uint8_t DelayTimeLo : 6;
+ // Byte 4
+ uint8_t DelayTimeHi : 5;
+ uint8_t TimerStartLo : 3;
+ // Byte 5
+ uint8_t TimerStartHi : 8;
+ // Byte 6
+ uint8_t TimerEndLo : 8;
+ // Byte 7
+ uint8_t TimerEndHi : 3;
+ uint8_t TimerActiveDaysLo : 5; // Bitmap (LSBit is Sunday)
+ // Byte 8
+ uint8_t TimerActiveDaysHi : 2; // Bitmap (LSBit is Sunday)
+ uint8_t Post1 : 1; // Unknown, always 1
+ uint8_t Checksum : 5;
+ } timer;
+ struct Config {
+ uint8_t :8; // Byte 0 {Pre1 | IrChannel | IrCommandType}
+ uint8_t Key :8; // Byte 1
+ uint8_t Value :8; // Byte 2
+ uint8_t Checksum :8; // Byte 3
+ } config;
+};
+
+// Constants (WREM-2). Store MSB left.
+const uint8_t kArgoHeatBit = 0b00100000;
+const uint8_t kArgoPreamble1 = 0b10101100;
+const uint8_t kArgoPreamble2 = 0b11110101;
+const uint8_t kArgoPost = 0b00000010;
+
+// Constants (generic)
+const uint16_t kArgoFrequency = 38000; // Hz
+// Temp
+const uint8_t kArgoTempDelta = 4;
+const uint8_t kArgoMaxRoomTemp = 35; // Celsius
+const uint8_t kArgoMinTemp = 10; // Celsius delta +4
+const uint8_t kArgoMaxTemp = 32; // Celsius
+const uint8_t kArgoMaxChannel = 3;
+
-const uint8_t kArgoHeatBit = 0b00100000;
+/// @brief IR message type (determines the payload part of IR command)
+/// @note Raw values match WREM-3 protocol, but the enum is used in generic
+/// context
+/// @note WREM-3 remote supports all commands separately, whereas
+/// WREM-2 (allegedly) only has the @c AC_CONTROL and @c IFEEL_TEMP_REPORT
+/// (timers are part of @c AC_CONTROL command), and there's no config.
+enum class argoIrMessageType_t : uint8_t {
+ AC_CONTROL = 0b00,
+ IFEEL_TEMP_REPORT = 0b01,
+ TIMER_COMMAND = 0b10, // WREM-3 only (WREM-2 has it under AC_CONTROL)
+ CONFIG_PARAM_SET = 0b11 // WREM-3 only
+};
-const uint8_t kArgoPreamble1 = 0b10101100;
-const uint8_t kArgoPreamble2 = 0b11110101;
-const uint8_t kArgoPost = 0b00000010;
+/// @brief A/C operation mode
+/// @note Raw values match WREM-3 protocol, but the enum is used in generic
+/// context
+enum class argoMode_t : uint8_t {
+ COOL = 0b001,
+ DRY = 0b010,
+ HEAT = 0b011,
+ FAN = 0b100,
+ AUTO = 0b101
+};
-// Mode 0b00111000
+// Raw mode definitions for WREM-2 remote
+// (not wraped into a ns nor enum for backwards-compat.)
const uint8_t kArgoCool = 0b000;
const uint8_t kArgoDry = 0b001;
const uint8_t kArgoAuto = 0b010;
@@ -92,19 +208,42 @@ const uint8_t kArgoHeatAuto = 0b101;
// ?no idea what mode that is
const uint8_t kArgoHeatBlink = 0b110;
-// Fan 0b00011000
+/// @brief Fan speed
+/// @note Raw values match WREM-3 protocol, but the enum is used in generic
+/// context
+enum class argoFan_t : uint8_t {
+ FAN_AUTO = 0b000,
+ FAN_LOWEST = 0b001,
+ FAN_LOWER = 0b010,
+ FAN_LOW = 0b011,
+ FAN_MEDIUM = 0b100,
+ FAN_HIGH = 0b101,
+ FAN_HIGHEST = 0b110
+};
+
+// Raw fan speed definitions for WREM-2 remote
+// (not wraped into a ns nor enum for backwards-compat.)
const uint8_t kArgoFanAuto = 0; // 0b00
const uint8_t kArgoFan1 = 1; // 0b01
const uint8_t kArgoFan2 = 2; // 0b10
const uint8_t kArgoFan3 = 3; // 0b11
-// Temp
-const uint8_t kArgoTempDelta = 4;
-const uint8_t kArgoMaxRoomTemp = 35; // Celsius
-const uint8_t kArgoMinTemp = 10; // Celsius delta +4
-const uint8_t kArgoMaxTemp = 32; // Celsius
+/// @brief Flap position (swing-V)
+/// @note Raw values match WREM-3 protocol, but the enum is used in generic
+/// context
+enum class argoFlap_t : uint8_t {
+ FLAP_AUTO = 0,
+ FLAP_1 = 1, // Highest
+ FLAP_2 = 2,
+ FLAP_3 = 3,
+ FLAP_4 = 4,
+ FLAP_5 = 5,
+ FLAP_6 = 6, // Lowest
+ FLAP_FULL = 7
+};
-// Flap/SwingV
+// Raw Flap/SwingV definitions for WREM-2 remote
+// (not wraped into a ns nor enum for backwards-compat.)
const uint8_t kArgoFlapAuto = 0;
const uint8_t kArgoFlap1 = 1;
const uint8_t kArgoFlap2 = 2;
@@ -138,22 +277,61 @@ const uint8_t kArgoFlapFull = 7;
#define ARGO_FLAP_FULL kArgoFlapFull
-/// Class for handling detailed Argo A/C messages.
-class IRArgoAC {
+/// @brief Timer type to set (for @c argoIrMessageType_t::TIMER_COMMAND)
+/// @note Raw values match WREM-3 protocol
+enum class argoTimerType_t : uint8_t {
+ NO_TIMER = 0b000,
+ DELAY_TIMER = 0b001,
+ SCHEDULE_TIMER_1 = 0b010,
+ SCHEDULE_TIMER_2 = 0b011,
+ SCHEDULE_TIMER_3 = 0b100
+};
+
+/// @brief Day type to set (for @c argoIrMessageType_t::TIMER_COMMAND)
+/// @note Raw values match WREM-3 protocol
+enum class argoWeekday : uint8_t {
+ SUNDAY = 0b000,
+ MONDAY = 0b001,
+ TUESDAY = 0b010,
+ WEDNESDAY = 0b011,
+ THURSDAY = 0b100,
+ FRIDAY = 0b101,
+ SATURDAY = 0b110
+};
+
+
+
+/// @brief Base class for handling *common* support for Argo remote protocols
+/// (functionality is shared across WREM-2 and WREM-3 IR protocols)
+/// @note This class uses static polymorphism and full template specializations
+/// when required, to avoid a performance penalty of doing v-table lookup.
+/// 2 instantiations are forced in impl. file: for @c ArgoProtocol and
+/// @c ArgoProtocolWREM3
+/// @note This class is abstract (though does not declare a pure-virtual fn.
+/// for abovementioned reasons), and instead declares protected c-tor
+/// @tparam ARGO_PROTOCOL_T The Raw device protocol/message used
+template
+class IRArgoACBase {
+#ifndef UNIT_TEST // A less cloggy way of expressing FRIEND_TEST(...)
+
+ protected:
+#else
+
public:
- explicit IRArgoAC(const uint16_t pin, const bool inverted = false,
+#endif
+ explicit IRArgoACBase(const uint16_t pin, const bool inverted = false,
const bool use_modulation = true);
+ public:
#if SEND_ARGO
void send(const uint16_t repeat = kArgoDefaultRepeat);
- void sendSensorTemp(const uint8_t degrees,
- const uint16_t repeat = kArgoDefaultRepeat);
/// Run the calibration to calculate uSec timing offsets for this platform.
/// @return The uSec timing offset needed per modulation of the IR Led.
/// @note This will produce a 65ms IR signal pulse at 38kHz.
/// Only ever needs to be run once per object instantiation, if at all.
int8_t calibrate(void) { return _irsend.calibrate(); }
#endif // SEND_ARGO
+
void begin(void);
void on(void);
void off(void);
@@ -164,16 +342,21 @@ class IRArgoAC {
void setTemp(const uint8_t degrees);
uint8_t getTemp(void) const;
+ void setRoomTemp(const uint8_t degrees);
+ uint8_t getRoomTemp(void) const;
uint8_t getSensorTemp(void) const;
- void setFan(const uint8_t fan);
- uint8_t getFan(void) const;
+ void setFan(const argoFan_t fan);
+ void setFanEx(const argoFan_t fan) { setFan(fan); }
+ argoFan_t getFanEx(void) const; ///< `-Ex` for backw. compat w/ @c IRArgoAC
- void setFlap(const uint8_t flap);
- uint8_t getFlap(void) const;
+ void setFlap(const argoFlap_t flap);
+ void setFlapEx(const argoFlap_t flap) { setFlap(flap); }
+ argoFlap_t getFlapEx(void) const; ///< `-Ex` for backw. compat w/ @c IRArgoAC
- void setMode(const uint8_t mode);
- uint8_t getMode(void) const;
+ void setMode(const argoMode_t mode);
+ void setModeEx(const argoMode_t mode) { setMode(mode); }
+ argoMode_t getModeEx(void) const; ///< `-Ex` for backw. compat w/ @c IRArgoAC
void setMax(const bool on);
bool getMax(void) const;
@@ -184,44 +367,160 @@ class IRArgoAC {
void setiFeel(const bool on);
bool getiFeel(void) const;
- void setTime(void);
- void setRoomTemp(const uint8_t degrees);
- uint8_t getRoomTemp(void) const;
+ void setMessageType(const argoIrMessageType_t msgType);
+ argoIrMessageType_t getMessageType(void) const;
+ static argoIrMessageType_t getMessageType(const uint8_t state[],
+ const uint16_t length);
uint8_t* getRaw(void);
- void setRaw(const uint8_t state[], const uint16_t length = kArgoStateLength);
- static uint8_t calcChecksum(const uint8_t state[],
- const uint16_t length = kArgoStateLength);
- static bool validChecksum(const uint8_t state[],
- const uint16_t length = kArgoStateLength);
- static uint8_t convertMode(const stdAc::opmode_t mode);
- static uint8_t convertFan(const stdAc::fanspeed_t speed);
- static uint8_t convertSwingV(const stdAc::swingv_t position);
- static stdAc::opmode_t toCommonMode(const uint8_t mode);
- static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed);
- stdAc::state_t toCommon(void) const;
- String toString(void) const;
+ uint16_t getRawByteLength() const;
+ static uint16_t getStateLengthForIrMsgType(argoIrMessageType_t type);
+ void setRaw(const uint8_t state[], const uint16_t length);
+
+ static bool validChecksum(const uint8_t state[], const uint16_t length);
+
+ static argoMode_t convertMode(const stdAc::opmode_t mode);
+ static argoFan_t convertFan(const stdAc::fanspeed_t speed);
+ static argoFlap_t convertSwingV(const stdAc::swingv_t position);
+ static argoIrMessageType_t convertCommand(const stdAc::ac_command_t command);
+
+ protected:
+ void _stateReset(ARGO_PROTOCOL_T *state, argoIrMessageType_t messageType
+ = argoIrMessageType_t::AC_CONTROL);
+ void stateReset(argoIrMessageType_t messageType
+ = argoIrMessageType_t::AC_CONTROL);
+ void _checksum(ARGO_PROTOCOL_T *state);
+ void checksum(void);
+ static uint16_t getRawByteLength(const ARGO_PROTOCOL_T& raw,
+ argoIrMessageType_t messageTypeHint = argoIrMessageType_t::AC_CONTROL);
+ static uint8_t calcChecksum(const uint8_t state[], const uint16_t length);
+ static uint8_t getChecksum(const uint8_t state[], const uint16_t length);
+
+ static stdAc::opmode_t toCommonMode(const argoMode_t mode);
+ static stdAc::fanspeed_t toCommonFanSpeed(const argoFan_t speed);
+ static stdAc::swingv_t toCommonSwingV(const uint8_t position);
+ static stdAc::ac_command_t toCommonCommand(const argoIrMessageType_t command);
+
+ // Attributes
+ ARGO_PROTOCOL_T _; ///< The raw protocol data
+ uint16_t _length = kArgoStateLength;
+ argoIrMessageType_t _messageType = argoIrMessageType_t::AC_CONTROL;
+
#ifndef UNIT_TEST
- private:
+ protected:
IRsend _irsend; ///< instance of the IR send class
#else
+
+ public:
/// @cond IGNORE
IRsendTest _irsend; ///< instance of the testing IR send class
/// @endcond
#endif
- // # of bytes per command
- ArgoProtocol _;
- void _stateReset(ArgoProtocol *state);
- void stateReset(void);
- void _checksum(ArgoProtocol *state);
- void checksum(void);
+};
- // Attributes
- uint8_t flap_mode;
- uint8_t heat_mode;
- uint8_t cool_mode;
- uint16_t _length = kArgoStateLength;
+/// @brief Supports Argo A/C SAC-WREM2 IR remote protocol
+class IRArgoAC : public IRArgoACBase {
+ public:
+ explicit IRArgoAC(const uint16_t pin, const bool inverted = false,
+ const bool use_modulation = true);
+
+ #if SEND_ARGO
+ void sendSensorTemp(const uint8_t degrees,
+ const uint16_t repeat = kArgoDefaultRepeat);
+ #endif // SEND_ARGO
+
+ String toString(void) const;
+ stdAc::state_t toCommon(void) const;
+
+ using IRArgoACBase::setMode;
+ void setMode(const uint8_t mode); /// @deprecated, for backwards-compat.
+ uint8_t getMode(void) const; /// @deprecated, for backwards-compat.
+
+ using IRArgoACBase::setFan;
+ void setFan(const uint8_t fan); /// @deprecated, for backwards-compat.
+ uint8_t getFan(void) const; /// @deprecated, for backwards-compat.
+
+ using IRArgoACBase::setFlap;
+ void setFlap(const uint8_t flap); /// @deprecated, for backwards-compat.
+ uint8_t getFlap(void) const; /// @deprecated, for backwards-compat.
+
+ private:
+ // Attributes
+ uint8_t flap_mode; ///< Unused, remove(?)
+};
+
+/// @brief Supports Argo A/C SAC-WREM3 IR remote protocol
+class IRArgoAC_WREM3 : public IRArgoACBase {
+ public:
+ explicit IRArgoAC_WREM3(const uint16_t pin, const bool inverted = false,
+ const bool use_modulation = true);
+
+ #if SEND_ARGO
+ void sendSensorTemp(const uint8_t degrees,
+ const uint16_t repeat = kArgoDefaultRepeat);
+ #endif // SEND_ARGO
+
+ argo_ac_remote_model_t getModel(void) const;
+
+
+ argoFan_t getFan(void) const;
+ argoFlap_t getFlap(void) const;
+ argoMode_t getMode(void) const;
+
+ void setEco(const bool on);
+ bool getEco(void) const;
+
+ void setFilter(const bool on);
+ bool getFilter(void) const;
+
+ void setLight(const bool on);
+ bool getLight(void) const;
+
+ void setChannel(const uint8_t channel);
+ uint8_t getChannel(void) const;
+
+ void setConfigEntry(const uint8_t paramId, const uint8_t value);
+ std::pair getConfigEntry(void) const;
+
+ void setCurrentTimeMinutes(uint16_t currentTimeMinutes);
+ uint16_t getCurrentTimeMinutes(void) const;
+
+ void setCurrentDayOfWeek(argoWeekday dayOfWeek);
+ argoWeekday getCurrentDayOfWeek(void) const;
+
+ void setTimerType(const argoTimerType_t timerType);
+ argoTimerType_t getTimerType(void) const;
+
+ void setDelayTimerMinutes(const uint16_t delayMinutes);
+ uint16_t getDelayTimerMinutes(void) const;
+
+ void setScheduleTimerStartMinutes(const uint16_t startTimeMinutes);
+ uint16_t getScheduleTimerStartMinutes(void) const;
+ // uint16_t getTimerXStartMinutes(void) const
+
+ void setScheduleTimerStopMinutes(const uint16_t stopTimeMinutes);
+ uint16_t getScheduleTimerStopMinutes(void) const;
+ // uint16_t getTimerXStopMinutes(void) const;
+
+
+ void setScheduleTimerActiveDays(const std::set& days);
+ std::set getScheduleTimerActiveDays(void) const;
+ uint8_t getTimerActiveDaysBitmap(void) const;
+
+ using IRArgoACBase::getMessageType;
+ static argoIrMessageType_t getMessageType(const ArgoProtocolWREM3& raw);
+
+ String toString(void) const;
+ stdAc::state_t toCommon(void) const;
+
+ static bool hasValidPreamble(const uint8_t state[], const uint16_t length);
+
+ public:
+#if DECODE_ARGO
+ static bool isValidWrem3Message(const uint8_t state[], const uint16_t nbits,
+ bool strict);
+#endif
};
#endif // IR_ARGO_H_
diff --git a/src/locale/defaults.h b/src/locale/defaults.h
index d60b9b24c..2e141f4fc 100644
--- a/src/locale/defaults.h
+++ b/src/locale/defaults.h
@@ -378,6 +378,9 @@ D_STR_INDIRECT " " D_STR_MODE
#ifndef D_STR_MEDIUM
#define D_STR_MEDIUM "Medium"
#endif // D_STR_MEDIUM
+#ifndef D_STR_MED_HIGH
+#define D_STR_MED_HIGH "Med-high"
+#endif // D_STR_MEDIUM
#ifndef D_STR_HIGHEST
#define D_STR_HIGHEST "Highest"
@@ -445,6 +448,39 @@ D_STR_INDIRECT " " D_STR_MODE
#ifndef D_STR_BOTTOM
#define D_STR_BOTTOM "Bottom"
#endif // D_STR_BOTTOM
+#ifndef D_STR_UPPER_MIDDLE
+#define D_STR_UPPER_MIDDLE "UpperMiddle"
+#endif // D_STR_UPPER_MIDDLE
+#ifndef D_STR_AC_COMMAND
+#define D_STR_AC_COMMAND "A/C Config"
+#endif // D_STR_AC_COMMAND
+#ifndef D_STR_AC_CONTROL
+#define D_STR_AC_CONTROL "A/C Control"
+#endif // D_STR_AC_CONTROL
+#ifndef D_STR_AC_TEMP_REPORT
+#define D_STR_AC_TEMP_REPORT "A/C Temp Report"
+#endif // D_STR_AC_TEMP_REPORT
+#ifndef D_STR_AC_TIMER
+#define D_STR_AC_TIMER "A/C Set Timer"
+#endif // D_STR_AC_TIMER
+#ifndef D_STR_COMMAND_TYPE
+#define D_STR_COMMAND_TYPE "A/C Command"
+#endif // D_STR_COMMAND_TYPE
+#ifndef D_STR_SCHEDULE
+#define D_STR_SCHEDULE "Schedule"
+#endif // D_STR_SCHEDULE
+#ifndef D_STR_CH
+#define D_STR_CH "CH#"
+#endif // D_STR_CH
+#ifndef D_STR_TIMER_ACTIVE_DAYS
+#define D_STR_TIMER_ACTIVE_DAYS "TimerActiveDays"
+#endif // D_STR_TIMER_ACTIVE_DAYS
+#ifndef D_STR_KEY
+#define D_STR_KEY "Key"
+#endif // D_STR_KEY
+#ifndef D_STR_VALUE
+#define D_STR_VALUE "Value"
+#endif // D_STR_VALUE
// Compound words/phrases/descriptions from pre-defined words.
// Note: Obviously these need to be defined *after* their component words.
@@ -689,6 +725,12 @@ D_STR_INDIRECT " " D_STR_MODE
#ifndef D_STR_DG11J191
#define D_STR_DG11J191 "DG11J191"
#endif // D_STR_DG11J191
+#ifndef D_STR_ARGO_WREM2
+#define D_STR_ARGO_WREM2 "WREM2"
+#endif // D_STR_ARGO_WREM2
+#ifndef D_STR_ARGO_WREM3
+#define D_STR_ARGO_WREM3 "WREM3"
+#endif // D_STR_ARGO_WREM3
// Protocols Names
#ifndef D_STR_AIRTON
diff --git a/test/ir_Argo_test.cpp b/test/ir_Argo_test.cpp
index cb4c24682..52fcac2a3 100644
--- a/test/ir_Argo_test.cpp
+++ b/test/ir_Argo_test.cpp
@@ -1,13 +1,19 @@
// Copyright 2019 David Conran
+// Copyright 2022 Mateusz Bronk (mbronk)
#include
#include
+#include
#include "ir_Argo.h"
#include "IRac.h"
#include "IRrecv.h"
#include "IRrecv_test.h"
#include "IRsend.h"
#include "IRsend_test.h"
+#include "./ut_utils.h"
+/******************************************************************************/
+/* Tests for toCommon() */
+/******************************************************************************/
TEST(TestArgoACClass, toCommon) {
IRArgoAC ac(kGpioUnused);
@@ -19,6 +25,7 @@ TEST(TestArgoACClass, toCommon) {
ac.setNight(true);
// Now test it.
ASSERT_EQ(decode_type_t::ARGO, ac.toCommon().protocol);
+ ASSERT_EQ(stdAc::ac_command_t::kControlCommand, ac.toCommon().command);
ASSERT_TRUE(ac.toCommon().power);
ASSERT_TRUE(ac.toCommon().celsius);
ASSERT_EQ(20, ac.toCommon().degrees);
@@ -26,8 +33,8 @@ TEST(TestArgoACClass, toCommon) {
ASSERT_EQ(stdAc::fanspeed_t::kMax, ac.toCommon().fanspeed);
ASSERT_EQ(0, ac.toCommon().sleep);
ASSERT_TRUE(ac.toCommon().turbo);
+ ASSERT_EQ(argo_ac_remote_model_t::SAC_WREM2, ac.toCommon().model);
// Unsupported.
- ASSERT_EQ(-1, ac.toCommon().model);
ASSERT_EQ(stdAc::swingv_t::kOff, ac.toCommon().swingv);
ASSERT_EQ(stdAc::swingh_t::kOff, ac.toCommon().swingh);
ASSERT_FALSE(ac.toCommon().econo);
@@ -37,8 +44,51 @@ TEST(TestArgoACClass, toCommon) {
ASSERT_FALSE(ac.toCommon().beep);
ASSERT_FALSE(ac.toCommon().quiet);
ASSERT_EQ(-1, ac.toCommon().clock);
+ ASSERT_FALSE(ac.toCommon().iFeel);
+ ASSERT_EQ(25, ac.toCommon().roomTemperature);
}
+TEST(TestArgoAC_WREM3Class, toCommon) {
+ IRArgoAC_WREM3 ac(kGpioUnused);
+ ac.setPower(true);
+ ac.setMode(argoMode_t::COOL);
+ ac.setTemp(21);
+ ac.setFan(argoFan_t::FAN_HIGHEST);
+ ac.setMax(true);
+ ac.setNight(true);
+ ac.setFlap(argoFlap_t::FLAP_4);
+ ac.setEco(true);
+ ac.setFilter(true);
+ ac.setLight(true);
+ ac.setiFeel(true);
+ // Now test it.
+ ASSERT_EQ(decode_type_t::ARGO, ac.toCommon().protocol);
+ ASSERT_EQ(argo_ac_remote_model_t::SAC_WREM3, ac.toCommon().model);
+ ASSERT_EQ(stdAc::ac_command_t::kControlCommand, ac.toCommon().command);
+ ASSERT_TRUE(ac.toCommon().celsius);
+ ASSERT_TRUE(ac.toCommon().beep); // Always on (except for iFeel)
+ ASSERT_FALSE(ac.toCommon().clean);
+ ASSERT_EQ(-1, ac.toCommon().clock);
+ ASSERT_EQ(0, ac.toCommon().sleep);
+ ASSERT_EQ(stdAc::swingh_t::kOff, ac.toCommon().swingh);
+ ASSERT_TRUE(ac.toCommon().power);
+ ASSERT_EQ(stdAc::opmode_t::kCool, ac.toCommon().mode);
+ ASSERT_EQ(21, ac.toCommon().degrees);
+ ASSERT_EQ(stdAc::fanspeed_t::kMax, ac.toCommon().fanspeed);
+ ASSERT_TRUE(ac.toCommon().turbo);
+ ASSERT_TRUE(ac.toCommon().quiet); // Night
+ ASSERT_EQ(stdAc::swingv_t::kUpperMiddle, ac.toCommon().swingv);
+ ASSERT_TRUE(ac.toCommon().econo);
+ ASSERT_TRUE(ac.toCommon().light);
+ ASSERT_TRUE(ac.toCommon().filter);
+ ASSERT_TRUE(ac.toCommon().iFeel);
+ ASSERT_EQ(25, ac.toCommon().roomTemperature);
+}
+
+/******************************************************************************/
+/* Tests of message construction */
+/******************************************************************************/
+
TEST(TestArgoACClass, MessageConstructon) {
IRArgoAC ac(kGpioUnused);
ac.setPower(true);
@@ -57,12 +107,166 @@ TEST(TestArgoACClass, MessageConstructon) {
EXPECT_THAT(std::vector(actual, actual + kArgoBits / 8),
::testing::ElementsAreArray(expected));
EXPECT_EQ(
- "Power: On, Mode: 0 (Cool), Fan: 0 (Auto), Temp: 20C, Room Temp: 21C, "
- "Max: On, IFeel: On, Night: On",
+ "Model: 1 (WREM2), Power: On, Mode: 0 (Cool), Fan: 0 (Auto), Temp: 20C, "
+ "Room Temp: 21C, Max: On, IFeel: On, Night: On",
+ ac.toString());
+}
+
+TEST(TestArgoAC_WREM3Class, MessageConstructon_ACControl) {
+ IRArgoAC_WREM3 ac(kGpioUnused);
+ ac.setChannel(0);
+ ac.setPower(true);
+ ac.setMode(argoMode_t::COOL);
+ ac.setTemp(22);
+ ac.setRoomTemp(26);
+ ac.setFan(argoFan_t::FAN_AUTO);
+ ac.setFlap(argoFlap_t::FLAP_FULL);
+ ac.setiFeel(false);
+ ac.setNight(false);
+ ac.setEco(false);
+ ac.setMax(false);
+ ac.setFilter(false);
+ ac.setLight(true);
+ auto expected = std::vector({
+ 0x0B, 0x36, 0x12, 0x0F, 0xC2, 0x24});
+ auto actual = ac.getRaw();
+ ASSERT_EQ(ac.getRawByteLength(), kArgo3AcControlStateLength);
+ EXPECT_THAT(std::vector(actual, actual + ac.getRawByteLength()),
+ ::testing::ElementsAreArray(expected));
+ EXPECT_EQ(
+ "Command[CH#0]: Model: 2 (WREM3), Power: On, Mode: 1 (Cool), Temp: 22C, "
+ "Room: 26C, Fan: 0 (Auto), Swing(V): 7 (Breeze), IFeel: Off, Night: Off, "
+ "Econo: Off, Max: Off, Filter: Off, Light: On",
+ ac.toString());
+}
+
+TEST(TestArgoAC_WREM3Class, MessageConstructon_ACControl_2) {
+ IRArgoAC_WREM3 ac(kGpioUnused);
+ ac.setChannel(2);
+ ac.setPower(true);
+ ac.setMode(argoMode_t::AUTO);
+ ac.setTemp(23);
+ ac.setRoomTemp(28);
+ ac.setFan(argoFan_t::FAN_LOWER);
+ ac.setMax(true);
+ ac.setNight(true);
+ ac.setFlap(argoFlap_t::FLAP_4);
+ ac.setEco(true);
+ ac.setFilter(true);
+ ac.setLight(true);
+ ac.setiFeel(true);
+ auto expected = std::vector({
+ 0x2B, 0xB8, 0x53, 0xFC, 0xC3, 0xF5});
+ auto actual = ac.getRaw();
+ ASSERT_EQ(ac.getRawByteLength(), kArgo3AcControlStateLength);
+ EXPECT_THAT(std::vector(actual, actual + ac.getRawByteLength()),
+ ::testing::ElementsAreArray(expected));
+ EXPECT_EQ(
+ "Command[CH#2]: Model: 2 (WREM3), Power: On, Mode: 5 (Auto), Temp: 23C, "
+ "Room: 28C, Fan: 2 (Low), Swing(V): 4 (Middle), IFeel: On, Night: On,"
+ " Econo: On, Max: On, Filter: On, Light: On",
+ ac.toString());
+}
+
+TEST(TestArgoAC_WREM3Class, MessageConstructon_iFeelReport) {
+ IRArgoAC_WREM3 ac(kGpioUnused);
+ ac.setMessageType(argoIrMessageType_t::IFEEL_TEMP_REPORT);
+ ac.setRoomTemp(31);
+
+ auto expected = std::vector({0x4B, 0xDB});
+ auto actual = ac.getRaw();
+ ASSERT_EQ(ac.getRawByteLength(), kArgo3iFeelReportStateLength);
+ EXPECT_THAT(std::vector(actual, actual + ac.getRawByteLength()),
+ ::testing::ElementsAreArray(expected));
+ EXPECT_EQ(
+ "Sensor Temp[CH#0]: Model: 2 (WREM3), Room: 31C",
+ ac.toString());
+}
+
+TEST(TestArgoAC_WREM3Class, MessageConstructon_Config) {
+ IRArgoAC_WREM3 ac(kGpioUnused);
+ ac.setMessageType(argoIrMessageType_t::CONFIG_PARAM_SET);
+ ac.setConfigEntry(6, 30);
+
+ auto expected = std::vector({0xCB, 0x06, 0x1E, 0xEF});
+ auto actual = ac.getRaw();
+ ASSERT_EQ(ac.getRawByteLength(), kArgo3ConfigStateLength);
+ EXPECT_THAT(std::vector(actual, actual + ac.getRawByteLength()),
+ ::testing::ElementsAreArray(expected));
+ EXPECT_EQ(
+ "A/C Config[CH#0]: Model: 2 (WREM3), Key: 6, Value: 30",
+ ac.toString());
+}
+
+TEST(TestArgoAC_WREM3Class, MessageConstructon_NoTimer) {
+ IRArgoAC_WREM3 ac(kGpioUnused);
+ ac.setMessageType(argoIrMessageType_t::TIMER_COMMAND);
+ ac.off();
+ ac.setCurrentTimeMinutes(1*60+59);
+ ac.setCurrentDayOfWeek(argoWeekday::MONDAY);
+ ac.setTimerType(argoTimerType_t::NO_TIMER);
+
+ auto expected = std::vector({0x8B, 0x70, 0x87, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x34});
+ auto actual = ac.getRaw();
+ ASSERT_EQ(ac.getRawByteLength(), kArgo3TimerStateLength);
+ EXPECT_THAT(std::vector(actual, actual + ac.getRawByteLength()),
+ ::testing::ElementsAreArray(expected));
+ EXPECT_EQ(
+ "Timer[CH#0]: Model: 2 (WREM3), Power: Off, Timer Mode: 0 (Off), "
+ "Clock: 01:59, Day: 1 (Mon), Timer: Off",
+ ac.toString());
+}
+
+TEST(TestArgoAC_WREM3Class, MessageConstructon_DelayTimer) {
+ IRArgoAC_WREM3 ac(kGpioUnused);
+ ac.setMessageType(argoIrMessageType_t::TIMER_COMMAND);
+ ac.off();
+ ac.setCurrentTimeMinutes(12*60+00);
+ ac.setCurrentDayOfWeek(argoWeekday::SATURDAY);
+ ac.setTimerType(argoTimerType_t::DELAY_TIMER);
+ ac.setDelayTimerMinutes(9*60+40);
+
+ auto expected = std::vector({0x8B, 0x02, 0x2D, 0x13, 0x09, 0x00,
+ 0x00, 0x00, 0xD4});
+ auto actual = ac.getRaw();
+ ASSERT_EQ(ac.getRawByteLength(), kArgo3TimerStateLength);
+ EXPECT_THAT(std::vector(actual, actual + ac.getRawByteLength()),
+ ::testing::ElementsAreArray(expected));
+ EXPECT_EQ(
+ "Timer[CH#0]: Model: 2 (WREM3), Power: Off, Timer Mode: 1 (Sleep Timer),"
+ " Clock: 12:00, Day: 6 (Sat), Timer: 09:40",
+ ac.toString());
+}
+
+TEST(TestArgoAC_WREM3Class, MessageConstructon_ScheduleTimer) {
+ IRArgoAC_WREM3 ac(kGpioUnused);
+ ac.setMessageType(argoIrMessageType_t::TIMER_COMMAND);
+ ac.on();
+ ac.setCurrentTimeMinutes(18*60+16);
+ ac.setCurrentDayOfWeek(argoWeekday::SATURDAY);
+ ac.setTimerType(argoTimerType_t::SCHEDULE_TIMER_3);
+ ac.setScheduleTimerStartMinutes(8*60+40);
+ ac.setScheduleTimerStopMinutes(19*60+50);
+ ac.setScheduleTimerActiveDays({argoWeekday::MONDAY, argoWeekday::SATURDAY,
+ argoWeekday::SUNDAY});
+
+ auto expected = std::vector({0x8B, 0x89, 0x44, 0x03, 0x00, 0x41,
+ 0xA6, 0x1C, 0x26});
+ auto actual = ac.getRaw();
+ ASSERT_EQ(ac.getRawByteLength(), kArgo3TimerStateLength);
+ EXPECT_THAT(std::vector(actual, actual + ac.getRawByteLength()),
+ ::testing::ElementsAreArray(expected));
+ EXPECT_EQ(
+ "Timer[CH#0]: Model: 2 (WREM3), Power: On, Timer Mode: 4 (Schedule3),"
+ " Clock: 18:16, Day: 6 (Sat), On Timer: 08:40, Off Timer: 19:50, "
+ "TimerActiveDays: Sun|Mon|Sat",
ac.toString());
}
-// Tests for sendArgo().
+/******************************************************************************/
+/* Tests for sendArgo(). */
+/******************************************************************************/
// Test sending typical data only.
TEST(TestSendArgo, SendDataOnly) {
@@ -91,9 +295,36 @@ TEST(TestSendArgo, SendDataOnly) {
irsend.outputStr());
}
-// Tests for decodeArgo().
-// Decode normal Argo messages.
+TEST(TestSendArgoWrem3, SendDataOnly) {
+ IRsendTest irsend(0);
+ irsend.begin();
+ uint8_t data[kArgoStateLength] = {
+ 0x0B, 0x31, 0x35, 0xFE, 0xC0, 0x2F};
+ irsend.sendArgoWREM3(data);
+ EXPECT_EQ(
+ "f38000d50"
+ "m6400s3300"
+ "m400s2200m400s2200m400s900m400s2200m400s900m400s900m400s900m400s900"
+ "m400s2200m400s900m400s900m400s900m400s2200m400s2200m400s900m400s900"
+ "m400s2200m400s900m400s2200m400s900m400s2200m400s2200m400s900m400s900"
+ "m400s900m400s2200m400s2200m400s2200m400s2200m400s2200m400s2200m400s2200"
+ "m400s900m400s900m400s900m400s900m400s900m400s900m400s2200m400s2200"
+ "m400s2200m400s2200m400s2200m400s2200m400s900m400s2200m400s900m400s900"
+ "m400s900m400s900m400s900m400s900m400s900m400s900m400s900m400s900m400s900"
+ "m400s900m400s900m400s900m400s900m400s900m400s900m400s900m400s900m400s900"
+ "m400s900m400s900m400s900m400s900m400s900m400s900m400s900m400s900m400s900"
+ "m400s900m400s900m400s900m400s900m400s900m400s900m400s900m400s900m400s900"
+ "m400s900m400s900m400s900m400s900m400s900m400s900m400s900m400s900m400s900"
+ "m400s900m400s900m400s900m400s100000",
+ irsend.outputStr());
+}
+
+/******************************************************************************/
+/* Tests for decodeArgo(). */
+/******************************************************************************/
+
+// Decode normal Argo messages.
TEST(TestDecodeArgo, SyntheticDecode) {
IRsendTest irsend(kGpioUnused);
IRrecv irrecv(kGpioUnused);
@@ -110,15 +341,141 @@ TEST(TestDecodeArgo, SyntheticDecode) {
EXPECT_EQ(kArgoBits, irsend.capture.bits);
EXPECT_STATE_EQ(expectedState, irsend.capture.state, irsend.capture.bits);
EXPECT_EQ(
- "Power: On, Mode: 0 (Cool), Fan: 0 (Auto), Temp: 20C, Room Temp: 21C, "
- "Max: On, IFeel: On, Night: On",
+ "Model: 1 (WREM2), Power: On, Mode: 0 (Cool), Fan: 0 (Auto), Temp: 20C, "
+ "Room Temp: 21C, Max: On, IFeel: On, Night: On",
IRAcUtils::resultAcToString(&irsend.capture));
stdAc::state_t r, p;
ASSERT_TRUE(IRAcUtils::decodeToState(&irsend.capture, &r, &p));
}
-TEST(TestArgoACClass, SetAndGetTemp) {
- IRArgoAC ac(kGpioUnused);
+// Synthetic send and decode ***via common*** interface
+TEST(TestIrAc, ArgoWrem2_SyntheticSendAndDecode_ACCommand) {
+ IRac irac(kGpioUnused);
+ auto capture = std::make_shared(kGpioUnused);
+ irac._utReceiver = capture;
+
+ stdAc::state_t state = {};
+ state.protocol = ARGO;
+ state.model = 1;
+ state.power = true;
+ state.mode = stdAc::opmode_t::kCool;
+
+ irac.sendAc(state, nullptr);
+ ASSERT_NE(nullptr, irac._lastDecodeResults);
+ EXPECT_EQ(ARGO, irac._lastDecodeResults->decode_type);
+ EXPECT_EQ("Model: 1 (WREM2), Power: On, Mode: 0 (Cool), Fan: 0 (Auto), "
+ "Temp: 25C, Room Temp: 25C, Max: Off, IFeel: Off, Night: Off",
+ IRAcUtils::resultAcToString(irac._lastDecodeResults.get()));
+
+ stdAc::state_t r = {};
+ ASSERT_TRUE(IRAcUtils::decodeToState(irac._lastDecodeResults.get(), &r,
+ nullptr));
+ EXPECT_EQ(ARGO, r.protocol);
+ EXPECT_EQ(1, r.model);
+ EXPECT_TRUE(r.power);
+ EXPECT_EQ(state.mode, r.mode);
+}
+
+TEST(TestIrAc, ArgoWrem3_SyntheticSendAndDecode_ACCommand) {
+ IRac irac(kGpioUnused);
+ auto capture = std::make_shared(kGpioUnused);
+ irac._utReceiver = capture;
+
+ stdAc::state_t state = {};
+ state.protocol = ARGO;
+ state.model = argo_ac_remote_model_t::SAC_WREM3;
+ state.power = true;
+ state.mode = stdAc::opmode_t::kCool;
+
+ irac.sendAc(state, nullptr);
+ ASSERT_NE(nullptr, irac._lastDecodeResults);
+ EXPECT_EQ(ARGO, irac._lastDecodeResults->decode_type);
+ EXPECT_EQ("Command[CH#0]: Model: 2 (WREM3), Power: On, Mode: 1 (Cool),"
+ " Temp: 25C, Room: 25C, Fan: 0 (Auto), Swing(V): 7 (Breeze), "
+ "IFeel: Off, Night: Off, Econo: Off, Max: Off, Filter: Off, "
+ "Light: Off",
+ IRAcUtils::resultAcToString(irac._lastDecodeResults.get()));
+
+ stdAc::state_t r = {};
+ ASSERT_TRUE(IRAcUtils::decodeToState(irac._lastDecodeResults.get(), &r,
+ nullptr));
+ EXPECT_EQ(ARGO, r.protocol);
+ EXPECT_EQ(state.model, r.model);
+ EXPECT_EQ(state.power, r.power);
+}
+
+TEST(TestIrAc, ArgoWrem3_SyntheticSendAndDecode_iFeelReport) {
+ IRac irac(kGpioUnused);
+ auto capture = std::make_shared(kGpioUnused);
+ irac._utReceiver = capture;
+
+ stdAc::state_t state = {};
+ state.protocol = ARGO;
+ state.model = argo_ac_remote_model_t::SAC_WREM3;
+ state.command = stdAc::ac_command_t::kTemperatureReport;
+ state.roomTemperature = 19;
+
+ irac.sendAc(state, nullptr);
+
+ ASSERT_NE(nullptr, irac._lastDecodeResults);
+ EXPECT_EQ(ARGO, irac._lastDecodeResults->decode_type);
+ EXPECT_EQ("Sensor Temp[CH#0]: Model: 2 (WREM3), Room: 19C",
+ IRAcUtils::resultAcToString(irac._lastDecodeResults.get()));
+
+ stdAc::state_t r = {};
+ ASSERT_TRUE(IRAcUtils::decodeToState(irac._lastDecodeResults.get(), &r,
+ nullptr));
+ EXPECT_EQ(ARGO, r.protocol);
+ EXPECT_EQ(state.model, r.model);
+ EXPECT_EQ(state.command, r.command);
+ EXPECT_EQ(state.roomTemperature, r.roomTemperature);
+}
+
+TEST(TestIrAc, ArgoWrem3_SyntheticSendAndDecode_Timer) {
+ IRac irac(kGpioUnused);
+ auto capture = std::make_shared(kGpioUnused);
+ irac._utReceiver = capture;
+
+ stdAc::state_t state = {};
+ state.protocol = ARGO;
+ state.model = argo_ac_remote_model_t::SAC_WREM3;
+ state.command = stdAc::ac_command_t::kTimerCommand;
+ state.power = true;
+ state.mode = stdAc::opmode_t::kAuto; // Needs to be set for `state.power`
+ // not to be ignored!
+ state.clock = 13*60+21;
+ state.sleep = 2*60+10;
+
+ irac.sendAc(state, nullptr);
+
+ ASSERT_NE(nullptr, irac._lastDecodeResults);
+ EXPECT_EQ(ARGO, irac._lastDecodeResults->decode_type);
+ EXPECT_EQ("Timer[CH#0]: Model: 2 (WREM3), Power: On, Timer Mode: 1 "
+ "(Sleep Timer), Clock: 13:21, Day: 0 (Sun), Timer: 02:10",
+ IRAcUtils::resultAcToString(irac._lastDecodeResults.get()));
+
+ stdAc::state_t r = {};
+ ASSERT_TRUE(IRAcUtils::decodeToState(irac._lastDecodeResults.get(), &r,
+ nullptr));
+ EXPECT_EQ(ARGO, r.protocol);
+ EXPECT_EQ(state.model, r.model);
+ EXPECT_EQ(state.command, r.command);
+ EXPECT_EQ(state.power, r.power);
+ EXPECT_EQ(state.clock, r.clock);
+ EXPECT_EQ(state.sleep, r.sleep);
+}
+
+/******************************************************************************/
+/* Tests for IRArgoACBase (comon functionality across WREM2 and WREM3) */
+/******************************************************************************/
+
+using IRArgoACBase_typelist = ::testing::Types;
+template struct TestArgoACBaseClass : public testing::Test {};
+TYPED_TEST_CASE(TestArgoACBaseClass, IRArgoACBase_typelist);
+
+
+TYPED_TEST(TestArgoACBaseClass, SetAndGetTemp) {
+ IRArgoACBase ac(kGpioUnused);
ac.setTemp(25);
EXPECT_EQ(25, ac.getTemp());
@@ -132,59 +489,117 @@ TEST(TestArgoACClass, SetAndGetTemp) {
EXPECT_EQ(kArgoMaxTemp, ac.getTemp());
}
-TEST(TestArgoACClass, SetAndGetRoomTemp) {
- IRArgoAC ac(kGpioUnused);
+TYPED_TEST(TestArgoACBaseClass, SetAndGetRoomTemp) {
+ IRArgoACBase ac(kGpioUnused);
+ // Room Temp from AC command
ac.setRoomTemp(25);
EXPECT_EQ(25, ac.getRoomTemp());
+ EXPECT_EQ(0, ac.getSensorTemp());
+ ac.setRoomTemp(kArgoTempDelta);
+ EXPECT_EQ(kArgoTempDelta, ac.getRoomTemp());
+ EXPECT_EQ(0, ac.getSensorTemp());
+ ac.setRoomTemp(kArgoMaxRoomTemp);
+ EXPECT_EQ(kArgoMaxRoomTemp, ac.getRoomTemp());
+ EXPECT_EQ(0, ac.getSensorTemp());
+ ac.setRoomTemp(kArgoTempDelta - 1);
+ EXPECT_EQ(kArgoTempDelta, ac.getRoomTemp());
+ EXPECT_EQ(0, ac.getSensorTemp());
+ ac.setRoomTemp(kArgoMaxRoomTemp + 1);
+ EXPECT_EQ(kArgoMaxRoomTemp, ac.getRoomTemp());
+ EXPECT_EQ(0, ac.getSensorTemp());
+
+ // Room temp from iFeel coommand
+ ac.setMessageType(argoIrMessageType_t::IFEEL_TEMP_REPORT); // reset
+ EXPECT_EQ(kArgoTempDelta, ac.getRoomTemp());
+ ac.setRoomTemp(19);
+ EXPECT_EQ(19, ac.getRoomTemp());
+ EXPECT_EQ(19, ac.getSensorTemp());
ac.setRoomTemp(kArgoTempDelta);
EXPECT_EQ(kArgoTempDelta, ac.getRoomTemp());
+ EXPECT_EQ(kArgoTempDelta, ac.getSensorTemp());
ac.setRoomTemp(kArgoMaxRoomTemp);
EXPECT_EQ(kArgoMaxRoomTemp, ac.getRoomTemp());
+ EXPECT_EQ(kArgoMaxRoomTemp, ac.getSensorTemp());
ac.setRoomTemp(kArgoTempDelta - 1);
EXPECT_EQ(kArgoTempDelta, ac.getRoomTemp());
+ EXPECT_EQ(kArgoTempDelta, ac.getSensorTemp());
ac.setRoomTemp(kArgoMaxRoomTemp + 1);
EXPECT_EQ(kArgoMaxRoomTemp, ac.getRoomTemp());
+ EXPECT_EQ(kArgoMaxRoomTemp, ac.getSensorTemp());
}
-TEST(TestArgoACClass, SetAndGetMode) {
- IRArgoAC ac(kGpioUnused);
+TYPED_TEST(TestArgoACBaseClass, SetAndGetModeEx) {
+ IRArgoACBase ac(kGpioUnused);
- ac.setMode(kArgoHeat);
- EXPECT_EQ(kArgoHeat, ac.getMode());
- ac.setMode(kArgoCool);
- EXPECT_EQ(kArgoCool, ac.getMode());
- ac.setMode(kArgoDry);
- EXPECT_EQ(kArgoDry, ac.getMode());
- ac.setMode(kArgoAuto);
- EXPECT_EQ(kArgoAuto, ac.getMode());
- ac.setMode(kArgoHeatAuto);
- EXPECT_EQ(kArgoHeatAuto, ac.getMode());
- ac.setMode(kArgoOff);
- EXPECT_EQ(kArgoOff, ac.getMode());
- ac.setMode(255);
- EXPECT_EQ(kArgoAuto, ac.getMode());
+ ac.setMode(argoMode_t::HEAT);
+ EXPECT_EQ(argoMode_t::HEAT, ac.getModeEx());
+ ac.setMode(argoMode_t::AUTO);
+ EXPECT_EQ(argoMode_t::AUTO, ac.getModeEx());
+ ac.setMode(argoMode_t::COOL);
+ EXPECT_EQ(argoMode_t::COOL, ac.getModeEx());
+ ac.setMode(argoMode_t::DRY);
+ EXPECT_EQ(argoMode_t::DRY, ac.getModeEx());
+ ac.setMode(argoMode_t::FAN);
+ EXPECT_EQ(argoMode_t::FAN, ac.getModeEx());
+ ac.setMode(static_cast(255));
+ EXPECT_EQ(argoMode_t::AUTO, ac.getModeEx());
}
-TEST(TestArgoACClass, SetAndGetFan) {
- IRArgoAC ac(kGpioUnused);
+TYPED_TEST(TestArgoACBaseClass, SetAndGetFanEx) {
+ IRArgoACBase ac(kGpioUnused);
- ac.setFan(kArgoFan3);
- EXPECT_EQ(kArgoFan3, ac.getFan());
- ac.setFan(kArgoFan1);
- EXPECT_EQ(kArgoFan1, ac.getFan());
- ac.setFan(kArgoFanAuto);
- EXPECT_EQ(kArgoFanAuto, ac.getFan());
- ac.setFan(kArgoFan3);
- EXPECT_EQ(kArgoFan3, ac.getFan());
- ASSERT_NE(7, kArgoFan3);
- // Now try some unexpected value.
- ac.setFan(7);
- EXPECT_EQ(kArgoFan3, ac.getFan());
+ ac.setFan(argoFan_t::FAN_AUTO);
+ EXPECT_EQ(argoFan_t::FAN_AUTO, ac.getFanEx());
+ ac.setFan(argoFan_t::FAN_HIGHEST);
+ EXPECT_EQ(argoFan_t::FAN_HIGHEST, ac.getFanEx());
+ if (std::is_same()) {
+ // Only supported on WREM3
+ ac.setFan(argoFan_t::FAN_HIGH);
+ EXPECT_EQ(argoFan_t::FAN_HIGH, ac.getFanEx());
+ }
+ ac.setFan(argoFan_t::FAN_MEDIUM);
+ EXPECT_EQ(argoFan_t::FAN_MEDIUM, ac.getFanEx());
+ if (std::is_same()) {
+ // Only supported on WREM3
+ ac.setFan(argoFan_t::FAN_LOW);
+ EXPECT_EQ(argoFan_t::FAN_LOW, ac.getFanEx());
+ ac.setFan(argoFan_t::FAN_LOWER);
+ EXPECT_EQ(argoFan_t::FAN_LOWER, ac.getFanEx());
+ }
+ ac.setFan(argoFan_t::FAN_LOWEST);
+ EXPECT_EQ(argoFan_t::FAN_LOWEST, ac.getFanEx());
+ ac.setFan(static_cast(255));
+ EXPECT_EQ(argoFan_t::FAN_AUTO, ac.getFanEx());
}
-TEST(TestArgoACClass, Night) {
- IRArgoAC ac(kGpioUnused);
+TYPED_TEST(TestArgoACBaseClass, SetAndGetFlapEx) {
+ IRArgoACBase ac(kGpioUnused);
+
+ ac.setFlap(argoFlap_t::FLAP_FULL);
+ EXPECT_EQ(argoFlap_t::FLAP_FULL, ac.getFlapEx());
+ ac.setFlap(argoFlap_t::FLAP_AUTO);
+ EXPECT_EQ(argoFlap_t::FLAP_AUTO, ac.getFlapEx());
+ ac.setFlap(argoFlap_t::FLAP_6);
+ EXPECT_EQ(argoFlap_t::FLAP_6, ac.getFlapEx());
+ ac.setFlap(argoFlap_t::FLAP_5);
+ EXPECT_EQ(argoFlap_t::FLAP_5, ac.getFlapEx());
+ ac.setFlap(argoFlap_t::FLAP_4);
+ EXPECT_EQ(argoFlap_t::FLAP_4, ac.getFlapEx());
+ ac.setFlap(argoFlap_t::FLAP_3);
+ EXPECT_EQ(argoFlap_t::FLAP_3, ac.getFlapEx());
+ ac.setFlap(argoFlap_t::FLAP_2);
+ EXPECT_EQ(argoFlap_t::FLAP_2, ac.getFlapEx());
+ ac.setFlap(argoFlap_t::FLAP_1);
+ EXPECT_EQ(argoFlap_t::FLAP_1, ac.getFlapEx());
+ ac.setFlap(argoFlap_t::FLAP_FULL);
+ ac.setFlap(static_cast(255));
+ EXPECT_EQ(argoFlap_t::FLAP_AUTO, ac.getFlapEx());
+}
+
+TYPED_TEST(TestArgoACBaseClass, Night) {
+ IRArgoACBase ac(kGpioUnused);
+
ac.setNight(false);
ASSERT_FALSE(ac.getNight());
ac.setNight(true);
@@ -193,8 +608,9 @@ TEST(TestArgoACClass, Night) {
ASSERT_FALSE(ac.getNight());
}
-TEST(TestArgoACClass, iFeel) {
- IRArgoAC ac(kGpioUnused);
+TYPED_TEST(TestArgoACBaseClass, iFeel) {
+ IRArgoACBase ac(kGpioUnused);
+
ac.setiFeel(false);
ASSERT_FALSE(ac.getiFeel());
ac.setiFeel(true);
@@ -203,8 +619,9 @@ TEST(TestArgoACClass, iFeel) {
ASSERT_FALSE(ac.getiFeel());
}
-TEST(TestArgoACClass, Power) {
- IRArgoAC ac(kGpioUnused);
+TYPED_TEST(TestArgoACBaseClass, Power) {
+ IRArgoACBase ac(kGpioUnused);
+
ac.setPower(false);
ASSERT_FALSE(ac.getPower());
ac.setPower(true);
@@ -213,8 +630,18 @@ TEST(TestArgoACClass, Power) {
ASSERT_FALSE(ac.getPower());
}
-TEST(TestArgoACClass, Max) {
- IRArgoAC ac(kGpioUnused);
+TYPED_TEST(TestArgoACBaseClass, OnOff) {
+ IRArgoACBase ac(kGpioUnused);
+
+ ASSERT_FALSE(ac.getPower());
+ ac.on();
+ ASSERT_TRUE(ac.getPower());
+ ac.off();
+ ASSERT_FALSE(ac.getPower());
+}
+
+TYPED_TEST(TestArgoACBaseClass, Max) {
+ IRArgoACBase ac(kGpioUnused);
ac.setMax(false);
ASSERT_FALSE(ac.getMax());
ac.setMax(true);
@@ -223,12 +650,697 @@ TEST(TestArgoACClass, Max) {
ASSERT_FALSE(ac.getMax());
}
+TYPED_TEST(TestArgoACBaseClass, SetAndGetMessageType) {
+ IRArgoACBase ac(kGpioUnused);
+
+ ac.setMessageType(argoIrMessageType_t::AC_CONTROL);
+ EXPECT_EQ(argoIrMessageType_t::AC_CONTROL, ac.getMessageType());
+ ac.setMessageType(argoIrMessageType_t::CONFIG_PARAM_SET);
+ EXPECT_EQ(argoIrMessageType_t::CONFIG_PARAM_SET, ac.getMessageType());
+ ac.setMessageType(argoIrMessageType_t::IFEEL_TEMP_REPORT);
+ EXPECT_EQ(argoIrMessageType_t::IFEEL_TEMP_REPORT, ac.getMessageType());
+ ac.setMessageType(argoIrMessageType_t::TIMER_COMMAND);
+ EXPECT_EQ(argoIrMessageType_t::TIMER_COMMAND, ac.getMessageType());
+}
+
+TYPED_TEST(TestArgoACBaseClass, SetMessageTypeResetsState) {
+ IRArgoACBase ac(kGpioUnused);
+
+ ac.on();
+ ac.setTemp(30);
+ ac.setRoomTemp(33);
+ ac.setMode(argoMode_t::COOL);
+ ac.setFan(argoFan_t::FAN_HIGHEST);
+
+ ac.setMessageType(argoIrMessageType_t::AC_CONTROL);
+ EXPECT_EQ(argoIrMessageType_t::AC_CONTROL, ac.getMessageType());
+ EXPECT_FALSE(ac.getPower());
+ EXPECT_EQ(20, ac.getTemp());
+ EXPECT_EQ(25, ac.getRoomTemp());
+ EXPECT_EQ(argoMode_t::AUTO, ac.getModeEx());
+ EXPECT_EQ(argoFan_t::FAN_AUTO, ac.getFanEx());
+
+ ac.setMessageType(argoIrMessageType_t::IFEEL_TEMP_REPORT);
+ EXPECT_EQ(argoIrMessageType_t::IFEEL_TEMP_REPORT, ac.getMessageType());
+ EXPECT_EQ(kArgoTempDelta, ac.getRoomTemp());
+}
+
+TYPED_TEST(TestArgoACBaseClass, staticGetMessageType) {
+ IRArgoACBase ac(kGpioUnused);
+
+ ac.setMessageType(argoIrMessageType_t::AC_CONTROL);
+ EXPECT_EQ(argoIrMessageType_t::AC_CONTROL,
+ IRArgoACBase::getMessageType(ac.getRaw(),
+ ac.getRawByteLength()));
+
+ ac.setMessageType(argoIrMessageType_t::IFEEL_TEMP_REPORT);
+ EXPECT_EQ(argoIrMessageType_t::IFEEL_TEMP_REPORT,
+ IRArgoACBase::getMessageType(ac.getRaw(),
+ ac.getRawByteLength()));
+
+ if (std::is_same()) {
+ // Only supported for WREM3
+ ac.setMessageType(argoIrMessageType_t::CONFIG_PARAM_SET);
+ EXPECT_EQ(argoIrMessageType_t::CONFIG_PARAM_SET,
+ IRArgoACBase::getMessageType(ac.getRaw(),
+ ac.getRawByteLength()));
+
+ ac.setMessageType(argoIrMessageType_t::TIMER_COMMAND);
+ EXPECT_EQ(argoIrMessageType_t::TIMER_COMMAND,
+ IRArgoACBase::getMessageType(ac.getRaw(),
+ ac.getRawByteLength()));
+ }
+}
+
+TYPED_TEST(TestArgoACBaseClass, staticgetStateLengthForIrMsgType) {
+ if (std::is_same()) {
+ EXPECT_EQ(kArgoStateLength,
+ IRArgoACBase::getStateLengthForIrMsgType(
+ argoIrMessageType_t::AC_CONTROL));
+ EXPECT_EQ(kArgoStateLength,
+ IRArgoACBase::getStateLengthForIrMsgType(
+ argoIrMessageType_t::TIMER_COMMAND));
+ EXPECT_EQ(0,
+ IRArgoACBase::getStateLengthForIrMsgType(
+ argoIrMessageType_t::CONFIG_PARAM_SET));
+ EXPECT_EQ(kArgoShortStateLength,
+ IRArgoACBase::getStateLengthForIrMsgType(
+ argoIrMessageType_t::IFEEL_TEMP_REPORT));
+ } else {
+ EXPECT_EQ(kArgo3AcControlStateLength,
+ IRArgoACBase::getStateLengthForIrMsgType(
+ argoIrMessageType_t::AC_CONTROL));
+ EXPECT_EQ(kArgo3TimerStateLength,
+ IRArgoACBase::getStateLengthForIrMsgType(
+ argoIrMessageType_t::TIMER_COMMAND));
+ EXPECT_EQ(kArgo3ConfigStateLength,
+ IRArgoACBase::getStateLengthForIrMsgType(
+ argoIrMessageType_t::CONFIG_PARAM_SET));
+ EXPECT_EQ(kArgo3iFeelReportStateLength,
+ IRArgoACBase::getStateLengthForIrMsgType(
+ argoIrMessageType_t::IFEEL_TEMP_REPORT));
+ }
+}
+
+TYPED_TEST(TestArgoACBaseClass, setRaw) {
+ TypeParam rawStateAC = {};
+ rawStateAC.RoomTemp = 30;
+
+ TypeParam rawStateIFeel = {};
+ rawStateIFeel.SensorT = 25;
+
+ IRArgoACBase ac(kGpioUnused);
+
+ if (std::is_same()) {
+ ac.setRaw(reinterpret_cast(&rawStateAC), std::min(
+ static_cast(kArgoStateLength), sizeof(TypeParam)));
+ EXPECT_EQ(30 + kArgoTempDelta, ac.getRoomTemp());
+ EXPECT_EQ(argoIrMessageType_t::AC_CONTROL, ac.getMessageType());
+
+ ac.setRaw(reinterpret_cast(&rawStateIFeel), std::min(
+ static_cast(kArgoShortStateLength), sizeof(TypeParam)));
+ EXPECT_EQ(25 + kArgoTempDelta, ac.getRoomTemp());
+ EXPECT_EQ(argoIrMessageType_t::IFEEL_TEMP_REPORT, ac.getMessageType());
+ } else {
+ ac.setRaw(reinterpret_cast(&rawStateAC), std::min(
+ static_cast(kArgo3AcControlStateLength), sizeof(TypeParam)));
+ EXPECT_EQ(30 + kArgoTempDelta, ac.getRoomTemp());
+ EXPECT_EQ(argoIrMessageType_t::AC_CONTROL, ac.getMessageType());
+
+ auto raw = reinterpret_cast(&rawStateIFeel);
+ raw[0] = 0x4B; // sets Byte0::IrCommandType to IFeel (0b01)
+ ac.setRaw(raw, std::min(static_cast(kArgo3iFeelReportStateLength),
+ sizeof(TypeParam)));
+ EXPECT_EQ(25 + kArgoTempDelta, ac.getRoomTemp());
+ EXPECT_EQ(argoIrMessageType_t::IFEEL_TEMP_REPORT, ac.getMessageType());
+ }
+}
+
+/******************************************************************************/
+/* Backward-compatibility tests of legacy IRArgoAc raw methods vs. base class */
+/******************************************************************************/
+
+/// @brief Tests interactions of raw setFan() method
+/// with a base-class getFanEx()
+TEST(TestArgoACClass, SetAndGetFanEx) {
+ IRArgoAC ac(kGpioUnused);
+
+ ac.setFan(kArgoFan3);
+ EXPECT_EQ(kArgoFan3, ac.getFan());
+ EXPECT_EQ(argoFan_t::FAN_HIGHEST, ac.getFanEx());
+ ac.setFan(kArgoFan1);
+ EXPECT_EQ(kArgoFan1, ac.getFan());
+ EXPECT_EQ(argoFan_t::FAN_LOWEST, ac.getFanEx());
+ ac.setFan(kArgoFanAuto);
+ EXPECT_EQ(kArgoFanAuto, ac.getFan());
+ EXPECT_EQ(argoFan_t::FAN_AUTO, ac.getFanEx());
+ ac.setFan(kArgoFan2);
+ EXPECT_EQ(kArgoFan2, ac.getFan());
+ EXPECT_EQ(argoFan_t::FAN_MEDIUM, ac.getFanEx());
+
+ ASSERT_NE(7, kArgoFan3);
+ // Now try some unexpected value.
+ ac.setFan(7);
+ EXPECT_EQ(kArgoFan3, ac.getFan());
+}
+
+/// @brief Tests interactions of base-class setFan() method
+/// with a raw getFan()
+TEST(TestArgoACClass, SetFanExAndGetFan) {
+ IRArgoAC ac(kGpioUnused);
+
+ ac.setFan(argoFan_t::FAN_AUTO);
+ EXPECT_EQ(kArgoFanAuto, ac.getFan());
+
+ ac.setFan(argoFan_t::FAN_HIGHEST);
+ EXPECT_EQ(kArgoFan3, ac.getFan());
+ ac.setFan(argoFan_t::FAN_HIGH);
+ EXPECT_EQ(kArgoFan3, ac.getFan());
+
+ ac.setFan(argoFan_t::FAN_MEDIUM);
+ EXPECT_EQ(kArgoFan2, ac.getFan());
+ ac.setFan(argoFan_t::FAN_LOW);
+ EXPECT_EQ(kArgoFan2, ac.getFan());
+
+ ac.setFan(argoFan_t::FAN_LOWER);
+ EXPECT_EQ(kArgoFan1, ac.getFan());
+ ac.setFan(argoFan_t::FAN_LOWEST);
+ EXPECT_EQ(kArgoFan1, ac.getFan());
+
+ ac.setFan(static_cast(255));
+ EXPECT_EQ(kArgoFanAuto, ac.getFan());
+}
+
+TEST(TestArgoACClass, SetFlapGetFlap) {
+ IRArgoAC ac(kGpioUnused);
+
+ ac.setFlap(kArgoFlapFull);
+ EXPECT_EQ(kArgoFlapFull, ac.getFlap());
+ ac.setFlap(kArgoFlapAuto);
+ EXPECT_EQ(kArgoFlapAuto, ac.getFlap());
+ ac.setFlap(kArgoFlap1);
+ EXPECT_EQ(kArgoFlap1, ac.getFlap());
+ ac.setFlap(kArgoFlap2);
+ EXPECT_EQ(kArgoFlap2, ac.getFlap());
+ ac.setFlap(kArgoFlap3);
+ EXPECT_EQ(kArgoFlap3, ac.getFlap());
+ ac.setFlap(kArgoFlap4);
+ EXPECT_EQ(kArgoFlap4, ac.getFlap());
+ ac.setFlap(kArgoFlap5);
+ EXPECT_EQ(kArgoFlap5, ac.getFlap());
+ ac.setFlap(kArgoFlap6);
+ EXPECT_EQ(kArgoFlap6, ac.getFlap());
+}
+
+/// @brief Tests interactions of raw setMode() method
+/// with a base-class getModeEx()
+TEST(TestArgoACClass, SetModeAndGetModeEx) {
+ IRArgoAC ac(kGpioUnused);
+
+ ac.setMode(kArgoHeat);
+ EXPECT_EQ(kArgoHeat, ac.getMode());
+ EXPECT_EQ(argoMode_t::HEAT, ac.getModeEx());
+ ac.setMode(kArgoCool);
+ EXPECT_EQ(kArgoCool, ac.getMode());
+ EXPECT_EQ(argoMode_t::COOL, ac.getModeEx());
+ ac.setMode(kArgoDry);
+ EXPECT_EQ(kArgoDry, ac.getMode());
+ EXPECT_EQ(argoMode_t::DRY, ac.getModeEx());
+ ac.setMode(kArgoAuto);
+ EXPECT_EQ(kArgoAuto, ac.getMode());
+ EXPECT_EQ(argoMode_t::AUTO, ac.getModeEx());
+ ac.setMode(kArgoHeatAuto);
+ EXPECT_EQ(kArgoHeatAuto, ac.getMode());
+ EXPECT_EQ(argoMode_t::AUTO, ac.getModeEx());
+ ac.setMode(kArgoOff);
+ EXPECT_EQ(kArgoOff, ac.getMode());
+ EXPECT_EQ(argoMode_t::FAN, ac.getModeEx());
+ ac.setMode(255);
+ EXPECT_EQ(kArgoAuto, ac.getMode());
+ EXPECT_EQ(argoMode_t::AUTO, ac.getModeEx());
+}
+
+/// @brief Tests interactions of base-class setMode() method
+/// with a raw getMode()
+TEST(TestArgoACClass, SetModeExAndGetMode) {
+ IRArgoAC ac(kGpioUnused);
+
+ ac.setMode(argoMode_t::HEAT);
+ EXPECT_EQ(kArgoHeat, ac.getMode());
+ EXPECT_EQ(argoMode_t::HEAT, ac.getModeEx());
+ ac.setMode(argoMode_t::AUTO);
+ EXPECT_EQ(kArgoAuto, ac.getMode());
+ EXPECT_EQ(argoMode_t::AUTO, ac.getModeEx());
+ ac.setMode(argoMode_t::COOL);
+ EXPECT_EQ(kArgoCool, ac.getMode());
+ EXPECT_EQ(argoMode_t::COOL, ac.getModeEx());
+ ac.setMode(argoMode_t::DRY);
+ EXPECT_EQ(kArgoDry, ac.getMode());
+ EXPECT_EQ(argoMode_t::DRY, ac.getModeEx());
+ ac.setMode(argoMode_t::FAN);
+ EXPECT_EQ(kArgoOff, ac.getMode()); // Fan is N/A (?) -> defaults to off
+ EXPECT_EQ(argoMode_t::FAN, ac.getModeEx());
+ ac.setMode(static_cast(kArgoHeatBlink));
+ EXPECT_EQ(kArgoHeatBlink, ac.getMode());
+ EXPECT_EQ(static_cast(kArgoHeatBlink), ac.getModeEx());
+ ac.setMode(static_cast(255));
+ EXPECT_EQ(kArgoAuto, ac.getMode());
+ EXPECT_EQ(argoMode_t::AUTO, ac.getModeEx());
+}
+
+TEST(TestArgoACClass, SendSensorTemp) {
+ IRrecv irrecv(kGpioUnused);
+
+ // Method 1 (via sendSensorTemp())
+ IRArgoAC ac(kGpioUnused);
+ ac.sendSensorTemp(10);
+ ac._irsend.makeDecodeResult();
+ EXPECT_TRUE(irrecv.decode(&ac._irsend.capture));
+ EXPECT_EQ(decode_type_t::ARGO, ac._irsend.capture.decode_type);
+ EXPECT_EQ("Model: 1 (WREM2), Sensor Temp: 10C",
+ IRAcUtils::resultAcToString(&ac._irsend.capture));
+
+ // Method 2 (via send())
+ IRArgoAC ac2(kGpioUnused);
+ ac2.setMessageType(argoIrMessageType_t::IFEEL_TEMP_REPORT);
+ ac2.setRoomTemp(19);
+ ac2.send();
+ ac2._irsend.makeDecodeResult();
+ EXPECT_TRUE(irrecv.decode(&ac2._irsend.capture));
+ EXPECT_EQ(decode_type_t::ARGO, ac2._irsend.capture.decode_type);
+ EXPECT_EQ("Model: 1 (WREM2), Sensor Temp: 19C",
+ IRAcUtils::resultAcToString(&ac2._irsend.capture));
+}
+
+/******************************************************************************/
+/* IRArgoAC_WREM3-specific tests */
+/******************************************************************************/
+
+TEST(TestArgoAC_WREM3Class, Eco) {
+ IRArgoAC_WREM3 ac(kGpioUnused);
+
+ ac.setEco(false);
+ ASSERT_FALSE(ac.getEco());
+ ac.setEco(true);
+ ASSERT_TRUE(ac.getEco());
+ ac.setEco(false);
+ ASSERT_FALSE(ac.getEco());
+}
+
+TEST(TestArgoAC_WREM3Class, Filter) {
+ IRArgoAC_WREM3 ac(kGpioUnused);
+
+ ac.setFilter(false);
+ ASSERT_FALSE(ac.getFilter());
+ ac.setFilter(true);
+ ASSERT_TRUE(ac.getFilter());
+ ac.setFilter(false);
+ ASSERT_FALSE(ac.getFilter());
+}
+
+TEST(TestArgoAC_WREM3Class, Light) {
+ IRArgoAC_WREM3 ac(kGpioUnused);
+
+ ac.setLight(false);
+ ASSERT_FALSE(ac.getLight());
+ ac.setLight(true);
+ ASSERT_TRUE(ac.getLight());
+ ac.setLight(false);
+ ASSERT_FALSE(ac.getLight());
+}
+
+TEST(TestArgoAC_WREM3Class, Channel) {
+ IRArgoAC_WREM3 ac(kGpioUnused);
+
+ ac.setChannel(0);
+ ASSERT_EQ(0, ac.getChannel());
+ ac.setChannel(1);
+ ASSERT_EQ(1, ac.getChannel());
+ ac.setChannel(2);
+ ASSERT_EQ(2, ac.getChannel());
+ ac.setChannel(3);
+ ASSERT_EQ(3, ac.getChannel());
+ ac.setChannel(4);
+ ASSERT_EQ(3, ac.getChannel());
+}
+
+TEST(TestArgoAC_WREM3Class, ConfigEntry) {
+ IRArgoAC_WREM3 ac(kGpioUnused);
+
+ ac.setConfigEntry(0, 0);
+ ASSERT_EQ(std::make_pair(static_cast(0), static_cast(0)),
+ ac.getConfigEntry());
+ ac.setConfigEntry(80, 86);
+ ASSERT_EQ(std::make_pair(static_cast(80), static_cast(86)),
+ ac.getConfigEntry());
+ ac.setConfigEntry(255, 255);
+ ASSERT_EQ(std::make_pair(static_cast(255),
+ static_cast(255)),
+ ac.getConfigEntry());
+}
+
+TEST(TestArgoAC_WREM3Class, CurrentTimeMinutes) {
+ IRArgoAC_WREM3 ac(kGpioUnused);
+
+ ac.setCurrentTimeMinutes(0);
+ ASSERT_EQ(0, ac.getCurrentTimeMinutes());
+ ac.setCurrentTimeMinutes(16*60+50);
+ ASSERT_EQ(16*60+50, ac.getCurrentTimeMinutes());
+ ac.setCurrentTimeMinutes(23*60+59);
+ ASSERT_EQ(23*60+59, ac.getCurrentTimeMinutes());
+ ac.setCurrentTimeMinutes(23*60+59+1);
+ ASSERT_EQ(23*60+59, ac.getCurrentTimeMinutes());
+}
+
+TEST(TestArgoAC_WREM3Class, CurrentDayOfWeek) {
+ IRArgoAC_WREM3 ac(kGpioUnused);
+
+ ac.setCurrentDayOfWeek(argoWeekday::SUNDAY);
+ ASSERT_EQ(argoWeekday::SUNDAY, ac.getCurrentDayOfWeek());
+ ac.setCurrentDayOfWeek(argoWeekday::MONDAY);
+ ASSERT_EQ(argoWeekday::MONDAY, ac.getCurrentDayOfWeek());
+ ac.setCurrentDayOfWeek(argoWeekday::TUESDAY);
+ ASSERT_EQ(argoWeekday::TUESDAY, ac.getCurrentDayOfWeek());
+ ac.setCurrentDayOfWeek(argoWeekday::WEDNESDAY);
+ ASSERT_EQ(argoWeekday::WEDNESDAY, ac.getCurrentDayOfWeek());
+ ac.setCurrentDayOfWeek(argoWeekday::THURSDAY);
+ ASSERT_EQ(argoWeekday::THURSDAY, ac.getCurrentDayOfWeek());
+ ac.setCurrentDayOfWeek(argoWeekday::FRIDAY);
+ ASSERT_EQ(argoWeekday::FRIDAY, ac.getCurrentDayOfWeek());
+ ac.setCurrentDayOfWeek(argoWeekday::SATURDAY);
+ ASSERT_EQ(argoWeekday::SATURDAY, ac.getCurrentDayOfWeek());
+ ac.setCurrentDayOfWeek(static_cast(200));
+ ASSERT_EQ(argoWeekday::SATURDAY, ac.getCurrentDayOfWeek());
+}
+
+TEST(TestArgoAC_WREM3Class, TimerType) {
+ IRArgoAC_WREM3 ac(kGpioUnused);
+
+ ac.setTimerType(argoTimerType_t::NO_TIMER);
+ ASSERT_EQ(argoTimerType_t::NO_TIMER, ac.getTimerType());
+ ac.setTimerType(argoTimerType_t::DELAY_TIMER);
+ ASSERT_EQ(argoTimerType_t::DELAY_TIMER, ac.getTimerType());
+ ac.setTimerType(argoTimerType_t::SCHEDULE_TIMER_1);
+ ASSERT_EQ(argoTimerType_t::SCHEDULE_TIMER_1, ac.getTimerType());
+ ac.setTimerType(argoTimerType_t::SCHEDULE_TIMER_2);
+ ASSERT_EQ(argoTimerType_t::SCHEDULE_TIMER_2, ac.getTimerType());
+ ac.setTimerType(argoTimerType_t::SCHEDULE_TIMER_3);
+ ASSERT_EQ(argoTimerType_t::SCHEDULE_TIMER_3, ac.getTimerType());
+ ac.setTimerType(static_cast(201));
+ ASSERT_EQ(argoTimerType_t::NO_TIMER, ac.getTimerType());
+}
+
+TEST(TestArgoAC_WREM3Class, DelayTimerMinutes) {
+ IRArgoAC_WREM3 ac(kGpioUnused);
+
+ ac.setDelayTimerMinutes(0);
+ ASSERT_EQ(0, ac.getDelayTimerMinutes());
+ ac.setDelayTimerMinutes(16*60+50);
+ ASSERT_EQ(16*60+50, ac.getDelayTimerMinutes());
+ ac.setDelayTimerMinutes(16*60+54);
+ ASSERT_EQ(16*60+50, ac.getDelayTimerMinutes());
+ ac.setDelayTimerMinutes(16*60+55);
+ ASSERT_EQ(16*60+60, ac.getDelayTimerMinutes());
+ ac.setDelayTimerMinutes(19*60+44);
+ ASSERT_EQ(19*60+40, ac.getDelayTimerMinutes());
+ ac.setDelayTimerMinutes(19*60+50);
+ ASSERT_EQ(19*60+50, ac.getDelayTimerMinutes());
+ ac.setDelayTimerMinutes(19*60+60);
+ ASSERT_EQ(19*60+50, ac.getDelayTimerMinutes());
+ ac.setDelayTimerMinutes(23*60+59); // Above max (19h50m)
+ ASSERT_EQ(19*60+50, ac.getDelayTimerMinutes());
+ ac.setDelayTimerMinutes(23*60+59+1);
+ ASSERT_EQ(19*60+50, ac.getDelayTimerMinutes());
+}
+
+TEST(TestArgoAC_WREM3Class, ScheduleTimerStartMinutes) {
+ IRArgoAC_WREM3 ac(kGpioUnused);
+
+ ac.setScheduleTimerStartMinutes(0);
+ ASSERT_EQ(0, ac.getScheduleTimerStartMinutes());
+ ac.setScheduleTimerStartMinutes(16*60+50);
+ ASSERT_EQ(16*60+50, ac.getScheduleTimerStartMinutes());
+ ac.setScheduleTimerStartMinutes(16*60+54);
+ ASSERT_EQ(16*60+50, ac.getScheduleTimerStartMinutes());
+ ac.setScheduleTimerStartMinutes(16*60+55);
+ ASSERT_EQ(16*60+60, ac.getScheduleTimerStartMinutes());
+ ac.setScheduleTimerStartMinutes(23*60+50);
+ ASSERT_EQ(23*60+50, ac.getScheduleTimerStartMinutes());
+ ac.setScheduleTimerStartMinutes(23*60+59); // Above max (23h50m)
+ ASSERT_EQ(23*60+50, ac.getScheduleTimerStartMinutes());
+ ac.setScheduleTimerStartMinutes(23*60+59+1);
+ ASSERT_EQ(23*60+50, ac.getScheduleTimerStartMinutes());
+}
+
+TEST(TestArgoAC_WREM3Class, ScheduleTimerStopMinutes) {
+ IRArgoAC_WREM3 ac(kGpioUnused);
+
+ ac.setScheduleTimerStopMinutes(0);
+ ASSERT_EQ(0, ac.getScheduleTimerStopMinutes());
+ ac.setScheduleTimerStopMinutes(16*60+50);
+ ASSERT_EQ(16*60+50, ac.getScheduleTimerStopMinutes());
+ ac.setScheduleTimerStopMinutes(16*60+54);
+ ASSERT_EQ(16*60+50, ac.getScheduleTimerStopMinutes());
+ ac.setScheduleTimerStopMinutes(16*60+55);
+ ASSERT_EQ(16*60+60, ac.getScheduleTimerStopMinutes());
+ ac.setScheduleTimerStopMinutes(23*60+50);
+ ASSERT_EQ(23*60+50, ac.getScheduleTimerStopMinutes());
+ ac.setScheduleTimerStopMinutes(23*60+59); // Above max (23h50m)
+ ASSERT_EQ(23*60+50, ac.getScheduleTimerStopMinutes());
+ ac.setScheduleTimerStopMinutes(23*60+59+1);
+ ASSERT_EQ(23*60+50, ac.getScheduleTimerStopMinutes());
+}
+
+TEST(TestArgoAC_WREM3Class, ScheduleTimerActiveDays) {
+ IRArgoAC_WREM3 ac(kGpioUnused);
+
+ ac.setScheduleTimerActiveDays(std::set({}));
+ EXPECT_THAT(ac.getScheduleTimerActiveDays(),
+ ::testing::IsEmpty());
+ EXPECT_EQ(0b0000000, ac.getTimerActiveDaysBitmap());
+
+ ac.setScheduleTimerActiveDays(std::set({argoWeekday::SUNDAY}));
+ EXPECT_THAT(ac.getScheduleTimerActiveDays(),
+ ::testing::ElementsAre(argoWeekday::SUNDAY));
+ EXPECT_EQ(0b0000001, ac.getTimerActiveDaysBitmap());
+
+ ac.setScheduleTimerActiveDays(std::set({argoWeekday::MONDAY}));
+ EXPECT_THAT(ac.getScheduleTimerActiveDays(),
+ ::testing::ElementsAre(argoWeekday::MONDAY));
+ EXPECT_EQ(0b0000010, ac.getTimerActiveDaysBitmap());
+
+ ac.setScheduleTimerActiveDays(std::set({argoWeekday::TUESDAY}));
+ EXPECT_THAT(ac.getScheduleTimerActiveDays(),
+ ::testing::ElementsAre(argoWeekday::TUESDAY));
+ EXPECT_EQ(0b0000100, ac.getTimerActiveDaysBitmap());
+
+ ac.setScheduleTimerActiveDays(std::set({
+ argoWeekday::WEDNESDAY}));
+ EXPECT_THAT(ac.getScheduleTimerActiveDays(),
+ ::testing::ElementsAre(argoWeekday::WEDNESDAY));
+ EXPECT_EQ(0b0001000, ac.getTimerActiveDaysBitmap());
+
+ ac.setScheduleTimerActiveDays(std::set({argoWeekday::THURSDAY}));
+ EXPECT_THAT(ac.getScheduleTimerActiveDays(),
+ ::testing::ElementsAre(argoWeekday::THURSDAY));
+ EXPECT_EQ(0b0010000, ac.getTimerActiveDaysBitmap());
+
+ ac.setScheduleTimerActiveDays(std::set({argoWeekday::FRIDAY}));
+ EXPECT_THAT(ac.getScheduleTimerActiveDays(),
+ ::testing::ElementsAre(argoWeekday::FRIDAY));
+ EXPECT_EQ(0b0100000, ac.getTimerActiveDaysBitmap());
+
+ ac.setScheduleTimerActiveDays(std::set({argoWeekday::SATURDAY}));
+ EXPECT_THAT(ac.getScheduleTimerActiveDays(),
+ ::testing::ElementsAre(argoWeekday::SATURDAY));
+ EXPECT_EQ(0b1000000, ac.getTimerActiveDaysBitmap());
+
+ ac.setScheduleTimerActiveDays(std::set