599 lines
23 KiB
C++
599 lines
23 KiB
C++
// Copyright 2020 Christian Nilsson
|
|
//
|
|
/// @file
|
|
/// @brief Corona A/C protocol
|
|
/// @note Unsupported:
|
|
/// - Auto/Max button press (special format)
|
|
|
|
#include "ir_Corona.h"
|
|
#include <algorithm>
|
|
#include <cstring>
|
|
#include "IRac.h"
|
|
#include "IRrecv.h"
|
|
#include "IRsend.h"
|
|
#include "IRtext.h"
|
|
#include "IRutils.h"
|
|
|
|
using irutils::addBoolToString;
|
|
using irutils::addLabeledString;
|
|
using irutils::addModeToString;
|
|
using irutils::addTempToString;
|
|
using irutils::addFanToString;
|
|
using irutils::minsToString;
|
|
using irutils::setBit;
|
|
using irutils::setBits;
|
|
|
|
// Constants
|
|
const uint16_t kCoronaAcHdrMark = 3500;
|
|
const uint16_t kCoronaAcHdrSpace = 1680;
|
|
const uint16_t kCoronaAcBitMark = 450;
|
|
const uint16_t kCoronaAcOneSpace = 1270;
|
|
const uint16_t kCoronaAcZeroSpace = 420;
|
|
const uint16_t kCoronaAcSpaceGap = 10800;
|
|
const uint16_t kCoronaAcFreq = 38000; // Hz.
|
|
const uint16_t kCoronaAcOverheadShort = 3;
|
|
const uint16_t kCoronaAcOverhead = 11; // full message
|
|
const uint8_t kCoronaTolerance = 5; // +5%
|
|
|
|
#if SEND_CORONA_AC
|
|
/// Send a CoronaAc formatted message.
|
|
/// Status: STABLE / Working on real device.
|
|
/// @param[in] data An array of bytes containing the IR command.
|
|
/// @param[in] nbytes Nr. of bytes of data in the array.
|
|
/// e.g.
|
|
/// @code
|
|
/// uint8_t data[kCoronaAcStateLength] = {
|
|
/// 0x28, 0x61, 0x3D, 0x19, 0xE6, 0x37, 0xC8,
|
|
/// 0x28, 0x61, 0x6D, 0xFF, 0x00, 0xFF, 0x00,
|
|
/// 0x28, 0x61, 0xCD, 0xFF, 0x00, 0xFF, 0x00};
|
|
/// @endcode
|
|
/// @param[in] repeat Nr. of times the message is to be repeated.
|
|
void IRsend::sendCoronaAc(const uint8_t data[],
|
|
const uint16_t nbytes, const uint16_t repeat) {
|
|
if (nbytes < kCoronaAcSectionBytes) return;
|
|
if (kCoronaAcSectionBytes < nbytes &&
|
|
nbytes < kCoronaAcStateLength) return;
|
|
for (uint16_t r = 0; r <= repeat; r++) {
|
|
uint16_t pos = 0;
|
|
// Data Section #1 - 3 loop
|
|
// e.g.
|
|
// bits = 56; bytes = 7;
|
|
// #1 *(data + pos) = {0x28, 0x61, 0x3D, 0x19, 0xE6, 0x37, 0xC8};
|
|
// #2 *(data + pos) = {0x28, 0x61, 0x6D, 0xFF, 0x00, 0xFF, 0x00};
|
|
// #3 *(data + pos) = {0x28, 0x61, 0xCD, 0xFF, 0x00, 0xFF, 0x00};
|
|
for (uint8_t section = 0; section < kCoronaAcSections; section++) {
|
|
sendGeneric(kCoronaAcHdrMark, kCoronaAcHdrSpace,
|
|
kCoronaAcBitMark, kCoronaAcOneSpace,
|
|
kCoronaAcBitMark, kCoronaAcZeroSpace,
|
|
kCoronaAcBitMark, kCoronaAcSpaceGap,
|
|
data + pos, kCoronaAcSectionBytes,
|
|
kCoronaAcFreq, false, kNoRepeat, kDutyDefault);
|
|
pos += kCoronaAcSectionBytes; // Adjust by how many bytes was sent
|
|
// don't send more data then what we have
|
|
if (nbytes <= pos)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
#endif // SEND_CORONA_AC
|
|
|
|
#if DECODE_CORONA_AC
|
|
/// Decode the supplied CoronaAc message.
|
|
/// Status: STABLE / Appears to be working.
|
|
/// @param[in,out] results Ptr to the data to decode & where to store it
|
|
/// @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.
|
|
bool IRrecv::decodeCoronaAc(decode_results *results, uint16_t offset,
|
|
const uint16_t nbits, const bool strict) {
|
|
bool isLong = results->rawlen >= kCoronaAcBits * 2;
|
|
if (results->rawlen < 2 * nbits +
|
|
(isLong ? kCoronaAcOverhead : kCoronaAcOverheadShort)
|
|
- offset)
|
|
return false; // Too short a message to match.
|
|
if (strict && nbits != kCoronaAcBits && nbits != kCoronaAcBitsShort)
|
|
return false;
|
|
|
|
uint16_t pos = 0;
|
|
uint16_t used = 0;
|
|
|
|
// Data Section #1 - 3 loop
|
|
// e.g.
|
|
// bits = 56; bytes = 7;
|
|
// #1 *(results->state + pos) = {0x28, 0x61, 0x3D, 0x19, 0xE6, 0x37, 0xC8};
|
|
// #2 *(results->state + pos) = {0x28, 0x61, 0x6D, 0xFF, 0x00, 0xFF, 0x00};
|
|
// #3 *(results->state + pos) = {0x28, 0x61, 0xCD, 0xFF, 0x00, 0xFF, 0x00};
|
|
for (uint8_t section = 0; section < kCoronaAcSections; section++) {
|
|
DPRINT(uint64ToString(section));
|
|
used = matchGeneric(results->rawbuf + offset, results->state + pos,
|
|
results->rawlen - offset, kCoronaAcBitsShort,
|
|
kCoronaAcHdrMark, kCoronaAcHdrSpace,
|
|
kCoronaAcBitMark, kCoronaAcOneSpace,
|
|
kCoronaAcBitMark, kCoronaAcZeroSpace,
|
|
kCoronaAcBitMark, kCoronaAcSpaceGap, true,
|
|
_tolerance + kCoronaTolerance, kMarkExcess, false);
|
|
if (used == 0) return false; // We failed to find any data.
|
|
// short versions section 0 is special
|
|
if (strict && !IRCoronaAc::validSection(results->state, pos,
|
|
isLong ? section : 3))
|
|
return false;
|
|
offset += used; // Adjust for how much of the message we read.
|
|
pos += kCoronaAcSectionBytes; // Adjust by how many bytes of data was read
|
|
// don't read more data then what we have
|
|
if (results->rawlen <= offset)
|
|
break;
|
|
}
|
|
|
|
// Re-check we got the correct size/length due to the way we read the data.
|
|
if (strict && pos * 8 != kCoronaAcBits && pos * 8 != kCoronaAcBitsShort) {
|
|
DPRINTLN("strict bit match fail");
|
|
return false;
|
|
}
|
|
|
|
// Success
|
|
results->decode_type = decode_type_t::CORONA_AC;
|
|
results->bits = pos * 8;
|
|
// 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;
|
|
}
|
|
#endif // DECODE_CORONA_AC
|
|
|
|
/// Class constructor for handling detailed Corona A/C messages.
|
|
/// @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?
|
|
IRCoronaAc::IRCoronaAc(const uint16_t pin, const bool inverted,
|
|
const bool use_modulation)
|
|
: _irsend(pin, inverted, use_modulation) { stateReset(); }
|
|
|
|
/// Reset the internal state to a fixed known good state.
|
|
/// @note The state is powered off.
|
|
void IRCoronaAc::stateReset(void) {
|
|
// known good state
|
|
remote_state[kCoronaAcSectionData0Pos] = kCoronaAcSectionData0Base;
|
|
remote_state[kCoronaAcSectionData1Pos] = 0x00; // ensure no unset mem
|
|
setPowerButton(true); // we default to this on, any timer removes it
|
|
setTemp(kCoronaAcMinTemp);
|
|
setMode(kCoronaAcModeCool);
|
|
setFan(kCoronaAcFanAuto);
|
|
setOnTimer(kCoronaAcTimerOff);
|
|
setOffTimer(kCoronaAcTimerOff);
|
|
// headers and checks are fixed in getRaw by checksum(remote_state)
|
|
}
|
|
|
|
/// Get the byte that identifies the section
|
|
/// @param[in] section Index of the section 0-2,
|
|
/// 3 and above is used as the special case for short message
|
|
/// @return The byte used for the section
|
|
uint8_t IRCoronaAc::getSectionByte(const uint8_t section) {
|
|
// base byte
|
|
uint8_t b = kCoronaAcSectionLabelBase;
|
|
// 2 enabled bits shifted 0-2 bits depending on section
|
|
if (section >= 3)
|
|
return 0b10010000 | b;
|
|
setBits(&b, kHighNibble, kNibbleSize, 0b11 << section);
|
|
return b;
|
|
}
|
|
|
|
/// Check that a CoronaAc Section part is valid with section byte and inverted
|
|
/// @param[in] state An array of bytes containing the section
|
|
/// @param[in] pos Where to start in the state array
|
|
/// @param[in] section Which section to work with
|
|
/// Used to get the section byte, and is validated against pos
|
|
/// @return true if section is valid, otherwise false
|
|
bool IRCoronaAc::validSection(const uint8_t state[], const uint16_t pos,
|
|
const uint8_t section) {
|
|
// sanity check, pos must match section, section 4 is at pos 0
|
|
if ((section % kCoronaAcSections) * kCoronaAcSectionBytes != pos)
|
|
return false;
|
|
// all individual sections has the same prefix
|
|
if (state[pos + kCoronaAcSectionHeader0Pos] != kCoronaAcSectionHeader0) {
|
|
DPRINT("State ");
|
|
DPRINT(pos + kCoronaAcSectionHeader0Pos);
|
|
DPRINT(" expected 0x28 was ");
|
|
DPRINTLN(uint64ToString(state[pos + kCoronaAcSectionHeader0Pos], 16));
|
|
return false;
|
|
}
|
|
if (state[pos + kCoronaAcSectionHeader1Pos] != kCoronaAcSectionHeader1) {
|
|
DPRINT("State ");
|
|
DPRINT(pos + kCoronaAcSectionHeader1Pos);
|
|
DPRINT(" expected 0x61 was ");
|
|
DPRINTLN(uint64ToString(state[pos + kCoronaAcSectionHeader1Pos], 16));
|
|
return false;
|
|
}
|
|
|
|
// checking section byte
|
|
if (state[pos + kCoronaAcSectionLabelPos] != getSectionByte(section)) {
|
|
DPRINT("check 2 not matching, got ");
|
|
DPRINT(uint64ToString(state[pos + kCoronaAcSectionLabelPos], 16));
|
|
DPRINT(" expected ");
|
|
DPRINTLN(uint64ToString(getSectionByte(section), 16));
|
|
return false;
|
|
}
|
|
|
|
// checking inverts
|
|
uint8_t d0invinv = ~state[pos + kCoronaAcSectionData0InvPos];
|
|
if (state[pos + kCoronaAcSectionData0Pos] != d0invinv) {
|
|
DPRINT("inverted 3 - 4 not matching, got ");
|
|
DPRINT(uint64ToString(state[pos + kCoronaAcSectionData0Pos], 16));
|
|
DPRINT(" vs ");
|
|
DPRINTLN(uint64ToString(state[pos + kCoronaAcSectionData0InvPos], 16));
|
|
return false;
|
|
}
|
|
uint8_t d1invinv = ~state[pos + kCoronaAcSectionData1InvPos];
|
|
if (state[pos + kCoronaAcSectionData1Pos] != d1invinv) {
|
|
DPRINT("inverted 5 - 6 not matching, got ");
|
|
DPRINT(uint64ToString(state[pos + kCoronaAcSectionData1Pos], 16));
|
|
DPRINT(" vs ");
|
|
DPRINTLN(uint64ToString(state[pos + kCoronaAcSectionData1InvPos], 16));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// Calculate and set the check values for the internal state.
|
|
/// @param[in,out] data The array to be modified
|
|
void IRCoronaAc::checksum(uint8_t* data) {
|
|
uint8_t pos;
|
|
for (uint8_t section = 0; section < kCoronaAcSections; section++) {
|
|
pos = section * kCoronaAcSectionBytes;
|
|
data[pos + kCoronaAcSectionHeader0Pos] = kCoronaAcSectionHeader0;
|
|
data[pos + kCoronaAcSectionHeader1Pos] = kCoronaAcSectionHeader1;
|
|
data[pos + kCoronaAcSectionLabelPos] = getSectionByte(section);
|
|
data[pos + kCoronaAcSectionData0InvPos] =
|
|
~data[pos + kCoronaAcSectionData0Pos];
|
|
data[pos + kCoronaAcSectionData1InvPos] =
|
|
~data[pos + kCoronaAcSectionData1Pos];
|
|
}
|
|
}
|
|
|
|
/// Set up hardware to be able to send a message.
|
|
void IRCoronaAc::begin(void) { _irsend.begin(); }
|
|
|
|
#if SEND_CORONA_AC
|
|
/// Send the current internal state as an IR message.
|
|
/// @param[in] repeat Nr. of times the message will be repeated.
|
|
void IRCoronaAc::send(const uint16_t repeat) {
|
|
// if no timer, always send once without power press
|
|
if (!getOnTimer() && !getOffTimer()) {
|
|
setPowerButton(false);
|
|
_irsend.sendCoronaAc(getRaw(), kCoronaAcStateLength, repeat);
|
|
// and then with power press
|
|
setPowerButton(true);
|
|
}
|
|
_irsend.sendCoronaAc(getRaw(), kCoronaAcStateLength, repeat);
|
|
}
|
|
#endif // SEND_CORONA_AC
|
|
|
|
/// Get a copy of the internal state as a valid code for this protocol.
|
|
/// @return A Ptr to a valid code for this protocol based on the current
|
|
/// internal state.
|
|
/// @note To get stable AC state, if no timers, send once
|
|
/// without PowerButton set, and once with
|
|
uint8_t* IRCoronaAc::getRaw(void) {
|
|
checksum(remote_state); // Ensure correct check bits before sending.
|
|
return remote_state;
|
|
}
|
|
|
|
/// Set the internal state from a valid code for this protocol.
|
|
/// @param[in] new_code A valid state for this protocol.
|
|
/// @param[in] length of the new_code array.
|
|
void IRCoronaAc::setRaw(const uint8_t new_code[], const uint16_t length) {
|
|
memcpy(remote_state, new_code, std::min(length, kCoronaAcStateLength));
|
|
}
|
|
|
|
/// Set the temp in deg C.
|
|
/// @param[in] temp The desired temperature in Celsius.
|
|
void IRCoronaAc::setTemp(const uint8_t temp) {
|
|
uint8_t degrees = std::max(temp, kCoronaAcMinTemp);
|
|
degrees = std::min(degrees, kCoronaAcMaxTemp);
|
|
setBits(&remote_state[kCoronaAcSectionData1Pos], kCoronaAcTempOffset,
|
|
kCoronaAcTempSize, degrees - kCoronaAcMinTemp + 1);
|
|
}
|
|
|
|
/// Get the current temperature from the internal state.
|
|
/// @return The current temperature in Celsius.
|
|
uint8_t IRCoronaAc::getTemp(void) {
|
|
return GETBITS8(remote_state[kCoronaAcSectionData1Pos], kCoronaAcTempOffset,
|
|
kCoronaAcTempSize) + kCoronaAcMinTemp - 1;
|
|
}
|
|
|
|
/// Change the power setting. (in practice Standby, remote power)
|
|
/// @param[in] on true, the setting is on. false, the setting is off.
|
|
void IRCoronaAc::_setPower(const bool on) {
|
|
setBit(&remote_state[kCoronaAcSectionData1Pos], kCoronaAcPowerOffset, on);
|
|
}
|
|
|
|
/// Change the power setting. (in practice Standby, remote power)
|
|
/// @param[in] on true, the setting is on. false, the setting is off.
|
|
/// @note If changed, setPowerButton is also needed,
|
|
/// unless timer is or was active
|
|
void IRCoronaAc::setPower(const bool on) {
|
|
_setPower(on);
|
|
// setting power state resets timers that would cause the state
|
|
if (on)
|
|
setOnTimer(kCoronaAcTimerOff);
|
|
else
|
|
setOffTimer(kCoronaAcTimerOff);
|
|
}
|
|
|
|
/// Get the current power setting. (in practice Standby, remote power)
|
|
/// @return true, the setting is on. false, the setting is off.
|
|
bool IRCoronaAc::getPower(void) {
|
|
return GETBIT8(remote_state[kCoronaAcSectionData1Pos], kCoronaAcPowerOffset);
|
|
}
|
|
|
|
/// Change the power button setting.
|
|
/// @param[in] on true, the setting is on. false, the setting is off.
|
|
/// @note this sets that the AC should set power,
|
|
/// use setPower to define if the AC should end up as on or off
|
|
/// When no timer is active, the below is a truth table
|
|
/// With AC On, a command with setPower and setPowerButton gives nothing
|
|
/// With AC On, a command with setPower but not setPowerButton is ok
|
|
/// With AC Off, a command with setPower but not setPowerButton gives nothing
|
|
/// With AC Off, a command with setPower and setPowerButton is ok
|
|
void IRCoronaAc::setPowerButton(const bool on) {
|
|
setBit(&remote_state[kCoronaAcSectionData1Pos],
|
|
kCoronaAcPowerButtonOffset, on);
|
|
}
|
|
|
|
/// Get the value of the current power button setting.
|
|
/// @return true, the setting is on. false, the setting is off.
|
|
bool IRCoronaAc::getPowerButton(void) {
|
|
return GETBIT8(remote_state[kCoronaAcSectionData1Pos],
|
|
kCoronaAcPowerButtonOffset);
|
|
}
|
|
|
|
/// Change the power setting to On.
|
|
void IRCoronaAc::on(void) { setPower(true); }
|
|
|
|
/// Change the power setting to Off.
|
|
void IRCoronaAc::off(void) { setPower(false); }
|
|
|
|
/// Get the operating mode setting of the A/C.
|
|
/// @return The current operating mode setting.
|
|
uint8_t IRCoronaAc::getMode(void) {
|
|
return GETBITS8(remote_state[kCoronaAcSectionData1Pos],
|
|
kCoronaAcModeOffset, kCoronaAcModeSize);
|
|
}
|
|
|
|
/// Set the operating mode of the A/C.
|
|
/// @param[in] mode The desired operating mode.
|
|
void IRCoronaAc::setMode(const uint8_t mode) {
|
|
switch (mode) {
|
|
case kCoronaAcModeCool:
|
|
case kCoronaAcModeDry:
|
|
case kCoronaAcModeFan:
|
|
case kCoronaAcModeHeat:
|
|
setBits(&remote_state[kCoronaAcSectionData1Pos],
|
|
kCoronaAcModeOffset, kCoronaAcModeSize,
|
|
mode);
|
|
return;
|
|
default:
|
|
this->setMode(kCoronaAcModeCool);
|
|
}
|
|
}
|
|
|
|
/// Convert a standard A/C mode into its native mode.
|
|
/// @param[in] mode A stdAc::opmode_t mode to be
|
|
/// converted to it's native equivalent
|
|
/// @return The corresponding native mode.
|
|
uint8_t IRCoronaAc::convertMode(const stdAc::opmode_t mode) {
|
|
switch (mode) {
|
|
case stdAc::opmode_t::kFan: return kCoronaAcModeFan;
|
|
case stdAc::opmode_t::kDry: return kCoronaAcModeDry;
|
|
case stdAc::opmode_t::kHeat: return kCoronaAcModeHeat;
|
|
default: return kCoronaAcModeCool;
|
|
}
|
|
}
|
|
|
|
/// Convert a native mode to it's common stdAc::opmode_t equivalent.
|
|
/// @param[in] mode A native operation mode to be converted.
|
|
/// @return The corresponding common stdAc::opmode_t mode.
|
|
stdAc::opmode_t IRCoronaAc::toCommonMode(const uint8_t mode) {
|
|
switch (mode) {
|
|
case kCoronaAcModeFan: return stdAc::opmode_t::kFan;
|
|
case kCoronaAcModeDry: return stdAc::opmode_t::kDry;
|
|
case kCoronaAcModeHeat: return stdAc::opmode_t::kHeat;
|
|
default: return stdAc::opmode_t::kCool;
|
|
}
|
|
}
|
|
|
|
/// Get the operating speed of the A/C Fan
|
|
/// @return The current operating fan speed setting
|
|
uint8_t IRCoronaAc::getFan(void) {
|
|
return GETBITS8(remote_state[kCoronaAcSectionData0Pos],
|
|
kCoronaAcFanOffset, kCoronaAcFanSize);
|
|
}
|
|
|
|
/// Set the operating speed of the A/C Fan
|
|
/// @param[in] speed The desired fan speed
|
|
void IRCoronaAc::setFan(const uint8_t speed) {
|
|
if (speed > kCoronaAcFanHigh)
|
|
setFan(kCoronaAcFanAuto);
|
|
else
|
|
setBits(&remote_state[kCoronaAcSectionData0Pos],
|
|
kCoronaAcFanOffset, kCoronaAcFanSize, speed);
|
|
}
|
|
|
|
/// Change the powersave setting.
|
|
/// @param[in] on true, the setting is on. false, the setting is off.
|
|
void IRCoronaAc::setEcono(const bool on) {
|
|
setBit(&remote_state[kCoronaAcSectionData0Pos], kCoronaAcPowerSaveOffset, on);
|
|
}
|
|
|
|
/// Get the value of the current powersave setting.
|
|
/// @return true, the setting is on. false, the setting is off.
|
|
bool IRCoronaAc::getEcono(void) {
|
|
return GETBIT8(remote_state[kCoronaAcSectionData0Pos],
|
|
kCoronaAcPowerSaveOffset);
|
|
}
|
|
|
|
/// Convert a standard A/C Fan speed into its native fan speed.
|
|
/// @param[in] speed The desired stdAc::fanspeed_t fan speed
|
|
/// @return The given fan speed in native format
|
|
uint8_t IRCoronaAc::convertFan(const stdAc::fanspeed_t speed) {
|
|
switch (speed) {
|
|
case stdAc::fanspeed_t::kMin:
|
|
case stdAc::fanspeed_t::kLow: return kCoronaAcFanLow;
|
|
case stdAc::fanspeed_t::kMedium: return kCoronaAcFanMedium;
|
|
case stdAc::fanspeed_t::kHigh:
|
|
case stdAc::fanspeed_t::kMax: return kCoronaAcFanHigh;
|
|
default: return kCoronaAcFanAuto;
|
|
}
|
|
}
|
|
|
|
/// Convert a native fan speed to it's common equivalent.
|
|
/// @param[in] speed The desired native fan speed
|
|
/// @return The given fan speed in stdAc::fanspeed_t format
|
|
stdAc::fanspeed_t IRCoronaAc::toCommonFanSpeed(const uint8_t speed) {
|
|
switch (speed) {
|
|
case kCoronaAcFanHigh: return stdAc::fanspeed_t::kHigh;
|
|
case kCoronaAcFanMedium: return stdAc::fanspeed_t::kMedium;
|
|
case kCoronaAcFanLow: return stdAc::fanspeed_t::kLow;
|
|
default: return stdAc::fanspeed_t::kAuto;
|
|
}
|
|
}
|
|
|
|
/// Set the Vertical Swing toggle setting
|
|
/// @param[in] on true, the setting is on. false, the setting is off.
|
|
/// @note This is a button press, and not a state
|
|
/// after sending it once you should turn it off
|
|
void IRCoronaAc::setSwingVToggle(const bool on) {
|
|
setBit(&remote_state[kCoronaAcSectionData0Pos],
|
|
kCoronaAcSwingVToggleOffset, on);
|
|
}
|
|
|
|
/// Get the Vertical Swing toggle setting
|
|
/// @return true, the setting is on. false, the setting is off.
|
|
bool IRCoronaAc::getSwingVToggle(void) {
|
|
return GETBIT64(remote_state[kCoronaAcSectionData0Pos],
|
|
kCoronaAcSwingVToggleOffset);
|
|
}
|
|
|
|
/// Set the Timer time
|
|
/// @param[in] section index of section, used for offset.
|
|
/// @param[in] nr_of_mins Number of minutes to set the timer to.
|
|
/// (non in range value is disable).
|
|
/// Valid is from 1 minute to 12 hours
|
|
void IRCoronaAc::_setTimer(const uint8_t section, const uint16_t nr_of_mins) {
|
|
// default to off
|
|
uint16_t hsecs = kCoronaAcTimerOff;
|
|
if (1 <= nr_of_mins && nr_of_mins <= kCoronaAcTimerMax)
|
|
hsecs = nr_of_mins * kCoronaAcTimerUnitsPerMin;
|
|
|
|
uint8_t pos = section * kCoronaAcSectionBytes;
|
|
// convert 16 bit value to separate 8 bit parts
|
|
remote_state[pos + kCoronaAcSectionData1Pos] = hsecs >> 8;
|
|
remote_state[pos + kCoronaAcSectionData0Pos] = hsecs;
|
|
|
|
// if any timer is enabled, then (remote) ac must be on (Standby)
|
|
if (hsecs != kCoronaAcTimerOff) {
|
|
_setPower(true);
|
|
setPowerButton(false);
|
|
}
|
|
}
|
|
|
|
/// Get the current Timer time
|
|
/// @return The number of minutes it is set for. 0 means it's off.
|
|
/// @note The A/C protocol supports 2 second increments
|
|
uint16_t IRCoronaAc::_getTimer(const uint8_t section) {
|
|
uint8_t pos = section * kCoronaAcSectionBytes;
|
|
// combine separate 8 bit parts to 16 bit value
|
|
uint16_t hsecs = remote_state[pos + kCoronaAcSectionData1Pos] << 8 |
|
|
remote_state[pos + kCoronaAcSectionData0Pos];
|
|
|
|
if (hsecs == kCoronaAcTimerOff)
|
|
return 0;
|
|
|
|
return hsecs / kCoronaAcTimerUnitsPerMin;
|
|
}
|
|
|
|
/// Get the current On Timer time
|
|
/// @return The number of minutes it is set for. 0 means it's off.
|
|
uint16_t IRCoronaAc::getOnTimer(void) {
|
|
return _getTimer(kCoronaAcOnTimerSection);
|
|
}
|
|
|
|
/// Set the On Timer time
|
|
/// @param[in] nr_of_mins Number of minutes to set the timer to.
|
|
/// (0 or kCoronaAcTimerOff is disable).
|
|
void IRCoronaAc::setOnTimer(const uint16_t nr_of_mins) {
|
|
_setTimer(kCoronaAcOnTimerSection, nr_of_mins);
|
|
// if we set a timer value, clear the other timer
|
|
if (getOnTimer())
|
|
setOffTimer(kCoronaAcTimerOff);
|
|
}
|
|
|
|
/// Get the current Off Timer time
|
|
/// @return The number of minutes it is set for. 0 means it's off.
|
|
uint16_t IRCoronaAc::getOffTimer(void) {
|
|
return _getTimer(kCoronaAcOffTimerSection);
|
|
}
|
|
|
|
/// Set the Off Timer time
|
|
/// @param[in] nr_of_mins Number of minutes to set the timer to.
|
|
/// (0 or kCoronaAcTimerOff is disable).
|
|
void IRCoronaAc::setOffTimer(const uint16_t nr_of_mins) {
|
|
_setTimer(kCoronaAcOffTimerSection, nr_of_mins);
|
|
// if we set a timer value, clear the other timer
|
|
if (getOffTimer())
|
|
setOnTimer(kCoronaAcTimerOff);
|
|
}
|
|
|
|
/// Convert the internal state into a human readable string.
|
|
/// @return The current internal state expressed as a human readable String.
|
|
String IRCoronaAc::toString(void) {
|
|
String result = "";
|
|
result.reserve(140); // Reserve some heap for the string to reduce fragging.
|
|
result += addBoolToString(getPower(), kPowerStr, false);
|
|
result += addBoolToString(getPowerButton(), kPowerButtonStr);
|
|
result += addModeToString(getMode(), 0xFF, kCoronaAcModeCool,
|
|
kCoronaAcModeHeat, kCoronaAcModeDry,
|
|
kCoronaAcModeFan);
|
|
result += addTempToString(getTemp());
|
|
result += addFanToString(getFan(), kCoronaAcFanHigh, kCoronaAcFanLow,
|
|
kCoronaAcFanAuto, kCoronaAcFanAuto,
|
|
kCoronaAcFanMedium);
|
|
result += addBoolToString(getSwingVToggle(), kSwingVToggleStr);
|
|
result += addBoolToString(getEcono(), kEconoStr);
|
|
result += addLabeledString(getOnTimer()
|
|
? minsToString(getOnTimer()) : kOffStr,
|
|
kOnTimerStr);
|
|
result += addLabeledString(getOffTimer()
|
|
? minsToString(getOffTimer()) : kOffStr,
|
|
kOffTimerStr);
|
|
return result;
|
|
}
|
|
|
|
/// Convert the A/C state to it's common stdAc::state_t equivalent.
|
|
/// @return A stdAc::state_t state.
|
|
stdAc::state_t IRCoronaAc::toCommon() {
|
|
stdAc::state_t result;
|
|
result.protocol = decode_type_t::CORONA_AC;
|
|
result.model = -1; // No models used.
|
|
result.power = getPower();
|
|
result.mode = toCommonMode(getMode());
|
|
result.celsius = true;
|
|
result.degrees = getTemp();
|
|
result.fanspeed = toCommonFanSpeed(getFan());
|
|
result.swingv = getSwingVToggle() ?
|
|
stdAc::swingv_t::kAuto : stdAc::swingv_t::kOff;
|
|
result.econo = getEcono();
|
|
// Not supported.
|
|
result.sleep = -1;
|
|
result.swingh = stdAc::swingh_t::kOff;
|
|
result.turbo = false;
|
|
result.quiet = false;
|
|
result.clean = false;
|
|
result.filter = false;
|
|
result.beep = false;
|
|
result.light = false;
|
|
result.clock = -1;
|
|
return result;
|
|
}
|