v0.9.337b

This commit is contained in:
e2002
2024-11-27 12:43:44 +03:00
parent 778dd1e847
commit 4f1582b7d6
193 changed files with 68179 additions and 593 deletions

View File

@@ -230,6 +230,11 @@ Work is in progress...
---
## Version history
#### v0.9.337b
- added support for Arduino ESP32 v3.0.0 and later
- disabled SD indexing on startup; now the card is indexed only if the `data/index.dat` file is missing from the card
- IRremoteESP8266 library integrated into the project (`yoRadio/src/IRremoteESP8266`)
#### v0.9.313b
- added support for ESP32-S3 boards (ESP32 S3 Dev Module) (esp32 cores version 3.x.x is not supported yet)
- fixes in displaying sliders in the web interface

View File

@@ -19,6 +19,9 @@
*/
#include "Arduino.h"
#include "AsyncEventSource.h"
#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0)
#include "rom/ets_sys.h"
#endif
static String generateEventMessage(const char *message, const char *event, uint32_t id, uint32_t reconnect){
String ev = "";

View File

@@ -28,6 +28,10 @@
#else
#include <Hash.h>
#endif
//https://github.com/me-no-dev/ESPAsyncWebServer/issues/1410
#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0)
#include "mbedtls/compat-2.x.h"
#endif
#define MAX_PRINTF_LEN 64

View File

@@ -22,6 +22,9 @@
#include <libb64/cencode.h>
#ifdef ESP32
#include "mbedtls/md5.h"
#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0)
#include "mbedtls/compat-2.x.h"
#endif
#else
#include "md5.h"
#endif

View File

@@ -0,0 +1 @@
filter=-build/include,+build/include_alpha,+build/include_order,+build/include_what_you_use

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,581 @@
#ifndef IRAC_H_
#define IRAC_H_
// Copyright 2019 David Conran
#ifndef UNIT_TEST
#include <Arduino.h>
#else
#include <memory>
#endif
#include "IRremoteESP8266.h"
#include "ir_Airton.h"
#include "ir_Airwell.h"
#include "ir_Amcor.h"
#include "ir_Argo.h"
#include "ir_Bosch.h"
#include "ir_Carrier.h"
#include "ir_Coolix.h"
#include "ir_Corona.h"
#include "ir_Daikin.h"
#include "ir_Delonghi.h"
#include "ir_Fujitsu.h"
#include "ir_Ecoclim.h"
#include "ir_Electra.h"
#include "ir_Goodweather.h"
#include "ir_Gree.h"
#include "ir_Haier.h"
#include "ir_Hitachi.h"
#include "ir_Kelon.h"
#include "ir_Kelvinator.h"
#include "ir_LG.h"
#include "ir_Midea.h"
#include "ir_Mirage.h"
#include "ir_Mitsubishi.h"
#include "ir_MitsubishiHeavy.h"
#include "ir_Neoclima.h"
#include "ir_Panasonic.h"
#include "ir_Rhoss.h"
#include "ir_Samsung.h"
#include "ir_Sanyo.h"
#include "ir_Sharp.h"
#include "ir_Tcl.h"
#include "ir_Technibel.h"
#include "ir_Teco.h"
#include "ir_Toshiba.h"
#include "ir_Transcold.h"
#include "ir_Trotec.h"
#include "ir_Truma.h"
#include "ir_Vestel.h"
#include "ir_Voltas.h"
#include "ir_Whirlpool.h"
#include "ir_York.h"
// Constants
const int8_t kGpioUnused = -1; ///< A placeholder for not using an actual GPIO.
// Class
/// A universal/common/generic interface for controling supported A/Cs.
class IRac {
public:
explicit IRac(const uint16_t pin, const bool inverted = false,
const bool use_modulation = true);
static bool isProtocolSupported(const decode_type_t protocol);
static void initState(stdAc::state_t *state,
const decode_type_t vendor, const int16_t model,
const bool power, const stdAc::opmode_t mode,
const float degrees, const bool celsius,
const stdAc::fanspeed_t fan,
const stdAc::swingv_t swingv,
const stdAc::swingh_t swingh,
const bool quiet, const bool turbo, const bool econo,
const bool light, const bool filter, const bool clean,
const bool beep, const int16_t sleep,
const int16_t clock);
static void initState(stdAc::state_t *state);
void markAsSent(void);
bool sendAc(void);
bool sendAc(const stdAc::state_t desired, const stdAc::state_t *prev = NULL);
bool sendAc(const decode_type_t vendor, const int16_t model,
const bool power, const stdAc::opmode_t mode, const float degrees,
const bool celsius, const stdAc::fanspeed_t fan,
const stdAc::swingv_t swingv, const stdAc::swingh_t swingh,
const bool quiet, const bool turbo, const bool econo,
const bool light, const bool filter, const bool clean,
const bool beep, const int16_t sleep = -1,
const int16_t clock = -1);
static bool cmpStates(const stdAc::state_t a, const stdAc::state_t b);
static bool strToBool(const char *str, const bool def = false);
static int16_t strToModel(const char *str, const int16_t def = -1);
static stdAc::ac_command_t strToCommandType(const char *str,
const stdAc::ac_command_t def = stdAc::ac_command_t::kControlCommand);
static stdAc::opmode_t strToOpmode(
const char *str, const stdAc::opmode_t def = stdAc::opmode_t::kAuto);
static stdAc::fanspeed_t strToFanspeed(
const char *str,
const stdAc::fanspeed_t def = stdAc::fanspeed_t::kAuto);
static stdAc::swingv_t strToSwingV(
const char *str, const stdAc::swingv_t def = stdAc::swingv_t::kOff);
static stdAc::swingh_t strToSwingH(
const char *str, const stdAc::swingh_t def = stdAc::swingh_t::kOff);
static String boolToString(const bool value);
static String commandTypeToString(const stdAc::ac_command_t cmdType);
static String opmodeToString(const stdAc::opmode_t mode,
const bool ha = false);
static String fanspeedToString(const stdAc::fanspeed_t speed);
static String swingvToString(const stdAc::swingv_t swingv);
static String swinghToString(const stdAc::swingh_t swingh);
stdAc::state_t getState(void);
stdAc::state_t getStatePrev(void);
bool hasStateChanged(void);
stdAc::state_t next; ///< The state we want the device to be in after we send
#ifdef UNIT_TEST
/// @cond IGNORE
/// UT-specific
/// See @c OUTPUT_DECODE_RESULTS_FOR_UT macro description in IRac.cpp
std::shared_ptr<IRrecv> _utReceiver = nullptr;
std::unique_ptr<decode_results> _lastDecodeResults = nullptr;
/// @endcond
#else
private:
#endif // UNIT_TEST
uint16_t _pin; ///< The GPIO to use to transmit messages from.
bool _inverted; ///< IR LED is lit when GPIO is LOW (true) or HIGH (false)?
bool _modulation; ///< Is frequency modulation to be used?
stdAc::state_t _prev; ///< The state we expect the device to currently be in.
#if SEND_AIRTON
void airton(IRAirtonAc *ac,
const bool on, const stdAc::opmode_t mode,
const float degrees, const stdAc::fanspeed_t fan,
const stdAc::swingv_t swingv, const bool turbo,
const bool light, const bool econo, const bool filter,
const int16_t sleep = -1);
#endif // SEND_AIRTON
#if SEND_AIRWELL
void airwell(IRAirwellAc *ac,
const bool on, const stdAc::opmode_t mode, const float degrees,
const stdAc::fanspeed_t fan);
#endif // SEND_AIRWELL
#if SEND_AMCOR
void amcor(IRAmcorAc *ac,
const bool on, const stdAc::opmode_t mode, const float degrees,
const stdAc::fanspeed_t fan);
#endif // SEND_AMCOR
#if SEND_ARGO
void argo(IRArgoAC *ac,
const bool on, const stdAc::opmode_t mode, const float degrees,
const float sensorTemp, const stdAc::fanspeed_t fan,
const stdAc::swingv_t swingv, const bool iFeel, const bool turbo,
const int16_t sleep = -1);
void argoWrem3_ACCommand(IRArgoAC_WREM3 *ac,
const bool on, const stdAc::opmode_t mode, const float degrees,
const float sensorTemp, const stdAc::fanspeed_t fan,
const stdAc::swingv_t swingv, const bool iFeel, const bool night,
const bool econo, const bool turbo, const bool filter, const bool light);
void argoWrem3_iFeelReport(IRArgoAC_WREM3 *ac, const float sensorTemp);
void argoWrem3_ConfigSet(IRArgoAC_WREM3 *ac, const uint8_t param,
const uint8_t value, bool safe = true);
void argoWrem3_SetTimer(IRArgoAC_WREM3 *ac, bool on,
const uint16_t currentTime, const uint16_t delayMinutes);
#endif // SEND_ARGO
#if SEND_BOSCH144
void bosch144(IRBosch144AC *ac,
const bool on, const stdAc::opmode_t mode, const float degrees,
const stdAc::fanspeed_t fan,
const bool quiet);
#endif // SEND_BOSCH144
#if SEND_CARRIER_AC64
void carrier64(IRCarrierAc64 *ac,
const bool on, const stdAc::opmode_t mode,
const float degrees, const stdAc::fanspeed_t fan,
const stdAc::swingv_t swingv, const int16_t sleep = -1);
#endif // SEND_CARRIER_AC64
#if SEND_COOLIX
void coolix(IRCoolixAC *ac,
const bool on, const stdAc::opmode_t mode, const float degrees,
const float sensorTemp, const stdAc::fanspeed_t fan,
const stdAc::swingv_t swingv, const stdAc::swingh_t swingh,
const bool iFeel, const bool turbo, const bool light,
const bool clean, const int16_t sleep = -1);
#endif // SEND_COOLIX
#if SEND_CORONA_AC
void corona(IRCoronaAc *ac,
const bool on, const stdAc::opmode_t mode,
const float degrees, const stdAc::fanspeed_t fan,
const stdAc::swingv_t swingv, const bool econo);
#endif // SEND_CORONA_AC
#if SEND_DAIKIN
void daikin(IRDaikinESP *ac,
const bool on, const stdAc::opmode_t mode, const float degrees,
const stdAc::fanspeed_t fan,
const stdAc::swingv_t swingv, const stdAc::swingh_t swingh,
const bool quiet, const bool turbo, const bool econo,
const bool clean);
#endif // SEND_DAIKIN
#if SEND_DAIKIN128
void daikin128(IRDaikin128 *ac,
const bool on, const stdAc::opmode_t mode,
const float degrees, const stdAc::fanspeed_t fan,
const stdAc::swingv_t swingv,
const bool quiet, const bool turbo, const bool light,
const bool econo, const int16_t sleep = -1,
const int16_t clock = -1);
#endif // SEND_DAIKIN128
#if SEND_DAIKIN152
void daikin152(IRDaikin152 *ac,
const bool on, const stdAc::opmode_t mode,
const float degrees, const stdAc::fanspeed_t fan,
const stdAc::swingv_t swingv,
const bool quiet, const bool turbo, const bool econo);
#endif // SEND_DAIKIN152
#if SEND_DAIKIN160
void daikin160(IRDaikin160 *ac,
const bool on, const stdAc::opmode_t mode,
const float degrees, const stdAc::fanspeed_t fan,
const stdAc::swingv_t swingv);
#endif // SEND_DAIKIN160
#if SEND_DAIKIN176
void daikin176(IRDaikin176 *ac,
const bool on, const stdAc::opmode_t mode,
const float degrees, const stdAc::fanspeed_t fan,
const stdAc::swingh_t swingh);
#endif // SEND_DAIKIN176
#if SEND_DAIKIN2
void daikin2(IRDaikin2 *ac,
const bool on, const stdAc::opmode_t mode,
const float degrees, const stdAc::fanspeed_t fan,
const stdAc::swingv_t swingv, const stdAc::swingh_t swingh,
const bool quiet, const bool turbo, const bool light,
const bool econo, const bool filter, const bool clean,
const bool beep, const int16_t sleep = -1,
const int16_t clock = -1);
#endif // SEND_DAIKIN2
#if SEND_DAIKIN216
void daikin216(IRDaikin216 *ac,
const bool on, const stdAc::opmode_t mode,
const float degrees, const stdAc::fanspeed_t fan,
const stdAc::swingv_t swingv, const stdAc::swingh_t swingh,
const bool quiet, const bool turbo);
#endif // SEND_DAIKIN216
#if SEND_DAIKIN64
void daikin64(IRDaikin64 *ac,
const bool on, const stdAc::opmode_t mode,
const float degrees, const stdAc::fanspeed_t fan,
const stdAc::swingv_t swingv,
const bool quiet, const bool turbo,
const int16_t sleep = -1, const int16_t clock = -1);
#endif // SEND_DAIKIN64
#if SEND_DELONGHI_AC
void delonghiac(IRDelonghiAc *ac,
const bool on, const stdAc::opmode_t mode, const bool celsius,
const float degrees, const stdAc::fanspeed_t fan,
const bool turbo, const int16_t sleep = -1);
#endif // SEND_DELONGHI_AC
#if SEND_ECOCLIM
void ecoclim(IREcoclimAc *ac,
const bool on, const stdAc::opmode_t mode,
const float degrees, const float sensorTemp,
const stdAc::fanspeed_t fan, const int16_t sleep = -1,
const int16_t clock = -1);
#endif // SEND_ECOCLIM
#if SEND_ELECTRA_AC
void electra(IRElectraAc *ac,
const bool on, const stdAc::opmode_t mode,
const float degrees, const float sensorTemp,
const stdAc::fanspeed_t fan, const stdAc::swingv_t swingv,
const stdAc::swingh_t swingh, const bool iFeel, const bool turbo,
const bool lighttoggle, const bool clean);
#endif // SEND_ELECTRA_AC
#if SEND_FUJITSU_AC
void fujitsu(IRFujitsuAC *ac, const fujitsu_ac_remote_model_t model,
const bool on, const stdAc::opmode_t mode,
const bool celsius, const float degrees,
const stdAc::fanspeed_t fan,
const stdAc::swingv_t swingv, const stdAc::swingh_t swingh,
const bool quiet, const bool turbo, const bool econo,
const bool filter, const bool clean, const int16_t sleep = -1);
#endif // SEND_FUJITSU_AC
#if SEND_GOODWEATHER
void goodweather(IRGoodweatherAc *ac,
const bool on, const stdAc::opmode_t mode,
const float degrees,
const stdAc::fanspeed_t fan,
const stdAc::swingv_t swingv,
const bool turbo, const bool light,
const int16_t sleep = -1);
#endif // SEND_GOODWEATHER
#if SEND_GREE
void gree(IRGreeAC *ac, const gree_ac_remote_model_t model,
const bool on, const stdAc::opmode_t mode, const bool celsius,
const float degrees, const stdAc::fanspeed_t fan,
const stdAc::swingv_t swingv, const stdAc::swingh_t swingh,
const bool iFeel, const bool turbo, const bool econo,
const bool light, const bool clean, const int16_t sleep = -1);
#endif // SEND_GREE
#if SEND_HAIER_AC
void haier(IRHaierAC *ac,
const bool on, const stdAc::opmode_t mode, const float degrees,
const stdAc::fanspeed_t fan, const stdAc::swingv_t swingv,
const bool filter, const int16_t sleep = -1,
const int16_t clock = -1);
#endif // SEND_HAIER_AC
#if SEND_HAIER_AC160
void haier160(IRHaierAC160 *ac,
const bool on, const stdAc::opmode_t mode, const bool celsius,
const float degrees, const stdAc::fanspeed_t fan,
const stdAc::swingv_t swingv,
const bool turbo, const bool quiet, const bool filter,
const bool clean, const bool light, const bool prevlight,
const int16_t sleep = -1);
#endif // SEND_HAIER_AC160
#if SEND_HAIER_AC176
void haier176(IRHaierAC176 *ac,
const haier_ac176_remote_model_t model, const bool on,
const stdAc::opmode_t mode, const bool celsius,
const float degrees, const stdAc::fanspeed_t fan,
const stdAc::swingv_t swingv, const stdAc::swingh_t swingh,
const bool turbo, const bool quiet, const bool filter,
const int16_t sleep = -1);
#endif // SEND_HAIER_AC176
#if SEND_HAIER_AC_YRW02
void haierYrwo2(IRHaierACYRW02 *ac,
const bool on, const stdAc::opmode_t mode,
const bool celsius, const float degrees,
const stdAc::fanspeed_t fan, const stdAc::swingv_t swingv,
const stdAc::swingh_t swingh, const bool turbo,
const bool quiet, const bool filter,
const int16_t sleep = -1);
#endif // SEND_HAIER_AC_YRW02
#if SEND_HITACHI_AC
void hitachi(IRHitachiAc *ac,
const bool on, const stdAc::opmode_t mode,
const float degrees, const stdAc::fanspeed_t fan,
const stdAc::swingv_t swingv, const stdAc::swingh_t swingh);
#endif // SEND_HITACHI_AC
#if SEND_HITACHI_AC1
void hitachi1(IRHitachiAc1 *ac, const hitachi_ac1_remote_model_t model,
const bool on, const bool power_toggle,
const stdAc::opmode_t mode,
const float degrees, const stdAc::fanspeed_t fan,
const stdAc::swingv_t swingv, const stdAc::swingh_t swingh,
const bool swing_toggle, const int16_t sleep = -1);
#endif // SEND_HITACHI_AC1
#if SEND_HITACHI_AC264
void hitachi264(IRHitachiAc264 *ac,
const bool on, const stdAc::opmode_t mode,
const float degrees, const stdAc::fanspeed_t fan);
#endif // SEND_HITACHI_AC264
#if SEND_HITACHI_AC296
void hitachi296(IRHitachiAc296 *ac,
const bool on, const stdAc::opmode_t mode,
const float degrees, const stdAc::fanspeed_t fan);
#endif // SEND_HITACHI_AC296
#if SEND_HITACHI_AC344
void hitachi344(IRHitachiAc344 *ac,
const bool on, const stdAc::opmode_t mode,
const float degrees, const stdAc::fanspeed_t fan,
const stdAc::swingv_t swingv,
const stdAc::swingh_t swingh);
#endif // SEND_HITACHI_AC344
#if SEND_HITACHI_AC424
void hitachi424(IRHitachiAc424 *ac,
const bool on, const stdAc::opmode_t mode,
const float degrees, const stdAc::fanspeed_t fan,
const stdAc::swingv_t swingv);
#endif // SEND_HITACHI_AC424
#if SEND_KELON
void kelon(IRKelonAc *ac, const bool togglePower, const stdAc::opmode_t mode,
const int8_t dryGrade, const float degrees,
const stdAc::fanspeed_t fan, const bool toggleSwing,
const bool superCool, const int16_t sleep);
#endif // SEND_KELON
#if SEND_KELVINATOR
void kelvinator(IRKelvinatorAC *ac,
const bool on, const stdAc::opmode_t mode,
const float degrees, const stdAc::fanspeed_t fan,
const stdAc::swingv_t swingv, const stdAc::swingh_t swingh,
const bool quiet, const bool turbo, const bool light,
const bool filter, const bool clean);
#endif // SEND_KELVINATOR
#if SEND_LG
void lg(IRLgAc *ac, const lg_ac_remote_model_t model,
const bool on, const stdAc::opmode_t mode,
const float degrees, const stdAc::fanspeed_t fan,
const stdAc::swingv_t swingv, const stdAc::swingv_t swingv_prev,
const stdAc::swingh_t swingh, const bool light);
#endif // SEND_LG
#if SEND_MIDEA
void midea(IRMideaAC *ac,
const bool on, const stdAc::opmode_t mode, const bool celsius,
const float degrees, const float sensorTemp,
const stdAc::fanspeed_t fan, const stdAc::swingv_t swingv,
const bool iFeel, const bool quiet, const bool quiet_prev,
const bool turbo, const bool econo, const bool light,
const bool clean, const int16_t sleep = -1);
#endif // SEND_MIDEA
#if SEND_MIRAGE
void mirage(IRMirageAc *ac, const stdAc::state_t state);
#endif // SEND_MIRAGE
#if SEND_MITSUBISHI_AC
void mitsubishi(IRMitsubishiAC *ac,
const bool on, const stdAc::opmode_t mode,
const float degrees,
const stdAc::fanspeed_t fan, const stdAc::swingv_t swingv,
const stdAc::swingh_t swingh,
const bool quiet, const int16_t clock = -1);
#endif // SEND_MITSUBISHI_AC
#if SEND_MITSUBISHI112
void mitsubishi112(IRMitsubishi112 *ac,
const bool on, const stdAc::opmode_t mode,
const float degrees, const stdAc::fanspeed_t fan,
const stdAc::swingv_t swingv,
const stdAc::swingh_t swingh,
const bool quiet);
#endif // SEND_MITSUBISHI112
#if SEND_MITSUBISHI136
void mitsubishi136(IRMitsubishi136 *ac,
const bool on, const stdAc::opmode_t mode,
const float degrees, const stdAc::fanspeed_t fan,
const stdAc::swingv_t swingv, const bool quiet);
#endif // SEND_MITSUBISHI136
#if SEND_MITSUBISHIHEAVY
void mitsubishiHeavy88(IRMitsubishiHeavy88Ac *ac,
const bool on, const stdAc::opmode_t mode,
const float degrees, const stdAc::fanspeed_t fan,
const stdAc::swingv_t swingv,
const stdAc::swingh_t swingh,
const bool turbo, const bool econo, const bool clean);
void mitsubishiHeavy152(IRMitsubishiHeavy152Ac *ac,
const bool on, const stdAc::opmode_t mode,
const float degrees, const stdAc::fanspeed_t fan,
const stdAc::swingv_t swingv,
const stdAc::swingh_t swingh,
const bool quiet, const bool turbo, const bool econo,
const bool filter, const bool clean,
const int16_t sleep = -1);
#endif // SEND_MITSUBISHIHEAVY
#if SEND_NEOCLIMA
void neoclima(IRNeoclimaAc *ac, const bool on, const stdAc::opmode_t mode,
const bool celsius, const float degrees,
const stdAc::fanspeed_t fan,
const stdAc::swingv_t swingv, const stdAc::swingh_t swingh,
const bool turbo, const bool econo, const bool light,
const bool filter, const int16_t sleep = -1);
#endif // SEND_NEOCLIMA
#if SEND_PANASONIC_AC
void panasonic(IRPanasonicAc *ac, const panasonic_ac_remote_model_t model,
const bool on, const stdAc::opmode_t mode, const float degrees,
const stdAc::fanspeed_t fan,
const stdAc::swingv_t swingv, const stdAc::swingh_t swingh,
const bool quiet, const bool turbo, const bool filter,
const int16_t clock = -1);
#endif // SEND_PANASONIC_AC
#if SEND_PANASONIC_AC32
void panasonic32(IRPanasonicAc32 *ac,
const bool on, const stdAc::opmode_t mode,
const float degrees, const stdAc::fanspeed_t fan,
const stdAc::swingv_t swingv, const stdAc::swingh_t swingh);
#endif // SEND_PANASONIC_AC32
#if SEND_RHOSS
void rhoss(IRRhossAc *ac,
const bool on, const stdAc::opmode_t mode, const float degrees,
const stdAc::fanspeed_t fan, const stdAc::swingv_t swing);
#endif // SEND_RHOSS
#if SEND_SAMSUNG_AC
void samsung(IRSamsungAc *ac,
const bool on, const stdAc::opmode_t mode, const float degrees,
const stdAc::fanspeed_t fan,
const stdAc::swingv_t swingv, const stdAc::swingh_t swingh,
const bool quiet, const bool turbo, const bool econo,
const bool light, const bool filter, const bool clean,
const bool beep, const int16_t sleep = -1,
const bool prevpower = true, const int16_t prevsleep = -1,
const bool forceextended = true);
#endif // SEND_SAMSUNG_AC
#if SEND_SANYO_AC
void sanyo(IRSanyoAc *ac,
const bool on, const stdAc::opmode_t mode, const float degrees,
const float sensorTemp, const stdAc::fanspeed_t fan,
const stdAc::swingv_t swingv, const bool iFeel, const bool beep,
const int16_t sleep = -1);
#endif // SEND_SANYO_AC
#if SEND_SANYO_AC88
void sanyo88(IRSanyoAc88 *ac,
const bool on, const stdAc::opmode_t mode,
const float degrees, const stdAc::fanspeed_t fan,
const stdAc::swingv_t swingv, const bool turbo,
const bool filter,
const int16_t sleep = -1, const int16_t clock = -1);
#endif // SEND_SANYO_AC88
#if SEND_SHARP_AC
void sharp(IRSharpAc *ac, const sharp_ac_remote_model_t model,
const bool on, const bool prev_power, const stdAc::opmode_t mode,
const float degrees, const stdAc::fanspeed_t fan,
const stdAc::swingv_t swingv, const stdAc::swingv_t swingv_prev,
const bool turbo, const bool light,
const bool filter, const bool clean);
#endif // SEND_SHARP_AC
#if SEND_TCL112AC
void tcl112(IRTcl112Ac *ac, const tcl_ac_remote_model_t model,
const bool on, const stdAc::opmode_t mode, const float degrees,
const stdAc::fanspeed_t fan,
const stdAc::swingv_t swingv, const stdAc::swingh_t swingh,
const bool quiet, const bool turbo, const bool light,
const bool econo, const bool filter);
#endif // SEND_TCL112AC
#if SEND_TECHNIBEL_AC
void technibel(IRTechnibelAc *ac,
const bool on, const stdAc::opmode_t mode, const bool celsius,
const float degrees, const stdAc::fanspeed_t fan,
const stdAc::swingv_t swingv, const int16_t sleep = -1);
#endif // SEND_TECHNIBEL_AC
#if SEND_TECO
void teco(IRTecoAc *ac,
const bool on, const stdAc::opmode_t mode, const float degrees,
const stdAc::fanspeed_t fan, const stdAc::swingv_t swingv,
const bool light, const int16_t sleep = -1);
#endif // SEND_TECO
#if SEND_TOSHIBA_AC
void toshiba(IRToshibaAC *ac,
const bool on, const stdAc::opmode_t mode, const float degrees,
const stdAc::fanspeed_t fan, const stdAc::swingv_t swingv,
const bool turbo, const bool econo, const bool filter);
#endif // SEND_TOSHIBA_AC
#if SEND_TROTEC
void trotec(IRTrotecESP *ac,
const bool on, const stdAc::opmode_t mode, const float degrees,
const stdAc::fanspeed_t fan, const int16_t sleep = -1);
#endif // SEND_TROTEC
#if SEND_TROTEC_3550
void trotec3550(IRTrotec3550 *ac,
const bool on, const stdAc::opmode_t mode,
const bool celsius, const float degrees,
const stdAc::fanspeed_t fan, const stdAc::swingv_t swingv);
#endif // SEND_TROTEC_3550
#if SEND_TRUMA
void truma(IRTrumaAc *ac,
const bool on, const stdAc::opmode_t mode, const float degrees,
const stdAc::fanspeed_t fan, const bool quiet);
#endif // SEND_TRUMA
#if SEND_VESTEL_AC
void vestel(IRVestelAc *ac,
const bool on, const stdAc::opmode_t mode, const float degrees,
const stdAc::fanspeed_t fan, const stdAc::swingv_t swingv,
const bool turbo, const bool filter,
const int16_t sleep = -1, const int16_t clock = -1,
const bool sendNormal = true);
#endif // SEND_VESTEL_AC
#if SEND_VOLTAS
void voltas(IRVoltas *ac, const voltas_ac_remote_model_t model,
const bool on, const stdAc::opmode_t mode,
const float degrees, const stdAc::fanspeed_t fan,
const stdAc::swingv_t swingv, const stdAc::swingh_t swingh,
const bool turbo, const bool econo, const bool light,
const int16_t sleep = -1);
#endif // SEND_VOLTAS
#if SEND_WHIRLPOOL_AC
void whirlpool(IRWhirlpoolAc *ac, const whirlpool_ac_remote_model_t model,
const bool on, const stdAc::opmode_t mode, const float degrees,
const stdAc::fanspeed_t fan, const stdAc::swingv_t swingv,
const bool turbo, const bool light,
const int16_t sleep = -1, const int16_t clock = -1);
#endif // SEND_WHIRLPOOL_AC
#if SEND_TRANSCOLD
void transcold(IRTranscoldAc *ac,
const bool on, const stdAc::opmode_t mode, const float degrees,
const stdAc::fanspeed_t fan,
const stdAc::swingv_t swingv, const stdAc::swingh_t swingh);
#endif // SEND_TRANSCOLD
static stdAc::state_t cleanState(const stdAc::state_t state);
static stdAc::state_t handleToggles(const stdAc::state_t desired,
const stdAc::state_t *prev = NULL);
}; // IRac class
/// Common functions for use with all A/Cs supported by the IRac class.
namespace IRAcUtils {
String resultAcToString(const decode_results * const results);
bool decodeToState(const decode_results *decode, stdAc::state_t *result,
const stdAc::state_t *prev = NULL);
} // namespace IRAcUtils
#endif // IRAC_H_

View File

@@ -0,0 +1,53 @@
#ifndef IRMACROS_H_
#define IRMACROS_H_
/****************************************************************
* Copyright 2022 IRremoteESP8266 project and others
*/
/// @file IRmacros.h
/**
* VA_OPT_SUPPORTED macro to check if __VA_OPT__ is supported
* Source: https://stackoverflow.com/a/48045656
*/
/// @cond TEST
#define PP_THIRD_ARG(a, b, c, ...) c
#define VA_OPT_SUPPORTED_I(...) \
PP_THIRD_ARG(__VA_OPT__(, false), true, false, false)
#define VA_OPT_SUPPORTED VA_OPT_SUPPORTED_I(?)
/// @endcond
/**
* VA_OPT_SUPPORTED end
*/
/**
* COND() Set of macros to facilitate single-line conditional compilation
* argument checking.
* Found here: https://www.reddit.com/r/C_Programming/comments/ud3xrv/
* conditional_preprocessor_macro_anyone/
*
* Usage:
* COND(<define_to_test>[||/&&<more_define>...], <true_result>, <false_result>)
*
* NB: If __VA_OPT__ macro not supported, the <true_result> will be expanded!
*/
/// @cond TEST
#if !VA_OPT_SUPPORTED
// #pragma message("Compiler without __VA_OPT__ support")
#define COND(cond, a, b) a
#else // !VA_OPT_SUPPORTED
#define NOTHING
#define EXPAND(...) __VA_ARGS__
#define STUFF_P(a, ...) __VA_OPT__(a)
#define STUFF(...) STUFF_P(__VA_ARGS__)
#define VA_TEST_P(a, ...) __VA_OPT__(NO)##THING
#define VA_TEST(...) VA_TEST_P(__VA_ARGS__)
#define NEGATE(a) VA_TEST(a, a)
#define COND_P(cond, a, b) STUFF(a, cond)STUFF(b, NEGATE(cond))
#define COND(cond, a, b) EXPAND(COND_P(cond, a, b))
#endif // !VA_OPT_SUPPORTED
/// @endcond
/**
* end of COND() set of macros
*/
#endif // IRMACROS_H_

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,888 @@
// Copyright 2009 Ken Shirriff
// Copyright 2015 Mark Szabo
// Copyright 2015 Sebastien Warin
// Copyright 2017 David Conran
#ifndef IRRECV_H_
#define IRRECV_H_
#ifndef UNIT_TEST
#include <Arduino.h>
#endif
#include <stddef.h>
#define __STDC_LIMIT_MACROS
#include <stdint.h>
#include "IRremoteESP8266.h"
// Constants
const uint16_t kHeader = 2; // Usual nr. of header entries.
const uint16_t kFooter = 2; // Usual nr. of footer (stop bits) entries.
const uint16_t kStartOffset = 1; // Usual rawbuf entry to start from.
#define MS_TO_USEC(x) ((x) * 1000U) // Convert milli-Seconds to micro-Seconds.
// Marks tend to be 100us too long, and spaces 100us too short
// when received due to sensor lag.
const uint16_t kMarkExcess = 50;
const uint16_t kRawBuf = 100; // Default length of raw capture buffer
const uint64_t kRepeat = UINT64_MAX;
// Default min size of reported UNKNOWN messages.
const uint16_t kUnknownThreshold = 6;
// receiver states
const uint8_t kIdleState = 2;
const uint8_t kMarkState = 3;
const uint8_t kSpaceState = 4;
const uint8_t kStopState = 5;
const uint8_t kTolerance = 25; // default percent tolerance in measurements.
const uint8_t kUseDefTol = 255; // Indicate to use the class default tolerance.
const uint16_t kRawTick = 2; // Capture tick to uSec factor.
#define RAWTICK kRawTick // Deprecated. For legacy user code support only.
// How long (ms) before we give up wait for more data?
// Don't exceed kMaxTimeoutMs without a good reason.
// That is the capture buffers maximum value size. (UINT16_MAX / kRawTick)
// Typically messages/protocols tend to repeat around the 100ms timeframe,
// thus we should timeout before that to give us some time to try to decode
// before we need to start capturing a possible new message.
// Typically 15ms suits most applications. However, some protocols demand a
// higher value. e.g. 90ms for XMP-1 and some aircon units.
const uint8_t kTimeoutMs = 15; // In MilliSeconds.
#define TIMEOUT_MS kTimeoutMs // For legacy documentation.
const uint16_t kMaxTimeoutMs = kRawTick * (UINT16_MAX / MS_TO_USEC(1));
// Use FNV hash algorithm: http://isthe.com/chongo/tech/comp/fnv/#FNV-param
const uint32_t kFnvPrime32 = 16777619UL;
const uint32_t kFnvBasis32 = 2166136261UL;
#ifdef ESP32
// Which of the ESP32 timers to use by default.
// (3 for most ESP32s, 1 for ESP32-C3s)
#ifdef SOC_TIMER_GROUP_TOTAL_TIMERS
const uint8_t kDefaultESP32Timer = SOC_TIMER_GROUP_TOTAL_TIMERS - 1;
#else // SOC_TIMER_GROUP_TOTAL_TIMERS
const uint8_t kDefaultESP32Timer = 3;
#endif // SOC_TIMER_GROUP_TOTAL_TIMERS
#endif // ESP32
#if DECODE_AC
// Hitachi AC is the current largest state size.
const uint16_t kStateSizeMax = kHitachiAc2StateLength;
#else // DECODE_AC
// Just define something (a uint64_t)
const uint16_t kStateSizeMax = sizeof(uint64_t);
#endif // DECODE_AC
// Types
/// Information for the interrupt handler
typedef struct {
uint8_t recvpin; // pin for IR data from detector
uint8_t rcvstate; // state machine
uint16_t timer; // state timer, counts 50uS ticks.
uint16_t bufsize; // max. nr. of entries in the capture buffer.
uint16_t *rawbuf; // raw data
// uint16_t is used for rawlen as it saves 3 bytes of iram in the interrupt
// handler. Don't ask why, I don't know. It just does.
uint16_t rawlen; // counter of entries in rawbuf.
uint8_t overflow; // Buffer overflow indicator.
uint8_t timeout; // Nr. of milliSeconds before we give up.
} irparams_t;
/// Results from a data match
typedef struct {
bool success; // Was the match successful?
uint64_t data; // The data found.
uint16_t used; // How many buffer positions were used.
} match_result_t;
// Classes
/// Results returned from the decoder
class decode_results {
public:
decode_type_t decode_type; // NEC, SONY, RC5, UNKNOWN
// value, address, & command are all mutually exclusive with state.
// i.e. They MUST NOT be used at the same time as state, so we can use a union
// structure to save us a handful of valuable bytes of memory.
union {
struct {
uint64_t value; // Decoded value
uint32_t address; // Decoded device address.
uint32_t command; // Decoded command.
};
uint8_t state[kStateSizeMax]; // Multi-byte results.
};
uint16_t bits; // Number of bits in decoded value
volatile uint16_t *rawbuf; // Raw intervals in .5 us ticks
uint16_t rawlen; // Number of records in rawbuf.
bool overflow;
bool repeat; // Is the result a repeat code?
};
/// Class for receiving IR messages.
class IRrecv {
public:
#if defined(ESP32)
explicit IRrecv(const uint16_t recvpin, const uint16_t bufsize = kRawBuf,
const uint8_t timeout = kTimeoutMs,
const bool save_buffer = false,
const uint8_t timer_num = kDefaultESP32Timer); // Constructor
#else // ESP32
explicit IRrecv(const uint16_t recvpin, const uint16_t bufsize = kRawBuf,
const uint8_t timeout = kTimeoutMs,
const bool save_buffer = false); // Constructor
#endif // ESP32
~IRrecv(void); // Destructor
void setTolerance(const uint8_t percent = kTolerance);
uint8_t getTolerance(void);
bool decode(decode_results *results, irparams_t *save = NULL,
uint8_t max_skip = 0, uint16_t noise_floor = 0);
void enableIRIn(const bool pullup = false);
void disableIRIn(void);
void pause(void);
void resume(void);
uint16_t getBufSize(void);
#if DECODE_HASH
void setUnknownThreshold(const uint16_t length);
#endif
bool match(const uint32_t measured, const uint32_t desired,
const uint8_t tolerance = kUseDefTol,
const uint16_t delta = 0);
bool matchMark(const uint32_t measured, const uint32_t desired,
const uint8_t tolerance = kUseDefTol,
const int16_t excess = kMarkExcess);
bool matchMarkRange(const uint32_t measured, const uint32_t desired,
const uint16_t range = 100,
const int16_t excess = kMarkExcess);
bool matchSpace(const uint32_t measured, const uint32_t desired,
const uint8_t tolerance = kUseDefTol,
const int16_t excess = kMarkExcess);
bool matchSpaceRange(const uint32_t measured, const uint32_t desired,
const uint16_t range = 100,
const int16_t excess = kMarkExcess);
#ifndef UNIT_TEST
private:
#endif
irparams_t *irparams_save;
uint8_t _tolerance;
#if defined(ESP32)
uint8_t _timer_num;
#endif // defined(ESP32)
#if DECODE_HASH
uint16_t _unknown_threshold;
#endif
#ifdef UNIT_TEST
volatile irparams_t *_getParamsPtr(void);
#endif // UNIT_TEST
// These are called by decode
uint8_t _validTolerance(const uint8_t percentage);
void copyIrParams(volatile irparams_t *src, irparams_t *dst);
uint16_t compare(const uint16_t oldval, const uint16_t newval);
uint32_t ticksLow(const uint32_t usecs,
const uint8_t tolerance = kUseDefTol,
const uint16_t delta = 0);
uint32_t ticksHigh(const uint32_t usecs,
const uint8_t tolerance = kUseDefTol,
const uint16_t delta = 0);
bool matchAtLeast(const uint32_t measured, const uint32_t desired,
const uint8_t tolerance = kUseDefTol,
const uint16_t delta = 0);
uint16_t _matchGeneric(volatile uint16_t *data_ptr,
uint64_t *result_bits_ptr,
uint8_t *result_ptr,
const bool use_bits,
const uint16_t remaining,
const uint16_t required,
const uint16_t hdrmark,
const uint32_t hdrspace,
const uint16_t onemark,
const uint32_t onespace,
const uint16_t zeromark,
const uint32_t zerospace,
const uint16_t footermark,
const uint32_t footerspace,
const bool atleast = false,
const uint8_t tolerance = kUseDefTol,
const int16_t excess = kMarkExcess,
const bool MSBfirst = true);
match_result_t matchData(volatile uint16_t *data_ptr, const uint16_t nbits,
const uint16_t onemark, const uint32_t onespace,
const uint16_t zeromark, const uint32_t zerospace,
const uint8_t tolerance = kUseDefTol,
const int16_t excess = kMarkExcess,
const bool MSBfirst = true,
const bool expectlastspace = true);
uint16_t matchBytes(volatile uint16_t *data_ptr, uint8_t *result_ptr,
const uint16_t remaining, const uint16_t nbytes,
const uint16_t onemark, const uint32_t onespace,
const uint16_t zeromark, const uint32_t zerospace,
const uint8_t tolerance = kUseDefTol,
const int16_t excess = kMarkExcess,
const bool MSBfirst = true,
const bool expectlastspace = true);
uint16_t matchGeneric(volatile uint16_t *data_ptr,
uint64_t *result_ptr,
const uint16_t remaining, const uint16_t nbits,
const uint16_t hdrmark, const uint32_t hdrspace,
const uint16_t onemark, const uint32_t onespace,
const uint16_t zeromark, const uint32_t zerospace,
const uint16_t footermark, const uint32_t footerspace,
const bool atleast = false,
const uint8_t tolerance = kUseDefTol,
const int16_t excess = kMarkExcess,
const bool MSBfirst = true);
uint16_t matchGeneric(volatile uint16_t *data_ptr, uint8_t *result_ptr,
const uint16_t remaining, const uint16_t nbits,
const uint16_t hdrmark, const uint32_t hdrspace,
const uint16_t onemark, const uint32_t onespace,
const uint16_t zeromark, const uint32_t zerospace,
const uint16_t footermark,
const uint32_t footerspace,
const bool atleast = false,
const uint8_t tolerance = kUseDefTol,
const int16_t excess = kMarkExcess,
const bool MSBfirst = true);
uint16_t matchGenericConstBitTime(volatile uint16_t *data_ptr,
uint64_t *result_ptr,
const uint16_t remaining,
const uint16_t nbits,
const uint16_t hdrmark,
const uint32_t hdrspace,
const uint16_t one,
const uint32_t zero,
const uint16_t footermark,
const uint32_t footerspace,
const bool atleast = false,
const uint8_t tolerance = kUseDefTol,
const int16_t excess = kMarkExcess,
const bool MSBfirst = true);
uint16_t matchManchesterData(volatile const uint16_t *data_ptr,
uint64_t *result_ptr,
const uint16_t remaining,
const uint16_t nbits,
const uint16_t half_period,
const uint16_t starting_balance = 0,
const uint8_t tolerance = kUseDefTol,
const int16_t excess = kMarkExcess,
const bool MSBfirst = true,
const bool GEThomas = true);
uint16_t matchManchester(volatile const uint16_t *data_ptr,
uint64_t *result_ptr,
const uint16_t remaining,
const uint16_t nbits,
const uint16_t hdrmark,
const uint32_t hdrspace,
const uint16_t clock_period,
const uint16_t footermark,
const uint32_t footerspace,
const bool atleast = false,
const uint8_t tolerance = kUseDefTol,
const int16_t excess = kMarkExcess,
const bool MSBfirst = true,
const bool GEThomas = true);
void crudeNoiseFilter(decode_results *results, const uint16_t floor = 0);
bool decodeHash(decode_results *results);
#if DECODE_VOLTAS
bool decodeVoltas(decode_results *results,
uint16_t offset = kStartOffset,
const uint16_t nbits = kVoltasBits,
const bool strict = true);
#endif // DECODE_VOLTAS
#if (DECODE_NEC || DECODE_SHERWOOD || DECODE_AIWA_RC_T501 || DECODE_SANYO)
bool decodeNEC(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kNECBits, const bool strict = true);
#endif
#if DECODE_ARGO
bool decodeArgo(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kArgoBits, const bool strict = true);
bool decodeArgoWREM3(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kArgo3AcControlStateLength * 8,
const bool strict = true);
#endif // DECODE_ARGO
#if DECODE_ARRIS
bool decodeArris(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kArrisBits, const bool strict = true);
#endif // DECODE_ARRIS
#if DECODE_SONY
bool decodeSony(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kSonyMinBits,
const bool strict = false);
#endif
#if DECODE_SANYO
// DISABLED due to poor quality.
// bool decodeSanyo(decode_results *results, uint16_t offset = kStartOffset,
// uint16_t nbits = kSanyoSA8650BBits,
// bool strict = false);
bool decodeSanyoLC7461(decode_results *results,
uint16_t offset = kStartOffset,
const uint16_t nbits = kSanyoLC7461Bits,
const bool strict = true);
#endif
#if DECODE_SANYO_AC
bool decodeSanyoAc(decode_results *results,
uint16_t offset = kStartOffset,
const uint16_t nbits = kSanyoAcBits,
const bool strict = true);
#endif // DECODE_SANYO_AC
#if DECODE_SANYO_AC88
bool decodeSanyoAc88(decode_results *results,
uint16_t offset = kStartOffset,
const uint16_t nbits = kSanyoAc88Bits,
const bool strict = true);
#endif // DECODE_SANYO_AC88
#if DECODE_SANYO_AC152
bool decodeSanyoAc152(decode_results *results,
uint16_t offset = kStartOffset,
const uint16_t nbits = kSanyoAc152Bits,
const bool strict = true);
#endif // DECODE_SANYO_AC152
#if DECODE_MITSUBISHI
bool decodeMitsubishi(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kMitsubishiBits,
const bool strict = true);
#endif
#if DECODE_MITSUBISHI2
bool decodeMitsubishi2(decode_results *results,
uint16_t offset = kStartOffset,
const uint16_t nbits = kMitsubishiBits,
const bool strict = true);
#endif
#if DECODE_MITSUBISHI_AC
bool decodeMitsubishiAC(decode_results *results,
uint16_t offset = kStartOffset,
const uint16_t nbits = kMitsubishiACBits,
const bool strict = false);
#endif
#if DECODE_MITSUBISHI136
bool decodeMitsubishi136(decode_results *results,
uint16_t offset = kStartOffset,
const uint16_t nbits = kMitsubishi136Bits,
const bool strict = true);
#endif
#if DECODE_MITSUBISHI112
bool decodeMitsubishi112(decode_results *results,
uint16_t offset = kStartOffset,
const uint16_t nbits = kMitsubishi112Bits,
const bool strict = true);
#endif
#if DECODE_MITSUBISHIHEAVY
bool decodeMitsubishiHeavy(decode_results *results,
uint16_t offset = kStartOffset,
const uint16_t nbits = kMitsubishiHeavy152Bits,
const bool strict = true);
#endif
#if (DECODE_RC5 || DECODE_RC6 || DECODE_LASERTAG || DECODE_MWM)
int16_t getRClevel(decode_results *results, uint16_t *offset, uint16_t *used,
uint16_t bitTime, const uint8_t tolerance = kUseDefTol,
const int16_t excess = kMarkExcess,
const uint16_t delta = 0, const uint8_t maxwidth = 3);
#endif
#if DECODE_RC5
bool decodeRC5(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kRC5XBits,
const bool strict = true);
#endif
#if DECODE_RC6
bool decodeRC6(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kRC6Mode0Bits,
const bool strict = false);
#endif
#if DECODE_RCMM
bool decodeRCMM(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kRCMMBits,
const bool strict = false);
#endif
#if (DECODE_PANASONIC || DECODE_DENON)
bool decodePanasonic(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kPanasonicBits,
const bool strict = false,
const uint32_t manufacturer = kPanasonicManufacturer);
#endif
#if DECODE_LG
bool decodeLG(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kLgBits,
const bool strict = false);
#endif
#if DECODE_INAX
bool decodeInax(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kInaxBits,
const bool strict = true);
#endif // DECODE_INAX
#if DECODE_JVC
bool decodeJVC(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kJvcBits,
const bool strict = true);
#endif
#if DECODE_SAMSUNG
bool decodeSAMSUNG(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kSamsungBits,
const bool strict = true);
#endif
#if DECODE_SAMSUNG
bool decodeSamsung36(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kSamsung36Bits,
const bool strict = true);
#endif
#if DECODE_SAMSUNG_AC
bool decodeSamsungAC(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kSamsungAcBits,
const bool strict = true);
#endif
#if DECODE_WHYNTER
bool decodeWhynter(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kWhynterBits,
const bool strict = true);
#endif
#if DECODE_COOLIX
bool decodeCOOLIX(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kCoolixBits,
const bool strict = true);
#endif // DECODE_COOLIX
#if DECODE_COOLIX48
bool decodeCoolix48(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kCoolix48Bits,
const bool strict = true);
#endif // DECODE_COOLIX48
#if DECODE_DENON
bool decodeDenon(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kDenonBits,
const bool strict = true);
#endif
#if DECODE_DISH
bool decodeDISH(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kDishBits,
const bool strict = true);
#endif
#if (DECODE_SHARP || DECODE_DENON)
bool decodeSharp(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kSharpBits,
const bool strict = true, const bool expansion = true);
#endif
#if DECODE_SHARP_AC
bool decodeSharpAc(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kSharpAcBits,
const bool strict = true);
#endif
#if DECODE_AIWA_RC_T501
bool decodeAiwaRCT501(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kAiwaRcT501Bits,
const bool strict = true);
#endif
#if DECODE_NIKAI
bool decodeNikai(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kNikaiBits,
const bool strict = true);
#endif
#if DECODE_MAGIQUEST
bool decodeMagiQuest(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kMagiquestBits,
const bool strict = true);
#endif
#if DECODE_KELVINATOR
bool decodeKelvinator(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kKelvinatorBits,
const bool strict = true);
#endif
#if DECODE_DAIKIN
bool decodeDaikin(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kDaikinBits,
const bool strict = true);
#endif
#if DECODE_DAIKIN64
bool decodeDaikin64(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kDaikin64Bits,
const bool strict = true);
#endif // DECODE_DAIKIN64
#if DECODE_DAIKIN128
bool decodeDaikin128(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kDaikin128Bits,
const bool strict = true);
#endif // DECODE_DAIKIN128
#if DECODE_DAIKIN152
bool decodeDaikin152(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kDaikin152Bits,
const bool strict = true);
#endif // DECODE_DAIKIN152
#if DECODE_DAIKIN160
bool decodeDaikin160(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kDaikin160Bits,
const bool strict = true);
#endif // DECODE_DAIKIN160
#if DECODE_DAIKIN176
bool decodeDaikin176(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kDaikin176Bits,
const bool strict = true);
#endif // DECODE_DAIKIN176
#if DECODE_DAIKIN2
bool decodeDaikin2(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kDaikin2Bits,
const bool strict = true);
#endif
#if DECODE_DAIKIN200
bool decodeDaikin200(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kDaikin200Bits,
const bool strict = true);
#endif // DECODE_DAIKIN200
#if DECODE_DAIKIN216
bool decodeDaikin216(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kDaikin216Bits,
const bool strict = true);
#endif // DECODE_DAIKIN216
#if DECODE_DAIKIN312
bool decodeDaikin312(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kDaikin312Bits,
const bool strict = true);
#endif // DECODE_DAIKIN312
#if DECODE_TOSHIBA_AC
bool decodeToshibaAC(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kToshibaACBits,
const bool strict = true);
#endif
#if DECODE_TROTEC
bool decodeTrotec(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kTrotecBits,
const bool strict = true);
#endif // DECODE_TROTEC
#if DECODE_TROTEC_3550
bool decodeTrotec3550(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kTrotecBits,
const bool strict = true);
#endif // DECODE_TROTEC_3550
#if DECODE_MIDEA
bool decodeMidea(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kMideaBits,
const bool strict = true);
#endif // DECODE_MIDEA
#if DECODE_MIDEA24
bool decodeMidea24(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kMidea24Bits,
const bool strict = true);
#endif // DECODE_MIDEA24
#if DECODE_FUJITSU_AC
bool decodeFujitsuAC(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kFujitsuAcBits,
const bool strict = false);
#endif
#if DECODE_LASERTAG
bool decodeLasertag(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kLasertagBits,
const bool strict = true);
#endif
#if DECODE_MILESTAG2
bool decodeMilestag2(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kMilesTag2ShotBits,
const bool strict = true);
#endif
#if DECODE_CARRIER_AC
bool decodeCarrierAC(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kCarrierAcBits,
const bool strict = true);
#endif // DECODE_CARRIER_AC
#if DECODE_CARRIER_AC40
bool decodeCarrierAC40(decode_results *results,
uint16_t offset = kStartOffset,
const uint16_t nbits = kCarrierAc40Bits,
const bool strict = true);
#endif // DECODE_CARRIER_AC40
#if DECODE_CARRIER_AC84
bool decodeCarrierAC84(decode_results *results,
uint16_t offset = kStartOffset,
const uint16_t nbits = kCarrierAc84Bits,
const bool strict = true);
#endif // DECODE_CARRIER_AC84
#if DECODE_CARRIER_AC64
bool decodeCarrierAC64(decode_results *results,
uint16_t offset = kStartOffset,
const uint16_t nbits = kCarrierAc64Bits,
const bool strict = true);
#endif // DECODE_CARRIER_AC64
#if DECODE_CARRIER_AC128
bool decodeCarrierAC128(decode_results *results,
uint16_t offset = kStartOffset,
const uint16_t nbits = kCarrierAc128Bits,
const bool strict = true);
#endif // DECODE_CARRIER_AC128
#if DECODE_GOODWEATHER
bool decodeGoodweather(decode_results *results,
uint16_t offset = kStartOffset,
const uint16_t nbits = kGoodweatherBits,
const bool strict = true);
#endif // DECODE_GOODWEATHER
#if DECODE_GORENJE
bool decodeGorenje(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kGorenjeBits,
const bool strict = true);
#endif // DECODE_GORENJE
#if DECODE_GREE
bool decodeGree(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kGreeBits,
const bool strict = true);
#endif
#if (DECODE_HAIER_AC | DECODE_HAIER_AC_YRW02 || DECODE_HAIER_AC160 || \
DECODE_HAIER_AC176)
bool decodeHaierAC(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kHaierACBits,
const bool strict = true);
#endif
#if DECODE_HAIER_AC_YRW02
bool decodeHaierACYRW02(decode_results *results,
uint16_t offset = kStartOffset,
const uint16_t nbits = kHaierACYRW02Bits,
const bool strict = true);
#endif
#if DECODE_HAIER_AC160
bool decodeHaierAC160(decode_results *results,
uint16_t offset = kStartOffset,
const uint16_t nbits = kHaierAC160Bits,
const bool strict = true);
#endif // DECODE_HAIER_AC160
#if DECODE_HAIER_AC176
bool decodeHaierAC176(decode_results *results,
uint16_t offset = kStartOffset,
const uint16_t nbits = kHaierAC176Bits,
const bool strict = true);
#endif // DECODE_HAIER_AC176
#if (DECODE_HITACHI_AC || DECODE_HITACHI_AC2 || DECODE_HITACHI_AC264 || \
DECODE_HITACHI_AC344)
bool decodeHitachiAC(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kHitachiAcBits,
const bool strict = true, const bool MSBfirst = true);
#endif // (DECODE_HITACHI_AC || DECODE_HITACHI_AC2 || DECODE_HITACHI_AC264 ||
// DECODE_HITACHI_AC344)
#if DECODE_HITACHI_AC1
bool decodeHitachiAC1(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kHitachiAc1Bits,
const bool strict = true);
#endif
#if DECODE_HITACHI_AC3
bool decodeHitachiAc3(decode_results *results,
uint16_t offset = kStartOffset,
const uint16_t nbits = kHitachiAc3Bits,
const bool strict = true);
#endif // DECODE_HITACHI_AC3
#if DECODE_HITACHI_AC296
bool decodeHitachiAc296(decode_results *results,
uint16_t offset = kStartOffset,
const uint16_t nbits = kHitachiAc296Bits,
const bool strict = true);
#endif // DECODE_HITACHI_AC296
#if DECODE_HITACHI_AC424
bool decodeHitachiAc424(decode_results *results,
uint16_t offset = kStartOffset,
const uint16_t nbits = kHitachiAc424Bits,
const bool strict = true);
#endif // DECODE_HITACHI_AC424
#if DECODE_GICABLE
bool decodeGICable(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kGicableBits,
const bool strict = true);
#endif
#if DECODE_WHIRLPOOL_AC
bool decodeWhirlpoolAC(decode_results *results,
uint16_t offset = kStartOffset,
const uint16_t nbits = kWhirlpoolAcBits,
const bool strict = true);
#endif
#if DECODE_LUTRON
bool decodeLutron(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kLutronBits,
const bool strict = true);
#endif
#if DECODE_ELECTRA_AC
bool decodeElectraAC(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kElectraAcBits,
const bool strict = true);
#endif
#if DECODE_PANASONIC_AC
bool decodePanasonicAC(decode_results *results,
uint16_t offset = kStartOffset,
const uint16_t nbits = kPanasonicAcBits,
const bool strict = true);
#endif // DECODE_PANASONIC_AC
#if DECODE_PANASONIC_AC32
bool decodePanasonicAC32(decode_results *results,
uint16_t offset = kStartOffset,
const uint16_t nbits = kPanasonicAc32Bits,
const bool strict = true);
#endif // DECODE_PANASONIC_AC32
#if DECODE_PIONEER
bool decodePioneer(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kPioneerBits,
const bool strict = true);
#endif
#if DECODE_MWM
bool decodeMWM(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = 24,
const bool strict = true);
#endif
#if DECODE_VESTEL_AC
bool decodeVestelAc(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kVestelAcBits,
const bool strict = true);
#endif
#if DECODE_TECO
bool decodeTeco(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kTecoBits,
const bool strict = false);
#endif
#if DECODE_LEGOPF
bool decodeLegoPf(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kLegoPfBits,
const bool strict = true);
#endif
#if DECODE_NEOCLIMA
bool decodeNeoclima(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kNeoclimaBits,
const bool strict = true);
#endif // DECODE_NEOCLIMA
#if DECODE_AMCOR
bool decodeAmcor(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kAmcorBits,
const bool strict = true);
#endif // DECODE_AMCOR
#if DECODE_EPSON
bool decodeEpson(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kEpsonBits,
const bool strict = true);
#endif // DECODE_EPSON
#if DECODE_SYMPHONY
bool decodeSymphony(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kSymphonyBits,
const bool strict = true);
#endif // DECODE_SYMPHONY
#if DECODE_AIRWELL
bool decodeAirwell(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kAirwellBits,
const bool strict = true);
#endif // DECODE_AIRWELL
#if DECODE_DELONGHI_AC
bool decodeDelonghiAc(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kDelonghiAcBits,
const bool strict = true);
#endif // DECODE_DELONGHI_AC
#if DECODE_DOSHISHA
bool decodeDoshisha(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kDoshishaBits,
const bool strict = true);
#endif // DECODE_DOSHISHA
#if DECODE_MULTIBRACKETS
bool decodeMultibrackets(decode_results *results,
uint16_t offset = kStartOffset,
const uint16_t nbits = kMultibracketsBits,
const bool strict = true);
#endif // DECODE_MULTIBRACKETS
#if DECODE_TECHNIBEL_AC
bool decodeTechnibelAc(decode_results *results,
uint16_t offset = kStartOffset,
const uint16_t nbits = kTechnibelAcBits,
const bool strict = true);
#endif // DECODE_TECHNIBEL_AC
#if DECODE_CORONA_AC
bool decodeCoronaAc(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kCoronaAcBitsShort,
const bool strict = true);
#endif // DECODE_CORONA_AC
#if DECODE_ZEPEAL
bool decodeZepeal(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kZepealBits,
const bool strict = true);
#endif // DECODE_ZEPEAL
#if DECODE_METZ
bool decodeMetz(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kMetzBits,
const bool strict = true);
#endif // DECODE_METZ
#if DECODE_TRANSCOLD
bool decodeTranscold(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kTranscoldBits,
const bool strict = true);
#endif // DECODE_TRANSCOLD
#if DECODE_MIRAGE
bool decodeMirage(decode_results *results,
uint16_t offset = kStartOffset,
const uint16_t nbits = kMirageBits,
const bool strict = true);
#endif // DECODE_MIRAGE
#if DECODE_ELITESCREENS
bool decodeElitescreens(decode_results *results,
uint16_t offset = kStartOffset,
const uint16_t nbits = kEliteScreensBits,
const bool strict = true);
#endif // DECODE_ELITESCREENS
#if DECODE_ECOCLIM
bool decodeEcoclim(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kEcoclimBits,
const bool strict = true);
#endif // DECODE_ECOCLIM
#if DECODE_XMP
bool decodeXmp(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kXmpBits, const bool strict = true);
#endif // DECODE_XMP
#if DECODE_TRUMA
bool decodeTruma(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kTrumaBits, const bool strict = true);
#endif // DECODE_TRUMA
#if DECODE_TEKNOPOINT
bool decodeTeknopoint(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kTeknopointBits,
const bool strict = true);
#endif // DECODE_TEKNOPOINT
#if DECODE_KELON
bool decodeKelon(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kKelonBits, const bool strict = true);
#endif // DECODE_KELON
#if DECODE_KELON168
bool decodeKelon168(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kKelon168Bits,
const bool strict = true);
#endif // DECODE_KELON168
#if DECODE_BOSE
bool decodeBose(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kBoseBits, const bool strict = true);
#endif // DECODE_BOSE
#if DECODE_RHOSS
bool decodeRhoss(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kRhossBits, const bool strict = true);
#endif // DECODE_RHOSS
#if DECODE_AIRTON
bool decodeAirton(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kAirtonBits,
const bool strict = true);
#endif // DECODE_AIRTON
#if DECODE_TOTO
bool decodeToto(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kTotoBits,
const bool strict = true);
#endif // DECODE_TOTO
#if DECODE_CLIMABUTLER
bool decodeClimaButler(decode_results *results,
uint16_t offset = kStartOffset,
const uint16_t nbits = kClimaButlerBits,
const bool strict = true);
#endif // DECODE_CLIMABUTLER
#if DECODE_TCL96AC
bool decodeTcl96Ac(decode_results *results,
uint16_t offset = kStartOffset,
const uint16_t nbits = kTcl96AcBits,
const bool strict = true);
#endif // DECODE_TCL96AC
#if DECODE_BOSCH144
bool decodeBosch144(decode_results *results,
uint16_t offset = kStartOffset,
const uint16_t nbits = kBosch144Bits,
const bool strict = true);
#endif // DECODE_BOSCH144
#if DECODE_WOWWEE
bool decodeWowwee(decode_results *results,
uint16_t offset = kStartOffset,
const uint16_t nbits = kWowweeBits,
const bool strict = true);
#endif // DECODE_WOWWEE
#if DECODE_YORK
bool decodeYork(decode_results *results,
uint16_t kStartOffset,
const uint16_t kYorkBits,
const bool strict = true);
#endif // DECODE_YORK
};
#endif // IRRECV_H_

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,921 @@
// Copyright 2009 Ken Shirriff
// Copyright 2015 Mark Szabo
// Copyright 2017 David Conran
#ifndef IRSEND_H_
#define IRSEND_H_
#define __STDC_LIMIT_MACROS
#include <stdint.h>
#include "IRremoteESP8266.h"
// Originally from https://github.com/shirriff/Arduino-IRremote/
// Updated by markszabo (https://github.com/crankyoldgit/IRremoteESP8266) for
// sending IR code on ESP8266
#if TEST || UNIT_TEST
#define VIRTUAL virtual
#else
#define VIRTUAL
#endif
// Constants
// Offset (in microseconds) to use in Period time calculations to account for
// code excution time in producing the software PWM signal.
#if defined(ESP32)
// Calculated on a generic ESP-WROOM-32 board with v3.2-18 SDK @ 240MHz
const int8_t kPeriodOffset = -2;
#elif (defined(ESP8266) && F_CPU == 160000000L) // NOLINT(whitespace/parens)
// Calculated on an ESP8266 NodeMCU v2 board using:
// v2.6.0 with v2.5.2 ESP core @ 160MHz
const int8_t kPeriodOffset = -2;
#else // (defined(ESP8266) && F_CPU == 160000000L)
// Calculated on ESP8266 Wemos D1 mini using v2.4.1 with v2.4.0 ESP core @ 40MHz
const int8_t kPeriodOffset = -5;
#endif // (defined(ESP8266) && F_CPU == 160000000L)
const uint8_t kDutyDefault = 50; // Percentage
const uint8_t kDutyMax = 100; // Percentage
// delayMicroseconds() is only accurate to 16383us.
// Ref: https://www.arduino.cc/en/Reference/delayMicroseconds
const uint16_t kMaxAccurateUsecDelay = 16383;
// Usecs to wait between messages we don't know the proper gap time.
const uint32_t kDefaultMessageGap = 100000;
/// Placeholder for missing sensor temp value
/// @note Not using "-1" as it may be a valid external temp
const float kNoTempValue = -100.0;
/// Enumerators and Structures for the Common A/C API.
namespace stdAc {
/// Common A/C settings for A/C operating modes.
enum class opmode_t {
kOff = -1,
kAuto = 0,
kCool = 1,
kHeat = 2,
kDry = 3,
kFan = 4,
// Add new entries before this one, and update it to point to the last entry
kLastOpmodeEnum = kFan,
};
/// Common A/C settings for Fan Speeds.
enum class fanspeed_t {
kAuto = 0,
kMin = 1,
kLow = 2,
kMedium = 3,
kHigh = 4,
kMax = 5,
kMediumHigh = 6,
// Add new entries before this one, and update it to point to the last entry
kLastFanspeedEnum = kMediumHigh,
};
/// Common A/C settings for Vertical Swing.
enum class swingv_t {
kOff = -1,
kAuto = 0,
kHighest = 1,
kHigh = 2,
kMiddle = 3,
kLow = 4,
kLowest = 5,
kUpperMiddle = 6,
// Add new entries before this one, and update it to point to the last entry
kLastSwingvEnum = kUpperMiddle,
};
/// @brief Tyoe of A/C command (if the remote uses different codes for each)
/// @note Most remotes support only a single command or aggregate multiple
/// into one (e.g. control+timer). Use @c kControlCommand in such case
enum class ac_command_t {
kControlCommand = 0,
kSensorTempReport = 1,
kTimerCommand = 2,
kConfigCommand = 3,
// Add new entries before this one, and update it to point to the last entry
kLastAcCommandEnum = kConfigCommand,
};
/// Common A/C settings for Horizontal Swing.
enum class swingh_t {
kOff = -1,
kAuto = 0, // a.k.a. On.
kLeftMax = 1,
kLeft = 2,
kMiddle = 3,
kRight = 4,
kRightMax = 5,
kWide = 6, // a.k.a. left & right at the same time.
// Add new entries before this one, and update it to point to the last entry
kLastSwinghEnum = kWide,
};
/// Structure to hold a common A/C state.
struct state_t {
decode_type_t protocol = decode_type_t::UNKNOWN;
int16_t model = -1; // `-1` means unused.
bool power = false;
stdAc::opmode_t mode = stdAc::opmode_t::kOff;
float degrees = 25;
bool celsius = true;
stdAc::fanspeed_t fanspeed = stdAc::fanspeed_t::kAuto;
stdAc::swingv_t swingv = stdAc::swingv_t::kOff;
stdAc::swingh_t swingh = stdAc::swingh_t::kOff;
bool quiet = false;
bool turbo = false;
bool econo = false;
bool light = false;
bool filter = false;
bool clean = false;
bool beep = false;
int16_t sleep = -1; // `-1` means off.
int16_t clock = -1; // `-1` means not set.
stdAc::ac_command_t command = stdAc::ac_command_t::kControlCommand;
bool iFeel = false;
float sensorTemperature = kNoTempValue; // `kNoTempValue` means not set.
};
}; // namespace stdAc
/// Fujitsu A/C model numbers
enum fujitsu_ac_remote_model_t {
ARRAH2E = 1, ///< (1) AR-RAH2E, AR-RAC1E, AR-RAE1E, AR-RCE1E, AR-RAH2U,
///< AR-REG1U (Default)
///< Warning: Use on incorrect models can cause the A/C to lock
///< up, requring the A/C to be physically powered off to fix.
///< e.g. AR-RAH1U may lock up with a Swing command.
ARDB1, ///< (2) AR-DB1, AR-DL10 (AR-DL10 swing doesn't work)
ARREB1E, ///< (3) AR-REB1E, AR-RAH1U (Similar to ARRAH2E but no horiz
///< control)
ARJW2, ///< (4) AR-JW2 (Same as ARDB1 but with horiz control)
ARRY4, ///< (5) AR-RY4 (Same as AR-RAH2E but with clean & filter)
ARREW4E, ///< (6) Similar to ARRAH2E, but with different temp config.
};
/// Gree A/C model numbers
enum gree_ac_remote_model_t {
YAW1F = 1, // (1) Ultimate, EKOKAI, RusClimate (Default)
YBOFB, // (2) Green, YBOFB2, YAPOF3
YX1FSF, // (3) Soleus Air window unit (Similar to YAW1F, but with an
// Operation mode of Energy Saver (Econo))
};
/// HAIER_AC176 A/C model numbers
enum haier_ac176_remote_model_t {
V9014557_A = 1, // (1) V9014557 Remote in "A" setting. (Default)
V9014557_B, // (2) V9014557 Remote in "B" setting.
};
/// HITACHI_AC1 A/C model numbers
enum hitachi_ac1_remote_model_t {
R_LT0541_HTA_A = 1, // (1) R-LT0541-HTA Remote in "A" setting. (Default)
R_LT0541_HTA_B, // (2) R-LT0541-HTA Remote in "B" setting.
};
/// MIRAGE A/C model numbers
enum mirage_ac_remote_model_t {
KKG9AC1 = 1, // (1) KKG9A-C1 Remote. (Default)
KKG29AC1, // (2) KKG29A-C1 Remote.
};
/// Panasonic A/C model numbers
enum panasonic_ac_remote_model_t {
kPanasonicUnknown = 0,
kPanasonicLke = 1,
kPanasonicNke = 2,
kPanasonicDke = 3, // PKR too.
kPanasonicJke = 4,
kPanasonicCkp = 5,
kPanasonicRkr = 6,
};
/// Sharp A/C model numbers
enum sharp_ac_remote_model_t {
A907 = 1,
A705 = 2,
A903 = 3, // 820 too
};
/// TCL (& Teknopoint) A/C model numbers
enum tcl_ac_remote_model_t {
TAC09CHSD = 1,
GZ055BE1 = 2, // Also Teknopoint GZ01-BEJ0-000
};
/// Voltas A/C model numbers
enum voltas_ac_remote_model_t {
kVoltasUnknown = 0, // Full Function
kVoltas122LZF = 1, // (1) 122LZF (No SwingH support) (Default)
};
/// Whirlpool A/C model numbers
enum whirlpool_ac_remote_model_t {
DG11J13A = 1, // DG11J1-04 too
DG11J191,
};
/// LG A/C model numbers
enum lg_ac_remote_model_t {
GE6711AR2853M = 1, // (1) LG 28-bit Protocol (default)
AKB75215403, // (2) LG2 28-bit Protocol
AKB74955603, // (3) LG2 28-bit Protocol variant
AKB73757604, // (4) LG2 Variant of AKB74955603
LG6711A20083V, // (5) Same as GE6711AR2853M, but only SwingV toggle.
};
/// Argo A/C model numbers
enum argo_ac_remote_model_t {
SAC_WREM2 = 1, // (1) ARGO WREM2 remote (default)
SAC_WREM3 // (2) ARGO WREM3 remote (touch buttons), bit-len vary by cmd
};
// Classes
/// Class for sending all basic IR protocols.
/// @note Originally from https://github.com/shirriff/Arduino-IRremote/
/// Updated by markszabo (https://github.com/crankyoldgit/IRremoteESP8266) for
/// sending IR code on ESP8266
class IRsend {
public:
explicit IRsend(uint16_t IRsendPin, bool inverted = false,
bool use_modulation = true);
void begin();
void enableIROut(uint32_t freq, uint8_t duty = kDutyDefault);
VIRTUAL void _delayMicroseconds(uint32_t usec);
VIRTUAL uint16_t mark(uint16_t usec);
VIRTUAL void space(uint32_t usec);
int8_t calibrate(uint16_t hz = 38000U);
void sendRaw(const uint16_t buf[], const uint16_t len, const uint16_t hz);
void sendData(uint16_t onemark, uint32_t onespace, uint16_t zeromark,
uint32_t zerospace, uint64_t data, uint16_t nbits,
bool MSBfirst = true);
void sendManchesterData(const uint16_t half_period, const uint64_t data,
const uint16_t nbits, const bool MSBfirst = true,
const bool GEThomas = true);
void sendManchester(const uint16_t headermark, const uint32_t headerspace,
const uint16_t half_period, const uint16_t footermark,
const uint32_t gap, const uint64_t data,
const uint16_t nbits, const uint16_t frequency = 38,
const bool MSBfirst = true,
const uint16_t repeat = kNoRepeat,
const uint8_t dutycycle = kDutyDefault,
const bool GEThomas = true);
void sendGeneric(const uint16_t headermark, const uint32_t headerspace,
const uint16_t onemark, const uint32_t onespace,
const uint16_t zeromark, const uint32_t zerospace,
const uint16_t footermark, const uint32_t gap,
const uint64_t data, const uint16_t nbits,
const uint16_t frequency, const bool MSBfirst,
const uint16_t repeat, const uint8_t dutycycle);
void sendGeneric(const uint16_t headermark, const uint32_t headerspace,
const uint16_t onemark, const uint32_t onespace,
const uint16_t zeromark, const uint32_t zerospace,
const uint16_t footermark, const uint32_t gap,
const uint32_t mesgtime, const uint64_t data,
const uint16_t nbits, const uint16_t frequency,
const bool MSBfirst, const uint16_t repeat,
const uint8_t dutycycle);
void sendGeneric(const uint16_t headermark, const uint32_t headerspace,
const uint16_t onemark, const uint32_t onespace,
const uint16_t zeromark, const uint32_t zerospace,
const uint16_t footermark, const uint32_t gap,
const uint8_t *dataptr, const uint16_t nbytes,
const uint16_t frequency, const bool MSBfirst,
const uint16_t repeat, const uint8_t dutycycle);
static uint16_t minRepeats(const decode_type_t protocol);
static uint16_t defaultBits(const decode_type_t protocol);
bool send(const decode_type_t type, const uint64_t data,
const uint16_t nbits, const uint16_t repeat = kNoRepeat);
bool send(const decode_type_t type, const uint8_t *state,
const uint16_t nbytes);
#if (SEND_NEC || SEND_SHERWOOD || SEND_AIWA_RC_T501 || SEND_SANYO || \
SEND_MIDEA24)
void sendNEC(uint64_t data, uint16_t nbits = kNECBits,
uint16_t repeat = kNoRepeat);
uint32_t encodeNEC(uint16_t address, uint16_t command);
#endif
#if SEND_SONY
// sendSony() should typically be called with repeat=2 as Sony devices
// expect the code to be sent at least 3 times. (code + 2 repeats = 3 codes)
// Legacy use of this procedure was to only send a single code so call it with
// repeat=0 for backward compatibility. As of v2.0 it defaults to sending
// a Sony command that will be accepted be a device.
void sendSony(const uint64_t data, const uint16_t nbits = kSony20Bits,
const uint16_t repeat = kSonyMinRepeat);
void sendSony38(const uint64_t data, const uint16_t nbits = kSony20Bits,
const uint16_t repeat = kSonyMinRepeat + 1);
uint32_t encodeSony(const uint16_t nbits, const uint16_t command,
const uint16_t address, const uint16_t extended = 0);
#endif // SEND_SONY
#if SEND_SHERWOOD
void sendSherwood(uint64_t data, uint16_t nbits = kSherwoodBits,
uint16_t repeat = kSherwoodMinRepeat);
#endif
// `sendSAMSUNG()` is required by `sendLG()`
#if (SEND_SAMSUNG || SEND_LG)
void sendSAMSUNG(const uint64_t data, const uint16_t nbits = kSamsungBits,
const uint16_t repeat = kNoRepeat);
uint32_t encodeSAMSUNG(const uint8_t customer, const uint8_t command);
#endif // (SEND_SAMSUNG || SEND_LG)
#if SEND_SAMSUNG36
void sendSamsung36(const uint64_t data, const uint16_t nbits = kSamsung36Bits,
const uint16_t repeat = kNoRepeat);
#endif
#if SEND_SAMSUNG_AC
void sendSamsungAC(const unsigned char data[],
const uint16_t nbytes = kSamsungAcStateLength,
const uint16_t repeat = kSamsungAcDefaultRepeat);
#endif
#if SEND_LG
void sendLG(uint64_t data, uint16_t nbits = kLgBits,
uint16_t repeat = kNoRepeat);
void sendLG2(uint64_t data, uint16_t nbits = kLgBits,
uint16_t repeat = kNoRepeat);
uint32_t encodeLG(uint16_t address, uint16_t command);
#endif
#if (SEND_SHARP || SEND_DENON)
uint32_t encodeSharp(const uint16_t address, const uint16_t command,
const uint16_t expansion = 1, const uint16_t check = 0,
const bool MSBfirst = false);
void sendSharp(const uint16_t address, const uint16_t command,
const uint16_t nbits = kSharpBits,
const uint16_t repeat = kNoRepeat);
void sendSharpRaw(const uint64_t data, const uint16_t nbits = kSharpBits,
const uint16_t repeat = kNoRepeat);
#endif
#if SEND_SHARP_AC
void sendSharpAc(const unsigned char data[],
const uint16_t nbytes = kSharpAcStateLength,
const uint16_t repeat = kSharpAcDefaultRepeat);
#endif // SEND_SHARP_AC
#if SEND_JVC
void sendJVC(uint64_t data, uint16_t nbits = kJvcBits,
uint16_t repeat = kNoRepeat);
uint16_t encodeJVC(uint8_t address, uint8_t command);
#endif
#if SEND_DENON
void sendDenon(uint64_t data, uint16_t nbits = kDenonBits,
uint16_t repeat = kNoRepeat);
#endif
#if SEND_SANYO
uint64_t encodeSanyoLC7461(uint16_t address, uint8_t command);
void sendSanyoLC7461(const uint64_t data,
const uint16_t nbits = kSanyoLC7461Bits,
const uint16_t repeat = kNoRepeat);
#endif
#if SEND_SANYO_AC
void sendSanyoAc(const uint8_t *data,
const uint16_t nbytes = kSanyoAcStateLength,
const uint16_t repeat = kNoRepeat);
#endif // SEND_SANYO_AC
#if SEND_SANYO_AC88
void sendSanyoAc88(const uint8_t *data,
const uint16_t nbytes = kSanyoAc88StateLength,
const uint16_t repeat = kSanyoAc88MinRepeat);
#endif // SEND_SANYO_AC88
#if SEND_SANYO_AC152
void sendSanyoAc152(const uint8_t *data,
const uint16_t nbytes = kSanyoAc152StateLength,
const uint16_t repeat = kSanyoAc152MinRepeat);
#endif // SEND_SANYO_AC152
#if SEND_DISH
// sendDISH() should typically be called with repeat=3 as DISH devices
// expect the code to be sent at least 4 times. (code + 3 repeats = 4 codes)
// Legacy use of this procedure was only to send a single code
// so use repeat=0 for backward compatibility.
void sendDISH(uint64_t data, uint16_t nbits = kDishBits,
uint16_t repeat = kDishMinRepeat);
#endif
#if (SEND_PANASONIC || SEND_DENON)
void sendPanasonic64(const uint64_t data,
const uint16_t nbits = kPanasonicBits,
const uint16_t repeat = kNoRepeat);
void sendPanasonic(const uint16_t address, const uint32_t data,
const uint16_t nbits = kPanasonicBits,
const uint16_t repeat = kNoRepeat);
uint64_t encodePanasonic(const uint16_t manufacturer, const uint8_t device,
const uint8_t subdevice, const uint8_t function);
#endif
#if SEND_RC5
void sendRC5(const uint64_t data, uint16_t nbits = kRC5XBits,
const uint16_t repeat = kNoRepeat);
uint16_t encodeRC5(const uint8_t address, const uint8_t command,
const bool key_released = false);
uint16_t encodeRC5X(const uint8_t address, const uint8_t command,
const bool key_released = false);
uint64_t toggleRC5(const uint64_t data);
#endif
#if SEND_RC6
void sendRC6(const uint64_t data, const uint16_t nbits = kRC6Mode0Bits,
const uint16_t repeat = kNoRepeat);
uint64_t encodeRC6(const uint32_t address, const uint8_t command,
const uint16_t mode = kRC6Mode0Bits);
uint64_t toggleRC6(const uint64_t data, const uint16_t nbits = kRC6Mode0Bits);
#endif
#if SEND_RCMM
void sendRCMM(uint64_t data, uint16_t nbits = kRCMMBits,
uint16_t repeat = kNoRepeat);
#endif
#if SEND_COOLIX
void sendCOOLIX(const uint64_t data, const uint16_t nbits = kCoolixBits,
const uint16_t repeat = kCoolixDefaultRepeat);
#endif // SEND_COOLIX
#if SEND_COOLIX48
void sendCoolix48(const uint64_t data, const uint16_t nbits = kCoolix48Bits,
const uint16_t repeat = kCoolixDefaultRepeat);
#endif // SEND_COOLIX48
#if SEND_WHYNTER
void sendWhynter(const uint64_t data, const uint16_t nbits = kWhynterBits,
const uint16_t repeat = kNoRepeat);
#endif
#if SEND_MIRAGE
void sendMirage(const unsigned char data[],
const uint16_t nbytes = kMirageStateLength,
const uint16_t repeat = kMirageMinRepeat);
#endif // SEND_MIRAGE
#if SEND_MITSUBISHI
void sendMitsubishi(uint64_t data, uint16_t nbits = kMitsubishiBits,
uint16_t repeat = kMitsubishiMinRepeat);
#endif
#if SEND_MITSUBISHI136
void sendMitsubishi136(const unsigned char data[],
const uint16_t nbytes = kMitsubishi136StateLength,
const uint16_t repeat = kMitsubishi136MinRepeat);
#endif
#if SEND_MITSUBISHI112
void sendMitsubishi112(const unsigned char data[],
const uint16_t nbytes = kMitsubishi112StateLength,
const uint16_t repeat = kMitsubishi112MinRepeat);
#endif
#if SEND_MITSUBISHI2
void sendMitsubishi2(uint64_t data, uint16_t nbits = kMitsubishiBits,
uint16_t repeat = kMitsubishiMinRepeat);
#endif
#if SEND_MITSUBISHI_AC
void sendMitsubishiAC(const unsigned char data[],
const uint16_t nbytes = kMitsubishiACStateLength,
const uint16_t repeat = kMitsubishiACMinRepeat);
#endif
#if SEND_MITSUBISHIHEAVY
void sendMitsubishiHeavy88(
const unsigned char data[],
const uint16_t nbytes = kMitsubishiHeavy88StateLength,
const uint16_t repeat = kMitsubishiHeavy88MinRepeat);
void sendMitsubishiHeavy152(
const unsigned char data[],
const uint16_t nbytes = kMitsubishiHeavy152StateLength,
const uint16_t repeat = kMitsubishiHeavy152MinRepeat);
#endif
#if SEND_FUJITSU_AC
void sendFujitsuAC(const unsigned char data[], const uint16_t nbytes,
const uint16_t repeat = kFujitsuAcMinRepeat);
#endif
#if SEND_INAX
void sendInax(const uint64_t data, const uint16_t nbits = kInaxBits,
const uint16_t repeat = kInaxMinRepeat);
#endif // SEND_INAX
#if SEND_GLOBALCACHE
void sendGC(uint16_t buf[], uint16_t len);
#endif
#if SEND_KELVINATOR
void sendKelvinator(const unsigned char data[],
const uint16_t nbytes = kKelvinatorStateLength,
const uint16_t repeat = kKelvinatorDefaultRepeat);
#endif
#if SEND_DAIKIN
void sendDaikin(const unsigned char data[],
const uint16_t nbytes = kDaikinStateLength,
const uint16_t repeat = kDaikinDefaultRepeat);
#endif
#if SEND_DAIKIN64
void sendDaikin64(const uint64_t data, const uint16_t nbits = kDaikin64Bits,
const uint16_t repeat = kDaikin64DefaultRepeat);
#endif // SEND_DAIKIN64
#if SEND_DAIKIN128
void sendDaikin128(const unsigned char data[],
const uint16_t nbytes = kDaikin128StateLength,
const uint16_t repeat = kDaikin128DefaultRepeat);
#endif // SEND_DAIKIN128
#if SEND_DAIKIN152
void sendDaikin152(const unsigned char data[],
const uint16_t nbytes = kDaikin152StateLength,
const uint16_t repeat = kDaikin152DefaultRepeat);
#endif // SEND_DAIKIN152
#if SEND_DAIKIN160
void sendDaikin160(const unsigned char data[],
const uint16_t nbytes = kDaikin160StateLength,
const uint16_t repeat = kDaikin160DefaultRepeat);
#endif // SEND_DAIKIN160
#if SEND_DAIKIN176
void sendDaikin176(const unsigned char data[],
const uint16_t nbytes = kDaikin176StateLength,
const uint16_t repeat = kDaikin176DefaultRepeat);
#endif // SEND_DAIKIN176
#if SEND_DAIKIN2
void sendDaikin2(const unsigned char data[],
const uint16_t nbytes = kDaikin2StateLength,
const uint16_t repeat = kDaikin2DefaultRepeat);
#endif
#if SEND_DAIKIN200
void sendDaikin200(const unsigned char data[],
const uint16_t nbytes = kDaikin200StateLength,
const uint16_t repeat = kDaikin200DefaultRepeat);
#endif // SEND_DAIKIN200
#if SEND_DAIKIN216
void sendDaikin216(const unsigned char data[],
const uint16_t nbytes = kDaikin216StateLength,
const uint16_t repeat = kDaikin216DefaultRepeat);
#endif // SEND_DAIKIN216
#if SEND_DAIKIN312
void sendDaikin312(const unsigned char data[],
const uint16_t nbytes = kDaikin312StateLength,
const uint16_t repeat = kDaikin312DefaultRepeat);
#endif // SEND_DAIKIN312
#if SEND_AIWA_RC_T501
void sendAiwaRCT501(uint64_t data, uint16_t nbits = kAiwaRcT501Bits,
uint16_t repeat = kAiwaRcT501MinRepeats);
#endif
#if SEND_GREE
void sendGree(const uint64_t data, const uint16_t nbits = kGreeBits,
const uint16_t repeat = kGreeDefaultRepeat);
void sendGree(const uint8_t data[], const uint16_t nbytes = kGreeStateLength,
const uint16_t repeat = kGreeDefaultRepeat);
#endif
#if SEND_GOODWEATHER
void sendGoodweather(const uint64_t data,
const uint16_t nbits = kGoodweatherBits,
const uint16_t repeat = kGoodweatherMinRepeat);
#endif // SEND_GOODWEATHER
#if SEND_GORENJE
void sendGorenje(const uint64_t data, const uint16_t nbits = kGorenjeBits,
const uint16_t repeat = kNoRepeat);
#endif // SEND_GORENJE
#if SEND_PRONTO
void sendPronto(uint16_t data[], uint16_t len, uint16_t repeat = kNoRepeat);
#endif
#if SEND_ARGO
void sendArgo(const unsigned char data[],
const uint16_t nbytes = kArgoStateLength,
const uint16_t repeat = kArgoDefaultRepeat,
bool sendFooter = false);
void sendArgoWREM3(const unsigned char data[],
const uint16_t nbytes = kArgoStateLength,
const uint16_t repeat = kArgoDefaultRepeat);
#endif // SEND_ARGO
#if SEND_TROTEC
void sendTrotec(const unsigned char data[],
const uint16_t nbytes = kTrotecStateLength,
const uint16_t repeat = kTrotecDefaultRepeat);
#endif // SEND_TROTEC
#if SEND_TROTEC_3550
void sendTrotec3550(const unsigned char data[],
const uint16_t nbytes = kTrotecStateLength,
const uint16_t repeat = kTrotecDefaultRepeat);
#endif // SEND_TROTEC_3550
#if SEND_NIKAI
void sendNikai(uint64_t data, uint16_t nbits = kNikaiBits,
uint16_t repeat = kNoRepeat);
#endif
#if SEND_TOSHIBA_AC
void sendToshibaAC(const uint8_t data[],
const uint16_t nbytes = kToshibaACStateLength,
const uint16_t repeat = kToshibaACMinRepeat);
#endif
#if SEND_MIDEA
void sendMidea(uint64_t data, uint16_t nbits = kMideaBits,
uint16_t repeat = kMideaMinRepeat);
#endif // SEND_MIDEA
#if SEND_MIDEA24
void sendMidea24(const uint64_t data, const uint16_t nbits = kMidea24Bits,
const uint16_t repeat = kMidea24MinRepeat);
#endif // SEND_MIDEA24
#if SEND_MAGIQUEST
void sendMagiQuest(const uint64_t data, const uint16_t nbits = kMagiquestBits,
const uint16_t repeat = kNoRepeat);
uint64_t encodeMagiQuest(const uint32_t wand_id, const uint16_t magnitude);
#endif
#if SEND_LASERTAG
void sendLasertag(uint64_t data, uint16_t nbits = kLasertagBits,
uint16_t repeat = kLasertagMinRepeat);
#endif
#if SEND_CARRIER_AC
void sendCarrierAC(uint64_t data, uint16_t nbits = kCarrierAcBits,
uint16_t repeat = kCarrierAcMinRepeat);
#endif
#if SEND_CARRIER_AC40
void sendCarrierAC40(uint64_t data, uint16_t nbits = kCarrierAc40Bits,
uint16_t repeat = kCarrierAc40MinRepeat);
#endif
#if SEND_CARRIER_AC64
void sendCarrierAC64(uint64_t data, uint16_t nbits = kCarrierAc64Bits,
uint16_t repeat = kCarrierAc64MinRepeat);
#endif
#if SEND_CARRIER_AC84
void sendCarrierAC84(const uint8_t data[],
const uint16_t nbytes = kCarrierAc84StateLength,
const uint16_t repeat = kNoRepeat);
#endif // SEND_CARRIER_AC84
#if SEND_CARRIER_AC128
void sendCarrierAC128(const uint8_t data[],
uint16_t nbytes = kCarrierAc128StateLength,
uint16_t repeat = kCarrierAc128MinRepeat);
#endif // SEND_CARRIER_AC128
#if (SEND_HAIER_AC || SEND_HAIER_AC_YRW02 || SEND_HAIER_AC176)
void sendHaierAC(const unsigned char data[],
const uint16_t nbytes = kHaierACStateLength,
const uint16_t repeat = kHaierAcDefaultRepeat);
#endif // (SEND_HAIER_AC || SEND_HAIER_AC_YRW02 || SEND_HAIER_AC176)
#if SEND_HAIER_AC_YRW02
void sendHaierACYRW02(const unsigned char data[],
const uint16_t nbytes = kHaierACYRW02StateLength,
const uint16_t repeat = kHaierAcYrw02DefaultRepeat);
#endif // SEND_HAIER_AC_YRW02
#if SEND_HAIER_AC160
void sendHaierAC160(const unsigned char data[],
const uint16_t nbytes = kHaierAC160StateLength,
const uint16_t repeat = kHaierAc160DefaultRepeat);
#endif // SEND_HAIER_AC160
#if SEND_HAIER_AC176
void sendHaierAC176(const unsigned char data[],
const uint16_t nbytes = kHaierAC176StateLength,
const uint16_t repeat = kHaierAc176DefaultRepeat);
#endif // SEND_HAIER_AC176
#if SEND_HITACHI_AC
void sendHitachiAC(const unsigned char data[],
const uint16_t nbytes = kHitachiAcStateLength,
const uint16_t repeat = kHitachiAcDefaultRepeat);
#endif
#if SEND_HITACHI_AC1
void sendHitachiAC1(const unsigned char data[],
const uint16_t nbytes = kHitachiAc1StateLength,
const uint16_t repeat = kHitachiAcDefaultRepeat);
#endif
#if SEND_HITACHI_AC2
void sendHitachiAC2(const unsigned char data[],
const uint16_t nbytes = kHitachiAc2StateLength,
const uint16_t repeat = kHitachiAcDefaultRepeat);
#endif
#if SEND_HITACHI_AC3
void sendHitachiAc3(const unsigned char data[],
const uint16_t nbytes, // No default as there as so many
// different sizes
const uint16_t repeat = kHitachiAcDefaultRepeat);
#endif // SEND_HITACHI_AC3
#if SEND_HITACHI_AC264
void sendHitachiAc264(const unsigned char data[],
const uint16_t nbytes = kHitachiAc264StateLength,
const uint16_t repeat = kHitachiAcDefaultRepeat);
#endif // SEND_HITACHI_AC264
#if SEND_HITACHI_AC296
void sendHitachiAc296(const unsigned char data[],
const uint16_t nbytes = kHitachiAc296StateLength,
const uint16_t repeat = kHitachiAcDefaultRepeat);
#endif // SEND_HITACHI_AC296
#if SEND_HITACHI_AC344
void sendHitachiAc344(const unsigned char data[],
const uint16_t nbytes = kHitachiAc344StateLength,
const uint16_t repeat = kHitachiAcDefaultRepeat);
#endif // SEND_HITACHI_AC344
#if SEND_HITACHI_AC424
void sendHitachiAc424(const unsigned char data[],
const uint16_t nbytes = kHitachiAc424StateLength,
const uint16_t repeat = kHitachiAcDefaultRepeat);
#endif // SEND_HITACHI_AC424
#if SEND_GICABLE
void sendGICable(uint64_t data, uint16_t nbits = kGicableBits,
uint16_t repeat = kGicableMinRepeat);
#endif
#if SEND_WHIRLPOOL_AC
void sendWhirlpoolAC(const unsigned char data[],
const uint16_t nbytes = kWhirlpoolAcStateLength,
const uint16_t repeat = kWhirlpoolAcDefaultRepeat);
#endif
#if SEND_LUTRON
void sendLutron(uint64_t data, uint16_t nbits = kLutronBits,
uint16_t repeat = kNoRepeat);
#endif
#if SEND_ELECTRA_AC
void sendElectraAC(const unsigned char data[],
const uint16_t nbytes = kElectraAcStateLength,
const uint16_t repeat = kNoRepeat);
#endif
#if SEND_PANASONIC_AC
void sendPanasonicAC(const unsigned char data[],
const uint16_t nbytes = kPanasonicAcStateLength,
const uint16_t repeat = kPanasonicAcDefaultRepeat);
#endif // SEND_PANASONIC_AC
#if SEND_PANASONIC_AC32
void sendPanasonicAC32(const uint64_t data,
const uint16_t nbits = kPanasonicAc32Bits,
const uint16_t repeat = kPanasonicAcDefaultRepeat);
#endif // SEND_PANASONIC_AC32
#if SEND_PIONEER
void sendPioneer(const uint64_t data, const uint16_t nbits = kPioneerBits,
const uint16_t repeat = kNoRepeat);
uint64_t encodePioneer(uint16_t address, uint16_t command);
#endif
#if SEND_MWM
void sendMWM(const unsigned char data[], const uint16_t nbytes,
const uint16_t repeat = kNoRepeat);
#endif
#if SEND_VESTEL_AC
void sendVestelAc(const uint64_t data, const uint16_t nbits = kVestelAcBits,
const uint16_t repeat = kNoRepeat);
#endif
#if SEND_TCL96AC
void sendTcl96Ac(const unsigned char data[],
const uint16_t nbytes = kTcl96AcStateLength,
const uint16_t repeat = kTcl96AcDefaultRepeat);
#endif // SEND_TCL96AC
#if SEND_TCL112AC
void sendTcl112Ac(const unsigned char data[],
const uint16_t nbytes = kTcl112AcStateLength,
const uint16_t repeat = kTcl112AcDefaultRepeat);
#endif // SEND_TCL112AC
#if SEND_TECO
void sendTeco(const uint64_t data, const uint16_t nbits = kTecoBits,
const uint16_t repeat = kNoRepeat);
#endif
#if SEND_LEGOPF
void sendLegoPf(const uint64_t data, const uint16_t nbits = kLegoPfBits,
const uint16_t repeat = kLegoPfMinRepeat);
#endif
#if SEND_NEOCLIMA
void sendNeoclima(const unsigned char data[],
const uint16_t nbytes = kNeoclimaStateLength,
const uint16_t repeat = kNeoclimaMinRepeat);
#endif // SEND_NEOCLIMA
#if SEND_AMCOR
void sendAmcor(const unsigned char data[],
const uint16_t nbytes = kAmcorStateLength,
const uint16_t repeat = kAmcorDefaultRepeat);
#endif // SEND_AMCOR
#if SEND_EPSON
void sendEpson(uint64_t data, uint16_t nbits = kEpsonBits,
uint16_t repeat = kEpsonMinRepeat);
#endif
#if SEND_SYMPHONY
void sendSymphony(uint64_t data, uint16_t nbits = kSymphonyBits,
uint16_t repeat = kSymphonyDefaultRepeat);
#endif
#if SEND_AIRWELL
void sendAirwell(uint64_t data, uint16_t nbits = kAirwellBits,
uint16_t repeat = kAirwellMinRepeats);
#endif
#if SEND_DELONGHI_AC
void sendDelonghiAc(uint64_t data, uint16_t nbits = kDelonghiAcBits,
uint16_t repeat = kDelonghiAcDefaultRepeat);
#endif
#if SEND_DOSHISHA
void sendDoshisha(const uint64_t data, uint16_t nbits = kDoshishaBits,
const uint16_t repeat = kNoRepeat);
uint64_t encodeDoshisha(const uint8_t command, const uint8_t channel = 0);
#endif // SEND_DOSHISHA
#if SEND_MULTIBRACKETS
void sendMultibrackets(const uint64_t data,
const uint16_t nbits = kMultibracketsBits,
const uint16_t repeat = kMultibracketsDefaultRepeat);
#endif
#if SEND_TECHNIBEL_AC
void sendTechnibelAc(uint64_t data, uint16_t nbits = kTechnibelAcBits,
uint16_t repeat = kTechnibelAcDefaultRepeat);
#endif
#if SEND_CORONA_AC
void sendCoronaAc(const uint8_t data[],
const uint16_t nbytes = kCoronaAcStateLength,
const uint16_t repeat = kNoRepeat);
#endif // SEND_CORONA_AC
#if SEND_ZEPEAL
void sendZepeal(const uint64_t data,
const uint16_t nbits = kZepealBits,
const uint16_t repeat = kZepealMinRepeat);
#endif // SEND_ZEPEAL
#if SEND_VOLTAS
void sendVoltas(const unsigned char data[],
const uint16_t nbytes = kVoltasStateLength,
const uint16_t repeat = kNoRepeat);
#endif // SEND_VOLTAS
#if SEND_METZ
void sendMetz(const uint64_t data,
const uint16_t nbits = kMetzBits,
const uint16_t repeat = kMetzMinRepeat);
static uint32_t encodeMetz(const uint8_t address, const uint8_t command,
const bool toggle = false);
#endif // SEND_METZ
#if SEND_TRANSCOLD
void sendTranscold(const uint64_t data, const uint16_t nbits = kTranscoldBits,
const uint16_t repeat = kTranscoldDefaultRepeat);
#endif // SEND_TRANSCOLD
#if SEND_ELITESCREENS
void sendElitescreens(const uint64_t data,
const uint16_t nbits = kEliteScreensBits,
const uint16_t repeat = kEliteScreensDefaultRepeat);
#endif // SEND_ELITESCREENS
#if SEND_MILESTAG2
// Since There 2 types of transmissions
// (14bits for Shooting by default, you can set 24 bit for msg delivery)
void sendMilestag2(const uint64_t data,
const uint16_t nbits = kMilesTag2ShotBits,
const uint16_t repeat = kMilesMinRepeat);
#endif // SEND_MILESTAG2
#if SEND_ECOCLIM
void sendEcoclim(const uint64_t data, const uint16_t nbits = kEcoclimBits,
const uint16_t repeat = kNoRepeat);
#endif // SEND_ECOCLIM
#if SEND_XMP
void sendXmp(const uint64_t data, const uint16_t nbits = kXmpBits,
const uint16_t repeat = kNoRepeat);
#endif // SEND_XMP
#if SEND_TRUMA
void sendTruma(const uint64_t data, const uint16_t nbits = kTrumaBits,
const uint16_t repeat = kNoRepeat);
#endif // SEND_TRUMA
#if SEND_TEKNOPOINT
void sendTeknopoint(const unsigned char data[],
const uint16_t nbytes = kTeknopointStateLength,
const uint16_t repeat = kNoRepeat);
#endif // SEND_TEKNOPOINT
#if SEND_KELON
void sendKelon(const uint64_t data, const uint16_t nbits = kKelonBits,
const uint16_t repeat = kNoRepeat);
#endif // SEND_KELON
#if SEND_KELON168
void sendKelon168(const unsigned char data[],
const uint16_t nbytes = kKelon168StateLength,
const uint16_t repeat = kNoRepeat);
#endif // SEND_KELON168
#if SEND_BOSE
void sendBose(const uint64_t data, const uint16_t nbits = kBoseBits,
const uint16_t repeat = kNoRepeat);
#endif // SEND_BOSE
#if SEND_ARRIS
void sendArris(const uint64_t data, const uint16_t nbits = kArrisBits,
const uint16_t repeat = kNoRepeat);
static uint32_t toggleArrisRelease(const uint32_t data);
static uint32_t encodeArris(const uint32_t command, const bool release);
#endif // SEND_ARRIS
#if SEND_RHOSS
void sendRhoss(const unsigned char data[],
const uint16_t nbytes = kRhossStateLength,
const uint16_t repeat = kRhossDefaultRepeat);
#endif // SEND_RHOSS
#if SEND_AIRTON
void sendAirton(const uint64_t data, const uint16_t nbits = kAirtonBits,
const uint16_t repeat = kAirtonDefaultRepeat);
#endif // SEND_AIRTON
#if SEND_TOTO
void sendToto(const uint64_t data, const uint16_t nbits = kTotoBits,
const uint16_t repeat = kTotoDefaultRepeat);
#endif // SEND_TOTO
#if SEND_CLIMABUTLER
void sendClimaButler(const uint64_t data,
const uint16_t nbits = kClimaButlerBits,
const uint16_t repeat = kNoRepeat);
#endif // SEND_CLIMABUTLER
#if SEND_BOSCH144
void sendBosch144(const unsigned char data[],
const uint16_t nbytes = kBosch144StateLength,
const uint16_t repeat = kNoRepeat);
#endif // SEND_BOSCH144
#if SEND_WOWWEE
void sendWowwee(const uint64_t data, const uint16_t nbits = kWowweeBits,
const uint16_t repeat = kWowweeDefaultRepeat);
#endif // SEND_WOWWEE
#if SEND_YORK
void sendYork(const unsigned char data[],
const uint16_t nbytes = kYorkStateLength,
const uint16_t repeat = kNoRepeat);
#endif // SEND_YORK
protected:
#ifdef UNIT_TEST
#ifndef HIGH
#define HIGH 0x1
#endif
#ifndef LOW
#define LOW 0x0
#endif
#endif // UNIT_TEST
uint8_t outputOn;
uint8_t outputOff;
VIRTUAL void ledOff();
VIRTUAL void ledOn();
#ifndef UNIT_TEST
private:
#else
uint32_t _freq_unittest;
#endif // UNIT_TEST
uint16_t onTimePeriod;
uint16_t offTimePeriod;
uint16_t IRpin;
int8_t periodOffset;
uint8_t _dutycycle;
bool modulation;
uint32_t calcUSecPeriod(uint32_t hz, bool use_offset = true);
#if SEND_SONY
void _sendSony(const uint64_t data, const uint16_t nbits,
const uint16_t repeat, const uint16_t freq);
#endif // SEND_SONY
};
#endif // IRSEND_H_

View File

@@ -0,0 +1,561 @@
// Copyright 2019-2021 - David Conran (@crankyoldgit)
/// @file IRtext.cpp
/// @warning If you add or remove an entry in this file, you should run:
/// '../tools/generate_irtext_h.sh' to rebuild the `IRtext.h` file.
#include "IRtext.h"
#ifndef UNIT_TEST
#include <Arduino.h>
#endif // UNIT_TEST
#include "IRremoteESP8266.h"
#include "i18n.h"
#include "IRmacros.h"
#ifndef PROGMEM
#define PROGMEM // Pretend we have the PROGMEM macro even if we really don't.
#endif
#ifndef FPSTR
#define FPSTR(X) X // Also pretend we have flash-string helper class cast.
#endif
#define IRTEXT_CONST_BLOB_NAME(NAME)\
NAME ## Blob
#define IRTEXT_CONST_BLOB_DECL(NAME)\
const char IRTEXT_CONST_BLOB_NAME(NAME) [] PROGMEM
#define IRTEXT_CONST_BLOB_PTR(NAME)\
IRTEXT_CONST_PTR(NAME) {\
IRTEXT_CONST_PTR_CAST(IRTEXT_CONST_BLOB_NAME(NAME)) }
#define IRTEXT_CONST_STRING(NAME, VALUE)\
static IRTEXT_CONST_BLOB_DECL(NAME) { VALUE };\
IRTEXT_CONST_PTR(NAME) PROGMEM {\
IRTEXT_CONST_PTR_CAST(&(IRTEXT_CONST_BLOB_NAME(NAME))[0]) }
// Common
IRTEXT_CONST_STRING(kUnknownStr, D_STR_UNKNOWN); ///< "Unknown"
IRTEXT_CONST_STRING(kProtocolStr, D_STR_PROTOCOL); ///< "Protocol"
IRTEXT_CONST_STRING(kPowerStr, D_STR_POWER); ///< "Power"
IRTEXT_CONST_STRING(kOnStr, D_STR_ON); ///< "On"
IRTEXT_CONST_STRING(kOffStr, D_STR_OFF); ///< "Off"
IRTEXT_CONST_STRING(k1Str, D_STR_1); ///< "1"
IRTEXT_CONST_STRING(k0Str, D_STR_0); ///< "0"
IRTEXT_CONST_STRING(kModeStr, D_STR_MODE); ///< "Mode"
IRTEXT_CONST_STRING(kToggleStr, D_STR_TOGGLE); ///< "Toggle"
IRTEXT_CONST_STRING(kTurboStr, D_STR_TURBO); ///< "Turbo"
IRTEXT_CONST_STRING(kSuperStr, D_STR_SUPER); ///< "Super"
IRTEXT_CONST_STRING(kSleepStr, D_STR_SLEEP); ///< "Sleep"
IRTEXT_CONST_STRING(kLightStr, D_STR_LIGHT); ///< "Light"
IRTEXT_CONST_STRING(kPowerfulStr, D_STR_POWERFUL); ///< "Powerful"
IRTEXT_CONST_STRING(kQuietStr, D_STR_QUIET); ///< "Quiet"
IRTEXT_CONST_STRING(kEconoStr, D_STR_ECONO); ///< "Econo"
IRTEXT_CONST_STRING(kSwingStr, D_STR_SWING); ///< "Swing"
IRTEXT_CONST_STRING(kSwingHStr, D_STR_SWINGH); ///< "SwingH"
IRTEXT_CONST_STRING(kSwingVStr, D_STR_SWINGV); ///< "SwingV"
IRTEXT_CONST_STRING(kBeepStr, D_STR_BEEP); ///< "Beep"
IRTEXT_CONST_STRING(kZoneFollowStr, D_STR_ZONEFOLLOW); ///< "Zone Follow"
IRTEXT_CONST_STRING(kFixedStr, D_STR_FIXED); ///< "Fixed"
IRTEXT_CONST_STRING(kMouldStr, D_STR_MOULD); ///< "Mould"
IRTEXT_CONST_STRING(kCleanStr, D_STR_CLEAN); ///< "Clean"
IRTEXT_CONST_STRING(kPurifyStr, D_STR_PURIFY); ///< "Purify"
IRTEXT_CONST_STRING(kTimerStr, D_STR_TIMER); ///< "Timer"
IRTEXT_CONST_STRING(kOnTimerStr, D_STR_ONTIMER); ///< "On Timer"
IRTEXT_CONST_STRING(kOffTimerStr, D_STR_OFFTIMER); ///< "Off Timer"
IRTEXT_CONST_STRING(kTimerModeStr, D_STR_TIMERMODE); ///< "Timer Mode"
IRTEXT_CONST_STRING(kClockStr, D_STR_CLOCK); ///< "Clock"
IRTEXT_CONST_STRING(kCommandStr, D_STR_COMMAND); ///< "Command"
IRTEXT_CONST_STRING(kConfigCommandStr, D_STR_CONFIG); ///< "Config"
IRTEXT_CONST_STRING(kControlCommandStr, D_STR_CONTROL); ///< "Control"
IRTEXT_CONST_STRING(kXFanStr, D_STR_XFAN); ///< "XFan"
IRTEXT_CONST_STRING(kHealthStr, D_STR_HEALTH); ///< "Health"
IRTEXT_CONST_STRING(kModelStr, D_STR_MODEL); ///< "Model"
IRTEXT_CONST_STRING(kTempStr, D_STR_TEMP); ///< "Temp"
IRTEXT_CONST_STRING(kIFeelReportStr, D_STR_IFEELREPORT); ///< "IFeel Report"
IRTEXT_CONST_STRING(kIFeelStr, D_STR_IFEEL); ///< "IFeel"
IRTEXT_CONST_STRING(kHumidStr, D_STR_HUMID); ///< "Humid"
IRTEXT_CONST_STRING(kSaveStr, D_STR_SAVE); ///< "Save"
IRTEXT_CONST_STRING(kEyeStr, D_STR_EYE); ///< "Eye"
IRTEXT_CONST_STRING(kFollowStr, D_STR_FOLLOW); ///< "Follow"
IRTEXT_CONST_STRING(kIonStr, D_STR_ION); ///< "Ion"
IRTEXT_CONST_STRING(kFreshStr, D_STR_FRESH); ///< "Fresh"
IRTEXT_CONST_STRING(kHoldStr, D_STR_HOLD); ///< "Hold"
IRTEXT_CONST_STRING(kButtonStr, D_STR_BUTTON); ///< "Button"
IRTEXT_CONST_STRING(k8CHeatStr, D_STR_8C_HEAT); ///< "8C Heat"
IRTEXT_CONST_STRING(k10CHeatStr, D_STR_10C_HEAT); ///< "10C Heat"
IRTEXT_CONST_STRING(kISeeStr, D_STR_ISEE); ///< "ISee"
IRTEXT_CONST_STRING(kAbsenseDetectStr, D_STR_ABSENSEDETECT);
///< "AbsenseDetect"
IRTEXT_CONST_STRING(kDirectIndirectModeStr, D_STR_DIRECTINDIRECTMODE);
///< "Direct/Indirect mode"
IRTEXT_CONST_STRING(kDirectStr, D_STR_DIRECT); ///< "Direct"
IRTEXT_CONST_STRING(kIndirectStr, D_STR_INDIRECT); ///< "Indirect"
IRTEXT_CONST_STRING(kNightStr, D_STR_NIGHT); ///< "Night"
IRTEXT_CONST_STRING(kSilentStr, D_STR_SILENT); ///< "Silent"
IRTEXT_CONST_STRING(kFilterStr, D_STR_FILTER); ///< "Filter"
IRTEXT_CONST_STRING(k3DStr, D_STR_3D); ///< "3D"
IRTEXT_CONST_STRING(kCelsiusStr, D_STR_CELSIUS); ///< "Celsius"
IRTEXT_CONST_STRING(kCelsiusFahrenheitStr, D_STR_CELSIUS_FAHRENHEIT); ///<
///< "Celsius/Fahrenheit"
IRTEXT_CONST_STRING(kTempUpStr, D_STR_TEMPUP); ///< "Temp Up"
IRTEXT_CONST_STRING(kTempDownStr, D_STR_TEMPDOWN); ///< "Temp Down"
IRTEXT_CONST_STRING(kStartStr, D_STR_START); ///< "Start"
IRTEXT_CONST_STRING(kStopStr, D_STR_STOP); ///< "Stop"
IRTEXT_CONST_STRING(kMoveStr, D_STR_MOVE); ///< "Move"
IRTEXT_CONST_STRING(kSetStr, D_STR_SET); ///< "Set"
IRTEXT_CONST_STRING(kCancelStr, D_STR_CANCEL); ///< "Cancel"
IRTEXT_CONST_STRING(kUpStr, D_STR_UP); ///< "Up"
IRTEXT_CONST_STRING(kDownStr, D_STR_DOWN); ///< "Down"
IRTEXT_CONST_STRING(kChangeStr, D_STR_CHANGE); ///< "Change"
IRTEXT_CONST_STRING(kComfortStr, D_STR_COMFORT); ///< "Comfort"
IRTEXT_CONST_STRING(kSensorStr, D_STR_SENSOR); ///< "Sensor"
IRTEXT_CONST_STRING(kWeeklyTimerStr, D_STR_WEEKLYTIMER); ///< "WeeklyTimer"
IRTEXT_CONST_STRING(kWifiStr, D_STR_WIFI); ///< "Wifi"
IRTEXT_CONST_STRING(kLastStr, D_STR_LAST); ///< "Last"
IRTEXT_CONST_STRING(kFastStr, D_STR_FAST); ///< "Fast"
IRTEXT_CONST_STRING(kSlowStr, D_STR_SLOW); ///< "Slow"
IRTEXT_CONST_STRING(kAirFlowStr, D_STR_AIRFLOW); ///< "Air Flow"
IRTEXT_CONST_STRING(kStepStr, D_STR_STEP); ///< "Step"
IRTEXT_CONST_STRING(kNAStr, D_STR_NA); ///< "N/A"
IRTEXT_CONST_STRING(kInsideStr, D_STR_INSIDE); ///< "Inside"
IRTEXT_CONST_STRING(kOutsideStr, D_STR_OUTSIDE); ///< "Outside"
IRTEXT_CONST_STRING(kLoudStr, D_STR_LOUD); ///< "Loud"
IRTEXT_CONST_STRING(kLowerStr, D_STR_LOWER); ///< "Lower"
IRTEXT_CONST_STRING(kUpperStr, D_STR_UPPER); ///< "Upper"
IRTEXT_CONST_STRING(kUpperMiddleStr, D_STR_UPPER_MIDDLE); ///< "Upper-Middle"
IRTEXT_CONST_STRING(kBreezeStr, D_STR_BREEZE); ///< "Breeze"
IRTEXT_CONST_STRING(kCirculateStr, D_STR_CIRCULATE); ///< "Circulate"
IRTEXT_CONST_STRING(kCeilingStr, D_STR_CEILING); ///< "Ceiling"
IRTEXT_CONST_STRING(kWallStr, D_STR_WALL); ///< "Wall"
IRTEXT_CONST_STRING(kRoomStr, D_STR_ROOM); ///< "Room"
IRTEXT_CONST_STRING(k6thSenseStr, D_STR_6THSENSE); ///< "6th Sense"
IRTEXT_CONST_STRING(kTypeStr, D_STR_TYPE); ///< "Type"
IRTEXT_CONST_STRING(kSpecialStr, D_STR_SPECIAL); ///< "Special"
IRTEXT_CONST_STRING(kIdStr, D_STR_ID); ///< "Id" / Device Identifier
IRTEXT_CONST_STRING(kVaneStr, D_STR_VANE); ///< "Vane"
IRTEXT_CONST_STRING(kLockStr, D_STR_LOCK); ///< "Lock"
IRTEXT_CONST_STRING(kAutoStr, D_STR_AUTO); ///< "Auto"
IRTEXT_CONST_STRING(kAutomaticStr, D_STR_AUTOMATIC); ///< "Automatic"
IRTEXT_CONST_STRING(kManualStr, D_STR_MANUAL); ///< "Manual"
IRTEXT_CONST_STRING(kCoolStr, D_STR_COOL); ///< "Cool"
IRTEXT_CONST_STRING(kCoolingStr, D_STR_COOLING); ///< "Cooling"
IRTEXT_CONST_STRING(kHeatStr, D_STR_HEAT); ///< "Heat"
IRTEXT_CONST_STRING(kHeatingStr, D_STR_HEATING); ///< "Heating"
IRTEXT_CONST_STRING(kDryStr, D_STR_DRY); ///< "Dry"
IRTEXT_CONST_STRING(kDryingStr, D_STR_DRYING); ///< "Drying"
IRTEXT_CONST_STRING(kDehumidifyStr, D_STR_DEHUMIDIFY); ///< "Dehumidify"
IRTEXT_CONST_STRING(kFanStr, D_STR_FAN); ///< "Fan"
// The following Fans strings with "only" are required to help with
// HomeAssistant & Google Home Climate integration. For compatibility only.
// Ref: https://www.home-assistant.io/integrations/google_assistant/#climate-operation-modes
IRTEXT_CONST_STRING(kFanOnlyStr, D_STR_FANONLY); ///< "fan-only"
IRTEXT_CONST_STRING(kFan_OnlyStr, D_STR_FAN_ONLY); ///< "fan_only" (HA/legacy)
IRTEXT_CONST_STRING(kFanOnlyWithSpaceStr, D_STR_FANSPACEONLY); ///< "Fan Only"
IRTEXT_CONST_STRING(kFanOnlyNoSpaceStr, D_STR_FANONLYNOSPACE); ///< "FanOnly"
IRTEXT_CONST_STRING(kRecycleStr, D_STR_RECYCLE); ///< "Recycle"
IRTEXT_CONST_STRING(kMaxStr, D_STR_MAX); ///< "Max"
IRTEXT_CONST_STRING(kMaximumStr, D_STR_MAXIMUM); ///< "Maximum"
IRTEXT_CONST_STRING(kMinStr, D_STR_MIN); ///< "Min"
IRTEXT_CONST_STRING(kMinimumStr, D_STR_MINIMUM); ///< "Minimum"
IRTEXT_CONST_STRING(kMedHighStr, D_STR_MED_HIGH); ///< "Med-high"
IRTEXT_CONST_STRING(kMedStr, D_STR_MED); ///< "Med"
IRTEXT_CONST_STRING(kMediumStr, D_STR_MEDIUM); ///< "Medium"
IRTEXT_CONST_STRING(kHighestStr, D_STR_HIGHEST); ///< "Highest"
IRTEXT_CONST_STRING(kHighStr, D_STR_HIGH); ///< "High"
IRTEXT_CONST_STRING(kHiStr, D_STR_HI); ///< "Hi"
IRTEXT_CONST_STRING(kMidStr, D_STR_MID); ///< "Mid"
IRTEXT_CONST_STRING(kMiddleStr, D_STR_MIDDLE); ///< "Middle"
IRTEXT_CONST_STRING(kLowStr, D_STR_LOW); ///< "Low"
IRTEXT_CONST_STRING(kLoStr, D_STR_LO); ///< "Lo"
IRTEXT_CONST_STRING(kLowestStr, D_STR_LOWEST); ///< "Lowest"
IRTEXT_CONST_STRING(kMaxRightStr, D_STR_MAXRIGHT); ///< "Max Right"
IRTEXT_CONST_STRING(kMaxRightNoSpaceStr, D_STR_MAXRIGHT_NOSPACE); ///<
///< "MaxRight"
IRTEXT_CONST_STRING(kRightMaxStr, D_STR_RIGHTMAX); ///< "Right Max"
IRTEXT_CONST_STRING(kRightMaxNoSpaceStr, D_STR_RIGHTMAX_NOSPACE); ///<
///< "RightMax"
IRTEXT_CONST_STRING(kRightStr, D_STR_RIGHT); ///< "Right"
IRTEXT_CONST_STRING(kLeftStr, D_STR_LEFT); ///< "Left"
IRTEXT_CONST_STRING(kMaxLeftStr, D_STR_MAXLEFT); ///< "Max Left"
IRTEXT_CONST_STRING(kMaxLeftNoSpaceStr, D_STR_MAXLEFT_NOSPACE); ///< "MaxLeft"
IRTEXT_CONST_STRING(kLeftMaxStr, D_STR_LEFTMAX); ///< "Left Max"
IRTEXT_CONST_STRING(kLeftMaxNoSpaceStr, D_STR_LEFTMAX_NOSPACE); ///< "LeftMax"
IRTEXT_CONST_STRING(kWideStr, D_STR_WIDE); ///< "Wide"
IRTEXT_CONST_STRING(kCentreStr, D_STR_CENTRE); ///< "Centre"
IRTEXT_CONST_STRING(kTopStr, D_STR_TOP); ///< "Top"
IRTEXT_CONST_STRING(kBottomStr, D_STR_BOTTOM); ///< "Bottom"
// Compound words/phrases/descriptions from pre-defined words.
IRTEXT_CONST_STRING(kEconoToggleStr, D_STR_ECONOTOGGLE); ///< "Econo Toggle"
IRTEXT_CONST_STRING(kEyeAutoStr, D_STR_EYEAUTO); ///< "Eye Auto"
IRTEXT_CONST_STRING(kLightToggleStr, D_STR_LIGHTTOGGLE); ///< "Light Toggle"
///< "Outside Quiet"
IRTEXT_CONST_STRING(kOutsideQuietStr, D_STR_OUTSIDEQUIET);
IRTEXT_CONST_STRING(kPowerToggleStr, D_STR_POWERTOGGLE); ///< "Power Toggle"
IRTEXT_CONST_STRING(kPowerButtonStr, D_STR_POWERBUTTON); ///< "Power Button"
IRTEXT_CONST_STRING(kPreviousPowerStr, D_STR_PREVIOUSPOWER); ///<
///< "Previous Power"
IRTEXT_CONST_STRING(kDisplayTempStr, D_STR_DISPLAYTEMP); ///< "Display Temp"
IRTEXT_CONST_STRING(kSensorTempStr, D_STR_SENSORTEMP); ///< "Sensor Temp"
IRTEXT_CONST_STRING(kSleepTimerStr, D_STR_SLEEP_TIMER); ///< "Sleep Timer"
IRTEXT_CONST_STRING(kSwingVModeStr, D_STR_SWINGVMODE); ///< "Swing(V) Mode"
IRTEXT_CONST_STRING(kSwingVToggleStr, D_STR_SWINGVTOGGLE); ///<
///< "Swing(V) Toggle"
IRTEXT_CONST_STRING(kTurboToggleStr, D_STR_TURBOTOGGLE); ///< "Turbo Toggle"
IRTEXT_CONST_STRING(kSetTimerCommandStr, D_STR_SET_TIMER); ///< "Set Timer"
IRTEXT_CONST_STRING(kScheduleStr, D_STR_SCHEDULE); ///< "Schedule"
IRTEXT_CONST_STRING(kChStr, D_STR_CH); ///< "CH#"
IRTEXT_CONST_STRING(kTimerActiveDaysStr, D_STR_TIMER_ACTIVE_DAYS);
///< "TimerActiveDays"
IRTEXT_CONST_STRING(kKeyStr, D_STR_KEY); ///< "Key"
IRTEXT_CONST_STRING(kValueStr, D_STR_VALUE); ///< "Value"
// Separators & Punctuation
const char kTimeSep = D_CHR_TIME_SEP; ///< ':'
IRTEXT_CONST_STRING(kSpaceLBraceStr, D_STR_SPACELBRACE); ///< " ("
IRTEXT_CONST_STRING(kCommaSpaceStr, D_STR_COMMASPACE); ///< ", "
IRTEXT_CONST_STRING(kColonSpaceStr, D_STR_COLONSPACE); ///< ": "
IRTEXT_CONST_STRING(kDashStr, D_STR_DASH); ///< "-"
// IRutils
// - Time
IRTEXT_CONST_STRING(kDayStr, D_STR_DAY); ///< "Day"
IRTEXT_CONST_STRING(kDaysStr, D_STR_DAYS); ///< "Days"
IRTEXT_CONST_STRING(kHourStr, D_STR_HOUR); ///< "Hour"
IRTEXT_CONST_STRING(kHoursStr, D_STR_HOURS); ///< "Hours"
IRTEXT_CONST_STRING(kMinuteStr, D_STR_MINUTE); ///< "Minute"
IRTEXT_CONST_STRING(kMinutesStr, D_STR_MINUTES); ///< "Minutes"
IRTEXT_CONST_STRING(kSecondStr, D_STR_SECOND); ///< "Second"
IRTEXT_CONST_STRING(kSecondsStr, D_STR_SECONDS); ///< "Seconds"
IRTEXT_CONST_STRING(kNowStr, D_STR_NOW); ///< "Now"
IRTEXT_CONST_STRING(kThreeLetterDayOfWeekStr, D_STR_THREELETTERDAYS); ///<
///< "SunMonTueWedThuFriSat"
IRTEXT_CONST_STRING(kYesStr, D_STR_YES); ///< "Yes"
IRTEXT_CONST_STRING(kNoStr, D_STR_NO); ///< "No"
IRTEXT_CONST_STRING(kTrueStr, D_STR_TRUE); ///< "True"
IRTEXT_CONST_STRING(kFalseStr, D_STR_FALSE); ///< "False"
IRTEXT_CONST_STRING(kRepeatStr, D_STR_REPEAT); ///< "Repeat"
IRTEXT_CONST_STRING(kCodeStr, D_STR_CODE); ///< "Code"
IRTEXT_CONST_STRING(kBitsStr, D_STR_BITS); ///< "Bits"
// Model Names
IRTEXT_CONST_STRING(kYaw1fStr, D_STR_YAW1F); ///< "YAW1F"
IRTEXT_CONST_STRING(kYbofbStr, D_STR_YBOFB); ///< "YBOFB"
IRTEXT_CONST_STRING(kYx1fsfStr, D_STR_YX1FSF); ///< "YX1FSF"
IRTEXT_CONST_STRING(kV9014557AStr, D_STR_V9014557_A); ///< "V9014557-A"
IRTEXT_CONST_STRING(kV9014557BStr, D_STR_V9014557_B); ///< "V9014557-B"
IRTEXT_CONST_STRING(kRlt0541htaaStr, D_STR_RLT0541HTA_A); ///< "R-LT0541-HTA-A"
IRTEXT_CONST_STRING(kRlt0541htabStr, D_STR_RLT0541HTA_B); ///< "R-LT0541-HTA-B"
IRTEXT_CONST_STRING(kArrah2eStr, D_STR_ARRAH2E); ///< "ARRAH2E"
IRTEXT_CONST_STRING(kArdb1Str, D_STR_ARDB1); ///< "ARDB1"
IRTEXT_CONST_STRING(kArreb1eStr, D_STR_ARREB1E); ///< "ARREB1E"
IRTEXT_CONST_STRING(kArjw2Str, D_STR_ARJW2); ///< "ARJW2"
IRTEXT_CONST_STRING(kArry4Str, D_STR_ARRY4); ///< "ARRY4"
IRTEXT_CONST_STRING(kArrew4eStr, D_STR_ARREW4E); ///< "ARREW4E"
IRTEXT_CONST_STRING(kGe6711ar2853mStr, D_STR_GE6711AR2853M); ///<
///< "GE6711AR2853M"
IRTEXT_CONST_STRING(kAkb75215403Str, D_STR_AKB75215403); ///< "AKB75215403"
IRTEXT_CONST_STRING(kAkb74955603Str, D_STR_AKB74955603); ///< "AKB74955603"
IRTEXT_CONST_STRING(kAkb73757604Str, D_STR_AKB73757604); ///< "AKB73757604"
IRTEXT_CONST_STRING(kLg6711a20083vStr, D_STR_LG6711A20083V); ///<
///< "LG6711A20083V"
IRTEXT_CONST_STRING(kKkg9ac1Str, D_STR_KKG9AC1); ///< "KKG9AC1"
IRTEXT_CONST_STRING(kKkg29ac1Str, D_STR_KKG29AC1); ///< "KKG29AC1"
IRTEXT_CONST_STRING(kLkeStr, D_STR_LKE); ///< "LKE"
IRTEXT_CONST_STRING(kNkeStr, D_STR_NKE); ///< "NKE"
IRTEXT_CONST_STRING(kDkeStr, D_STR_DKE); ///< "DKE"
IRTEXT_CONST_STRING(kPkrStr, D_STR_PKR); ///< "PKR"
IRTEXT_CONST_STRING(kJkeStr, D_STR_JKE); ///< "JKE"
IRTEXT_CONST_STRING(kCkpStr, D_STR_CKP); ///< "CKP"
IRTEXT_CONST_STRING(kRkrStr, D_STR_RKR); ///< "RKR"
IRTEXT_CONST_STRING(kPanasonicLkeStr, D_STR_PANASONICLKE); ///< "PANASONICLKE"
IRTEXT_CONST_STRING(kPanasonicNkeStr, D_STR_PANASONICNKE); ///< "PANASONICNKE"
IRTEXT_CONST_STRING(kPanasonicDkeStr, D_STR_PANASONICDKE); ///< "PANASONICDKE"
IRTEXT_CONST_STRING(kPanasonicPkrStr, D_STR_PANASONICPKR); ///< "PANASONICPKR"
IRTEXT_CONST_STRING(kPanasonicJkeStr, D_STR_PANASONICJKE); ///< "PANASONICJKE"
IRTEXT_CONST_STRING(kPanasonicCkpStr, D_STR_PANASONICCKP); ///< "PANASONICCKP"
IRTEXT_CONST_STRING(kPanasonicRkrStr, D_STR_PANASONICRKR); ///< "PANASONICRKR"
IRTEXT_CONST_STRING(kA907Str, D_STR_A907); ///< "A907"
IRTEXT_CONST_STRING(kA705Str, D_STR_A705); ///< "A705"
IRTEXT_CONST_STRING(kA903Str, D_STR_A903); ///< "A903"
IRTEXT_CONST_STRING(kTac09chsdStr, D_STR_TAC09CHSD); ///< "TAC09CHSD"
IRTEXT_CONST_STRING(kGz055be1Str, D_STR_GZ055BE1); ///< "GZ055BE1"
IRTEXT_CONST_STRING(k122lzfStr, D_STR_122LZF); ///< "122LZF"
IRTEXT_CONST_STRING(kDg11j13aStr, D_STR_DG11J13A); ///< "DG11J13A"
IRTEXT_CONST_STRING(kDg11j104Str, D_STR_DG11J104); ///< "DG11J104"
IRTEXT_CONST_STRING(kDg11j191Str, D_STR_DG11J191); ///< "DG11J191"
IRTEXT_CONST_STRING(kArgoWrem2Str, D_STR_ARGO_WREM2); ///< "WREM3"
IRTEXT_CONST_STRING(kArgoWrem3Str, D_STR_ARGO_WREM3); ///< "WREM3"
#define D_STR_UNSUPPORTED "?" // Unsupported protocols will be showing as
// a question mark, check for length > 1
// to show only currently included protocols
// Protocol Names
// Needs to be in decode_type_t order.
IRTEXT_CONST_BLOB_DECL(kAllProtocolNamesStr) {
D_STR_UNUSED "\x0"
COND(DECODE_RC5 || SEND_RC5,
D_STR_RC5, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_RC6 || SEND_RC6,
D_STR_RC6, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_NEC || SEND_NEC,
D_STR_NEC, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_SONY || SEND_SONY,
D_STR_SONY, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_PANASONIC || SEND_PANASONIC,
D_STR_PANASONIC, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_JVC || SEND_JVC,
D_STR_JVC, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_SAMSUNG || SEND_SAMSUNG,
D_STR_SAMSUNG, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_WHYNTER || SEND_WHYNTER,
D_STR_WHYNTER, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_AIWA_RC_T501 || SEND_AIWA_RC_T501,
D_STR_AIWA_RC_T501, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_LG || SEND_LG,
D_STR_LG, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_SANYO || SEND_SANYO,
D_STR_SANYO, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_MITSUBISHI || SEND_MITSUBISHI,
D_STR_MITSUBISHI, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_DISH || SEND_DISH,
D_STR_DISH, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_SHARP || SEND_SHARP,
D_STR_SHARP, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_COOLIX || SEND_COOLIX,
D_STR_COOLIX, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_DAIKIN || SEND_DAIKIN,
D_STR_DAIKIN, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_DENON || SEND_DENON,
D_STR_DENON, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_KELVINATOR || SEND_KELVINATOR,
D_STR_KELVINATOR, D_STR_UNSUPPORTED) "\x0"
COND(SEND_SHERWOOD,
D_STR_SHERWOOD, D_STR_UNSUPPORTED) "\x0" // SEND-ONLY
COND(DECODE_MITSUBISHI_AC || SEND_MITSUBISHI_AC,
D_STR_MITSUBISHI_AC, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_RCMM || SEND_RCMM,
D_STR_RCMM, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_SANYO || SEND_SANYO,
D_STR_SANYO_LC7461, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_RC5 || SEND_RC5,
D_STR_RC5X, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_GREE || SEND_GREE,
D_STR_GREE, D_STR_UNSUPPORTED) "\x0"
COND(SEND_PRONTO,
D_STR_PRONTO, D_STR_UNSUPPORTED) "\x0" // SEND-ONLY
COND(DECODE_NEC || SEND_NEC,
D_STR_NEC_LIKE, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_ARGO || SEND_ARGO,
D_STR_ARGO, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_TROTEC || SEND_TROTEC,
D_STR_TROTEC, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_NIKAI || SEND_NIKAI,
D_STR_NIKAI, D_STR_UNSUPPORTED) "\x0"
COND(SEND_RAW,
D_STR_RAW, D_STR_UNSUPPORTED) "\x0" // SEND-ONLY
COND(SEND_GLOBALCACHE,
D_STR_GLOBALCACHE, D_STR_UNSUPPORTED) "\x0" // SEND
COND(DECODE_TOSHIBA_AC || SEND_TOSHIBA_AC,
D_STR_TOSHIBA_AC, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_FUJITSU_AC || SEND_FUJITSU_AC,
D_STR_FUJITSU_AC, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_MIDEA || SEND_MIDEA,
D_STR_MIDEA, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_MAGIQUEST || SEND_MAGIQUEST,
D_STR_MAGIQUEST, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_LASERTAG || SEND_LASERTAG,
D_STR_LASERTAG, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_CARRIER_AC || SEND_CARRIER_AC,
D_STR_CARRIER_AC, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_HAIER_AC || SEND_HAIER_AC,
D_STR_HAIER_AC, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_MITSUBISHI2 || SEND_MITSUBISHI2,
D_STR_MITSUBISHI2, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_HITACHI_AC || SEND_HITACHI_AC,
D_STR_HITACHI_AC, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_HITACHI_AC1 || SEND_HITACHI_AC1,
D_STR_HITACHI_AC1, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_HITACHI_AC2 || SEND_HITACHI_AC2,
D_STR_HITACHI_AC2, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_GICABLE || SEND_GICABLE,
D_STR_GICABLE, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_HAIER_AC_YRW02 || SEND_HAIER_AC_YRW02,
D_STR_HAIER_AC_YRW02, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_WHIRLPOOL_AC || SEND_WHIRLPOOL_AC,
D_STR_WHIRLPOOL_AC, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_SAMSUNG_AC || SEND_SAMSUNG_AC,
D_STR_SAMSUNG_AC, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_LUTRON || SEND_LUTRON,
D_STR_LUTRON, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_ELECTRA_AC || SEND_ELECTRA_AC,
D_STR_ELECTRA_AC, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_PANASONIC_AC || SEND_PANASONIC_AC,
D_STR_PANASONIC_AC, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_PIONEER || SEND_PIONEER,
D_STR_PIONEER, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_LG || SEND_LG,
D_STR_LG2, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_MWM || SEND_MWM,
D_STR_MWM, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_DAIKIN2 || SEND_DAIKIN2,
D_STR_DAIKIN2, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_VESTEL_AC || SEND_VESTEL_AC,
D_STR_VESTEL_AC, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_TECO || SEND_TECO,
D_STR_TECO, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_SAMSUNG36 || SEND_SAMSUNG36,
D_STR_SAMSUNG36, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_TCL112AC || SEND_TCL112AC,
D_STR_TCL112AC, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_LEGOPF || SEND_LEGOPF,
D_STR_LEGOPF, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_MITSUBISHIHEAVY || SEND_MITSUBISHIHEAVY,
D_STR_MITSUBISHI_HEAVY_88, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_MITSUBISHIHEAVY || SEND_MITSUBISHIHEAVY,
D_STR_MITSUBISHI_HEAVY_152, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_DAIKIN216 || SEND_DAIKIN216,
D_STR_DAIKIN216, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_SHARP_AC || SEND_SHARP_AC,
D_STR_SHARP_AC, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_GOODWEATHER || SEND_GOODWEATHER,
D_STR_GOODWEATHER, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_INAX || SEND_INAX,
D_STR_INAX, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_DAIKIN160 || SEND_DAIKIN160,
D_STR_DAIKIN160, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_NEOCLIMA || SEND_NEOCLIMA,
D_STR_NEOCLIMA, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_DAIKIN176 || SEND_DAIKIN176,
D_STR_DAIKIN176, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_DAIKIN128 || SEND_DAIKIN128,
D_STR_DAIKIN128, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_AMCOR || SEND_AMCOR,
D_STR_AMCOR, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_DAIKIN152 || SEND_DAIKIN152,
D_STR_DAIKIN152, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_MITSUBISHI136 || SEND_MITSUBISHI136,
D_STR_MITSUBISHI136, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_MITSUBISHI112 || SEND_MITSUBISHI112,
D_STR_MITSUBISHI112, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_HITACHI_AC424 || SEND_HITACHI_AC424,
D_STR_HITACHI_AC424, D_STR_UNSUPPORTED) "\x0"
COND(SEND_SONY,
D_STR_SONY_38K, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_EPSON || SEND_EPSON,
D_STR_EPSON, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_SYMPHONY || SEND_SYMPHONY,
D_STR_SYMPHONY, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_HITACHI_AC3 || SEND_HITACHI_AC3,
D_STR_HITACHI_AC3, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_DAIKIN64 || SEND_DAIKIN64,
D_STR_DAIKIN64, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_AIRWELL || SEND_AIRWELL,
D_STR_AIRWELL, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_DELONGHI_AC || SEND_DELONGHI_AC,
D_STR_DELONGHI_AC, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_DOSHISHA || SEND_DOSHISHA,
D_STR_DOSHISHA, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_MULTIBRACKETS || SEND_MULTIBRACKETS,
D_STR_MULTIBRACKETS, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_CARRIER_AC40 || SEND_CARRIER_AC40,
D_STR_CARRIER_AC40, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_CARRIER_AC64 || SEND_CARRIER_AC64,
D_STR_CARRIER_AC64, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_HITACHI_AC344 || SEND_HITACHI_AC344,
D_STR_HITACHI_AC344, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_CORONA_AC || SEND_CORONA_AC,
D_STR_CORONA_AC, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_MIDEA24 || SEND_MIDEA24,
D_STR_MIDEA24, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_ZEPEAL || SEND_ZEPEAL,
D_STR_ZEPEAL, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_SANYO_AC || SEND_SANYO_AC,
D_STR_SANYO_AC, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_VOLTAS || SEND_VOLTAS,
D_STR_VOLTAS, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_METZ || SEND_METZ,
D_STR_METZ, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_TRANSCOLD || SEND_TRANSCOLD,
D_STR_TRANSCOLD, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_TECHNIBEL_AC || SEND_TECHNIBEL_AC,
D_STR_TECHNIBEL_AC, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_MIRAGE || SEND_MIRAGE,
D_STR_MIRAGE, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_ELITESCREENS || SEND_ELITESCREENS,
D_STR_ELITESCREENS, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_PANASONIC_AC32 || SEND_PANASONIC_AC32,
D_STR_PANASONIC_AC32, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_MILESTAG2 || SEND_MILESTAG2,
D_STR_MILESTAG2, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_ECOCLIM || SEND_ECOCLIM,
D_STR_ECOCLIM, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_XMP || SEND_XMP,
D_STR_XMP, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_TRUMA || SEND_TRUMA,
D_STR_TRUMA, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_HAIER_AC176 || SEND_HAIER_AC176,
D_STR_HAIER_AC176, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_TEKNOPOINT || SEND_TEKNOPOINT,
D_STR_TEKNOPOINT, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_KELON || SEND_KELON,
D_STR_KELON, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_TROTEC_3550 || SEND_TROTEC_3550,
D_STR_TROTEC_3550, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_SANYO_AC88 || SEND_SANYO_AC88,
D_STR_SANYO_AC88, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_BOSE || SEND_BOSE,
D_STR_BOSE, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_ARRIS || SEND_ARRIS,
D_STR_ARRIS, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_RHOSS || SEND_RHOSS,
D_STR_RHOSS, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_AIRTON || SEND_AIRTON,
D_STR_AIRTON, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_COOLIX48 || SEND_COOLIX48,
D_STR_COOLIX48, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_HITACHI_AC264 || SEND_HITACHI_AC264,
D_STR_HITACHI_AC264, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_KELON168 || SEND_KELON168,
D_STR_KELON168, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_HITACHI_AC296 || SEND_HITACHI_AC296,
D_STR_HITACHI_AC296, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_DAIKIN200 || SEND_DAIKIN200,
D_STR_DAIKIN200, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_HAIER_AC160 || SEND_HAIER_AC160,
D_STR_HAIER_AC160, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_CARRIER_AC128 || SEND_CARRIER_AC128,
D_STR_CARRIER_AC128, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_TOTO || SEND_TOTO,
D_STR_TOTO, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_CLIMABUTLER || SEND_CLIMABUTLER,
D_STR_CLIMABUTLER, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_TCL96AC || SEND_TCL96AC,
D_STR_TCL96AC, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_BOSCH144 || SEND_BOSCH144,
D_STR_BOSCH144, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_SANYO_AC152 || SEND_SANYO_AC152,
D_STR_SANYO_AC152, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_DAIKIN312 || SEND_DAIKIN312,
D_STR_DAIKIN312, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_GORENJE || SEND_GORENJE,
D_STR_GORENJE, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_WOWWEE || SEND_WOWWEE,
D_STR_WOWWEE, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_CARRIER_AC84 || SEND_CARRIER_AC84,
D_STR_CARRIER_AC84, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_YORK || SEND_YORK,
D_STR_YORK, D_STR_UNSUPPORTED) "\x0"
///< New protocol (macro) strings should be added just above this line.
"\x0" ///< This string requires double null termination.
};
IRTEXT_CONST_BLOB_PTR(kAllProtocolNamesStr);

View File

@@ -0,0 +1,256 @@
// Copyright 2019-2022 - David Conran (@crankyoldgit)
// This header file is to be included in files **other than** 'IRtext.cpp'.
//
// WARNING: Do not edit this file! This file is automatically generated by
// '../tools/generate_irtext_h.sh'.
#ifndef IRTEXT_H_
#define IRTEXT_H_
#include "i18n.h"
// Constant text to be shared across all object files.
// This means there is only one copy of the character/string/text etc.
#ifdef ESP8266
class __FlashStringHelper;
#define IRTEXT_CONST_PTR_CAST(PTR)\
reinterpret_cast<const __FlashStringHelper*>(PTR)
#define IRTEXT_CONST_PTR(NAME) const __FlashStringHelper* const NAME
#else // ESP8266
#define IRTEXT_CONST_PTR_CAST(PTR) PTR
#define IRTEXT_CONST_PTR(NAME) const char* const NAME
#endif // ESP8266
extern const char kTimeSep;
extern IRTEXT_CONST_PTR(k0Str);
extern IRTEXT_CONST_PTR(k10CHeatStr);
extern IRTEXT_CONST_PTR(k122lzfStr);
extern IRTEXT_CONST_PTR(k1Str);
extern IRTEXT_CONST_PTR(k3DStr);
extern IRTEXT_CONST_PTR(k6thSenseStr);
extern IRTEXT_CONST_PTR(k8CHeatStr);
extern IRTEXT_CONST_PTR(kA705Str);
extern IRTEXT_CONST_PTR(kA903Str);
extern IRTEXT_CONST_PTR(kA907Str);
extern IRTEXT_CONST_PTR(kAbsenseDetectStr);
extern IRTEXT_CONST_PTR(kAirFlowStr);
extern IRTEXT_CONST_PTR(kAkb73757604Str);
extern IRTEXT_CONST_PTR(kAkb74955603Str);
extern IRTEXT_CONST_PTR(kAkb75215403Str);
extern IRTEXT_CONST_PTR(kArdb1Str);
extern IRTEXT_CONST_PTR(kArgoWrem2Str);
extern IRTEXT_CONST_PTR(kArgoWrem3Str);
extern IRTEXT_CONST_PTR(kArjw2Str);
extern IRTEXT_CONST_PTR(kArrah2eStr);
extern IRTEXT_CONST_PTR(kArreb1eStr);
extern IRTEXT_CONST_PTR(kArrew4eStr);
extern IRTEXT_CONST_PTR(kArry4Str);
extern IRTEXT_CONST_PTR(kAutoStr);
extern IRTEXT_CONST_PTR(kAutomaticStr);
extern IRTEXT_CONST_PTR(kBeepStr);
extern IRTEXT_CONST_PTR(kBitsStr);
extern IRTEXT_CONST_PTR(kBottomStr);
extern IRTEXT_CONST_PTR(kBreezeStr);
extern IRTEXT_CONST_PTR(kButtonStr);
extern IRTEXT_CONST_PTR(kCancelStr);
extern IRTEXT_CONST_PTR(kCeilingStr);
extern IRTEXT_CONST_PTR(kCelsiusFahrenheitStr);
extern IRTEXT_CONST_PTR(kCelsiusStr);
extern IRTEXT_CONST_PTR(kCentreStr);
extern IRTEXT_CONST_PTR(kChangeStr);
extern IRTEXT_CONST_PTR(kChStr);
extern IRTEXT_CONST_PTR(kCirculateStr);
extern IRTEXT_CONST_PTR(kCkpStr);
extern IRTEXT_CONST_PTR(kCleanStr);
extern IRTEXT_CONST_PTR(kClockStr);
extern IRTEXT_CONST_PTR(kCodeStr);
extern IRTEXT_CONST_PTR(kColonSpaceStr);
extern IRTEXT_CONST_PTR(kComfortStr);
extern IRTEXT_CONST_PTR(kCommaSpaceStr);
extern IRTEXT_CONST_PTR(kCommandStr);
extern IRTEXT_CONST_PTR(kConfigCommandStr);
extern IRTEXT_CONST_PTR(kControlCommandStr);
extern IRTEXT_CONST_PTR(kCoolStr);
extern IRTEXT_CONST_PTR(kCoolingStr);
extern IRTEXT_CONST_PTR(kDashStr);
extern IRTEXT_CONST_PTR(kDayStr);
extern IRTEXT_CONST_PTR(kDaysStr);
extern IRTEXT_CONST_PTR(kDehumidifyStr);
extern IRTEXT_CONST_PTR(kDg11j104Str);
extern IRTEXT_CONST_PTR(kDg11j13aStr);
extern IRTEXT_CONST_PTR(kDg11j191Str);
extern IRTEXT_CONST_PTR(kDirectIndirectModeStr);
extern IRTEXT_CONST_PTR(kDirectStr);
extern IRTEXT_CONST_PTR(kDisplayTempStr);
extern IRTEXT_CONST_PTR(kDkeStr);
extern IRTEXT_CONST_PTR(kDownStr);
extern IRTEXT_CONST_PTR(kDryStr);
extern IRTEXT_CONST_PTR(kDryingStr);
extern IRTEXT_CONST_PTR(kEconoStr);
extern IRTEXT_CONST_PTR(kEconoToggleStr);
extern IRTEXT_CONST_PTR(kEyeAutoStr);
extern IRTEXT_CONST_PTR(kEyeStr);
extern IRTEXT_CONST_PTR(kFalseStr);
extern IRTEXT_CONST_PTR(kFanOnlyNoSpaceStr);
extern IRTEXT_CONST_PTR(kFanOnlyStr);
extern IRTEXT_CONST_PTR(kFanOnlyWithSpaceStr);
extern IRTEXT_CONST_PTR(kFanStr);
extern IRTEXT_CONST_PTR(kFan_OnlyStr);
extern IRTEXT_CONST_PTR(kFastStr);
extern IRTEXT_CONST_PTR(kFilterStr);
extern IRTEXT_CONST_PTR(kFixedStr);
extern IRTEXT_CONST_PTR(kFollowStr);
extern IRTEXT_CONST_PTR(kFreshStr);
extern IRTEXT_CONST_PTR(kGe6711ar2853mStr);
extern IRTEXT_CONST_PTR(kGz055be1Str);
extern IRTEXT_CONST_PTR(kHealthStr);
extern IRTEXT_CONST_PTR(kHeatStr);
extern IRTEXT_CONST_PTR(kHeatingStr);
extern IRTEXT_CONST_PTR(kHiStr);
extern IRTEXT_CONST_PTR(kHighStr);
extern IRTEXT_CONST_PTR(kHighestStr);
extern IRTEXT_CONST_PTR(kHoldStr);
extern IRTEXT_CONST_PTR(kHourStr);
extern IRTEXT_CONST_PTR(kHoursStr);
extern IRTEXT_CONST_PTR(kHumidStr);
extern IRTEXT_CONST_PTR(kIFeelReportStr);
extern IRTEXT_CONST_PTR(kIFeelStr);
extern IRTEXT_CONST_PTR(kISeeStr);
extern IRTEXT_CONST_PTR(kIdStr);
extern IRTEXT_CONST_PTR(kIndirectStr);
extern IRTEXT_CONST_PTR(kInsideStr);
extern IRTEXT_CONST_PTR(kIonStr);
extern IRTEXT_CONST_PTR(kJkeStr);
extern IRTEXT_CONST_PTR(kKeyStr);
extern IRTEXT_CONST_PTR(kKkg29ac1Str);
extern IRTEXT_CONST_PTR(kKkg9ac1Str);
extern IRTEXT_CONST_PTR(kLastStr);
extern IRTEXT_CONST_PTR(kLeftMaxNoSpaceStr);
extern IRTEXT_CONST_PTR(kLeftMaxStr);
extern IRTEXT_CONST_PTR(kLeftStr);
extern IRTEXT_CONST_PTR(kLg6711a20083vStr);
extern IRTEXT_CONST_PTR(kLightStr);
extern IRTEXT_CONST_PTR(kLightToggleStr);
extern IRTEXT_CONST_PTR(kLkeStr);
extern IRTEXT_CONST_PTR(kLoStr);
extern IRTEXT_CONST_PTR(kLockStr);
extern IRTEXT_CONST_PTR(kLoudStr);
extern IRTEXT_CONST_PTR(kLowStr);
extern IRTEXT_CONST_PTR(kLowerStr);
extern IRTEXT_CONST_PTR(kLowestStr);
extern IRTEXT_CONST_PTR(kManualStr);
extern IRTEXT_CONST_PTR(kMaxLeftNoSpaceStr);
extern IRTEXT_CONST_PTR(kMaxLeftStr);
extern IRTEXT_CONST_PTR(kMaxRightNoSpaceStr);
extern IRTEXT_CONST_PTR(kMaxRightStr);
extern IRTEXT_CONST_PTR(kMaxStr);
extern IRTEXT_CONST_PTR(kMaximumStr);
extern IRTEXT_CONST_PTR(kMedHighStr);
extern IRTEXT_CONST_PTR(kMedStr);
extern IRTEXT_CONST_PTR(kMediumStr);
extern IRTEXT_CONST_PTR(kMidStr);
extern IRTEXT_CONST_PTR(kMiddleStr);
extern IRTEXT_CONST_PTR(kMinStr);
extern IRTEXT_CONST_PTR(kMinimumStr);
extern IRTEXT_CONST_PTR(kMinuteStr);
extern IRTEXT_CONST_PTR(kMinutesStr);
extern IRTEXT_CONST_PTR(kModeStr);
extern IRTEXT_CONST_PTR(kModelStr);
extern IRTEXT_CONST_PTR(kMouldStr);
extern IRTEXT_CONST_PTR(kMoveStr);
extern IRTEXT_CONST_PTR(kNAStr);
extern IRTEXT_CONST_PTR(kNightStr);
extern IRTEXT_CONST_PTR(kNkeStr);
extern IRTEXT_CONST_PTR(kNoStr);
extern IRTEXT_CONST_PTR(kNowStr);
extern IRTEXT_CONST_PTR(kOffStr);
extern IRTEXT_CONST_PTR(kOffTimerStr);
extern IRTEXT_CONST_PTR(kOnStr);
extern IRTEXT_CONST_PTR(kOnTimerStr);
extern IRTEXT_CONST_PTR(kOutsideQuietStr);
extern IRTEXT_CONST_PTR(kOutsideStr);
extern IRTEXT_CONST_PTR(kPanasonicCkpStr);
extern IRTEXT_CONST_PTR(kPanasonicDkeStr);
extern IRTEXT_CONST_PTR(kPanasonicJkeStr);
extern IRTEXT_CONST_PTR(kPanasonicLkeStr);
extern IRTEXT_CONST_PTR(kPanasonicNkeStr);
extern IRTEXT_CONST_PTR(kPanasonicPkrStr);
extern IRTEXT_CONST_PTR(kPanasonicRkrStr);
extern IRTEXT_CONST_PTR(kPkrStr);
extern IRTEXT_CONST_PTR(kPowerButtonStr);
extern IRTEXT_CONST_PTR(kPowerStr);
extern IRTEXT_CONST_PTR(kPowerToggleStr);
extern IRTEXT_CONST_PTR(kPowerfulStr);
extern IRTEXT_CONST_PTR(kPreviousPowerStr);
extern IRTEXT_CONST_PTR(kProtocolStr);
extern IRTEXT_CONST_PTR(kPurifyStr);
extern IRTEXT_CONST_PTR(kQuietStr);
extern IRTEXT_CONST_PTR(kRecycleStr);
extern IRTEXT_CONST_PTR(kRepeatStr);
extern IRTEXT_CONST_PTR(kRightMaxNoSpaceStr);
extern IRTEXT_CONST_PTR(kRightMaxStr);
extern IRTEXT_CONST_PTR(kRightStr);
extern IRTEXT_CONST_PTR(kRkrStr);
extern IRTEXT_CONST_PTR(kRlt0541htaaStr);
extern IRTEXT_CONST_PTR(kRlt0541htabStr);
extern IRTEXT_CONST_PTR(kRoomStr);
extern IRTEXT_CONST_PTR(kSaveStr);
extern IRTEXT_CONST_PTR(kScheduleStr);
extern IRTEXT_CONST_PTR(kSecondStr);
extern IRTEXT_CONST_PTR(kSecondsStr);
extern IRTEXT_CONST_PTR(kSensorReportStr);
extern IRTEXT_CONST_PTR(kSensorStr);
extern IRTEXT_CONST_PTR(kSensorTempStr);
extern IRTEXT_CONST_PTR(kSetStr);
extern IRTEXT_CONST_PTR(kSilentStr);
extern IRTEXT_CONST_PTR(kSleepStr);
extern IRTEXT_CONST_PTR(kSleepTimerStr);
extern IRTEXT_CONST_PTR(kSlowStr);
extern IRTEXT_CONST_PTR(kSpaceLBraceStr);
extern IRTEXT_CONST_PTR(kSpecialStr);
extern IRTEXT_CONST_PTR(kStartStr);
extern IRTEXT_CONST_PTR(kStepStr);
extern IRTEXT_CONST_PTR(kStopStr);
extern IRTEXT_CONST_PTR(kSuperStr);
extern IRTEXT_CONST_PTR(kSwingHStr);
extern IRTEXT_CONST_PTR(kSwingStr);
extern IRTEXT_CONST_PTR(kSwingVModeStr);
extern IRTEXT_CONST_PTR(kSwingVStr);
extern IRTEXT_CONST_PTR(kSwingVToggleStr);
extern IRTEXT_CONST_PTR(kTac09chsdStr);
extern IRTEXT_CONST_PTR(kTempDownStr);
extern IRTEXT_CONST_PTR(kTempStr);
extern IRTEXT_CONST_PTR(kTempUpStr);
extern IRTEXT_CONST_PTR(kThreeLetterDayOfWeekStr);
extern IRTEXT_CONST_PTR(kTimerActiveDaysStr);
extern IRTEXT_CONST_PTR(kTimerModeStr);
extern IRTEXT_CONST_PTR(kSetTimerCommandStr);
extern IRTEXT_CONST_PTR(kTimerStr);
extern IRTEXT_CONST_PTR(kToggleStr);
extern IRTEXT_CONST_PTR(kTopStr);
extern IRTEXT_CONST_PTR(kTrueStr);
extern IRTEXT_CONST_PTR(kTurboStr);
extern IRTEXT_CONST_PTR(kTurboToggleStr);
extern IRTEXT_CONST_PTR(kTypeStr);
extern IRTEXT_CONST_PTR(kUnknownStr);
extern IRTEXT_CONST_PTR(kUpStr);
extern IRTEXT_CONST_PTR(kUpperStr);
extern IRTEXT_CONST_PTR(kUpperMiddleStr);
extern IRTEXT_CONST_PTR(kValueStr);
extern IRTEXT_CONST_PTR(kV9014557AStr);
extern IRTEXT_CONST_PTR(kV9014557BStr);
extern IRTEXT_CONST_PTR(kVaneStr);
extern IRTEXT_CONST_PTR(kWallStr);
extern IRTEXT_CONST_PTR(kWeeklyTimerStr);
extern IRTEXT_CONST_PTR(kWideStr);
extern IRTEXT_CONST_PTR(kWifiStr);
extern IRTEXT_CONST_PTR(kXFanStr);
extern IRTEXT_CONST_PTR(kYaw1fStr);
extern IRTEXT_CONST_PTR(kYbofbStr);
extern IRTEXT_CONST_PTR(kYesStr);
extern IRTEXT_CONST_PTR(kYx1fsfStr);
extern IRTEXT_CONST_PTR(kZoneFollowStr);
extern IRTEXT_CONST_PTR(kAllProtocolNamesStr);
#endif // IRTEXT_H_

View File

@@ -0,0 +1,78 @@
// Copyright 2017 David Conran
#include "IRtimer.h"
#ifndef UNIT_TEST
#include <Arduino.h>
#endif
#ifdef UNIT_TEST
// Used to help simulate elapsed time in unit tests.
uint32_t _IRtimer_unittest_now = 0;
uint32_t _TimerMs_unittest_now = 0;
#endif // UNIT_TEST
/// Class constructor.
IRtimer::IRtimer() { reset(); }
/// Resets the IRtimer object. I.e. The counter starts again from now.
void IRtimer::reset() {
#ifndef UNIT_TEST
start = micros();
#else
start = _IRtimer_unittest_now;
#endif
}
/// Calculate how many microseconds have elapsed since the timer was started.
/// @return Nr. of microseconds.
uint32_t IRtimer::elapsed() {
#ifndef UNIT_TEST
uint32_t now = micros();
#else
uint32_t now = _IRtimer_unittest_now;
#endif
if (start <= now) // Check if the system timer has wrapped.
return now - start; // No wrap.
else
return UINT32_MAX - start + now; // Has wrapped.
}
/// Add time to the timer to simulate elapsed time.
/// @param[in] usecs Nr. of uSeconds to be added.
/// @note Only used in unit testing.
#ifdef UNIT_TEST
void IRtimer::add(uint32_t usecs) { _IRtimer_unittest_now += usecs; }
#endif // UNIT_TEST
/// Class constructor.
TimerMs::TimerMs() { reset(); }
/// Resets the TimerMs object. I.e. The counter starts again from now.
void TimerMs::reset() {
#ifndef UNIT_TEST
start = millis();
#else
start = _TimerMs_unittest_now;
#endif
}
/// Calculate how many milliseconds have elapsed since the timer was started.
/// @return Nr. of milliseconds.
uint32_t TimerMs::elapsed() {
#ifndef UNIT_TEST
uint32_t now = millis();
#else
uint32_t now = _TimerMs_unittest_now;
#endif
if (start <= now) // Check if the system timer has wrapped.
return now - start; // No wrap.
else
return UINT32_MAX - start + now; // Has wrapped.
}
/// Add time to the timer to simulate elapsed time.
/// @param[in] msecs Nr. of mSeconds to be added.
/// @note Only used in unit testing.
#ifdef UNIT_TEST
void TimerMs::add(uint32_t msecs) { _IRtimer_unittest_now += msecs; }
#endif // UNIT_TEST

View File

@@ -0,0 +1,40 @@
// Copyright 2017 David Conran
#ifndef IRTIMER_H_
#define IRTIMER_H_
#define __STDC_LIMIT_MACROS
#include <stdint.h>
// Classes
/// This class offers a simple counter in micro-seconds since instantiated.
/// @note Handles when the system timer wraps around (once).
class IRtimer {
public:
IRtimer();
void reset();
uint32_t elapsed();
#ifdef UNIT_TEST
static void add(uint32_t usecs);
#endif // UNIT_TEST
private:
uint32_t start; ///< Time in uSeconds when the class was instantiated/reset.
};
/// This class offers a simple counter in milli-seconds since instantiated.
/// @note Handles when the system timer wraps around (once).
class TimerMs {
public:
TimerMs();
void reset();
uint32_t elapsed();
#ifdef UNIT_TEST
static void add(uint32_t msecs);
#endif // UNIT_TEST
private:
uint32_t start; ///< Time in mSeconds when the class was instantiated/reset.
};
#endif // IRTIMER_H_

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,155 @@
#ifndef IRUTILS_H_
#define IRUTILS_H_
// Copyright 2017 David Conran
#ifndef UNIT_TEST
#include <Arduino.h>
#endif
#define __STDC_LIMIT_MACROS
#include <stdint.h>
#ifndef ARDUINO
#include <string>
#endif
#include "IRremoteESP8266.h"
#include "IRrecv.h"
const uint8_t kNibbleSize = 4;
const uint8_t kLowNibble = 0;
const uint8_t kHighNibble = 4;
const uint8_t kModeBitsSize = 3;
uint64_t reverseBits(uint64_t input, uint16_t nbits);
String uint64ToString(uint64_t input, uint8_t base = 10);
String int64ToString(int64_t input, uint8_t base = 10);
String typeToString(const decode_type_t protocol,
const bool isRepeat = false);
void serialPrintUint64(uint64_t input, uint8_t base = 10);
String resultToSourceCode(const decode_results * const results);
String resultToTimingInfo(const decode_results * const results);
String resultToHumanReadableBasic(const decode_results * const results);
String resultToHexidecimal(const decode_results * const result);
bool hasACState(const decode_type_t protocol);
uint16_t getCorrectedRawLength(const decode_results * const results);
uint16_t *resultToRawArray(const decode_results * const decode);
uint8_t sumBytes(const uint8_t * const start, const uint16_t length,
const uint8_t init = 0);
uint8_t xorBytes(const uint8_t * const start, const uint16_t length,
const uint8_t init = 0);
uint16_t countBits(const uint8_t * const start, const uint16_t length,
const bool ones = true, const uint16_t init = 0);
uint16_t countBits(const uint64_t data, const uint8_t length,
const bool ones = true, const uint16_t init = 0);
uint64_t invertBits(const uint64_t data, const uint16_t nbits);
decode_type_t strToDecodeType(const char *str);
float celsiusToFahrenheit(const float deg);
float fahrenheitToCelsius(const float deg);
/// Namespace for covering common functions & procedures for advancd protocol
/// handlers
namespace irutils {
String addBoolToString(const bool value, const String label,
const bool precomma = true);
String addToggleToString(const bool toggle, const String label,
const bool precomma = true);
String addIntToString(const uint16_t value, const String label,
const bool precomma = true);
String addSignedIntToString(const int16_t value, const String label,
const bool precomma = true);
String modelToStr(const decode_type_t protocol, const int16_t model);
String addModelToString(const decode_type_t protocol, const int16_t model,
const bool precomma = true);
String addLabeledString(const String value, const String label,
const bool precomma = true);
String addTempToString(const uint16_t degrees, const bool celsius = true,
const bool precomma = true,
const bool isSensorTemp = false);
String addTempFloatToString(const float degrees, const bool celsius = true,
const bool precomma = true,
const bool isSensorTemp = false);
String addModeToString(const uint8_t mode, const uint8_t automatic,
const uint8_t cool, const uint8_t heat,
const uint8_t dry, const uint8_t fan);
String addFanToString(const uint8_t speed, const uint8_t high,
const uint8_t low, const uint8_t automatic,
const uint8_t quiet, const uint8_t medium,
const uint8_t maximum = 0xFF,
const uint8_t medium_high = 0xFF);
String addSwingHToString(const uint8_t position, const uint8_t automatic,
const uint8_t maxleft, const uint8_t left,
const uint8_t middle,
const uint8_t right, const uint8_t maxright,
const uint8_t off,
const uint8_t leftright, const uint8_t rightleft,
const uint8_t threed, const uint8_t wide);
String addSwingVToString(const uint8_t position, const uint8_t automatic,
const uint8_t highest, const uint8_t high,
const uint8_t uppermiddle,
const uint8_t middle,
const uint8_t lowermiddle,
const uint8_t low, const uint8_t lowest,
const uint8_t off, const uint8_t swing,
const uint8_t breeze, const uint8_t circulate);
String addDayToString(const uint8_t day_of_week, const int8_t offset = 0,
const bool precomma = true);
String addTimerModeToString(const uint8_t timerType, const uint8_t noTimer,
const uint8_t delayTimer,
const uint8_t schedule1 = 0xFF,
const uint8_t schedule2 = 0xFF,
const uint8_t schedule3 = 0xFF,
const bool precomma = true);
String irCommandTypeToString(uint8_t commandType, uint8_t acControlCmd,
uint8_t iFeelReportCmd = 0xFF,
uint8_t timerCmd = 0xFF,
uint8_t configCmd = 0xFF);
String dayToString(const uint8_t day_of_week, const int8_t offset = 0);
String daysBitmaskToString(uint8_t daysBitmap, uint8_t offset = 0);
String channelToString(const uint8_t channel);
String htmlEscape(const String unescaped);
String msToString(uint32_t const msecs);
String minsToString(const uint16_t mins);
uint8_t sumNibbles(const uint8_t * const start, const uint16_t length,
const uint8_t init = 0);
uint8_t sumNibbles(const uint64_t data, const uint8_t count = 16,
const uint8_t init = 0, const bool nibbleonly = true);
uint16_t sumBytes(const uint64_t data, const uint8_t count = 8,
const uint8_t init = 0, const bool byteonly = true);
uint8_t bcdToUint8(const uint8_t bcd);
uint8_t uint8ToBcd(const uint8_t integer);
bool getBit(const uint64_t data, const uint8_t position,
const uint8_t size = 64);
bool getBit(const uint8_t data, const uint8_t position);
#define GETBIT8(a, b) ((a) & ((uint8_t)1 << (b)))
#define GETBIT16(a, b) ((a) & ((uint16_t)1 << (b)))
#define GETBIT32(a, b) ((a) & ((uint32_t)1 << (b)))
#define GETBIT64(a, b) ((a) & ((uint64_t)1 << (b)))
#define GETBITS8(data, offset, size) \
(((data) & (((uint8_t)UINT8_MAX >> (8 - (size))) << (offset))) >> (offset))
#define GETBITS16(data, offset, size) \
(((data) & (((uint16_t)UINT16_MAX >> (16 - (size))) << (offset))) >> \
(offset))
#define GETBITS32(data, offset, size) \
(((data) & (((uint32_t)UINT32_MAX >> (32 - (size))) << (offset))) >> \
(offset))
#define GETBITS64(data, offset, size) \
(((data) & (((uint64_t)UINT64_MAX >> (64 - (size))) << (offset))) >> \
(offset))
uint64_t setBit(const uint64_t data, const uint8_t position,
const bool on = true, const uint8_t size = 64);
uint8_t setBit(const uint8_t data, const uint8_t position,
const bool on = true);
void setBit(uint8_t * const data, const uint8_t position,
const bool on = true);
void setBit(uint32_t * const data, const uint8_t position,
const bool on = true);
void setBit(uint64_t * const data, const uint8_t position,
const bool on = true);
void setBits(uint8_t * const dst, const uint8_t offset, const uint8_t nbits,
const uint8_t data);
void setBits(uint32_t * const dst, const uint8_t offset, const uint8_t nbits,
const uint32_t data);
void setBits(uint64_t * const dst, const uint8_t offset, const uint8_t nbits,
const uint64_t data);
uint8_t * invertBytePairs(uint8_t *ptr, const uint16_t length);
bool checkInvertedBytePairs(const uint8_t * const ptr, const uint16_t length);
uint8_t lowLevelSanityCheck(void);
} // namespace irutils
#endif // IRUTILS_H_

View File

@@ -0,0 +1,25 @@
// Copyright 2019 - David Conran (@crankyoldgit)
#ifndef I18N_H_
#define I18N_H_
#include "IRremoteESP8266.h"
// Load the appropriate locale header file.
#ifndef _IR_LOCALE_
#define _IR_LOCALE_ en-AU
#endif // _IR_LOCALE_
#define ENQUOTE_(x) #x
#define ENQUOTE(x) ENQUOTE_(x)
// Load the desired/requested locale.
#ifdef _IR_LOCALE_
#include ENQUOTE(locale/_IR_LOCALE_.h)
#endif // _IR_LOCALE_
// Now that any specific locale has been loaded, we can safely load the defaults
// as the defaults should not override anything that has now set.
#include "locale/defaults.h"
#endif // I18N_H_

View File

@@ -0,0 +1,362 @@
// Copyright 2021 David Conran (crankyoldgit)
/// @file
/// @brief Support for Airton protocol
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1670
#include "ir_Airton.h"
#include <algorithm>
#include "IRrecv.h"
#include "IRsend.h"
#include "IRtext.h"
#include "IRutils.h"
const uint16_t kAirtonHdrMark = 6630;
const uint16_t kAirtonBitMark = 400;
const uint16_t kAirtonHdrSpace = 3350;
const uint16_t kAirtonOneSpace = 1260;
const uint16_t kAirtonZeroSpace = 430;
const uint16_t kAirtonFreq = 38000; // Hz. (Just a guess)
using irutils::addBoolToString;
using irutils::addModeToString;
using irutils::addFanToString;
using irutils::addTempToString;
using irutils::sumBytes;
#if SEND_AIRTON
// Function should be safe up to 64 bits.
/// Send a Airton formatted message.
/// Status: STABLE / Confirmed working.
/// @param[in] data containing the IR command.
/// @param[in] nbits Nr. of bits to send. usually kAirtonBits
/// @param[in] repeat Nr. of times the message is to be repeated.
void IRsend::sendAirton(const uint64_t data, const uint16_t nbits,
const uint16_t repeat) {
sendGeneric(kAirtonHdrMark, kAirtonHdrSpace,
kAirtonBitMark, kAirtonOneSpace,
kAirtonBitMark, kAirtonZeroSpace,
kAirtonBitMark, kDefaultMessageGap,
data, nbits, kAirtonFreq, false, repeat, kDutyDefault);
}
#endif // SEND_AIRTON
#if DECODE_AIRTON
/// Decode the supplied Airton message.
/// Status: STABLE / Confirmed working. LSBF ordering confirmed via temperature.
/// @param[in,out] results Ptr to the data to decode & where to store the decode
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// @param[in] strict Flag indicating if we should perform strict matching.
/// @return A boolean. True if it can decode it, false if it can't.
bool IRrecv::decodeAirton(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
if (results->rawlen < 2 * nbits + kHeader + kFooter - offset)
return false; // Too short a message to match.
if (strict && nbits != kAirtonBits)
return false;
// Header + Data + Footer
if (!matchGeneric(&(results->rawbuf[offset]), &(results->value),
results->rawlen - offset, nbits,
kAirtonHdrMark, kAirtonHdrSpace,
kAirtonBitMark, kAirtonOneSpace,
kAirtonBitMark, kAirtonZeroSpace,
kAirtonBitMark, kDefaultMessageGap,
true, kUseDefTol, kMarkExcess, false)) return false;
// Compliance
if (strict && !IRAirtonAc::validChecksum(results->value)) return false;
// Success
results->decode_type = decode_type_t::AIRTON;
results->bits = nbits;
results->command = 0;
results->address = 0;
return true;
}
#endif // DECODE_AIRTON
/// 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?
IRAirtonAc::IRAirtonAc(const uint16_t pin, const bool inverted,
const bool use_modulation)
: _irsend(pin, inverted, use_modulation) { stateReset(); }
/// Set up hardware to be able to send a message.
void IRAirtonAc::begin(void) { _irsend.begin(); }
#if SEND_AIRTON
/// Send the current internal state as an IR message.
/// @param[in] repeat Nr. of times the message will be repeated.
void IRAirtonAc::send(const uint16_t repeat) {
_irsend.sendAirton(getRaw(), kAirtonBits, repeat);
}
#endif // SEND_AIRTON
/// Calculate the checksum for the supplied state.
/// @param[in] state The source state to generate the checksum from.
/// @return The checksum value.
uint8_t IRAirtonAc::calcChecksum(const uint64_t state) {
return (uint8_t)(0x7F - sumBytes(state, 6)) ^ 0x2C;
}
/// Verify the checksum is valid for a given state.
/// @param[in] state The value to verify the checksum of.
/// @return A boolean indicating if it's checksum is valid.
bool IRAirtonAc::validChecksum(const uint64_t state) {
AirtonProtocol p;
p.raw = state;
return p.Sum == IRAirtonAc::calcChecksum(state);
}
/// Update the checksum value for the internal state.
void IRAirtonAc::checksum(void) { _.Sum = IRAirtonAc::calcChecksum(_.raw); }
/// Reset the internals of the object to a known good state.
void IRAirtonAc::stateReset(void) { setRaw(0x11D3); }
/// Get the raw state of the object, suitable to be sent with the appropriate
/// IRsend object method.
/// @return A copy to the internal state.
uint64_t IRAirtonAc::getRaw(void) {
checksum(); // Ensure correct bit array before returning
return _.raw;
}
/// Set the raw state of the object.
/// @param[in] state The raw state from the native IR message.
void IRAirtonAc::setRaw(const uint64_t state) { _.raw = state; }
/// Set the internal state to have the power on.
void IRAirtonAc::on(void) { setPower(true); }
/// Set the internal state to have the power off.
void IRAirtonAc::off(void) { setPower(false); }
/// Set the internal state to have the desired power.
/// @param[in] on The desired power state.
void IRAirtonAc::setPower(const bool on) {
_.Power = on;
setMode(getMode()); // Re-do the mode incase we need to do something special.
}
/// Get the power setting from the internal state.
/// @return A boolean indicating the power setting.
bool IRAirtonAc::getPower(void) const { return _.Power; }
/// Get the current operation mode setting.
/// @return The current operation mode.
uint8_t IRAirtonAc::getMode(void) const { return _.Mode; }
/// Set the desired operation mode.
/// @param[in] mode The desired operation mode.
void IRAirtonAc::setMode(const uint8_t mode) {
// Changing the mode always removes the sleep setting.
if (mode != _.Mode) setSleep(false);
// Set the actual mode.
_.Mode = (mode > kAirtonHeat) ? kAirtonAuto : mode;
// Handle special settings for each mode.
switch (_.Mode) {
case kAirtonAuto:
setTemp(25); // Auto has a fixed temp.
_.NotAutoOn = !getPower();
break;
case kAirtonHeat:
// When powered on and in Heat mode, set a special bit.
_.HeatOn = getPower();
// FALL-THRU
default:
_.NotAutoOn = true;
}
// Reset the economy setting if we need to.
setEcono(getEcono());
}
/// Convert a stdAc::opmode_t enum into its native mode.
/// @param[in] mode The enum to be converted.
/// @return The native equivalent of the enum.
uint8_t IRAirtonAc::convertMode(const stdAc::opmode_t mode) {
switch (mode) {
case stdAc::opmode_t::kCool: return kAirtonCool;
case stdAc::opmode_t::kHeat: return kAirtonHeat;
case stdAc::opmode_t::kDry: return kAirtonDry;
case stdAc::opmode_t::kFan: return kAirtonFan;
default: return kAirtonAuto;
}
}
/// Convert a native mode into its stdAc equivalent.
/// @param[in] mode The native setting to be converted.
/// @return The stdAc equivalent of the native setting.
stdAc::opmode_t IRAirtonAc::toCommonMode(const uint8_t mode) {
switch (mode) {
case kAirtonCool: return stdAc::opmode_t::kCool;
case kAirtonHeat: return stdAc::opmode_t::kHeat;
case kAirtonDry: return stdAc::opmode_t::kDry;
case kAirtonFan: return stdAc::opmode_t::kFan;
default: return stdAc::opmode_t::kAuto;
}
}
/// Set the temperature.
/// @param[in] degrees The temperature in degrees celsius.
void IRAirtonAc::setTemp(const uint8_t degrees) {
uint8_t temp = std::max(kAirtonMinTemp, degrees);
temp = std::min(kAirtonMaxTemp, temp);
if (_.Mode == kAirtonAuto) temp = kAirtonMaxTemp; // Auto has a fixed temp.
_.Temp = temp - kAirtonMinTemp;
}
/// Get the current temperature setting.
/// @return Get current setting for temp. in degrees celsius.
uint8_t IRAirtonAc::getTemp(void) const { return _.Temp + kAirtonMinTemp; }
/// Set the speed of the fan.
/// @param[in] speed The desired setting.
void IRAirtonAc::setFan(const uint8_t speed) {
_.Fan = (speed > kAirtonFanMax) ? kAirtonFanAuto : speed;
}
/// Get the current fan speed setting.
/// @return The current fan speed.
uint8_t IRAirtonAc::getFan(void) const { return _.Fan; }
/// Convert a stdAc::fanspeed_t enum into it's native speed.
/// @param[in] speed The enum to be converted.
/// @return The native equivalent of the enum.
uint8_t IRAirtonAc::convertFan(const stdAc::fanspeed_t speed) {
switch (speed) {
case stdAc::fanspeed_t::kMin: return kAirtonFanMin;
case stdAc::fanspeed_t::kLow: return kAirtonFanLow;
case stdAc::fanspeed_t::kMedium: return kAirtonFanMed;
case stdAc::fanspeed_t::kHigh: return kAirtonFanHigh;
case stdAc::fanspeed_t::kMax: return kAirtonFanMax;
default: return kAirtonFanAuto;
}
}
/// Convert a native fan speed into its stdAc equivalent.
/// @param[in] speed The native setting to be converted.
/// @return The stdAc equivalent of the native setting.
stdAc::fanspeed_t IRAirtonAc::toCommonFanSpeed(const uint8_t speed) {
switch (speed) {
case kAirtonFanMax: return stdAc::fanspeed_t::kMax;
case kAirtonFanHigh: return stdAc::fanspeed_t::kHigh;
case kAirtonFanMed: return stdAc::fanspeed_t::kMedium;
case kAirtonFanLow: return stdAc::fanspeed_t::kLow;
case kAirtonFanMin: return stdAc::fanspeed_t::kMin;
default: return stdAc::fanspeed_t::kAuto;
}
}
/// Set the Vertical Swing setting of the A/C.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRAirtonAc::setSwingV(const bool on) { _.SwingV = on; }
/// Get the Vertical Swing setting of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRAirtonAc::getSwingV(void) const { return _.SwingV; }
/// Set the Light/LED/Display setting of the A/C.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRAirtonAc::setLight(const bool on) { _.Light = on; }
/// Get the Light/LED/Display setting of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRAirtonAc::getLight(void) const { return _.Light; }
/// Set the Economy setting of the A/C.
/// @param[in] on true, the setting is on. false, the setting is off.
/// @note Only available in Cool mode.
void IRAirtonAc::setEcono(const bool on) {
_.Econo = on && (getMode() == kAirtonCool);
}
/// Get the Economy setting of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRAirtonAc::getEcono(void) const { return _.Econo; }
/// Set the Turbo setting of the A/C.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRAirtonAc::setTurbo(const bool on) {
_.Turbo = on;
// Pressing the turbo button sets the fan to max as well.
if (on) setFan(kAirtonFanMax);
}
/// Get the Turbo setting of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRAirtonAc::getTurbo(void) const { return _.Turbo; }
/// Set the Sleep setting of the A/C.
/// @param[in] on true, the setting is on. false, the setting is off.
/// @note Sleep not available in fan or auto mode.
void IRAirtonAc::setSleep(const bool on) {
switch (getMode()) {
case kAirtonAuto:
case kAirtonFan: _.Sleep = false; break;
default: _.Sleep = on;
}
}
/// Get the Sleep setting of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRAirtonAc::getSleep(void) const { return _.Sleep; }
/// Set the Health/Filter setting of the A/C.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRAirtonAc::setHealth(const bool on) { _.Health = on; }
/// Get the Health/Filter setting of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRAirtonAc::getHealth(void) const { return _.Health; }
/// Convert the current internal state into its stdAc::state_t equivalent.
/// @return The stdAc equivalent of the native settings.
stdAc::state_t IRAirtonAc::toCommon(void) const {
stdAc::state_t result{};
result.protocol = decode_type_t::AIRTON;
result.power = getPower();
result.mode = toCommonMode(getMode());
result.celsius = true;
result.degrees = getTemp();
result.fanspeed = toCommonFanSpeed(getFan());
result.swingv = getSwingV() ? stdAc::swingv_t::kAuto : stdAc::swingv_t::kOff;
result.econo = getEcono();
result.turbo = getTurbo();
result.filter = getHealth();
result.light = getLight();
result.sleep = getSleep() ? 0 : -1;
// Not supported.
result.model = -1;
result.swingh = stdAc::swingh_t::kOff;
result.quiet = false;
result.clean = 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 IRAirtonAc::toString(void) const {
String result = "";
result.reserve(135); // Reserve some heap for the string to reduce fragging.
result += addBoolToString(getPower(), kPowerStr, false);
result += addModeToString(_.Mode, kAirtonAuto, kAirtonCool,
kAirtonHeat, kAirtonDry, kAirtonFan);
result += addFanToString(_.Fan, kAirtonFanHigh, kAirtonFanLow,
kAirtonFanAuto, kAirtonFanMin, kAirtonFanMed,
kAirtonFanMax);
result += addTempToString(getTemp());
result += addBoolToString(getSwingV(), kSwingVStr);
result += addBoolToString(getEcono(), kEconoStr);
result += addBoolToString(getTurbo(), kTurboStr);
result += addBoolToString(getLight(), kLightStr);
result += addBoolToString(getHealth(), kHealthStr);
result += addBoolToString(getSleep(), kSleepStr);
return result;
}

View File

@@ -0,0 +1,134 @@
// Copyright 2021 David Conran (crankyoldgit)
/// @file
/// @brief Support for Airton protocol
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1670
// Supports:
// Brand: Airton, Model: SMVH09B-2A2A3NH ref. 409730 A/C
// Brand: Airton, Model: RD1A1 remote
#ifndef IR_AIRTON_H_
#define IR_AIRTON_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
/// Native representation of a Airton 56 A/C message.
/// @see https://docs.google.com/spreadsheets/d/1Kpq7WCkh85heLnTQGlwUfCR6eeu_vfBHvhii8wtP4LU/edit?usp=sharing
union AirtonProtocol{
uint64_t raw; ///< The state in code form.
struct { // Common
// Byte 1 & 0 (LSB)
uint16_t Header :16; // Header. (0x11D3)
// Byte 2
uint8_t Mode :3; // Operating Mode
uint8_t Power :1; // Power Control
uint8_t Fan :3;
uint8_t Turbo :1;
// Byte 3
uint8_t Temp :4; // Degrees Celsius (+16 offset)
uint8_t :4; // Unknown / Unused.
// Byte 4
uint8_t SwingV :1;
uint8_t :7; // Unknown / Unused.
// Byte 5
uint8_t Econo :1;
uint8_t Sleep :1;
uint8_t NotAutoOn :1;
uint8_t :1; // Unknown / Unused.
uint8_t HeatOn :1;
uint8_t :1; // Unknown / Unused.
uint8_t Health :1;
uint8_t Light :1;
// Byte 6
uint8_t Sum :8; // Sepecial checksum value
};
};
// Constants
const uint8_t kAirtonAuto = 0b000; // 0
const uint8_t kAirtonCool = 0b001; // 1
const uint8_t kAirtonDry = 0b010; // 2
const uint8_t kAirtonFan = 0b011; // 3
const uint8_t kAirtonHeat = 0b100; // 4
const uint8_t kAirtonFanAuto = 0b000; // 0
const uint8_t kAirtonFanMin = 0b001; // 1
const uint8_t kAirtonFanLow = 0b010; // 2
const uint8_t kAirtonFanMed = 0b011; // 3
const uint8_t kAirtonFanHigh = 0b100; // 4
const uint8_t kAirtonFanMax = 0b101; // 5
const uint8_t kAirtonMinTemp = 16; // 16C
const uint8_t kAirtonMaxTemp = 25; // 25C
/// Class for handling detailed Airton 56-bit A/C messages.
class IRAirtonAc {
public:
explicit IRAirtonAc(const uint16_t pin, const bool inverted = false,
const bool use_modulation = true);
void stateReset(void);
#if SEND_AIRTON
void send(const uint16_t repeat = kAirtonDefaultRepeat);
/// 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_AIRTON
void begin(void);
void on(void);
void off(void);
void setPower(const bool on);
bool getPower(void) const;
void setTemp(const uint8_t degrees);
uint8_t getTemp(void) const;
void setFan(const uint8_t speed);
uint8_t getFan(void) const;
void setMode(const uint8_t mode);
uint8_t getMode(void) const;
uint64_t getRaw(void);
void setRaw(const uint64_t data);
void setLight(const bool on);
bool getLight(void) const;
void setEcono(const bool on);
bool getEcono(void) const;
void setTurbo(const bool on);
bool getTurbo(void) const;
void setHealth(const bool on);
bool getHealth(void) const;
void setSleep(const bool on);
bool getSleep(void) const;
void setSwingV(const bool on);
bool getSwingV(void) const;
static bool validChecksum(const uint64_t data);
static uint8_t calcChecksum(const uint64_t data);
static uint8_t convertMode(const stdAc::opmode_t mode);
static uint8_t convertFan(const stdAc::fanspeed_t speed);
static uint8_t convertSwingV(const stdAc::swingv_t position);
static stdAc::opmode_t toCommonMode(const uint8_t mode);
static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed);
stdAc::state_t toCommon(void) const;
String toString(void) const;
#ifndef UNIT_TEST
private:
IRsend _irsend; ///< Instance of the IR send class
#else // UNIT_TEST
/// @cond IGNORE
IRsendTest _irsend; ///< Instance of the testing IR send class
/// @endcond
#endif // UNIT_TEST
AirtonProtocol _;
void checksum(void);
};
#endif // IR_AIRTON_H_

View File

@@ -0,0 +1,286 @@
// Copyright 2020 David Conran
#include "ir_Airwell.h"
#include <algorithm>
#include "IRrecv.h"
#include "IRsend.h"
#include "IRtext.h"
#include "IRutils.h"
/// @file
/// @brief Airwell "Manchester code" based protocol.
/// Some other Airwell products use the COOLIX protocol.
const uint8_t kAirwellOverhead = 4;
const uint16_t kAirwellHalfClockPeriod = 950; // uSeconds
const uint16_t kAirwellHdrMark = 3 * kAirwellHalfClockPeriod; // uSeconds
const uint16_t kAirwellHdrSpace = 3 * kAirwellHalfClockPeriod; // uSeconds
const uint16_t kAirwellFooterMark = 5 * kAirwellHalfClockPeriod; // uSeconds
using irutils::addBoolToString;
using irutils::addModeToString;
using irutils::addFanToString;
using irutils::addTempToString;
#if SEND_AIRWELL
/// Send an Airwell Manchester Code formatted message.
/// Status: BETA / Appears to be working.
/// @param[in] data The message to be sent.
/// @param[in] nbits The number of bits of the message to be sent.
/// @param[in] repeat The number of times the command is to be repeated.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1069
void IRsend::sendAirwell(uint64_t data, uint16_t nbits, uint16_t repeat) {
// Header + Data
sendManchester(kAirwellHdrMark, kAirwellHdrMark, kAirwellHalfClockPeriod,
0, 0, data, nbits, 38000, true, repeat, kDutyDefault, false);
// Footer
mark(kAirwellHdrMark + kAirwellHalfClockPeriod);
space(kDefaultMessageGap); // A guess.
}
#endif
#if DECODE_AIRWELL
/// Decode the supplied Airwell "Manchester code" message.
///
/// Status: BETA / Appears to be working.
/// @param[in,out] results Ptr to the data to decode & where to store the decode
/// result.
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// @param[in] strict Flag indicating if we should perform strict matching.
/// @return A boolean. True if it can decode it, false if it can't.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1069
bool IRrecv::decodeAirwell(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
if (results->rawlen < nbits + kAirwellOverhead - offset)
return false; // Too short a message to match.
// Compliance
if (strict && nbits != kAirwellBits)
return false; // Doesn't match our protocol defn.
// Header #1 + Data #1 + Footer #1 (There are total of 3 sections)
uint16_t used = matchManchester(results->rawbuf + offset, &results->value,
results->rawlen - offset, nbits,
kAirwellHdrMark, kAirwellHdrMark,
kAirwellHalfClockPeriod,
kAirwellHdrMark, kAirwellHdrSpace,
true, kUseDefTol, kMarkExcess, true, false);
if (used == 0) return false;
offset += used;
// Success
results->decode_type = decode_type_t::AIRWELL;
results->bits = nbits;
results->address = 0;
results->command = 0;
return true;
}
#endif
/// 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?
IRAirwellAc::IRAirwellAc(const uint16_t pin, const bool inverted,
const bool use_modulation)
: _irsend(pin, inverted, use_modulation) { stateReset(); }
/// Set up hardware to be able to send a message.
void IRAirwellAc::begin(void) { _irsend.begin(); }
/// Get the raw state of the object, suitable to be sent with the appropriate
/// IRsend object method.
/// @return A copy of the internal state.
uint64_t IRAirwellAc::getRaw(void) const {
return _.raw;
}
/// Set the raw state of the object.
/// @param[in] state The raw state from the native IR message.
void IRAirwellAc::setRaw(const uint64_t state) {
_.raw = state;
}
#if SEND_AIRWELL
/// Send the current internal state as an IR message.
/// @param[in] repeat Nr. of times the message will be repeated.
void IRAirwellAc::send(const uint16_t repeat) {
_irsend.sendAirwell(getRaw(), kAirwellBits, repeat);
}
#endif // SEND_AIRWELL
/// Reset the internals of the object to a known good state.
void IRAirwellAc::stateReset(void) {
_.raw = kAirwellKnownGoodState;
}
/// Turn on/off the Power Airwell setting.
/// @param[in] on The desired setting state.
void IRAirwellAc::setPowerToggle(const bool on) {
_.PowerToggle = on;
}
/// Get the power toggle setting from the internal state.
/// @return A boolean indicating the setting.
bool IRAirwellAc::getPowerToggle(void) const {
return _.PowerToggle;
}
/// Get the current operation mode setting.
/// @return The current operation mode.
uint8_t IRAirwellAc::getMode(void) const {
return _.Mode;
}
/// Set the desired operation mode.
/// @param[in] mode The desired operation mode.
void IRAirwellAc::setMode(const uint8_t mode) {
switch (mode) {
case kAirwellFan:
case kAirwellCool:
case kAirwellHeat:
case kAirwellDry:
case kAirwellAuto:
_.Mode = mode;
break;
default:
_.Mode = kAirwellAuto;
}
setFan(getFan()); // Ensure the fan is at the correct speed for the new mode.
}
/// Convert a stdAc::opmode_t enum into its native mode.
/// @param[in] mode The enum to be converted.
/// @return The native equivalent of the enum.
uint8_t IRAirwellAc::convertMode(const stdAc::opmode_t mode) {
switch (mode) {
case stdAc::opmode_t::kCool: return kAirwellCool;
case stdAc::opmode_t::kHeat: return kAirwellHeat;
case stdAc::opmode_t::kDry: return kAirwellDry;
case stdAc::opmode_t::kFan: return kAirwellFan;
default: return kAirwellAuto;
}
}
/// Convert a native mode into its stdAc equivalent.
/// @param[in] mode The native setting to be converted.
/// @return The stdAc equivalent of the native setting.
stdAc::opmode_t IRAirwellAc::toCommonMode(const uint8_t mode) {
switch (mode) {
case kAirwellCool: return stdAc::opmode_t::kCool;
case kAirwellHeat: return stdAc::opmode_t::kHeat;
case kAirwellDry: return stdAc::opmode_t::kDry;
case kAirwellFan: return stdAc::opmode_t::kFan;
default: return stdAc::opmode_t::kAuto;
}
}
/// Set the speed of the fan.
/// @param[in] speed The desired setting.
/// @note The speed is locked to Low when in Dry mode.
void IRAirwellAc::setFan(const uint8_t speed) {
_.Fan = (_.Mode == kAirwellDry) ? kAirwellFanLow
: std::min(speed, kAirwellFanAuto);
}
/// Get the current fan speed setting.
/// @return The current fan speed.
uint8_t IRAirwellAc::getFan(void) const {
return _.Fan;
}
/// Convert a stdAc::fanspeed_t enum into it's native speed.
/// @param[in] speed The enum to be converted.
/// @return The native equivalent of the enum.
uint8_t IRAirwellAc::convertFan(const stdAc::fanspeed_t speed) {
switch (speed) {
case stdAc::fanspeed_t::kMin:
case stdAc::fanspeed_t::kLow:
return kAirwellFanLow;
case stdAc::fanspeed_t::kMedium:
return kAirwellFanMedium;
case stdAc::fanspeed_t::kHigh:
case stdAc::fanspeed_t::kMax:
return kAirwellFanHigh;
default:
return kAirwellFanAuto;
}
}
/// Convert a native fan speed into its stdAc equivalent.
/// @param[in] speed The native setting to be converted.
/// @return The stdAc equivalent of the native setting.
stdAc::fanspeed_t IRAirwellAc::toCommonFanSpeed(const uint8_t speed) {
switch (speed) {
case kAirwellFanHigh: return stdAc::fanspeed_t::kMax;
case kAirwellFanMedium: return stdAc::fanspeed_t::kMedium;
case kAirwellFanLow: return stdAc::fanspeed_t::kMin;
default: return stdAc::fanspeed_t::kAuto;
}
}
/// Set the temperature.
/// @param[in] degrees The temperature in degrees celsius.
void IRAirwellAc::setTemp(const uint8_t degrees) {
uint8_t temp = std::max(kAirwellMinTemp, degrees);
temp = std::min(kAirwellMaxTemp, temp);
_.Temp = (temp - kAirwellMinTemp + 1);
}
/// Get the current temperature setting.
/// @return Get current setting for temp. in degrees celsius.
uint8_t IRAirwellAc::getTemp(void) const {
return _.Temp + kAirwellMinTemp - 1;
}
/// Convert the current internal state into its stdAc::state_t equivalent.
/// @param[in] prev Ptr to the previous state if required.
/// @return The stdAc equivalent of the native settings.
stdAc::state_t IRAirwellAc::toCommon(const stdAc::state_t *prev) const {
stdAc::state_t result{};
// Start with the previous state if given it.
if (prev != NULL) {
result = *prev;
} else {
// Set defaults for non-zero values that are not implicitly set for when
// there is no previous state.
// e.g. Any setting that toggles should probably go here.
result.power = false;
}
result.protocol = decode_type_t::AIRWELL;
if (_.PowerToggle) result.power = !result.power;
result.mode = toCommonMode(_.Mode);
result.celsius = true;
result.degrees = getTemp();
result.fanspeed = toCommonFanSpeed(_.Fan);
// Not supported.
result.model = -1;
result.turbo = false;
result.swingv = stdAc::swingv_t::kOff;
result.swingh = stdAc::swingh_t::kOff;
result.light = false;
result.filter = false;
result.econo = false;
result.quiet = false;
result.clean = false;
result.beep = false;
result.sleep = -1;
result.clock = -1;
return result;
}
/// Convert the current internal state into a human readable string.
/// @return A human readable string.
String IRAirwellAc::toString(void) const {
String result = "";
result.reserve(70); // Reserve some heap for the string to reduce fragging.
result += addBoolToString(_.PowerToggle, kPowerToggleStr, false);
result += addModeToString(_.Mode, kAirwellAuto, kAirwellCool,
kAirwellHeat, kAirwellDry, kAirwellFan);
result += addFanToString(_.Fan, kAirwellFanHigh, kAirwellFanLow,
kAirwellFanAuto, kAirwellFanAuto,
kAirwellFanMedium);
result += addTempToString(getTemp());
return result;
}

View File

@@ -0,0 +1,101 @@
// Copyright 2020 David Conran
/// @file
/// @brief Airwell "Manchester code" based protocol.
/// Some other Airwell products use the COOLIX protocol.
// Supports:
// Brand: Airwell, Model: RC08W remote
// Brand: Airwell, Model: RC04 remote
// Brand: Airwell, Model: DLS 21 DCI R410 AW A/C
#ifndef IR_AIRWELL_H_
#define IR_AIRWELL_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
/// Native representation of a Airwell A/C message.
union AirwellProtocol{
uint64_t raw; // The state of the IR remote in native IR code form.
struct {
uint64_t :19;
uint64_t Temp :4;
uint64_t :5;
uint64_t Fan :2;
uint64_t Mode :3;
uint64_t PowerToggle:1;
uint64_t :0;
};
};
// Constants
const uint64_t kAirwellKnownGoodState = 0x140500002; // Mode Fan, Speed 1, 25C
// Temperature
const uint8_t kAirwellMinTemp = 16; // Celsius
const uint8_t kAirwellMaxTemp = 30; // Celsius
// Fan
const uint8_t kAirwellFanLow = 0; // 0b00
const uint8_t kAirwellFanMedium = 1; // 0b01
const uint8_t kAirwellFanHigh = 2; // 0b10
const uint8_t kAirwellFanAuto = 3; // 0b11
// Modes
const uint8_t kAirwellCool = 1; // 0b001
const uint8_t kAirwellHeat = 2; // 0b010
const uint8_t kAirwellAuto = 3; // 0b011
const uint8_t kAirwellDry = 4; // 0b100
const uint8_t kAirwellFan = 5; // 0b101
// Classes
/// Class for handling detailed Airwell A/C messages.
class IRAirwellAc {
public:
explicit IRAirwellAc(const uint16_t pin, const bool inverted = false,
const bool use_modulation = true);
void stateReset();
#if SEND_AIRWELL
void send(const uint16_t repeat = kAirwellMinRepeats);
/// 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_AIRWELL
void begin();
void setPowerToggle(const bool on);
bool getPowerToggle() const;
void setTemp(const uint8_t temp);
uint8_t getTemp() const;
void setFan(const uint8_t speed);
uint8_t getFan() const;
void setMode(const uint8_t mode);
uint8_t getMode() const;
uint64_t getRaw() const;
void setRaw(const uint64_t state);
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);
static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed);
stdAc::state_t toCommon(const stdAc::state_t *prev = NULL) const;
String toString() const;
#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
AirwellProtocol _;
};
#endif // IR_AIRWELL_H_

View File

@@ -0,0 +1,105 @@
// Copyright 2017 David Conran
#include "IRrecv.h"
#include "IRsend.h"
/// @file
/// @brief Aiwa based protocol.
/// Based off the RC-T501 RCU
/// Inspired by IRremoteESP8266's implementation
/// @see https://github.com/z3t0/Arduino-IRremote
// Supports:
// Brand: Aiwa, Model: RC-T501 RCU
const uint16_t kAiwaRcT501PreBits = 26;
const uint16_t kAiwaRcT501PostBits = 1;
// NOTE: These are the compliment (inverted) of lirc values as
// lirc uses a '0' for a mark, and a '1' for a space.
const uint64_t kAiwaRcT501PreData = 0x1D8113FULL; // 26-bits
const uint64_t kAiwaRcT501PostData = 1ULL;
#if SEND_AIWA_RC_T501
/// Send an Aiwa RC T501 formatted message.
/// Status: BETA / Should work.
/// @param[in] data The message to be sent.
/// @param[in] nbits The number of bits of the message to be sent.
/// Typically kAiwaRcT501Bits. Max is 37 = (64 - 27)
/// @param[in] repeat The number of times the command is to be repeated.
/// @see http://lirc.sourceforge.net/remotes/aiwa/RC-T501
void IRsend::sendAiwaRCT501(uint64_t data, uint16_t nbits, uint16_t repeat) {
// Appears to be an extended NEC1 protocol. i.e. 42 bits instead of 32 bits.
// So use sendNEC instead, however the twist is it has a fixed 26 bit
// prefix, and a fixed postfix bit.
uint64_t new_data = ((kAiwaRcT501PreData << (nbits + kAiwaRcT501PostBits)) |
(data << kAiwaRcT501PostBits) | kAiwaRcT501PostData);
nbits += kAiwaRcT501PreBits + kAiwaRcT501PostBits;
if (nbits > sizeof(new_data) * 8)
return; // We are overflowing. Abort, and don't send.
sendNEC(new_data, nbits, repeat);
}
#endif
#if DECODE_AIWA_RC_T501
/// Decode the supplied Aiwa RC T501 message.
/// Status: BETA / Should work.
/// @param[in,out] results Ptr to the data to decode & where to store the decode
/// result.
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// @param[in] strict Flag indicating if we should perform strict matching.
/// @return A boolean. True if it can decode it, false if it can't.
/// @note
/// Aiwa RC T501 appears to be a 42 bit variant of the NEC1 protocol.
/// However, we historically (original Arduino IRremote project) treats it as
/// a 15 bit (data) protocol. So, we expect nbits to typically be 15, and we
/// will remove the prefix and postfix from the raw data, and use that as
/// the result.
/// @see http://www.sbprojects.net/knowledge/ir/nec.php
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1069
bool IRrecv::decodeAiwaRCT501(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
// Compliance
if (strict && nbits != kAiwaRcT501Bits)
return false; // Doesn't match our protocol defn.
// Add on the pre & post bits to our requested bit length.
uint16_t expected_nbits = nbits + kAiwaRcT501PreBits + kAiwaRcT501PostBits;
uint64_t new_data;
if (expected_nbits > sizeof(new_data) * 8)
return false; // We can't possibly match something that big.
// Decode it as a much bigger (non-standard) NEC message, so we have to turn
// off strict mode checking for NEC.
if (!decodeNEC(results, offset, expected_nbits, false))
return false; // The NEC decode had a problem, so we should too.
uint16_t actual_bits = results->bits;
new_data = results->value;
if (actual_bits < expected_nbits)
return false; // The data we caught was undersized. Throw it back.
if ((new_data & 0x1ULL) != kAiwaRcT501PostData)
return false; // The post data doesn't match, so it can't be this protocol.
// Trim off the post data bit.
new_data >>= kAiwaRcT501PostBits;
actual_bits -= kAiwaRcT501PostBits;
// Extract out our likely new value and put it back in the results.
actual_bits -= kAiwaRcT501PreBits;
results->value = new_data & ((1ULL << actual_bits) - 1);
// Check the prefix data matches.
new_data >>= actual_bits; // Trim off the new data to expose the prefix.
if (new_data != kAiwaRcT501PreData) // Check the prefix.
return false;
// Compliance
if (strict && results->bits != expected_nbits) return false;
// Success
results->decode_type = AIWA_RC_T501;
results->bits = actual_bits;
results->address = 0;
results->command = 0;
return true;
}
#endif

View File

@@ -0,0 +1,354 @@
// Copyright 2019 David Conran
/// @file
/// @brief Amcor A/C protocol.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/385
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/834
#include "ir_Amcor.h"
#include <algorithm>
#include <cstring>
#include "IRrecv.h"
#include "IRsend.h"
#include "IRtext.h"
#include "IRutils.h"
// Constants
const uint16_t kAmcorHdrMark = 8200;
const uint16_t kAmcorHdrSpace = 4200;
const uint16_t kAmcorOneMark = 1500;
const uint16_t kAmcorZeroMark = 600;
const uint16_t kAmcorOneSpace = kAmcorZeroMark;
const uint16_t kAmcorZeroSpace = kAmcorOneMark;
const uint16_t kAmcorFooterMark = 1900;
const uint16_t kAmcorGap = 34300;
const uint8_t kAmcorTolerance = 40;
using irutils::addBoolToString;
using irutils::addModeToString;
using irutils::addFanToString;
using irutils::addTempToString;
#if SEND_AMCOR
/// Send a Amcor HVAC formatted message.
/// Status: STABLE / Reported as working.
/// @param[in] data The message to be sent.
/// @param[in] nbytes The number of bytes of message to be sent.
/// @param[in] repeat The number of times the command is to be repeated.
void IRsend::sendAmcor(const unsigned char data[], const uint16_t nbytes,
const uint16_t repeat) {
// Check if we have enough bytes to send a proper message.
if (nbytes < kAmcorStateLength) return;
sendGeneric(kAmcorHdrMark, kAmcorHdrSpace, kAmcorOneMark, kAmcorOneSpace,
kAmcorZeroMark, kAmcorZeroSpace, kAmcorFooterMark, kAmcorGap,
data, nbytes, 38, false, repeat, kDutyDefault);
}
#endif
#if DECODE_AMCOR
/// Decode the supplied Amcor HVAC message.
/// Status: STABLE / Reported as working.
/// @param[in,out] results Ptr to the data to decode & where to store the decode
/// result.
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// @param[in] strict Flag indicating if we should perform strict matching.
/// @return A boolean. True if it can decode it, false if it can't.
bool IRrecv::decodeAmcor(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
if (results->rawlen <= 2 * nbits + kHeader - 1 + offset)
return false; // Can't possibly be a valid Amcor message.
if (strict && nbits != kAmcorBits)
return false; // We expect Amcor to be 64 bits of message.
uint16_t used;
// Header + Data Block (64 bits) + Footer
used = matchGeneric(results->rawbuf + offset, results->state,
results->rawlen - offset, 64,
kAmcorHdrMark, kAmcorHdrSpace,
kAmcorOneMark, kAmcorOneSpace,
kAmcorZeroMark, kAmcorZeroSpace,
kAmcorFooterMark, kAmcorGap, true,
kAmcorTolerance, 0, false);
if (!used) return false;
offset += used;
if (strict) {
if (!IRAmcorAc::validChecksum(results->state)) return false;
}
// Success
results->bits = nbits;
results->decode_type = AMCOR;
// 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
/// 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?
IRAmcorAc::IRAmcorAc(const uint16_t pin, const bool inverted,
const bool use_modulation)
: _irsend(pin, inverted, use_modulation) { this->stateReset(); }
/// Set up hardware to be able to send a message.
void IRAmcorAc::begin(void) { _irsend.begin(); }
#if SEND_AMCOR
/// Send the current internal state as an IR message.
/// @param[in] repeat Nr. of times the message will be repeated.
void IRAmcorAc::send(const uint16_t repeat) {
_irsend.sendAmcor(getRaw(), kAmcorStateLength, repeat);
}
#endif // SEND_AMCOR
/// Calculate the checksum for the supplied state.
/// @param[in] state The source state to generate the checksum from.
/// @param[in] length Length of the supplied state to checksum.
/// @return The checksum value.
uint8_t IRAmcorAc::calcChecksum(const uint8_t state[], const uint16_t length) {
return irutils::sumNibbles(state, length - 1);
}
/// Verify the checksum is valid for a given state.
/// @param[in] state The array to verify the checksum of.
/// @param[in] length The size of the state.
/// @return A boolean indicating if it's checksum is valid.
bool IRAmcorAc::validChecksum(const uint8_t state[], const uint16_t length) {
return (state[length - 1] == IRAmcorAc::calcChecksum(state, length));
}
/// Update the checksum value for the internal state.
void IRAmcorAc::checksum(void) {
_.Sum = IRAmcorAc::calcChecksum(_.raw, kAmcorStateLength);
}
/// Reset the internals of the object to a known good state.
void IRAmcorAc::stateReset(void) {
for (uint8_t i = 1; i < kAmcorStateLength; i++) _.raw[i] = 0x0;
_.raw[0] = 0x01;
_.Fan = kAmcorFanAuto;
_.Mode = kAmcorAuto;
_.Temp = 25; // 25C
}
/// Get the raw state of the object, suitable to be sent with the appropriate
/// IRsend object method.
/// @return A PTR to the internal state.
uint8_t* IRAmcorAc::getRaw(void) {
checksum(); // Ensure correct bit array before returning
return _.raw;
}
/// Set the raw state of the object.
/// @param[in] state The raw state from the native IR message.
void IRAmcorAc::setRaw(const uint8_t state[]) {
std::memcpy(_.raw, state, kAmcorStateLength);
}
/// Set the internal state to have the power on.
void IRAmcorAc::on(void) { setPower(true); }
/// Set the internal state to have the power off.
void IRAmcorAc::off(void) { setPower(false); }
/// Set the internal state to have the desired power.
/// @param[in] on The desired power state.
void IRAmcorAc::setPower(const bool on) {
_.Power = (on ? kAmcorPowerOn : kAmcorPowerOff);
}
/// Get the power setting from the internal state.
/// @return A boolean indicating the power setting.
bool IRAmcorAc::getPower(void) const {
return _.Power == kAmcorPowerOn;
}
/// Set the temperature.
/// @param[in] degrees The temperature in degrees celsius.
void IRAmcorAc::setTemp(const uint8_t degrees) {
uint8_t temp = std::max(kAmcorMinTemp, degrees);
temp = std::min(kAmcorMaxTemp, temp);
_.Temp = temp;
}
/// Get the current temperature setting.
/// @return Get current setting for temp. in degrees celsius.
uint8_t IRAmcorAc::getTemp(void) const {
return _.Temp;
}
/// Control the current Maximum Cooling or Heating setting. (i.e. Turbo)
/// @note Only allowed in Cool or Heat mode.
/// @param[in] on The desired setting.
void IRAmcorAc::setMax(const bool on) {
if (on) {
switch (_.Mode) {
case kAmcorCool: _.Temp = kAmcorMinTemp; break;
case kAmcorHeat: _.Temp = kAmcorMaxTemp; break;
// Not allowed in all other operating modes.
default: return;
}
}
_.Max = (on ? kAmcorMax : 0);
}
/// Is the Maximum Cooling or Heating setting (i.e. Turbo) setting on?
/// @return The current value.
bool IRAmcorAc::getMax(void) const {
return _.Max == kAmcorMax;
}
/// Set the speed of the fan.
/// @param[in] speed The desired setting.
void IRAmcorAc::setFan(const uint8_t speed) {
switch (speed) {
case kAmcorFanAuto:
case kAmcorFanMin:
case kAmcorFanMed:
case kAmcorFanMax:
_.Fan = speed;
break;
default:
_.Fan = kAmcorFanAuto;
}
}
/// Get the current fan speed setting.
/// @return The current fan speed.
uint8_t IRAmcorAc::getFan(void) const {
return _.Fan;
}
/// Get the current operation mode setting.
/// @return The current operation mode.
uint8_t IRAmcorAc::getMode(void) const {
return _.Mode;
}
/// Set the desired operation mode.
/// @param[in] mode The desired operation mode.
void IRAmcorAc::setMode(const uint8_t mode) {
switch (mode) {
case kAmcorFan:
case kAmcorCool:
case kAmcorHeat:
case kAmcorDry:
case kAmcorAuto:
_.Vent = (mode == kAmcorFan) ? kAmcorVentOn : 0;
_.Mode = mode;
return;
default:
_.Vent = 0;
_.Mode = kAmcorAuto;
break;
}
}
/// Convert a stdAc::opmode_t enum into its native mode.
/// @param[in] mode The enum to be converted.
/// @return The native equivalent of the enum.
uint8_t IRAmcorAc::convertMode(const stdAc::opmode_t mode) {
switch (mode) {
case stdAc::opmode_t::kCool:
return kAmcorCool;
case stdAc::opmode_t::kHeat:
return kAmcorHeat;
case stdAc::opmode_t::kDry:
return kAmcorDry;
case stdAc::opmode_t::kFan:
return kAmcorFan;
default:
return kAmcorAuto;
}
}
/// Convert a stdAc::fanspeed_t enum into it's native speed.
/// @param[in] speed The enum to be converted.
/// @return The native equivalent of the enum.
uint8_t IRAmcorAc::convertFan(const stdAc::fanspeed_t speed) {
switch (speed) {
case stdAc::fanspeed_t::kMin:
case stdAc::fanspeed_t::kLow:
return kAmcorFanMin;
case stdAc::fanspeed_t::kMedium:
return kAmcorFanMed;
case stdAc::fanspeed_t::kHigh:
case stdAc::fanspeed_t::kMax:
return kAmcorFanMax;
default:
return kAmcorFanAuto;
}
}
/// Convert a native mode into its stdAc equivalent.
/// @param[in] mode The native setting to be converted.
/// @return The stdAc equivalent of the native setting.
stdAc::opmode_t IRAmcorAc::toCommonMode(const uint8_t mode) {
switch (mode) {
case kAmcorCool: return stdAc::opmode_t::kCool;
case kAmcorHeat: return stdAc::opmode_t::kHeat;
case kAmcorDry: return stdAc::opmode_t::kDry;
case kAmcorFan: return stdAc::opmode_t::kFan;
default: return stdAc::opmode_t::kAuto;
}
}
/// Convert a native fan speed into its stdAc equivalent.
/// @param[in] speed The native setting to be converted.
/// @return The stdAc equivalent of the native setting.
stdAc::fanspeed_t IRAmcorAc::toCommonFanSpeed(const uint8_t speed) {
switch (speed) {
case kAmcorFanMax: return stdAc::fanspeed_t::kMax;
case kAmcorFanMed: return stdAc::fanspeed_t::kMedium;
case kAmcorFanMin: return stdAc::fanspeed_t::kMin;
default: return stdAc::fanspeed_t::kAuto;
}
}
/// Convert the current internal state into its stdAc::state_t equivalent.
/// @return The stdAc equivalent of the native settings.
stdAc::state_t IRAmcorAc::toCommon(void) const {
stdAc::state_t result{};
result.protocol = decode_type_t::AMCOR;
result.power = getPower();
result.mode = toCommonMode(_.Mode);
result.celsius = true;
result.degrees = _.Temp;
result.fanspeed = toCommonFanSpeed(_.Fan);
// Not supported.
result.model = -1;
result.turbo = false;
result.swingv = stdAc::swingv_t::kOff;
result.swingh = stdAc::swingh_t::kOff;
result.light = false;
result.filter = false;
result.econo = false;
result.quiet = false;
result.clean = false;
result.beep = false;
result.sleep = -1;
result.clock = -1;
return result;
}
/// Convert the current internal state into a human readable string.
/// @return A human readable string.
String IRAmcorAc::toString(void) const {
String result = "";
result.reserve(70); // Reserve some heap for the string to reduce fragging.
result += addBoolToString(getPower(), kPowerStr, false);
result += addModeToString(_.Mode, kAmcorAuto, kAmcorCool,
kAmcorHeat, kAmcorDry, kAmcorFan);
result += addFanToString(_.Fan, kAmcorFanMax, kAmcorFanMin,
kAmcorFanAuto, kAmcorFanAuto,
kAmcorFanMed);
result += addTempToString(_.Temp);
result += addBoolToString(getMax(), kMaxStr);
return result;
}

View File

@@ -0,0 +1,141 @@
// Copyright 2019 David Conran
/// @file
/// @brief Amcor A/C protocol.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/834
/// @remark Kudos to ldellus; For the breakdown and mapping of the bit values.
// Supports:
// Brand: Amcor, Model: ADR-853H A/C
// Brand: Amcor, Model: TAC-495 remote
// Brand: Amcor, Model: TAC-444 remote
#ifndef IR_AMCOR_H_
#define IR_AMCOR_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
/// Native representation of a Amcor A/C message.
union AmcorProtocol{
uint8_t raw[kAmcorStateLength]; // The state of the IR remote.
struct {
// Byte 0
uint8_t :8; // Typically 0x01
// Byte 1
uint8_t Mode :3;
uint8_t :1;
uint8_t Fan :3;
uint8_t :1;
// Byte 2
uint8_t :1;
uint8_t Temp :6;
uint8_t :1;
// Byte 3
uint8_t :8;
// Byte 4
uint8_t :8;
// Byte 5
uint8_t :4;
uint8_t Power :4;
// Byte 6
uint8_t Max :2;
uint8_t :4;
uint8_t Vent :2;
// Byte 7
uint8_t Sum :8;
};
};
// Constants
// Fan Control
const uint8_t kAmcorFanMin = 0b001;
const uint8_t kAmcorFanMed = 0b010;
const uint8_t kAmcorFanMax = 0b011;
const uint8_t kAmcorFanAuto = 0b100;
// Modes
const uint8_t kAmcorCool = 0b001;
const uint8_t kAmcorHeat = 0b010;
const uint8_t kAmcorFan = 0b011; // Aka "Vent"
const uint8_t kAmcorDry = 0b100;
const uint8_t kAmcorAuto = 0b101;
// Temperature
const uint8_t kAmcorMinTemp = 12; // Celsius
const uint8_t kAmcorMaxTemp = 32; // Celsius
// Power
const uint8_t kAmcorPowerOn = 0b0011; // 0x3
const uint8_t kAmcorPowerOff = 0b1100; // 0xC
// Max Mode (aka "Lo" in Cool and "Hi" in Heat)
const uint8_t kAmcorMax = 0b11;
// "Vent" Mode
const uint8_t kAmcorVentOn = 0b11;
// Classes
/// Class for handling detailed Amcor A/C messages.
class IRAmcorAc {
public:
explicit IRAmcorAc(const uint16_t pin, const bool inverted = false,
const bool use_modulation = true);
void stateReset();
#if SEND_AMCOR
void send(const uint16_t repeat = kAmcorDefaultRepeat);
/// 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_AMCOR
void begin();
static uint8_t calcChecksum(const uint8_t state[],
const uint16_t length = kAmcorStateLength);
static bool validChecksum(const uint8_t state[],
const uint16_t length = kAmcorStateLength);
void setPower(const bool state);
bool getPower(void) const;
void on(void);
void off(void);
void setTemp(const uint8_t temp);
uint8_t getTemp(void) const;
void setMax(const bool on);
bool getMax(void) const;
void setFan(const uint8_t speed);
uint8_t getFan(void) const;
void setMode(const uint8_t mode);
uint8_t getMode(void) const;
uint8_t* getRaw(void);
void setRaw(const uint8_t state[]);
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);
static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed);
stdAc::state_t toCommon(void) const;
String toString(void) const;
#ifndef UNIT_TEST
private:
IRsend _irsend;
#else
/// @cond IGNORE
IRsendTest _irsend;
/// @endcond
#endif
AmcorProtocol _;
void checksum(void);
};
#endif // IR_AMCOR_H_

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,521 @@
// Copyright 2017 Schmolders
// Copyright 2022 crankyoldgit
// Copyright 2022 Mateusz Bronk (mbronk)
/// @file
/// @brief Support for Argo Ulisse 13 DCI Mobile Split ACs.
// Supports:
// Brand: Argo, Model: Ulisse 13 DCI Mobile Split A/C [WREM2 remote]
// Brand: Argo, Model: Ulisse Eco Mobile Split A/C (Wifi) [WREM3 remote]
#ifndef IR_ARGO_H_
#define IR_ARGO_H_
#include <set>
#include <utility>
#ifndef UNIT_TEST
#include <Arduino.h>
#endif
#include "IRremoteESP8266.h"
#include "IRsend.h"
#ifdef UNIT_TEST
#include "IRsend_test.h"
#endif
// ARGO Ulisse DCI
/// Native representation of a Argo A/C message for WREM-2 remote.
union ArgoProtocol {
uint8_t raw[kArgoStateLength]; ///< The state in native IR code form
struct {
// Byte 0
uint64_t Pre1 :8; // Typically 0b00110101
// Byte 1
uint64_t Pre2 :8; // Typically 0b10101111
// Byte 2~4
uint64_t :3;
uint64_t Mode :3;
uint64_t Temp :5; // straddle byte 2 and 3
uint64_t Fan :2;
uint64_t RoomTemp :5; // straddle byte 3 and 4
uint64_t Flap :3; // SwingV
uint64_t :3; // OnTimer, maybe hours
// Byte 5
uint64_t :8; // OnTimer, maybe minutes
// Byte 6
uint64_t :8; // OffTimer, maybe minutes
// Byte 7
uint64_t :3; // OffTimer, maybe hours
uint64_t :5; // Time
// Byte 8
uint32_t :6; // Time
uint32_t :1; // Timer On/Off
uint32_t :1; // Timer Program
// Byte 9
uint32_t :1; // Timer Program
uint32_t :1; // Timer 1h
uint32_t Night :1;
uint32_t Max :1;
uint32_t :1; // Filter
uint32_t Power :1;
uint32_t :1; // const 0
uint32_t iFeel :1;
// Byte 10~11
uint32_t Post :2;
uint32_t Sum :8; // straddle byte 10 and 11
uint32_t :6;
};
struct {
// Byte 0-1
uint8_t :8;
uint8_t :8;
// Byte 2-3
uint8_t CheckHi :3;
uint8_t SensorT :5;
uint8_t Fixed :3; // Typically 0b011
uint8_t CheckLo :5;
};
};
/// Native representation of A/C IR message for WREM-3 remote
/// @note The remote sends 4 different IR command types, varying in length
/// and methods of checksum calculation
/// - [0b00] Regular A/C command (change operation mode) - 6-byte
/// - [0b01] iFeel Temperature report - 2-byte
/// - [0b10] Timer command - 9-byte
/// - [0b11] Config command - 4-byte
/// @note The 1st 2 structures are unnamed for compat. with @c ArgoProtocol
/// 1st byte definition is a header common across all commands though
union ArgoProtocolWREM3 {
uint8_t raw[kArgoStateLength]; ///< The state in native IR code form
struct {
// Byte 0 (same definition across the union)
uint8_t Pre1 :4; /// Preamble: 0b1011 @ref kArgoWrem3Preamble
uint8_t IrChannel :2; /// 0..3 range
uint8_t IrCommandType :2; /// @ref argoIrMessageType_t
// Byte 1
uint8_t RoomTemp :5; // in Celsius, range: 4..35 (offset by -4[*C])
uint8_t Mode :3; /// @ref argoMode_t
// Byte 2
uint8_t Temp :5; // in Celsius, range: 10..32 (offset by -4[*C])
uint8_t Fan :3; /// @ref argoFan_t
// Byte3
uint8_t Flap :3; /// SwingV @ref argoFlap_t
uint8_t Power :1;
uint8_t iFeel :1;
uint8_t Night :1;
uint8_t Eco :1;
uint8_t Max :1; ///< a.k.a. Turbo
// Byte4
uint8_t Filter :1;
uint8_t Light :1;
uint8_t Post1 :6; /// Unknown, always 0b110000 (TempScale?)
// Byte5
uint8_t Sum :8; /// Checksum
};
struct {
// Byte 0 (same definition across the union)
uint8_t :8; // {Pre1 | IrChannel | IrCommandType}
// Byte 1
uint8_t SensorT :5; // in Celsius, range: 4..35 (offset by -4[*C])
uint8_t CheckHi :3; // Checksum (short)
};
struct Timer {
// Byte 0 (same definition across the union)
uint8_t : 8; // {Pre1 | IrChannel | IrCommandType}
// Byte 1
uint8_t IsOn : 1;
uint8_t TimerType : 3;
uint8_t CurrentTimeLo : 4;
// Byte 2
uint8_t CurrentTimeHi : 7;
uint8_t CurrentWeekdayLo : 1;
// Byte 3
uint8_t CurrentWeekdayHi : 2;
uint8_t DelayTimeLo : 6;
// Byte 4
uint8_t DelayTimeHi : 5;
uint8_t TimerStartLo : 3;
// Byte 5
uint8_t TimerStartHi : 8;
// Byte 6
uint8_t TimerEndLo : 8;
// Byte 7
uint8_t TimerEndHi : 3;
uint8_t TimerActiveDaysLo : 5; // Bitmap (LSBit is Sunday)
// Byte 8
uint8_t TimerActiveDaysHi : 2; // Bitmap (LSBit is Sunday)
uint8_t Post1 : 1; // Unknown, always 1
uint8_t Checksum : 5;
} timer;
struct Config {
uint8_t :8; // Byte 0 {Pre1 | IrChannel | IrCommandType}
uint8_t Key :8; // Byte 1
uint8_t Value :8; // Byte 2
uint8_t Checksum :8; // Byte 3
} config;
};
// Constants (WREM-2). Store MSB left.
const uint8_t kArgoHeatBit = 0b00100000;
const uint8_t kArgoPreamble1 = 0b10101100;
const uint8_t kArgoPreamble2 = 0b11110101;
const uint8_t kArgoPost = 0b00000010;
// Constants (generic)
const uint16_t kArgoFrequency = 38000; // Hz
// Temp
const uint8_t kArgoTempDelta = 4;
const uint8_t kArgoMaxRoomTemp = 35; // Celsius
const uint8_t kArgoMinTemp = 10; // Celsius delta +4
const uint8_t kArgoMaxTemp = 32; // Celsius
const uint8_t kArgoMaxChannel = 3;
/// @brief IR message type (determines the payload part of IR command)
/// @note Raw values match WREM-3 protocol, but the enum is used in generic
/// context
/// @note WREM-3 remote supports all commands separately, whereas
/// WREM-2 (allegedly) only has the @c AC_CONTROL and @c IFEEL_TEMP_REPORT
/// (timers are part of @c AC_CONTROL command), and there's no config.
enum class argoIrMessageType_t : uint8_t {
AC_CONTROL = 0b00,
IFEEL_TEMP_REPORT = 0b01,
TIMER_COMMAND = 0b10, // WREM-3 only (WREM-2 has it under AC_CONTROL)
CONFIG_PARAM_SET = 0b11 // WREM-3 only
};
/// @brief A/C operation mode
/// @note Raw values match WREM-3 protocol, but the enum is used in generic
/// context
enum class argoMode_t : uint8_t {
COOL = 0b001,
DRY = 0b010,
HEAT = 0b011,
FAN = 0b100,
AUTO = 0b101
};
// Raw mode definitions for WREM-2 remote
// (not wraped into a ns nor enum for backwards-compat.)
const uint8_t kArgoCool = 0b000;
const uint8_t kArgoDry = 0b001;
const uint8_t kArgoAuto = 0b010;
const uint8_t kArgoOff = 0b011;
const uint8_t kArgoHeat = 0b100;
const uint8_t kArgoHeatAuto = 0b101;
// ?no idea what mode that is
const uint8_t kArgoHeatBlink = 0b110;
/// @brief Fan speed
/// @note Raw values match WREM-3 protocol, but the enum is used in generic
/// context
enum class argoFan_t : uint8_t {
FAN_AUTO = 0b000,
FAN_LOWEST = 0b001,
FAN_LOWER = 0b010,
FAN_LOW = 0b011,
FAN_MEDIUM = 0b100,
FAN_HIGH = 0b101,
FAN_HIGHEST = 0b110
};
// Raw fan speed definitions for WREM-2 remote
// (not wraped into a ns nor enum for backwards-compat.)
const uint8_t kArgoFanAuto = 0; // 0b00
const uint8_t kArgoFan1 = 1; // 0b01
const uint8_t kArgoFan2 = 2; // 0b10
const uint8_t kArgoFan3 = 3; // 0b11
/// @brief Flap position (swing-V)
/// @note Raw values match WREM-3 protocol, but the enum is used in generic
/// context
enum class argoFlap_t : uint8_t {
FLAP_AUTO = 0,
FLAP_1 = 1, // Highest
FLAP_2 = 2,
FLAP_3 = 3,
FLAP_4 = 4,
FLAP_5 = 5,
FLAP_6 = 6, // Lowest
FLAP_FULL = 7
};
// Raw Flap/SwingV definitions for WREM-2 remote
// (not wraped into a ns nor enum for backwards-compat.)
const uint8_t kArgoFlapAuto = 0;
const uint8_t kArgoFlap1 = 1;
const uint8_t kArgoFlap2 = 2;
const uint8_t kArgoFlap3 = 3;
const uint8_t kArgoFlap4 = 4;
const uint8_t kArgoFlap5 = 5;
const uint8_t kArgoFlap6 = 6;
const uint8_t kArgoFlapFull = 7;
// Legacy defines. (Deprecated)
#define ARGO_COOL_ON kArgoCoolOn
#define ARGO_COOL_OFF kArgoCoolOff
#define ARGO_COOL_AUTO kArgoCoolAuto
#define ARGO_COOL_HUM kArgoCoolHum
#define ARGO_HEAT_ON kArgoHeatOn
#define ARGO_HEAT_AUTO kArgoHeatAuto
#define ARGO_HEAT_BLINK kArgoHeatBlink
#define ARGO_MIN_TEMP kArgoMinTemp
#define ARGO_MAX_TEMP kArgoMaxTemp
#define ARGO_FAN_AUTO kArgoFanAuto
#define ARGO_FAN_3 kArgoFan3
#define ARGO_FAN_2 kArgoFan2
#define ARGO_FAN_1 kArgoFan1
#define ARGO_FLAP_AUTO kArgoFlapAuto
#define ARGO_FLAP_1 kArgoFlap1
#define ARGO_FLAP_2 kArgoFlap2
#define ARGO_FLAP_3 kArgoFlap3
#define ARGO_FLAP_4 kArgoFlap4
#define ARGO_FLAP_5 kArgoFlap5
#define ARGO_FLAP_6 kArgoFlap6
#define ARGO_FLAP_FULL kArgoFlapFull
/// @brief Timer type to set (for @c argoIrMessageType_t::TIMER_COMMAND)
/// @note Raw values match WREM-3 protocol
enum class argoTimerType_t : uint8_t {
NO_TIMER = 0b000,
DELAY_TIMER = 0b001,
SCHEDULE_TIMER_1 = 0b010,
SCHEDULE_TIMER_2 = 0b011,
SCHEDULE_TIMER_3 = 0b100
};
/// @brief Day type to set (for @c argoIrMessageType_t::TIMER_COMMAND)
/// @note Raw values match WREM-3 protocol
enum class argoWeekday : uint8_t {
SUNDAY = 0b000,
MONDAY = 0b001,
TUESDAY = 0b010,
WEDNESDAY = 0b011,
THURSDAY = 0b100,
FRIDAY = 0b101,
SATURDAY = 0b110
};
/// @brief Base class for handling *common* support for Argo remote protocols
/// (functionality is shared across WREM-2 and WREM-3 IR protocols)
/// @note This class uses static polymorphism and full template specializations
/// when required, to avoid a performance penalty of doing v-table lookup.
/// 2 instantiations are forced in impl. file: for @c ArgoProtocol and
/// @c ArgoProtocolWREM3
/// @note This class is abstract (though does not declare a pure-virtual fn.
/// for abovementioned reasons), and instead declares protected c-tor
/// @tparam ARGO_PROTOCOL_T The Raw device protocol/message used
template <typename ARGO_PROTOCOL_T>
class IRArgoACBase {
#ifndef UNIT_TEST // A less cloggy way of expressing FRIEND_TEST(...)
protected:
#else
public:
#endif
explicit IRArgoACBase(const uint16_t pin, const bool inverted = false,
const bool use_modulation = true);
public:
#if SEND_ARGO
void send(const uint16_t repeat = kArgoDefaultRepeat);
/// 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_ARGO
void begin(void);
void on(void);
void off(void);
void setPower(const bool on);
bool getPower(void) const;
void setTemp(const uint8_t degrees);
uint8_t getTemp(void) const;
void setSensorTemp(const uint8_t degrees);
uint8_t getSensorTemp(void) const;
void setFan(const argoFan_t fan);
void setFanEx(const argoFan_t fan) { setFan(fan); }
argoFan_t getFanEx(void) const; ///< `-Ex` for backw. compat w/ @c IRArgoAC
void setFlap(const argoFlap_t flap);
void setFlapEx(const argoFlap_t flap) { setFlap(flap); }
argoFlap_t getFlapEx(void) const; ///< `-Ex` for backw. compat w/ @c IRArgoAC
void setMode(const argoMode_t mode);
void setModeEx(const argoMode_t mode) { setMode(mode); }
argoMode_t getModeEx(void) const; ///< `-Ex` for backw. compat w/ @c IRArgoAC
void setMax(const bool on);
bool getMax(void) const;
void setNight(const bool on);
bool getNight(void) const;
void setiFeel(const bool on);
bool getiFeel(void) const;
void setMessageType(const argoIrMessageType_t msgType);
argoIrMessageType_t getMessageType(void) const;
static argoIrMessageType_t getMessageType(const uint8_t state[],
const uint16_t length);
uint8_t* getRaw(void);
uint16_t getRawByteLength() const;
static uint16_t getStateLengthForIrMsgType(argoIrMessageType_t type);
void setRaw(const uint8_t state[], const uint16_t length);
static bool validChecksum(const uint8_t state[], const uint16_t length);
static argoMode_t convertMode(const stdAc::opmode_t mode);
static argoFan_t convertFan(const stdAc::fanspeed_t speed);
static argoFlap_t convertSwingV(const stdAc::swingv_t position);
static argoIrMessageType_t convertCommand(const stdAc::ac_command_t command);
protected:
void _stateReset(ARGO_PROTOCOL_T *state, argoIrMessageType_t messageType
= argoIrMessageType_t::AC_CONTROL);
void stateReset(argoIrMessageType_t messageType
= argoIrMessageType_t::AC_CONTROL);
void _checksum(ARGO_PROTOCOL_T *state);
void checksum(void);
static uint16_t getRawByteLength(const ARGO_PROTOCOL_T& raw,
argoIrMessageType_t messageTypeHint = argoIrMessageType_t::AC_CONTROL);
static uint8_t calcChecksum(const uint8_t state[], const uint16_t length);
static uint8_t getChecksum(const uint8_t state[], const uint16_t length);
static stdAc::opmode_t toCommonMode(const argoMode_t mode);
static stdAc::fanspeed_t toCommonFanSpeed(const argoFan_t speed);
static stdAc::swingv_t toCommonSwingV(const uint8_t position);
static stdAc::ac_command_t toCommonCommand(const argoIrMessageType_t command);
// Attributes
ARGO_PROTOCOL_T _; ///< The raw protocol data
uint16_t _length = kArgoStateLength;
argoIrMessageType_t _messageType = argoIrMessageType_t::AC_CONTROL;
#ifndef UNIT_TEST
protected:
IRsend _irsend; ///< instance of the IR send class
#else
public:
/// @cond IGNORE
IRsendTest _irsend; ///< instance of the testing IR send class
/// @endcond
#endif
};
/// @brief Supports Argo A/C SAC-WREM2 IR remote protocol
class IRArgoAC : public IRArgoACBase<ArgoProtocol> {
public:
explicit IRArgoAC(const uint16_t pin, const bool inverted = false,
const bool use_modulation = true);
#if SEND_ARGO
void sendSensorTemp(const uint8_t degrees,
const uint16_t repeat = kArgoDefaultRepeat);
#endif // SEND_ARGO
String toString(void) const;
stdAc::state_t toCommon(void) const;
using IRArgoACBase<ArgoProtocol>::setMode;
void setMode(const uint8_t mode); /// @deprecated, for backwards-compat.
uint8_t getMode(void) const; /// @deprecated, for backwards-compat.
using IRArgoACBase<ArgoProtocol>::setFan;
void setFan(const uint8_t fan); /// @deprecated, for backwards-compat.
uint8_t getFan(void) const; /// @deprecated, for backwards-compat.
using IRArgoACBase<ArgoProtocol>::setFlap;
void setFlap(const uint8_t flap); /// @deprecated, for backwards-compat.
uint8_t getFlap(void) const; /// @deprecated, for backwards-compat.
};
/// @brief Supports Argo A/C SAC-WREM3 IR remote protocol
class IRArgoAC_WREM3 : public IRArgoACBase<ArgoProtocolWREM3> {
public:
explicit IRArgoAC_WREM3(const uint16_t pin, const bool inverted = false,
const bool use_modulation = true);
#if SEND_ARGO
void sendSensorTemp(const uint8_t degrees,
const uint16_t repeat = kArgoDefaultRepeat);
#endif // SEND_ARGO
argo_ac_remote_model_t getModel(void) const;
argoFan_t getFan(void) const;
argoFlap_t getFlap(void) const;
argoMode_t getMode(void) const;
void setEco(const bool on);
bool getEco(void) const;
void setFilter(const bool on);
bool getFilter(void) const;
void setLight(const bool on);
bool getLight(void) const;
void setChannel(const uint8_t channel);
uint8_t getChannel(void) const;
void setConfigEntry(const uint8_t paramId, const uint8_t value);
std::pair<uint8_t, uint8_t> getConfigEntry(void) const;
void setCurrentTimeMinutes(uint16_t currentTimeMinutes);
uint16_t getCurrentTimeMinutes(void) const;
void setCurrentDayOfWeek(argoWeekday dayOfWeek);
argoWeekday getCurrentDayOfWeek(void) const;
void setTimerType(const argoTimerType_t timerType);
argoTimerType_t getTimerType(void) const;
void setDelayTimerMinutes(const uint16_t delayMinutes);
uint16_t getDelayTimerMinutes(void) const;
void setScheduleTimerStartMinutes(const uint16_t startTimeMinutes);
uint16_t getScheduleTimerStartMinutes(void) const;
// uint16_t getTimerXStartMinutes(void) const
void setScheduleTimerStopMinutes(const uint16_t stopTimeMinutes);
uint16_t getScheduleTimerStopMinutes(void) const;
// uint16_t getTimerXStopMinutes(void) const;
void setScheduleTimerActiveDays(const std::set<argoWeekday>& days);
std::set<argoWeekday> getScheduleTimerActiveDays(void) const;
uint8_t getTimerActiveDaysBitmap(void) const;
using IRArgoACBase<ArgoProtocolWREM3>::getMessageType;
static argoIrMessageType_t getMessageType(const ArgoProtocolWREM3& raw);
String toString(void) const;
stdAc::state_t toCommon(void) const;
static bool hasValidPreamble(const uint8_t state[], const uint16_t length);
public:
#if DECODE_ARGO
static bool isValidWrem3Message(const uint8_t state[], const uint16_t nbits,
bool verifyChecksum = true);
#endif
};
#endif // IR_ARGO_H_

View File

@@ -0,0 +1,123 @@
// Copyright 2021 David Conran
#include "IRrecv.h"
#include "IRsend.h"
#include "IRutils.h"
/// @file
/// @brief Arris "Manchester code" based protocol.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1595
// Supports:
// Brand: Arris, Model: VIP1113M Set-top box
// Brand: Arris, Model: 120A V1.0 A18 remote
const uint8_t kArrisOverhead = 2;
const uint16_t kArrisHalfClockPeriod = 320; // uSeconds
const uint16_t kArrisHdrMark = 8 * kArrisHalfClockPeriod; // uSeconds
const uint16_t kArrisHdrSpace = 6 * kArrisHalfClockPeriod; // uSeconds
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1595#issuecomment-913755841
// aka. 77184 uSeconds.
const uint32_t kArrisGapSpace = 102144 - ((8 + 6 + kArrisBits * 2) *
kArrisHalfClockPeriod); // uSeconds
const uint32_t kArrisReleaseToggle = 0x800008;
const uint8_t kArrisChecksumSize = 4;
const uint8_t kArrisCommandSize = 19;
const uint8_t kArrisReleaseBit = kArrisChecksumSize + kArrisCommandSize;
using irutils::sumNibbles;
#if SEND_ARRIS
/// Send an Arris Manchester Code formatted message.
/// Status: STABLE / Confirmed working.
/// @param[in] data The message to be sent.
/// @param[in] nbits The number of bits of the message to be sent.
/// @param[in] repeat The number of times the command is to be repeated.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1595
void IRsend::sendArris(const uint64_t data, const uint16_t nbits,
const uint16_t repeat) {
enableIROut(38);
for (uint16_t r = 0; r <= repeat; r++) {
// Header (part 1)
mark(kArrisHdrMark);
space(kArrisHdrSpace);
// Header (part 2) + Data + Footer
sendManchester(kArrisHalfClockPeriod * 2, 0, kArrisHalfClockPeriod,
0, kArrisGapSpace, data, nbits);
}
}
/// Flip the toggle button release bits of an Arris message.
/// Used to indicate a change of remote button's state. e.g. Press vs. Release.
/// @param[in] data The existing Arris message.
/// @return A data message suitable for use in sendArris() with the release bits
/// flipped.
uint32_t IRsend::toggleArrisRelease(const uint32_t data) {
return data ^ kArrisReleaseToggle;
}
/// Construct a raw 32-bit Arris message code from the supplied command &
/// release setting.
/// @param[in] command The command code.
/// @param[in] release The button/command action: press (false), release (true)
/// @return A raw 32-bit Arris message code suitable for sendArris() etc.
/// @note Sequence of bits = header + release + command + checksum.
uint32_t IRsend::encodeArris(const uint32_t command, const bool release) {
uint32_t result = 0x10000000;
irutils::setBits(&result, kArrisChecksumSize, kArrisCommandSize, command);
irutils::setBit(&result, kArrisReleaseBit, release);
return result + sumNibbles(result);
}
#endif // SEND_ARRIS
#if DECODE_ARRIS
/// Decode the supplied Arris "Manchester code" message.
/// Status: STABLE / Confirmed working.
/// @param[in,out] results Ptr to the data to decode & where to store the decode
/// result.
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// @param[in] strict Flag indicating if we should perform strict matching.
/// @return A boolean. True if it can decode it, false if it can't.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1595
bool IRrecv::decodeArris(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
if (results->rawlen < nbits + kArrisOverhead - offset)
return false; // Too short a message to match.
// Compliance
if (strict && nbits != kArrisBits)
return false; // Doesn't match our protocol defn.
// Header (part 1)
if (!matchMark(results->rawbuf[offset++], kArrisHdrMark)) return false;
if (!matchSpace(results->rawbuf[offset++], kArrisHdrSpace)) return false;
// Header (part 2) + Data
uint64_t data = 0;
if (!matchManchester(results->rawbuf + offset, &data,
results->rawlen - offset, nbits,
kArrisHalfClockPeriod * 2, 0,
kArrisHalfClockPeriod, 0, 0,
false, kUseDefTol, kMarkExcess, true, false))
return false;
// Compliance
if (strict)
// Validate the checksum.
if (GETBITS32(data, 0, kArrisChecksumSize) !=
sumNibbles(data >> kArrisChecksumSize))
return false;
// Success
results->decode_type = decode_type_t::ARRIS;
results->bits = nbits;
results->value = data;
// Set the address as the Release Bit for something useful.
results->address = static_cast<bool>(GETBIT32(data, kArrisReleaseBit));
// The last 4 bits are likely a checksum value, so skip those. Everything else
// after the release bit. e.g. Bits 10-28
results->command = GETBITS32(data, kArrisChecksumSize, kArrisCommandSize);
return true;
}
#endif // DECODE_ARRIS

View File

@@ -0,0 +1,331 @@
// Copyright 2022 David Conran
// Copyright 2022 Nico Thien
/// @file
/// @brief Support for the Bosch A/C / heatpump protocol
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1787
#include "ir_Bosch.h"
#if SEND_BOSCH144
/// Send a Bosch 144-bit / 18-byte message (96-bit message are also possible)
/// Status: BETA / Probably Working.
/// @param[in] data The message to be sent.
/// @param[in] nbytes The number of bytes of message to be sent.
/// @param[in] repeat The number of times the command is to be repeated.
void IRsend::sendBosch144(const unsigned char data[], const uint16_t nbytes,
const uint16_t repeat) {
// nbytes is required to be a multiple of kBosch144BytesPerSection.
if (nbytes % kBosch144BytesPerSection != 0)return;
// Set IR carrier frequency
enableIROut(kBoschFreq);
for (uint16_t r = 0; r <= repeat; r++) {
for (uint16_t offset=0; offset < nbytes; offset += kBosch144BytesPerSection)
// Section Header + Data + Footer
sendGeneric(kBoschHdrMark, kBoschHdrSpace,
kBoschBitMark, kBoschOneSpace,
kBoschBitMark, kBoschZeroSpace,
kBoschBitMark, kBoschFooterSpace,
data + offset, kBosch144BytesPerSection,
kBoschFreq, true, 0, kDutyDefault);
space(kDefaultMessageGap); // Complete guess
}
}
#endif // SEND_BOSCH144
/// 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?
IRBosch144AC::IRBosch144AC(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 IRBosch144AC::stateReset(void) {
setRaw(kBosch144DefaultState, kBosch144StateLength);
setPower(true);
}
/// Set up hardware to be able to send a message.
void IRBosch144AC::begin(void) { _irsend.begin(); }
#if SEND_BOSCH144
/// Send the current internal state as an IR message.
/// @param[in] repeat Nr. of times the message will be repeated.
void IRBosch144AC::send(const uint16_t repeat) {
if (!powerFlag) { // "Off" is a 96bit message
_irsend.sendBosch144(kBosch144Off, sizeof(kBosch144Off), repeat);
} else {
_irsend.sendBosch144(getRaw(), kBosch144StateLength, repeat);
} // other 96bit messages are not yet supported
}
#endif // SEND_BOSCH144
/// Get a copy of the internal state as a valid code for this protocol.
/// @return A valid code for this protocol based on the current internal state.
unsigned char* IRBosch144AC::getRaw(void) {
setInvertBytes();
setCheckSumS3();
return _.raw;
}
/// Set the internal state from a valid code for this protocol.
/// @param[in] new_code A valid code for this protocol.
/// @param[in] length Size of the array being passed in in bytes.
void IRBosch144AC::setRaw(const uint8_t new_code[], const uint16_t length) {
const uint16_t len = min(length, kBosch144StateLength);
const uint16_t lenOff = sizeof(kBosch144Off);
// Is it an off message?
if (memcmp(kBosch144Off, new_code, min(lenOff, len)) == 0)
setPower(false); // It is.
else
setPower(true);
memcpy(_.raw, new_code, len);
}
void IRBosch144AC::setPower(const bool on) {
powerFlag = on;
}
bool IRBosch144AC::getPower(void) const {
return powerFlag;
}
void IRBosch144AC::setTempRaw(const uint8_t code) {
_.TempS1 = _.TempS2 = code >> 1; // save 4 bits in S1 and S2
_.TempS3 = code & 1; // save 1 bit in Section3
}
/// Set the temperature.
/// @param[in] degrees The temperature in degrees celsius.
void IRBosch144AC::setTemp(const uint8_t degrees) {
uint8_t temp = max(kBosch144TempMin, degrees);
temp = min(kBosch144TempMax, temp);
setTempRaw(kBosch144TempMap[temp - kBosch144TempMin]);
}
uint8_t IRBosch144AC::getTemp(void) const {
uint8_t temp = (_.TempS1 << 1) + _.TempS3;
uint8_t retemp = 25;
for (uint8_t i = 0; i < kBosch144TempRange; i++) {
if (temp == kBosch144TempMap[i]) {
retemp = kBosch144TempMin + i;
}
}
return retemp;
}
/// Set the speed of the fan.
/// @param[in] speed The desired setting.
void IRBosch144AC::setFan(const uint16_t speed) {
_.FanS1 = _.FanS2 = speed >> 6; // save 3 bits in S1 and S2
_.FanS3 = speed & 0b111111; // save 6 bits in Section3
}
uint16_t IRBosch144AC::getFan(void) const {
return (_.FanS1 << 6) + _.FanS3;
}
/// Set the desired operation mode.
/// @param[in] mode The desired operation mode.
void IRBosch144AC::setMode(const uint8_t mode) {
_.ModeS1 = _.ModeS2 = mode >> 1; // save 2 bits in S1 and S2
_.ModeS3 = mode & 0b1; // save 1 bit in Section3
if (mode == kBosch144Auto || mode == kBosch144Dry) {
_.FanS1 = _.FanS2 = 0b000; // save 3 bits in S1 and S2
_.FanS3 = kBosch144FanAuto0; // save 6 bits in Section3
}
}
uint8_t IRBosch144AC::getMode(void) const {
return (_.ModeS1 << 1) + _.ModeS3;
}
/// Set the Quiet mode of the A/C.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRBosch144AC::setQuiet(const bool on) {
_.Quiet = on; // save 1 bit in Section3
setFan(kBosch144FanAuto); // set Fan -> Auto
}
/// Get the Quiet mode of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRBosch144AC::getQuiet(void) const { return _.Quiet; }
/// Convert a stdAc::opmode_t enum into its native mode.
/// @param[in] mode The enum to be converted.
/// @return The native equivalent of the enum.
uint8_t IRBosch144AC::convertMode(const stdAc::opmode_t mode) {
switch (mode) {
case stdAc::opmode_t::kCool:
return kBosch144Cool;
case stdAc::opmode_t::kHeat:
return kBosch144Heat;
case stdAc::opmode_t::kDry:
return kBosch144Dry;
case stdAc::opmode_t::kFan:
return kBosch144Fan;
default:
return kBosch144Auto;
}
}
/// Convert a stdAc::fanspeed_t enum into it's native speed.
/// @param[in] speed The enum to be converted.
/// @return The native equivalent of the enum.
uint16_t IRBosch144AC::convertFan(const stdAc::fanspeed_t speed) {
switch (speed) {
case stdAc::fanspeed_t::kMin:
return kBosch144Fan20;
case stdAc::fanspeed_t::kLow:
return kBosch144Fan40;
case stdAc::fanspeed_t::kMedium:
return kBosch144Fan60;
case stdAc::fanspeed_t::kHigh:
return kBosch144Fan80;
case stdAc::fanspeed_t::kMax:
return kBosch144Fan100;
default:
return kBosch144FanAuto;
}
}
/// Convert a native mode into its stdAc equivalent.
/// @param[in] mode The native setting to be converted.
/// @return The stdAc equivalent of the native setting.
stdAc::opmode_t IRBosch144AC::toCommonMode(const uint8_t mode) {
switch (mode) {
case kBosch144Cool: return stdAc::opmode_t::kCool;
case kBosch144Heat: return stdAc::opmode_t::kHeat;
case kBosch144Dry: return stdAc::opmode_t::kDry;
case kBosch144Fan: return stdAc::opmode_t::kFan;
default: return stdAc::opmode_t::kAuto;
}
}
/// Convert a native fan speed into its stdAc equivalent.
/// @param[in] speed The native setting to be converted.
/// @return The stdAc equivalent of the native setting.
stdAc::fanspeed_t IRBosch144AC::toCommonFanSpeed(const uint16_t speed) {
switch (speed) {
case kBosch144Fan100: return stdAc::fanspeed_t::kMax;
case kBosch144Fan80: return stdAc::fanspeed_t::kHigh;
case kBosch144Fan60: return stdAc::fanspeed_t::kMedium;
case kBosch144Fan40: return stdAc::fanspeed_t::kLow;
case kBosch144Fan20: return stdAc::fanspeed_t::kMin;
default: return stdAc::fanspeed_t::kAuto;
}
}
/// Convert the current internal state into its stdAc::state_t equivalent.
/// @return The stdAc equivalent of the native settings.
stdAc::state_t IRBosch144AC::toCommon(void) const {
stdAc::state_t result{};
result.protocol = decode_type_t::BOSCH144;
result.power = getPower();
result.mode = toCommonMode(getMode());
result.celsius = true;
result.degrees = getTemp();
result.fanspeed = toCommonFanSpeed(getFan());
result.quiet = getQuiet();
// Not supported.
result.model = -1;
result.turbo = false;
result.swingv = stdAc::swingv_t::kOff;
result.swingh = stdAc::swingh_t::kOff;
result.light = false;
result.filter = false;
result.econo = false;
result.clean = false;
result.beep = false;
result.clock = -1;
result.sleep = -1;
return result;
}
/// Convert the current internal state into a human readable string.
/// @return A human readable string.
String IRBosch144AC::toString(void) const {
uint8_t mode = getMode();
uint8_t fan = static_cast<int>(toCommonFanSpeed(getFan()));
String result = "";
result.reserve(70); // Reserve some heap for the string to reduce fragging.
result += addBoolToString(getPower(), kPowerStr, false);
result += addModeToString(mode, kBosch144Auto, kBosch144Cool,
kBosch144Heat, kBosch144Dry, kBosch144Fan);
result += addFanToString(fan, static_cast<int>(stdAc::fanspeed_t::kMax),
static_cast<int>(stdAc::fanspeed_t::kMin),
static_cast<int>(stdAc::fanspeed_t::kAuto),
static_cast<int>(stdAc::fanspeed_t::kAuto),
static_cast<int>(stdAc::fanspeed_t::kMedium));
result += addTempToString(getTemp());
result += addBoolToString(_.Quiet, kQuietStr);
return result;
}
void IRBosch144AC::setInvertBytes() {
for (uint8_t i = 0; i <= 10; i += 2) {
_.raw[i + 1] = ~_.raw[i];
}
}
void IRBosch144AC::setCheckSumS3() {
_.ChecksumS3 = sumBytes(&(_.raw[12]), 5);
}
#if DECODE_BOSCH144
/// Decode the supplied Bosch 144-bit / 18-byte A/C message.
/// Status: STABLE / Confirmed Working.
/// @param[in,out] results Ptr to the data to decode & where to store the decode
/// result.
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// @param[in] strict Flag indicating if we should perform strict matching.
/// @return A boolean. True if it can decode it, false if it can't.
bool IRrecv::decodeBosch144(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
if (results->rawlen < 2 * nbits +
kBosch144NrOfSections * (kHeader + kFooter) -
1 + offset)
return false; // Can't possibly be a valid BOSCH144 message.
if (strict && nbits != kBosch144Bits)
return false; // Not strictly a BOSCH144 message.
if (nbits % 8 != 0) // nbits has to be a multiple of nr. of bits in a byte.
return false;
if (nbits % kBosch144NrOfSections != 0)
return false; // nbits has to be a multiple of kBosch144NrOfSections.
const uint16_t kSectionBits = nbits / kBosch144NrOfSections;
const uint16_t kSectionBytes = kSectionBits / 8;
const uint16_t kNBytes = kSectionBytes * kBosch144NrOfSections;
// Capture each section individually
for (uint16_t pos = 0, section = 0;
pos < kNBytes;
pos += kSectionBytes, section++) {
uint16_t used = 0;
// Section Header + Section Data + Section Footer
used = matchGeneric(results->rawbuf + offset, results->state + pos,
results->rawlen - offset, kSectionBits,
kBoschHdrMark, kBoschHdrSpace,
kBoschBitMark, kBoschOneSpace,
kBoschBitMark, kBoschZeroSpace,
kBoschBitMark, kBoschFooterSpace,
section >= kBosch144NrOfSections - 1,
_tolerance, kMarkExcess, true);
if (!used) return false; // Didn't match.
offset += used;
}
// Compliance
// Success
results->decode_type = decode_type_t::BOSCH144;
results->bits = nbits;
// 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_BOSCH144

View File

@@ -0,0 +1,193 @@
// Copyright 2022 Nico Thien
/// @file
/// @brief Support for Bosch A/C protocol
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1787
// Supports:
// Brand: Bosch, Model: CL3000i-Set 26 E A/C
// Brand: Bosch, Model: RG10A(G2S)BGEF remote
#ifndef IR_BOSCH_H_
#define IR_BOSCH_H_
#define __STDC_LIMIT_MACROS
#include <stdint.h>
#include <algorithm>
#include <cstring>
#include "IRremoteESP8266.h"
#include "IRsend.h"
#include "IRrecv.h"
#include "IRtext.h"
#include "IRutils.h"
#ifndef UNIT_TEST
#include <Arduino.h>
#endif
#ifdef UNIT_TEST
#include "IRsend_test.h"
#endif
// Constants
const uint16_t kBoschHdrMark = 4366;
const uint16_t kBoschBitMark = 502;
const uint16_t kBoschHdrSpace = 4415;
const uint16_t kBoschOneSpace = 1645;
const uint16_t kBoschZeroSpace = 571;
const uint16_t kBoschFooterSpace = 5235;
const uint16_t kBoschFreq = 38000; // Hz. (Guessing the most common frequency.)
const uint16_t kBosch144NrOfSections = 3;
const uint16_t kBosch144BytesPerSection = 6;
using irutils::addBoolToString;
using irutils::addModeToString;
using irutils::addFanToString;
using irutils::addTempToString;
using std::min;
using std::max;
using std::memcpy;
using std::memcmp;
// Modes Bit[0] to Section 3 Bit[1-2] to Section 1
// ModeS3 ModeS1
const uint8_t kBosch144Cool = 0b000;
const uint8_t kBosch144Dry = 0b011;
const uint8_t kBosch144Auto = 0b101;
const uint8_t kBosch144Heat = 0b110;
const uint8_t kBosch144Fan = 0b010;
// Fan Control Bit[0-5] to Section 3 Bit[6-8] to Section 1
// FanS3 FanS1
const uint16_t kBosch144Fan20 = 0b111001010;
const uint16_t kBosch144Fan40 = 0b100010100;
const uint16_t kBosch144Fan60 = 0b010011110;
const uint16_t kBosch144Fan80 = 0b001101000;
const uint16_t kBosch144Fan100 = 0b001110010;
const uint16_t kBosch144FanAuto = 0b101110011;
const uint16_t kBosch144FanAuto0 = 0b000110011;
// Temperature
const uint8_t kBosch144TempMin = 16; // Celsius
const uint8_t kBosch144TempMax = 30; // Celsius
const uint8_t kBosch144TempRange = kBosch144TempMax - kBosch144TempMin + 1;
const uint8_t kBosch144TempMap[kBosch144TempRange] = {
0b00001, // 16C // Bit[0] to Section 3 Bit[1-4] to Section 1
0b00000, // 17C // TempS3 TempS1
0b00010, // 18c
0b00110, // 19C
0b00100, // 20C
0b01100, // 21C
0b01110, // 22C
0b01010, // 23C
0b01000, // 24C
0b11000, // 25C
0b11010, // 26C
0b10010, // 27C
0b10000, // 28C
0b10100, // 29C
0b10110 // 30C
};
// "OFF" is a 96bit-message the same as Coolix protocol
const uint8_t kBosch144Off[] = {0xB2, 0x4D, 0x7B, 0x84, 0xE0, 0x1F,
0xB2, 0x4D, 0x7B, 0x84, 0xE0, 0x1F};
// On, 25C, Mode: Auto
const uint8_t kBosch144DefaultState[kBosch144StateLength] = {
0xB2, 0x4D, 0x1F, 0xE0, 0xC8, 0x37,
0xB2, 0x4D, 0x1F, 0xE0, 0xC8, 0x37,
0xD5, 0x65, 0x00, 0x00, 0x00, 0x3A};
union Bosch144Protocol {
uint8_t raw[kBosch144StateLength]; ///< The state in IR code form.
struct {
uint8_t :8; // Fixed value 0b10110010 / 0xB2. ############
uint8_t InnvertS1_1:8; // Invert byte 0b01001101 / 0x4D #
uint8_t :5; // not used (without timer use) #
uint8_t FanS1 :3; // Fan speed bits in Section 1 #
uint8_t InnvertS1_2:8; // Invert byte # Section 1 =
uint8_t :2; // not used (without timer use) # Sektion 2
uint8_t ModeS1 :2; // Operation mode bits S1 #
uint8_t TempS1 :4; // Desired temperature (Celsius) S2 #
uint8_t InnvertS1_3:8; // Invert byte (without timer use) ############
uint8_t :8; // Fixed value 0b10110010 / 0xB2. ############
uint8_t InnvertS2_1:8; // Invert byte 0b01001101 / 0x4D #
uint8_t :5; // not used (without timer use) #
uint8_t FanS2 :3; // Fan speed bits in Section 2 #
uint8_t InnvertS2_2:8; // Invert byte # Section 2 =
uint8_t :2; // not used (without timer use) # Sektion 1
uint8_t ModeS2 :2; // Operation mode bits S2 #
uint8_t TempS2 :4; // Desired temperature (Celsius) S2 #
uint8_t InnvertS2_3:8; // Invert byte (without timer use) ###########
uint8_t :8; // Fixed value 0b11010101 / 0xD5 ###########
uint8_t ModeS3 :1; // ModeBit in Section 3 #
uint8_t FanS3 :6; // Fan speed bits in Section 3 #
uint8_t :1; // Unknown #
uint8_t :7; // Unknown #
uint8_t Quiet :1; // Silent-Mode # Section 3
uint8_t :4; // Unknown #
uint8_t TempS3 :1; // Desired temp. Bit in Section3 #
uint8_t :3; // Unknown #
uint8_t :8; // Unknown #
uint8_t ChecksumS3 :8; // Checksum from byte 13-17 ###########
};
};
// Classes
/// Class for handling detailed Bosch144 A/C messages.
class IRBosch144AC {
public:
explicit IRBosch144AC(const uint16_t pin, const bool inverted = false,
const bool use_modulation = true);
void stateReset(void);
#if SEND_BOSCH144
void send(const uint16_t repeat = 0);
/// 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_BOSCH144
void begin();
void setPower(const bool state);
bool getPower(void) const;
void setTemp(const uint8_t temp);
uint8_t getTemp(void) const;
void setFan(const uint16_t speed);
uint16_t getFan(void) const;
void setMode(const uint8_t mode);
uint8_t getMode(void) const;
void setQuiet(const bool on);
bool getQuiet(void) const;
uint8_t* getRaw(void);
void setRaw(const uint8_t new_code[],
const uint16_t length = kBosch144StateLength);
static uint8_t convertMode(const stdAc::opmode_t mode);
static uint16_t convertFan(const stdAc::fanspeed_t speed);
static stdAc::opmode_t toCommonMode(const uint8_t mode);
static stdAc::fanspeed_t toCommonFanSpeed(const uint16_t speed);
stdAc::state_t toCommon(void) const;
String toString(void) const;
#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
Bosch144Protocol _; ///< The state of the IR remote in IR code form.
// Internal State settings
bool powerFlag;
void setInvertBytes();
void setCheckSumS3();
void setTempRaw(const uint8_t code);
uint8_t getTempRaw(void) const;
};
#endif // IR_BOSCH_H_

View File

@@ -0,0 +1,69 @@
// Copyright 2021 parsnip42
// Copyright 2021 David Conran
/// @file
/// @brief Support for Bose protocols.
/// @note Currently only tested against Bose TV Speaker.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/pull/1579
// Supports:
// Brand: Bose, Model: Bose TV Speaker
#include "IRrecv.h"
#include "IRsend.h"
const uint16_t kBoseHdrMark = 1100;
const uint16_t kBoseHdrSpace = 1350;
const uint16_t kBoseBitMark = 555;
const uint16_t kBoseOneSpace = 1435;
const uint16_t kBoseZeroSpace = 500;
const uint32_t kBoseGap = kDefaultMessageGap;
const uint16_t kBoseFreq = 38;
#if SEND_BOSE
/// Send a Bose formatted message.
/// Status: STABLE / Known working.
/// @param[in] data The message to be sent.
/// @param[in] nbits The number of bits of message to be sent.
/// @param[in] repeat The number of times the command is to be repeated.
void IRsend::sendBose(const uint64_t data, const uint16_t nbits,
const uint16_t repeat) {
sendGeneric(kBoseHdrMark, kBoseHdrSpace,
kBoseBitMark, kBoseOneSpace,
kBoseBitMark, kBoseZeroSpace,
kBoseBitMark, kBoseGap,
data, nbits, kBoseFreq, false,
repeat, kDutyDefault);
}
#endif // SEND_BOSE
#if DECODE_BOSE
/// Decode the supplied Bose formatted message.
/// Status: STABLE / Known working.
/// @param[in,out] results Ptr to the data to decode & where to store the result
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// @param[in] strict Flag indicating if we should perform strict matching.
bool IRrecv::decodeBose(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
if (strict && nbits != kBoseBits) return false;
if (!matchGeneric(results->rawbuf + offset, &(results->value),
results->rawlen - offset, nbits,
kBoseHdrMark, kBoseHdrSpace,
kBoseBitMark, kBoseOneSpace,
kBoseBitMark, kBoseZeroSpace,
kBoseBitMark, kBoseGap, true,
kUseDefTol, 0, false)) {
return false;
}
//
results->decode_type = decode_type_t::BOSE;
results->bits = nbits;
results->address = 0;
results->command = 0;
return true;
}
#endif // DECODE_BOSE

View File

@@ -0,0 +1,747 @@
// Copyright 2018-2022 David Conran
/// @file
/// @brief Carrier protocols.
/// @see CarrierAc https://github.com/crankyoldgit/IRremoteESP8266/issues/385
/// @see CarrierAc64 https://github.com/crankyoldgit/IRremoteESP8266/issues/1127
/// @see CarrierAc128 https://github.com/crankyoldgit/IRremoteESP8266/issues/1797
#include "ir_Carrier.h"
#include <algorithm>
#include "IRac.h"
#include "IRrecv.h"
#include "IRsend.h"
#include "IRtext.h"
#include "IRutils.h"
using irutils::addBoolToString;
using irutils::addIntToString;
using irutils::addLabeledString;
using irutils::addModeToString;
using irutils::addTempToString;
using irutils::addFanToString;
using irutils::minsToString;
using irutils::sumNibbles;
// Constants
const uint16_t kCarrierAcHdrMark = 8532;
const uint16_t kCarrierAcHdrSpace = 4228;
const uint16_t kCarrierAcBitMark = 628;
const uint16_t kCarrierAcOneSpace = 1320;
const uint16_t kCarrierAcZeroSpace = 532;
const uint16_t kCarrierAcGap = 20000;
const uint16_t kCarrierAcFreq = 38; // kHz. (An educated guess)
const uint16_t kCarrierAc40HdrMark = 8402;
const uint16_t kCarrierAc40HdrSpace = 4166;
const uint16_t kCarrierAc40BitMark = 547;
const uint16_t kCarrierAc40OneSpace = 1540;
const uint16_t kCarrierAc40ZeroSpace = 497;
const uint32_t kCarrierAc40Gap = 150000; ///<
///< @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1190#issuecomment-643380155
const uint16_t kCarrierAc64HdrMark = 8940;
const uint16_t kCarrierAc64HdrSpace = 4556;
const uint16_t kCarrierAc64BitMark = 503;
const uint16_t kCarrierAc64OneSpace = 1736;
const uint16_t kCarrierAc64ZeroSpace = 615;
const uint32_t kCarrierAc64Gap = kDefaultMessageGap; // A guess.
//< @see: https://github.com/crankyoldgit/IRremoteESP8266/issues/1943#issue-1519570772
const uint16_t kCarrierAc84HdrMark = 5850;
const uint16_t kCarrierAc84Zero = 1175;
const uint16_t kCarrierAc84One = 430;
const uint16_t kCarrierAc84HdrSpace = kCarrierAc84Zero;
const uint32_t kCarrierAc84Gap = kDefaultMessageGap; // A guess.
const uint8_t kCarrierAc84ExtraBits = 4;
const uint8_t kCarrierAc84ExtraTolerance = 5;
const uint16_t kCarrierAc128HdrMark = 4600;
const uint16_t kCarrierAc128HdrSpace = 2600;
const uint16_t kCarrierAc128Hdr2Mark = 9300;
const uint16_t kCarrierAc128Hdr2Space = 5000;
const uint16_t kCarrierAc128BitMark = 340;
const uint16_t kCarrierAc128OneSpace = 1000;
const uint16_t kCarrierAc128ZeroSpace = 400;
const uint16_t kCarrierAc128SectionGap = 20600;
const uint16_t kCarrierAc128InterSpace = 6700;
const uint16_t kCarrierAc128SectionBits = kCarrierAc128Bits / 2;
#if SEND_CARRIER_AC
/// Send a Carrier HVAC formatted message.
/// Status: STABLE / Works on real devices.
/// @param[in] data The message to be sent.
/// @param[in] nbits The number of bits of message to be sent.
/// @param[in] repeat The number of times the command is to be repeated.
void IRsend::sendCarrierAC(uint64_t data, uint16_t nbits, uint16_t repeat) {
for (uint16_t r = 0; r <= repeat; r++) {
uint64_t temp_data = data;
// Carrier sends the data block three times. normal + inverted + normal.
for (uint16_t i = 0; i < 3; i++) {
sendGeneric(kCarrierAcHdrMark, kCarrierAcHdrSpace, kCarrierAcBitMark,
kCarrierAcOneSpace, kCarrierAcBitMark, kCarrierAcZeroSpace,
kCarrierAcBitMark, kCarrierAcGap, temp_data, nbits, 38, true,
0, kDutyDefault);
temp_data = invertBits(temp_data, nbits);
}
}
}
#endif
#if DECODE_CARRIER_AC
/// Decode the supplied Carrier HVAC message.
/// @note Carrier HVAC messages contain only 32 bits, but it is sent three(3)
/// times. i.e. normal + inverted + normal
/// Status: BETA / Probably works.
/// @param[in,out] results Ptr to the data to decode & where to store the decode
/// result.
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// @param[in] strict Flag indicating if we should perform strict matching.
/// @return A boolean. True if it can decode it, false if it can't.
bool IRrecv::decodeCarrierAC(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
if (results->rawlen < ((2 * nbits + kHeader + kFooter) * 3) - 1 + offset)
return false; // Can't possibly be a valid Carrier message.
if (strict && nbits != kCarrierAcBits)
return false; // We expect Carrier to be 32 bits of message.
uint64_t data = 0;
uint64_t prev_data = 0;
for (uint8_t i = 0; i < 3; i++) {
prev_data = data;
// Match Header + Data + Footer
uint16_t used;
used = matchGeneric(results->rawbuf + offset, &data,
results->rawlen - offset, nbits,
kCarrierAcHdrMark, kCarrierAcHdrSpace,
kCarrierAcBitMark, kCarrierAcOneSpace,
kCarrierAcBitMark, kCarrierAcZeroSpace,
kCarrierAcBitMark, kCarrierAcGap, true);
if (!used) return false;
offset += used;
// Compliance.
if (strict) {
// Check if the data is an inverted copy of the previous data.
if (i > 0 && prev_data != invertBits(data, nbits)) return false;
}
}
// Success
results->bits = nbits;
results->value = data;
results->decode_type = CARRIER_AC;
results->address = data >> 16;
results->command = data & 0xFFFF;
return true;
}
#endif // DECODE_CARRIER_AC
#if SEND_CARRIER_AC40
/// Send a Carrier 40bit HVAC formatted message.
/// Status: STABLE / Tested against a real device.
/// @param[in] data The message to be sent.
/// @param[in] nbits The bit size of the message being sent.
/// @param[in] repeat The number of times the message is to be repeated.
void IRsend::sendCarrierAC40(const uint64_t data, const uint16_t nbits,
const uint16_t repeat) {
sendGeneric(kCarrierAc40HdrMark, kCarrierAc40HdrSpace, kCarrierAc40BitMark,
kCarrierAc40OneSpace, kCarrierAc40BitMark, kCarrierAc40ZeroSpace,
kCarrierAc40BitMark, kCarrierAc40Gap,
data, nbits, kCarrierAcFreq, true, repeat, kDutyDefault);
}
#endif // SEND_CARRIER_AC40
#if DECODE_CARRIER_AC40
/// Decode the supplied Carrier 40-bit HVAC message.
/// Carrier HVAC messages contain only 40 bits, but it is sent three(3) times.
/// Status: STABLE / Tested against a real device.
/// @param[in,out] results Ptr to the data to decode & where to store the decode
/// result.
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// @param[in] strict Flag indicating if we should perform strict matching.
/// @return A boolean. True if it can decode it, false if it can't.
bool IRrecv::decodeCarrierAC40(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
if (results->rawlen < 2 * nbits + kHeader + kFooter - 1 + offset)
return false; // Can't possibly be a valid Carrier message.
if (strict && nbits != kCarrierAc40Bits)
return false; // We expect Carrier to be 40 bits of message.
if (!matchGeneric(results->rawbuf + offset, &(results->value),
results->rawlen - offset, nbits,
kCarrierAc40HdrMark, kCarrierAc40HdrSpace,
kCarrierAc40BitMark, kCarrierAc40OneSpace,
kCarrierAc40BitMark, kCarrierAc40ZeroSpace,
kCarrierAc40BitMark, kCarrierAc40Gap, true)) return false;
// Success
results->bits = nbits;
results->decode_type = CARRIER_AC40;
results->address = 0;
results->command = 0;
return true;
}
#endif // DECODE_CARRIER_AC40
#if SEND_CARRIER_AC64
/// Send a Carrier 64bit HVAC formatted message.
/// Status: STABLE / Known to be working.
/// @param[in] data The message to be sent.
/// @param[in] nbits The bit size of the message being sent.
/// @param[in] repeat The number of times the message is to be repeated.
void IRsend::sendCarrierAC64(const uint64_t data, const uint16_t nbits,
const uint16_t repeat) {
sendGeneric(kCarrierAc64HdrMark, kCarrierAc64HdrSpace, kCarrierAc64BitMark,
kCarrierAc64OneSpace, kCarrierAc64BitMark, kCarrierAc64ZeroSpace,
kCarrierAc64BitMark, kCarrierAc64Gap,
data, nbits, kCarrierAcFreq, false, repeat, kDutyDefault);
}
#endif // SEND_CARRIER_AC64
#if DECODE_CARRIER_AC64
/// Decode the supplied Carrier 64-bit HVAC message.
/// Status: STABLE / Known to be working.
/// @param[in,out] results Ptr to the data to decode & where to store the decode
/// result.
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// @param[in] strict Flag indicating if we should perform strict matching.
/// @return A boolean. True if it can decode it, false if it can't.
bool IRrecv::decodeCarrierAC64(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
if (results->rawlen < 2 * nbits + kHeader + kFooter - 1 + offset)
return false; // Can't possibly be a valid Carrier message.
if (strict && nbits != kCarrierAc64Bits)
return false; // We expect Carrier to be 64 bits of message.
if (!matchGeneric(results->rawbuf + offset, &(results->value),
results->rawlen - offset, nbits,
kCarrierAc64HdrMark, kCarrierAc64HdrSpace,
kCarrierAc64BitMark, kCarrierAc64OneSpace,
kCarrierAc64BitMark, kCarrierAc64ZeroSpace,
kCarrierAc64BitMark, kCarrierAc64Gap, true,
kUseDefTol, kMarkExcess, false)) return false;
// Compliance
if (strict && !IRCarrierAc64::validChecksum(results->value)) return false;
// Success
results->bits = nbits;
results->decode_type = CARRIER_AC64;
results->address = 0;
results->command = 0;
return true;
}
#endif // DECODE_CARRIER_AC64
/// 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?
IRCarrierAc64::IRCarrierAc64(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.
/// @note The state is powered off.
void IRCarrierAc64::stateReset(void) { _.raw = 0x109000002C2A5584; }
/// Calculate the checksum for a given state.
/// @param[in] state The value to calc the checksum of.
/// @return The 4-bit checksum stored in a uint_8.
uint8_t IRCarrierAc64::calcChecksum(const uint64_t state) {
uint64_t data = GETBITS64(state,
kCarrierAc64ChecksumOffset + kCarrierAc64ChecksumSize, kCarrierAc64Bits -
(kCarrierAc64ChecksumOffset + kCarrierAc64ChecksumSize));
uint8_t result = 0;
for (; data; data >>= 4) // Add each nibble together.
result += GETBITS64(data, 0, 4);
return result & 0xF;
}
/// Verify the checksum is valid for a given state.
/// @param[in] state The array to verify the checksum of.
/// @return true, if the state has a valid checksum. Otherwise, false.
bool IRCarrierAc64::validChecksum(const uint64_t state) {
// Validate the checksum of the given state.
return (GETBITS64(state, kCarrierAc64ChecksumOffset,
kCarrierAc64ChecksumSize) == calcChecksum(state));
}
/// Calculate and set the checksum values for the internal state.
void IRCarrierAc64::checksum(void) {
_.Sum = calcChecksum(_.raw);
}
/// Set up hardware to be able to send a message.
void IRCarrierAc64::begin(void) { _irsend.begin(); }
#if SEND_CARRIER_AC64
/// Send the current internal state as an IR message.
/// @param[in] repeat Nr. of times the message will be repeated.
void IRCarrierAc64::send(const uint16_t repeat) {
_irsend.sendCarrierAC64(getRaw(), kCarrierAc64Bits, repeat);
}
#endif // SEND_CARRIER_AC64
/// Get a copy of the internal state as a valid code for this protocol.
/// @return A valid code for this protocol based on the current internal state.
uint64_t IRCarrierAc64::getRaw(void) {
checksum(); // Ensure correct settings before sending.
return _.raw;
}
/// Set the internal state from a valid code for this protocol.
/// @param[in] state A valid code for this protocol.
void IRCarrierAc64::setRaw(const uint64_t state) { _.raw = state; }
/// Set the temp in deg C.
/// @param[in] temp The desired temperature in Celsius.
void IRCarrierAc64::setTemp(const uint8_t temp) {
uint8_t degrees = std::max(temp, kCarrierAc64MinTemp);
degrees = std::min(degrees, kCarrierAc64MaxTemp);
_.Temp = degrees - kCarrierAc64MinTemp;
}
/// Get the current temperature from the internal state.
/// @return The current temperature in Celsius.
uint8_t IRCarrierAc64::getTemp(void) const {
return _.Temp + kCarrierAc64MinTemp;
}
/// Change the power setting.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRCarrierAc64::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 IRCarrierAc64::getPower(void) const {
return _.Power;
}
/// Change the power setting to On.
void IRCarrierAc64::on(void) { setPower(true); }
/// Change the power setting to Off.
void IRCarrierAc64::off(void) { setPower(false); }
/// Get the operating mode setting of the A/C.
/// @return The current operating mode setting.
uint8_t IRCarrierAc64::getMode(void) const {
return _.Mode;
}
/// Set the operating mode of the A/C.
/// @param[in] mode The desired operating mode.
void IRCarrierAc64::setMode(const uint8_t mode) {
switch (mode) {
case kCarrierAc64Heat:
case kCarrierAc64Cool:
case kCarrierAc64Fan:
_.Mode = mode;
return;
default:
_.Mode = kCarrierAc64Cool;
}
}
/// Convert a standard A/C mode into its native mode.
/// @param[in] mode A stdAc::opmode_t to be converted to it's native equivalent.
/// @return The corresponding native mode.
uint8_t IRCarrierAc64::convertMode(const stdAc::opmode_t mode) {
switch (mode) {
case stdAc::opmode_t::kHeat: return kCarrierAc64Heat;
case stdAc::opmode_t::kFan: return kCarrierAc64Fan;
default: return kCarrierAc64Cool;
}
}
/// Convert a native mode to it's common stdAc::opmode_t equivalent.
/// @param[in] mode A native operation mode to be converted.
/// @return The corresponding common stdAc::opmode_t mode.
stdAc::opmode_t IRCarrierAc64::toCommonMode(const uint8_t mode) {
switch (mode) {
case kCarrierAc64Heat: return stdAc::opmode_t::kHeat;
case kCarrierAc64Fan: return stdAc::opmode_t::kFan;
default: return stdAc::opmode_t::kCool;
}
}
/// Get the current fan speed setting.
/// @return The current fan speed.
uint8_t IRCarrierAc64::getFan(void) const {
return _.Fan;
}
/// Set the speed of the fan.
/// @param[in] speed The desired setting.
void IRCarrierAc64::setFan(const uint8_t speed) {
if (speed > kCarrierAc64FanHigh)
_.Fan = kCarrierAc64FanAuto;
else
_.Fan = speed;
}
/// Convert a stdAc::fanspeed_t enum into it's native speed.
/// @param[in] speed The enum to be converted.
/// @return The native equivalent of the enum.
uint8_t IRCarrierAc64::convertFan(const stdAc::fanspeed_t speed) {
switch (speed) {
case stdAc::fanspeed_t::kMin:
case stdAc::fanspeed_t::kLow: return kCarrierAc64FanLow;
case stdAc::fanspeed_t::kMedium: return kCarrierAc64FanMedium;
case stdAc::fanspeed_t::kHigh:
case stdAc::fanspeed_t::kMax: return kCarrierAc64FanHigh;
default: return kCarrierAc64FanAuto;
}
}
/// Convert a native fan speed into its stdAc equivalent.
/// @param[in] speed The native setting to be converted.
/// @return The stdAc equivalent of the native setting.
stdAc::fanspeed_t IRCarrierAc64::toCommonFanSpeed(const uint8_t speed) {
switch (speed) {
case kCarrierAc64FanHigh: return stdAc::fanspeed_t::kHigh;
case kCarrierAc64FanMedium: return stdAc::fanspeed_t::kMedium;
case kCarrierAc64FanLow: return stdAc::fanspeed_t::kLow;
default: return stdAc::fanspeed_t::kAuto;
}
}
/// Set the Vertical Swing mode of the A/C.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRCarrierAc64::setSwingV(const bool on) {
_.SwingV = on;
}
/// Get the Vertical Swing mode of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRCarrierAc64::getSwingV(void) const {
return _.SwingV;
}
/// Set the Sleep mode of the A/C.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRCarrierAc64::setSleep(const bool on) {
if (on) {
// Sleep sets a default value in the Off timer, and disables both timers.
setOffTimer(2 * 60);
// Clear the enable bits for each timer.
_cancelOnTimer();
_cancelOffTimer();
}
_.Sleep = on;
}
/// Get the Sleep mode of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRCarrierAc64::getSleep(void) const {
return _.Sleep;
}
/// Clear the On Timer enable bit.
void IRCarrierAc64::_cancelOnTimer(void) {
_.OnTimerEnable = false;
}
/// Get the current On Timer time.
/// @return The number of minutes it is set for. 0 means it's off.
/// @note The A/C protocol only supports one hour increments.
uint16_t IRCarrierAc64::getOnTimer(void) const {
if (_.OnTimerEnable)
return _.OnTimer * 60;
else
return 0;
}
/// Set the On Timer time.
/// @param[in] nr_of_mins Number of minutes to set the timer to.
/// (< 60 is disable).
/// @note The A/C protocol only supports one hour increments.
void IRCarrierAc64::setOnTimer(const uint16_t nr_of_mins) {
uint8_t hours = std::min((uint8_t)(nr_of_mins / 60), kCarrierAc64TimerMax);
_.OnTimerEnable = static_cast<bool>(hours); // Enable
_.OnTimer = std::max(kCarrierAc64TimerMin, hours); // Hours
if (hours) { // If enabled, disable the Off Timer & Sleep mode.
_cancelOffTimer();
setSleep(false);
}
}
/// Clear the Off Timer enable bit.
void IRCarrierAc64::_cancelOffTimer(void) {
_.OffTimerEnable = false;
}
/// Get the current Off Timer time.
/// @return The number of minutes it is set for. 0 means it's off.
/// @note The A/C protocol only supports one hour increments.
uint16_t IRCarrierAc64::getOffTimer(void) const {
if (_.OffTimerEnable)
return _.OffTimer * 60;
else
return 0;
}
/// Set the Off Timer time.
/// @param[in] nr_of_mins Number of minutes to set the timer to.
/// (< 60 is disable).
/// @note The A/C protocol only supports one hour increments.
void IRCarrierAc64::setOffTimer(const uint16_t nr_of_mins) {
uint8_t hours = std::min((uint8_t)(nr_of_mins / 60), kCarrierAc64TimerMax);
// The time can be changed in sleep mode, but doesn't set the flag.
_.OffTimerEnable = (hours && !_.Sleep);
_.OffTimer = std::max(kCarrierAc64TimerMin, hours); // Hours
if (hours) { // If enabled, disable the On Timer & Sleep mode.
_cancelOnTimer();
setSleep(false);
}
}
/// Convert the internal state into a human readable string.
/// @return The current internal state expressed as a human readable String.
String IRCarrierAc64::toString(void) const {
String result = "";
result.reserve(120); // Reserve some heap for the string to reduce fragging.
result += addBoolToString(_.Power, kPowerStr, false);
result += addModeToString(_.Mode, 0xFF, kCarrierAc64Cool,
kCarrierAc64Heat, 0xFF, kCarrierAc64Fan);
result += addTempToString(getTemp());
result += addFanToString(_.Fan, kCarrierAc64FanHigh, kCarrierAc64FanLow,
kCarrierAc64FanAuto, kCarrierAc64FanAuto,
kCarrierAc64FanMedium);
result += addBoolToString(_.SwingV, kSwingVStr);
result += addBoolToString(_.Sleep, kSleepStr);
result += addLabeledString(getOnTimer()
? minsToString(getOnTimer()) : kOffStr,
kOnTimerStr);
result += addLabeledString(getOffTimer()
? minsToString(getOffTimer()) : kOffStr,
kOffTimerStr);
return result;
}
/// Convert the A/C state to it's common stdAc::state_t equivalent.
/// @return A stdAc::state_t state.
stdAc::state_t IRCarrierAc64::toCommon(void) const {
stdAc::state_t result{};
result.protocol = decode_type_t::CARRIER_AC64;
result.model = -1; // No models used.
result.power = _.Power;
result.mode = toCommonMode(_.Mode);
result.celsius = true;
result.degrees = getTemp();
result.fanspeed = toCommonFanSpeed(_.Fan);
result.swingv = _.SwingV ? stdAc::swingv_t::kAuto : stdAc::swingv_t::kOff;
result.sleep = _.Sleep ? 0 : -1;
// Not supported.
result.swingh = stdAc::swingh_t::kOff;
result.turbo = false;
result.quiet = false;
result.clean = false;
result.filter = false;
result.beep = false;
result.econo = false;
result.light = false;
result.clock = -1;
return result;
}
#if SEND_CARRIER_AC128
/// Send a Carrier 128bit HVAC formatted message.
/// Status: BETA / Seems to work with tests. Needs testing agaisnt real devices.
/// @param[in] data The message to be sent.
/// @param[in] nbytes The byte size of the message being sent.
/// @param[in] repeat The number of times the message is to be repeated.
void IRsend::sendCarrierAC128(const uint8_t data[], const uint16_t nbytes,
const uint16_t repeat) {
// Min length check.
if (nbytes <= kCarrierAc128StateLength / 2) return;
enableIROut(kCarrierAcFreq);
// Handle repeats.
for (uint16_t r = 0; r <= repeat; r++) {
// First part of the message.
// Headers + Data + SectionGap
sendGeneric(kCarrierAc128HdrMark, kCarrierAc128HdrSpace,
kCarrierAc128BitMark, kCarrierAc128OneSpace,
kCarrierAc128BitMark, kCarrierAc128ZeroSpace,
kCarrierAc128BitMark, kCarrierAc128SectionGap,
data, nbytes / 2, kCarrierAcFreq, false, 0, kDutyDefault);
// Inter-message markers
mark(kCarrierAc128HdrMark);
space(kCarrierAc128InterSpace);
// Second part of the message
// Headers + Data + SectionGap
sendGeneric(kCarrierAc128Hdr2Mark, kCarrierAc128Hdr2Space,
kCarrierAc128BitMark, kCarrierAc128OneSpace,
kCarrierAc128BitMark, kCarrierAc128ZeroSpace,
kCarrierAc128BitMark, kCarrierAc128SectionGap,
data + (nbytes / 2), nbytes / 2, kCarrierAcFreq,
false, 0, kDutyDefault);
// Footer
mark(kCarrierAc128HdrMark);
space(kDefaultMessageGap);
}
}
#endif // SEND_CARRIER_AC128
#if DECODE_CARRIER_AC128
/// Decode the supplied Carrier 128-bit HVAC message.
/// Status: STABLE / Expected to work.
/// @param[in,out] results Ptr to the data to decode & where to store the decode
/// result.
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// @param[in] strict Flag indicating if we should perform strict matching.
/// @return A boolean. True if it can decode it, false if it can't.
bool IRrecv::decodeCarrierAC128(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
if (results->rawlen < 2 * (nbits + 2 * kHeader + kFooter) - 1 + offset)
return false; // Can't possibly be a valid Carrier message.
if (strict && nbits != kCarrierAc128Bits)
return false; // We expect Carrier to be 128 bits of message.
uint16_t used;
uint16_t pos = 0;
const uint16_t sectionbits = nbits / 2;
// Match the first section.
used = matchGeneric(results->rawbuf + offset, results->state,
results->rawlen - offset, sectionbits,
kCarrierAc128HdrMark, kCarrierAc128HdrSpace,
kCarrierAc128BitMark, kCarrierAc128OneSpace,
kCarrierAc128BitMark, kCarrierAc128ZeroSpace,
kCarrierAc128BitMark, kCarrierAc128SectionGap, true,
kUseDefTol, kMarkExcess, false);
if (used == 0) return false; // No match.
offset += used;
pos += sectionbits / 8;
// Look for the inter-message markers.
if (!matchMark(results->rawbuf[offset++], kCarrierAc128HdrMark))
return false;
if (!matchSpace(results->rawbuf[offset++], kCarrierAc128InterSpace))
return false;
// Now look for the second section.
used = matchGeneric(results->rawbuf + offset, results->state + pos,
results->rawlen - offset, sectionbits,
kCarrierAc128Hdr2Mark, kCarrierAc128Hdr2Space,
kCarrierAc128BitMark, kCarrierAc128OneSpace,
kCarrierAc128BitMark, kCarrierAc128ZeroSpace,
kCarrierAc128BitMark, kCarrierAc128SectionGap, true,
kUseDefTol, kMarkExcess, false);
if (used == 0) return false; // No match.
offset += used;
// Now check for the Footer.
if (!matchMark(results->rawbuf[offset++], kCarrierAc128HdrMark)) return false;
if (offset < results->rawlen &&
!matchAtLeast(results->rawbuf[offset], kDefaultMessageGap)) return false;
// Compliance
// if (strict && !IRCarrierAc128::validChecksum(results->value)) return false;
// Success
results->bits = nbits;
results->decode_type = CARRIER_AC128;
return true;
}
#endif // DECODE_CARRIER_AC128
#if SEND_CARRIER_AC84
/// Send a Carroer A/C 84 Bit formatted message.
/// Status: BETA / Untested but probably works.
/// @param[in] data The message to be sent.
/// @param[in] nbytes The byte size of the message being sent.
/// @param[in] repeat The number of times the command is to be repeated.
void IRsend::sendCarrierAC84(const uint8_t data[], const uint16_t nbytes,
const uint16_t repeat) {
// Protocol uses a constant bit time encoding.
for (uint16_t r = 0; r <= repeat; r++) {
if (nbytes) {
// The least significant `kCarrierAc84ExtraBits` bits of the first byte
sendGeneric(kCarrierAc84HdrMark, kCarrierAc84HdrSpace, // Header
kCarrierAc84Zero, kCarrierAc84One, // Data
kCarrierAc84One, kCarrierAc84Zero,
0, 0, // No footer
GETBITS64(data[0], 0, kCarrierAc84ExtraBits),
kCarrierAc84ExtraBits,
38000, false, 0, 33);
// The rest of the data.
sendGeneric(0, 0, // No Header
kCarrierAc84Zero, kCarrierAc84One, // Data
kCarrierAc84One, kCarrierAc84Zero,
kCarrierAc84Zero, kDefaultMessageGap, // Footer
data + 1, nbytes - 1, 38000, false, 0, 33);
}
}
}
#endif // SEND_CARRIER_AC84
#if DECODE_CARRIER_AC84
/// Decode the supplied Carroer A/C 84 Bit formatted message.
/// Status: STABLE / Confirmed Working.
/// @param[in,out] results Ptr to the data to decode & where to store the decode
/// result.
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// @param[in] strict Flag indicating if we should perform strict matching.
/// @return A boolean. True if it can decode it, false if it can't.
bool IRrecv::decodeCarrierAC84(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
// Check if we have enough data to even possibly match.
if (results->rawlen < 2 * nbits + kHeader + kFooter - 1 + offset)
return false; // Can't possibly be a valid Carrier message.
// Compliance check.
if (strict && nbits != kCarrierAc84Bits) return false;
// This decoder expects to decode an unusual number of bits. Check before we
// start.
if (nbits % 8 != kCarrierAc84ExtraBits) return false;
uint64_t data = 0;
uint16_t used = 0;
// Header + Data (kCarrierAc84ExtraBits only)
used = matchGenericConstBitTime(results->rawbuf + offset, &data,
results->rawlen - offset,
kCarrierAc84ExtraBits,
// Header (None)
kCarrierAc84HdrMark, kCarrierAc84HdrSpace,
// Data
kCarrierAc84Zero, kCarrierAc84One,
// No Footer
0, 0,
false,
_tolerance + kCarrierAc84ExtraTolerance,
kMarkExcess, false);
if (!used) return false;
// Stuff the captured data so far into the first byte of the state.
*results->state = data;
offset += used;
// Capture the rest of the data as normal as we should be on a byte boundary.
// Data + Footer
if (!matchGeneric(results->rawbuf + offset, results->state + 1,
results->rawlen - offset, nbits - kCarrierAc84ExtraBits,
0, 0, // No Header expected.
kCarrierAc84Zero, kCarrierAc84One, // Data
kCarrierAc84One, kCarrierAc84Zero,
kCarrierAc84Zero, kDefaultMessageGap, true,
_tolerance + kCarrierAc84ExtraTolerance,
kMarkExcess, false)) return false;
// Success
results->decode_type = decode_type_t::CARRIER_AC84;
results->bits = nbits;
results->repeat = false;
return true;
}
#endif // DECODE_CARRIER_AC84

View File

@@ -0,0 +1,146 @@
// Copyright 2020-2022 David Conran
/// @file
/// @brief Carrier A/C
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1127
/// @see https://docs.google.com/spreadsheets/d/1EZy78L0cn1KDIX1aKq2biptejFqCjD5HO3tLiRvXf48/edit#gid=0
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1797
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1943
// Supports:
// Brand: Carrier/Surrey, Model: 42QG5A55970 remote
// Brand: Carrier/Surrey, Model: 619EGX0090E0 A/C
// Brand: Carrier/Surrey, Model: 619EGX0120E0 A/C
// Brand: Carrier/Surrey, Model: 619EGX0180E0 A/C
// Brand: Carrier/Surrey, Model: 619EGX0220E0 A/C
// Brand: Carrier/Surrey, Model: 53NGK009/012 Inverter
// Brand: Carrier, Model: 40GKX0E2006 remote (CARRIER_AC128)
// Brand: Carrier, Model: 3021203 RR03-S-Remote (CARRIER_AC84)
// Brand: Carrier, Model: 342WM100CT A/C (CARRIER_AC84)
#ifndef IR_CARRIER_H_
#define IR_CARRIER_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
/// Native representation of a Carrier A/C message.
union CarrierProtocol {
uint64_t raw; ///< The state of the IR remote.
struct {
// Byte 0
uint8_t :8;
// Byte 1
uint8_t :8;
// Byte 2
uint8_t Sum:4;
uint8_t Mode:2;
uint8_t Fan:2;
// Byte 3
uint8_t Temp:4;
uint8_t :1;
uint8_t SwingV:1;
uint8_t :2;
// Byte 4
uint8_t :4;
uint8_t Power:1;
uint8_t OffTimerEnable:1;
uint8_t OnTimerEnable:1;
uint8_t Sleep:1;
// Byte 5
uint8_t :8;
// Byte 6
uint8_t :4;
uint8_t OnTimer:4;
// Byte 7
uint8_t :4;
uint8_t OffTimer:4;
};
};
// Constants
// CARRIER_AC64
const uint8_t kCarrierAc64ChecksumOffset = 16;
const uint8_t kCarrierAc64ChecksumSize = 4;
const uint8_t kCarrierAc64Heat = 0b01; // 1
const uint8_t kCarrierAc64Cool = 0b10; // 2
const uint8_t kCarrierAc64Fan = 0b11; // 3
const uint8_t kCarrierAc64FanAuto = 0b00; // 0
const uint8_t kCarrierAc64FanLow = 0b01; // 1
const uint8_t kCarrierAc64FanMedium = 0b10; // 2
const uint8_t kCarrierAc64FanHigh = 0b11; // 3
const uint8_t kCarrierAc64MinTemp = 16; // Celsius
const uint8_t kCarrierAc64MaxTemp = 30; // Celsius
const uint8_t kCarrierAc64TimerMax = 9; // Hours.
const uint8_t kCarrierAc64TimerMin = 1; // Hours.
// Classes
/// Class for handling detailed Carrier 64 bit A/C messages.
class IRCarrierAc64 {
public:
explicit IRCarrierAc64(const uint16_t pin, const bool inverted = false,
const bool use_modulation = true);
void stateReset();
#if SEND_CARRIER_AC64
void send(const uint16_t repeat = kCarrierAc64MinRepeat);
/// 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_CARRIER_AC64
void begin();
static uint8_t calcChecksum(const uint64_t state);
static bool validChecksum(const uint64_t state);
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) const;
void setSwingV(const bool on);
bool getSwingV(void) const;
void setSleep(const bool on);
bool getSleep(void) const;
void setFan(const uint8_t speed);
uint8_t getFan(void) const;
void setMode(const uint8_t mode);
uint8_t getMode(void) const;
void setOnTimer(const uint16_t nr_of_mins);
uint16_t getOnTimer(void) const;
void setOffTimer(const uint16_t nr_of_mins);
uint16_t getOffTimer(void) const;
uint64_t getRaw(void);
void setRaw(const uint64_t state);
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);
static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed);
stdAc::state_t toCommon(void) const;
String toString(void) const;
#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
CarrierProtocol _;
void checksum(void);
void _cancelOnTimer(void);
void _cancelOffTimer(void);
};
#endif // IR_CARRIER_H_

View File

@@ -0,0 +1,86 @@
// Copyright 2022 benjy3gg
// Copyright 2022 David Conran (crankyoldgit)
/// @file
/// @brief Support for Clima-Butler protocol
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1812
// Supports:
// Brand: Clima-Butler, Model: AR-715 remote
// Brand: Clima-Butler, Model: RCS-SD43UWI A/C
#include "IRrecv.h"
#include "IRsend.h"
#include "IRutils.h"
const uint16_t kClimaButlerBitMark = 511; // uSeconds
const uint16_t kClimaButlerHdrMark = kClimaButlerBitMark;
const uint16_t kClimaButlerHdrSpace = 3492; // uSeconds
const uint16_t kClimaButlerOneSpace = 1540; // uSeconds
const uint16_t kClimaButlerZeroSpace = 548; // uSeconds
const uint32_t kClimaButlerGap = kDefaultMessageGap; // uSeconds (A guess.)
const uint16_t kClimaButlerFreq = 38000; // Hz. (Guess.)
#if SEND_CLIMABUTLER
/// Send a ClimaButler formatted message.
/// Status: STABLE / Confirmed working.
/// @param[in] data containing the IR command.
/// @param[in] nbits Nr. of bits to send. usually kClimaButlerBits
/// @param[in] repeat Nr. of times the message is to be repeated.
void IRsend::sendClimaButler(const uint64_t data, const uint16_t nbits,
const uint16_t repeat) {
enableIROut(kClimaButlerFreq);
for (uint16_t r = 0; r <= repeat; r++) {
// Header + Data
sendGeneric(kClimaButlerHdrMark, kClimaButlerHdrSpace,
kClimaButlerBitMark, kClimaButlerOneSpace,
kClimaButlerBitMark, kClimaButlerZeroSpace,
kClimaButlerBitMark, kClimaButlerHdrSpace,
data, nbits, kClimaButlerFreq, true, 0, kDutyDefault);
// Footer
mark(kClimaButlerBitMark);
space(kClimaButlerGap);
}
}
#endif // SEND_CLIMABUTLER
#if DECODE_CLIMABUTLER
/// Decode the supplied ClimaButler message.
/// Status: STABLE / Confirmed working.
/// @param[in,out] results Ptr to the data to decode & where to store the decode
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// @param[in] strict Flag indicating if we should perform strict matching.
/// @return A boolean. True if it can decode it, false if it can't.
bool IRrecv::decodeClimaButler(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
if (results->rawlen < 2 * nbits + kHeader + 2 * kFooter - offset)
return false; // Too short a message to match.
if (strict && nbits != kClimaButlerBits)
return false;
// Header + Data
uint16_t used = matchGeneric(results->rawbuf + offset, &(results->value),
results->rawlen - offset, nbits,
kClimaButlerHdrMark, kClimaButlerHdrSpace,
kClimaButlerBitMark, kClimaButlerOneSpace,
kClimaButlerBitMark, kClimaButlerZeroSpace,
kClimaButlerBitMark, kClimaButlerHdrSpace);
if (!used) return false; // Didn't matched.
offset += used;
// Footer
if (!matchMark(results->rawbuf[offset++], kClimaButlerBitMark))
return false;
if (results->rawlen <= offset && !matchAtLeast(results->rawbuf[offset],
kClimaButlerGap))
return false;
// Success
results->decode_type = decode_type_t::CLIMABUTLER;
results->bits = nbits;
results->command = 0;
results->address = 0;
return true;
}
#endif // DECODE_CLIMABUTLER

View File

@@ -0,0 +1,760 @@
// Copyright bakrus
// Copyright 2017,2019 David Conran
// added by (send) bakrus & (decode) crankyoldgit
/// @file
/// @brief Coolix A/C / heatpump
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/484
#include "ir_Coolix.h"
#include <algorithm>
#ifndef ARDUINO
#include <string>
#endif
#include "IRrecv.h"
#include "IRsend.h"
#include "IRtext.h"
#include "IRutils.h"
// Constants
// Pulse parms are *50-100 for the Mark and *50+100 for the space
// First MARK is the one after the long gap
// pulse parameters in usec
const uint16_t kCoolixTick = 276; // Approximately 10.5 cycles at 38kHz
const uint16_t kCoolixBitMarkTicks = 2;
const uint16_t kCoolixBitMark = kCoolixBitMarkTicks * kCoolixTick; // 552us
const uint16_t kCoolixOneSpaceTicks = 6;
const uint16_t kCoolixOneSpace = kCoolixOneSpaceTicks * kCoolixTick; // 1656us
const uint16_t kCoolixZeroSpaceTicks = 2;
const uint16_t kCoolixZeroSpace = kCoolixZeroSpaceTicks * kCoolixTick; // 552us
const uint16_t kCoolixHdrMarkTicks = 17;
const uint16_t kCoolixHdrMark = kCoolixHdrMarkTicks * kCoolixTick; // 4692us
const uint16_t kCoolixHdrSpaceTicks = 16;
const uint16_t kCoolixHdrSpace = kCoolixHdrSpaceTicks * kCoolixTick; // 4416us
const uint16_t kCoolixMinGapTicks = kCoolixHdrMarkTicks + kCoolixZeroSpaceTicks;
const uint16_t kCoolixMinGap = kCoolixMinGapTicks * kCoolixTick; // 5244us
const uint8_t kCoolixExtraTolerance = 5; // Percent
using irutils::addBoolToString;
using irutils::addIntToString;
using irutils::addLabeledString;
using irutils::addModeToString;
using irutils::addTempToString;
#if SEND_COOLIX
/// Send a Coolix 24-bit message
/// Status: STABLE / Confirmed Working.
/// @param[in] data The message to be sent.
/// @param[in] nbits The number of bits of message to be sent.
/// @param[in] repeat The number of times the command is to be repeated.
/// @see https://github.com/z3t0/Arduino-IRremote/blob/master/ir_COOLIX.cpp
void IRsend::sendCOOLIX(uint64_t data, uint16_t nbits, uint16_t repeat) {
if (nbits % 8 != 0) return; // nbits is required to be a multiple of 8.
// Set IR carrier frequency
enableIROut(38);
for (uint16_t r = 0; r <= repeat; r++) {
// Header
mark(kCoolixHdrMark);
space(kCoolixHdrSpace);
// Data
// Break data into byte segments, starting at the Most Significant
// Byte. Each byte then being sent normal, then followed inverted.
for (uint16_t i = 8; i <= nbits; i += 8) {
// Grab a bytes worth of data.
uint8_t segment = (data >> (nbits - i)) & 0xFF;
// Normal
sendData(kCoolixBitMark, kCoolixOneSpace, kCoolixBitMark,
kCoolixZeroSpace, segment, 8, true);
// Inverted.
sendData(kCoolixBitMark, kCoolixOneSpace, kCoolixBitMark,
kCoolixZeroSpace, segment ^ 0xFF, 8, true);
}
// Footer
mark(kCoolixBitMark);
space(kCoolixMinGap); // Pause before repeating
}
space(kDefaultMessageGap);
}
#endif // SEND_COOLIX
/// 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?
IRCoolixAC::IRCoolixAC(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 IRCoolixAC::stateReset(void) {
setRaw(kCoolixDefaultState);
savedFan = getFan();
clearSensorTemp();
powerFlag = false;
turboFlag = false;
ledFlag = false;
cleanFlag = false;
sleepFlag = false;
swingFlag = false;
}
/// Set up hardware to be able to send a message.
void IRCoolixAC::begin(void) { _irsend.begin(); }
#if SEND_COOLIX
/// Send the current internal state as an IR message.
/// @param[in] repeat Nr. of times the message will be repeated.
void IRCoolixAC::send(const uint16_t repeat) {
// SwingVStep (aka. Direct / Vane step) needs to be sent with `0` repeats.
// Typically repeat is `kCoolixDefaultRepeat` which is `1`, so this allows
// it to be 0 normally for this command, and allows additional repeats if
// requested rather always 0 for that command.
_irsend.sendCOOLIX(getRaw(), kCoolixBits, repeat - (getSwingVStep() &&
repeat > 0) ? 1 : 0);
// make sure to remove special state from the internal state
// after command has being transmitted.
recoverSavedState();
}
#endif // SEND_COOLIX
/// Get a copy of the internal state as a valid code for this protocol.
/// @return A valid code for this protocol based on the current internal state.
uint32_t IRCoolixAC::getRaw(void) const { return _.raw; }
/// Set the internal state from a valid code for this protocol.
/// @param[in] new_code A valid code for this protocol.
void IRCoolixAC::setRaw(const uint32_t new_code) {
powerFlag = true; // Everything that is not the special power off mesg is On.
if (!handleSpecialState(new_code)) {
// it isn`t special so might affect Temp|mode|Fan
if (new_code == kCoolixCmdFan) {
setMode(kCoolixFan);
return;
}
}
// must be a command changing Temp|Mode|Fan
// it is safe to just copy to remote var
_.raw = new_code;
}
/// Is the current state is a special state?
/// @return true, if it is. false if it isn't.
bool IRCoolixAC::isSpecialState(void) const {
switch (_.raw) {
case kCoolixClean:
case kCoolixLed:
case kCoolixOff:
case kCoolixSwing:
case kCoolixSwingV:
case kCoolixSleep:
case kCoolixTurbo: return true;
default: return false;
}
}
/// Adjust any internal settings based on the type of special state we are
/// supplied. Does nothing if it isn't a special state.
/// @param[in] data The state we need to act upon.
/// @note Special state means commands that are not affecting
/// Temperature/Mode/Fan, and they toggle a setting.
/// e.g. Swing Step is not a special state by this definition.
/// @return true, if it is a special state. false if it isn't.
bool IRCoolixAC::handleSpecialState(const uint32_t data) {
switch (data) {
case kCoolixClean:
cleanFlag = !cleanFlag;
break;
case kCoolixLed:
ledFlag = !ledFlag;
break;
case kCoolixOff:
powerFlag = false;
break;
case kCoolixSwing:
swingFlag = !swingFlag;
break;
case kCoolixSleep:
sleepFlag = !sleepFlag;
break;
case kCoolixTurbo:
turboFlag = !turboFlag;
break;
default:
return false;
}
return true;
}
/// Backup the current internal state as long as it isn't a special state and
/// set the new state.
/// @note: Must be called before every special state to make sure the
/// internal state is safe.
/// @param[in] raw_state A valid raw state/code for this protocol.
void IRCoolixAC::updateAndSaveState(const uint32_t raw_state) {
if (!isSpecialState()) _saved = _;
_.raw = raw_state;
}
/// Restore the current internal state from backup as long as it isn't a
/// special state.
void IRCoolixAC::recoverSavedState(void) {
// If the current state is a special one, last known normal one.
if (isSpecialState()) _ = _saved;
// If the saved state was also a special state, reset as we expect a normal
// state out of all this.
if (isSpecialState()) stateReset();
}
/// Set the raw (native) temperature value.
/// @note Bypasses any checks.
/// @param[in] code The desired native temperature.
void IRCoolixAC::setTempRaw(const uint8_t code) { _.Temp = code; }
/// Get the raw (native) temperature value.
/// @return The native temperature value.
uint8_t IRCoolixAC::getTempRaw(void) const { return _.Temp; }
/// Set the temperature.
/// @param[in] desired The temperature in degrees celsius.
void IRCoolixAC::setTemp(const uint8_t desired) {
// Range check.
uint8_t temp = std::min(desired, kCoolixTempMax);
temp = std::max(temp, kCoolixTempMin);
setTempRaw(kCoolixTempMap[temp - kCoolixTempMin]);
}
/// Get the current temperature setting.
/// @return The current setting for temp. in degrees celsius.
uint8_t IRCoolixAC::getTemp(void) const {
const uint8_t code = getTempRaw();
for (uint8_t i = 0; i < kCoolixTempRange; i++)
if (kCoolixTempMap[i] == code) return kCoolixTempMin + i;
return kCoolixTempMax; // Not a temp we expected.
}
/// Set the raw (native) sensor temperature value.
/// @note Bypasses any checks or additional actions.
/// @param[in] code The desired native sensor temperature.
void IRCoolixAC::setSensorTempRaw(const uint8_t code) { _.SensorTemp = code; }
/// Set the sensor temperature.
/// @param[in] temp The temperature in degrees celsius.
/// @warning Do not send messages with a Sensor Temp more frequently than once
/// per minute, otherwise the A/C unit will ignore them.
void IRCoolixAC::setSensorTemp(const uint8_t temp) {
setSensorTempRaw(std::min(temp, kCoolixSensorTempMax));
setZoneFollow(true); // Setting a Sensor temp means you want to Zone Follow.
}
/// Get the sensor temperature setting.
/// @return The current setting for sensor temp. in degrees celsius.
uint8_t IRCoolixAC::getSensorTemp(void) const { return _.SensorTemp; }
/// Get the value of the current power setting.
/// @return true, the setting is on. false, the setting is off.
/// @note There is only an "off" state. Everything else is "on".
bool IRCoolixAC::getPower(void) const { return powerFlag; }
/// Change the power setting.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRCoolixAC::setPower(const bool on) {
if (!on)
updateAndSaveState(kCoolixOff);
else if (!powerFlag)
// at this point state must be ready
// to be transmitted
recoverSavedState();
powerFlag = on;
}
/// Change the power setting to On.
void IRCoolixAC::on(void) { setPower(true); }
/// Change the power setting to Off.
void IRCoolixAC::off(void) { setPower(false); }
/// Get the Swing setting of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRCoolixAC::getSwing(void) const { return swingFlag; }
/// Toggle the Swing mode of the A/C.
void IRCoolixAC::setSwing(void) {
// Assumes that repeated sending "swing" toggles the action on the device.
updateAndSaveState(kCoolixSwing);
swingFlag = !swingFlag;
}
/// Get the Vertical Swing Step setting of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRCoolixAC::getSwingVStep(void) const { return _.raw == kCoolixSwingV; }
/// Set the Vertical Swing Step setting of the A/C.
void IRCoolixAC::setSwingVStep(void) {
updateAndSaveState(kCoolixSwingV);
}
/// Get the Sleep setting of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRCoolixAC::getSleep(void) const { return sleepFlag; }
/// Toggle the Sleep mode of the A/C.
void IRCoolixAC::setSleep(void) {
updateAndSaveState(kCoolixSleep);
sleepFlag = !sleepFlag;
}
/// Get the Turbo setting of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRCoolixAC::getTurbo(void) const { return turboFlag; }
/// Toggle the Turbo mode of the A/C.
void IRCoolixAC::setTurbo(void) {
// Assumes that repeated sending "turbo" toggles the action on the device.
updateAndSaveState(kCoolixTurbo);
turboFlag = !turboFlag;
}
/// Get the Led (light) setting of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRCoolixAC::getLed(void) const { return ledFlag; }
/// Toggle the Led (light) mode of the A/C.
void IRCoolixAC::setLed(void) {
// Assumes that repeated sending "Led" toggles the action on the device.
updateAndSaveState(kCoolixLed);
ledFlag = !ledFlag;
}
/// Get the Clean setting of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRCoolixAC::getClean(void) const { return cleanFlag; }
/// Toggle the Clean mode of the A/C.
void IRCoolixAC::setClean(void) {
updateAndSaveState(kCoolixClean);
cleanFlag = !cleanFlag;
}
/// Get the Zone Follow setting of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRCoolixAC::getZoneFollow(void) const {
return _.ZoneFollow1 && _.ZoneFollow2;
}
/// Change the Zone Follow setting.
/// @note Internal use only.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRCoolixAC::setZoneFollow(const bool on) {
_.ZoneFollow1 = on;
_.ZoneFollow2 = on;
setFan(on ? kCoolixFanZoneFollow : savedFan);
}
/// Clear the Sensor Temperature setting..
void IRCoolixAC::clearSensorTemp(void) {
setZoneFollow(false);
setSensorTempRaw(kCoolixSensorTempIgnoreCode);
}
/// Set the operating mode of the A/C.
/// @param[in] mode The desired operating mode.
void IRCoolixAC::setMode(const uint8_t mode) {
uint32_t actualmode = mode;
switch (actualmode) {
case kCoolixAuto:
case kCoolixDry:
setFan(kCoolixFanAuto0, false);
break;
case kCoolixCool:
case kCoolixHeat:
case kCoolixFan:
setFan(kCoolixFanAuto, false);
break;
default: // Anything else, go with Auto mode.
setMode(kCoolixAuto);
setFan(kCoolixFanAuto0, false);
return;
}
setTemp(getTemp());
// Fan mode is a special case of Dry.
if (mode == kCoolixFan) {
actualmode = kCoolixDry;
setTempRaw(kCoolixFanTempCode);
}
_.Mode = actualmode;
}
/// Get the operating mode setting of the A/C.
/// @return The current operating mode setting.
uint8_t IRCoolixAC::getMode(void) const {
const uint8_t mode = _.Mode;
if (mode == kCoolixDry)
if (getTempRaw() == kCoolixFanTempCode) return kCoolixFan;
return mode;
}
/// Get the current fan speed setting.
/// @return The current fan speed.
uint8_t IRCoolixAC::getFan(void) const { return _.Fan; }
/// Set the speed of the fan.
/// @param[in] speed The desired setting.
/// @param[in] modecheck Do we enforce any mode limitations before setting?
void IRCoolixAC::setFan(const uint8_t speed, const bool modecheck) {
uint8_t newspeed = speed;
switch (speed) {
case kCoolixFanAuto: // Dry & Auto mode can't have this speed.
if (modecheck) {
switch (getMode()) {
case kCoolixAuto:
case kCoolixDry:
newspeed = kCoolixFanAuto0;
break;
}
}
break;
case kCoolixFanAuto0: // Only Dry & Auto mode can have this speed.
if (modecheck) {
switch (getMode()) {
case kCoolixAuto:
case kCoolixDry: break;
default: newspeed = kCoolixFanAuto;
}
}
break;
case kCoolixFanMin:
case kCoolixFanMed:
case kCoolixFanMax:
case kCoolixFanZoneFollow:
case kCoolixFanFixed:
break;
default: // Unknown speed requested.
newspeed = kCoolixFanAuto;
break;
}
// Keep a copy of the last non-ZoneFollow fan setting.
savedFan = (_.Fan == kCoolixFanZoneFollow) ? savedFan : _.Fan;
_.Fan = newspeed;
}
/// Convert a standard A/C mode into its native mode.
/// @param[in] mode A stdAc::opmode_t to be converted to it's native equivalent.
/// @return The corresponding native mode.
uint8_t IRCoolixAC::convertMode(const stdAc::opmode_t mode) {
switch (mode) {
case stdAc::opmode_t::kCool: return kCoolixCool;
case stdAc::opmode_t::kHeat: return kCoolixHeat;
case stdAc::opmode_t::kDry: return kCoolixDry;
case stdAc::opmode_t::kFan: return kCoolixFan;
default: return kCoolixAuto;
}
}
/// Convert a stdAc::fanspeed_t enum into it's native speed.
/// @param[in] speed The enum to be converted.
/// @return The native equivalent of the enum.
uint8_t IRCoolixAC::convertFan(const stdAc::fanspeed_t speed) {
switch (speed) {
case stdAc::fanspeed_t::kMin:
case stdAc::fanspeed_t::kLow: return kCoolixFanMin;
case stdAc::fanspeed_t::kMedium: return kCoolixFanMed;
case stdAc::fanspeed_t::kHigh:
case stdAc::fanspeed_t::kMax: return kCoolixFanMax;
default: return kCoolixFanAuto;
}
}
/// Convert a native mode to it's common stdAc::opmode_t equivalent.
/// @param[in] mode A native operation mode to be converted.
/// @return The corresponding common stdAc::opmode_t mode.
stdAc::opmode_t IRCoolixAC::toCommonMode(const uint8_t mode) {
switch (mode) {
case kCoolixCool: return stdAc::opmode_t::kCool;
case kCoolixHeat: return stdAc::opmode_t::kHeat;
case kCoolixDry: return stdAc::opmode_t::kDry;
case kCoolixFan: return stdAc::opmode_t::kFan;
default: return stdAc::opmode_t::kAuto;
}
}
/// Convert a native fan speed into its stdAc equivalent.
/// @param[in] speed The native setting to be converted.
/// @return The stdAc equivalent of the native setting.
stdAc::fanspeed_t IRCoolixAC::toCommonFanSpeed(const uint8_t speed) {
switch (speed) {
case kCoolixFanMax: return stdAc::fanspeed_t::kMax;
case kCoolixFanMed: return stdAc::fanspeed_t::kMedium;
case kCoolixFanMin: return stdAc::fanspeed_t::kMin;
default: return stdAc::fanspeed_t::kAuto;
}
}
/// Convert the A/C state to it's common stdAc::state_t equivalent.
/// @param[in] prev Ptr to the previous state if required.
/// @return A stdAc::state_t state.
stdAc::state_t IRCoolixAC::toCommon(const stdAc::state_t *prev) const {
stdAc::state_t result{};
// Start with the previous state if given it.
if (prev != NULL) {
result = *prev;
} else {
// Set defaults for non-zero values that are not implicitly set for when
// there is no previous state.
// e.g. Any setting that toggles should probably go here.
result.swingv = stdAc::swingv_t::kOff;
result.turbo = false;
result.clean = false;
result.light = false;
result.sleep = -1;
}
// Not supported.
result.model = -1; // No models used.
result.swingh = stdAc::swingh_t::kOff;
result.quiet = false;
result.econo = false;
result.filter = false;
result.beep = false;
result.clock = -1;
// Supported.
result.protocol = decode_type_t::COOLIX;
result.celsius = true;
result.power = getPower();
// Power off state no other state info. Use the previous state if we have it.
if (!result.power) return result;
// Handle the special single command (Swing/Turbo/Light/Clean/Sleep) toggle
// messages. These have no other state info so use the rest of the previous
// state if we have it for them.
if (getSwing()) {
result.swingv = result.swingv != stdAc::swingv_t::kOff ?
stdAc::swingv_t::kOff : stdAc::swingv_t::kAuto; // Invert swing.
return result;
} else if (getTurbo()) {
result.turbo = !result.turbo;
return result;
} else if (getLed()) {
result.light = !result.light;
return result;
} else if (getClean()) {
result.clean = !result.clean;
return result;
} else if (getSleep()) {
result.sleep = result.sleep >= 0 ? -1 : 0; // Invert sleep.
return result;
}
// Back to "normal" stateful messages.
result.mode = toCommonMode(getMode());
result.degrees = getTemp();
result.sensorTemperature = getSensorTemp();
if (result.sensorTemperature == kCoolixSensorTempIgnoreCode) {
result.sensorTemperature = kNoTempValue;
}
result.iFeel = getZoneFollow();
result.fanspeed = toCommonFanSpeed(getFan());
return result;
}
/// Convert the internal state into a human readable string.
/// @return The current internal state expressed as a human readable String.
String IRCoolixAC::toString(void) const {
String result = "";
result.reserve(100); // Reserve some heap for the string to reduce fragging.
result += addBoolToString(getPower(), kPowerStr, false);
if (!getPower()) return result; // If it's off, there is no other info.
if (isSpecialState()) {
// Special modes.
result += kCommaSpaceStr;
if (getSwing()) result += kSwingStr;
else if (getSwingVStep()) result += kSwingVStr;
else if (getSleep()) result += kSleepStr;
else if (getTurbo()) result += kTurboStr;
else if (getLed()) result += kLightStr;
else if (getClean()) result += kCleanStr;
result += kColonSpaceStr;
if (getSwingVStep())
result += kStepStr;
else
result += kToggleStr;
return result;
}
result += addModeToString(getMode(), kCoolixAuto, kCoolixCool, kCoolixHeat,
kCoolixDry, kCoolixFan);
result += addIntToString(getFan(), kFanStr);
result += kSpaceLBraceStr;
switch (getFan()) {
case kCoolixFanAuto:
result += kAutoStr;
break;
case kCoolixFanAuto0:
result += kAutoStr;
result += '0';
break;
case kCoolixFanMax:
result += kMaxStr;
break;
case kCoolixFanMin:
result += kMinStr;
break;
case kCoolixFanMed:
result += kMedStr;
break;
case kCoolixFanZoneFollow:
result += kZoneFollowStr;
break;
case kCoolixFanFixed:
result += kFixedStr;
break;
default:
result += kUnknownStr;
}
result += ')';
// Fan mode doesn't have a temperature.
if (getMode() != kCoolixFan) result += addTempToString(getTemp());
result += addBoolToString(getZoneFollow(), kZoneFollowStr);
result += addLabeledString(
(getSensorTemp() == kCoolixSensorTempIgnoreCode)
// Encasing with String(blah) to keep compatible with old arduino
// frameworks. Not needed with 3.0.2.
///> @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1639#issuecomment-944906016
? kOffStr : String(uint64ToString(getSensorTemp()) + 'C'),
kSensorTempStr);
return result;
}
#if DECODE_COOLIX
/// Decode the supplied Coolix 24-bit A/C message.
/// Status: STABLE / Known Working.
/// @param[in,out] results Ptr to the data to decode & where to store the decode
/// result.
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// @param[in] strict Flag indicating if we should perform strict matching.
/// @return A boolean. True if it can decode it, false if it can't.
bool IRrecv::decodeCOOLIX(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
// The protocol sends the data normal + inverted, alternating on
// each byte. Hence twice the number of expected data bits.
if (results->rawlen < 2 * 2 * nbits + kHeader + kFooter - 1 + offset)
return false; // Can't possibly be a valid COOLIX message.
if (strict && nbits != kCoolixBits)
return false; // Not strictly a COOLIX message.
if (nbits % 8 != 0) // nbits has to be a multiple of nr. of bits in a byte.
return false;
uint64_t data = 0;
uint64_t inverted = 0;
if (nbits > sizeof(data) * 8)
return false; // We can't possibly capture a Coolix packet that big.
// Header
if (!matchMark(results->rawbuf[offset++], kCoolixHdrMark)) return false;
if (!matchSpace(results->rawbuf[offset++], kCoolixHdrSpace)) return false;
// Data
// Twice as many bits as there are normal plus inverted bits.
for (uint16_t i = 0; i < nbits * 2; i += 8) {
const bool flip = (i / 8) % 2;
uint64_t result = 0;
// Read the next byte of data.
const uint16_t used = matchGeneric(results->rawbuf + offset, &result,
results->rawlen - offset, 8,
0, 0, // No Header
kCoolixBitMark, kCoolixOneSpace, // Data
kCoolixBitMark, kCoolixZeroSpace,
0, 0, // No Footer
false,
_tolerance + kCoolixExtraTolerance,
0, true);
if (!used) return false; // Didn't match a bytes worth of data.
offset += used;
if (flip) { // The inverted byte.
inverted <<= 8;
inverted |= result;
} else {
data <<= 8;
data |= result;
}
}
// Footer
if (!matchMark(results->rawbuf[offset++], kCoolixBitMark)) return false;
if (offset < results->rawlen &&
!matchAtLeast(results->rawbuf[offset], kCoolixMinGap)) return false;
// Compliance
uint64_t orig = data; // Save a copy of the data.
if (strict) {
for (uint16_t i = 0; i < nbits; i += 8, data >>= 8, inverted >>= 8)
if ((data & 0xFF) != ((inverted & 0xFF) ^ 0xFF)) return false;
}
// Success
results->decode_type = COOLIX;
results->bits = nbits;
results->value = orig;
results->address = 0;
results->command = 0;
return true;
}
#endif // DECODE_COOLIX
#if SEND_COOLIX48
/// Send a Coolix 48-bit message.
/// Status: ALPHA / Untested.
/// @param[in] data The message to be sent.
/// @param[in] nbits The number of bits of message to be sent.
/// @param[in] repeat The number of times the command is to be repeated.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1694
/// @note This is effectively the same as `sendCOOLIX()` except requiring the
/// bit flipping be done prior to the call.
void IRsend::sendCoolix48(const uint64_t data, const uint16_t nbits,
const uint16_t repeat) {
// Header + Data + Footer
sendGeneric(kCoolixHdrMark, kCoolixHdrSpace,
kCoolixBitMark, kCoolixOneSpace,
kCoolixBitMark, kCoolixZeroSpace,
kCoolixBitMark, kCoolixMinGap,
data, nbits, 38000, true, repeat, 33);
}
#endif // SEND_COOLIX48
#if DECODE_COOLIX48
/// Decode the supplied Coolix 48-bit A/C message.
/// Status: BETA / Probably Working.
/// @param[in,out] results Ptr to the data to decode & where to store the decode
/// result.
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// @param[in] strict Flag indicating if we should perform strict matching.
/// @return A boolean. True if it can decode it, false if it can't.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1694
bool IRrecv::decodeCoolix48(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
if (strict && nbits != kCoolix48Bits)
return false; // Not strictly a COOLIX48 message.
// Header + Data + Footer
if (!matchGeneric(results->rawbuf + offset, &(results->value),
results->rawlen - offset, nbits,
kCoolixHdrMark, kCoolixHdrSpace,
kCoolixBitMark, kCoolixOneSpace,
kCoolixBitMark, kCoolixZeroSpace,
kCoolixBitMark, kCoolixMinGap,
true, _tolerance + kCoolixExtraTolerance, 0, true))
return false;
// Success
results->decode_type = COOLIX48;
results->bits = nbits;
results->address = 0;
results->command = 0;
return true;
}
#endif // DECODE_COOLIX48

View File

@@ -0,0 +1,200 @@
// Copyright 2018 David Conran
/// @file
/// @brief Support for Coolix A/C protocols.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/484
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1318
/// @note Kudos:
/// Hamper: For the breakdown and mapping of the bit values.
/// fraschizzato: For additional ZoneFollow & SwingVStep analysis.
/// @note Timers seem to use the `COOLIX48` protocol.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1694
// Supports:
// Brand: Beko, Model: RG57K7(B)/BGEF Remote
// Brand: Beko, Model: BINR 070/071 split-type A/C
// Brand: Midea, Model: RG52D/BGE Remote
// Brand: Midea, Model: MS12FU-10HRDN1-QRD0GW(B) A/C
// Brand: Midea, Model: MSABAU-07HRFN1-QRD0GW A/C (circa 2016)
// Brand: Tokio, Model: AATOEMF17-12CHR1SW split-type RG51|50/BGE Remote
// Brand: Airwell, Model: RC08B remote
// Brand: Kastron, Model: RG57A7/BGEF Inverter remote
// Brand: Kaysun, Model: Casual CF A/C
// Brand: Toshiba, Model: RAS-M10YKV-E A/C
// Brand: Toshiba, Model: RAS-M13YKV-E A/C
// Brand: Toshiba, Model: RAS-4M27YAV-E A/C
// Brand: Toshiba, Model: WH-E1YE remote
// Brand: Bosch, Model: RG36B4/BGE remote
// Brand: Bosch, Model: B1ZAI2441W/B1ZAO2441W A/C
#ifndef IR_COOLIX_H_
#define IR_COOLIX_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
// Constants
// Modes
const uint8_t kCoolixCool = 0b000;
const uint8_t kCoolixDry = 0b001;
const uint8_t kCoolixAuto = 0b010;
const uint8_t kCoolixHeat = 0b011;
const uint8_t kCoolixFan = 0b100; // Synthetic.
// const uint32_t kCoolixModeMask = 0b000000000000000000001100; // 0xC
// const uint32_t kCoolixZoneFollowMask = 0b000010000000000000000010 0x80002
// Fan Control
const uint8_t kCoolixFanMin = 0b100;
const uint8_t kCoolixFanMed = 0b010;
const uint8_t kCoolixFanMax = 0b001;
const uint8_t kCoolixFanAuto = 0b101;
const uint8_t kCoolixFanAuto0 = 0b000;
const uint8_t kCoolixFanZoneFollow = 0b110;
const uint8_t kCoolixFanFixed = 0b111;
// Temperature
const uint8_t kCoolixTempMin = 17; // Celsius
const uint8_t kCoolixTempMax = 30; // Celsius
const uint8_t kCoolixTempRange = kCoolixTempMax - kCoolixTempMin + 1;
const uint8_t kCoolixFanTempCode = 0b1110; // Part of Fan Mode.
const uint8_t kCoolixTempMap[kCoolixTempRange] = {
0b0000, // 17C
0b0001, // 18c
0b0011, // 19C
0b0010, // 20C
0b0110, // 21C
0b0111, // 22C
0b0101, // 23C
0b0100, // 24C
0b1100, // 25C
0b1101, // 26C
0b1001, // 27C
0b1000, // 28C
0b1010, // 29C
0b1011 // 30C
};
const uint8_t kCoolixSensorTempMax = 30; // Celsius
const uint8_t kCoolixSensorTempIgnoreCode = 0b11111; // 0x1F / 31 (DEC)
// kCoolixSensorTempMask = 0b000000000000111100000000; // 0xF00
// Fixed states/messages.
const uint32_t kCoolixOff = 0b101100100111101111100000; // 0xB27BE0
const uint32_t kCoolixSwing = 0b101100100110101111100000; // 0xB26BE0
const uint32_t kCoolixSwingH = 0b101100101111010110100010; // 0xB5F5A2
const uint32_t kCoolixSwingV = 0b101100100000111111100000; // 0xB20FE0
const uint32_t kCoolixSleep = 0b101100101110000000000011; // 0xB2E003
const uint32_t kCoolixTurbo = 0b101101011111010110100010; // 0xB5F5A2
const uint32_t kCoolixLed = 0b101101011111010110100101; // 0xB5F5A5
const uint32_t kCoolixClean = 0b101101011111010110101010; // 0xB5F5AA
const uint32_t kCoolixCmdFan = 0b101100101011111111100100; // 0xB2BFE4
// On, 25C, Mode: Auto, Fan: Auto, Zone Follow: Off, Sensor Temp: Ignore.
const uint32_t kCoolixDefaultState = 0b101100100001111111001000; // 0xB21FC8
/// Native representation of a Coolix A/C message.
union CoolixProtocol {
uint32_t raw; ///< The state in IR code form.
struct { // Only 24 bits are used.
// Byte
uint32_t :1; // Unknown
uint32_t ZoneFollow1:1; ///< Control bit for Zone Follow mode.
uint32_t Mode :2; ///< Operation mode.
uint32_t Temp :4; ///< Desired temperature (Celsius)
// Byte
uint32_t SensorTemp :5; ///< The temperature sensor in the IR remote.
uint32_t Fan :3; ///< Fan speed
// Byte
uint32_t :3; // Unknown
uint32_t ZoneFollow2:1; ///< Additional control bit for Zone Follow mode.
uint32_t :4; ///< Fixed value 0b1011 / 0xB.
};
};
// Classes
/// Class for handling detailed Coolix A/C messages.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/484
class IRCoolixAC {
public:
explicit IRCoolixAC(const uint16_t pin, const bool inverted = false,
const bool use_modulation = true);
void stateReset(void);
#if SEND_COOLIX
void send(const uint16_t repeat = kCoolixDefaultRepeat);
/// 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_COOLIX
void begin(void);
void on(void);
void off(void);
void setPower(const bool on);
bool getPower(void) const;
void setTemp(const uint8_t temp);
uint8_t getTemp(void) const;
void setSensorTemp(const uint8_t temp);
uint8_t getSensorTemp(void) const;
void clearSensorTemp(void);
void setFan(const uint8_t speed, const bool modecheck = true);
uint8_t getFan(void) const;
void setMode(const uint8_t mode);
uint8_t getMode(void) const;
void setSwing(void);
bool getSwing(void) const;
void setSwingVStep(void);
bool getSwingVStep(void) const;
void setSleep(void);
bool getSleep(void) const;
void setTurbo(void);
bool getTurbo(void) const;
void setLed(void);
bool getLed(void) const;
void setClean(void);
bool getClean(void) const;
bool getZoneFollow(void) const;
uint32_t getRaw(void) const;
void setRaw(const uint32_t new_code);
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);
static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed);
stdAc::state_t toCommon(const stdAc::state_t *prev = NULL) const;
String toString(void) const;
void setZoneFollow(const bool on);
#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
CoolixProtocol _; ///< The state of the IR remote in IR code form.
CoolixProtocol _saved; ///< Copy of the state if we required a special mode.
// Internal State settings
bool powerFlag;
bool turboFlag;
bool ledFlag;
bool cleanFlag;
bool sleepFlag;
bool swingFlag;
uint8_t savedFan;
void setTempRaw(const uint8_t code);
uint8_t getTempRaw(void) const;
void setSensorTempRaw(const uint8_t code);
bool isSpecialState(void) const;
bool handleSpecialState(const uint32_t data);
void updateAndSaveState(const uint32_t raw_state);
void recoverSavedState(void);
uint32_t getNormalState(void);
};
#endif // IR_COOLIX_H_

View File

@@ -0,0 +1,575 @@
// Copyright 2020 Christian Nilsson
//
/// @file
/// @brief Corona A/C protocol
/// @note Unsupported:
/// - Auto/Max button press (special format)
#include "ir_Corona.h"
#include <algorithm>
#include <cstring>
#include "IRac.h"
#include "IRrecv.h"
#include "IRsend.h"
#include "IRtext.h"
#include "IRutils.h"
using irutils::addBoolToString;
using irutils::addLabeledString;
using irutils::addModeToString;
using irutils::addTempToString;
using irutils::addFanToString;
using irutils::minsToString;
using irutils::setBits;
// Constants
const uint16_t kCoronaAcHdrMark = 3500;
const uint16_t kCoronaAcHdrSpace = 1680;
const uint16_t kCoronaAcBitMark = 450;
const uint16_t kCoronaAcOneSpace = 1270;
const uint16_t kCoronaAcZeroSpace = 420;
const uint16_t kCoronaAcSpaceGap = 10800;
const uint16_t kCoronaAcFreq = 38000; // Hz.
const uint16_t kCoronaAcOverheadShort = 3;
const uint16_t kCoronaAcOverhead = 11; // full message
const uint8_t kCoronaTolerance = 5; // +5%
#if SEND_CORONA_AC
/// Send a CoronaAc formatted message.
/// Status: STABLE / Working on real device.
/// @param[in] data An array of bytes containing the IR command.
/// @param[in] nbytes Nr. of bytes of data in the array.
/// e.g.
/// @code
/// uint8_t data[kCoronaAcStateLength] = {
/// 0x28, 0x61, 0x3D, 0x19, 0xE6, 0x37, 0xC8,
/// 0x28, 0x61, 0x6D, 0xFF, 0x00, 0xFF, 0x00,
/// 0x28, 0x61, 0xCD, 0xFF, 0x00, 0xFF, 0x00};
/// @endcode
/// @param[in] repeat Nr. of times the message is to be repeated.
void IRsend::sendCoronaAc(const uint8_t data[],
const uint16_t nbytes, const uint16_t repeat) {
if (nbytes < kCoronaAcSectionBytes) return;
if (kCoronaAcSectionBytes < nbytes &&
nbytes < kCoronaAcStateLength) return;
for (uint16_t r = 0; r <= repeat; r++) {
uint16_t pos = 0;
// Data Section #1 - 3 loop
// e.g.
// bits = 56; bytes = 7;
// #1 *(data + pos) = {0x28, 0x61, 0x3D, 0x19, 0xE6, 0x37, 0xC8};
// #2 *(data + pos) = {0x28, 0x61, 0x6D, 0xFF, 0x00, 0xFF, 0x00};
// #3 *(data + pos) = {0x28, 0x61, 0xCD, 0xFF, 0x00, 0xFF, 0x00};
for (uint8_t section = 0; section < kCoronaAcSections; section++) {
sendGeneric(kCoronaAcHdrMark, kCoronaAcHdrSpace,
kCoronaAcBitMark, kCoronaAcOneSpace,
kCoronaAcBitMark, kCoronaAcZeroSpace,
kCoronaAcBitMark, kCoronaAcSpaceGap,
data + pos, kCoronaAcSectionBytes,
kCoronaAcFreq, false, kNoRepeat, kDutyDefault);
pos += kCoronaAcSectionBytes; // Adjust by how many bytes was sent
// don't send more data then what we have
if (nbytes <= pos)
break;
}
}
}
#endif // SEND_CORONA_AC
#if DECODE_CORONA_AC
/// Decode the supplied CoronaAc message.
/// Status: STABLE / Appears to be working.
/// @param[in,out] results Ptr to the data to decode & where to store it
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// @param[in] strict Flag indicating if we should perform strict matching.
/// @return A boolean. True if it can decode it, false if it can't.
bool IRrecv::decodeCoronaAc(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
bool isLong = results->rawlen >= kCoronaAcBits * 2;
if (results->rawlen < 2 * nbits +
(isLong ? kCoronaAcOverhead : kCoronaAcOverheadShort)
- offset)
return false; // Too short a message to match.
if (strict && nbits != kCoronaAcBits && nbits != kCoronaAcBitsShort)
return false;
uint16_t pos = 0;
uint16_t used = 0;
// Data Section #1 - 3 loop
// e.g.
// bits = 56; bytes = 7;
// #1 *(results->state + pos) = {0x28, 0x61, 0x3D, 0x19, 0xE6, 0x37, 0xC8};
// #2 *(results->state + pos) = {0x28, 0x61, 0x6D, 0xFF, 0x00, 0xFF, 0x00};
// #3 *(results->state + pos) = {0x28, 0x61, 0xCD, 0xFF, 0x00, 0xFF, 0x00};
for (uint8_t section = 0; section < kCoronaAcSections; section++) {
DPRINT(uint64ToString(section));
used = matchGeneric(results->rawbuf + offset, results->state + pos,
results->rawlen - offset, kCoronaAcBitsShort,
kCoronaAcHdrMark, kCoronaAcHdrSpace,
kCoronaAcBitMark, kCoronaAcOneSpace,
kCoronaAcBitMark, kCoronaAcZeroSpace,
kCoronaAcBitMark, kCoronaAcSpaceGap, true,
_tolerance + kCoronaTolerance, kMarkExcess, false);
if (used == 0) return false; // We failed to find any data.
// short versions section 0 is special
if (strict && !IRCoronaAc::validSection(results->state, pos,
isLong ? section : 3))
return false;
offset += used; // Adjust for how much of the message we read.
pos += kCoronaAcSectionBytes; // Adjust by how many bytes of data was read
// don't read more data then what we have
if (results->rawlen <= offset)
break;
}
// Re-check we got the correct size/length due to the way we read the data.
if (strict && pos * 8 != kCoronaAcBits && pos * 8 != kCoronaAcBitsShort) {
DPRINTLN("strict bit match fail");
return false;
}
// Success
results->decode_type = decode_type_t::CORONA_AC;
results->bits = pos * 8;
// 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_CORONA_AC
/// Class constructor for handling detailed Corona A/C messages.
/// @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?
IRCoronaAc::IRCoronaAc(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.
/// @note The state is powered off.
void IRCoronaAc::stateReset(void) {
// known good state
_.sections[kCoronaAcSettingsSection].Data0 = kCoronaAcSectionData0Base;
_.sections[kCoronaAcSettingsSection].Data1 = 0x00; // ensure no unset mem
setPowerButton(true); // we default to this on, any timer removes it
setTemp(kCoronaAcMinTemp);
setMode(kCoronaAcModeCool);
setFan(kCoronaAcFanAuto);
setOnTimer(kCoronaAcTimerOff);
setOffTimer(kCoronaAcTimerOff);
// headers and checks are fixed in getRaw by checksum(_.raw)
}
/// Get the byte that identifies the section
/// @param[in] section Index of the section 0-2,
/// 3 and above is used as the special case for short message
/// @return The byte used for the section
uint8_t IRCoronaAc::getSectionByte(const uint8_t section) {
// base byte
uint8_t b = kCoronaAcSectionLabelBase;
// 2 enabled bits shifted 0-2 bits depending on section
if (section >= 3)
return 0b10010000 | b;
setBits(&b, kHighNibble, kNibbleSize, 0b11 << section);
return b;
}
/// Check that a CoronaAc Section part is valid with section byte and inverted
/// @param[in] state An array of bytes containing the section
/// @param[in] pos Where to start in the state array
/// @param[in] section Which section to work with
/// Used to get the section byte, and is validated against pos
/// @return true if section is valid, otherwise false
bool IRCoronaAc::validSection(const uint8_t state[], const uint16_t pos,
const uint8_t section) {
// sanity check, pos must match section, section 4 is at pos 0
if ((section % kCoronaAcSections) * kCoronaAcSectionBytes != pos)
return false;
// all individual sections has the same prefix
const CoronaSection *p = reinterpret_cast<const CoronaSection*>(state + pos);
if (p->Header0 != kCoronaAcSectionHeader0) {
DPRINT("State ");
DPRINT(&(p->Header0) - state);
DPRINT(" expected 0x28 was ");
DPRINTLN(uint64ToString(p->Header0, 16));
return false;
}
if (p->Header1 != kCoronaAcSectionHeader1) {
DPRINT("State ");
DPRINT(&(p->Header1) - state);
DPRINT(" expected 0x61 was ");
DPRINTLN(uint64ToString(p->Header1, 16));
return false;
}
// checking section byte
if (p->Label != getSectionByte(section)) {
DPRINT("check 2 not matching, got ");
DPRINT(uint64ToString(p->Label, 16));
DPRINT(" expected ");
DPRINTLN(uint64ToString(getSectionByte(section), 16));
return false;
}
// checking inverts
uint8_t d0invinv = ~p->Data0Inv;
if (p->Data0 != d0invinv) {
DPRINT("inverted 3 - 4 not matching, got ");
DPRINT(uint64ToString(p->Data0, 16));
DPRINT(" vs ");
DPRINTLN(uint64ToString(p->Data0Inv, 16));
return false;
}
uint8_t d1invinv = ~p->Data1Inv;
if (p->Data1 != d1invinv) {
DPRINT("inverted 5 - 6 not matching, got ");
DPRINT(uint64ToString(p->Data1, 16));
DPRINT(" vs ");
DPRINTLN(uint64ToString(p->Data1Inv, 16));
return false;
}
return true;
}
/// Calculate and set the check values for the internal state.
/// @param[in,out] data The array to be modified
void IRCoronaAc::checksum(uint8_t* data) {
CoronaProtocol *p = reinterpret_cast<CoronaProtocol*>(data);
for (uint8_t i = 0; i < kCoronaAcSections; i++) {
p->sections[i].Header0 = kCoronaAcSectionHeader0;
p->sections[i].Header1 = kCoronaAcSectionHeader1;
p->sections[i].Label = getSectionByte(i);
p->sections[i].Data0Inv = ~p->sections[i].Data0;
p->sections[i].Data1Inv = ~p->sections[i].Data1;
}
}
/// Set up hardware to be able to send a message.
void IRCoronaAc::begin(void) { _irsend.begin(); }
#if SEND_CORONA_AC
/// Send the current internal state as an IR message.
/// @param[in] repeat Nr. of times the message will be repeated.
void IRCoronaAc::send(const uint16_t repeat) {
// if no timer, always send once without power press
if (!getOnTimer() && !getOffTimer()) {
setPowerButton(false);
_irsend.sendCoronaAc(getRaw(), kCoronaAcStateLength, repeat);
// and then with power press
setPowerButton(true);
}
_irsend.sendCoronaAc(getRaw(), kCoronaAcStateLength, repeat);
}
#endif // SEND_CORONA_AC
/// Get a copy of the internal state as a valid code for this protocol.
/// @return A Ptr to a valid code for this protocol based on the current
/// internal state.
/// @note To get stable AC state, if no timers, send once
/// without PowerButton set, and once with
uint8_t* IRCoronaAc::getRaw(void) {
checksum(_.raw); // Ensure correct check bits before sending.
return _.raw;
}
/// Set the internal state from a valid code for this protocol.
/// @param[in] new_code A valid state for this protocol.
/// @param[in] length of the new_code array.
void IRCoronaAc::setRaw(const uint8_t new_code[], const uint16_t length) {
memcpy(_.raw, new_code, std::min(length, kCoronaAcStateLength));
}
/// Set the temp in deg C.
/// @param[in] temp The desired temperature in Celsius.
void IRCoronaAc::setTemp(const uint8_t temp) {
uint8_t degrees = std::max(temp, kCoronaAcMinTemp);
degrees = std::min(degrees, kCoronaAcMaxTemp);
_.Temp = degrees - kCoronaAcMinTemp + 1;
}
/// Get the current temperature from the internal state.
/// @return The current temperature in Celsius.
uint8_t IRCoronaAc::getTemp(void) const {
return _.Temp + kCoronaAcMinTemp - 1;
}
/// Change the power setting. (in practice Standby, remote power)
/// @param[in] on true, the setting is on. false, the setting is off.
/// @note If changed, setPowerButton is also needed,
/// unless timer is or was active
void IRCoronaAc::setPower(const bool on) {
_.Power = on;
// setting power state resets timers that would cause the state
if (on)
setOnTimer(kCoronaAcTimerOff);
else
setOffTimer(kCoronaAcTimerOff);
}
/// Get the current power setting. (in practice Standby, remote power)
/// @return true, the setting is on. false, the setting is off.
bool IRCoronaAc::getPower(void) const {
return _.Power;
}
/// Change the power button setting.
/// @param[in] on true, the setting is on. false, the setting is off.
/// @note this sets that the AC should set power,
/// use setPower to define if the AC should end up as on or off
/// When no timer is active, the below is a truth table
/// With AC On, a command with setPower and setPowerButton gives nothing
/// With AC On, a command with setPower but not setPowerButton is ok
/// With AC Off, a command with setPower but not setPowerButton gives nothing
/// With AC Off, a command with setPower and setPowerButton is ok
void IRCoronaAc::setPowerButton(const bool on) {
_.PowerButton = on;
}
/// Get the value of the current power button setting.
/// @return true, the setting is on. false, the setting is off.
bool IRCoronaAc::getPowerButton(void) const {
return _.PowerButton;
}
/// Change the power setting to On.
void IRCoronaAc::on(void) { setPower(true); }
/// Change the power setting to Off.
void IRCoronaAc::off(void) { setPower(false); }
/// Get the operating mode setting of the A/C.
/// @return The current operating mode setting.
uint8_t IRCoronaAc::getMode(void) const {
return _.Mode;
}
/// Set the operating mode of the A/C.
/// @param[in] mode The desired operating mode.
void IRCoronaAc::setMode(const uint8_t mode) {
switch (mode) {
case kCoronaAcModeCool:
case kCoronaAcModeDry:
case kCoronaAcModeFan:
case kCoronaAcModeHeat:
_.Mode = mode;
return;
default:
_.Mode = kCoronaAcModeCool;
}
}
/// Convert a standard A/C mode into its native mode.
/// @param[in] mode A stdAc::opmode_t mode to be
/// converted to it's native equivalent
/// @return The corresponding native mode.
uint8_t IRCoronaAc::convertMode(const stdAc::opmode_t mode) {
switch (mode) {
case stdAc::opmode_t::kFan: return kCoronaAcModeFan;
case stdAc::opmode_t::kDry: return kCoronaAcModeDry;
case stdAc::opmode_t::kHeat: return kCoronaAcModeHeat;
default: return kCoronaAcModeCool;
}
}
/// Convert a native mode to it's common stdAc::opmode_t equivalent.
/// @param[in] mode A native operation mode to be converted.
/// @return The corresponding common stdAc::opmode_t mode.
stdAc::opmode_t IRCoronaAc::toCommonMode(const uint8_t mode) {
switch (mode) {
case kCoronaAcModeFan: return stdAc::opmode_t::kFan;
case kCoronaAcModeDry: return stdAc::opmode_t::kDry;
case kCoronaAcModeHeat: return stdAc::opmode_t::kHeat;
default: return stdAc::opmode_t::kCool;
}
}
/// Get the operating speed of the A/C Fan
/// @return The current operating fan speed setting
uint8_t IRCoronaAc::getFan(void) const {
return _.Fan;
}
/// Set the operating speed of the A/C Fan
/// @param[in] speed The desired fan speed
void IRCoronaAc::setFan(const uint8_t speed) {
if (speed > kCoronaAcFanHigh)
_.Fan = kCoronaAcFanAuto;
else
_.Fan = speed;
}
/// Change the powersave setting.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRCoronaAc::setEcono(const bool on) {
_.Econo = on;
}
/// Get the value of the current powersave setting.
/// @return true, the setting is on. false, the setting is off.
bool IRCoronaAc::getEcono(void) const {
return _.Econo;
}
/// Convert a standard A/C Fan speed into its native fan speed.
/// @param[in] speed The desired stdAc::fanspeed_t fan speed
/// @return The given fan speed in native format
uint8_t IRCoronaAc::convertFan(const stdAc::fanspeed_t speed) {
switch (speed) {
case stdAc::fanspeed_t::kMin:
case stdAc::fanspeed_t::kLow: return kCoronaAcFanLow;
case stdAc::fanspeed_t::kMedium: return kCoronaAcFanMedium;
case stdAc::fanspeed_t::kHigh:
case stdAc::fanspeed_t::kMax: return kCoronaAcFanHigh;
default: return kCoronaAcFanAuto;
}
}
/// Convert a native fan speed to it's common equivalent.
/// @param[in] speed The desired native fan speed
/// @return The given fan speed in stdAc::fanspeed_t format
stdAc::fanspeed_t IRCoronaAc::toCommonFanSpeed(const uint8_t speed) {
switch (speed) {
case kCoronaAcFanHigh: return stdAc::fanspeed_t::kHigh;
case kCoronaAcFanMedium: return stdAc::fanspeed_t::kMedium;
case kCoronaAcFanLow: return stdAc::fanspeed_t::kLow;
default: return stdAc::fanspeed_t::kAuto;
}
}
/// Set the Vertical Swing toggle setting
/// @param[in] on true, the setting is on. false, the setting is off.
/// @note This is a button press, and not a state
/// after sending it once you should turn it off
void IRCoronaAc::setSwingVToggle(const bool on) {
_.SwingVToggle = on;
}
/// Get the Vertical Swing toggle setting
/// @return true, the setting is on. false, the setting is off.
bool IRCoronaAc::getSwingVToggle(void) const {
return _.SwingVToggle;
}
/// Set the Timer time
/// @param[in] section index of section, used for offset.
/// @param[in] nr_of_mins Number of minutes to set the timer to.
/// (non in range value is disable).
/// Valid is from 1 minute to 12 hours
void IRCoronaAc::_setTimer(const uint8_t section, const uint16_t nr_of_mins) {
// default to off
uint16_t hsecs = kCoronaAcTimerOff;
if (1 <= nr_of_mins && nr_of_mins <= kCoronaAcTimerMax)
hsecs = nr_of_mins * kCoronaAcTimerUnitsPerMin;
// convert 16 bit value to separate 8 bit parts
_.sections[section].Data1 = hsecs >> 8;
_.sections[section].Data0 = hsecs;
// if any timer is enabled, then (remote) ac must be on (Standby)
if (hsecs != kCoronaAcTimerOff) {
_.Power = true;
setPowerButton(false);
}
}
/// Get the current Timer time
/// @return The number of minutes it is set for. 0 means it's off.
/// @note The A/C protocol supports 2 second increments
uint16_t IRCoronaAc::_getTimer(const uint8_t section) const {
// combine separate 8 bit parts to 16 bit value
uint16_t hsecs = _.sections[section].Data1 << 8 |
_.sections[section].Data0;
if (hsecs == kCoronaAcTimerOff)
return 0;
return hsecs / kCoronaAcTimerUnitsPerMin;
}
/// Get the current On Timer time
/// @return The number of minutes it is set for. 0 means it's off.
uint16_t IRCoronaAc::getOnTimer(void) const {
return _getTimer(kCoronaAcOnTimerSection);
}
/// Set the On Timer time
/// @param[in] nr_of_mins Number of minutes to set the timer to.
/// (0 or kCoronaAcTimerOff is disable).
void IRCoronaAc::setOnTimer(const uint16_t nr_of_mins) {
_setTimer(kCoronaAcOnTimerSection, nr_of_mins);
// if we set a timer value, clear the other timer
if (getOnTimer())
setOffTimer(kCoronaAcTimerOff);
}
/// Get the current Off Timer time
/// @return The number of minutes it is set for. 0 means it's off.
uint16_t IRCoronaAc::getOffTimer(void) const {
return _getTimer(kCoronaAcOffTimerSection);
}
/// Set the Off Timer time
/// @param[in] nr_of_mins Number of minutes to set the timer to.
/// (0 or kCoronaAcTimerOff is disable).
void IRCoronaAc::setOffTimer(const uint16_t nr_of_mins) {
_setTimer(kCoronaAcOffTimerSection, nr_of_mins);
// if we set a timer value, clear the other timer
if (getOffTimer())
setOnTimer(kCoronaAcTimerOff);
}
/// Convert the internal state into a human readable string.
/// @return The current internal state expressed as a human readable String.
String IRCoronaAc::toString(void) const {
String result = "";
result.reserve(140); // Reserve some heap for the string to reduce fragging.
result += addBoolToString(_.Power, kPowerStr, false);
result += addBoolToString(_.PowerButton, kPowerButtonStr);
result += addModeToString(_.Mode, 0xFF, kCoronaAcModeCool,
kCoronaAcModeHeat, kCoronaAcModeDry,
kCoronaAcModeFan);
result += addTempToString(getTemp());
result += addFanToString(_.Fan, kCoronaAcFanHigh, kCoronaAcFanLow,
kCoronaAcFanAuto, kCoronaAcFanAuto,
kCoronaAcFanMedium);
result += addBoolToString(_.SwingVToggle, kSwingVToggleStr);
result += addBoolToString(_.Econo, kEconoStr);
result += addLabeledString(getOnTimer()
? minsToString(getOnTimer()) : kOffStr,
kOnTimerStr);
result += addLabeledString(getOffTimer()
? minsToString(getOffTimer()) : kOffStr,
kOffTimerStr);
return result;
}
/// Convert the A/C state to it's common stdAc::state_t equivalent.
/// @return A stdAc::state_t state.
stdAc::state_t IRCoronaAc::toCommon() const {
stdAc::state_t result{};
result.protocol = decode_type_t::CORONA_AC;
result.model = -1; // No models used.
result.power = _.Power;
result.mode = toCommonMode(_.Mode);
result.celsius = true;
result.degrees = getTemp();
result.fanspeed = toCommonFanSpeed(_.Fan);
result.swingv = _.SwingVToggle ?
stdAc::swingv_t::kAuto : stdAc::swingv_t::kOff;
result.econo = _.Econo;
// Not supported.
result.sleep = -1;
result.swingh = stdAc::swingh_t::kOff;
result.turbo = false;
result.quiet = false;
result.clean = false;
result.filter = false;
result.beep = false;
result.light = false;
result.clock = -1;
return result;
}

View File

@@ -0,0 +1,168 @@
// Corona A/C
//
// Copyright 2020 Christian Nilsson
// Supports:
// Brand: Corona, Model: CSH-N2211 A/C
// Brand: Corona, Model: CSH-N2511 A/C
// Brand: Corona, Model: CSH-N2811 A/C
// Brand: Corona, Model: CSH-N4011 A/C
// Brand: Corona, Model: AR-01 remote
//
// Ref: https://docs.google.com/spreadsheets/d/1zzDEUQ52y7MZ7_xCU3pdjdqbRXOwZLsbTGvKWcicqCI/
// Ref: https://www.corona.co.jp/box/download.php?id=145060636229
#ifndef IR_CORONA_H_
#define IR_CORONA_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
/// Native representation of a section of a Corona A/C message.
struct CoronaSection {
uint8_t Header0;
uint8_t Header1;
uint8_t Label;
uint8_t Data0;
uint8_t Data0Inv;
uint8_t Data1;
uint8_t Data1Inv;
};
const uint8_t kCoronaAcSections = 3;
/// Native representation of a Corona A/C message.
union CoronaProtocol {
uint8_t raw[kCoronaAcStateLength]; ///< The state of the IR remote.
CoronaSection sections[kCoronaAcSections];
struct {
// Byte 0
uint8_t :8;
// Byte 1
uint8_t :8;
// Byte 2
uint8_t :8;
// Byte 3
uint8_t Fan :2;
uint8_t :1;
uint8_t Econo :1;
uint8_t :1; // always on
uint8_t :1;
uint8_t SwingVToggle :1;
uint8_t :1;
// Byte 4
uint8_t :8;
// Byte 5
uint8_t Temp :4;
uint8_t Power :1;
uint8_t PowerButton :1;
uint8_t Mode :2;
};
};
// Constants
// CORONA_AC
const uint8_t kCoronaAcSectionBytes = 7; // kCoronaAcStateLengthShort
const uint8_t kCoronaAcSectionHeader0 = 0x28;
const uint8_t kCoronaAcSectionHeader1 = 0x61;
const uint8_t kCoronaAcSectionLabelBase = 0x0D; // 0b1101
const uint8_t kCoronaAcSectionData0Base = 0x10; // D0 Pos 4 always on
const uint8_t kCoronaAcFanAuto = 0b00; // 0
const uint8_t kCoronaAcFanLow = 0b01; // 1
const uint8_t kCoronaAcFanMedium = 0b10; // 2
const uint8_t kCoronaAcFanHigh = 0b11; // 3
/* full auto mode not supported by this code yet
const uint8_t kCoronaAcAutoD0 = 0b00010100; // only combined with power save
const uint8_t kCoronaAcAutoD1 = 0b10000011; // only combined with power
*/
const uint8_t kCoronaAcMinTemp = 17; // Celsius = 0b0001
const uint8_t kCoronaAcMaxTemp = 30; // Celsius = 0b1110
const uint8_t kCoronaAcModeHeat = 0b00; // 0
const uint8_t kCoronaAcModeDry = 0b01; // 1
const uint8_t kCoronaAcModeCool = 0b10; // 2
const uint8_t kCoronaAcModeFan = 0b11; // 3
const uint8_t kCoronaAcSettingsSection = 0;
const uint8_t kCoronaAcOnTimerSection = 1;
const uint8_t kCoronaAcOffTimerSection = 2;
const uint16_t kCoronaAcTimerMax = 12 * 60; // 12H in Minutes
// Min value on remote is 1 hour, actual sent value can be 2 secs
const uint16_t kCoronaAcTimerOff = 0xffff;
const uint16_t kCoronaAcTimerUnitsPerMin = 30; // 30 units = 1 minute
// Classes
/// Class for handling detailed Corona A/C messages.
class IRCoronaAc {
public:
explicit IRCoronaAc(const uint16_t pin, const bool inverted = false,
const bool use_modulation = true);
void stateReset();
#if SEND_CORONA_AC
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_CORONA_AC
void begin();
static bool validSection(const uint8_t state[], const uint16_t pos,
const uint8_t section);
void setPower(const bool on);
bool getPower(void) const;
bool getPowerButton(void) const;
void on(void);
void off(void);
void setTemp(const uint8_t temp);
uint8_t getTemp(void) const;
void setSwingVToggle(const bool on);
bool getSwingVToggle(void) const;
void setFan(const uint8_t speed);
uint8_t getFan(void) const;
void setMode(const uint8_t mode);
uint8_t getMode(void) const;
void setEcono(const bool on);
bool getEcono(void) const;
void setOnTimer(const uint16_t nr_of_mins);
uint16_t getOnTimer(void) const;
void setOffTimer(const uint16_t nr_of_mins);
uint16_t getOffTimer(void) const;
uint8_t* getRaw();
void setRaw(const uint8_t new_code[],
const uint16_t length = kCoronaAcStateLength);
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);
static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed);
stdAc::state_t toCommon(void) const;
String toString(void) const;
#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
CoronaProtocol _;
static uint8_t getSectionByte(const uint8_t section);
static void checksum(uint8_t* data);
void setPowerButton(const bool on);
void _setTimer(const uint8_t section, const uint16_t nr_of_mins);
uint16_t _getTimer(const uint8_t section) const;
};
#endif // IR_CORONA_H_

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,470 @@
// Copyright 2020 David Conran
/// @file
/// @brief Delonghi based protocol.
#include "ir_Delonghi.h"
#include "IRrecv.h"
#include "IRsend.h"
#include "IRtext.h"
#include "IRutils.h"
#include <algorithm>
using irutils::addBoolToString;
using irutils::addModeToString;
using irutils::addFanToString;
using irutils::addLabeledString;
using irutils::addTempToString;
using irutils::minsToString;
const uint16_t kDelonghiAcHdrMark = 8984;
const uint16_t kDelonghiAcBitMark = 572;
const uint16_t kDelonghiAcHdrSpace = 4200;
const uint16_t kDelonghiAcOneSpace = 1558;
const uint16_t kDelonghiAcZeroSpace = 510;
const uint32_t kDelonghiAcGap = kDefaultMessageGap; // A totally made-up guess.
const uint16_t kDelonghiAcFreq = 38000; // Hz. (Guess: most common frequency.)
const uint16_t kDelonghiAcOverhead = 3;
#if SEND_DELONGHI_AC
/// Send a Delonghi A/C formatted message.
/// Status: STABLE / Reported as working on a real device.
/// @param[in] data The message to be sent.
/// @param[in] nbits The number of bits of message to be sent.
/// @param[in] repeat The number of times the command is to be repeated.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1096
void IRsend::sendDelonghiAc(const uint64_t data, const uint16_t nbits,
const uint16_t repeat) {
sendGeneric(kDelonghiAcHdrMark, kDelonghiAcHdrSpace,
kDelonghiAcBitMark, kDelonghiAcOneSpace,
kDelonghiAcBitMark, kDelonghiAcZeroSpace,
kDelonghiAcBitMark, kDelonghiAcGap,
data, nbits, kDelonghiAcFreq, false, // LSB First.
repeat, kDutyDefault);
}
#endif // SEND_DELONGHI_AC
#if DECODE_DELONGHI_AC
/// Decode the supplied Delonghi A/C message.
/// Status: STABLE / Expected to be working.
/// @param[in,out] results Ptr to the data to decode & where to store the decode
/// result.
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// @param[in] strict Flag indicating if we should perform strict matching.
/// @return A boolean. True if it can decode it, false if it can't.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1096
bool IRrecv::decodeDelonghiAc(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
if (results->rawlen < 2 * nbits + kDelonghiAcOverhead - offset)
return false; // Too short a message to match.
if (strict && nbits != kDelonghiAcBits)
return false;
uint64_t data = 0;
// Header + Data + Footer
if (!matchGeneric(results->rawbuf + offset, &data,
results->rawlen - offset, nbits,
kDelonghiAcHdrMark, kDelonghiAcHdrSpace,
kDelonghiAcBitMark, kDelonghiAcOneSpace,
kDelonghiAcBitMark, kDelonghiAcZeroSpace,
kDelonghiAcBitMark, kDelonghiAcGap, true,
_tolerance, kMarkExcess, false)) return false;
// Compliance
if (strict && !IRDelonghiAc::validChecksum(data)) return false;
// Success
results->decode_type = decode_type_t::DELONGHI_AC;
results->bits = nbits;
results->value = data;
results->command = 0;
results->address = 0;
return true;
}
#endif // DECODE_DELONGHI_AC
/// 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?
IRDelonghiAc::IRDelonghiAc(const uint16_t pin, const bool inverted,
const bool use_modulation)
: _irsend(pin, inverted, use_modulation) { stateReset(); }
/// Set up hardware to be able to send a message.
void IRDelonghiAc::begin(void) { _irsend.begin(); }
#if SEND_DELONGHI_AC
/// Send the current internal state as an IR message.
/// @param[in] repeat Nr. of times the message will be repeated.
void IRDelonghiAc::send(const uint16_t repeat) {
_irsend.sendDelonghiAc(getRaw(), kDelonghiAcBits, repeat);
}
#endif // SEND_DELONGHI_AC
/// Calculate the checksum for a given state.
/// @param[in] state The value to calc the checksum of.
/// @return A valid checksum value.
uint8_t IRDelonghiAc::calcChecksum(const uint64_t state) {
uint8_t sum = 0;
// Add up all the 8 bit chunks except for Most-significant 8 bits.
for (uint8_t offset = 0; offset < kDelonghiAcChecksumOffset; offset += 8) {
sum += GETBITS64(state, offset, 8);
}
return sum;
}
/// Verify the checksum is valid for a given state.
/// @param[in] state The state to verify the checksum of.
/// @return true, if the state has a valid checksum. Otherwise, false.
bool IRDelonghiAc::validChecksum(const uint64_t state) {
DelonghiProtocol dp;
dp.raw = state;
return (dp.Sum == IRDelonghiAc::calcChecksum(state));
}
/// Calculate and set the checksum values for the internal state.
void IRDelonghiAc::checksum(void) {
_.Sum = calcChecksum(_.raw);
}
/// Reset the internal state to a fixed known good state.
void IRDelonghiAc::stateReset(void) {
_.raw = 0x5400000000000153;
_saved_temp = 23; // DegC (Random reasonable default value)
_saved_temp_units = 0; // Celsius
}
/// Get a copy of the internal state as a valid code for this protocol.
/// @return A valid code for this protocol based on the current internal state.
uint64_t IRDelonghiAc::getRaw(void) {
checksum(); // Ensure correct bit array before returning
return _.raw;
}
/// Set the internal state from a valid code for this protocol.
/// @param[in] state A valid code for this protocol.
void IRDelonghiAc::setRaw(const uint64_t state) { _.raw = state; }
/// Change the power setting to On.
void IRDelonghiAc::on(void) { setPower(true); }
/// Change the power setting to Off.
void IRDelonghiAc::off(void) { setPower(false); }
/// Change the power setting.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRDelonghiAc::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 IRDelonghiAc::getPower(void) const {
return _.Power;
}
/// Change the temperature scale units.
/// @param[in] fahrenheit true, use Fahrenheit. false, use Celsius.
void IRDelonghiAc::setTempUnit(const bool fahrenheit) {
_.Fahrenheit = fahrenheit;
}
/// Get the temperature scale unit of measure currently in use.
/// @return true, is Fahrenheit. false, is Celsius.
bool IRDelonghiAc::getTempUnit(void) const {
return _.Fahrenheit;
}
/// Set the temperature.
/// @param[in] degrees The temperature in degrees.
/// @param[in] fahrenheit Use Fahrenheit as the temperature scale.
/// @param[in] force Do we ignore any sanity checks?
void IRDelonghiAc::setTemp(const uint8_t degrees, const bool fahrenheit,
const bool force) {
uint8_t temp;
if (force) {
temp = degrees; // We've been asked to force set this value.
} else {
uint8_t temp_min = kDelonghiAcTempMinC;
uint8_t temp_max = kDelonghiAcTempMaxC;
setTempUnit(fahrenheit);
if (fahrenheit) {
temp_min = kDelonghiAcTempMinF;
temp_max = kDelonghiAcTempMaxF;
}
temp = std::max(temp_min, degrees);
temp = std::min(temp_max, temp);
_saved_temp = temp;
_saved_temp_units = fahrenheit;
temp = temp - temp_min + 1;
}
_.Temp = temp;
}
/// Get the current temperature setting.
/// @return The current setting for temp. in currently configured units/scale.
uint8_t IRDelonghiAc::getTemp(void) const {
return _.Temp + (_.Fahrenheit ? kDelonghiAcTempMinF
: kDelonghiAcTempMinC) - 1;
}
/// Set the speed of the fan.
/// @param[in] speed The desired native setting.
void IRDelonghiAc::setFan(const uint8_t speed) {
// Mode fan speed rules.
switch (_.Mode) {
case kDelonghiAcFan:
// Fan mode can't have auto fan speed.
if (speed == kDelonghiAcFanAuto) {
if (_.Fan == kDelonghiAcFanAuto) _.Fan = kDelonghiAcFanHigh;
return;
}
break;
case kDelonghiAcAuto:
case kDelonghiAcDry:
// Auto & Dry modes only allows auto fan speed.
if (speed != kDelonghiAcFanAuto) {
_.Fan = kDelonghiAcFanAuto;
return;
}
break;
}
// Bounds check enforcement
if (speed > kDelonghiAcFanLow)
_.Fan = kDelonghiAcFanAuto;
else
_.Fan = speed;
}
/// Get the current native fan speed setting.
/// @return The current fan speed.
uint8_t IRDelonghiAc::getFan(void) const {
return _.Fan;
}
/// Convert a stdAc::fanspeed_t enum into it's native speed.
/// @param[in] speed The enum to be converted.
/// @return The native equivalent of the enum.
uint8_t IRDelonghiAc::convertFan(const stdAc::fanspeed_t speed) {
switch (speed) {
case stdAc::fanspeed_t::kMin:
case stdAc::fanspeed_t::kLow:
return kDelonghiAcFanLow;
case stdAc::fanspeed_t::kMedium:
return kDelonghiAcFanMedium;
case stdAc::fanspeed_t::kHigh:
case stdAc::fanspeed_t::kMax:
return kDelonghiAcFanHigh;
default:
return kDelonghiAcFanAuto;
}
}
/// Convert a native fan speed into its stdAc equivalent.
/// @param[in] speed The native setting to be converted.
/// @return The stdAc equivalent of the native setting.
stdAc::fanspeed_t IRDelonghiAc::toCommonFanSpeed(const uint8_t speed) {
switch (speed) {
case kDelonghiAcFanHigh: return stdAc::fanspeed_t::kMax;
case kDelonghiAcFanMedium: return stdAc::fanspeed_t::kMedium;
case kDelonghiAcFanLow: return stdAc::fanspeed_t::kMin;
default: return stdAc::fanspeed_t::kAuto;
}
}
/// Get the operating mode setting of the A/C.
/// @return The current operating mode setting.
uint8_t IRDelonghiAc::getMode(void) const {
return _.Mode;
}
/// Set the operating mode of the A/C.
/// @param[in] mode The desired native operating mode.
void IRDelonghiAc::setMode(const uint8_t mode) {
_.Mode = mode;
switch (mode) {
case kDelonghiAcAuto:
case kDelonghiAcDry:
// Set special temp for these modes.
setTemp(kDelonghiAcTempAutoDryMode, _.Fahrenheit, true);
break;
case kDelonghiAcFan:
// Set special temp for this mode.
setTemp(kDelonghiAcTempFanMode, _.Fahrenheit, true);
break;
case kDelonghiAcCool:
// Restore previous temp settings for cool mode.
setTemp(_saved_temp, _saved_temp_units);
break;
default:
_.Mode = kDelonghiAcAuto;
setTemp(kDelonghiAcTempAutoDryMode, _.Fahrenheit, true);
break;
}
setFan(_.Fan); // Re-force any fan speed constraints.
}
/// Convert a stdAc::opmode_t enum into its native mode.
/// @param[in] mode The enum to be converted.
/// @return The native equivalent of the enum.
uint8_t IRDelonghiAc::convertMode(const stdAc::opmode_t mode) {
switch (mode) {
case stdAc::opmode_t::kCool:
return kDelonghiAcCool;
case stdAc::opmode_t::kDry:
return kDelonghiAcDry;
case stdAc::opmode_t::kFan:
return kDelonghiAcFan;
default:
return kDelonghiAcAuto;
}
}
/// Convert a native mode into its stdAc equivalent.
/// @param[in] mode The native setting to be converted.
/// @return The stdAc equivalent of the native setting.
stdAc::opmode_t IRDelonghiAc::toCommonMode(const uint8_t mode) {
switch (mode) {
case kDelonghiAcCool: return stdAc::opmode_t::kCool;
case kDelonghiAcDry: return stdAc::opmode_t::kDry;
case kDelonghiAcFan: return stdAc::opmode_t::kFan;
default: return stdAc::opmode_t::kAuto;
}
}
/// Set the Boost (Turbo) mode of the A/C.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRDelonghiAc::setBoost(const bool on) {
_.Boost = on;
}
/// Get the Boost (Turbo) mode of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRDelonghiAc::getBoost(void) const {
return _.Boost;
}
/// Set the Sleep mode of the A/C.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRDelonghiAc::setSleep(const bool on) {
_.Sleep = on;
}
/// Get the Sleep mode status of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRDelonghiAc::getSleep(void) const {
return _.Sleep;
}
/// Set the enable status of the On Timer.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRDelonghiAc::setOnTimerEnabled(const bool on) {
_.OnTimer = on;
}
/// Get the enable status of the On Timer.
/// @return true, the setting is on. false, the setting is off.
bool IRDelonghiAc::getOnTimerEnabled(void) const {
return _.OnTimer;
}
/// Set the On timer to activate in nr of minutes.
/// @param[in] nr_of_mins Total nr of mins to wait before waking the device.
/// @note Max 23 hrs and 59 minutes. i.e. 1439 mins.
void IRDelonghiAc::setOnTimer(const uint16_t nr_of_mins) {
uint16_t value = std::min(kDelonghiAcTimerMax, nr_of_mins);
_.OnMins = value % 60;
_.OnHours = value / 60;
// Enable or not?
setOnTimerEnabled(value > 0);
}
/// Get the On timer time.
/// @return Total nr of mins before the device turns on.
uint16_t IRDelonghiAc::getOnTimer(void) const {
return _.OnHours * 60 + _.OnMins;
}
/// Set the enable status of the Off Timer.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRDelonghiAc::setOffTimerEnabled(const bool on) {
_.OffTimer = on;
}
/// Get the enable status of the Off Timer.
/// @return true, the setting is on. false, the setting is off.
bool IRDelonghiAc::getOffTimerEnabled(void) const {
return _.OffTimer;
}
/// Set the Off timer to activate in nr of minutes.
/// @param[in] nr_of_mins Total nr of mins to wait before turning off the device
/// @note Max 23 hrs and 59 minutes. i.e. 1439 mins.
void IRDelonghiAc::setOffTimer(const uint16_t nr_of_mins) {
uint16_t value = std::min(kDelonghiAcTimerMax, nr_of_mins);
_.OffMins = value % 60;
_.OffHours = value / 60;
// Enable or not?
setOffTimerEnabled(value > 0);
}
/// Get the Off timer time.
/// @return Total nr of mins before the device turns off.
uint16_t IRDelonghiAc::getOffTimer(void) const {
return _.OffHours * 60 + _.OffMins;
}
/// Convert the current internal state into its stdAc::state_t equivalent.
/// @return The stdAc equivalent of the native settings.
stdAc::state_t IRDelonghiAc::toCommon(void) const {
stdAc::state_t result{};
result.protocol = decode_type_t::DELONGHI_AC;
result.power = _.Power;
// result.mode = toCommonMode(getMode());
result.celsius = !_.Fahrenheit;
result.degrees = getTemp();
result.fanspeed = toCommonFanSpeed(_.Fan);
result.turbo = _.Boost;
result.sleep = _.Sleep ? 0 : -1;
// Not supported.
result.model = -1;
result.swingv = stdAc::swingv_t::kOff;
result.swingh = stdAc::swingh_t::kOff;
result.light = false;
result.filter = false;
result.econo = false;
result.quiet = false;
result.clean = 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 IRDelonghiAc::toString(void) const {
String result = "";
result.reserve(80); // Reserve some heap for the string to reduce fragging.
result += addBoolToString(_.Power, kPowerStr, false);
result += addModeToString(_.Mode, kDelonghiAcAuto, kDelonghiAcCool,
kDelonghiAcAuto, kDelonghiAcDry, kDelonghiAcFan);
result += addFanToString(_.Fan, kDelonghiAcFanHigh, kDelonghiAcFanLow,
kDelonghiAcFanAuto, kDelonghiAcFanAuto,
kDelonghiAcFanMedium);
result += addTempToString(getTemp(), !_.Fahrenheit);
result += addBoolToString(_.Boost, kTurboStr);
result += addBoolToString(_.Sleep, kSleepStr);
uint16_t mins = getOnTimer();
result += addLabeledString((mins && _.OnTimer) ? minsToString(mins)
: kOffStr,
kOnTimerStr);
mins = getOffTimer();
result += addLabeledString((mins && _.OffTimer) ? minsToString(mins)
: kOffStr,
kOffTimerStr);
return result;
}

View File

@@ -0,0 +1,136 @@
// Copyright 2020 David Conran
/// @file
/// @brief Delonghi A/C
/// @note Kudos to TheMaxxz For the breakdown and mapping of the bit values.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1096
// Supports:
// Brand: Delonghi, Model: PAC A95
#ifndef IR_DELONGHI_H_
#define IR_DELONGHI_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
/// Native representation of a Delonghi A/C message.
union DelonghiProtocol{
uint64_t raw; ///< The state of the IR remote.
struct {
uint8_t :8; // Header
uint8_t Temp :5;
uint8_t Fan :2;
uint8_t Fahrenheit:1;
uint8_t Power :1;
uint8_t Mode :3;
uint8_t Boost :1;
uint8_t Sleep :1;
uint8_t :2;
uint8_t OnTimer :1;
uint8_t OnHours :5;
uint8_t :2;
uint8_t OnMins :6;
uint8_t :2;
uint8_t OffTimer :1;
uint8_t OffHours :5;
uint8_t :2;
uint8_t OffMins :6;
uint8_t :2;
uint8_t Sum :8;
};
};
// Constants
const uint8_t kDelonghiAcTempMinC = 18; // Deg C
const uint8_t kDelonghiAcTempMaxC = 32; // Deg C
const uint8_t kDelonghiAcTempMinF = 64; // Deg F
const uint8_t kDelonghiAcTempMaxF = 90; // Deg F
const uint8_t kDelonghiAcTempAutoDryMode = 0;
const uint8_t kDelonghiAcTempFanMode = 0b00110;
const uint8_t kDelonghiAcFanAuto = 0b00;
const uint8_t kDelonghiAcFanHigh = 0b01;
const uint8_t kDelonghiAcFanMedium = 0b10;
const uint8_t kDelonghiAcFanLow = 0b11;
const uint8_t kDelonghiAcCool = 0b000;
const uint8_t kDelonghiAcDry = 0b001;
const uint8_t kDelonghiAcFan = 0b010;
const uint8_t kDelonghiAcAuto = 0b100;
const uint16_t kDelonghiAcTimerMax = 23 * 60 + 59;
const uint8_t kDelonghiAcChecksumOffset = 56;
// Classes
/// Class for handling detailed Delonghi A/C messages.
class IRDelonghiAc {
public:
explicit IRDelonghiAc(const uint16_t pin, const bool inverted = false,
const bool use_modulation = true);
void stateReset(void);
#if SEND_DELONGHI_AC
void send(const uint16_t repeat = kDelonghiAcDefaultRepeat);
/// 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_DELONGHI_AC
void begin(void);
static uint8_t calcChecksum(const uint64_t state);
static bool validChecksum(const uint64_t state);
void setPower(const bool on);
bool getPower(void) const;
void on(void);
void off(void);
void setTempUnit(const bool celsius);
bool getTempUnit(void) const;
void setTemp(const uint8_t temp, const bool fahrenheit = false,
const bool force = false);
uint8_t getTemp(void) const;
void setFan(const uint8_t speed);
uint8_t getFan(void) const;
void setMode(const uint8_t mode);
uint8_t getMode(void) const;
void setBoost(const bool on); // Aka Turbo
bool getBoost(void) const; // Aka Turbo
void setSleep(const bool on);
bool getSleep(void) const;
void setOnTimerEnabled(const bool on);
bool getOnTimerEnabled(void) const;
void setOnTimer(const uint16_t nr_of_mins);
uint16_t getOnTimer(void) const;
void setOffTimerEnabled(const bool on);
bool getOffTimerEnabled(void) const;
void setOffTimer(const uint16_t nr_of_mins);
uint16_t getOffTimer(void) const;
uint64_t getRaw(void);
void setRaw(const uint64_t state);
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);
static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed);
stdAc::state_t toCommon(void) const;
String toString(void) const;
#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
DelonghiProtocol _;
uint8_t _saved_temp; ///< The previously user requested temp value.
uint8_t _saved_temp_units; ///< The previously user requested temp units.
void checksum(void);
};
#endif // IR_DELONGHI_H_

View File

@@ -0,0 +1,122 @@
// Copyright 2016 Massimiliano Pinto
// Copyright 2017 David Conran
/// @file
/// @brief Denon support
/// Original Denon support added by https://github.com/csBlueChip
/// Ported over by Massimiliano Pinto
/// @see https://github.com/z3t0/Arduino-IRremote/blob/master/ir_Denon.cpp
/// @see http://assets.denon.com/documentmaster/us/denon%20master%20ir%20hex.xls
// Supports:
// Brand: Denon, Model: AVR-3801 A/V Receiver (probably)
#include <algorithm>
#include "IRrecv.h"
#include "IRsend.h"
#include "IRutils.h"
// Constants
const uint16_t kDenonTick = 263;
const uint16_t kDenonHdrMarkTicks = 1;
const uint16_t kDenonHdrMark = kDenonHdrMarkTicks * kDenonTick;
const uint16_t kDenonHdrSpaceTicks = 3;
const uint16_t kDenonHdrSpace = kDenonHdrSpaceTicks * kDenonTick;
const uint16_t kDenonBitMarkTicks = 1;
const uint16_t kDenonBitMark = kDenonBitMarkTicks * kDenonTick;
const uint16_t kDenonOneSpaceTicks = 7;
const uint16_t kDenonOneSpace = kDenonOneSpaceTicks * kDenonTick;
const uint16_t kDenonZeroSpaceTicks = 3;
const uint16_t kDenonZeroSpace = kDenonZeroSpaceTicks * kDenonTick;
const uint16_t kDenonMinCommandLengthTicks = 510;
const uint16_t kDenonMinGapTicks =
kDenonMinCommandLengthTicks -
(kDenonHdrMarkTicks + kDenonHdrSpaceTicks +
kDenonBits * (kDenonBitMarkTicks + kDenonOneSpaceTicks) +
kDenonBitMarkTicks);
const uint32_t kDenonMinGap = kDenonMinGapTicks * kDenonTick;
const uint64_t kDenonManufacturer = 0x2A4CULL;
#if SEND_DENON
/// Send a Denon formatted message.
/// Status: STABLE / Should be working.
/// @param[in] data The message to be sent.
/// @param[in] nbits The number of bits of message to be sent.
/// @param[in] repeat The number of times the command is to be repeated.
/// @note Some Denon devices use a Kaseikyo/Panasonic 48-bit format
/// Others use the Sharp protocol.
void IRsend::sendDenon(uint64_t data, uint16_t nbits, uint16_t repeat) {
if (nbits >= kPanasonicBits) // Is this really Panasonic?
sendPanasonic64(data, nbits, repeat);
else if (nbits == kDenonLegacyBits)
// Support legacy (broken) calls of sendDenon().
sendSharpRaw(data & (~0x2000ULL), nbits + 1, repeat);
else
sendSharpRaw(data, nbits, repeat);
}
#endif
#if DECODE_DENON
/// Decode the supplied Delonghi A/C message.
/// Status: STABLE / Should work fine.
/// @param[in,out] results Ptr to the data to decode & where to store the decode
/// result.
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// @param[in] strict Flag indicating if we should perform strict matching.
/// @return A boolean. True if it can decode it, false if it can't.
/// @see https://github.com/z3t0/Arduino-IRremote/blob/master/ir_Denon.cpp
bool IRrecv::decodeDenon(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
// Compliance
if (strict) {
switch (nbits) {
case kDenonBits:
case kDenon48Bits:
case kDenonLegacyBits:
break;
default:
return false;
}
}
// Denon uses the Sharp & Panasonic(Kaseikyo) protocol for some
// devices, so check for those first.
// It is not exactly like Sharp's protocols, but close enough.
// e.g. The expansion bit is not set for Denon vs. set for Sharp.
// Ditto for Panasonic, it's the same except for a different
// manufacturer code.
if (!decodeSharp(results, offset, nbits, true, false) &&
!decodePanasonic(results, offset, nbits, true, kDenonManufacturer)) {
// We couldn't decode it as expected, so try the old legacy method.
// NOTE: I don't think this following protocol actually exists.
// Looks like a partial version of the Sharp protocol.
if (strict && nbits != kDenonLegacyBits) return false;
uint64_t data = 0;
// Match Header + Data + Footer
if (!matchGeneric(results->rawbuf + offset, &data,
results->rawlen - offset, nbits,
kDenonHdrMark, kDenonHdrSpace,
kDenonBitMark, kDenonOneSpace,
kDenonBitMark, kDenonZeroSpace,
kDenonBitMark, 0, false)) return false;
// Success
results->bits = nbits;
results->value = data;
results->address = 0;
results->command = 0;
} // Legacy decode.
// Compliance
if (strict && nbits != results->bits) return false;
// Success
results->decode_type = DENON;
return true;
}
#endif

View File

@@ -0,0 +1,103 @@
// Copyright Todd Treece
// Copyright 2017 David Conran
/// @file
/// @brief DISH Network protocol support
/// DISH support originally by Todd Treece
/// @see http://unionbridge.org/design/ircommand
/// @see https://github.com/marcosamarinho/IRremoteESP8266/blob/master/ir_Dish.cpp
/// @see http://www.hifi-remote.com/wiki/index.php?title=Dish
// Supports:
// Brand: DISH NETWORK, Model: echostar 301
#include "IRrecv.h"
#include "IRsend.h"
#include "IRutils.h"
// Constants
const uint16_t kDishTick = 100;
const uint16_t kDishHdrMarkTicks = 4;
const uint16_t kDishHdrMark = kDishHdrMarkTicks * kDishTick;
const uint16_t kDishHdrSpaceTicks = 61;
const uint16_t kDishHdrSpace = kDishHdrSpaceTicks * kDishTick;
const uint16_t kDishBitMarkTicks = 4;
const uint16_t kDishBitMark = kDishBitMarkTicks * kDishTick;
const uint16_t kDishOneSpaceTicks = 17;
const uint16_t kDishOneSpace = kDishOneSpaceTicks * kDishTick;
const uint16_t kDishZeroSpaceTicks = 28;
const uint16_t kDishZeroSpace = kDishZeroSpaceTicks * kDishTick;
const uint16_t kDishRptSpaceTicks = kDishHdrSpaceTicks;
const uint16_t kDishRptSpace = kDishRptSpaceTicks * kDishTick;
#if SEND_DISH
/// Send a DISH NETWORK formatted message.
/// Status: STABLE / Working.
/// @param[in] data The message to be sent.
/// @param[in] nbits The number of bits of message to be sent.
/// @param[in] repeat The number of times the command is to be repeated.
/// @note Dishplayer is a different protocol.
/// Typically a DISH device needs to get a command a total of at least 4
/// times to accept it. e.g. repeat=3
///
/// Here is the LIRC file I found that seems to match the remote codes from the
/// oscilloscope:
/// DISH NETWORK (echostar 301):
/// @see http://lirc.sourceforge.net/remotes/echostar/301_501_3100_5100_58xx_59xx
/// @see http://www.hifi-remote.com/wiki/index.php?title=Dish
void IRsend::sendDISH(uint64_t data, uint16_t nbits, uint16_t repeat) {
enableIROut(57600); // Set modulation freq. to 57.6kHz.
// Header is only ever sent once.
mark(kDishHdrMark);
space(kDishHdrSpace);
sendGeneric(0, 0, // No headers from here on in.
kDishBitMark, kDishOneSpace, kDishBitMark, kDishZeroSpace,
kDishBitMark, kDishRptSpace, data, nbits, 57600, true, repeat,
50);
}
#endif
#if DECODE_DISH
/// Decode the supplied DISH NETWORK message.
/// Status: ALPHA (untested and unconfirmed.)
/// @param[in,out] results Ptr to the data to decode & where to store the decode
/// result.
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// @param[in] strict Flag indicating if we should perform strict matching.
/// @return A boolean. True if it can decode it, false if it can't.
/// @note Dishplayer is a different protocol.
/// Typically a DISH device needs to get a command a total of at least 4
/// times to accept it.
/// @see http://www.hifi-remote.com/wiki/index.php?title=Dish
/// @see http://lirc.sourceforge.net/remotes/echostar/301_501_3100_5100_58xx_59xx
/// @see https://github.com/marcosamarinho/IRremoteESP8266/blob/master/ir_Dish.cpp
bool IRrecv::decodeDISH(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
if (strict && nbits != kDishBits) return false; // Not strictly compliant.
uint64_t data = 0;
// Match Header + Data + Footer
if (!matchGeneric(results->rawbuf + offset, &data,
results->rawlen - offset, nbits,
kDishHdrMark, kDishHdrSpace,
kDishBitMark, kDishOneSpace,
kDishBitMark, kDishZeroSpace,
kDishBitMark,
// The DISH protocol calls for a repeated message, so
// strictly speaking there should be a code following this.
// Only require it if we are set to strict matching.
strict ? kDishRptSpace : 0, false)) return false;
// Success
results->decode_type = DISH;
results->bits = nbits;
results->value = data;
results->address = 0;
results->command = 0;
return true;
}
#endif

View File

@@ -0,0 +1,124 @@
// Copyright 2020 Christian (nikize)
/// @file
/// @brief Doshisha protocol support
/// @see https://www.doshisha-led.com/
// Supports:
// Brand: Doshisha, Model: CZ-S32D LED Light
// Brand: Doshisha, Model: CZ-S38D LED Light
// Brand: Doshisha, Model: CZ-S50D LED Light
// Brand: Doshisha, Model: RCZ01 remote
#include "IRrecv.h"
#include "IRsend.h"
#include "IRutils.h"
const uint16_t kDoshishaHdrMark = 3412;
const uint16_t kDoshishaHdrSpace = 1722;
const uint16_t kDoshishaBitMark = 420;
const uint16_t kDoshishaOneSpace = 1310;
const uint16_t kDoshishaZeroSpace = 452;
// basic structure of bits, and mask
const uint64_t kRcz01SignatureMask = 0xffffffff00;
const uint64_t kRcz01Signature = 0x800B304800;
const uint8_t kRcz01CommandMask = 0xFE;
const uint8_t kRcz01ChannelMask = 0x01;
// Known commands - Here for documentation rather than actual usage
const uint8_t kRcz01CommandSwitchChannel = 0xD2;
const uint8_t kRcz01CommandTimmer60 = 0x52;
const uint8_t kRcz01CommandTimmer30 = 0x92;
const uint8_t kRcz01CommandOff = 0xA0;
const uint8_t kRcz01CommandLevelDown = 0x2C;
const uint8_t kRcz01CommandLevelUp = 0xCC;
// below are the only ones that turns it on
const uint8_t kRcz01CommandLevel1 = 0xA4;
const uint8_t kRcz01CommandLevel2 = 0x24;
const uint8_t kRcz01CommandLevel3 = 0xC4;
const uint8_t kRcz01CommandLevel4 = 0xD0;
const uint8_t kRcz01CommandOn = 0xC0;
const uint8_t kRcz01CommandNightLight = 0xC8;
// end Known commands
#if SEND_DOSHISHA
/// Send a Doshisha formatted message.
/// Status: STABLE / Works on real device.
/// @param[in] data The message to be sent.
/// @param[in] nbits The number of bits of message to be sent.
/// @param[in] repeat The number of times the command is to be repeated.
void IRsend::sendDoshisha(const uint64_t data, const uint16_t nbits,
const uint16_t repeat) {
sendGeneric(kDoshishaHdrMark, kDoshishaHdrSpace,
kDoshishaBitMark, kDoshishaOneSpace,
kDoshishaBitMark, kDoshishaZeroSpace,
kDoshishaBitMark, kDefaultMessageGap,
data, nbits, 38, true, repeat, kDutyDefault);
}
/// Encode Doshisha combining constant values with command and channel.
/// Status: STABLE / Working.
/// @param[in] command The command code to be sent.
/// @param[in] channel The one bit channel 0 for CH1 and 1 for CH2
/// @return The corresponding Doshisha code.
uint64_t IRsend::encodeDoshisha(const uint8_t command, const uint8_t channel) {
uint64_t data = kRcz01Signature |
(command & kRcz01CommandMask) |
(channel & kRcz01ChannelMask);
return data;
}
#endif // SEND_DOSHISHA
#if DECODE_DOSHISHA
/// Decode the supplied Doshisha message.
/// Status: STABLE / Works on real device.
/// @param[in,out] results Ptr to the data to decode & where to store the decode
/// result.
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// @param[in] strict Flag indicating if we should perform strict matching.
/// @return A boolean. True if it can decode it, false if it can't.
bool IRrecv::decodeDoshisha(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
if (results->rawlen < 2 * nbits + kHeader + kFooter - 1 + offset)
return false; // Can't possibly be a valid message.
if (strict && nbits != kDoshishaBits)
return false;
uint64_t data = 0;
// Match Header + Data
if (!matchGeneric(results->rawbuf + offset, &data,
results->rawlen - offset, nbits,
kDoshishaHdrMark, kDoshishaHdrSpace,
kDoshishaBitMark, kDoshishaOneSpace,
kDoshishaBitMark, kDoshishaZeroSpace,
kDoshishaBitMark, 0,
true, kTolerance, kMarkExcess, true)) return false;
// e.g. data = 0x800B3048C0, nbits = 40
// RCZ01 remote commands starts with a lead bit set
if ((data & kRcz01SignatureMask) != kRcz01Signature) {
DPRINT(" decodeDoshisha data ");
DPRINT(uint64ToString(data, 16));
DPRINT(" masked ");
DPRINT(uint64ToString(data & kRcz01SignatureMask, 16));
DPRINT(" not matching ");
DPRINT(uint64ToString(kRcz01Signature, 16));
DPRINTLN(" .");
return false; // expected lead bits not matching
}
// Success
results->decode_type = decode_type_t::DOSHISHA;
results->bits = nbits;
results->value = data;
results->command = data & kRcz01CommandMask;
results->address = data & kRcz01ChannelMask;
return true;
}
#endif // DECODE_DOSHISHA

View File

@@ -0,0 +1,424 @@
// Copyright 2021 David Conran
/// @file
/// @brief EcoClim A/C protocol.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1397
#include "ir_Ecoclim.h"
#include <algorithm>
#include <cstring>
#include "IRac.h"
#include "IRrecv.h"
#include "IRsend.h"
#include "IRtext.h"
#include "IRutils.h"
// Constants
const uint8_t kEcoclimSections = 3;
const uint8_t kEcoclimExtraTolerance = 5; ///< Percentage (extra)
const uint16_t kEcoclimHdrMark = 5730; ///< uSeconds
const uint16_t kEcoclimHdrSpace = 1935; ///< uSeconds
const uint16_t kEcoclimBitMark = 440; ///< uSeconds
const uint16_t kEcoclimOneSpace = 1739; ///< uSeconds
const uint16_t kEcoclimZeroSpace = 637; ///< uSeconds
const uint16_t kEcoclimFooterMark = 7820; ///< uSeconds
const uint32_t kEcoclimGap = kDefaultMessageGap; // Just a guess.
using irutils::addBoolToString;
using irutils::addFanToString;
using irutils::addIntToString;
using irutils::addLabeledString;
using irutils::addModeToString;
using irutils::addTempToString;
using irutils::minsToString;
#if SEND_ECOCLIM
/// Send a EcoClim A/C formatted message.
/// Status: STABLE / Confirmed working on real device.
/// @param[in] data The message to be sent.
/// @param[in] nbits The number of bits of message to be sent.
/// @param[in] repeat The number of times the command is to be repeated.
void IRsend::sendEcoclim(const uint64_t data, const uint16_t nbits,
const uint16_t repeat) {
enableIROut(38, kDutyDefault);
for (uint16_t r = 0; r <= repeat; r++) {
for (uint8_t section = 0; section < kEcoclimSections; section++) {
// Header + Data
sendGeneric(kEcoclimHdrMark, kEcoclimHdrSpace,
kEcoclimBitMark, kEcoclimOneSpace,
kEcoclimBitMark, kEcoclimZeroSpace,
0, 0, data, nbits, 38, true, 0, kDutyDefault);
}
mark(kEcoclimFooterMark);
space(kEcoclimGap);
}
}
#endif // SEND_ECOCLIM
#if DECODE_ECOCLIM
/// Decode the supplied EcoClim A/C message.
/// Status: STABLE / Confirmed working on real remote.
/// @param[in,out] results Ptr to the data to decode & where to store the decode
/// result.
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// @param[in] strict Flag indicating if we should perform strict matching.
/// @return A boolean. True if it can decode it, false if it can't.
bool IRrecv::decodeEcoclim(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
if (results->rawlen < (2 * nbits + kHeader) * kEcoclimSections +
kFooter - 1 + offset)
return false; // Can't possibly be a valid Ecoclim message.
if (strict) {
switch (nbits) {
case kEcoclimShortBits:
case kEcoclimBits:
break;
default:
return false; // Unexpected bit size.
}
}
for (uint8_t section = 0; section < kEcoclimSections; section++) {
uint16_t used;
uint64_t data;
// Header + Data Block
used = matchGeneric(results->rawbuf + offset, &data,
results->rawlen - offset, nbits,
kEcoclimHdrMark, kEcoclimHdrSpace,
kEcoclimBitMark, kEcoclimOneSpace,
kEcoclimBitMark, kEcoclimZeroSpace,
0, 0, // No footer.
false, _tolerance + kEcoclimExtraTolerance);
if (!used) return false;
DPRINTLN("DEBUG: Data section matched okay.");
offset += used;
// Compliance
if (strict) {
if (section) { // Each section should contain the same data.
if (data != results->value) return false;
} else {
results->value = data;
}
}
}
// Footer
if (!matchMark(results->rawbuf[offset++], kEcoclimFooterMark,
_tolerance + kEcoclimExtraTolerance))
return false;
if (results->rawlen <= offset && !matchAtLeast(results->rawbuf[offset++],
kEcoclimGap))
return false;
// Success
results->bits = nbits;
results->decode_type = ECOCLIM;
// No need to record the value as we stored it as we decoded it.
return true;
}
#endif // DECODE_ECOCLIM
/// 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?
IREcoclimAc::IREcoclimAc(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 IREcoclimAc::stateReset(void) { _.raw = kEcoclimDefaultState; }
/// Set up hardware to be able to send a message.
void IREcoclimAc::begin(void) { _irsend.begin(); }
#if SEND_ECOCLIM
/// Send the current internal state as an IR message.
/// @param[in] repeat Nr. of times the message will be repeated.
void IREcoclimAc::send(const uint16_t repeat) {
_irsend.sendEcoclim(getRaw(), kEcoclimBits, repeat);
}
#endif // SEND_ECOCLIM
/// Get a copy of the internal state as a valid code for this protocol.
/// @return A valid code for this protocol based on the current internal state.
uint64_t IREcoclimAc::getRaw(void) const { return _.raw; }
/// Set the internal state from a valid code for this protocol.
/// @param[in] new_code A valid code for this protocol.
void IREcoclimAc::setRaw(const uint64_t new_code) { _.raw = new_code; }
/// Set the temperature.
/// @param[in] celsius The temperature in degrees celsius.
void IREcoclimAc::setTemp(const uint8_t celsius) {
// Range check.
uint8_t temp = std::min(celsius, kEcoclimTempMax);
temp = std::max(temp, kEcoclimTempMin);
_.Temp = temp - kEcoclimTempMin;
}
/// Get the current temperature setting.
/// @return The current setting for temp. in degrees celsius.
uint8_t IREcoclimAc::getTemp(void) const { return _.Temp + kEcoclimTempMin; }
/// Set the sensor temperature.
/// @param[in] celsius The temperature in degrees celsius.
void IREcoclimAc::setSensorTemp(const uint8_t celsius) {
// Range check.
uint8_t temp = std::min(celsius, kEcoclimTempMax);
temp = std::max(temp, kEcoclimTempMin);
_.SensorTemp = temp - kEcoclimTempMin;
}
/// Get the sensor temperature setting.
/// @return The current setting for sensor temp. in degrees celsius.
uint8_t IREcoclimAc::getSensorTemp(void) const {
return _.SensorTemp + kEcoclimTempMin;
}
/// Get the value of the current power setting.
/// @return true, the setting is on. false, the setting is off.
bool IREcoclimAc::getPower(void) const { return _.Power; }
/// Change the power setting.
/// @param[in] on true, the setting is on. false, the setting is off.
void IREcoclimAc::setPower(const bool on) { _.Power = on; }
/// Change the power setting to On.
void IREcoclimAc::on(void) { setPower(true); }
/// Change the power setting to Off.
void IREcoclimAc::off(void) { setPower(false); }
/// Get the current fan speed setting.
/// @return The current fan speed.
uint8_t IREcoclimAc::getFan(void) const { return _.Fan; }
/// Set the speed of the fan.
/// @param[in] speed The desired setting.
void IREcoclimAc::setFan(const uint8_t speed) {
_.Fan = std::min(speed, kEcoclimFanAuto);
}
/// Convert a stdAc::fanspeed_t enum into it's native speed.
/// @param[in] speed The enum to be converted.
/// @return The native equivalent of the enum.
uint8_t IREcoclimAc::convertFan(const stdAc::fanspeed_t speed) {
switch (speed) {
case stdAc::fanspeed_t::kMin:
case stdAc::fanspeed_t::kLow: return kEcoclimFanMin;
case stdAc::fanspeed_t::kMedium: return kEcoclimFanMed;
case stdAc::fanspeed_t::kHigh:
case stdAc::fanspeed_t::kMax: return kEcoclimFanMax;
default: return kCoolixFanAuto;
}
}
/// Convert a native fan speed into its stdAc equivalent.
/// @param[in] speed The native setting to be converted.
/// @return The stdAc equivalent of the native setting.
stdAc::fanspeed_t IREcoclimAc::toCommonFanSpeed(const uint8_t speed) {
switch (speed) {
case kEcoclimFanMax: return stdAc::fanspeed_t::kMax;
case kEcoclimFanMed: return stdAc::fanspeed_t::kMedium;
case kEcoclimFanMin: return stdAc::fanspeed_t::kMin;
default: return stdAc::fanspeed_t::kAuto;
}
}
/// Get the operating mode setting of the A/C.
/// @return The current operating mode setting.
uint8_t IREcoclimAc::getMode(void) const { return _.Mode; }
/// Set the operating mode of the A/C.
/// @param[in] mode The desired operating mode.
void IREcoclimAc::setMode(const uint8_t mode) {
switch (mode) {
case kEcoclimAuto:
case kEcoclimCool:
case kEcoclimDry:
case kEcoclimRecycle:
case kEcoclimFan:
case kEcoclimHeat:
case kEcoclimSleep:
_.Mode = mode;
break;
default: // Anything else, go with Auto mode.
setMode(kEcoclimAuto);
}
}
/// Convert a standard A/C mode into its native mode.
/// @param[in] mode A stdAc::opmode_t to be converted to it's native equivalent.
/// @return The corresponding native mode.
uint8_t IREcoclimAc::convertMode(const stdAc::opmode_t mode) {
switch (mode) {
case stdAc::opmode_t::kCool: return kEcoclimCool;
case stdAc::opmode_t::kHeat: return kEcoclimHeat;
case stdAc::opmode_t::kDry: return kEcoclimDry;
case stdAc::opmode_t::kFan: return kEcoclimFan;
default: return kEcoclimAuto;
}
}
/// Convert a native mode to it's common stdAc::opmode_t equivalent.
/// @param[in] mode A native operation mode to be converted.
/// @return The corresponding common stdAc::opmode_t mode.
stdAc::opmode_t IREcoclimAc::toCommonMode(const uint8_t mode) {
switch (mode) {
case kEcoclimCool: return stdAc::opmode_t::kCool;
case kEcoclimHeat: return stdAc::opmode_t::kHeat;
case kEcoclimDry: return stdAc::opmode_t::kDry;
case kEcoclimFan: return stdAc::opmode_t::kFan;
default: return stdAc::opmode_t::kAuto;
}
}
/// Get the clock time of the A/C unit.
/// @return Nr. of minutes past midnight.
uint16_t IREcoclimAc::getClock(void) const { return _.Clock; }
/// Set the clock time on the A/C unit.
/// @param[in] nr_of_mins Nr. of minutes past midnight.
void IREcoclimAc::setClock(const uint16_t nr_of_mins) {
_.Clock = std::min(nr_of_mins, (uint16_t)(24 * 60 - 1));
}
/// Get the Unit type/DIP switch settings of the remote.
/// @return The binary representation of the 4 DIP switches on the remote.
uint8_t IREcoclimAc::getType(void) const { return _.DipConfig; }
/// Set the Unit type/DIP switch settings for the remote.
/// @param[in] code The binary representation of the remote's 4 DIP switches.
void IREcoclimAc::setType(const uint8_t code) {
switch (code) {
case kEcoclimDipMaster:
case kEcoclimDipSlave:
_.DipConfig = code;
break;
default:
setType(kEcoclimDipMaster);
}
}
/// Set & enable the On Timer for the A/C.
/// @param[in] nr_of_mins The time, in minutes since midnight.
void IREcoclimAc::setOnTimer(const uint16_t nr_of_mins) {
if (nr_of_mins < 24 * 60) {
_.OnHours = nr_of_mins / 60;
_.OnTenMins = (nr_of_mins % 60) / 10; // Store in tens of mins resolution.
}
}
/// Get the On Timer for the A/C.
/// @return The On Time, in minutes since midnight.
uint16_t IREcoclimAc::getOnTimer(void) const {
return _.OnHours * 60 + _.OnTenMins * 10;
}
/// Check if the On Timer is enabled.
/// @return true, if the timer is enabled, otherwise false.
bool IREcoclimAc::isOnTimerEnabled(void) const {
return (getOnTimer() != kEcoclimTimerDisable);
}
/// Disable & clear the On Timer.
void IREcoclimAc::disableOnTimer(void) {
_.OnHours = 0x1F;
_.OnTenMins = 0x7;
}
/// Set & enable the Off Timer for the A/C.
/// @param[in] nr_of_mins The time, in minutes since midnight.
void IREcoclimAc::setOffTimer(const uint16_t nr_of_mins) {
if (nr_of_mins < 24 * 60) {
_.OffHours = nr_of_mins / 60;
_.OffTenMins = (nr_of_mins % 60) / 10; // Store in tens of mins resolution.
}
}
/// Get the Off Timer for the A/C.
/// @return The Off Time, in minutes since midnight.
uint16_t IREcoclimAc::getOffTimer(void) const {
return _.OffHours * 60 + _.OffTenMins * 10;
}
/// Check if the Off Timer is enabled.
/// @return true, if the timer is enabled, otherwise false.
bool IREcoclimAc::isOffTimerEnabled(void) const {
return (getOffTimer() != kEcoclimTimerDisable);
}
/// Disable & clear the Off Timer.
void IREcoclimAc::disableOffTimer(void) {
_.OffHours = 0x1F;
_.OffTenMins = 0x7;
}
/// Convert the current internal state into its stdAc::state_t equivalent.
/// @return The stdAc equivalent of the native settings.
stdAc::state_t IREcoclimAc::toCommon(void) const {
stdAc::state_t result{};
result.protocol = decode_type_t::ECOCLIM;
result.power = _.Power;
result.mode = toCommonMode(getMode());
result.celsius = true;
result.degrees = getTemp();
result.sensorTemperature = getSensorTemp();
result.fanspeed = toCommonFanSpeed(_.Fan);
result.sleep = (getMode() == kEcoclimSleep) ? 0 : -1;
result.clock = getClock();
// Not supported.
result.model = -1;
result.turbo = false;
result.swingv = stdAc::swingv_t::kOff;
result.swingh = stdAc::swingh_t::kOff;
result.light = false;
result.filter = false;
result.econo = false;
result.quiet = false;
result.clean = false;
result.beep = false;
return result;
}
/// Convert the internal state into a human readable string.
/// @return A string containing the settings in human-readable form.
String IREcoclimAc::toString(void) const {
String result = "";
result.reserve(140); // Reserve some heap for the string to reduce fragging.
result += addBoolToString(_.Power, kPowerStr, false);
// Custom Mode output as this protocol has Recycle and Sleep as modes.
result += addIntToString(_.Mode, kModeStr);
result += kSpaceLBraceStr;
switch (_.Mode) {
case kEcoclimAuto: result += kAutoStr; break;
case kEcoclimCool: result += kCoolStr; break;
case kEcoclimHeat: result += kHeatStr; break;
case kEcoclimDry: result += kDryStr; break;
case kEcoclimFan: result += kFanStr; break;
case kEcoclimRecycle: result += kRecycleStr; break;
case kEcoclimSleep: result += kSleepStr; break;
default: result += kUnknownStr;
}
result += ')';
result += addTempToString(getTemp());
result += kCommaSpaceStr;
result += kSensorStr;
result += addTempToString(getSensorTemp(), true, false);
result += addFanToString(_.Fan, kEcoclimFanMax,
kEcoclimFanMin,
kEcoclimFanAuto,
kEcoclimFanAuto, // Unused (No Quiet)
kEcoclimFanMed,
kEcoclimFanMax);
result += addLabeledString(minsToString(_.Clock), kClockStr);
result += addLabeledString(
isOnTimerEnabled() ? minsToString(getOnTimer()) : kOffStr, kOnTimerStr);
result += addLabeledString(
isOffTimerEnabled() ? minsToString(getOffTimer()) : kOffStr,
kOffTimerStr);
result += addIntToString(_.DipConfig, kTypeStr);
return result;
}

View File

@@ -0,0 +1,142 @@
// Copyright 2021 David Conran
/// @file
/// @brief EcoClim A/C protocol.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1397
// Supports:
// Brand: EcoClim, Model: HYSFR-P348 remote
// Brand: EcoClim, Model: ZC200DPO A/C
#ifndef IR_ECOCLIM_H_
#define IR_ECOCLIM_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
// Constants
// Modes
const uint8_t kEcoclimAuto = 0b000; ///< 0. a.k.a Slave
const uint8_t kEcoclimCool = 0b001; ///< 1
const uint8_t kEcoclimDry = 0b010; ///< 2
const uint8_t kEcoclimRecycle = 0b011; ///< 3
const uint8_t kEcoclimFan = 0b100; ///< 4
const uint8_t kEcoclimHeat = 0b101; ///< 5
const uint8_t kEcoclimSleep = 0b111; ///< 7
// Fan Control
const uint8_t kEcoclimFanMin = 0b00; ///< 0
const uint8_t kEcoclimFanMed = 0b01; ///< 1
const uint8_t kEcoclimFanMax = 0b10; ///< 2
const uint8_t kEcoclimFanAuto = 0b11; ///< 3
// DIP settings
const uint8_t kEcoclimDipMaster = 0b0000;
const uint8_t kEcoclimDipSlave = 0b0111;
// Temperature
const uint8_t kEcoclimTempMin = 5; // Celsius
const uint8_t kEcoclimTempMax = kEcoclimTempMin + 31; // Celsius
// Timer
const uint16_t kEcoclimTimerDisable = 0x1F * 60 + 7 * 10; // 4774
// Power: Off, Mode: Auto, Temp: 11C, Sensor: 22C, Fan: Auto, Clock: 00:00
const uint64_t kEcoclimDefaultState = 0x11063000FFFF02;
/// Native representation of a Ecoclim A/C message.
union EcoclimProtocol {
uint64_t raw; ///< The state in IR code form.
struct { // Only 56 bits (7 bytes are used.
// Byte
uint64_t :3; ///< Fixed 0b010
uint64_t :1; ///< Unknown
uint64_t DipConfig :4; ///< 0b0000 = Master, 0b0111 = Slave
// Byte
uint64_t OffTenMins :3; ///< Off Timer minutes (in tens of mins)
uint64_t OffHours :5; ///< Off Timer nr of Hours
// Byte
uint64_t OnTenMins :3; ///< On Timer minutes (in tens of mins)
uint64_t OnHours :5; ///< On Timer nr of Hours
// Byte+Byte
uint64_t Clock :11;
uint64_t :1; ///< Unknown
uint64_t Fan :2; ///< Fan Speed
uint64_t Power :1; ///< Power control
uint64_t Clear :1; // Not sure what this is
// Byte
uint64_t Temp :5; ///< Desired Temperature (Celsius)
uint64_t Mode :3; ///< Operating Mode
// Byte
uint64_t SensorTemp :5; ///< Sensed Temperature (Celsius)
uint64_t :3; ///< Fixed
};
};
// Classes
/// Class for handling detailed EcoClim A/C 56 bit messages.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1397
class IREcoclimAc {
public:
explicit IREcoclimAc(const uint16_t pin, const bool inverted = false,
const bool use_modulation = true);
void stateReset(void);
#if SEND_ECOCLIM
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_ECOCLIM
void begin(void);
void on(void);
void off(void);
void setPower(const bool on);
bool getPower(void) const;
void setTemp(const uint8_t celsius);
uint8_t getTemp(void) const;
void setSensorTemp(const uint8_t celsius);
uint8_t getSensorTemp(void) const;
void setFan(const uint8_t speed);
uint8_t getFan(void) const;
void setMode(const uint8_t mode);
uint8_t getMode(void) const;
void setClock(const uint16_t nr_of_mins);
uint16_t getClock(void) const;
uint64_t getRaw(void) const;
void setRaw(const uint64_t new_code);
void setType(const uint8_t code);
uint8_t getType(void) const;
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);
static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed);
stdAc::state_t toCommon(void) const;
void setOnTimer(const uint16_t nr_of_mins);
uint16_t getOnTimer(void) const;
bool isOnTimerEnabled(void) const;
void disableOnTimer(void);
void setOffTimer(const uint16_t nr_of_mins);
uint16_t getOffTimer(void) const;
bool isOffTimerEnabled(void) const;
void disableOffTimer(void);
String toString(void) const;
#ifndef UNIT_TEST
private:
IRsend _irsend; ///< Instance of the IR send class
#else // UNIT_TEST
/// @cond IGNORE
IRsendTest _irsend; ///< Instance of the testing IR send class
/// @endcond
#endif // UNIT_TEST
EcoclimProtocol _; ///< The state of the IR remote in IR code form.
};
#endif // IR_ECOCLIM_H_

View File

@@ -0,0 +1,457 @@
// Copyright 2018-2021 David Conran
/// @file
/// @brief Support for Electra A/C protocols.
/// @see https://github.com/ToniA/arduino-heatpumpir/blob/master/AUXHeatpumpIR.cpp
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/527
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/642
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/778
#include "ir_Electra.h"
#include <algorithm>
#include <cstring>
#include "IRrecv.h"
#include "IRsend.h"
#include "IRtext.h"
#include "IRutils.h"
// Constants
const uint16_t kElectraAcHdrMark = 9166;
const uint16_t kElectraAcBitMark = 646;
const uint16_t kElectraAcHdrSpace = 4470;
const uint16_t kElectraAcOneSpace = 1647;
const uint16_t kElectraAcZeroSpace = 547;
const uint32_t kElectraAcMessageGap = kDefaultMessageGap; // Just a guess.
using irutils::addBoolToString;
using irutils::addIntToString;
using irutils::addLabeledString;
using irutils::addModeToString;
using irutils::addFanToString;
using irutils::addTempToString;
using irutils::addToggleToString;
#if SEND_ELECTRA_AC
/// Send a Electra A/C formatted message.
/// Status: Alpha / Needs testing against a real device.
/// @param[in] data The message to be sent.
/// @note Guessing MSBF order.
/// @param[in] nbytes The number of bytes of message to be sent.
/// @param[in] repeat The number of times the command is to be repeated.
void IRsend::sendElectraAC(const uint8_t data[], const uint16_t nbytes,
const uint16_t repeat) {
for (uint16_t r = 0; r <= repeat; r++)
sendGeneric(kElectraAcHdrMark, kElectraAcHdrSpace, kElectraAcBitMark,
kElectraAcOneSpace, kElectraAcBitMark, kElectraAcZeroSpace,
kElectraAcBitMark, kElectraAcMessageGap, data, nbytes,
38000, // Complete guess of the modulation frequency.
false, // Send data in LSB order per byte
0, 50);
}
#endif
/// 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?
IRElectraAc::IRElectraAc(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 IRElectraAc::stateReset(void) {
for (uint8_t i = 1; i < kElectraAcStateLength - 2; i++) _.raw[i] = 0;
_.raw[0] = 0xC3;
_.LightToggle = kElectraAcLightToggleOff;
// [12] is the checksum.
}
/// Set up hardware to be able to send a message.
void IRElectraAc::begin(void) { _irsend.begin(); }
/// Calculate the checksum for a given state.
/// @param[in] state The value to calc the checksum of.
/// @param[in] length The length of the state array.
/// @return The calculated checksum stored in a uint_8.
uint8_t IRElectraAc::calcChecksum(const uint8_t state[],
const uint16_t length) {
if (length == 0) return state[0];
return sumBytes(state, length - 1);
}
/// Verify the checksum is valid for a given state.
/// @param[in] state The state 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 IRElectraAc::validChecksum(const uint8_t state[], const uint16_t length) {
if (length < 2)
return true; // No checksum to compare with. Assume okay.
return (state[length - 1] == calcChecksum(state, length));
}
/// Calculate and set the checksum values for the internal state.
/// @param[in] length The length of the state array.
void IRElectraAc::checksum(uint16_t length) {
if (length < 2) return;
_.Sum = calcChecksum(_.raw, length);
}
#if SEND_ELECTRA_AC
/// Send the current internal state as an IR message.
/// @param[in] repeat Nr. of times the message will be repeated.
void IRElectraAc::send(const uint16_t repeat) {
_irsend.sendElectraAC(getRaw(), kElectraAcStateLength, repeat);
}
#endif // SEND_ELECTRA_AC
/// 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 *IRElectraAc::getRaw(void) {
checksum();
return _.raw;
}
/// Set the internal state from a valid code for this protocol.
/// @param[in] new_code A valid code for this protocol.
/// @param[in] length The length of the code array.
void IRElectraAc::setRaw(const uint8_t new_code[], const uint16_t length) {
std::memcpy(_.raw, new_code, std::min(length, kElectraAcStateLength));
}
/// Change the power setting to On.
void IRElectraAc::on(void) { setPower(true); }
/// Change the power setting to Off.
void IRElectraAc::off(void) { setPower(false); }
/// Change the power setting.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRElectraAc::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 IRElectraAc::getPower(void) const {
return _.Power;
}
/// Set the operating mode of the A/C.
/// @param[in] mode The desired operating mode.
void IRElectraAc::setMode(const uint8_t mode) {
switch (mode) {
case kElectraAcAuto:
case kElectraAcDry:
case kElectraAcCool:
case kElectraAcHeat:
case kElectraAcFan:
_.Mode = mode;
break;
default:
// If we get an unexpected mode, default to AUTO.
_.Mode = kElectraAcAuto;
}
}
/// Get the operating mode setting of the A/C.
/// @return The current operating mode setting.
uint8_t IRElectraAc::getMode(void) const {
return _.Mode;
}
/// Convert a stdAc::opmode_t enum into its native mode.
/// @param[in] mode The enum to be converted.
/// @return The native equivalent of the enum.
uint8_t IRElectraAc::convertMode(const stdAc::opmode_t mode) {
switch (mode) {
case stdAc::opmode_t::kCool: return kElectraAcCool;
case stdAc::opmode_t::kHeat: return kElectraAcHeat;
case stdAc::opmode_t::kDry: return kElectraAcDry;
case stdAc::opmode_t::kFan: return kElectraAcFan;
default: return kElectraAcAuto;
}
}
/// Convert a native mode into its stdAc equivalent.
/// @param[in] mode The native setting to be converted.
/// @return The stdAc equivalent of the native setting.
stdAc::opmode_t IRElectraAc::toCommonMode(const uint8_t mode) {
switch (mode) {
case kElectraAcCool: return stdAc::opmode_t::kCool;
case kElectraAcHeat: return stdAc::opmode_t::kHeat;
case kElectraAcDry: return stdAc::opmode_t::kDry;
case kElectraAcFan: return stdAc::opmode_t::kFan;
default: return stdAc::opmode_t::kAuto;
}
}
/// Set the temperature.
/// @param[in] temp The temperature in degrees celsius.
void IRElectraAc::setTemp(const uint8_t temp) {
uint8_t newtemp = std::max(kElectraAcMinTemp, temp);
newtemp = std::min(kElectraAcMaxTemp, newtemp) - kElectraAcTempDelta;
_.Temp = newtemp;
}
/// Get the current temperature setting.
/// @return The current setting for temp. in degrees celsius.
uint8_t IRElectraAc::getTemp(void) const {
return _.Temp + kElectraAcTempDelta;
}
/// Set the speed of the fan.
/// @param[in] speed The desired setting.
/// @note 0 is auto, 1-3 is the speed
void IRElectraAc::setFan(const uint8_t speed) {
switch (speed) {
case kElectraAcFanAuto:
case kElectraAcFanHigh:
case kElectraAcFanMed:
case kElectraAcFanLow:
_.Fan = speed;
break;
default:
// If we get an unexpected speed, default to Auto.
_.Fan = kElectraAcFanAuto;
}
}
/// Get the current fan speed setting.
/// @return The current fan speed.
uint8_t IRElectraAc::getFan(void) const {
return _.Fan;
}
/// Convert a stdAc::fanspeed_t enum into it's native speed.
/// @param[in] speed The enum to be converted.
/// @return The native equivalent of the enum.
uint8_t IRElectraAc::convertFan(const stdAc::fanspeed_t speed) {
switch (speed) {
case stdAc::fanspeed_t::kMin:
case stdAc::fanspeed_t::kLow: return kElectraAcFanLow;
case stdAc::fanspeed_t::kMedium: return kElectraAcFanMed;
case stdAc::fanspeed_t::kHigh:
case stdAc::fanspeed_t::kMax: return kElectraAcFanHigh;
default: return kElectraAcFanAuto;
}
}
/// Convert a native fan speed into its stdAc equivalent.
/// @param[in] speed The native setting to be converted.
/// @return The stdAc equivalent of the native setting.
stdAc::fanspeed_t IRElectraAc::toCommonFanSpeed(const uint8_t speed) {
switch (speed) {
case kElectraAcFanHigh: return stdAc::fanspeed_t::kMax;
case kElectraAcFanMed: return stdAc::fanspeed_t::kMedium;
case kElectraAcFanLow: return stdAc::fanspeed_t::kMin;
default: return stdAc::fanspeed_t::kAuto;
}
}
/// Set the Vertical Swing mode of the A/C.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRElectraAc::setSwingV(const bool on) {
_.SwingV = (on ? kElectraAcSwingOn : kElectraAcSwingOff);
}
/// Get the Vertical Swing mode of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRElectraAc::getSwingV(void) const {
return !_.SwingV;
}
/// Set the Horizontal Swing mode of the A/C.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRElectraAc::setSwingH(const bool on) {
_.SwingH = (on ? kElectraAcSwingOn : kElectraAcSwingOff);
}
/// Get the Horizontal Swing mode of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRElectraAc::getSwingH(void) const {
return !_.SwingH;
}
/// Set the Light (LED) Toggle mode of the A/C.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRElectraAc::setLightToggle(const bool on) {
_.LightToggle = (on ? kElectraAcLightToggleOn : kElectraAcLightToggleOff);
}
/// Get the Light (LED) Toggle mode of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRElectraAc::getLightToggle(void) const {
return (_.LightToggle & kElectraAcLightToggleMask) ==
kElectraAcLightToggleMask;
}
/// Set the Clean mode of the A/C.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRElectraAc::setClean(const bool on) {
_.Clean = on;
}
/// Get the Clean mode of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRElectraAc::getClean(void) const {
return _.Clean;
}
/// Set the Turbo mode of the A/C.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRElectraAc::setTurbo(const bool on) {
_.Turbo = on;
}
/// Get the Turbo mode of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRElectraAc::getTurbo(void) const {
return _.Turbo;
}
/// Get the IFeel mode of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRElectraAc::getIFeel(void) const { return _.IFeel; }
/// Set the IFeel mode of the A/C.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRElectraAc::setIFeel(const bool on) {
_.IFeel = on;
if (_.IFeel)
// Make sure there is a reasonable value in _.SensorTemp
setSensorTemp(getSensorTemp());
else
// Clear any previous stored temp..
_.SensorTemp = kElectraAcSensorMinTemp;
}
/// Get the silent Sensor Update setting of the message.
/// i.e. Is this _just_ a sensor temp update message from the remote?
/// @note The A/C just takes the sensor temp value from the message and
/// will not follow any of the other settings in the message.
/// @return true, the setting is on. false, the setting is off.
bool IRElectraAc::getSensorUpdate(void) const { return _.SensorUpdate; }
/// Set the silent Sensor Update setting of the message.
/// i.e. Is this _just_ a sensor temp update message from the remote?
/// @note The A/C will just take the sensor temp value from the message and
/// will not follow any of the other settings in the message. If set, the A/C
/// unit will also not beep in response to the message.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRElectraAc::setSensorUpdate(const bool on) { _.SensorUpdate = on; }
/// Set the Sensor temperature for the IFeel mode.
/// @param[in] temp The temperature in degrees celsius.
void IRElectraAc::setSensorTemp(const uint8_t temp) {
_.SensorTemp = std::min(kElectraAcSensorMaxTemp,
std::max(kElectraAcSensorMinTemp, temp)) +
kElectraAcSensorTempDelta;
}
/// Get the current sensor temperature setting for the IFeel mode.
/// @return The current setting for temp. in degrees celsius.
uint8_t IRElectraAc::getSensorTemp(void) const {
return std::max(kElectraAcSensorTempDelta, _.SensorTemp) -
kElectraAcSensorTempDelta;
}
/// Convert the current internal state into its stdAc::state_t equivalent.
/// @return The stdAc equivalent of the native settings.
stdAc::state_t IRElectraAc::toCommon(void) const {
stdAc::state_t result{};
result.protocol = decode_type_t::ELECTRA_AC;
result.power = _.Power;
result.mode = toCommonMode(_.Mode);
result.celsius = true;
result.degrees = getTemp();
result.sensorTemperature = getSensorTemp();
result.fanspeed = toCommonFanSpeed(_.Fan);
result.swingv = getSwingV() ? stdAc::swingv_t::kAuto
: stdAc::swingv_t::kOff;
result.swingh = getSwingH() ? stdAc::swingh_t::kAuto
: stdAc::swingh_t::kOff;
result.light = getLightToggle();
result.turbo = _.Turbo;
result.clean = _.Clean;
result.iFeel = getIFeel();
// Not supported.
result.model = -1; // No models used.
result.quiet = false;
result.econo = false;
result.filter = false;
result.beep = false;
result.sleep = -1;
result.clock = -1;
return result;
}
/// Convert the current internal state into a human readable string.
/// @return A human readable string.
String IRElectraAc::toString(void) const {
String result = "";
result.reserve(160); // Reserve some heap for the string to reduce fragging.
if (!_.SensorUpdate) {
result += addBoolToString(_.Power, kPowerStr, false);
result += addModeToString(_.Mode, kElectraAcAuto, kElectraAcCool,
kElectraAcHeat, kElectraAcDry, kElectraAcFan);
result += addTempToString(getTemp());
result += addFanToString(_.Fan, kElectraAcFanHigh, kElectraAcFanLow,
kElectraAcFanAuto, kElectraAcFanAuto,
kElectraAcFanMed);
result += addBoolToString(getSwingV(), kSwingVStr);
result += addBoolToString(getSwingH(), kSwingHStr);
result += addToggleToString(getLightToggle(), kLightStr);
result += addBoolToString(_.Clean, kCleanStr);
result += addBoolToString(_.Turbo, kTurboStr);
result += addBoolToString(_.IFeel, kIFeelStr);
}
if (_.IFeel || _.SensorUpdate) {
result += addIntToString(getSensorTemp(), kSensorTempStr, !_.SensorUpdate);
result += 'C';
}
return result;
}
#if DECODE_ELECTRA_AC
/// Decode the supplied Electra A/C message.
/// Status: STABLE / Known working.
/// @param[in,out] results Ptr to the data to decode & where to store the decode
/// result.
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// @param[in] strict Flag indicating if we should perform strict matching.
/// @return A boolean. True if it can decode it, false if it can't.
bool IRrecv::decodeElectraAC(decode_results *results, uint16_t offset,
const uint16_t nbits,
const bool strict) {
if (strict) {
if (nbits != kElectraAcBits)
return false; // Not strictly a ELECTRA_AC message.
}
// Match Header + Data + Footer
if (!matchGeneric(results->rawbuf + offset, results->state,
results->rawlen - offset, nbits,
kElectraAcHdrMark, kElectraAcHdrSpace,
kElectraAcBitMark, kElectraAcOneSpace,
kElectraAcBitMark, kElectraAcZeroSpace,
kElectraAcBitMark, kElectraAcMessageGap, true,
_tolerance, 0, false)) return false;
// Compliance
if (strict) {
// Verify the checksum.
if (!IRElectraAc::validChecksum(results->state)) return false;
}
// Success
results->decode_type = decode_type_t::ELECTRA_AC;
results->bits = nbits;
// 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_ELECTRA_AC

View File

@@ -0,0 +1,179 @@
// Copyright 2019-2021 David Conran
/// @file
/// @brief Support for Electra A/C protocols.
/// @see https://github.com/ToniA/arduino-heatpumpir/blob/master/AUXHeatpumpIR.cpp
// Supports:
// Brand: AUX, Model: KFR-35GW/BpNFW=3 A/C
// Brand: AUX, Model: YKR-T/011 remote
// Brand: Electra, Model: Classic INV 17 / AXW12DCS A/C
// Brand: Electra, Model: YKR-M/003E remote
// Brand: Frigidaire, Model: FGPC102AB1 A/C
// Brand: Subtropic, Model: SUB-07HN1_18Y A/C
// Brand: Subtropic, Model: YKR-H/102E remote
// Brand: Centek, Model: SCT-65Q09 A/C
// Brand: Centek, Model: YKR-P/002E remote
// Brand: AEG, Model: Chillflex Pro AXP26U338CW A/C
// Brand: Electrolux, Model: YKR-H/531E A/C
#ifndef IR_ELECTRA_H_
#define IR_ELECTRA_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
/// Native representation of a Electra A/C message.
union ElectraProtocol {
uint8_t raw[kElectraAcStateLength]; ///< The state of the IR remote
struct {
// Byte 0
uint8_t :8;
// Byte 1
uint8_t SwingV :3;
uint8_t Temp :5;
// Byte 2
uint8_t :5;
uint8_t SwingH :3;
// Byte 3
uint8_t :6;
uint8_t SensorUpdate :1;
uint8_t :1;
// Byte 4
uint8_t :5;
uint8_t Fan :3;
// Byte 5
uint8_t :6;
uint8_t Turbo :1;
uint8_t :1;
// Byte 6
uint8_t :3;
uint8_t IFeel :1;
uint8_t :1;
uint8_t Mode :3;
// Byte 7
uint8_t SensorTemp :8;
// Byte 8
uint8_t :8;
// Byte 9
uint8_t :2;
uint8_t Clean :1;
uint8_t :2;
uint8_t Power :1;
uint8_t :2;
// Byte 10
uint8_t :8;
// Byte 11
uint8_t LightToggle :8;
// Byte 12
uint8_t Sum :8;
};
};
// Constants
const uint8_t kElectraAcMinTemp = 16; // 16C
const uint8_t kElectraAcMaxTemp = 32; // 32C
const uint8_t kElectraAcTempDelta = 8;
const uint8_t kElectraAcSwingOn = 0b000;
const uint8_t kElectraAcSwingOff = 0b111;
const uint8_t kElectraAcFanAuto = 0b101;
const uint8_t kElectraAcFanLow = 0b011;
const uint8_t kElectraAcFanMed = 0b010;
const uint8_t kElectraAcFanHigh = 0b001;
const uint8_t kElectraAcAuto = 0b000;
const uint8_t kElectraAcCool = 0b001;
const uint8_t kElectraAcDry = 0b010;
const uint8_t kElectraAcHeat = 0b100;
const uint8_t kElectraAcFan = 0b110;
const uint8_t kElectraAcLightToggleOn = 0x15;
// Light has known ON values of 0x15 (0b00010101) or 0x19 (0b00011001)
// Thus common bits ON are: 0b00010001 (0x11)
// We will use this for the getLightToggle() test.
const uint8_t kElectraAcLightToggleMask = 0x11;
// and known OFF values of 0x08 (0b00001000) & 0x05 (0x00000101)
const uint8_t kElectraAcLightToggleOff = 0x08;
// Re: Byte[7]. Or Delta == 0xA and Temperature are stored in last 6 bits,
// and bit 7 stores Unknown flag
const uint8_t kElectraAcSensorTempDelta = 0x4A;
const uint8_t kElectraAcSensorMinTemp = 0; // 0C
const uint8_t kElectraAcSensorMaxTemp = 50; // 50C
// Classes
/// Class for handling detailed Electra A/C messages.
class IRElectraAc {
public:
explicit IRElectraAc(const uint16_t pin, const bool inverted = false,
const bool use_modulation = true);
void stateReset(void);
#if SEND_ELECTRA_AC
void send(const uint16_t repeat = kElectraAcMinRepeat);
/// 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_ELECTRA_AC
void begin(void);
void on(void);
void off(void);
void setPower(const bool on);
bool getPower(void) const;
void setMode(const uint8_t mode);
uint8_t getMode(void) const;
void setTemp(const uint8_t temp);
uint8_t getTemp(void) const;
void setFan(const uint8_t speed);
uint8_t getFan(void) const;
void setSwingV(const bool on);
bool getSwingV(void) const;
void setSwingH(const bool on);
bool getSwingH(void) const;
void setClean(const bool on);
bool getClean(void) const;
void setLightToggle(const bool on);
bool getLightToggle(void) const;
void setTurbo(const bool on);
bool getTurbo(void) const;
void setIFeel(const bool on);
bool getIFeel(void) const;
void setSensorUpdate(const bool on);
bool getSensorUpdate(void) const;
void setSensorTemp(const uint8_t temp);
uint8_t getSensorTemp(void) const;
uint8_t* getRaw(void);
void setRaw(const uint8_t new_code[],
const uint16_t length = kElectraAcStateLength);
static bool validChecksum(const uint8_t state[],
const uint16_t length = kElectraAcStateLength);
static uint8_t calcChecksum(const uint8_t state[],
const uint16_t length = kElectraAcStateLength);
String toString(void) const;
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);
static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed);
stdAc::state_t toCommon(void) const;
#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
ElectraProtocol _;
void checksum(const uint16_t length = kElectraAcStateLength);
};
#endif // IR_ELECTRA_H_

View File

@@ -0,0 +1,89 @@
// Copyright 2020 David Conran
/// @file
/// @brief Elite Screens protocol support
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1306
/// @see https://elitescreens.com/kcfinder/upload/files/FAQ/ir_commands.pdf
// Supports:
// Brand: Elite Screens, Model: Spectrum series
// Brand: Elite Screens, Model: VMAX2 / VMAX2 Plus series
// Brand: Elite Screens, Model: VMAX Plus4 series
// Brand: Elite Screens, Model: Home2 / Home3 series
// Brand: Elite Screens, Model: CineTension2 / CineTension3 series
// Brand: Elite Screens, Model: ZSP-IR-B / ZSP-IR-W remote
// Brand: Lumene Screens, Model: Embassy
// Known Elite Screens commands:
// 0xFEA3387 (STOP)
// 0xFDA2256 (UP)
// 0xFBA1136 (DOWN)
// Known Lumene Screens commands:
// 0xFDE3322 (STOP)
// 0xFEE2221 (UP)
// 0xFBE11E0 (DOWN)
// 0xF7E2EBD (STEP UP)
// 0xEFE1E2C (STEP DOWN)
#include "IRrecv.h"
#include "IRsend.h"
#include "IRutils.h"
// Constants
const uint16_t kEliteScreensOne = 470;
const uint16_t kEliteScreensZero = 1214;
const uint16_t kEliteScreensGap = 29200;
#if SEND_ELITESCREENS
/// Send an Elite Screens formatted message.
/// Status: BETA / Probably Working.
/// @param[in] data The message to be sent.
/// @param[in] nbits The number of bits of message to be sent.
/// @param[in] repeat The number of times the command is to be repeated.
void IRsend::sendElitescreens(uint64_t data, uint16_t nbits, uint16_t repeat) {
// Protocol uses a constant bit time encoding.
sendGeneric(0, 0, // No header.
kEliteScreensOne, kEliteScreensZero,
kEliteScreensZero, kEliteScreensOne,
0, kEliteScreensGap, data, nbits, 38000, true, repeat, 50);
}
#endif
#if DECODE_ELITESCREENS
/// Decode the supplied Elite Screens message.
/// Status: STABLE / Confirmed working.
/// @param[in,out] results Ptr to the data to decode & where to store the decode
/// result.
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// @param[in] strict Flag indicating if we should perform strict matching.
/// @return A boolean. True if it can decode it, false if it can't.
bool IRrecv::decodeElitescreens(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
// Compliance check.
if (strict && nbits != kEliteScreensBits) return false;
uint64_t data = 0;
// Data + Footer
if (!matchGenericConstBitTime(results->rawbuf + offset, &data,
results->rawlen - offset, nbits,
// Header (None)
0, 0,
// Data
kEliteScreensOne, kEliteScreensZero,
// Footer (None)
0, kEliteScreensGap, true)) return false;
// Success
results->decode_type = decode_type_t::ELITESCREENS;
results->bits = nbits;
results->value = data;
results->address = 0;
results->command = 0;
results->repeat = false;
return true;
}
#endif

View File

@@ -0,0 +1,111 @@
// Copyright 2020 David Conran
/// @file
/// @brief Support for Epson protocols.
/// Epson is an NEC-like protocol, except it doesn't use the NEC style repeat.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1034
// Supports:
// Brand: Epson, Model: EN-TW9100W Projector
// Brand: Epson, Model: VS230 Projector
// Brand: Epson, Model: VS330 Projector
// Brand: Epson, Model: EX3220 Projector
// Brand: Epson, Model: EX5220 Projector
// Brand: Epson, Model: EX5230 Projector
// Brand: Epson, Model: EX6220 Projector
// Brand: Epson, Model: EX7220 Projector
#define __STDC_LIMIT_MACROS
#include <stdint.h>
#include <algorithm>
#include "IRrecv.h"
#include "IRsend.h"
#include "IRutils.h"
#include "ir_NEC.h"
#if SEND_EPSON
/// Send an Epson formatted message.
/// Status: Beta / Probably works.
/// @param[in] data The message to be sent.
/// @param[in] nbits The number of nbits of message to be sent.
/// @param[in] repeat The number of times the command is to be repeated.
void IRsend::sendEpson(uint64_t data, uint16_t nbits, uint16_t repeat) {
sendGeneric(kNecHdrMark, kNecHdrSpace, kNecBitMark, kNecOneSpace, kNecBitMark,
kNecZeroSpace, kNecBitMark, kNecMinGap, kNecMinCommandLength,
data, nbits, 38, true, repeat, 33);
}
#endif // SEND_EPSON
#if DECODE_EPSON
/// Decode the supplied Epson message.
/// Status: Beta / Probably works.
/// @param[in,out] results Ptr to the data to decode & where to store the decode
/// result.
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// @param[in] strict Flag indicating if we should perform strict matching.
/// @return A boolean. True if it can decode it, false if it can't.
/// @note Experimental data indicates there are at least three messages
/// (first + 2 repeats). We only require the first + a single repeat to match.
/// This helps us distinguish it from NEC messages which are near identical.
bool IRrecv::decodeEpson(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
const uint8_t kEpsonMinMesgsForDecode = 2;
if (results->rawlen < kEpsonMinMesgsForDecode * (2 * nbits + kHeader +
kFooter) + offset - 1)
return false; // Can't possibly be a valid Epson message.
if (strict && nbits != kEpsonBits)
return false; // Not strictly an Epson message.
uint64_t data = 0;
uint64_t first_data = 0;
bool first = true;
for (uint8_t i = 0; i < kEpsonMinMesgsForDecode; i++) {
// Match Header + Data + Footer
uint16_t delta = matchGeneric(results->rawbuf + offset, &data,
results->rawlen - offset, nbits,
kNecHdrMark, kNecHdrSpace,
kNecBitMark, kNecOneSpace,
kNecBitMark, kNecZeroSpace,
kNecBitMark, kNecMinGap, true);
if (!delta) return false;
offset += delta;
if (first)
first_data = data;
else if (data != first_data) return false;
first = false; // No longer the first message.
}
// Compliance
// Calculate command and optionally enforce integrity checking.
uint8_t command = (data & 0xFF00) >> 8;
// Command is sent twice, once as plain and then inverted.
if ((command ^ 0xFF) != (data & 0xFF)) {
if (strict) return false; // Command integrity failed.
command = 0; // The command value isn't valid, so default to zero.
}
// Success
results->bits = nbits;
results->value = data;
results->decode_type = EPSON;
// Epson command and address are technically in LSB first order so the
// final versions have to be reversed.
results->command = reverseBits(command, 8);
// Normal Epson (NEC) protocol has an 8 bit address sent,
// followed by it inverted.
uint8_t address = (data & 0xFF000000) >> 24;
uint8_t address_inverted = (data & 0x00FF0000) >> 16;
if (address == (address_inverted ^ 0xFF))
// Inverted, so it is normal Epson (NEC) protocol.
results->address = reverseBits(address, 8);
else
// Not inverted, so must be Extended Epson (NEC) protocol,
// thus 16 bit address.
results->address = reverseBits((data >> 16) & UINT16_MAX, 16);
results->repeat = !first;
return true;
}
#endif // DECODE_EPSON

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,266 @@
// Copyright 2017 Jonny Graham
// Copyright 2018-2022 David Conran
// Copyright 2021 siriuslzx
/// @file
/// @brief Support for Fujitsu A/C protocols.
/// Fujitsu A/C support added by Jonny Graham
/// @warning Use of incorrect model may cause the A/C unit to lock up.
/// e.g. An A/C that uses an AR-RAH1U remote may lock up requiring a physical
/// power rest, if incorrect model (ARRAH2E) is used with a Swing command.
/// The correct model for it is ARREB1E.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1376
// Supports:
// Brand: Fujitsu, Model: AR-RAH2E remote (ARRAH2E)
// Brand: Fujitsu, Model: ASYG30LFCA A/C (ARRAH2E)
// Brand: Fujitsu General, Model: AR-RCE1E remote (ARRAH2E)
// Brand: Fujitsu General, Model: ASHG09LLCA A/C (ARRAH2E)
// Brand: Fujitsu General, Model: AOHG09LLC A/C (ARRAH2E)
// Brand: Fujitsu, Model: AR-DB1 remote (ARDB1)
// Brand: Fujitsu, Model: AST9RSGCW A/C (ARDB1)
// Brand: Fujitsu, Model: AR-REB1E remote (ARREB1E)
// Brand: Fujitsu, Model: ASYG7LMCA A/C (ARREB1E)
// Brand: Fujitsu, Model: AR-RAE1E remote (ARRAH2E)
// Brand: Fujitsu, Model: AGTV14LAC A/C (ARRAH2E)
// Brand: Fujitsu, Model: AR-RAC1E remote (ARRAH2E)
// Brand: Fujitsu, Model: ASTB09LBC A/C (ARRY4)
// Brand: Fujitsu, Model: AR-RY4 remote (ARRY4)
// Brand: Fujitsu General, Model: AR-JW2 remote (ARJW2)
// Brand: Fujitsu, Model: AR-DL10 remote (ARDB1)
// Brand: Fujitsu, Model: ASU30C1 A/C (ARDB1)
// Brand: Fujitsu, Model: AR-RAH1U remote (ARREB1E)
// Brand: Fujitsu, Model: AR-RAH2U remote (ARRAH2E)
// Brand: Fujitsu, Model: ASU12RLF A/C (ARREB1E)
// Brand: Fujitsu, Model: AR-REW4E remote (ARREW4E)
// Brand: Fujitsu, Model: ASYG09KETA-B A/C (ARREW4E)
// Brand: Fujitsu, Model: AR-REB4E remote (ARREB1E)
// Brand: Fujitsu, Model: ASTG09K A/C (ARREW4E)
// Brand: Fujitsu, Model: ASTG18K A/C (ARREW4E)
// Brand: Fujitsu, Model: AR-REW1E remote (ARREW4E)
// Brand: Fujitsu, Model: AR-REG1U remote (ARRAH2E)
// Brand: OGeneral, Model: AR-RCL1E remote (ARRAH2E)
// Brand: Fujitsu General, Model: AR-JW17 remote (ARDB1)
#ifndef IR_FUJITSU_H_
#define IR_FUJITSU_H_
#define __STDC_LIMIT_MACROS
#include <stdint.h>
#ifdef ARDUINO
#include <Arduino.h>
#endif
#include "IRrecv.h"
#include "IRremoteESP8266.h"
#include "IRsend.h"
#ifdef UNIT_TEST
#include "IRsend_test.h"
#endif
/// Native representation of a Fujitsu A/C message.
union FujitsuProtocol {
struct {
uint8_t longcode[kFujitsuAcStateLength]; ///< The state of the IR remote.
uint8_t shortcode[kFujitsuAcStateLengthShort];
};
struct {
// Byte 0~1
uint64_t :16; // Fixed header
// Byte 2
uint64_t :4;
uint64_t Id :2; // Device Number/Identifier
uint64_t :2;
// Byte 3-4
uint64_t :16;
// Byte 5
uint64_t Cmd :8; // short codes:cmd; long codes:fixed value
// Byte 6
uint64_t RestLength :8; // Nr. of bytes in the message after this byte.
// Byte 7
uint64_t Protocol :8; // Seems like a protocol version number. Not sure.
// Byte 8
uint64_t Power :1;
uint64_t Fahrenheit :1;
uint64_t Temp :6; // Internal representation varies between models.
// Byte 9
uint64_t Mode :3;
uint64_t Clean :1; // Also 10C Heat in ARREW4E.
uint64_t TimerType :2;
uint64_t :2;
// Byte 10
uint64_t Fan :3;
uint64_t :1;
uint64_t Swing :2;
uint64_t :2;
// Byte 11~13
uint64_t OffTimer :11; // Also is the sleep timer value
uint64_t OffTimerEnable :1;
uint64_t OnTimer :11;
uint64_t OnTimerEnable :1;
// Byte 14
uint64_t :3;
uint64_t Filter :1;
uint64_t :1;
uint64_t unknown :1;
uint64_t :1;
uint64_t OutsideQuiet :1;
// Byte 15
uint64_t :0; // Checksum
};
};
// Constants
const uint8_t kFujitsuAcModeAuto = 0x0; // 0b000
const uint8_t kFujitsuAcModeCool = 0x1; // 0b001
const uint8_t kFujitsuAcModeDry = 0x2; // 0b010
const uint8_t kFujitsuAcModeFan = 0x3; // 0b011
const uint8_t kFujitsuAcModeHeat = 0x4; // 0b100
const uint8_t kFujitsuAcCmdStayOn = 0x00; // b00000000
const uint8_t kFujitsuAcCmdTurnOn = 0x01; // b00000001
const uint8_t kFujitsuAcCmdTurnOff = 0x02; // b00000010
const uint8_t kFujitsuAcCmdEcono = 0x09; // b00001001
const uint8_t kFujitsuAcCmdPowerful = 0x39; // b00111001
const uint8_t kFujitsuAcCmdStepVert = 0x6C; // b01101100
const uint8_t kFujitsuAcCmdToggleSwingVert = 0x6D; // b01101101
const uint8_t kFujitsuAcCmdStepHoriz = 0x79; // b01111001
const uint8_t kFujitsuAcCmdToggleSwingHoriz = 0x7A; // b01111010
const uint8_t kFujitsuAcFanAuto = 0x00;
const uint8_t kFujitsuAcFanHigh = 0x01;
const uint8_t kFujitsuAcFanMed = 0x02;
const uint8_t kFujitsuAcFanLow = 0x03;
const uint8_t kFujitsuAcFanQuiet = 0x04;
const float kFujitsuAcMinHeat = 10; // 10C
const float kFujitsuAcMinTemp = 16; // 16C
const float kFujitsuAcMaxTemp = 30; // 30C
const uint8_t kFujitsuAcTempOffsetC = kFujitsuAcMinTemp;
const float kFujitsuAcMinHeatF = 50; // 50F
const float kFujitsuAcMinTempF = 60; // 60F
const float kFujitsuAcMaxTempF = 88; // 88F
const uint8_t kFujitsuAcTempOffsetF = 44;
const uint8_t kFujitsuAcSwingOff = 0x00;
const uint8_t kFujitsuAcSwingVert = 0x01;
const uint8_t kFujitsuAcSwingHoriz = 0x02;
const uint8_t kFujitsuAcSwingBoth = 0x03;
const uint8_t kFujitsuAcStopTimers = 0b00; // 0
const uint8_t kFujitsuAcSleepTimer = 0b01; // 1
const uint8_t kFujitsuAcOffTimer = 0b10; // 2
const uint8_t kFujitsuAcOnTimer = 0b11; // 3
const uint16_t kFujitsuAcTimerMax = 12 * 60; ///< Minutes.
// Legacy defines.
#define FUJITSU_AC_MODE_AUTO kFujitsuAcModeAuto
#define FUJITSU_AC_MODE_COOL kFujitsuAcModeCool
#define FUJITSU_AC_MODE_DRY kFujitsuAcModeDry
#define FUJITSU_AC_MODE_FAN kFujitsuAcModeFan
#define FUJITSU_AC_MODE_HEAT kFujitsuAcModeHeat
#define FUJITSU_AC_CMD_STAY_ON kFujitsuAcCmdStayOn
#define FUJITSU_AC_CMD_TURN_ON kFujitsuAcCmdTurnOn
#define FUJITSU_AC_CMD_TURN_OFF kFujitsuAcCmdTurnOff
#define FUJITSU_AC_CMD_STEP_HORIZ kFujitsuAcCmdStepHoriz
#define FUJITSU_AC_CMD_STEP_VERT kFujitsuAcCmdStepVert
#define FUJITSU_AC_FAN_AUTO kFujitsuAcFanAuto
#define FUJITSU_AC_FAN_HIGH kFujitsuAcFanHigh
#define FUJITSU_AC_FAN_MED kFujitsuAcFanMed
#define FUJITSU_AC_FAN_LOW kFujitsuAcFanLow
#define FUJITSU_AC_FAN_QUIET kFujitsuAcFanQuiet
#define FUJITSU_AC_MIN_TEMP kFujitsuAcMinTemp
#define FUJITSU_AC_MAX_TEMP kFujitsuAcMaxTemp
#define FUJITSU_AC_SWING_OFF kFujitsuAcSwingOff
#define FUJITSU_AC_SWING_VERT kFujitsuAcSwingVert
#define FUJITSU_AC_SWING_HORIZ kFujitsuAcSwingHoriz
#define FUJITSU_AC_SWING_BOTH kFujitsuAcSwingBoth
/// Class for handling detailed Fujitsu A/C messages.
class IRFujitsuAC {
public:
explicit IRFujitsuAC(const uint16_t pin,
const fujitsu_ac_remote_model_t model = ARRAH2E,
const bool inverted = false,
const bool use_modulation = true);
void setModel(const fujitsu_ac_remote_model_t model);
fujitsu_ac_remote_model_t getModel(void) const;
void stateReset(void);
#if SEND_FUJITSU_AC
void send(const uint16_t repeat = kFujitsuAcMinRepeat);
/// 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_FUJITSU_AC
void begin(void);
void stepHoriz(void);
void toggleSwingHoriz(const bool update = true);
void stepVert(void);
void toggleSwingVert(const bool update = true);
void setCmd(const uint8_t cmd);
uint8_t getCmd(void) const;
void setTemp(const float temp, const bool useCelsius = true);
float getTemp(void) const;
void setFanSpeed(const uint8_t fan);
uint8_t getFanSpeed(void) const;
void setMode(const uint8_t mode);
uint8_t getMode(void) const;
void setSwing(const uint8_t mode);
uint8_t getSwing(void) const;
uint8_t* getRaw(void);
bool setRaw(const uint8_t newState[], const uint16_t length);
uint8_t getStateLength(void);
static bool validChecksum(uint8_t* state, const uint16_t length);
bool isLongCode(void) const;
void setPower(const bool on);
void off(void);
void on(void);
bool getPower(void) const;
void setClean(const bool on);
bool getClean(void) const;
void setFilter(const bool on);
bool getFilter(void) const;
void set10CHeat(const bool on);
bool get10CHeat(void) const;
void setOutsideQuiet(const bool on);
bool getOutsideQuiet(void) const;
uint8_t getTimerType(void) const;
void setTimerType(const uint8_t timertype);
uint16_t getOnTimer(void) const;
void setOnTimer(const uint16_t nr_mins);
uint16_t getOffSleepTimer(void) const;
void setOffTimer(const uint16_t nr_mins);
void setSleepTimer(const uint16_t nr_mins);
void setId(const uint8_t num);
uint8_t getId(void) const;
void setCelsius(const bool on);
bool getCelsius(void) const;
static uint8_t convertMode(const stdAc::opmode_t mode);
static uint8_t convertFan(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(const stdAc::state_t *prev = NULL);
String toString(void) const;
#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
FujitsuProtocol _;
uint8_t _cmd;
fujitsu_ac_remote_model_t _model;
uint8_t _state_length;
uint8_t _state_length_short;
bool _rawstatemodified;
void checkSum(void);
bool updateUseLongOrShort(void);
void buildFromState(const uint16_t length);
void setOffSleepTimer(const uint16_t nr_mins);
};
#endif // IR_FUJITSU_H_

View File

@@ -0,0 +1,95 @@
// Copyright 2018 David Conran
/// @file
/// @brief G.I. Cable
/// @see https://github.com/cyborg5/IRLib2/blob/master/IRLibProtocols/IRLib_P09_GICable.h
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/447
// Supports:
// Brand: G.I. Cable, Model: XRC-200 remote
#define __STDC_LIMIT_MACROS
#include <stdint.h>
#include <algorithm>
#include "IRrecv.h"
#include "IRsend.h"
#include "IRutils.h"
// Constants
const uint16_t kGicableHdrMark = 9000;
const uint16_t kGicableHdrSpace = 4400;
const uint16_t kGicableBitMark = 550;
const uint16_t kGicableOneSpace = 4400;
const uint16_t kGicableZeroSpace = 2200;
const uint16_t kGicableRptSpace = 2200;
const uint32_t kGicableMinCommandLength = 99600;
const uint32_t kGicableMinGap =
kGicableMinCommandLength -
(kGicableHdrMark + kGicableHdrSpace +
kGicableBits * (kGicableBitMark + kGicableOneSpace) + kGicableBitMark);
#if SEND_GICABLE
/// Send a raw G.I. Cable formatted message.
/// Status: Alpha / Untested.
/// @param[in] data The message to be sent.
/// @param[in] nbits The number of bits of message to be sent.
/// @param[in] repeat The number of times the command is to be repeated.
void IRsend::sendGICable(uint64_t data, uint16_t nbits, uint16_t repeat) {
sendGeneric(kGicableHdrMark, kGicableHdrSpace, kGicableBitMark,
kGicableOneSpace, kGicableBitMark, kGicableZeroSpace,
kGicableBitMark, kGicableMinGap, kGicableMinCommandLength, data,
nbits, 39, true, 0, // Repeats are handled later.
50);
// Message repeat sequence.
if (repeat)
sendGeneric(kGicableHdrMark, kGicableRptSpace, 0, 0, 0,
0, // No actual data sent.
kGicableBitMark, kGicableMinGap, kGicableMinCommandLength, 0,
0, // No data to be sent.
39, true, repeat - 1, 50);
}
#endif // SEND_GICABLE
#if DECODE_GICABLE
/// Decode the supplied G.I. Cable message.
/// Status: Alpha / Not tested against a real device.
/// @param[in,out] results Ptr to the data to decode & where to store the decode
/// result.
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// @param[in] strict Flag indicating if we should perform strict matching.
/// @return A boolean. True if it can decode it, false if it can't.
bool IRrecv::decodeGICable(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
if (strict && nbits != kGicableBits)
return false; // Not strictly an GICABLE message.
uint64_t data = 0;
// Match Header + Data + Footer
uint16_t used;
used = matchGeneric(results->rawbuf + offset, &data,
results->rawlen - offset, nbits,
kGicableHdrMark, kGicableHdrSpace,
kGicableBitMark, kGicableOneSpace,
kGicableBitMark, kGicableZeroSpace,
kGicableBitMark, kGicableMinGap, true);
if (!used) return false;
offset += used;
// Compliance
if (strict) {
// We expect a repeat frame.
if (!matchMark(results->rawbuf[offset++], kGicableHdrMark)) return false;
if (!matchSpace(results->rawbuf[offset++], kGicableRptSpace)) return false;
if (!matchMark(results->rawbuf[offset++], kGicableBitMark)) return false;
}
// Success
results->bits = nbits;
results->value = data;
results->decode_type = GICABLE;
results->command = 0;
results->address = 0;
return true;
}
#endif // DECODE_GICABLE

View File

@@ -0,0 +1,63 @@
// Copyright 2016 Hisham Khalifa
// Copyright 2017 David Conran
/// @file
/// @brief Global Cache IR format sender
/// Originally added by Hisham Khalifa (http://www.hishamkhalifa.com)
/// @see https://irdb.globalcache.com/Home/Database
// Supports:
// Brand: Global Cache, Model: Control Tower IR DB
#include <algorithm>
#include "IRsend.h"
// Constants
const uint16_t kGlobalCacheMaxRepeat = 50;
const uint32_t kGlobalCacheMinUsec = 80;
const uint8_t kGlobalCacheFreqIndex = 0;
const uint8_t kGlobalCacheRptIndex = kGlobalCacheFreqIndex + 1;
const uint8_t kGlobalCacheRptStartIndex = kGlobalCacheRptIndex + 1;
const uint8_t kGlobalCacheStartIndex = kGlobalCacheRptStartIndex + 1;
#if SEND_GLOBALCACHE
/// Send a shortened GlobalCache (GC) IRdb/control tower formatted message.
/// Status: STABLE / Known working.
/// @param[in] buf Array of uint16_t containing the shortened GlobalCache data.
/// @param[in] len Nr. of entries in the buf[] array.
/// @note Global Cache format without the emitter ID or request ID.
/// Starts at the frequency (Hertz), followed by nr. of times to emit (count),
/// then the offset for repeats (where a repeat will start from),
/// then the rest of entries are the actual IR message as units of periodic
/// time.
/// e.g. sendir,1:1,1,38000,1,1,9,70,9,30,9,... -> 38000,1,1,9,70,9,30,9,...
/// @see https://irdb.globalcache.com/Home/Database
void IRsend::sendGC(uint16_t buf[], uint16_t len) {
uint16_t hz = buf[kGlobalCacheFreqIndex]; // GC frequency is in Hz.
enableIROut(hz);
uint32_t periodic_time = calcUSecPeriod(hz, false);
uint8_t emits =
std::min(buf[kGlobalCacheRptIndex], (uint16_t)kGlobalCacheMaxRepeat);
// Repeat
for (uint8_t repeat = 0; repeat < emits; repeat++) {
// First time through, start at the beginning (kGlobalCacheStartIndex),
// otherwise for repeats, we start a specified offset from that.
uint16_t offset = kGlobalCacheStartIndex;
if (repeat) offset += buf[kGlobalCacheRptStartIndex] - 1;
// Data
for (; offset < len; offset++) {
// Convert periodic units to microseconds.
// Minimum is kGlobalCacheMinUsec for actual GC units.
uint32_t microseconds =
std::max(buf[offset] * periodic_time, kGlobalCacheMinUsec);
// These codes start at an odd index (not even as with sendRaw).
if (offset & 1) // Odd bit.
mark(microseconds);
else // Even bit.
space(microseconds);
}
}
// It's possible that we've ended on a mark(), thus ensure the LED is off.
ledOff();
}
#endif

View File

@@ -0,0 +1,499 @@
// Copyright 2019 ribeirodanielf
// Copyright 2019 David Conran
/// @file
/// @brief Support for Goodweather compatible HVAC protocols.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/697
#include "ir_Goodweather.h"
#include <algorithm>
#ifndef ARDUINO
#include <string>
#endif
#include "IRrecv.h"
#include "IRremoteESP8266.h"
#include "IRsend.h"
#include "IRtext.h"
#include "IRutils.h"
using irutils::addBoolToString;
using irutils::addIntToString;
using irutils::addLabeledString;
using irutils::addModeToString;
using irutils::addFanToString;
using irutils::addTempToString;
using irutils::addToggleToString;
#if SEND_GOODWEATHER
/// Send a Goodweather HVAC formatted message.
/// Status: BETA / Needs testing on real device.
/// @param[in] data The message to be sent.
/// @param[in] nbits The number of bits of message to be sent.
/// @param[in] repeat The number of times the command is to be repeated.
void IRsend::sendGoodweather(const uint64_t data, const uint16_t nbits,
const uint16_t repeat) {
if (nbits != kGoodweatherBits)
return; // Wrong nr. of bits to send a proper message.
// Set IR carrier frequency
enableIROut(38);
for (uint16_t r = 0; r <= repeat; r++) {
// Header
mark(kGoodweatherHdrMark);
space(kGoodweatherHdrSpace);
// Data
for (int16_t i = 0; i < nbits; i += 8) {
uint16_t chunk = (data >> i) & 0xFF; // Grab a byte at a time.
chunk = (~chunk) << 8 | chunk; // Prepend a inverted copy of the byte.
sendData(kGoodweatherBitMark, kGoodweatherOneSpace,
kGoodweatherBitMark, kGoodweatherZeroSpace,
chunk, 16, false);
}
// Footer
mark(kGoodweatherBitMark);
space(kGoodweatherHdrSpace);
mark(kGoodweatherBitMark);
space(kDefaultMessageGap);
}
}
#endif // SEND_GOODWEATHER
/// 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?
IRGoodweatherAc::IRGoodweatherAc(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 IRGoodweatherAc::stateReset(void) { _.raw = kGoodweatherStateInit; }
/// Set up hardware to be able to send a message.
void IRGoodweatherAc::begin(void) { _irsend.begin(); }
#if SEND_GOODWEATHER
/// Send the current internal state as an IR message.
/// @param[in] repeat Nr. of times the message will be repeated.
void IRGoodweatherAc::send(const uint16_t repeat) {
_irsend.sendGoodweather(getRaw(), kGoodweatherBits, repeat);
}
#endif // SEND_GOODWEATHER
/// Get a copy of the internal state as a valid code for this protocol.
/// @return A valid code for this protocol based on the current internal state.
uint64_t IRGoodweatherAc::getRaw(void) { return _.raw; }
/// Set the internal state from a valid code for this protocol.
/// @param[in] state A valid code for this protocol.
void IRGoodweatherAc::setRaw(const uint64_t state) { _.raw = state; }
/// Change the power setting to On.
void IRGoodweatherAc::on(void) { setPower(true); }
/// Change the power setting to Off.
void IRGoodweatherAc::off(void) { setPower(false); }
/// Change the power setting.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRGoodweatherAc::setPower(const bool on) {
_.Command = kGoodweatherCmdPower;
_.Power = on;
}
/// Get the value of the current power setting.
/// @return true, the setting is on. false, the setting is off.
bool IRGoodweatherAc::getPower(void) const {
return _.Power;
}
/// Set the temperature.
/// @param[in] temp The temperature in degrees celsius.
void IRGoodweatherAc::setTemp(const uint8_t temp) {
uint8_t new_temp = std::max(kGoodweatherTempMin, temp);
new_temp = std::min(kGoodweatherTempMax, new_temp);
if (new_temp > getTemp()) _.Command = kGoodweatherCmdUpTemp;
if (new_temp < getTemp()) _.Command = kGoodweatherCmdDownTemp;
_.Temp = new_temp - kGoodweatherTempMin;
}
/// Get the current temperature setting.
/// @return The current setting for temp. in degrees celsius.
uint8_t IRGoodweatherAc::getTemp(void) const {
return _.Temp + kGoodweatherTempMin;
}
/// Set the speed of the fan.
/// @param[in] speed The desired setting.
void IRGoodweatherAc::setFan(const uint8_t speed) {
_.Command = kGoodweatherCmdFan;
switch (speed) {
case kGoodweatherFanAuto:
case kGoodweatherFanLow:
case kGoodweatherFanMed:
case kGoodweatherFanHigh:
_.Fan = speed;
break;
default:
_.Fan = kGoodweatherFanAuto;
}
}
/// Get the current fan speed setting.
/// @return The current fan speed.
uint8_t IRGoodweatherAc::getFan(void) const {
return _.Fan;
}
/// Set the operating mode of the A/C.
/// @param[in] mode The desired operating mode.
void IRGoodweatherAc::setMode(const uint8_t mode) {
_.Command = kGoodweatherCmdMode;
switch (mode) {
case kGoodweatherAuto:
case kGoodweatherDry:
case kGoodweatherCool:
case kGoodweatherFan:
case kGoodweatherHeat:
_.Mode = mode;
break;
default:
_.Mode = kGoodweatherAuto;
}
}
/// Get the operating mode setting of the A/C.
/// @return The current operating mode setting.
uint8_t IRGoodweatherAc::getMode(void) const {
return _.Mode;
}
/// Set the Light (LED) Toggle setting of the A/C.
/// @param[in] toggle true, the setting is on. false, the setting is off.
void IRGoodweatherAc::setLight(const bool toggle) {
_.Command = kGoodweatherCmdLight;
_.Light = toggle;
}
/// Get the Light (LED) Toggle setting of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRGoodweatherAc::getLight(void) const {
return _.Light;
}
/// Set the Sleep Toggle setting of the A/C.
/// @param[in] toggle true, the setting is on. false, the setting is off.
void IRGoodweatherAc::setSleep(const bool toggle) {
_.Command = kGoodweatherCmdSleep;
_.Sleep = toggle;
}
/// Get the Sleep Toggle setting of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRGoodweatherAc::getSleep(void) const {
return _.Sleep;
}
/// Set the Turbo Toggle setting of the A/C.
/// @param[in] toggle true, the setting is on. false, the setting is off.
void IRGoodweatherAc::setTurbo(const bool toggle) {
_.Command = kGoodweatherCmdTurbo;
_.Turbo = toggle;
}
/// Get the Turbo Toggle setting of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRGoodweatherAc::getTurbo(void) const {
return _.Turbo;
}
/// Set the Vertical Swing speed of the A/C.
/// @param[in] speed The speed to set the swing to.
void IRGoodweatherAc::setSwing(const uint8_t speed) {
_.Command = kGoodweatherCmdSwing;
switch (speed) {
case kGoodweatherSwingOff:
case kGoodweatherSwingSlow:
case kGoodweatherSwingFast:
_.Swing = speed;
break;
default:
_.Swing = kGoodweatherSwingOff;
}
}
/// Get the Vertical Swing speed of the A/C.
/// @return The native swing speed setting.
uint8_t IRGoodweatherAc::getSwing(void) const {
return _.Swing;
}
/// Set the remote Command type/button pressed.
/// @param[in] cmd The command/button that was issued/pressed.
void IRGoodweatherAc::setCommand(const uint8_t cmd) {
if (cmd <= kGoodweatherCmdLight)
_.Command = cmd;
}
/// Get the Command type/button pressed from the current settings
/// @return The command/button that was issued/pressed.
uint8_t IRGoodweatherAc::getCommand(void) const {
return _.Command;
}
/// Convert a stdAc::opmode_t enum into its native mode.
/// @param[in] mode The enum to be converted.
/// @return The native equivalent of the enum.
uint8_t IRGoodweatherAc::convertMode(const stdAc::opmode_t mode) {
switch (mode) {
case stdAc::opmode_t::kCool: return kGoodweatherCool;
case stdAc::opmode_t::kHeat: return kGoodweatherHeat;
case stdAc::opmode_t::kDry: return kGoodweatherDry;
case stdAc::opmode_t::kFan: return kGoodweatherFan;
default: return kGoodweatherAuto;
}
}
/// Convert a stdAc::fanspeed_t enum into it's native speed.
/// @param[in] speed The enum to be converted.
/// @return The native equivalent of the enum.
uint8_t IRGoodweatherAc::convertFan(const stdAc::fanspeed_t speed) {
switch (speed) {
case stdAc::fanspeed_t::kMin:
case stdAc::fanspeed_t::kLow: return kGoodweatherFanLow;
case stdAc::fanspeed_t::kMedium: return kGoodweatherFanMed;
case stdAc::fanspeed_t::kHigh:
case stdAc::fanspeed_t::kMax: return kGoodweatherFanHigh;
default: return kGoodweatherFanAuto;
}
}
/// Convert a stdAc::swingv_t enum into it's native setting.
/// @param[in] swingv The enum to be converted.
/// @return The native equivalent of the enum.
uint8_t IRGoodweatherAc::convertSwingV(const stdAc::swingv_t swingv) {
switch (swingv) {
case stdAc::swingv_t::kHighest:
case stdAc::swingv_t::kHigh:
case stdAc::swingv_t::kMiddle: return kGoodweatherSwingFast;
case stdAc::swingv_t::kLow:
case stdAc::swingv_t::kLowest:
case stdAc::swingv_t::kAuto: return kGoodweatherSwingSlow;
default: return kGoodweatherSwingOff;
}
}
/// Convert a native mode into its stdAc equivalent.
/// @param[in] mode The native setting to be converted.
/// @return The stdAc equivalent of the native setting.
stdAc::opmode_t IRGoodweatherAc::toCommonMode(const uint8_t mode) {
switch (mode) {
case kGoodweatherCool: return stdAc::opmode_t::kCool;
case kGoodweatherHeat: return stdAc::opmode_t::kHeat;
case kGoodweatherDry: return stdAc::opmode_t::kDry;
case kGoodweatherFan: return stdAc::opmode_t::kFan;
default: return stdAc::opmode_t::kAuto;
}
}
/// Convert a native fan speed into its stdAc equivalent.
/// @param[in] speed The native setting to be converted.
/// @return The stdAc equivalent of the native setting.
stdAc::fanspeed_t IRGoodweatherAc::toCommonFanSpeed(const uint8_t speed) {
switch (speed) {
case kGoodweatherFanHigh: return stdAc::fanspeed_t::kMax;
case kGoodweatherFanMed: return stdAc::fanspeed_t::kMedium;
case kGoodweatherFanLow: return stdAc::fanspeed_t::kMin;
default: return stdAc::fanspeed_t::kAuto;
}
}
/// Convert the current internal state into its stdAc::state_t equivalent.
/// @return The stdAc equivalent of the native settings.
stdAc::state_t IRGoodweatherAc::toCommon(void) const {
stdAc::state_t result{};
result.protocol = decode_type_t::GOODWEATHER;
result.power = _.Power;
result.mode = toCommonMode(_.Mode);
result.celsius = true;
result.degrees = getTemp();
result.fanspeed = toCommonFanSpeed(_.Fan);
result.swingv = (_.Swing == kGoodweatherSwingOff ?
stdAc::swingv_t::kOff : stdAc::swingv_t::kAuto);
result.turbo = _.Turbo;
result.light = _.Light;
result.sleep = _.Sleep ? 0: -1;
// Not supported.
result.model = -1;
result.swingh = stdAc::swingh_t::kOff;
result.quiet = false;
result.econo = false;
result.filter = false;
result.clean = 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 IRGoodweatherAc::toString(void) const {
String result = "";
result.reserve(150); // Reserve some heap for the string to reduce fragging.
result += addBoolToString(_.Power, kPowerStr, false);
result += addModeToString(_.Mode, kGoodweatherAuto, kGoodweatherCool,
kGoodweatherHeat, kGoodweatherDry, kGoodweatherFan);
result += addTempToString(getTemp());
result += addFanToString(_.Fan, kGoodweatherFanHigh, kGoodweatherFanLow,
kGoodweatherFanAuto, kGoodweatherFanAuto,
kGoodweatherFanMed);
result += addToggleToString(_.Turbo, kTurboStr);
result += addToggleToString(_.Light, kLightStr);
result += addToggleToString(_.Sleep, kSleepStr);
result += addIntToString(_.Swing, kSwingStr);
result += kSpaceLBraceStr;
switch (_.Swing) {
case kGoodweatherSwingFast:
result += kFastStr;
break;
case kGoodweatherSwingSlow:
result += kSlowStr;
break;
case kGoodweatherSwingOff:
result += kOffStr;
break;
default:
result += kUnknownStr;
}
result += ')';
result += addIntToString(_.Command, kCommandStr);
result += kSpaceLBraceStr;
switch (_.Command) {
case kGoodweatherCmdPower:
result += kPowerStr;
break;
case kGoodweatherCmdMode:
result += kModeStr;
break;
case kGoodweatherCmdUpTemp:
result += kTempUpStr;
break;
case kGoodweatherCmdDownTemp:
result += kTempDownStr;
break;
case kGoodweatherCmdSwing:
result += kSwingStr;
break;
case kGoodweatherCmdFan:
result += kFanStr;
break;
case kGoodweatherCmdTimer:
result += kTimerStr;
break;
case kGoodweatherCmdAirFlow:
result += kAirFlowStr;
break;
case kGoodweatherCmdHold:
result += kHoldStr;
break;
case kGoodweatherCmdSleep:
result += kSleepStr;
break;
case kGoodweatherCmdTurbo:
result += kTurboStr;
break;
case kGoodweatherCmdLight:
result += kLightStr;
break;
default:
result += kUnknownStr;
}
result += ')';
return result;
}
#if DECODE_GOODWEATHER
/// Decode the supplied Goodweather message.
/// Status: BETA / Probably works.
/// @param[in,out] results Ptr to the data to decode & where to store the decode
/// result.
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// @param[in] strict Flag indicating if we should perform strict matching.
/// @return A boolean. True if it can decode it, false if it can't.
bool IRrecv::decodeGoodweather(decode_results* results, uint16_t offset,
const uint16_t nbits,
const bool strict) {
if (results->rawlen < 2 * (2 * nbits) + kHeader + 2 * kFooter - 1 + offset)
return false; // Can't possibly be a valid Goodweather message.
if (strict && nbits != kGoodweatherBits)
return false; // Not strictly a Goodweather message.
uint64_t dataSoFar = 0;
uint16_t dataBitsSoFar = 0;
match_result_t data_result;
// Header
if (!matchMark(results->rawbuf[offset++], kGoodweatherHdrMark)) return false;
if (!matchSpace(results->rawbuf[offset++], kGoodweatherHdrSpace))
return false;
// Data
for (; offset <= results->rawlen - 32 && dataBitsSoFar < nbits;
dataBitsSoFar += 8) {
DPRINT("DEBUG: Attempting Byte #");
DPRINTLN(dataBitsSoFar / 8);
// Read in a byte at a time.
// Normal first.
data_result = matchData(&(results->rawbuf[offset]), 8,
kGoodweatherBitMark, kGoodweatherOneSpace,
kGoodweatherBitMark, kGoodweatherZeroSpace,
_tolerance + kGoodweatherExtraTolerance,
kMarkExcess, false);
if (data_result.success == false) return false;
DPRINTLN("DEBUG: Normal byte read okay.");
offset += data_result.used;
uint8_t data = (uint8_t)data_result.data;
// Then inverted.
data_result = matchData(&(results->rawbuf[offset]), 8,
kGoodweatherBitMark, kGoodweatherOneSpace,
kGoodweatherBitMark, kGoodweatherZeroSpace,
_tolerance + kGoodweatherExtraTolerance,
kMarkExcess, false);
if (data_result.success == false) return false;
DPRINTLN("DEBUG: Inverted byte read okay.");
offset += data_result.used;
uint8_t inverted = (uint8_t)data_result.data;
DPRINT("DEBUG: data = ");
DPRINTLN((uint16_t)data);
DPRINT("DEBUG: inverted = ");
DPRINTLN((uint16_t)inverted);
if (data != (inverted ^ 0xFF)) return false; // Data integrity failed.
dataSoFar |= (uint64_t)data << dataBitsSoFar;
}
// Footer.
if (!matchMark(results->rawbuf[offset++], kGoodweatherBitMark,
_tolerance + kGoodweatherExtraTolerance)) return false;
if (!matchSpace(results->rawbuf[offset++], kGoodweatherHdrSpace))
return false;
if (!matchMark(results->rawbuf[offset++], kGoodweatherBitMark,
_tolerance + kGoodweatherExtraTolerance)) return false;
if (offset <= results->rawlen &&
!matchAtLeast(results->rawbuf[offset], kGoodweatherHdrSpace))
return false;
// Compliance
if (strict && (dataBitsSoFar != kGoodweatherBits)) return false;
// Success
results->decode_type = decode_type_t::GOODWEATHER;
results->bits = dataBitsSoFar;
results->value = dataSoFar;
results->address = 0;
results->command = 0;
return true;
}
#endif // DECODE_GOODWEATHER

View File

@@ -0,0 +1,154 @@
// Copyright 2019 ribeirodanielf
// Copyright 2019 David Conran
/// @file
/// @brief Support for Goodweather compatible HVAC protocols.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/697
// Supports:
// Brand: Goodweather, Model: ZH/JT-03 remote
#ifndef IR_GOODWEATHER_H_
#define IR_GOODWEATHER_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
/// Native representation of a Goodweather A/C message.
union GoodweatherProtocol {
uint64_t raw; ///< The state of the IR remote in IR code form.
struct {
// Byte 0
uint8_t :8;
// Byte 1
uint8_t Light :1;
uint8_t :2;
uint8_t Turbo :1;
uint8_t :0;
// Byte 2
uint8_t Command :4;
uint8_t :0;
// Byte 3
uint8_t Sleep :1;
uint8_t Power :1;
uint8_t Swing :2;
uint8_t AirFlow :1;
uint8_t Fan :2;
uint8_t :0;
// Byte 4
uint8_t Temp :4;
uint8_t :1;
uint8_t Mode :3;
uint8_t :0;
};
};
// Constants
// Timing
const uint16_t kGoodweatherBitMark = 580;
const uint16_t kGoodweatherOneSpace = 580;
const uint16_t kGoodweatherZeroSpace = 1860;
const uint16_t kGoodweatherHdrMark = 6820;
const uint16_t kGoodweatherHdrSpace = 6820;
const uint8_t kGoodweatherExtraTolerance = 12; // +12% extra
// Modes
const uint8_t kGoodweatherAuto = 0b000;
const uint8_t kGoodweatherCool = 0b001;
const uint8_t kGoodweatherDry = 0b010;
const uint8_t kGoodweatherFan = 0b011;
const uint8_t kGoodweatherHeat = 0b100;
// Swing
const uint8_t kGoodweatherSwingFast = 0b00;
const uint8_t kGoodweatherSwingSlow = 0b01;
const uint8_t kGoodweatherSwingOff = 0b10;
// Fan Control
const uint8_t kGoodweatherFanAuto = 0b00;
const uint8_t kGoodweatherFanHigh = 0b01;
const uint8_t kGoodweatherFanMed = 0b10;
const uint8_t kGoodweatherFanLow = 0b11;
// Temperature
const uint8_t kGoodweatherTempMin = 16; // Celsius
const uint8_t kGoodweatherTempMax = 31; // Celsius
// Commands
const uint8_t kGoodweatherCmdPower = 0x00;
const uint8_t kGoodweatherCmdMode = 0x01;
const uint8_t kGoodweatherCmdUpTemp = 0x02;
const uint8_t kGoodweatherCmdDownTemp = 0x03;
const uint8_t kGoodweatherCmdSwing = 0x04;
const uint8_t kGoodweatherCmdFan = 0x05;
const uint8_t kGoodweatherCmdTimer = 0x06;
const uint8_t kGoodweatherCmdAirFlow = 0x07;
const uint8_t kGoodweatherCmdHold = 0x08;
const uint8_t kGoodweatherCmdSleep = 0x09;
const uint8_t kGoodweatherCmdTurbo = 0x0A;
const uint8_t kGoodweatherCmdLight = 0x0B;
// PAD EOF
const uint64_t kGoodweatherStateInit = 0xD50000000000;
// Classes
/// Class for handling detailed Goodweather A/C messages.
class IRGoodweatherAc {
public:
explicit IRGoodweatherAc(const uint16_t pin, const bool inverted = false,
const bool use_modulation = true);
void stateReset(void);
#if SEND_GOODWEATHER
void send(const uint16_t repeat = kGoodweatherMinRepeat);
/// 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_GOODWEATHER
void begin(void);
void on(void);
void off(void);
void setPower(const bool on);
bool getPower(void) const;
void setTemp(const uint8_t temp);
uint8_t getTemp(void) const;
void setFan(const uint8_t speed);
uint8_t getFan(void) const;
void setMode(const uint8_t mode);
uint8_t getMode(void) const;
void setSwing(const uint8_t speed);
uint8_t getSwing(void) const;
void setSleep(const bool toggle);
bool getSleep(void) const;
void setTurbo(const bool toggle);
bool getTurbo(void) const;
void setLight(const bool toggle);
bool getLight(void) const;
void setCommand(const uint8_t cmd);
uint8_t getCommand(void) const;
uint64_t getRaw(void);
void setRaw(const uint64_t state);
static uint8_t convertMode(const stdAc::opmode_t mode);
static uint8_t convertFan(const stdAc::fanspeed_t speed);
static uint8_t convertSwingV(const stdAc::swingv_t swingv);
static stdAc::opmode_t toCommonMode(const uint8_t mode);
static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed);
stdAc::state_t toCommon(void) const;
String toString(void) const;
#ifndef UNIT_TEST
private:
IRsend _irsend; ///< Instance of the IR send class
#else // UNIT_TEST
/// @cond IGNORE
IRsendTest _irsend; ///< Instance of the testing IR send class
/// @endcond
#endif // UNIT_TEST
GoodweatherProtocol _;
};
#endif // IR_GOODWEATHER_H_

View File

@@ -0,0 +1,71 @@
// Copyright 2022 Mateusz Bronk (mbronk)
/// @file
/// @brief Support for the Gorenje cooker hood IR protocols.
/// @see https://techfresh.pl/wp-content/uploads/2017/08/Gorenje-DKF-2600-MWT.pdf
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1887
// Supports:
// Brand: Gorenje, Model: DKF 2600 MWT Cooker Hood
#include "IRrecv.h"
#include "IRsend.h"
#include "IRutils.h"
const uint32_t kGorenjeMinGap = 100000U; // 0.1s
const uint16_t kGorenjeHdrMark = 0;
const uint32_t kGorenjeHdrSpace = 0;
const uint16_t kGorenjeBitMark = 1300;
const uint32_t kGorenjeOneSpace = 5700;
const uint32_t kGorenjeZeroSpace = 1700;
const uint16_t kGorenjeFreq = 38000; // Hz
const uint16_t kGorenjeTolerance = 7; // %
#if SEND_GORENJE
/// Send a Gorenje Cooker Hood formatted message.
/// Status: STABLE / Known working.
/// @param[in] data containing the IR command to be sent.
/// @param[in] nbits Nr. of bits of the message to send. usually kGorenjeBits
/// @param[in] repeat Nr. of times the message is to be repeated.
void IRsend::sendGorenje(const uint64_t data, const uint16_t nbits,
const uint16_t repeat) {
sendGeneric(kGorenjeHdrMark, kGorenjeHdrSpace,
kGorenjeBitMark, kGorenjeOneSpace,
kGorenjeBitMark, kGorenjeZeroSpace,
kGorenjeBitMark, kGorenjeMinGap,
data, nbits, kGorenjeFreq, true, repeat, kDutyDefault);
}
#endif // SEND_GORENJE
#if DECODE_GORENJE
/// Decode the supplied Gorenje Cooker Hood message.
/// Status: STABLE / Known working.
/// @param[in,out] results Ptr to the data to decode & where to store the
/// decoded result
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// @param[in] strict Flag indicating if we should perform strict matching.
/// @return A boolean. True if it can decode it, false if it can't.
bool IRrecv::decodeGorenje(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
if (strict && nbits != kGorenjeBits)
return false; // We expect Gorenje to be a certain sized message.
uint64_t data = 0;
if (!matchGeneric(results->rawbuf + offset, &data,
results->rawlen - offset, nbits,
kGorenjeHdrMark, kGorenjeHdrSpace,
kGorenjeBitMark, kGorenjeOneSpace,
kGorenjeBitMark, kGorenjeZeroSpace,
kGorenjeBitMark, kGorenjeMinGap,
true, kGorenjeTolerance)) return false;
// Matched!
results->bits = nbits;
results->value = data;
results->decode_type = decode_type_t::GORENJE;
results->command = 0;
results->address = 0;
return true;
}
#endif // DECODE_GORENJE

View File

@@ -0,0 +1,750 @@
// Copyright 2017 Ville Skyttä (scop)
// Copyright 2017, 2018 David Conran
/// @file
/// @brief Support for Gree A/C protocols.
/// @see https://github.com/ToniA/arduino-heatpumpir/blob/master/GreeHeatpumpIR.h
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1508
#include "ir_Gree.h"
#include <algorithm>
#include <cstring>
#ifndef ARDUINO
#include <string>
#endif
#include "IRrecv.h"
#include "IRremoteESP8266.h"
#include "IRsend.h"
#include "IRtext.h"
#include "IRutils.h"
#include "ir_Kelvinator.h"
// Constants
const uint16_t kGreeHdrMark = 9000;
const uint16_t kGreeHdrSpace = 4500; ///< See #684 & real example in unit tests
const uint16_t kGreeBitMark = 620;
const uint16_t kGreeOneSpace = 1600;
const uint16_t kGreeZeroSpace = 540;
const uint16_t kGreeMsgSpace = 19980; ///< See #1508, #386, & Kelvinator
const uint8_t kGreeBlockFooter = 0b010;
const uint8_t kGreeBlockFooterBits = 3;
using irutils::addBoolToString;
using irutils::addIntToString;
using irutils::addLabeledString;
using irutils::addModeToString;
using irutils::addModelToString;
using irutils::addFanToString;
using irutils::addSwingHToString;
using irutils::addTempToString;
using irutils::minsToString;
#if SEND_GREE
/// Send a Gree Heat Pump formatted message.
/// Status: STABLE / Working.
/// @param[in] data The message to be sent.
/// @param[in] nbytes The number of bytes of message to be sent.
/// @param[in] repeat The number of times the command is to be repeated.
void IRsend::sendGree(const uint8_t data[], const uint16_t nbytes,
const uint16_t repeat) {
if (nbytes < kGreeStateLength)
return; // Not enough bytes to send a proper message.
for (uint16_t r = 0; r <= repeat; r++) {
// Block #1
sendGeneric(kGreeHdrMark, kGreeHdrSpace, kGreeBitMark, kGreeOneSpace,
kGreeBitMark, kGreeZeroSpace, 0, 0, // No Footer.
data, 4, 38, false, 0, 50);
// Footer #1
sendGeneric(0, 0, // No Header
kGreeBitMark, kGreeOneSpace, kGreeBitMark, kGreeZeroSpace,
kGreeBitMark, kGreeMsgSpace, 0b010, 3, 38, false, 0, 50);
// Block #2
sendGeneric(0, 0, // No Header for Block #2
kGreeBitMark, kGreeOneSpace, kGreeBitMark, kGreeZeroSpace,
kGreeBitMark, kGreeMsgSpace, data + 4, nbytes - 4, 38, false, 0,
50);
}
}
/// Send a Gree Heat Pump formatted message.
/// Status: STABLE / Working.
/// @param[in] data The message to be sent.
/// @param[in] nbits The number of bits of message to be sent.
/// @param[in] repeat The number of times the command is to be repeated.
void IRsend::sendGree(const uint64_t data, const uint16_t nbits,
const uint16_t repeat) {
if (nbits != kGreeBits)
return; // Wrong nr. of bits to send a proper message.
// Set IR carrier frequency
enableIROut(38);
for (uint16_t r = 0; r <= repeat; r++) {
// Header
mark(kGreeHdrMark);
space(kGreeHdrSpace);
// Data
for (int16_t i = 8; i <= nbits; i += 8) {
sendData(kGreeBitMark, kGreeOneSpace, kGreeBitMark, kGreeZeroSpace,
(data >> (nbits - i)) & 0xFF, 8, false);
if (i == nbits / 2) {
// Send the mid-message Footer.
sendData(kGreeBitMark, kGreeOneSpace, kGreeBitMark, kGreeZeroSpace,
0b010, 3);
mark(kGreeBitMark);
space(kGreeMsgSpace);
}
}
// Footer
mark(kGreeBitMark);
space(kGreeMsgSpace);
}
}
#endif // SEND_GREE
/// Class constructor
/// @param[in] pin GPIO to be used when sending.
/// @param[in] model The enum of the model to be emulated.
/// @param[in] inverted Is the output signal to be inverted?
/// @param[in] use_modulation Is frequency modulation to be used?
IRGreeAC::IRGreeAC(const uint16_t pin, const gree_ac_remote_model_t model,
const bool inverted, const bool use_modulation)
: _irsend(pin, inverted, use_modulation) {
stateReset();
setModel(model);
}
/// Reset the internal state to a fixed known good state.
void IRGreeAC::stateReset(void) {
// This resets to a known-good state to Power Off, Fan Auto, Mode Auto, 25C.
std::memset(_.remote_state, 0, sizeof _.remote_state);
_.Temp = 9; // _.remote_state[1] = 0x09;
_.Light = true; // _.remote_state[2] = 0x20;
_.unknown1 = 5; // _.remote_state[3] = 0x50;
_.unknown2 = 4; // _.remote_state[5] = 0x20;
}
/// Fix up the internal state so it is correct.
/// @note Internal use only.
void IRGreeAC::fixup(void) {
setPower(getPower()); // Redo the power bits as they differ between models.
checksum(); // Calculate the checksums
}
/// Set up hardware to be able to send a message.
void IRGreeAC::begin(void) { _irsend.begin(); }
#if SEND_GREE
/// Send the current internal state as an IR message.
/// @param[in] repeat Nr. of times the message will be repeated.
void IRGreeAC::send(const uint16_t repeat) {
_irsend.sendGree(getRaw(), kGreeStateLength, repeat);
}
#endif // SEND_GREE
/// 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* IRGreeAC::getRaw(void) {
fixup(); // Ensure correct settings before sending.
return _.remote_state;
}
/// Set the internal state from a valid code for this protocol.
/// @param[in] new_code A valid code for this protocol.
void IRGreeAC::setRaw(const uint8_t new_code[]) {
std::memcpy(_.remote_state, new_code, kGreeStateLength);
// We can only detect the difference between models when the power is on.
if (_.Power) {
if (_.ModelA)
_model = gree_ac_remote_model_t::YAW1F;
else
_model = gree_ac_remote_model_t::YBOFB;
}
if (_.Mode == kGreeEcono) _model = gree_ac_remote_model_t::YX1FSF;
}
/// Calculate and set the checksum values for the internal state.
/// @param[in] length The size/length of the state array to fix the checksum of.
void IRGreeAC::checksum(const uint16_t length) {
// Gree uses the same checksum alg. as Kelvinator's block checksum.
_.Sum = IRKelvinatorAC::calcBlockChecksum(_.remote_state, length);
}
/// 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 IRGreeAC::validChecksum(const uint8_t state[], const uint16_t length) {
// Top 4 bits of the last byte in the state is the state's checksum.
return GETBITS8(state[length - 1], kHighNibble, kNibbleSize) ==
IRKelvinatorAC::calcBlockChecksum(state, length);
}
/// Set the model of the A/C to emulate.
/// @param[in] model The enum of the appropriate model.
void IRGreeAC::setModel(const gree_ac_remote_model_t model) {
switch (model) {
case gree_ac_remote_model_t::YAW1F:
case gree_ac_remote_model_t::YBOFB:
case gree_ac_remote_model_t::YX1FSF: _model = model; break;
default: _model = gree_ac_remote_model_t::YAW1F;
}
}
/// Get/Detect the model of the A/C.
/// @return The enum of the compatible model.
gree_ac_remote_model_t IRGreeAC::getModel(void) const { return _model; }
/// Change the power setting to On.
void IRGreeAC::on(void) { setPower(true); }
/// Change the power setting to Off.
void IRGreeAC::off(void) { setPower(false); }
/// Change the power setting.
/// @param[in] on true, the setting is on. false, the setting is off.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/814
void IRGreeAC::setPower(const bool on) {
_.Power = on;
// May not be needed. See #814
_.ModelA = (on && _model == gree_ac_remote_model_t::YAW1F);
}
/// Get the value of the current power setting.
/// @return true, the setting is on. false, the setting is off.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/814
bool IRGreeAC::getPower(void) const {
// See #814. Not checking/requiring: (_.ModelA)
return _.Power;
}
/// Set the default temperature units to use.
/// @param[in] on Use Fahrenheit as the units.
/// true is Fahrenheit, false is Celsius.
void IRGreeAC::setUseFahrenheit(const bool on) { _.UseFahrenheit = on; }
/// Get the default temperature units in use.
/// @return true is Fahrenheit, false is Celsius.
bool IRGreeAC::getUseFahrenheit(void) const { return _.UseFahrenheit; }
/// Set the temp. in degrees
/// @param[in] temp Desired temperature in Degrees.
/// @param[in] fahrenheit Use units of Fahrenheit and set that as units used.
/// false is Celsius (Default), true is Fahrenheit.
/// @note The unit actually works in Celsius with a special optional
/// "extra degree" when sending Fahrenheit.
void IRGreeAC::setTemp(const uint8_t temp, const bool fahrenheit) {
float safecelsius = temp;
if (fahrenheit)
// Covert to F, and add a fudge factor to round to the expected degree.
// Why 0.6 you ask?! Because it works. Ya'd thing 0.5 would be good for
// rounding, but Noooooo!
safecelsius = fahrenheitToCelsius(temp + 0.6);
setUseFahrenheit(fahrenheit); // Set the correct Temp units.
// Make sure we have desired temp in the correct range.
safecelsius = std::max(static_cast<float>(kGreeMinTempC), safecelsius);
safecelsius = std::min(static_cast<float>(kGreeMaxTempC), safecelsius);
// An operating mode of Auto locks the temp to a specific value. Do so.
if (_.Mode == kGreeAuto) safecelsius = 25;
// Set the "main" Celsius degrees.
_.Temp = safecelsius - kGreeMinTempC;
// Deal with the extra degree fahrenheit difference.
_.TempExtraDegreeF = (static_cast<uint8_t>(safecelsius * 2) & 1);
}
/// Get the set temperature
/// @return The temperature in degrees in the current units (C/F) set.
uint8_t IRGreeAC::getTemp(void) const {
uint8_t deg = kGreeMinTempC + _.Temp;
if (_.UseFahrenheit) {
deg = celsiusToFahrenheit(deg);
// Retrieve the "extra" fahrenheit from elsewhere in the code.
if (_.TempExtraDegreeF) deg++;
deg = std::max(deg, kGreeMinTempF); // Cover the fact that 61F is < 16C
}
return deg;
}
/// Set the speed of the fan.
/// @param[in] speed The desired setting. 0 is auto, 1-3 is the speed.
void IRGreeAC::setFan(const uint8_t speed) {
uint8_t fan = std::min(kGreeFanMax, speed); // Bounds check
if (_.Mode == kGreeDry) fan = 1; // DRY mode is always locked to fan 1.
// Set the basic fan values.
_.Fan = fan;
}
/// Get the current fan speed setting.
/// @return The current fan speed.
uint8_t IRGreeAC::getFan(void) const { return _.Fan; }
/// Set the operating mode of the A/C.
/// @param[in] new_mode The desired operating mode.
void IRGreeAC::setMode(const uint8_t new_mode) {
uint8_t mode = new_mode;
switch (mode) {
// AUTO is locked to 25C
case kGreeAuto: setTemp(25); break;
// DRY always sets the fan to 1.
case kGreeDry: setFan(1); break;
case kGreeCool:
case kGreeFan:
case kGreeEcono:
case kGreeHeat: break;
// If we get an unexpected mode, default to AUTO.
default: mode = kGreeAuto;
}
_.Mode = mode;
}
/// Get the operating mode setting of the A/C.
/// @return The current operating mode setting.
uint8_t IRGreeAC::getMode(void) const { return _.Mode; }
/// Set the Light (LED) setting of the A/C.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRGreeAC::setLight(const bool on) { _.Light = on; }
/// Get the Light (LED) setting of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRGreeAC::getLight(void) const { return _.Light; }
/// Set the IFeel setting of the A/C.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRGreeAC::setIFeel(const bool on) { _.IFeel = on; }
/// Get the IFeel setting of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRGreeAC::getIFeel(void) const { return _.IFeel; }
/// Set the Wifi (enabled) setting of the A/C.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRGreeAC::setWiFi(const bool on) { _.WiFi = on; }
/// Get the Wifi (enabled) setting of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRGreeAC::getWiFi(void) const { return _.WiFi; }
/// Set the XFan (Mould) setting of the A/C.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRGreeAC::setXFan(const bool on) { _.Xfan = on; }
/// Get the XFan (Mould) setting of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRGreeAC::getXFan(void) const { return _.Xfan; }
/// Set the Sleep setting of the A/C.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRGreeAC::setSleep(const bool on) { _.Sleep = on; }
/// Get the Sleep setting of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRGreeAC::getSleep(void) const { return _.Sleep; }
/// Set the Turbo setting of the A/C.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRGreeAC::setTurbo(const bool on) { _.Turbo = on; }
/// Get the Turbo setting of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRGreeAC::getTurbo(void) const { return _.Turbo; }
/// Set the Econo setting of the A/C.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRGreeAC::setEcono(const bool on) {
_.Econo = on;
if (on && getModel() == gree_ac_remote_model_t::YX1FSF)
setMode(kGreeEcono);
}
/// Get the Econo setting of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRGreeAC::getEcono(void) const {
return _.Econo || getMode() == kGreeEcono;
}
/// Set the Vertical Swing mode of the A/C.
/// @param[in] automatic Do we use the automatic setting?
/// @param[in] position The position/mode to set the vanes to.
void IRGreeAC::setSwingVertical(const bool automatic, const uint8_t position) {
_.SwingAuto = automatic;
uint8_t new_position = position;
if (!automatic) {
switch (position) {
case kGreeSwingUp:
case kGreeSwingMiddleUp:
case kGreeSwingMiddle:
case kGreeSwingMiddleDown:
case kGreeSwingDown:
break;
default:
new_position = kGreeSwingLastPos;
}
} else {
switch (position) {
case kGreeSwingAuto:
case kGreeSwingDownAuto:
case kGreeSwingMiddleAuto:
case kGreeSwingUpAuto:
break;
default:
new_position = kGreeSwingAuto;
}
}
_.SwingV = new_position;
}
/// Get the Vertical Swing Automatic mode setting of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRGreeAC::getSwingVerticalAuto(void) const { return _.SwingAuto; }
/// Get the Vertical Swing position setting of the A/C.
/// @return The native position/mode.
uint8_t IRGreeAC::getSwingVerticalPosition(void) const { return _.SwingV; }
/// Get the Horizontal Swing position setting of the A/C.
/// @return The native position/mode.
uint8_t IRGreeAC::getSwingHorizontal(void) const { return _.SwingH; }
/// Set the Horizontal Swing mode of the A/C.
/// @param[in] position The position/mode to set the vanes to.
void IRGreeAC::setSwingHorizontal(const uint8_t position) {
if (position <= kGreeSwingHMaxRight)
_.SwingH = position;
else
_.SwingH = kGreeSwingHOff;
}
/// Set the timer enable setting of the A/C.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRGreeAC::setTimerEnabled(const bool on) { _.TimerEnabled = on; }
/// Get the timer enabled setting of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRGreeAC::getTimerEnabled(void) const { return _.TimerEnabled; }
/// Get the timer time value from the A/C.
/// @return The number of minutes the timer is set for.
uint16_t IRGreeAC::getTimer(void) const {
uint16_t hrs = irutils::bcdToUint8((_.TimerTensHr << kNibbleSize) |
_.TimerHours);
return hrs * 60 + (_.TimerHalfHr ? 30 : 0);
}
/// Set the A/C's timer to turn off in X many minutes.
/// @param[in] minutes The number of minutes the timer should be set for.
/// @note Stores time internally in 30 min units.
/// e.g. 5 mins means 0 (& Off), 95 mins is 90 mins (& On). Max is 24 hours.
void IRGreeAC::setTimer(const uint16_t minutes) {
uint16_t mins = std::min(kGreeTimerMax, minutes); // Bounds check.
setTimerEnabled(mins >= 30); // Timer is enabled when >= 30 mins.
uint8_t hours = mins / 60;
// Set the half hour bit.
_.TimerHalfHr = (mins % 60) >= 30;
// Set the "tens" digit of hours.
_.TimerTensHr = hours / 10;
// Set the "units" digit of hours.
_.TimerHours = hours % 10;
}
/// Set temperature display mode.
/// i.e. Internal, External temperature sensing.
/// @param[in] mode The desired temp source to display.
/// @note In order for the A/C unit properly accept these settings. You must
/// cycle (send) in the following order:
/// kGreeDisplayTempOff(0) -> kGreeDisplayTempSet(1) ->
/// kGreeDisplayTempInside(2) ->kGreeDisplayTempOutside(3) ->
/// kGreeDisplayTempOff(0).
/// The unit will no behave correctly if the changes of this setting are sent
/// out of order.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1118#issuecomment-628242152
void IRGreeAC::setDisplayTempSource(const uint8_t mode) {
_.DisplayTemp = mode;
}
/// Get the temperature display mode.
/// i.e. Internal, External temperature sensing.
/// @return The current temp source being displayed.
uint8_t IRGreeAC::getDisplayTempSource(void) const { return _.DisplayTemp; }
/// Convert a stdAc::opmode_t enum into its native mode.
/// @param[in] mode The enum to be converted.
/// @return The native equivalent of the enum.
uint8_t IRGreeAC::convertMode(const stdAc::opmode_t mode) {
switch (mode) {
case stdAc::opmode_t::kCool: return kGreeCool;
case stdAc::opmode_t::kHeat: return kGreeHeat;
case stdAc::opmode_t::kDry: return kGreeDry;
case stdAc::opmode_t::kFan: return kGreeFan;
default: return kGreeAuto;
}
}
/// Convert a stdAc::fanspeed_t enum into it's native speed.
/// @param[in] speed The enum to be converted.
/// @return The native equivalent of the enum.
uint8_t IRGreeAC::convertFan(const stdAc::fanspeed_t speed) {
switch (speed) {
case stdAc::fanspeed_t::kMin: return kGreeFanMin;
case stdAc::fanspeed_t::kLow:
case stdAc::fanspeed_t::kMedium: return kGreeFanMax - 1;
case stdAc::fanspeed_t::kHigh:
case stdAc::fanspeed_t::kMax: return kGreeFanMax;
default: return kGreeFanAuto;
}
}
/// Convert a stdAc::swingv_t enum into it's native setting.
/// @param[in] swingv The enum to be converted.
/// @return The native equivalent of the enum.
uint8_t IRGreeAC::convertSwingV(const stdAc::swingv_t swingv) {
switch (swingv) {
case stdAc::swingv_t::kHighest: return kGreeSwingUp;
case stdAc::swingv_t::kHigh: return kGreeSwingMiddleUp;
case stdAc::swingv_t::kMiddle: return kGreeSwingMiddle;
case stdAc::swingv_t::kLow: return kGreeSwingMiddleDown;
case stdAc::swingv_t::kLowest: return kGreeSwingDown;
default: return kGreeSwingAuto;
}
}
/// Convert a stdAc::swingh_t enum into it's native setting.
/// @param[in] swingh The enum to be converted.
/// @return The native equivalent of the enum.
uint8_t IRGreeAC::convertSwingH(const stdAc::swingh_t swingh) {
switch (swingh) {
case stdAc::swingh_t::kAuto: return kGreeSwingHAuto;
case stdAc::swingh_t::kLeftMax: return kGreeSwingHMaxLeft;
case stdAc::swingh_t::kLeft: return kGreeSwingHLeft;
case stdAc::swingh_t::kMiddle: return kGreeSwingHMiddle;
case stdAc::swingh_t::kRight: return kGreeSwingHRight;
case stdAc::swingh_t::kRightMax: return kGreeSwingHMaxRight;
default: return kGreeSwingHOff;
}
}
/// Convert a native mode into its stdAc equivalent.
/// @param[in] mode The native setting to be converted.
/// @return The stdAc equivalent of the native setting.
stdAc::opmode_t IRGreeAC::toCommonMode(const uint8_t mode) {
switch (mode) {
case kGreeCool: return stdAc::opmode_t::kCool;
case kGreeHeat: return stdAc::opmode_t::kHeat;
case kGreeDry: return stdAc::opmode_t::kDry;
case kGreeFan: return stdAc::opmode_t::kFan;
default: return stdAc::opmode_t::kAuto;
}
}
/// Convert a native fan speed into its stdAc equivalent.
/// @param[in] speed The native setting to be converted.
/// @return The stdAc equivalent of the native setting.
stdAc::fanspeed_t IRGreeAC::toCommonFanSpeed(const uint8_t speed) {
switch (speed) {
case kGreeFanMax: return stdAc::fanspeed_t::kMax;
case kGreeFanMax - 1: return stdAc::fanspeed_t::kMedium;
case kGreeFanMin: return stdAc::fanspeed_t::kMin;
default: return stdAc::fanspeed_t::kAuto;
}
}
/// Convert a native Vertical Swing into its stdAc equivalent.
/// @param[in] pos The native setting to be converted.
/// @return The stdAc equivalent of the native setting.
stdAc::swingv_t IRGreeAC::toCommonSwingV(const uint8_t pos) {
switch (pos) {
case kGreeSwingUp: return stdAc::swingv_t::kHighest;
case kGreeSwingMiddleUp: return stdAc::swingv_t::kHigh;
case kGreeSwingMiddle: return stdAc::swingv_t::kMiddle;
case kGreeSwingMiddleDown: return stdAc::swingv_t::kLow;
case kGreeSwingDown: return stdAc::swingv_t::kLowest;
default: return stdAc::swingv_t::kAuto;
}
}
/// Convert a native Horizontal Swing into its stdAc equivalent.
/// @param[in] pos The native setting to be converted.
/// @return The stdAc equivalent of the native setting.
stdAc::swingh_t IRGreeAC::toCommonSwingH(const uint8_t pos) {
switch (pos) {
case kGreeSwingHAuto: return stdAc::swingh_t::kAuto;
case kGreeSwingHMaxLeft: return stdAc::swingh_t::kLeftMax;
case kGreeSwingHLeft: return stdAc::swingh_t::kLeft;
case kGreeSwingHMiddle: return stdAc::swingh_t::kMiddle;
case kGreeSwingHRight: return stdAc::swingh_t::kRight;
case kGreeSwingHMaxRight: return stdAc::swingh_t::kRightMax;
default: return stdAc::swingh_t::kOff;
}
}
/// Convert the current internal state into its stdAc::state_t equivalent.
/// @return The stdAc equivalent of the native settings.
stdAc::state_t IRGreeAC::toCommon(void) {
stdAc::state_t result{};
result.protocol = decode_type_t::GREE;
result.model = _model;
result.power = _.Power;
result.mode = toCommonMode(_.Mode);
result.celsius = !_.UseFahrenheit;
result.degrees = getTemp();
// no support for Sensor temp.
result.iFeel = getIFeel();
result.fanspeed = toCommonFanSpeed(_.Fan);
if (_.SwingAuto)
result.swingv = stdAc::swingv_t::kAuto;
else
result.swingv = toCommonSwingV(_.SwingV);
result.swingh = toCommonSwingH(_.SwingH);
result.turbo = _.Turbo;
result.econo = getEcono();
result.light = _.Light;
result.clean = _.Xfan;
result.sleep = _.Sleep ? 0 : -1;
// Not supported.
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 IRGreeAC::toString(void) {
String result = "";
result.reserve(220); // Reserve some heap for the string to reduce fragging.
result += addModelToString(decode_type_t::GREE, _model, false);
result += addBoolToString(_.Power, kPowerStr);
if (_model == gree_ac_remote_model_t::YX1FSF && _.Mode == kGreeEcono) {
result += addIntToString(_.Mode, kModeStr);
result += kSpaceLBraceStr;
result += kEconoStr;
result += ')';
} else {
result += addModeToString(_.Mode, kGreeAuto, kGreeCool, kGreeHeat,
kGreeDry, kGreeFan);
}
result += addTempToString(getTemp(), !_.UseFahrenheit);
result += addFanToString(_.Fan, kGreeFanMax, kGreeFanMin, kGreeFanAuto,
kGreeFanAuto, kGreeFanMed);
result += addBoolToString(_.Turbo, kTurboStr);
result += addBoolToString(_.Econo, kEconoStr);
result += addBoolToString(_.IFeel, kIFeelStr);
result += addBoolToString(_.WiFi, kWifiStr);
result += addBoolToString(_.Xfan, kXFanStr);
result += addBoolToString(_.Light, kLightStr);
result += addBoolToString(_.Sleep, kSleepStr);
result += addLabeledString(_.SwingAuto ? kAutoStr : kManualStr,
kSwingVModeStr);
result += addIntToString(_.SwingV, kSwingVStr);
result += kSpaceLBraceStr;
switch (_.SwingV) {
case kGreeSwingLastPos:
result += kLastStr;
break;
case kGreeSwingAuto:
result += kAutoStr;
break;
default: result += kUnknownStr;
}
result += ')';
result += addSwingHToString(_.SwingH, kGreeSwingHAuto, kGreeSwingHMaxLeft,
kGreeSwingHLeft, kGreeSwingHMiddle,
kGreeSwingHRight, kGreeSwingHMaxRight,
kGreeSwingHOff,
// rest are unused.
0xFF, 0xFF, 0xFF, 0xFF);
result += addLabeledString(
_.TimerEnabled ? minsToString(getTimer()) : kOffStr, kTimerStr);
uint8_t src = _.DisplayTemp;
result += addIntToString(src, kDisplayTempStr);
result += kSpaceLBraceStr;
switch (src) {
case kGreeDisplayTempOff:
result += kOffStr;
break;
case kGreeDisplayTempSet:
result += kSetStr;
break;
case kGreeDisplayTempInside:
result += kInsideStr;
break;
case kGreeDisplayTempOutside:
result += kOutsideStr;
break;
default: result += kUnknownStr;
}
result += ')';
return result;
}
#if DECODE_GREE
/// Decode the supplied Gree HVAC message.
/// Status: STABLE / Working.
/// @param[in,out] results Ptr to the data to decode & where to store the decode
/// result.
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// @param[in] strict Flag indicating if we should perform strict matching.
/// @return A boolean. True if it can decode it, false if it can't.
bool IRrecv::decodeGree(decode_results* results, uint16_t offset,
const uint16_t nbits, bool const strict) {
if (results->rawlen <=
2 * (nbits + kGreeBlockFooterBits) + (kHeader + kFooter + 1) - 1 + offset)
return false; // Can't possibly be a valid Gree message.
if (strict && nbits != kGreeBits)
return false; // Not strictly a Gree message.
// There are two blocks back-to-back in a full Gree IR message
// sequence.
uint16_t used;
// Header + Data Block #1 (32 bits)
used = matchGeneric(results->rawbuf + offset, results->state,
results->rawlen - offset, nbits / 2,
kGreeHdrMark, kGreeHdrSpace,
kGreeBitMark, kGreeOneSpace,
kGreeBitMark, kGreeZeroSpace,
0, 0, false,
_tolerance, kMarkExcess, false);
if (used == 0) return false;
offset += used;
// Block #1 footer (3 bits, B010)
match_result_t data_result;
data_result = matchData(&(results->rawbuf[offset]), kGreeBlockFooterBits,
kGreeBitMark, kGreeOneSpace, kGreeBitMark,
kGreeZeroSpace, _tolerance, kMarkExcess, false);
if (data_result.success == false) return false;
if (data_result.data != kGreeBlockFooter) return false;
offset += data_result.used;
// Inter-block gap + Data Block #2 (32 bits) + Footer
if (!matchGeneric(results->rawbuf + offset, results->state + 4,
results->rawlen - offset, nbits / 2,
kGreeBitMark, kGreeMsgSpace,
kGreeBitMark, kGreeOneSpace,
kGreeBitMark, kGreeZeroSpace,
kGreeBitMark, kGreeMsgSpace, true,
_tolerance, kMarkExcess, false)) return false;
// Compliance
if (strict) {
// Verify the message's checksum is correct.
if (!IRGreeAC::validChecksum(results->state)) return false;
}
// Success
results->decode_type = GREE;
results->bits = nbits;
// 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_GREE

View File

@@ -0,0 +1,240 @@
// Copyright 2016-2022 David Conran
/// @file
/// @brief Support for Gree A/C protocols.
/// @see https://github.com/ToniA/arduino-heatpumpir/blob/master/GreeHeatpumpIR.h
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1508
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1821
// Supports:
// Brand: Ultimate, Model: Heat Pump
// Brand: EKOKAI, Model: A/C
// Brand: RusClimate, Model: EACS/I-09HAR_X/N3 A/C
// Brand: RusClimate, Model: YAW1F remote
// Brand: Green, Model: YBOFB remote
// Brand: Green, Model: YBOFB2 remote
// Brand: Gree, Model: YAA1FBF remote
// Brand: Gree, Model: YB1F2F remote
// Brand: Gree, Model: YAN1F1 remote
// Brand: Gree, Model: YX1F2F remote (YX1FSF)
// Brand: Gree, Model: VIR09HP115V1AH A/C
// Brand: Gree, Model: VIR12HP230V1AH A/C
// Brand: Amana, Model: PBC093G00CC A/C
// Brand: Amana, Model: YX1FF remote
// Brand: Cooper & Hunter, Model: YB1F2 remote
// Brand: Cooper & Hunter, Model: CH-S09FTXG A/C
// Brand: Vailland, Model: YACIFB remote
// Brand: Vailland, Model: VAI5-035WNI A/C
// Brand: Soleus Air, Model: window A/C (YX1FSF)
#ifndef IR_GREE_H_
#define IR_GREE_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
/// Native representation of a Gree A/C message.
union GreeProtocol{
uint8_t remote_state[kGreeStateLength]; ///< The state in native IR code form
struct {
// Byte 0
uint8_t Mode :3;
uint8_t Power :1;
uint8_t Fan :2;
uint8_t SwingAuto :1;
uint8_t Sleep :1;
// Byte 1
uint8_t Temp :4;
uint8_t TimerHalfHr :1;
uint8_t TimerTensHr :2;
uint8_t TimerEnabled:1;
// Byte 2
uint8_t TimerHours:4;
uint8_t Turbo :1;
uint8_t Light :1;
uint8_t ModelA :1; // model==YAW1F
uint8_t Xfan :1;
// Byte 3
uint8_t :2;
uint8_t TempExtraDegreeF:1;
uint8_t UseFahrenheit :1;
uint8_t unknown1 :4; // value=0b0101
// Byte 4
uint8_t SwingV :4;
uint8_t SwingH :3;
uint8_t :1;
// Byte 5
uint8_t DisplayTemp :2;
uint8_t IFeel :1;
uint8_t unknown2 :3; // value = 0b100
uint8_t WiFi :1;
uint8_t :1;
// Byte 6
uint8_t :8;
// Byte 7
uint8_t :2;
uint8_t Econo :1;
uint8_t :1;
uint8_t Sum :4;
};
};
// Constants
const uint8_t kGreeAuto = 0;
const uint8_t kGreeCool = 1;
const uint8_t kGreeDry = 2;
const uint8_t kGreeFan = 3;
const uint8_t kGreeHeat = 4;
const uint8_t kGreeEcono = 5;
const uint8_t kGreeFanAuto = 0;
const uint8_t kGreeFanMin = 1;
const uint8_t kGreeFanMed = 2;
const uint8_t kGreeFanMax = 3;
const uint8_t kGreeMinTempC = 16; // Celsius
const uint8_t kGreeMaxTempC = 30; // Celsius
const uint8_t kGreeMinTempF = 61; // Fahrenheit
const uint8_t kGreeMaxTempF = 86; // Fahrenheit
const uint16_t kGreeTimerMax = 24 * 60;
const uint8_t kGreeSwingLastPos = 0b0000; // 0
const uint8_t kGreeSwingAuto = 0b0001; // 1
const uint8_t kGreeSwingUp = 0b0010; // 2
const uint8_t kGreeSwingMiddleUp = 0b0011; // 3
const uint8_t kGreeSwingMiddle = 0b0100; // 4
const uint8_t kGreeSwingMiddleDown = 0b0101; // 5
const uint8_t kGreeSwingDown = 0b0110; // 6
const uint8_t kGreeSwingDownAuto = 0b0111; // 7
const uint8_t kGreeSwingMiddleAuto = 0b1001; // 9
const uint8_t kGreeSwingUpAuto = 0b1011; // 11
const uint8_t kGreeSwingHOff = 0b000; // 0
const uint8_t kGreeSwingHAuto = 0b001; // 1
const uint8_t kGreeSwingHMaxLeft = 0b010; // 2
const uint8_t kGreeSwingHLeft = 0b011; // 3
const uint8_t kGreeSwingHMiddle = 0b100; // 4
const uint8_t kGreeSwingHRight = 0b101; // 5
const uint8_t kGreeSwingHMaxRight = 0b110; // 6
const uint8_t kGreeDisplayTempOff = 0b00; // 0
const uint8_t kGreeDisplayTempSet = 0b01; // 1
const uint8_t kGreeDisplayTempInside = 0b10; // 2
const uint8_t kGreeDisplayTempOutside = 0b11; // 3
// Legacy defines.
#define GREE_AUTO kGreeAuto
#define GREE_COOL kGreeCool
#define GREE_DRY kGreeDry
#define GREE_FAN kGreeFan
#define GREE_HEAT kGreeHeat
#define GREE_MIN_TEMP kGreeMinTempC
#define GREE_MAX_TEMP kGreeMaxTempC
#define GREE_FAN_MAX kGreeFanMax
#define GREE_SWING_LAST_POS kGreeSwingLastPos
#define GREE_SWING_AUTO kGreeSwingAuto
#define GREE_SWING_UP kGreeSwingUp
#define GREE_SWING_MIDDLE_UP kGreeSwingMiddleUp
#define GREE_SWING_MIDDLE kGreeSwingMiddle
#define GREE_SWING_MIDDLE_DOWN kGreeSwingMiddleDown
#define GREE_SWING_DOWN kGreeSwingDown
#define GREE_SWING_DOWN_AUTO kGreeSwingDownAuto
#define GREE_SWING_MIDDLE_AUTO kGreeSwingMiddleAuto
#define GREE_SWING_UP_AUTO kGreeSwingUpAuto
// Classes
/// Class for handling detailed Gree A/C messages.
class IRGreeAC {
public:
explicit IRGreeAC(
const uint16_t pin,
const gree_ac_remote_model_t model = gree_ac_remote_model_t::YAW1F,
const bool inverted = false, const bool use_modulation = true);
void stateReset(void);
#if SEND_GREE
void send(const uint16_t repeat = kGreeDefaultRepeat);
/// 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_GREE
void begin(void);
void on(void);
void off(void);
void setModel(const gree_ac_remote_model_t model);
gree_ac_remote_model_t getModel(void) const;
void setPower(const bool on);
bool getPower(void) const;
void setTemp(const uint8_t temp, const bool fahrenheit = false);
uint8_t getTemp(void) const;
void setUseFahrenheit(const bool on);
bool getUseFahrenheit(void) const;
void setFan(const uint8_t speed);
uint8_t getFan(void) const;
void setMode(const uint8_t new_mode);
uint8_t getMode(void) const;
void setLight(const bool on);
bool getLight(void) const;
void setXFan(const bool on);
bool getXFan(void) const;
void setSleep(const bool on);
bool getSleep(void) const;
void setTurbo(const bool on);
bool getTurbo(void) const;
void setEcono(const bool on);
bool getEcono(void) const;
void setIFeel(const bool on);
bool getIFeel(void) const;
void setWiFi(const bool on);
bool getWiFi(void) const;
void setSwingVertical(const bool automatic, const uint8_t position);
bool getSwingVerticalAuto(void) const;
uint8_t getSwingVerticalPosition(void) const;
void setSwingHorizontal(const uint8_t position);
uint8_t getSwingHorizontal(void) const;
uint16_t getTimer(void) const;
void setTimer(const uint16_t minutes);
void setDisplayTempSource(const uint8_t mode);
uint8_t getDisplayTempSource(void) const;
static uint8_t convertMode(const stdAc::opmode_t mode);
static uint8_t convertFan(const stdAc::fanspeed_t speed);
static uint8_t convertSwingV(const stdAc::swingv_t swingv);
static uint8_t convertSwingH(const stdAc::swingh_t swingh);
static stdAc::opmode_t toCommonMode(const uint8_t mode);
static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed);
static stdAc::swingv_t toCommonSwingV(const uint8_t pos);
static stdAc::swingh_t toCommonSwingH(const uint8_t pos);
stdAc::state_t toCommon(void);
uint8_t* getRaw(void);
void setRaw(const uint8_t new_code[]);
static bool validChecksum(const uint8_t state[],
const uint16_t length = kGreeStateLength);
String toString(void);
#ifndef UNIT_TEST
private:
IRsend _irsend; ///< Instance of the IR send class
#else // UNIT_TEST
/// @cond IGNORE
IRsendTest _irsend; ///< Instance of the testing IR send class
/// @endcond
#endif // UNIT_TEST
GreeProtocol _;
gree_ac_remote_model_t _model;
void checksum(const uint16_t length = kGreeStateLength);
void fixup(void);
void setTimerEnabled(const bool on);
bool getTimerEnabled(void) const;
};
#endif // IR_GREE_H_

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,653 @@
// Copyright 2018-2021 crankyoldgit
/// @file
/// @brief Support for Haier A/C protocols.
/// The specifics of reverse engineering the protocols details:
/// * HSU07-HEA03 by kuzin2006.
/// * YR-W02/HSU-09HMC203 by non7top.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/404
/// @see https://www.dropbox.com/s/mecyib3lhdxc8c6/IR%20data%20reverse%20engineering.xlsx?dl=0
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/485
/// @see https://www.dropbox.com/sh/w0bt7egp0fjger5/AADRFV6Wg4wZskJVdFvzb8Z0a?dl=0&preview=haer2.ods
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1804
// Supports:
// Brand: Haier, Model: HSU07-HEA03 remote (HAIER_AC)
// Brand: Haier, Model: YR-W02 remote (HAIER_AC_YRW02)
// Brand: Haier, Model: HSU-09HMC203 A/C (HAIER_AC_YRW02)
// Brand: Haier, Model: V9014557 M47 8D remote (HAIER_AC176)
// Brand: Mabe, Model: MMI18HDBWCA6MI8 A/C (HAIER_AC176)
// Brand: Mabe, Model: V12843 HJ200223 remote (HAIER_AC176)
// Brand: Daichi, Model: D-H A/C (HAIER_AC176)
// Brand: Haier, Model: KFR-26GW/83@UI-Ge A/C (HAIER_AC160)
#ifndef IR_HAIER_H_
#define IR_HAIER_H_
#ifndef UNIT_TEST
#include <Arduino.h>
#endif
#include "IRremoteESP8266.h"
#include "IRsend.h"
#ifdef UNIT_TEST
#include "IRsend_test.h"
#endif
/// Native representation of a Haier HSU07-HEA03 A/C message.
union HaierProtocol{
///< The state in native IR code form
uint8_t remote_state[kHaierACStateLength];
struct {
// Byte 0
uint8_t Prefix;
// Byte 1
uint8_t Command:4;
uint8_t Temp :4;
// Byte 2
uint8_t CurrHours:5;
uint8_t unknown :1; // value=1
uint8_t SwingV :2;
// Byte 3
uint8_t CurrMins:6;
uint8_t OffTimer:1;
uint8_t OnTimer :1;
// Byte 4
uint8_t OffHours:5;
uint8_t Health :1;
uint8_t :0;
// Byte 5
uint8_t OffMins:6;
uint8_t Fan :2;
// Byte 6
uint8_t OnHours:5;
uint8_t Mode :3;
// Byte 7
uint8_t OnMins:6;
uint8_t Sleep :1;
uint8_t :0;
// Byte 8
uint8_t Sum;
};
};
// Constants
const uint8_t kHaierAcPrefix = 0b10100101;
const uint8_t kHaierAcMinTemp = 16;
const uint8_t kHaierAcDefTemp = 25;
const uint8_t kHaierAcMaxTemp = 30;
const uint8_t kHaierAcCmdOff = 0b0000;
const uint8_t kHaierAcCmdOn = 0b0001;
const uint8_t kHaierAcCmdMode = 0b0010;
const uint8_t kHaierAcCmdFan = 0b0011;
const uint8_t kHaierAcCmdTempUp = 0b0110;
const uint8_t kHaierAcCmdTempDown = 0b0111;
const uint8_t kHaierAcCmdSleep = 0b1000;
const uint8_t kHaierAcCmdTimerSet = 0b1001;
const uint8_t kHaierAcCmdTimerCancel = 0b1010;
const uint8_t kHaierAcCmdHealth = 0b1100;
const uint8_t kHaierAcCmdSwing = 0b1101;
const uint8_t kHaierAcSwingVOff = 0b00;
const uint8_t kHaierAcSwingVUp = 0b01;
const uint8_t kHaierAcSwingVDown = 0b10;
const uint8_t kHaierAcSwingVChg = 0b11;
const uint8_t kHaierAcAuto = 0;
const uint8_t kHaierAcCool = 1;
const uint8_t kHaierAcDry = 2;
const uint8_t kHaierAcHeat = 3;
const uint8_t kHaierAcFan = 4;
const uint8_t kHaierAcFanAuto = 0;
const uint8_t kHaierAcFanLow = 1;
const uint8_t kHaierAcFanMed = 2;
const uint8_t kHaierAcFanHigh = 3;
const uint16_t kHaierAcMaxTime = (23 * 60) + 59;
const uint8_t kHaierAcSleepBit = 0b01000000;
// Legacy Haier AC defines.
#define HAIER_AC_MIN_TEMP kHaierAcMinTemp
#define HAIER_AC_DEF_TEMP kHaierAcDefTemp
#define HAIER_AC_MAX_TEMP kHaierAcMaxTemp
#define HAIER_AC_CMD_OFF kHaierAcCmdOff
#define HAIER_AC_CMD_ON kHaierAcCmdOn
#define HAIER_AC_CMD_MODE kHaierAcCmdMode
#define HAIER_AC_CMD_FAN kHaierAcCmdFan
#define HAIER_AC_CMD_TEMP_UP kHaierAcCmdTempUp
#define HAIER_AC_CMD_TEMP_DOWN kHaierAcCmdTempDown
#define HAIER_AC_CMD_SLEEP kHaierAcCmdSleep
#define HAIER_AC_CMD_TIMER_SET kHaierAcCmdTimerSet
#define HAIER_AC_CMD_TIMER_CANCEL kHaierAcCmdTimerCancel
#define HAIER_AC_CMD_HEALTH kHaierAcCmdHealth
#define HAIER_AC_CMD_SWINGV kHaierAcCmdSwing
#define HAIER_AC_SWINGV_OFF kHaierAcSwingVOff
#define HAIER_AC_SWINGV_UP kHaierAcSwingVUp
#define HAIER_AC_SWINGV_DOWN kHaierAcSwingVDown
#define HAIER_AC_SWINGV_CHG kHaierAcSwingVChg
#define HAIER_AC_AUTO kHaierAcAuto
#define HAIER_AC_COOL kHaierAcCool
#define HAIER_AC_DRY kHaierAcDry
#define HAIER_AC_HEAT kHaierAcHeat
#define HAIER_AC_FAN kHaierAcFan
#define HAIER_AC_FAN_AUTO kHaierAcFanAuto
#define HAIER_AC_FAN_LOW kHaierAcFanLow
#define HAIER_AC_FAN_MED kHaierAcFanMed
#define HAIER_AC_FAN_HIGH kHaierAcFanHigh
const uint8_t kHaierAcYrw02MinTempC = 16;
const uint8_t kHaierAcYrw02MaxTempC = 30;
const uint8_t kHaierAcYrw02MinTempF = 60;
const uint8_t kHaierAcYrw02MaxTempF = 86;
const uint8_t kHaierAcYrw02DefTempC = 25;
const uint8_t kHaierAcYrw02ModelA = 0xA6;
const uint8_t kHaierAcYrw02ModelB = 0x59;
const uint8_t kHaierAc176Prefix = 0xB7;
const uint8_t kHaierAc160Prefix = 0xB5;
const uint8_t kHaierAcYrw02SwingVOff = 0x0;
const uint8_t kHaierAcYrw02SwingVTop = 0x1;
const uint8_t kHaierAcYrw02SwingVMiddle = 0x2; // Not available in heat mode.
const uint8_t kHaierAcYrw02SwingVBottom = 0x3; // Only available in heat mode.
const uint8_t kHaierAcYrw02SwingVDown = 0xA;
const uint8_t kHaierAcYrw02SwingVAuto = 0xC; // Airflow
const uint8_t kHaierAc160SwingVOff = 0b0000;
const uint8_t kHaierAc160SwingVTop = 0b0001;
const uint8_t kHaierAc160SwingVHighest = 0b0010;
const uint8_t kHaierAc160SwingVHigh = 0b0100;
const uint8_t kHaierAc160SwingVMiddle = 0b0110;
const uint8_t kHaierAc160SwingVLow = 0b1000;
const uint8_t kHaierAc160SwingVLowest = 0b0011;
const uint8_t kHaierAc160SwingVAuto = 0b1100; // Airflow
const uint8_t kHaierAcYrw02SwingHMiddle = 0x0;
const uint8_t kHaierAcYrw02SwingHLeftMax = 0x3;
const uint8_t kHaierAcYrw02SwingHLeft = 0x4;
const uint8_t kHaierAcYrw02SwingHRight = 0x5;
const uint8_t kHaierAcYrw02SwingHRightMax = 0x6;
const uint8_t kHaierAcYrw02SwingHAuto = 0x7;
const uint8_t kHaierAcYrw02FanHigh = 0b001;
const uint8_t kHaierAcYrw02FanMed = 0b010;
const uint8_t kHaierAcYrw02FanLow = 0b011;
const uint8_t kHaierAcYrw02FanAuto = 0b101; // HAIER_AC176 uses `0` in Fan2
const uint8_t kHaierAcYrw02Auto = 0b000; // 0
const uint8_t kHaierAcYrw02Cool = 0b001; // 1
const uint8_t kHaierAcYrw02Dry = 0b010; // 2
const uint8_t kHaierAcYrw02Heat = 0b100; // 4
const uint8_t kHaierAcYrw02Fan = 0b110; // 5
const uint8_t kHaierAcYrw02ButtonTempUp = 0b00000;
const uint8_t kHaierAcYrw02ButtonTempDown = 0b00001;
const uint8_t kHaierAcYrw02ButtonSwingV = 0b00010;
const uint8_t kHaierAcYrw02ButtonSwingH = 0b00011;
const uint8_t kHaierAcYrw02ButtonFan = 0b00100;
const uint8_t kHaierAcYrw02ButtonPower = 0b00101;
const uint8_t kHaierAcYrw02ButtonMode = 0b00110;
const uint8_t kHaierAcYrw02ButtonHealth = 0b00111;
const uint8_t kHaierAcYrw02ButtonTurbo = 0b01000;
const uint8_t kHaierAcYrw02ButtonSleep = 0b01011;
const uint8_t kHaierAcYrw02ButtonTimer = 0b10000;
const uint8_t kHaierAcYrw02ButtonLock = 0b10100;
const uint8_t kHaierAc160ButtonLight = 0b10101;
const uint8_t kHaierAc160ButtonAuxHeating = 0b10110;
const uint8_t kHaierAc160ButtonClean = 0b11001;
const uint8_t kHaierAcYrw02ButtonCFAB = 0b11010;
const uint8_t kHaierAcYrw02NoTimers = 0b000;
const uint8_t kHaierAcYrw02OffTimer = 0b001;
const uint8_t kHaierAcYrw02OnTimer = 0b010;
const uint8_t kHaierAcYrw02OnThenOffTimer = 0b100;
const uint8_t kHaierAcYrw02OffThenOnTimer = 0b101;
/// Native representation of a Haier 176 bit A/C message.
union HaierAc176Protocol{
uint8_t raw[kHaierAC176StateLength]; ///< The state in native form
struct {
// Byte 0
uint8_t Model :8;
// Byte 1
uint8_t SwingV :4;
uint8_t Temp :4; // 16C~30C
// Byte 2
uint8_t :5;
uint8_t SwingH :3;
// Byte 3
uint8_t :1;
uint8_t Health :1;
uint8_t :3;
uint8_t TimerMode :3;
// Byte 4
uint8_t :6;
uint8_t Power :1;
uint8_t :1;
// Byte 5
uint8_t OffTimerHrs :5;
uint8_t Fan :3;
// Byte 6
uint8_t OffTimerMins:6;
uint8_t Turbo :1;
uint8_t Quiet :1;
// Byte 7
uint8_t OnTimerHrs :5;
uint8_t Mode :3;
// Byte 8
uint8_t OnTimerMins :6;
uint8_t :1;
uint8_t Sleep :1;
// Byte 9
uint8_t :8;
// Byte 10
uint8_t ExtraDegreeF :1;
uint8_t :4;
uint8_t UseFahrenheit:1;
uint8_t :2;
// Byte 11
uint8_t :8;
// Byte 12
uint8_t Button :5;
uint8_t Lock :1;
uint8_t :2;
// Byte 13
uint8_t Sum :8;
// Byte 14
uint8_t Prefix2 :8;
// Byte 15
uint8_t :8;
// Byte 16
uint8_t :6;
uint8_t Fan2 :2;
// Byte 17
uint8_t :8;
// Byte 18
uint8_t :8;
// Byte 19
uint8_t :8;
// Byte 20
uint8_t :8;
// Byte 21
uint8_t Sum2 :8;
};
};
/// Native representation of a Haier 160 bit A/C message.
union HaierAc160Protocol{
uint8_t raw[kHaierAC160StateLength]; ///< The state in native form
struct {
// Byte 0
uint8_t Model :8;
// Byte 1
uint8_t SwingV :4;
uint8_t Temp :4; // 16C~30C
// Byte 2
uint8_t :5;
uint8_t SwingH :3;
// Byte 3
uint8_t :1;
uint8_t Health :1;
uint8_t :3;
uint8_t TimerMode :3;
// Byte 4
uint8_t :6;
uint8_t Power :1;
uint8_t AuxHeating :1;
// Byte 5
uint8_t OffTimerHrs :5;
uint8_t Fan :3;
// Byte 6
uint8_t OffTimerMins:6;
uint8_t Turbo :1;
uint8_t Quiet :1;
// Byte 7
uint8_t OnTimerHrs :5;
uint8_t Mode :3;
// Byte 8
uint8_t OnTimerMins :6;
uint8_t :1;
uint8_t Sleep :1;
// Byte 9
uint8_t :8;
// Byte 10
uint8_t ExtraDegreeF :1;
uint8_t :3;
uint8_t Clean :1;
uint8_t UseFahrenheit:1;
uint8_t :2;
// Byte 11
uint8_t :8;
// Byte 12
uint8_t Button :5;
uint8_t Lock :1;
uint8_t :2;
// Byte 13
uint8_t Sum :8;
// Byte 14
uint8_t Prefix :8;
// Byte 15
uint8_t :6;
uint8_t Clean2 :1;
uint8_t :1;
// Byte 16
uint8_t :5;
uint8_t Fan2 :3;
// Byte 17
uint8_t :8;
// Byte 18
uint8_t :8;
// Byte 19
uint8_t Sum2 :8;
};
};
// Legacy Haier YRW02 remote defines.
#define HAIER_AC_YRW02_SWING_OFF kHaierAcYrw02SwingOff
#define HAIER_AC_YRW02_SWING_TOP kHaierAcYrw02SwingTop
#define HAIER_AC_YRW02_SWING_MIDDLE kHaierAcYrw02SwingMiddle
#define HAIER_AC_YRW02_SWING_BOTTOM kHaierAcYrw02SwingBottom
#define HAIER_AC_YRW02_SWING_DOWN kHaierAcYrw02SwingDown
#define HAIER_AC_YRW02_SWING_AUTO kHaierAcYrw02SwingAuto
#define HAIER_AC_YRW02_FAN_HIGH kHaierAcYrw02FanHigh
#define HAIER_AC_YRW02_FAN_MED kHaierAcYrw02FanMed
#define HAIER_AC_YRW02_FAN_LOW kHaierAcYrw02FanLow
#define HAIER_AC_YRW02_FAN_AUTO kHaierAcYrw02FanAuto
#define HAIER_AC_YRW02_TURBO_OFF kHaierAcYrw02TurboOff
#define HAIER_AC_YRW02_AUTO kHaierAcYrw02Auto
#define HAIER_AC_YRW02_COOL kHaierAcYrw02Cool
#define HAIER_AC_YRW02_DRY kHaierAcYrw02Dry
#define HAIER_AC_YRW02_HEAT kHaierAcYrw02Heat
#define HAIER_AC_YRW02_FAN kHaierAcYrw02Fan
#define HAIER_AC_YRW02_BUTTON_TEMP_UP kHaierAcYrw02ButtonTempUp
#define HAIER_AC_YRW02_BUTTON_TEMP_DOWN kHaierAcYrw02ButtonTempDown
#define HAIER_AC_YRW02_BUTTON_SWING kHaierAcYrw02ButtonSwing
#define HAIER_AC_YRW02_BUTTON_FAN kHaierAcYrw02ButtonFan
#define HAIER_AC_YRW02_BUTTON_POWER kHaierAcYrw02ButtonPower
#define HAIER_AC_YRW02_BUTTON_MODE kHaierAcYrw02ButtonMode
#define HAIER_AC_YRW02_BUTTON_HEALTH kHaierAcYrw02ButtonHealth
#define HAIER_AC_YRW02_BUTTON_TURBO kHaierAcYrw02ButtonTurbo
#define HAIER_AC_YRW02_BUTTON_SLEEP kHaierAcYrw02ButtonSleep
// Classes
/// Class for handling detailed Haier A/C messages.
class IRHaierAC {
public:
explicit IRHaierAC(const uint16_t pin, const bool inverted = false,
const bool use_modulation = true);
#if SEND_HAIER_AC
void send(const uint16_t repeat = kHaierAcDefaultRepeat);
/// 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_HAIER_AC
void begin(void);
void stateReset(void);
void setCommand(const uint8_t command);
uint8_t getCommand(void) const;
void setTemp(const uint8_t temp);
uint8_t getTemp(void) const;
void setFan(const uint8_t speed);
uint8_t getFan(void) const;
uint8_t getMode(void) const;
void setMode(const uint8_t mode);
bool getSleep(void) const;
void setSleep(const bool on);
bool getHealth(void) const;
void setHealth(const bool on);
int16_t getOnTimer(void) const;
void setOnTimer(const uint16_t mins);
int16_t getOffTimer(void) const;
void setOffTimer(const uint16_t mins);
void cancelTimers(void);
uint16_t getCurrTime(void) const;
void setCurrTime(const uint16_t mins);
uint8_t getSwingV(void) const;
void setSwingV(const uint8_t state);
uint8_t* getRaw(void);
void setRaw(const uint8_t new_code[]);
static bool validChecksum(uint8_t state[],
const uint16_t length = kHaierACStateLength);
static uint8_t convertMode(const stdAc::opmode_t mode);
static uint8_t convertFan(const stdAc::fanspeed_t speed);
static uint8_t convertSwingV(const stdAc::swingv_t position);
static stdAc::opmode_t toCommonMode(const uint8_t mode);
static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed);
static stdAc::swingv_t toCommonSwingV(const uint8_t pos);
stdAc::state_t toCommon(void) const;
String toString(void) const;
#ifndef UNIT_TEST
private:
IRsend _irsend; ///< Instance of the IR send class
#else // UNIT_TEST
/// @cond IGNORE
IRsendTest _irsend; ///< Instance of the testing IR send class
/// @endcond
#endif
HaierProtocol _;
void checksum(void);
};
/// Class for handling detailed Haier 176 bit A/C messages.
class IRHaierAC176 {
friend class IRHaierACYRW02;
public:
explicit IRHaierAC176(const uint16_t pin, const bool inverted = false,
const bool use_modulation = true);
#if SEND_HAIER_AC176
virtual void send(const uint16_t repeat = kHaierAc176DefaultRepeat);
/// 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_HAIER_AC176
void begin(void);
void stateReset(void);
void setModel(const haier_ac176_remote_model_t model);
haier_ac176_remote_model_t getModel(void) const;
void setButton(const uint8_t button);
uint8_t getButton(void) const;
void setUseFahrenheit(const bool on);
bool getUseFahrenheit(void) const;
void setTemp(const uint8_t temp, const bool fahrenheit = false);
uint8_t getTemp(void) const;
void setFan(const uint8_t speed);
uint8_t getFan(void) const;
uint8_t getMode(void) const;
void setMode(const uint8_t mode);
bool getPower(void) const;
void setPower(const bool on);
void on(void);
void off(void);
bool getSleep(void) const;
void setSleep(const bool on);
bool getHealth(void) const;
void setHealth(const bool on);
bool getTurbo(void) const;
void setTurbo(const bool on);
bool getQuiet(void) const;
void setQuiet(const bool on);
uint8_t getSwingV(void) const;
void setSwingV(const uint8_t pos);
uint8_t getSwingH(void) const;
void setSwingH(const uint8_t pos);
/// These functions are for backward compatibility.
/// Use getSwingV() and setSwingV() instead.
uint8_t getSwing(void) const;
void setSwing(const uint8_t pos);
void setTimerMode(const uint8_t setting);
uint8_t getTimerMode(void) const;
void setOnTimer(const uint16_t mins);
uint16_t getOnTimer(void) const;
void setOffTimer(const uint16_t mins);
uint16_t getOffTimer(void) const;
bool getLock(void) const;
void setLock(const bool on);
uint8_t* getRaw(void);
virtual void setRaw(const uint8_t new_code[]);
static bool validChecksum(const uint8_t state[],
const uint16_t length = kHaierAC176StateLength);
static uint8_t convertMode(const stdAc::opmode_t mode);
static uint8_t convertFan(const stdAc::fanspeed_t speed);
static uint8_t convertSwingV(const stdAc::swingv_t position);
static uint8_t convertSwingH(const stdAc::swingh_t position);
static stdAc::opmode_t toCommonMode(const uint8_t mode);
static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed);
static stdAc::swingv_t toCommonSwingV(const uint8_t pos);
static stdAc::swingh_t toCommonSwingH(const uint8_t pos);
static bool toCommonTurbo(const uint8_t speed);
static bool toCommonQuiet(const uint8_t speed);
stdAc::state_t toCommon(void) const;
String toString(void) const;
#ifndef UNIT_TEST
private:
IRsend _irsend; ///< Instance of the IR send class
#else // UNIT_TEST
/// @cond IGNORE
IRsendTest _irsend; ///< Instance of the testing IR send class
/// @endcond
#endif // UNIT_TEST
HaierAc176Protocol _;
void checksum(void);
};
/// Class for handling detailed Haier ACYRW02 A/C messages.
class IRHaierACYRW02 : public IRHaierAC176 {
public:
explicit IRHaierACYRW02(const uint16_t pin, const bool inverted = false,
const bool use_modulation = true);
#if SEND_HAIER_AC_YRW02
void send(const uint16_t repeat = kHaierAcYrw02DefaultRepeat) override;
/// 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_HAIER_AC_YRW02
void setRaw(const uint8_t new_code[]) override;
static bool validChecksum(
const uint8_t state[],
const uint16_t length = kHaierACYRW02StateLength);
};
/// Class for handling detailed Haier 160 bit A/C messages.
class IRHaierAC160 {
public:
explicit IRHaierAC160(const uint16_t pin, const bool inverted = false,
const bool use_modulation = true);
#if SEND_HAIER_AC160
virtual void send(const uint16_t repeat = kHaierAc160DefaultRepeat);
/// 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_HAIER_AC160
void begin(void);
void stateReset(void);
void setButton(const uint8_t button);
uint8_t getButton(void) const;
void setUseFahrenheit(const bool on);
bool getUseFahrenheit(void) const;
void setTemp(const uint8_t temp, const bool fahrenheit = false);
uint8_t getTemp(void) const;
void setFan(const uint8_t speed);
uint8_t getFan(void) const;
uint8_t getMode(void) const;
void setMode(const uint8_t mode);
bool getPower(void) const;
void setPower(const bool on);
void on(void);
void off(void);
bool getSleep(void) const;
void setSleep(const bool on);
bool getClean(void) const;
void setClean(const bool on);
bool getLightToggle(void) const;
void setLightToggle(const bool on);
bool getTurbo(void) const;
void setTurbo(const bool on);
bool getQuiet(void) const;
void setQuiet(const bool on);
bool getAuxHeating(void) const;
void setAuxHeating(const bool on);
uint8_t getSwingV(void) const;
void setSwingV(const uint8_t pos);
void setTimerMode(const uint8_t setting);
uint8_t getTimerMode(void) const;
void setOnTimer(const uint16_t mins);
uint16_t getOnTimer(void) const;
void setOffTimer(const uint16_t mins);
uint16_t getOffTimer(void) const;
bool getLock(void) const;
void setLock(const bool on);
bool getHealth(void) const;
void setHealth(const bool on);
uint8_t* getRaw(void);
virtual void setRaw(const uint8_t new_code[]);
static bool validChecksum(const uint8_t state[],
const uint16_t length = kHaierAC160StateLength);
static uint8_t convertMode(const stdAc::opmode_t mode);
static uint8_t convertFan(const stdAc::fanspeed_t speed);
static uint8_t convertSwingV(const stdAc::swingv_t position);
static stdAc::opmode_t toCommonMode(const uint8_t mode);
static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed);
static stdAc::swingv_t toCommonSwingV(const uint8_t pos);
static bool toCommonTurbo(const uint8_t speed);
static bool toCommonQuiet(const uint8_t speed);
stdAc::state_t toCommon(const stdAc::state_t *prev = NULL) const;
String toString(void) const;
#ifndef UNIT_TEST
private:
IRsend _irsend; ///< Instance of the IR send class
#else // UNIT_TEST
/// @cond IGNORE
IRsendTest _irsend; ///< Instance of the testing IR send class
/// @endcond
#endif // UNIT_TEST
HaierAc160Protocol _;
void checksum(void);
};
#endif // IR_HAIER_H_

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,667 @@
// Copyright 2018-2020 David Conran
/// @file
/// @brief Support for Hitachi A/C protocols.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/417
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/453
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/973
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1056
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1060
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1134
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1729
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1757
// Supports:
// Brand: Hitachi, Model: RAS-35THA6 remote
// Brand: Hitachi, Model: LT0541-HTA remote (HITACHI_AC1)
// Brand: Hitachi, Model: Series VI A/C (Circa 2007) (HITACHI_AC1)
// Brand: Hitachi, Model: RAR-8P2 remote (HITACHI_AC424)
// Brand: Hitachi, Model: RAS-AJ25H A/C (HITACHI_AC424)
// Brand: Hitachi, Model: PC-LH3B (HITACHI_AC3)
// Brand: Hitachi, Model: KAZE-312KSDP A/C (HITACHI_AC1)
// Brand: Hitachi, Model: R-LT0541-HTA/Y.K.1.1-1 V2.3 remote (HITACHI_AC1)
// Brand: Hitachi, Model: RAS-22NK A/C (HITACHI_AC344)
// Brand: Hitachi, Model: RF11T1 remote (HITACHI_AC344)
// Brand: Hitachi, Model: RAR-2P2 remote (HITACHI_AC264)
// Brand: Hitachi, Model: RAK-25NH5 A/C (HITACHI_AC264)
// Brand: Hitachi, Model: RAR-3U3 remote (HITACHI_AC296)
// Brand: Hitachi, Model: RAS-70YHA3 A/C (HITACHI_AC296)
#ifndef IR_HITACHI_H_
#define IR_HITACHI_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
/// Native representation of a Hitachi 224-bit A/C message.
union HitachiProtocol{
uint8_t raw[kHitachiAcStateLength]; ///< The state in native code.
struct {
// Byte 0~9
uint8_t pad0[10];
// Byte 10
uint8_t Mode :8;
// Byte 11
uint8_t Temp :8;
// Byte 12
uint8_t :8;
// Byte 13
uint8_t Fan :8;
// Byte 14
uint8_t :7;
uint8_t SwingV :1;
// Byte 15
uint8_t :7;
uint8_t SwingH :1;
// Byte 16
uint8_t :8;
// Byte 17
uint8_t Power :1;
uint8_t :7;
// Byte 18~26
uint8_t pad1[9];
// Byte 27
uint8_t Sum :8;
};
};
// Constants
const uint16_t kHitachiAcFreq = 38000; // Hz.
const uint8_t kHitachiAcAuto = 2;
const uint8_t kHitachiAcHeat = 3;
const uint8_t kHitachiAcCool = 4;
const uint8_t kHitachiAcDry = 5;
const uint8_t kHitachiAcFan = 0xC;
const uint8_t kHitachiAcFanAuto = 1;
const uint8_t kHitachiAcFanLow = 2;
const uint8_t kHitachiAcFanMed = 3;
const uint8_t kHitachiAcFanHigh = 5;
const uint8_t kHitachiAcMinTemp = 16; // 16C
const uint8_t kHitachiAcMaxTemp = 32; // 32C
const uint8_t kHitachiAcAutoTemp = 23; // 23C
/// Native representation of a Hitachi 53-byte/424-bit A/C message.
union Hitachi424Protocol{
uint8_t raw[kHitachiAc424StateLength]; ///< The state in native code
struct {
// Byte 0~10
uint8_t pad0[11];
// Byte 11
uint8_t Button :8;
// Byte 12
uint8_t :8;
// Byte 13
uint8_t :2;
uint8_t Temp :6;
// Byte 14~24
uint8_t pad1[11];
// Byte 25
uint8_t Mode :4;
uint8_t Fan :4;
// Byte 26
uint8_t :8;
// Byte 27
uint8_t :4;
uint8_t Power :1;
uint8_t :3;
// Byte 28~34
uint8_t pad2[7];
// Byte 35
uint8_t SwingH :3;
uint8_t :5;
// Byte 36
uint8_t :8;
// Byte 37
uint8_t :5;
uint8_t SwingV :1;
uint8_t :2;
};
};
// HitachiAc424 & HitachiAc344
const uint8_t kHitachiAc424ButtonPowerMode = 0x13;
const uint8_t kHitachiAc424ButtonFan = 0x42;
const uint8_t kHitachiAc424ButtonTempDown = 0x43;
const uint8_t kHitachiAc424ButtonTempUp = 0x44;
const uint8_t kHitachiAc424ButtonSwingV = 0x81;
const uint8_t kHitachiAc424ButtonSwingH = 0x8C;
const uint8_t kHitachiAc344ButtonPowerMode = kHitachiAc424ButtonPowerMode;
const uint8_t kHitachiAc344ButtonFan = kHitachiAc424ButtonFan;
const uint8_t kHitachiAc344ButtonTempDown = kHitachiAc424ButtonTempDown;
const uint8_t kHitachiAc344ButtonTempUp = kHitachiAc424ButtonTempUp;
const uint8_t kHitachiAc344ButtonSwingV = kHitachiAc424ButtonSwingV;
const uint8_t kHitachiAc344ButtonSwingH = kHitachiAc424ButtonSwingH;
const uint8_t kHitachiAc424MinTemp = 16; // 16C
const uint8_t kHitachiAc424MaxTemp = 32; // 32C
const uint8_t kHitachiAc344MinTemp = kHitachiAc424MinTemp;
const uint8_t kHitachiAc344MaxTemp = kHitachiAc424MaxTemp;
const uint8_t kHitachiAc424FanTemp = 27; // 27C
const uint8_t kHitachiAc424Fan = 1;
const uint8_t kHitachiAc424Cool = 3;
const uint8_t kHitachiAc424Dry = 5;
const uint8_t kHitachiAc424Heat = 6;
const uint8_t kHitachiAc344Fan = kHitachiAc424Fan;
const uint8_t kHitachiAc344Cool = kHitachiAc424Cool;
const uint8_t kHitachiAc344Dry = kHitachiAc424Dry;
const uint8_t kHitachiAc344Heat = kHitachiAc424Heat;
const uint8_t kHitachiAc424FanMin = 1;
const uint8_t kHitachiAc424FanLow = 2;
const uint8_t kHitachiAc424FanMedium = 3;
const uint8_t kHitachiAc424FanHigh = 4;
const uint8_t kHitachiAc424FanAuto = 5;
const uint8_t kHitachiAc424FanMax = 6;
const uint8_t kHitachiAc424FanMaxDry = 2;
const uint8_t kHitachiAc344FanMin = kHitachiAc424FanMin;
const uint8_t kHitachiAc344FanLow = kHitachiAc424FanLow;
const uint8_t kHitachiAc344FanMedium = kHitachiAc424FanMedium;
const uint8_t kHitachiAc344FanHigh = kHitachiAc424FanHigh;
const uint8_t kHitachiAc344FanAuto = kHitachiAc424FanAuto;
const uint8_t kHitachiAc344FanMax = kHitachiAc424FanMax;
const uint8_t kHitachiAc344SwingHAuto = 0; // 0b000
const uint8_t kHitachiAc344SwingHRightMax = 1; // 0b001
const uint8_t kHitachiAc344SwingHRight = 2; // 0b010
const uint8_t kHitachiAc344SwingHMiddle = 3; // 0b011
const uint8_t kHitachiAc344SwingHLeft = 4; // 0b100
const uint8_t kHitachiAc344SwingHLeftMax = 5; // 0b101
/// Native representation of a Hitachi 104-bit A/C message.
union Hitachi1Protocol{
uint8_t raw[kHitachiAc1StateLength]; ///< The state in native code.
struct {
// Byte 0~2
uint8_t pad[3];
// Byte 3
uint8_t :6;
uint8_t Model :2;
// Byte 4
uint8_t :8;
// Byte 5
uint8_t Fan :4;
uint8_t Mode :4;
// Byte 6
uint8_t :2;
uint8_t Temp :5; // stored in LSB order.
uint8_t :1;
// Byte 7
uint8_t OffTimerLow :8; // nr. of minutes
// Byte 8
uint8_t OffTimerHigh :8; // & in LSB order.
// Byte 9
uint8_t OnTimerLow :8; // nr. of minutes
// Byte 10
uint8_t OnTimerHigh :8; // & in LSB order.
// Byte 11
uint8_t SwingToggle :1;
uint8_t Sleep :3;
uint8_t PowerToggle :1;
uint8_t Power :1;
uint8_t SwingV :1;
uint8_t SwingH :1;
// Byte 12
uint8_t Sum :8;
};
};
// HitachiAc1
// Model
const uint8_t kHitachiAc1Model_A = 0b10;
const uint8_t kHitachiAc1Model_B = 0b01;
// Mode & Fan
const uint8_t kHitachiAc1Dry = 0b0010; // 2
const uint8_t kHitachiAc1Fan = 0b0100; // 4
const uint8_t kHitachiAc1Cool = 0b0110; // 6
const uint8_t kHitachiAc1Heat = 0b1001; // 9
const uint8_t kHitachiAc1Auto = 0b1110; // 14
const uint8_t kHitachiAc1FanAuto = 1; // 0b0001
const uint8_t kHitachiAc1FanHigh = 2; // 0b0010
const uint8_t kHitachiAc1FanMed = 4; // 0b0100
const uint8_t kHitachiAc1FanLow = 8; // 0b1000
// Temp
const uint8_t kHitachiAc1TempSize = 5; // Mask 0b01111100
const uint8_t kHitachiAc1TempDelta = 7;
const uint8_t kHitachiAc1TempAuto = 25; // Celsius
// Timer
const uint8_t kHitachiAc1TimerSize = 16; // Mask 0b1111111111111111
// Sleep
const uint8_t kHitachiAc1SleepOff = 0b000;
const uint8_t kHitachiAc1Sleep1 = 0b001;
const uint8_t kHitachiAc1Sleep2 = 0b010;
const uint8_t kHitachiAc1Sleep3 = 0b011;
const uint8_t kHitachiAc1Sleep4 = 0b100;
// Checksum
const uint8_t kHitachiAc1ChecksumStartByte = 5;
/// Native representation of a Hitachi 164-bit A/C message.
union HitachiAC264Protocol{
uint8_t raw[kHitachiAc264StateLength]; ///< The state in native code.
struct {
// Bytes 0~10
uint8_t pad0[11];
// Byte 11
uint8_t Button :8;
// Byte 12
uint8_t :8;
// Byte 13
uint8_t :2;
uint8_t Temp :6;
// Byte 14
uint8_t :8;
// Bytes 14~24
uint8_t pad1[10];
// Byte 25
uint8_t Mode :4;
uint8_t Fan :4;
// Byte 26
uint8_t :8;
// Byte 27
uint8_t :4;
uint8_t Power :1;
uint8_t :3;
// Byte 28
uint8_t :8;
// Bytes 29~32
uint8_t pad2[4];
};
};
// HitachiAc264
const uint8_t kHitachiAc264ButtonPowerMode = kHitachiAc424ButtonPowerMode;
const uint8_t kHitachiAc264ButtonFan = kHitachiAc424ButtonFan;
const uint8_t kHitachiAc264ButtonTempDown = kHitachiAc424ButtonTempDown;
const uint8_t kHitachiAc264ButtonTempUp = kHitachiAc424ButtonTempUp;
const uint8_t kHitachiAc264ButtonSwingV = kHitachiAc424ButtonSwingV;
const uint8_t kHitachiAc264MinTemp = kHitachiAc424MinTemp; // 16C
const uint8_t kHitachiAc264MaxTemp = kHitachiAc424MaxTemp; // 32C
const uint8_t kHitachiAc264Fan = kHitachiAc424Fan;
const uint8_t kHitachiAc264Cool = kHitachiAc424Cool;
const uint8_t kHitachiAc264Dry = kHitachiAc424Dry;
const uint8_t kHitachiAc264Heat = kHitachiAc424Heat;
const uint8_t kHitachiAc264FanMin = kHitachiAc424FanMin;
const uint8_t kHitachiAc264FanLow = kHitachiAc424FanMin;
const uint8_t kHitachiAc264FanMedium = kHitachiAc424FanMedium;
const uint8_t kHitachiAc264FanHigh = kHitachiAc424FanHigh;
const uint8_t kHitachiAc264FanAuto = kHitachiAc424FanAuto;
// HitachiAc296
union HitachiAC296Protocol{
uint8_t raw[kHitachiAc296StateLength];
struct {
// Byte 0~12
uint8_t pad0[13];
// Byte 13
uint8_t :2;
uint8_t Temp :5; // LSB
uint8_t :1;
uint8_t :8;
// Byte 15~16
uint8_t :8;
uint8_t :8;
// Byte 17~24
uint8_t OffTimerLow :8; // LSB
uint8_t /* Parity */ :8;
uint8_t OffTimerHigh :8;
uint8_t /* Parity */ :8;
uint8_t OnTimerLow :8; // LSB
uint8_t /* Parity */ :8;
uint8_t OnTimerHigh :4;
uint8_t OffTimerActive :1;
uint8_t OnTimerActive :1;
uint8_t :2;
uint8_t /* Parity */ :8;
// Byte 25~26
uint8_t Mode :4;
uint8_t Fan :3;
uint8_t :1;
uint8_t :8;
// Byte 27~28
uint8_t :4;
uint8_t Power :1;
uint8_t :2;
uint8_t TimerActive :1;
uint8_t :8;
// Byte 29~34
uint8_t pad1[6];
// Byte 35~36
uint8_t :4;
uint8_t Humidity :4; // LSB
uint8_t :8;
};
};
// Mode & Fan
const uint8_t kHitachiAc296Cool = 0b0011;
const uint8_t kHitachiAc296DryCool = 0b0100;
const uint8_t kHitachiAc296Dehumidify = 0b0101;
const uint8_t kHitachiAc296Heat = 0b0110;
const uint8_t kHitachiAc296Auto = 0b0111;
const uint8_t kHitachiAc296AutoDehumidifying = 0b1001;
const uint8_t kHitachiAc296QuickLaundry = 0b1010;
const uint8_t kHitachiAc296CondensationControl = 0b1100;
const uint8_t kHitachiAc296FanSilent = 0b001;
const uint8_t kHitachiAc296FanLow = 0b010;
const uint8_t kHitachiAc296FanMedium = 0b011;
const uint8_t kHitachiAc296FanHigh = 0b100;
const uint8_t kHitachiAc296FanAuto = 0b101;
const uint8_t kHitachiAc296TempAuto = 1; // Special value for "Auto" op mode.
const uint8_t kHitachiAc296MinTemp = 16;
const uint8_t kHitachiAc296MaxTemp = 31; // Max value you can store in 5 bits.
const uint8_t kHitachiAc296PowerOn = 1;
const uint8_t kHitachiAc296PowerOff = 0;
// Classes
/// Class for handling detailed Hitachi 224-bit A/C messages.
/// @see https://github.com/ToniA/arduino-heatpumpir/blob/master/HitachiHeatpumpIR.cpp
class IRHitachiAc {
public:
explicit IRHitachiAc(const uint16_t pin, const bool inverted = false,
const bool use_modulation = true);
void stateReset(void);
#if SEND_HITACHI_AC
void send(const uint16_t repeat = kHitachiAcDefaultRepeat);
/// 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_HITACHI_AC
void begin(void);
void on(void);
void off(void);
void setPower(const bool on);
bool getPower(void) const;
void setTemp(const uint8_t temp);
uint8_t getTemp(void) const;
void setFan(const uint8_t speed);
uint8_t getFan(void) const;
void setMode(const uint8_t mode);
uint8_t getMode(void) const;
void setSwingVertical(const bool on);
bool getSwingVertical(void) const;
void setSwingHorizontal(const bool on);
bool getSwingHorizontal(void) const;
uint8_t* getRaw(void);
void setRaw(const uint8_t new_code[],
const uint16_t length = kHitachiAcStateLength);
static bool validChecksum(const uint8_t state[],
const uint16_t length = kHitachiAcStateLength);
static uint8_t calcChecksum(const uint8_t state[],
const uint16_t length = kHitachiAcStateLength);
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);
static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed);
stdAc::state_t toCommon(void) const;
String toString(void) const;
#ifndef UNIT_TEST
private:
IRsend _irsend; ///< Instance of the IR send class
#else // UNIT_TEST
/// @cond IGNORE
IRsendTest _irsend; ///< Instance of the testing IR send class
/// @endcond
#endif // UNIT_TEST
HitachiProtocol _;
void checksum(const uint16_t length = kHitachiAcStateLength);
uint8_t _previoustemp;
};
/// Class for handling detailed Hitachi 104-bit A/C messages.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1056
class IRHitachiAc1 {
public:
explicit IRHitachiAc1(const uint16_t pin, const bool inverted = false,
const bool use_modulation = true);
void stateReset(void);
#if SEND_HITACHI_AC1
void send(const uint16_t repeat = kHitachiAcDefaultRepeat);
/// 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_HITACHI_AC1
void begin(void);
void on(void);
void off(void);
void setModel(const hitachi_ac1_remote_model_t model);
hitachi_ac1_remote_model_t getModel(void) const;
void setPower(const bool on);
bool getPower(void) const;
void setPowerToggle(const bool on);
bool getPowerToggle(void) const;
void setTemp(const uint8_t temp);
uint8_t getTemp(void) const;
void setFan(const uint8_t speed, const bool force = false);
uint8_t getFan(void) const;
void setMode(const uint8_t mode);
uint8_t getMode(void) const;
void setSwingToggle(const bool toggle);
bool getSwingToggle(void) const;
void setSwingV(const bool on);
bool getSwingV(void) const;
void setSwingH(const bool on);
bool getSwingH(void) const;
void setSleep(const uint8_t mode);
uint8_t getSleep(void) const;
void setOnTimer(const uint16_t mins);
uint16_t getOnTimer(void) const;
void setOffTimer(const uint16_t mins);
uint16_t getOffTimer(void) const;
uint8_t* getRaw(void);
void setRaw(const uint8_t new_code[],
const uint16_t length = kHitachiAc1StateLength);
static bool validChecksum(const uint8_t state[],
const uint16_t length = kHitachiAc1StateLength);
static uint8_t calcChecksum(const uint8_t state[],
const uint16_t length = kHitachiAc1StateLength);
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);
static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed);
stdAc::state_t toCommon(void) const;
String toString(void) const;
#ifndef UNIT_TEST
private:
IRsend _irsend; ///< Instance of the IR send class
#else // UNIT_TEST
/// @cond IGNORE
IRsendTest _irsend; ///< Instance of the testing IR send class
/// @endcond
#endif // UNIT_TEST
Hitachi1Protocol _;
void checksum(const uint16_t length = kHitachiAc1StateLength);
};
/// Class for handling detailed Hitachi 53-byte/424-bit A/C messages.
class IRHitachiAc424 {
friend class IRHitachiAc264;
friend class IRHitachiAc344;
public:
explicit IRHitachiAc424(const uint16_t pin, const bool inverted = false,
const bool use_modulation = true);
virtual void stateReset(void);
#if SEND_HITACHI_AC424
virtual void send(const uint16_t repeat = kHitachiAcDefaultRepeat);
/// 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_HITACHI_AC424
void begin(void);
void on(void);
void off(void);
void setPower(const bool on);
bool getPower(void) const;
void setTemp(const uint8_t temp, bool setPrevious = true);
uint8_t getTemp(void) const;
virtual void setFan(const uint8_t speed);
uint8_t getFan(void) const;
uint8_t getButton(void) const;
void setButton(const uint8_t button);
void setSwingVToggle(const bool on);
bool getSwingVToggle(void) const;
void setMode(const uint8_t mode);
uint8_t getMode(void) const;
uint8_t* getRaw(void);
virtual void setRaw(const uint8_t new_code[],
const uint16_t length = kHitachiAc424StateLength);
static uint8_t convertMode(const stdAc::opmode_t mode);
virtual uint8_t convertFan(const stdAc::fanspeed_t speed) const;
static stdAc::opmode_t toCommonMode(const uint8_t mode);
virtual stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed) const;
virtual stdAc::state_t toCommon(void) const;
virtual String toString(void) const;
#ifndef UNIT_TEST
private:
IRsend _irsend; ///< Instance of the IR send class
#else // UNIT_TEST
/// @cond IGNORE
IRsendTest _irsend; ///< Instance of the testing IR send class
/// @endcond
#endif // UNIT_TEST
Hitachi424Protocol _;
void setInvertedStates(void);
String _toString(void) const;
uint8_t _previoustemp;
};
/// Class for handling detailed Hitachi 15to27-byte/120to216-bit A/C messages.
class IRHitachiAc3 {
public:
explicit IRHitachiAc3(const uint16_t pin, const bool inverted = false,
const bool use_modulation = true);
void stateReset(void);
#if SEND_HITACHI_AC3
void send(const uint16_t repeat = kHitachiAcDefaultRepeat);
/// 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_HITACHI_AC3
void begin(void);
uint8_t getMode(void);
uint8_t* getRaw(void);
void setRaw(const uint8_t new_code[],
const uint16_t length = kHitachiAc3StateLength);
static bool hasInvertedStates(const uint8_t state[], const uint16_t length);
#ifndef UNIT_TEST
private:
IRsend _irsend; ///< Instance of the IR send class
#else // UNIT_TEST
/// @cond IGNORE
IRsendTest _irsend; ///< Instance of the testing IR send class
/// @endcond
#endif // UNIT_TEST
uint8_t remote_state[kHitachiAc3StateLength]; ///< The state in native code.
void setInvertedStates(const uint16_t length = kHitachiAc3StateLength);
};
/// Class for handling detailed Hitachi 344-bit A/C messages.
class IRHitachiAc344: public IRHitachiAc424 {
public:
explicit IRHitachiAc344(const uint16_t pin, const bool inverted = false,
const bool use_modulation = true);
void stateReset(void) override;
void setRaw(const uint8_t new_code[],
const uint16_t length = kHitachiAc344StateLength) override;
stdAc::state_t toCommon(void) const override;
#if SEND_HITACHI_AC344
void send(const uint16_t repeat = kHitachiAcDefaultRepeat) override;
#endif // SEND_HITACHI_AC344
void setSwingV(const bool on);
bool getSwingV(void) const;
void setSwingH(const uint8_t position);
uint8_t getSwingH(void) const;
static uint8_t convertSwingH(const stdAc::swingh_t position);
static stdAc::swingh_t toCommonSwingH(const uint8_t pos);
String toString(void) const override;
};
/// Class for handling detailed Hitachi 264-bit A/C messages.
class IRHitachiAc264: public IRHitachiAc424 {
public:
explicit IRHitachiAc264(const uint16_t pin, const bool inverted = false,
const bool use_modulation = true);
void stateReset(void) override;
void setRaw(const uint8_t new_code[],
const uint16_t length = kHitachiAc264StateLength) override;
void setFan(const uint8_t speed) override;
uint8_t convertFan(const stdAc::fanspeed_t speed) const override;
stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed) const override;
stdAc::state_t toCommon(void) const override;
#if SEND_HITACHI_AC264
void send(const uint16_t repeat = kHitachiAcDefaultRepeat) override;
#endif // SEND_HITACHI_AC264
String toString(void) const override;
};
class IRHitachiAc296 {
public:
explicit IRHitachiAc296(const uint16_t pin, const bool inverted = false,
const bool use_modulation = true);
void stateReset(void);
#if SEND_HITACHI_AC296
void send(const uint16_t repeat = kHitachiAcDefaultRepeat);
#endif // SEND_HITACHI_AC296
void begin(void);
void on(void);
void off(void);
void setPower(const bool on);
bool getPower(void) const;
void setTemp(const uint8_t temp);
uint8_t getTemp(void) const;
void setFan(const uint8_t speed);
uint8_t getFan(void) const;
void setMode(const uint8_t mode);
uint8_t getMode(void) const;
static bool hasInvertedStates(const uint8_t state[], const uint16_t length);
uint8_t* getRaw(void);
void setRaw(const uint8_t new_code[],
const uint16_t length = kHitachiAc296StateLength);
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);
static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed);
stdAc::state_t toCommon(void) const;
String toString(void) const;
#ifndef UNIT_TEST
private:
IRsend _irsend; ///< Instance of the IR send class
#else // UNIT_TEST
/// @cond IGNORE
IRsendTest _irsend; ///< Instance of the testing IR send class
/// @endcond
#endif // UNIT_TEST
HitachiAC296Protocol _;
void setInvertedStates(void);
};
#endif // IR_HITACHI_H_

View File

@@ -0,0 +1,73 @@
// Copyright 2019 David Conran (crankyoldgit)
/// @file
/// @brief Support for the Inax Robot Toilet IR protocols.
/// @see https://www.lixil-manual.com/GCW-1365-16050/GCW-1365-16050.pdf
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/706
// Supports:
// Brand: Lixil, Model: Inax DT-BA283 Toilet
#include <algorithm>
#include "IRrecv.h"
#include "IRsend.h"
#include "IRutils.h"
// Constants
const uint16_t kInaxTick = 500;
const uint16_t kInaxHdrMark = 9000;
const uint16_t kInaxHdrSpace = 4500;
const uint16_t kInaxBitMark = 560;
const uint16_t kInaxOneSpace = 1675;
const uint16_t kInaxZeroSpace = kInaxBitMark;
const uint16_t kInaxMinGap = 40000;
#if SEND_INAX
/// Send a Inax Toilet formatted message.
/// Status: STABLE / Working.
/// @param[in] data The message to be sent.
/// @param[in] nbits The number of bits of message to be sent.
/// @param[in] repeat The number of times the command is to be repeated.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/706
void IRsend::sendInax(const uint64_t data, const uint16_t nbits,
const uint16_t repeat) {
sendGeneric(kInaxHdrMark, kInaxHdrSpace,
kInaxBitMark, kInaxOneSpace,
kInaxBitMark, kInaxZeroSpace,
kInaxBitMark, kInaxMinGap,
data, nbits, 38, true, repeat, kDutyDefault);
}
#endif // SEND_INAX
#if DECODE_INAX
/// Decode the supplied Inax Toilet message.
/// Status: Stable / Known working.
/// @param[in,out] results Ptr to the data to decode & where to store the result
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// @param[in] strict Flag indicating if we should perform strict matching.
/// @return True if it can decode it, false if it can't.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/706
bool IRrecv::decodeInax(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
if (strict && nbits != kInaxBits)
return false; // We expect Inax to be a certain sized message.
uint64_t data = 0;
// Match Header + Data + Footer
if (!matchGeneric(results->rawbuf + offset, &data,
results->rawlen - offset, nbits,
kInaxHdrMark, kInaxHdrSpace,
kInaxBitMark, kInaxOneSpace,
kInaxBitMark, kInaxZeroSpace,
kInaxBitMark, kInaxMinGap, true)) return false;
// Success
results->bits = nbits;
results->value = data;
results->decode_type = decode_type_t::INAX;
results->command = 0;
results->address = 0;
return true;
}
#endif // DECODE_INAX

View File

@@ -0,0 +1,131 @@
// Copyright 2015 Kristian Lauszus
// Copyright 2017 David Conran
/// @file
/// @brief Support for JVC protocols.
/// Originally added by Kristian Lauszus
/// Thanks to zenwheel and other people at the original blog post.
/// @see http://www.sbprojects.net/knowledge/ir/jvc.php
// Supports:
// Brand: JVC, Model: PTU94023B remote
#include <algorithm>
#include "IRrecv.h"
#include "IRsend.h"
#include "IRtimer.h"
#include "IRutils.h"
// Constants
const uint16_t kJvcTick = 75;
const uint16_t kJvcHdrMarkTicks = 112;
const uint16_t kJvcHdrMark = kJvcHdrMarkTicks * kJvcTick;
const uint16_t kJvcHdrSpaceTicks = 56;
const uint16_t kJvcHdrSpace = kJvcHdrSpaceTicks * kJvcTick;
const uint16_t kJvcBitMarkTicks = 7;
const uint16_t kJvcBitMark = kJvcBitMarkTicks * kJvcTick;
const uint16_t kJvcOneSpaceTicks = 23;
const uint16_t kJvcOneSpace = kJvcOneSpaceTicks * kJvcTick;
const uint16_t kJvcZeroSpaceTicks = 7;
const uint16_t kJvcZeroSpace = kJvcZeroSpaceTicks * kJvcTick;
const uint16_t kJvcRptLengthTicks = 800;
const uint16_t kJvcRptLength = kJvcRptLengthTicks * kJvcTick;
const uint16_t kJvcMinGapTicks =
kJvcRptLengthTicks -
(kJvcHdrMarkTicks + kJvcHdrSpaceTicks +
kJvcBits * (kJvcBitMarkTicks + kJvcOneSpaceTicks) + kJvcBitMarkTicks);
const uint16_t kJvcMinGap = kJvcMinGapTicks * kJvcTick;
#if SEND_JVC
/// Send a JVC formatted message.
/// Status: STABLE / Working.
/// @param[in] data The message to be sent.
/// @param[in] nbits The number of bits of message to be sent.
/// @param[in] repeat The number of times the command is to be repeated.
/// @see http://www.sbprojects.net/knowledge/ir/jvc.php
void IRsend::sendJVC(uint64_t data, uint16_t nbits, uint16_t repeat) {
// Set 38kHz IR carrier frequency & a 1/3 (33%) duty cycle.
enableIROut(38, 33);
IRtimer usecs = IRtimer();
// Header
// Only sent for the first message.
mark(kJvcHdrMark);
space(kJvcHdrSpace);
// We always send the data & footer at least once, hence '<= repeat'.
for (uint16_t i = 0; i <= repeat; i++) {
sendGeneric(0, 0, // No Header
kJvcBitMark, kJvcOneSpace, kJvcBitMark, kJvcZeroSpace,
kJvcBitMark, kJvcMinGap, data, nbits, 38, true,
0, // Repeats are handles elsewhere.
33);
// Wait till the end of the repeat time window before we send another code.
uint32_t elapsed = usecs.elapsed();
// Avoid potential unsigned integer underflow.
// e.g. when elapsed > kJvcRptLength.
if (elapsed < kJvcRptLength) space(kJvcRptLength - elapsed);
usecs.reset();
}
}
/// Calculate the raw JVC data based on address and command.
/// Status: STABLE / Works fine.
/// @param[in] address An 8-bit address value.
/// @param[in] command An 8-bit command value.
/// @return A raw JVC message code, suitable for sendJVC()..
/// @see http://www.sbprojects.net/knowledge/ir/jvc.php
uint16_t IRsend::encodeJVC(uint8_t address, uint8_t command) {
return reverseBits((command << 8) | address, 16);
}
#endif // SEND_JVC
#if DECODE_JVC
/// Decode the supplied JVC message.
/// Status: Stable / Known working.
/// @param[in,out] results Ptr to the data to decode & where to store the result
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// @param[in] strict Flag indicating if we should perform strict matching.
/// @return True if it can decode it, false if it can't.
/// @note JVC repeat codes don't have a header.
/// @see http://www.sbprojects.net/knowledge/ir/jvc.php
bool IRrecv::decodeJVC(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
if (strict && nbits != kJvcBits)
return false; // Must be called with the correct nr. of bits.
if (results->rawlen <= 2 * nbits + kFooter - 1 + offset)
return false; // Can't possibly be a valid JVC message.
uint64_t data = 0;
bool isRepeat = true;
// Header
// (Optional as repeat codes don't have the header)
if (matchMark(results->rawbuf[offset], kJvcHdrMark)) {
isRepeat = false;
offset++;
if (results->rawlen < 2 * nbits + 4)
return false; // Can't possibly be a valid JVC message with a header.
if (!matchSpace(results->rawbuf[offset++], kJvcHdrSpace)) return false;
}
// Data + Footer
if (!matchGeneric(results->rawbuf + offset, &data,
results->rawlen - offset, nbits,
0, 0,
kJvcBitMark, kJvcOneSpace,
kJvcBitMark, kJvcZeroSpace,
kJvcBitMark, kJvcMinGap, true)) return false;
// Success
results->decode_type = JVC;
results->bits = nbits;
results->value = data;
// command & address are transmitted LSB first, so we need to reverse them.
results->address = reverseBits(data >> 8, 8); // The first 8 bits sent.
results->command = reverseBits(data & 0xFF, 8); // The last 8 bits sent.
results->repeat = isRepeat;
return true;
}
#endif // DECODE_JVC

View File

@@ -0,0 +1,551 @@
// Copyright 2021 Davide Depau
// Copyright 2022 David Conran
/// @file
/// @brief Support for Kelon AC protocols.
/// Both sending and decoding should be functional for models of series
/// KELON ON/OFF 9000-12000.
/// All features of the standard remote are implemented.
///
/// @note Unsupported:
/// - Explicit on/off due to AC unit limitations
/// - Explicit swing position due to AC unit limitations
/// - Fahrenheit.
#include <algorithm>
#include <cassert>
#include "ir_Kelon.h"
#include "IRrecv.h"
#include "IRsend.h"
#include "IRutils.h"
#include "IRtext.h"
using irutils::addBoolToString;
using irutils::addIntToString;
using irutils::addSignedIntToString;
using irutils::addModeToString;
using irutils::addFanToString;
using irutils::addTempToString;
using irutils::addLabeledString;
using irutils::minsToString;
// Constants
const uint16_t kKelonHdrMark = 9000;
const uint16_t kKelonHdrSpace = 4600;
const uint16_t kKelonBitMark = 560;
const uint16_t kKelonOneSpace = 1680;
const uint16_t kKelonZeroSpace = 600;
const uint32_t kKelonGap = 2 * kDefaultMessageGap;
const uint16_t kKelonFreq = 38000;
const uint32_t kKelon168FooterSpace = 8000;
const uint16_t kKelon168Section1Size = 6;
const uint16_t kKelon168Section2Size = 8;
const uint16_t kKelon168Section3Size = 7;
#if SEND_KELON
/// Send a Kelon 48-bit message.
/// Status: STABLE / Working.
/// @param[in] data The data to be transmitted.
/// @param[in] nbits Nr. of bits of data to be sent.
/// @param[in] repeat The number of times the command is to be repeated.
void IRsend::sendKelon(const uint64_t data, const uint16_t nbits,
const uint16_t repeat) {
sendGeneric(kKelonHdrMark, kKelonHdrSpace,
kKelonBitMark, kKelonOneSpace,
kKelonBitMark, kKelonZeroSpace,
kKelonBitMark, kKelonGap,
data, nbits, kKelonFreq, false, // LSB First.
repeat, kDutyDefault);
}
#endif // SEND_KELON
#if DECODE_KELON
/// Decode the supplied Kelon 48-bit message.
/// Status: STABLE / Working.
/// @param[in,out] results Ptr to the data to decode & where to store the result
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// @param[in] strict Flag indicating if we should perform strict matching.
/// @return True if it can decode it, false if it can't.
bool IRrecv::decodeKelon(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
if (strict && nbits != kKelonBits) return false;
if (!matchGeneric(results->rawbuf + offset, &(results->value),
results->rawlen - offset, nbits,
kKelonHdrMark, kKelonHdrSpace,
kKelonBitMark, kKelonOneSpace,
kKelonBitMark, kKelonZeroSpace,
kKelonBitMark, kKelonGap, true,
_tolerance, 0, false)) return false;
results->decode_type = decode_type_t::KELON;
results->address = 0;
results->command = 0;
results->bits = nbits;
return true;
}
#endif // DECODE_KELON
/// 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?
IRKelonAc::IRKelonAc(const uint16_t pin, const bool inverted,
const bool use_modulation)
: _irsend{pin, inverted, use_modulation}, _{} { stateReset(); }
/// Reset the internals of the object to a known good state.
void IRKelonAc::stateReset() {
_.raw = 0L;
_.preamble[0] = 0b10000011;
_.preamble[1] = 0b00000110;
}
#if SEND_KELON
/// Send the current internal state as an IR message.
/// @param[in] repeat Nr. of times the message will be repeated.
void IRKelonAc::send(const uint16_t repeat) {
_irsend.sendKelon(getRaw(), kKelonBits, repeat);
// Reset toggle flags
_.PowerToggle = false;
_.SwingVToggle = false;
// Remove the timer time setting
_.TimerHours = 0;
_.TimerHalfHour = 0;
}
/// Ensures the AC is on or off by exploiting the fact that setting
/// it to "smart" will always turn it on if it's off.
/// This method will send 2 commands to the AC to do the trick
/// @param[in] on Whether to ensure the AC is on or off
void IRKelonAc::ensurePower(bool on) {
// Try to avoid turning on the compressor for this operation.
// "Dry grade", when in "smart" mode, acts as a temperature offset that
// the user can configure if they feel too cold or too hot. By setting it
// to +2 we're setting the temperature to ~28°C, which will effectively
// set the AC to fan mode.
int8_t previousDry = getDryGrade();
setDryGrade(2);
setMode(kKelonModeSmart);
send();
setDryGrade(previousDry);
setMode(_previousMode);
send();
// Now we're sure it's on. Turn it back off. The AC seems to turn back on if
// we don't send this separately
if (!on) {
setTogglePower(true);
send();
}
}
#endif // SEND_KELON
/// Set up hardware to be able to send a message.
void IRKelonAc::begin() { _irsend.begin(); }
/// Request toggling power - will be reset to false after sending
/// @param[in] toggle Whether to toggle the power state
void IRKelonAc::setTogglePower(const bool toggle) { _.PowerToggle = toggle; }
/// Get whether toggling power will be requested
/// @return The power toggle state
bool IRKelonAc::getTogglePower() const { return _.PowerToggle; }
/// Set the temperature setting.
/// @param[in] degrees The temperature in degrees celsius.
void IRKelonAc::setTemp(const uint8_t degrees) {
uint8_t temp = std::max(kKelonMinTemp, degrees);
temp = std::min(kKelonMaxTemp, temp);
_previousTemp = _.Temperature;
_.Temperature = temp - kKelonMinTemp;
}
/// Get the current temperature setting.
/// @return Get current setting for temp. in degrees celsius.
uint8_t IRKelonAc::getTemp() const { return _.Temperature + kKelonMinTemp; }
/// Set the speed of the fan.
/// @param[in] speed 0 is auto, 1-5 is the speed
void IRKelonAc::setFan(const uint8_t speed) {
uint8_t fan = std::min(speed, kKelonFanMax);
_previousFan = _.Fan;
// Note: Kelon fan speeds are backwards! This code maps the range 0,1:3 to
// 0,3:1 to save the API's user's sanity.
_.Fan = ((static_cast<int16_t>(fan) - 4) * -1) % 4;
}
/// Get the current fan speed setting.
/// @return The current fan speed.
uint8_t IRKelonAc::getFan() const {
return ((static_cast<int16_t>(_.Fan) - 4) * -1) % 4;;
}
/// Set the dehumidification intensity.
/// @param[in] grade has to be in the range [-2 : +2]
void IRKelonAc::setDryGrade(const int8_t grade) {
int8_t drygrade = std::max(kKelonDryGradeMin, grade);
drygrade = std::min(kKelonDryGradeMax, drygrade);
// Two's complement is clearly too bleeding edge for this manufacturer
uint8_t outval;
if (drygrade < 0)
outval = 0b100 | (-drygrade & 0b011);
else
outval = drygrade & 0b011;
_.DehumidifierGrade = outval;
}
/// Get the current dehumidification intensity setting. In smart mode, this
/// controls the temperature adjustment.
/// @return The current dehumidification intensity.
int8_t IRKelonAc::getDryGrade() const {
return static_cast<int8_t>(_.DehumidifierGrade & 0b011) *
((_.DehumidifierGrade & 0b100) ? -1 : 1);
}
/// Set the desired operation mode.
/// @param[in] mode The desired operation mode.
void IRKelonAc::setMode(const uint8_t mode) {
if (_.Mode == kKelonModeSmart || _.Mode == kKelonModeFan ||
_.Mode == kKelonModeDry) {
_.Temperature = _previousTemp;
}
if (_.SuperCoolEnabled1) {
// Cancel supercool
_.SuperCoolEnabled1 = false;
_.SuperCoolEnabled2 = false;
_.Temperature = _previousTemp;
_.Fan = _previousFan;
}
_previousMode = _.Mode;
switch (mode) {
case kKelonModeSmart:
setTemp(26);
_.SmartModeEnabled = true;
_.Mode = mode;
break;
case kKelonModeDry:
case kKelonModeFan:
setTemp(25);
// fallthrough
case kKelonModeCool:
case kKelonModeHeat:
_.Mode = mode;
// fallthrough
default:
_.SmartModeEnabled = false;
}
}
/// Get the current operation mode setting.
/// @return The current operation mode.
uint8_t IRKelonAc::getMode() const { return _.Mode; }
/// Request toggling the vertical swing - will be reset to false after sending
/// @param[in] toggle If true, the swing mode will be toggled when sent.
void IRKelonAc::setToggleSwingVertical(const bool toggle) {
_.SwingVToggle = toggle;
}
/// Get whether the swing mode is set to be toggled
/// @return Whether the toggle bit is set
bool IRKelonAc::getToggleSwingVertical() const { return _.SwingVToggle; }
/// Control the current sleep (quiet) setting.
/// @param[in] on The desired setting.
void IRKelonAc::setSleep(const bool on) { _.SleepEnabled = on; }
/// Is the sleep setting on?
/// @return The current value.
bool IRKelonAc::getSleep() const { return _.SleepEnabled; }
/// Control the current super cool mode setting.
/// @param[in] on The desired setting.
void IRKelonAc::setSupercool(const bool on) {
if (on) {
setTemp(kKelonMinTemp);
setMode(kKelonModeCool);
setFan(kKelonFanMax);
} else {
// All reverts to previous are handled by setMode as needed
setMode(_previousMode);
}
_.SuperCoolEnabled1 = on;
_.SuperCoolEnabled2 = on;
}
/// Is the super cool mode setting on?
/// @return The current value.
bool IRKelonAc::getSupercool() const { return _.SuperCoolEnabled1; }
/// Set the timer time and enable it. Timer is an off timer if the unit is on,
/// it is an on timer if the unit is off.
/// Only multiples of 30m are supported for < 10h, then only multiples of 60m
/// @param[in] mins Nr. of minutes
void IRKelonAc::setTimer(uint16_t mins) {
const uint16_t minutes = std::min(static_cast<int>(mins), 24 * 60);
if (minutes / 60 >= 10) {
uint8_t hours = minutes / 60 + 10;
_.TimerHalfHour = hours & 1;
_.TimerHours = hours >> 1;
} else {
_.TimerHalfHour = (minutes % 60) >= 30 ? 1 : 0;
_.TimerHours = minutes / 60;
}
setTimerEnabled(true);
}
/// Get the set timer. Timer set time is deleted once the command is sent, so
/// calling this after send() will return 0.
/// The AC unit will continue keeping track of the remaining time unless it is
/// later disabled.
/// @return The timer set minutes
uint16_t IRKelonAc::getTimer() const {
if (_.TimerHours >= 10)
return ((uint16_t) ((_.TimerHours << 1) | _.TimerHalfHour) - 10) * 60;
return (((uint16_t) _.TimerHours) * 60) + (_.TimerHalfHour ? 30 : 0);
}
/// Enable or disable the timer. Note that in order to enable the timer the
/// minutes must be set with setTimer().
/// @param[in] on Whether to enable or disable the timer
void IRKelonAc::setTimerEnabled(bool on) { _.TimerEnabled = on; }
/// Get the current timer status
/// @return Whether the timer is enabled.
bool IRKelonAc::getTimerEnabled() const { return _.TimerEnabled; }
/// Get the raw state of the object, suitable to be sent with the appropriate
/// IRsend object method.
/// @return A PTR to the internal state.
uint64_t IRKelonAc::getRaw() const { return _.raw; }
/// Set the raw state of the object.
/// @param[in] new_code The raw state from the native IR message.
void IRKelonAc::setRaw(const uint64_t new_code) { _.raw = new_code; }
/// Convert a standard A/C mode (stdAc::opmode_t) into it a native mode.
/// @param[in] mode A stdAc::opmode_t operation mode.
/// @return The native mode equivalent.
uint8_t IRKelonAc::convertMode(const stdAc::opmode_t mode) {
switch (mode) {
case stdAc::opmode_t::kCool: return kKelonModeCool;
case stdAc::opmode_t::kHeat: return kKelonModeHeat;
case stdAc::opmode_t::kDry: return kKelonModeDry;
case stdAc::opmode_t::kFan: return kKelonModeFan;
default: return kKelonModeSmart; // aka Auto.
}
}
/// Convert a standard A/C fan speed (stdAc::fanspeed_t) into it a native speed.
/// @param[in] fan A stdAc::fanspeed_t fan speed
/// @return The native speed equivalent.
uint8_t IRKelonAc::convertFan(stdAc::fanspeed_t fan) {
switch (fan) {
case stdAc::fanspeed_t::kMin:
case stdAc::fanspeed_t::kLow: return kKelonFanMin;
case stdAc::fanspeed_t::kMedium: return kKelonFanMedium;
case stdAc::fanspeed_t::kHigh:
case stdAc::fanspeed_t::kMax: return kKelonFanMax;
default: return kKelonFanAuto;
}
}
/// Convert a native mode to it's stdAc::opmode_t equivalent.
/// @param[in] mode A native operating mode value.
/// @return The stdAc::opmode_t equivalent.
stdAc::opmode_t IRKelonAc::toCommonMode(const uint8_t mode) {
switch (mode) {
case kKelonModeCool: return stdAc::opmode_t::kCool;
case kKelonModeHeat: return stdAc::opmode_t::kHeat;
case kKelonModeDry: return stdAc::opmode_t::kDry;
case kKelonModeFan: return stdAc::opmode_t::kFan;
default: return stdAc::opmode_t::kAuto;
}
}
/// Convert a native fan speed to it's stdAc::fanspeed_t equivalent.
/// @param[in] speed A native fan speed value.
/// @return The stdAc::fanspeed_t equivalent.
stdAc::fanspeed_t IRKelonAc::toCommonFanSpeed(const uint8_t speed) {
switch (speed) {
case kKelonFanMin: return stdAc::fanspeed_t::kLow;
case kKelonFanMedium: return stdAc::fanspeed_t::kMedium;
case kKelonFanMax: return stdAc::fanspeed_t::kHigh;
default: return stdAc::fanspeed_t::kAuto;
}
}
/// Convert the internal A/C object state to it's stdAc::state_t equivalent.
/// @return A stdAc::state_t containing the current settings.
stdAc::state_t IRKelonAc::toCommon(const stdAc::state_t *prev) const {
stdAc::state_t result{};
result.protocol = decode_type_t::KELON;
result.model = -1; // Unused.
// AC only supports toggling it
result.power = (prev == nullptr || prev->power) ^ _.PowerToggle;
result.mode = toCommonMode(getMode());
result.celsius = true;
result.degrees = getTemp();
result.fanspeed = toCommonFanSpeed(getFan());
// AC only supports toggling it
result.swingv = stdAc::swingv_t::kAuto;
if (prev != nullptr &&
(prev->swingv != stdAc::swingv_t::kAuto) ^ _.SwingVToggle)
result.swingv = stdAc::swingv_t::kOff;
result.turbo = getSupercool();
result.sleep = getSleep() ? 0 : -1;
// Not supported.
result.swingh = stdAc::swingh_t::kOff;
result.light = true;
result.beep = true;
result.quiet = false;
result.filter = false;
result.clean = false;
result.econo = false;
result.clock = -1;
return result;
}
/// Convert the internal settings into a human readable string.
/// @return A String.
String IRKelonAc::toString() const {
String result = "";
// Reserve some heap for the string to reduce fragging.
result.reserve(160);
result += addTempToString(getTemp(), true, false);
result += addModeToString(_.Mode, kKelonModeSmart, kKelonModeCool,
kKelonModeHeat, kKelonModeDry, kKelonModeFan);
result += addFanToString(_.Fan, kKelonFanMax, kKelonFanMin, kKelonFanAuto,
-1, kKelonFanMedium, kKelonFanMax);
result += addBoolToString(_.SleepEnabled, kSleepStr);
result += addSignedIntToString(getDryGrade(), kDryStr);
result += addLabeledString(
getTimerEnabled() ? (getTimer() > 0 ? minsToString(getTimer()) : kOnStr)
: kOffStr,
kTimerStr);
result += addBoolToString(getSupercool(), kTurboStr);
if (getTogglePower())
result += addBoolToString(true, kPowerToggleStr);
if (getToggleSwingVertical())
result += addBoolToString(true, kSwingVToggleStr);
return result;
}
#if SEND_KELON168
/// Send a Kelon 168 bit / 21 byte message.
/// Status: BETA / Probably works.
/// @param[in] data The data to be transmitted.
/// @param[in] nbytes Nr. of bytes of data to be sent.
/// @param[in] repeat The number of times the command is to be repeated.
void IRsend::sendKelon168(const uint8_t data[], const uint16_t nbytes,
const uint16_t repeat) {
assert(kKelon168StateLength == kKelon168Section1Size + kKelon168Section2Size +
kKelon168Section3Size);
// Enough bytes to send a proper message?
if (nbytes < kKelon168StateLength) return;
for (uint16_t r = 0; r <= repeat; r++) {
// Section #1 (48 bits)
sendGeneric(kKelonHdrMark, kKelonHdrSpace,
kKelonBitMark, kKelonOneSpace,
kKelonBitMark, kKelonZeroSpace,
kKelonBitMark, kKelon168FooterSpace,
data, kKelon168Section1Size, kKelonFreq, false, // LSB First.
0, // No repeats here
kDutyDefault);
// Section #2 (64 bits)
sendGeneric(0, 0,
kKelonBitMark, kKelonOneSpace,
kKelonBitMark, kKelonZeroSpace,
kKelonBitMark, kKelon168FooterSpace,
data + kKelon168Section1Size, kKelon168Section2Size,
kKelonFreq, false, // LSB First.
0, // No repeats here
kDutyDefault);
// Section #3 (56 bits)
sendGeneric(0, 0,
kKelonBitMark, kKelonOneSpace,
kKelonBitMark, kKelonZeroSpace,
kKelonBitMark, kKelonGap,
data + kKelon168Section1Size + kKelon168Section2Size,
nbytes - (kKelon168Section1Size + kKelon168Section2Size),
kKelonFreq, false, // LSB First.
0, // No repeats here
kDutyDefault);
}
}
#endif // SEND_KELON168
#if DECODE_KELON168
/// Decode the supplied Kelon 168 bit / 21 byte message.
/// Status: BETA / Probably Working.
/// @param[in,out] results Ptr to the data to decode & where to store the result
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// @param[in] strict Flag indicating if we should perform strict matching.
/// @return True if it can decode it, false if it can't.
bool IRrecv::decodeKelon168(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
if (strict && nbits != kKelon168Bits) return false;
if (results->rawlen <= 2 * nbits + kHeader + kFooter * 2 - 1 + offset)
return false; // Can't possibly be a valid Kelon 168 bit message.
uint16_t used = 0;
used = matchGeneric(results->rawbuf + offset, results->state,
results->rawlen - offset, kKelon168Section1Size * 8,
kKelonHdrMark, kKelonHdrSpace,
kKelonBitMark, kKelonOneSpace,
kKelonBitMark, kKelonZeroSpace,
kKelonBitMark, kKelon168FooterSpace,
false, _tolerance, 0, false);
if (!used) return false; // Failed to match.
offset += used;
used = matchGeneric(results->rawbuf + offset,
results->state + kKelon168Section1Size,
results->rawlen - offset, kKelon168Section2Size * 8,
0, 0,
kKelonBitMark, kKelonOneSpace,
kKelonBitMark, kKelonZeroSpace,
kKelonBitMark, kKelon168FooterSpace,
false, _tolerance, 0, false);
if (!used) return false; // Failed to match.
offset += used;
used = matchGeneric(results->rawbuf + offset,
results->state + (kKelon168Section1Size +
kKelon168Section2Size),
results->rawlen - offset,
nbits - (kKelon168Section1Size +
kKelon168Section2Size) * 8,
0, 0,
kKelonBitMark, kKelonOneSpace,
kKelonBitMark, kKelonZeroSpace,
kKelonBitMark, kKelonGap,
true, _tolerance, 0, false);
if (!used) return false; // Failed to match.
results->decode_type = decode_type_t::KELON168;
results->bits = nbits;
return true;
}
#endif // DECODE_KELON168

View File

@@ -0,0 +1,142 @@
// Copyright 2021 Davide Depau
/// @file
/// @brief Support for Kelan AC protocol.
/// @note Both sending and decoding should be functional for models of series
/// KELON ON/OFF 9000-12000.
/// All features of the standard remote are implemented.
///
/// @note Unsupported:
/// - Explicit on/off due to AC unit limitations
/// - Explicit swing position due to AC unit limitations
/// - Fahrenheit.
///
/// For KELON168:
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1745
// Supports:
// Brand: Kelon, Model: ON/OFF 9000-12000 (KELON)
// Brand: Kelon, Model: DG11R2-01 remote (KELON168)
// Brand: Kelon, Model: AST-09UW4RVETG00A A/C (KELON168)
// Brand: Hisense, Model: AST-09UW4RVETG00A A/C (KELON168)
#ifndef IR_KELON_H_
#define IR_KELON_H_
#ifdef UNIT_TEST
#include "IRsend_test.h"
#endif
#include "IRremoteESP8266.h"
#include "IRsend.h"
#include "IRutils.h"
union KelonProtocol {
uint64_t raw;
struct {
uint8_t preamble[2];
uint8_t Fan: 2;
uint8_t PowerToggle: 1;
uint8_t SleepEnabled: 1;
uint8_t DehumidifierGrade: 3;
uint8_t SwingVToggle: 1;
uint8_t Mode: 3;
uint8_t TimerEnabled: 1;
uint8_t Temperature: 4;
uint8_t TimerHalfHour: 1;
uint8_t TimerHours: 6;
uint8_t SmartModeEnabled: 1;
uint8_t pad1: 4;
uint8_t SuperCoolEnabled1: 1;
uint8_t pad2: 2;
uint8_t SuperCoolEnabled2: 1;
};
};
// Constants
const uint8_t kKelonModeHeat = 0;
const uint8_t kKelonModeSmart = 1; // (temp = 26C, but not shown)
const uint8_t kKelonModeCool = 2;
const uint8_t kKelonModeDry = 3; // (temp = 25C, but not shown)
const uint8_t kKelonModeFan = 4; // (temp = 25C, but not shown)
const uint8_t kKelonFanAuto = 0;
// Note! Kelon fan speeds are actually 0:AUTO, 1:MAX, 2:MED, 3:MIN
// Since this is insane, I decided to invert them in the public API, they are
// converted back in setFan/getFan
const uint8_t kKelonFanMin = 1;
const uint8_t kKelonFanMedium = 2;
const uint8_t kKelonFanMax = 3;
const int8_t kKelonDryGradeMin = -2;
const int8_t kKelonDryGradeMax = +2;
const uint8_t kKelonMinTemp = 18;
const uint8_t kKelonMaxTemp = 32;
class IRKelonAc {
public:
explicit IRKelonAc(uint16_t pin, bool inverted = false,
bool use_modulation = true);
void stateReset(void);
#if SEND_KELON
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(); }
/// Since the AC does not support actually setting the power state to a known
/// value, this utility allow ensuring the AC is on or off by exploiting
/// the fact that the AC, according to the user manual, will always turn on
/// when setting it to "smart" or "super" mode.
void ensurePower(const bool on);
#endif // SEND_KELON
void begin(void);
void setTogglePower(const bool toggle);
bool getTogglePower(void) const;
void setTemp(const uint8_t degrees);
uint8_t getTemp(void) const;
void setFan(const uint8_t speed);
uint8_t getFan(void) const;
void setDryGrade(const int8_t grade);
int8_t getDryGrade(void) const;
void setMode(const uint8_t mode);
uint8_t getMode(void) const;
void setToggleSwingVertical(const bool toggle);
bool getToggleSwingVertical(void) const;
void setSleep(const bool on);
bool getSleep(void) const;
void setSupercool(const bool on);
bool getSupercool(void) const;
void setTimer(const uint16_t mins);
uint16_t getTimer(void) const;
void setTimerEnabled(const bool on);
bool getTimerEnabled(void) const;
uint64_t getRaw(void) const;
void setRaw(const uint64_t new_code);
static uint8_t convertMode(const stdAc::opmode_t mode);
static uint8_t convertFan(const stdAc::fanspeed_t fan);
static stdAc::opmode_t toCommonMode(const uint8_t mode);
static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed);
stdAc::state_t toCommon(const stdAc::state_t *prev = nullptr) const;
String toString(void) const;
private:
#ifndef UNIT_TEST
IRsend _irsend; ///< Instance of the IR send class
#else // UNIT_TEST
/// @cond IGNORE
IRsendTest _irsend; ///< Instance of the testing IR send class
/// @endcond
#endif // UNIT_TEST
KelonProtocol _;
// Used when exiting supercool mode
uint8_t _previousMode = 0;
uint8_t _previousTemp = kKelonMinTemp;
uint8_t _previousFan = kKelonFanAuto;
};
#endif // IR_KELON_H_

View File

@@ -0,0 +1,581 @@
// Copyright 2016 David Conran
/// @file
/// @brief Support for Kelvinator A/C protocols.
/// Code to emulate IR Kelvinator YALIF remote control unit, which should
/// control at least the following Kelvinator A/C units:
/// KSV26CRC, KSV26HRC, KSV35CRC, KSV35HRC, KSV53HRC, KSV62HRC, KSV70CRC,
/// KSV70HRC, KSV80HRC.
///
/// @note Unsupported:
/// - All Sleep modes.
/// - All Timer modes.
/// - "I Feel" button & mode.
/// - Energy Saving mode.
/// - Low Heat mode.
/// - Fahrenheit.
#include "ir_Kelvinator.h"
#include <algorithm>
#include <cstring>
#ifndef ARDUINO
#include <string>
#endif
#include "IRac.h"
#include "IRrecv.h"
#include "IRsend.h"
#include "IRtext.h"
#include "IRutils.h"
// Constants
const uint16_t kKelvinatorTick = 85;
const uint16_t kKelvinatorHdrMarkTicks = 106;
const uint16_t kKelvinatorHdrMark = kKelvinatorHdrMarkTicks * kKelvinatorTick;
const uint16_t kKelvinatorHdrSpaceTicks = 53;
const uint16_t kKelvinatorHdrSpace = kKelvinatorHdrSpaceTicks * kKelvinatorTick;
const uint16_t kKelvinatorBitMarkTicks = 8;
const uint16_t kKelvinatorBitMark = kKelvinatorBitMarkTicks * kKelvinatorTick;
const uint16_t kKelvinatorOneSpaceTicks = 18;
const uint16_t kKelvinatorOneSpace = kKelvinatorOneSpaceTicks * kKelvinatorTick;
const uint16_t kKelvinatorZeroSpaceTicks = 6;
const uint16_t kKelvinatorZeroSpace =
kKelvinatorZeroSpaceTicks * kKelvinatorTick;
const uint16_t kKelvinatorGapSpaceTicks = 235;
const uint16_t kKelvinatorGapSpace = kKelvinatorGapSpaceTicks * kKelvinatorTick;
const uint8_t kKelvinatorCmdFooter = 2;
const uint8_t kKelvinatorCmdFooterBits = 3;
const uint8_t kKelvinatorChecksumStart = 10;
using irutils::addBoolToString;
using irutils::addIntToString;
using irutils::addLabeledString;
using irutils::addModeToString;
using irutils::addFanToString;
using irutils::addTempToString;
using irutils::addSwingVToString;
#if SEND_KELVINATOR
/// Send a Kelvinator A/C message.
/// Status: STABLE / Known working.
/// @param[in] data The message to be sent.
/// @param[in] nbytes The number of bytes of message to be sent.
/// @param[in] repeat The number of times the command is to be repeated.
void IRsend::sendKelvinator(const unsigned char data[], const uint16_t nbytes,
const uint16_t repeat) {
if (nbytes < kKelvinatorStateLength)
return; // Not enough bytes to send a proper message.
for (uint16_t r = 0; r <= repeat; r++) {
// Command Block #1 (4 bytes)
sendGeneric(kKelvinatorHdrMark, kKelvinatorHdrSpace, kKelvinatorBitMark,
kKelvinatorOneSpace, kKelvinatorBitMark, kKelvinatorZeroSpace,
0, 0, // No Footer yet.
data, 4, 38, false, 0, 50);
// Send Footer for the command block (3 bits (b010))
sendGeneric(0, 0, // No Header
kKelvinatorBitMark, kKelvinatorOneSpace, kKelvinatorBitMark,
kKelvinatorZeroSpace, kKelvinatorBitMark, kKelvinatorGapSpace,
kKelvinatorCmdFooter, kKelvinatorCmdFooterBits, 38, false, 0,
50);
// Data Block #1 (4 bytes)
sendGeneric(0, 0, // No header
kKelvinatorBitMark, kKelvinatorOneSpace, kKelvinatorBitMark,
kKelvinatorZeroSpace, kKelvinatorBitMark,
kKelvinatorGapSpace * 2, data + 4, 4, 38, false, 0, 50);
// Command Block #2 (4 bytes)
sendGeneric(kKelvinatorHdrMark, kKelvinatorHdrSpace, kKelvinatorBitMark,
kKelvinatorOneSpace, kKelvinatorBitMark, kKelvinatorZeroSpace,
0, 0, // No Footer yet.
data + 8, 4, 38, false, 0, 50);
// Send Footer for the command block (3 bits (B010))
sendGeneric(0, 0, // No Header
kKelvinatorBitMark, kKelvinatorOneSpace, kKelvinatorBitMark,
kKelvinatorZeroSpace, kKelvinatorBitMark, kKelvinatorGapSpace,
kKelvinatorCmdFooter, kKelvinatorCmdFooterBits, 38, false, 0,
50);
// Data Block #2 (4 bytes)
sendGeneric(0, 0, // No header
kKelvinatorBitMark, kKelvinatorOneSpace, kKelvinatorBitMark,
kKelvinatorZeroSpace, kKelvinatorBitMark,
kKelvinatorGapSpace * 2, data + 12, 4, 38, false, 0, 50);
}
}
#endif // SEND_KELVINATOR
/// 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?
IRKelvinatorAC::IRKelvinatorAC(const uint16_t pin, const bool inverted,
const bool use_modulation)
: _irsend(pin, inverted, use_modulation) { stateReset(); }
/// Reset the internals of the object to a known good state.
void IRKelvinatorAC::stateReset(void) {
for (uint8_t i = 0; i < kKelvinatorStateLength; i++) _.raw[i] = 0x0;
_.raw[3] = 0x50;
_.raw[11] = 0x70;
}
/// Set up hardware to be able to send a message.
void IRKelvinatorAC::begin(void) { _irsend.begin(); }
/// Fix up any odd conditions for the current state.
void IRKelvinatorAC::fixup(void) {
// X-Fan mode is only valid in COOL or DRY modes.
if (_.Mode != kKelvinatorCool && _.Mode != kKelvinatorDry)
setXFan(false);
// Duplicate to the 2nd command chunk.
_.raw[8] = _.raw[0];
_.raw[9] = _.raw[1];
_.raw[10] = _.raw[2];
checksum(); // Calculate the checksums
}
#if SEND_KELVINATOR
/// Send the current internal state as an IR message.
/// @param[in] repeat Nr. of times the message will be repeated.
void IRKelvinatorAC::send(const uint16_t repeat) {
_irsend.sendKelvinator(getRaw(), kKelvinatorStateLength, repeat);
}
#endif // SEND_KELVINATOR
/// Get the raw state of the object, suitable to be sent with the appropriate
/// IRsend object method.
/// @return A PTR to the internal state.
uint8_t *IRKelvinatorAC::getRaw(void) {
fixup(); // Ensure correct settings before sending.
return _.raw;
}
/// Set the raw state of the object.
/// @param[in] new_code The raw state from the native IR message.
void IRKelvinatorAC::setRaw(const uint8_t new_code[]) {
std::memcpy(_.raw, new_code, kKelvinatorStateLength);
}
/// Calculate the checksum for a given block of state.
/// @param[in] block A pointer to a block to calc the checksum of.
/// @param[in] length Length of the block array to checksum.
/// @return The calculated checksum value.
/// @note Many Bothans died to bring us this information.
uint8_t IRKelvinatorAC::calcBlockChecksum(const uint8_t *block,
const uint16_t length) {
uint8_t sum = kKelvinatorChecksumStart;
// Sum the lower half of the first 4 bytes of this block.
for (uint8_t i = 0; i < 4 && i < length - 1; i++, block++)
sum += (*block & 0b1111);
// then sum the upper half of the next 3 bytes.
for (uint8_t i = 4; i < length - 1; i++, block++) sum += (*block >> 4);
// Trim it down to fit into the 4 bits allowed. i.e. Mod 16.
return sum & 0b1111;
}
/// Calculate the checksum for the internal state.
void IRKelvinatorAC::checksum(void) {
_.Sum1 = calcBlockChecksum(_.raw);
_.Sum2 = calcBlockChecksum(_.raw + 8);
}
/// Verify the checksum is valid for a given state.
/// @param[in] state The array to verify the checksum of.
/// @param[in] length The size of the state.
/// @return A boolean indicating if it is valid.
bool IRKelvinatorAC::validChecksum(const uint8_t state[],
const uint16_t length) {
for (uint16_t offset = 0; offset + 7 < length; offset += 8) {
// Top 4 bits of the last byte in the block is the block's checksum.
if (GETBITS8(state[offset + 7], kHighNibble, kNibbleSize) !=
calcBlockChecksum(state + offset))
return false;
}
return true;
}
/// Set the internal state to have the power on.
void IRKelvinatorAC::on(void) { setPower(true); }
/// Set the internal state to have the power off.
void IRKelvinatorAC::off(void) {setPower(false); }
/// Set the internal state to have the desired power.
/// @param[in] on The desired power state.
void IRKelvinatorAC::setPower(const bool on) {
_.Power = on;
}
/// Get the power setting from the internal state.
/// @return A boolean indicating if the power setting.
bool IRKelvinatorAC::getPower(void) const {
return _.Power;
}
/// Set the temperature setting.
/// @param[in] degrees The temperature in degrees celsius.
void IRKelvinatorAC::setTemp(const uint8_t degrees) {
uint8_t temp = std::max(kKelvinatorMinTemp, degrees);
temp = std::min(kKelvinatorMaxTemp, temp);
_.Temp = temp - kKelvinatorMinTemp;
}
/// Get the current temperature setting.
/// @return Get current setting for temp. in degrees celsius.
uint8_t IRKelvinatorAC::getTemp(void) const {
return _.Temp + kKelvinatorMinTemp;
}
/// Set the speed of the fan.
/// @param[in] speed 0 is auto, 1-5 is the speed
void IRKelvinatorAC::setFan(const uint8_t speed) {
uint8_t fan = std::min(kKelvinatorFanMax, speed); // Bounds check
// Only change things if we need to.
if (fan != _.Fan) {
// Set the basic fan values.
_.BasicFan = std::min(kKelvinatorBasicFanMax, fan);
// Set the advanced(?) fan value.
_.Fan = fan;
// Turbo mode is turned off if we change the fan settings.
setTurbo(false);
}
}
/// Get the current fan speed setting.
/// @return The current fan speed.
uint8_t IRKelvinatorAC::getFan(void) const {
return _.Fan;
}
/// Get the current operation mode setting.
/// @return The current operation mode.
uint8_t IRKelvinatorAC::getMode(void) const {
return _.Mode;
}
/// Set the desired operation mode.
/// @param[in] mode The desired operation mode.
void IRKelvinatorAC::setMode(const uint8_t mode) {
switch (mode) {
case kKelvinatorAuto:
case kKelvinatorDry:
// When the remote is set to Auto or Dry, it defaults to 25C and doesn't
// show it.
setTemp(kKelvinatorAutoTemp);
// FALL-THRU
case kKelvinatorHeat:
case kKelvinatorCool:
case kKelvinatorFan:
_.Mode = mode;
break;
default:
setTemp(kKelvinatorAutoTemp);
_.Mode = kKelvinatorAuto;
break;
}
}
/// Set the Vertical Swing mode of the A/C.
/// @param[in] automatic Do we use the automatic setting?
/// @param[in] position The position/mode to set the vanes to.
void IRKelvinatorAC::setSwingVertical(const bool automatic,
const uint8_t position) {
_.SwingAuto = (automatic || _.SwingH);
uint8_t new_position = position;
if (!automatic) {
switch (position) {
case kKelvinatorSwingVHighest:
case kKelvinatorSwingVUpperMiddle:
case kKelvinatorSwingVMiddle:
case kKelvinatorSwingVLowerMiddle:
case kKelvinatorSwingVLowest:
break;
default:
new_position = kKelvinatorSwingVOff;
}
} else {
switch (position) {
case kKelvinatorSwingVAuto:
case kKelvinatorSwingVLowAuto:
case kKelvinatorSwingVMiddleAuto:
case kKelvinatorSwingVHighAuto:
break;
default:
new_position = kKelvinatorSwingVAuto;
}
}
_.SwingV = new_position;
}
/// Get the Vertical Swing Automatic mode setting of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRKelvinatorAC::getSwingVerticalAuto(void) const {
return _.SwingV & 0b0001;
}
/// Get the Vertical Swing position setting of the A/C.
/// @return The native position/mode.
uint8_t IRKelvinatorAC::getSwingVerticalPosition(void) const {
return _.SwingV;
}
/// Control the current horizontal swing setting.
/// @param[in] on The desired setting.
void IRKelvinatorAC::setSwingHorizontal(const bool on) {
_.SwingH = on;
_.SwingAuto = (on || (_.SwingV & 0b0001));
}
/// Is the horizontal swing setting on?
/// @return The current value.
bool IRKelvinatorAC::getSwingHorizontal(void) const {
return _.SwingH;
}
/// Control the current Quiet setting.
/// @param[in] on The desired setting.
void IRKelvinatorAC::setQuiet(const bool on) {
_.Quiet = on;
}
/// Is the Quiet setting on?
/// @return The current value.
bool IRKelvinatorAC::getQuiet(void) const {
return _.Quiet;
}
/// Control the current Ion Filter setting.
/// @param[in] on The desired setting.
void IRKelvinatorAC::setIonFilter(const bool on) {
_.IonFilter = on;
}
/// Is the Ion Filter setting on?
/// @return The current value.
bool IRKelvinatorAC::getIonFilter(void) const {
return _.IonFilter;
}
/// Control the current Light setting.
/// i.e. The LED display on the A/C unit that shows the basic settings.
/// @param[in] on The desired setting.
void IRKelvinatorAC::setLight(const bool on) {
_.Light = on;
}
/// Is the Light (Display) setting on?
/// @return The current value.
bool IRKelvinatorAC::getLight(void) const {
return _.Light;
}
/// Control the current XFan setting.
/// This setting will cause the unit blow air after power off to dry out the
/// A/C device.
/// @note XFan mode is only valid in Cool or Dry mode.
/// @param[in] on The desired setting.
void IRKelvinatorAC::setXFan(const bool on) {
_.XFan = on;
}
/// Is the XFan setting on?
/// @return The current value.
bool IRKelvinatorAC::getXFan(void) const {
return _.XFan;
}
/// Control the current Turbo setting.
/// @note Turbo mode is turned off if the fan speed is changed.
/// @param[in] on The desired setting.
void IRKelvinatorAC::setTurbo(const bool on) {
_.Turbo = on;
}
/// Is the Turbo setting on?
/// @return The current value.
bool IRKelvinatorAC::getTurbo(void) const {
return _.Turbo;
}
/// Convert a standard A/C mode (stdAc::opmode_t) into it a native mode.
/// @param[in] mode A stdAc::opmode_t operation mode.
/// @return The native mode equivalent.
uint8_t IRKelvinatorAC::convertMode(const stdAc::opmode_t mode) {
switch (mode) {
case stdAc::opmode_t::kCool: return kKelvinatorCool;
case stdAc::opmode_t::kHeat: return kKelvinatorHeat;
case stdAc::opmode_t::kDry: return kKelvinatorDry;
case stdAc::opmode_t::kFan: return kKelvinatorFan;
default: return kKelvinatorAuto;
}
}
/// Convert a stdAc::swingv_t enum into it's native setting.
/// @param[in] swingv The enum to be converted.
/// @return The native equivalent of the enum.
uint8_t IRKelvinatorAC::convertSwingV(const stdAc::swingv_t swingv) {
switch (swingv) {
case stdAc::swingv_t::kHighest: return kKelvinatorSwingVHighest;
case stdAc::swingv_t::kHigh: return kKelvinatorSwingVHighAuto;
case stdAc::swingv_t::kMiddle: return kKelvinatorSwingVMiddle;
case stdAc::swingv_t::kLow: return kKelvinatorSwingVLowAuto;
case stdAc::swingv_t::kLowest: return kKelvinatorSwingVLowest;
default: return kKelvinatorSwingVAuto;
}
}
/// Convert a native mode to it's stdAc::opmode_t equivalent.
/// @param[in] mode A native operating mode value.
/// @return The stdAc::opmode_t equivalent.
stdAc::opmode_t IRKelvinatorAC::toCommonMode(const uint8_t mode) {
switch (mode) {
case kKelvinatorCool: return stdAc::opmode_t::kCool;
case kKelvinatorHeat: return stdAc::opmode_t::kHeat;
case kKelvinatorDry: return stdAc::opmode_t::kDry;
case kKelvinatorFan: return stdAc::opmode_t::kFan;
default: return stdAc::opmode_t::kAuto;
}
}
/// Convert a native fan speed to it's stdAc::fanspeed_t equivalent.
/// @param[in] speed A native fan speed value.
/// @return The stdAc::fanspeed_t equivalent.
stdAc::fanspeed_t IRKelvinatorAC::toCommonFanSpeed(const uint8_t speed) {
return (stdAc::fanspeed_t)speed;
}
/// Convert the internal A/C object state to it's stdAc::state_t equivalent.
/// @return A stdAc::state_t containing the current settings.
stdAc::state_t IRKelvinatorAC::toCommon(void) const {
stdAc::state_t result{};
result.protocol = decode_type_t::KELVINATOR;
result.model = -1; // Unused.
result.power = _.Power;
result.mode = toCommonMode(_.Mode);
result.celsius = true;
result.degrees = getTemp();
result.fanspeed = toCommonFanSpeed(_.Fan);
result.swingv = _.SwingV ? stdAc::swingv_t::kAuto : stdAc::swingv_t::kOff;
result.swingh = _.SwingH ? stdAc::swingh_t::kAuto : stdAc::swingh_t::kOff;
result.quiet = _.Quiet;
result.turbo = _.Turbo;
result.light = _.Light;
result.filter = _.IonFilter;
result.clean = _.XFan;
// Not supported.
result.econo = false;
result.beep = false;
result.sleep = -1;
result.clock = -1;
return result;
}
/// Convert the internal settings into a human readable string.
/// @return A String.
String IRKelvinatorAC::toString(void) const {
String result = "";
result.reserve(160); // Reserve some heap for the string to reduce fragging.
result += addBoolToString(_.Power, kPowerStr, false);
result += addModeToString(_.Mode, kKelvinatorAuto, kKelvinatorCool,
kKelvinatorHeat, kKelvinatorDry, kKelvinatorFan);
result += addTempToString(getTemp());
result += addFanToString(_.Fan, kKelvinatorFanMax, kKelvinatorFanMin,
kKelvinatorFanAuto, kKelvinatorFanAuto,
kKelvinatorBasicFanMax);
result += addBoolToString(_.Turbo, kTurboStr);
result += addBoolToString(_.Quiet, kQuietStr);
result += addBoolToString(_.XFan, kXFanStr);
result += addBoolToString(_.IonFilter, kIonStr);
result += addBoolToString(_.Light, kLightStr);
result += addBoolToString(_.SwingH, kSwingHStr);
result += addSwingVToString(_.SwingV, kKelvinatorSwingVAuto,
kKelvinatorSwingVHighest,
kKelvinatorSwingVHighAuto,
kKelvinatorSwingVUpperMiddle,
kKelvinatorSwingVMiddle,
kKelvinatorSwingVLowerMiddle,
kKelvinatorSwingVLowAuto,
kKelvinatorSwingVLowest,
kKelvinatorSwingVOff,
kKelvinatorSwingVAuto, kKelvinatorSwingVAuto,
kKelvinatorSwingVAuto);
return result;
}
#if DECODE_KELVINATOR
/// Decode the supplied Kelvinator message.
/// Status: STABLE / Known working.
/// @param[in,out] results Ptr to the data to decode & where to store the decode
/// result.
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// @param[in] strict Flag indicating if we should perform strict matching.
/// @return A boolean. True if it can decode it, false if it can't.
bool IRrecv::decodeKelvinator(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
if (results->rawlen <=
2 * (nbits + kKelvinatorCmdFooterBits) + (kHeader + kFooter + 1) * 2 - 1 +
offset)
return false; // Can't possibly be a valid Kelvinator message.
if (strict && nbits != kKelvinatorBits)
return false; // Not strictly a Kelvinator message.
// There are two messages back-to-back in a full Kelvinator IR message
// sequence.
int8_t pos = 0;
for (uint8_t s = 0; s < 2; s++) {
match_result_t data_result;
uint16_t used;
// Header + Data Block #1 (32 bits)
used = matchGeneric(results->rawbuf + offset, results->state + pos,
results->rawlen - offset, 32,
kKelvinatorHdrMark, kKelvinatorHdrSpace,
kKelvinatorBitMark, kKelvinatorOneSpace,
kKelvinatorBitMark, kKelvinatorZeroSpace,
0, 0, false,
_tolerance, kMarkExcess, false);
if (used == 0) return false;
offset += used;
pos += 4;
// Command data footer (3 bits, B010)
data_result = matchData(
&(results->rawbuf[offset]), kKelvinatorCmdFooterBits,
kKelvinatorBitMark, kKelvinatorOneSpace,
kKelvinatorBitMark, kKelvinatorZeroSpace,
_tolerance, kMarkExcess, false);
if (data_result.success == false) return false;
if (data_result.data != kKelvinatorCmdFooter) return false;
offset += data_result.used;
// Gap + Data (Options) (32 bits)
used = matchGeneric(results->rawbuf + offset, results->state + pos,
results->rawlen - offset, 32,
kKelvinatorBitMark, kKelvinatorGapSpace,
kKelvinatorBitMark, kKelvinatorOneSpace,
kKelvinatorBitMark, kKelvinatorZeroSpace,
kKelvinatorBitMark, kKelvinatorGapSpace * 2,
s > 0,
_tolerance, kMarkExcess, false);
if (used == 0) return false;
offset += used;
pos += 4;
}
// Compliance
if (strict) {
// Verify the message's checksum is correct.
if (!IRKelvinatorAC::validChecksum(results->state)) return false;
}
// Success
results->decode_type = decode_type_t::KELVINATOR;
results->bits = nbits;
// 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_KELVINATOR

View File

@@ -0,0 +1,198 @@
// Copyright 2016 David Conran
/// @file
/// @brief Support for Kelvinator A/C protocols.
// Supports:
// Brand: Kelvinator, Model: YALIF Remote
// Brand: Kelvinator, Model: KSV26CRC A/C
// Brand: Kelvinator, Model: KSV26HRC A/C
// Brand: Kelvinator, Model: KSV35CRC A/C
// Brand: Kelvinator, Model: KSV35HRC A/C
// Brand: Kelvinator, Model: KSV53HRC A/C
// Brand: Kelvinator, Model: KSV62HRC A/C
// Brand: Kelvinator, Model: KSV70CRC A/C
// Brand: Kelvinator, Model: KSV70HRC A/C
// Brand: Kelvinator, Model: KSV80HRC A/C
// Brand: Gree, Model: YAPOF3 remote
// Brand: Gree, Model: YAP0F8 remote
// Brand: Sharp, Model: YB1FA remote
// Brand: Sharp, Model: A5VEY A/C
#ifndef IR_KELVINATOR_H_
#define IR_KELVINATOR_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
/// Native representation of a Kelvinator A/C message.
union KelvinatorProtocol{
uint8_t raw[kKelvinatorStateLength]; ///< The state in IR code form.
struct {
// Byte 0
uint8_t Mode :3;
uint8_t Power :1;
uint8_t BasicFan :2;
uint8_t SwingAuto :1;
uint8_t :1; // Sleep Modes 1 & 3 (1 = On, 0 = Off)
// Byte 1
uint8_t Temp :4; // Degrees C.
uint8_t :4;
// Byte 2
uint8_t :4;
uint8_t Turbo :1;
uint8_t Light :1;
uint8_t IonFilter :1;
uint8_t XFan :1;
// Byte 3
uint8_t :4;
uint8_t :2; // (possibly timer related) (Typically 0b01)
uint8_t :2; // End of command block (B01)
// (B010 marker and a gap of 20ms)
// Byte 4
uint8_t SwingV :4;
uint8_t SwingH :1;
uint8_t :3;
// Byte 5~6
uint8_t pad0[2]; // Timer related. Typically 0 except when timer in use.
// Byte 7
uint8_t :4; // (Used in Timer mode)
uint8_t Sum1 :4; // checksum of the previous bytes (0-6)
// (gap of 40ms)
// (header mark and space)
// Byte 8~10
uint8_t pad1[3]; // Repeat of byte 0~2
// Byte 11
uint8_t :4;
uint8_t :2; // (possibly timer related) (Typically 0b11)
uint8_t :2; // End of command block (B01)
// (B010 marker and a gap of 20ms)
// Byte 12
uint8_t :1; // Sleep mode 2 (1 = On, 0=Off)
uint8_t :6; // (Used in Sleep Mode 3, Typically 0b000000)
uint8_t Quiet :1;
// Byte 13
uint8_t :8; // (Sleep Mode 3 related, Typically 0x00)
// Byte 14
uint8_t :4; // (Sleep Mode 3 related, Typically 0b0000)
uint8_t Fan :3;
// Byte 15
uint8_t :4;
uint8_t Sum2 :4; // checksum of the previous bytes (8-14)
};
};
// Constants
const uint8_t kKelvinatorAuto = 0; // (temp = 25C)
const uint8_t kKelvinatorCool = 1;
const uint8_t kKelvinatorDry = 2; // (temp = 25C, but not shown)
const uint8_t kKelvinatorFan = 3;
const uint8_t kKelvinatorHeat = 4;
const uint8_t kKelvinatorBasicFanMax = 3;
const uint8_t kKelvinatorFanAuto = 0;
const uint8_t kKelvinatorFanMin = 1;
const uint8_t kKelvinatorFanMax = 5;
const uint8_t kKelvinatorMinTemp = 16; // 16C
const uint8_t kKelvinatorMaxTemp = 30; // 30C
const uint8_t kKelvinatorAutoTemp = 25; // 25C
const uint8_t kKelvinatorSwingVOff = 0b0000; // 0
const uint8_t kKelvinatorSwingVAuto = 0b0001; // 1
const uint8_t kKelvinatorSwingVHighest = 0b0010; // 2
const uint8_t kKelvinatorSwingVUpperMiddle = 0b0011; // 3
const uint8_t kKelvinatorSwingVMiddle = 0b0100; // 4
const uint8_t kKelvinatorSwingVLowerMiddle = 0b0101; // 5
const uint8_t kKelvinatorSwingVLowest = 0b0110; // 6
const uint8_t kKelvinatorSwingVLowAuto = 0b0111; // 7
const uint8_t kKelvinatorSwingVMiddleAuto = 0b1001; // 9
const uint8_t kKelvinatorSwingVHighAuto = 0b1011; // 11
// Legacy defines (Deprecated)
#define KELVINATOR_MIN_TEMP kKelvinatorMinTemp
#define KELVINATOR_MAX_TEMP kKelvinatorMaxTemp
#define KELVINATOR_HEAT kKelvinatorHeat
#define KELVINATOR_FAN_MAX kKelvinatorFanMax
#define KELVINATOR_FAN_AUTO kKelvinatorFanAuto
#define KELVINATOR_FAN kKelvinatorFan
#define KELVINATOR_DRY kKelvinatorDry
#define KELVINATOR_COOL kKelvinatorCool
#define KELVINATOR_BASIC_FAN_MAX kKelvinatorBasicFanMax
#define KELVINATOR_AUTO_TEMP kKelvinatorAutoTemp
#define KELVINATOR_AUTO kKelvinatorAuto
// Classes
/// Class for handling detailed Kelvinator A/C messages.
class IRKelvinatorAC {
public:
explicit IRKelvinatorAC(const uint16_t pin, const bool inverted = false,
const bool use_modulation = true);
void stateReset(void);
#if SEND_KELVINATOR
void send(const uint16_t repeat = kKelvinatorDefaultRepeat);
/// 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_KELVINATOR
void begin(void);
void on(void);
void off(void);
void setPower(const bool on);
bool getPower(void) const;
void setTemp(const uint8_t degrees);
uint8_t getTemp(void) const;
void setFan(const uint8_t speed);
uint8_t getFan(void) const;
void setMode(const uint8_t mode);
uint8_t getMode(void) const;
void setSwingVertical(const bool automatic, const uint8_t position);
bool getSwingVerticalAuto(void) const;
uint8_t getSwingVerticalPosition(void) const;
static uint8_t convertSwingV(const stdAc::swingv_t swingv);
static stdAc::swingv_t toCommonSwingV(const uint8_t pos);
void setSwingHorizontal(const bool on);
bool getSwingHorizontal(void) const;
void setQuiet(const bool on);
bool getQuiet(void) const;
void setIonFilter(const bool on);
bool getIonFilter(void) const;
void setLight(const bool on);
bool getLight(void) const;
void setXFan(const bool on);
bool getXFan(void) const;
void setTurbo(const bool on);
bool getTurbo(void) const;
uint8_t* getRaw(void);
void setRaw(const uint8_t new_code[]);
static uint8_t calcBlockChecksum(
const uint8_t* block, const uint16_t length = kKelvinatorStateLength / 2);
static bool validChecksum(const uint8_t state[],
const uint16_t length = kKelvinatorStateLength);
static uint8_t convertMode(const stdAc::opmode_t mode);
static stdAc::opmode_t toCommonMode(const uint8_t mode);
static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed);
stdAc::state_t toCommon(void) const;
String toString(void) const;
#ifndef UNIT_TEST
private:
IRsend _irsend; ///< Instance of the IR send class
#else // UNIT_TEST
/// @cond IGNORE
IRsendTest _irsend; ///< Instance of the testing IR send class
/// @endcond
#endif // UNIT_TEST
KelvinatorProtocol _;
void checksum(void);
void fixup(void);
};
#endif // IR_KELVINATOR_H_

View File

@@ -0,0 +1,862 @@
// Copyright 2015 Darryl Smith
// Copyright 2015 cheaplin
// Copyright 2017-2021 David Conran
/// @file
/// @brief Support for LG protocols.
/// LG decode originally added by Darryl Smith (based on the JVC protocol)
/// LG send originally added by https://github.com/chaeplin
/// @see https://github.com/arendst/Tasmota/blob/54c2eb283a02e4287640a4595e506bc6eadbd7f2/sonoff/xdrv_05_irremote.ino#L327-438
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1513
#include "ir_LG.h"
#include <algorithm>
#include "IRac.h"
#include "IRrecv.h"
#include "IRsend.h"
#include "IRtext.h"
#include "IRutils.h"
using irutils::addBoolToString;
using irutils::addModeToString;
using irutils::addModelToString;
using irutils::addFanToString;
using irutils::addTempToString;
using irutils::addToggleToString;
using irutils::addSwingVToString;
using irutils::addIntToString;
// Constants
// Common timings
const uint16_t kLgBitMark = 550; ///< uSeconds.
const uint16_t kLgOneSpace = 1600; ///< uSeconds.
const uint16_t kLgZeroSpace = 550; ///< uSeconds.
const uint16_t kLgRptSpace = 2250; ///< uSeconds.
const uint16_t kLgMinGap = 39750; ///< uSeconds.
const uint32_t kLgMinMessageLength = 108050; ///< uSeconds.
// LG (28 Bit)
const uint16_t kLgHdrMark = 8500; ///< uSeconds.
const uint16_t kLgHdrSpace = 4250; ///< uSeconds.
// LG (32 Bit)
const uint16_t kLg32HdrMark = 4500; ///< uSeconds.
const uint16_t kLg32HdrSpace = 4450; ///< uSeconds.
const uint16_t kLg32RptHdrMark = 8950; ///< uSeconds.
// LG2 (28 Bit)
const uint16_t kLg2HdrMark = 3200; ///< uSeconds.
const uint16_t kLg2HdrSpace = 9900; ///< uSeconds.
const uint16_t kLg2BitMark = 480; ///< uSeconds.
const uint32_t kLgAcAKB74955603DetectionMask = 0x0000080;
const uint8_t kLgAcChecksumSize = 4; ///< Size in bits.
// Signature has the checksum removed, and another bit to match both Auto & Off.
const uint8_t kLgAcSwingHOffsetSize = kLgAcChecksumSize + 1;
const uint32_t kLgAcSwingHSignature = kLgAcSwingHOff >> kLgAcSwingHOffsetSize;
const uint32_t kLgAcVaneSwingVBase = 0x8813200;
#ifdef VANESWINGVPOS
#undef VANESWINGVPOS
#endif
#define VANESWINGVPOS(code) (code % kLgAcVaneSwingVSize)
#if SEND_LG
/// Send an LG formatted message. (LG)
/// Status: Beta / Should be working.
/// @param[in] data The message to be sent.
/// @param[in] nbits The number of bits of message to be sent.
/// Typically kLgBits or kLg32Bits.
/// @param[in] repeat The number of times the command is to be repeated.
/// @note LG has a separate message to indicate a repeat, like NEC does.
void IRsend::sendLG(uint64_t data, uint16_t nbits, uint16_t repeat) {
uint16_t repeatHeaderMark = 0;
uint8_t duty = kDutyDefault;
if (nbits >= kLg32Bits) {
// LG 32bit protocol is near identical to Samsung except for repeats.
sendSAMSUNG(data, nbits, 0); // Send it as a single Samsung message.
repeatHeaderMark = kLg32RptHdrMark;
duty = 33;
repeat++;
} else {
// LG (28-bit) protocol.
repeatHeaderMark = kLgHdrMark;
sendGeneric(kLgHdrMark, kLgHdrSpace, kLgBitMark, kLgOneSpace, kLgBitMark,
kLgZeroSpace, kLgBitMark, kLgMinGap, kLgMinMessageLength, data,
nbits, 38, true, 0, // Repeats are handled later.
duty);
}
// Repeat
// Protocol has a mandatory repeat-specific code sent after every command.
if (repeat)
sendGeneric(repeatHeaderMark, kLgRptSpace, 0, 0, 0, 0, // No data is sent.
kLgBitMark, kLgMinGap, kLgMinMessageLength, 0, 0, // No data.
38, true, repeat - 1, duty);
}
/// Send an LG Variant-2 formatted message. (LG2)
/// Status: Beta / Should be working.
/// @param[in] data The message to be sent.
/// @param[in] nbits The number of bits of message to be sent.
/// Typically kLgBits or kLg32Bits.
/// @param[in] repeat The number of times the command is to be repeated.
/// @note LG has a separate message to indicate a repeat, like NEC does.
void IRsend::sendLG2(uint64_t data, uint16_t nbits, uint16_t repeat) {
if (nbits >= kLg32Bits) {
// Let the original routine handle it.
sendLG(data, nbits, repeat); // Send it as a single Samsung message.
return;
}
// LGv2 (28-bit) protocol.
sendGeneric(kLg2HdrMark, kLg2HdrSpace, kLg2BitMark, kLgOneSpace, kLg2BitMark,
kLgZeroSpace, kLg2BitMark, kLgMinGap, kLgMinMessageLength, data,
nbits, 38, true, 0, // Repeats are handled later.
33); // Use a duty cycle of 33% (Testing)
// TODO(crackn): Verify the details of what repeat messages look like.
// Repeat
// Protocol has a mandatory repeat-specific code sent after every command.
if (repeat)
sendGeneric(kLg2HdrMark, kLgRptSpace, 0, 0, 0, 0, // No data is sent.
kLgBitMark, kLgMinGap, kLgMinMessageLength, 0, 0, // No data.
38, true, repeat - 1, 50);
}
/// Construct a raw 28-bit LG message code from the supplied address & command.
/// Status: STABLE / Works.
/// @param[in] address The address code.
/// @param[in] command The command code.
/// @return A raw 28-bit LG message code suitable for sendLG() etc.
/// @note Sequence of bits = address + command + checksum.
uint32_t IRsend::encodeLG(uint16_t address, uint16_t command) {
return ((address << 20) | (command << kLgAcChecksumSize) |
irutils::sumNibbles(command, 4));
}
#endif // SEND_LG
#if DECODE_LG
/// Decode the supplied LG message.
/// Status: STABLE / Working.
/// @param[in,out] results Ptr to the data to decode & where to store the result
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// Typically kLgBits or kLg32Bits.
/// @param[in] strict Flag indicating if we should perform strict matching.
/// @return True if it can decode it, false if it can't.
/// @note LG protocol has a repeat code which is 4 items long.
/// Even though the protocol has 28/32 bits of data, only 24/28 bits are
/// distinct.
/// In transmission order, the 28/32 bits are constructed as follows:
/// 8/12 bits of address + 16 bits of command + 4 bits of checksum.
/// @note LG 32bit protocol appears near identical to the Samsung protocol.
/// They possibly differ on how they repeat and initial HDR mark.
/// @see https://funembedded.wordpress.com/2014/11/08/ir-remote-control-for-lg-conditioner-using-stm32f302-mcu-on-mbed-platform/
bool IRrecv::decodeLG(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
if (nbits >= kLg32Bits) {
if (results->rawlen <= 2 * nbits + 2 * (kHeader + kFooter) - 1 + offset)
return false; // Can't possibly be a valid LG32 message.
} else {
if (results->rawlen <= 2 * nbits + kHeader - 1 + offset)
return false; // Can't possibly be a valid LG message.
}
// Compliance
if (strict && nbits != kLgBits && nbits != kLg32Bits)
return false; // Doesn't comply with expected LG protocol.
// Header (Mark)
uint32_t kHdrSpace;
if (matchMark(results->rawbuf[offset], kLgHdrMark))
kHdrSpace = kLgHdrSpace;
else if (matchMark(results->rawbuf[offset], kLg2HdrMark))
kHdrSpace = kLg2HdrSpace;
else if (matchMark(results->rawbuf[offset], kLg32HdrMark))
kHdrSpace = kLg32HdrSpace;
else
return false;
offset++;
// Set up the expected data section values.
const uint16_t kBitmark = (kHdrSpace == kLg2HdrSpace) ? kLg2BitMark
: kLgBitMark;
// Header Space + Data + Footer
uint64_t data = 0;
uint16_t used = matchGeneric(results->rawbuf + offset, &data,
results->rawlen - offset, nbits,
0, // Already matched the Header mark.
kHdrSpace,
kBitmark, kLgOneSpace, kBitmark, kLgZeroSpace,
kBitmark, kLgMinGap, true, kUseDefTol, 0, true);
if (!used) return false;
offset += used;
// Repeat
if (nbits >= kLg32Bits) {
// If we are expecting the LG 32-bit protocol, there is always
// a repeat message. So, check for it.
uint64_t unused;
if (!matchGeneric(results->rawbuf + offset, &unused,
results->rawlen - offset, 0, // No Data bits to match.
kLg32RptHdrMark, kLgRptSpace,
kBitmark, kLgOneSpace, kBitmark, kLgZeroSpace,
kBitmark, kLgMinGap, true, kUseDefTol)) return false;
}
// The 16 bits before the checksum.
uint16_t command = (data >> kLgAcChecksumSize);
// Compliance
if (strict && (data & 0xF) != irutils::sumNibbles(command, 4))
return false; // The last 4 bits sent are the expected checksum.
// Success
if (kHdrSpace == kLg2HdrSpace) // Was it an LG2 message?
results->decode_type = LG2;
else
results->decode_type = LG;
results->bits = nbits;
results->value = data;
results->command = command;
results->address = data >> 20; // The bits before the command.
return true;
}
#endif // DECODE_LG
// LG A/C Class
/// 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?
IRLgAc::IRLgAc(const uint16_t pin, const bool inverted,
const bool use_modulation)
: _irsend(pin, inverted, use_modulation) { stateReset(); }
/// Reset the internals of the object to a known good state.
void IRLgAc::stateReset(void) {
setRaw(kLgAcOffCommand);
setModel(lg_ac_remote_model_t::GE6711AR2853M);
_light = true;
_swingv = kLgAcSwingVOff;
_swingh = false;
for (uint8_t i = 0; i < kLgAcSwingVMaxVanes; i++)
_vaneswingv[i] = 0; // Reset to an unused value.
updateSwingPrev();
}
/// Set up hardware to be able to send a message.
void IRLgAc::begin(void) { _irsend.begin(); }
#if SEND_LG
/// Send the current internal state as an IR message.
/// @param[in] repeat Nr. of times the message will be repeated.
void IRLgAc::send(const uint16_t repeat) {
if (getPower()) {
_irsend.send(_protocol, getRaw(), kLgBits, repeat);
// Some models have extra/special settings & controls
switch (getModel()) {
case lg_ac_remote_model_t::LG6711A20083V:
// Only send the swing setting if we need to.
if (_swingv != _swingv_prev)
_irsend.send(_protocol, _swingv, kLgBits, repeat);
break;
case lg_ac_remote_model_t::AKB74955603:
// Only send the swing setting if we need to.
if (_swingv != _swingv_prev)
_irsend.send(_protocol, _swingv, kLgBits, repeat);
// Any "normal" command sent will always turn the light on, thus we only
// send it when we want it off. Must be sent last!
// Ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/1513#issuecomment-877283080
if (!_light) _irsend.send(_protocol, kLgAcLightToggle, kLgBits, repeat);
break;
case lg_ac_remote_model_t::AKB73757604:
// Check if we need to send any vane specific swingv's.
for (uint8_t i = 0; i < kLgAcSwingVMaxVanes; i++) // For all vanes
if (_vaneswingv[i] != _vaneswingv_prev[i]) // Only send if we must.
_irsend.send(_protocol, calcVaneSwingV(i, _vaneswingv[i]), kLgBits,
repeat);
// and if we need to send a swingh message.
if (_swingh != _swingh_prev)
_irsend.send(_protocol, _swingh ? kLgAcSwingHAuto : kLgAcSwingHOff,
kLgBits, repeat);
break;
default:
break;
}
updateSwingPrev(); // Swing changes will have been sent, so make them prev.
} else {
// Always send the special Off command if the power is set to off.
// Ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/1008#issuecomment-570763580
_irsend.send(_protocol, kLgAcOffCommand, kLgBits, repeat);
}
}
#endif // SEND_LG
/// Is the current message a normal (non-special) message?
/// @return True, if it is a normal message, False, if it is special.
bool IRLgAc::_isNormal(void) const {
switch (_.raw) {
case kLgAcOffCommand:
case kLgAcLightToggle:
return false;
}
if (isSwing()) return false;
return true;
}
/// Set the model of the A/C to emulate.
/// @param[in] model The enum of the appropriate model.
void IRLgAc::setModel(const lg_ac_remote_model_t model) {
switch (model) {
case lg_ac_remote_model_t::AKB75215403:
case lg_ac_remote_model_t::AKB74955603:
case lg_ac_remote_model_t::AKB73757604:
_protocol = decode_type_t::LG2;
break;
case lg_ac_remote_model_t::GE6711AR2853M:
case lg_ac_remote_model_t::LG6711A20083V:
_protocol = decode_type_t::LG;
break;
default:
return;
}
_model = model;
}
/// Get the model of the A/C.
/// @return The enum of the compatible model.
lg_ac_remote_model_t IRLgAc::getModel(void) const { return _model; }
/// Check if the stored code must belong to a AKB74955603 model.
/// @return true, if it is AKB74955603 message. Otherwise, false.
/// @note Internal use only.
bool IRLgAc::_isAKB74955603(void) const {
return ((_.raw & kLgAcAKB74955603DetectionMask) && _isNormal()) ||
(isSwingV() && !isSwingVToggle()) || isLightToggle();
}
/// Check if the stored code must belong to a AKB73757604 model.
/// @return true, if it is AKB73757604 message. Otherwise, false.
/// @note Internal use only.
bool IRLgAc::_isAKB73757604(void) const { return isSwingH() || isVaneSwingV(); }
/// Check if the stored code must belong to a LG6711A20083V model.
/// @return true, if it is LG6711A20083V message. Otherwise, false.
/// @note Internal use only.
bool IRLgAc::_isLG6711A20083V(void) const { return isSwingVToggle(); }
/// Get a copy of the internal state/code for this protocol.
/// @return The code for this protocol based on the current internal state.
uint32_t IRLgAc::getRaw(void) {
checksum();
return _.raw;
}
/// Set the internal state from a valid code for this protocol.
/// @param[in] new_code A valid code for this protocol.
/// @param[in] protocol A valid decode protocol type to use.
void IRLgAc::setRaw(const uint32_t new_code, const decode_type_t protocol) {
_.raw = new_code;
// Set the default model for this protocol, if the protocol is supplied.
switch (protocol) {
case decode_type_t::LG:
if (isSwingVToggle()) // This model uses a swingv toggle message.
setModel(lg_ac_remote_model_t::LG6711A20083V);
else // Assume others are a different model.
setModel(lg_ac_remote_model_t::GE6711AR2853M);
break;
case decode_type_t::LG2:
setModel(lg_ac_remote_model_t::AKB75215403);
break;
default:
// Don't change anything if it isn't an expected protocol.
break;
}
// Look for model specific settings/features to improve model detection.
if (_isAKB74955603()) {
setModel(lg_ac_remote_model_t::AKB74955603);
if (isSwingV()) _swingv = new_code;
}
if (_isAKB73757604()) {
setModel(lg_ac_remote_model_t::AKB73757604);
if (isVaneSwingV()) {
// Extract just the vane nr and position part of the message.
const uint32_t vanecode = getVaneCode(_.raw);
_vaneswingv[vanecode / kLgAcVaneSwingVSize] = VANESWINGVPOS(vanecode);
} else if (isSwingH()) {
_swingh = (_.raw == kLgAcSwingHAuto);
}
}
_temp = 15; // Ensure there is a "sane" previous temp.
_temp = getTemp();
}
/// Calculate the checksum for a given state.
/// @param[in] state The value to calc the checksum of.
/// @return The calculated checksum value.
uint8_t IRLgAc::calcChecksum(const uint32_t state) {
return irutils::sumNibbles(state >> kLgAcChecksumSize, 4);
}
/// Verify the checksum is valid for a given state.
/// @param[in] state The value to verify the checksum of.
/// @return true, if the state has a valid checksum. Otherwise, false.
bool IRLgAc::validChecksum(const uint32_t state) {
LGProtocol LGp;
LGp.raw = state;
return calcChecksum(state) == LGp.Sum;
}
/// Calculate and set the checksum values for the internal state.
void IRLgAc::checksum(void) {
_.Sum = calcChecksum(_.raw);
}
/// Change the power setting to On.
void IRLgAc::on(void) { setPower(true); }
/// Change the power setting to Off.
void IRLgAc::off(void) { setPower(false); }
/// Change the power setting.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRLgAc::setPower(const bool on) {
_.Power = (on ? kLgAcPowerOn : kLgAcPowerOff);
if (on)
setTemp(_temp); // Reset the temp if we are on.
else
_setTemp(0); // Off clears the temp.
}
/// Get the value of the current power setting.
/// @return true, the setting is on. false, the setting is off.
bool IRLgAc::getPower(void) const {
return _.Power == kLgAcPowerOn;
}
/// Is the message a Power Off message?
/// @return true, if it is. false, if not.
bool IRLgAc::isOffCommand(void) const { return _.raw == kLgAcOffCommand; }
/// Change the light/led/display setting.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRLgAc::setLight(const bool on) { _light = on; }
/// Get the value of the current light setting.
/// @return true, the setting is on. false, the setting is off.
bool IRLgAc::getLight(void) const { return _light; }
/// Is the message a Light Toggle message?
/// @return true, if it is. false, if not.
bool IRLgAc::isLightToggle(void) const { return _.raw == kLgAcLightToggle; }
/// Set the temperature.
/// @param[in] value The native temperature.
/// @note Internal use only.
inline void IRLgAc::_setTemp(const uint8_t value) { _.Temp = value; }
/// Set the temperature.
/// @param[in] degrees The temperature in degrees celsius.
void IRLgAc::setTemp(const uint8_t degrees) {
uint8_t temp = std::max(kLgAcMinTemp, degrees);
temp = std::min(kLgAcMaxTemp, temp);
_temp = temp;
_setTemp(temp - kLgAcTempAdjust);
}
/// Get the current temperature setting.
/// @return The current setting for temp. in degrees celsius.
uint8_t IRLgAc::getTemp(void) const {
return _isNormal() ? _.Temp + kLgAcTempAdjust : _temp;
}
/// Set the speed of the fan.
/// @param[in] speed The desired setting.
void IRLgAc::setFan(const uint8_t speed) {
uint8_t _speed = speed;
// Only model AKB74955603 has these speeds, so convert if we have to.
if (getModel() != lg_ac_remote_model_t::AKB74955603) {
switch (speed) {
case kLgAcFanLowAlt:
_.Fan = kLgAcFanLow;
return;
case kLgAcFanHigh:
_.Fan = kLgAcFanMax;
return;
}
}
switch (speed) {
case kLgAcFanLow:
case kLgAcFanLowAlt:
_speed = (getModel() != lg_ac_remote_model_t::AKB74955603)
? kLgAcFanLow : kLgAcFanLowAlt;
break;
case kLgAcFanHigh:
_speed = (getModel() != lg_ac_remote_model_t::AKB74955603)
? kLgAcFanMax : speed;
break;
case kLgAcFanAuto:
case kLgAcFanLowest:
case kLgAcFanMedium:
case kLgAcFanMax:
_speed = speed;
break;
default:
_speed = kLgAcFanAuto;
}
_.Fan = _speed;
}
/// Get the current fan speed setting.
/// @return The current fan speed.
uint8_t IRLgAc::getFan(void) const { return _.Fan; }
/// Get the operating mode setting of the A/C.
/// @return The current operating mode setting.
uint8_t IRLgAc::getMode(void) const { return _.Mode; }
/// Set the operating mode of the A/C.
/// @param[in] mode The desired operating mode.
void IRLgAc::setMode(const uint8_t mode) {
switch (mode) {
case kLgAcAuto:
case kLgAcDry:
case kLgAcHeat:
case kLgAcCool:
case kLgAcFan:
_.Mode = mode;
break;
default:
_.Mode = kLgAcAuto;
}
}
/// Check if the stored code is a SwingV Toggle message.
/// @return true, if it is. Otherwise, false.
bool IRLgAc::isSwingVToggle(void) const { return _.raw == kLgAcSwingVToggle; }
/// Check if the stored code is a Swing message.
/// @return true, if it is. Otherwise, false.
bool IRLgAc::isSwing(void) const {
return ((_.raw >> 12) == kLgAcSwingSignature) || isSwingVToggle();
}
/// Check if the stored code is a non-vane SwingV message.
/// @return true, if it is. Otherwise, false.
bool IRLgAc::isSwingV(void) const {
const uint32_t code = _.raw >> kLgAcChecksumSize;
return (code >= (kLgAcSwingVLowest >> kLgAcChecksumSize) &&
code < (kLgAcSwingHAuto >> kLgAcChecksumSize)) || isSwingVToggle();
}
/// Check if the stored code is a SwingH message.
/// @return true, if it is. Otherwise, false.
bool IRLgAc::isSwingH(void) const {
return (_.raw >> kLgAcSwingHOffsetSize) == kLgAcSwingHSignature;
}
/// Get the Horizontal Swing position setting of the A/C.
/// @return true, if it is. Otherwise, false.
bool IRLgAc::getSwingH(void) const { return _swingh; }
/// Set the Horizontal Swing mode of the A/C.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRLgAc::setSwingH(const bool on) { _swingh = on; }
/// Check if the stored code is a vane specific SwingV message.
/// @return true, if it is. Otherwise, false.
bool IRLgAc::isVaneSwingV(void) const {
return _.raw > kLgAcVaneSwingVBase &&
_.raw < (kLgAcVaneSwingVBase +
((kLgAcSwingVMaxVanes *
kLgAcVaneSwingVSize) << kLgAcChecksumSize));
}
/// Set the Vertical Swing mode of the A/C.
/// @param[in] position The position/mode to set the vanes to.
void IRLgAc::setSwingV(const uint32_t position) {
// Is it a valid position code?
if (position == kLgAcSwingVOff || position == kLgAcSwingVToggle ||
toCommonSwingV(position) != stdAc::swingv_t::kOff) {
if (position <= 0xFF) { // It's a short code, convert it.
_swingv = (kLgAcSwingSignature << 8 | position) << kLgAcChecksumSize;
_swingv |= calcChecksum(_swingv);
} else {
_swingv = position;
}
}
}
// Copy the previous swing settings from the current ones.
void IRLgAc::updateSwingPrev(void) {
_swingv_prev = _swingv;
for (uint8_t i = 0; i < kLgAcSwingVMaxVanes; i++)
_vaneswingv_prev[i] = _vaneswingv[i];
}
/// Get the Vertical Swing position setting of the A/C.
/// @return The native position/mode.
uint32_t IRLgAc::getSwingV(void) const { return _swingv; }
/// Set the per Vane Vertical Swing mode of the A/C.
/// @param[in] vane The nr. of the vane to control.
/// @param[in] position The position/mode to set the vanes to.
void IRLgAc::setVaneSwingV(const uint8_t vane, const uint8_t position) {
if (vane < kLgAcSwingVMaxVanes) // It's a valid vane nr.
if (position && position <= kLgAcVaneSwingVLowest) // Valid position
_vaneswingv[vane] = position;
}
/// Get the Vertical Swing position for the given vane of the A/C.
/// @return The native position/mode.
uint8_t IRLgAc::getVaneSwingV(const uint8_t vane) const {
return (vane < kLgAcSwingVMaxVanes) ? _vaneswingv[vane] : 0;
}
/// Get the vane code of a Vane Vertical Swing message.
/// @param[in] raw A raw number representing a native LG message.
/// @return A number containing just the vane nr, and the position.
uint8_t IRLgAc::getVaneCode(const uint32_t raw) {
return (raw - kLgAcVaneSwingVBase) >> kLgAcChecksumSize;
}
/// Calculate the Vane specific Vertical Swing code for the A/C.
/// @return The native raw code.
uint32_t IRLgAc::calcVaneSwingV(const uint8_t vane, const uint8_t position) {
uint32_t result = kLgAcVaneSwingVBase;
if (vane < kLgAcSwingVMaxVanes) // It's a valid vane nr.
if (position && position <= kLgAcVaneSwingVLowest) // Valid position
result += ((vane * kLgAcVaneSwingVSize + position) << kLgAcChecksumSize);
return result | calcChecksum(result);
}
/// Convert a stdAc::opmode_t enum into its native mode.
/// @param[in] mode The enum to be converted.
/// @return The native equivalent of the enum.
uint8_t IRLgAc::convertMode(const stdAc::opmode_t mode) {
switch (mode) {
case stdAc::opmode_t::kCool: return kLgAcCool;
case stdAc::opmode_t::kHeat: return kLgAcHeat;
case stdAc::opmode_t::kFan: return kLgAcFan;
case stdAc::opmode_t::kDry: return kLgAcDry;
default: return kLgAcAuto;
}
}
/// Convert a native mode into its stdAc equivalent.
/// @param[in] mode The native setting to be converted.
/// @return The stdAc equivalent of the native setting.
stdAc::opmode_t IRLgAc::toCommonMode(const uint8_t mode) {
switch (mode) {
case kLgAcCool: return stdAc::opmode_t::kCool;
case kLgAcHeat: return stdAc::opmode_t::kHeat;
case kLgAcDry: return stdAc::opmode_t::kDry;
case kLgAcFan: return stdAc::opmode_t::kFan;
default: return stdAc::opmode_t::kAuto;
}
}
/// Convert a stdAc::fanspeed_t enum into it's native speed.
/// @param[in] speed The enum to be converted.
/// @return The native equivalent of the enum.
uint8_t IRLgAc::convertFan(const stdAc::fanspeed_t speed) {
switch (speed) {
case stdAc::fanspeed_t::kMin: return kLgAcFanLowest;
case stdAc::fanspeed_t::kLow: return kLgAcFanLow;
case stdAc::fanspeed_t::kMedium: return kLgAcFanMedium;
case stdAc::fanspeed_t::kHigh: return kLgAcFanHigh;
case stdAc::fanspeed_t::kMax: return kLgAcFanMax;
default: return kLgAcFanAuto;
}
}
/// Convert a native fan speed into its stdAc equivalent.
/// @param[in] speed The native setting to be converted.
/// @return The stdAc equivalent of the native setting.
stdAc::fanspeed_t IRLgAc::toCommonFanSpeed(const uint8_t speed) {
switch (speed) {
case kLgAcFanMax: return stdAc::fanspeed_t::kMax;
case kLgAcFanHigh: return stdAc::fanspeed_t::kHigh;
case kLgAcFanMedium: return stdAc::fanspeed_t::kMedium;
case kLgAcFanLow:
case kLgAcFanLowAlt: return stdAc::fanspeed_t::kLow;
case kLgAcFanLowest: return stdAc::fanspeed_t::kMin;
default: return stdAc::fanspeed_t::kAuto;
}
}
/// Convert a stdAc::swingv_t enum into it's native setting.
/// @param[in] swingv The enum to be converted.
/// @return The native equivalent of the enum.
uint32_t IRLgAc::convertSwingV(const stdAc::swingv_t swingv) {
switch (swingv) {
case stdAc::swingv_t::kHighest: return kLgAcSwingVHighest;
case stdAc::swingv_t::kHigh: return kLgAcSwingVHigh;
case stdAc::swingv_t::kMiddle: return kLgAcSwingVMiddle;
case stdAc::swingv_t::kLow: return kLgAcSwingVLow;
case stdAc::swingv_t::kLowest: return kLgAcSwingVLowest;
case stdAc::swingv_t::kAuto: return kLgAcSwingVSwing;
default: return kLgAcSwingVOff;
}
}
/// Convert a native Vertical Swing into its stdAc equivalent.
/// @param[in] code The native code to be converted.
/// @return The stdAc equivalent of the native setting.
stdAc::swingv_t IRLgAc::toCommonSwingV(const uint32_t code) {
switch (code) {
case kLgAcSwingVHighest_Short:
case kLgAcSwingVHighest: return stdAc::swingv_t::kHighest;
case kLgAcSwingVHigh_Short:
case kLgAcSwingVHigh: return stdAc::swingv_t::kHigh;
case kLgAcSwingVUpperMiddle_Short:
case kLgAcSwingVUpperMiddle:
case kLgAcSwingVMiddle_Short:
case kLgAcSwingVMiddle: return stdAc::swingv_t::kMiddle;
case kLgAcSwingVLow_Short:
case kLgAcSwingVLow: return stdAc::swingv_t::kLow;
case kLgAcSwingVLowest_Short:
case kLgAcSwingVLowest: return stdAc::swingv_t::kLowest;
case kLgAcSwingVToggle:
case kLgAcSwingVSwing_Short:
case kLgAcSwingVSwing: return stdAc::swingv_t::kAuto;
default: return stdAc::swingv_t::kOff;
}
}
/// Convert a native Vane specific Vertical Swing into its stdAc equivalent.
/// @param[in] pos The native position to be converted.
/// @return The stdAc equivalent of the native setting.
stdAc::swingv_t IRLgAc::toCommonVaneSwingV(const uint8_t pos) {
switch (pos) {
case kLgAcVaneSwingVHigh: return stdAc::swingv_t::kHigh;
case kLgAcVaneSwingVUpperMiddle:
case kLgAcVaneSwingVMiddle: return stdAc::swingv_t::kMiddle;
case kLgAcVaneSwingVLow: return stdAc::swingv_t::kLow;
case kLgAcVaneSwingVLowest: return stdAc::swingv_t::kLowest;
default: return stdAc::swingv_t::kHighest;
}
}
/// Convert a stdAc::swingv_t enum into it's native setting.
/// @param[in] swingv The enum to be converted.
/// @return The native equivalent of the enum.
uint8_t IRLgAc::convertVaneSwingV(const stdAc::swingv_t swingv) {
switch (swingv) {
case stdAc::swingv_t::kHigh: return kLgAcVaneSwingVHigh;
case stdAc::swingv_t::kMiddle: return kLgAcVaneSwingVMiddle;
case stdAc::swingv_t::kLow: return kLgAcVaneSwingVLow;
case stdAc::swingv_t::kLowest: return kLgAcVaneSwingVLowest;
default: return kLgAcVaneSwingVHighest;
}
}
/// Convert the current internal state into its stdAc::state_t equivalent.
/// @param[in] prev Ptr to the previous state if required.
/// @return The stdAc equivalent of the native settings.
stdAc::state_t IRLgAc::toCommon(const stdAc::state_t *prev) const {
stdAc::state_t result{};
// Start with the previous state if given it.
if (prev != NULL) {
result = *prev;
} else {
// Set defaults for non-zero values that are not implicitly set for when
// there is no previous state.
// e.g. Any setting that toggles should probably go here.
result.light = true;
result.swingv = toCommonSwingV(getSwingV());
}
result.protocol = _protocol;
if (isLightToggle()) {
result.light = !result.light;
return result;
} else {
result.light = _light;
}
result.model = getModel();
result.power = getPower();
result.mode = toCommonMode(_.Mode);
result.celsius = true;
result.degrees = getTemp();
result.fanspeed = toCommonFanSpeed(_.Fan);
if (isSwingV()) result.swingv = toCommonSwingV(getSwingV());
if (isVaneSwingV())
result.swingv = toCommonVaneSwingV(VANESWINGVPOS(getVaneCode(_.raw)));
result.swingh = isSwingH() ? stdAc::swingh_t::kAuto : stdAc::swingh_t::kOff;
// Not supported.
result.quiet = false;
result.turbo = false;
result.filter = false;
result.clean = false;
result.econo = false;
result.beep = false;
result.sleep = -1;
result.clock = -1;
return result;
}
/// Convert the current internal state into a human readable string.
/// @return A human readable string.
String IRLgAc::toString(void) const {
String result = "";
result.reserve(80); // Reserve some heap for the string to reduce fragging.
result += addModelToString(_protocol, getModel(), false);
if (_isNormal()) { // A "Normal" generic settings message.
result += addBoolToString(getPower(), kPowerStr);
if (getPower()) { // Only display the rest if is in power on state.
result += addModeToString(_.Mode, kLgAcAuto, kLgAcCool,
kLgAcHeat, kLgAcDry, kLgAcFan);
result += addTempToString(getTemp());
result += addFanToString(_.Fan, kLgAcFanHigh,
_isAKB74955603() ? kLgAcFanLowAlt : kLgAcFanLow,
kLgAcFanAuto, kLgAcFanLowest, kLgAcFanMedium,
kLgAcFanMax);
}
} else { // It must be a special single purpose code.
if (isOffCommand()) {
result += addBoolToString(false, kPowerStr);
} else if (isLightToggle()) {
result += addBoolToString(true, kLightToggleStr);
} else if (isSwingH()) {
result += addBoolToString(_swingh, kSwingHStr);
} else if (isSwingV()) {
if (isSwingVToggle())
result += addToggleToString(isSwingVToggle(), kSwingVStr);
else
result += addSwingVToString((uint8_t)(_swingv >> kLgAcChecksumSize),
0, // No Auto, See "swing". Unused
kLgAcSwingVHighest_Short,
kLgAcSwingVHigh_Short,
kLgAcSwingVUpperMiddle_Short,
kLgAcSwingVMiddle_Short,
0, // Unused
kLgAcSwingVLow_Short,
kLgAcSwingVLowest_Short,
kLgAcSwingVOff_Short,
kLgAcSwingVSwing_Short,
0, 0);
} else if (isVaneSwingV()) {
const uint8_t vane = getVaneCode(_.raw) / kLgAcVaneSwingVSize;
result += addIntToString(vane, kVaneStr);
result += addSwingVToString(_vaneswingv[vane],
0, // No Auto, See "swing". Unused
kLgAcVaneSwingVHighest,
kLgAcVaneSwingVHigh,
kLgAcVaneSwingVUpperMiddle,
kLgAcVaneSwingVMiddle,
0, // Unused
kLgAcVaneSwingVLow,
kLgAcVaneSwingVLowest,
// Rest unused
0, 0, 0, 0);
}
}
return result;
}
/// Check if the internal state looks like a valid LG A/C message.
/// @return true, the internal state is a valid LG A/C mesg. Otherwise, false.
bool IRLgAc::isValidLgAc(void) const {
return validChecksum(_.raw) && (_.Sign == kLgAcSignature);
}

View File

@@ -0,0 +1,202 @@
// Copyright 2017-2021 David Conran
/// @file
/// @brief Support for LG protocols.
/// @see https://github.com/arendst/Tasmota/blob/54c2eb283a02e4287640a4595e506bc6eadbd7f2/sonoff/xdrv_05_irremote.ino#L327-438
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1513
// Supports:
// Brand: LG, Model: 6711A20083V remote (LG - LG6711A20083V)
// Brand: LG, Model: TS-H122ERM1 remote (LG - LG6711A20083V)
// Brand: LG, Model: AKB74395308 remote (LG2)
// Brand: LG, Model: S4-W12JA3AA A/C (LG2)
// Brand: LG, Model: AKB75215403 remote (LG2)
// Brand: LG, Model: AKB74955603 remote (LG2 - AKB74955603)
// Brand: LG, Model: A4UW30GFA2 A/C (LG2 - AKB74955603 & AKB73757604)
// Brand: LG, Model: AMNW09GSJA0 A/C (LG2 - AKB74955603)
// Brand: LG, Model: AMNW24GTPA1 A/C (LG2 - AKB73757604)
// Brand: LG, Model: AKB73757604 remote (LG2 - AKB73757604)
// Brand: LG, Model: AKB73315611 remote (LG2 - AKB74955603)
// Brand: LG, Model: MS05SQ NW0 A/C (LG2 - AKB74955603)
// Brand: General Electric, Model: AG1BH09AW101 A/C (LG - GE6711AR2853M)
// Brand: General Electric, Model: 6711AR2853M Remote (LG - GE6711AR2853M)
#ifndef IR_LG_H_
#define IR_LG_H_
#define __STDC_LIMIT_MACROS
#include <stdint.h>
#ifndef UNIT_TEST
#include <Arduino.h>
#endif
#include "IRremoteESP8266.h"
#include "IRsend.h"
#include "IRutils.h"
#ifdef UNIT_TEST
#include "IRsend_test.h"
#endif
/// Native representation of a LG A/C message.
union LGProtocol{
uint32_t raw; ///< The state of the IR remote in IR code form.
struct {
uint32_t Sum :4;
uint32_t Fan :4;
uint32_t Temp :4;
uint32_t Mode :3;
uint32_t :3;
uint32_t Power:2;
uint32_t Sign :8;
};
};
const uint8_t kLgAcFanLowest = 0; // 0b0000
const uint8_t kLgAcFanLow = 1; // 0b0001
const uint8_t kLgAcFanMedium = 2; // 0b0010
const uint8_t kLgAcFanMax = 4; // 0b0100
const uint8_t kLgAcFanAuto = 5; // 0b0101
const uint8_t kLgAcFanLowAlt = 9; // 0b1001
const uint8_t kLgAcFanHigh = 10; // 0b1010
// Nr. of slots in the look-up table
const uint8_t kLgAcFanEntries = kLgAcFanHigh + 1;
const uint8_t kLgAcTempAdjust = 15;
const uint8_t kLgAcMinTemp = 16; // Celsius
const uint8_t kLgAcMaxTemp = 30; // Celsius
const uint8_t kLgAcCool = 0; // 0b000
const uint8_t kLgAcDry = 1; // 0b001
const uint8_t kLgAcFan = 2; // 0b010
const uint8_t kLgAcAuto = 3; // 0b011
const uint8_t kLgAcHeat = 4; // 0b100
const uint8_t kLgAcPowerOff = 3; // 0b11
const uint8_t kLgAcPowerOn = 0; // 0b00
const uint8_t kLgAcSignature = 0x88;
const uint32_t kLgAcOffCommand = 0x88C0051;
const uint32_t kLgAcLightToggle = 0x88C00A6;
const uint32_t kLgAcSwingVToggle = 0x8810001;
const uint32_t kLgAcSwingSignature = 0x8813;
const uint32_t kLgAcSwingVLowest = 0x8813048;
const uint32_t kLgAcSwingVLow = 0x8813059;
const uint32_t kLgAcSwingVMiddle = 0x881306A;
const uint32_t kLgAcSwingVUpperMiddle = 0x881307B;
const uint32_t kLgAcSwingVHigh = 0x881308C;
const uint32_t kLgAcSwingVHighest = 0x881309D;
const uint32_t kLgAcSwingVSwing = 0x8813149;
const uint32_t kLgAcSwingVAuto = kLgAcSwingVSwing;
const uint32_t kLgAcSwingVOff = 0x881315A;
const uint8_t kLgAcSwingVLowest_Short = 0x04;
const uint8_t kLgAcSwingVLow_Short = 0x05;
const uint8_t kLgAcSwingVMiddle_Short = 0x06;
const uint8_t kLgAcSwingVUpperMiddle_Short = 0x07;
const uint8_t kLgAcSwingVHigh_Short = 0x08;
const uint8_t kLgAcSwingVHighest_Short = 0x09;
const uint8_t kLgAcSwingVSwing_Short = 0x14;
const uint8_t kLgAcSwingVAuto_Short = kLgAcSwingVSwing_Short;
const uint8_t kLgAcSwingVOff_Short = 0x15;
// AKB73757604 Constants
// SwingH
const uint32_t kLgAcSwingHAuto = 0x881316B;
const uint32_t kLgAcSwingHOff = 0x881317C;
// SwingV
const uint8_t kLgAcVaneSwingVHighest = 1; ///< 0b001
const uint8_t kLgAcVaneSwingVHigh = 2; ///< 0b010
const uint8_t kLgAcVaneSwingVUpperMiddle = 3; ///< 0b011
const uint8_t kLgAcVaneSwingVMiddle = 4; ///< 0b100
const uint8_t kLgAcVaneSwingVLow = 5; ///< 0b101
const uint8_t kLgAcVaneSwingVLowest = 6; ///< 0b110
const uint8_t kLgAcVaneSwingVSize = 8;
const uint8_t kLgAcSwingVMaxVanes = 4; ///< Max Nr. of Vanes
// Classes
/// Class for handling detailed LG A/C messages.
class IRLgAc {
public:
explicit IRLgAc(const uint16_t pin, const bool inverted = false,
const bool use_modulation = true);
void stateReset(void);
static uint8_t calcChecksum(const uint32_t state);
static bool validChecksum(const uint32_t state);
bool isValidLgAc(void) const;
#if SEND_LG
void send(const uint16_t repeat = kLgDefaultRepeat);
/// 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_LG
void begin(void);
void on(void);
void off(void);
void setPower(const bool on);
bool getPower(void) const;
bool isOffCommand(void) const;
void setTemp(const uint8_t degrees);
uint8_t getTemp(void) const;
void setFan(const uint8_t speed);
uint8_t getFan(void) const;
void setMode(const uint8_t mode);
uint8_t getMode(void) const;
void setLight(const bool on);
bool getLight(void) const;
bool isLightToggle(void) const;
bool isSwing(void) const;
void setSwingH(const bool on);
bool getSwingH(void) const;
bool isSwingV(void) const;
bool isSwingVToggle(void) const;
bool isVaneSwingV(void) const;
void setSwingV(const uint32_t position);
uint32_t getSwingV(void) const;
void setVaneSwingV(const uint8_t vane, const uint8_t position);
uint8_t getVaneSwingV(const uint8_t vane) const;
static uint32_t calcVaneSwingV(const uint8_t vane, const uint8_t position);
static uint8_t getVaneCode(const uint32_t raw);
bool isSwingH(void) const;
void updateSwingPrev(void);
uint32_t getRaw(void);
void setRaw(const uint32_t new_code,
const decode_type_t protocol = decode_type_t::UNKNOWN);
static uint8_t convertMode(const stdAc::opmode_t mode);
static stdAc::opmode_t toCommonMode(const uint8_t mode);
static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed);
static stdAc::swingv_t toCommonSwingV(const uint32_t code);
static stdAc::swingv_t toCommonVaneSwingV(const uint8_t pos);
static uint8_t convertFan(const stdAc::fanspeed_t speed);
static uint32_t convertSwingV(const stdAc::swingv_t swingv);
static uint8_t convertVaneSwingV(const stdAc::swingv_t swingv);
stdAc::state_t toCommon(const stdAc::state_t *prev = NULL) const;
String toString(void) const;
void setModel(const lg_ac_remote_model_t model);
lg_ac_remote_model_t getModel(void) const;
#ifndef UNIT_TEST
private:
IRsend _irsend; ///< Instance of the IR send class
#else // UNIT_TEST
/// @cond IGNORE
IRsendTest _irsend; ///< Instance of the testing IR send class
/// @endcond
#endif // UNIT_TEST
LGProtocol _;
uint8_t _temp;
bool _light;
uint32_t _swingv;
uint32_t _swingv_prev;
uint8_t _vaneswingv[kLgAcSwingVMaxVanes];
uint8_t _vaneswingv_prev[kLgAcSwingVMaxVanes];
bool _swingh;
bool _swingh_prev;
decode_type_t _protocol; ///< Protocol version
lg_ac_remote_model_t _model; ///< Model type
void checksum(void);
void _setTemp(const uint8_t value);
bool _isAKB74955603(void) const;
bool _isAKB73757604(void) const;
bool _isLG6711A20083V(void) const;
bool _isNormal(void) const;
};
#endif // IR_LG_H_

View File

@@ -0,0 +1,116 @@
// Copyright 2017 David Conran
/// @file
/// @brief Support for Lasertag protocols.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/366
// Supports:
// Brand: Lasertag, Model: Phaser emitters
#include <algorithm>
#include "IRrecv.h"
#include "IRsend.h"
#include "IRutils.h"
// Constants
const uint16_t kLasertagMinSamples = 13;
const uint16_t kLasertagTick = 333;
const uint32_t kLasertagMinGap = kDefaultMessageGap; // Just a guess.
const uint8_t kLasertagTolerance = 0; // Percentage error margin.
const uint16_t kLasertagExcess = 0; // See kMarkExcess.
const uint16_t kLasertagDelta = 165; // Use instead of Excess and Tolerance.
const int16_t kSpace = 1;
const int16_t kMark = 0;
#if SEND_LASERTAG
/// Send a Lasertag packet/message.
/// Status: STABLE / Working.
/// @param[in] data The message to be sent.
/// @param[in] nbits The number of bits of message to be sent.
/// @param[in] repeat The number of times the command is to be repeated.
/// @note This protocol is pretty much just raw Manchester encoding.
/// @todo Convert this to use `sendManchester()` if we can.`
void IRsend::sendLasertag(uint64_t data, uint16_t nbits, uint16_t repeat) {
if (nbits > sizeof(data) * 8) return; // We can't send something that big.
// Set 36kHz IR carrier frequency & a 1/4 (25%) duty cycle.
// NOTE: duty cycle is not confirmed. Just guessing based on RC5/6 protocols.
enableIROut(36, 25);
for (uint16_t i = 0; i <= repeat; i++) {
// Data
for (uint64_t mask = 1ULL << (nbits - 1); mask; mask >>= 1)
if (data & mask) { // 1
space(kLasertagTick); // 1 is space, then mark.
mark(kLasertagTick);
} else { // 0
mark(kLasertagTick); // 0 is mark, then space.
space(kLasertagTick);
}
// Footer
space(kLasertagMinGap);
}
}
#endif // SEND_LASERTAG
#if DECODE_LASERTAG
/// Decode the supplied Lasertag message.
/// Status: BETA / Appears to be working 90% of the time.
/// @param[in,out] results Ptr to the data to decode & where to store the result
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// @param[in] strict Flag indicating if we should perform strict matching.
/// @return True if it can decode it, false if it can't.
/// @note This protocol is pretty much just raw Manchester encoding.
/// @see http://www.sbprojects.net/knowledge/ir/rc5.php
/// @see https://en.wikipedia.org/wiki/RC-5
/// @see https://en.wikipedia.org/wiki/Manchester_code
/// @todo Convert to using `matchManchester()` if we can.
bool IRrecv::decodeLasertag(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
if (results->rawlen <= kLasertagMinSamples + offset) return false;
// Compliance
if (strict && nbits != kLasertagBits) return false;
uint16_t used = 0;
uint64_t data = 0;
uint16_t actual_bits = 0;
// No Header
// Data
for (; offset <= results->rawlen; actual_bits++) {
int16_t levelA =
getRClevel(results, &offset, &used, kLasertagTick, kLasertagTolerance,
kLasertagExcess, kLasertagDelta);
int16_t levelB =
getRClevel(results, &offset, &used, kLasertagTick, kLasertagTolerance,
kLasertagExcess, kLasertagDelta);
if (levelA == kSpace && levelB == kMark) {
data = (data << 1) | 1; // 1
} else {
if (levelA == kMark && levelB == kSpace) {
data <<= 1; // 0
} else {
break;
}
}
}
// Footer (None)
// Compliance
if (actual_bits < nbits) return false; // Less data than we expected.
if (strict && actual_bits != kLasertagBits) return false;
// Success
results->decode_type = LASERTAG;
results->value = data;
results->address = data & 0xF; // Unit
results->command = data >> 4; // Team
results->repeat = false;
results->bits = actual_bits;
return true;
}
#endif // DECODE_LASERTAG

View File

@@ -0,0 +1,106 @@
// Copyright 2019 David Conran
/// @file
/// @brief Support for LEGO protocols.
/// @note LEGO is a Registrated Trademark of the Lego Group.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/641
/// @see https://github.com/crankyoldgit/IRremoteESP8266/files/2974525/LEGO_Power_Functions_RC_v120.pdf
// Supports:
// Brand: LEGO Power Functions, Model: IR Receiver
#include <algorithm>
#include "IRrecv.h"
#include "IRsend.h"
#include "IRutils.h"
// Constants
const uint16_t kLegoPfBitMark = 158;
const uint16_t kLegoPfHdrSpace = 1026;
const uint16_t kLegoPfZeroSpace = 263;
const uint16_t kLegoPfOneSpace = 553;
const uint32_t kLegoPfMinCommandLength = 16000; // 16ms
#if SEND_LEGOPF
/// Send a LEGO Power Functions message.
/// Status: Beta / Should work.
/// @param[in] data The message to be sent.
/// @param[in] nbits The number of bits of message to be sent.
/// @param[in] repeat The number of times the command is to be repeated.
/// @note Non-zero repeats results in at least 5 messages per spec.
void IRsend::sendLegoPf(const uint64_t data, const uint16_t nbits,
const uint16_t repeat) {
uint8_t channelid = ((data >> (nbits - 4)) & 0b11) + 1;
if (repeat) {
// We are in repeat mode.
// Spec says a pause before transmittion.
if (channelid < 4) space((4 - channelid) * kLegoPfMinCommandLength);
// Spec says there are a minimum of 5 message repeats.
for (uint16_t r = 0; r < std::max(repeat, (uint16_t)5); r++) {
// Lego has a special repeat mode which repeats a message with varying
// start to start times.
sendGeneric(kLegoPfBitMark, kLegoPfHdrSpace,
kLegoPfBitMark, kLegoPfOneSpace,
kLegoPfBitMark, kLegoPfZeroSpace,
kLegoPfBitMark, kLegoPfHdrSpace,
((r < 2) ? 5 : (6 + 2 * channelid)) * kLegoPfMinCommandLength,
data, nbits, 38000, true, 0, kDutyDefault);
}
} else { // No repeat, just a simple message.
sendGeneric(kLegoPfBitMark, kLegoPfHdrSpace,
kLegoPfBitMark, kLegoPfOneSpace,
kLegoPfBitMark, kLegoPfZeroSpace,
kLegoPfBitMark, kLegoPfHdrSpace,
kLegoPfMinCommandLength * 5,
data, nbits, 38000, true, 0, kDutyDefault);
}
}
#endif // SEND_LEGO
#if DECODE_LEGOPF
/// Decode the supplied LEGO Power Functions message.
/// Status: STABLE / Appears to work.
/// @param[in,out] results Ptr to the data to decode & where to store the result
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// @param[in] strict Flag indicating if we should perform strict matching.
/// @return True if it can decode it, false if it can't.
bool IRrecv::decodeLegoPf(decode_results* results, uint16_t offset,
const uint16_t nbits, const bool strict) {
// Check if can possibly be a valid LEGO message.
if (strict && nbits != kLegoPfBits) return false; // Not what is expected
uint64_t data = 0;
// Match Header + Data + Footer
if (!matchGeneric(results->rawbuf + offset, &data,
results->rawlen - offset, nbits,
kLegoPfBitMark, kLegoPfHdrSpace,
kLegoPfBitMark, kLegoPfOneSpace,
kLegoPfBitMark, kLegoPfZeroSpace,
kLegoPfBitMark, kLegoPfMinCommandLength,
true)) return false;
// Compliance
if (strict) {
// Verify the Longitudinal Redundancy Check (LRC)
uint16_t lrc_data = data;
uint8_t lrc = 0xF;
for (uint8_t i = 0; i < 4; i++) {
lrc ^= (lrc_data & 0xF);
lrc_data >>= 4;
}
if (lrc) return false;
}
// Success
results->decode_type = LEGOPF;
results->bits = nbits;
results->value = data;
results->address = ((data >> (nbits - 4)) & 0b11) + 1; // Channel Id
results->command = (data >> 4) & 0xFF; // Stuff between Channel Id and LRC.
return true;
}
#endif // DECODE_LEGOPF

View File

@@ -0,0 +1,143 @@
// Copyright 2018 David Conran
/// @file
/// @brief Support for Lutron protocols.
/// @note The Lutron protocol uses a sort of Run Length encoding to encode
/// its data. There is no header or footer per-se.
/// As a mark is the first data we will notice, we always assume the First
/// bit of the technically 36-bit protocol is '1'. So it is assumed, and thus
/// we only care about the 35 bits of data.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/515
/// @see http://www.lutron.com/TechnicalDocumentLibrary/048158.doc
// Supports:
// Brand: Lutron, Model: SP-HT remote
// Brand: Lutron, Model: MIR-ITFS remote
// Brand: Lutron, Model: MIR-ITFS-LF remote
// Brand: Lutron, Model: MIR-ITFS-F remote
#define __STDC_LIMIT_MACROS
#include <stdint.h>
#include <algorithm>
#include "IRrecv.h"
#include "IRsend.h"
#include "IRutils.h"
// Constants
const uint16_t kLutronTick = 2288;
const uint32_t kLutronGap = 150000; // Completely made up value.
const uint16_t kLutronDelta = 400; // +/- 300 usecs.
#if SEND_LUTRON
/// Send a Lutron formatted message.
/// Status: Stable / Appears to be working for real devices.
/// @param[in] data The message to be sent.
/// @param[in] nbits The number of bits of message to be sent.
/// @param[in] repeat The number of times the command is to be repeated.
/// @note The protocol is really 36 bits long, but the first bit is always a 1.
/// So, assume the 1 and only have a normal payload of 35 bits.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/515
void IRsend::sendLutron(uint64_t data, uint16_t nbits, uint16_t repeat) {
enableIROut(40000, 40); // 40Khz & 40% dutycycle.
for (uint16_t r = 0; r <= repeat; r++) {
mark(kLutronTick); // 1st bit is always '1'.
// Send the supplied data in MSB First order.
for (uint64_t mask = 1ULL << (nbits - 1); mask; mask >>= 1)
if (data & mask)
mark(kLutronTick); // Send a 1
else
space(kLutronTick); // Send a 0
space(kLutronGap); // Inter-message gap.
}
}
#endif // SEND_LUTRON
#if DECODE_LUTRON
/// Decode the supplied Lutron message.
/// Status: STABLE / Working.
/// @param[in,out] results Ptr to the data to decode & where to store the result
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// @param[in] strict Flag indicating if we should perform strict matching.
/// @return True if it can decode it, false if it can't.
bool IRrecv::decodeLutron(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
// Technically the smallest number of entries for the smallest message is '1'.
// i.e. All the bits set to 1, would produce a single huge mark signal.
// So no minimum length check is required.
if (strict && nbits != kLutronBits)
return false; // Not strictly an Lutron message.
uint64_t data = 0;
int16_t bitsSoFar = -1;
if (nbits > sizeof(data) * 8) return false; // To large to store the data.
for (; bitsSoFar < nbits && offset < results->rawlen; offset++) {
uint16_t entry = results->rawbuf[offset];
// It has to be large enough to qualify as a bit.
if (!matchAtLeast(entry, kLutronTick, 0, kLutronDelta)) {
DPRINTLN("Entry too small. Aborting.");
return false;
}
// Keep reading bits of the same value until we run out.
while (entry != 0 && matchAtLeast(entry, kLutronTick, 0, kLutronDelta)) {
bitsSoFar++;
DPRINT("Bit: ");
DPRINT(bitsSoFar);
if (offset % 2) { // Is Odd?
data = (data << 1) + 1; // Append a '1'.
DPRINTLN(" is a 1.");
} else { // Is it Even?
data <<= 1; // Append a '0'.
DPRINTLN(" is a 0.");
if (bitsSoFar == nbits && matchAtLeast(entry, kLutronGap))
break; // We've likely reached the end of a message.
}
// Remove a bit length from the current entry.
entry = std::max(entry, (uint16_t)(kLutronTick / kRawTick)) -
kLutronTick / kRawTick;
}
if (offset % 2 && !match(entry, kLutronDelta, 0, kLutronDelta)) {
DPRINT("offset = ");
DPRINTLN(offset);
DPRINT("rawlen = ");
DPRINTLN(results->rawlen);
DPRINT("entry = ");
DPRINTLN(entry);
DPRINTLN("Odd Entry has too much left over. Aborting.");
return false; // Too much left over to be a good value. Reject it.
}
if (offset % 2 == 0 && offset <= results->rawlen - 1 &&
!matchAtLeast(entry, kLutronDelta, 0, kLutronDelta)) {
DPRINT("offset = ");
DPRINTLN(offset);
DPRINT("rawlen = ");
DPRINTLN(results->rawlen);
DPRINT("entry = ");
DPRINTLN(entry);
DPRINTLN("Entry has too much left over. Aborting.");
return false; // Too much left over to be a good value. Reject it.
}
}
// We got too many bits.
if (bitsSoFar > nbits || bitsSoFar < 0) {
DPRINTLN("Wrong number of bits found. Aborting.");
return false;
}
// If we got less bits than we were expecting, we need to pad with zeros
// until we get the correct number of bits.
if (bitsSoFar < nbits) data <<= (nbits - bitsSoFar);
// Success
DPRINTLN("Lutron Success!");
results->decode_type = LUTRON;
results->bits = bitsSoFar;
results->value = data ^ (1ULL << nbits); // Mask off the initial '1'.
results->address = 0;
results->command = 0;
return true;
}
#endif // DECODE_LUTRON

View File

@@ -0,0 +1,197 @@
// Copyright 2018 Brett T. Warden
/// @file
/// @brief Disney Made With Magic (MWM) Support
/// derived from ir_Lasertag.cpp
/// @see https://github.com/crankyoldgit/IRremoteESP8266/pull/557
// Supports:
// Brand: Disney, Model: Made With Magic (Glow With The Show) wand
#include <algorithm>
#include "IRrecv.h"
#include "IRsend.h"
#include "IRutils.h"
// Constants
const uint16_t kMWMMinSamples = 6; // Msgs are >=3 bytes, bytes have >=2
// samples
const uint16_t kMWMTick = 417;
const uint32_t kMWMMinGap = 30000; // Typical observed delay b/w commands
const uint8_t kMWMTolerance = 0; // Percentage error margin.
const uint16_t kMWMExcess = 0; // See kMarkExcess.
const uint16_t kMWMDelta = 150; // Use instead of Excess and Tolerance.
const uint8_t kMWMMaxWidth = 9; // Maximum number of successive bits at a
// single level - worst case
const int16_t kSpace = 1;
const int16_t kMark = 0;
#if SEND_MWM
/// Send a MWM packet/message.
/// Status: Implemented.
/// @param[in] data The message to be sent.
/// @param[in] nbytes The number of bytes of message to be sent.
/// @param[in] repeat The number of times the command is to be repeated.
/// @note This protocol is 2400 bps serial, 1 start bit (mark),
/// 1 stop bit (space), no parity
void IRsend::sendMWM(const uint8_t data[], const uint16_t nbytes,
const uint16_t repeat) {
if (nbytes < 3) return; // Shortest possible message is 3 bytes
// Set 38kHz IR carrier frequency & a 1/4 (25%) duty cycle.
// NOTE: duty cycle is not confirmed. Just guessing based on RC5/6 protocols.
enableIROut(38, 25);
for (uint16_t r = 0; r <= repeat; r++) {
// Data
for (uint16_t i = 0; i < nbytes; i++) {
uint8_t byte = data[i];
// Start bit
mark(kMWMTick);
// LSB first, space=1
for (uint8_t mask = 0x1; mask; mask <<= 1) {
if (byte & mask) { // 1
space(kMWMTick);
} else { // 0
mark(kMWMTick);
}
}
// Stop bit
space(kMWMTick);
}
// Footer
space(kMWMMinGap);
}
}
#endif // SEND_MWM
#if DECODE_MWM
/// Decode the supplied MWM message.
/// Status: Implemented.
/// @param[in,out] results Ptr to the data to decode & where to store the result
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// @param[in] strict Flag indicating if we should perform strict matching.
/// @return True if it can decode it, false if it can't.
/// @note This protocol is 2400 bps serial, 1 start bit (mark),
/// 1 stop bit (space), no parity
bool IRrecv::decodeMWM(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
DPRINTLN("DEBUG: decodeMWM");
// Compliance
if (results->rawlen <= kMWMMinSamples + offset) {
DPRINTLN("DEBUG: decodeMWM: too few samples");
return false;
}
uint16_t used = 0;
uint64_t data = 0;
uint16_t frame_bits = 0;
uint16_t data_bits = 0;
// No Header
// Data
uint8_t bits_per_frame = 10;
for (; offset < results->rawlen && results->bits < 8 * kStateSizeMax;
frame_bits++) {
DPRINT("DEBUG: decodeMWM: offset = ");
DPRINTLN(offset);
int16_t level = getRClevel(results, &offset, &used, kMWMTick, kMWMTolerance,
kMWMExcess, kMWMDelta, kMWMMaxWidth);
if (level < 0) {
DPRINTLN("DEBUG: decodeMWM: getRClevel returned error");
break;
}
switch (frame_bits % bits_per_frame) {
case 0:
// Start bit
if (level != kMark) {
DPRINTLN("DEBUG: decodeMWM: framing error - invalid start bit");
goto done;
}
break;
case 9:
// Stop bit
if (level != kSpace) {
DPRINTLN("DEBUG: decodeMWM: framing error - invalid stop bit");
return false;
} else {
DPRINT("DEBUG: decodeMWM: data_bits = ");
DPRINTLN(data_bits);
DPRINT("DEBUG: decodeMWM: Finished byte: ");
DPRINTLN(uint64ToString(data));
results->state[data_bits / 8 - 1] = data & 0xFF;
results->bits = data_bits;
data = 0;
}
break;
default:
// Data bits
DPRINT("DEBUG: decodeMWM: Storing bit: ");
DPRINTLN((level == kSpace));
// Transmission is LSB-first, space=1
data |= ((level == kSpace)) << 8;
data >>= 1;
data_bits++;
break;
}
}
done:
// Footer (None)
// Compliance
DPRINT("DEBUG: decodeMWM: frame_bits = ");
DPRINTLN(frame_bits);
DPRINT("DEBUG: decodeMWM: data_bits = ");
DPRINTLN(data_bits);
if (data_bits < nbits) {
DPRINT("DEBUG: decodeMWM: too few bits; expected ");
DPRINTLN(nbits);
return false; // Less data than we expected.
}
uint16_t payload_length = 0;
switch (results->state[0] & 0xf0) {
case 0x90:
case 0xf0:
// Normal commands
payload_length = results->state[0] & 0x0f;
DPRINT("DEBUG: decodeMWM: payload_length = ");
DPRINTLN(payload_length);
break;
default:
if (strict) {
// Show commands
if (results->state[0] != 0x55 && results->state[1] != 0xAA) {
return false;
}
}
break;
}
if (data_bits < (payload_length + 3) * 8) {
DPRINT("DEBUG: decodeMWM: too few bytes; expected ");
DPRINTLN((payload_length + 3));
return false;
}
if (strict) {
if (payload_length && (data_bits > (payload_length + 3) * 8)) {
DPRINT("DEBUG: decodeMWM: too many bytes; expected ");
DPRINTLN((payload_length + 3));
return false;
}
}
// Success
results->decode_type = MWM;
results->repeat = false;
return true;
}
#endif // DECODE_MWM
// vim: et:ts=2:sw=2

View File

@@ -0,0 +1,154 @@
// Copyright 2013 mpflaga
// Copyright 2015 kitlaan
// Copyright 2017 Jason kendall, David Conran
/// @file
/// @brief Support for MagiQuest protocols.
/// @see https://github.com/kitlaan/Arduino-IRremote/blob/master/ir_Magiquest.cpp
/// @see https://github.com/mpflaga/Arduino-IRremote
#include "ir_Magiquest.h"
#include <algorithm>
#include "IRrecv.h"
#include "IRsend.h"
#include "IRutils.h"
#define IS_ZERO(m, s) (((m)*100 / ((m) + (s))) <= kMagiQuestZeroRatio)
#define IS_ONE(m, s) (((m)*100 / ((m) + (s))) >= kMagiQuestOneRatio)
#if SEND_MAGIQUEST
/// Send a MagiQuest formatted message.
/// Status: Beta / Should be working.
/// @param[in] data The message to be sent.
/// @param[in] nbits The number of bits of message to be sent.
/// @param[in] repeat The number of times the command is to be repeated.
void IRsend::sendMagiQuest(const uint64_t data, const uint16_t nbits,
const uint16_t repeat) {
sendGeneric(0, 0, // No Headers - Technically it's included in the data.
// i.e. 8 zeros.
kMagiQuestMarkOne, kMagiQuestSpaceOne, kMagiQuestMarkZero,
kMagiQuestSpaceZero,
0, // No footer mark.
kMagiQuestGap, data, nbits, 36, true, repeat, 50);
}
/// Encode a MagiQuest wand_id, and a magnitude into a single 64bit value.
/// (Only 48 bits of real data + 8 leading zero bits)
/// This is suitable for calling sendMagiQuest() with.
/// e.g. sendMagiQuest(encodeMagiQuest(wand_id, magnitude))
/// @param[in] wand_id The value for the wand ID.
/// @param[in] magnitude The value for the magnitude
/// @return A code suitable for calling sendMagiQuest() with.
uint64_t IRsend::encodeMagiQuest(const uint32_t wand_id,
const uint16_t magnitude) {
uint64_t result = 0;
result = wand_id;
result <<= 16;
result |= magnitude;
// Shouldn't be needed, but ensure top 8/16 bit are zero.
result &= 0xFFFFFFFFFFFFULL;
return result;
}
#endif // SEND_MAGIQUEST
#if DECODE_MAGIQUEST
/// Decode the supplied MagiQuest message.
/// Status: Beta / Should work.
/// @param[in,out] results Ptr to the data to decode & where to store the result
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// @param[in] strict Flag indicating if we should perform strict matching.
/// @return True if it can decode it, false if it can't.
/// @note MagiQuest protocol appears to be a header of 8 'zero' bits, followed
/// by 32 bits of "wand ID" and finally 16 bits of "magnitude".
/// Even though we describe this protocol as 56 bits, it really only has
/// 48 bits of data that matter.
/// In transmission order, 8 zeros + 32 wand_id + 16 magnitude.
/// @see https://github.com/kitlaan/Arduino-IRremote/blob/master/ir_Magiquest.cpp
bool IRrecv::decodeMagiQuest(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
uint16_t bits = 0;
uint64_t data = 0;
if (results->rawlen < (2 * kMagiquestBits) + offset - 1) {
DPRINT("Not enough bits to be Magiquest - Rawlen: ");
DPRINT(results->rawlen);
DPRINT(" Expected: ");
DPRINTLN(2 * kMagiquestBits + offset - 1);
return false;
}
// Compliance
if (strict && nbits != kMagiquestBits) return false;
// Of six wands as datapoints, so far they all start with 8 ZEROs.
// For example, here is the data from two wands
// 00000000 00100011 01001100 00100110 00000010 00000010 00010111
// 00000000 00100000 10001000 00110001 00000010 00000010 10110100
// Decode the (MARK + SPACE) bits
while (offset + 1 < results->rawlen && bits < nbits - 1) {
uint16_t mark = results->rawbuf[offset];
uint16_t space = results->rawbuf[offset + 1];
if (!matchMark(mark + space, kMagiQuestTotalUsec)) {
DPRINT("Not enough time to be Magiquest - Mark: ");
DPRINT(mark);
DPRINT(" Space: ");
DPRINT(space);
DPRINT(" Total: ");
DPRINT(mark + space);
DPRINT("Expected: ");
DPRINTLN(kMagiQuestTotalUsec);
return false;
}
if (IS_ZERO(mark, space))
data = (data << 1) | 0;
else if (IS_ONE(mark, space))
data = (data << 1) | 1;
else
return false;
bits++;
offset += 2;
// Compliance
// The first 8 bits of this protocol are supposed to all be 0.
// Exit out early as it is never going to match.
if (strict && bits == 8 && data != 0) return false;
}
// Last bit is special as the protocol ends with a SPACE, not a MARK.
// Grab the last MARK bit, assuming a good SPACE after it
if (offset < results->rawlen) {
uint16_t mark = results->rawbuf[offset];
uint16_t space = (kMagiQuestTotalUsec / kRawTick) - mark;
if (IS_ZERO(mark, space))
data = (data << 1) | 0;
else if (IS_ONE(mark, space))
data = (data << 1) | 1;
else
return false;
bits++;
}
if (bits != nbits) return false;
if (strict) {
// The top 8 bits of the 56 bits needs to be 0x00 to be valid.
// i.e. bits 56 to 49 are all zero.
if ((data >> (nbits - 8)) != 0) return false;
}
// Success
results->decode_type = MAGIQUEST;
results->bits = bits;
results->value = data;
results->address = data >> 16; // Wand ID
results->command = data & 0xFFFF; // Magnitude
return true;
}
#endif // DECODE_MAGIQUEST

View File

@@ -0,0 +1,43 @@
// Copyright 2013 mpflaga
// Copyright 2015 kitlaan
// Copyright 2017 Jason kendall, David Conran
/// @file
/// @brief Support for MagiQuest protocols.
/// @see https://github.com/kitlaan/Arduino-IRremote/blob/master/ir_Magiquest.cpp
/// @see https://github.com/mpflaga/Arduino-IRremote
// Supports:
// Brand: MagiQuest, Model: Wand
#ifndef IR_MAGIQUEST_H_
#define IR_MAGIQUEST_H_
#define __STDC_LIMIT_MACROS
#include <stdint.h>
#include "IRremoteESP8266.h"
#include "IRsend.h"
/// MagiQuest packet is both Wand ID and magnitude of swish and flick
union magiquest {
uint64_t llword;
uint8_t byte[8];
// uint16_t word[4];
uint32_t lword[2];
struct {
uint16_t magnitude;
uint32_t wand_id;
uint8_t padding;
uint8_t scrap;
} cmd;
};
const uint16_t kMagiQuestTotalUsec = 1150;
const uint8_t kMagiQuestZeroRatio = 30; // usually <= ~25%
const uint8_t kMagiQuestOneRatio = 38; // usually >= ~50%
const uint16_t kMagiQuestMarkZero = 280;
const uint16_t kMagiQuestSpaceZero = 850;
const uint16_t kMagiQuestMarkOne = 580;
const uint16_t kMagiQuestSpaceOne = 600;
const uint32_t kMagiQuestGap = kDefaultMessageGap; // Just a guess.
#endif // IR_MAGIQUEST_H_

View File

@@ -0,0 +1,101 @@
// Copyright 2020 David Conran (crankyoldgit)
/// @file
/// @brief Support for Metz protocol
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1241
// Supports:
// Brand: Metz, Model: RM16 remote
// Brand: Metz, Model: RM17 remote
// Brand: Metz, Model: RM19 remote
// Brand: Metz, Model: CH610 TV
#include "IRrecv.h"
#include "IRsend.h"
#include "IRutils.h"
// Constants.
const uint16_t kMetzHdrMark = 880; ///< uSeconds.
const uint16_t kMetzHdrSpace = 2336; ///< uSeconds.
const uint16_t kMetzBitMark = 473; ///< uSeconds.
const uint16_t kMetzOneSpace = 1640; ///< uSeconds.
const uint16_t kMetzZeroSpace = 940; ///< uSeconds.
const uint16_t kMetzFreq = 38000; ///< Hz.
const uint8_t kMetzAddressBits = 3;
const uint8_t kMetzCommandBits = 6;
#if SEND_METZ
/// Send a Metz formatted message.
/// Status: Beta / Needs testing against a real device.
/// @param[in] data containing the IR command.
/// @param[in] nbits Nr. of bits to send. usually kMetzBits
/// @param[in] repeat Nr. of times the message is to be repeated.
void IRsend::sendMetz(const uint64_t data, const uint16_t nbits,
const uint16_t repeat) {
sendGeneric(kMetzHdrMark, kMetzHdrSpace, // Header
kMetzBitMark, kMetzOneSpace, // Data
kMetzBitMark, kMetzZeroSpace,
kMetzBitMark, kDefaultMessageGap, // Footer.
data, nbits, // Payload
kMetzFreq, true, repeat, kDutyDefault);
}
/// Encode a Metz address, command, and toggle bits into a code suitable
/// for use with sendMetz().
/// @param[in] address A 3-bit address value.
/// @param[in] command A 6-bit command value.
/// @param[in] toggle Should the toggle bit be set in the result?
/// @return A 19-bit value suitable for use with `sendMetz()`.
uint32_t IRsend::encodeMetz(const uint8_t address, const uint8_t command,
const bool toggle) {
return toggle << (2 * (kMetzAddressBits + kMetzCommandBits)) |
(address & 0x7) << (2 * kMetzCommandBits + kMetzAddressBits) |
(~address & 0x7) << (2 * kMetzCommandBits) |
(command & 0x3F) << kMetzCommandBits |
(~command & 0x3F);
}
#endif // SEND_METZ
#if DECODE_METZ
/// Decode the supplied Metz message.
/// Status: BETA / Probably works.
/// @param[in,out] results Ptr to the data to decode & where to store the decode
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// @param[in] strict Flag indicating if we should perform strict matching.
/// @return A boolean. True if it can decode it, false if it can't.
bool IRrecv::decodeMetz(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
if (strict && nbits != kMetzBits) return false;
uint64_t data = 0;
// Match Header + Data + Footer
if (!matchGeneric(results->rawbuf + offset, &data,
results->rawlen - offset, nbits,
kMetzHdrMark, kMetzHdrSpace, // Header
kMetzBitMark, kMetzOneSpace, // Data
kMetzBitMark, kMetzZeroSpace,
kMetzBitMark, kDefaultMessageGap, // Footer
true, _tolerance, 0, true)) return false;
uint16_t command = GETBITS64(data, kMetzCommandBits, kMetzCommandBits);
uint16_t address = GETBITS64(data, 2 * kMetzCommandBits + kMetzAddressBits,
kMetzAddressBits);
// Compliance
if (strict) {
if (command != invertBits(GETBITS64(data, 0, kMetzCommandBits),
kMetzCommandBits) ||
address != invertBits(GETBITS64(data, 2 * kMetzCommandBits,
kMetzAddressBits),
kMetzAddressBits)) return false;
}
// Success
results->decode_type = decode_type_t::METZ;
results->bits = nbits;
results->value = data;
results->address = address;
results->command = command;
return true;
}
#endif // DECODE_METZ

View File

@@ -0,0 +1,886 @@
// Copyright 2017 bwze, crankyoldgit
/// @file
/// @brief Support for Midea protocols.
/// Midea added by crankyoldgit & bwze.
/// send: bwze/crankyoldgit, decode: crankyoldgit
/// @note SwingV has the function of an Ion Filter on Danby A/C units.
/// @see https://docs.google.com/spreadsheets/d/1TZh4jWrx4h9zzpYUI9aYXMl1fYOiqu-xVuOOMqagxrs/edit?usp=sharing
/// @see https://github.com/crankyoldgit/IRremoteESP8266/pull/1213
#include "ir_Midea.h"
#include "ir_NEC.h"
#include <algorithm>
#ifndef ARDUINO
#include <string>
#endif
#include "IRrecv.h"
#include "IRsend.h"
#include "IRtext.h"
#include "IRutils.h"
// Constants
const uint16_t kMideaTick = 80;
const uint16_t kMideaBitMarkTicks = 7;
const uint16_t kMideaBitMark = kMideaBitMarkTicks * kMideaTick;
const uint16_t kMideaOneSpaceTicks = 21;
const uint16_t kMideaOneSpace = kMideaOneSpaceTicks * kMideaTick;
const uint16_t kMideaZeroSpaceTicks = 7;
const uint16_t kMideaZeroSpace = kMideaZeroSpaceTicks * kMideaTick;
const uint16_t kMideaHdrMarkTicks = 56;
const uint16_t kMideaHdrMark = kMideaHdrMarkTicks * kMideaTick;
const uint16_t kMideaHdrSpaceTicks = 56;
const uint16_t kMideaHdrSpace = kMideaHdrSpaceTicks * kMideaTick;
const uint16_t kMideaMinGapTicks =
kMideaHdrMarkTicks + kMideaZeroSpaceTicks + kMideaBitMarkTicks;
const uint16_t kMideaMinGap = kMideaMinGapTicks * kMideaTick;
const uint8_t kMideaTolerance = 30; // Percent
const uint16_t kMidea24MinGap = 13000; ///< uSecs
using irutils::addBoolToString;
using irutils::addFanToString;
using irutils::addIntToString;
using irutils::addLabeledString;
using irutils::addModeToString;
using irutils::addTempToString;
using irutils::addToggleToString;
using irutils::minsToString;
#if SEND_MIDEA
/// Send a Midea message
/// Status: Alpha / Needs testing against a real device.
/// @param[in] data The message to be sent.
/// @param[in] nbits The number of bits of message to be sent.
/// @param[in] repeat The number of times the command is to be repeated.
void IRsend::sendMidea(uint64_t data, uint16_t nbits, uint16_t repeat) {
if (nbits % 8 != 0) return; // nbits is required to be a multiple of 8.
// Set IR carrier frequency
enableIROut(38);
for (uint16_t r = 0; r <= repeat; r++) {
// The protocol sends the message, then follows up with an entirely
// inverted payload.
for (size_t inner_loop = 0; inner_loop < 2; inner_loop++) {
// Header
mark(kMideaHdrMark);
space(kMideaHdrSpace);
// Data
// Break data into byte segments, starting at the Most Significant
// Byte. Each byte then being sent normal, then followed inverted.
for (uint16_t i = 8; i <= nbits; i += 8) {
// Grab a bytes worth of data.
uint8_t segment = (data >> (nbits - i)) & 0xFF;
sendData(kMideaBitMark, kMideaOneSpace, kMideaBitMark, kMideaZeroSpace,
segment, 8, true);
}
// Footer
mark(kMideaBitMark);
space(kMideaMinGap); // Pause before repeating
// Invert the data for the 2nd phase of the message.
// As we get called twice in the inner loop, we will always revert
// to the original 'data' state.
data = ~data;
}
space(kDefaultMessageGap);
}
}
#endif // SEND_MIDEA
// Code to emulate Midea A/C IR remote control unit.
/// 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?
IRMideaAC::IRMideaAC(const uint16_t pin, const bool inverted,
const bool use_modulation)
: _irsend(pin, inverted, use_modulation) { this->stateReset(); }
/// Reset the state of the remote to a known good state/sequence.
void IRMideaAC::stateReset(void) {
// Power On, Mode Auto, Fan Auto, Temp = 25C/77F
_.remote_state = 0xA1826FFFFF62;
_CleanToggle = false;
_EconoToggle = false;
_8CHeatToggle = false;
_LightToggle = false;
_Quiet = _Quiet_prev = false;
_SwingVToggle = false;
_TurboToggle = false;
#if KAYSUN_AC
_SwingVStep = false;
#endif // KAYSUN_AC
}
/// Set up hardware to be able to send a message.
void IRMideaAC::begin(void) { _irsend.begin(); }
#if SEND_MIDEA
/// Send the current internal state as an IR message.
/// @param[in] repeat Nr. of times the message will be repeated.
void IRMideaAC::send(const uint16_t repeat) {
_irsend.sendMidea(getRaw(), kMideaBits, repeat);
// Handle the toggle/special "one-off" settings if we need to.
if (_SwingVToggle && !isSwingVToggle())
_irsend.sendMidea(kMideaACToggleSwingV, kMideaBits, repeat);
_SwingVToggle = false;
#if KAYSUN_AC
if (_SwingVStep && !isSwingVStep())
_irsend.sendMidea(kMideaACSwingVStep, kMideaBits, repeat);
_SwingVStep = false;
#endif // KAYSUN_AC
if (_EconoToggle && !isEconoToggle())
_irsend.sendMidea(kMideaACToggleEcono, kMideaBits, repeat);
_EconoToggle = false;
if (_TurboToggle && !isTurboToggle())
_irsend.sendMidea(kMideaACToggleTurbo, kMideaBits, repeat);
_TurboToggle = false;
if (_LightToggle && !isLightToggle())
_irsend.sendMidea(kMideaACToggleLight, kMideaBits, repeat);
_LightToggle = false;
if (getMode() <= kMideaACAuto) { // Only available in Cool, Dry, or Auto mode
if (_CleanToggle && !isCleanToggle())
_irsend.sendMidea(kMideaACToggleSelfClean, kMideaBits, repeat);
_CleanToggle = false;
} else if (getMode() == kMideaACHeat) { // Only available in Heat mode
if (_8CHeatToggle && !is8CHeatToggle())
_irsend.sendMidea(kMideaACToggle8CHeat, kMideaBits, repeat);
_8CHeatToggle = false;
}
if (_Quiet != _Quiet_prev)
_irsend.sendMidea(_Quiet ? kMideaACQuietOn : kMideaACQuietOff,
kMideaBits, repeat);
_Quiet_prev = _Quiet;
}
#endif // SEND_MIDEA
/// Get a copy of the internal state/code for this protocol.
/// @return The code for this protocol based on the current internal state.
uint64_t IRMideaAC::getRaw(void) {
checksum(); // Ensure correct checksum before sending.
return _.remote_state;
}
/// Set the internal state from a valid code for this protocol.
/// @param[in] newState A valid code for this protocol.
void IRMideaAC::setRaw(const uint64_t newState) { _.remote_state = newState; }
/// Set the requested power state of the A/C to on.
void IRMideaAC::on(void) { setPower(true); }
/// Set the requested power state of the A/C to off.
void IRMideaAC::off(void) { setPower(false); }
/// Change the power setting.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRMideaAC::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 IRMideaAC::getPower(void) const {
return _.Power;
}
/// Is the device currently using Celsius or the Fahrenheit temp scale?
/// @return true, the A/C unit uses Celsius natively, false, is Fahrenheit.
bool IRMideaAC::getUseCelsius(void) const {
return !_.useFahrenheit;
}
/// Set the A/C unit to use Celsius natively.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRMideaAC::setUseCelsius(const bool on) {
if (on == _.useFahrenheit) { // We need to change.
uint8_t native_temp = getTemp(!on); // Get the old native temp.
_.useFahrenheit = !on; // Cleared is on.
setTemp(native_temp, !on); // Reset temp using the old native temp.
}
}
/// Set the temperature.
/// @param[in] temp The temperature in degrees celsius.
/// @param[in] useCelsius true, use the Celsius temp scale. false, is Fahrenheit
void IRMideaAC::setTemp(const uint8_t temp, const bool useCelsius) {
uint8_t max_temp = kMideaACMaxTempF;
uint8_t min_temp = kMideaACMinTempF;
if (useCelsius) {
max_temp = kMideaACMaxTempC;
min_temp = kMideaACMinTempC;
}
uint8_t new_temp = std::min(max_temp, std::max(min_temp, temp));
if (!_.useFahrenheit && !useCelsius) // Native is in C, new_temp is in F
new_temp = fahrenheitToCelsius(new_temp) - kMideaACMinTempC;
else if (_.useFahrenheit && useCelsius) // Native is in F, new_temp is in C
new_temp = celsiusToFahrenheit(new_temp) - kMideaACMinTempF;
else // Native and desired are the same units.
new_temp -= min_temp;
// Set the actual data.
_.Temp = new_temp;
}
/// Get the current temperature setting.
/// @param[in] celsius true, the results are in Celsius. false, in Fahrenheit.
/// @return The current setting for temp. in the requested units/scale.
uint8_t IRMideaAC::getTemp(const bool celsius) const {
uint8_t temp = _.Temp;
if (!_.useFahrenheit)
temp += kMideaACMinTempC;
else
temp += kMideaACMinTempF;
if (celsius && _.useFahrenheit) temp = fahrenheitToCelsius(temp) + 0.5;
if (!celsius && !_.useFahrenheit) temp = celsiusToFahrenheit(temp);
return temp;
}
/// Set the Sensor temperature.
/// @param[in] temp The temperature in degrees celsius.
/// @param[in] useCelsius true, use the Celsius temp scale. false, is Fahrenheit
/// @note Also known as FollowMe
void IRMideaAC::setSensorTemp(const uint8_t temp, const bool useCelsius) {
uint8_t max_temp = kMideaACMaxSensorTempF;
uint8_t min_temp = kMideaACMinSensorTempF;
if (useCelsius) {
max_temp = kMideaACMaxSensorTempC;
min_temp = kMideaACMinSensorTempC;
}
uint8_t new_temp = std::min(max_temp, std::max(min_temp, temp));
if (!_.useFahrenheit && !useCelsius) // Native is in C, new_temp is in F
new_temp = fahrenheitToCelsius(new_temp) - kMideaACMinSensorTempC;
else if (_.useFahrenheit && useCelsius) // Native is in F, new_temp is in C
new_temp = celsiusToFahrenheit(new_temp) - kMideaACMinSensorTempF;
else // Native and desired are the same units.
new_temp -= min_temp;
// Set the actual data.
_.SensorTemp = new_temp + 1;
setEnableSensorTemp(true);
}
/// Get the current Sensor temperature setting.
/// @param[in] celsius true, the results are in Celsius. false, in Fahrenheit.
/// @return The current setting for temp. in the requested units/scale.
/// @note Also known as FollowMe
uint8_t IRMideaAC::getSensorTemp(const bool celsius) const {
uint8_t temp = _.SensorTemp - 1;
if (!_.useFahrenheit)
temp += kMideaACMinSensorTempC;
else
temp += kMideaACMinSensorTempF;
if (celsius && _.useFahrenheit) temp = fahrenheitToCelsius(temp) + 0.5;
if (!celsius && !_.useFahrenheit) temp = celsiusToFahrenheit(temp);
return temp;
}
/// Enable the remote's Sensor temperature.
/// @param[in] on true, the setting is on. false, the setting is off.
/// @note Also known as FollowMe
void IRMideaAC::setEnableSensorTemp(const bool on) {
_.disableSensor = !on;
if (on) {
setType(kMideaACTypeFollow);
} else {
setType(kMideaACTypeCommand);
_.SensorTemp = kMideaACSensorTempOnTimerOff; // Apply special value if off.
}
}
/// Is the remote temperature sensor enabled?
/// @return A boolean indicating if it is enabled or not.
/// @note Also known as FollowMe
bool IRMideaAC::getEnableSensorTemp(void) const { return !_.disableSensor; }
/// Set the speed of the fan.
/// @param[in] fan The desired setting. 1-3 set the speed, 0 for auto.
void IRMideaAC::setFan(const uint8_t fan) {
_.Fan = (fan > kMideaACFanHigh) ? kMideaACFanAuto : fan;
}
/// Get the current fan speed setting.
/// @return The current fan speed.
uint8_t IRMideaAC::getFan(void) const {
return _.Fan;
}
/// Get the operating mode setting of the A/C.
/// @return The current operating mode setting.
uint8_t IRMideaAC::getMode(void) const {
return _.Mode;
}
/// Set the operating mode of the A/C.
/// @param[in] mode The desired operating mode.
void IRMideaAC::setMode(const uint8_t mode) {
switch (mode) {
case kMideaACAuto:
case kMideaACCool:
case kMideaACHeat:
case kMideaACDry:
case kMideaACFan:
_.Mode = mode;
break;
default:
_.Mode = kMideaACAuto;
}
}
/// Set the Sleep setting of the A/C.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRMideaAC::setSleep(const bool on) {
_.Sleep = on;
}
/// Get the Sleep setting of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRMideaAC::getSleep(void) const {
return _.Sleep;
}
/// Set the A/C to toggle the vertical swing toggle for the next send.
/// @note On Danby A/C units, this is associated with the Ion Filter instead.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRMideaAC::setSwingVToggle(const bool on) { _SwingVToggle = on; }
/// Is the current state a vertical swing toggle message?
/// @note On Danby A/C units, this is associated with the Ion Filter instead.
/// @return true, it is. false, it isn't.
bool IRMideaAC::isSwingVToggle(void) const {
return _.remote_state == kMideaACToggleSwingV;
}
// Get the vertical swing toggle state of the A/C.
/// @note On Danby A/C units, this is associated with the Ion Filter instead.
/// @return true, the setting is on. false, the setting is off.
bool IRMideaAC::getSwingVToggle(void) {
_SwingVToggle |= isSwingVToggle();
return _SwingVToggle;
}
#if KAYSUN_AC
/// Set the A/C to step the vertical swing for the next send.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRMideaAC::setSwingVStep(const bool on) { _SwingVStep = on; }
/// Is the current state a step vertical swing message?
/// @return true, it is. false, it isn't.
bool IRMideaAC::isSwingVStep(void) const {
return _.remote_state == kMideaACSwingVStep;
}
// Get the step vertical swing state of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRMideaAC::getSwingVStep(void) {
_SwingVStep |= isSwingVStep();
return _SwingVStep;
}
#endif // KAYSUN_AC
/// Set the A/C to toggle the Econo (energy saver) mode for the next send.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRMideaAC::setEconoToggle(const bool on) { _EconoToggle = on; }
/// Is the current state an Econo (energy saver) toggle message?
/// @return true, it is. false, it isn't.
bool IRMideaAC::isEconoToggle(void) const {
return _.remote_state == kMideaACToggleEcono;
}
// Get the Econo (energy saver) toggle state of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRMideaAC::getEconoToggle(void) {
_EconoToggle |= isEconoToggle();
return _EconoToggle;
}
/// Set the A/C to toggle the Turbo mode for the next send.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRMideaAC::setTurboToggle(const bool on) { _TurboToggle = on; }
/// Is the current state a Turbo toggle message?
/// @return true, it is. false, it isn't.
bool IRMideaAC::isTurboToggle(void) const {
return _.remote_state == kMideaACToggleTurbo;
}
// Get the Turbo toggle state of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRMideaAC::getTurboToggle(void) {
_TurboToggle |= isTurboToggle();
return _TurboToggle;
}
/// Set the A/C to toggle the Light (LED) mode for the next send.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRMideaAC::setLightToggle(const bool on) { _LightToggle = on; }
/// Is the current state a Light (LED) toggle message?
/// @return true, it is. false, it isn't.
bool IRMideaAC::isLightToggle(void) const {
return _.remote_state == kMideaACToggleLight;
}
// Get the Light (LED) toggle state of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRMideaAC::getLightToggle(void) {
_LightToggle |= isLightToggle();
return _LightToggle;
}
/// Is the current state a Self-Clean toggle message?
/// @return true, it is. false, it isn't.
bool IRMideaAC::isCleanToggle(void) const {
return _.remote_state == kMideaACToggleSelfClean;
}
/// Set the A/C to toggle the Self Clean mode for the next send.
/// @note Only works in Cool, Dry, or Auto modes.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRMideaAC::setCleanToggle(const bool on) {
_CleanToggle = on && getMode() <= kMideaACAuto;
}
// Get the Self-Clean toggle state of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRMideaAC::getCleanToggle(void) {
_CleanToggle |= isCleanToggle();
return _CleanToggle;
}
/// Is the current state a 8C Heat (Freeze Protect) toggle message?
/// @note Only works in Heat mode.
/// @return true, it is. false, it isn't.
bool IRMideaAC::is8CHeatToggle(void) const {
return _.remote_state == kMideaACToggle8CHeat;
}
/// Set the A/C to toggle the 8C Heat (Freeze Protect) mode for the next send.
/// @note Only works in Heat mode.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRMideaAC::set8CHeatToggle(const bool on) {
_8CHeatToggle = on && getMode() == kMideaACHeat;
}
// Get the 8C Heat (Freeze Protect) toggle state of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRMideaAC::get8CHeatToggle(void) {
_8CHeatToggle |= is8CHeatToggle();
return _8CHeatToggle;
}
/// Is the current state a Quiet(Silent) message?
/// @return true, it is. false, it isn't.
bool IRMideaAC::isQuiet(void) const {
return (_.remote_state == kMideaACQuietOff ||
_.remote_state == kMideaACQuietOn);
}
/// Set the Quiet (Silent) mode for the next send.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRMideaAC::setQuiet(const bool on) { _Quiet = on; }
/// Set the Quiet (Silent) mode for the next send.
/// @param[in] on true, the setting is on. false, the setting is off.
/// @param[in] prev true, previously the setting was on. false, setting was off.
void IRMideaAC::setQuiet(const bool on, const bool prev) {
setQuiet(on);
_Quiet_prev = prev;
}
// Get the Quiet (Silent) mode state of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRMideaAC::getQuiet(void) const {
if (isQuiet())
return _.remote_state == kMideaACQuietOn;
else
return _Quiet;
}
/// Calculate the checksum for a given state.
/// @param[in] state The value to calc the checksum of.
/// @return The calculated checksum value.
uint8_t IRMideaAC::calcChecksum(const uint64_t state) {
uint8_t sum = 0;
uint64_t temp_state = state;
for (uint8_t i = 0; i < 5; i++) {
temp_state >>= 8;
sum += reverseBits((temp_state & 0xFF), 8);
}
sum = 256 - sum;
return reverseBits(sum, 8);
}
/// Verify the checksum is valid for a given state.
/// @param[in] state The state to verify the checksum of.
/// @return true, if the state has a valid checksum. Otherwise, false.
bool IRMideaAC::validChecksum(const uint64_t state) {
return GETBITS64(state, 0, 8) == calcChecksum(state);
}
/// Calculate & set the checksum for the current internal state of the remote.
void IRMideaAC::checksum(void) {
// Stored the checksum value in the last byte.
_.Sum = calcChecksum(_.remote_state);
}
/// Get the message type setting of the A/C message.
/// @return The message type setting.
uint8_t IRMideaAC::getType(void) const { return _.Type; }
/// Set the message type setting of the A/C message.
/// @param[in] setting The desired message type setting.
void IRMideaAC::setType(const uint8_t setting) {
switch (setting) {
case kMideaACTypeFollow:
_.BeepDisable = false;
// FALL-THRU
case kMideaACTypeSpecial:
_.Type = setting;
break;
default:
_.Type = kMideaACTypeCommand;
_.BeepDisable = true;
}
}
/// Is the OnTimer enabled?
/// @return true for yes, false for no.
bool IRMideaAC::isOnTimerEnabled(void) const {
return getType() == kMideaACTypeCommand &&
_.SensorTemp != kMideaACSensorTempOnTimerOff;
}
/// Get the value of the OnTimer is currently set to.
/// @return The number of minutes.
uint16_t IRMideaAC::getOnTimer(void) const {
return (_.SensorTemp >> 1) * 30 + 30;
}
/// Set the value of the On Timer.
/// @param[in] mins The number of minutes for the timer.
/// @note Time will be rounded down to nearest 30 min as that is the resolution
/// of the actual device/protocol.
/// @note A value of less than 30 will disable the Timer.
/// @warning On Timer is incompatible with Sensor Temp/Follow Me messages.
/// Setting it will disable that mode/settings.
void IRMideaAC::setOnTimer(const uint16_t mins) {
setEnableSensorTemp(false);
uint8_t halfhours = std::min((uint16_t)(24 * 60), mins) / 30;
if (halfhours)
_.SensorTemp = ((halfhours - 1) << 1) | 1;
else
_.SensorTemp = kMideaACSensorTempOnTimerOff;
}
/// Is the OffTimer enabled?
/// @return true for yes, false for no.
bool IRMideaAC::isOffTimerEnabled(void) const {
return _.OffTimer != kMideaACTimerOff;
}
/// Get the value of the OffTimer is currently set to.
/// @return The number of minutes.
uint16_t IRMideaAC::getOffTimer(void) const { return _.OffTimer * 30 + 30; }
/// Set the value of the Off Timer.
/// @param[in] mins The number of minutes for the timer.
/// @note Time will be rounded down to nearest 30 min as that is the resolution
/// of the actual device/protocol.
/// @note A value of less than 30 will disable the Timer.
void IRMideaAC::setOffTimer(const uint16_t mins) {
uint8_t halfhours = std::min((uint16_t)(24 * 60), mins) / 30;
if (halfhours)
_.OffTimer = halfhours - 1;
else
_.OffTimer = kMideaACTimerOff;
}
/// Convert a stdAc::opmode_t enum into its native mode.
/// @param[in] mode The enum to be converted.
/// @return The native equivalent of the enum.
uint8_t IRMideaAC::convertMode(const stdAc::opmode_t mode) {
switch (mode) {
case stdAc::opmode_t::kCool: return kMideaACCool;
case stdAc::opmode_t::kHeat: return kMideaACHeat;
case stdAc::opmode_t::kDry: return kMideaACDry;
case stdAc::opmode_t::kFan: return kMideaACFan;
default: return kMideaACAuto;
}
}
/// Convert a stdAc::fanspeed_t enum into it's native speed.
/// @param[in] speed The enum to be converted.
/// @return The native equivalent of the enum.
uint8_t IRMideaAC::convertFan(const stdAc::fanspeed_t speed) {
switch (speed) {
case stdAc::fanspeed_t::kMin:
case stdAc::fanspeed_t::kLow: return kMideaACFanLow;
case stdAc::fanspeed_t::kMedium: return kMideaACFanMed;
case stdAc::fanspeed_t::kHigh:
case stdAc::fanspeed_t::kMax: return kMideaACFanHigh;
default: return kMideaACFanAuto;
}
}
/// Convert a native mode into its stdAc equivalent.
/// @param[in] mode The native setting to be converted.
/// @return The stdAc equivalent of the native setting.
stdAc::opmode_t IRMideaAC::toCommonMode(const uint8_t mode) {
switch (mode) {
case kMideaACCool: return stdAc::opmode_t::kCool;
case kMideaACHeat: return stdAc::opmode_t::kHeat;
case kMideaACDry: return stdAc::opmode_t::kDry;
case kMideaACFan: return stdAc::opmode_t::kFan;
default: return stdAc::opmode_t::kAuto;
}
}
/// Convert a native fan speed into its stdAc equivalent.
/// @param[in] speed The native setting to be converted.
/// @return The stdAc equivalent of the native setting.
stdAc::fanspeed_t IRMideaAC::toCommonFanSpeed(const uint8_t speed) {
switch (speed) {
case kMideaACFanHigh: return stdAc::fanspeed_t::kMax;
case kMideaACFanMed: return stdAc::fanspeed_t::kMedium;
case kMideaACFanLow: return stdAc::fanspeed_t::kMin;
default: return stdAc::fanspeed_t::kAuto;
}
}
/// Convert the current internal state into its stdAc::state_t equivalent.
/// @param[in] prev A Ptr to the previous state.
/// @return The stdAc equivalent of the native settings.
stdAc::state_t IRMideaAC::toCommon(const stdAc::state_t *prev) {
stdAc::state_t result{};
if (prev != NULL) {
result = *prev;
} else {
// Fixed/Not supported/Non-zero defaults.
result.protocol = decode_type_t::MIDEA;
result.model = -1; // No models used.
result.swingh = stdAc::swingh_t::kOff;
result.swingv = stdAc::swingv_t::kOff;
result.quiet = false;
result.turbo = false;
result.econo = false;
result.filter = false;
result.light = false;
result.beep = false;
result.sleep = -1;
result.clock = -1;
}
if (isSwingVToggle()) {
result.swingv = (result.swingv != stdAc::swingv_t::kOff) ?
stdAc::swingv_t::kAuto : stdAc::swingv_t::kOff;
return result;
}
result.power = _.Power;
result.mode = toCommonMode(_.Mode);
result.celsius = !_.useFahrenheit;
result.degrees = getTemp(result.celsius);
result.sensorTemperature = getSensorTemp(result.celsius);
result.fanspeed = toCommonFanSpeed(_.Fan);
result.sleep = _.Sleep ? 0 : -1;
result.econo = getEconoToggle();
result.clean ^= getCleanToggle();
return result;
}
/// Convert the current internal state into a human readable string.
/// @return A human readable string.
String IRMideaAC::toString(void) {
String result = "";
const uint8_t message_type = getType();
result.reserve(230); // Reserve some heap for the string to reduce fragging.
result += addIntToString(message_type, kTypeStr, false);
result += kSpaceLBraceStr;
switch (message_type) {
case kMideaACTypeCommand: result += kCommandStr; break;
case kMideaACTypeSpecial: result += kSpecialStr; break;
case kMideaACTypeFollow: result += kFollowStr; break;
default: result += kUnknownStr;
}
result += ')';
if (message_type != kMideaACTypeSpecial) {
result += addBoolToString(_.Power, kPowerStr);
result += addModeToString(_.Mode, kMideaACAuto, kMideaACCool,
kMideaACHeat, kMideaACDry, kMideaACFan);
result += addBoolToString(!_.useFahrenheit, kCelsiusStr);
result += addTempToString(getTemp(true));
result += '/';
result += uint64ToString(getTemp(false));
result += 'F';
if (getEnableSensorTemp()) {
result += kCommaSpaceStr;
result += kSensorStr;
result += addTempToString(getSensorTemp(true), true, false);
result += '/';
result += uint64ToString(getSensorTemp(false));
result += 'F';
} else {
result += addLabeledString(
isOnTimerEnabled() ? minsToString(getOnTimer()) : kOffStr,
kOnTimerStr);
}
result += addLabeledString(
isOffTimerEnabled() ? minsToString(getOffTimer()) : kOffStr,
kOffTimerStr);
result += addFanToString(_.Fan, kMideaACFanHigh, kMideaACFanLow,
kMideaACFanAuto, kMideaACFanAuto, kMideaACFanMed);
result += addBoolToString(_.Sleep, kSleepStr);
}
result += addToggleToString(getSwingVToggle(), kSwingVStr);
#if KAYSUN_AC
result += addBoolToString(getSwingVStep(), kStepStr);
#endif // KAYSUN_AC
result += addToggleToString(getEconoToggle(), kEconoStr);
result += addToggleToString(getTurboToggle(), kTurboStr);
result += addBoolToString(getQuiet(), kQuietStr);
result += addToggleToString(getLightToggle(), kLightStr);
result += addToggleToString(getCleanToggle(), kCleanStr);
result += addToggleToString(get8CHeatToggle(), k8CHeatStr);
return result;
}
#if DECODE_MIDEA
/// Decode the supplied Midea message.
/// Status: Alpha / Needs testing against a real device.
/// @param[in,out] results Ptr to the data to decode & where to store the result
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// Typically kHitachiAcBits, kHitachiAc1Bits, kHitachiAc2Bits,
/// kHitachiAc344Bits
/// @param[in] strict Flag indicating if we should perform strict matching.
bool IRrecv::decodeMidea(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
uint8_t min_nr_of_messages = 1;
if (strict) {
if (nbits != kMideaBits) return false; // Not strictly a MIDEA message.
min_nr_of_messages = 2;
}
// The protocol sends the data normal + inverted, alternating on
// each byte. Hence twice the number of expected data bits.
if (results->rawlen <
min_nr_of_messages * (2 * nbits + kHeader + kFooter) - 1 + offset)
return false; // Can't possibly be a valid MIDEA message.
uint64_t data = 0;
uint64_t inverted = 0;
if (nbits > sizeof(data) * 8)
return false; // We can't possibly capture a Midea packet that big.
for (uint8_t i = 0; i < min_nr_of_messages; i++) {
// Match Header + Data + Footer
uint16_t used;
used = matchGeneric(results->rawbuf + offset, i % 2 ? &inverted : &data,
results->rawlen - offset, nbits,
kMideaHdrMark, kMideaHdrSpace,
kMideaBitMark, kMideaOneSpace,
kMideaBitMark, kMideaZeroSpace,
kMideaBitMark, kMideaMinGap,
i % 2, // No "atleast" on 1st part, but yes on the 2nd.
kMideaTolerance);
if (!used) return false;
offset += used;
}
// Compliance
if (strict) {
// Protocol requires a second message with all the data bits inverted.
// We should have checked we got a second message in the previous loop.
// Just need to check it's value is an inverted copy of the first message.
uint64_t mask = (1ULL << kMideaBits) - 1;
if ((data & mask) != ((inverted ^ mask) & mask)) return false;
if (!IRMideaAC::validChecksum(data)) return false;
}
// Success
results->decode_type = MIDEA;
results->bits = nbits;
results->value = data;
results->address = 0;
results->command = 0;
return true;
}
#endif // DECODE_MIDEA
#if SEND_MIDEA24
/// Send a Midea24 formatted message.
/// Status: STABLE / Confirmed working on a real device.
/// @param[in] data The message to be sent.
/// @param[in] nbits The number of bits of message to be sent.
/// @param[in] repeat The number of times the command is to be repeated.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1170
/// @note This protocol is basically a 48-bit version of the NEC protocol with
/// alternate bytes inverted, thus only 24 bits of real data, and with at
/// least a single repeat.
/// @warning Can't be used beyond 32 bits.
void IRsend::sendMidea24(const uint64_t data, const uint16_t nbits,
const uint16_t repeat) {
uint64_t newdata = 0;
// Construct the data into bye & inverted byte pairs.
for (int16_t i = nbits - 8; i >= 0; i -= 8) {
// Shuffle the data to be sent so far.
newdata <<= 16;
uint8_t next = GETBITS64(data, i, 8);
newdata |= ((next << 8) | (next ^ 0xFF));
}
sendNEC(newdata, nbits * 2, repeat);
}
#endif // SEND_MIDEA24
#if DECODE_MIDEA24
/// Decode the supplied Midea24 message.
/// Status: STABLE / Confirmed working on a real device.
/// @param[in,out] results Ptr to the data to decode & where to store the decode
/// result.
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// @param[in] strict Flag indicating if we should perform strict matching.
/// @return A boolean. True if it can decode it, false if it can't.
/// @note This protocol is basically a 48-bit version of the NEC protocol with
/// alternate bytes inverted, thus only 24 bits of real data.
/// @warning Can't be used beyond 32 bits.
bool IRrecv::decodeMidea24(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
// Not strictly a MIDEA24 message.
if (strict && nbits != kMidea24Bits) return false;
if (nbits > 32) return false; // Can't successfully match something that big.
uint64_t longdata = 0;
if (!matchGeneric(results->rawbuf + offset, &longdata,
results->rawlen - offset, nbits * 2,
kNecHdrMark, kNecHdrSpace,
kNecBitMark, kNecOneSpace,
kNecBitMark, kNecZeroSpace,
kNecBitMark, kMidea24MinGap, true)) return false;
// Build the result by checking every second byte is a complement(inversion)
// of the previous one.
uint32_t data = 0;
for (uint8_t i = nbits * 2; i >= 16;) {
// Shuffle the data collected so far.
data <<= 8;
i -= 8;
uint8_t current = GETBITS64(longdata, i, 8);
i -= 8;
uint8_t next = GETBITS64(longdata, i, 8);
// Check they are an inverted pair.
if (current != (next ^ 0xFF)) return false; // They are not, so abort.
data |= current;
}
// Success
results->decode_type = decode_type_t::MIDEA24;
results->bits = nbits;
results->value = data;
results->address = 0;
results->command = 0;
return true;
}
#endif // DECODE_MIDEA24

View File

@@ -0,0 +1,276 @@
// Copyright 2017 David Conran
/// @file
/// @brief Support for Midea protocols.
/// Midea added by crankyoldgit & bwze
/// @see https://docs.google.com/spreadsheets/d/1TZh4jWrx4h9zzpYUI9aYXMl1fYOiqu-xVuOOMqagxrs/edit?usp=sharing
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1342#issuecomment-733721085
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1733
// Supports:
// Brand: Pioneer System, Model: RYBO12GMFILCAD A/C (12K BTU) (MIDEA)
// Brand: Pioneer System, Model: RUBO18GMFILCAD A/C (18K BTU) (MIDEA)
// Brand: Pioneer System, Model: WS012GMFI22HLD A/C (12K BTU) (MIDEA)
// Brand: Pioneer System, Model: WS018GMFI22HLD A/C (12K BTU) (MIDEA)
// Brand: Pioneer System, Model: UB018GMFILCFHD A/C (12K BTU) (MIDEA)
// Brand: Pioneer System, Model: RG66B6(B)/BGEFU1 remote (MIDEA)
// Brand: Comfee, Model: MPD1-12CRN7 A/C (MIDEA)
// Brand: Kaysun, Model: Casual CF A/C (MIDEA)
// Brand: Keystone, Model: RG57H4(B)BGEF remote (MIDEA)
// Brand: MrCool, Model: RG57A6/BGEFU1 remote (MIDEA)
// Brand: Midea, Model: FS40-7AR Stand Fan (MIDEA24)
// Brand: Danby, Model: DAC080BGUWDB (MIDEA)
// Brand: Danby, Model: DAC100BGUWDB (MIDEA)
// Brand: Danby, Model: DAC120BGUWDB (MIDEA)
// Brand: Danby, Model: R09C/BCGE remote (MIDEA)
// Brand: Trotec, Model: TROTEC PAC 2100 X (MIDEA)
// Brand: Trotec, Model: TROTEC PAC 3900 X (MIDEA)
// Brand: Trotec, Model: RG57H(B)/BGE remote (MIDEA)
// Brand: Trotec, Model: RG57H3(B)/BGCEF-M remote (MIDEA)
// Brand: Lennox, Model: RG57A6/BGEFU1 remote (MIDEA)
// Brand: Lennox, Model: MWMA009S4-3P A/C (MIDEA)
// Brand: Lennox, Model: MWMA012S4-3P A/C (MIDEA)
// Brand: Lennox, Model: MCFA indoor split A/C (MIDEA)
// Brand: Lennox, Model: MCFB indoor split A/C (MIDEA)
// Brand: Lennox, Model: MMDA indoor split A/C (MIDEA)
// Brand: Lennox, Model: MMDB indoor split A/C (MIDEA)
// Brand: Lennox, Model: MWMA indoor split A/C (MIDEA)
// Brand: Lennox, Model: MWMB indoor split A/C (MIDEA)
// Brand: Lennox, Model: M22A indoor split A/C (MIDEA)
// Brand: Lennox, Model: M33A indoor split A/C (MIDEA)
// Brand: Lennox, Model: M33B indoor split A/C (MIDEA)
#ifndef IR_MIDEA_H_
#define IR_MIDEA_H_
#define __STDC_LIMIT_MACROS
#include <stdint.h>
#ifdef ARDUINO
#include <Arduino.h>
#endif
#include "IRremoteESP8266.h"
#include "IRsend.h"
#ifdef UNIT_TEST
#include "IRsend_test.h"
#endif
/// @note
/// Compile-time model specific overrides.
/// Uncomment one of these if you have such a devices to better match your A/C.
/// It changes some of the special commands/settings.
//
// #define DANBY_DAC true
// #define KAYSUN_AC true
/// @note Some Pioneer Systems have required a special bit to be set in order
/// for the A/C unit to accept the message. We don't currently understand what
/// this bit does. See the link for details of how to set this if needed.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1342#issuecomment-733721085
/// Native representation of a Midea A/C message.
union MideaProtocol{
uint64_t remote_state; ///< The state in native IR code form
// only use 48bits
struct {
// Byte 0
uint8_t Sum;
// Byte 1 (value=0xFF when not in use.)
// This byte gets dual usage as Sensor Temp and On Timer
// Depending on "Type" below.
// When in "OnTimer", the nr of half hours is stored with mask 0b01111110
// i.e.
// uint8_t :1;
// uint8_t OnTimerHalfHours:6;
// uint8_t :1;
uint8_t SensorTemp:7; ///< Degrees or OnTimer.
uint8_t disableSensor:1;
// Byte 2 (value=0xFF when not in use.)
uint8_t :1; // 0b1
uint8_t OffTimer:6; ///< Nr of Half hours. Off is 0b111111
uint8_t BeepDisable:1; ///< 0 = no beep in follow me messages, 1 = beep.
// Byte 3
uint8_t Temp:5;
uint8_t useFahrenheit:1;
uint8_t :0;
// Byte 4
uint8_t Mode:3;
uint8_t Fan:2;
/// @todo Find out what this bit controls.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1342#issuecomment-733721085
uint8_t :1; ///< Unknown, but set on _some_ Pioneer System A/Cs.
uint8_t Sleep:1;
uint8_t Power:1;
// Byte 5
uint8_t Type:3; ///< Normal, Special, or FollowMe message type
uint8_t Header:5; ///< Typically 0b10100
};
};
// Constants
const uint8_t kMideaACMinTempF = 62; ///< Fahrenheit
const uint8_t kMideaACMaxTempF = 86; ///< Fahrenheit
const uint8_t kMideaACMinTempC = 17; ///< Celsius
const uint8_t kMideaACMaxTempC = 30; ///< Celsius
const uint8_t kMideaACMinSensorTempC = 0; ///< Celsius
const uint8_t kMideaACMaxSensorTempC = 37; ///< Celsius
const uint8_t kMideaACMinSensorTempF = 32; ///< Fahrenheit
const uint8_t kMideaACMaxSensorTempF = 99; ///< Fahrenheit (Guess only!)
const uint8_t kMideaACSensorTempOnTimerOff = 0b1111111;
const uint8_t kMideaACTimerOff = 0b111111;
const uint8_t kMideaACCool = 0; // 0b000
const uint8_t kMideaACDry = 1; // 0b001
const uint8_t kMideaACAuto = 2; // 0b010
const uint8_t kMideaACHeat = 3; // 0b011
const uint8_t kMideaACFan = 4; // 0b100
const uint8_t kMideaACFanAuto = 0; // 0b00
const uint8_t kMideaACFanLow = 1; // 0b01
const uint8_t kMideaACFanMed = 2; // 0b10
const uint8_t kMideaACFanHigh = 3; // 0b11
#if KAYSUN_AC
// For Kaysun AC units, Toggle SwingV is 0xA202FFFFFF7E
const uint64_t kMideaACToggleSwingV = 0xA202FFFFFF7E;
const uint64_t kMideaACSwingVStep = 0xA201FFFFFF7C;
#else // KAYSUN_AC
const uint64_t kMideaACToggleSwingV = 0xA201FFFFFF7C;
#endif // KAYSUN_AC
#if DANBY_DAC
// For Danby DAC unit, the Ionizer toggle is the same as ToggleSwingV
// const uint64_t kMideaACToggleIonizer = 0xA201FFFFFF7C;
kSwingVToggleStr = kIonStr;
#endif // DANBY_DAC
const uint64_t kMideaACToggleEcono = 0xA202FFFFFF7E;
const uint64_t kMideaACToggleLight = 0xA208FFFFFF75;
const uint64_t kMideaACToggleTurbo = 0xA209FFFFFF74;
// Mode must be Auto, Cool, or Dry
const uint64_t kMideaACToggleSelfClean = 0xA20DFFFFFF70;
// 8C Heat AKA Freeze Protection
const uint64_t kMideaACToggle8CHeat = 0xA20FFFFFFF73; // Only in Heat
const uint64_t kMideaACQuietOn = 0xA212FFFFFF6E;
const uint64_t kMideaACQuietOff = 0xA213FFFFFF6F;
const uint8_t kMideaACTypeCommand = 0b001; ///< Message type
const uint8_t kMideaACTypeSpecial = 0b010; ///< Message type
const uint8_t kMideaACTypeFollow = 0b100; ///< Message type
// Legacy defines. (Deprecated)
#define MIDEA_AC_COOL kMideaACCool
#define MIDEA_AC_DRY kMideaACDry
#define MIDEA_AC_AUTO kMideaACAuto
#define MIDEA_AC_HEAT kMideaACHeat
#define MIDEA_AC_FAN kMideaACFan
#define MIDEA_AC_FAN_AUTO kMideaACFanAuto
#define MIDEA_AC_FAN_LOW kMideaACFanLow
#define MIDEA_AC_FAN_MED kMideaACFanMed
#define MIDEA_AC_FAN_HI kMideaACFanHigh
#define MIDEA_AC_POWER kMideaACPower
#define MIDEA_AC_SLEEP kMideaACSleep
#define MIDEA_AC_MIN_TEMP_F kMideaACMinTempF
#define MIDEA_AC_MAX_TEMP_F kMideaACMaxTempF
#define MIDEA_AC_MIN_TEMP_C kMideaACMinTempC
#define MIDEA_AC_MAX_TEMP_C kMideaACMaxTempC
// Classes
/// Class for handling detailed Midea A/C messages.
/// @warning Consider this very alpha code.
class IRMideaAC {
public:
explicit IRMideaAC(const uint16_t pin, const bool inverted = false,
const bool use_modulation = true);
void stateReset(void);
#if SEND_MIDEA
void send(const uint16_t repeat = kMideaMinRepeat);
/// 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_MIDEA
void begin(void);
void on(void);
void off(void);
void setPower(const bool on);
bool getPower(void) const;
bool getUseCelsius(void) const;
void setUseCelsius(const bool celsius);
void setTemp(const uint8_t temp, const bool useCelsius = false);
uint8_t getTemp(const bool useCelsius = false) const;
void setSensorTemp(const uint8_t temp, const bool useCelsius = false);
uint8_t getSensorTemp(const bool useCelsius = false) const;
void setEnableSensorTemp(const bool on);
bool getEnableSensorTemp(void) const;
void setFan(const uint8_t fan);
uint8_t getFan(void) const;
void setMode(const uint8_t mode);
uint8_t getMode(void) const;
void setRaw(const uint64_t newState);
uint64_t getRaw(void);
static bool validChecksum(const uint64_t state);
void setSleep(const bool on);
bool getSleep(void) const;
bool isSwingVToggle(void) const;
void setSwingVToggle(const bool on);
bool getSwingVToggle(void);
#if KAYSUN_AC
bool isSwingVStep(void) const;
void setSwingVStep(const bool on);
bool getSwingVStep(void);
#endif // KAYSUN_AC
bool isEconoToggle(void) const;
void setEconoToggle(const bool on);
bool getEconoToggle(void);
bool isTurboToggle(void) const;
void setTurboToggle(const bool on);
bool getTurboToggle(void);
bool isLightToggle(void) const;
void setLightToggle(const bool on);
bool getLightToggle(void);
bool isCleanToggle(void) const;
void setCleanToggle(const bool on);
bool getCleanToggle(void);
bool is8CHeatToggle(void) const;
void set8CHeatToggle(const bool on);
bool get8CHeatToggle(void);
bool isQuiet(void) const;
void setQuiet(const bool on);
void setQuiet(const bool on, const bool prev);
bool getQuiet(void) const;
uint8_t getType(void) const;
bool isOnTimerEnabled(void) const;
uint16_t getOnTimer(void) const;
void setOnTimer(const uint16_t mins);
bool isOffTimerEnabled(void) const;
uint16_t getOffTimer(void) const;
void setOffTimer(const uint16_t mins);
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);
static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed);
stdAc::state_t toCommon(const stdAc::state_t *prev = NULL);
String toString(void);
#ifndef UNIT_TEST
private:
IRsend _irsend; ///< Instance of the IR send class
#else // UNIT_TEST
/// @cond IGNORE
IRsendTest _irsend; ///< Instance of the testing IR send class
/// @endcond
#endif // UNIT_TEST
MideaProtocol _;
bool _CleanToggle;
bool _EconoToggle;
bool _8CHeatToggle;
bool _LightToggle;
bool _Quiet;
bool _Quiet_prev;
bool _SwingVToggle;
#if KAYSUN_AC
bool _SwingVStep;
#endif // KAYSUN_AC
bool _TurboToggle;
void checksum(void);
static uint8_t calcChecksum(const uint64_t state);
void setType(const uint8_t type);
};
#endif // IR_MIDEA_H_

View File

@@ -0,0 +1,113 @@
// Copyright 2021 Victor Mukayev (vitos1k)
// Copyright 2021 David Conran (crankyoldgit)
/// @file
/// @brief Support for the MilesTag2 IR protocol for LaserTag gaming
/// @see http://hosting.cmalton.me.uk/chrism/lasertag/MT2Proto.pdf
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1360
// Supports:
// Brand: Milestag2, Model: Various
// TODO(vitos1k): This implementation would support only
// short SHOT packets(14bits) and MSGs = 24bits. Support
// for long MSGs > 24bits is TODO
#include <algorithm>
#include "IRrecv.h"
#include "IRsend.h"
#include "IRutils.h"
// Constants
// Shot packets have this bit as `0`
const uint16_t kMilesTag2ShotMask = 1 << (kMilesTag2ShotBits - 1);
// Msg packets have this bit as `1`
const uint32_t kMilesTag2MsgMask = 1 << (kMilesTag2MsgBits - 1);
const uint8_t kMilesTag2MsgTerminator = 0xE8;
const uint16_t kMilesTag2HdrMark = 2400; /// uSeconds.
const uint16_t kMilesTag2Space = 600; /// uSeconds.
const uint16_t kMilesTag2OneMark = 1200; /// uSeconds.
const uint16_t kMilesTag2ZeroMark = 600; /// uSeconds.
const uint16_t kMilesTag2RptLength = 32000; /// uSeconds.
const uint16_t kMilesTag2StdFreq = 38000; /// Hz.
const uint16_t kMilesTag2StdDuty = 25; /// Percentage.
#if SEND_MILESTAG2
/// Send a MilesTag2 formatted Shot/Msg packet.
/// Status: ALPHA / Probably works but needs testing with a real device.
/// @param[in] data The message to be sent.
/// @param[in] nbits The number of bits of message to be sent.
/// @param[in] repeat The number of times the command is to be repeated.
void IRsend::sendMilestag2(const uint64_t data, const uint16_t nbits,
const uint16_t repeat) {
sendGeneric(
kMilesTag2HdrMark, kMilesTag2Space, // Header
kMilesTag2OneMark, kMilesTag2Space, // 1 bit
kMilesTag2ZeroMark, kMilesTag2Space, // 0 bit
0, // No footer mark
kMilesTag2RptLength, data, nbits, kMilesTag2StdFreq, true, // MSB First
repeat, kMilesTag2StdDuty);
}
#endif // SEND_MILESTAG2
#if DECODE_MILESTAG2
/// Decode the supplied MilesTag2 message.
/// Status: ALPHA / Probably works but needs testing with a real device.
/// @param[in,out] results Ptr to the data to decode & where to store the result
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// @param[in] strict Flag indicating if we should perform strict matching.
/// @return True if it can decode it, false if it can't.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1360
bool IRrecv::decodeMilestag2(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
uint64_t data = 0;
// Header + Data + Optional Footer
if (!matchGeneric(results->rawbuf + offset, &data,
results->rawlen - offset, nbits,
kMilesTag2HdrMark, kMilesTag2Space,
kMilesTag2OneMark, kMilesTag2Space,
kMilesTag2ZeroMark, kMilesTag2Space,
0, kMilesTag2RptLength, true)) return false;
// Compliance
if (strict) {
switch (nbits) {
case kMilesTag2ShotBits:
// Is it a valid shot packet?
if (data & kMilesTag2ShotMask) return false;
break;
case kMilesTag2MsgBits:
// Is it a valid msg packet? i.e. Msg bit set & Terminator present.
if (!(data & kMilesTag2MsgMask) ||
((data & 0xFF) != kMilesTag2MsgTerminator))
return false;
break;
default:
DPRINT("incorrect nbits:");
DPRINTLN(nbits);
return false; // The request doesn't strictly match the protocol defn.
}
}
// Success
results->bits = nbits;
results->value = data;
results->decode_type = decode_type_t::MILESTAG2;
switch (nbits) {
case kMilesTag2ShotBits:
results->command = data & 0x3F; // Team & Damage
results->address = data >> 6; // Player ID.
break;
case kMilesTag2MsgBits:
results->command = (data >> 8) & 0xFF; // Message data
results->address = (data >> 16) & 0x7F; // Message ID
break;
default:
results->command = 0;
results->address = 0;
}
return true;
}
#endif // DECODE_MILESTAG2

View File

@@ -0,0 +1,853 @@
// Copyright 2020-2021 David Conran (crankyoldgit)
/// @file
/// @brief Support for Mirage protocol
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1289
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1573
#include "ir_Mirage.h"
#include <algorithm>
#include <cstring>
#ifndef ARDUINO
#include <string>
#endif
#include "IRrecv.h"
#include "IRsend.h"
#include "IRtext.h"
#include "IRutils.h"
using irutils::addBoolToString;
using irutils::addFanToString;
using irutils::addIntToString;
using irutils::addLabeledString;
using irutils::addModeToString;
using irutils::addModelToString;
using irutils::addSwingHToString;
using irutils::addSwingVToString;
using irutils::addTempToString;
using irutils::addToggleToString;
using irutils::minsToString;
using irutils::bcdToUint8;
using irutils::uint8ToBcd;
using irutils::sumNibbles;
// Constants
const uint16_t kMirageHdrMark = 8360; ///< uSeconds
const uint16_t kMirageBitMark = 554; ///< uSeconds
const uint16_t kMirageHdrSpace = 4248; ///< uSeconds
const uint16_t kMirageOneSpace = 1592; ///< uSeconds
const uint16_t kMirageZeroSpace = 545; ///< uSeconds
const uint32_t kMirageGap = kDefaultMessageGap; ///< uSeconds (just a guess)
const uint16_t kMirageFreq = 38000; ///< Hz. (Just a guess)
const uint8_t kMirageAcKKG29AC1PowerOn = 0b00; // 0
const uint8_t kMirageAcKKG29AC1PowerOff = 0b11; // 3
#if SEND_MIRAGE
/// Send a Mirage formatted message.
/// Status: STABLE / Reported as working.
/// @param[in] data An array of bytes containing the IR command.
/// @param[in] nbytes Nr. of bytes of data in the array. (>=kMirageStateLength)
/// @param[in] repeat Nr. of times the message is to be repeated.
void IRsend::sendMirage(const uint8_t data[], const uint16_t nbytes,
const uint16_t repeat) {
sendGeneric(kMirageHdrMark, kMirageHdrSpace,
kMirageBitMark, kMirageOneSpace,
kMirageBitMark, kMirageZeroSpace,
kMirageBitMark, kMirageGap,
data, nbytes, kMirageFreq, false, // LSB
repeat, kDutyDefault);
}
#endif // SEND_MIRAGE
#if DECODE_MIRAGE
/// Decode the supplied Mirage message.
/// Status: STABLE / Reported as working.
/// @param[in,out] results Ptr to the data to decode & where to store the decode
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// @param[in] strict Flag indicating if we should perform strict matching.
/// @return A boolean. True if it can decode it, false if it can't.
bool IRrecv::decodeMirage(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
if (strict && nbits != kMirageBits) return false; // Compliance.
if (!matchGeneric(results->rawbuf + offset, results->state,
results->rawlen - offset, nbits,
kMirageHdrMark, kMirageHdrSpace,
kMirageBitMark, kMirageOneSpace,
kMirageBitMark, kMirageZeroSpace,
kMirageBitMark, kMirageGap, true,
kUseDefTol, kMarkExcess, false)) return false;
// Compliance
if (strict && !IRMirageAc::validChecksum(results->state)) return false;
// Success
results->decode_type = decode_type_t::MIRAGE;
results->bits = nbits;
// 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;
}
// Code to emulate Mirage A/C IR remote control unit.
/// 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?
IRMirageAc::IRMirageAc(const uint16_t pin, const bool inverted,
const bool use_modulation)
: _irsend(pin, inverted, use_modulation) { stateReset(); }
/// Reset the state of the remote to a known good state/sequence.
void IRMirageAc::stateReset(void) {
// The state of the IR remote in IR code form.
static const uint8_t kReset[kMirageStateLength] = {
0x56, 0x6C, 0x00, 0x00, 0x20, 0x1A, 0x00, 0x00,
0x0C, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x42};
setRaw(kReset);
_model = mirage_ac_remote_model_t::KKG9AC1;
}
/// Set up hardware to be able to send a message.
void IRMirageAc::begin(void) { _irsend.begin(); }
#if SEND_MITSUBISHI_AC
/// Send the current internal state as an IR message.
/// @param[in] repeat Nr. of times the message will be repeated.
void IRMirageAc::send(const uint16_t repeat) {
_irsend.sendMirage(getRaw(), kMirageStateLength, repeat);
// Reset any toggles after a send.
switch (_model) {
case mirage_ac_remote_model_t::KKG29AC1:
setCleanToggle(false);
setLight(false); // For this model (only), Light is a toggle.
break;
default:
break;
}
}
#endif // SEND_MITSUBISHI_AC
/// 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 *IRMirageAc::getRaw(void) {
checksum();
return _.raw;
}
/// Set the internal state from a valid code for this protocol.
/// @param[in] data A valid code for this protocol.
void IRMirageAc::setRaw(const uint8_t *data) {
std::memcpy(_.raw, data, kMirageStateLength);
_model = getModel(true);
}
/// Guess the Mirage remote model from the supplied state code.
/// @param[in] state A valid state code for this protocol.
/// @return The model code.
/// @note This result isn't perfect. Both protocols can look the same but have
/// wildly different settings.
mirage_ac_remote_model_t IRMirageAc::getModel(const uint8_t *state) {
Mirage120Protocol p;
std::memcpy(p.raw, state, kMirageStateLength);
// Check for KKG29AC1 specific settings.
if (p.RecycleHeat || p.Filter || p.Sleep_Kkg29ac1 || p.CleanToggle ||
p.IFeel || p.OffTimerEnable || p.OnTimerEnable)
return mirage_ac_remote_model_t::KKG29AC1;
// Check for things specific to KKG9AC1
if ((p.Minutes || p.Seconds) || // Is part of the clock set?
// Are the timer times set, but not enabled? (enable check filtered above)
(p.OffTimerHours || p.OffTimerMins) ||
(p.OnTimerHours || p.OnTimerMins))
return mirage_ac_remote_model_t::KKG9AC1;
// As the above test has a 1 in 3600+ (for 1 second an hour) chance of a false
// negative in theory, we are going assume that anything left should be a
// KKG29AC1 model.
return mirage_ac_remote_model_t::KKG29AC1; // Default.
}
/// Get the model code of the interal message state.
/// @param[in] useRaw If set, we try to get the model info from just the state.
/// @return The model code.
mirage_ac_remote_model_t IRMirageAc::getModel(const bool useRaw) const {
return useRaw ? getModel(_.raw) : _model;
}
/// Set the model code of the interal message state.
/// @param[in] model The desired model to use for the settings.
void IRMirageAc::setModel(const mirage_ac_remote_model_t model) {
if (model != _model) { // Only change things if we need to.
// Save the old settings.
stdAc::state_t state = toCommon();
const uint16_t ontimer = getOnTimer();
const uint16_t offtimer = getOffTimer();
const bool ifeel = getIFeel();
const uint8_t sensor = getSensorTemp();
// Change the model.
state.model = model;
// Restore/Convert the settings.
fromCommon(state);
setOnTimer(ontimer);
setOffTimer(offtimer);
setIFeel(ifeel);
setSensorTemp(sensor);
}
}
/// Calculate and set the checksum values for the internal state.
void IRMirageAc::checksum(void) { _.Sum = calculateChecksum(_.raw); }
/// Verify the checksum is valid for a given state.
/// @param[in] data The array to verify the checksum of.
/// @return true, if the state has a valid checksum. Otherwise, false.
bool IRMirageAc::validChecksum(const uint8_t *data) {
return calculateChecksum(data) == data[kMirageStateLength - 1];
}
/// Calculate the checksum for a given state.
/// @param[in] data The value to calc the checksum of.
/// @return The calculated checksum value.
uint8_t IRMirageAc::calculateChecksum(const uint8_t *data) {
return sumNibbles(data, kMirageStateLength - 1);
}
/// Set the requested power state of the A/C to on.
void IRMirageAc::on(void) { setPower(true); }
/// Set the requested power state of the A/C to off.
void IRMirageAc::off(void) { setPower(false); }
/// Change the power setting.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRMirageAc::setPower(bool on) {
switch (_model) {
case mirage_ac_remote_model_t::KKG29AC1:
_.Power = on ? kMirageAcKKG29AC1PowerOn : kMirageAcKKG29AC1PowerOff;
break;
default:
// In order to change the power setting, it seems must be less than
// kMirageAcPowerOff. kMirageAcPowerOff is larger than half of the
// possible value stored in the allocated bit space.
// Thus if the value is larger than kMirageAcPowerOff the power is off.
// Less than, then power is on.
// We can't just aribitarily add or subtract the value (which analysis
// indicates is how the power status changes. Very weird, I know!) as that
// is not an idempotent action, we must check if the addition or
// substraction is needed first. e.g. via getPower()
// i.e. If we added or subtracted twice, we would cause a wrap of the
// integer and not get the desired result.
if (on)
_.SwingAndPower -= getPower() ? 0 : kMirageAcPowerOff;
else
_.SwingAndPower += getPower() ? kMirageAcPowerOff : 0;
}
}
/// Get the value of the current power setting.
/// @return true, the setting is on. false, the setting is off.
bool IRMirageAc::getPower(void) const {
switch (_model) {
case mirage_ac_remote_model_t::KKG29AC1:
return _.Power == kMirageAcKKG29AC1PowerOn;
default:
return _.SwingAndPower < kMirageAcPowerOff;
}
}
/// Get the operating mode setting of the A/C.
/// @return The current operating mode setting.
uint8_t IRMirageAc::getMode(void) const { return _.Mode; }
/// Set the operating mode of the A/C.
/// @param[in] mode The desired operating mode.
void IRMirageAc::setMode(const uint8_t mode) {
switch (mode) {
case kMirageAcCool:
case kMirageAcDry:
case kMirageAcHeat:
case kMirageAcFan:
case kMirageAcRecycle:
_.Mode = mode;
// Reset turbo if we have to.
setTurbo(getTurbo());
break;
default: // Default to cool mode for anything else.
setMode(kMirageAcCool);
}
}
/// Set the temperature.
/// @param[in] degrees The temperature in degrees celsius.
void IRMirageAc::setTemp(const uint8_t degrees) {
// Make sure we have desired temp in the correct range.
uint8_t celsius = std::max(degrees, kMirageAcMinTemp);
_.Temp = std::min(celsius, kMirageAcMaxTemp) + kMirageAcTempOffset;
}
/// Get the current temperature setting.
/// @return The current setting for temp. in degrees celsius.
uint8_t IRMirageAc::getTemp(void) const { return _.Temp - kMirageAcTempOffset; }
/// Set the speed of the fan.
/// @param[in] speed The desired setting.
void IRMirageAc::setFan(const uint8_t speed) {
_.Fan = (speed <= kMirageAcFanLow) ? speed : kMirageAcFanAuto;
}
/// Get the current fan speed setting.
/// @return The current fan speed/mode.
uint8_t IRMirageAc::getFan(void) const { return _.Fan; }
/// Change the Turbo setting.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRMirageAc::setTurbo(bool on) {
const bool value = (on && (getMode() == kMirageAcCool));
switch (_model) {
case mirage_ac_remote_model_t::KKG29AC1:
_.Turbo_Kkg29ac1 = value;
break;
default:
_.Turbo_Kkg9ac1 = value;
}
}
/// Get the value of the current Turbo setting.
/// @return true, the setting is on. false, the setting is off.
bool IRMirageAc::getTurbo(void) const {
switch (_model) {
case mirage_ac_remote_model_t::KKG29AC1: return _.Turbo_Kkg29ac1;
default: return _.Turbo_Kkg9ac1;
}
}
/// Change the Sleep setting.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRMirageAc::setSleep(bool on) {
switch (_model) {
case mirage_ac_remote_model_t::KKG29AC1:
_.Sleep_Kkg29ac1 = on;
break;
default:
_.Sleep_Kkg9ac1 = on;
}
}
/// Get the value of the current Sleep setting.
/// @return true, the setting is on. false, the setting is off.
bool IRMirageAc::getSleep(void) const {
switch (_model) {
case mirage_ac_remote_model_t::KKG29AC1: return _.Sleep_Kkg29ac1;
default: return _.Sleep_Kkg9ac1;
}
}
/// Change the Light/Display setting.
/// @param[in] on true, the setting is on. false, the setting is off.
/// @note Light is a toggle on the KKG29AC1 model.
void IRMirageAc::setLight(bool on) {
switch (_model) {
case mirage_ac_remote_model_t::KKG29AC1:
_.LightToggle_Kkg29ac1 = on;
break;
default:
_.Light_Kkg9ac1 = on;
}
}
/// Get the value of the current Light/Display setting.
/// @return true, the setting is on. false, the setting is off.
/// @note Light is a toggle on the KKG29AC1 model.
bool IRMirageAc::getLight(void) const {
switch (_model) {
case mirage_ac_remote_model_t::KKG29AC1: return _.LightToggle_Kkg29ac1;
default: return _.Light_Kkg9ac1;
}
}
/// Get the clock time of the A/C unit.
/// @return Nr. of seconds past midnight.
uint32_t IRMirageAc::getClock(void) const {
switch (_model) {
case mirage_ac_remote_model_t::KKG29AC1:
return 0;
default:
return ((bcdToUint8(_.Hours) * 60) + bcdToUint8(_.Minutes)) * 60 +
bcdToUint8(_.Seconds);
}
}
/// Set the clock time on the A/C unit.
/// @param[in] nr_of_seconds Nr. of seconds past midnight.
void IRMirageAc::setClock(const uint32_t nr_of_seconds) {
switch (_model) {
case mirage_ac_remote_model_t::KKG29AC1:
_.Minutes = _.Seconds = 0; // No clock setting. Clear it just in case.
break;
default:
uint32_t remaining = std::min(
nr_of_seconds, (uint32_t)(24 * 60 * 60 - 1)); // Limit to 23:59:59.
_.Seconds = uint8ToBcd(remaining % 60);
remaining /= 60;
_.Minutes = uint8ToBcd(remaining % 60);
remaining /= 60;
_.Hours = uint8ToBcd(remaining);
}
}
/// Set the Vertical Swing setting/position of the A/C.
/// @param[in] position The desired swing setting.
void IRMirageAc::setSwingV(const uint8_t position) {
switch (position) {
case kMirageAcSwingVOff:
case kMirageAcSwingVLowest:
case kMirageAcSwingVLow:
case kMirageAcSwingVMiddle:
case kMirageAcSwingVHigh:
case kMirageAcSwingVHighest:
case kMirageAcSwingVAuto:
switch (_model) {
case mirage_ac_remote_model_t::KKG29AC1:
_.SwingV = (position != kMirageAcSwingVOff);
break;
default:
const bool power = getPower();
_.SwingAndPower = position;
// Power needs to be reapplied after overwriting SwingAndPower
setPower(power);
}
break;
default: // Default to Auto for anything else.
setSwingV(kMirageAcSwingVAuto);
}
}
/// Get the Vertical Swing setting/position of the A/C.
/// @return The desired Vertical Swing setting/position.
uint8_t IRMirageAc::getSwingV(void) const {
switch (_model) {
case mirage_ac_remote_model_t::KKG29AC1:
return _.SwingV ? kMirageAcSwingVAuto : kMirageAcSwingVOff;
default:
return _.SwingAndPower - (getPower() ? 0 : kMirageAcPowerOff);
}
}
/// Set the Horizontal Swing setting of the A/C.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRMirageAc::setSwingH(const bool on) {
switch (_model) {
case mirage_ac_remote_model_t::KKG29AC1:
_.SwingH = on;
break;
default:
break;
}
}
/// Get the Horizontal Swing setting of the A/C.
/// @return on true, the setting is on. false, the setting is off.
bool IRMirageAc::getSwingH(void) const {
switch (_model) {
case mirage_ac_remote_model_t::KKG29AC1: return _.SwingH;
default: return false;
}
}
/// Set the Quiet setting of the A/C.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRMirageAc::setQuiet(const bool on) {
switch (_model) {
case mirage_ac_remote_model_t::KKG29AC1:
_.Quiet = on;
break;
default:
break;
}
}
/// Get the Quiet setting of the A/C.
/// @return on true, the setting is on. false, the setting is off.
bool IRMirageAc::getQuiet(void) const {
switch (_model) {
case mirage_ac_remote_model_t::KKG29AC1: return _.Quiet;
default: return false;
}
}
/// Set the CleanToggle setting of the A/C.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRMirageAc::setCleanToggle(const bool on) {
switch (_model) {
case mirage_ac_remote_model_t::KKG29AC1:
_.CleanToggle = on;
break;
default:
break;
}
}
/// Get the Clean Toggle setting of the A/C.
/// @return on true, the setting is on. false, the setting is off.
bool IRMirageAc::getCleanToggle(void) const {
switch (_model) {
case mirage_ac_remote_model_t::KKG29AC1: return _.CleanToggle;
default: return false;
}
}
/// Set the Filter setting of the A/C.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRMirageAc::setFilter(const bool on) {
switch (_model) {
case mirage_ac_remote_model_t::KKG29AC1:
_.Filter = on;
break;
default:
break;
}
}
/// Get the Filter setting of the A/C.
/// @return on true, the setting is on. false, the setting is off.
bool IRMirageAc::getFilter(void) const {
switch (_model) {
case mirage_ac_remote_model_t::KKG29AC1: return _.Filter;
default: return false;
}
}
/// Set the IFeel setting of the A/C.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRMirageAc::setIFeel(const bool on) {
switch (_model) {
case mirage_ac_remote_model_t::KKG29AC1:
_.IFeel = on;
if (on) {
// If no previous sensor temp, default to currently desired temp.
if (!_.SensorTemp) _.SensorTemp = getTemp();
} else {
_.SensorTemp = 0; // When turning it off, clear the Sensor Temp.
}
break;
default:
break;
}
}
/// Get the IFeel setting of the A/C.
/// @return on true, the setting is on. false, the setting is off.
bool IRMirageAc::getIFeel(void) const {
switch (_model) {
case mirage_ac_remote_model_t::KKG29AC1: return _.IFeel;
default: return false;
}
}
/// Set the Sensor Temp setting of the A/C's remote.
/// @param[in] degrees The desired sensor temp. in degrees celsius.
void IRMirageAc::setSensorTemp(const uint8_t degrees) {
switch (_model) {
case mirage_ac_remote_model_t::KKG29AC1:
_.SensorTemp = std::min(kMirageAcSensorTempMax, degrees) +
kMirageAcSensorTempOffset;
break;
default:
break;
}
}
/// Get the Sensor Temp setting of the A/C's remote.
/// @return The current setting for the sensor temp. in degrees celsius.
uint16_t IRMirageAc::getSensorTemp(void) const {
switch (_model) {
case mirage_ac_remote_model_t::KKG29AC1:
return _.SensorTemp - kMirageAcSensorTempOffset;
default:
return false;
}
}
/// Get the number of minutes the On Timer is currently set for.
/// @return Nr. of Minutes the timer is set for. 0, is the timer is not in use.
uint16_t IRMirageAc::getOnTimer(void) const {
switch (_model) {
case mirage_ac_remote_model_t::KKG29AC1:
return _.OnTimerEnable ? _.OnTimerHours * 60 + _.OnTimerMins : 0;
default:
return 0;
}
}
/// Set the number of minutes for the On Timer.
/// @param[in] nr_of_mins How long to set the timer for. 0 disables the timer.
void IRMirageAc::setOnTimer(const uint16_t nr_of_mins) {
uint16_t mins = std::min(nr_of_mins, (uint16_t)(24 * 60));
switch (_model) {
case mirage_ac_remote_model_t::KKG29AC1:
_.OnTimerEnable = (mins > 0);
_.OnTimerHours = mins / 60;
_.OnTimerMins = mins % 60;
break;
default:
break;
}
}
/// Get the number of minutes the Off Timer is currently set for.
/// @return Nr. of Minutes the timer is set for. 0, is the timer is not in use.
uint16_t IRMirageAc::getOffTimer(void) const {
switch (_model) {
case mirage_ac_remote_model_t::KKG29AC1:
return _.OffTimerEnable ? _.OffTimerHours * 60 + _.OffTimerMins : 0;
default:
return 0;
}
}
/// Set the number of minutes for the Off Timer.
/// @param[in] nr_of_mins How long to set the timer for. 0 disables the timer.
void IRMirageAc::setOffTimer(const uint16_t nr_of_mins) {
uint16_t mins = std::min(nr_of_mins, (uint16_t)(24 * 60));
switch (_model) {
case mirage_ac_remote_model_t::KKG29AC1:
_.OffTimerEnable = (mins > 0);
_.OffTimerHours = mins / 60;
_.OffTimerMins = mins % 60;
break;
default:
break;
}
}
/// Convert a native mode into its stdAc equivalent.
/// @param[in] mode The native setting to be converted.
/// @return The stdAc equivalent of the native setting.
stdAc::opmode_t IRMirageAc::toCommonMode(const uint8_t mode) {
switch (mode) {
case kMirageAcHeat: return stdAc::opmode_t::kHeat;
case kMirageAcDry: return stdAc::opmode_t::kDry;
case kMirageAcFan: return stdAc::opmode_t::kFan;
default: return stdAc::opmode_t::kCool;
}
}
/// Convert a native fan speed into its stdAc equivalent.
/// @param[in] speed The native setting to be converted.
/// @param[in] model The model type to use to influence the conversion.
/// @return The stdAc equivalent of the native setting.
stdAc::fanspeed_t IRMirageAc::toCommonFanSpeed(const uint8_t speed,
const mirage_ac_remote_model_t model) {
switch (model) {
case mirage_ac_remote_model_t::KKG29AC1:
switch (speed) {
case kMirageAcKKG29AC1FanHigh: return stdAc::fanspeed_t::kHigh;
case kMirageAcKKG29AC1FanMed: return stdAc::fanspeed_t::kMedium;
case kMirageAcKKG29AC1FanLow: return stdAc::fanspeed_t::kLow;
default: return stdAc::fanspeed_t::kAuto;
}
break;
default:
switch (speed) {
case kMirageAcFanHigh: return stdAc::fanspeed_t::kHigh;
case kMirageAcFanMed: return stdAc::fanspeed_t::kMedium;
case kMirageAcFanLow: return stdAc::fanspeed_t::kLow;
default: return stdAc::fanspeed_t::kAuto;
}
}
}
/// Convert a stdAc::opmode_t enum into its native mode.
/// @param[in] mode The enum to be converted.
/// @return The native equivalent of the enum.
uint8_t IRMirageAc::convertMode(const stdAc::opmode_t mode) {
switch (mode) {
case stdAc::opmode_t::kHeat: return kMirageAcHeat;
case stdAc::opmode_t::kDry: return kMirageAcDry;
case stdAc::opmode_t::kFan: return kMirageAcFan;
default: return kMirageAcCool;
}
}
/// Convert a stdAc::fanspeed_t enum into it's native speed.
/// @param[in] speed The enum to be converted.
/// @param[in] model The model type to use to influence the conversion.
/// @return The native equivalent of the enum.
uint8_t IRMirageAc::convertFan(const stdAc::fanspeed_t speed,
const mirage_ac_remote_model_t model) {
uint8_t low;
uint8_t med;
switch (model) {
case mirage_ac_remote_model_t::KKG29AC1:
low = kMirageAcKKG29AC1FanLow;
med = kMirageAcKKG29AC1FanMed;
break;
default:
low = kMirageAcFanLow;
med = kMirageAcFanMed;
}
switch (speed) {
case stdAc::fanspeed_t::kMin:
case stdAc::fanspeed_t::kLow: return low;
case stdAc::fanspeed_t::kMedium: return med;
case stdAc::fanspeed_t::kHigh:
case stdAc::fanspeed_t::kMax: return kMirageAcFanHigh;
default: return kMirageAcFanAuto;
}
}
/// Convert a stdAc::swingv_t enum into it's native setting.
/// @param[in] position The enum to be converted.
/// @return The native equivalent of the enum.
uint8_t IRMirageAc::convertSwingV(const stdAc::swingv_t position) {
switch (position) {
case stdAc::swingv_t::kHighest: return kMirageAcSwingVHighest;
case stdAc::swingv_t::kHigh: return kMirageAcSwingVHigh;
case stdAc::swingv_t::kMiddle: return kMirageAcSwingVMiddle;
case stdAc::swingv_t::kLow: return kMirageAcSwingVLow;
case stdAc::swingv_t::kLowest: return kMirageAcSwingVLowest;
case stdAc::swingv_t::kOff: return kMirageAcSwingVOff;
default: return kMirageAcSwingVAuto;
}
}
/// Convert a native vertical swing postion to it's common equivalent.
/// @param[in] pos A native position to convert.
/// @return The common vertical swing position.
stdAc::swingv_t IRMirageAc::toCommonSwingV(const uint8_t pos) {
switch (pos) {
case kMirageAcSwingVHighest: return stdAc::swingv_t::kHighest;
case kMirageAcSwingVHigh: return stdAc::swingv_t::kHigh;
case kMirageAcSwingVMiddle: return stdAc::swingv_t::kMiddle;
case kMirageAcSwingVLow: return stdAc::swingv_t::kLow;
case kMirageAcSwingVLowest: return stdAc::swingv_t::kLowest;
case kMirageAcSwingVAuto: return stdAc::swingv_t::kAuto;
default: return stdAc::swingv_t::kOff;
}
}
/// Convert the current internal state into its stdAc::state_t equivalent.
/// @return The stdAc equivalent of the native settings.
stdAc::state_t IRMirageAc::toCommon(void) const {
stdAc::state_t result{};
result.protocol = decode_type_t::MIRAGE;
result.model = _model;
result.power = getPower();
result.mode = toCommonMode(_.Mode);
result.celsius = true;
result.degrees = getTemp();
result.sensorTemperature = getSensorTemp();
result.fanspeed = toCommonFanSpeed(getFan(), _model);
result.swingv = toCommonSwingV(getSwingV());
result.swingh = getSwingH() ? stdAc::swingh_t::kAuto : stdAc::swingh_t::kOff;
result.turbo = getTurbo();
result.light = getLight();
result.clean = getCleanToggle();
result.filter = getFilter();
result.sleep = getSleep() ? 0 : -1;
result.quiet = getQuiet();
result.clock = getClock() / 60;
result.iFeel = getIFeel();
// Not supported.
result.econo = false;
result.beep = false;
return result;
}
/// Convert & set a stdAc::state_t to its equivalent internal settings.
/// @param[in] state The desired state in stdAc::state_t form.
void IRMirageAc::fromCommon(const stdAc::state_t state) {
stateReset();
_model = (mirage_ac_remote_model_t)state.model; // Set directly to avoid loop
setPower(state.power);
setTemp(state.celsius ? state.degrees : fahrenheitToCelsius(state.degrees));
setMode(convertMode(state.mode));
setFan(convertFan(state.fanspeed, _model));
setTurbo(state.turbo);
setSleep(state.sleep >= 0);
setLight(state.light);
setSwingV(convertSwingV(state.swingv));
setSwingH(state.swingh != stdAc::swingh_t::kOff);
setQuiet(state.quiet);
setCleanToggle(state.clean);
setFilter(state.filter);
// setClock() expects seconds, not minutes.
setClock((state.clock > 0) ? state.clock * 60 : 0);
setIFeel(state.iFeel);
if (state.sensorTemperature != kNoTempValue) {
setSensorTemp(state.celsius ? state.sensorTemperature
: fahrenheitToCelsius(state.sensorTemperature));
}
// Non-common settings.
setOnTimer(0);
setOffTimer(0);
}
/// Convert the internal state into a human readable string.
/// @return A string containing the settings in human-readable form.
String IRMirageAc::toString(void) const {
String result = "";
result.reserve(240); // Reserve some heap for the string to reduce fragging.
result += addModelToString(decode_type_t::MIRAGE, _model, false);
result += addBoolToString(getPower(), kPowerStr);
result += addModeToString(_.Mode, 0xFF, kMirageAcCool,
kMirageAcHeat, kMirageAcDry,
kMirageAcFan);
result += addTempToString(getTemp());
uint8_t fanlow;
uint8_t fanmed;
switch (_model) {
case mirage_ac_remote_model_t::KKG29AC1:
fanlow = kMirageAcKKG29AC1FanLow;
fanmed = kMirageAcKKG29AC1FanMed;
break;
default: // e.g. Model KKG9AC1
fanlow = kMirageAcFanLow;
fanmed = kMirageAcFanMed;
}
result += addFanToString(_.Fan, kMirageAcFanHigh, fanlow, kMirageAcFanAuto,
kMirageAcFanAuto, fanmed);
result += addBoolToString(getTurbo(), kTurboStr);
result += addBoolToString(getSleep(), kSleepStr);
switch (_model) {
case mirage_ac_remote_model_t::KKG29AC1:
result += addBoolToString(_.Quiet, kQuietStr);
result += addToggleToString(getLight(), kLightStr);
result += addBoolToString(_.SwingV, kSwingVStr);
result += addBoolToString(_.SwingH, kSwingHStr);
result += addBoolToString(_.Filter, kFilterStr);
result += addToggleToString(_.CleanToggle, kCleanStr);
result += addLabeledString(getOnTimer() ? minsToString(getOnTimer())
: kOffStr,
kOnTimerStr);
result += addLabeledString(getOffTimer() ? minsToString(getOffTimer())
: kOffStr,
kOffTimerStr);
result += addBoolToString(_.IFeel, kIFeelStr);
if (_.IFeel) {
result += addIntToString(getSensorTemp(), kSensorTempStr);
result += 'C';
}
break;
default: // e.g. Model KKG9AC1
result += addBoolToString(getLight(), kLightStr);
result += addSwingVToString(getSwingV(),
kMirageAcSwingVAuto,
kMirageAcSwingVHighest,
kMirageAcSwingVHigh,
0xFF, // Unused.
kMirageAcSwingVMiddle,
0xFF, // Unused.
kMirageAcSwingVLow,
kMirageAcSwingVLowest,
kMirageAcSwingVOff,
0xFF, 0xFF, 0xFF); // Unused.
result += addLabeledString(minsToString(getClock() / 60), kClockStr);
}
return result;
}
#endif // DECODE_MIRAGE

View File

@@ -0,0 +1,277 @@
// Copyright 2020-2021 David Conran (crankyoldgit)
/// @file
/// @brief Support for Mirage protocol
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1289
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1573
// Supports:
// Brand: Mirage, Model: VLU series A/C
// Brand: Maxell, Model: MX-CH18CF A/C
// Brand: Maxell, Model: KKG9A-C1 remote
// Brand: Tronitechnik, Model: Reykir 9000 A/C
// Brand: Tronitechnik, Model: KKG29A-C1 remote
#ifndef IR_MIRAGE_H_
#define IR_MIRAGE_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
/// Native representation of a Mirage 120-bit A/C message.
/// @see https://docs.google.com/spreadsheets/d/1Ucu9mOOIIJoWQjUJq_VCvwgV3EwKaRk8K2AuZgccYEk/edit#gid=0
union Mirage120Protocol{
uint8_t raw[kMirageStateLength]; ///< The state in code form.
struct { // Common
// Byte 0
uint8_t Header :8; // Header. (0x56)
// Byte 1
uint8_t Temp :8; // Celsius minus 0x5C.
// Byte 2
uint8_t :8; // Unknown / Unused.
// Byte 3
uint8_t :8; // Unknown / Unused.
// Byte 4
uint8_t Fan :2; // Fan Speed.
uint8_t :2; // Unknown / Unused.
uint8_t Mode :4; // Cool, Heat, Dry, Fan, Recycle
// Byte 5
uint8_t :8;
// Byte 6
uint8_t :8;
// Byte 7
uint8_t :8;
// Byte 8
uint8_t :8;
// Byte 9
uint8_t :8;
// Byte 10
uint8_t :8;
// Byte 11
uint8_t :8;
// Byte 12
uint8_t :8;
// Byte 13
uint8_t :8;
// Byte 14
uint8_t Sum :8; // Sum of all the previous nibbles.
};
struct { // KKG9AC1 remote
// Byte 0
uint8_t :8; // Header
// Byte 1
uint8_t :8; // Temp
// Byte 2
uint8_t :8; // Unknown / Unused.
// Byte 3
uint8_t :3; // Unknown / Unused.
uint8_t Light_Kkg9ac1 :1; // Aka. Display. Seems linked to Sleep mode.
uint8_t :4; // Unknown / Unused.
// Byte 4
uint8_t :8; // Fan & Mode
// Byte 5
uint8_t :1; // Unknown
uint8_t SwingAndPower :7;
// Byte 6
uint8_t :7; // Unknown / Unused.
uint8_t Sleep_Kkg9ac1 :1; // Sleep mode on or off.
// Byte 7
uint8_t :3; // Unknown / Unused.
uint8_t Turbo_Kkg9ac1 :1; // Turbo mode on or off. Only works in Cool mode.
uint8_t :4; // Unknown / Unused.
// Byte 8
uint8_t :8; // Unknown / Unused.
// Byte 9
uint8_t :8; // Unknown / Unused.
// Byte 10
uint8_t :8; // Unknown / Unused.
// Byte 11
uint8_t Seconds :8; // Nr. of Seconds in BCD.
// Byte 12
uint8_t Minutes :8; // Nr. of Minutes in BCD.
// Byte 13
uint8_t Hours :8; // Nr. of Hours in BCD.
// Byte 14
uint8_t :8; // Sum
};
struct { // KKG29A-C1 remote
// Byte 0
uint8_t :8; // Header
// Byte 1
uint8_t :8; // Temp
// Byte 2
uint8_t :8;
// Byte 3
uint8_t Quiet :1;
uint8_t :7;
// Byte 4
uint8_t :2; // Fan
uint8_t OffTimerEnable :1;
uint8_t OnTimerEnable :1;
uint8_t :3; // Mode
uint8_t :1;
// Byte 5
uint8_t SwingH :1;
uint8_t SwingV :1;
uint8_t LightToggle_Kkg29ac1 :1; // Aka. Display Toggle.
uint8_t :3;
uint8_t Power :2;
// Byte 6
uint8_t :1;
uint8_t Filter :1; // Aka. UVC
uint8_t :1;
uint8_t Sleep_Kkg29ac1 :1; // Sleep mode on or off.
uint8_t :2;
uint8_t RecycleHeat :1;
uint8_t :1;
// Byte 7
uint8_t SensorTemp :6; // Temperature at the remote
uint8_t CleanToggle :1;
uint8_t IFeel :1;
// Byte 8
uint8_t OnTimerHours :5;
uint8_t :2;
uint8_t Turbo_Kkg29ac1 :1; // Turbo mode on or off.
// Byte 9
uint8_t OnTimerMins :6;
uint8_t :2;
// Byte 10
uint8_t OffTimerHours :5;
uint8_t :3;
// Byte 11
uint8_t OffTimerMins :6;
uint8_t :2;
// Byte 12
uint8_t :8;
// Byte 13
uint8_t :8;
// Byte 14
uint8_t :8; // Sum
};
};
// Constants
const uint8_t kMirageAcHeat = 0b001; // 1
const uint8_t kMirageAcCool = 0b010; // 2
const uint8_t kMirageAcDry = 0b011; // 3
const uint8_t kMirageAcRecycle = 0b100; // 4
const uint8_t kMirageAcFan = 0b101; // 5
const uint8_t kMirageAcFanAuto = 0b00; // 0
const uint8_t kMirageAcFanHigh = 0b01; // 1
const uint8_t kMirageAcFanMed = 0b10; // 2
const uint8_t kMirageAcFanLow = 0b11; // 3
const uint8_t kMirageAcKKG29AC1FanAuto = 0b00; // 0
const uint8_t kMirageAcKKG29AC1FanHigh = 0b01; // 1
const uint8_t kMirageAcKKG29AC1FanLow = 0b10; // 2
const uint8_t kMirageAcKKG29AC1FanMed = 0b11; // 3
const uint8_t kMirageAcMinTemp = 16; // 16C
const uint8_t kMirageAcMaxTemp = 32; // 32C
const uint8_t kMirageAcTempOffset = 0x5C;
const uint8_t kMirageAcSensorTempOffset = 20;
const uint8_t kMirageAcSensorTempMax = 43; // Celsius
const uint8_t kMirageAcPowerOff = 0x5F;
const uint8_t kMirageAcSwingVOff = 0b0000; // 0
const uint8_t kMirageAcSwingVLowest = 0b0011; // 3
const uint8_t kMirageAcSwingVLow = 0b0101; // 5
const uint8_t kMirageAcSwingVMiddle = 0b0111; // 7
const uint8_t kMirageAcSwingVHigh = 0b1001; // 9
const uint8_t kMirageAcSwingVHighest = 0b1011; // 11
const uint8_t kMirageAcSwingVAuto = 0b1101; // 13
/// Class for handling detailed Mirage 120-bit A/C messages.
/// @note Inspired and derived from the work done at: https://github.com/r45635/HVAC-IR-Control
/// @warning Consider this very alpha code. Seems to work, but not validated.
class IRMirageAc {
public:
explicit IRMirageAc(const uint16_t pin, const bool inverted = false,
const bool use_modulation = true);
void stateReset(void);
#if SEND_MIRAGE
void send(const uint16_t repeat = kMirageMinRepeat);
/// 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_MIRAGE
void begin(void);
void on(void);
void off(void);
void setPower(const bool on);
bool getPower(void) const;
void setTemp(const uint8_t degrees);
uint8_t getTemp(void) const;
void setFan(const uint8_t speed);
uint8_t getFan(void) const;
void setMode(const uint8_t mode);
uint8_t getMode(void) const;
uint8_t* getRaw(void);
void setRaw(const uint8_t* data);
uint32_t getClock(void) const;
void setClock(const uint32_t nr_of_seconds);
void setTurbo(const bool on);
bool getTurbo(void) const;
void setLight(const bool on);
bool getLight(void) const;
void setSleep(const bool on);
bool getSleep(void) const;
void setSwingV(const uint8_t position);
uint8_t getSwingV(void) const;
void setSwingH(const bool on);
bool getSwingH(void) const;
void setQuiet(const bool on);
bool getQuiet(void) const;
void setCleanToggle(const bool on);
bool getCleanToggle(void) const;
void setFilter(const bool on);
bool getFilter(void) const;
void setIFeel(const bool on);
bool getIFeel(void) const;
void setSensorTemp(const uint8_t degrees);
uint16_t getSensorTemp(void) const;
uint16_t getOnTimer(void) const;
uint16_t getOffTimer(void) const;
void setOnTimer(const uint16_t nr_of_mins);
void setOffTimer(const uint16_t nr_of_mins);
mirage_ac_remote_model_t getModel(const bool useRaw = false) const;
void setModel(const mirage_ac_remote_model_t model);
static mirage_ac_remote_model_t getModel(const uint8_t *state);
static bool validChecksum(const uint8_t* data);
static uint8_t calculateChecksum(const uint8_t* data);
static uint8_t convertMode(const stdAc::opmode_t mode);
static uint8_t convertFan(const stdAc::fanspeed_t speed,
const mirage_ac_remote_model_t model = mirage_ac_remote_model_t::KKG9AC1);
static uint8_t convertSwingV(const stdAc::swingv_t position);
static stdAc::opmode_t toCommonMode(const uint8_t mode);
static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed,
const mirage_ac_remote_model_t model = mirage_ac_remote_model_t::KKG9AC1);
static stdAc::swingv_t toCommonSwingV(const uint8_t pos);
stdAc::state_t toCommon(void) const;
void fromCommon(const stdAc::state_t state);
String toString(void) const;
#ifndef UNIT_TEST
private:
IRsend _irsend; ///< Instance of the IR send class
#else // UNIT_TEST
/// @cond IGNORE
IRsendTest _irsend; ///< Instance of the testing IR send class
/// @endcond
#endif // UNIT_TEST
Mirage120Protocol _;
mirage_ac_remote_model_t _model;
void checksum(void);
};
#endif // IR_MIRAGE_H_

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,456 @@
// Copyright 2009 Ken Shirriff
// Copyright 2017-2021 David Conran
// Copyright 2019 Mark Kuchel
/// @file
/// @brief Support for Mitsubishi protocols.
/// Mitsubishi (TV) decoding added from https://github.com/z3t0/Arduino-IRremote
/// Mitsubishi (TV) sending & Mitsubishi A/C support added by David Conran
/// @see GlobalCache's Control Tower's Mitsubishi TV data.
/// @see https://github.com/marcosamarinho/IRremoteESP8266/blob/master/ir_Mitsubishi.cpp
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/441
/// @see https://github.com/r45635/HVAC-IR-Control/blob/master/HVAC_ESP8266/HVAC_ESP8266.ino#L84
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/619
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/888
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/947
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1398
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1399
/// @see https://github.com/kuchel77
// Supports:
// Brand: Mitsubishi, Model: TV (MITSUBISHI)
// Brand: Mitsubishi, Model: HC3000 Projector (MITSUBISHI2)
// Brand: Mitsubishi, Model: MS-GK24VA A/C
// Brand: Mitsubishi, Model: KM14A 0179213 remote
// Brand: Mitsubishi Electric, Model: PEAD-RP71JAA Ducted A/C (MITSUBISHI136)
// Brand: Mitsubishi Electric, Model: 001CP T7WE10714 remote (MITSUBISHI136)
// Brand: Mitsubishi Electric, Model: MSH-A24WV A/C (MITSUBISHI112)
// Brand: Mitsubishi Electric, Model: MUH-A24WV A/C (MITSUBISHI112)
// Brand: Mitsubishi Electric, Model: KPOA remote (MITSUBISHI112)
// Brand: Mitsubishi Electric, Model: MLZ-RX5017AS A/C (MITSUBISHI_AC)
// Brand: Mitsubishi Electric, Model: SG153/M21EDF426 remote (MITSUBISHI_AC)
// Brand: Mitsubishi Electric, Model: MSZ-GV2519 A/C (MITSUBISHI_AC)
// Brand: Mitsubishi Electric, Model: RH151/M21ED6426 remote (MITSUBISHI_AC)
// Brand: Mitsubishi Electric, Model: MSZ-SF25VE3 A/C (MITSUBISHI_AC)
// Brand: Mitsubishi Electric, Model: SG15D remote (MITSUBISHI_AC)
// Brand: Mitsubishi Electric, Model: MSZ-ZW4017S A/C (MITSUBISHI_AC)
// Brand: Mitsubishi Electric, Model: MSZ-FHnnVE A/C (MITSUBISHI_AC)
// Brand: Mitsubishi Electric, Model: RH151 remote (MITSUBISHI_AC)
// Brand: Mitsubishi Electric, Model: PAR-FA32MA remote (MITSUBISHI136)
#ifndef IR_MITSUBISHI_H_
#define IR_MITSUBISHI_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
/// Native representation of a Mitsubishi 144-bit A/C message.
union Mitsubishi144Protocol{
uint8_t raw[kMitsubishiACStateLength]; ///< The state in code form.
struct {
// Byte 0~4
uint8_t pad0[5];
// Byte 5
uint8_t :5;
uint8_t Power :1;
uint8_t :2;
// Byte 6
uint8_t :3;
uint8_t Mode :3;
uint8_t ISee : 1;
uint8_t :1;
// Byte 7
uint8_t Temp :4;
uint8_t HalfDegree :1;
uint8_t :3;
// Byte 8
uint8_t :4;
uint8_t WideVane:4; // SwingH
// Byte 9
uint8_t Fan :3;
uint8_t Vane :3; // SwingV or VaneRight
uint8_t VaneBit :1;
uint8_t FanAuto :1;
// Byte 10
uint8_t Clock :8;
// Byte 11
uint8_t StopClock :8;
// Byte 12
uint8_t StartClock:8;
// Byte 13
uint8_t Timer :3;
uint8_t WeeklyTimer :1;
uint8_t :4;
// Byte 14
uint8_t :5;
uint8_t Ecocool :1;
uint8_t :2;
// Byte 15
uint8_t DirectIndirect:2;
uint8_t AbsenseDetect :1;
uint8_t :2;
uint8_t iSave10C :1; // i-SAVE:mode=Heat & iSave=on AND 10C on remote
uint8_t :2;
// Byte 16
uint8_t :1;
uint8_t NaturalFlow :1;
uint8_t :1;
uint8_t VaneLeft :3; // SwingV(Left)
uint8_t :2;
// Byte 17
uint8_t Sum :8;
};
};
// Constants
const uint8_t kMitsubishiAcAuto = 0b100;
const uint8_t kMitsubishiAcCool = 0b011;
const uint8_t kMitsubishiAcDry = 0b010;
const uint8_t kMitsubishiAcHeat = 0b001;
const uint8_t kMitsubishiAcFan = 0b111;
const uint8_t kMitsubishiAcFanAuto = 0;
const uint8_t kMitsubishiAcFanMax = 5;
const uint8_t kMitsubishiAcFanRealMax = 4;
const uint8_t kMitsubishiAcFanSilent = 6;
const uint8_t kMitsubishiAcFanQuiet = kMitsubishiAcFanSilent;
const float kMitsubishiAcMinTemp = 16.0; // 16C
const float kMitsubishiAcMaxTemp = 31.0; // 31C
const uint8_t kMitsubishiAcVaneAuto = 0b000; // Vanes move when AC wants to.
const uint8_t kMitsubishiAcVaneHighest = 0b001;
const uint8_t kMitsubishiAcVaneHigh = 0b010;
const uint8_t kMitsubishiAcVaneMiddle = 0b011;
const uint8_t kMitsubishiAcVaneLow = 0b100;
const uint8_t kMitsubishiAcVaneLowest = 0b101;
const uint8_t kMitsubishiAcVaneSwing = 0b111; // Vanes move all the time.
const uint8_t kMitsubishiAcVaneAutoMove = kMitsubishiAcVaneSwing; // Deprecated
const uint8_t kMitsubishiAcWideVaneLeftMax = 0b0001; // 1
const uint8_t kMitsubishiAcWideVaneLeft = 0b0010; // 2
const uint8_t kMitsubishiAcWideVaneMiddle = 0b0011; // 3
const uint8_t kMitsubishiAcWideVaneRight = 0b0100; // 4
const uint8_t kMitsubishiAcWideVaneRightMax = 0b0101; // 5
const uint8_t kMitsubishiAcWideVaneWide = 0b0110; // 6
const uint8_t kMitsubishiAcWideVaneAuto = 0b1000; // 8
const uint8_t kMitsubishiAcDirectOff = 0b00; // Vanes move when AC wants to.
const uint8_t kMitsubishiAcIndirect = 0b01;
const uint8_t kMitsubishiAcDirect = 0b11;
const uint8_t kMitsubishiAcNoTimer = 0;
const uint8_t kMitsubishiAcStartTimer = 5;
const uint8_t kMitsubishiAcStopTimer = 3;
const uint8_t kMitsubishiAcStartStopTimer = 7;
/// Native representation of a Mitsubishi 136-bit A/C message.
union Mitsubishi136Protocol{
uint8_t raw[kMitsubishi136StateLength]; ///< The state in code form.
struct {
// Byte 0~4
uint8_t pad[5];
// Byte 5
uint8_t :6;
uint8_t Power :1;
uint8_t :1;
// Byte 6
uint8_t Mode :3;
uint8_t :1;
uint8_t Temp :4;
// Byte 7
uint8_t :1;
uint8_t Fan :2;
uint8_t :1;
uint8_t SwingV :4;
};
};
const uint8_t kMitsubishi136PowerByte = 5;
const uint8_t kMitsubishi136MinTemp = 17; // 17C
const uint8_t kMitsubishi136MaxTemp = 30; // 30C
const uint8_t kMitsubishi136Fan = 0b000;
const uint8_t kMitsubishi136Cool = 0b001;
const uint8_t kMitsubishi136Heat = 0b010;
const uint8_t kMitsubishi136Auto = 0b011;
const uint8_t kMitsubishi136Dry = 0b101;
const uint8_t kMitsubishi136SwingVLowest = 0b0000;
const uint8_t kMitsubishi136SwingVLow = 0b0001;
const uint8_t kMitsubishi136SwingVHigh = 0b0010;
const uint8_t kMitsubishi136SwingVHighest = 0b0011;
const uint8_t kMitsubishi136SwingVAuto = 0b1100;
const uint8_t kMitsubishi136FanMin = 0b00;
const uint8_t kMitsubishi136FanLow = 0b01;
const uint8_t kMitsubishi136FanMed = 0b10;
const uint8_t kMitsubishi136FanMax = 0b11;
const uint8_t kMitsubishi136FanQuiet = kMitsubishi136FanMin;
/// Native representation of a Mitsubishi 112-bit A/C message.
union Mitsubishi112Protocol{
uint8_t raw[kMitsubishi112StateLength]; ///< The state in code form.
struct {
// Byte 0~4
uint8_t pad0[5];
// Byte 5
uint8_t :2;
uint8_t Power :1;
uint8_t :5;
// Byte 6
uint8_t Mode :3;
uint8_t :5;
// Byte 7
uint8_t Temp :4;
uint8_t :4;
// Byte 8
uint8_t Fan :3;
uint8_t SwingV :3;
uint8_t :2;
// Byte 9~11
uint8_t pad1[3];
// Byte 12
uint8_t :2;
uint8_t SwingH :4;
uint8_t :2;
// Byte 13
uint8_t Sum :8;
};
};
const uint8_t kMitsubishi112Cool = 0b011;
const uint8_t kMitsubishi112Heat = 0b001;
const uint8_t kMitsubishi112Auto = 0b111;
const uint8_t kMitsubishi112Dry = 0b010;
const uint8_t kMitsubishi112MinTemp = 16; // 16C
const uint8_t kMitsubishi112MaxTemp = 31; // 31C
const uint8_t kMitsubishi112FanMin = 0b010;
const uint8_t kMitsubishi112FanLow = 0b011;
const uint8_t kMitsubishi112FanMed = 0b101;
const uint8_t kMitsubishi112FanMax = 0b000;
const uint8_t kMitsubishi112FanQuiet = kMitsubishi112FanMin;
const uint8_t kMitsubishi112SwingVLowest = 0b101;
const uint8_t kMitsubishi112SwingVLow = 0b100;
const uint8_t kMitsubishi112SwingVMiddle = 0b011;
const uint8_t kMitsubishi112SwingVHigh = 0b010;
const uint8_t kMitsubishi112SwingVHighest = 0b001;
const uint8_t kMitsubishi112SwingVAuto = 0b111;
const uint8_t kMitsubishi112SwingHLeftMax = 0b0001;
const uint8_t kMitsubishi112SwingHLeft = 0b0010;
const uint8_t kMitsubishi112SwingHMiddle = 0b0011;
const uint8_t kMitsubishi112SwingHRight = 0b0100;
const uint8_t kMitsubishi112SwingHRightMax = 0b0101;
const uint8_t kMitsubishi112SwingHWide = 0b1000;
const uint8_t kMitsubishi112SwingHAuto = 0b1100;
// Legacy defines (Deprecated)
#define MITSUBISHI_AC_VANE_AUTO_MOVE kMitsubishiAcVaneAutoMove
#define MITSUBISHI_AC_VANE_AUTO kMitsubishiAcVaneAuto
#define MITSUBISHI_AC_MIN_TEMP kMitsubishiAcMinTemp
#define MITSUBISHI_AC_MAX_TEMP kMitsubishiAcMaxTemp
#define MITSUBISHI_AC_HEAT kMitsubishiAcHeat
#define MITSUBISHI_AC_FAN_SILENT kMitsubishiAcFanSilent
#define MITSUBISHI_AC_FAN_REAL_MAX kMitsubishiAcFanRealMax
#define MITSUBISHI_AC_FAN_MAX kMitsubishiAcFanMax
#define MITSUBISHI_AC_FAN_AUTO kMitsubishiAcFanAuto
#define MITSUBISHI_AC_DRY kMitsubishiAcDry
#define MITSUBISHI_AC_COOL kMitsubishiAcCool
#define MITSUBISHI_AC_AUTO kMitsubishiAcAuto
/// Class for handling detailed Mitsubishi 144-bit A/C messages.
/// @note Inspired and derived from the work done at: https://github.com/r45635/HVAC-IR-Control
/// @warning Consider this very alpha code. Seems to work, but not validated.
class IRMitsubishiAC {
public:
explicit IRMitsubishiAC(const uint16_t pin, const bool inverted = false,
const bool use_modulation = true);
void stateReset(void);
static bool validChecksum(const uint8_t* data);
#if SEND_MITSUBISHI_AC
void send(const uint16_t repeat = kMitsubishiACMinRepeat);
/// 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_MITSUBISHI_AC
void begin(void);
void on(void);
void off(void);
void setPower(const bool on);
bool getPower(void) const;
void setTemp(const float degrees);
float getTemp(void) const;
void setFan(const uint8_t speed);
uint8_t getFan(void) const;
void setMode(const uint8_t mode);
uint8_t getMode(void) const;
void setISave10C(const bool state);
bool getISave10C(void) const;
void setISee(const bool state);
bool getISee(void) const;
void setDirectIndirect(const uint8_t position);
uint8_t getDirectIndirect(void) const;
void setEcocool(const bool state);
bool getEcocool(void) const;
void setAbsenseDetect(const bool state);
bool getAbsenseDetect(void) const;
void setNaturalFlow(const bool state);
bool getNaturalFlow(void) const;
void setVane(const uint8_t position); // controls RIGHT vane on some models
uint8_t getVane(void) const;
void setVaneLeft(const uint8_t position);
uint8_t getVaneLeft(void) const;
void setWideVane(const uint8_t position);
uint8_t getWideVane(void) const;
uint8_t* getRaw(void);
void setRaw(const uint8_t* data);
uint8_t getClock(void) const;
void setClock(const uint8_t clock);
uint8_t getStartClock(void) const;
void setStartClock(const uint8_t clock);
uint8_t getStopClock(void) const;
void setStopClock(const uint8_t clock);
uint8_t getTimer(void) const;
void setTimer(const uint8_t timer);
bool getWeeklyTimerEnabled(void) const;
void setWeeklyTimerEnabled(const bool on);
static uint8_t convertMode(const stdAc::opmode_t mode);
static uint8_t convertFan(const stdAc::fanspeed_t speed);
static uint8_t convertSwingV(const stdAc::swingv_t position);
static uint8_t convertSwingH(const stdAc::swingh_t position);
static stdAc::opmode_t toCommonMode(const uint8_t mode);
static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed);
static stdAc::swingv_t toCommonSwingV(const uint8_t pos);
static stdAc::swingh_t toCommonSwingH(const uint8_t pos);
stdAc::state_t toCommon(void) const;
String toString(void) const;
#ifndef UNIT_TEST
private:
IRsend _irsend; ///< Instance of the IR send class
#else // UNIT_TEST
/// @cond IGNORE
IRsendTest _irsend; ///< Instance of the testing IR send class
/// @endcond
#endif // UNIT_TEST
Mitsubishi144Protocol _;
void checksum(void);
static uint8_t calculateChecksum(const uint8_t* data);
};
/// Class for handling detailed Mitsubishi 136-bit A/C messages.
class IRMitsubishi136 {
public:
explicit IRMitsubishi136(const uint16_t pin, const bool inverted = false,
const bool use_modulation = true);
void stateReset(void);
#if SEND_MITSUBISHI136
void send(const uint16_t repeat = kMitsubishi136MinRepeat);
/// 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_MITSUBISHI136
void begin(void);
static bool validChecksum(const uint8_t* data,
const uint16_t len = kMitsubishi136StateLength);
void on(void);
void off(void);
void setPower(const bool on);
bool getPower(void) const;
void setTemp(const uint8_t degrees);
uint8_t getTemp(void) const;
void setFan(const uint8_t speed);
uint8_t getFan(void) const;
void setMode(const uint8_t mode);
uint8_t getMode(void) const;
void setSwingV(const uint8_t position);
uint8_t getSwingV(void) const;
void setQuiet(const bool on);
bool getQuiet(void) const;
uint8_t* getRaw(void);
void setRaw(const uint8_t* data);
static uint8_t convertMode(const stdAc::opmode_t mode);
static uint8_t convertFan(const stdAc::fanspeed_t speed);
static uint8_t convertSwingV(const stdAc::swingv_t position);
static stdAc::opmode_t toCommonMode(const uint8_t mode);
static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed);
static stdAc::swingv_t toCommonSwingV(const uint8_t pos);
stdAc::state_t toCommon(void) const;
String toString(void) const;
#ifndef UNIT_TEST
private:
IRsend _irsend; ///< Instance of the IR send class
#else // UNIT_TEST
/// @cond IGNORE
IRsendTest _irsend; ///< Instance of the testing IR send class
/// @endcond
#endif // UNIT_TEST
Mitsubishi136Protocol _;
void checksum(void);
};
/// Class for handling detailed Mitsubishi 122-bit A/C messages.
class IRMitsubishi112 {
public:
explicit IRMitsubishi112(const uint16_t pin, const bool inverted = false,
const bool use_modulation = true);
void stateReset(void);
#if SEND_MITSUBISHI112
void send(const uint16_t repeat = kMitsubishi112MinRepeat);
/// 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_MITSUBISHI112
void begin(void);
void on(void);
void off(void);
void setPower(const bool on);
bool getPower(void) const;
void setTemp(const uint8_t degrees);
uint8_t getTemp(void) const;
void setFan(const uint8_t speed);
uint8_t getFan(void) const;
void setMode(const uint8_t mode);
uint8_t getMode(void) const;
void setSwingV(const uint8_t position);
uint8_t getSwingV(void) const;
void setSwingH(const uint8_t position);
uint8_t getSwingH(void) const;
void setQuiet(const bool on);
bool getQuiet(void) const;
uint8_t* getRaw(void);
void setRaw(const uint8_t* data);
static uint8_t convertMode(const stdAc::opmode_t mode);
static uint8_t convertFan(const stdAc::fanspeed_t speed);
static uint8_t convertSwingV(const stdAc::swingv_t position);
static uint8_t convertSwingH(const stdAc::swingh_t position);
static stdAc::opmode_t toCommonMode(const uint8_t mode);
static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed);
static stdAc::swingv_t toCommonSwingV(const uint8_t pos);
static stdAc::swingh_t toCommonSwingH(const uint8_t pos);
stdAc::state_t toCommon(void) const;
String toString(void) const;
#ifndef UNIT_TEST
private:
IRsend _irsend; ///< Instance of the IR send class
#else // UNIT_TEST
/// @cond IGNORE
IRsendTest _irsend; ///< Instance of the testing IR send class
/// @endcond
#endif // UNIT_TEST
Mitsubishi112Protocol _;
void checksum(void);
};
#endif // IR_MITSUBISHI_H_

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,346 @@
// Copyright 2019 David Conran
/// @file
/// @brief Support for Mitsubishi Heavy Industry protocols.
/// Code to emulate Mitsubishi Heavy Industries A/C IR remote control units.
/// @note This code was *heavily* influenced by ToniA's great work & code,
/// but it has been written from scratch.
/// Nothing was copied other than constants and message analysis.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/660
/// @see https://github.com/ToniA/Raw-IR-decoder-for-Arduino/blob/master/MitsubishiHeavy.cpp
/// @see https://github.com/ToniA/arduino-heatpumpir/blob/master/MitsubishiHeavyHeatpumpIR.cpp
// Supports:
// Brand: Mitsubishi Heavy Industries, Model: RLA502A700B remote (152 bit)
// Brand: Mitsubishi Heavy Industries, Model: SRKxxZM-S A/C (152 bit)
// Brand: Mitsubishi Heavy Industries, Model: SRKxxZMXA-S A/C (152 bit)
// Brand: Mitsubishi Heavy Industries, Model: RKX502A001C remote (88 bit)
// Brand: Mitsubishi Heavy Industries, Model: SRKxxZJ-S A/C (88 bit)
#ifndef IR_MITSUBISHIHEAVY_H_
#define IR_MITSUBISHIHEAVY_H_
#ifndef UNIT_TEST
#include <Arduino.h>
#endif
#include "IRremoteESP8266.h"
#include "IRsend.h"
#ifdef UNIT_TEST
#include "IRsend_test.h"
#endif
/// Native representation of a Mitsubishi Heavy 152-bit A/C message.
union Mitsubishi152Protocol{
uint8_t raw[kMitsubishiHeavy152StateLength]; ///< State in code form
struct {
// Byte 0~4
uint8_t Sig[5];
// Byte 5
uint8_t Mode :3;
uint8_t Power :1;
uint8_t :1;
uint8_t Clean :1;
uint8_t Filter:1;
uint8_t :1;
// Byte 6
uint8_t :8;
// Byte 7
uint8_t Temp :4;
uint8_t :4;
// Byte 8
uint8_t :8;
// Byte 9
uint8_t Fan :4;
uint8_t :4;
// Byte 10
uint8_t :8;
// Byte 11
uint8_t :1;
uint8_t Three :1;
uint8_t :2;
uint8_t D :1; // binding with "Three"
uint8_t SwingV :3;
// Byte 12
uint8_t :8;
// Byte 13
uint8_t SwingH :4;
uint8_t :4;
// Byte 14
uint8_t :8;
// Byte 15
uint8_t :6;
uint8_t Night :1;
uint8_t Silent :1;
};
};
// Constants.
const uint8_t kMitsubishiHeavySigLength = 5;
// ZMS (152 bit)
const uint8_t kMitsubishiHeavyZmsSig[kMitsubishiHeavySigLength] = {
0xAD, 0x51, 0x3C, 0xE5, 0x1A};
const uint8_t kMitsubishiHeavyAuto = 0; // 0b000
const uint8_t kMitsubishiHeavyCool = 1; // 0b001
const uint8_t kMitsubishiHeavyDry = 2; // 0b010
const uint8_t kMitsubishiHeavyFan = 3; // 0b011
const uint8_t kMitsubishiHeavyHeat = 4; // 0b100
const uint8_t kMitsubishiHeavyMinTemp = 17; // 17C
const uint8_t kMitsubishiHeavyMaxTemp = 31; // 31C
const uint8_t kMitsubishiHeavy152FanAuto = 0x0; // 0b0000
const uint8_t kMitsubishiHeavy152FanLow = 0x1; // 0b0001
const uint8_t kMitsubishiHeavy152FanMed = 0x2; // 0b0010
const uint8_t kMitsubishiHeavy152FanHigh = 0x3; // 0b0011
const uint8_t kMitsubishiHeavy152FanMax = 0x4; // 0b0100
const uint8_t kMitsubishiHeavy152FanEcono = 0x6; // 0b0110
const uint8_t kMitsubishiHeavy152FanTurbo = 0x8; // 0b1000
const uint8_t kMitsubishiHeavy152SwingVAuto = 0; // 0b000
const uint8_t kMitsubishiHeavy152SwingVHighest = 1; // 0b001
const uint8_t kMitsubishiHeavy152SwingVHigh = 2; // 0b010
const uint8_t kMitsubishiHeavy152SwingVMiddle = 3; // 0b011
const uint8_t kMitsubishiHeavy152SwingVLow = 4; // 0b100
const uint8_t kMitsubishiHeavy152SwingVLowest = 5; // 0b101
const uint8_t kMitsubishiHeavy152SwingVOff = 6; // 0b110
const uint8_t kMitsubishiHeavy152SwingHAuto = 0; // 0b0000
const uint8_t kMitsubishiHeavy152SwingHLeftMax = 1; // 0b0001
const uint8_t kMitsubishiHeavy152SwingHLeft = 2; // 0b0010
const uint8_t kMitsubishiHeavy152SwingHMiddle = 3; // 0b0011
const uint8_t kMitsubishiHeavy152SwingHRight = 4; // 0b0100
const uint8_t kMitsubishiHeavy152SwingHRightMax = 5; // 0b0101
const uint8_t kMitsubishiHeavy152SwingHRightLeft = 6; // 0b0110
const uint8_t kMitsubishiHeavy152SwingHLeftRight = 7; // 0b0111
const uint8_t kMitsubishiHeavy152SwingHOff = 8; // 0b1000
/// Native representation of a Mitsubishi Heavy 88-bit A/C message.
union Mitsubishi88Protocol{
uint8_t raw[kMitsubishiHeavy88StateLength]; ///< State in code form
struct {
// Byte 0~4
uint8_t Sig[5];
// Byte 5
uint8_t :1;
uint8_t SwingV5 :1;
uint8_t SwingH1 :2;
uint8_t :1;
uint8_t Clean :1;
uint8_t SwingH2 :2;
// Byte 6
uint8_t :8;
// Byte 7
uint8_t :3;
uint8_t SwingV7 :2;
uint8_t Fan :3;
// Byte 8
uint8_t :8;
// Byte 9
uint8_t Mode :3;
uint8_t Power :1;
uint8_t Temp :4;
};
};
// ZJS (88 bit)
const uint8_t kMitsubishiHeavyZjsSig[kMitsubishiHeavySigLength] = {
0xAD, 0x51, 0x3C, 0xD9, 0x26};
const uint8_t kMitsubishiHeavy88SwingHSize = 2; // Bits (per offset)
const uint8_t kMitsubishiHeavy88SwingHOff = 0b0000;
const uint8_t kMitsubishiHeavy88SwingHAuto = 0b1000;
const uint8_t kMitsubishiHeavy88SwingHLeftMax = 0b0001;
const uint8_t kMitsubishiHeavy88SwingHLeft = 0b0101;
const uint8_t kMitsubishiHeavy88SwingHMiddle = 0b1001;
const uint8_t kMitsubishiHeavy88SwingHRight = 0b1101;
const uint8_t kMitsubishiHeavy88SwingHRightMax = 0b0010;
const uint8_t kMitsubishiHeavy88SwingHRightLeft = 0b1010;
const uint8_t kMitsubishiHeavy88SwingHLeftRight = 0b0110;
const uint8_t kMitsubishiHeavy88SwingH3D = 0b1110;
const uint8_t kMitsubishiHeavy88FanAuto = 0; // 0b000
const uint8_t kMitsubishiHeavy88FanLow = 2; // 0b010
const uint8_t kMitsubishiHeavy88FanMed = 3; // 0b011
const uint8_t kMitsubishiHeavy88FanHigh = 4; // 0b100
const uint8_t kMitsubishiHeavy88FanTurbo = 6; // 0b110
const uint8_t kMitsubishiHeavy88FanEcono = 7; // 0b111
const uint8_t kMitsubishiHeavy88SwingVByte5Size = 1;
// Mask 0b111
const uint8_t kMitsubishiHeavy88SwingVOff = 0b000; // 0
const uint8_t kMitsubishiHeavy88SwingVAuto = 0b100; // 4
const uint8_t kMitsubishiHeavy88SwingVHighest = 0b110; // 6
const uint8_t kMitsubishiHeavy88SwingVHigh = 0b001; // 1
const uint8_t kMitsubishiHeavy88SwingVMiddle = 0b011; // 3
const uint8_t kMitsubishiHeavy88SwingVLow = 0b101; // 5
const uint8_t kMitsubishiHeavy88SwingVLowest = 0b111; // 7
// Classes
/// Class for handling detailed Mitsubishi Heavy 152-bit A/C messages.
class IRMitsubishiHeavy152Ac {
public:
explicit IRMitsubishiHeavy152Ac(const uint16_t pin,
const bool inverted = false,
const bool use_modulation = true);
void stateReset(void);
#if SEND_MITSUBISHIHEAVY
void send(const uint16_t repeat = kMitsubishiHeavy152MinRepeat);
/// 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_MITSUBISHIHEAVY
void begin(void);
void on(void);
void off(void);
void setPower(const bool on);
bool getPower(void) const;
void setTemp(const uint8_t temp);
uint8_t getTemp(void) const;
void setFan(const uint8_t fan);
uint8_t getFan(void) const;
void setMode(const uint8_t mode);
uint8_t getMode(void) const;
void setSwingVertical(const uint8_t pos);
uint8_t getSwingVertical(void) const;
void setSwingHorizontal(const uint8_t pos);
uint8_t getSwingHorizontal(void) const;
void setNight(const bool on);
bool getNight(void) const;
void set3D(const bool on);
bool get3D(void) const;
void setSilent(const bool on);
bool getSilent(void) const;
void setFilter(const bool on);
bool getFilter(void) const;
void setClean(const bool on);
bool getClean(void) const;
void setTurbo(const bool on);
bool getTurbo(void) const;
void setEcono(const bool on);
bool getEcono(void) const;
uint8_t* getRaw(void);
void setRaw(const uint8_t* data);
static bool checkZmsSig(const uint8_t *state);
static bool validChecksum(
const uint8_t *state,
const uint16_t length = kMitsubishiHeavy152StateLength);
static uint8_t convertMode(const stdAc::opmode_t mode);
static uint8_t convertFan(const stdAc::fanspeed_t speed);
static uint8_t convertSwingV(const stdAc::swingv_t position);
static uint8_t convertSwingH(const stdAc::swingh_t position);
static stdAc::opmode_t toCommonMode(const uint8_t mode);
static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed);
static stdAc::swingv_t toCommonSwingV(const uint8_t pos);
static stdAc::swingh_t toCommonSwingH(const uint8_t pos);
stdAc::state_t toCommon(void) const;
String toString(void) const;
#ifndef UNIT_TEST
private:
IRsend _irsend; ///< Instance of the IR send class
#else // UNIT_TEST
/// @cond IGNORE
IRsendTest _irsend; ///< Instance of the testing IR send class
/// @endcond
#endif // UNIT_TEST
Mitsubishi152Protocol _;
void checksum(void);
};
/// Class for handling detailed Mitsubishi Heavy 88-bit A/C messages.
class IRMitsubishiHeavy88Ac {
public:
explicit IRMitsubishiHeavy88Ac(const uint16_t pin,
const bool inverted = false,
const bool use_modulation = true);
void stateReset(void);
#if SEND_MITSUBISHIHEAVY
void send(const uint16_t repeat = kMitsubishiHeavy88MinRepeat);
/// 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_MITSUBISHIHEAVY
void begin(void);
void on(void);
void off(void);
void setPower(const bool on);
bool getPower(void) const;
void setTemp(const uint8_t temp);
uint8_t getTemp(void) const;
void setFan(const uint8_t fan);
uint8_t getFan(void) const;
void setMode(const uint8_t mode);
uint8_t getMode(void) const;
void setSwingVertical(const uint8_t pos);
uint8_t getSwingVertical(void) const;
void setSwingHorizontal(const uint8_t pos);
uint8_t getSwingHorizontal(void) const;
void setTurbo(const bool on);
bool getTurbo(void) const;
void setEcono(const bool on);
bool getEcono(void) const;
void set3D(const bool on);
bool get3D(void) const;
void setClean(const bool on);
bool getClean(void) const;
uint8_t* getRaw(void);
void setRaw(const uint8_t* data);
static bool checkZjsSig(const uint8_t *state);
static bool validChecksum(
const uint8_t *state,
const uint16_t length = kMitsubishiHeavy88StateLength);
static uint8_t convertMode(const stdAc::opmode_t mode);
static uint8_t convertFan(const stdAc::fanspeed_t speed);
static uint8_t convertSwingV(const stdAc::swingv_t position);
static uint8_t convertSwingH(const stdAc::swingh_t position);
static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed);
static stdAc::swingv_t toCommonSwingV(const uint8_t pos);
static stdAc::swingh_t toCommonSwingH(const uint8_t pos);
stdAc::state_t toCommon(void) const;
String toString(void) const;
#ifndef UNIT_TEST
private:
IRsend _irsend; ///< Instance of the IR send class
#else // UNIT_TEST
/// @cond IGNORE
IRsendTest _irsend; ///< Instance of the testing IR send class
/// @endcond
#endif // UNIT_TEST
Mitsubishi88Protocol _;
void checksum(void);
};
#endif // IR_MITSUBISHIHEAVY_H_

View File

@@ -0,0 +1,115 @@
// Copyright 2020 David Conran
/// @file
/// @brief Support for Multibrackets protocols.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1103
/// @see http://info.multibrackets.com/data/common/manuals/4500_code.pdf
// Supports:
// Brand: Multibrackets, Model: Motorized Swing mount large - 4500
#include "IRrecv.h"
#include "IRsend.h"
const uint16_t kMultibracketsTick = 5000; // uSeconds
const uint16_t kMultibracketsHdrMark = 3 * kMultibracketsTick; // uSeconds
const uint16_t kMultibracketsFooterSpace = 6 * kMultibracketsTick; // uSeconds
const uint8_t kMultibracketsTolerance = 5; // Percent
const uint16_t kMultibracketsFreq = 38000; // Hertz
#if SEND_MULTIBRACKETS
/// Send a Multibrackets formatted message.
/// Status: BETA / Appears to be working.
/// @param[in] data The message to be sent.
/// @param[in] nbits The number of bits of message to be sent.
/// @param[in] repeat The number of times the command is to be repeated.
void IRsend::sendMultibrackets(uint64_t data, uint16_t nbits, uint16_t repeat) {
enableIROut(kMultibracketsFreq);
for (uint16_t r = 0; r <= repeat; r++) {
uint16_t bits = nbits;
// Header
mark(kMultibracketsHdrMark);
// Data
// Send 0's until we get down to a bit size we can actually manage.
while (bits > sizeof(data) * 8) {
space(kMultibracketsTick);
bits--;
}
// Send the supplied data.
for (uint64_t mask = 1ULL << (bits - 1); mask; mask >>= 1)
if (data & mask) // Send a 1
mark(kMultibracketsTick);
else // Send a 0
space(kMultibracketsTick);
// Footer
space(kMultibracketsFooterSpace);
}
}
#endif // SEND_MULTIBRACKETS
#if DECODE_MULTIBRACKETS
/// Decode the Multibrackets message.
/// Status: BETA / Appears to be working.
/// @param[in,out] results Ptr to the data to decode & where to store the result
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// @param[in] strict Flag indicating if we should perform strict matching.
/// @return True if it can decode it, false if it can't.
bool IRrecv::decodeMultibrackets(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
// Compliance
if (strict && nbits != kMultibracketsBits)
return false; // Doesn't match our protocol defn.
// Check there is enough unprocessed buffer left.
if (results->rawlen < offset) return false;
// Header
int32_t remaining = *(results->rawbuf + offset);
if (!matchAtLeast(remaining, kMultibracketsHdrMark, kMultibracketsTolerance))
return false;
remaining -= (kMultibracketsHdrMark / kRawTick); // Remove the header.
// We are done with the header. Onto the data.
bool bit = true;
uint16_t bitsSoFar = 0;
uint64_t data = 0;
// Keep going till we run out of message or expected bits.
while (offset <= results->rawlen && bitsSoFar < nbits) {
// Have we finished processing this rawbuf value yet?
if (remaining <= 0) { // No more possible "bits" left in this value.
// Invert the bit for next time, and move along the rawbuf.
bit = !bit;
offset++;
// Load the next data point if there is one.
if (offset <= results->rawlen) remaining = *(results->rawbuf + offset);
} else { // Look for more bits in this entry.
if (matchAtLeast(remaining, kMultibracketsTick,
kMultibracketsTolerance)) { // There is!
data <<= 1;
data += bit;
bitsSoFar++;
}
remaining -= (kMultibracketsTick / kRawTick); // Remove the "bit".
}
}
// Compliance
if (bitsSoFar != nbits) return false;
// Footer
if (results->rawlen <= offset && !matchAtLeast(*(results->rawbuf + offset),
kMultibracketsFooterSpace,
kMultibracketsTolerance))
return false;
// Success
results->decode_type = decode_type_t::MULTIBRACKETS;
results->value = data;
results->bits = nbits;
results->address = 0;
results->command = 0;
return true;
}
#endif // DECODE_MULTIBRACKETS

View File

@@ -0,0 +1,140 @@
// Copyright 2009 Ken Shirriff
// Copyright 2017 David Conran
/// @file
/// @brief Support for NEC (Renesas) protocols.
/// NEC originally added from https://github.com/shirriff/Arduino-IRremote/
/// @see http://www.sbprojects.net/knowledge/ir/nec.php
#define __STDC_LIMIT_MACROS
#include "ir_NEC.h"
#include <stdint.h>
#include <algorithm>
#include "IRrecv.h"
#include "IRsend.h"
#include "IRutils.h"
// This protocol is used by a lot of other protocols, hence the long list.
#if (SEND_NEC || SEND_SHERWOOD || SEND_AIWA_RC_T501 || SEND_SANYO || \
SEND_MIDEA24)
/// Send a raw NEC(Renesas) formatted message.
/// Status: STABLE / Known working.
/// @param[in] data The message to be sent.
/// @param[in] nbits The number of bits of message to be sent.
/// @param[in] repeat The number of times the command is to be repeated.
/// @note This protocol appears to have no header.
/// @see http://www.sbprojects.net/knowledge/ir/nec.php
void IRsend::sendNEC(uint64_t data, uint16_t nbits, uint16_t repeat) {
sendGeneric(kNecHdrMark, kNecHdrSpace, kNecBitMark, kNecOneSpace, kNecBitMark,
kNecZeroSpace, kNecBitMark, kNecMinGap, kNecMinCommandLength,
data, nbits, 38, true, 0, // Repeats are handled later.
33);
// Optional command repeat sequence.
if (repeat)
sendGeneric(kNecHdrMark, kNecRptSpace, 0, 0, 0, 0, // No actual data sent.
kNecBitMark, kNecMinGap, kNecMinCommandLength, 0,
0, // No data to be sent.
38, true, repeat - 1, // We've already sent a one message.
33);
}
/// Calculate the raw NEC data based on address and command.
/// Status: STABLE / Expected to work.
/// @param[in] address An address value.
/// @param[in] command An 8-bit command value.
/// @return A raw 32-bit NEC message suitable for use with `sendNEC()`.
/// @see http://www.sbprojects.net/knowledge/ir/nec.php
uint32_t IRsend::encodeNEC(uint16_t address, uint16_t command) {
command &= 0xFF; // We only want the least significant byte of command.
// sendNEC() sends MSB first, but protocol says this is LSB first.
command = reverseBits(command, 8);
command = (command << 8) + (command ^ 0xFF); // Calculate the new command.
if (address > 0xFF) { // Is it Extended NEC?
address = reverseBits(address, 16);
return ((address << 16) + command); // Extended.
} else {
address = reverseBits(address, 8);
return (address << 24) + ((address ^ 0xFF) << 16) + command; // Normal.
}
}
#endif // (SEND_NEC || SEND_SHERWOOD || SEND_AIWA_RC_T501 || SEND_SANYO ||
// SEND_MIDEA24)
// This protocol is used by a lot of other protocols, hence the long list.
#if (DECODE_NEC || DECODE_SHERWOOD || DECODE_AIWA_RC_T501 || DECODE_SANYO)
/// Decode the supplied NEC (Renesas) message.
/// Status: STABLE / Known good.
/// @param[in,out] results Ptr to the data to decode & where to store the result
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// @param[in] strict Flag indicating if we should perform strict matching.
/// @return True if it can decode it, false if it can't.
/// @note NEC protocol has three variants/forms.
/// Normal: an 8 bit address & an 8 bit command in 32 bit data form.
/// i.e. address + inverted(address) + command + inverted(command)
/// Extended: a 16 bit address & an 8 bit command in 32 bit data form.
/// i.e. address + command + inverted(command)
/// Repeat: a 0-bit code. i.e. No data bits. Just the header + footer.
/// @see http://www.sbprojects.net/knowledge/ir/nec.php
bool IRrecv::decodeNEC(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
if (results->rawlen < kNecRptLength + offset - 1)
return false; // Can't possibly be a valid NEC message.
if (strict && nbits != kNECBits)
return false; // Not strictly an NEC message.
uint64_t data = 0;
// Header - All NEC messages have this Header Mark.
if (!matchMark(results->rawbuf[offset++], kNecHdrMark)) return false;
// Check if it is a repeat code.
if (matchSpace(results->rawbuf[offset], kNecRptSpace) &&
matchMark(results->rawbuf[offset + 1], kNecBitMark) &&
(offset + 2 <= results->rawlen ||
matchAtLeast(results->rawbuf[offset + 2], kNecMinGap))) {
results->value = kRepeat;
results->decode_type = NEC;
results->bits = 0;
results->address = 0;
results->command = 0;
results->repeat = true;
return true;
}
// Match Header (cont.) + Data + Footer
if (!matchGeneric(results->rawbuf + offset, &data,
results->rawlen - offset, nbits,
0, kNecHdrSpace,
kNecBitMark, kNecOneSpace,
kNecBitMark, kNecZeroSpace,
kNecBitMark, kNecMinGap, true)) return false;
// Compliance
// Calculate command and optionally enforce integrity checking.
uint8_t command = (data & 0xFF00) >> 8;
// Command is sent twice, once as plain and then inverted.
if ((command ^ 0xFF) != (data & 0xFF)) {
if (strict) return false; // Command integrity failed.
command = 0; // The command value isn't valid, so default to zero.
}
// Success
results->bits = nbits;
results->value = data;
results->decode_type = NEC;
// NEC command and address are technically in LSB first order so the
// final versions have to be reversed.
results->command = reverseBits(command, 8);
// Normal NEC protocol has an 8 bit address sent, followed by it inverted.
uint8_t address = (data & 0xFF000000) >> 24;
uint8_t address_inverted = (data & 0x00FF0000) >> 16;
if (address == (address_inverted ^ 0xFF))
// Inverted, so it is normal NEC protocol.
results->address = reverseBits(address, 8);
else // Not inverted, so must be Extended NEC protocol, thus 16 bit address.
results->address = reverseBits((data >> 16) & UINT16_MAX, 16);
return true;
}
#endif // (DECODE_NEC || DECODE_SHERWOOD || DECODE_AIWA_RC_T501 ||
// DECODE_SANYO)

View File

@@ -0,0 +1,80 @@
// Copyright 2009 Ken Shirriff
// Copyright 2017, 2018 David Conran
/// @file
/// @brief Support for NEC (Renesas) protocols.
/// NEC originally added from https://github.com/shirriff/Arduino-IRremote/
/// @see http://www.sbprojects.net/knowledge/ir/nec.php
// Supports:
// Brand: Yamaha, Model: RAV561 remote
// Brand: Yamaha, Model: RXV585B A/V Receiver
// Brand: Aloka, Model: SleepyLights LED Lamp
// Brand: Toshiba, Model: 42TL838 LCD TV
// Brand: Duux, Model: Blizzard Smart 10K / DXMA04 A/C
// Brand: Duux, Model: YJ-A081 TR Remote
// Brand: Silan Microelectronics, Model: SC6121-001 IC
// Brand: BBK, Model: SP550S 5.1 sound system
// Brand: Tanix, Model: TX3 mini Android TV Box
#ifndef IR_NEC_H_
#define IR_NEC_H_
#include <stdint.h>
#include "IRremoteESP8266.h"
// Constants
const uint16_t kNecTick = 560;
const uint16_t kNecHdrMarkTicks = 16;
const uint16_t kNecHdrMark = kNecHdrMarkTicks * kNecTick;
const uint16_t kNecHdrSpaceTicks = 8;
const uint16_t kNecHdrSpace = kNecHdrSpaceTicks * kNecTick;
const uint16_t kNecBitMarkTicks = 1;
const uint16_t kNecBitMark = kNecBitMarkTicks * kNecTick;
const uint16_t kNecOneSpaceTicks = 3;
const uint16_t kNecOneSpace = kNecOneSpaceTicks * kNecTick;
const uint16_t kNecZeroSpaceTicks = 1;
const uint16_t kNecZeroSpace = kNecZeroSpaceTicks * kNecTick;
const uint16_t kNecRptSpaceTicks = 4;
const uint16_t kNecRptSpace = kNecRptSpaceTicks * kNecTick;
const uint16_t kNecRptLength = 4;
const uint16_t kNecMinCommandLengthTicks = 193;
const uint32_t kNecMinCommandLength = kNecMinCommandLengthTicks * kNecTick;
const uint32_t kNecMinGap =
kNecMinCommandLength -
(kNecHdrMark + kNecHdrSpace + kNECBits * (kNecBitMark + kNecOneSpace) +
kNecBitMark);
const uint16_t kNecMinGapTicks =
kNecMinCommandLengthTicks -
(kNecHdrMarkTicks + kNecHdrSpaceTicks +
kNECBits * (kNecBitMarkTicks + kNecOneSpaceTicks) + kNecBitMarkTicks);
// IR codes and structure for kids ALOKA SleepyLights LED Lamp.
// https://aloka-designs.com/
// Ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/1004
//
// May be useful for someone wanting to control the lamp.
//
// The lamp is toggled On and Off with the same power button.
// The colour, when selected, is the brightest and there are 4 levels of
// brightness that decrease on each send of the colour. A fifth send of the
// colour resets to brightest again.
//
// Remote buttons defined left to right, top line to bottom line on the remote.
const uint32_t kAlokaPower = 0xFF609F;
const uint32_t kAlokaLedWhite = 0xFF906F;
const uint32_t kAlokaLedGreen = 0xFF9867;
const uint32_t kAlokaLedBlue = 0xFFD827;
const uint32_t kAlokaLedPinkRed = 0xFF8877;
const uint32_t kAlokaLedRed = 0xFFA857;
const uint32_t kAlokaLedLightGreen = 0xFFE817;
const uint32_t kAlokaLedMidBlue = 0xFF48B7;
const uint32_t kAlokaLedPink = 0xFF6897;
const uint32_t kAlokaLedOrange = 0xFFB24D;
const uint32_t kAlokaLedYellow = 0xFF00FF;
const uint32_t kAlokaNightFade = 0xFF50AF;
const uint32_t kAlokaNightTimer = 0xFF7887;
const uint32_t kAlokaLedRainbow = 0xFF708F;
// Didn't have a better description for it...
const uint32_t kAlokaLedTreeGrow = 0xFF58A7;
#endif // IR_NEC_H_

View File

@@ -0,0 +1,608 @@
// Copyright 2019-2020 David Conran
/// @file
/// @brief Support for Neoclima protocols.
/// Analysis by crankyoldgit, AndreyShpilevoy, & griffisc306
/// Code by crankyoldgit
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/764
/// @see https://drive.google.com/file/d/1kjYk4zS9NQcMQhFkak-L4mp4UuaAIesW/view
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1260
#include "ir_Neoclima.h"
#include <algorithm>
#include <cstring>
#include "IRrecv.h"
#include "IRsend.h"
#include "IRtext.h"
#include "IRutils.h"
// Constants
const uint16_t kNeoclimaHdrMark = 6112;
const uint16_t kNeoclimaHdrSpace = 7391;
const uint16_t kNeoclimaBitMark = 537;
const uint16_t kNeoclimaOneSpace = 1651;
const uint16_t kNeoclimaZeroSpace = 571;
const uint32_t kNeoclimaMinGap = kDefaultMessageGap;
using irutils::addBoolToString;
using irutils::addFanToString;
using irutils::addIntToString;
using irutils::addLabeledString;
using irutils::addModeToString;
using irutils::addTempToString;
#if SEND_NEOCLIMA
/// Send a Neoclima message.
/// Status: STABLE / Known to be working.
/// @param[in] data The message to be sent.
/// @param[in] nbytes The number of bytes of message to be sent.
/// @param[in] repeat The number of times the command is to be repeated.
void IRsend::sendNeoclima(const unsigned char data[], const uint16_t nbytes,
const uint16_t repeat) {
// Set IR carrier frequency
enableIROut(38);
for (uint16_t i = 0; i <= repeat; i++) {
sendGeneric(kNeoclimaHdrMark, kNeoclimaHdrSpace,
kNeoclimaBitMark, kNeoclimaOneSpace,
kNeoclimaBitMark, kNeoclimaZeroSpace,
kNeoclimaBitMark, kNeoclimaHdrSpace,
data, nbytes, 38000, false, 0, // Repeats are already handled.
50);
// Extra footer.
mark(kNeoclimaBitMark);
space(kNeoclimaMinGap);
}
}
#endif // SEND_NEOCLIMA
/// 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?
IRNeoclimaAc::IRNeoclimaAc(const uint16_t pin, const bool inverted,
const bool use_modulation)
: _irsend(pin, inverted, use_modulation) {
stateReset();
}
/// Reset the state of the remote to a known good state/sequence.
void IRNeoclimaAc::stateReset(void) {
static const uint8_t kReset[kNeoclimaStateLength] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, 0x00, 0x2A, 0xA5};
setRaw(kReset);
}
/// Set up hardware to be able to send a message.
void IRNeoclimaAc::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.
/// @return The calculated checksum value.
uint8_t IRNeoclimaAc::calcChecksum(const uint8_t state[],
const uint16_t length) {
if (length == 0) return state[0];
return sumBytes(state, length - 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/size of the array.
/// @return true, if the state has a valid checksum. Otherwise, false.
bool IRNeoclimaAc::validChecksum(const uint8_t state[], const uint16_t length) {
if (length < 2)
return true; // No checksum to compare with. Assume okay.
return (state[length - 1] == calcChecksum(state, length));
}
/// Calculate & update the checksum for the internal state.
/// @param[in] length The length/size of the internal state.
void IRNeoclimaAc::checksum(uint16_t length) {
if (length < 2) return;
_.Sum = calcChecksum(_.raw, length);
}
#if SEND_NEOCLIMA
/// Send the current internal state as an IR message.
/// @param[in] repeat Nr. of times the message will be repeated.
void IRNeoclimaAc::send(const uint16_t repeat) {
_irsend.sendNeoclima(getRaw(), kNeoclimaStateLength, repeat);
}
#endif // SEND_NEOCLIMA
/// 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 *IRNeoclimaAc::getRaw(void) {
checksum();
return _.raw;
}
/// Set the internal state from a valid code for this protocol.
/// @param[in] new_code A valid code for this protocol.
/// @param[in] length The length/size of the new_code array.
void IRNeoclimaAc::setRaw(const uint8_t new_code[], const uint16_t length) {
std::memcpy(_.raw, new_code, std::min(length, kNeoclimaStateLength));
}
/// Set the Button/Command pressed setting of the A/C.
/// @param[in] button The value of the button/command that was pressed.
void IRNeoclimaAc::setButton(const uint8_t button) {
switch (button) {
case kNeoclimaButtonPower:
case kNeoclimaButtonMode:
case kNeoclimaButtonTempUp:
case kNeoclimaButtonTempDown:
case kNeoclimaButtonSwing:
case kNeoclimaButtonFanSpeed:
case kNeoclimaButtonAirFlow:
case kNeoclimaButtonHold:
case kNeoclimaButtonSleep:
case kNeoclimaButtonLight:
case kNeoclimaButtonEye:
case kNeoclimaButtonFollow:
case kNeoclimaButtonIon:
case kNeoclimaButtonFresh:
case kNeoclimaButton8CHeat:
case kNeoclimaButtonTurbo:
case kNeoclimaButtonEcono:
case kNeoclimaButtonTempUnit:
_.Button = button;
break;
default:
_.Button = kNeoclimaButtonPower;
}
}
/// Get the Button/Command setting of the A/C.
/// @return The value of the button/command that was pressed.
uint8_t IRNeoclimaAc::getButton(void) const {
return _.Button;
}
/// Set the requested power state of the A/C to on.
void IRNeoclimaAc::on(void) { setPower(true); }
/// Set the requested power state of the A/C to off.
void IRNeoclimaAc::off(void) { setPower(false); }
/// Change the power setting.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRNeoclimaAc::setPower(const bool on) {
_.Button = kNeoclimaButtonPower;
_.Power = on;
}
/// Get the value of the current power setting.
/// @return true, the setting is on. false, the setting is off.
bool IRNeoclimaAc::getPower(void) const {
return _.Power;
}
/// Set the operating mode of the A/C.
/// @param[in] mode The desired operating mode.
void IRNeoclimaAc::setMode(const uint8_t mode) {
switch (mode) {
case kNeoclimaDry:
// In this mode fan speed always LOW
setFan(kNeoclimaFanLow);
// FALL THRU
case kNeoclimaAuto:
case kNeoclimaCool:
case kNeoclimaFan:
case kNeoclimaHeat:
_.Mode = mode;
_.Button = kNeoclimaButtonMode;
break;
default:
// If we get an unexpected mode, default to AUTO.
_.Mode = kNeoclimaAuto;
_.Button = kNeoclimaButtonMode;
}
}
/// Get the operating mode setting of the A/C.
/// @return The current operating mode setting.
uint8_t IRNeoclimaAc::getMode(void) const {
return _.Mode;
}
/// Convert a stdAc::opmode_t enum into its native mode.
/// @param[in] mode The enum to be converted.
/// @return The native equivalent of the enum.
uint8_t IRNeoclimaAc::convertMode(const stdAc::opmode_t mode) {
switch (mode) {
case stdAc::opmode_t::kCool: return kNeoclimaCool;
case stdAc::opmode_t::kHeat: return kNeoclimaHeat;
case stdAc::opmode_t::kDry: return kNeoclimaDry;
case stdAc::opmode_t::kFan: return kNeoclimaFan;
default: return kNeoclimaAuto;
}
}
/// Convert a native mode into its stdAc equivalent.
/// @param[in] mode The native setting to be converted.
/// @return The stdAc equivalent of the native setting.
stdAc::opmode_t IRNeoclimaAc::toCommonMode(const uint8_t mode) {
switch (mode) {
case kNeoclimaCool: return stdAc::opmode_t::kCool;
case kNeoclimaHeat: return stdAc::opmode_t::kHeat;
case kNeoclimaDry: return stdAc::opmode_t::kDry;
case kNeoclimaFan: return stdAc::opmode_t::kFan;
default: return stdAc::opmode_t::kAuto;
}
}
/// Set the temperature.
/// @param[in] temp The temperature in degrees celsius.
/// @param[in] celsius Use Fahrenheit (false) or Celsius (true).
void IRNeoclimaAc::setTemp(const uint8_t temp, const bool celsius) {
uint8_t oldtemp = getTemp();
_.UseFah = !celsius;
const uint8_t min_temp = celsius ? kNeoclimaMinTempC : kNeoclimaMinTempF;
const uint8_t max_temp = celsius ? kNeoclimaMaxTempC : kNeoclimaMaxTempF;
const uint8_t newtemp = std::min(max_temp, std::max(min_temp, temp));
if (oldtemp > newtemp)
_.Button = kNeoclimaButtonTempDown;
else if (newtemp > oldtemp)
_.Button = kNeoclimaButtonTempUp;
_.Temp = newtemp - min_temp;
}
/// Get the current temperature setting.
/// @return The current setting for temp. in degrees.
/// @note The units of the temperature (F/C) is determined by `getTempUnits()`.
uint8_t IRNeoclimaAc::getTemp(void) const {
const uint8_t min_temp = getTempUnits() ? kNeoclimaMinTempC
: kNeoclimaMinTempF;
return _.Temp + min_temp;
}
/// Set the speed of the fan.
/// @param[in] speed The desired setting. 0-3, 0 is auto, 1-3 is the speed
void IRNeoclimaAc::setFan(const uint8_t speed) {
_.Button = kNeoclimaButtonFanSpeed;
if (_.Mode == kNeoclimaDry) { // Dry mode only allows low speed.
_.Fan = kNeoclimaFanLow;
return;
}
switch (speed) {
case kNeoclimaFanAuto:
case kNeoclimaFanHigh:
case kNeoclimaFanMed:
case kNeoclimaFanLow:
_.Fan = speed;
break;
default:
// If we get an unexpected speed, default to Auto.
_.Fan = kNeoclimaFanAuto;
}
}
/// Get the current fan speed setting.
/// @return The current fan speed/mode.
uint8_t IRNeoclimaAc::getFan(void) const {
return _.Fan;
}
/// Convert a stdAc::fanspeed_t enum into it's native speed.
/// @param[in] speed The enum to be converted.
/// @return The native equivalent of the enum.
uint8_t IRNeoclimaAc::convertFan(const stdAc::fanspeed_t speed) {
switch (speed) {
case stdAc::fanspeed_t::kMin:
case stdAc::fanspeed_t::kLow: return kNeoclimaFanLow;
case stdAc::fanspeed_t::kMedium: return kNeoclimaFanMed;
case stdAc::fanspeed_t::kHigh:
case stdAc::fanspeed_t::kMax: return kNeoclimaFanHigh;
default: return kNeoclimaFanAuto;
}
}
/// Convert a native fan speed into its stdAc equivalent.
/// @param[in] speed The native setting to be converted.
/// @return The stdAc equivalent of the native setting.
stdAc::fanspeed_t IRNeoclimaAc::toCommonFanSpeed(const uint8_t speed) {
switch (speed) {
case kNeoclimaFanHigh: return stdAc::fanspeed_t::kMax;
case kNeoclimaFanMed: return stdAc::fanspeed_t::kMedium;
case kNeoclimaFanLow: return stdAc::fanspeed_t::kMin;
default: return stdAc::fanspeed_t::kAuto;
}
}
/// Set the Sleep setting of the A/C.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRNeoclimaAc::setSleep(const bool on) {
_.Button = kNeoclimaButtonSleep;
_.Sleep = on;
}
/// Get the Sleep setting of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRNeoclimaAc::getSleep(void) const {
return _.Sleep;
}
/// Set the vertical swing setting of the A/C.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRNeoclimaAc::setSwingV(const bool on) {
_.Button = kNeoclimaButtonSwing;
_.SwingV = (on ? kNeoclimaSwingVOn : kNeoclimaSwingVOff);
}
/// Get the vertical swing setting of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRNeoclimaAc::getSwingV(void) const {
return _.SwingV == kNeoclimaSwingVOn;
}
/// Set the horizontal swing setting of the A/C.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRNeoclimaAc::setSwingH(const bool on) {
_.Button = kNeoclimaButtonAirFlow;
_.SwingH = !on; // Cleared when `on`
}
/// Get the horizontal swing (Air Flow) setting of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRNeoclimaAc::getSwingH(void) const {
return !_.SwingH;
}
/// Set the Turbo setting of the A/C.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRNeoclimaAc::setTurbo(const bool on) {
_.Button = kNeoclimaButtonTurbo;
_.Turbo = on;
}
/// Get the Turbo setting of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRNeoclimaAc::getTurbo(void) const {
return _.Turbo;
}
/// Set the Economy (Energy Saver) setting of the A/C.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRNeoclimaAc::setEcono(const bool on) {
_.Button = kNeoclimaButtonEcono;
_.Econo = on;
}
/// Get the Economy (Energy Saver) setting of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRNeoclimaAc::getEcono(void) const {
return _.Econo;
}
/// Set the Fresh (air) setting of the A/C.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRNeoclimaAc::setFresh(const bool on) {
_.Button = kNeoclimaButtonFresh;
_.Fresh = on;
}
/// Get the Fresh (air) setting of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRNeoclimaAc::getFresh(void) const {
return _.Fresh;
}
/// Set the Hold setting of the A/C.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRNeoclimaAc::setHold(const bool on) {
_.Button = kNeoclimaButtonHold;
_.Hold = on;
}
/// Get the Hold setting of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRNeoclimaAc::getHold(void) const {
return _.Hold;
}
/// Set the Ion (filter) setting of the A/C.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRNeoclimaAc::setIon(const bool on) {
_.Button = kNeoclimaButtonIon;
_.Ion = on;
}
/// Get the Ion (filter) setting of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRNeoclimaAc::getIon(void) const {
return _.Ion;
}
/// Set the Light(LED display) setting of the A/C.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRNeoclimaAc::setLight(const bool on) {
_.Button = kNeoclimaButtonLight;
_.Light = on;
}
/// Get the Light (LED display) setting of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRNeoclimaAc::getLight(void) const {
return _.Light;
}
/// Set the 8°C Heat setting of the A/C.
/// @param[in] on true, the setting is on. false, the setting is off.
/// @note This feature maintains the room temperature steadily at 8°C and
/// prevents the room from freezing by activating the heating operation
/// automatically when nobody is at home over a longer period during severe
/// winter.
void IRNeoclimaAc::set8CHeat(const bool on) {
_.Button = kNeoclimaButton8CHeat;
_.CHeat = on;
}
/// Get the 8°C Heat setting of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRNeoclimaAc::get8CHeat(void) const {
return _.CHeat;
}
/// Set the Eye (Sensor) setting of the A/C.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRNeoclimaAc::setEye(const bool on) {
_.Button = kNeoclimaButtonEye;
_.Eye = on;
}
/// Get the Eye (Sensor) setting of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRNeoclimaAc::getEye(void) const {
return _.Eye;
}
/// Is the A/C unit using Fahrenheit or Celsius for temperature units.
/// @return false, Fahrenheit. true, Celsius.
bool IRNeoclimaAc::getTempUnits(void) const {
return !_.UseFah;
}
/* DISABLED
TODO(someone): Work out why "on" is either 0x5D or 0x5F
void IRNeoclimaAc::setFollow(const bool on) {
setButton(kNeoclimaButtonFollow);
if (on)
remote_state[8] = kNeoclimaFollowMe;
else
remote_state[8] = 0;
}
*/
/// Get the Follow Me setting of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRNeoclimaAc::getFollow(void) const {
return (_.Follow & kNeoclimaFollowMe) == kNeoclimaFollowMe;
}
/// Convert the current internal state into its stdAc::state_t equivalent.
/// @return The stdAc equivalent of the native settings.
stdAc::state_t IRNeoclimaAc::toCommon(void) const {
stdAc::state_t result{};
result.protocol = decode_type_t::NEOCLIMA;
result.model = -1; // No models used.
result.power = _.Power;
result.mode = toCommonMode(_.Mode);
result.celsius = getTempUnits();
result.degrees = getTemp();
result.fanspeed = toCommonFanSpeed(_.Fan);
result.swingv = getSwingV() ? stdAc::swingv_t::kAuto
: stdAc::swingv_t::kOff;
result.swingh = getSwingH() ? stdAc::swingh_t::kAuto
: stdAc::swingh_t::kOff;
result.turbo = _.Turbo;
result.econo = _.Econo;
result.light = _.Light;
result.filter = _.Ion;
result.sleep = _.Sleep ? 0 : -1;
// Not supported.
result.quiet = false;
result.clean = 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 IRNeoclimaAc::toString(void) const {
String result = "";
result.reserve(110); // Reserve some heap for the string to reduce fragging.
result += addBoolToString(_.Power, kPowerStr, false);
result += addModeToString(_.Mode, kNeoclimaAuto, kNeoclimaCool,
kNeoclimaHeat, kNeoclimaDry, kNeoclimaFan);
result += addTempToString(getTemp(), getTempUnits());
result += addFanToString(_.Fan, kNeoclimaFanHigh, kNeoclimaFanLow,
kNeoclimaFanAuto, kNeoclimaFanAuto, kNeoclimaFanMed);
result += addBoolToString(getSwingV(), kSwingVStr);
result += addBoolToString(getSwingH(), kSwingHStr);
result += addBoolToString(_.Sleep, kSleepStr);
result += addBoolToString(_.Turbo, kTurboStr);
result += addBoolToString(_.Econo, kEconoStr);
result += addBoolToString(_.Hold, kHoldStr);
result += addBoolToString(_.Ion, kIonStr);
result += addBoolToString(_.Eye, kEyeStr);
result += addBoolToString(_.Light, kLightStr);
result += addBoolToString(getFollow(), kFollowStr);
result += addBoolToString(_.CHeat, k8CHeatStr);
result += addBoolToString(_.Fresh, kFreshStr);
result += addIntToString(_.Button, kButtonStr);
result += kSpaceLBraceStr;
switch (_.Button) {
case kNeoclimaButtonPower: result += kPowerStr; break;
case kNeoclimaButtonMode: result += kModeStr; break;
case kNeoclimaButtonTempUp: result += kTempUpStr; break;
case kNeoclimaButtonTempDown: result += kTempDownStr; break;
case kNeoclimaButtonSwing: result += kSwingStr; break;
case kNeoclimaButtonFanSpeed: result += kFanStr; break;
case kNeoclimaButtonAirFlow: result += kAirFlowStr; break;
case kNeoclimaButtonHold: result += kHoldStr; break;
case kNeoclimaButtonSleep: result += kSleepStr; break;
case kNeoclimaButtonLight: result += kLightStr; break;
case kNeoclimaButtonEye: result += kEyeStr; break;
case kNeoclimaButtonFollow: result += kFollowStr; break;
case kNeoclimaButtonIon: result += kIonStr; break;
case kNeoclimaButtonFresh: result += kFreshStr; break;
case kNeoclimaButton8CHeat: result += k8CHeatStr; break;
case kNeoclimaButtonTurbo: result += kTurboStr; break;
case kNeoclimaButtonEcono: result += kEconoStr; break;
case kNeoclimaButtonTempUnit: result += kCelsiusFahrenheitStr; break;
default: result += kUnknownStr;
}
result += ')';
return result;
}
#if DECODE_NEOCLIMA
/// Decode the supplied Neoclima message.
/// Status: STABLE / Known working
/// @param[in,out] results Ptr to the data to decode & where to store the result
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// @param[in] strict Flag indicating if we should perform strict matching.
/// @return True if it can decode it, false if it can't.
bool IRrecv::decodeNeoclima(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
// Compliance
if (strict && nbits != kNeoclimaBits)
return false; // Incorrect nr. of bits per spec.
// Match Main Header + Data + Footer
uint16_t used;
used = matchGeneric(results->rawbuf + offset, results->state,
results->rawlen - offset, nbits,
kNeoclimaHdrMark, kNeoclimaHdrSpace,
kNeoclimaBitMark, kNeoclimaOneSpace,
kNeoclimaBitMark, kNeoclimaZeroSpace,
kNeoclimaBitMark, kNeoclimaHdrSpace, false,
_tolerance, 0, false);
if (!used) return false;
offset += used;
// Extra footer.
uint64_t unused;
if (!matchGeneric(results->rawbuf + offset, &unused,
results->rawlen - offset, 0, 0, 0, 0, 0, 0, 0,
kNeoclimaBitMark, kNeoclimaHdrSpace, true)) return false;
// Compliance
if (strict) {
// Check we got a valid checksum.
if (!IRNeoclimaAc::validChecksum(results->state, nbits / 8)) return false;
}
// Success
results->decode_type = decode_type_t::NEOCLIMA;
results->bits = nbits;
// 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_NEOCLIMA

View File

@@ -0,0 +1,198 @@
// Copyright 2019 David Conran
/// @file
/// @brief Support for Neoclima protocols.
/// Analysis by crankyoldgit & AndreyShpilevoy
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/764
/// @see https://drive.google.com/file/d/1kjYk4zS9NQcMQhFkak-L4mp4UuaAIesW/view
// Supports:
// Brand: Neoclima, Model: NS-09AHTI A/C
// Brand: Neoclima, Model: ZH/TY-01 remote
// Brand: Soleus Air, Model: TTWM1-10-01 A/C
// Brand: Soleus Air, Model: ZCF/TL-05 remote
#ifndef IR_NEOCLIMA_H_
#define IR_NEOCLIMA_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
/// Native representation of a Neoclima A/C message.
union NeoclimaProtocol {
uint8_t raw[kNeoclimaStateLength]; ///< State of the remote in code.
struct {
// Byte 0
uint8_t :8;
// Byte 1
uint8_t :1;
uint8_t CHeat :1;
uint8_t Ion :1;
uint8_t :5;
// Byte 2
uint8_t :8;
// Byte 3
uint8_t Light :1;
uint8_t :1;
uint8_t Hold :1;
uint8_t Turbo :1;
uint8_t Econo :1;
uint8_t :1;
uint8_t Eye :1;
uint8_t :1;
// Byte 4
uint8_t :8;
// Byte 5
uint8_t Button :5;
uint8_t :2;
uint8_t Fresh :1;
// Byte 6
uint8_t :8;
// Byte 7
uint8_t Sleep :1;
uint8_t Power :1;
uint8_t SwingV :2;
uint8_t SwingH :1;
uint8_t Fan :2;
uint8_t UseFah :1;
// Byte 8
uint8_t Follow :8;
// Byte 9
uint8_t Temp :5;
uint8_t Mode :3;
// Byte 10
uint8_t :8;
// Byte 11
uint8_t Sum :8;
};
};
// Constants
const uint8_t kNeoclimaButtonPower = 0x00;
const uint8_t kNeoclimaButtonMode = 0x01;
const uint8_t kNeoclimaButtonTempUp = 0x02;
const uint8_t kNeoclimaButtonTempDown = 0x03;
const uint8_t kNeoclimaButtonSwing = 0x04;
const uint8_t kNeoclimaButtonFanSpeed = 0x05;
const uint8_t kNeoclimaButtonAirFlow = 0x07;
const uint8_t kNeoclimaButtonHold = 0x08;
const uint8_t kNeoclimaButtonSleep = 0x09;
const uint8_t kNeoclimaButtonTurbo = 0x0A;
const uint8_t kNeoclimaButtonLight = 0x0B;
const uint8_t kNeoclimaButtonEcono = 0x0D;
const uint8_t kNeoclimaButtonEye = 0x0E;
const uint8_t kNeoclimaButtonFollow = 0x13;
const uint8_t kNeoclimaButtonIon = 0x14;
const uint8_t kNeoclimaButtonFresh = 0x15;
const uint8_t kNeoclimaButton8CHeat = 0x1D;
const uint8_t kNeoclimaButtonTempUnit = 0x1E;
const uint8_t kNeoclimaSwingVOn = 0b01;
const uint8_t kNeoclimaSwingVOff = 0b10;
const uint8_t kNeoclimaFanAuto = 0b00;
const uint8_t kNeoclimaFanHigh = 0b01;
const uint8_t kNeoclimaFanMed = 0b10;
const uint8_t kNeoclimaFanLow = 0b11;
const uint8_t kNeoclimaFollowMe = 0x5D; // Also 0x5F
const uint8_t kNeoclimaMinTempC = 16; // 16C
const uint8_t kNeoclimaMaxTempC = 32; // 32C
const uint8_t kNeoclimaMinTempF = 61; // 61F
const uint8_t kNeoclimaMaxTempF = 90; // 90F
const uint8_t kNeoclimaAuto = 0b000;
const uint8_t kNeoclimaCool = 0b001;
const uint8_t kNeoclimaDry = 0b010;
const uint8_t kNeoclimaFan = 0b011;
const uint8_t kNeoclimaHeat = 0b100;
// Classes
/// Class for handling detailed Neoclima A/C messages.
class IRNeoclimaAc {
public:
explicit IRNeoclimaAc(const uint16_t pin, const bool inverted = false,
const bool use_modulation = true);
void stateReset(void);
#if SEND_NEOCLIMA
void send(const uint16_t repeat = kNeoclimaMinRepeat);
/// 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_NEOCLIMA
void begin(void);
void setButton(const uint8_t button);
uint8_t getButton(void) const;
void on(void);
void off(void);
void setPower(const bool on);
bool getPower(void) const;
void setMode(const uint8_t mode);
uint8_t getMode(void) const;
void setTemp(const uint8_t temp, const bool celsius = true);
uint8_t getTemp(void) const;
void setFan(const uint8_t speed);
uint8_t getFan(void) const;
void setSwingV(const bool on);
bool getSwingV(void) const;
void setSwingH(const bool on);
bool getSwingH(void) const;
void setSleep(const bool on);
bool getSleep(void) const;
void setTurbo(const bool on);
bool getTurbo(void) const;
void setEcono(const bool on);
bool getEcono(void) const;
void setFresh(const bool on);
bool getFresh(void) const;
void setHold(const bool on);
bool getHold(void) const;
void setIon(const bool on);
bool getIon(void) const;
void setLight(const bool on);
bool getLight(void) const;
void set8CHeat(const bool on);
bool get8CHeat(void) const;
void setEye(const bool on);
bool getEye(void) const;
bool getTempUnits(void) const;
// DISABLED: See TODO in ir_Neoclima.cpp
// void setFollow(const bool on);
bool getFollow(void) const;
uint8_t* getRaw(void);
void setRaw(const uint8_t new_code[],
const uint16_t length = kNeoclimaStateLength);
static bool validChecksum(const uint8_t state[],
const uint16_t length = kNeoclimaStateLength);
static uint8_t calcChecksum(const uint8_t state[],
const uint16_t length = kNeoclimaStateLength);
String toString(void) const;
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);
static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed);
stdAc::state_t toCommon(void) const;
#ifndef UNIT_TEST
private:
IRsend _irsend; ///< Instance of the IR send class
#else // UNIT_TEST
/// @cond IGNORE
IRsendTest _irsend; ///< Instance of the testing IR send class
/// @endcond
#endif // UNIT_TEST
NeoclimaProtocol _;
void checksum(const uint16_t length = kNeoclimaStateLength);
};
#endif // IR_NEOCLIMA_H_

View File

@@ -0,0 +1,74 @@
// Copyright 2009 Ken Shirriff
// Copyright 2017 David Conran
/// @file
/// @brief Nikai
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/309
// Supports:
// Brand: Nikai, Model: Unknown LCD TV
#include <algorithm>
#include "IRrecv.h"
#include "IRsend.h"
#include "IRutils.h"
// Constants
const uint16_t kNikaiTick = 500;
const uint16_t kNikaiHdrMarkTicks = 8;
const uint16_t kNikaiHdrMark = kNikaiHdrMarkTicks * kNikaiTick;
const uint16_t kNikaiHdrSpaceTicks = 8;
const uint16_t kNikaiHdrSpace = kNikaiHdrSpaceTicks * kNikaiTick;
const uint16_t kNikaiBitMarkTicks = 1;
const uint16_t kNikaiBitMark = kNikaiBitMarkTicks * kNikaiTick;
const uint16_t kNikaiOneSpaceTicks = 2;
const uint16_t kNikaiOneSpace = kNikaiOneSpaceTicks * kNikaiTick;
const uint16_t kNikaiZeroSpaceTicks = 4;
const uint16_t kNikaiZeroSpace = kNikaiZeroSpaceTicks * kNikaiTick;
const uint16_t kNikaiMinGapTicks = 17;
const uint16_t kNikaiMinGap = kNikaiMinGapTicks * kNikaiTick;
#if SEND_NIKAI
/// Send a Nikai formatted message.
/// Status: STABLE / Working.
/// @param[in] data The message to be sent.
/// @param[in] nbits The number of bits of message to be sent.
/// @param[in] repeat The number of times the command is to be repeated.
void IRsend::sendNikai(uint64_t data, uint16_t nbits, uint16_t repeat) {
sendGeneric(kNikaiHdrMark, kNikaiHdrSpace, kNikaiBitMark, kNikaiOneSpace,
kNikaiBitMark, kNikaiZeroSpace, kNikaiBitMark, kNikaiMinGap, data,
nbits, 38, true, repeat, 33);
}
#endif // SEND_NIKAI
#if DECODE_NIKAI
/// Decode the supplied Nikai message.
/// Status: STABLE / Working.
/// @param[in,out] results Ptr to the data to decode & where to store the result
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// @param[in] strict Flag indicating if we should perform strict matching.
bool IRrecv::decodeNikai(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
if (strict && nbits != kNikaiBits)
return false; // We expect Nikai to be a certain sized message.
uint64_t data = 0;
// Match Header + Data + Footer
if (!matchGeneric(results->rawbuf + offset, &data,
results->rawlen - offset, nbits,
kNikaiHdrMark, kNikaiHdrSpace,
kNikaiBitMark, kNikaiOneSpace,
kNikaiBitMark, kNikaiZeroSpace,
kNikaiBitMark, kNikaiMinGap, true)) return false;
// Success
results->bits = nbits;
results->value = data;
results->decode_type = NIKAI;
results->command = 0;
results->address = 0;
return true;
}
#endif // DECODE_NIKAI

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,271 @@
// Copyright 2018 David Conran
/// @file
/// @brief Support for Panasonic protocols.
/// @see Panasonic A/C support heavily influenced by https://github.com/ToniA/ESPEasy/blob/HeatpumpIR/lib/HeatpumpIR/PanasonicHeatpumpIR.cpp
// Supports:
// Brand: Panasonic, Model: TV (PANASONIC)
// Brand: Panasonic, Model: NKE series A/C (PANASONIC_AC NKE/2)
// Brand: Panasonic, Model: DKE series A/C (PANASONIC_AC DKE/3)
// Brand: Panasonic, Model: DKW series A/C (PANASONIC_AC DKE/3)
// Brand: Panasonic, Model: PKR series A/C (PANASONIC_AC DKE/3)
// Brand: Panasonic, Model: JKE series A/C (PANASONIC_AC JKE/4)
// Brand: Panasonic, Model: CKP series A/C (PANASONIC_AC CKP/5)
// Brand: Panasonic, Model: RKR series A/C (PANASONIC_AC RKR/6)
// Brand: Panasonic, Model: CS-ME10CKPG A/C (PANASONIC_AC CKP/5)
// Brand: Panasonic, Model: CS-ME12CKPG A/C (PANASONIC_AC CKP/5)
// Brand: Panasonic, Model: CS-ME14CKPG A/C (PANASONIC_AC CKP/5)
// Brand: Panasonic, Model: CS-E7PKR A/C (PANASONIC_AC DKE/2)
// Brand: Panasonic, Model: CS-Z9RKR A/C (PANASONIC_AC RKR/6)
// Brand: Panasonic, Model: CS-Z24RKR A/C (PANASONIC_AC RKR/6)
// Brand: Panasonic, Model: CS-YW9MKD A/C (PANASONIC_AC JKE/4)
// Brand: Panasonic, Model: A75C2311 remote (PANASONIC_AC CKP/5)
// Brand: Panasonic, Model: A75C2616-1 remote (PANASONIC_AC DKE/3)
// Brand: Panasonic, Model: A75C3704 remote (PANASONIC_AC DKE/3)
// Brand: Panasonic, Model: A75C3747 remote (PANASONIC_AC JKE/4)
// Brand: Panasonic, Model: CS-E9CKP series A/C (PANASONIC_AC32)
// Brand: Panasonic, Model: A75C2295 remote (PANASONIC_AC32)
// Brand: Panasonic, Model: A75C4762 remote (PANASONIC_AC RKR/6)
#ifndef IR_PANASONIC_H_
#define IR_PANASONIC_H_
#define __STDC_LIMIT_MACROS
#include <stdint.h>
#ifdef ARDUINO
#include <Arduino.h>
#endif
#include "IRremoteESP8266.h"
#include "IRsend.h"
#ifdef UNIT_TEST
#include "IRsend_test.h"
#endif
// Constants
const uint16_t kPanasonicFreq = 36700;
const uint16_t kPanasonicAcExcess = 0;
// Much higher than usual. See issue #540.
const uint16_t kPanasonicAcTolerance = 40;
const uint8_t kPanasonicAcAuto = 0; // 0b000
const uint8_t kPanasonicAcDry = 2; // 0b010
const uint8_t kPanasonicAcCool = 3; // 0b011
const uint8_t kPanasonicAcHeat = 4; // 0b010
const uint8_t kPanasonicAcFan = 6; // 0b110
const uint8_t kPanasonicAcFanMin = 0;
const uint8_t kPanasonicAcFanLow = 1;
const uint8_t kPanasonicAcFanMed = 2;
const uint8_t kPanasonicAcFanHigh = 3;
const uint8_t kPanasonicAcFanMax = 4;
const uint8_t kPanasonicAcFanAuto = 7;
const uint8_t kPanasonicAcFanDelta = 3;
const uint8_t kPanasonicAcPowerOffset = 0;
const uint8_t kPanasonicAcTempOffset = 1; // Bits
const uint8_t kPanasonicAcTempSize = 5; // Bits
const uint8_t kPanasonicAcMinTemp = 16; // Celsius
const uint8_t kPanasonicAcMaxTemp = 30; // Celsius
const uint8_t kPanasonicAcFanModeTemp = 27; // Celsius
const uint8_t kPanasonicAcQuietOffset = 0;
const uint8_t kPanasonicAcPowerfulOffset = 5; // 0b100000
// CKP & RKR models have Powerful and Quiet bits swapped.
const uint8_t kPanasonicAcQuietCkpOffset = kPanasonicAcPowerfulOffset;
const uint8_t kPanasonicAcPowerfulCkpOffset = kPanasonicAcQuietOffset;
const uint8_t kPanasonicAcSwingVHighest = 0x1; // 0b0001
const uint8_t kPanasonicAcSwingVHigh = 0x2; // 0b0010
const uint8_t kPanasonicAcSwingVMiddle = 0x3; // 0b0011
const uint8_t kPanasonicAcSwingVLow = 0x4; // 0b0100
const uint8_t kPanasonicAcSwingVLowest = 0x5; // 0b0101
const uint8_t kPanasonicAcSwingVAuto = 0xF; // 0b1111
const uint8_t kPanasonicAcSwingHMiddle = 0x6; // 0b0110
const uint8_t kPanasonicAcSwingHFullLeft = 0x9; // 0b1001
const uint8_t kPanasonicAcSwingHLeft = 0xA; // 0b1010
const uint8_t kPanasonicAcSwingHRight = 0xB; // 0b1011
const uint8_t kPanasonicAcSwingHFullRight = 0xC; // 0b1100
const uint8_t kPanasonicAcSwingHAuto = 0xD; // 0b1101
const uint8_t kPanasonicAcChecksumInit = 0xF4;
const uint8_t kPanasonicAcOnTimerOffset = 1;
const uint8_t kPanasonicAcOffTimerOffset = 2;
const uint8_t kPanasonicAcTimeSize = 11; // Bits
const uint8_t kPanasonicAcTimeOverflowSize = 3; // Bits
const uint16_t kPanasonicAcTimeMax = 23 * 60 + 59; // Mins since midnight.
const uint16_t kPanasonicAcTimeSpecial = 0x600;
const uint8_t kPanasonicAcIonFilterByte = 22; // Byte
const uint8_t kPanasonicAcIonFilterOffset = 0; // Bit
const uint8_t kPanasonicKnownGoodState[kPanasonicAcStateLength] = {
0x02, 0x20, 0xE0, 0x04, 0x00, 0x00, 0x00, 0x06, 0x02,
0x20, 0xE0, 0x04, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
0x00, 0x0E, 0xE0, 0x00, 0x00, 0x81, 0x00, 0x00, 0x00};
/// Class for handling detailed Panasonic A/C messages.
class IRPanasonicAc {
public:
explicit IRPanasonicAc(const uint16_t pin, const bool inverted = false,
const bool use_modulation = true);
void stateReset(void);
#if SEND_PANASONIC_AC
void send(const uint16_t repeat = kPanasonicAcDefaultRepeat);
/// 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_PANASONIC_AC
void begin(void);
void on(void);
void off(void);
void setPower(const bool on);
bool getPower(void);
void setTemp(const uint8_t temp, const bool remember = true);
uint8_t getTemp(void);
void setFan(const uint8_t fan);
uint8_t getFan(void);
void setMode(const uint8_t mode);
uint8_t getMode(void);
void setRaw(const uint8_t state[]);
uint8_t *getRaw(void);
static bool validChecksum(const uint8_t *state,
const uint16_t length = kPanasonicAcStateLength);
static uint8_t calcChecksum(const uint8_t *state,
const uint16_t length = kPanasonicAcStateLength);
void setQuiet(const bool on);
bool getQuiet(void);
void setPowerful(const bool on);
bool getPowerful(void);
void setIon(const bool on);
bool getIon(void);
void setModel(const panasonic_ac_remote_model_t model);
panasonic_ac_remote_model_t getModel(void);
void setSwingVertical(const uint8_t elevation);
uint8_t getSwingVertical(void);
void setSwingHorizontal(const uint8_t direction);
uint8_t getSwingHorizontal(void);
static uint16_t encodeTime(const uint8_t hours, const uint8_t mins);
uint16_t getClock(void);
void setClock(const uint16_t mins_since_midnight);
uint16_t getOnTimer(void);
void setOnTimer(const uint16_t mins_since_midnight, const bool enable = true);
void cancelOnTimer(void);
bool isOnTimerEnabled(void);
uint16_t getOffTimer(void);
void setOffTimer(const uint16_t mins_since_midnight,
const bool enable = true);
void cancelOffTimer(void);
bool isOffTimerEnabled(void);
static uint8_t convertMode(const stdAc::opmode_t mode);
static uint8_t convertFan(const stdAc::fanspeed_t speed);
static uint8_t convertSwingV(const stdAc::swingv_t position);
static uint8_t convertSwingH(const stdAc::swingh_t position);
static stdAc::opmode_t toCommonMode(const uint8_t mode);
static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed);
static stdAc::swingv_t toCommonSwingV(const uint8_t pos);
static stdAc::swingh_t toCommonSwingH(const uint8_t pos);
stdAc::state_t toCommon(void);
String toString(void);
#ifndef UNIT_TEST
private:
IRsend _irsend; ///< Instance of the IR send class
#else // UNIT_TEST
/// @cond IGNORE
IRsendTest _irsend; ///< Instance of the testing IR send class
/// @endcond
#endif // UNIT_TEST
uint8_t remote_state[kPanasonicAcStateLength]; ///< The state in code form.
uint8_t _swingh;
uint8_t _temp;
void fixChecksum(const uint16_t length = kPanasonicAcStateLength);
static uint16_t _getTime(const uint8_t ptr[]);
static void _setTime(uint8_t * const ptr, const uint16_t mins_since_midnight,
const bool round_down);
};
/// Native representation of a Panasonic 32-bit A/C message.
union PanasonicAc32Protocol {
uint32_t raw; ///< The state in IR code form.
struct {
// Byte 0
uint8_t :3;
uint8_t SwingH :1;
uint8_t SwingV :3;
uint8_t :1; ///< Always appears to be set. (1)
// Byte 1
uint8_t :8; // Always seems to be 0x36.
// Byte 2
uint8_t Temp :4;
uint8_t Fan :4;
// Byte 3
uint8_t Mode :3;
uint8_t PowerToggle :1; // 0 means toggle, 1 = keep the same.
uint8_t :4;
};
};
const uint8_t kPanasonicAc32Fan = 1; // 0b001
const uint8_t kPanasonicAc32Cool = 2; // 0b010
const uint8_t kPanasonicAc32Dry = 3; // 0b011
const uint8_t kPanasonicAc32Heat = 4; // 0b010
const uint8_t kPanasonicAc32Auto = 6; // 0b110
const uint8_t kPanasonicAc32FanMin = 2;
const uint8_t kPanasonicAc32FanLow = 3;
const uint8_t kPanasonicAc32FanMed = 4;
const uint8_t kPanasonicAc32FanHigh = 5;
const uint8_t kPanasonicAc32FanMax = 6;
const uint8_t kPanasonicAc32FanAuto = 0xF;
const uint8_t kPanasonicAc32SwingVAuto = 0x7; // 0b111
const uint32_t kPanasonicAc32KnownGood = 0x0AF136FC; ///< Cool, Auto, 16C
/// Class for handling detailed Panasonic 32bit A/C messages.
class IRPanasonicAc32 {
public:
explicit IRPanasonicAc32(const uint16_t pin, const bool inverted = false,
const bool use_modulation = true);
void stateReset(void);
#if SEND_PANASONIC_AC32
void send(const uint16_t repeat = kPanasonicAcDefaultRepeat);
/// 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_PANASONIC_AC32
void begin(void);
void setPowerToggle(const bool on);
bool getPowerToggle(void) const;
void setTemp(const uint8_t temp);
uint8_t getTemp(void) const;
void setFan(const uint8_t fan);
uint8_t getFan(void) const;
void setMode(const uint8_t mode);
uint8_t getMode(void) const;
void setRaw(const uint32_t state);
uint32_t getRaw(void) const;
void setSwingVertical(const uint8_t pos);
uint8_t getSwingVertical(void) const;
void setSwingHorizontal(const bool on);
bool getSwingHorizontal(void) const;
static uint8_t convertMode(const stdAc::opmode_t mode);
static uint8_t convertFan(const stdAc::fanspeed_t speed);
static uint8_t convertSwingV(const stdAc::swingv_t position);
static stdAc::opmode_t toCommonMode(const uint8_t mode);
static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed);
static stdAc::swingv_t toCommonSwingV(const uint8_t pos);
stdAc::state_t toCommon(const stdAc::state_t *prev = NULL) const;
String toString(void) const;
#ifndef UNIT_TEST
private:
IRsend _irsend; ///< Instance of the IR send class
#else // UNIT_TEST
/// @cond IGNORE
IRsendTest _irsend; ///< Instance of the testing IR send class
/// @endcond
#endif // UNIT_TEST
PanasonicAc32Protocol _; ///< The state in code form.
};
#endif // IR_PANASONIC_H_

View File

@@ -0,0 +1,143 @@
// Copyright 2009 Ken Shirriff
// Copyright 2017, 2018 David Conran
// Copyright 2018 Kamil Palczewski
// Copyright 2019 s-hadinger
/// @file
/// @brief Pioneer remote emulation
/// @see http://www.adrian-kingston.com/IRFormatPioneer.htm
/// @see https://github.com/crankyoldgit/IRremoteESP8266/pull/547
/// @see https://www.pioneerelectronics.com/PUSA/Support/Home-Entertainment-Custom-Install/IR+Codes/A+V+Receivers
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1220
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1749#issuecomment-1028122645
// Supports:
// Brand: Pioneer, Model: AV Receivers
// Brand: Pioneer, Model: VSX-324 AV Receiver
// Brand: Pioneer, Model: AXD7690 Remote
#define __STDC_LIMIT_MACROS
#include <stdint.h>
#include <algorithm>
#include "IRrecv.h"
#include "IRsend.h"
#include "IRutils.h"
// Constants
// Ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/1220
const uint16_t kPioneerTick = 534; ///< uSeconds.
const uint16_t kPioneerHdrMark = 8506; ///< uSeconds.
const uint16_t kPioneerHdrSpace = 4191; ///< uSeconds.
const uint16_t kPioneerBitMark = 568; ///< uSeconds.
const uint16_t kPioneerOneSpace = 1542; ///< uSeconds.
const uint16_t kPioneerZeroSpace = 487; ///< uSeconds.
const uint32_t kPioneerMinCommandLength = 84906; ///< uSeconds.
const uint32_t kPioneerMinGap = 25181; ///< uSeconds.
#if SEND_PIONEER
/// Send a raw Pioneer formatted message.
/// Status: STABLE / Expected to be working.
/// @param[in] data The message to be sent.
/// @param[in] nbits The number of bits of message to be sent.
/// @param[in] repeat The number of times the command is to be repeated.
void IRsend::sendPioneer(const uint64_t data, const uint16_t nbits,
const uint16_t repeat) {
// If nbits is to big, abort.
if (nbits > sizeof(data) * 8) return;
for (uint16_t r = 0; r <= repeat; r++) {
// don't use NEC repeat but repeat the whole sequence
if (nbits > 32) {
sendGeneric(kPioneerHdrMark, kPioneerHdrSpace,
kPioneerBitMark, kPioneerOneSpace,
kPioneerBitMark, kPioneerZeroSpace,
kPioneerBitMark, kPioneerMinGap,
kPioneerMinCommandLength,
data >> 32, nbits - 32, 40, true, 0, 33);
}
sendGeneric(kPioneerHdrMark, kPioneerHdrSpace,
kPioneerBitMark, kPioneerOneSpace,
kPioneerBitMark, kPioneerZeroSpace,
kPioneerBitMark, kPioneerMinGap,
kPioneerMinCommandLength,
data, nbits > 32 ? 32 : nbits, 40, true, 0, 33);
}
}
/// Calculate the raw Pioneer data code based on two NEC sub-codes
/// Status: STABLE / Expected to work.
/// @param[in] address A 16-bit "published" NEC value.
/// @param[in] command A 16-bit "published" NEC value.
/// @return A raw 64-bit Pioneer message code for use with `sendPioneer()``
/// @note Address & Command can be take from a decode result OR from the
/// spreadsheets located at:
/// https://www.pioneerelectronics.com/PUSA/Support/Home-Entertainment-Custom-Install/IR+Codes/A+V+Receivers
/// where the first part is considered the address,
/// and the second the command.
/// e.g.
/// "A556+AF20" is an Address of 0xA556 & a Command of 0xAF20.
/// @note If the Address is 0, use it like the following:
/// `irsend.sendPioneer(irsend.encodePioneer(0, 0xAA1C), 32, 1);` or
/// `irsend.sendPioneer(irsend.encodePioneer(0xAA1C, 0xAA1C), 64, 0);`
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1749#issuecomment-1028122645
uint64_t IRsend::encodePioneer(const uint16_t address, const uint16_t command) {
return (((uint64_t)encodeNEC(address >> 8, address & 0xFF)) << 32) |
encodeNEC(command >> 8, command & 0xFF);
}
#endif // SEND_PIONEER
#if DECODE_PIONEER
/// Decode the supplied Pioneer message.
/// Status: STABLE / Should be working. (Self decodes & real examples)
/// @param[in,out] results Ptr to the data to decode & where to store the result
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// @param[in] strict Flag indicating if we should perform strict matching.
/// @return True if it can decode it, false if it can't.
bool IRrecv::decodePioneer(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
if (results->rawlen < 2 * (nbits + kHeader + kFooter) - 1 + offset)
return false; // Can't possibly be a valid Pioneer message.
if (strict && nbits != kPioneerBits)
return false; // Not strictly an Pioneer message.
uint64_t data = 0;
results->value = 0;
for (uint16_t section = 0; section < 2; section++) {
// Match Header + Data + Footer
uint16_t used;
used = matchGeneric(results->rawbuf + offset, &data,
results->rawlen - offset, nbits / 2,
kPioneerHdrMark, kPioneerHdrSpace,
kPioneerBitMark, kPioneerOneSpace,
kPioneerBitMark, kPioneerZeroSpace,
kPioneerBitMark, kPioneerMinGap, true);
if (!used) return false;
offset += used;
uint8_t command = data >> 8;
uint8_t command_inverted = data;
uint8_t address = data >> 24;
uint8_t address_inverted = data >> 16;
// Compliance
if (strict) {
if (command != (command_inverted ^ 0xFF))
return false; // Command integrity failed.
if (address != (address_inverted ^ 0xFF))
return false; // Address integrity failed.
}
results->value = (results->value << (nbits / 2)) + data;
// NEC-like commands and addresses are technically in LSB first order so the
// final versions have to be reversed.
uint16_t code = reverseBits((command << 8) + address, 16);
if (section)
results->command = code;
else
results->address = code;
}
// Success
results->bits = nbits;
results->decode_type = PIONEER;
return true;
}
#endif // DECODE_PIONEER

View File

@@ -0,0 +1,107 @@
// Copyright 2017 David Conran
/// @file
/// @brief Pronto code message generation
/// @see http://www.etcwiki.org/wiki/Pronto_Infrared_Format
/// @see http://www.remotecentral.com/features/irdisp2.htm
/// @see http://harctoolbox.org/Glossary.html#ProntoSemantics
/// @see https://irdb.globalcache.com/
// Supports:
// Brand: Pronto, Model: Pronto Hex
#include <algorithm>
#include "IRsend.h"
// Constants
const float kProntoFreqFactor = 0.241246;
const uint16_t kProntoTypeOffset = 0;
const uint16_t kProntoFreqOffset = 1;
const uint16_t kProntoSeq1LenOffset = 2;
const uint16_t kProntoSeq2LenOffset = 3;
const uint16_t kProntoDataOffset = 4;
#if SEND_PRONTO
/// Send a Pronto Code formatted message.
/// Status: STABLE / Known working.
/// @param[in] data An array of uint16_t containing the pronto codes.
/// @param[in] len Nr. of entries in the data[] array.
/// @param[in] repeat Nr. of times to repeat the message.
/// @note Pronto codes are typically represented in hexadecimal.
/// You will need to convert the code to an array of integers, and calculate
/// it's length.
/// e.g.
/// @code
/// A Sony 20 bit DVD remote command.
/// "0000 0067 0000 0015 0060 0018 0018 0018 0030 0018 0030 0018 0030 0018
/// 0018 0018 0030 0018 0018 0018 0018 0018 0030 0018 0018 0018 0030 0018
/// 0030 0018 0030 0018 0018 0018 0018 0018 0030 0018 0018 0018 0018 0018
/// 0030 0018 0018 03f6"
/// @endcode
/// converts to:
/// @code{.cpp}
/// uint16_t prontoCode[46] = {
/// 0x0000, 0x0067, 0x0000, 0x0015,
/// 0x0060, 0x0018, 0x0018, 0x0018, 0x0030, 0x0018, 0x0030, 0x0018,
/// 0x0030, 0x0018, 0x0018, 0x0018, 0x0030, 0x0018, 0x0018, 0x0018,
/// 0x0018, 0x0018, 0x0030, 0x0018, 0x0018, 0x0018, 0x0030, 0x0018,
/// 0x0030, 0x0018, 0x0030, 0x0018, 0x0018, 0x0018, 0x0018, 0x0018,
/// 0x0030, 0x0018, 0x0018, 0x0018, 0x0018, 0x0018, 0x0030, 0x0018,
/// 0x0018, 0x03f6};
/// // Send the Pronto(Sony) code. Repeat twice as Sony's require that.
/// sendPronto(prontoCode, 46, kSonyMinRepeat);
/// @endcode
/// @see http://www.etcwiki.org/wiki/Pronto_Infrared_Format
/// @see http://www.remotecentral.com/features/irdisp2.htm
void IRsend::sendPronto(uint16_t data[], uint16_t len, uint16_t repeat) {
// Check we have enough data to work out what to send.
if (len < kProntoMinLength) return;
// We only know how to deal with 'raw' pronto codes types. Reject all others.
if (data[kProntoTypeOffset] != 0) return;
// Pronto frequency is in Hz.
uint16_t hz =
(uint16_t)(1000000U / (data[kProntoFreqOffset] * kProntoFreqFactor));
enableIROut(hz);
// Grab the length of the two sequences.
uint16_t seq_1_len = data[kProntoSeq1LenOffset] * 2;
uint16_t seq_2_len = data[kProntoSeq2LenOffset] * 2;
// Calculate where each sequence starts in the buffer.
uint16_t seq_1_start = kProntoDataOffset;
uint16_t seq_2_start = kProntoDataOffset + seq_1_len;
uint32_t periodic_time_x10 = calcUSecPeriod(hz / 10, false);
// Normal (1st sequence) case.
// Is there a first (normal) sequence to send?
if (seq_1_len > 0) {
// Check we have enough data to send the complete first sequence.
if (seq_1_len + seq_1_start > len) return;
// Send the contents of the 1st sequence.
for (uint16_t i = seq_1_start; i < seq_1_start + seq_1_len; i += 2) {
mark((data[i] * periodic_time_x10) / 10);
space((data[i + 1] * periodic_time_x10) / 10);
}
} else {
// There was no first sequence to send, it is implied that we have to send
// the 2nd/repeat sequence an additional time. i.e. At least once.
repeat++;
}
// Repeat (2nd sequence) case.
// Is there a second (repeat) sequence to be sent?
if (seq_2_len > 0) {
// Check we have enough data to send the complete second sequence.
if (seq_2_len + seq_2_start > len) return;
// Send the contents of the 2nd sequence.
for (uint16_t r = 0; r < repeat; r++)
for (uint16_t i = seq_2_start; i < seq_2_start + seq_2_len; i += 2) {
mark((data[i] * periodic_time_x10) / 10);
space((data[i + 1] * periodic_time_x10) / 10);
}
}
}
#endif // SEND_PRONTO

Some files were not shown because too many files have changed in this diff Show More