Files
yoradio/yoRadio/src/IRremoteESP8266/ir_Argo.cpp
2024-11-27 12:43:44 +03:00

1823 lines
67 KiB
C++

// Copyright 2017 Schmolders
// Copyright 2019 crankyoldgit
// Copyright 2022 Mateusz Bronk (mbronk)
/// @file
/// @brief Argo A/C protocol.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1859
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1912
#include "ir_Argo.h"
#include <algorithm>
#include <cmath>
#include <cstring>
#ifndef UNIT_TEST
#include <Arduino.h>
#endif // UNIT_TEST
#include "IRremoteESP8266.h"
#include "IRtext.h"
#include "IRutils.h"
// Constants
// using SPACE modulation. MARK is always const 400u
const uint16_t kArgoHdrMark = 6400;
const uint16_t kArgoHdrSpace = 3300;
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;
using irutils::daysBitmaskToString;
using irutils::addTimerModeToString;
#if SEND_ARGO
/// Send a Argo A/C formatted message.
/// 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, 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,
_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?
template <typename T>
IRArgoACBase<T>::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<ArgoProtocol>(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<ArgoProtocolWREM3>(pin, inverted, use_modulation) {}
/// Set up hardware to be able to send a message.
template<typename T>
void IRArgoACBase<T>::begin(void) { _irsend.begin(); }
/// @cond
/// @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
/// @relates IRArgoACBase\<ARGO_PROTOCOL_T\>
template<>
uint16_t IRArgoACBase<ArgoProtocol>::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
}
}
/// @endcond
/// @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
/// @relates IRArgoACBase\<ARGO_PROTOCOL_T\>
template<>
uint16_t IRArgoACBase<ArgoProtocolWREM3>::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;
}
}
/// @cond
/// @brief Get message type from raw WREM-2 data
/// 1st param ignored: WREM-2 does not carry 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
/// @relates IRArgoACBase\<ARGO_PROTOCOL_T\>
template<>
argoIrMessageType_t IRArgoACBase<ArgoProtocol>::getMessageType(
const uint8_t[], const uint16_t length) {
if (length == kArgoShortStateLength) {
return argoIrMessageType_t::IFEEL_TEMP_REPORT;
}
return argoIrMessageType_t::AC_CONTROL;
}
/// @endcond
/// @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
/// @relates IRArgoACBase\<ARGO_PROTOCOL_T\>
template<>
argoIrMessageType_t IRArgoACBase<ArgoProtocolWREM3>::getMessageType(
const uint8_t state[], const uint16_t length) {
if (length < 1) {
return static_cast<argoIrMessageType_t>(-1);
}
return static_cast<argoIrMessageType_t>(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<argoIrMessageType_t>(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
/// @relates IRArgoACBase\<ARGO_PROTOCOL_T\>
template<>
uint16_t IRArgoACBase<ArgoProtocol>::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
/// @relates IRArgoACBase\<ARGO_PROTOCOL_T\>
template<>
uint16_t IRArgoACBase<ArgoProtocolWREM3>::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<typename T>
uint16_t IRArgoACBase<T>::getRawByteLength() const {
return getRawByteLength(_, _messageType);
}
/// @cond
/// 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 The 8-bit calculated result.
/// @relates IRArgoACBase\<ARGO_PROTOCOL_T\>
template<>
uint8_t IRArgoACBase<ArgoProtocol>::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);
}
/// @endcond
/// 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 The 8-bit calculated result.
/// @relates IRArgoACBase\<ARGO_PROTOCOL_T\>
template<>
uint8_t IRArgoACBase<ArgoProtocolWREM3>::calcChecksum(const uint8_t state[],
const uint16_t length) {
if (length < 1) {
return -1; // Nothing to compute on
}
uint16_t payloadSizeBits = (length - 1) * 8; // Last byte carries checksum
argoIrMessageType_t 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 (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.
/// @relates IRArgoACBase\<ARGO_PROTOCOL_T\>
template<>
void IRArgoACBase<ArgoProtocol>::_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.
/// @relates IRArgoACBase\<ARGO_PROTOCOL_T\>
template<>
void IRArgoACBase<ArgoProtocolWREM3>::_checksum(ArgoProtocolWREM3 *state) {
argoIrMessageType_t 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.
template<typename T>
void IRArgoACBase<T>::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.
/// _param 2nd param unused (always resets to AC_CONTROL state)
/// @relates IRArgoACBase\<ARGO_PROTOCOL_T\>
template<>
void IRArgoACBase<ArgoProtocol>::_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;
state->Post = kArgoPost;
}
/// 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
/// @relates IRArgoACBase\<ARGO_PROTOCOL_T\>
template<>
void IRArgoACBase<ArgoProtocolWREM3>::_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<uint8_t>(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<typename T>
void IRArgoACBase<T>::stateReset(argoIrMessageType_t messageType) {
_stateReset(&_, messageType);
if (messageType == argoIrMessageType_t::AC_CONTROL) {
off();
setTemp(20);
setSensorTemp(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)
/// @relates IRArgoACBase\<ARGO_PROTOCOL_T\>
template<>
uint8_t IRArgoACBase<ArgoProtocol>::getChecksum(const uint8_t state[],
const uint16_t length) {
if (length < 1) {
return -1;
}
return (state[length - 2] >> 2) + (state[length - 1] << 6);
}
/// @cond
/// @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<ArgoProtocolWREM3>::getChecksum(const uint8_t state[],
const uint16_t length) {
if (length < 1) {
return -1;
}
argoIrMessageType_t 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]);
}
/// @endcond
/// 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<typename T>
bool IRArgoACBase<T>::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<typename T>
void IRArgoACBase<T>::send(const uint16_t repeat) {
_irsend.sendArgo(getRaw(), getRawByteLength(), repeat);
}
/// @cond
/// 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.
/// @relates IRArgoACBase\<ARGO_PROTOCOL_T\>
template<>
void IRArgoACBase<ArgoProtocolWREM3>::send(const uint16_t repeat) {
_irsend.sendArgoWREM3(getRaw(), getRawByteLength(), repeat);
}
/// @endcond
/// 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);
uint16_t 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);
uint16_t 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.
template<typename T>
uint8_t* IRArgoACBase<T>::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.
template<typename T>
void IRArgoACBase<T>::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.
template<typename T>
void IRArgoACBase<T>::on(void) { setPower(true); }
/// Set the internal state to have the power off.
template<typename T>
void IRArgoACBase<T>::off(void) { setPower(false); }
/// @cond
/// Set the internal state to have the desired power.
/// @param[in] on The desired power 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
/// @relates IRArgoACBase\<ARGO_PROTOCOL_T\>
template<>
void IRArgoACBase<ArgoProtocol>::setPower(const bool on) {
_.Power = on;
}
/// @endcond
/// @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.
/// @relates IRArgoACBase\<ARGO_PROTOCOL_T\>
template<>
void IRArgoACBase<ArgoProtocolWREM3>::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.
/// @relates IRArgoACBase\<ARGO_PROTOCOL_T\>
template<>
bool IRArgoACBase<ArgoProtocol>::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.
/// @relates IRArgoACBase\<ARGO_PROTOCOL_T\>
template<>
bool IRArgoACBase<ArgoProtocolWREM3>::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.
template<typename T>
void IRArgoACBase<T>::setMax(const bool on) {
_.Max = on;
}
/// Is the Max (i.e. Turbo) setting on?
/// @return The current value.
template<typename T>
bool IRArgoACBase<T>::getMax(void) const { return _.Max; }
/// Set the temperature.
/// @param[in] degrees The temperature in degrees celsius.
/// @note Sending 0 equals +4
template<typename T>
void IRArgoACBase<T>::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
_.Temp = temp;
}
/// Get the current temperature setting.
/// @return The current setting for temp. in degrees celsius.
template<typename T>
uint8_t IRArgoACBase<T>::getTemp(void) const {
return _.Temp + kArgoTempDelta;
}
/// @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.
/// @relates IRArgoACBase\<ARGO_PROTOCOL_T\>
template<>
argoFan_t IRArgoACBase<ArgoProtocol>::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<argoFan_t>(_.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.
/// @relates IRArgoACBase\<ARGO_PROTOCOL_T\>
template<>
argoFan_t IRArgoACBase<ArgoProtocolWREM3>::getFanEx(void) const {
return static_cast<argoFan_t>(_.Fan);
}
/// @cond
/// 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)
/// @relates IRArgoACBase\<ARGO_PROTOCOL_T\>
template<>
void IRArgoACBase<ArgoProtocol>::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:
uint8_t raw_value = static_cast<uint8_t>(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;
}
}
/// @endcond
/// 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] fan The desired fan speed.
/// @relates IRArgoACBase\<ARGO_PROTOCOL_T\>
template<>
void IRArgoACBase<ArgoProtocolWREM3>::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<uint8_t>(fan);
break;
default:
_.Fan = static_cast<uint8_t>(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;
}
/// @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<typename T>
argoFlap_t IRArgoACBase<T>::getFlapEx(void) const {
return static_cast<argoFlap_t>(_.Flap);
}
/// Set the desired flap mode
/// @param[in] flap The desired flap mode.
template<typename T>
void IRArgoACBase<T>::setFlap(argoFlap_t flap) {
uint8_t raw_value = static_cast<uint8_t>(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<uint8_t>(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<argoFlap_t>(flap));
// TODO(kaschmo): set correct bits for flap mode
}
/// 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;
}
/// 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
/// @relates IRArgoACBase\<ARGO_PROTOCOL_T\>
template<>
argoMode_t IRArgoACBase<ArgoProtocol>::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<argoMode_t>(_.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.
/// @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
/// @relates IRArgoACBase\<ARGO_PROTOCOL_T\>
template<>
argoMode_t IRArgoACBase<ArgoProtocolWREM3>::getModeEx(void) const {
return static_cast<argoMode_t>(_.Mode);
}
/// @cond
/// Set the desired operation mode.
/// @param[in] mode The desired operation 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
/// @relates IRArgoACBase\<ARGO_PROTOCOL_T\>
template<>
void IRArgoACBase<ArgoProtocol>::setMode(argoMode_t mode) {
switch (mode) {
case argoMode_t::COOL:
_.Mode = static_cast<uint8_t>(kArgoCool);
break;
case argoMode_t::DRY:
_.Mode = static_cast<uint8_t>(kArgoDry);
break;
case argoMode_t::HEAT:
_.Mode = static_cast<uint8_t>(kArgoHeat);
break;
case argoMode_t::FAN:
_.Mode = static_cast<uint8_t>(kArgoOff);
break;
case argoMode_t::AUTO:
_.Mode = static_cast<uint8_t>(kArgoAuto);
break;
default:
uint8_t raw_value = static_cast<uint8_t>(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<uint8_t>(kArgoAuto);
}
break;
}
}
/// @endcond
/// @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.
/// @relates IRArgoACBase\<ARGO_PROTOCOL_T\>
template<>
void IRArgoACBase<ArgoProtocolWREM3>::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<uint8_t>(mode);
break;
default:
_.Mode = static_cast<uint8_t>(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:
case kArgoAuto:
case kArgoOff:
case kArgoHeat:
case kArgoHeatAuto:
_.Mode = mode;
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.
template<typename T>
void IRArgoACBase<T>::setNight(const bool on) { _.Night = on; }
/// Get the status of Night mode. i.e. Sleep.
/// @return true if on, false if off.
template<typename T>
bool IRArgoACBase<T>::getNight(void) const { return _.Night; }
/// @brief Turn on/off the Economy mode (lowered power mode)
/// @param[in] on The desired setting.
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<uint8_t, uint8_t> 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<typename T>
void IRArgoACBase<T>::setiFeel(const bool on) { _.iFeel = on; }
/// Get the status of iFeel mode.
/// @return true if on, false if off.
template<typename T>
bool IRArgoACBase<T>::getiFeel(void) const { return _.iFeel; }
/// @brief Set the message type of the next command (setting this resets state)
/// @param msgType The message type to set
template<typename T>
void IRArgoACBase<T>::setMessageType(const argoIrMessageType_t msgType) {
stateReset(msgType);
}
/// @brief Get the message type
/// @return Message type currently set
template<typename T>
argoIrMessageType_t IRArgoACBase<T>::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.
template<typename T>
void IRArgoACBase<T>::setSensorTemp(const uint8_t degrees) {
uint8_t temp = std::min(degrees, kArgoMaxRoomTemp);
temp = std::max(temp, kArgoTempDelta) - kArgoTempDelta;
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.
template<typename T>
uint8_t IRArgoACBase<T>::getSensorTemp(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<typename T>
argoIrMessageType_t IRArgoACBase<T>::convertCommand(
const stdAc::ac_command_t command) {
switch (command) {
case stdAc::ac_command_t::kSensorTempReport:
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.
template<typename T>
argoMode_t IRArgoACBase<T>::convertMode(const stdAc::opmode_t mode) {
switch (mode) {
case stdAc::opmode_t::kCool:
return argoMode_t::COOL;
case stdAc::opmode_t::kHeat:
return argoMode_t::HEAT;
case stdAc::opmode_t::kDry:
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.
template<typename T>
argoFan_t IRArgoACBase<T>::convertFan(const stdAc::fanspeed_t speed) {
switch (speed) {
case stdAc::fanspeed_t::kMin:
return argoFan_t::FAN_LOWEST;
case stdAc::fanspeed_t::kLow:
return argoFan_t::FAN_LOWER;
case stdAc::fanspeed_t::kMedium:
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 argoFan_t::FAN_HIGHEST;
default:
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.
template<typename T>
argoFlap_t IRArgoACBase<T>::convertSwingV(const stdAc::swingv_t position) {
switch (position) {
case stdAc::swingv_t::kHighest:
return argoFlap_t::FLAP_1;
case stdAc::swingv_t::kHigh:
return argoFlap_t::FLAP_2;
case stdAc::swingv_t::kUpperMiddle:
return argoFlap_t::FLAP_3;
case stdAc::swingv_t::kMiddle:
return argoFlap_t::FLAP_4;
case stdAc::swingv_t::kLow:
return argoFlap_t::FLAP_5;
case stdAc::swingv_t::kLowest:
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 argoFlap_t::FLAP_AUTO;
}
}
/// @cond
/// 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.
/// @relates IRArgoACBase\<ARGO_PROTOCOL_T\>
template<>
stdAc::swingv_t IRArgoACBase<ArgoProtocol>::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;
}
}
/// @endcond
/// 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.
/// @relates IRArgoACBase\<ARGO_PROTOCOL_T\>
template<>
stdAc::swingv_t IRArgoACBase<ArgoProtocolWREM3>::toCommonSwingV(
const uint8_t position) {
switch (static_cast<argoFlap_t>(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] command The native setting to be converted.
/// @return The stdAc equivalent of the native setting.
template<typename T>
stdAc::ac_command_t IRArgoACBase<T>::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::kSensorTempReport;
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.
template<typename T>
stdAc::opmode_t IRArgoACBase<T>::toCommonMode(const argoMode_t mode) {
switch (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;
}
}
/// 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.
template<typename T>
stdAc::fanspeed_t IRArgoACBase<T>::toCommonFanSpeed(const argoFan_t speed) {
switch (speed) {
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;
}
}
/// Convert the current internal state into its stdAc::state_t equivalent.
/// @return The stdAc equivalent of the native settings.
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(getModeEx());
result.celsius = true;
result.degrees = getTemp();
result.sensorTemperature = getSensorTemp();
result.iFeel = getiFeel();
result.fanspeed = toCommonFanSpeed(getFanEx());
result.turbo = _.Max;
result.sleep = _.Night ? 0 : -1;
// Not supported.
result.swingv = stdAc::swingv_t::kOff;
result.swingh = stdAc::swingh_t::kOff;
result.light = false;
result.filter = false;
result.econo = false;
result.quiet = false;
result.clean = false;
result.beep = false;
result.clock = -1;
return result;
}
/// 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.sensorTemperature = getSensorTemp();
result.iFeel = getiFeel();
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();
}
// 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 <typename E>
constexpr typename std::underlying_type<E>::type to_underlying(E e) noexcept {
return static_cast<typename std::underlying_type<E>::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(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);
result += addIntToString(_.Mode, kModeStr);
result += kSpaceLBraceStr;
switch (_.Mode) {
case kArgoAuto:
result += kAutoStr;
break;
case kArgoCool:
result += kCoolStr;
break;
case kArgoHeat:
result += kHeatStr;
break;
case kArgoDry:
result += kDryStr;
break;
case kArgoHeatAuto:
result += kHeatStr;
result += ' ';
result += kAutoStr;
break;
case kArgoOff:
result += kOffStr;
break;
default:
result += kUnknownStr;
}
result += ')';
result += addIntToString(_.Fan, kFanStr);
result += kSpaceLBraceStr;
switch (_.Fan) {
case kArgoFanAuto:
result += kAutoStr;
break;
case kArgoFan3:
result += kMaxStr;
break;
case kArgoFan1:
result += kMinStr;
break;
case kArgoFan2:
result += kMedStr;
break;
default:
result += kUnknownStr;
}
result += ')';
result += addTempToString(getTemp());
result += addTempToString(getSensorTemp(), true, true, true);
result += addBoolToString(_.Max, kMaxStr);
result += addBoolToString(_.iFeel, kIFeelStr);
result += addBoolToString(_.Night, kNightStr);
}
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<uint16_t>(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) {
uint8_t 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<argoWeekday>((_.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<x> 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<argoTimerType_t>(_.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<uint16_t>((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<uint16_t>((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<uint16_t>((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<argoWeekday>& days) {
uint8_t daysBitmap = 0;
for (const argoWeekday& 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<argoWeekday> IRArgoAC_WREM3::getScheduleTimerActiveDays(void) const {
std::set<argoWeekday> 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<argoWeekday>(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 {
String commandTypeToString(argoIrMessageType_t type, uint8_t channel) {
String result = irutils::irCommandTypeToString(to_underlying(type),
to_underlying(argoIrMessageType_t::AC_CONTROL),
to_underlying(argoIrMessageType_t::IFEEL_TEMP_REPORT),
to_underlying(argoIrMessageType_t::TIMER_COMMAND),
to_underlying(argoIrMessageType_t::CONFIG_PARAM_SET));
result += irutils::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
argoIrMessageType_t 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(getSensorTemp(), 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 += addTimerModeToString(to_underlying(getTimerType()),
to_underlying(argoTimerType_t::NO_TIMER),
to_underlying(argoTimerType_t::DELAY_TIMER),
to_underlying(argoTimerType_t::SCHEDULE_TIMER_1),
to_underlying(argoTimerType_t::SCHEDULE_TIMER_2),
to_underlying(argoTimerType_t::SCHEDULE_TIMER_3));
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(daysBitmaskToString(
getTimerActiveDaysBitmap()), 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;
}
uint8_t preamble = state[0] & 0x0F;
return preamble == kArgoWrem3Preamble;
}
#if DECODE_ARGO
/// 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.
/// @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 based soley off sendArgo(). We have no actual captures
/// to test this against. If you have one of these units, please let us know.
bool IRrecv::decodeArgo(decode_results *results, uint16_t offset,
const uint16_t nbits,
const bool strict) {
if (strict && nbits != kArgoBits) return false;
// Match Header + Data
if (!matchGeneric(results->rawbuf + offset, results->state,
results->rawlen - offset, nbits,
kArgoHdrMark, kArgoHdrSpace,
kArgoBitMark, kArgoOneSpace,
kArgoBitMark, kArgoZeroSpace,
0, 0, // Footer (None, allegedly. This seems very wrong.)
true, _tolerance, 0, false)) return false;
// Compliance
// Verify we got a valid checksum.
if (strict && !IRArgoAC::validChecksum(results->state, kArgoStateLength)) {
return false;
}
// Success
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;
}
/// 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;
}
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, 0,
false);
if (!bytesRead) {
return false;
}
// If 'strict', assert it is a valid WREM-3 'model' protocolar message
// vs. just 'any ARGO'
if (strict &&
!IRArgoAC_WREM3::isValidWrem3Message(results->state, nbits, true)) {
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 verifyChecksum Whether to perform checksum verification
/// @return True if the message is a WREM-3 one
bool IRArgoAC_WREM3::isValidWrem3Message(const uint8_t state[],
const uint16_t nbits,
bool verifyChecksum) {
if ((nbits % 8) != 0) {
return false; // WREM-3 protocol always has a full byte length commands
}
uint16_t stateLengthBytes = std::min(static_cast<uint16_t>(nbits / 8),
kStateSizeMax);
if (!IRArgoAC_WREM3::hasValidPreamble(state, stateLengthBytes)) {
return false;
}
argoIrMessageType_t messageType = IRArgoACBase<ArgoProtocolWREM3>
::getMessageType(state, stateLengthBytes);
switch (messageType) {
case argoIrMessageType_t::AC_CONTROL :
if (stateLengthBytes != kArgo3AcControlStateLength) { return false; }
break;
case argoIrMessageType_t::CONFIG_PARAM_SET:
if (stateLengthBytes != kArgo3ConfigStateLength) { return false; }
break;
case argoIrMessageType_t::TIMER_COMMAND:
if (stateLengthBytes != kArgo3TimerStateLength) { return false; }
break;
case argoIrMessageType_t::IFEEL_TEMP_REPORT:
if (stateLengthBytes != kArgo3iFeelReportStateLength) { return false; }
break;
default:
return false;
}
// Compliance: Verify we got a valid checksum.
if (verifyChecksum &&
!IRArgoAC_WREM3::validChecksum(state, stateLengthBytes)) {
return false;
}
return true;
}
#endif // DECODE_ARGO
// force template instantiation
template class IRArgoACBase<ArgoProtocol>;
template class IRArgoACBase<ArgoProtocolWREM3>;