350 lines
10 KiB
C++
350 lines
10 KiB
C++
// Copyright 2017 David Conran
|
|
|
|
#include "ir_Toshiba.h"
|
|
#include <algorithm>
|
|
#ifndef ARDUINO
|
|
#include <string>
|
|
#endif
|
|
#include "IRrecv.h"
|
|
#include "IRsend.h"
|
|
#include "IRutils.h"
|
|
|
|
// TTTTTTT OOOOO SSSSS HH HH IIIII BBBBB AAA
|
|
// TTT OO OO SS HH HH III BB B AAAAA
|
|
// TTT OO OO SSSSS HHHHHHH III BBBBBB AA AA
|
|
// TTT OO OO SS HH HH III BB BB AAAAAAA
|
|
// TTT OOOO0 SSSSS HH HH IIIII BBBBBB AA AA
|
|
|
|
// Toshiba A/C support added by David Conran
|
|
//
|
|
// Equipment it seems compatible with:
|
|
// * Toshiba RAS-B13N3KV2 / Akita EVO II
|
|
// * Toshiba RAS-B13N3KVP-E, RAS 18SKP-ES
|
|
// * Toshiba WH-TA04NE, WC-L03SE
|
|
// * <Add models (A/C & remotes) you've gotten it working with here>
|
|
|
|
// Constants
|
|
|
|
// Toshiba A/C
|
|
// Ref:
|
|
// https://github.com/r45635/HVAC-IR-Control/blob/master/HVAC_ESP8266/HVAC_ESP8266T.ino#L77
|
|
#define TOSHIBA_AC_HDR_MARK 4400U
|
|
#define TOSHIBA_AC_HDR_SPACE 4300U
|
|
#define TOSHIBA_AC_BIT_MARK 543U
|
|
#define TOSHIBA_AC_ONE_SPACE 1623U
|
|
#define TOSHIBA_AC_ZERO_SPACE 472U
|
|
#define TOSHIBA_AC_MIN_GAP 7048U
|
|
|
|
#if SEND_TOSHIBA_AC
|
|
// Send a Toshiba A/C message.
|
|
//
|
|
// Args:
|
|
// data: An array of bytes containing the IR command.
|
|
// nbytes: Nr. of bytes of data in the array. (>=TOSHIBA_AC_STATE_LENGTH)
|
|
// repeat: Nr. of times the message is to be repeated.
|
|
// (Default = TOSHIBA_AC_MIN_REPEAT).
|
|
//
|
|
// Status: StABLE / Working.
|
|
//
|
|
void IRsend::sendToshibaAC(unsigned char data[], uint16_t nbytes,
|
|
uint16_t repeat) {
|
|
if (nbytes < TOSHIBA_AC_STATE_LENGTH)
|
|
return; // Not enough bytes to send a proper message.
|
|
sendGeneric(TOSHIBA_AC_HDR_MARK, TOSHIBA_AC_HDR_SPACE,
|
|
TOSHIBA_AC_BIT_MARK, TOSHIBA_AC_ONE_SPACE,
|
|
TOSHIBA_AC_BIT_MARK, TOSHIBA_AC_ZERO_SPACE,
|
|
TOSHIBA_AC_BIT_MARK, TOSHIBA_AC_MIN_GAP,
|
|
data, nbytes, 38, true, repeat, 50);
|
|
}
|
|
#endif // SEND_TOSHIBA_AC
|
|
|
|
// Code to emulate Toshiba A/C IR remote control unit.
|
|
// Inspired and derived from the work done at:
|
|
// https://github.com/r45635/HVAC-IR-Control
|
|
//
|
|
// Status: STABLE / Working.
|
|
//
|
|
// Initialise the object.
|
|
IRToshibaAC::IRToshibaAC(uint16_t pin) : _irsend(pin) {
|
|
stateReset();
|
|
}
|
|
|
|
// Reset the state of the remote to a known good state/sequence.
|
|
void IRToshibaAC::stateReset() {
|
|
// The state of the IR remote in IR code form.
|
|
// Known good state obtained from:
|
|
// https://github.com/r45635/HVAC-IR-Control/blob/master/HVAC_ESP8266/HVAC_ESP8266T.ino#L103
|
|
// Note: Can't use the following because it requires -std=c++11
|
|
// uint8_t remote_state[TOSHIBA_AC_STATE_LENGTH] = {
|
|
// 0xF2, 0x0D, 0x03, 0xFC, 0x01, 0x00, 0x00, 0x00, 0x00 };
|
|
remote_state[0] = 0xF2;
|
|
remote_state[1] = 0x0D;
|
|
remote_state[2] = 0x03;
|
|
remote_state[3] = 0xFC;
|
|
remote_state[4] = 0x01;
|
|
for (uint8_t i = 5; i < TOSHIBA_AC_STATE_LENGTH; i++)
|
|
remote_state[i] = 0;
|
|
mode_state = remote_state[6] & 0b00000011;
|
|
checksum(); // Calculate the checksum
|
|
}
|
|
|
|
// Configure the pin for output.
|
|
void IRToshibaAC::begin() {
|
|
_irsend.begin();
|
|
}
|
|
|
|
#if SEND_TOSHIBA_AC
|
|
// Send the current desired state to the IR LED.
|
|
void IRToshibaAC::send() {
|
|
checksum(); // Ensure correct checksum before sending.
|
|
_irsend.sendToshibaAC(remote_state);
|
|
}
|
|
#endif // SEND_TOSHIBA_AC
|
|
|
|
// Return a pointer to the internal state date of the remote.
|
|
uint8_t* IRToshibaAC::getRaw() {
|
|
checksum();
|
|
return remote_state;
|
|
}
|
|
|
|
// Override the internal state with the new state.
|
|
void IRToshibaAC::setRaw(uint8_t newState[]) {
|
|
for (uint8_t i = 0; i < TOSHIBA_AC_STATE_LENGTH; i++) {
|
|
remote_state[i] = newState[i];
|
|
}
|
|
mode_state = getMode(true);
|
|
}
|
|
|
|
// Calculate the checksum for a given array.
|
|
// Args:
|
|
// state: The array to calculate the checksum over.
|
|
// length: The size of the array.
|
|
// Returns:
|
|
// The 8 bit checksum value.
|
|
uint8_t IRToshibaAC::calcChecksum(const uint8_t state[],
|
|
const uint16_t length) {
|
|
uint8_t checksum = 0;
|
|
// Only calculate it for valid lengths.
|
|
if (length > 1) {
|
|
// Checksum is simple XOR of all bytes except the last one.
|
|
for (uint8_t i = 0; i < length - 1; i++)
|
|
checksum ^= state[i];
|
|
}
|
|
return checksum;
|
|
}
|
|
|
|
// Verify the checksum is valid for a given state.
|
|
// Args:
|
|
// state: The array to verify the checksum of.
|
|
// length: The size of the state.
|
|
// Returns:
|
|
// A boolean.
|
|
bool IRToshibaAC::validChecksum(const uint8_t state[],
|
|
const uint16_t length) {
|
|
return (length > 1 && state[length - 1] == calcChecksum(state, length));
|
|
}
|
|
|
|
// Calculate & set the checksum for the current internal state of the remote.
|
|
void IRToshibaAC::checksum(const uint16_t length) {
|
|
// Stored the checksum value in the last byte.
|
|
if (length > 1)
|
|
remote_state[length - 1] = calcChecksum(remote_state, length);
|
|
}
|
|
|
|
// Set the requested power state of the A/C to off.
|
|
void IRToshibaAC::on() {
|
|
// state = ON;
|
|
remote_state[6] &= ~TOSHIBA_AC_POWER;
|
|
setMode(mode_state);
|
|
}
|
|
|
|
// Set the requested power state of the A/C to off.
|
|
void IRToshibaAC::off() {
|
|
// state = OFF;
|
|
remote_state[6] |= (TOSHIBA_AC_POWER | 0b00000011);
|
|
}
|
|
|
|
// Set the requested power state of the A/C.
|
|
void IRToshibaAC::setPower(bool state) {
|
|
if (state)
|
|
on();
|
|
else
|
|
off();
|
|
}
|
|
|
|
// Return the requested power state of the A/C.
|
|
bool IRToshibaAC::getPower() {
|
|
return((remote_state[6] & TOSHIBA_AC_POWER) == 0);
|
|
}
|
|
|
|
// Set the temp. in deg C
|
|
void IRToshibaAC::setTemp(uint8_t temp) {
|
|
temp = std::max((uint8_t) TOSHIBA_AC_MIN_TEMP, temp);
|
|
temp = std::min((uint8_t) TOSHIBA_AC_MAX_TEMP, temp);
|
|
remote_state[5] = (temp - TOSHIBA_AC_MIN_TEMP) << 4;
|
|
}
|
|
|
|
// Return the set temp. in deg C
|
|
uint8_t IRToshibaAC::getTemp() {
|
|
return((remote_state[5] >> 4) + TOSHIBA_AC_MIN_TEMP);
|
|
}
|
|
|
|
// Set the speed of the fan, 0-5.
|
|
// 0 is auto, 1-5 is the speed, 5 is Max.
|
|
void IRToshibaAC::setFan(uint8_t fan) {
|
|
// Bounds check
|
|
if (fan > TOSHIBA_AC_FAN_MAX)
|
|
fan = TOSHIBA_AC_FAN_MAX; // Set the fan to maximum if out of range.
|
|
if (fan > TOSHIBA_AC_FAN_AUTO) fan++;
|
|
remote_state[6] &= 0b00011111; // Clear the previous fan state
|
|
remote_state[6] |= (fan << 5);
|
|
}
|
|
|
|
// Return the requested state of the unit's fan.
|
|
uint8_t IRToshibaAC::getFan() {
|
|
uint8_t fan = remote_state[6] >> 5;
|
|
if (fan == TOSHIBA_AC_FAN_AUTO) return TOSHIBA_AC_FAN_AUTO;
|
|
return --fan;
|
|
}
|
|
|
|
// Get the requested climate operation mode of the a/c unit.
|
|
// Args:
|
|
// useRaw: Indicate to get the mode from the state array. (Default: false)
|
|
// Returns:
|
|
// A uint8_t containing the A/C mode.
|
|
uint8_t IRToshibaAC::getMode(bool useRaw) {
|
|
if (useRaw)
|
|
return (remote_state[6] & 0b00000011);
|
|
else
|
|
return mode_state;
|
|
}
|
|
|
|
// Set the requested climate operation mode of the a/c unit.
|
|
void IRToshibaAC::setMode(uint8_t mode) {
|
|
// If we get an unexpected mode, default to AUTO.
|
|
switch (mode) {
|
|
case TOSHIBA_AC_AUTO: break;
|
|
case TOSHIBA_AC_COOL: break;
|
|
case TOSHIBA_AC_DRY: break;
|
|
case TOSHIBA_AC_HEAT: break;
|
|
default: mode = TOSHIBA_AC_AUTO;
|
|
}
|
|
mode_state = mode;
|
|
// Only adjust the remote_state if we have power set to on.
|
|
if (getPower()) {
|
|
remote_state[6] &= 0b11111100; // Clear the previous mode.
|
|
remote_state[6] |= mode_state;
|
|
}
|
|
}
|
|
|
|
// Convert the internal state into a human readable string.
|
|
#ifdef ARDUINO
|
|
String IRToshibaAC::toString() {
|
|
String result = "";
|
|
#else
|
|
std::string IRToshibaAC::toString() {
|
|
std::string result = "";
|
|
#endif // ARDUINO
|
|
result += "Power: ";
|
|
if (getPower())
|
|
result += "On";
|
|
else
|
|
result += "Off";
|
|
result += ", Mode: " + uint64ToString(getMode());
|
|
switch (getMode()) {
|
|
case TOSHIBA_AC_AUTO:
|
|
result += " (AUTO)";
|
|
break;
|
|
case TOSHIBA_AC_COOL:
|
|
result += " (COOL)";
|
|
break;
|
|
case TOSHIBA_AC_HEAT:
|
|
result += " (HEAT)";
|
|
break;
|
|
case TOSHIBA_AC_DRY:
|
|
result += " (DRY)";
|
|
break;
|
|
default:
|
|
result += " (UNKNOWN)";
|
|
}
|
|
result += ", Temp: " + uint64ToString(getTemp()) + "C";
|
|
result += ", Fan: " + uint64ToString(getFan());
|
|
switch (getFan()) {
|
|
case TOSHIBA_AC_FAN_AUTO:
|
|
result += " (AUTO)";
|
|
break;
|
|
case TOSHIBA_AC_FAN_MAX:
|
|
result += " (MAX)";
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#if DECODE_TOSHIBA_AC
|
|
// Decode a Toshiba AC IR message if possible.
|
|
// Places successful decode information in the results pointer.
|
|
// Args:
|
|
// results: Ptr to the data to decode and where to store the decode result.
|
|
// nbits: The number of data bits to expect. Typically TOSHIBA_AC_BITS.
|
|
// strict: Flag to indicate if we strictly adhere to the specification.
|
|
// Returns:
|
|
// boolean: True if it can decode it, false if it can't.
|
|
//
|
|
// Status: STABLE / Working.
|
|
//
|
|
// Ref:
|
|
//
|
|
bool IRrecv::decodeToshibaAC(decode_results *results, uint16_t nbits,
|
|
bool strict) {
|
|
uint16_t offset = OFFSET_START;
|
|
uint16_t dataBitsSoFar = 0;
|
|
|
|
// Have we got enough data to successfully decode?
|
|
if (results->rawlen < TOSHIBA_AC_BITS + HEADER + FOOTER - 1)
|
|
return false; // Can't possibly be a valid message.
|
|
|
|
|
|
// Compliance
|
|
if (strict && nbits != TOSHIBA_AC_BITS)
|
|
return false; // Must be called with the correct nr. of bytes.
|
|
|
|
// Header
|
|
if (!matchMark(results->rawbuf[offset++], TOSHIBA_AC_HDR_MARK))
|
|
return false;
|
|
if (!matchSpace(results->rawbuf[offset++], TOSHIBA_AC_HDR_SPACE))
|
|
return false;
|
|
|
|
// Data
|
|
for (uint8_t i = 0; i < TOSHIBA_AC_STATE_LENGTH; i++) {
|
|
// Read a byte's worth of data.
|
|
match_result_t data_result = matchData(&(results->rawbuf[offset]), 8,
|
|
TOSHIBA_AC_BIT_MARK,
|
|
TOSHIBA_AC_ONE_SPACE,
|
|
TOSHIBA_AC_BIT_MARK,
|
|
TOSHIBA_AC_ZERO_SPACE);
|
|
if (data_result.success == false) return false; // Fail
|
|
dataBitsSoFar += 8;
|
|
results->state[i] = (uint8_t) data_result.data;
|
|
offset += data_result.used;
|
|
}
|
|
|
|
// Footer
|
|
if (!matchMark(results->rawbuf[offset++], TOSHIBA_AC_BIT_MARK)) return false;
|
|
if (!matchSpace(results->rawbuf[offset++], TOSHIBA_AC_MIN_GAP)) return false;
|
|
|
|
// Compliance
|
|
if (strict) {
|
|
// Check that the checksum of the message is correct.
|
|
if (!IRToshibaAC::validChecksum(results->state)) return false;
|
|
}
|
|
|
|
// Success
|
|
results->decode_type = TOSHIBA_AC;
|
|
results->bits = dataBitsSoFar;
|
|
// No need to record the state as we stored it as we decoded it.
|
|
// As we use result->state, we don't record value, address, or command as it
|
|
// is a union data type.
|
|
return true;
|
|
}
|
|
#endif // DECODE_TOSHIBA_AC
|