mirror of
https://github.com/arendst/Tasmota.git
synced 2025-07-27 04:36:31 +00:00
IR support data larger than 64 bits (#20831)
This commit is contained in:
parent
7ad95faad2
commit
a2bb0afea2
@ -14,6 +14,7 @@ All notable changes to this project will be documented in this file.
|
|||||||
- Show calculated heat index if temperature and humidity is available with ``#define USE_HEAT_INDEX`` (#4771)
|
- Show calculated heat index if temperature and humidity is available with ``#define USE_HEAT_INDEX`` (#4771)
|
||||||
- Berry add explicit error log when memory allocation fails (#20807)
|
- Berry add explicit error log when memory allocation fails (#20807)
|
||||||
- Support for AMS5915/AMS6915 temperature and pressure sensors (#20814)
|
- Support for AMS5915/AMS6915 temperature and pressure sensors (#20814)
|
||||||
|
- IR support data larger than 64 bits
|
||||||
|
|
||||||
### Breaking Changed
|
### Breaking Changed
|
||||||
|
|
||||||
|
@ -537,6 +537,7 @@
|
|||||||
// Commands xdrv_05_irremote.ino
|
// Commands xdrv_05_irremote.ino
|
||||||
#define D_CMND_IRSEND "IRSend"
|
#define D_CMND_IRSEND "IRSend"
|
||||||
#define D_JSON_INVALID_JSON "Invalid JSON"
|
#define D_JSON_INVALID_JSON "Invalid JSON"
|
||||||
|
#define D_JSON_INVALID_HEXDATA "Invalid Hex data"
|
||||||
#define D_JSON_INVALID_RAWDATA "Invalid RawData"
|
#define D_JSON_INVALID_RAWDATA "Invalid RawData"
|
||||||
#define D_JSON_NO_BUFFER_SPACE "No buffer space"
|
#define D_JSON_NO_BUFFER_SPACE "No buffer space"
|
||||||
#define D_JSON_PROTOCOL_NOT_SUPPORTED "Protocol not supported"
|
#define D_JSON_PROTOCOL_NOT_SUPPORTED "Protocol not supported"
|
||||||
|
@ -608,6 +608,34 @@ String HexToString(uint8_t* data, uint32_t length) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Converts a Hex string (case insensitive) into an array of bytes
|
||||||
|
// Returns the number of bytes in the array, or -1 if an error occured
|
||||||
|
// The `out` buffer must be at least half the size of hex string
|
||||||
|
int32_t HexToBytes(const char* hex, uint8_t* out, size_t* outLen) {
|
||||||
|
size_t len = strlen_P(hex);
|
||||||
|
*outLen = 0;
|
||||||
|
if (len % 2 != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t outLength = len / 2;
|
||||||
|
|
||||||
|
for(size_t i = 0; i < outLength; i++) {
|
||||||
|
char byte[3];
|
||||||
|
byte[0] = hex[i*2];
|
||||||
|
byte[1] = hex[i*2 + 1];
|
||||||
|
byte[2] = '\0';
|
||||||
|
|
||||||
|
char* endPtr;
|
||||||
|
out[i] = strtoul(byte, &endPtr, 16);
|
||||||
|
|
||||||
|
if(*endPtr != '\0') {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return outLength;
|
||||||
|
}
|
||||||
|
|
||||||
String UrlEncode(const String& text) {
|
String UrlEncode(const String& text) {
|
||||||
const char hex[] = "0123456789ABCDEF";
|
const char hex[] = "0123456789ABCDEF";
|
||||||
|
|
||||||
|
@ -72,7 +72,7 @@ def ir_expand(ir_compact):
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
enum IrErrors { IE_RESPONSE_PROVIDED, IE_NO_ERROR, IE_INVALID_RAWDATA, IE_INVALID_JSON, IE_SYNTAX_IRSEND, IE_SYNTAX_IRHVAC,
|
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, IE_MEMORY };
|
IE_UNSUPPORTED_HVAC, IE_UNSUPPORTED_PROTOCOL, IE_MEMORY, IE_INVALID_HEXDATA };
|
||||||
|
|
||||||
const char kIrRemoteCommands[] PROGMEM = "|"
|
const char kIrRemoteCommands[] PROGMEM = "|"
|
||||||
D_CMND_IRHVAC "|" D_CMND_IRSEND ; // No prefix
|
D_CMND_IRHVAC "|" D_CMND_IRSEND ; // No prefix
|
||||||
@ -544,6 +544,40 @@ void CmndIrHvac(void)
|
|||||||
if (error != IE_RESPONSE_PROVIDED) { IrRemoteCmndResponse(error); } // otherwise response was already provided
|
if (error != IE_RESPONSE_PROVIDED) { IrRemoteCmndResponse(error); } // otherwise response was already provided
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper function
|
||||||
|
// Reverse an arbitrary long field of bits
|
||||||
|
// Code produced by Mistral chatbot
|
||||||
|
void reverseBits(uint8_t* arr, int n) {
|
||||||
|
int numBytes = (n + 7) / 8; // number of bytes needed to represent n bits
|
||||||
|
|
||||||
|
// reverse bits in each byte
|
||||||
|
for(int i = 0; i < numBytes; i++) {
|
||||||
|
arr[i] = (arr[i] & 0x55) << 1 | (arr[i] & 0xAA) >> 1;
|
||||||
|
arr[i] = (arr[i] & 0x33) << 2 | (arr[i] & 0xCC) >> 2;
|
||||||
|
arr[i] = (arr[i] & 0x0F) << 4 | (arr[i] & 0xF0) >> 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
// reverse bytes
|
||||||
|
for(int i = 0; i < numBytes / 2; i++) {
|
||||||
|
uint8_t temp = arr[i];
|
||||||
|
arr[i] = arr[numBytes - i - 1];
|
||||||
|
arr[numBytes - i - 1] = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// reverse bits across bytes
|
||||||
|
int bitIndex = 0;
|
||||||
|
for(int i = numBytes - 1; i >= 0; i--) {
|
||||||
|
uint8_t byte = arr[i];
|
||||||
|
for(int j = 7; j >= 0 && bitIndex < n; j--, bitIndex++) {
|
||||||
|
if((byte >> j) & 1) {
|
||||||
|
arr[bitIndex / 8] |= 1 << (7 - (bitIndex % 8));
|
||||||
|
} else {
|
||||||
|
arr[bitIndex / 8] &= ~(1 << (7 - (bitIndex % 8)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*********************************************************************************************\
|
/*********************************************************************************************\
|
||||||
* Commands
|
* Commands
|
||||||
\*********************************************************************************************/
|
\*********************************************************************************************/
|
||||||
@ -552,6 +586,7 @@ uint32_t IrRemoteCmndIrSendJson(void)
|
|||||||
{
|
{
|
||||||
// IRsend { "protocol": "RC5", "bits": 12, "data":"0xC86" }
|
// IRsend { "protocol": "RC5", "bits": 12, "data":"0xC86" }
|
||||||
// IRsend { "protocol": "SAMSUNG", "bits": 32, "data": 551502015 }
|
// IRsend { "protocol": "SAMSUNG", "bits": 32, "data": 551502015 }
|
||||||
|
// IRsend {"Protocol":"CARRIER_AC84","Bits":84,"Data":0x0C04233200120012001204}
|
||||||
RemoveSpace(XdrvMailbox.data); // TODO is this really needed?
|
RemoveSpace(XdrvMailbox.data); // TODO is this really needed?
|
||||||
JsonParser parser(XdrvMailbox.data);
|
JsonParser parser(XdrvMailbox.data);
|
||||||
JsonParserObject root = parser.getRootObject();
|
JsonParserObject root = parser.getRootObject();
|
||||||
@ -567,31 +602,71 @@ uint32_t IrRemoteCmndIrSendJson(void)
|
|||||||
value = root[PSTR(D_JSON_IRHVAC_PROTOCOL)];
|
value = root[PSTR(D_JSON_IRHVAC_PROTOCOL)];
|
||||||
if (root) { protocol = strToDecodeType(value.getStr()); }
|
if (root) { protocol = strToDecodeType(value.getStr()); }
|
||||||
if (decode_type_t::UNKNOWN == protocol) { return IE_UNSUPPORTED_PROTOCOL; }
|
if (decode_type_t::UNKNOWN == protocol) { return IE_UNSUPPORTED_PROTOCOL; }
|
||||||
|
AddLog(LOG_LEVEL_INFO, PSTR("IRS: protocol %d"), protocol);
|
||||||
|
|
||||||
uint16_t bits = root.getUInt(PSTR(D_JSON_IR_BITS), 0);
|
uint16_t bits = root.getUInt(PSTR(D_JSON_IR_BITS), 0);
|
||||||
uint16_t repeat = root.getUInt(PSTR(D_JSON_IR_REPEAT), 0);
|
uint16_t repeat = root.getUInt(PSTR(D_JSON_IR_REPEAT), 0);
|
||||||
int8_t channel = root.getUInt(PSTR(D_JSON_IR_CHANNEL), 1) - 1;
|
int8_t channel = root.getUInt(PSTR(D_JSON_IR_CHANNEL), 1) - 1;
|
||||||
|
|
||||||
uint64_t data;
|
|
||||||
value = root[PSTR(D_JSON_IR_DATALSB)];
|
|
||||||
if (root) { data = reverseBitsInBytes64(value.getULong()); } // accept LSB values
|
|
||||||
value = root[PSTR(D_JSON_IR_DATA)];
|
|
||||||
if (value) { data = value.getULong(); } // or classical MSB (takes priority)
|
|
||||||
if (0 == bits) { return IE_SYNTAX_IRSEND; }
|
if (0 == bits) { return IE_SYNTAX_IRSEND; }
|
||||||
|
|
||||||
// check if the IRSend<x> is greater than repeat, but can be overriden with JSON
|
// check if the IRSend<x> is greater than repeat, but can be overriden with JSON
|
||||||
if (XdrvMailbox.index > repeat + 1) { repeat = XdrvMailbox.index - 1; }
|
if (XdrvMailbox.index > repeat + 1) { repeat = XdrvMailbox.index - 1; }
|
||||||
|
|
||||||
char dvalue[32];
|
bool success = false;
|
||||||
char hvalue[32];
|
if (bits <= 64) {
|
||||||
// AddLog(LOG_LEVEL_DEBUG, PSTR("IRS: protocol %d, bits %d, data 0x%s (%s), repeat %d"),
|
uint64_t data64; // for 64 bits and less
|
||||||
// protocol, bits, ulltoa(data, dvalue, 10), Uint64toHex(data, hvalue, bits), repeat);
|
value = root[PSTR(D_JSON_IR_DATALSB)];
|
||||||
|
if (value) { data64 = reverseBitsInBytes64(value.getULong()); } // accept LSB values
|
||||||
|
value = root[PSTR(D_JSON_IR_DATA)];
|
||||||
|
if (value) { data64 = value.getULong(); } // or classical MSB (takes priority)
|
||||||
|
|
||||||
if (!IR_RCV_WHILE_SENDING && (irrecv != nullptr)) { irrecv->pause(); }
|
if (!IR_RCV_WHILE_SENDING && (irrecv != nullptr)) { irrecv->pause(); }
|
||||||
IRsend irsend = IrSendInitGPIO(channel);
|
IRsend irsend = IrSendInitGPIO(channel);
|
||||||
bool success = irsend.send(protocol, data, bits, repeat);
|
// AddLog(LOG_LEVEL_INFO, PSTR("IRS: protocol %d, bits %d, data 0x%08X, repeat %d"), protocol, bits, (uint32_t) data64, repeat);
|
||||||
|
success = irsend.send(protocol, data64, bits, repeat);
|
||||||
if (!IR_RCV_WHILE_SENDING && (irrecv != nullptr)) { irrecv->resume(); }
|
if (!IR_RCV_WHILE_SENDING && (irrecv != nullptr)) { irrecv->resume(); }
|
||||||
|
|
||||||
|
} else {
|
||||||
|
uint8_t * data65 = nullptr; // for 65 bits and more
|
||||||
|
// bits >= 65
|
||||||
|
// Always MSB - LSB conversion would cost some decent
|
||||||
|
const char * data_hex = nullptr;
|
||||||
|
bool lsb = false;
|
||||||
|
value = root[PSTR(D_JSON_IR_DATALSB)];
|
||||||
|
if (value) { data_hex = value.getStr(); lsb = true; }
|
||||||
|
value = root[PSTR(D_JSON_IR_DATA)];
|
||||||
|
if (value) { data_hex = value.getStr(); lsb = false; }
|
||||||
|
|
||||||
|
if (!data_hex) { return IE_INVALID_HEXDATA; }
|
||||||
|
// check that the value starts with "0x" or "0X"
|
||||||
|
if ((data_hex[0] != '0') || ((data_hex[1] != 'x') && (data_hex[1] != 'X'))) {
|
||||||
|
AddLog(LOG_LEVEL_INFO, PSTR("IRS: data or data_lsb must start with '0x'"));
|
||||||
|
return IE_INVALID_HEXDATA;
|
||||||
|
}
|
||||||
|
data_hex += 2; // skip '0x'
|
||||||
|
size_t data_hex_len = strlen_P(data_hex);
|
||||||
|
|
||||||
|
// convert hex string to bytes
|
||||||
|
size_t num_bytes = (bits + 7) / 8;
|
||||||
|
|
||||||
|
if (num_bytes * 2 != data_hex_len) {
|
||||||
|
AddLog(LOG_LEVEL_INFO, PSTR("IRS: data or data_lsb must have %d digits"), num_bytes * 2);
|
||||||
|
return IE_INVALID_HEXDATA;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t data_bytes[num_bytes]; // allocate on stack since it's small enough
|
||||||
|
if (HexToBytes(data_hex, data_bytes, &num_bytes) <= 0) { return IE_INVALID_HEXDATA; }
|
||||||
|
|
||||||
|
if (lsb) {
|
||||||
|
reverseBits(data_bytes, bits);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IR_RCV_WHILE_SENDING && (irrecv != nullptr)) { irrecv->pause(); }
|
||||||
|
IRsend irsend = IrSendInitGPIO(channel);
|
||||||
|
// AddLog(LOG_LEVEL_INFO, PSTR("IRS: protocol %d, bits %d, data 0x%08X"), protocol, bits, *(uint32_t*)data_bytes);
|
||||||
|
success = irsend.send(protocol, data_bytes, (bits + 7) / 8);
|
||||||
|
if (!IR_RCV_WHILE_SENDING && (irrecv != nullptr)) { irrecv->resume(); }
|
||||||
|
}
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
ResponseCmndChar(D_JSON_PROTOCOL_NOT_SUPPORTED);
|
ResponseCmndChar(D_JSON_PROTOCOL_NOT_SUPPORTED);
|
||||||
}
|
}
|
||||||
@ -859,6 +934,9 @@ void IrRemoteCmndResponse(uint32_t error)
|
|||||||
case IE_INVALID_JSON:
|
case IE_INVALID_JSON:
|
||||||
ResponseCmndChar_P(PSTR(D_JSON_INVALID_JSON));
|
ResponseCmndChar_P(PSTR(D_JSON_INVALID_JSON));
|
||||||
break;
|
break;
|
||||||
|
case IE_INVALID_HEXDATA:
|
||||||
|
ResponseCmndChar_P(PSTR(D_JSON_INVALID_HEXDATA));
|
||||||
|
break;
|
||||||
case IE_SYNTAX_IRSEND:
|
case IE_SYNTAX_IRSEND:
|
||||||
Response_P(PSTR("{\"" D_CMND_IRSEND "\":\"" D_JSON_NO " " D_JSON_IR_BITS " " D_JSON_OR " " D_JSON_IR_DATA "\"}"));
|
Response_P(PSTR("{\"" D_CMND_IRSEND "\":\"" D_JSON_NO " " D_JSON_IR_BITS " " D_JSON_OR " " D_JSON_IR_DATA "\"}"));
|
||||||
break;
|
break;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user