Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Experimental timer/clock support for Panasonic A/Cs. #546

Merged
merged 1 commit into from
Oct 11, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -299,9 +304,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 @@ -1324,6 +1327,7 @@ kPanasonicAcBits LITERAL1
kPanasonicAcChecksumInit LITERAL1
kPanasonicAcCool LITERAL1
kPanasonicAcDry LITERAL1
kPanasonicAcExcess LITERAL1
kPanasonicAcFan LITERAL1
kPanasonicAcFanAuto LITERAL1
kPanasonicAcFanMax LITERAL1
Expand Down Expand Up @@ -1351,6 +1355,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