Skip to content

Commit

Permalink
SamsungAc: Redo/fix checksum calculations. (#1554)
Browse files Browse the repository at this point in the history
* Refactor & Fix checksum calcs so it works per section & thus with extended states.
* Add additional unit tests checking the section calcs.
* Uncomment the failing unit test as it now works and is explained.
  - Mark TODO as done.
* Cleanup code style & elements in `ir_Samsung_test.cpp`.

Huge kudos to @thermseekr for explaining the algorithm.

Fixes #1538
  • Loading branch information
crankyoldgit committed Aug 10, 2021
1 parent c04c705 commit 0ec49ff
Show file tree
Hide file tree
Showing 3 changed files with 314 additions and 215 deletions.
72 changes: 44 additions & 28 deletions src/ir_Samsung.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/621
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1062
/// @see http://elektrolab.wz.cz/katalog/samsung_protocol.pdf
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1538 (Checksum)

#include "ir_Samsung.h"
#include <algorithm>
Expand Down Expand Up @@ -290,46 +291,61 @@ void IRSamsungAc::stateReset(const bool forcepower, const bool initialPower) {
/// Set up hardware to be able to send a message.
void IRSamsungAc::begin(void) { _irsend.begin(); }

/// Calculate the checksum for a given state.
/// @param[in] state The array to calc the checksum of.
/// @param[in] length The length/size of the array.
/// Get the existing checksum for a given state section.
/// @param[in] section The array to extract the checksum from.
/// @return The existing checksum value.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1538#issuecomment-894645947
uint8_t IRSamsungAc::getSectionChecksum(const uint8_t *section) {
return ((GETBITS8(*(section + 2), kLowNibble, kNibbleSize) << kNibbleSize) +
GETBITS8(*(section + 1), kHighNibble, kNibbleSize));
}

/// Calculate the checksum for a given state section.
/// @param[in] section The array to calc the checksum of.
/// @return The calculated checksum value.
uint8_t IRSamsungAc::calcChecksum(const uint8_t state[],
const uint16_t length) {
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1538#issuecomment-894645947
uint8_t IRSamsungAc::calcSectionChecksum(const uint8_t *section) {
uint8_t sum = 0;
// Safety check so we don't go outside the array.
if (length < 7) return 255;
// 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.
sum += countBits(state[length - 7], 8);
sum -= countBits(GETBITS8(state[length - 6], kLowNibble, kNibbleSize), 8);
sum += countBits(GETBITS8(state[length - 5], 1, 7), 8);
sum += countBits(state + length - 4, 3);
return GETBITS8(28 - sum, kLowNibble, kNibbleSize);

sum += countBits(*section, 8); // Include the entire first byte
// The lower half of the second byte.
sum += countBits(GETBITS8(*(section + 1), kLowNibble, kNibbleSize), 8);
// The upper half of the third byte.
sum += countBits(GETBITS8(*(section + 2), kHighNibble, kNibbleSize), 8);
// The next 4 bytes.
sum += countBits(section + 3, 4);
// Bitwise invert the result.
return sum ^ UINT8_MAX;
}

/// Verify the checksum is valid for a given state.
/// @param[in] state The array to verify the checksum of.
/// @param[in] length The length/size of the array.
/// @return true, if the state has a valid checksum. Otherwise, false.
bool IRSamsungAc::validChecksum(const uint8_t state[], const uint16_t length) {
if (length < kSamsungAcStateLength)
return true; // No checksum to compare with. Assume okay.
uint8_t offset = 0;
if (length >= kSamsungAcExtendedStateLength) offset = 7;
return (GETBITS8(state[length - 6], kHighNibble, kNibbleSize) ==
IRSamsungAc::calcChecksum(state, length)) &&
(GETBITS8(state[length - (13 + offset)], kHighNibble, kNibbleSize) ==
IRSamsungAc::calcChecksum(state, length - (7 + offset)));
bool result = true;
const uint16_t maxlength =
(length > kSamsungAcExtendedStateLength) ? kSamsungAcExtendedStateLength
: length;
for (uint16_t offset = 0;
offset + kSamsungAcSectionLength <= maxlength;
offset += kSamsungAcSectionLength)
result &= (getSectionChecksum(state + offset) ==
calcSectionChecksum(state + offset));
return result;
}

/// Update the checksum for the internal state.
/// @param[in] length The length/size of the internal array to checksum.
void IRSamsungAc::checksum(uint16_t length) {
if (length < 13) return;
_.Sum2 = calcChecksum(_.raw, length);
_.Sum1 = calcChecksum(_.raw, length - 7);
void IRSamsungAc::checksum(void) {
uint8_t sectionsum = calcSectionChecksum(_.raw);
_.Sum1Upper = GETBITS8(sectionsum, kHighNibble, kNibbleSize);
_.Sum1Lower = GETBITS8(sectionsum, kLowNibble, kNibbleSize);
sectionsum = calcSectionChecksum(_.raw + kSamsungAcSectionLength);
_.Sum2Upper = GETBITS8(sectionsum, kHighNibble, kNibbleSize);
_.Sum2Lower = GETBITS8(sectionsum, kLowNibble, kNibbleSize);
sectionsum = calcSectionChecksum(_.raw + kSamsungAcSectionLength * 2);
_.Sum3Upper = GETBITS8(sectionsum, kHighNibble, kNibbleSize);
_.Sum3Lower = GETBITS8(sectionsum, kLowNibble, kNibbleSize);
}

#if SEND_SAMSUNG_AC
Expand Down
71 changes: 56 additions & 15 deletions src/ir_Samsung.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/621
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1062
/// @see http://elektrolab.wz.cz/katalog/samsung_protocol.pdf
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1538 (Checksum)

// Supports:
// Brand: Samsung, Model: UA55H6300 TV (SAMSUNG)
Expand Down Expand Up @@ -88,19 +89,59 @@ union SamsungProtocol{
uint8_t :6;
};
struct {
uint8_t :8;
// 1st Section
// Byte 0
uint8_t :8;
// Byte 1
uint8_t :4;
uint8_t Sum1 :4;
uint8_t pad1[6];
uint8_t :4;
uint8_t Sum1Lower :4;
// Byte 2
uint8_t Sum1Upper :4;
uint8_t :4;
// Byte 3
uint8_t :8;
// Byte 4
uint8_t :8;
// Byte 5
uint8_t :8;
// Byte 6
uint8_t :8;
// 2nd Section
// Byte 7
uint8_t :8;
// Byte 8
uint8_t :4;
uint8_t Sum2 :4;
uint8_t :8;
uint8_t :4;
uint8_t Sum2Lower :4;
// Byte 9
uint8_t Sum2Upper :4;
uint8_t :4;
// Byte 10
uint8_t :1;
uint8_t Breeze :3; // WindFree
uint8_t :4;
uint8_t :1;
uint8_t Breeze :3; // WindFree
uint8_t :4;
// Byte 11
uint8_t :8;
// Byte 12
uint8_t :8;
// Byte 13
uint8_t :8;
// 3rd Section
// Byte 14
uint8_t :8;
// Byte 15
uint8_t :4;
uint8_t Sum3Lower :4;
// Byte 16
uint8_t Sum3Upper :4;
uint8_t :4;
// Byte 17
uint8_t :8;
// Byte 18
uint8_t :8;
// Byte 19
uint8_t :8;
// Byte 20
uint8_t :8;
};
};

Expand All @@ -110,8 +151,8 @@ const uint8_t kSamsungAcSwingMove = 0b010;
const uint8_t kSamsungAcSwingStop = 0b111;
const uint8_t kSamsungAcPowerful10On = 0b011;
const uint8_t kSamsungAcBreezeOn = 0b101;
const uint8_t kSamsungAcMinTemp = 16; // C Mask 0b11110000
const uint8_t kSamsungAcMaxTemp = 30; // C Mask 0b11110000
const uint8_t kSamsungAcMinTemp = 16; // C Mask 0b11110000
const uint8_t kSamsungAcMaxTemp = 30; // C Mask 0b11110000
const uint8_t kSamsungAcAutoTemp = 25; // C Mask 0b11110000
const uint8_t kSamsungAcAuto = 0;
const uint8_t kSamsungAcCool = 1;
Expand Down Expand Up @@ -177,10 +218,10 @@ class IRSamsungAc {
uint8_t* getRaw(void);
void setRaw(const uint8_t new_code[],
const uint16_t length = kSamsungAcStateLength);
static uint8_t calcSectionChecksum(const uint8_t *section);
static uint8_t getSectionChecksum(const uint8_t *section);
static bool validChecksum(const uint8_t state[],
const uint16_t length = kSamsungAcStateLength);
static uint8_t calcChecksum(const uint8_t state[],
const uint16_t length = kSamsungAcStateLength);
static uint8_t convertMode(const stdAc::opmode_t mode);
static uint8_t convertFan(const stdAc::fanspeed_t speed);
static stdAc::opmode_t toCommonMode(const uint8_t mode);
Expand All @@ -199,7 +240,7 @@ class IRSamsungAc {
SamsungProtocol _;
bool _forcepower; ///< Hack to know when we need to send a special power mesg
bool _lastsentpowerstate;
void checksum(const uint16_t length = kSamsungAcStateLength);
void checksum(void);
};

#endif // IR_SAMSUNG_H_
Loading

0 comments on commit 0ec49ff

Please sign in to comment.