From 29d8dd734fcd452dfbde0ccbe229cec27f0f1473 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xose=20P=C3=A9rez?= Date: Wed, 29 Aug 2018 21:29:33 +0200 Subject: [PATCH] Support for IR transmit and MQTT-IR bridge, also in RAW mode (#556, #907) --- code/espurna/config/general.h | 65 ++++-- code/espurna/config/hardware.h | 10 +- code/espurna/ir.ino | 377 +++++++++++++++++++++++++++++---- 3 files changed, 399 insertions(+), 53 deletions(-) diff --git a/code/espurna/config/general.h b/code/espurna/config/general.h index 67457ab7..5c1431b2 100644 --- a/code/espurna/config/general.h +++ b/code/espurna/config/general.h @@ -727,7 +727,8 @@ #define MQTT_TOPIC_BOARD "board" #define MQTT_TOPIC_PULSE "pulse" #define MQTT_TOPIC_SPEED "speed" -#define MQTT_TOPIC_IR "ir" +#define MQTT_TOPIC_IRIN "irin" +#define MQTT_TOPIC_IROUT "irout" // Light module #define MQTT_TOPIC_CHANNEL "channel" @@ -1065,33 +1066,51 @@ #ifndef RF_RAW_SUPPORT #define RF_RAW_SUPPORT 0 // RF raw codes require a specific firmware for the EFM8BB1 - // https://github.com/rhx/RF-Bridge-EFM8BB1 + // https://github.com/rhx/RF-Bridge-EFM8BB1 #endif - // ----------------------------------------------------------------------------- -// IR +// IR Bridge // ----------------------------------------------------------------------------- #ifndef IR_SUPPORT #define IR_SUPPORT 0 // Do not build with IR support by default (10.25Kb) #endif -#ifndef IR_RECEIVER_PIN -#define IR_RECEIVER_PIN 4 // IR LED +//#define IR_RX_PIN 5 // GPIO the receiver is connected to +//#define IR_TX_PIN 4 // GPIO the transmitter is connected to + +#ifndef IR_USE_RAW +#define IR_USE_RAW 0 // Use raw codes #endif -// 24 Buttons Set of the IR Remote -#ifndef IR_BUTTON_SET -#define IR_BUTTON_SET 1 // IR button set to use (see below) +#ifndef IR_BUFFER_SIZE +#define IR_BUFFER_SIZE 1024 +#endif + +#ifndef IR_TIMEOUT +#define IR_TIMEOUT 15U +#endif + +#ifndef IR_REPEAT +#define IR_REPEAT 1 +#endif + +#ifndef IR_DELAY +#define IR_DELAY 100 #endif #ifndef IR_DEBOUNCE #define IR_DEBOUNCE 500 // IR debounce time in milliseconds #endif -//Remote Buttons SET 1 (for the original Remote shipped with the controller) -#if IR_SUPPORT +#ifndef IR_BUTTON_SET +#define IR_BUTTON_SET 0 // IR button set to use (see below) +#endif + +// ----------------------------------------------------------------------------- + +// Remote Buttons SET 1 (for the original Remote shipped with the controller) #if IR_BUTTON_SET == 1 /* @@ -1237,7 +1256,29 @@ //{ 0xE0E08877, IR_BUTTON_MODE_TOGGLE, 9 } //Extra Button }; #endif -#endif // IR_SUPPORT + +//Remote Buttons SET 4 +#if IR_BUTTON_SET == 4 +/* + +------+------+------+ + | OFF | SRC | MUTE | + +------+------+------+ + ... + +------+------+------+ +*/ +#define IR_BUTTON_COUNT 1 + + const unsigned long IR_BUTTON[IR_BUTTON_COUNT][3] PROGMEM = { + + { 0xFFB24D, IR_BUTTON_MODE_TOGGLE, 0 } // Toggle Relay #0 + + }; + +#endif + +#ifndef IR_BUTTON_COUNT +#define IR_BUTTON_COUNT 0 +#endif //-------------------------------------------------------------------------------- // Custom RF module diff --git a/code/espurna/config/hardware.h b/code/espurna/config/hardware.h index 24de3afd..cdfabf8a 100644 --- a/code/espurna/config/hardware.h +++ b/code/espurna/config/hardware.h @@ -1156,7 +1156,7 @@ // IR #define IR_SUPPORT 1 - #define IR_RECEIVER_PIN 4 + #define IR_RX_PIN 4 #define IR_BUTTON_SET 1 #elif defined(MAGICHOME_LED_CONTROLLER_20) @@ -1185,7 +1185,7 @@ // IR #define IR_SUPPORT 1 - #define IR_RECEIVER_PIN 4 + #define IR_RX_PIN 4 #define IR_BUTTON_SET 1 // ----------------------------------------------------------------------------- @@ -2770,7 +2770,7 @@ #define SHT3X_I2C_SUPPORT 1 #define SI7021_SUPPORT 1 #define PMSX003_SUPPORT 1 - #define SENSEAIR_SUPPORT 1 + #define SENSEAIR_SUPPORT1 // A bit of lights - pin 5 @@ -2829,7 +2829,7 @@ // IR - pin 4 #define IR_SUPPORT 1 - #define IR_RECEIVER_PIN 4 + #define IR_RX_PIN 4 #define IR_BUTTON_SET 1 // A bit of DHT - pin 5 @@ -2873,7 +2873,7 @@ #define NOFUSS_SUPPORT 1 #define UART_MQTT_SUPPORT 1 #define INFLUXDB_SUPPORT 1 - #define IR_SUPPORT 1 + #define IR_SUPPORT 1 #elif defined(TRAVIS03) diff --git a/code/espurna/ir.ino b/code/espurna/ir.ino index deba94ee..b43d93fe 100644 --- a/code/espurna/ir.ino +++ b/code/espurna/ir.ino @@ -2,35 +2,292 @@ IR MODULE -Copyright (C) 2016-2018 by Xose Pérez +Copyright (C) 2018 by Alexander Kolesnikov (raw and MQTT implementation) Copyright (C) 2017-2018 by François Déchery +Copyright (C) 2016-2018 by Xose Pérez + +----------------------------------------------------------------------------- +Configuration +----------------------------------------------------------------------------- + +To enable transmit functions define IR_TX_PIN +To enable receiver functions define IR_RX_PIN +MQTT input topic: {root}/irin +MQTT output topic: {root}/irout/set + +-------------------------------------------------------------------------------- +MQTT messages +-------------------------------------------------------------------------------- + +Decoded messages: + + Transmitting: + Payload: 2:121944:32:1 (::[:]) + The repeat value is optional and defaults to 1 + Receiving: + Payload: 2:121944:32 (::) + +Raw messages: + + Transmitting: + Payload: 1000,1000,1000,1000,1000,DELAY,COUNT,FREQ:500,500,500,500,500 + | IR codes | | IR repeat codes | + codes - time in microseconds when IR LED On/Off. First value - ON, second - Off ... + DELAY - delay in milliseconds between sending repeats + COUNT - how many repeats send. Max 120. + FREQ - modulation frequency. Usually 38kHz. You may set 38, it means 38kHz or set 38000, it meant same. + Repeat codes is optional. You may omit ":" and codes. In this case if repeat count > 0 we repeat main code. + + Receiving: + Payload: 1000,1000,1000,1000,1000 + | IR codes | + + * To support long codes (Air Conditioneer) increase MQTT packet size -DMQTT_MAX_PACKET_SIZE=1200 + +-------------------------------------------------------------------------------- */ #if IR_SUPPORT #include -#include -IRrecv * _ir_recv; -decode_results _ir_results; -unsigned long _ir_last_toggle = 0; +#if defined(IR_RX_PIN) -// ----------------------------------------------------------------------------- -// PRIVATE -// ----------------------------------------------------------------------------- + #include + IRrecv _ir_receiver(IR_RX_PIN, IR_BUFFER_SIZE, IR_TIMEOUT, true); + + decode_results _ir_results; + +#endif // defined(IR_RX_PIN) + +#if defined(IR_TX_PIN) + + #include + IRsend _ir_sender(IR_TX_PIN); + + #if IR_USE_RAW + uint16_t _ir_freq = 38; // IR modulation freq. for sending codes and repeat codes + uint8_t _ir_repeat_size = 0; // size of repeat array + uint16_t * _ir_raw; // array for sending codes and repeat codes + #else + uint8_t _ir_type = 0; // Type of encoding + uint64_t _ir_code = 0; // Code to transmit + uint16_t _ir_bits = 0; // Code bits + #endif + + uint8_t _ir_repeat = 0; // count of times repeating of repeat_code + uint32_t _ir_delay = IR_DELAY; // delay between repeat codes + +#endif // defined(IR_TX_PIN) + +// MQTT to IR +#if MQTT_SUPPORT && defined(IR_TX_PIN) + +void _irMqttCallback(unsigned int type, const char * topic, const char * payload) { + + if (type == MQTT_CONNECT_EVENT) { + mqttSubscribe(MQTT_TOPIC_IROUT); + } + + if (type == MQTT_MESSAGE_EVENT) { + + String t = mqttMagnitude((char *) topic); + + // Match topic + if (t.equals(MQTT_TOPIC_IROUT)) { + + String data = String(payload); + unsigned int len = data.length(); + int col = data.indexOf(":"); // position of ":" which means repeat_code + + #if IR_USE_RAW + + unsigned char count = 1; // count of code values for allocating array + + if (col > 2) { // count & validating repeat code + + _ir_repeat_size = 1; + + // count & validate repeat-string + for(int i = col+1; i < len; i++) { + if (i < len-1) { + if ( payload[i] == ',' && isDigit(payload[i+1]) && i>0 ) { //validate string + _ir_repeat_size++; + } else if (!isDigit(payload[i])) { + // Error in repeat_code. Use comma separated unsigned integer values. + // Last three is repeat delay, repeat count(<120) and frequency. + // After all you may write ':' and specify repeat code followed by comma. + DEBUG_MSG_P(PSTR("[IR] Error in repeat code.\n")); + return; + } + } + } + + len = col; //cut repeat code from main code processing + + } // end of counting & validating repeat code + + // count & validate main code string + for(int i = 0; i < len; i++) { + if (i0 ) { //validate string + count++; + } else if (!isDigit(payload[i])) { + // Error in main code. Use comma separated unsigned integer values. + // Last three is repeat delay, repeat count(<120) and frequency. + // After all you may write ':' and specify repeat code followed by comma. + DEBUG_MSG_P(PSTR("[IR] Error in main code.\n")); + return; + } + } + + } + + _ir_raw = (uint16_t*)calloc(count, sizeof(uint16_t)); // allocating array for main codes + String value = ""; // for populating values of array from comma separated string + int j = 0; // for populating values of array from comma separated string + + // populating main code array from part of MQTT string + for (int i = 0; i < len; i++) { + if (payload[i] != ',') { + value = value + data[i]; + } + if ((payload[i] == ',') || (i == len - 1)) { + _ir_raw[j]= value.toInt(); + value = ""; + j++; + } + } + + // if count>3 then we have values, repeat delay, count and modulation frequency + _ir_repeat=0; + if (count>3) { + if (_ir_raw[count-2] <= 120) { // if repeat count > 120 it's to long and ussualy unusual. maybe we get raw code without this parameters and just use defaults for freq. + _ir_freq = _ir_raw[count-1]; + _ir_repeat = _ir_raw[count-2]; + _ir_delay = _ir_raw[count-3]; + count = count - 3; + } + } + + DEBUG_MSG_P(PSTR("[IR] Raw IR output %d codes, repeat %d times on %d(k)Hz freq.\n"), count, _ir_repeat, _ir_freq); + + /* + DEBUG_MSG("[IR] main codes: "); + for(int i = 0; i < count; i++) { + DEBUG_MSG("%d,",_ir_raw[i]); + } + DEBUG_MSG("\n"); + */ + + #if defined(IR_RX_PIN) + _ir_receiver.disableIRIn(); + #endif + _ir_sender.sendRaw(_ir_raw, count, _ir_freq); + + if (_ir_repeat==0) { // no repeat, cleaning array, enabling receiver + free(_ir_raw); + #if defined(IR_RX_PIN) + _ir_receiver.enableIRIn(); + #endif + } else if (col>2) { // repeat with repeat_code + + DEBUG_MSG_P(PSTR("[IR] Repeat codes count: %d\n"), _ir_repeat_size); + + free(_ir_raw); + _ir_raw = (uint16_t*)calloc(_ir_repeat_size, sizeof(uint16_t)); + + String value = ""; // for populating values of array from comma separated string + int j = 0; // for populating values of array from comma separated string + len = data.length(); //redifining length to full lenght + + // populating repeat code array from part of MQTT string + for (int i = col+1; i < len; i++) { + value = value + data[i]; + if ((payload[i] == ',') || (i == len - 1)) { + _ir_raw[j]= value.toInt(); + value = ""; + j++; + } + } + + } else { // if repeat code not specified (col<=2) repeat with current main code + _ir_repeat_size = count; + } + + #else + + _ir_repeat = 0; + + if (col > 0) { + + _ir_type = data.toInt(); + _ir_code = data.substring(col+1).toInt(); + + col = data.indexOf(":", col+1); + if (col > 0) { + _ir_bits = data.substring(col+1).toInt(); + col = data.indexOf(":", col+1); + if (col > 2) { + _ir_repeat = data.substring(col+1).toInt(); + } else { + _ir_repeat = IR_REPEAT; + } + + } + + } + + if (_ir_repeat > 0) { + DEBUG_MSG_P(PSTR("[IR] IROUT: %d:%lu:%d:%d\n"), _ir_type, (unsigned long) _ir_code, _ir_bits, _ir_repeat); + } else { + DEBUG_MSG_P(PSTR("[IR] Wrong MQTT payload format (%s)\n"), payload); + } + + #endif // IR_USE_RAW + + } // end of match topic + + } // end of MQTT message + +} //end of function + +void _irTXLoop() { + + static uint32_t last = 0; + if ((_ir_repeat > 0) && (millis() - last > _ir_delay)) { + last = millis(); + + // Send message + #if IR_USE_RAW + _ir_sender.sendRaw(_ir_raw, _ir_repeat_size, _ir_freq); + #else + _ir_sender.send(_ir_type, _ir_code, _ir_bits); + #endif -void _irProcessCode(unsigned long code, unsigned char type) { + // Update repeat count + --_ir_repeat; + if (0 == _ir_repeat) { + #if IR_USE_RAW + free(_ir_raw); + #endif + #if defined(IR_RX_PIN) + _ir_receiver.enableIRIn(); + #endif + } - // Check valid code - static unsigned long last_code = 0; - static unsigned long last_time = 0; - if (code == 0xFFFFFFFF) return; - if (type == 0xFF) return; - if ((last_code == code) && (millis() - last_time < IR_DEBOUNCE)) return; - last_code = code; - last_time = millis(); - DEBUG_MSG_P(PSTR("[IR] Received 0x%08X (%d)\n"), code, type); + } + +} + +#endif // MQTT_SUPPORT && defined(IR_TX_PIN) + +// Receiving + +#if defined(IR_RX_PIN) + +void _irProcess(unsigned char type, unsigned long code) { #if IR_BUTTON_SET > 0 @@ -89,38 +346,86 @@ void _irProcessCode(unsigned long code, unsigned char type) { } if (!found) { - DEBUG_MSG_P(PSTR("[IR] Ignoring code\n")); + DEBUG_MSG_P(PSTR("[IR] Code does not match any action\n")); } #endif - #if MQTT_SUPPORT - char buffer[16]; - snprintf_P(buffer, sizeof(buffer), "0x%08X", code); - mqttSend(MQTT_TOPIC_IR, buffer); - #endif +} + +void _irRXLoop() { + + if (_ir_receiver.decode(&_ir_results)) { + + _ir_receiver.resume(); // Receive the next value + + // Debounce + static unsigned long last_time = 0; + if (millis() - last_time < IR_DEBOUNCE) return; + last_time = millis(); + + // Check code + if (_ir_results.value < 1) return; + if (_ir_results.decode_type < 1) return; + if (_ir_results.bits < 1) return; + + #if IR_USE_RAW + char * payload; + String value = ""; + for (int i = 1; i < _ir_results.rawlen; i++) { + if (i>1) value = value + ","; + value = value + String(_ir_results.rawbuf[i] * RAWTICK); + } + payload = const_cast(value.c_str()); + #else + char payload[32]; + snprintf_P(payload, sizeof(payload), PSTR("%u:%lu:%u"), _ir_results.decode_type, (unsigned long) _ir_results.value, _ir_results.bits); + #endif + + DEBUG_MSG_P(PSTR("[IR] IRIN: %s\n"), payload); + + #if not IR_USE_RAW + _irProcess(_ir_results.decode_type, (unsigned long) _ir_results.value); + #endif + + #if MQTT_SUPPORT + if (strlen(payload)>0) { + mqttSend(MQTT_TOPIC_IRIN, (const char *) payload); + } + #endif + + } } -// ----------------------------------------------------------------------------- -// PUBLIC API +#endif // defined(IR_RX_PIN) + // ----------------------------------------------------------------------------- +void _irLoop() { + #if defined(IR_RX_PIN) + _irRXLoop(); + #endif + #if MQTT_SUPPORT && defined(IR_TX_PIN) + _irTXLoop(); + #endif +} + void irSetup() { - _ir_recv = new IRrecv(IR_RECEIVER_PIN); - _ir_recv->enableIRIn(); + #if defined(IR_RX_PIN) + _ir_receiver.enableIRIn(); + DEBUG_MSG_P(PSTR("[IR] Receiver initialized \n")); + #endif - // Register loop - espurnaRegisterLoop(irLoop); + #if MQTT_SUPPORT && defined(IR_TX_PIN) + _ir_sender.begin(); + mqttRegister(_irMqttCallback); + DEBUG_MSG_P(PSTR("[IR] Transmitter initialized \n")); + #endif -} + espurnaRegisterLoop(_irLoop); -void irLoop() { - if (_ir_recv->decode(&_ir_results)) { - _irProcessCode(_ir_results.value, _ir_results.decode_type); - _ir_recv->resume(); // Receive the next value - } } #endif // IR_SUPPORT