Skip to content

Commit

Permalink
Voltas: Progress check-in
Browse files Browse the repository at this point in the history
* Checksum support.
* Power, toString, toCommon, set/GetRaw etc.

For #1238
  • Loading branch information
crankyoldgit committed Aug 16, 2020
1 parent d64f07b commit 6c7b52c
Show file tree
Hide file tree
Showing 4 changed files with 328 additions and 3 deletions.
16 changes: 16 additions & 0 deletions src/IRac.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
#include "ir_Toshiba.h"
#include "ir_Trotec.h"
#include "ir_Vestel.h"
#include "ir_Voltas.h"
#include "ir_Whirlpool.h"

/// Class constructor
Expand Down Expand Up @@ -3112,6 +3113,13 @@ namespace IRAcUtils {
return ac.toString();
}
#endif // DECODE_VESTEL_AC
#if DECODE_VOLTAS
case decode_type_t::VOLTAS: {
IRVoltas ac(kGpioUnused);
ac.setRaw(result->state);
return ac.toString();
}
#endif // DECODE_VOLTAS
#if DECODE_TECO
case decode_type_t::TECO: {
IRTecoAc ac(kGpioUnused);
Expand Down Expand Up @@ -3513,6 +3521,14 @@ namespace IRAcUtils {
break;
}
#endif // DECODE_VESTEL_AC
#if DECODE_VOLTAS
case decode_type_t::VOLTAS: {
IRVestelAc ac(kGpioUnused);
ac.setRaw(decode->state);
*result = ac.toCommon();
break;
}
#endif // DECODE_VOLTAS
#if DECODE_WHIRLPOOL_AC
case decode_type_t::WHIRLPOOL_AC: {
IRWhirlpoolAc ac(kGpioUnused);
Expand Down
143 changes: 140 additions & 3 deletions src/ir_Voltas.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,19 @@
/// @brief Support for Voltas A/C protocol
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1238

// Supports:
// Brand: Voltas, Model: 122LZF 4011252 Window A/C

#include "ir_Voltas.h"
#include <cstring>
#include "IRrecv.h"
#include "IRsend.h"
#include "IRtext.h"
#include "IRutils.h"

using irutils::addBoolToString;
using irutils::addModeToString;
using irutils::addFanToString;
using irutils::addTempToString;
using irutils::minsToString;

// Constants
const uint16_t kVoltasBitMark = 1026; ///< uSeconds.
const uint16_t kVoltasOneSpace = 2553; ///< uSeconds.
Expand Down Expand Up @@ -61,9 +67,140 @@ bool IRrecv::decodeVoltas(decode_results *results, uint16_t offset,
kVoltasBitMark, kVoltasZeroSpace,
kVoltasBitMark, kDefaultMessageGap, true)) return false;

// Compliance
if (strict && !IRVoltas::validChecksum(results->state, nbits / 8))
return false;
// Success
results->decode_type = decode_type_t::VOLTAS;
results->bits = nbits;
return true;
}
#endif // DECODE_VOLTAS

/// Class constructor
/// @param[in] pin GPIO to be used when sending.
/// @param[in] inverted Is the output signal to be inverted?
/// @param[in] use_modulation Is frequency modulation to be used?
IRVoltas::IRVoltas(const uint16_t pin, const bool inverted,
const bool use_modulation)
: _irsend(pin, inverted, use_modulation) {
stateReset();
}

// Reset the internal state to a fixed known good state.
void IRVoltas::stateReset() {
// This resets to a known-good state.
std::memset(_.raw, 0, sizeof _.raw);
}

/// Set up hardware to be able to send a message.
void IRVoltas::begin() { _irsend.begin(); }

#if SEND_VOLTAS
/// Send the current internal state as an IR message.
/// @param[in] repeat Nr. of times the message will be repeated.
void IRVoltas::send(const uint16_t repeat) {
_irsend.sendVoltas(getRaw(), kVoltasStateLength, repeat);
}
#endif // SEND_VOLTAS

/// Get a PTR to the internal state/code for this protocol.
/// @return PTR to a code for this protocol based on the current internal state.
uint8_t* IRVoltas::getRaw(void) {
checksum(); // Ensure correct settings before sending.
return _.raw;
}

/// Set the internal state from a valid code for this protocol.
/// @param[in] new_code A valid code for this protocol.
void IRVoltas::setRaw(const uint8_t new_code[]) {
std::memcpy(_.raw, new_code, kVoltasStateLength);
}

/// Calculate and set the checksum values for the internal state.
void IRVoltas::checksum(void) {
_.Checksum = calcChecksum(_.raw, kVoltasStateLength - 1);
}

/// Verify the checksum is valid for a given state.
/// @param[in] state The array to verify the checksum of.
/// @param[in] length The length of the state array.
/// @return true, if the state has a valid checksum. Otherwise, false.
bool IRVoltas::validChecksum(const uint8_t state[], const uint16_t length) {
if (length) return state[length - 1] == calcChecksum(state, length);
return true;
}

/// Calculate the checksum is valid for a given state.
/// @param[in] state The array to calculate the checksum of.
/// @param[in] length The length of the state array.
/// @return The valid checksum value for the state.
uint8_t IRVoltas::calcChecksum(const uint8_t state[], const uint16_t length) {
uint8_t result = 0;
if (length)
result = sumBytes(state, length - 1);
return ~result;
}

/// Change the power setting to On.
void IRVoltas::on() { setPower(true); }

/// Change the power setting to Off.
void IRVoltas::off() { setPower(false); }

/// Change the power setting.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRVoltas::setPower(const bool on) { _.Power = on; }

/// Get the value of the current power setting.
/// @return true, the setting is on. false, the setting is off.
bool IRVoltas::getPower(void) const { return _.Power; }

/// Convert the current internal state into its stdAc::state_t equivilant.
/// @return The stdAc equivilant of the native settings.
stdAc::state_t IRVoltas::toCommon() {
stdAc::state_t result;
result.protocol = decode_type_t::VOLTAS;
result.power = _.Power;
// result.mode = toCommonMode(getMode());
result.celsius = true;
/*
result.degrees = getTemp();
result.fanspeed = toCommonFanSpeed(getFan());
if (getSwingVerticalAuto())
result.swingv = stdAc::swingv_t::kAuto;
else
result.swingv = toCommonSwingV(getSwingVerticalPosition());
*/
result.turbo = _.Turbo;
result.econo = _.Econo;
result.light = _.Light;
/*
result.clean = getXFan();
result.sleep = getSleep() ? 0 : -1;
*/
// Not supported.
result.model = -1;
result.swingh = stdAc::swingh_t::kOff;
result.quiet = false;
result.filter = false;
result.beep = false;
result.clock = -1;
return result;
}

/// Convert the current internal state into a human readable string.
/// @return A human readable string.
String IRVoltas::toString() {
String result = "";
result.reserve(80); // Reserve some heap for the string to reduce fragging.
result += addBoolToString(_.Power, kPowerStr, false);
/*
result += addModeToString(getMode(), kVoltasAuto, kVoltasCool, kVoltasHeat,
kVoltasDry, kVoltasFan);
*/
result += addBoolToString(_.Turbo, kTurboStr);
result += addBoolToString(_.Wifi, kWifiStr);
result += addBoolToString(_.Light, kLightStr);
return result;
}
128 changes: 128 additions & 0 deletions src/ir_Voltas.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// Copyright 2020 David Conran (crankyoldgit)
// Copyright 2020 manj9501
/// @file
/// @brief Support for Voltas A/C protocol
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1238

// Supports:
// Brand: Voltas, Model: 122LZF 4011252 Window A/C
//
// Ref: https://docs.google.com/spreadsheets/d/1zzDEUQ52y7MZ7_xCU3pdjdqbRXOwZLsbTGvKWcicqCI/
// Ref: https://www.corona.co.jp/box/download.php?id=145060636229

#ifndef IR_VOLTAS_H_
#define IR_VOLTAS_H_

#define __STDC_LIMIT_MACROS
#include <stdint.h>
#ifndef UNIT_TEST
#include <Arduino.h>
#endif
#include "IRremoteESP8266.h"
#include "IRsend.h"
#ifdef UNIT_TEST
#include "IRsend_test.h"
#endif


union VoltasProtocol {
uint8_t raw[kVoltasStateLength]; ///< The state in native IR code form
struct {
// Byte 0
uint8_t SwingH :1;
uint8_t Unknown0 :7;
// Byte 1
uint8_t Mode :4;
uint8_t :1;
uint8_t FanSpeed :3;
// Byte 2
uint8_t SwingV :3;
uint8_t Wifi :1;
uint8_t :1;
uint8_t Turbo :1;
uint8_t Sleep :1;
uint8_t Power :1;
// Byte 3
uint8_t Temp :4;
uint8_t Unknown3 :2; // Typically 0b01
uint8_t Econo :1;
uint8_t TempSet :1;
// Byte 4
uint8_t OffTimer24h4 :1;
uint8_t :7; // Typically 0b0011101
// Byte 5
uint8_t OffTimer24h5 :1;
uint8_t :6; // Typically 0b011101
uint8_t TimerAdd12Hr :1;
// Byte 6
uint8_t :8; // Typically 0b00111011(0x3B)
// Byte 7
uint8_t :4; // Typically 0b0001
uint8_t TimerHrs :4; // Nr of Hours.
// Byte 8
uint8_t :5; // Typically 0b00000
uint8_t Light :1;
uint8_t OffTimerEnable :1;
uint8_t :1; // Typically 0b0
// Byte 9
uint8_t Checksum :8;
};
};

// Constants

// Classes
/// Class for handling detailed Voltas A/C messages.
class IRVoltas {
public:
explicit IRVoltas(const uint16_t pin, const bool inverted = false,
const bool use_modulation = true);
void stateReset();
#if SEND_VOLTAS
void send(const uint16_t repeat = kNoRepeat);
/// Run the calibration to calculate uSec timing offsets for this platform.
/// @return The uSec timing offset needed per modulation of the IR Led.
/// @note This will produce a 65ms IR signal pulse at 38kHz.
/// Only ever needs to be run once per object instantiation, if at all.
int8_t calibrate(void) { return _irsend.calibrate(); }
#endif // SEND_VOLTAS
void begin();
static bool validChecksum(const uint8_t state[],
const uint16_t length = kVoltasStateLength);
void setPower(const bool on);
bool getPower(void) const;
void on(void);
void off(void);
void setTemp(const uint8_t temp);
uint8_t getTemp(void);
void setFan(const uint8_t speed);
uint8_t getFan(void);
void setMode(const uint8_t mode);
uint8_t getMode(void);
void setEcono(const bool on);
bool getEcono(void);
void setOffTimer(const uint16_t nr_of_mins);
uint16_t getOffTimer(void);
uint8_t* getRaw(void);
void setRaw(const uint8_t new_code[]);
uint8_t convertMode(const stdAc::opmode_t mode);
uint8_t convertFan(const stdAc::fanspeed_t speed);
static stdAc::opmode_t toCommonMode(const uint8_t mode);
static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed);
stdAc::state_t toCommon(void);
String toString(void);
#ifndef UNIT_TEST

private:
IRsend _irsend; ///< Instance of the IR send class
#else
/// @cond IGNORE
IRsendTest _irsend; ///< Instance of the testing IR send class
/// @endcond
#endif
VoltasProtocol _; ///< The state of the IR remote.
void checksum(void);
static uint8_t calcChecksum(const uint8_t state[],
const uint16_t length = kVoltasStateLength);
};
#endif // IR_VOLTAS_H_
44 changes: 44 additions & 0 deletions test/ir_Voltas_test.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Copyright 2020 crankyoldgit

#include "ir_Voltas.h"
#include "IRac.h"
#include "IRrecv.h"
#include "IRrecv_test.h"
Expand Down Expand Up @@ -38,6 +39,11 @@ TEST(TestDecodeVoltas, RealExample) {
ASSERT_EQ(decode_type_t::VOLTAS, irsend.capture.decode_type);
ASSERT_EQ(kVoltasBits, irsend.capture.bits);
EXPECT_STATE_EQ(expected, irsend.capture.state, irsend.capture.bits);
EXPECT_EQ(
"Power: On, Turbo: Off, WiFi: On, Light: Off",
IRAcUtils::resultAcToString(&irsend.capture));
stdAc::state_t r, p;
ASSERT_TRUE(IRAcUtils::decodeToState(&irsend.capture, &r, &p));
}

TEST(TestDecodeVoltas, SyntheticExample) {
Expand Down Expand Up @@ -65,3 +71,41 @@ TEST(TestUtils, Housekeeping) {
ASSERT_EQ(kVoltasBits, IRsend::defaultBits(decode_type_t::VOLTAS));
ASSERT_EQ(kNoRepeat, IRsend::minRepeats(decode_type_t::VOLTAS));
}

TEST(TestIRVoltasClass, Checksums) {
const uint8_t valid[kVoltasStateLength] = {
0x33, 0x84, 0x88, 0x18, 0x3B, 0x3B, 0x3B, 0x11, 0x00, 0xE6};
EXPECT_TRUE(IRVoltas::validChecksum(valid));
EXPECT_FALSE(IRVoltas::validChecksum(valid, kVoltasStateLength - 1));
EXPECT_EQ(0xE6, IRVoltas::calcChecksum(valid));
}

TEST(TestIRVoltasClass, SetandGetRaw) {
const uint8_t valid[kVoltasStateLength] = {
0x33, 0x84, 0x88, 0x18, 0x3B, 0x3B, 0x3B, 0x11, 0x00, 0xE6};
const uint8_t badchecksum[kVoltasStateLength] = {
0x33, 0x84, 0x88, 0x18, 0x3B, 0x3B, 0x3B, 0x11, 0x00, 0xE6};
IRVoltas ac(kGpioUnused);

ac.setRaw(valid);
EXPECT_STATE_EQ(valid, ac.getRaw(), kVoltasBits);
ac.setRaw(badchecksum);
EXPECT_STATE_EQ(valid, ac.getRaw(), kVoltasBits);
}

TEST(TestIRVoltasClass, Power) {
IRVoltas ac(kGpioUnused);
ac.begin();

ac.on();
EXPECT_TRUE(ac.getPower());

ac.off();
EXPECT_FALSE(ac.getPower());

ac.setPower(true);
EXPECT_TRUE(ac.getPower());

ac.setPower(false);
EXPECT_FALSE(ac.getPower());
}

0 comments on commit 6c7b52c

Please sign in to comment.