Skip to content

Commit

Permalink
Experimental timer/clock support for Panasonic A/Cs. (#546)
Browse files Browse the repository at this point in the history
- Clock, On Timer, & Off Timer methods for the IRPanasonicAc class.
- Unit tests for those.
- Change SetSwingV() -> SetSwingVertical()
- Change SetSwingH() -> SetSwingHorizontal()

Ref: #544
  • Loading branch information
crankyoldgit authored Oct 11, 2018
1 parent 9e29f94 commit 9aaa511
Show file tree
Hide file tree
Showing 4 changed files with 263 additions and 31 deletions.
11 changes: 9 additions & 2 deletions keywords.txt
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ calcLGChecksum KEYWORD2
calcUSecPeriod KEYWORD2
calculateChecksum KEYWORD2
calibrate KEYWORD2
cancelOffTimer KEYWORD2
cancelOnTimer KEYWORD2
cancelTimers KEYWORD2
checkheader KEYWORD2
checksum KEYWORD2
Expand Down Expand Up @@ -125,6 +127,7 @@ encodeSAMSUNG KEYWORD2
encodeSanyoLC7461 KEYWORD2
encodeSharp KEYWORD2
encodeSony KEYWORD2
encodeTime KEYWORD2
fixChecksum KEYWORD2
fixup KEYWORD2
getBeep KEYWORD2
Expand Down Expand Up @@ -187,6 +190,8 @@ getZoneFollow KEYWORD2
getiFeel KEYWORD2
hasACState KEYWORD2
invertBits KEYWORD2
isOffTimerEnabled KEYWORD2
isOnTimerEnabled KEYWORD2
ledOff KEYWORD2
ledOn KEYWORD2
mark KEYWORD2
Expand Down Expand Up @@ -300,9 +305,7 @@ setSpeed KEYWORD2
setStartClock KEYWORD2
setStopClock KEYWORD2
setSwing KEYWORD2
setSwingH KEYWORD2
setSwingHorizontal KEYWORD2
setSwingV KEYWORD2
setSwingVertical KEYWORD2
setTemp KEYWORD2
setTempRaw KEYWORD2
Expand Down Expand Up @@ -1325,6 +1328,7 @@ kPanasonicAcBits LITERAL1
kPanasonicAcChecksumInit LITERAL1
kPanasonicAcCool LITERAL1
kPanasonicAcDry LITERAL1
kPanasonicAcExcess LITERAL1
kPanasonicAcFan LITERAL1
kPanasonicAcFanAuto LITERAL1
kPanasonicAcFanMax LITERAL1
Expand Down Expand Up @@ -1352,6 +1356,9 @@ kPanasonicAcSwingHRight LITERAL1
kPanasonicAcSwingVAuto LITERAL1
kPanasonicAcSwingVDown LITERAL1
kPanasonicAcSwingVUp LITERAL1
kPanasonicAcTimeMax LITERAL1
kPanasonicAcTimeSpecial LITERAL1
kPanasonicAcTolerance LITERAL1
kPanasonicBitMark LITERAL1
kPanasonicBitMarkTicks LITERAL1
kPanasonicBits LITERAL1
Expand Down
124 changes: 119 additions & 5 deletions src/ir_Panasonic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,18 @@
//
// Panasonic A/C support add by crankyoldgit but heavily influenced by:
// https://github.com/ToniA/ESPEasy/blob/HeatpumpIR/lib/HeatpumpIR/PanasonicHeatpumpIR.cpp
// Panasonic A/C Clock & Timer support:
// Reverse Engineering by MikkelTb
// Code by crankyoldgit
// Panasonic A/C models supported:
// A/C Series/models:
// JKE, LKE, DKE, & NKE series. (In theory)
// CS-YW9MKD (confirmed)
// CS-ME14CKPG
// A/C Remotes:
// A75C3747 (confirmed)
// A75C3704
// A75C2311

// Constants
// Ref:
Expand Down Expand Up @@ -311,8 +316,8 @@ void IRPanasonicAc::setModel(const panasonic_ac_remote_model_t model) {
case kPanasonicDke:
remote_state[23] = 0x01;
remote_state[25] = 0x06;
// Has to be done last as setSwingH has model check built-in
setSwingH(_swingh);
// Has to be done last as setSwingHorizontal has model check built-in
setSwingHorizontal(_swingh);
break;
case kPanasonicNke:
remote_state[17] = 0x06;
Expand All @@ -329,7 +334,7 @@ panasonic_ac_remote_model_t IRPanasonicAc::getModel() {
return kPanasonicJke;
if (remote_state[17] == 0x06 && (remote_state[13] & 0x0F) == 0x02)
return kPanasonicLke;
if (remote_state[23] == 0x01 && remote_state[25] == 0x06)
if (remote_state[23] == 0x01)
return kPanasonicDke;
if (remote_state[17] == 0x06)
return kPanasonicNke;
Expand Down Expand Up @@ -413,7 +418,7 @@ uint8_t IRPanasonicAc::getSwingVertical() {
return remote_state[16] & 0x0F;
}

void IRPanasonicAc::setSwingV(const uint8_t desired_elevation) {
void IRPanasonicAc::setSwingVertical(const uint8_t desired_elevation) {
uint8_t elevation = desired_elevation;
if (elevation != kPanasonicAcSwingVAuto) {
elevation = std::max(elevation, kPanasonicAcSwingVUp);
Expand All @@ -427,7 +432,7 @@ uint8_t IRPanasonicAc::getSwingHorizontal() {
return remote_state[17];
}

void IRPanasonicAc::setSwingH(const uint8_t desired_direction) {
void IRPanasonicAc::setSwingHorizontal(const uint8_t desired_direction) {
switch (desired_direction) {
case kPanasonicAcSwingHAuto:
case kPanasonicAcSwingHMiddle:
Expand Down Expand Up @@ -490,6 +495,104 @@ void IRPanasonicAc::setPowerful(const bool state) {
}
}

uint16_t IRPanasonicAc::encodeTime(const uint8_t hours, const uint8_t mins) {
return std::min(hours, (uint8_t) 23) * 60 + std::min(mins, (uint8_t) 59);
}

uint16_t IRPanasonicAc::getClock() {
uint16_t result = ((remote_state[25] & 0b00000111) << 8) + remote_state[24];
if (result == kPanasonicAcTimeSpecial) return 0;
return result;
}

void IRPanasonicAc::setClock(const uint16_t mins_since_midnight) {
uint16_t corrected = std::min(mins_since_midnight, kPanasonicAcTimeMax);
if (mins_since_midnight == kPanasonicAcTimeSpecial)
corrected = kPanasonicAcTimeSpecial;
remote_state[24] = corrected & 0xFF;
remote_state[25] &= 0b11111000;
remote_state[25] |= (corrected >> 8);
}

uint16_t IRPanasonicAc::getOnTimer() {
uint16_t result = ((remote_state[19] & 0b00000111) << 8) + remote_state[18];
if (result == kPanasonicAcTimeSpecial) return 0;
return result;
}

void IRPanasonicAc::setOnTimer(const uint16_t mins_since_midnight,
const bool enable) {
// Ensure it's on a 10 minute boundary and no overflow.
uint16_t corrected = std::min(mins_since_midnight, kPanasonicAcTimeMax);
corrected -= corrected % 10;
if (mins_since_midnight == kPanasonicAcTimeSpecial)
corrected = kPanasonicAcTimeSpecial;

if (enable)
remote_state[13] |= kPanasonicAcOnTimer; // Set the Ontimer flag.
else
remote_state[13] &= ~kPanasonicAcOnTimer; // Clear the Ontimer flag.
// Store the time.
remote_state[18] = corrected & 0xFF;
remote_state[19] &= 0b11111000;
remote_state[19] |= (corrected >> 8);
}

void IRPanasonicAc::cancelOnTimer() {
setOnTimer(0, false);
}

bool IRPanasonicAc::isOnTimerEnabled() {
return remote_state[13] & kPanasonicAcOnTimer;
}

uint16_t IRPanasonicAc::getOffTimer() {
uint16_t result = ((remote_state[20] & 0b01111111) << 4) +
(remote_state[19] >> 4);
if (result == kPanasonicAcTimeSpecial) return 0;
return result;
}

void IRPanasonicAc::setOffTimer(const uint16_t mins_since_midnight,
const bool enable) {
// Ensure its on a 10 minute boundary and no overflow.
uint16_t corrected = std::min(mins_since_midnight, kPanasonicAcTimeMax);
corrected -= corrected % 10;
if (mins_since_midnight == kPanasonicAcTimeSpecial)
corrected = kPanasonicAcTimeSpecial;

if (enable)
remote_state[13] |= kPanasonicAcOffTimer; // Set the OffTimer flag.
else
remote_state[13] &= ~kPanasonicAcOffTimer; // Clear the OffTimer flag.
// Store the time.
remote_state[19] &= 0b00001111;
remote_state[19] |= (corrected & 0b00001111) << 4;
remote_state[20] &= 0b10000000;
remote_state[20] |= corrected >> 4;
}

void IRPanasonicAc::cancelOffTimer() {
setOffTimer(0, false);
}

bool IRPanasonicAc::isOffTimerEnabled() {
return remote_state[13] & kPanasonicAcOffTimer;
}

#ifdef ARDUINO
String IRPanasonicAc::timeToString(const uint16_t mins_since_midnight) {
String result = "";
#else
std::string IRPanasonicAc::timeToString(const uint16_t mins_since_midnight) {
std::string result = "";
#endif // ARDUINO
result += uint64ToString(mins_since_midnight / 60) + ":";
uint8_t mins = mins_since_midnight % 60;
if (mins < 10) result += "0"; // Zero pad the minutes.
return result + uint64ToString(mins);
}

// Convert the internal state into a human readable string.
#ifdef ARDUINO
String IRPanasonicAc::toString() {
Expand Down Expand Up @@ -611,6 +714,17 @@ std::string IRPanasonicAc::toString() {
result += "On";
else
result += "Off";
result += ", Clock: " + timeToString(getClock());
result += ", On Timer: ";
if (isOnTimerEnabled())
result += timeToString(getOnTimer());
else
result += "Off";
result += ", Off Timer: ";
if (isOffTimerEnabled())
result += timeToString(getOffTimer());
else
result += "Off";
return result;
}

Expand Down
22 changes: 18 additions & 4 deletions src/ir_Panasonic.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ const uint8_t kPanasonicAcSwingHFullRight = 0xC;
const uint8_t kPanasonicAcChecksumInit = 0xF4;
const uint8_t kPanasonicAcOnTimer = 0b00000010;
const uint8_t kPanasonicAcOffTimer = 0b00000100;

const uint16_t kPanasonicAcTimeMax = 23 * 60 + 59; // Mins since midnight.
const uint16_t kPanasonicAcTimeSpecial = 0x600;

const uint8_t kPanasonicKnownGoodState[kPanasonicAcStateLength] = {
0x02, 0x20, 0xE0, 0x04, 0x00, 0x00, 0x00, 0x06,
Expand Down Expand Up @@ -102,15 +103,28 @@ class IRPanasonicAc {
bool getPowerful();
void setModel(const panasonic_ac_remote_model_t model);
panasonic_ac_remote_model_t getModel();
void setSwingV(const uint8_t elevation);
void setSwingVertical(const uint8_t elevation);
uint8_t getSwingVertical();
void setSwingH(const uint8_t direction);
void setSwingHorizontal(const uint8_t direction);
uint8_t getSwingHorizontal();

static uint16_t encodeTime(const uint8_t hours, const uint8_t mins);
uint16_t getClock();
void setClock(const uint16_t mins_since_midnight);
uint16_t getOnTimer();
void setOnTimer(const uint16_t mins_since_midnight, const bool enable = true);
void cancelOnTimer();
bool isOnTimerEnabled();
uint16_t getOffTimer();
void setOffTimer(const uint16_t mins_since_midnight,
const bool enable = true);
void cancelOffTimer();
bool isOffTimerEnabled();
#ifdef ARDUINO
String toString();
static String timeToString(const uint16_t mins_since_midnight);
#else
std::string toString();
static std::string timeToString(const uint16_t mins_since_midnight);
#endif
#ifndef UNIT_TEST

Expand Down
Loading

0 comments on commit 9aaa511

Please sign in to comment.