Skip to content

Commit

Permalink
Experimental support for detailed Samsung AirCon messages. (#521)
Browse files Browse the repository at this point in the history
* Construction and decoding via IRSamsungAc class.
* Unit tests covering new code.
* Update IRrecvDumpV2 example code to reflect new features.
* Ref: #505

Note:
  Checksum support needs more data to be certain it is correct.
  "Quiet" & "Clean" modes are guesses. May not be correct.
  • Loading branch information
crankyoldgit authored Sep 20, 2018
1 parent cf175f7 commit a560e39
Show file tree
Hide file tree
Showing 5 changed files with 616 additions and 4 deletions.
8 changes: 8 additions & 0 deletions examples/IRrecvDumpV2/IRrecvDumpV2.ino
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include <ir_Kelvinator.h>
#include <ir_Mitsubishi.h>
#include <ir_Midea.h>
#include <ir_Samsung.h>
#include <ir_Toshiba.h>

// ==================== start of TUNEABLE PARAMETERS ====================
Expand Down Expand Up @@ -177,6 +178,13 @@ void dumpACInfo(decode_results *results) {
description = ac.toString();
}
#endif // DECODE_HAIER_AC_YRW02
#if DECODE_SAMSUNG_AC
if (results->decode_type == SAMSUNG_AC) {
IRSamsungAc ac(0);
ac.setRaw(results->state);
description = ac.toString();
}
#endif // DECODE_SAMSUNG_AC
#if DECODE_COOLIX
if (results->decode_type == COOLIX) {
IRCoolixAC ac(0);
Expand Down
299 changes: 297 additions & 2 deletions src/ir_Samsung.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
// Copyright 2009 Ken Shirriff
// Copyright 2017 David Conran

#include "ir_Samsung.h"
#include <algorithm>
#ifndef ARDUINO
#include <string>
#endif
#include "IRrecv.h"
#include "IRsend.h"
#include "IRutils.h"
Expand Down Expand Up @@ -211,6 +215,287 @@ void IRsend::sendSamsungAC(uint8_t data[], uint16_t nbytes, uint16_t repeat) {
}
#endif // SEND_SAMSUNG_AC


IRSamsungAc::IRSamsungAc(uint16_t pin) : _irsend(pin) {
stateReset();
}

void IRSamsungAc::stateReset() {
for (uint8_t i = 0; i < kSamsungAcStateLength; i++)
remote_state[i] = 0x0;
remote_state[0] = 0x02;
remote_state[1] = 0x92;
remote_state[2] = 0x0F;
remote_state[6] = 0xF0;
remote_state[7] = 0x01;
remote_state[8] = 0x02;
remote_state[9] = 0xAF;
remote_state[10] = 0x71;
remote_state[12] = 0x15;
remote_state[13] = 0xF0;
}

void IRSamsungAc::begin() {
_irsend.begin();
}

uint8_t IRSamsungAc::calcChecksum(
const uint8_t state[], const uint16_t length) {
uint8_t sum = 0;
uint8_t currentbyte;

// Shamelessly inspired by:
// https://github.com/adafruit/Raw-IR-decoder-for-Arduino/pull/3/files
// Count most of the '1' bits after the checksum location.
for (uint8_t i = 9; i < length - 1; i++) {
currentbyte = state[i];
if (i == 9) currentbyte = state[9] & 0b11111110;
for (; currentbyte; currentbyte >>= 1)
if (currentbyte & 1) sum++;
}
return (28 - sum) & 0xF;
}

bool IRSamsungAc::validChecksum(const uint8_t state[],
const uint16_t length) {
if (length < 9) return true; // No checksum to compare with. Assume okay.
return (state[8] >> 4) == calcChecksum(state, length);
}

// Update the checksum for the internal state.
void IRSamsungAc::checksum(uint16_t length) {
if (length < 9) return;
remote_state[8] &= 0x0F;
remote_state[8] |= (calcChecksum(remote_state, length) << 4);
}

#if SEND_SAMSUNG_AC
void IRSamsungAc::send() {
checksum();
_irsend.sendSamsungAC(remote_state);
}
#endif // SEND_SAMSUNG_AC

uint8_t* IRSamsungAc::getRaw() {
checksum();
return remote_state;
}

void IRSamsungAc::setRaw(const uint8_t new_code[]) {
for (uint8_t i = 0; i < kSamsungAcStateLength; i++) {
remote_state[i] = new_code[i];
}
}

void IRSamsungAc::on() {
remote_state[1] &= ~kSamsungAcPowerMask1;
remote_state[6] |= kSamsungAcPowerMask2;
}

void IRSamsungAc::off() {
remote_state[1] |= kSamsungAcPowerMask1;
remote_state[6] &= ~kSamsungAcPowerMask2;
}

void IRSamsungAc::setPower(const bool state) {
if (state)
on();
else
off();
}

bool IRSamsungAc::getPower() {
return ((remote_state[6] & kSamsungAcPowerMask2) != 0) &&
((remote_state[1] & kSamsungAcPowerMask1) == 0);
}

// Set the temp. in deg C
void IRSamsungAc::setTemp(const uint8_t temp) {
uint8_t newtemp = std::max(kSamsungAcMinTemp, temp);
newtemp = std::min(kSamsungAcMaxTemp, newtemp);
remote_state[11] = (remote_state[11] & ~kSamsungAcTempMask) |
((newtemp - kSamsungAcMinTemp) << 4);
}

// Return the set temp. in deg C
uint8_t IRSamsungAc::getTemp() {
return ((remote_state[11] & kSamsungAcTempMask) >> 4) + kSamsungAcMinTemp;
}

void IRSamsungAc::setMode(const uint8_t mode) {
// If we get an unexpected mode, default to AUTO.
uint8_t newmode = mode;
if (newmode > kSamsungAcHeat) newmode = kSamsungAcAuto;
remote_state[12] = (remote_state[12] & ~kSamsungAcModeMask) | (newmode << 4);

// Auto mode has a special fan setting valid only in auto mode.
if (newmode == kSamsungAcAuto) {
setFan(kSamsungAcFanAuto2);
} else {
if (getFan() == kSamsungAcFanAuto2) // Non-Auto can't have this fan setting
setFan(kSamsungAcFanAuto); // Default to something safe.
}
}

uint8_t IRSamsungAc::getMode() {
return ((remote_state[12] & kSamsungAcModeMask) >> 4);
}

void IRSamsungAc::setFan(const uint8_t speed) {
switch (speed) {
case kSamsungAcFanAuto:
case kSamsungAcFanLow:
case kSamsungAcFanMed:
case kSamsungAcFanHigh:
case kSamsungAcFanTurbo:
if (getMode() == kSamsungAcAuto) return; // Not valid in Auto mode.
break;
case kSamsungAcFanAuto2: // Special fan setting for when in Auto mode.
if (getMode() != kSamsungAcAuto) return;
break;
default:
return;
}
remote_state[12] = (remote_state[12] & ~kSamsungAcFanMask) | (speed << 1);
}

uint8_t IRSamsungAc::getFan() {
return ((remote_state[12] & kSamsungAcFanMask) >> 1);
}

bool IRSamsungAc::getSwing() {
return (remote_state[8] & kSamsungAcSwingMask) == (kSamsungAcSwingMove << 4);
}

void IRSamsungAc::setSwing(const bool state) {
remote_state[8] &= ~kSamsungAcSwingMask; // Clear the previous swing state.
if (state)
remote_state[8] |= (kSamsungAcSwingMove << 4);
else
remote_state[8] |= (kSamsungAcSwingStop << 4);
}

bool IRSamsungAc::getBeep() {
return remote_state[13] & kSamsungAcBeepMask;
}

void IRSamsungAc::setBeep(const bool state) {
if (state)
remote_state[13] |= kSamsungAcBeepMask;
else
remote_state[13] &= ~kSamsungAcBeepMask;
}

bool IRSamsungAc::getClean() {
return (remote_state[10] & kSamsungAcCleanMask10) &&
(remote_state[11] & kSamsungAcCleanMask11);
}

void IRSamsungAc::setClean(const bool state) {
if (state) {
remote_state[10] |= kSamsungAcCleanMask10;
remote_state[11] |= kSamsungAcCleanMask11;
} else {
remote_state[10] &= ~kSamsungAcCleanMask10;
remote_state[11] &= ~kSamsungAcCleanMask11;
}
}

// Very unsure this is correct.
bool IRSamsungAc::getQuiet() {
return remote_state[11] & kSamsungAcQuietMask11;
}

// Very unsure this is correct.
void IRSamsungAc::setQuiet(const bool state) {
if (state) {
remote_state[11] |= kSamsungAcQuietMask11;
setFan(kSamsungAcFanAuto); // Quiet mode seems to set fan speed to auto.
} else {
remote_state[11] &= ~kSamsungAcQuietMask11;
}
}

// Convert the internal state into a human readable string.
#ifdef ARDUINO
String IRSamsungAc::toString() {
String result = "";
#else
std::string IRSamsungAc::toString() {
std::string result = "";
#endif // ARDUINO
result += "Power: ";
if (getPower())
result += "On";
else
result += "Off";
result += ", Mode: " + uint64ToString(getMode());
switch (getMode()) {
case kSamsungAcAuto:
result += " (AUTO)";
break;
case kSamsungAcCool:
result += " (COOL)";
break;
case kSamsungAcHeat:
result += " (HEAT)";
break;
case kSamsungAcDry:
result += " (DRY)";
break;
case kSamsungAcFan:
result += " (FAN)";
break;
default:
result += " (UNKNOWN)";
}
result += ", Temp: " + uint64ToString(getTemp()) + "C";
result += ", Fan: " + uint64ToString(getFan());
switch (getFan()) {
case kSamsungAcFanAuto:
case kSamsungAcFanAuto2:
result += " (AUTO)";
break;
case kSamsungAcFanLow:
result += " (LOW)";
break;
case kSamsungAcFanMed:
result += " (MED)";
break;
case kSamsungAcFanHigh:
result += " (HIGH)";
break;
case kSamsungAcFanTurbo:
result += " (TURBO)";
break;
default:
result += " (UNKNOWN)";
break;
}
result += ", Swing: ";
if (getSwing())
result += "On";
else
result += "Off";
result += ", Beep: ";
if (getBeep())
result += "On";
else
result += "Off";
result += ", Clean: ";
if (getBeep())
result += "On";
else
result += "Off";
result += ", Quiet: ";
if (getQuiet())
result += "On";
else
result += "Off";
return result;
}


#if DECODE_SAMSUNG_AC
// Decode the supplied Samsung A/C message.
//
Expand Down Expand Up @@ -262,8 +547,12 @@ bool IRrecv::decodeSamsungAC(decode_results *results, uint16_t nbits,
kSamsungAcOneSpace,
kSamsungAcBitMark,
kSamsungAcZeroSpace,
kTolerance, kMarkExcess, false);
if (data_result.success == false) break; // Fail
kTolerance, 0, false);
if (data_result.success == false) {
DPRINT("DEBUG: offset = ");
DPRINTLN(offset + data_result.used);
return false; // Fail
}
results->state[i] = data_result.data;
}
// Section Footer
Expand All @@ -282,6 +571,12 @@ bool IRrecv::decodeSamsungAC(decode_results *results, uint16_t nbits,
if (strict) {
// Re-check we got the correct size/length due to the way we read the data.
if (dataBitsSoFar != kSamsungAcBits) return false;
// Is the model signature correct?
if (results->state[0] != 0x02 || results->state[1] != 0x92 ||
results->state[2] != 0x0F)
return false;
// Is the checksum valid?
if (!IRSamsungAc::validChecksum(results->state)) return false;
}
// Success
results->decode_type = SAMSUNG_AC;
Expand Down
Loading

0 comments on commit a560e39

Please sign in to comment.