/* xdrv_05_irremote_full.ino - complete integration of IRremoteESP8266 for Tasmota Copyright (C) 2019 Heiko Krupp, Lazar Obradovic, Theo Arends, Stephan Hadinger 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 . */ #ifdef USE_IR_REMOTE_FULL /*********************************************************************************************\ * IR Remote send and receive using IRremoteESP8266 library \*********************************************************************************************/ #define XDRV_05 5 #include #include #include #include #include enum IrErrors { IE_RESPONSE_PROVIDED, IE_NO_ERROR, IE_INVALID_RAWDATA, IE_INVALID_JSON, IE_SYNTAX_IRSEND, IE_SYNTAX_IRHVAC, IE_UNSUPPORTED_HVAC, IE_UNSUPPORTED_PROTOCOL }; const char kIrRemoteCommands[] PROGMEM = "|" D_CMND_IRHVAC "|" D_CMND_IRSEND ; // No prefix void (* const IrRemoteCommand[])(void) PROGMEM = { &CmndIrHvac, &CmndIrSend }; /*********************************************************************************************\ * IR Send \*********************************************************************************************/ IRsend *irsend = nullptr; bool irsend_active = false; void IrSendInit(void) { irsend = new IRsend(pin[GPIO_IRSEND]); // an IR led is at GPIO_IRSEND irsend->begin(); } // from https://stackoverflow.com/questions/2602823/in-c-c-whats-the-simplest-way-to-reverse-the-order-of-bits-in-a-byte // First the left four bits are swapped with the right four bits. Then all adjacent pairs are swapped and then all adjacent single bits. This results in a reversed order. uint8_t reverseBitsInByte(uint8_t b) { b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; b = (b & 0xCC) >> 2 | (b & 0x33) << 2; b = (b & 0xAA) >> 1 | (b & 0x55) << 1; return b; } // reverse bits in each byte uint64_t reverseBitsInBytes64(uint64_t b) { union { uint8_t b[8]; uint64_t i; } a; a.i = b; for (uint32_t i=0; i<8; i++) { a.b[i] = reverseBitsInByte(a.b[i]); } return a.i; } /*********************************************************************************************\ * IR Receive \*********************************************************************************************/ const bool IR_FULL_RCV_SAVE_BUFFER = false; // false = do not use buffer, true = use buffer for decoding const uint32_t IR_TIME_AVOID_DUPLICATE = 500; // Milliseconds // Below is from IRrecvDumpV2.ino // As this program is a special purpose capture/decoder, let us use a larger // than normal buffer so we can handle Air Conditioner remote codes. const uint16_t IR_FULL_BUFFER_SIZE = 1024; // Some A/C units have gaps in their protocols of ~40ms. e.g. Kelvinator // A value this large may swallow repeats of some protocols const uint8_t IR__FULL_RCV_TIMEOUT = 50; IRrecv *irrecv = nullptr; unsigned long ir_lasttime = 0; void IrReceiveUpdateThreshold() { if (irrecv != nullptr) { if (Settings.param[P_IR_UNKNOW_THRESHOLD] < 6) { Settings.param[P_IR_UNKNOW_THRESHOLD] = 6; } irrecv->setUnknownThreshold(Settings.param[P_IR_UNKNOW_THRESHOLD]); } } void IrReceiveInit(void) { // an IR led is at GPIO_IRRECV irrecv = new IRrecv(pin[GPIO_IRRECV], IR_FULL_BUFFER_SIZE, IR__FULL_RCV_TIMEOUT, IR_FULL_RCV_SAVE_BUFFER); irrecv->setUnknownThreshold(Settings.param[P_IR_UNKNOW_THRESHOLD]); irrecv->enableIRIn(); // Start the receiver } String sendACJsonState(const stdAc::state_t &state) { DynamicJsonBuffer jsonBuffer; JsonObject& json = jsonBuffer.createObject(); json[D_JSON_IRHVAC_VENDOR] = typeToString(state.protocol); json[D_JSON_IRHVAC_MODEL] = state.model; json[D_JSON_IRHVAC_POWER] = IRac::boolToString(state.power); json[D_JSON_IRHVAC_MODE] = IRac::opmodeToString(state.mode); // Home Assistant wants mode to be off if power is also off & vice-versa. if (state.mode == stdAc::opmode_t::kOff || !state.power) { json[D_JSON_IRHVAC_MODE] = IRac::opmodeToString(stdAc::opmode_t::kOff); json[D_JSON_IRHVAC_POWER] = IRac::boolToString(false); } json[D_JSON_IRHVAC_CELSIUS] = IRac::boolToString(state.celsius); if (floorf(state.degrees) == state.degrees) { json[D_JSON_IRHVAC_TEMP] = floorf(state.degrees); // integer } else { json[D_JSON_IRHVAC_TEMP] = RawJson(String(state.degrees, 1)); // non-integer, limit to only 1 sub-digit } json[D_JSON_IRHVAC_FANSPEED] = IRac::fanspeedToString(state.fanspeed); json[D_JSON_IRHVAC_SWINGV] = IRac::swingvToString(state.swingv); json[D_JSON_IRHVAC_SWINGH] = IRac::swinghToString(state.swingh); json[D_JSON_IRHVAC_QUIET] = IRac::boolToString(state.quiet); json[D_JSON_IRHVAC_TURBO] = IRac::boolToString(state.turbo); json[D_JSON_IRHVAC_ECONO] = IRac::boolToString(state.econo); json[D_JSON_IRHVAC_LIGHT] = IRac::boolToString(state.light); json[D_JSON_IRHVAC_FILTER] = IRac::boolToString(state.filter); json[D_JSON_IRHVAC_CLEAN] = IRac::boolToString(state.clean); json[D_JSON_IRHVAC_BEEP] = IRac::boolToString(state.beep); json[D_JSON_IRHVAC_SLEEP] = state.sleep; String payload = ""; payload.reserve(200); json.printTo(payload); return payload; } String sendIRJsonState(const struct decode_results &results) { String json("{"); json += "\"" D_JSON_IR_PROTOCOL "\":\""; json += typeToString(results.decode_type); json += "\",\"" D_JSON_IR_BITS "\":"; json += results.bits; if (hasACState(results.decode_type)) { json += ",\"" D_JSON_IR_DATA "\":\"0x"; json += resultToHexidecimal(&results); json += "\""; } else { if (UNKNOWN != results.decode_type) { json += ",\"" D_JSON_IR_DATA "\":"; } else { json += ",\"" D_JSON_IR_HASH "\":"; } if (Settings.flag.ir_receive_decimal) { char svalue[32]; ulltoa(results.value, svalue, 10); json += svalue; } else { char hvalue[64]; if (UNKNOWN != results.decode_type) { Uint64toHex(results.value, hvalue, results.bits); // Get 64bit value as hex 0x00123456 json += "\"0x"; json += hvalue; json += "\",\"" D_JSON_IR_DATALSB "\":\"0x"; Uint64toHex(reverseBitsInBytes64(results.value), hvalue, results.bits); // Get 64bit value as hex 0x00123456, LSB json += hvalue; json += "\""; } else { // UNKNOWN Uint64toHex(results.value, hvalue, 32); // Unknown is always 32 bits json += "\"0x"; json += hvalue; json += "\""; } } } json += ",\"" D_JSON_IR_REPEAT "\":"; json += results.repeat; stdAc::state_t ac_result; if (IRAcUtils::decodeToState(&results, &ac_result, nullptr)) { // we have a decoded state json += ",\"" D_CMND_IRHVAC "\":"; json += sendACJsonState(ac_result); } return json; } void IrReceiveCheck(void) { decode_results results; if (irrecv->decode(&results)) { uint32_t now = millis(); // if ((now - ir_lasttime > IR_TIME_AVOID_DUPLICATE) && (UNKNOWN != results.decode_type) && (results.bits > 0)) { if (!irsend_active && (now - ir_lasttime > IR_TIME_AVOID_DUPLICATE)) { ir_lasttime = now; Response_P(PSTR("{\"" D_JSON_IRRECEIVED "\":%s"), sendIRJsonState(results).c_str()); if (Settings.flag3.receive_raw) { ResponseAppend_P(PSTR(",\"" D_JSON_IR_RAWDATA "\":[")); uint16_t i; for (i = 1; i < results.rawlen; i++) { if (i > 1) { ResponseAppend_P(PSTR(",")); } uint32_t usecs; for (usecs = results.rawbuf[i] * kRawTick; usecs > UINT16_MAX; usecs -= UINT16_MAX) { ResponseAppend_P(PSTR("%d,0,"), UINT16_MAX); } ResponseAppend_P(PSTR("%d"), usecs); if (strlen(mqtt_data) > sizeof(mqtt_data) - 40) { break; } // Quit if char string becomes too long } uint16_t extended_length = results.rawlen - 1; for (uint32_t j = 0; j < results.rawlen - 1; j++) { uint32_t usecs = results.rawbuf[j] * kRawTick; // Add two extra entries for multiple larger than UINT16_MAX it is. extended_length += (usecs / (UINT16_MAX + 1)) * 2; } ResponseAppend_P(PSTR("],\"" D_JSON_IR_RAWDATA "Info\":[%d,%d,%d]"), extended_length, i -1, results.overflow); } ResponseJsonEndEnd(); MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_IRRECEIVED)); XdrvRulesProcess(); } irrecv->resume(); } } /*********************************************************************************************\ * IR Heating, Ventilation and Air Conditioning \*********************************************************************************************/ // list all supported protocols, either for IRSend or for IRHVAC, separated by '|' String listSupportedProtocols(bool hvac) { String l(""); bool first = true; for (uint32_t i = UNUSED + 1; i <= kLastDecodeType; i++) { bool found = false; if (hvac) { found = IRac::isProtocolSupported((decode_type_t)i); } else { found = (IRsend::defaultBits((decode_type_t)i) > 0) && (!IRac::isProtocolSupported((decode_type_t)i)); } if (found) { if (first) { first = false; } else { l += "|"; } l += typeToString((decode_type_t)i); } } return l; } // used to convert values 0-5 to fanspeed_t const stdAc::fanspeed_t IrHvacFanSpeed[] PROGMEM = { stdAc::fanspeed_t::kAuto, stdAc::fanspeed_t::kMin, stdAc::fanspeed_t::kLow,stdAc::fanspeed_t::kMedium, stdAc::fanspeed_t::kHigh, stdAc::fanspeed_t::kMax }; uint32_t IrRemoteCmndIrHvacJson(void) { stdAc::state_t state, prev; char parm_uc[12]; //AddLog_P2(LOG_LEVEL_DEBUG, PSTR("IRHVAC: Received %s"), XdrvMailbox.data); char dataBufUc[XdrvMailbox.data_len]; UpperCase(dataBufUc, XdrvMailbox.data); RemoveSpace(dataBufUc); if (strlen(dataBufUc) < 8) { return IE_INVALID_JSON; } DynamicJsonBuffer jsonBuf; JsonObject &json = jsonBuf.parseObject(dataBufUc); if (!json.success()) { return IE_INVALID_JSON; } // from: https://github.com/crankyoldgit/IRremoteESP8266/blob/master/examples/CommonAcControl/CommonAcControl.ino state.protocol = decode_type_t::UNKNOWN; state.model = 1; // Some A/C's have different models. Let's try using just 1. state.mode = stdAc::opmode_t::kAuto; // Run in cool mode initially. state.power = false; // Initially start with the unit off. state.celsius = true; // Use Celsius for units of temp. False = Fahrenheit state.degrees = 21.0f; // 21 degrees. state.fanspeed = stdAc::fanspeed_t::kMedium; // Start with the fan at medium. state.swingv = stdAc::swingv_t::kOff; // Don't swing the fan up or down. state.swingh = stdAc::swingh_t::kOff; // Don't swing the fan left or right. state.light = false; // Turn off any LED/Lights/Display that we can. state.beep = false; // Turn off any beep from the A/C if we can. state.econo = false; // Turn off any economy modes if we can. state.filter = false; // Turn off any Ion/Mold/Health filters if we can. state.turbo = false; // Don't use any turbo/powerful/etc modes. state.quiet = false; // Don't use any quiet/silent/etc modes. state.sleep = -1; // Don't set any sleep time or modes. state.clean = false; // Turn off any Cleaning options if we can. state.clock = -1; // Don't set any current time if we can avoid it. UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_VENDOR)); if (json.containsKey(parm_uc)) { state.protocol = strToDecodeType(json[parm_uc]); } UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_PROTOCOL)); if (json.containsKey(parm_uc)) { state.protocol = strToDecodeType(json[parm_uc]); } // also support 'protocol' if (decode_type_t::UNKNOWN == state.protocol) { return IE_UNSUPPORTED_HVAC; } if (!IRac::isProtocolSupported(state.protocol)) { return IE_UNSUPPORTED_HVAC; } // for fan speed, we also support 1-5 values UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_FANSPEED)); if (json.containsKey(parm_uc)) { uint32_t fan_speed = json[parm_uc]; if ((fan_speed >= 1) && (fan_speed <= 5)) { state.fanspeed = (stdAc::fanspeed_t) pgm_read_byte(&IrHvacFanSpeed[fan_speed]); } else { state.fanspeed = IRac::strToFanspeed(json[parm_uc]); } } UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_MODEL)); if (json.containsKey(parm_uc)) { state.model = IRac::strToModel(json[parm_uc]); } UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_MODE)); if (json.containsKey(parm_uc)) { state.mode = IRac::strToOpmode(json[parm_uc]); } UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_SWINGV)); if (json.containsKey(parm_uc)) { state.swingv = IRac::strToSwingV(json[parm_uc]); } UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_SWINGH)); if (json.containsKey(parm_uc)) { state.swingh = IRac::strToSwingH(json[parm_uc]); } UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_TEMP)); if (json.containsKey(parm_uc)) { state.degrees = json[parm_uc]; } // AddLog_P2(LOG_LEVEL_DEBUG, PSTR("model %d, mode %d, fanspeed %d, swingv %d, swingh %d"), // state.model, state.mode, state.fanspeed, state.swingv, state.swingh); // decode booleans UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_POWER)); if (json.containsKey(parm_uc)) { state.power = IRac::strToBool(json[parm_uc]); } UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_CELSIUS)); if (json.containsKey(parm_uc)) { state.celsius = IRac::strToBool(json[parm_uc]); } UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_LIGHT)); if (json.containsKey(parm_uc)) { state.light = IRac::strToBool(json[parm_uc]); } UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_BEEP)); if (json.containsKey(parm_uc)) { state.beep = IRac::strToBool(json[parm_uc]); } UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_ECONO)); if (json.containsKey(parm_uc)) { state.econo = IRac::strToBool(json[parm_uc]); } UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_FILTER)); if (json.containsKey(parm_uc)) { state.filter = IRac::strToBool(json[parm_uc]); } UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_TURBO)); if (json.containsKey(parm_uc)) { state.turbo = IRac::strToBool(json[parm_uc]); } UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_QUIET)); if (json.containsKey(parm_uc)) { state.quiet = IRac::strToBool(json[parm_uc]); } UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_CLEAN)); if (json.containsKey(parm_uc)) { state.clean = IRac::strToBool(json[parm_uc]); } // optional timer and clock UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_SLEEP)); if (json[parm_uc]) { state.sleep = json[parm_uc]; } //if (json[D_JSON_IRHVAC_CLOCK]) { state.clock = json[D_JSON_IRHVAC_CLOCK]; } // not sure it's useful to support 'clock' IRac ac(pin[GPIO_IRSEND]); bool success = ac.sendAc(state, &prev); if (!success) { return IE_SYNTAX_IRHVAC; } Response_P(PSTR("{\"" D_CMND_IRHVAC "\":%s}"), sendACJsonState(state).c_str()); return IE_RESPONSE_PROVIDED; } void CmndIrHvac(void) { uint8_t error = IE_SYNTAX_IRHVAC; if (XdrvMailbox.data_len) { error = IrRemoteCmndIrHvacJson(); } if (error != IE_RESPONSE_PROVIDED) { IrRemoteCmndResponse(error); } // otherwise response was already provided } /*********************************************************************************************\ * Commands \*********************************************************************************************/ uint32_t IrRemoteCmndIrSendJson(void) { char parm_uc[12]; // used to convert JSON keys to uppercase // ArduinoJSON entry used to calculate jsonBuf: JSON_OBJECT_SIZE(3) + 40 = 96 // IRsend { "protocol": "RC5", "bits": 12, "data":"0xC86" } // IRsend { "protocol": "SAMSUNG", "bits": 32, "data": 551502015 } char dataBufUc[XdrvMailbox.data_len]; UpperCase(dataBufUc, XdrvMailbox.data); RemoveSpace(dataBufUc); if (strlen(dataBufUc) < 8) { return IE_INVALID_JSON; } DynamicJsonBuffer jsonBuf; JsonObject &json = jsonBuf.parseObject(dataBufUc); if (!json.success()) { return IE_INVALID_JSON; } // IRsend { "protocol": "SAMSUNG", "bits": 32, "data": 551502015 } // IRsend { "protocol": "NEC", "bits": 32, "data":"0x02FDFE80", "repeat": 2 } decode_type_t protocol = decode_type_t::UNKNOWN; uint16_t bits = 0; uint64_t data; uint8_t repeat = 0; UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_VENDOR)); if (json.containsKey(parm_uc)) { protocol = strToDecodeType(json[parm_uc]); } UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_PROTOCOL)); if (json.containsKey(parm_uc)) { protocol = strToDecodeType(json[parm_uc]); } // also support 'protocol' if (decode_type_t::UNKNOWN == protocol) { return IE_UNSUPPORTED_PROTOCOL; } UpperCase_P(parm_uc, PSTR(D_JSON_IR_BITS)); if (json.containsKey(parm_uc)) { bits = json[parm_uc]; } UpperCase_P(parm_uc, PSTR(D_JSON_IR_REPEAT)); if (json.containsKey(parm_uc)) { repeat = json[parm_uc]; } UpperCase_P(parm_uc, PSTR(D_JSON_IR_DATALSB)); // accept LSB values if (json.containsKey(parm_uc)) { data = reverseBitsInBytes64(strtoull(json[parm_uc], nullptr, 0)); } UpperCase_P(parm_uc, PSTR(D_JSON_IR_DATA)); // or classical MSB (takes priority) if (json.containsKey(parm_uc)) { data = strtoull(json[parm_uc], nullptr, 0); } if (0 == bits) { return IE_SYNTAX_IRSEND; } // check if the IRSend is greater than repeat, but can be overriden with JSON if (XdrvMailbox.index > repeat + 1) { repeat = XdrvMailbox.index - 1; } char dvalue[32]; char hvalue[32]; AddLog_P2(LOG_LEVEL_DEBUG, PSTR("IRS: protocol %d, bits %d, data 0x%s (%s), repeat %d"), protocol, bits, ulltoa(data, dvalue, 10), Uint64toHex(data, hvalue, bits), repeat); irsend_active = true; // deactivate receive bool success = irsend->send(protocol, data, bits, repeat); if (!success) { irsend_active = false; ResponseCmndChar(D_JSON_PROTOCOL_NOT_SUPPORTED); } return IE_NO_ERROR; } uint32_t IrRemoteCmndIrSendRaw(void) { // IRsend ,, ... // or // IRsend raw,,, (one space = zero space *2) // IRsend raw,,,, // IRsend raw,,,, // IRsend raw,,
,
,,,, char *p; char *str = strtok_r(XdrvMailbox.data, ", ", &p); if (p == nullptr) { return IE_INVALID_RAWDATA; } // repeat uint16_t repeat = XdrvMailbox.index > 0 ? XdrvMailbox.index - 1 : 0; uint16_t freq = atoi(str); if (!freq && (*str != '0')) { // First parameter is any string uint16_t count = 0; char *q = p; for (; *q; count += (*q++ == ',')); if (count < 2) { return IE_INVALID_RAWDATA; } // Parameters must be at least 3 uint16_t parm[count]; for (uint32_t i = 0; i < count; i++) { parm[i] = strtol(strtok_r(nullptr, ", ", &p), nullptr, 0); if (!parm[i]) { if (!i) { parm[0] = 38000; // Frequency default to 38kHz } else { return IE_INVALID_RAWDATA; // Other parameters may not be 0 } } } uint16_t i = 0; if (count < 4) { // IRsend raw,0,889,000000100110000001001 uint16_t mark = parm[1] *2; // Protocol where 0 = t, 1 = 2t (RC5) if (3 == count) { if (parm[2] < parm[1]) { // IRsend raw,0,889,2,000000100110000001001 mark = parm[1] * parm[2]; // Protocol where 0 = t1, 1 = t1*t2 (Could be RC5) } else { // IRsend raw,0,889,1778,000000100110000001001 mark = parm[2]; // Protocol where 0 = t1, 1 = t2 (Could be RC5) } } uint16_t raw_array[strlen(p)]; // Bits for (; *p; *p++) { if (*p == '0') { raw_array[i++] = parm[1]; // Space } else if (*p == '1') { raw_array[i++] = mark; // Mark } } irsend_active = true; for (uint32_t r = 0; r <= repeat; r++) { irsend->sendRaw(raw_array, i, parm[0]); 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 } } } else if (6 == count) { // NEC Protocol // IRsend raw,0,8620,4260,544,411,1496,010101101000111011001110000000001100110000000001100000000000000010001100 uint16_t raw_array[strlen(p)*2+3]; // Header + bits + end raw_array[i++] = parm[1]; // Header mark raw_array[i++] = parm[2]; // Header space uint32_t inter_message_32 = (parm[1] + parm[2]) * 3; // compute an inter-message gap (32 bits) uint16_t inter_message = (inter_message_32 > 65000) ? 65000 : inter_message_32; // avoid 16 bits overflow for (; *p; *p++) { if (*p == '0') { raw_array[i++] = parm[3]; // Bit mark raw_array[i++] = parm[4]; // Zero space } else if (*p == '1') { raw_array[i++] = parm[3]; // Bit mark raw_array[i++] = parm[5]; // One space } } raw_array[i++] = parm[3]; // Trailing mark irsend_active = true; for (uint32_t r = 0; r <= repeat; r++) { irsend->sendRaw(raw_array, i, parm[0]); 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 } } } else { return IE_INVALID_RAWDATA; // Invalid number of parameters } } else { if (!freq) { freq = 38000; } // Default to 38kHz uint16_t count = 0; char *q = p; for (; *q; count += (*q++ == ',')); if (0 == count) { return IE_INVALID_RAWDATA; } // IRsend 0,896,876,900,888,894,876,1790,874,872,1810,1736,948,872,880,872,936,872,1792,900,888,1734 count++; if (count < 200) { uint16_t raw_array[count]; // It's safe to use stack for up to 200 packets (limited by mqtt_data length) for (uint32_t i = 0; i < count; i++) { raw_array[i] = strtol(strtok_r(nullptr, ", ", &p), nullptr, 0); // Allow decimal (20496) and hexadecimal (0x5010) input } // AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DBG: stack count %d"), count); irsend_active = true; for (uint32_t r = 0; r <= repeat; r++) { irsend->sendRaw(raw_array, count, freq); } } else { uint16_t *raw_array = reinterpret_cast(malloc(count * sizeof(uint16_t))); if (raw_array == nullptr) { return IE_INVALID_RAWDATA; } for (uint32_t i = 0; i < count; i++) { raw_array[i] = strtol(strtok_r(nullptr, ", ", &p), nullptr, 0); // Allow decimal (20496) and hexadecimal (0x5010) input } // AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DBG: heap count %d"), count); irsend_active = true; for (uint32_t r = 0; r <= repeat; r++) { irsend->sendRaw(raw_array, count, freq); } free(raw_array); } } return IE_NO_ERROR; } void CmndIrSend(void) { uint8_t error = IE_SYNTAX_IRSEND; if (XdrvMailbox.data_len) { if (strstr(XdrvMailbox.data, "{") == nullptr) { error = IrRemoteCmndIrSendRaw(); } else { error = IrRemoteCmndIrSendJson(); } } IrRemoteCmndResponse(error); } void IrRemoteCmndResponse(uint32_t error) { switch (error) { case IE_INVALID_RAWDATA: ResponseCmndChar(D_JSON_INVALID_RAWDATA); break; case IE_INVALID_JSON: ResponseCmndChar(D_JSON_INVALID_JSON); break; case IE_SYNTAX_IRSEND: Response_P(PSTR("{\"" D_CMND_IRSEND "\":\"" D_JSON_NO " " D_JSON_IR_BITS " " D_JSON_OR " " D_JSON_IR_DATA "\"}")); break; case IE_SYNTAX_IRHVAC: Response_P(PSTR("{\"" D_CMND_IRHVAC "\":\"" D_JSON_WRONG " " D_JSON_IRHVAC_VENDOR ", " D_JSON_IRHVAC_MODE " " D_JSON_OR " " D_JSON_IRHVAC_FANSPEED "\"}")); break; case IE_UNSUPPORTED_HVAC: Response_P(PSTR("{\"" D_CMND_IRHVAC "\":\"" D_JSON_WRONG " " D_JSON_IRHVAC_VENDOR " (%s)\"}"), listSupportedProtocols(true).c_str()); break; case IE_UNSUPPORTED_PROTOCOL: Response_P(PSTR("{\"" D_CMND_IRSEND "\":\"" D_JSON_WRONG " " D_JSON_IRHVAC_PROTOCOL " (%s)\"}"), listSupportedProtocols(false).c_str()); break; default: // IE_NO_ERROR ResponseCmndDone(); } } /*********************************************************************************************\ * Interface \*********************************************************************************************/ bool Xdrv05(uint8_t function) { bool result = false; if ((pin[GPIO_IRSEND] < 99) || (pin[GPIO_IRRECV] < 99)) { switch (function) { case FUNC_PRE_INIT: if (pin[GPIO_IRSEND] < 99) { IrSendInit(); } if (pin[GPIO_IRRECV] < 99) { IrReceiveInit(); } break; case FUNC_EVERY_50_MSECOND: if (pin[GPIO_IRRECV] < 99) { IrReceiveCheck(); // check if there's anything on IR side } irsend_active = false; // re-enable IR reception break; case FUNC_COMMAND: if (pin[GPIO_IRSEND] < 99) { result = DecodeCommand(kIrRemoteCommands, IrRemoteCommand); } break; } } return result; } #endif // USE_IR_REMOTE_FULL