diff --git a/CHANGELOG.md b/CHANGELOG.md index 66cbb8cbd..5a99ee2ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ All notable changes to this project will be documented in this file. ### Added - Command ``SetOption45 1..250`` to change default bistable latching relay pulse length of 40 milliseconds - Support for Modbus bridge adding commands ``ModbusSend``, ``ModbusBaudrate`` and ``ModbusSerialConfig`` (#16013) +- Support for multiple `IRsend` GPIOs ### Changed - ESP32 LVGL library from v8.2.0 to v8.3.0 diff --git a/tasmota/include/i18n.h b/tasmota/include/i18n.h index a4eb333da..3a8a7382c 100644 --- a/tasmota/include/i18n.h +++ b/tasmota/include/i18n.h @@ -511,6 +511,7 @@ #define D_JSON_IR_HASH "Hash" #define D_JSON_IR_RAWDATA "RawData" #define D_JSON_IR_REPEAT "Repeat" + #define D_JSON_IR_CHANNEL "Channel" #define D_CMND_IRHVAC "IRHVAC" #define D_JSON_IRHVAC_VENDOR "Vendor" #define D_JSON_IRHVAC_PROTOCOL "Protocol" diff --git a/tasmota/include/tasmota.h b/tasmota/include/tasmota.h index b210e0932..e993167e6 100644 --- a/tasmota/include/tasmota.h +++ b/tasmota/include/tasmota.h @@ -93,6 +93,7 @@ const uint8_t MAX_SHUTTERS = 4; // Max number of shutters const uint8_t MAX_SHUTTER_KEYS = 4; // Max number of shutter keys or buttons const uint8_t MAX_PCF8574 = 4; // Max number of PCF8574 devices const uint8_t MAX_DS3502 = 4; // Max number of DS3502 digitsal potentiometer devices +const uint8_t MAX_IRSEND = 16; // Max number of IRSEND GPIOs const uint8_t MAX_RULE_SETS = 3; // Max number of rule sets of size 512 characters const uint16_t MAX_RULE_SIZE = 512; // Max number of characters in rules const uint16_t VL53LXX_MAX_SENSORS = 8; // Max number of VL53L0X sensors diff --git a/tasmota/include/tasmota_template.h b/tasmota/include/tasmota_template.h index 06bbfb0a6..59fd24f54 100644 --- a/tasmota/include/tasmota_template.h +++ b/tasmota/include/tasmota_template.h @@ -721,7 +721,7 @@ const uint16_t kGpioNiceList[] PROGMEM = { \*-------------------------------------------------------------------------------------------*/ #if defined(USE_IR_REMOTE) || defined(USE_IR_REMOTE_FULL) - AGPIO(GPIO_IRSEND), // IR remote + AGPIO(GPIO_IRSEND) + MAX_IRSEND, // IR remote #if defined(USE_IR_RECEIVE) || defined(USE_IR_REMOTE_FULL) AGPIO(GPIO_IRRECV), // IR receiver #endif diff --git a/tasmota/tasmota_xdrv_driver/xdrv_05_irremote_full.ino b/tasmota/tasmota_xdrv_driver/xdrv_05_irremote_full.ino index 53e157c86..0c6da380f 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_05_irremote_full.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_05_irremote_full.ino @@ -80,6 +80,9 @@ const char kIrRemoteCommands[] PROGMEM = "|" void (* const IrRemoteCommand[])(void) PROGMEM = { &CmndIrHvac, &CmndIrSend }; +bool ir_send_active = false; // do we have a GPIO configured for IR_SEND +bool ir_recv_active = false; // do we have a GPIO configured for IR_RECV + /*********************************************************************************************\ * Class used to make a compact IR Raw format. * @@ -122,7 +125,6 @@ protected: * IR Send \*********************************************************************************************/ -IRsend *irsend = nullptr; // some ACs send toggle messages rather than state. we need to help IRremoteESP8266 keep track of the state // have a flag that is a variable, can be later used to convert this functionality to an option (as in SetOptionXX) bool irhvac_stateful = true; @@ -134,10 +136,18 @@ enum class StateModes { SEND_ONLY, // just send the IR signal, this is the defau SEND_STORE }; // send IR signal but also update stored state. this is for use cases when there is just one transmitter and there is no receiver in the device. StateModes strToStateMode(class JsonParserToken token, StateModes def); // declate to prevent errors related to ino files -void IrSendInit(void) -{ - irsend = new IRsend(Pin(GPIO_IRSEND), IR_SEND_INVERTED, IR_SEND_USE_MODULATION); // an IR led is at GPIO_IRSEND - irsend->begin(); +// initialize an `IRsend` static instance +// channel is the IRSEND channel number (from 0..) +class IRsend IrSendInitGPIO(int32_t channel = -1) { + if (channel < 0) { channel = GPIO_ANY; } // take first available GPIO + int32_t pin = Pin(GPIO_IRSEND, channel); + if (pin < 0) { + pin = Pin(GPIO_IRSEND, GPIO_ANY); + AddLog(LOG_LEVEL_INFO, PSTR("IR : GPIO 'IRsend-%i' not assigned, revert to GPIO %i"), channel+1, pin); + } + IRsend irsend(pin, IR_SEND_INVERTED, IR_SEND_USE_MODULATION); // an IR led is at GPIO_IRSEND + irsend.begin(); + return irsend; } // from https://stackoverflow.com/questions/2602823/in-c-c-whats-the-simplest-way-to-reverse-the-order-of-bits-in-a-byte @@ -279,8 +289,7 @@ void sendIRJsonState(const struct decode_results &results) { } } -void IrReceiveCheck(void) -{ +void IrReceiveCheck(void) { decode_results results; if (irrecv->decode(&results)) { @@ -462,7 +471,14 @@ uint32_t IrRemoteCmndIrHvacJson(void) if (!IR_RCV_WHILE_SENDING && (irrecv != nullptr)) { irrecv->disableIRIn(); } if (stateMode == StateModes::SEND_ONLY || stateMode == StateModes::SEND_STORE) { - IRac ac(Pin(GPIO_IRSEND), IR_SEND_INVERTED, IR_SEND_USE_MODULATION); + int8_t channel = root.getUInt(PSTR(D_JSON_IR_CHANNEL), 1) - 1; + if (channel < 0) { channel = GPIO_ANY; } // take first available GPIO + int32_t pin = Pin(GPIO_IRSEND, channel); + if (pin < 0) { + pin = Pin(GPIO_IRSEND, GPIO_ANY); + AddLog(LOG_LEVEL_INFO, PSTR("IR : GPIO 'IRsend-%i' not assigned, revert to GPIO %i"), channel+1, pin); + } + IRac ac(pin, IR_SEND_INVERTED, IR_SEND_USE_MODULATION); bool success = ac.sendAc(state, irhvac_stateful && irac_prev_state.protocol == state.protocol ? &irac_prev_state : nullptr); if (!success) { return IE_SYNTAX_IRHVAC; } } @@ -511,6 +527,7 @@ uint32_t IrRemoteCmndIrSendJson(void) uint16_t bits = root.getUInt(PSTR(D_JSON_IR_BITS), 0); uint16_t repeat = root.getUInt(PSTR(D_JSON_IR_REPEAT), 0); + int8_t channel = root.getUInt(PSTR(D_JSON_IR_CHANNEL), 1) - 1; uint64_t data; value = root[PSTR(D_JSON_IR_DATALSB)]; @@ -528,7 +545,8 @@ uint32_t IrRemoteCmndIrSendJson(void) // protocol, bits, ulltoa(data, dvalue, 10), Uint64toHex(data, hvalue, bits), repeat); if (!IR_RCV_WHILE_SENDING && (irrecv != nullptr)) { irrecv->disableIRIn(); } - bool success = irsend->send(protocol, data, bits, repeat); + IRsend irsend = IrSendInitGPIO(channel); + bool success = irsend.send(protocol, data, bits, repeat); if (!IR_RCV_WHILE_SENDING && (irrecv != nullptr)) { irrecv->enableIRIn(); } if (!success) { @@ -553,8 +571,9 @@ uint32_t IrRemoteSendGC(char ** pp, uint32_t count, uint32_t repeat) { if (!GC[i]) { return IE_INVALID_RAWDATA; } } if (!IR_RCV_WHILE_SENDING && (irrecv != nullptr)) { irrecv->disableIRIn(); } + IRsend irsend = IrSendInitGPIO(); for (uint32_t r = 0; r <= repeat; r++) { - irsend->sendGC(GC, count+1); + irsend.sendGC(GC, count+1); } if (!IR_RCV_WHILE_SENDING && (irrecv != nullptr)) { irrecv->enableIRIn(); } return IE_NO_ERROR; @@ -608,11 +627,12 @@ uint32_t IrRemoteSendRawFormatted(char ** pp, uint32_t count, uint32_t repeat) { } } if (!IR_RCV_WHILE_SENDING && (irrecv != nullptr)) { irrecv->disableIRIn(); } + IRsend irsend = IrSendInitGPIO(); for (uint32_t r = 0; r <= repeat; r++) { // AddLog(LOG_LEVEL_DEBUG, PSTR("sendRaw count=%d, space=%d, mark=%d, freq=%d"), count, space, mark, freq); - irsend->sendRaw(raw_array, i, freq); + irsend.sendRaw(raw_array, i, freq); if (r < repeat) { // if it's not the last message - irsend->space(40000); // since we don't know the inter-message gap, place an arbitrary 40ms gap + irsend.space(40000); // since we don't know the inter-message gap, place an arbitrary 40ms gap } } if (!IR_RCV_WHILE_SENDING && (irrecv != nullptr)) { irrecv->enableIRIn(); } @@ -635,11 +655,12 @@ uint32_t IrRemoteSendRawFormatted(char ** pp, uint32_t count, uint32_t repeat) { } raw_array[i++] = parm[2]; // Trailing mark if (!IR_RCV_WHILE_SENDING && (irrecv != nullptr)) { irrecv->disableIRIn(); } + IRsend irsend = IrSendInitGPIO(); for (uint32_t r = 0; r <= repeat; r++) { // AddLog(LOG_LEVEL_DEBUG, PSTR("sendRaw %d %d %d %d %d %d"), raw_array[0], raw_array[1], raw_array[2], raw_array[3], raw_array[4], raw_array[5]); - irsend->sendRaw(raw_array, i, freq); + irsend.sendRaw(raw_array, i, freq); if (r < repeat) { // if it's not the last message - irsend->space(inter_message); // since we don't know the inter-message gap, place an arbitrary 40ms gap + irsend.space(inter_message); // since we don't know the inter-message gap, place an arbitrary 40ms gap } } if (!IR_RCV_WHILE_SENDING && (irrecv != nullptr)) { irrecv->enableIRIn(); } @@ -712,8 +733,9 @@ uint32_t IrRemoteSendRawStandard(char ** pp, uint16_t freq, uint32_t count, uint if (0 == count) { return IE_INVALID_RAWDATA; } if (!IR_RCV_WHILE_SENDING && (irrecv != nullptr)) { irrecv->disableIRIn(); } + IRsend irsend = IrSendInitGPIO(); for (uint32_t r = 0; r <= repeat; r++) { - irsend->sendRaw(arr, count, freq); + irsend.sendRaw(arr, count, freq); } if (!IR_RCV_WHILE_SENDING && (irrecv != nullptr)) { irrecv->enableIRIn(); } @@ -772,8 +794,7 @@ uint32_t IrRemoteCmndIrSendRaw(void) } } -void CmndIrSend(void) -{ +void CmndIrSend(void) { uint8_t error = IE_SYNTAX_IRSEND; if (XdrvMailbox.data_len) { @@ -815,6 +836,11 @@ void IrRemoteCmndResponse(uint32_t error) } } +void IrInit(void) { + ir_send_active = PinUsed(GPIO_IRSEND, GPIO_ANY); + ir_recv_active = PinUsed(GPIO_IRRECV, GPIO_ANY); +} + /*********************************************************************************************\ * Interface \*********************************************************************************************/ @@ -823,27 +849,23 @@ bool Xdrv05(uint8_t function) { bool result = false; - if (PinUsed(GPIO_IRSEND) || PinUsed(GPIO_IRRECV)) { - switch (function) { - case FUNC_PRE_INIT: - if (PinUsed(GPIO_IRSEND)) { - IrSendInit(); - } - if (PinUsed(GPIO_IRRECV)) { - IrReceiveInit(); - } - break; - case FUNC_EVERY_50_MSECOND: - if (PinUsed(GPIO_IRRECV)) { - IrReceiveCheck(); // check if there's anything on IR side - } - break; - case FUNC_COMMAND: - if (PinUsed(GPIO_IRSEND)) { - result = DecodeCommand(kIrRemoteCommands, IrRemoteCommand); - } - break; - } + switch (function) { + case FUNC_PRE_INIT: + IrInit(); + if (ir_recv_active) { + IrReceiveInit(); + } + break; + case FUNC_EVERY_50_MSECOND: + if (ir_recv_active) { + IrReceiveCheck(); // check if there's anything on IR side + } + break; + case FUNC_COMMAND: + if (ir_send_active) { + result = DecodeCommand(kIrRemoteCommands, IrRemoteCommand); + } + break; } return result; }