3070 lines
99 KiB
C++
Executable File
3070 lines
99 KiB
C++
Executable File
/*
|
|
An Arduino sketch to emulate IR Daikin ARC433** & ARC477A1 remote control unit
|
|
Read more at:
|
|
http://harizanov.com/2012/02/control-daikin-air-conditioner-over-the-internet/
|
|
|
|
Copyright 2016 sillyfrog
|
|
Copyright 2017 sillyfrog, crankyoldgit
|
|
Copyright 2018-2019 crankyoldgit
|
|
Copyright 2019 pasna (IRDaikin160 class / Daikin176 class)
|
|
*/
|
|
|
|
#include "ir_Daikin.h"
|
|
#include <algorithm>
|
|
#ifndef ARDUINO
|
|
#include <string>
|
|
#endif
|
|
#include "IRrecv.h"
|
|
#include "IRremoteESP8266.h"
|
|
#include "IRsend.h"
|
|
#ifdef UNIT_TEST
|
|
#include "IRsend_test.h"
|
|
#endif
|
|
#include "IRutils.h"
|
|
|
|
// Constants
|
|
// Ref:
|
|
// https://github.com/mharizanov/Daikin-AC-remote-control-over-the-Internet/tree/master/IRremote
|
|
// http://rdlab.cdmt.vn/project-2013/daikin-ir-protocol
|
|
// https://github.com/crankyoldgit/IRremoteESP8266/issues/582
|
|
|
|
using irutils::addBoolToString;
|
|
using irutils::addIntToString;
|
|
using irutils::addLabeledString;
|
|
using irutils::addModeToString;
|
|
using irutils::addTempToString;
|
|
using irutils::addFanToString;
|
|
using irutils::bcdToUint8;
|
|
using irutils::minsToString;
|
|
using irutils::sumNibbles;
|
|
using irutils::uint8ToBcd;
|
|
|
|
|
|
#if SEND_DAIKIN
|
|
// Send a Daikin A/C message.
|
|
//
|
|
// Args:
|
|
// data: An array of kDaikinStateLength bytes containing the IR command.
|
|
//
|
|
// Status: STABLE
|
|
//
|
|
// Ref:
|
|
// IRDaikinESP.cpp
|
|
// https://github.com/mharizanov/Daikin-AC-remote-control-over-the-Internet/tree/master/IRremote
|
|
// https://github.com/blafois/Daikin-IR-Reverse
|
|
void IRsend::sendDaikin(const unsigned char data[], const uint16_t nbytes,
|
|
const uint16_t repeat) {
|
|
if (nbytes < kDaikinStateLengthShort)
|
|
return; // Not enough bytes to send a proper message.
|
|
|
|
for (uint16_t r = 0; r <= repeat; r++) {
|
|
uint16_t offset = 0;
|
|
// Send the header, 0b00000
|
|
sendGeneric(0, 0, // No header for the header
|
|
kDaikinBitMark, kDaikinOneSpace, kDaikinBitMark,
|
|
kDaikinZeroSpace, kDaikinBitMark, kDaikinZeroSpace + kDaikinGap,
|
|
(uint64_t)0b00000, kDaikinHeaderLength, 38, false, 0, 50);
|
|
// Data #1
|
|
if (nbytes < kDaikinStateLength) { // Are we using the legacy size?
|
|
// Do this as a constant to save RAM and keep in flash memory
|
|
sendGeneric(kDaikinHdrMark, kDaikinHdrSpace, kDaikinBitMark,
|
|
kDaikinOneSpace, kDaikinBitMark, kDaikinZeroSpace,
|
|
kDaikinBitMark, kDaikinZeroSpace + kDaikinGap,
|
|
kDaikinFirstHeader64, 64, 38, false, 0, 50);
|
|
} else { // We are using the newer/more correct size.
|
|
sendGeneric(kDaikinHdrMark, kDaikinHdrSpace, kDaikinBitMark,
|
|
kDaikinOneSpace, kDaikinBitMark, kDaikinZeroSpace,
|
|
kDaikinBitMark, kDaikinZeroSpace + kDaikinGap,
|
|
data, kDaikinSection1Length, 38, false, 0, 50);
|
|
offset += kDaikinSection1Length;
|
|
}
|
|
// Data #2
|
|
sendGeneric(kDaikinHdrMark, kDaikinHdrSpace, kDaikinBitMark,
|
|
kDaikinOneSpace, kDaikinBitMark, kDaikinZeroSpace,
|
|
kDaikinBitMark, kDaikinZeroSpace + kDaikinGap,
|
|
data + offset, kDaikinSection2Length, 38, false, 0, 50);
|
|
offset += kDaikinSection2Length;
|
|
// Data #3
|
|
sendGeneric(kDaikinHdrMark, kDaikinHdrSpace, kDaikinBitMark,
|
|
kDaikinOneSpace, kDaikinBitMark, kDaikinZeroSpace,
|
|
kDaikinBitMark, kDaikinZeroSpace + kDaikinGap,
|
|
data + offset, nbytes - offset, 38, false, 0, 50);
|
|
}
|
|
}
|
|
#endif // SEND_DAIKIN
|
|
|
|
IRDaikinESP::IRDaikinESP(const uint16_t pin, const bool inverted,
|
|
const bool use_modulation)
|
|
: _irsend(pin, inverted, use_modulation) { stateReset(); }
|
|
|
|
void IRDaikinESP::begin(void) { _irsend.begin(); }
|
|
|
|
#if SEND_DAIKIN
|
|
void IRDaikinESP::send(const uint16_t repeat) {
|
|
this->checksum();
|
|
_irsend.sendDaikin(remote, kDaikinStateLength, repeat);
|
|
}
|
|
#endif // SEND_DAIKIN
|
|
|
|
// Verify the checksums are valid for a given state.
|
|
// Args:
|
|
// state: The array to verify the checksums of.
|
|
// length: The size of the state.
|
|
// Returns:
|
|
// A boolean.
|
|
bool IRDaikinESP::validChecksum(uint8_t state[], const uint16_t length) {
|
|
// Data #1
|
|
if (length < kDaikinSection1Length ||
|
|
state[kDaikinByteChecksum1] != sumBytes(state, kDaikinSection1Length - 1))
|
|
return false;
|
|
// Data #2
|
|
if (length < kDaikinSection1Length + kDaikinSection2Length ||
|
|
state[kDaikinByteChecksum2] != sumBytes(state + kDaikinSection1Length,
|
|
kDaikinSection2Length - 1))
|
|
return false;
|
|
// Data #3
|
|
if (length < kDaikinSection1Length + kDaikinSection2Length + 2 ||
|
|
state[length - 1] != sumBytes(state + kDaikinSection1Length +
|
|
kDaikinSection2Length,
|
|
length - (kDaikinSection1Length +
|
|
kDaikinSection2Length) - 1))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
// Calculate and set the checksum values for the internal state.
|
|
void IRDaikinESP::checksum(void) {
|
|
remote[kDaikinByteChecksum1] = sumBytes(remote, kDaikinSection1Length - 1);
|
|
remote[kDaikinByteChecksum2] = sumBytes(remote + kDaikinSection1Length,
|
|
kDaikinSection2Length - 1);
|
|
remote[kDaikinByteChecksum3] = sumBytes(remote + kDaikinSection1Length +
|
|
kDaikinSection2Length,
|
|
kDaikinSection3Length - 1);
|
|
}
|
|
|
|
void IRDaikinESP::stateReset(void) {
|
|
for (uint8_t i = 0; i < kDaikinStateLength; i++) remote[i] = 0x0;
|
|
|
|
remote[0] = 0x11;
|
|
remote[1] = 0xDA;
|
|
remote[2] = 0x27;
|
|
remote[4] = 0xC5;
|
|
// remote[7] is a checksum byte, it will be set by checksum().
|
|
|
|
remote[8] = 0x11;
|
|
remote[9] = 0xDA;
|
|
remote[10] = 0x27;
|
|
remote[12] = 0x42;
|
|
// remote[15] is a checksum byte, it will be set by checksum().
|
|
remote[16] = 0x11;
|
|
remote[17] = 0xDA;
|
|
remote[18] = 0x27;
|
|
remote[21] = 0x49;
|
|
remote[22] = 0x1E;
|
|
remote[24] = 0xB0;
|
|
remote[27] = 0x06;
|
|
remote[28] = 0x60;
|
|
remote[31] = 0xC0;
|
|
// remote[34] is a checksum byte, it will be set by checksum().
|
|
this->checksum();
|
|
}
|
|
|
|
uint8_t *IRDaikinESP::getRaw(void) {
|
|
this->checksum(); // Ensure correct settings before sending.
|
|
return remote;
|
|
}
|
|
|
|
void IRDaikinESP::setRaw(const uint8_t new_code[], const uint16_t length) {
|
|
uint8_t offset = 0;
|
|
if (length == kDaikinStateLengthShort) { // Handle the "short" length case.
|
|
offset = kDaikinStateLength - kDaikinStateLengthShort;
|
|
this->stateReset();
|
|
}
|
|
for (uint8_t i = 0; i < length && i < kDaikinStateLength; i++)
|
|
remote[i + offset] = new_code[i];
|
|
}
|
|
|
|
void IRDaikinESP::on(void) { remote[kDaikinBytePower] |= kDaikinBitPower; }
|
|
|
|
void IRDaikinESP::off(void) { remote[kDaikinBytePower] &= ~kDaikinBitPower; }
|
|
|
|
void IRDaikinESP::setPower(const bool on) {
|
|
if (on)
|
|
this->on();
|
|
else
|
|
this->off();
|
|
}
|
|
|
|
bool IRDaikinESP::getPower(void) {
|
|
return remote[kDaikinBytePower] & kDaikinBitPower;
|
|
}
|
|
|
|
// Set the temp in deg C
|
|
void IRDaikinESP::setTemp(const uint8_t temp) {
|
|
uint8_t degrees = std::max(temp, kDaikinMinTemp);
|
|
degrees = std::min(degrees, kDaikinMaxTemp);
|
|
remote[kDaikinByteTemp] = degrees << 1;
|
|
}
|
|
|
|
uint8_t IRDaikinESP::getTemp(void) { return remote[kDaikinByteTemp] >> 1; }
|
|
|
|
// Set the speed of the fan, 1-5 or kDaikinFanAuto or kDaikinFanQuiet
|
|
void IRDaikinESP::setFan(const uint8_t fan) {
|
|
// Set the fan speed bits, leave low 4 bits alone
|
|
uint8_t fanset;
|
|
if (fan == kDaikinFanQuiet || fan == kDaikinFanAuto)
|
|
fanset = fan;
|
|
else if (fan < kDaikinFanMin || fan > kDaikinFanMax)
|
|
fanset = kDaikinFanAuto;
|
|
else
|
|
fanset = 2 + fan;
|
|
remote[kDaikinByteFan] &= 0x0F;
|
|
remote[kDaikinByteFan] |= (fanset << 4);
|
|
}
|
|
|
|
uint8_t IRDaikinESP::getFan(void) {
|
|
uint8_t fan = remote[kDaikinByteFan] >> 4;
|
|
if (fan != kDaikinFanQuiet && fan != kDaikinFanAuto) fan -= 2;
|
|
return fan;
|
|
}
|
|
|
|
uint8_t IRDaikinESP::getMode(void) { return remote[kDaikinBytePower] >> 4; }
|
|
|
|
void IRDaikinESP::setMode(const uint8_t mode) {
|
|
switch (mode) {
|
|
case kDaikinAuto:
|
|
case kDaikinCool:
|
|
case kDaikinHeat:
|
|
case kDaikinFan:
|
|
case kDaikinDry:
|
|
remote[kDaikinBytePower] &= 0b10001111;
|
|
remote[kDaikinBytePower] |= (mode << 4);
|
|
break;
|
|
default:
|
|
this->setMode(kDaikinAuto);
|
|
}
|
|
}
|
|
|
|
void IRDaikinESP::setSwingVertical(const bool on) {
|
|
if (on)
|
|
remote[kDaikinByteFan] |= 0x0F;
|
|
else
|
|
remote[kDaikinByteFan] &= 0xF0;
|
|
}
|
|
|
|
bool IRDaikinESP::getSwingVertical(void) {
|
|
return remote[kDaikinByteFan] & 0x0F;
|
|
}
|
|
|
|
void IRDaikinESP::setSwingHorizontal(const bool on) {
|
|
if (on)
|
|
remote[kDaikinByteSwingH] |= 0x0F;
|
|
else
|
|
remote[kDaikinByteSwingH] &= 0xF0;
|
|
}
|
|
|
|
bool IRDaikinESP::getSwingHorizontal(void) {
|
|
return remote[kDaikinByteSwingH] & 0x0F;
|
|
}
|
|
|
|
void IRDaikinESP::setQuiet(const bool on) {
|
|
if (on) {
|
|
remote[kDaikinByteSilent] |= kDaikinBitSilent;
|
|
// Powerful & Quiet mode being on are mutually exclusive.
|
|
this->setPowerful(false);
|
|
} else {
|
|
remote[kDaikinByteSilent] &= ~kDaikinBitSilent;
|
|
}
|
|
}
|
|
|
|
bool IRDaikinESP::getQuiet(void) {
|
|
return remote[kDaikinByteSilent] & kDaikinBitSilent;
|
|
}
|
|
|
|
void IRDaikinESP::setPowerful(const bool on) {
|
|
if (on) {
|
|
remote[kDaikinBytePowerful] |= kDaikinBitPowerful;
|
|
// Powerful, Quiet, & Econo mode being on are mutually exclusive.
|
|
this->setQuiet(false);
|
|
this->setEcono(false);
|
|
} else {
|
|
remote[kDaikinBytePowerful] &= ~kDaikinBitPowerful;
|
|
}
|
|
}
|
|
|
|
bool IRDaikinESP::getPowerful(void) {
|
|
return remote[kDaikinBytePowerful] & kDaikinBitPowerful;
|
|
}
|
|
|
|
void IRDaikinESP::setSensor(const bool on) {
|
|
if (on)
|
|
remote[kDaikinByteSensor] |= kDaikinBitSensor;
|
|
else
|
|
remote[kDaikinByteSensor] &= ~kDaikinBitSensor;
|
|
}
|
|
|
|
bool IRDaikinESP::getSensor(void) {
|
|
return remote[kDaikinByteSensor] & kDaikinBitSensor;
|
|
}
|
|
|
|
void IRDaikinESP::setEcono(const bool on) {
|
|
if (on) {
|
|
remote[kDaikinByteEcono] |= kDaikinBitEcono;
|
|
// Powerful & Econo mode being on are mutually exclusive.
|
|
this->setPowerful(false);
|
|
} else {
|
|
remote[kDaikinByteEcono] &= ~kDaikinBitEcono;
|
|
}
|
|
}
|
|
|
|
bool IRDaikinESP::getEcono(void) {
|
|
return remote[kDaikinByteEcono] & kDaikinBitEcono;
|
|
}
|
|
|
|
void IRDaikinESP::setMold(const bool on) {
|
|
if (on)
|
|
remote[kDaikinByteMold] |= kDaikinBitMold;
|
|
else
|
|
remote[kDaikinByteMold] &= ~kDaikinBitMold;
|
|
}
|
|
|
|
bool IRDaikinESP::getMold(void) {
|
|
return remote[kDaikinByteMold] & kDaikinBitMold;
|
|
}
|
|
|
|
void IRDaikinESP::setComfort(const bool on) {
|
|
if (on)
|
|
remote[kDaikinByteComfort] |= kDaikinBitComfort;
|
|
else
|
|
remote[kDaikinByteComfort] &= ~kDaikinBitComfort;
|
|
}
|
|
|
|
bool IRDaikinESP::getComfort(void) {
|
|
return remote[kDaikinByteComfort] & kDaikinBitComfort;
|
|
}
|
|
|
|
// starttime: Number of minutes after midnight.
|
|
void IRDaikinESP::enableOnTimer(const uint16_t starttime) {
|
|
remote[kDaikinByteOnTimer] |= kDaikinBitOnTimer;
|
|
remote[kDaikinByteOnTimerMinsLow] = starttime;
|
|
// only keep 4 bits
|
|
remote[kDaikinByteOnTimerMinsHigh] &= 0xF0;
|
|
remote[kDaikinByteOnTimerMinsHigh] |= ((starttime >> 8) & 0x0F);
|
|
}
|
|
|
|
void IRDaikinESP::disableOnTimer(void) {
|
|
this->enableOnTimer(kDaikinUnusedTime);
|
|
remote[kDaikinByteOnTimer] &= ~kDaikinBitOnTimer;
|
|
}
|
|
|
|
uint16_t IRDaikinESP::getOnTime(void) {
|
|
return ((remote[kDaikinByteOnTimerMinsHigh] & 0x0F) << 8) +
|
|
remote[kDaikinByteOnTimerMinsLow];
|
|
}
|
|
|
|
bool IRDaikinESP::getOnTimerEnabled(void) {
|
|
return remote[kDaikinByteOnTimer] & kDaikinBitOnTimer;
|
|
}
|
|
|
|
// endtime: Number of minutes after midnight.
|
|
void IRDaikinESP::enableOffTimer(const uint16_t endtime) {
|
|
remote[kDaikinByteOffTimer] |= kDaikinBitOffTimer;
|
|
remote[kDaikinByteOffTimerMinsHigh] = endtime >> 4;
|
|
remote[kDaikinByteOffTimerMinsLow] &= 0x0F;
|
|
remote[kDaikinByteOffTimerMinsLow] |= ((endtime & 0x0F) << 4);
|
|
}
|
|
|
|
void IRDaikinESP::disableOffTimer(void) {
|
|
this->enableOffTimer(kDaikinUnusedTime);
|
|
remote[kDaikinByteOffTimer] &= ~kDaikinBitOffTimer;
|
|
}
|
|
|
|
uint16_t IRDaikinESP::getOffTime(void) {
|
|
return (remote[kDaikinByteOffTimerMinsHigh] << 4) +
|
|
((remote[kDaikinByteOffTimerMinsLow] & 0xF0) >> 4);
|
|
}
|
|
|
|
bool IRDaikinESP::getOffTimerEnabled(void) {
|
|
return remote[kDaikinByteOffTimer] & kDaikinBitOffTimer;
|
|
}
|
|
|
|
void IRDaikinESP::setCurrentTime(const uint16_t mins_since_midnight) {
|
|
uint16_t mins = mins_since_midnight;
|
|
if (mins > 24 * 60) mins = 0; // If > 23:59, set to 00:00
|
|
remote[kDaikinByteClockMinsLow] = mins;
|
|
// only keep 3 bits
|
|
remote[kDaikinByteClockMinsHigh] &= 0xF8;
|
|
remote[kDaikinByteClockMinsHigh] |= ((mins >> 8) & 0x07);
|
|
}
|
|
|
|
uint16_t IRDaikinESP::getCurrentTime(void) {
|
|
return ((remote[kDaikinByteClockMinsHigh] & 0x07) << 8) +
|
|
remote[kDaikinByteClockMinsLow];
|
|
}
|
|
|
|
void IRDaikinESP::setCurrentDay(const uint8_t day_of_week) {
|
|
// 1 is SUN, 2 is MON, ..., 7 is SAT
|
|
uint8_t days = day_of_week;
|
|
if (days > 7) days = 0; // Enforce the limit
|
|
// Update bits 5-3
|
|
remote[kDaikinByteClockMinsHigh] &= 0xc7;
|
|
remote[kDaikinByteClockMinsHigh] |= days << 3;
|
|
}
|
|
|
|
uint8_t IRDaikinESP::getCurrentDay(void) {
|
|
return ((remote[kDaikinByteClockMinsHigh] & 0x38) >> 3);
|
|
}
|
|
|
|
void IRDaikinESP::setWeeklyTimerEnable(const bool on) {
|
|
if (on)
|
|
remote[kDaikinByteWeeklyTimer] &= ~kDaikinBitWeeklyTimer; // Clear the bit.
|
|
else
|
|
remote[kDaikinByteWeeklyTimer] |= kDaikinBitWeeklyTimer; // Set the bit.
|
|
}
|
|
|
|
bool IRDaikinESP::getWeeklyTimerEnable(void) {
|
|
return !(remote[kDaikinByteWeeklyTimer] & kDaikinBitWeeklyTimer);
|
|
}
|
|
|
|
// Convert a standard A/C mode into its native mode.
|
|
uint8_t IRDaikinESP::convertMode(const stdAc::opmode_t mode) {
|
|
switch (mode) {
|
|
case stdAc::opmode_t::kCool:
|
|
return kDaikinCool;
|
|
case stdAc::opmode_t::kHeat:
|
|
return kDaikinHeat;
|
|
case stdAc::opmode_t::kDry:
|
|
return kDaikinDry;
|
|
case stdAc::opmode_t::kFan:
|
|
return kDaikinFan;
|
|
default:
|
|
return kDaikinAuto;
|
|
}
|
|
}
|
|
|
|
// Convert a standard A/C Fan speed into its native fan speed.
|
|
uint8_t IRDaikinESP::convertFan(const stdAc::fanspeed_t speed) {
|
|
switch (speed) {
|
|
case stdAc::fanspeed_t::kMin:
|
|
return kDaikinFanQuiet;
|
|
case stdAc::fanspeed_t::kLow:
|
|
return kDaikinFanMin;
|
|
case stdAc::fanspeed_t::kMedium:
|
|
return kDaikinFanMin + 1;
|
|
case stdAc::fanspeed_t::kHigh:
|
|
return kDaikinFanMax - 1;
|
|
case stdAc::fanspeed_t::kMax:
|
|
return kDaikinFanMax;
|
|
default:
|
|
return kDaikinFanAuto;
|
|
}
|
|
}
|
|
|
|
// Convert a native mode to it's common equivalent.
|
|
stdAc::opmode_t IRDaikinESP::toCommonMode(const uint8_t mode) {
|
|
switch (mode) {
|
|
case kDaikinCool: return stdAc::opmode_t::kCool;
|
|
case kDaikinHeat: return stdAc::opmode_t::kHeat;
|
|
case kDaikinDry: return stdAc::opmode_t::kDry;
|
|
case kDaikinFan: return stdAc::opmode_t::kFan;
|
|
default: return stdAc::opmode_t::kAuto;
|
|
}
|
|
}
|
|
|
|
// Convert a native fan speed to it's common equivalent.
|
|
stdAc::fanspeed_t IRDaikinESP::toCommonFanSpeed(const uint8_t speed) {
|
|
switch (speed) {
|
|
case kDaikinFanMax: return stdAc::fanspeed_t::kMax;
|
|
case kDaikinFanMax - 1: return stdAc::fanspeed_t::kHigh;
|
|
case kDaikinFanMin + 1: return stdAc::fanspeed_t::kMedium;
|
|
case kDaikinFanMin: return stdAc::fanspeed_t::kLow;
|
|
case kDaikinFanQuiet: return stdAc::fanspeed_t::kMin;
|
|
default: return stdAc::fanspeed_t::kAuto;
|
|
}
|
|
}
|
|
|
|
// Convert the A/C state to it's common equivalent.
|
|
stdAc::state_t IRDaikinESP::toCommon(void) {
|
|
stdAc::state_t result;
|
|
result.protocol = decode_type_t::DAIKIN;
|
|
result.model = -1; // No models used.
|
|
result.power = this->getPower();
|
|
result.mode = this->toCommonMode(this->getMode());
|
|
result.celsius = true;
|
|
result.degrees = this->getTemp();
|
|
result.fanspeed = this->toCommonFanSpeed(this->getFan());
|
|
result.swingv = this->getSwingVertical() ? stdAc::swingv_t::kAuto :
|
|
stdAc::swingv_t::kOff;
|
|
result.swingh = this->getSwingHorizontal() ? stdAc::swingh_t::kAuto :
|
|
stdAc::swingh_t::kOff;
|
|
result.quiet = this->getQuiet();
|
|
result.turbo = this->getPowerful();
|
|
result.clean = this->getMold();
|
|
result.econo = this->getEcono();
|
|
// Not supported.
|
|
result.filter = false;
|
|
result.light = false;
|
|
result.beep = false;
|
|
result.sleep = -1;
|
|
result.clock = -1;
|
|
return result;
|
|
}
|
|
|
|
// Convert the internal state into a human readable string.
|
|
String IRDaikinESP::toString(void) {
|
|
String result = "";
|
|
result.reserve(230); // Reserve some heap for the string to reduce fragging.
|
|
result += addBoolToString(getPower(), F("Power"), false);
|
|
result += addModeToString(getMode(), kDaikinAuto, kDaikinCool, kDaikinHeat,
|
|
kDaikinDry, kDaikinFan);
|
|
result += addTempToString(getTemp());
|
|
result += addFanToString(getFan(), kDaikinFanMax, kDaikinFanMin,
|
|
kDaikinFanAuto, kDaikinFanQuiet, kDaikinFanMed);
|
|
result += addBoolToString(getPowerful(), F("Powerful"));
|
|
result += addBoolToString(getQuiet(), F("Quiet"));
|
|
result += addBoolToString(getSensor(), F("Sensor"));
|
|
result += addBoolToString(getMold(), F("Mold"));
|
|
result += addBoolToString(getComfort(), F("Comfort"));
|
|
result += addBoolToString(getSwingHorizontal(), F("Swing (Horizontal)"));
|
|
result += addBoolToString(getSwingVertical(), F("Swing (Vertical)"));
|
|
result += addLabeledString(minsToString(this->getCurrentTime()),
|
|
F("Current Time"));
|
|
result += F(", Current Day: ");
|
|
switch (this->getCurrentDay()) {
|
|
case 1:
|
|
result +=F("SUN"); break;
|
|
case 2:
|
|
result +=F("MON"); break;
|
|
case 3:
|
|
result +=F("TUE"); break;
|
|
case 4:
|
|
result +=F("WED"); break;
|
|
case 5:
|
|
result +=F("THU"); break;
|
|
case 6:
|
|
result +=F("FRI"); break;
|
|
case 7:
|
|
result +=F("SAT"); break;
|
|
default:
|
|
result +=F("(UNKNOWN)"); break;
|
|
}
|
|
result += addLabeledString(getOnTimerEnabled()
|
|
? minsToString(this->getOnTime()) : F("Off"),
|
|
F("On Time"));
|
|
result += addLabeledString(getOffTimerEnabled()
|
|
? minsToString(this->getOffTime()) : F("Off"),
|
|
F("Off Time"));
|
|
result += addBoolToString(getWeeklyTimerEnable(), F("Weekly Timer"));
|
|
return result;
|
|
}
|
|
|
|
#if DECODE_DAIKIN
|
|
// Decode the supplied Daikin A/C message.
|
|
// Args:
|
|
// results: Ptr to the data to decode and where to store the decode result.
|
|
// nbits: Nr. of bits to expect in the data portion. (kDaikinBits)
|
|
// strict: Flag to indicate if we strictly adhere to the specification.
|
|
// Returns:
|
|
// boolean: True if it can decode it, false if it can't.
|
|
//
|
|
// Status: BETA / Should be working.
|
|
//
|
|
// Ref:
|
|
// https://github.com/mharizanov/Daikin-AC-remote-control-over-the-Internet/tree/master/IRremote
|
|
bool IRrecv::decodeDaikin(decode_results *results, const uint16_t nbits,
|
|
const bool strict) {
|
|
// Is there enough data to match successfully?
|
|
if (results->rawlen < (2 * (nbits + kDaikinHeaderLength) +
|
|
kDaikinSections * (kHeader + kFooter) + kFooter - 1))
|
|
return false;
|
|
|
|
// Compliance
|
|
if (strict && nbits != kDaikinBits) return false;
|
|
|
|
uint16_t offset = kStartOffset;
|
|
match_result_t data_result;
|
|
|
|
// Header #1 - Doesn't count as data.
|
|
data_result = matchData(&(results->rawbuf[offset]), kDaikinHeaderLength,
|
|
kDaikinBitMark, kDaikinOneSpace,
|
|
kDaikinBitMark, kDaikinZeroSpace,
|
|
kDaikinTolerance, kDaikinMarkExcess, false);
|
|
offset += data_result.used;
|
|
if (data_result.success == false) return false; // Fail
|
|
if (data_result.data) return false; // The header bits should be zero.
|
|
// Footer
|
|
if (!matchMark(results->rawbuf[offset++], kDaikinBitMark,
|
|
kDaikinTolerance, kDaikinMarkExcess)) return false;
|
|
if (!matchSpace(results->rawbuf[offset++], kDaikinZeroSpace + kDaikinGap,
|
|
kDaikinTolerance, kDaikinMarkExcess)) return false;
|
|
// Sections
|
|
const uint8_t ksectionSize[kDaikinSections] = {
|
|
kDaikinSection1Length, kDaikinSection2Length, kDaikinSection3Length};
|
|
uint16_t pos = 0;
|
|
for (uint8_t section = 0; section < kDaikinSections; section++) {
|
|
uint16_t used;
|
|
// Section Header + Section Data (7 bytes) + Section Footer
|
|
used = matchGeneric(results->rawbuf + offset, results->state + pos,
|
|
results->rawlen - offset, ksectionSize[section] * 8,
|
|
kDaikinHdrMark, kDaikinHdrSpace,
|
|
kDaikinBitMark, kDaikinOneSpace,
|
|
kDaikinBitMark, kDaikinZeroSpace,
|
|
kDaikinBitMark, kDaikinZeroSpace + kDaikinGap,
|
|
section >= kDaikinSections - 1,
|
|
kDaikinTolerance, kDaikinMarkExcess, false);
|
|
if (used == 0) return false;
|
|
offset += used;
|
|
pos += ksectionSize[section];
|
|
}
|
|
// Compliance
|
|
if (strict) {
|
|
// Re-check we got the correct size/length due to the way we read the data.
|
|
if (pos * 8 != kDaikinBits) return false;
|
|
// Validate the checksum.
|
|
if (!IRDaikinESP::validChecksum(results->state)) return false;
|
|
}
|
|
|
|
// Success
|
|
results->decode_type = DAIKIN;
|
|
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;
|
|
}
|
|
#endif // DECODE_DAIKIN
|
|
|
|
#if SEND_DAIKIN2
|
|
// Send a Daikin2 A/C message.
|
|
//
|
|
// Args:
|
|
// data: An array of kDaikin2StateLength bytes containing the IR command.
|
|
//
|
|
// Status: BETA/Appears to work.
|
|
//
|
|
// Ref:
|
|
// https://github.com/crankyoldgit/IRremoteESP8266/issues/582
|
|
void IRsend::sendDaikin2(const unsigned char data[], const uint16_t nbytes,
|
|
const uint16_t repeat) {
|
|
if (nbytes < kDaikin2Section1Length)
|
|
return; // Not enough bytes to send a partial message.
|
|
|
|
for (uint16_t r = 0; r <= repeat; r++) {
|
|
// Leader
|
|
sendGeneric(kDaikin2LeaderMark, kDaikin2LeaderSpace,
|
|
0, 0, 0, 0, 0, 0, (uint64_t) 0, // No data payload.
|
|
0, kDaikin2Freq, false, 0, 50);
|
|
// Section #1
|
|
sendGeneric(kDaikin2HdrMark, kDaikin2HdrSpace, kDaikin2BitMark,
|
|
kDaikin2OneSpace, kDaikin2BitMark, kDaikin2ZeroSpace,
|
|
kDaikin2BitMark, kDaikin2Gap, data, kDaikin2Section1Length,
|
|
kDaikin2Freq, false, 0, 50);
|
|
// Section #2
|
|
sendGeneric(kDaikin2HdrMark, kDaikin2HdrSpace, kDaikin2BitMark,
|
|
kDaikin2OneSpace, kDaikin2BitMark, kDaikin2ZeroSpace,
|
|
kDaikin2BitMark, kDaikin2Gap, data + kDaikin2Section1Length,
|
|
nbytes - kDaikin2Section1Length,
|
|
kDaikin2Freq, false, 0, 50);
|
|
}
|
|
}
|
|
#endif // SEND_DAIKIN2
|
|
|
|
// Class for handling Daikin2 A/C messages.
|
|
//
|
|
// Code by crankyoldgit, Reverse engineering analysis by sheppy99
|
|
//
|
|
// Supported Remotes: Daikin ARC477A1 remote
|
|
//
|
|
// Ref:
|
|
// https://github.com/crankyoldgit/IRremoteESP8266/issues/582
|
|
// https://docs.google.com/spreadsheets/d/1f8EGfIbBUo2B-CzUFdrgKQprWakoYNKM80IKZN4KXQE/edit?usp=sharing
|
|
// https://www.daikin.co.nz/sites/default/files/daikin-split-system-US7-FTXZ25-50NV1B.pdf
|
|
IRDaikin2::IRDaikin2(const uint16_t pin, const bool inverted,
|
|
const bool use_modulation)
|
|
: _irsend(pin, inverted, use_modulation) { stateReset(); }
|
|
|
|
void IRDaikin2::begin() { _irsend.begin(); }
|
|
|
|
#if SEND_DAIKIN2
|
|
void IRDaikin2::send(const uint16_t repeat) {
|
|
checksum();
|
|
_irsend.sendDaikin2(remote_state, kDaikin2StateLength, repeat);
|
|
}
|
|
#endif // SEND_DAIKIN2
|
|
|
|
// Verify the checksum is valid for a given state.
|
|
// Args:
|
|
// state: The array to verify the checksum of.
|
|
// length: The size of the state.
|
|
// Returns:
|
|
// A boolean.
|
|
bool IRDaikin2::validChecksum(uint8_t state[], const uint16_t length) {
|
|
// Validate the checksum of section #1.
|
|
if (length <= kDaikin2Section1Length - 1 ||
|
|
state[kDaikin2Section1Length - 1] != sumBytes(state,
|
|
kDaikin2Section1Length - 1))
|
|
return false;
|
|
// Validate the checksum of section #2 (a.k.a. the rest)
|
|
if (length <= kDaikin2Section1Length + 1 ||
|
|
state[length - 1] != sumBytes(state + kDaikin2Section1Length,
|
|
length - kDaikin2Section1Length - 1))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
// Calculate and set the checksum values for the internal state.
|
|
void IRDaikin2::checksum() {
|
|
remote_state[kDaikin2Section1Length - 1] = sumBytes(
|
|
remote_state, kDaikin2Section1Length - 1);
|
|
remote_state[kDaikin2StateLength -1 ] = sumBytes(
|
|
remote_state + kDaikin2Section1Length, kDaikin2Section2Length - 1);
|
|
}
|
|
|
|
void IRDaikin2::stateReset() {
|
|
for (uint8_t i = 0; i < kDaikin2StateLength; i++) remote_state[i] = 0x0;
|
|
|
|
remote_state[0] = 0x11;
|
|
remote_state[1] = 0xDA;
|
|
remote_state[2] = 0x27;
|
|
remote_state[4] = 0x01;
|
|
remote_state[6] = 0xC0;
|
|
remote_state[7] = 0x70;
|
|
remote_state[8] = 0x08;
|
|
remote_state[9] = 0x0C;
|
|
remote_state[10] = 0x80;
|
|
remote_state[11] = 0x04;
|
|
remote_state[12] = 0xB0;
|
|
remote_state[13] = 0x16;
|
|
remote_state[14] = 0x24;
|
|
remote_state[17] = 0xBE;
|
|
remote_state[18] = 0xD0;
|
|
// remote_state[19] is a checksum byte, it will be set by checksum().
|
|
remote_state[20] = 0x11;
|
|
remote_state[21] = 0xDA;
|
|
remote_state[22] = 0x27;
|
|
remote_state[25] = 0x08;
|
|
remote_state[28] = 0xA0;
|
|
remote_state[35] = 0xC1;
|
|
remote_state[36] = 0x80;
|
|
remote_state[37] = 0x60;
|
|
// remote_state[38] is a checksum byte, it will be set by checksum().
|
|
disableOnTimer();
|
|
disableOffTimer();
|
|
disableSleepTimer();
|
|
checksum();
|
|
}
|
|
|
|
uint8_t *IRDaikin2::getRaw() {
|
|
checksum(); // Ensure correct settings before sending.
|
|
return remote_state;
|
|
}
|
|
|
|
void IRDaikin2::setRaw(const uint8_t new_code[]) {
|
|
for (uint8_t i = 0; i < kDaikin2StateLength; i++)
|
|
remote_state[i] = new_code[i];
|
|
}
|
|
|
|
void IRDaikin2::on() {
|
|
remote_state[25] |= kDaikinBitPower;
|
|
remote_state[6] &= ~kDaikin2BitPower;
|
|
}
|
|
|
|
void IRDaikin2::off() {
|
|
remote_state[25] &= ~kDaikinBitPower;
|
|
remote_state[6] |= kDaikin2BitPower;
|
|
}
|
|
|
|
void IRDaikin2::setPower(const bool state) {
|
|
if (state)
|
|
on();
|
|
else
|
|
off();
|
|
}
|
|
|
|
bool IRDaikin2::getPower() {
|
|
return (remote_state[25] & kDaikinBitPower) &&
|
|
!(remote_state[6] & kDaikin2BitPower);
|
|
}
|
|
|
|
uint8_t IRDaikin2::getMode() { return remote_state[25] >> 4; }
|
|
|
|
void IRDaikin2::setMode(const uint8_t desired_mode) {
|
|
uint8_t mode = desired_mode;
|
|
switch (mode) {
|
|
case kDaikinCool:
|
|
case kDaikinHeat:
|
|
case kDaikinFan:
|
|
case kDaikinDry:
|
|
break;
|
|
default:
|
|
mode = kDaikinAuto;
|
|
}
|
|
remote_state[25] &= 0b10001111;
|
|
remote_state[25] |= (mode << 4);
|
|
// Redo the temp setting as Cool mode has a different min temp.
|
|
if (mode == kDaikinCool) this->setTemp(this->getTemp());
|
|
}
|
|
|
|
// Set the temp in deg C
|
|
void IRDaikin2::setTemp(const uint8_t desired) {
|
|
// The A/C has a different min temp if in cool mode.
|
|
uint8_t temp = std::max(
|
|
(this->getMode() == kDaikinCool) ? kDaikin2MinCoolTemp : kDaikinMinTemp,
|
|
desired);
|
|
temp = std::min(kDaikinMaxTemp, temp);
|
|
remote_state[26] = temp * 2;
|
|
}
|
|
|
|
// Set the speed of the fan, 1-5 or kDaikinFanAuto or kDaikinFanQuiet
|
|
void IRDaikin2::setFan(const uint8_t fan) {
|
|
// Set the fan speed bits, leave low 4 bits alone
|
|
uint8_t fanset;
|
|
if (fan == kDaikinFanQuiet || fan == kDaikinFanAuto)
|
|
fanset = fan;
|
|
else if (fan < kDaikinFanMin || fan > kDaikinFanMax)
|
|
fanset = kDaikinFanAuto;
|
|
else
|
|
fanset = 2 + fan;
|
|
remote_state[28] &= 0x0F;
|
|
remote_state[28] |= (fanset << 4);
|
|
}
|
|
|
|
uint8_t IRDaikin2::getFan() {
|
|
uint8_t fan = remote_state[28] >> 4;
|
|
if (fan != kDaikinFanQuiet && fan != kDaikinFanAuto) fan -= 2;
|
|
return fan;
|
|
}
|
|
|
|
uint8_t IRDaikin2::getTemp() { return remote_state[26] / 2; }
|
|
|
|
void IRDaikin2::setSwingVertical(const uint8_t position) {
|
|
switch (position) {
|
|
case kDaikin2SwingVHigh:
|
|
case 2:
|
|
case 3:
|
|
case 4:
|
|
case 5:
|
|
case kDaikin2SwingVLow:
|
|
case kDaikin2SwingVBreeze:
|
|
case kDaikin2SwingVCirculate:
|
|
case kDaikin2SwingVAuto:
|
|
remote_state[18] &= 0xF0;
|
|
remote_state[18] |= (position & 0x0F);
|
|
}
|
|
}
|
|
|
|
uint8_t IRDaikin2::getSwingVertical() { return remote_state[18] & 0x0F; }
|
|
|
|
void IRDaikin2::setSwingHorizontal(const uint8_t position) {
|
|
remote_state[17] = position;
|
|
}
|
|
|
|
uint8_t IRDaikin2::getSwingHorizontal() { return remote_state[17]; }
|
|
|
|
void IRDaikin2::setCurrentTime(const uint16_t numMins) {
|
|
uint16_t mins = numMins;
|
|
if (numMins > 24 * 60) mins = 0; // If > 23:59, set to 00:00
|
|
remote_state[5] = (uint8_t)(mins & 0xFF);
|
|
// only keep 4 bits
|
|
remote_state[6] &= 0xF0;
|
|
remote_state[6] |= (uint8_t)((mins >> 8) & 0x0F);
|
|
}
|
|
|
|
uint16_t IRDaikin2::getCurrentTime() {
|
|
return ((remote_state[6] & 0x0F) << 8) + remote_state[5];
|
|
}
|
|
|
|
// starttime: Number of minutes after midnight.
|
|
// Note: Timer location is shared with sleep timer.
|
|
void IRDaikin2::enableOnTimer(const uint16_t starttime) {
|
|
clearSleepTimerFlag();
|
|
remote_state[25] |= kDaikinBitOnTimer; // Set the On Timer flag.
|
|
remote_state[30] = (uint8_t)(starttime & 0xFF);
|
|
// only keep 4 bits
|
|
remote_state[31] &= 0xF0;
|
|
remote_state[31] |= (uint8_t)((starttime >> 8) & 0x0F);
|
|
}
|
|
|
|
void IRDaikin2::clearOnTimerFlag() {
|
|
remote_state[25] &= ~kDaikinBitOnTimer;
|
|
}
|
|
|
|
void IRDaikin2::disableOnTimer() {
|
|
enableOnTimer(kDaikinUnusedTime);
|
|
clearOnTimerFlag();
|
|
clearSleepTimerFlag();
|
|
}
|
|
|
|
uint16_t IRDaikin2::getOnTime() {
|
|
return ((remote_state[31] & 0x0F) << 8) + remote_state[30];
|
|
}
|
|
|
|
bool IRDaikin2::getOnTimerEnabled() {
|
|
return remote_state[25] & kDaikinBitOnTimer;
|
|
}
|
|
|
|
// endtime: Number of minutes after midnight.
|
|
void IRDaikin2::enableOffTimer(const uint16_t endtime) {
|
|
remote_state[25] |= kDaikinBitOffTimer; // Set the Off Timer flag.
|
|
remote_state[32] = (uint8_t)((endtime >> 4) & 0xFF);
|
|
remote_state[31] &= 0x0F;
|
|
remote_state[31] |= (uint8_t)((endtime & 0xF) << 4);
|
|
}
|
|
|
|
void IRDaikin2::disableOffTimer() {
|
|
enableOffTimer(kDaikinUnusedTime);
|
|
remote_state[25] &= ~kDaikinBitOffTimer; // Clear the Off Timer flag.
|
|
}
|
|
|
|
uint16_t IRDaikin2::getOffTime() {
|
|
return (remote_state[32] << 4) + (remote_state[31] >> 4);
|
|
}
|
|
|
|
bool IRDaikin2::getOffTimerEnabled() {
|
|
return remote_state[25] & kDaikinBitOffTimer;
|
|
}
|
|
|
|
uint8_t IRDaikin2::getBeep() {
|
|
return remote_state[7] >> 6;
|
|
}
|
|
|
|
void IRDaikin2::setBeep(const uint8_t beep) {
|
|
remote_state[7] &= ~kDaikin2BeepMask;
|
|
remote_state[7] |= ((beep << 6) & kDaikin2BeepMask);
|
|
}
|
|
|
|
uint8_t IRDaikin2::getLight() {
|
|
return (remote_state[7] & kDaikin2LightMask) >> 4;
|
|
}
|
|
|
|
void IRDaikin2::setLight(const uint8_t light) {
|
|
remote_state[7] &= ~kDaikin2LightMask;
|
|
remote_state[7] |= ((light << 4) & kDaikin2LightMask);
|
|
}
|
|
|
|
void IRDaikin2::setMold(const bool on) {
|
|
if (on)
|
|
remote_state[8] |= kDaikin2BitMold;
|
|
else
|
|
remote_state[8] &= ~kDaikin2BitMold;
|
|
}
|
|
|
|
bool IRDaikin2::getMold() {
|
|
return remote_state[8] & kDaikin2BitMold;
|
|
}
|
|
|
|
// Auto clean setting.
|
|
void IRDaikin2::setClean(const bool on) {
|
|
if (on)
|
|
remote_state[8] |= kDaikin2BitClean;
|
|
else
|
|
remote_state[8] &= ~kDaikin2BitClean;
|
|
}
|
|
|
|
bool IRDaikin2::getClean() {
|
|
return remote_state[8] & kDaikin2BitClean;
|
|
}
|
|
|
|
// Fresh Air settings.
|
|
void IRDaikin2::setFreshAir(const bool on) {
|
|
if (on)
|
|
remote_state[8] |= kDaikin2BitFreshAir;
|
|
else
|
|
remote_state[8] &= ~kDaikin2BitFreshAir;
|
|
}
|
|
|
|
bool IRDaikin2::getFreshAir() {
|
|
return remote_state[8] & kDaikin2BitFreshAir;
|
|
}
|
|
|
|
void IRDaikin2::setFreshAirHigh(const bool on) {
|
|
if (on)
|
|
remote_state[8] |= kDaikin2BitFreshAirHigh;
|
|
else
|
|
remote_state[8] &= ~kDaikin2BitFreshAirHigh;
|
|
}
|
|
|
|
bool IRDaikin2::getFreshAirHigh() {
|
|
return remote_state[8] & kDaikin2BitFreshAirHigh;
|
|
}
|
|
|
|
void IRDaikin2::setEyeAuto(bool on) {
|
|
if (on)
|
|
remote_state[13] |= kDaikin2BitEyeAuto;
|
|
else
|
|
remote_state[13] &= ~kDaikin2BitEyeAuto;
|
|
}
|
|
|
|
bool IRDaikin2::getEyeAuto() {
|
|
return remote_state[13] & kDaikin2BitEyeAuto;
|
|
}
|
|
|
|
void IRDaikin2::setEye(bool on) {
|
|
if (on)
|
|
remote_state[36] |= kDaikin2BitEye;
|
|
else
|
|
remote_state[36] &= ~kDaikin2BitEye;
|
|
}
|
|
|
|
bool IRDaikin2::getEye() {
|
|
return remote_state[36] & kDaikin2BitEye;
|
|
}
|
|
|
|
void IRDaikin2::setEcono(bool on) {
|
|
if (on)
|
|
remote_state[36] |= kDaikinBitEcono;
|
|
else
|
|
remote_state[36] &= ~kDaikinBitEcono;
|
|
}
|
|
|
|
bool IRDaikin2::getEcono() {
|
|
return remote_state[36] & kDaikinBitEcono;
|
|
}
|
|
|
|
// sleeptime: Number of minutes.
|
|
// Note: Timer location is shared with On Timer.
|
|
void IRDaikin2::enableSleepTimer(const uint16_t sleeptime) {
|
|
enableOnTimer(sleeptime);
|
|
clearOnTimerFlag();
|
|
remote_state[36] |= kDaikin2BitSleepTimer; // Set the Sleep Timer flag.
|
|
}
|
|
|
|
void IRDaikin2::clearSleepTimerFlag() {
|
|
remote_state[36] &= ~kDaikin2BitSleepTimer;
|
|
}
|
|
|
|
void IRDaikin2::disableSleepTimer() {
|
|
disableOnTimer();
|
|
}
|
|
|
|
uint16_t IRDaikin2::getSleepTime() {
|
|
return getOnTime();
|
|
}
|
|
|
|
bool IRDaikin2::getSleepTimerEnabled() {
|
|
return remote_state[36] & kDaikin2BitSleepTimer;
|
|
}
|
|
|
|
void IRDaikin2::setQuiet(const bool on) {
|
|
if (on) {
|
|
remote_state[33] |= kDaikinBitSilent;
|
|
// Powerful & Quiet mode being on are mutually exclusive.
|
|
setPowerful(false);
|
|
} else {
|
|
remote_state[33] &= ~kDaikinBitSilent;
|
|
}
|
|
}
|
|
|
|
bool IRDaikin2::getQuiet() { return remote_state[33] & kDaikinBitSilent; }
|
|
|
|
void IRDaikin2::setPowerful(const bool on) {
|
|
if (on) {
|
|
remote_state[33] |= kDaikinBitPowerful;
|
|
// Powerful & Quiet mode being on are mutually exclusive.
|
|
setQuiet(false);
|
|
} else {
|
|
remote_state[33] &= ~kDaikinBitPowerful;
|
|
}
|
|
}
|
|
|
|
bool IRDaikin2::getPowerful() { return remote_state[33] & kDaikinBitPowerful; }
|
|
|
|
void IRDaikin2::setPurify(const bool on) {
|
|
if (on)
|
|
remote_state[36] |= kDaikin2BitPurify;
|
|
else
|
|
remote_state[36] &= ~kDaikin2BitPurify;
|
|
}
|
|
|
|
bool IRDaikin2::getPurify() { return remote_state[36] & kDaikin2BitPurify; }
|
|
|
|
// Convert a standard A/C mode into its native mode.
|
|
uint8_t IRDaikin2::convertMode(const stdAc::opmode_t mode) {
|
|
return IRDaikinESP::convertMode(mode);
|
|
}
|
|
|
|
// Convert a standard A/C Fan speed into its native fan speed.
|
|
uint8_t IRDaikin2::convertFan(const stdAc::fanspeed_t speed) {
|
|
return IRDaikinESP::convertFan(speed);
|
|
}
|
|
|
|
// Convert a standard A/C vertical swing into its native version.
|
|
uint8_t IRDaikin2::convertSwingV(const stdAc::swingv_t position) {
|
|
switch (position) {
|
|
case stdAc::swingv_t::kHighest:
|
|
case stdAc::swingv_t::kHigh:
|
|
case stdAc::swingv_t::kMiddle:
|
|
case stdAc::swingv_t::kLow:
|
|
case stdAc::swingv_t::kLowest:
|
|
return (uint8_t)position + kDaikin2SwingVHigh;
|
|
default:
|
|
return kDaikin2SwingVAuto;
|
|
}
|
|
}
|
|
|
|
// Convert a native vertical swing to it's common equivalent.
|
|
stdAc::swingv_t IRDaikin2::toCommonSwingV(const uint8_t setting) {
|
|
switch (setting) {
|
|
case kDaikin2SwingVHigh: return stdAc::swingv_t::kHighest;
|
|
case kDaikin2SwingVHigh + 1: return stdAc::swingv_t::kHigh;
|
|
case kDaikin2SwingVHigh + 2:
|
|
case kDaikin2SwingVHigh + 3: return stdAc::swingv_t::kMiddle;
|
|
case kDaikin2SwingVLow - 1: return stdAc::swingv_t::kLow;
|
|
case kDaikin2SwingVLow: return stdAc::swingv_t::kLowest;
|
|
default: return stdAc::swingv_t::kAuto;
|
|
}
|
|
}
|
|
|
|
// Convert a native horizontal swing to it's common equivalent.
|
|
stdAc::swingh_t IRDaikin2::toCommonSwingH(const uint8_t setting) {
|
|
switch (setting) {
|
|
case kDaikin2SwingHSwing:
|
|
case kDaikin2SwingHAuto: return stdAc::swingh_t::kAuto;
|
|
default: return stdAc::swingh_t::kOff;
|
|
}
|
|
}
|
|
|
|
// Convert the A/C state to it's common equivalent.
|
|
stdAc::state_t IRDaikin2::toCommon(void) {
|
|
stdAc::state_t result;
|
|
result.protocol = decode_type_t::DAIKIN2;
|
|
result.model = -1; // No models used.
|
|
result.power = this->getPower();
|
|
result.mode = IRDaikinESP::toCommonMode(this->getMode());
|
|
result.celsius = true;
|
|
result.degrees = this->getTemp();
|
|
result.fanspeed = IRDaikinESP::toCommonFanSpeed(this->getFan());
|
|
result.swingv = this->toCommonSwingV(this->getSwingVertical());
|
|
result.swingh = this->toCommonSwingH(this->getSwingHorizontal());
|
|
result.quiet = this->getQuiet();
|
|
result.light = this->getLight();
|
|
result.turbo = this->getPowerful();
|
|
result.clean = this->getMold();
|
|
result.econo = this->getEcono();
|
|
result.filter = this->getPurify();
|
|
result.beep = this->getBeep();
|
|
result.sleep = this->getSleepTimerEnabled() ? this->getSleepTime() : -1;
|
|
// Not supported.
|
|
result.clock = -1;
|
|
return result;
|
|
}
|
|
|
|
// Convert the internal state into a human readable string.
|
|
String IRDaikin2::toString() {
|
|
String result = "";
|
|
result.reserve(310); // Reserve some heap for the string to reduce fragging.
|
|
result += addBoolToString(getPower(), F("Power"), false);
|
|
result += addModeToString(getMode(), kDaikinAuto, kDaikinCool, kDaikinHeat,
|
|
kDaikinDry, kDaikinFan);
|
|
result += addTempToString(getTemp());
|
|
result += addFanToString(getFan(), kDaikinFanMax, kDaikinFanMin,
|
|
kDaikinFanAuto, kDaikinFanQuiet, kDaikinFanMed);
|
|
result += addIntToString(getSwingVertical(), F("Swing (V)"));
|
|
switch (getSwingVertical()) {
|
|
case kDaikin2SwingVHigh:
|
|
result += F(" (Highest)");
|
|
break;
|
|
case 2:
|
|
case 3:
|
|
case 4:
|
|
case 5:
|
|
break;
|
|
case kDaikin2SwingVLow:
|
|
result += F(" (Lowest)");
|
|
break;
|
|
case kDaikin2SwingVBreeze:
|
|
result += F(" (Breeze)");
|
|
break;
|
|
case kDaikin2SwingVCirculate:
|
|
result += F(" (Circulate)");
|
|
break;
|
|
case kDaikin2SwingVAuto:
|
|
result += F(" (Auto)");
|
|
break;
|
|
default:
|
|
result += F(" (Unknown)");
|
|
}
|
|
result += addIntToString(getSwingHorizontal(), F("Swing (H)"));
|
|
switch (getSwingHorizontal()) {
|
|
case kDaikin2SwingHAuto:
|
|
result += F(" (Auto)");
|
|
break;
|
|
case kDaikin2SwingHSwing:
|
|
result += F(" (Swing)");
|
|
break;
|
|
}
|
|
result += addLabeledString(minsToString(getCurrentTime()), F("Clock"));
|
|
result += addLabeledString(
|
|
getOnTimerEnabled() ? minsToString(getOnTime()) : F("Off"), F("On Time"));
|
|
result += addLabeledString(
|
|
getOffTimerEnabled() ? minsToString(getOffTime()) : F("Off"),
|
|
F("Off Time"));
|
|
result += addLabeledString(
|
|
getSleepTimerEnabled() ? minsToString(getSleepTime()) : F("Off"),
|
|
F("Sleep Time"));
|
|
result += addIntToString(getBeep(), F("Beep"));
|
|
switch (getBeep()) {
|
|
case kDaikinBeepLoud:
|
|
result += F(" (Loud)");
|
|
break;
|
|
case kDaikinBeepQuiet:
|
|
result += F(" (Quiet)");
|
|
break;
|
|
case kDaikinBeepOff:
|
|
result += F(" (Off)");
|
|
break;
|
|
default:
|
|
result += F(" (UNKNOWN)");
|
|
}
|
|
result += addIntToString(getLight(), F("Light"));
|
|
switch (getLight()) {
|
|
case kDaikinLightBright:
|
|
result += F(" (Bright)");
|
|
break;
|
|
case kDaikinLightDim:
|
|
result += F(" (Dim)");
|
|
break;
|
|
case kDaikinLightOff:
|
|
result += F(" (Off)");
|
|
break;
|
|
default:
|
|
result += F(" (UNKNOWN)");
|
|
}
|
|
result += addBoolToString(getMold(), F("Mold"));
|
|
result += addBoolToString(getClean(), F("Clean"));
|
|
result += addLabeledString(
|
|
getFreshAir() ? (getFreshAirHigh() ? F("High") : F("On")) : F("Off"),
|
|
F("Fresh Air"));
|
|
result += addBoolToString(getEye(), F("Eye"));
|
|
result += addBoolToString(getEyeAuto(), F("Eye Auto"));
|
|
result += addBoolToString(getQuiet(), F("Quiet"));
|
|
result += addBoolToString(getPowerful(), F("Powerful"));
|
|
result += addBoolToString(getPurify(), F("Purify"));
|
|
result += addBoolToString(getEcono(), F("Econo"));
|
|
return result;
|
|
}
|
|
|
|
#if DECODE_DAIKIN2
|
|
// Decode the supplied Daikin2 A/C message.
|
|
// Args:
|
|
// results: Ptr to the data to decode and where to store the decode result.
|
|
// nbits: Nr. of bits to expect in the data portion. (kDaikin2Bits)
|
|
// strict: Flag to indicate if we strictly adhere to the specification.
|
|
// Returns:
|
|
// boolean: True if it can decode it, false if it can't.
|
|
//
|
|
// Supported devices:
|
|
// - Daikin FTXZ25NV1B, FTXZ35NV1B, FTXZ50NV1B Aircon
|
|
// - Daikin ARC477A1 remote
|
|
//
|
|
// Status: BETA / Work as expected.
|
|
//
|
|
// Ref:
|
|
// https://github.com/mharizanov/Daikin-AC-remote-control-over-the-Internet/tree/master/IRremote
|
|
bool IRrecv::decodeDaikin2(decode_results *results, uint16_t nbits,
|
|
bool strict) {
|
|
if (results->rawlen < 2 * (nbits + kHeader + kFooter) + kHeader - 1)
|
|
return false;
|
|
|
|
// Compliance
|
|
if (strict && nbits != kDaikin2Bits) return false;
|
|
|
|
uint16_t offset = kStartOffset;
|
|
const uint8_t ksectionSize[kDaikin2Sections] = {kDaikin2Section1Length,
|
|
kDaikin2Section2Length};
|
|
|
|
// Leader
|
|
if (!matchMark(results->rawbuf[offset++], kDaikin2LeaderMark,
|
|
_tolerance + kDaikin2Tolerance)) return false;
|
|
if (!matchSpace(results->rawbuf[offset++], kDaikin2LeaderSpace,
|
|
_tolerance + kDaikin2Tolerance)) return false;
|
|
|
|
// Sections
|
|
uint16_t pos = 0;
|
|
for (uint8_t section = 0; section < kDaikin2Sections; section++) {
|
|
uint16_t used;
|
|
// Section Header + Section Data + Section Footer
|
|
used = matchGeneric(results->rawbuf + offset, results->state + pos,
|
|
results->rawlen - offset, ksectionSize[section] * 8,
|
|
kDaikin2HdrMark, kDaikin2HdrSpace,
|
|
kDaikin2BitMark, kDaikin2OneSpace,
|
|
kDaikin2BitMark, kDaikin2ZeroSpace,
|
|
kDaikin2BitMark, kDaikin2Gap,
|
|
section >= kDaikin2Sections - 1,
|
|
_tolerance + kDaikin2Tolerance, kDaikinMarkExcess,
|
|
false);
|
|
if (used == 0) return false;
|
|
offset += used;
|
|
pos += ksectionSize[section];
|
|
}
|
|
// Compliance
|
|
if (strict) {
|
|
// Re-check we got the correct size/length due to the way we read the data.
|
|
if (pos * 8 != kDaikin2Bits) return false;
|
|
// Validate the checksum.
|
|
if (!IRDaikin2::validChecksum(results->state)) return false;
|
|
}
|
|
|
|
// Success
|
|
results->decode_type = DAIKIN2;
|
|
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;
|
|
}
|
|
#endif // DECODE_DAIKIN2
|
|
|
|
#if SEND_DAIKIN216
|
|
// Send a Daikin 216 bit A/C message.
|
|
//
|
|
// Args:
|
|
// data: An array of kDaikin216StateLength bytes containing the IR command.
|
|
//
|
|
// Status: Alpha/Untested on a real device.
|
|
//
|
|
// Supported devices:
|
|
// - Daikin ARC433B69 remote.
|
|
//
|
|
// Ref:
|
|
// https://github.com/crankyoldgit/IRremoteESP8266/issues/689
|
|
// https://github.com/danny-source/Arduino_DY_IRDaikin
|
|
void IRsend::sendDaikin216(const unsigned char data[], const uint16_t nbytes,
|
|
const uint16_t repeat) {
|
|
if (nbytes < kDaikin216Section1Length)
|
|
return; // Not enough bytes to send a partial message.
|
|
|
|
for (uint16_t r = 0; r <= repeat; r++) {
|
|
// Section #1
|
|
sendGeneric(kDaikin216HdrMark, kDaikin216HdrSpace, kDaikin216BitMark,
|
|
kDaikin216OneSpace, kDaikin216BitMark, kDaikin216ZeroSpace,
|
|
kDaikin216BitMark, kDaikin216Gap, data,
|
|
kDaikin216Section1Length,
|
|
kDaikin216Freq, false, 0, kDutyDefault);
|
|
// Section #2
|
|
sendGeneric(kDaikin216HdrMark, kDaikin216HdrSpace, kDaikin216BitMark,
|
|
kDaikin216OneSpace, kDaikin216BitMark, kDaikin216ZeroSpace,
|
|
kDaikin216BitMark, kDaikin216Gap,
|
|
data + kDaikin216Section1Length,
|
|
nbytes - kDaikin216Section1Length,
|
|
kDaikin216Freq, false, 0, kDutyDefault);
|
|
}
|
|
}
|
|
#endif // SEND_DAIKIN216
|
|
|
|
// Class for handling Daikin 216 bit / 27 byte A/C messages.
|
|
//
|
|
// Code by crankyoldgit.
|
|
//
|
|
// Supported Remotes: Daikin ARC433B69 remote
|
|
//
|
|
// Ref:
|
|
// https://github.com/crankyoldgit/IRremoteESP8266/issues/689
|
|
// https://github.com/danny-source/Arduino_DY_IRDaikin
|
|
IRDaikin216::IRDaikin216(const uint16_t pin, const bool inverted,
|
|
const bool use_modulation)
|
|
: _irsend(pin, inverted, use_modulation) { stateReset(); }
|
|
|
|
void IRDaikin216::begin() { _irsend.begin(); }
|
|
|
|
#if SEND_DAIKIN216
|
|
void IRDaikin216::send(const uint16_t repeat) {
|
|
checksum();
|
|
_irsend.sendDaikin216(remote_state, kDaikin216StateLength, repeat);
|
|
}
|
|
#endif // SEND_DAIKIN216
|
|
|
|
// Verify the checksum is valid for a given state.
|
|
// Args:
|
|
// state: The array to verify the checksum of.
|
|
// length: The size of the state.
|
|
// Returns:
|
|
// A boolean.
|
|
bool IRDaikin216::validChecksum(uint8_t state[], const uint16_t length) {
|
|
// Validate the checksum of section #1.
|
|
if (length <= kDaikin216Section1Length - 1 ||
|
|
state[kDaikin216Section1Length - 1] != sumBytes(
|
|
state, kDaikin216Section1Length - 1))
|
|
return false;
|
|
// Validate the checksum of section #2 (a.k.a. the rest)
|
|
if (length <= kDaikin216Section1Length + 1 ||
|
|
state[length - 1] != sumBytes(state + kDaikin216Section1Length,
|
|
length - kDaikin216Section1Length - 1))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
// Calculate and set the checksum values for the internal state.
|
|
void IRDaikin216::checksum() {
|
|
remote_state[kDaikin216Section1Length - 1] = sumBytes(
|
|
remote_state, kDaikin216Section1Length - 1);
|
|
remote_state[kDaikin216StateLength - 1] = sumBytes(
|
|
remote_state + kDaikin216Section1Length, kDaikin216Section2Length - 1);
|
|
}
|
|
|
|
void IRDaikin216::stateReset() {
|
|
for (uint8_t i = 0; i < kDaikin216StateLength; i++) remote_state[i] = 0x00;
|
|
remote_state[0] = 0x11;
|
|
remote_state[1] = 0xDA;
|
|
remote_state[2] = 0x27;
|
|
remote_state[3] = 0xF0;
|
|
// remote_state[7] is a checksum byte, it will be set by checksum().
|
|
remote_state[8] = 0x11;
|
|
remote_state[9] = 0xDA;
|
|
remote_state[10] = 0x27;
|
|
remote_state[23] = 0xC0;
|
|
// remote_state[26] is a checksum byte, it will be set by checksum().
|
|
}
|
|
|
|
uint8_t *IRDaikin216::getRaw() {
|
|
checksum(); // Ensure correct settings before sending.
|
|
return remote_state;
|
|
}
|
|
|
|
void IRDaikin216::setRaw(const uint8_t new_code[]) {
|
|
for (uint8_t i = 0; i < kDaikin216StateLength; i++)
|
|
remote_state[i] = new_code[i];
|
|
}
|
|
|
|
|
|
void IRDaikin216::on() {
|
|
remote_state[kDaikin216BytePower] |= kDaikinBitPower;
|
|
}
|
|
|
|
void IRDaikin216::off() {
|
|
remote_state[kDaikin216BytePower] &= ~kDaikinBitPower;
|
|
}
|
|
|
|
void IRDaikin216::setPower(const bool state) {
|
|
if (state)
|
|
on();
|
|
else
|
|
off();
|
|
}
|
|
|
|
bool IRDaikin216::getPower() {
|
|
return remote_state[kDaikin216BytePower] & kDaikinBitPower;
|
|
}
|
|
|
|
uint8_t IRDaikin216::getMode() {
|
|
return (remote_state[kDaikin216ByteMode] & kDaikin216MaskMode) >> 4;
|
|
}
|
|
|
|
void IRDaikin216::setMode(const uint8_t mode) {
|
|
switch (mode) {
|
|
case kDaikinAuto:
|
|
case kDaikinCool:
|
|
case kDaikinHeat:
|
|
case kDaikinFan:
|
|
case kDaikinDry:
|
|
remote_state[kDaikin216ByteMode] &= ~kDaikin216MaskMode;
|
|
remote_state[kDaikin216ByteMode] |= (mode << 4);
|
|
break;
|
|
default:
|
|
this->setMode(kDaikinAuto);
|
|
}
|
|
}
|
|
|
|
// Convert a standard A/C mode into its native mode.
|
|
uint8_t IRDaikin216::convertMode(const stdAc::opmode_t mode) {
|
|
return IRDaikinESP::convertMode(mode);
|
|
}
|
|
|
|
// Set the temp in deg C
|
|
void IRDaikin216::setTemp(const uint8_t temp) {
|
|
uint8_t degrees = std::max(temp, kDaikinMinTemp);
|
|
degrees = std::min(degrees, kDaikinMaxTemp);
|
|
remote_state[kDaikin216ByteTemp] &= ~kDaikin216MaskTemp;
|
|
remote_state[kDaikin216ByteTemp] |= (degrees << 1);
|
|
}
|
|
|
|
uint8_t IRDaikin216::getTemp(void) {
|
|
return (remote_state[kDaikin216ByteTemp] & kDaikin216MaskTemp) >> 1;
|
|
}
|
|
|
|
// Set the speed of the fan, 1-5 or kDaikinFanAuto or kDaikinFanQuiet
|
|
void IRDaikin216::setFan(const uint8_t fan) {
|
|
// Set the fan speed bits, leave low 4 bits alone
|
|
uint8_t fanset;
|
|
if (fan == kDaikinFanQuiet || fan == kDaikinFanAuto)
|
|
fanset = fan;
|
|
else if (fan < kDaikinFanMin || fan > kDaikinFanMax)
|
|
fanset = kDaikinFanAuto;
|
|
else
|
|
fanset = 2 + fan;
|
|
remote_state[kDaikin216ByteFan] &= ~kDaikin216MaskFan;
|
|
remote_state[kDaikin216ByteFan] |= (fanset << 4);
|
|
}
|
|
|
|
uint8_t IRDaikin216::getFan() {
|
|
uint8_t fan = remote_state[kDaikin216ByteFan] >> 4;
|
|
if (fan != kDaikinFanQuiet && fan != kDaikinFanAuto) fan -= 2;
|
|
return fan;
|
|
}
|
|
|
|
// Convert a standard A/C Fan speed into its native fan speed.
|
|
uint8_t IRDaikin216::convertFan(const stdAc::fanspeed_t speed) {
|
|
return IRDaikinESP::convertFan(speed);
|
|
}
|
|
|
|
void IRDaikin216::setSwingVertical(const bool on) {
|
|
if (on)
|
|
remote_state[kDaikin216ByteSwingV] |= kDaikin216MaskSwingV;
|
|
else
|
|
remote_state[kDaikin216ByteSwingV] &= ~kDaikin216MaskSwingV;
|
|
}
|
|
|
|
bool IRDaikin216::getSwingVertical(void) {
|
|
return remote_state[kDaikin216ByteSwingV] & kDaikin216MaskSwingV;
|
|
}
|
|
|
|
void IRDaikin216::setSwingHorizontal(const bool on) {
|
|
if (on)
|
|
remote_state[kDaikin216ByteSwingH] |= kDaikin216MaskSwingH;
|
|
else
|
|
remote_state[kDaikin216ByteSwingH] &= ~kDaikin216MaskSwingH;
|
|
}
|
|
|
|
bool IRDaikin216::getSwingHorizontal(void) {
|
|
return remote_state[kDaikin216ByteSwingH] & kDaikin216MaskSwingH;
|
|
}
|
|
|
|
// This is a horrible hack till someone works out the quiet mode bit.
|
|
void IRDaikin216::setQuiet(const bool on) {
|
|
if (on) {
|
|
this->setFan(kDaikinFanQuiet);
|
|
// Powerful & Quiet mode being on are mutually exclusive.
|
|
this->setPowerful(false);
|
|
} else if (this->getFan() == kDaikinFanQuiet) {
|
|
this->setFan(kDaikinFanAuto);
|
|
}
|
|
}
|
|
|
|
// This is a horrible hack till someone works out the quiet mode bit.
|
|
bool IRDaikin216::getQuiet(void) {
|
|
return this->getFan() == kDaikinFanQuiet;
|
|
}
|
|
|
|
void IRDaikin216::setPowerful(const bool on) {
|
|
if (on) {
|
|
remote_state[kDaikin216BytePowerful] |= kDaikinBitPowerful;
|
|
// Powerful & Quiet mode being on are mutually exclusive.
|
|
this->setQuiet(false);
|
|
} else {
|
|
remote_state[kDaikin216BytePowerful] &= ~kDaikinBitPowerful;
|
|
}
|
|
}
|
|
|
|
bool IRDaikin216::getPowerful() {
|
|
return remote_state[kDaikin216BytePowerful] & kDaikinBitPowerful;
|
|
}
|
|
|
|
// Convert the A/C state to it's common equivalent.
|
|
stdAc::state_t IRDaikin216::toCommon(void) {
|
|
stdAc::state_t result;
|
|
result.protocol = decode_type_t::DAIKIN216;
|
|
result.model = -1; // No models used.
|
|
result.power = this->getPower();
|
|
result.mode = IRDaikinESP::toCommonMode(this->getMode());
|
|
result.celsius = true;
|
|
result.degrees = this->getTemp();
|
|
result.fanspeed = IRDaikinESP::toCommonFanSpeed(this->getFan());
|
|
result.swingv = this->getSwingVertical() ? stdAc::swingv_t::kAuto :
|
|
stdAc::swingv_t::kOff;
|
|
result.swingh = this->getSwingHorizontal() ? stdAc::swingh_t::kAuto :
|
|
stdAc::swingh_t::kOff;
|
|
result.quiet = this->getQuiet();
|
|
result.turbo = this->getPowerful();
|
|
// Not supported.
|
|
result.light = false;
|
|
result.clean = false;
|
|
result.econo = false;
|
|
result.filter = false;
|
|
result.beep = false;
|
|
result.sleep = -1;
|
|
result.clock = -1;
|
|
return result;
|
|
}
|
|
|
|
// Convert the internal state into a human readable string.
|
|
String IRDaikin216::toString() {
|
|
String result = "";
|
|
result.reserve(120); // Reserve some heap for the string to reduce fragging.
|
|
result += addBoolToString(getPower(), F("Power"), false);
|
|
result += addModeToString(getMode(), kDaikinAuto, kDaikinCool, kDaikinHeat,
|
|
kDaikinDry, kDaikinFan);
|
|
result += addTempToString(getTemp());
|
|
result += addFanToString(getFan(), kDaikinFanMax, kDaikinFanMin,
|
|
kDaikinFanAuto, kDaikinFanQuiet, kDaikinFanMed);
|
|
result += addBoolToString(getSwingHorizontal(), F("Swing (Horizontal)"));
|
|
result += addBoolToString(getSwingVertical(), F("Swing (Vertical)"));
|
|
result += addBoolToString(getQuiet(), F("Quiet"));
|
|
result += addBoolToString(getPowerful(), F("Powerful"));
|
|
return result;
|
|
}
|
|
|
|
#if DECODE_DAIKIN216
|
|
// Decode the supplied Daikin 216 bit A/C message.
|
|
// Args:
|
|
// results: Ptr to the data to decode and where to store the decode result.
|
|
// nbits: Nr. of bits to expect in the data portion. (kDaikin216Bits)
|
|
// strict: Flag to indicate if we strictly adhere to the specification.
|
|
// Returns:
|
|
// boolean: True if it can decode it, false if it can't.
|
|
//
|
|
// Supported devices:
|
|
// - Daikin ARC433B69 remote.
|
|
//
|
|
// Status: BETA / Should be working.
|
|
//
|
|
// Ref:
|
|
// https://github.com/crankyoldgit/IRremoteESP8266/issues/689
|
|
// https://github.com/danny-source/Arduino_DY_IRDaikin
|
|
bool IRrecv::decodeDaikin216(decode_results *results, const uint16_t nbits,
|
|
const bool strict) {
|
|
if (results->rawlen < 2 * (nbits + kHeader + kFooter) - 1)
|
|
return false;
|
|
|
|
// Compliance
|
|
if (strict && nbits != kDaikin216Bits) return false;
|
|
|
|
uint16_t offset = kStartOffset;
|
|
const uint8_t ksectionSize[kDaikin216Sections] = {kDaikin216Section1Length,
|
|
kDaikin216Section2Length};
|
|
// Sections
|
|
uint16_t pos = 0;
|
|
for (uint8_t section = 0; section < kDaikin216Sections; section++) {
|
|
uint16_t used;
|
|
// Section Header + Section Data + Section Footer
|
|
used = matchGeneric(results->rawbuf + offset, results->state + pos,
|
|
results->rawlen - offset, ksectionSize[section] * 8,
|
|
kDaikin216HdrMark, kDaikin216HdrSpace,
|
|
kDaikin216BitMark, kDaikin216OneSpace,
|
|
kDaikin216BitMark, kDaikin216ZeroSpace,
|
|
kDaikin216BitMark, kDaikin216Gap,
|
|
section >= kDaikin216Sections - 1,
|
|
kDaikinTolerance, kDaikinMarkExcess, false);
|
|
if (used == 0) return false;
|
|
offset += used;
|
|
pos += ksectionSize[section];
|
|
}
|
|
// Compliance
|
|
if (strict) {
|
|
if (pos * 8 != kDaikin216Bits) return false;
|
|
// Validate the checksum.
|
|
if (!IRDaikin216::validChecksum(results->state)) return false;
|
|
}
|
|
|
|
// Success
|
|
results->decode_type = decode_type_t::DAIKIN216;
|
|
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;
|
|
}
|
|
#endif // DECODE_DAIKIN216
|
|
|
|
#if SEND_DAIKIN160
|
|
// Send a Daikin 160 bit A/C message.
|
|
//
|
|
// Args:
|
|
// data: An array of kDaikin160StateLength bytes containing the IR command.
|
|
//
|
|
// Status: STABLE / Confirmed working.
|
|
//
|
|
// Supported devices:
|
|
// - Daikin ARC423A5 remote.
|
|
//
|
|
// Ref:
|
|
// https://github.com/crankyoldgit/IRremoteESP8266/issues/731
|
|
void IRsend::sendDaikin160(const unsigned char data[], const uint16_t nbytes,
|
|
const uint16_t repeat) {
|
|
if (nbytes < kDaikin160Section1Length)
|
|
return; // Not enough bytes to send a partial message.
|
|
|
|
for (uint16_t r = 0; r <= repeat; r++) {
|
|
// Section #1
|
|
sendGeneric(kDaikin160HdrMark, kDaikin160HdrSpace, kDaikin160BitMark,
|
|
kDaikin160OneSpace, kDaikin160BitMark, kDaikin160ZeroSpace,
|
|
kDaikin160BitMark, kDaikin160Gap, data,
|
|
kDaikin160Section1Length,
|
|
kDaikin160Freq, false, 0, kDutyDefault);
|
|
// Section #2
|
|
sendGeneric(kDaikin160HdrMark, kDaikin160HdrSpace, kDaikin160BitMark,
|
|
kDaikin160OneSpace, kDaikin160BitMark, kDaikin160ZeroSpace,
|
|
kDaikin160BitMark, kDaikin160Gap,
|
|
data + kDaikin160Section1Length,
|
|
nbytes - kDaikin160Section1Length,
|
|
kDaikin160Freq, false, 0, kDutyDefault);
|
|
}
|
|
}
|
|
#endif // SEND_DAIKIN160
|
|
|
|
// Class for handling Daikin 160 bit / 20 byte A/C messages.
|
|
//
|
|
// Code by crankyoldgit.
|
|
//
|
|
// Supported Remotes: Daikin ARC423A5 remote
|
|
//
|
|
// Ref:
|
|
// https://github.com/crankyoldgit/IRremoteESP8266/issues/731
|
|
IRDaikin160::IRDaikin160(const uint16_t pin, const bool inverted,
|
|
const bool use_modulation)
|
|
: _irsend(pin, inverted, use_modulation) { stateReset(); }
|
|
|
|
void IRDaikin160::begin() { _irsend.begin(); }
|
|
|
|
// Verify the checksum is valid for a given state.
|
|
// Args:
|
|
// state: The array to verify the checksum of.
|
|
// length: The size of the state.
|
|
// Returns:
|
|
// A boolean.
|
|
bool IRDaikin160::validChecksum(uint8_t state[], const uint16_t length) {
|
|
// Validate the checksum of section #1.
|
|
if (length <= kDaikin160Section1Length - 1 ||
|
|
state[kDaikin160Section1Length - 1] != sumBytes(
|
|
state, kDaikin160Section1Length - 1))
|
|
return false;
|
|
// Validate the checksum of section #2 (a.k.a. the rest)
|
|
if (length <= kDaikin160Section1Length + 1 ||
|
|
state[length - 1] != sumBytes(state + kDaikin160Section1Length,
|
|
length - kDaikin160Section1Length - 1))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
// Calculate and set the checksum values for the internal state.
|
|
void IRDaikin160::checksum() {
|
|
remote_state[kDaikin160Section1Length - 1] = sumBytes(
|
|
remote_state, kDaikin160Section1Length - 1);
|
|
remote_state[kDaikin160StateLength - 1] = sumBytes(
|
|
remote_state + kDaikin160Section1Length, kDaikin160Section2Length - 1);
|
|
}
|
|
|
|
void IRDaikin160::stateReset() {
|
|
for (uint8_t i = 0; i < kDaikin160StateLength; i++) remote_state[i] = 0x00;
|
|
remote_state[0] = 0x11;
|
|
remote_state[1] = 0xDA;
|
|
remote_state[2] = 0x27;
|
|
remote_state[3] = 0xF0;
|
|
remote_state[4] = 0x0D;
|
|
// remote_state[6] is a checksum byte, it will be set by checksum().
|
|
remote_state[7] = 0x11;
|
|
remote_state[8] = 0xDA;
|
|
remote_state[9] = 0x27;
|
|
remote_state[11] = 0xD3;
|
|
remote_state[12] = 0x30;
|
|
remote_state[13] = 0x11;
|
|
remote_state[16] = 0x1E;
|
|
remote_state[17] = 0x0A;
|
|
remote_state[18] = 0x08;
|
|
// remote_state[19] is a checksum byte, it will be set by checksum().
|
|
}
|
|
|
|
uint8_t *IRDaikin160::getRaw() {
|
|
checksum(); // Ensure correct settings before sending.
|
|
return remote_state;
|
|
}
|
|
|
|
void IRDaikin160::setRaw(const uint8_t new_code[]) {
|
|
for (uint8_t i = 0; i < kDaikin160StateLength; i++)
|
|
remote_state[i] = new_code[i];
|
|
}
|
|
|
|
#if SEND_DAIKIN160
|
|
void IRDaikin160::send(const uint16_t repeat) {
|
|
checksum();
|
|
_irsend.sendDaikin160(remote_state, kDaikin160StateLength, repeat);
|
|
}
|
|
#endif // SEND_DAIKIN160
|
|
|
|
void IRDaikin160::on() {
|
|
remote_state[kDaikin160BytePower] |= kDaikinBitPower;
|
|
}
|
|
|
|
void IRDaikin160::off() {
|
|
remote_state[kDaikin160BytePower] &= ~kDaikinBitPower;
|
|
}
|
|
|
|
void IRDaikin160::setPower(const bool state) {
|
|
if (state)
|
|
on();
|
|
else
|
|
off();
|
|
}
|
|
|
|
bool IRDaikin160::getPower() {
|
|
return remote_state[kDaikin160BytePower] & kDaikinBitPower;
|
|
}
|
|
|
|
uint8_t IRDaikin160::getMode() {
|
|
return (remote_state[kDaikin160ByteMode] & kDaikin160MaskMode) >> 4;
|
|
}
|
|
|
|
void IRDaikin160::setMode(const uint8_t mode) {
|
|
switch (mode) {
|
|
case kDaikinAuto:
|
|
case kDaikinCool:
|
|
case kDaikinHeat:
|
|
case kDaikinFan:
|
|
case kDaikinDry:
|
|
remote_state[kDaikin160ByteMode] &= ~kDaikin160MaskMode;
|
|
remote_state[kDaikin160ByteMode] |= (mode << 4);
|
|
break;
|
|
default:
|
|
this->setMode(kDaikinAuto);
|
|
}
|
|
}
|
|
|
|
// Convert a standard A/C mode into its native mode.
|
|
uint8_t IRDaikin160::convertMode(const stdAc::opmode_t mode) {
|
|
return IRDaikinESP::convertMode(mode);
|
|
}
|
|
|
|
// Set the temp in deg C
|
|
void IRDaikin160::setTemp(const uint8_t temp) {
|
|
uint8_t degrees = std::max(temp, kDaikinMinTemp);
|
|
degrees = std::min(degrees, kDaikinMaxTemp) * 2 - 20;
|
|
remote_state[kDaikin160ByteTemp] &= ~kDaikin160MaskTemp;
|
|
remote_state[kDaikin160ByteTemp] |= degrees;
|
|
}
|
|
|
|
uint8_t IRDaikin160::getTemp(void) {
|
|
return (((remote_state[kDaikin160ByteTemp] & kDaikin160MaskTemp) / 2 ) + 10);
|
|
}
|
|
|
|
// Set the speed of the fan, 1-5 or kDaikinFanAuto or kDaikinFanQuiet
|
|
void IRDaikin160::setFan(const uint8_t fan) {
|
|
uint8_t fanset;
|
|
if (fan == kDaikinFanQuiet || fan == kDaikinFanAuto)
|
|
fanset = fan;
|
|
else if (fan < kDaikinFanMin || fan > kDaikinFanMax)
|
|
fanset = kDaikinFanAuto;
|
|
else
|
|
fanset = 2 + fan;
|
|
// Set the fan speed bits, leave *upper* 4 bits alone
|
|
remote_state[kDaikin160ByteFan] &= ~kDaikin160MaskFan;
|
|
remote_state[kDaikin160ByteFan] |= fanset;
|
|
}
|
|
|
|
uint8_t IRDaikin160::getFan() {
|
|
uint8_t fan = remote_state[kDaikin160ByteFan] & kDaikin160MaskFan;
|
|
if (fan != kDaikinFanQuiet && fan != kDaikinFanAuto) fan -= 2;
|
|
return fan;
|
|
}
|
|
|
|
// Convert a standard A/C Fan speed into its native fan speed.
|
|
uint8_t IRDaikin160::convertFan(const stdAc::fanspeed_t speed) {
|
|
switch (speed) {
|
|
case stdAc::fanspeed_t::kMin: return kDaikinFanMin;
|
|
case stdAc::fanspeed_t::kLow: return kDaikinFanMin + 1;
|
|
case stdAc::fanspeed_t::kMedium: return kDaikinFanMin + 2;
|
|
case stdAc::fanspeed_t::kHigh: return kDaikinFanMax - 1;
|
|
case stdAc::fanspeed_t::kMax: return kDaikinFanMax;
|
|
default:
|
|
return kDaikinFanAuto;
|
|
}
|
|
}
|
|
|
|
void IRDaikin160::setSwingVertical(const uint8_t position) {
|
|
switch (position) {
|
|
case kDaikin160SwingVLowest:
|
|
case kDaikin160SwingVLow:
|
|
case kDaikin160SwingVMiddle:
|
|
case kDaikin160SwingVHigh:
|
|
case kDaikin160SwingVHighest:
|
|
case kDaikin160SwingVAuto:
|
|
remote_state[kDaikin160ByteSwingV] &= ~kDaikin160MaskSwingV;
|
|
remote_state[kDaikin160ByteSwingV] |= (position << 4);
|
|
break;
|
|
default:
|
|
setSwingVertical(kDaikin160SwingVAuto);
|
|
}
|
|
}
|
|
|
|
uint8_t IRDaikin160::getSwingVertical(void) {
|
|
return remote_state[kDaikin160ByteSwingV] >> 4;
|
|
}
|
|
|
|
// Convert a standard A/C vertical swing into its native version.
|
|
uint8_t IRDaikin160::convertSwingV(const stdAc::swingv_t position) {
|
|
switch (position) {
|
|
case stdAc::swingv_t::kHighest:
|
|
case stdAc::swingv_t::kHigh:
|
|
case stdAc::swingv_t::kMiddle:
|
|
case stdAc::swingv_t::kLow:
|
|
case stdAc::swingv_t::kLowest:
|
|
return kDaikin160SwingVHighest + 1 - (uint8_t)position;
|
|
default:
|
|
return kDaikin160SwingVAuto;
|
|
}
|
|
}
|
|
|
|
// Convert a native vertical swing to it's common equivalent.
|
|
stdAc::swingv_t IRDaikin160::toCommonSwingV(const uint8_t setting) {
|
|
switch (setting) {
|
|
case kDaikin160SwingVHighest: return stdAc::swingv_t::kHighest;
|
|
case kDaikin160SwingVHigh: return stdAc::swingv_t::kHigh;
|
|
case kDaikin160SwingVMiddle: return stdAc::swingv_t::kMiddle;
|
|
case kDaikin160SwingVLow: return stdAc::swingv_t::kLow;
|
|
case kDaikin160SwingVLowest: return stdAc::swingv_t::kLowest;
|
|
default:
|
|
return stdAc::swingv_t::kAuto;
|
|
}
|
|
}
|
|
|
|
// Convert the A/C state to it's common equivalent.
|
|
stdAc::state_t IRDaikin160::toCommon(void) {
|
|
stdAc::state_t result;
|
|
result.protocol = decode_type_t::DAIKIN160;
|
|
result.model = -1; // No models used.
|
|
result.power = this->getPower();
|
|
result.mode = IRDaikinESP::toCommonMode(this->getMode());
|
|
result.celsius = true;
|
|
result.degrees = this->getTemp();
|
|
result.fanspeed = IRDaikinESP::toCommonFanSpeed(this->getFan());
|
|
result.swingv = this->toCommonSwingV(this->getSwingVertical());
|
|
// Not supported.
|
|
result.swingh = stdAc::swingh_t::kOff;
|
|
result.quiet = false;
|
|
result.turbo = false;
|
|
result.light = false;
|
|
result.clean = false;
|
|
result.econo = false;
|
|
result.filter = false;
|
|
result.beep = false;
|
|
result.sleep = -1;
|
|
result.clock = -1;
|
|
return result;
|
|
}
|
|
|
|
// Convert the internal state into a human readable string.
|
|
String IRDaikin160::toString() {
|
|
String result = "";
|
|
result.reserve(150); // Reserve some heap for the string to reduce fragging.
|
|
result += addBoolToString(getPower(), F("Power"), false);
|
|
result += addModeToString(getMode(), kDaikinAuto, kDaikinCool, kDaikinHeat,
|
|
kDaikinDry, kDaikinFan);
|
|
result += addTempToString(getTemp());
|
|
result += addFanToString(getFan(), kDaikinFanMax, kDaikinFanMin,
|
|
kDaikinFanAuto, kDaikinFanQuiet, kDaikinFanMed);
|
|
result += addIntToString(getSwingVertical(), F("Vent Position (V)"));
|
|
switch (getSwingVertical()) {
|
|
case kDaikin160SwingVHighest: result += F(" (Highest)"); break;
|
|
case kDaikin160SwingVHigh: result += F(" (High)"); break;
|
|
case kDaikin160SwingVMiddle: result += F(" (Middle)"); break;
|
|
case kDaikin160SwingVLow: result += F(" (Low)"); break;
|
|
case kDaikin160SwingVLowest: result += F(" (Lowest)"); break;
|
|
case kDaikin160SwingVAuto: result += F(" (Auto)"); break;
|
|
default:
|
|
result += F(" (Unknown)");
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#if DECODE_DAIKIN160
|
|
// Decode the supplied Daikin 160 bit A/C message.
|
|
// Args:
|
|
// results: Ptr to the data to decode and where to store the decode result.
|
|
// nbits: Nr. of bits to expect in the data portion. (kDaikin160Bits)
|
|
// strict: Flag to indicate if we strictly adhere to the specification.
|
|
// Returns:
|
|
// boolean: True if it can decode it, false if it can't.
|
|
//
|
|
// Supported devices:
|
|
// - Daikin ARC423A5 remote.
|
|
//
|
|
// Status: STABLE / Confirmed working.
|
|
//
|
|
// Ref:
|
|
// https://github.com/crankyoldgit/IRremoteESP8266/issues/731
|
|
bool IRrecv::decodeDaikin160(decode_results *results, const uint16_t nbits,
|
|
const bool strict) {
|
|
if (results->rawlen < 2 * (nbits + kHeader + kFooter) - 1)
|
|
return false;
|
|
|
|
// Compliance
|
|
if (strict && nbits != kDaikin160Bits) return false;
|
|
|
|
uint16_t offset = kStartOffset;
|
|
const uint8_t ksectionSize[kDaikin160Sections] = {kDaikin160Section1Length,
|
|
kDaikin160Section2Length};
|
|
|
|
// Sections
|
|
uint16_t pos = 0;
|
|
for (uint8_t section = 0; section < kDaikin160Sections; section++) {
|
|
uint16_t used;
|
|
// Section Header + Section Data (7 bytes) + Section Footer
|
|
used = matchGeneric(results->rawbuf + offset, results->state + pos,
|
|
results->rawlen - offset, ksectionSize[section] * 8,
|
|
kDaikin160HdrMark, kDaikin160HdrSpace,
|
|
kDaikin160BitMark, kDaikin160OneSpace,
|
|
kDaikin160BitMark, kDaikin160ZeroSpace,
|
|
kDaikin160BitMark, kDaikin160Gap,
|
|
section >= kDaikin160Sections - 1,
|
|
kDaikinTolerance, kDaikinMarkExcess, false);
|
|
if (used == 0) return false;
|
|
offset += used;
|
|
pos += ksectionSize[section];
|
|
}
|
|
// Compliance
|
|
if (strict) {
|
|
// Validate the checksum.
|
|
if (!IRDaikin160::validChecksum(results->state)) return false;
|
|
}
|
|
|
|
// Success
|
|
results->decode_type = decode_type_t::DAIKIN160;
|
|
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;
|
|
}
|
|
#endif // DECODE_DAIKIN160
|
|
|
|
#if SEND_DAIKIN176
|
|
// Send a Daikin 176 bit A/C message.
|
|
//
|
|
// Args:
|
|
// data: An array of kDaikin176StateLength bytes containing the IR command.
|
|
//
|
|
// Status: Alpha/Untested on a real device.
|
|
//
|
|
// Supported devices:
|
|
// - Daikin BRC4C153 remote.
|
|
//
|
|
void IRsend::sendDaikin176(const unsigned char data[], const uint16_t nbytes,
|
|
const uint16_t repeat) {
|
|
if (nbytes < kDaikin176Section1Length)
|
|
return; // Not enough bytes to send a partial message.
|
|
|
|
for (uint16_t r = 0; r <= repeat; r++) {
|
|
// Section #1
|
|
sendGeneric(kDaikin176HdrMark, kDaikin176HdrSpace, kDaikin176BitMark,
|
|
kDaikin176OneSpace, kDaikin176BitMark, kDaikin176ZeroSpace,
|
|
kDaikin176BitMark, kDaikin176Gap, data,
|
|
kDaikin176Section1Length,
|
|
kDaikin176Freq, false, 0, kDutyDefault);
|
|
// Section #2
|
|
sendGeneric(kDaikin176HdrMark, kDaikin176HdrSpace, kDaikin176BitMark,
|
|
kDaikin176OneSpace, kDaikin176BitMark, kDaikin176ZeroSpace,
|
|
kDaikin176BitMark, kDaikin176Gap,
|
|
data + kDaikin176Section1Length,
|
|
nbytes - kDaikin176Section1Length,
|
|
kDaikin176Freq, false, 0, kDutyDefault);
|
|
}
|
|
}
|
|
#endif // SEND_DAIKIN176
|
|
|
|
// Class for handling Daikin 176 bit / 22 byte A/C messages.
|
|
//
|
|
// Code by crankyoldgit.
|
|
//
|
|
// Supported Remotes: Daikin BRC4C153 remote
|
|
//
|
|
IRDaikin176::IRDaikin176(const uint16_t pin, const bool inverted,
|
|
const bool use_modulation)
|
|
: _irsend(pin, inverted, use_modulation) { stateReset(); }
|
|
|
|
void IRDaikin176::begin() { _irsend.begin(); }
|
|
|
|
// Verify the checksum is valid for a given state.
|
|
// Args:
|
|
// state: The array to verify the checksum of.
|
|
// length: The size of the state.
|
|
// Returns:
|
|
// A boolean.
|
|
bool IRDaikin176::validChecksum(uint8_t state[], const uint16_t length) {
|
|
// Validate the checksum of section #1.
|
|
if (length <= kDaikin176Section1Length - 1 ||
|
|
state[kDaikin176Section1Length - 1] != sumBytes(
|
|
state, kDaikin176Section1Length - 1))
|
|
return false;
|
|
// Validate the checksum of section #2 (a.k.a. the rest)
|
|
if (length <= kDaikin176Section1Length + 1 ||
|
|
state[length - 1] != sumBytes(state + kDaikin176Section1Length,
|
|
length - kDaikin176Section1Length - 1))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
// Calculate and set the checksum values for the internal state.
|
|
void IRDaikin176::checksum() {
|
|
remote_state[kDaikin176Section1Length - 1] = sumBytes(
|
|
remote_state, kDaikin176Section1Length - 1);
|
|
remote_state[kDaikin176StateLength - 1] = sumBytes(
|
|
remote_state + kDaikin176Section1Length, kDaikin176Section2Length - 1);
|
|
}
|
|
|
|
void IRDaikin176::stateReset() {
|
|
for (uint8_t i = 0; i < kDaikin176StateLength; i++) remote_state[i] = 0x00;
|
|
remote_state[0] = 0x11;
|
|
remote_state[1] = 0xDA;
|
|
remote_state[2] = 0x17;
|
|
remote_state[3] = 0x18;
|
|
remote_state[4] = 0x04;
|
|
// remote_state[6] is a checksum byte, it will be set by checksum().
|
|
remote_state[7] = 0x11;
|
|
remote_state[8] = 0xDA;
|
|
remote_state[9] = 0x17;
|
|
remote_state[10] = 0x18;
|
|
remote_state[12] = 0x73;
|
|
remote_state[14] = 0x20;
|
|
remote_state[18] = 0x16; // Fan speed and swing
|
|
remote_state[20] = 0x20;
|
|
// remote_state[21] is a checksum byte, it will be set by checksum().
|
|
_saved_temp = getTemp();
|
|
}
|
|
|
|
uint8_t *IRDaikin176::getRaw() {
|
|
checksum(); // Ensure correct settings before sending.
|
|
return remote_state;
|
|
}
|
|
|
|
void IRDaikin176::setRaw(const uint8_t new_code[]) {
|
|
for (uint8_t i = 0; i < kDaikin176StateLength; i++)
|
|
remote_state[i] = new_code[i];
|
|
_saved_temp = getTemp();
|
|
}
|
|
|
|
#if SEND_DAIKIN176
|
|
void IRDaikin176::send(const uint16_t repeat) {
|
|
checksum();
|
|
_irsend.sendDaikin176(remote_state, kDaikin176StateLength, repeat);
|
|
}
|
|
#endif // SEND_DAIKIN176
|
|
|
|
void IRDaikin176::on() { setPower(true); }
|
|
|
|
void IRDaikin176::off() { setPower(false); }
|
|
|
|
void IRDaikin176::setPower(const bool state) {
|
|
remote_state[kDaikin176ByteModeButton] = 0;
|
|
if (state)
|
|
remote_state[kDaikin176BytePower] |= kDaikinBitPower;
|
|
else
|
|
remote_state[kDaikin176BytePower] &= ~kDaikinBitPower;
|
|
}
|
|
|
|
bool IRDaikin176::getPower() {
|
|
return remote_state[kDaikin176BytePower] & kDaikinBitPower;
|
|
}
|
|
|
|
uint8_t IRDaikin176::getMode() {
|
|
return (remote_state[kDaikin176ByteMode] & kDaikin176MaskMode) >> 4;
|
|
}
|
|
|
|
void IRDaikin176::setMode(const uint8_t mode) {
|
|
uint8_t altmode = 0;
|
|
switch (mode) {
|
|
case kDaikinFan: altmode = 0; break;
|
|
case kDaikinDry: altmode = 7; break;
|
|
case kDaikin176Cool: altmode = 2; break;
|
|
default: this->setMode(kDaikin176Cool); return;
|
|
}
|
|
// Set the mode.
|
|
remote_state[kDaikin176ByteMode] &= ~kDaikin176MaskMode;
|
|
remote_state[kDaikin176ByteMode] |= (mode << 4);
|
|
// Set the altmode
|
|
remote_state[kDaikin176BytePower] &= ~kDaikin176MaskMode;
|
|
remote_state[kDaikin176BytePower] |= (altmode << 4);
|
|
setTemp(_saved_temp);
|
|
// Needs to happen after setTemp() as it will clear it.
|
|
remote_state[kDaikin176ByteModeButton] = kDaikin176ModeButton;
|
|
}
|
|
|
|
// Convert a standard A/C mode into its native mode.
|
|
uint8_t IRDaikin176::convertMode(const stdAc::opmode_t mode) {
|
|
switch (mode) {
|
|
case stdAc::opmode_t::kDry:
|
|
return kDaikinDry;
|
|
case stdAc::opmode_t::kHeat: // Heat not supported, but fan is the closest.
|
|
case stdAc::opmode_t::kFan:
|
|
return kDaikinFan;
|
|
default:
|
|
return kDaikin176Cool;
|
|
}
|
|
}
|
|
|
|
// Convert a native mode to it's common equivalent.
|
|
stdAc::opmode_t IRDaikin176::toCommonMode(const uint8_t mode) {
|
|
switch (mode) {
|
|
case kDaikinDry: return stdAc::opmode_t::kDry;
|
|
case kDaikinHeat: // There is no heat mode, but fan is the closest.
|
|
case kDaikinFan: return stdAc::opmode_t::kFan;
|
|
default: return stdAc::opmode_t::kCool;
|
|
}
|
|
}
|
|
|
|
// Set the temp in deg C
|
|
void IRDaikin176::setTemp(const uint8_t temp) {
|
|
uint8_t degrees = std::min(kDaikinMaxTemp, std::max(temp, kDaikinMinTemp));
|
|
_saved_temp = degrees;
|
|
switch (getMode()) {
|
|
case kDaikinDry:
|
|
case kDaikinFan:
|
|
degrees = kDaikin176DryFanTemp;
|
|
}
|
|
degrees = degrees * 2 - 18;
|
|
remote_state[kDaikin176ByteTemp] &= ~kDaikin176MaskTemp;
|
|
remote_state[kDaikin176ByteTemp] |= degrees;
|
|
remote_state[kDaikin176ByteModeButton] = 0;
|
|
}
|
|
|
|
uint8_t IRDaikin176::getTemp(void) {
|
|
return (((remote_state[kDaikin176ByteTemp] & kDaikin176MaskTemp) / 2 ) + 9);
|
|
}
|
|
|
|
// Set the speed of the fan, 1 for Min or 3 for Max
|
|
void IRDaikin176::setFan(const uint8_t fan) {
|
|
switch (fan) {
|
|
case kDaikinFanMin:
|
|
case kDaikin176FanMax:
|
|
remote_state[kDaikin176ByteFan] &= ~kDaikin176MaskFan;
|
|
remote_state[kDaikin176ByteFan] |= (fan << 4);
|
|
remote_state[kDaikin176ByteModeButton] = 0;
|
|
break;
|
|
default:
|
|
setFan(kDaikin176FanMax);
|
|
}
|
|
}
|
|
|
|
uint8_t IRDaikin176::getFan() { return remote_state[kDaikin176ByteFan] >> 4; }
|
|
|
|
// Convert a standard A/C Fan speed into its native fan speed.
|
|
uint8_t IRDaikin176::convertFan(const stdAc::fanspeed_t speed) {
|
|
switch (speed) {
|
|
case stdAc::fanspeed_t::kMin:
|
|
case stdAc::fanspeed_t::kLow:
|
|
return kDaikinFanMin;
|
|
default:
|
|
return kDaikin176FanMax;
|
|
}
|
|
}
|
|
|
|
void IRDaikin176::setSwingHorizontal(const uint8_t position) {
|
|
switch (position) {
|
|
case kDaikin176SwingHOff:
|
|
case kDaikin176SwingHAuto:
|
|
remote_state[kDaikin176ByteSwingH] &= ~kDaikin176MaskSwingH;
|
|
remote_state[kDaikin176ByteSwingH] |= position;
|
|
break;
|
|
default:
|
|
setSwingHorizontal(kDaikin176SwingHAuto);
|
|
}
|
|
}
|
|
|
|
uint8_t IRDaikin176::getSwingHorizontal() {
|
|
return remote_state[kDaikin176ByteSwingH] & kDaikin176MaskSwingH;
|
|
}
|
|
|
|
// Convert a standard A/C horizontal swing into its native version.
|
|
uint8_t IRDaikin176::convertSwingH(const stdAc::swingh_t position) {
|
|
switch (position) {
|
|
case stdAc::swingh_t::kOff:
|
|
return kDaikin176SwingHOff;
|
|
case stdAc::swingh_t::kAuto:
|
|
return kDaikin176SwingHAuto;
|
|
default:
|
|
return kDaikin176SwingHAuto;
|
|
}
|
|
}
|
|
// Convert a native horizontal swing to it's common equivalent.
|
|
stdAc::swingh_t IRDaikin176::toCommonSwingH(const uint8_t setting) {
|
|
switch (setting) {
|
|
case kDaikin176SwingHOff: return stdAc::swingh_t::kOff;
|
|
case kDaikin176SwingHAuto: return stdAc::swingh_t::kAuto;
|
|
default:
|
|
return stdAc::swingh_t::kAuto;
|
|
}
|
|
}
|
|
|
|
// Convert a native fan speed to it's common equivalent.
|
|
stdAc::fanspeed_t IRDaikin176::toCommonFanSpeed(const uint8_t speed) {
|
|
return (speed == kDaikinFanMin) ? stdAc::fanspeed_t::kMin
|
|
: stdAc::fanspeed_t::kMax;
|
|
}
|
|
|
|
// Convert the A/C state to it's common equivalent.
|
|
stdAc::state_t IRDaikin176::toCommon(void) {
|
|
stdAc::state_t result;
|
|
result.protocol = decode_type_t::DAIKIN176;
|
|
result.model = -1; // No models used.
|
|
result.power = this->getPower();
|
|
result.mode = IRDaikin176::toCommonMode(this->getMode());
|
|
result.celsius = true;
|
|
result.degrees = this->getTemp();
|
|
result.fanspeed = this->toCommonFanSpeed(this->getFan());
|
|
result.swingh = this->toCommonSwingH(this->getSwingHorizontal());
|
|
|
|
// Not supported.
|
|
result.swingv = stdAc::swingv_t::kOff;
|
|
result.quiet = false;
|
|
result.turbo = false;
|
|
result.light = false;
|
|
result.clean = false;
|
|
result.econo = false;
|
|
result.filter = false;
|
|
result.beep = false;
|
|
result.sleep = -1;
|
|
result.clock = -1;
|
|
return result;
|
|
}
|
|
|
|
// Convert the internal state into a human readable string.
|
|
String IRDaikin176::toString() {
|
|
String result = "";
|
|
result.reserve(80); // Reserve some heap for the string to reduce fragging.
|
|
result += addBoolToString(getPower(), F("Power"), false);
|
|
result += addModeToString(getMode(), kDaikinAuto, kDaikin176Cool, kDaikinHeat,
|
|
kDaikinDry, kDaikinFan);
|
|
result += addTempToString(getTemp());
|
|
result += addFanToString(getFan(), kDaikin176FanMax, kDaikinFanMin,
|
|
kDaikinFanMin, kDaikinFanMin, kDaikinFanMin);
|
|
result += F(", Swing (H): ");
|
|
result += uint64ToString(getSwingHorizontal());
|
|
switch (getSwingHorizontal()) {
|
|
case kDaikin176SwingHAuto:
|
|
result += F(" (Auto)");
|
|
break;
|
|
case kDaikin176SwingHOff:
|
|
result += F(" (Off)");
|
|
break;
|
|
default:
|
|
result += F(" (UNKNOWN)");
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#if DECODE_DAIKIN176
|
|
// Decode the supplied Daikin 176 bit A/C message.
|
|
// Args:
|
|
// results: Ptr to the data to decode and where to store the decode result.
|
|
// nbits: Nr. of bits to expect in the data portion. (kDaikin176Bits)
|
|
// strict: Flag to indicate if we strictly adhere to the specification.
|
|
// Returns:
|
|
// boolean: True if it can decode it, false if it can't.
|
|
//
|
|
// Supported devices:
|
|
// - Daikin BRC4C153 remote.
|
|
//
|
|
// Status: BETA / Probably works.
|
|
//
|
|
|
|
bool IRrecv::decodeDaikin176(decode_results *results, const uint16_t nbits,
|
|
const bool strict) {
|
|
if (results->rawlen < 2 * (nbits + kHeader + kFooter) - 1)
|
|
return false;
|
|
|
|
// Compliance
|
|
if (strict && nbits != kDaikin176Bits) return false;
|
|
|
|
uint16_t offset = kStartOffset;
|
|
const uint8_t ksectionSize[kDaikin176Sections] = {kDaikin176Section1Length,
|
|
kDaikin176Section2Length};
|
|
|
|
// Sections
|
|
uint16_t pos = 0;
|
|
for (uint8_t section = 0; section < kDaikin176Sections; section++) {
|
|
uint16_t used;
|
|
// Section Header + Section Data (7 bytes) + Section Footer
|
|
used = matchGeneric(results->rawbuf + offset, results->state + pos,
|
|
results->rawlen - offset, ksectionSize[section] * 8,
|
|
kDaikin176HdrMark, kDaikin176HdrSpace,
|
|
kDaikin176BitMark, kDaikin176OneSpace,
|
|
kDaikin176BitMark, kDaikin176ZeroSpace,
|
|
kDaikin176BitMark, kDaikin176Gap,
|
|
section >= kDaikin176Sections - 1,
|
|
kDaikinTolerance, kDaikinMarkExcess, false);
|
|
if (used == 0) return false;
|
|
offset += used;
|
|
pos += ksectionSize[section];
|
|
}
|
|
// Compliance
|
|
if (strict) {
|
|
// Validate the checksum.
|
|
if (!IRDaikin176::validChecksum(results->state)) return false;
|
|
}
|
|
|
|
// Success
|
|
results->decode_type = decode_type_t::DAIKIN176;
|
|
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;
|
|
}
|
|
#endif // DECODE_DAIKIN176
|
|
|
|
#if SEND_DAIKIN128
|
|
// Send a Daikin 128 bit A/C message.
|
|
//
|
|
// Args:
|
|
// data: An array of kDaikin128StateLength bytes containing the IR command.
|
|
//
|
|
// Status: STABLE / Known Working.
|
|
//
|
|
// Supported devices:
|
|
// - Daikin BRC52B63 remote.
|
|
//
|
|
// Ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/827
|
|
void IRsend::sendDaikin128(const unsigned char data[], const uint16_t nbytes,
|
|
const uint16_t repeat) {
|
|
if (nbytes < kDaikin128SectionLength)
|
|
return; // Not enough bytes to send a partial message.
|
|
|
|
for (uint16_t r = 0; r <= repeat; r++) {
|
|
enableIROut(kDaikin128Freq);
|
|
// Leader
|
|
for (uint8_t i = 0; i < 2; i++) {
|
|
mark(kDaikin128LeaderMark);
|
|
space(kDaikin128LeaderSpace);
|
|
}
|
|
// Section #1 (Header + Data)
|
|
sendGeneric(kDaikin128HdrMark, kDaikin128HdrSpace, kDaikin128BitMark,
|
|
kDaikin128OneSpace, kDaikin128BitMark, kDaikin128ZeroSpace,
|
|
kDaikin128BitMark, kDaikin128Gap, data,
|
|
kDaikin128SectionLength,
|
|
kDaikin128Freq, false, 0, kDutyDefault);
|
|
// Section #2 (Data + Footer)
|
|
sendGeneric(0, 0, kDaikin128BitMark,
|
|
kDaikin128OneSpace, kDaikin128BitMark, kDaikin128ZeroSpace,
|
|
kDaikin128FooterMark, kDaikin128Gap,
|
|
data + kDaikin128SectionLength,
|
|
nbytes - kDaikin128SectionLength,
|
|
kDaikin128Freq, false, 0, kDutyDefault);
|
|
}
|
|
}
|
|
#endif // SEND_DAIKIN128
|
|
|
|
// Class for handling Daikin 128 bit / 16 byte A/C messages.
|
|
//
|
|
// Code by crankyoldgit.
|
|
// Analysis by Daniel Vena
|
|
//
|
|
// Status: STABLE / Known Working.
|
|
//
|
|
// Supported Remotes: Daikin BRC52B63 remote
|
|
//
|
|
IRDaikin128::IRDaikin128(const uint16_t pin, const bool inverted,
|
|
const bool use_modulation)
|
|
: _irsend(pin, inverted, use_modulation) { stateReset(); }
|
|
|
|
void IRDaikin128::begin() { _irsend.begin(); }
|
|
|
|
uint8_t IRDaikin128::calcFirstChecksum(const uint8_t state[]) {
|
|
return sumNibbles(state, kDaikin128SectionLength - 1,
|
|
state[kDaikin128SectionLength - 1] & 0x0F) & 0x0F;
|
|
}
|
|
|
|
uint8_t IRDaikin128::calcSecondChecksum(const uint8_t state[]) {
|
|
return sumNibbles(state + kDaikin128SectionLength,
|
|
kDaikin128SectionLength - 1);
|
|
}
|
|
|
|
// Verify the checksum is valid for a given state.
|
|
// Args:
|
|
// state: The array to verify the checksum of.
|
|
// Returns:
|
|
// A boolean.
|
|
bool IRDaikin128::validChecksum(uint8_t state[]) {
|
|
// Validate the checksum of section #1.
|
|
if (state[kDaikin128SectionLength - 1] >> 4 != calcFirstChecksum(state))
|
|
return false;
|
|
// Validate the checksum of section #2
|
|
if (state[kDaikin128StateLength - 1] != calcSecondChecksum(state))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
// Calculate and set the checksum values for the internal state.
|
|
void IRDaikin128::checksum() {
|
|
remote_state[kDaikin128SectionLength - 1] &= 0x0F; // Clear upper half.
|
|
remote_state[kDaikin128SectionLength - 1] |=
|
|
(calcFirstChecksum(remote_state) << 4);
|
|
remote_state[kDaikin128StateLength - 1] = calcSecondChecksum(remote_state);
|
|
}
|
|
|
|
void IRDaikin128::stateReset() {
|
|
for (uint8_t i = 0; i < kDaikin128StateLength; i++) remote_state[i] = 0x00;
|
|
remote_state[0] = 0x16;
|
|
remote_state[7] = 0x04; // Most significant nibble is a checksum.
|
|
remote_state[8] = 0xA1;
|
|
// remote_state[15] is a checksum byte, it will be set by checksum().
|
|
}
|
|
|
|
uint8_t *IRDaikin128::getRaw() {
|
|
checksum(); // Ensure correct settings before sending.
|
|
return remote_state;
|
|
}
|
|
|
|
void IRDaikin128::setRaw(const uint8_t new_code[]) {
|
|
for (uint8_t i = 0; i < kDaikin128StateLength; i++)
|
|
remote_state[i] = new_code[i];
|
|
}
|
|
|
|
#if SEND_DAIKIN128
|
|
void IRDaikin128::send(const uint16_t repeat) {
|
|
checksum();
|
|
_irsend.sendDaikin128(remote_state, kDaikin128StateLength, repeat);
|
|
}
|
|
#endif // SEND_DAIKIN128
|
|
|
|
void IRDaikin128::setPowerToggle(const bool toggle) {
|
|
if (toggle)
|
|
remote_state[kDaikin128BytePowerSwingSleep] |= kDaikin128BitPowerToggle;
|
|
else
|
|
remote_state[kDaikin128BytePowerSwingSleep] &= ~kDaikin128BitPowerToggle;
|
|
}
|
|
|
|
bool IRDaikin128::getPowerToggle(void) {
|
|
return remote_state[kDaikin128BytePowerSwingSleep] & kDaikin128BitPowerToggle;
|
|
}
|
|
|
|
uint8_t IRDaikin128::getMode() {
|
|
return remote_state[kDaikin128ByteModeFan] & kDaikin128MaskMode;
|
|
}
|
|
|
|
void IRDaikin128::setMode(const uint8_t mode) {
|
|
switch (mode) {
|
|
case kDaikin128Auto:
|
|
case kDaikin128Cool:
|
|
case kDaikin128Heat:
|
|
case kDaikin128Fan:
|
|
case kDaikin128Dry:
|
|
remote_state[kDaikin128ByteModeFan] &= ~kDaikin128MaskMode;
|
|
remote_state[kDaikin128ByteModeFan] |= mode;
|
|
break;
|
|
default:
|
|
this->setMode(kDaikin128Auto);
|
|
return;
|
|
}
|
|
// Force a reset of mode dependant things.
|
|
setFan(getFan()); // Covers Quiet & Powerful too.
|
|
setEcono(getEcono());
|
|
}
|
|
|
|
// Convert a standard A/C mode into its native mode.
|
|
uint8_t IRDaikin128::convertMode(const stdAc::opmode_t mode) {
|
|
switch (mode) {
|
|
case stdAc::opmode_t::kCool:
|
|
return kDaikin128Cool;
|
|
case stdAc::opmode_t::kHeat:
|
|
return kDaikin128Heat;
|
|
case stdAc::opmode_t::kDry:
|
|
return kDaikinDry;
|
|
case stdAc::opmode_t::kFan:
|
|
return kDaikin128Fan;
|
|
default:
|
|
return kDaikin128Auto;
|
|
}
|
|
}
|
|
|
|
// Convert a native mode to it's common equivalent.
|
|
stdAc::opmode_t IRDaikin128::toCommonMode(const uint8_t mode) {
|
|
switch (mode) {
|
|
case kDaikin128Cool: return stdAc::opmode_t::kCool;
|
|
case kDaikin128Heat: return stdAc::opmode_t::kHeat;
|
|
case kDaikin128Dry: return stdAc::opmode_t::kDry;
|
|
case kDaikin128Fan: return stdAc::opmode_t::kFan;
|
|
default: return stdAc::opmode_t::kAuto;
|
|
}
|
|
}
|
|
|
|
// Set the temp in deg C
|
|
void IRDaikin128::setTemp(const uint8_t temp) {
|
|
remote_state[kDaikin128ByteTemp] = uint8ToBcd(
|
|
std::min(kDaikin128MaxTemp, std::max(temp, kDaikin128MinTemp)));
|
|
}
|
|
|
|
uint8_t IRDaikin128::getTemp(void) {
|
|
return bcdToUint8(remote_state[kDaikin128ByteTemp]);
|
|
}
|
|
|
|
uint8_t IRDaikin128::getFan() {
|
|
return (remote_state[kDaikin128ByteModeFan] & kDaikin128MaskFan) >> 4;
|
|
}
|
|
|
|
void IRDaikin128::setFan(const uint8_t speed) {
|
|
uint8_t new_speed = speed;
|
|
uint8_t mode = getMode();
|
|
switch (speed) {
|
|
case kDaikin128FanQuiet:
|
|
case kDaikin128FanPowerful:
|
|
if (mode == kDaikin128Auto) new_speed = kDaikin128FanAuto;
|
|
// FALL-THRU
|
|
case kDaikin128FanAuto:
|
|
case kDaikin128FanHigh:
|
|
case kDaikin128FanMed:
|
|
case kDaikin128FanLow:
|
|
// if (mode == kDaikinDry) new_speed = kDaikin128FanMed;
|
|
remote_state[kDaikin128ByteModeFan] &= ~kDaikin128MaskFan;
|
|
remote_state[kDaikin128ByteModeFan] |= (new_speed << 4);
|
|
break;
|
|
default:
|
|
this->setFan(kDaikin128FanAuto);
|
|
}
|
|
}
|
|
|
|
// Convert a standard A/C Fan speed into its native fan speed.
|
|
uint8_t IRDaikin128::convertFan(const stdAc::fanspeed_t speed) {
|
|
switch (speed) {
|
|
case stdAc::fanspeed_t::kMin: return kDaikinFanQuiet;
|
|
case stdAc::fanspeed_t::kLow: return kDaikin128FanLow;
|
|
case stdAc::fanspeed_t::kMedium: return kDaikin128FanMed;
|
|
case stdAc::fanspeed_t::kHigh: return kDaikin128FanHigh;
|
|
case stdAc::fanspeed_t::kMax: return kDaikin128FanPowerful;
|
|
default: return kDaikin128FanAuto;
|
|
}
|
|
}
|
|
|
|
// Convert a native fan speed to it's common equivalent.
|
|
stdAc::fanspeed_t IRDaikin128::toCommonFanSpeed(const uint8_t speed) {
|
|
switch (speed) {
|
|
case kDaikin128FanPowerful: return stdAc::fanspeed_t::kMax;
|
|
case kDaikin128FanHigh: return stdAc::fanspeed_t::kHigh;
|
|
case kDaikin128FanMed: return stdAc::fanspeed_t::kMedium;
|
|
case kDaikin128FanLow: return stdAc::fanspeed_t::kLow;
|
|
case kDaikinFanQuiet: return stdAc::fanspeed_t::kMin;
|
|
default: return stdAc::fanspeed_t::kAuto;
|
|
}
|
|
}
|
|
|
|
void IRDaikin128::setSwingVertical(const bool on) {
|
|
if (on)
|
|
remote_state[kDaikin128BytePowerSwingSleep] |= kDaikin128BitSwing;
|
|
else
|
|
remote_state[kDaikin128BytePowerSwingSleep] &= ~kDaikin128BitSwing;
|
|
}
|
|
|
|
bool IRDaikin128::getSwingVertical(void) {
|
|
return remote_state[kDaikin128BytePowerSwingSleep] & kDaikin128BitSwing;
|
|
}
|
|
|
|
void IRDaikin128::setSleep(const bool on) {
|
|
if (on)
|
|
remote_state[kDaikin128BytePowerSwingSleep] |= kDaikin128BitSleep;
|
|
else
|
|
remote_state[kDaikin128BytePowerSwingSleep] &= ~kDaikin128BitSleep;
|
|
}
|
|
|
|
bool IRDaikin128::getSleep(void) {
|
|
return remote_state[kDaikin128BytePowerSwingSleep] & kDaikin128BitSleep;
|
|
}
|
|
|
|
void IRDaikin128::setEcono(const bool on) {
|
|
uint8_t mode = getMode();
|
|
if (on && (mode == kDaikin128Cool || mode == kDaikin128Heat))
|
|
remote_state[kDaikin128ByteEconoLight] |= kDaikin128BitEcono;
|
|
else
|
|
remote_state[kDaikin128ByteEconoLight] &= ~kDaikin128BitEcono;
|
|
}
|
|
|
|
bool IRDaikin128::getEcono(void) {
|
|
return remote_state[kDaikin128ByteEconoLight] & kDaikin128BitEcono;
|
|
}
|
|
|
|
void IRDaikin128::setQuiet(const bool on) {
|
|
uint8_t mode = getMode();
|
|
if (on && (mode == kDaikin128Cool || mode == kDaikin128Heat))
|
|
setFan(kDaikin128FanQuiet);
|
|
else if (getFan() == kDaikin128FanQuiet)
|
|
setFan(kDaikin128FanAuto);
|
|
}
|
|
|
|
bool IRDaikin128::getQuiet(void) {
|
|
return getFan() == kDaikin128FanQuiet;
|
|
}
|
|
|
|
void IRDaikin128::setPowerful(const bool on) {
|
|
uint8_t mode = getMode();
|
|
if (on && (mode == kDaikin128Cool || mode == kDaikin128Heat))
|
|
setFan(kDaikin128FanPowerful);
|
|
else if (getFan() == kDaikin128FanPowerful)
|
|
setFan(kDaikin128FanAuto);
|
|
}
|
|
|
|
bool IRDaikin128::getPowerful(void) {
|
|
return getFan() == kDaikin128FanPowerful;
|
|
}
|
|
|
|
// Set the clock in mins since midnight
|
|
void IRDaikin128::setClock(const uint16_t mins_since_midnight) {
|
|
uint16_t mins = mins_since_midnight;
|
|
if (mins_since_midnight >= 24 * 60) mins = 0; // Bounds check.
|
|
// Hours.
|
|
remote_state[kDaikin128ByteClockHours] = uint8ToBcd(mins / 60);
|
|
// Minutes.
|
|
remote_state[kDaikin128ByteClockMins] = uint8ToBcd(mins % 60);
|
|
}
|
|
|
|
uint16_t IRDaikin128::getClock(void) {
|
|
return bcdToUint8(remote_state[kDaikin128ByteClockHours]) * 60 +
|
|
bcdToUint8(remote_state[kDaikin128ByteClockMins]);
|
|
}
|
|
|
|
void IRDaikin128::setOnTimerEnabled(const bool on) {
|
|
if (on)
|
|
remote_state[kDaikin128ByteOnTimer] |= kDaikin128BitTimerEnabled;
|
|
else
|
|
remote_state[kDaikin128ByteOnTimer] &= ~kDaikin128BitTimerEnabled;
|
|
}
|
|
|
|
bool IRDaikin128::getOnTimerEnabled(void) {
|
|
return remote_state[kDaikin128ByteOnTimer] & kDaikin128BitTimerEnabled;
|
|
}
|
|
|
|
// Timer is rounds down to the nearest half hour.
|
|
// Args:
|
|
// ptr: A PTR to the byte containing the Timer value to be updated.
|
|
// mins_since_midnight: The number of minutes the new timer should be set to.
|
|
void IRDaikin128::setTimer(uint8_t *ptr, const uint16_t mins_since_midnight) {
|
|
uint16_t mins = mins_since_midnight;
|
|
if (mins_since_midnight >= 24 * 60) mins = 0; // Bounds check.
|
|
// Clear the time component
|
|
*ptr &= kDaikin128BitTimerEnabled;
|
|
uint8_t bcdhours = uint8ToBcd(mins / 60);
|
|
bool addhalf = (mins % 60) >= 30;
|
|
*ptr |= ((addhalf << 6) | bcdhours);
|
|
}
|
|
|
|
// Timer is stored in nr of half hours internally.
|
|
// Args:
|
|
// ptr: A PTR to the byte containing the Timer value.
|
|
// Returns:
|
|
// A uint16_t containing the number of minutes since midnight.
|
|
uint16_t IRDaikin128::getTimer(const uint8_t *ptr) {
|
|
uint8_t bcdhours = *ptr & kDaikin128MaskHours;
|
|
bool addhalf = *ptr & kDaikin128BitHalfHour;
|
|
return bcdToUint8(bcdhours) * 60 + (addhalf ? 30 : 0);
|
|
}
|
|
|
|
void IRDaikin128::setOnTimer(const uint16_t mins_since_midnight) {
|
|
setTimer(remote_state + kDaikin128ByteOnTimer, mins_since_midnight);
|
|
}
|
|
|
|
uint16_t IRDaikin128::getOnTimer(void) {
|
|
return getTimer(remote_state + kDaikin128ByteOnTimer);
|
|
}
|
|
|
|
void IRDaikin128::setOffTimerEnabled(const bool on) {
|
|
if (on)
|
|
remote_state[kDaikin128ByteOffTimer] |= kDaikin128BitTimerEnabled;
|
|
else
|
|
remote_state[kDaikin128ByteOffTimer] &= ~kDaikin128BitTimerEnabled;
|
|
}
|
|
|
|
bool IRDaikin128::getOffTimerEnabled(void) {
|
|
return remote_state[kDaikin128ByteOffTimer] & kDaikin128BitTimerEnabled;
|
|
}
|
|
|
|
void IRDaikin128::setOffTimer(const uint16_t mins_since_midnight) {
|
|
setTimer(remote_state + kDaikin128ByteOffTimer, mins_since_midnight);
|
|
}
|
|
|
|
uint16_t IRDaikin128::getOffTimer(void) {
|
|
return getTimer(remote_state + kDaikin128ByteOffTimer);
|
|
}
|
|
|
|
void IRDaikin128::setLightToggle(const uint8_t unit) {
|
|
switch (unit) {
|
|
case kDaikin128BitCeiling:
|
|
case kDaikin128BitWall:
|
|
case 0:
|
|
remote_state[kDaikin128ByteEconoLight] &= ~kDaikin128MaskLight;
|
|
remote_state[kDaikin128ByteEconoLight] |= unit;
|
|
break;
|
|
default: setLightToggle(0);
|
|
}
|
|
}
|
|
|
|
uint8_t IRDaikin128::getLightToggle(void) {
|
|
return remote_state[kDaikin128ByteEconoLight] & kDaikin128MaskLight;
|
|
}
|
|
|
|
// Convert the internal state into a human readable string.
|
|
String IRDaikin128::toString(void) {
|
|
String result = "";
|
|
result.reserve(240); // Reserve some heap for the string to reduce fragging.
|
|
result += addBoolToString(getPowerToggle(), F("Power Toggle"), false);
|
|
result += addModeToString(getMode(), kDaikin128Auto, kDaikin128Cool,
|
|
kDaikin128Heat, kDaikin128Dry, kDaikin128Fan);
|
|
result += addTempToString(getTemp());
|
|
result += addFanToString(getFan(), kDaikin128FanHigh, kDaikin128FanLow,
|
|
kDaikin128FanAuto, kDaikin128FanQuiet,
|
|
kDaikin128FanMed);
|
|
result += addBoolToString(getPowerful(), F("Powerful"));
|
|
result += addBoolToString(getQuiet(), F("Quiet"));
|
|
result += addBoolToString(getSwingVertical(), F("Swing (V)"));
|
|
result += addBoolToString(getSleep(), F("Sleep"));
|
|
result += addBoolToString(getEcono(), F("Econo"));
|
|
result += addLabeledString(minsToString(getClock()), F("Clock"));
|
|
result += addBoolToString(getOnTimerEnabled(), F("On Timer"));
|
|
result += addLabeledString(minsToString(getOnTimer()), F("On Time"));
|
|
result += addBoolToString(getOffTimerEnabled(), F("Off Timer"));
|
|
result += addLabeledString(minsToString(getOffTimer()), F("Off Time"));
|
|
result += addIntToString(getLightToggle(), F("Light Toggle"));
|
|
result += F(" (");
|
|
switch (getLightToggle()) {
|
|
case kDaikin128BitCeiling: result += F("Ceiling"); break;
|
|
case kDaikin128BitWall: result += F("Wall"); break;
|
|
case 0: result += F("Off"); break;
|
|
default: result += F("UNKNOWN");
|
|
}
|
|
result += ')';
|
|
return result;
|
|
}
|
|
|
|
// Convert the A/C state to it's common equivalent.
|
|
stdAc::state_t IRDaikin128::toCommon(const stdAc::state_t *prev) {
|
|
stdAc::state_t result;
|
|
if (prev != NULL) result = *prev;
|
|
result.protocol = decode_type_t::DAIKIN128;
|
|
result.model = -1; // No models used.
|
|
result.power ^= getPowerToggle();
|
|
result.mode = toCommonMode(getMode());
|
|
result.celsius = true;
|
|
result.degrees = getTemp();
|
|
result.fanspeed = toCommonFanSpeed(getFan());
|
|
result.swingv = getSwingVertical() ? stdAc::swingv_t::kAuto
|
|
: stdAc::swingv_t::kOff;
|
|
result.quiet = getQuiet();
|
|
result.turbo = getPowerful();
|
|
result.econo = getEcono();
|
|
result.light ^= (getLightToggle() != 0);
|
|
result.sleep = getSleep() ? 0 : -1;
|
|
result.clock = getClock();
|
|
// Not supported.
|
|
result.swingh = stdAc::swingh_t::kOff;
|
|
result.clean = false;
|
|
result.filter = false;
|
|
result.beep = false;
|
|
return result;
|
|
}
|
|
|
|
#if DECODE_DAIKIN128
|
|
// Decode the supplied Daikin 128 bit A/C message.
|
|
// Args:
|
|
// results: Ptr to the data to decode and where to store the decode result.
|
|
// nbits: Nr. of bits to expect in the data portion. (kDaikin128Bits)
|
|
// strict: Flag to indicate if we strictly adhere to the specification.
|
|
// Returns:
|
|
// boolean: True if it can decode it, false if it can't.
|
|
//
|
|
// Supported devices:
|
|
// - Daikin BRC52B63 remote.
|
|
//
|
|
// Status: STABLE / Known Working.
|
|
//
|
|
// Ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/827
|
|
bool IRrecv::decodeDaikin128(decode_results *results, const uint16_t nbits,
|
|
const bool strict) {
|
|
if (results->rawlen < 2 * (nbits + kHeader) + kFooter - 1)
|
|
return false;
|
|
if (nbits / 8 <= kDaikin128SectionLength) return false;
|
|
|
|
// Compliance
|
|
if (strict && nbits != kDaikin128Bits) return false;
|
|
|
|
uint16_t offset = kStartOffset;
|
|
|
|
// Leader
|
|
for (uint8_t i = 0; i < 2; i++) {
|
|
if (!matchMark(results->rawbuf[offset++], kDaikin128LeaderMark,
|
|
kDaikinTolerance, kDaikinMarkExcess)) return false;
|
|
if (!matchSpace(results->rawbuf[offset++], kDaikin128LeaderSpace,
|
|
kDaikinTolerance, kDaikinMarkExcess)) return false;
|
|
}
|
|
const uint16_t ksectionSize[kDaikin128Sections] = {
|
|
kDaikin128SectionLength, (uint16_t)(nbits / 8 - kDaikin128SectionLength)};
|
|
// Data Sections
|
|
uint16_t pos = 0;
|
|
for (uint8_t section = 0; section < kDaikin128Sections; section++) {
|
|
uint16_t used;
|
|
// Section Header (first section only) + Section Data (8 bytes) +
|
|
// Section Footer (Not for first section)
|
|
used = matchGeneric(results->rawbuf + offset, results->state + pos,
|
|
results->rawlen - offset, ksectionSize[section] * 8,
|
|
section == 0 ? kDaikin128HdrMark : 0,
|
|
section == 0 ? kDaikin128HdrSpace : 0,
|
|
kDaikin128BitMark, kDaikin128OneSpace,
|
|
kDaikin128BitMark, kDaikin128ZeroSpace,
|
|
section > 0 ? kDaikin128FooterMark : kDaikin128BitMark,
|
|
kDaikin128Gap,
|
|
section > 0,
|
|
kDaikinTolerance, kDaikinMarkExcess, false);
|
|
if (used == 0) return false;
|
|
offset += used;
|
|
pos += ksectionSize[section];
|
|
}
|
|
// Compliance
|
|
if (strict) {
|
|
if (!IRDaikin128::validChecksum(results->state)) return false;
|
|
}
|
|
|
|
// Success
|
|
results->decode_type = decode_type_t::DAIKIN128;
|
|
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;
|
|
}
|
|
#endif // DECODE_DAIKIN128
|
|
|
|
#if SEND_DAIKIN152
|
|
// Send a Daikin 152 bit A/C message.
|
|
//
|
|
// Args:
|
|
// data: An array of kDaikin152StateLength bytes containing the IR command.
|
|
//
|
|
// Supported devices:
|
|
// - Daikin ARC480A5 remote.
|
|
//
|
|
// Status: Beta / Probably working.
|
|
//
|
|
// Ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/873
|
|
void IRsend::sendDaikin152(const unsigned char data[], const uint16_t nbytes,
|
|
const uint16_t repeat) {
|
|
for (uint16_t r = 0; r <= repeat; r++) {
|
|
// Leader
|
|
sendGeneric(0, 0, kDaikin152BitMark, kDaikin152OneSpace,
|
|
kDaikin152BitMark, kDaikin152ZeroSpace,
|
|
kDaikin152BitMark, kDaikin152Gap,
|
|
(uint64_t)0, kDaikin152LeaderBits,
|
|
kDaikin152Freq, false, 0, kDutyDefault);
|
|
// Header + Data + Footer
|
|
sendGeneric(kDaikin152HdrMark, kDaikin152HdrSpace, kDaikin152BitMark,
|
|
kDaikin152OneSpace, kDaikin152BitMark, kDaikin152ZeroSpace,
|
|
kDaikin152BitMark, kDaikin152Gap, data,
|
|
nbytes, kDaikin152Freq, false, 0, kDutyDefault);
|
|
}
|
|
}
|
|
#endif // SEND_DAIKIN152
|
|
|
|
#if DECODE_DAIKIN152
|
|
// Decode the supplied Daikin 152 bit A/C message.
|
|
// Args:
|
|
// results: Ptr to the data to decode and where to store the decode result.
|
|
// nbits: Nr. of bits to expect in the data portion. (kDaikin152Bits)
|
|
// strict: Flag to indicate if we strictly adhere to the specification.
|
|
// Returns:
|
|
// boolean: True if it can decode it, false if it can't.
|
|
//
|
|
// Supported devices:
|
|
// - Daikin ARC480A5 remote.
|
|
//
|
|
// Status: Beta / Probably working.
|
|
//
|
|
// Ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/873
|
|
bool IRrecv::decodeDaikin152(decode_results *results, const uint16_t nbits,
|
|
const bool strict) {
|
|
if (results->rawlen < 2 * (5 + nbits + kFooter) + kHeader - 1)
|
|
return false;
|
|
if (nbits / 8 < kDaikin152StateLength) return false;
|
|
|
|
// Compliance
|
|
if (strict && nbits != kDaikin152Bits) return false;
|
|
|
|
uint16_t offset = kStartOffset;
|
|
uint16_t used;
|
|
|
|
// Leader
|
|
uint64_t leader = 0;
|
|
used = matchGeneric(results->rawbuf + offset, &leader,
|
|
results->rawlen - offset, kDaikin152LeaderBits,
|
|
0, 0, // No Header
|
|
kDaikin152BitMark, kDaikin152OneSpace,
|
|
kDaikin152BitMark, kDaikin152ZeroSpace,
|
|
kDaikin152BitMark, kDaikin152Gap, // Footer gap
|
|
false, _tolerance, kMarkExcess, false);
|
|
if (used == 0 || leader != 0) return false;
|
|
offset += used;
|
|
|
|
// Header + Data + Footer
|
|
used = matchGeneric(results->rawbuf + offset, results->state,
|
|
results->rawlen - offset, nbits,
|
|
kDaikin152HdrMark, kDaikin152HdrSpace,
|
|
kDaikin152BitMark, kDaikin152OneSpace,
|
|
kDaikin152BitMark, kDaikin152ZeroSpace,
|
|
kDaikin152BitMark, kDaikin152Gap,
|
|
true, _tolerance, kMarkExcess, false);
|
|
if (used == 0) return false;
|
|
|
|
// Compliance
|
|
if (strict) {
|
|
if (!IRDaikin152::validChecksum(results->state)) return false;
|
|
}
|
|
|
|
// Success
|
|
results->decode_type = decode_type_t::DAIKIN152;
|
|
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;
|
|
}
|
|
#endif // DECODE_DAIKIN152
|
|
|
|
// Class for handling Daikin 152 bit / 19 byte A/C messages.
|
|
//
|
|
// Code by crankyoldgit.
|
|
//
|
|
// Supported Remotes: Daikin ARC480A5 remote
|
|
//
|
|
// Ref:
|
|
// https://github.com/crankyoldgit/IRremoteESP8266/issues/873
|
|
IRDaikin152::IRDaikin152(const uint16_t pin, const bool inverted,
|
|
const bool use_modulation)
|
|
: _irsend(pin, inverted, use_modulation) { stateReset(); }
|
|
|
|
void IRDaikin152::begin() { _irsend.begin(); }
|
|
|
|
#if SEND_DAIKIN152
|
|
void IRDaikin152::send(const uint16_t repeat) {
|
|
checksum();
|
|
_irsend.sendDaikin152(remote_state, kDaikin152StateLength, repeat);
|
|
}
|
|
#endif // SEND_DAIKIN152
|
|
|
|
// Verify the checksum is valid for a given state.
|
|
// Args:
|
|
// state: The array to verify the checksum of.
|
|
// length: The size of the state.
|
|
// Returns:
|
|
// A boolean.
|
|
bool IRDaikin152::validChecksum(uint8_t state[], const uint16_t length) {
|
|
// Validate the checksum of the given state.
|
|
if (length <= 1 || state[length - 1] != sumBytes(state, length - 1))
|
|
return false;
|
|
else
|
|
return true;
|
|
}
|
|
|
|
// Calculate and set the checksum values for the internal state.
|
|
void IRDaikin152::checksum() {
|
|
remote_state[kDaikin152StateLength - 1] = sumBytes(
|
|
remote_state, kDaikin152StateLength - 1);
|
|
}
|
|
|
|
void IRDaikin152::stateReset() {
|
|
for (uint8_t i = 3; i < kDaikin152StateLength; i++) remote_state[i] = 0x00;
|
|
remote_state[0] = 0x11;
|
|
remote_state[1] = 0xDA;
|
|
remote_state[2] = 0x27;
|
|
// remote_state[19] is a checksum byte, it will be set by checksum().
|
|
}
|
|
|
|
uint8_t *IRDaikin152::getRaw() {
|
|
checksum(); // Ensure correct settings before sending.
|
|
return remote_state;
|
|
}
|
|
|
|
void IRDaikin152::setRaw(const uint8_t new_code[]) {
|
|
for (uint8_t i = 0; i < kDaikin152StateLength; i++)
|
|
remote_state[i] = new_code[i];
|
|
}
|