Skip to content

Commit 0ec49ff

Browse files
authored
SamsungAc: Redo/fix checksum calculations. (#1554)
* 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
1 parent c04c705 commit 0ec49ff

File tree

3 files changed

+314
-215
lines changed

3 files changed

+314
-215
lines changed

src/ir_Samsung.cpp

Lines changed: 44 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/621
88
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1062
99
/// @see http://elektrolab.wz.cz/katalog/samsung_protocol.pdf
10+
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1538 (Checksum)
1011

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

293-
/// Calculate the checksum for a given state.
294-
/// @param[in] state The array to calc the checksum of.
295-
/// @param[in] length The length/size of the array.
294+
/// Get the existing checksum for a given state section.
295+
/// @param[in] section The array to extract the checksum from.
296+
/// @return The existing checksum value.
297+
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1538#issuecomment-894645947
298+
uint8_t IRSamsungAc::getSectionChecksum(const uint8_t *section) {
299+
return ((GETBITS8(*(section + 2), kLowNibble, kNibbleSize) << kNibbleSize) +
300+
GETBITS8(*(section + 1), kHighNibble, kNibbleSize));
301+
}
302+
303+
/// Calculate the checksum for a given state section.
304+
/// @param[in] section The array to calc the checksum of.
296305
/// @return The calculated checksum value.
297-
uint8_t IRSamsungAc::calcChecksum(const uint8_t state[],
298-
const uint16_t length) {
306+
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1538#issuecomment-894645947
307+
uint8_t IRSamsungAc::calcSectionChecksum(const uint8_t *section) {
299308
uint8_t sum = 0;
300-
// Safety check so we don't go outside the array.
301-
if (length < 7) return 255;
302-
// Shamelessly inspired by:
303-
// https://github.com/adafruit/Raw-IR-decoder-for-Arduino/pull/3/files
304-
// Count most of the '1' bits after the checksum location.
305-
sum += countBits(state[length - 7], 8);
306-
sum -= countBits(GETBITS8(state[length - 6], kLowNibble, kNibbleSize), 8);
307-
sum += countBits(GETBITS8(state[length - 5], 1, 7), 8);
308-
sum += countBits(state + length - 4, 3);
309-
return GETBITS8(28 - sum, kLowNibble, kNibbleSize);
309+
310+
sum += countBits(*section, 8); // Include the entire first byte
311+
// The lower half of the second byte.
312+
sum += countBits(GETBITS8(*(section + 1), kLowNibble, kNibbleSize), 8);
313+
// The upper half of the third byte.
314+
sum += countBits(GETBITS8(*(section + 2), kHighNibble, kNibbleSize), 8);
315+
// The next 4 bytes.
316+
sum += countBits(section + 3, 4);
317+
// Bitwise invert the result.
318+
return sum ^ UINT8_MAX;
310319
}
311320

312321
/// Verify the checksum is valid for a given state.
313322
/// @param[in] state The array to verify the checksum of.
314323
/// @param[in] length The length/size of the array.
315324
/// @return true, if the state has a valid checksum. Otherwise, false.
316325
bool IRSamsungAc::validChecksum(const uint8_t state[], const uint16_t length) {
317-
if (length < kSamsungAcStateLength)
318-
return true; // No checksum to compare with. Assume okay.
319-
uint8_t offset = 0;
320-
if (length >= kSamsungAcExtendedStateLength) offset = 7;
321-
return (GETBITS8(state[length - 6], kHighNibble, kNibbleSize) ==
322-
IRSamsungAc::calcChecksum(state, length)) &&
323-
(GETBITS8(state[length - (13 + offset)], kHighNibble, kNibbleSize) ==
324-
IRSamsungAc::calcChecksum(state, length - (7 + offset)));
326+
bool result = true;
327+
const uint16_t maxlength =
328+
(length > kSamsungAcExtendedStateLength) ? kSamsungAcExtendedStateLength
329+
: length;
330+
for (uint16_t offset = 0;
331+
offset + kSamsungAcSectionLength <= maxlength;
332+
offset += kSamsungAcSectionLength)
333+
result &= (getSectionChecksum(state + offset) ==
334+
calcSectionChecksum(state + offset));
335+
return result;
325336
}
326337

327338
/// Update the checksum for the internal state.
328-
/// @param[in] length The length/size of the internal array to checksum.
329-
void IRSamsungAc::checksum(uint16_t length) {
330-
if (length < 13) return;
331-
_.Sum2 = calcChecksum(_.raw, length);
332-
_.Sum1 = calcChecksum(_.raw, length - 7);
339+
void IRSamsungAc::checksum(void) {
340+
uint8_t sectionsum = calcSectionChecksum(_.raw);
341+
_.Sum1Upper = GETBITS8(sectionsum, kHighNibble, kNibbleSize);
342+
_.Sum1Lower = GETBITS8(sectionsum, kLowNibble, kNibbleSize);
343+
sectionsum = calcSectionChecksum(_.raw + kSamsungAcSectionLength);
344+
_.Sum2Upper = GETBITS8(sectionsum, kHighNibble, kNibbleSize);
345+
_.Sum2Lower = GETBITS8(sectionsum, kLowNibble, kNibbleSize);
346+
sectionsum = calcSectionChecksum(_.raw + kSamsungAcSectionLength * 2);
347+
_.Sum3Upper = GETBITS8(sectionsum, kHighNibble, kNibbleSize);
348+
_.Sum3Lower = GETBITS8(sectionsum, kLowNibble, kNibbleSize);
333349
}
334350

335351
#if SEND_SAMSUNG_AC

src/ir_Samsung.h

Lines changed: 56 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/621
77
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1062
88
/// @see http://elektrolab.wz.cz/katalog/samsung_protocol.pdf
9+
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1538 (Checksum)
910

1011
// Supports:
1112
// Brand: Samsung, Model: UA55H6300 TV (SAMSUNG)
@@ -88,19 +89,59 @@ union SamsungProtocol{
8889
uint8_t :6;
8990
};
9091
struct {
91-
uint8_t :8;
92+
// 1st Section
93+
// Byte 0
94+
uint8_t :8;
9295
// Byte 1
93-
uint8_t :4;
94-
uint8_t Sum1 :4;
95-
uint8_t pad1[6];
96+
uint8_t :4;
97+
uint8_t Sum1Lower :4;
98+
// Byte 2
99+
uint8_t Sum1Upper :4;
100+
uint8_t :4;
101+
// Byte 3
102+
uint8_t :8;
103+
// Byte 4
104+
uint8_t :8;
105+
// Byte 5
106+
uint8_t :8;
107+
// Byte 6
108+
uint8_t :8;
109+
// 2nd Section
110+
// Byte 7
111+
uint8_t :8;
96112
// Byte 8
97-
uint8_t :4;
98-
uint8_t Sum2 :4;
99-
uint8_t :8;
113+
uint8_t :4;
114+
uint8_t Sum2Lower :4;
115+
// Byte 9
116+
uint8_t Sum2Upper :4;
117+
uint8_t :4;
100118
// Byte 10
101-
uint8_t :1;
102-
uint8_t Breeze :3; // WindFree
103-
uint8_t :4;
119+
uint8_t :1;
120+
uint8_t Breeze :3; // WindFree
121+
uint8_t :4;
122+
// Byte 11
123+
uint8_t :8;
124+
// Byte 12
125+
uint8_t :8;
126+
// Byte 13
127+
uint8_t :8;
128+
// 3rd Section
129+
// Byte 14
130+
uint8_t :8;
131+
// Byte 15
132+
uint8_t :4;
133+
uint8_t Sum3Lower :4;
134+
// Byte 16
135+
uint8_t Sum3Upper :4;
136+
uint8_t :4;
137+
// Byte 17
138+
uint8_t :8;
139+
// Byte 18
140+
uint8_t :8;
141+
// Byte 19
142+
uint8_t :8;
143+
// Byte 20
144+
uint8_t :8;
104145
};
105146
};
106147

@@ -110,8 +151,8 @@ const uint8_t kSamsungAcSwingMove = 0b010;
110151
const uint8_t kSamsungAcSwingStop = 0b111;
111152
const uint8_t kSamsungAcPowerful10On = 0b011;
112153
const uint8_t kSamsungAcBreezeOn = 0b101;
113-
const uint8_t kSamsungAcMinTemp = 16; // C Mask 0b11110000
114-
const uint8_t kSamsungAcMaxTemp = 30; // C Mask 0b11110000
154+
const uint8_t kSamsungAcMinTemp = 16; // C Mask 0b11110000
155+
const uint8_t kSamsungAcMaxTemp = 30; // C Mask 0b11110000
115156
const uint8_t kSamsungAcAutoTemp = 25; // C Mask 0b11110000
116157
const uint8_t kSamsungAcAuto = 0;
117158
const uint8_t kSamsungAcCool = 1;
@@ -177,10 +218,10 @@ class IRSamsungAc {
177218
uint8_t* getRaw(void);
178219
void setRaw(const uint8_t new_code[],
179220
const uint16_t length = kSamsungAcStateLength);
221+
static uint8_t calcSectionChecksum(const uint8_t *section);
222+
static uint8_t getSectionChecksum(const uint8_t *section);
180223
static bool validChecksum(const uint8_t state[],
181224
const uint16_t length = kSamsungAcStateLength);
182-
static uint8_t calcChecksum(const uint8_t state[],
183-
const uint16_t length = kSamsungAcStateLength);
184225
static uint8_t convertMode(const stdAc::opmode_t mode);
185226
static uint8_t convertFan(const stdAc::fanspeed_t speed);
186227
static stdAc::opmode_t toCommonMode(const uint8_t mode);
@@ -199,7 +240,7 @@ class IRSamsungAc {
199240
SamsungProtocol _;
200241
bool _forcepower; ///< Hack to know when we need to send a special power mesg
201242
bool _lastsentpowerstate;
202-
void checksum(const uint16_t length = kSamsungAcStateLength);
243+
void checksum(void);
203244
};
204245

205246
#endif // IR_SAMSUNG_H_

0 commit comments

Comments
 (0)