5.9.0 20171030 * Rewrite code (partly) using Google C++ Style Guide (https://google.github.io/styleguide/cppguide.html) * Rewrite code by using command lookup tables and javascript (client side) web page expansions * Change HTML/CSS to enable nicer form field entry * Change default PWM assignments for H801 RGB(CW) led controller to support optional Color/Dimmer control * GPIO04 (W2) from GPIO_PWM2 to GPIO_USER to be user configurable for GPIO_PWM5 (second White - Warm if W1 is Cold) * GPIO12 (Blue) GPIO_PWM3 no change * GPIO13 (Green) from GPIO_PWM4 to GPIO_PWM2 * GPIO14 (W1) from GPIO_PWM1 to GPIO_USER to be user configurable for GPIO_PWM4 (first White - Cold or Warm) * GPIO15 (Red) from GPIO_PWM5 to GPIO_PWM1 * Change default PWM assignments for MagicHome RGB(W) led controller to support optional Color/Dimmer control * GPIO05 (Green) from GPIO_PWM4 to GPIO_PWM2 * GPIO12 (Blue) from GPIO_PWM5 to GPIO_PWM3 * GPIO13 (White) GPIO_USER to be user configurable for GPIO_PWM4 (White - Cold or Warm) * GPIO14 (Red) from GPIO_PWM3 to GPIO_PWM1 * Change default PWM assignment for Witty Cloud to support optional Color/Dimmer control (#976) * GPIO12 (Green) from GPIO_PWM4 to GPIO_PWM2 * GPIO13 (Blue) from GPIO_PWM5 to GPIO_PWM3 * GPIO15 (Red) from GPIO_PWM3 to GPIO_PWM1 * Change when another module is selected now all GPIO user configuration is removed * Change command name IRRemote to IRSend (#956) * Remove Arduino IDE version too low warning as it interferes with platformio.ini platform = espressif8266_stage * Fix command FullTopic entry when using serial or console interface * Fix possible UDP syslog blocking * Fix minimum TelePeriod of 10 seconds set by web page * Fix command GPIOx JSON response (#897) * Fix inverted relay power on state (#909) * Fix compile error when DOMOTICZ_UPDATE_TIMER is not defined (#930) * Fix alignment of web page items in some browsers (#935) * Fix setting all saved power settings to Off when SetOption0 (SaveState) = 0 (#955) * Fix timezone range from -12/12 to -13/13 (#968) * Fix Southern Hemisphere TIME_STD/TIME_DST (#968) * Fix TLS MQTT SSL fingerprint test (#970, #808) * Fix virtual relay status message used with Color/Dimmer control (#989) * Fix command IRSend and IRHvac case sensitive parameter regression introduced with version 5.8.0 (#993) * Fix pressure calculation for some BMP versions regression introduced with version 5.8.0i (#974) * Fix Domoticz Dimmer set to same level not powering on (#945) * Fix Blocked Loop when erasing large flash using command reset 2 (#1002) * Fix relay power control when light power control is also configured as regression from 5.8.0 (#1016) * Fix Mqtt server mDNS lookup only when MqttHost name is empty (#1026) * Add debug information to MQTT subscribe * Add translations to I2Cscan * Add translation to BH1750 unit lx * Add light scheme options (Color cycle Up, Down, Random) and moving WS2812 schemes up by 3 * Add Domoticz counter sensor to IrReceive representing Received IR Protocol and Data * Add option 0 to MqttHost to allow empty Mqtt host name * Add support for Arilux AL-LC01 RGB Led controller (#370) * Add esp8266 de-blocking to PubSubClient library (#790) * Add Domoticz sensors for Voltage and Current (#903) * Add platformio OTA upload support (#928, #934) * Add warning to webpage when USE_MINIMAL is selected (#929) * Add smoother movement of hour hand in WS2812 led clock (#936) * Add support for Magic Home RGBW and some Arilux Led controllers (#940) * Add command SetOption15 0 (default) for command PWM control or SetOption15 1 for commands Color/Dimmer control to PWM RGB(CW) leds (#941) * Add Domoticz counter sensor to Sonoff Bridge representing Received RF code (#943) * Add support for Luani HVIO board (https://luani.de/projekte/esp8266-hvio/) (#953) * Add PWM initialization after restart (#955) * Add IR Receiver support. Disable in user_config.h (#956) * Add support for inverted PWM (#960) * Add Sea level pressure calculation and Provide command Altitude (#974) * Add support for up to 8 relays (#995) * Add commands RfSync, RfLow, RfHigh, RfHost and RfCode to allow sending custom RF codes (#1001) * Add retain to ENERGY messages controlled by command SensorRetain (#1013) * Add commands Color2, Color3, Color4, Width2, Width3, Width4 and SetOption16 to set Ws2812 Clock parameters (#1019) * Add German language file (#1022) * Add support for connecting to MQTT brokers without userid and/or password (#1023) * Add support for esp8266 core v2.4.0-rc2 (#1024) * Add commands PwmRange 1,255..1023 and PwmFrequency 1,100..4000 (#1025) * Add Polish language file (#1044, #1047) * Add support for KMC 70011 Power Monitoring Smart Plug (#1045) * Add support for VEML6070 I2C Ultra Violet level sensor (#1053) * Add light turn Off Fade (#925) * Add IrSend command option Panasonic as IrSend {"Protocol":"Panasonic", "Bits":16388, "Data":<Panasonic data>} * where 16388 is 0x4004 hexadecimal (#1014) * Add retry counter to DHT11/21/22 sensors (#1082)
384 lines
12 KiB
C++
384 lines
12 KiB
C++
/*
|
|
xdrv_ir_send.ino - infra red support for Sonoff-Tasmota
|
|
|
|
Copyright (C) 2017 Heiko Krupp, Lazar Obradovic and Theo Arends
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#ifdef USE_IR_REMOTE
|
|
/*********************************************************************************************\
|
|
* IR Remote send and receive using IRremoteESP8266 library
|
|
\*********************************************************************************************/
|
|
|
|
#ifndef USE_IR_HVAC
|
|
#include <IRremoteESP8266.h>
|
|
#else
|
|
#include <IRMitsubishiAC.h>
|
|
|
|
// HVAC TOSHIBA_
|
|
#define HVAC_TOSHIBA_HDR_MARK 4400
|
|
#define HVAC_TOSHIBA_HDR_SPACE 4300
|
|
#define HVAC_TOSHIBA_BIT_MARK 543
|
|
#define HVAC_TOSHIBA_ONE_SPACE 1623
|
|
#define HVAC_MISTUBISHI_ZERO_SPACE 472
|
|
#define HVAC_TOSHIBA_RPT_MARK 440
|
|
#define HVAC_TOSHIBA_RPT_SPACE 7048 // Above original iremote limit
|
|
#define HVAC_TOSHIBA_DATALEN 9
|
|
|
|
IRMitsubishiAC *mitsubir = NULL;
|
|
|
|
const char kFanSpeedOptions[] = "A12345S";
|
|
const char kHvacModeOptions[] = "HDCA";
|
|
#endif
|
|
|
|
/*********************************************************************************************\
|
|
* IR Send
|
|
\*********************************************************************************************/
|
|
|
|
IRsend *irsend = NULL;
|
|
|
|
void IrSendInit(void)
|
|
{
|
|
irsend = new IRsend(pin[GPIO_IRSEND]); // an IR led is at GPIO_IRSEND
|
|
irsend->begin();
|
|
|
|
#ifdef USE_IR_HVAC
|
|
mitsubir = new IRMitsubishiAC(pin[GPIO_IRSEND]);
|
|
#endif //USE_IR_HVAC
|
|
}
|
|
|
|
#ifdef USE_IR_RECEIVE
|
|
/*********************************************************************************************\
|
|
* IR Receive
|
|
\*********************************************************************************************/
|
|
|
|
#define IR_TIME_AVOID_DUPLICATE 500 // Milliseconds
|
|
|
|
// Based on IRremoteESP8266.h enum decode_type_t
|
|
const char kIrRemoteProtocols[] PROGMEM =
|
|
"UNKNOWN|RC5|RC6|NEC|SONY|PANASONIC|JVC|SAMSUNG|WHYNTER|AIWA_RC_T501|LG|SANYO|MITSUBISHI|DISH|SHARP";
|
|
|
|
IRrecv *irrecv = NULL;
|
|
unsigned long ir_lasttime = 0;
|
|
|
|
void IrReceiveInit(void)
|
|
{
|
|
irrecv = new IRrecv(pin[GPIO_IRRECV]); // an IR led is at GPIO_IRRECV
|
|
irrecv->enableIRIn(); // Start the receiver
|
|
|
|
// AddLog_P(LOG_LEVEL_DEBUG, PSTR("IrReceive initialized"));
|
|
}
|
|
|
|
void IrReceiveCheck()
|
|
{
|
|
char sirtype[14]; // Max is AIWA_RC_T501
|
|
int8_t iridx = 0;
|
|
|
|
decode_results results;
|
|
|
|
if (irrecv->decode(&results)) {
|
|
|
|
snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_IRR "RawLen %d, Bits %d, Value %08X, Decode %d"),
|
|
results.rawlen, results.bits, results.value, results.decode_type);
|
|
AddLog(LOG_LEVEL_DEBUG);
|
|
|
|
unsigned long now = millis();
|
|
if ((now - ir_lasttime > IR_TIME_AVOID_DUPLICATE) && (UNKNOWN != results.decode_type) && (results.bits > 0)) {
|
|
ir_lasttime = now;
|
|
|
|
iridx = results.decode_type;
|
|
if ((iridx < 0) || (iridx > 14)) {
|
|
iridx = 0;
|
|
}
|
|
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_IRRECEIVED "\":{\"" D_IR_PROTOCOL "\":\"%s\", \"" D_IR_BITS "\":%d, \"" D_IR_DATA "\":\"%X\"}}"),
|
|
GetTextIndexed(sirtype, sizeof(sirtype), iridx, kIrRemoteProtocols), results.bits, results.value);
|
|
MqttPublishPrefixTopic_P(6, PSTR(D_IRRECEIVED));
|
|
#ifdef USE_DOMOTICZ
|
|
unsigned long value = results.value | (iridx << 28); // [Protocol:4, Data:28]
|
|
DomoticzSensor(DZ_COUNT, value); // Send data as Domoticz Counter value
|
|
#endif // USE_DOMOTICZ
|
|
}
|
|
|
|
irrecv->resume();
|
|
}
|
|
}
|
|
#endif // USE_IR_RECEIVE
|
|
|
|
#ifdef USE_IR_HVAC
|
|
/*********************************************************************************************\
|
|
* IR Heating, Ventilation and Air Conditioning using IRMitsubishiAC library
|
|
\*********************************************************************************************/
|
|
|
|
boolean IrHvacToshiba(const char *HVAC_Mode, const char *HVAC_FanMode, boolean HVAC_Power, int HVAC_Temp)
|
|
{
|
|
unsigned int rawdata[2 + 2 * 8 * HVAC_TOSHIBA_DATALEN + 2];
|
|
byte data[HVAC_TOSHIBA_DATALEN] = {0xF2, 0x0D, 0x03, 0xFC, 0x01, 0x00, 0x00, 0x00, 0x00};
|
|
|
|
char *p;
|
|
char *token;
|
|
uint8_t mode;
|
|
|
|
if (HVAC_Mode == NULL) {
|
|
p = (char *)kHvacModeOptions; // default HVAC_HOT
|
|
}
|
|
else {
|
|
p = strchr(kHvacModeOptions, toupper(HVAC_Mode[0]));
|
|
}
|
|
if (!p) {
|
|
return true;
|
|
}
|
|
data[6] = (p - kHvacModeOptions) ^ 0x03; // HOT = 0x03, DRY = 0x02, COOL = 0x01, AUTO = 0x00
|
|
|
|
if (!HVAC_Power) {
|
|
data[6] = (byte)0x07; // Turn OFF HVAC
|
|
}
|
|
|
|
if (HVAC_FanMode == NULL) {
|
|
p = (char *)kFanSpeedOptions; // default FAN_SPEED_AUTO
|
|
}
|
|
else {
|
|
p = strchr(kFanSpeedOptions, toupper(HVAC_FanMode[0]));
|
|
}
|
|
if (!p) {
|
|
return true;
|
|
}
|
|
mode = p - kFanSpeedOptions + 1;
|
|
if ((1 == mode) || (7 == mode)) {
|
|
mode = 0;
|
|
}
|
|
mode = mode << 5; // AUTO = 0x00, SPEED = 0x40, 0x60, 0x80, 0xA0, 0xC0, SILENT = 0x00
|
|
data[6] = data[6] | mode;
|
|
|
|
byte Temp;
|
|
if (HVAC_Temp > 30) {
|
|
Temp = 30;
|
|
}
|
|
else if (HVAC_Temp < 17) {
|
|
Temp = 17;
|
|
}
|
|
else {
|
|
Temp = HVAC_Temp;
|
|
}
|
|
data[5] = (byte)Temp - 17 << 4;
|
|
|
|
data[HVAC_TOSHIBA_DATALEN - 1] = 0;
|
|
for (int x = 0; x < HVAC_TOSHIBA_DATALEN - 1; x++) {
|
|
data[HVAC_TOSHIBA_DATALEN - 1] = (byte)data[x] ^ data[HVAC_TOSHIBA_DATALEN - 1]; // CRC is a simple bits addition
|
|
}
|
|
|
|
int i = 0;
|
|
byte mask = 1;
|
|
|
|
//header
|
|
rawdata[i++] = HVAC_TOSHIBA_HDR_MARK;
|
|
rawdata[i++] = HVAC_TOSHIBA_HDR_SPACE;
|
|
|
|
//data
|
|
for (int b = 0; b < HVAC_TOSHIBA_DATALEN; b++) {
|
|
for (mask = B10000000; mask > 0; mask >>= 1) { //iterate through bit mask
|
|
if (data[b] & mask) { // Bit ONE
|
|
rawdata[i++] = HVAC_TOSHIBA_BIT_MARK;
|
|
rawdata[i++] = HVAC_TOSHIBA_ONE_SPACE;
|
|
}
|
|
else { // Bit ZERO
|
|
rawdata[i++] = HVAC_TOSHIBA_BIT_MARK;
|
|
rawdata[i++] = HVAC_MISTUBISHI_ZERO_SPACE;
|
|
}
|
|
}
|
|
}
|
|
|
|
//trailer
|
|
rawdata[i++] = HVAC_TOSHIBA_RPT_MARK;
|
|
rawdata[i++] = HVAC_TOSHIBA_RPT_SPACE;
|
|
|
|
noInterrupts();
|
|
irsend->sendRaw(rawdata, i, 38);
|
|
irsend->sendRaw(rawdata, i, 38);
|
|
interrupts();
|
|
|
|
return false;
|
|
}
|
|
|
|
boolean IrHvacMitsubishi(const char *HVAC_Mode, const char *HVAC_FanMode, boolean HVAC_Power, int HVAC_Temp)
|
|
{
|
|
char *p;
|
|
char *token;
|
|
uint8_t mode;
|
|
|
|
mitsubir->stateReset();
|
|
|
|
if (HVAC_Mode == NULL) {
|
|
p = (char *)kHvacModeOptions; // default HVAC_HOT
|
|
}
|
|
else {
|
|
p = strchr(kHvacModeOptions, toupper(HVAC_Mode[0]));
|
|
}
|
|
if (!p) {
|
|
return true;
|
|
}
|
|
mode = (p - kHvacModeOptions + 1) << 3; // HOT = 0x08, DRY = 0x10, COOL = 0x18, AUTO = 0x20
|
|
mitsubir->setMode(mode);
|
|
|
|
mitsubir->setPower(HVAC_Power);
|
|
|
|
if (HVAC_FanMode == NULL) {
|
|
p = (char *)kFanSpeedOptions; // default FAN_SPEED_AUTO
|
|
}
|
|
else {
|
|
p = strchr(kFanSpeedOptions, toupper(HVAC_FanMode[0]));
|
|
}
|
|
if (!p) {
|
|
return true;
|
|
}
|
|
mode = p - kFanSpeedOptions; // AUTO = 0, SPEED = 1 .. 5, SILENT = 6
|
|
mitsubir->setFan(mode);
|
|
|
|
mitsubir->setTemp(HVAC_Temp);
|
|
mitsubir->setVane(MITSUBISHI_AC_VANE_AUTO);
|
|
mitsubir->send();
|
|
|
|
// snprintf_P(log_data, sizeof(log_data), PSTR("IRHVAC: Mitsubishi Power %d, Mode %d, FanSpeed %d, Temp %d, VaneMode %d"),
|
|
// mitsubir->getPower(), mitsubir->getMode(), mitsubir->getFan(), mitsubir->getTemp(), mitsubir->getVane());
|
|
// AddLog(LOG_LEVEL_DEBUG);
|
|
|
|
return false;
|
|
}
|
|
#endif // USE_IR_HVAC
|
|
|
|
/*********************************************************************************************\
|
|
* Commands
|
|
\*********************************************************************************************/
|
|
|
|
/*
|
|
* ArduinoJSON entry used to calculate jsonBuf: JSON_OBJECT_SIZE(3) + 40 = 96
|
|
IRsend:
|
|
{ "protocol": "SAMSUNG", "bits": 32, "data": 551502015 }
|
|
|
|
IRhvac:
|
|
{ "Vendor": "<Toshiba|Mitsubishi>", "Power": <0|1>, "Mode": "<Hot|Cold|Dry|Auto>", "FanSpeed": "<1|2|3|4|5|Auto|Silence>", "Temp": <17..30> }
|
|
*/
|
|
|
|
boolean IrSendCommand(char *type, uint16_t index, char *dataBuf, uint16_t data_len, int16_t payload)
|
|
{
|
|
boolean serviced = true;
|
|
boolean error = false;
|
|
char dataBufUc[data_len];
|
|
char protocol_text[20];
|
|
const char *protocol;
|
|
uint32_t bits = 0;
|
|
uint32_t data = 0;
|
|
|
|
const char *HVAC_Mode;
|
|
const char *HVAC_FanMode;
|
|
const char *HVAC_Vendor;
|
|
int HVAC_Temp = 21;
|
|
boolean HVAC_Power = true;
|
|
|
|
for (uint16_t i = 0; i <= sizeof(dataBufUc); i++) {
|
|
dataBufUc[i] = toupper(dataBuf[i]);
|
|
}
|
|
if (!strcasecmp_P(type, PSTR(D_CMND_IRSEND))) {
|
|
if (data_len) {
|
|
StaticJsonBuffer<128> jsonBuf;
|
|
JsonObject &ir_json = jsonBuf.parseObject(dataBufUc);
|
|
if (!ir_json.success()) {
|
|
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_CMND_IRSEND "\":\"" D_INVALID_JSON "\"}")); // JSON decode failed
|
|
}
|
|
else {
|
|
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_CMND_IRSEND "\":\"" D_DONE "\"}"));
|
|
protocol = ir_json[D_IR_PROTOCOL];
|
|
bits = ir_json[D_IR_BITS];
|
|
data = ir_json[D_IR_DATA];
|
|
if (protocol && bits && data) {
|
|
int protocol_code = GetCommandCode(protocol_text, sizeof(protocol_text), protocol, kIrRemoteProtocols);
|
|
switch (protocol_code) {
|
|
case NEC:
|
|
irsend->sendNEC(data, bits); break;
|
|
case SONY:
|
|
irsend->sendSony(data, bits); break;
|
|
case RC5:
|
|
irsend->sendRC5(data, bits); break;
|
|
case RC6:
|
|
irsend->sendRC6(data, bits); break;
|
|
case DISH:
|
|
irsend->sendDISH(data, bits); break;
|
|
case JVC:
|
|
irsend->sendJVC(data, bits, 1); break;
|
|
case SAMSUNG:
|
|
irsend->sendSAMSUNG(data, bits); break;
|
|
case PANASONIC:
|
|
irsend->sendPanasonic(bits, data); break;
|
|
default:
|
|
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_CMND_IRSEND "\":\"" D_PROTOCOL_NOT_SUPPORTED "\"}"));
|
|
}
|
|
}
|
|
else {
|
|
error = true;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
error = true;
|
|
}
|
|
if (error) {
|
|
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_CMND_IRSEND "\":\"" D_NO " " D_IR_PROTOCOL ", " D_IR_BITS " " D_OR " " D_IR_DATA "\"}"));
|
|
}
|
|
}
|
|
#ifdef USE_IR_HVAC
|
|
else if (!strcasecmp_P(type, PSTR(D_CMND_IRHVAC))) {
|
|
if (data_len) {
|
|
StaticJsonBuffer<164> jsonBufer;
|
|
JsonObject &root = jsonBufer.parseObject(dataBufUc);
|
|
if (!root.success()) {
|
|
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_CMND_IRHVAC "\":\"" D_INVALID_JSON "\"}")); // JSON decode failed
|
|
}
|
|
else {
|
|
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_CMND_IRHVAC "\":\"" D_DONE "\"}"));
|
|
HVAC_Vendor = root[D_IRHVAC_VENDOR];
|
|
HVAC_Power = root[D_IRHVAC_POWER];
|
|
HVAC_Mode = root[D_IRHVAC_MODE];
|
|
HVAC_FanMode = root[D_IRHVAC_FANSPEED];
|
|
HVAC_Temp = root[D_IRHVAC_TEMP];
|
|
|
|
// snprintf_P(log_data, sizeof(log_data), PSTR("IRHVAC: Received Vendor %s, Power %d, Mode %s, FanSpeed %s, Temp %d"),
|
|
// HVAC_Vendor, HVAC_Power, HVAC_Mode, HVAC_FanMode, HVAC_Temp);
|
|
// AddLog(LOG_LEVEL_DEBUG);
|
|
|
|
if (HVAC_Vendor == NULL || !strcasecmp_P(HVAC_Vendor, PSTR("TOSHIBA"))) {
|
|
error = IrHvacToshiba(HVAC_Mode, HVAC_FanMode, HVAC_Power, HVAC_Temp);
|
|
}
|
|
else if (!strcasecmp_P(HVAC_Vendor, PSTR("MITSUBISHI"))) {
|
|
error = IrHvacMitsubishi(HVAC_Mode, HVAC_FanMode, HVAC_Power, HVAC_Temp);
|
|
}
|
|
else {
|
|
error = true;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
error = true;
|
|
}
|
|
if (error) {
|
|
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_CMND_IRHVAC "\":\"" D_WRONG " " D_IRHVAC_VENDOR ", " D_IRHVAC_MODE " " D_OR " " D_IRHVAC_FANSPEED "\"}"));
|
|
}
|
|
}
|
|
#endif // USE_IR_HVAC
|
|
else {
|
|
serviced = false; // Unknown command
|
|
}
|
|
return serviced;
|
|
}
|
|
#endif // USE_IR_REMOTE
|