790 lines
20 KiB
C++
790 lines
20 KiB
C++
/*
|
|
An Arduino sketch to emulate IR Daikin ARC433** 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
|
|
*/
|
|
|
|
#include "ir_Daikin.h"
|
|
#include <algorithm>
|
|
#ifndef ARDUINO
|
|
#include <string>
|
|
#endif
|
|
#include "IRremoteESP8266.h"
|
|
#include "IRutils.h"
|
|
#include "IRrecv.h"
|
|
#include "IRsend.h"
|
|
|
|
// DDDDD AAA IIIII KK KK IIIII NN NN
|
|
// DD DD AAAAA III KK KK III NNN NN
|
|
// DD DD AA AA III KKKK III NN N NN
|
|
// DD DD AAAAAAA III KK KK III NN NNN
|
|
// DDDDDD AA AA IIIII KK KK IIIII NN NN
|
|
|
|
// 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
|
|
|
|
#if SEND_DAIKIN
|
|
// Original header
|
|
// static uint8_t header1[DAIKIN_HEADER1_LENGTH];
|
|
// header1[0] = 0b00010001;
|
|
// header1[1] = 0b11011010;
|
|
// header1[2] = 0b00100111;
|
|
// header1[3] = 0b00000000;
|
|
// header1[4] = 0b11000101;
|
|
// header1[5] = 0b00000000;
|
|
// header1[6] = 0b00000000;
|
|
// header1[7] = 0b11010111;
|
|
|
|
// Send a Daikin A/C message.
|
|
//
|
|
// Args:
|
|
// data: An array of DAIKIN_COMMAND_LENGTH bytes containing the IR command.
|
|
//
|
|
// Status: STABLE
|
|
//
|
|
// Ref:
|
|
// IRDaikinESP.cpp
|
|
// https://github.com/mharizanov/Daikin-AC-remote-control-over-the-Internet/tree/master/IRremote
|
|
void IRsend::sendDaikin(unsigned char data[], uint16_t nbytes,
|
|
uint16_t repeat) {
|
|
if (nbytes < DAIKIN_COMMAND_LENGTH)
|
|
return; // Not enough bytes to send a proper message.
|
|
|
|
for (uint16_t r = 0; r <= repeat; r++) {
|
|
// Send the header, 0b00000
|
|
sendGeneric(0, 0, // No header for the header
|
|
DAIKIN_BIT_MARK, DAIKIN_ONE_SPACE,
|
|
DAIKIN_BIT_MARK, DAIKIN_ZERO_SPACE,
|
|
DAIKIN_BIT_MARK, DAIKIN_ZERO_SPACE + DAIKIN_GAP,
|
|
(uint64_t) 0b00000, 5, 38, false, 0, 50);
|
|
// Leading header
|
|
// Do this as a constant to save RAM and keep in flash memory
|
|
sendGeneric(DAIKIN_HDR_MARK, DAIKIN_HDR_SPACE,
|
|
DAIKIN_BIT_MARK, DAIKIN_ONE_SPACE,
|
|
DAIKIN_BIT_MARK, DAIKIN_ZERO_SPACE,
|
|
DAIKIN_BIT_MARK, DAIKIN_ZERO_SPACE + DAIKIN_GAP,
|
|
DAIKIN_FIRST_HEADER64, 64, 38, false, 0, 50);
|
|
// Data #1
|
|
sendGeneric(DAIKIN_HDR_MARK, DAIKIN_HDR_SPACE,
|
|
DAIKIN_BIT_MARK, DAIKIN_ONE_SPACE,
|
|
DAIKIN_BIT_MARK, DAIKIN_ZERO_SPACE,
|
|
DAIKIN_BIT_MARK, DAIKIN_ZERO_SPACE + DAIKIN_GAP,
|
|
data, 8, 38, false, 0, 50);
|
|
// Data #2
|
|
sendGeneric(DAIKIN_HDR_MARK, DAIKIN_HDR_SPACE,
|
|
DAIKIN_BIT_MARK, DAIKIN_ONE_SPACE,
|
|
DAIKIN_BIT_MARK, DAIKIN_ZERO_SPACE,
|
|
DAIKIN_BIT_MARK, DAIKIN_ZERO_SPACE + DAIKIN_GAP,
|
|
data + 8, nbytes - 8, 38, false, 0, 50);
|
|
}
|
|
}
|
|
#endif // SEND_DAIKIN
|
|
|
|
IRDaikinESP::IRDaikinESP(uint16_t pin) : _irsend(pin) {
|
|
stateReset();
|
|
}
|
|
|
|
void IRDaikinESP::begin() {
|
|
_irsend.begin();
|
|
}
|
|
|
|
#if SEND_DAIKIN
|
|
void IRDaikinESP::send() {
|
|
checksum();
|
|
_irsend.sendDaikin(daikin);
|
|
}
|
|
#endif // SEND_DAIKIN
|
|
|
|
// Calculate the checksum for a given data block.
|
|
// Args:
|
|
// block: Ptr to the start of the data block.
|
|
// length: Nr. of bytes to checksum.
|
|
// Returns:
|
|
// A byte containing the calculated checksum.
|
|
uint8_t IRDaikinESP::calcBlockChecksum(const uint8_t *block,
|
|
const uint16_t length) {
|
|
uint8_t sum = 0;
|
|
// Daikin checksum is just the addition of all the data bytes
|
|
// in the block but capped to 8 bits.
|
|
for (uint16_t i = 0; i < length; i++, block++)
|
|
sum += *block;
|
|
return sum & 0xFFU;
|
|
}
|
|
|
|
// 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 IRDaikinESP::validChecksum(const uint8_t state[],
|
|
const uint16_t length) {
|
|
if (length < 8 || state[7] != calcBlockChecksum(state, 7)) return false;
|
|
if (length < 10 ||
|
|
state[length - 1] != calcBlockChecksum(state + 8, length - 9))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
// Calculate and set the checksum values for the internal state.
|
|
void IRDaikinESP::checksum() {
|
|
daikin[7] = calcBlockChecksum(daikin, 7);
|
|
daikin[26] = calcBlockChecksum(daikin + 8, 17);
|
|
}
|
|
|
|
void IRDaikinESP::stateReset() {
|
|
for (uint8_t i = 0; i < DAIKIN_COMMAND_LENGTH; i++)
|
|
daikin[i] = 0x0;
|
|
|
|
daikin[0] = 0x11;
|
|
daikin[1] = 0xDA;
|
|
daikin[2] = 0x27;
|
|
daikin[4] = 0x42;
|
|
// daikin[7] is a checksum byte, it will be set by checksum().
|
|
daikin[8] = 0x11;
|
|
daikin[9] = 0xDA;
|
|
daikin[10] = 0x27;
|
|
daikin[13] = 0x49;
|
|
daikin[14] = 0x1E;
|
|
daikin[16] = 0xB0;
|
|
daikin[19] = 0x06;
|
|
daikin[20] = 0x60;
|
|
daikin[23] = 0xC0;
|
|
// daikin[26] is a checksum byte, it will be set by checksum().
|
|
checksum();
|
|
}
|
|
|
|
uint8_t* IRDaikinESP::getRaw() {
|
|
checksum(); // Ensure correct settings before sending.
|
|
return daikin;
|
|
}
|
|
|
|
void IRDaikinESP::setRaw(uint8_t new_code[]) {
|
|
for (uint8_t i = 0; i < DAIKIN_COMMAND_LENGTH; i++)
|
|
daikin[i] = new_code[i];
|
|
}
|
|
|
|
void IRDaikinESP::on() {
|
|
// state = ON;
|
|
setBit(DAIKIN_BYTE_POWER, DAIKIN_BIT_POWER);
|
|
}
|
|
|
|
void IRDaikinESP::off() {
|
|
// state = OFF;
|
|
clearBit(DAIKIN_BYTE_POWER, DAIKIN_BIT_POWER);
|
|
}
|
|
|
|
void IRDaikinESP::setPower(bool state) {
|
|
if (state)
|
|
on();
|
|
else
|
|
off();
|
|
}
|
|
|
|
bool IRDaikinESP::getPower() {
|
|
return (getBit(DAIKIN_BYTE_POWER, DAIKIN_BIT_POWER) > 0);
|
|
}
|
|
|
|
// Set the temp in deg C
|
|
void IRDaikinESP::setTemp(uint8_t temp) {
|
|
if (temp < DAIKIN_MIN_TEMP)
|
|
temp = DAIKIN_MIN_TEMP;
|
|
else if (temp > DAIKIN_MAX_TEMP)
|
|
temp = DAIKIN_MAX_TEMP;
|
|
daikin[14] = temp * 2;
|
|
}
|
|
|
|
uint8_t IRDaikinESP::getTemp() {
|
|
return daikin[14] / 2;
|
|
}
|
|
|
|
// Set the speed of the fan, 1-5 or DAIKIN_FAN_AUTO or DAIKIN_FAN_QUIET
|
|
void IRDaikinESP::setFan(uint8_t fan) {
|
|
// Set the fan speed bits, leave low 4 bits alone
|
|
uint8_t fanset;
|
|
if (fan == DAIKIN_FAN_QUIET || fan == DAIKIN_FAN_AUTO)
|
|
fanset = fan;
|
|
else if (fan < DAIKIN_FAN_MIN || fan > DAIKIN_FAN_MAX)
|
|
fanset = DAIKIN_FAN_AUTO;
|
|
else
|
|
fanset = 2 + fan;
|
|
daikin[16] &= 0x0F;
|
|
daikin[16] |= (fanset << 4);
|
|
}
|
|
|
|
uint8_t IRDaikinESP::getFan() {
|
|
uint8_t fan = daikin[16] >> 4;
|
|
if (fan != DAIKIN_FAN_QUIET && fan != DAIKIN_FAN_AUTO)
|
|
fan -= 2;
|
|
return fan;
|
|
}
|
|
|
|
uint8_t IRDaikinESP::getMode() {
|
|
/*
|
|
DAIKIN_COOL
|
|
DAIKIN_HEAT
|
|
DAIKIN_FAN
|
|
DAIKIN_AUTO
|
|
DAIKIN_DRY
|
|
*/
|
|
return daikin[13] >> 4;
|
|
}
|
|
|
|
void IRDaikinESP::setMode(uint8_t mode) {
|
|
switch (mode) {
|
|
case DAIKIN_COOL:
|
|
case DAIKIN_HEAT:
|
|
case DAIKIN_FAN:
|
|
case DAIKIN_DRY:
|
|
break;
|
|
default:
|
|
mode = DAIKIN_AUTO;
|
|
}
|
|
mode <<= 4;
|
|
daikin[13] &= 0b10001111;
|
|
daikin[13] |= mode;
|
|
}
|
|
|
|
void IRDaikinESP::setSwingVertical(bool state) {
|
|
if (state)
|
|
daikin[16] |= 0x0F;
|
|
else
|
|
daikin[16] &= 0xF0;
|
|
}
|
|
|
|
bool IRDaikinESP::getSwingVertical() {
|
|
return daikin[16] & 0x01;
|
|
}
|
|
|
|
void IRDaikinESP::setSwingHorizontal(bool state) {
|
|
if (state)
|
|
daikin[17] |= 0x0F;
|
|
else
|
|
daikin[17] &= 0xF0;
|
|
}
|
|
|
|
bool IRDaikinESP::getSwingHorizontal() {
|
|
return daikin[17] & 0x01;
|
|
}
|
|
|
|
void IRDaikinESP::setQuiet(bool state) {
|
|
if (state) {
|
|
setBit(DAIKIN_BYTE_SILENT, DAIKIN_BIT_SILENT);
|
|
// Powerful & Quiet mode being on are mutually exclusive.
|
|
setPowerful(false);
|
|
} else {
|
|
clearBit(DAIKIN_BYTE_SILENT, DAIKIN_BIT_SILENT);
|
|
}
|
|
}
|
|
|
|
bool IRDaikinESP::getQuiet() {
|
|
return (getBit(DAIKIN_BYTE_SILENT, DAIKIN_BIT_SILENT) > 0);
|
|
}
|
|
|
|
void IRDaikinESP::setPowerful(bool state) {
|
|
if (state) {
|
|
setBit(DAIKIN_BYTE_POWERFUL, DAIKIN_BIT_POWERFUL);
|
|
// Powerful, Quiet, & Econo mode being on are mutually exclusive.
|
|
setQuiet(false);
|
|
setEcono(false);
|
|
} else {
|
|
clearBit(DAIKIN_BYTE_POWERFUL, DAIKIN_BIT_POWERFUL);
|
|
}
|
|
}
|
|
|
|
bool IRDaikinESP::getPowerful() {
|
|
return (getBit(DAIKIN_BYTE_POWERFUL, DAIKIN_BIT_POWERFUL) > 0);
|
|
}
|
|
|
|
void IRDaikinESP::setSensor(bool state) {
|
|
if (state)
|
|
setBit(DAIKIN_BYTE_SENSOR, DAIKIN_BIT_SENSOR);
|
|
else
|
|
clearBit(DAIKIN_BYTE_SENSOR, DAIKIN_BIT_SENSOR);
|
|
}
|
|
|
|
bool IRDaikinESP::getSensor() {
|
|
return (getBit(DAIKIN_BYTE_SENSOR, DAIKIN_BIT_SENSOR) > 0);
|
|
}
|
|
|
|
void IRDaikinESP::setEcono(bool state) {
|
|
if (state) {
|
|
setBit(DAIKIN_BYTE_ECONO, DAIKIN_BIT_ECONO);
|
|
// Powerful & Econo mode being on are mutually exclusive.
|
|
setPowerful(false);
|
|
} else {
|
|
clearBit(DAIKIN_BYTE_ECONO, DAIKIN_BIT_ECONO);
|
|
}
|
|
}
|
|
|
|
bool IRDaikinESP::getEcono() {
|
|
return (getBit(DAIKIN_BYTE_ECONO, DAIKIN_BIT_ECONO) > 0);
|
|
}
|
|
|
|
void IRDaikinESP::setEye(bool state) {
|
|
if (state)
|
|
setBit(DAIKIN_BYTE_EYE, DAIKIN_BIT_EYE);
|
|
else
|
|
clearBit(DAIKIN_BYTE_EYE, DAIKIN_BIT_EYE);
|
|
}
|
|
|
|
bool IRDaikinESP::getEye() {
|
|
return (getBit(DAIKIN_BYTE_EYE, DAIKIN_BIT_EYE) > 0);
|
|
}
|
|
|
|
void IRDaikinESP::setMold(bool state) {
|
|
if (state)
|
|
setBit(DAIKIN_BYTE_MOLD, DAIKIN_BIT_MOLD);
|
|
else
|
|
clearBit(DAIKIN_BYTE_MOLD, DAIKIN_BIT_MOLD);
|
|
}
|
|
|
|
bool IRDaikinESP::getMold() {
|
|
return (getBit(DAIKIN_BYTE_MOLD, DAIKIN_BIT_MOLD) > 0);
|
|
}
|
|
|
|
void IRDaikinESP::setBit(uint8_t byte, uint8_t bitmask) {
|
|
daikin[byte] |= bitmask;
|
|
}
|
|
|
|
void IRDaikinESP::clearBit(uint8_t byte, uint8_t bitmask) {
|
|
bitmask = ~bitmask;
|
|
daikin[byte] &= bitmask;
|
|
}
|
|
|
|
uint8_t IRDaikinESP::getBit(uint8_t byte, uint8_t bitmask) {
|
|
return daikin[byte] & bitmask;
|
|
}
|
|
|
|
// starttime: Number of minutes after midnight, in 10 minutes increments
|
|
void IRDaikinESP::enableOnTimer(uint16_t starttime) {
|
|
setBit(DAIKIN_BYTE_ON_TIMER, DAIKIN_BIT_ON_TIMER);
|
|
daikin[18] = (uint8_t) (starttime & 0x00FF);
|
|
// only keep 4 bits
|
|
daikin[19] &= 0xF0;
|
|
daikin[19] |= (uint8_t) ((starttime >> 8) & 0x0F);
|
|
}
|
|
|
|
void IRDaikinESP::disableOnTimer() {
|
|
enableOnTimer(0x600);
|
|
clearBit(DAIKIN_BYTE_ON_TIMER, DAIKIN_BIT_ON_TIMER);
|
|
}
|
|
|
|
uint16_t IRDaikinESP::getOnTime() {
|
|
uint16_t ret;
|
|
ret = daikin[19] & 0x0F;
|
|
ret = ret << 8;
|
|
ret += daikin[18];
|
|
return ret;
|
|
}
|
|
|
|
bool IRDaikinESP::getOnTimerEnabled() {
|
|
return getBit(DAIKIN_BYTE_ON_TIMER, DAIKIN_BIT_ON_TIMER);
|
|
}
|
|
|
|
// endtime: Number of minutes after midnight, in 10 minutes increments
|
|
void IRDaikinESP::enableOffTimer(uint16_t endtime) {
|
|
setBit(DAIKIN_BYTE_OFF_TIMER, DAIKIN_BIT_OFF_TIMER);
|
|
daikin[20] = (uint8_t)((endtime >> 4) & 0xFF);
|
|
daikin[19] &= 0x0F;
|
|
daikin[19] |= (uint8_t) ((endtime & 0x000F) << 4);
|
|
}
|
|
|
|
void IRDaikinESP::disableOffTimer() {
|
|
enableOffTimer(0x600);
|
|
clearBit(DAIKIN_BYTE_OFF_TIMER, DAIKIN_BIT_OFF_TIMER);
|
|
}
|
|
|
|
uint16_t IRDaikinESP::getOffTime() {
|
|
uint16_t ret, tmp;
|
|
ret = daikin[20];
|
|
ret <<= 4;
|
|
tmp = daikin[19] & 0xF0;
|
|
tmp >>= 4;
|
|
ret += tmp;
|
|
return ret;
|
|
}
|
|
|
|
bool IRDaikinESP::getOffTimerEnabled() {
|
|
return getBit(DAIKIN_BYTE_OFF_TIMER, DAIKIN_BIT_OFF_TIMER);
|
|
}
|
|
|
|
void IRDaikinESP::setCurrentTime(uint16_t numMins) {
|
|
if (numMins > 24 * 60) numMins = 0; // If > 23:59, set to 00:00
|
|
daikin[5] = (uint8_t) (numMins & 0x00FF);
|
|
// only keep 4 bits
|
|
daikin[6] &= 0xF0;
|
|
daikin[6] |= (uint8_t) ((numMins >> 8) & 0x0F);
|
|
}
|
|
|
|
uint16_t IRDaikinESP::getCurrentTime() {
|
|
uint16_t ret;
|
|
ret = daikin[6] & 0x0F;
|
|
ret <<= 8;
|
|
ret += daikin[5];
|
|
return ret;
|
|
}
|
|
|
|
#ifdef ARDUINO
|
|
String IRDaikinESP::renderTime(uint16_t timemins) {
|
|
String ret;
|
|
#else // ARDUINO
|
|
std::string IRDaikinESP::renderTime(uint16_t timemins) {
|
|
std::string ret;
|
|
#endif // ARDUINO
|
|
uint16_t hours, mins;
|
|
hours = timemins / 60;
|
|
ret = uint64ToString(hours) + ":";
|
|
mins = timemins - (hours * 60);
|
|
if (mins < 10)
|
|
ret += "0";
|
|
ret += uint64ToString(mins);
|
|
return ret;
|
|
}
|
|
|
|
// Convert the internal state into a human readable string.
|
|
#ifdef ARDUINO
|
|
String IRDaikinESP::toString() {
|
|
String result = "";
|
|
#else // ARDUINO
|
|
std::string IRDaikinESP::toString() {
|
|
std::string result = "";
|
|
#endif // ARDUINO
|
|
result += "Power: ";
|
|
if (getPower())
|
|
result += "On";
|
|
else
|
|
result += "Off";
|
|
result += ", Mode: " + uint64ToString(getMode());
|
|
switch (getMode()) {
|
|
case DAIKIN_AUTO:
|
|
result += " (AUTO)";
|
|
break;
|
|
case DAIKIN_COOL:
|
|
result += " (COOL)";
|
|
break;
|
|
case DAIKIN_HEAT:
|
|
result += " (HEAT)";
|
|
break;
|
|
case DAIKIN_DRY:
|
|
result += " (DRY)";
|
|
break;
|
|
case DAIKIN_FAN:
|
|
result += " (FAN)";
|
|
break;
|
|
default:
|
|
result += " (UNKNOWN)";
|
|
}
|
|
result += ", Temp: " + uint64ToString(getTemp()) + "C";
|
|
result += ", Fan: " + uint64ToString(getFan());
|
|
switch (getFan()) {
|
|
case DAIKIN_FAN_AUTO:
|
|
result += " (AUTO)";
|
|
break;
|
|
case DAIKIN_FAN_QUIET:
|
|
result += " (QUIET)";
|
|
break;
|
|
case DAIKIN_FAN_MIN:
|
|
result += " (MIN)";
|
|
break;
|
|
case DAIKIN_FAN_MAX:
|
|
result += " (MAX)";
|
|
break;
|
|
}
|
|
result += ", Powerful: ";
|
|
if (getPowerful())
|
|
result += "On";
|
|
else
|
|
result += "Off";
|
|
result += ", Quiet: ";
|
|
if (getQuiet())
|
|
result += "On";
|
|
else
|
|
result += "Off";
|
|
result += ", Sensor: ";
|
|
if (getSensor())
|
|
result += "On";
|
|
else
|
|
result += "Off";
|
|
result += ", Eye: ";
|
|
if (getEye())
|
|
result += "On";
|
|
else
|
|
result += "Off";
|
|
result += ", Mold: ";
|
|
if (getMold())
|
|
result += "On";
|
|
else
|
|
result += "Off";
|
|
result += ", Swing (Horizontal): ";
|
|
if (getSwingHorizontal())
|
|
result += "On";
|
|
else
|
|
result += "Off";
|
|
result += ", Swing (Vertical): ";
|
|
if (getSwingVertical())
|
|
result += "On";
|
|
else
|
|
result += "Off";
|
|
result += ", Current Time: " + renderTime(getCurrentTime());
|
|
result += ", On Time: ";
|
|
if (getOnTimerEnabled())
|
|
result += renderTime(getOnTime());
|
|
else
|
|
result += "Off";
|
|
result += ", Off Time: ";
|
|
if (getOffTimerEnabled())
|
|
result += renderTime(getOffTime());
|
|
else
|
|
result += "Off";
|
|
|
|
return result;
|
|
}
|
|
|
|
#if DAIKIN_DEBUG
|
|
// Print what we have
|
|
void IRDaikinESP::printState() {
|
|
#ifdef ARDUINO
|
|
String strbits;
|
|
#else // ARDUINO
|
|
std::string strbits;
|
|
#endif // ARDUINO
|
|
DPRINTLN("Raw Bits:");
|
|
for (uint8_t i = 0; i < DAIKIN_COMMAND_LENGTH; i++) {
|
|
strbits = uint64ToString(daikin[i], BIN);
|
|
while (strbits.length() < 8)
|
|
strbits = "0" + strbits;
|
|
DPRINT(strbits);
|
|
DPRINT(" ");
|
|
}
|
|
DPRINTLN("");
|
|
DPRINTLN(toString());
|
|
}
|
|
#endif // DAIKIN_DEBUG
|
|
|
|
/*
|
|
* Return most important bits to allow replay
|
|
* layout is:
|
|
* 0: Power
|
|
* 1-3: Mode
|
|
* 4-7: Fan speed/mode
|
|
* 8-14: Target Temperature
|
|
* 15: Econo
|
|
* 16: Powerful
|
|
* 17: Quiet
|
|
* 18: Sensor
|
|
* 19: Swing Vertical
|
|
* 20-31: Current time (mins since midnight)
|
|
* */
|
|
uint32_t IRDaikinESP::getCommand() {
|
|
uint32_t ret = 0;
|
|
uint32_t tmp = 0;
|
|
if (getPower())
|
|
ret |= 0b00000000000000000000000000000001;
|
|
tmp = getMode();
|
|
tmp = tmp << 1;
|
|
ret |= tmp;
|
|
|
|
tmp = getFan();
|
|
tmp <<= 4;
|
|
ret |= tmp;
|
|
|
|
tmp = getTemp();
|
|
tmp <<= 8;
|
|
ret |= tmp;
|
|
|
|
if (getEcono())
|
|
ret |= 0b00000000000000001000000000000000;
|
|
if (getPowerful())
|
|
ret |= 0b00000000000000010000000000000000;
|
|
if (getQuiet())
|
|
ret |= 0b00000000000000100000000000000000;
|
|
if (getSensor())
|
|
ret |= 0b00000000000001000000000000000000;
|
|
if (getSwingVertical())
|
|
ret |= 0b00000000000010000000000000000000;
|
|
ret |= (getCurrentTime() << 20);
|
|
return ret;
|
|
}
|
|
|
|
void IRDaikinESP::setCommand(uint32_t value) {
|
|
uint32_t tmp = 0;
|
|
if (value & 0b00000000000000000000000000000001)
|
|
setPower(true);
|
|
tmp = value & 0b00000000000000000000000000001110;
|
|
tmp >>= 1;
|
|
setMode(tmp);
|
|
|
|
tmp = value & 0b00000000000000000000000011110000;
|
|
tmp >>= 4;
|
|
setFan(tmp);
|
|
|
|
tmp = value & 0b00000000000000000111111100000000;
|
|
tmp >>= 8;
|
|
setTemp(tmp);
|
|
|
|
if (value & 0b00000000000000001000000000000000)
|
|
setEcono(true);
|
|
if (value & 0b00000000000000010000000000000000)
|
|
setPowerful(true);
|
|
if (value & 0b00000000000000100000000000000000)
|
|
setQuiet(true);
|
|
if (value & 0b00000000000001000000000000000000)
|
|
setSensor(true);
|
|
if (value & 0b00000000000010000000000000000000)
|
|
setSwingVertical(true);
|
|
|
|
value >>= 20;
|
|
setCurrentTime(value);
|
|
}
|
|
|
|
#if DECODE_DAIKIN
|
|
|
|
void addbit(bool val, unsigned char data[]) {
|
|
uint8_t curbit = data[DAIKIN_CURBIT];
|
|
uint8_t curindex = data[DAIKIN_CURINDEX];
|
|
if (val) {
|
|
unsigned char bit = 1;
|
|
bit = bit << curbit;
|
|
data[curindex] |= bit;
|
|
}
|
|
curbit++;
|
|
if (curbit == 8) {
|
|
curbit = 0;
|
|
curindex++;
|
|
}
|
|
data[DAIKIN_CURBIT] = curbit;
|
|
data[DAIKIN_CURINDEX] = curindex;
|
|
}
|
|
|
|
bool checkheader(decode_results *results, uint16_t* offset) {
|
|
if (!IRrecv::matchMark(results->rawbuf[(*offset)++], DAIKIN_BIT_MARK,
|
|
DAIKIN_TOLERANCE, DAIKIN_MARK_EXCESS))
|
|
return false;
|
|
if (!IRrecv::matchSpace(results->rawbuf[(*offset)++],
|
|
DAIKIN_ZERO_SPACE + DAIKIN_GAP,
|
|
DAIKIN_TOLERANCE, DAIKIN_MARK_EXCESS))
|
|
return false;
|
|
if (!IRrecv::matchMark(results->rawbuf[(*offset)++], DAIKIN_HDR_MARK,
|
|
DAIKIN_TOLERANCE, DAIKIN_MARK_EXCESS))
|
|
return false;
|
|
if (!IRrecv::matchSpace(results->rawbuf[(*offset)++], DAIKIN_HDR_SPACE,
|
|
DAIKIN_TOLERANCE, DAIKIN_MARK_EXCESS))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool readbits(decode_results *results, uint16_t *offset,
|
|
unsigned char daikin_code[], uint16_t countbits) {
|
|
for (uint16_t i = 0; i < countbits && *offset < results->rawlen - 1;
|
|
i++, (*offset)++) {
|
|
if (!IRrecv::matchMark(results->rawbuf[(*offset)++], DAIKIN_BIT_MARK,
|
|
DAIKIN_TOLERANCE, DAIKIN_MARK_EXCESS))
|
|
return false;
|
|
if (IRrecv::matchSpace(results->rawbuf[*offset], DAIKIN_ONE_SPACE,
|
|
DAIKIN_TOLERANCE, DAIKIN_MARK_EXCESS))
|
|
addbit(1, daikin_code);
|
|
else if (IRrecv::matchSpace(results->rawbuf[*offset], DAIKIN_ZERO_SPACE,
|
|
DAIKIN_TOLERANCE, DAIKIN_MARK_EXCESS))
|
|
addbit(0, daikin_code);
|
|
else
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// 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. (DAIKIN_RAW_BITS)
|
|
// 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.
|
|
//
|
|
// Notes:
|
|
// If DAIKIN_DEBUG enabled, will print all the set options and values.
|
|
//
|
|
// Ref:
|
|
// https://github.com/mharizanov/Daikin-AC-remote-control-over-the-Internet/tree/master/IRremote
|
|
bool IRrecv::decodeDaikin(decode_results *results, uint16_t nbits,
|
|
bool strict) {
|
|
if (results->rawlen < DAIKIN_RAW_BITS)
|
|
return false;
|
|
|
|
// Compliance
|
|
if (strict && nbits != DAIKIN_RAW_BITS)
|
|
return false;
|
|
|
|
uint16_t offset = OFFSET_START;
|
|
unsigned char daikin_code[DAIKIN_COMMAND_LENGTH + 2];
|
|
for (uint8_t i = 0; i < DAIKIN_COMMAND_LENGTH+2; i++)
|
|
daikin_code[i] = 0;
|
|
|
|
// Header (#1)
|
|
for (uint8_t i = 0; i < 10; i++) {
|
|
if (!matchMark(results->rawbuf[offset++], DAIKIN_BIT_MARK))
|
|
return false;
|
|
}
|
|
if (!checkheader(results, &offset)) return false;
|
|
|
|
// Data (#1)
|
|
if (!readbits(results, &offset, daikin_code, 8 * 8)) return false;
|
|
|
|
// Ignore everything that has just been captured as it is not needed.
|
|
// Some remotes may not send this portion, my remote did, but it's not
|
|
// required.
|
|
for (uint8_t i = 0; i < DAIKIN_COMMAND_LENGTH + 2; i++)
|
|
daikin_code[i] = 0;
|
|
|
|
// Header (#2)
|
|
if (!checkheader(results, &offset)) return false;
|
|
|
|
// Data (#2)
|
|
if (!readbits(results, &offset, daikin_code, 8 * 8)) return false;
|
|
|
|
// Header (#3)
|
|
if (!checkheader(results, &offset)) return false;
|
|
|
|
// Data (#3), read up everything else
|
|
if (!readbits(results, &offset, daikin_code, DAIKIN_BITS - (8 * 8)))
|
|
return false;
|
|
|
|
// Footer
|
|
if (!matchMark(results->rawbuf[offset++], DAIKIN_BIT_MARK))
|
|
return false;
|
|
if (offset < results->rawlen && !matchAtLeast(results->rawbuf[offset],
|
|
DAIKIN_GAP))
|
|
return false;
|
|
|
|
// Compliance
|
|
if (strict) {
|
|
if (!IRDaikinESP::validChecksum(daikin_code)) return false;
|
|
}
|
|
|
|
// Success
|
|
#if DAIKIN_DEBUG
|
|
IRDaikinESP dako = IRDaikinESP(0);
|
|
dako.setRaw(daikin_code);
|
|
#ifdef ARDUINO
|
|
yield();
|
|
#endif // ARDUINO
|
|
dako.printState();
|
|
#endif // DAIKIN_DEBUG
|
|
|
|
// Copy across the bits to state
|
|
for (uint8_t i = 0; i < DAIKIN_COMMAND_LENGTH; i++)
|
|
results->state[i] = daikin_code[i];
|
|
results->bits = DAIKIN_COMMAND_LENGTH * 8;
|
|
results->decode_type = DAIKIN;
|
|
return true;
|
|
}
|
|
#endif // DECODE_DAIKIN
|