2019-08-28 21:01:01 +01:00
/*
2019-10-27 10:13:24 +00:00
xdrv_05_irremote_full . ino - complete integration of IRremoteESP8266 for Tasmota
2019-08-28 21:01:01 +01:00
2021-01-01 12:44:04 +00:00
Copyright ( C ) 2021 Heiko Krupp , Lazar Obradovic , Theo Arends , Stephan Hadinger
2019-08-28 21:01:01 +01:00
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/>.
*/
2020-10-01 21:34:26 +01:00
/*
Below is the Pyhton3 code to decompress IR comact format .
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
import re
def ir_expand ( ir_compact ) :
count = ir_compact . count ( ' , ' ) # number of occurence of comma
if count > 1 :
return " Unsupported format "
if count = = 1 :
ir_compact = input . split ( ' , ' ) [ 1 ] # if 1 comma , skip the frequency
arr = re . findall ( " ( \ d+|[A-Za-z]) " , ir_compact )
comp_table = [ ] # compression history table
arr2 = [ ] # output
for elt in arr :
if len ( elt ) = = 1 :
c = ord ( elt . upper ( ) ) - ord ( ' A ' )
if c > = len ( arr ) : return " Error index undefined "
arr2 . append ( comp_table [ c ] )
else :
comp_table . append ( elt )
arr2 . append ( elt )
out = " , " . join ( arr2 )
return out
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
*/
2019-08-28 21:01:01 +01:00
# ifdef USE_IR_REMOTE_FULL
/*********************************************************************************************\
* IR Remote send and receive using IRremoteESP8266 library
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
# define XDRV_05 5
# include <IRremoteESP8266.h>
# include <IRsend.h>
# include <IRrecv.h>
2019-08-30 21:33:21 +01:00
# include <IRutils.h>
2019-08-28 21:01:01 +01:00
# include <IRac.h>
2020-12-26 18:45:06 +00:00
// Receiving IR while sending at the same time (i.e. receiving your own signal) was dsiabled in #10041
// At the demand of @pilaGit, you can `#define IR_RCV_WHILE_SENDING 1` to bring back this behavior
# ifndef IR_RCV_WHILE_SENDING
# define IR_RCV_WHILE_SENDING 0
# endif
2019-08-28 21:01:01 +01:00
enum IrErrors { IE_RESPONSE_PROVIDED , IE_NO_ERROR , IE_INVALID_RAWDATA , IE_INVALID_JSON , IE_SYNTAX_IRSEND , IE_SYNTAX_IRHVAC ,
2024-02-28 21:42:29 +00:00
IE_UNSUPPORTED_HVAC , IE_UNSUPPORTED_PROTOCOL , IE_MEMORY , IE_INVALID_HEXDATA } ;
2019-08-28 21:01:01 +01:00
2019-11-24 11:24:35 +00:00
const char kIrRemoteCommands [ ] PROGMEM = " | "
D_CMND_IRHVAC " | " D_CMND_IRSEND ; // No prefix
2019-08-28 21:01:01 +01:00
2019-11-24 11:24:35 +00:00
void ( * const IrRemoteCommand [ ] ) ( void ) PROGMEM = {
& CmndIrHvac , & CmndIrSend } ;
2019-08-28 21:01:01 +01:00
2022-08-02 21:52:35 +01:00
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
2020-10-01 21:34:26 +01:00
/*********************************************************************************************\
* Class used to make a compact IR Raw format .
2020-10-30 11:29:48 +00:00
*
2020-10-01 21:34:26 +01:00
* We round timings to the closest 10 ms value ,
* and store up to last 26 values with seen .
* A value already seen is encoded with a letter indicating the position in the table .
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
class IRRawTable {
public :
IRRawTable ( ) : timings ( ) { } // zero initialize the array
int32_t getTimingForLetter ( uint8_t l ) const {
l = toupper ( l ) ;
if ( ( l < ' A ' ) | | ( l > ' Z ' ) ) { return - 1 ; }
return timings [ l - ' A ' ] ;
}
uint8_t findOrAdd ( uint16_t t ) {
if ( 0 = = t ) { return 0 ; }
for ( uint32_t i = 0 ; i < 26 ; i + + ) {
if ( timings [ i ] = = t ) { return i + ' A ' ; }
if ( timings [ i ] = = 0 ) { timings [ i ] = t ; break ; } // add new value
}
return 0 ; // not found
}
void add ( uint16_t t ) {
if ( 0 = = t ) { return ; }
for ( uint32_t i = 0 ; i < 26 ; i + + ) {
if ( timings [ i ] = = 0 ) { timings [ i ] = t ; break ; } // add new value
}
}
protected :
uint16_t timings [ 26 ] ;
} ;
2019-08-28 21:01:01 +01:00
/*********************************************************************************************\
* IR Send
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2020-09-20 22:18:00 +01:00
// 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 ;
2023-06-10 13:53:02 +01:00
bool irhvac_incremental = true ;
2020-09-20 22:18:00 +01:00
stdAc : : state_t irac_prev_state ; // this implementations only keeps one state so if you use a single tasmota-ir device to command more than one AC it might not work
2019-08-28 21:01:01 +01:00
2020-10-06 16:10:07 +01:00
// different modes on how to handle state when sending HVAC commands. needed for ACs with a differential/toggle protocol.
enum class StateModes { SEND_ONLY , // just send the IR signal, this is the default. expect the state to update when the IR receiver gets the command that IR transmitter sent.
STORE_ONLY , // just update the state to what is provided, this is when one needs to sync actual and stored states.
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
2022-08-02 21:52:35 +01:00
// 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 ;
2019-08-28 21:01:01 +01:00
}
// 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
2021-03-22 04:03:44 +00:00
# ifndef IR_TIME_AVOID_DUPLICATE
2021-03-22 04:25:01 +00:00
# define IR_TIME_AVOID_DUPLICATE 50 // Milliseconds
2021-03-22 04:03:44 +00:00
# endif // IR_TIME_AVOID_DUPLICATE
2019-08-28 21:01:01 +01:00
// 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 ;
2019-11-20 19:53:12 +00:00
void IrReceiveUpdateThreshold ( void )
2019-08-28 21:01:01 +01:00
{
if ( irrecv ! = nullptr ) {
2021-06-11 17:14:12 +01:00
if ( Settings - > param [ P_IR_UNKNOW_THRESHOLD ] < 6 ) { Settings - > param [ P_IR_UNKNOW_THRESHOLD ] = 6 ; }
irrecv - > setUnknownThreshold ( Settings - > param [ P_IR_UNKNOW_THRESHOLD ] ) ;
2019-08-28 21:01:01 +01:00
}
}
2022-01-21 15:24:32 +00:00
void IrReceiveUpdateTolerance ( void )
{
if ( irrecv ! = nullptr ) {
2022-01-21 16:56:19 +00:00
if ( Settings - > param [ P_IR_TOLERANCE ] = = 0 ) { Settings - > param [ P_IR_TOLERANCE ] = IR_RCV_TOLERANCE ; }
2022-01-21 15:24:32 +00:00
if ( Settings - > param [ P_IR_TOLERANCE ] > 100 ) { Settings - > param [ P_IR_TOLERANCE ] = 100 ; }
irrecv - > setTolerance ( Settings - > param [ P_IR_TOLERANCE ] ) ;
}
}
2019-08-28 21:01:01 +01:00
void IrReceiveInit ( void )
{
// an IR led is at GPIO_IRRECV
2020-04-26 16:33:27 +01:00
irrecv = new IRrecv ( Pin ( GPIO_IRRECV ) , IR_FULL_BUFFER_SIZE , IR__FULL_RCV_TIMEOUT , IR_FULL_RCV_SAVE_BUFFER ) ;
2021-06-11 17:14:12 +01:00
irrecv - > setUnknownThreshold ( Settings - > param [ P_IR_UNKNOW_THRESHOLD ] ) ;
2022-02-07 12:51:02 +00:00
IrReceiveUpdateTolerance ( ) ;
2019-08-28 21:01:01 +01:00
irrecv - > enableIRIn ( ) ; // Start the receiver
}
2023-05-09 20:35:56 +01:00
namespace {
void addFloatToJson ( JsonGeneratorObject & json , const char * key , float value , float noValueConstant = NAN ) {
if ( ! isnan ( noValueConstant ) & & value = = noValueConstant ) {
//The "no sensor value" may not be straightforward (e.g.-100.0), hence replacing with explicit n/a
2023-08-26 14:04:08 +01:00
json . addStrRaw ( key , D_JSON_NULL ) ;
2023-05-09 20:35:56 +01:00
return ;
}
char s [ 6 ] ; // Range: -99.9 <> 999.9 should be fine for any sensible temperature value :)
ext_snprintf_P ( s , sizeof ( s ) , PSTR ( " %-1_f " ) , & value ) ;
json . addStrRaw ( key , s ) ;
}
void addModelToJson ( JsonGeneratorObject & json , const char * key , decode_type_t protocol , const int16_t model ) {
String modelStr = irutils : : modelToStr ( protocol , model ) ;
if ( modelStr ! = kUnknownStr ) {
json . add ( key , modelStr ) ;
} else { // Fallback to int value
2023-09-17 20:12:36 +01:00
json . add ( key , ( int32_t ) model ) ;
2023-05-09 20:35:56 +01:00
}
}
} // namespace {
2019-08-28 21:01:01 +01:00
String sendACJsonState ( const stdAc : : state_t & state ) {
2020-09-24 07:51:43 +01:00
JsonGeneratorObject json ;
json . add ( PSTR ( D_JSON_IRHVAC_VENDOR ) , typeToString ( state . protocol ) ) ;
2023-05-09 20:35:56 +01:00
addModelToJson ( json , PSTR ( D_JSON_IRHVAC_MODEL ) , state . protocol , state . model ) ;
json . add ( PSTR ( D_JSON_IRHVAC_COMMAND ) , IRac : : commandTypeToString ( state . command ) ) ;
switch ( state . command ) {
case stdAc : : ac_command_t : : kSensorTempReport :
addFloatToJson ( json , PSTR ( D_JSON_IRHVAC_SENSOR_TEMP ) , state . sensorTemperature , kNoTempValue ) ;
break ;
case stdAc : : ac_command_t : : kConfigCommand :
// Note: for `kConfigCommand` the semantics of clock/sleep is abused IRremoteESP8266 lib-side for key/value pair
// Ref: lib/lib_basic/IRremoteESP8266/IRremoteESP8266/src/IRac.cpp[L3062-3066]
2023-09-17 20:12:36 +01:00
json . add ( PSTR ( D_JSON_IRHVAC_CONFIG_KEY ) , ( int32_t ) state . clock ) ;
json . add ( PSTR ( D_JSON_IRHVAC_CONFIG_VALUE ) , ( int32_t ) state . sleep ) ;
2023-05-09 20:35:56 +01:00
break ;
case stdAc : : ac_command_t : : kTimerCommand :
json . add ( PSTR ( D_JSON_IRHVAC_POWER ) , IRac : : boolToString ( state . power ) ) ;
if ( state . clock ! = - 1 ) { json . add ( PSTR ( D_JSON_IRHVAC_CLOCK ) , irutils : : minsToString ( state . clock ) ) ; }
2023-09-17 20:12:36 +01:00
json . add ( PSTR ( D_JSON_IRHVAC_SLEEP ) , ( int32_t ) state . sleep ) ;
2023-05-09 20:35:56 +01:00
break ;
case stdAc : : ac_command_t : : kControlCommand :
default :
json . add ( PSTR ( D_JSON_IRHVAC_MODE ) , IRac : : opmodeToString ( state . mode ) ) ;
// Home Assistant wants power to be off if mode is also off.
if ( state . mode = = stdAc : : opmode_t : : kOff ) {
json . add ( PSTR ( D_JSON_IRHVAC_POWER ) , IRac : : boolToString ( false ) ) ;
} else {
json . add ( PSTR ( D_JSON_IRHVAC_POWER ) , IRac : : boolToString ( state . power ) ) ;
}
json . add ( PSTR ( D_JSON_IRHVAC_CELSIUS ) , IRac : : boolToString ( state . celsius ) ) ;
addFloatToJson ( json , PSTR ( D_JSON_IRHVAC_TEMP ) , state . degrees ) ;
json . add ( PSTR ( D_JSON_IRHVAC_FANSPEED ) , IRac : : fanspeedToString ( state . fanspeed ) ) ;
json . add ( PSTR ( D_JSON_IRHVAC_SWINGV ) , IRac : : swingvToString ( state . swingv ) ) ;
json . add ( PSTR ( D_JSON_IRHVAC_SWINGH ) , IRac : : swinghToString ( state . swingh ) ) ;
json . add ( PSTR ( D_JSON_IRHVAC_QUIET ) , IRac : : boolToString ( state . quiet ) ) ;
json . add ( PSTR ( D_JSON_IRHVAC_TURBO ) , IRac : : boolToString ( state . turbo ) ) ;
json . add ( PSTR ( D_JSON_IRHVAC_ECONO ) , IRac : : boolToString ( state . econo ) ) ;
json . add ( PSTR ( D_JSON_IRHVAC_LIGHT ) , IRac : : boolToString ( state . light ) ) ;
json . add ( PSTR ( D_JSON_IRHVAC_FILTER ) , IRac : : boolToString ( state . filter ) ) ;
json . add ( PSTR ( D_JSON_IRHVAC_CLEAN ) , IRac : : boolToString ( state . clean ) ) ;
json . add ( PSTR ( D_JSON_IRHVAC_BEEP ) , IRac : : boolToString ( state . beep ) ) ;
2023-09-17 20:12:36 +01:00
json . add ( PSTR ( D_JSON_IRHVAC_SLEEP ) , ( int32_t ) state . sleep ) ;
if ( state . clock ! = - 1 ) { json . add ( PSTR ( D_JSON_IRHVAC_CLOCK ) , ( int32_t ) state . clock ) ; }
2023-05-09 20:35:56 +01:00
json . add ( PSTR ( D_JSON_IRHVAC_IFEEL ) , IRac : : boolToString ( state . iFeel ) ) ;
addFloatToJson ( json , PSTR ( D_JSON_IRHVAC_SENSOR_TEMP ) , state . sensorTemperature , kNoTempValue ) ;
break ;
2019-08-28 21:01:01 +01:00
}
2020-09-24 07:51:43 +01:00
String payload = json . toString ( ) ; // copy string before returning, the original is on the stack
2019-08-28 21:01:01 +01:00
return payload ;
}
2021-01-24 15:35:36 +00:00
void sendIRJsonState ( const struct decode_results & results ) {
2021-02-01 10:54:50 +00:00
ResponseAppend_P ( PSTR ( " \" " D_JSON_IR_PROTOCOL " \" : \" %s \" , \" " D_JSON_IR_BITS " \" :%d " ) ,
2021-01-24 15:35:36 +00:00
typeToString ( results . decode_type ) . c_str ( ) ,
results . bits ) ;
2019-08-28 21:01:01 +01:00
if ( hasACState ( results . decode_type ) ) {
2021-01-24 15:35:36 +00:00
ResponseAppend_P ( PSTR ( " , \" " D_JSON_IR_DATA " \" : \" %s \" " ) ,
resultToHexidecimal ( & results ) . c_str ( ) ) ;
2019-08-28 21:01:01 +01:00
} else {
2021-01-24 15:35:36 +00:00
ResponseAppend_P ( PSTR ( " , \" %s \" : " ) , UNKNOWN ! = results . decode_type ? PSTR ( D_JSON_IR_DATA ) : PSTR ( D_JSON_IR_HASH ) ) ;
2021-06-11 17:14:12 +01:00
if ( Settings - > flag . ir_receive_decimal ) { // SetOption29 - IR receive data format
2021-01-24 15:35:36 +00:00
ResponseAppend_P ( PSTR ( " %u " ) , ( uint32_t ) results . value ) ;
2019-08-28 21:01:01 +01:00
} else {
2019-09-10 19:45:27 +01:00
if ( UNKNOWN ! = results . decode_type ) {
2021-01-24 15:35:36 +00:00
uint64_t reverse = reverseBitsInBytes64 ( results . value ) ;
2021-02-01 16:46:28 +00:00
ResponseAppend_P ( PSTR ( " \" 0x%0_X \" , \" " D_JSON_IR_DATALSB " \" : \" 0x%0_X \" " ) ,
2021-01-24 15:35:36 +00:00
& results . value , & reverse ) ;
2019-09-10 19:45:27 +01:00
} else { // UNKNOWN
2021-02-02 09:15:14 +00:00
ResponseAppend_P ( PSTR ( " \" 0x%08X \" " ) , ( uint32_t ) results . value ) ; // Unknown is always 32 bits
2019-09-10 19:45:27 +01:00
}
2019-08-28 21:01:01 +01:00
}
}
2021-01-24 15:35:36 +00:00
ResponseAppend_P ( PSTR ( " , \" " D_JSON_IR_REPEAT " \" :%d " ) , results . repeat ) ;
2019-08-28 21:01:01 +01:00
2020-09-20 22:18:00 +01:00
stdAc : : state_t new_state ;
if ( IRAcUtils : : decodeToState ( & results , & new_state , irhvac_stateful & & irac_prev_state . protocol = = results . decode_type ? & irac_prev_state : nullptr ) ) {
2019-08-28 21:01:01 +01:00
// we have a decoded state
2021-01-24 15:35:36 +00:00
ResponseAppend_P ( PSTR ( " , \" " D_CMND_IRHVAC " \" :%s " ) , sendACJsonState ( new_state ) . c_str ( ) ) ;
2020-09-20 22:18:00 +01:00
irac_prev_state = new_state ; // store for next time
2019-08-28 21:01:01 +01:00
}
}
2022-08-02 21:52:35 +01:00
void IrReceiveCheck ( void ) {
2019-08-28 21:01:01 +01:00
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)) {
2020-12-02 21:09:30 +00:00
if ( now - ir_lasttime > IR_TIME_AVOID_DUPLICATE ) {
2019-08-28 21:01:01 +01:00
ir_lasttime = now ;
2021-01-24 15:35:36 +00:00
Response_P ( PSTR ( " { \" " D_JSON_IRRECEIVED " \" :{ " ) ) ;
sendIRJsonState ( results ) ;
2019-08-28 21:01:01 +01:00
2020-10-01 21:34:26 +01:00
IRRawTable raw_table ;
bool prev_number = false ; // was the previous value a number, meaning we may need a comma prefix
bool ir_high = true ; // alternate high/low
// Add raw data in a compact format
2021-06-11 17:14:12 +01:00
if ( Settings - > flag3 . receive_raw ) { // SetOption58 - Add IR Raw data to JSON message
2020-10-01 21:34:26 +01:00
ResponseAppend_P ( PSTR ( " , \" " D_JSON_IR_RAWDATA " \" : \" " ) ) ;
size_t rawlen = results . rawlen ;
uint32_t i ;
2020-10-30 11:29:48 +00:00
2020-10-01 21:34:26 +01:00
for ( i = 1 ; i < rawlen ; i + + ) {
// round to closest 10ms
uint32_t raw_val_millis = results . rawbuf [ i ] * kRawTick ;
uint16_t raw_dms = ( raw_val_millis * 2 + 5 ) / 10 ; // in 5 micro sec steps
// look if the data is already seen
uint8_t letter = raw_table . findOrAdd ( raw_dms ) ;
if ( letter ) {
if ( ! ir_high ) { letter = tolower ( letter ) ; }
ResponseAppend_P ( PSTR ( " %c " ) , letter ) ;
prev_number = false ;
} else {
// number
ResponseAppend_P ( PSTR ( " %c%d " ) , ir_high ? ' + ' : ' - ' , ( uint32_t ) raw_dms * 5 ) ;
prev_number = true ;
2019-08-28 21:01:01 +01:00
}
2020-10-01 21:34:26 +01:00
ir_high = ! ir_high ;
2021-05-23 17:22:29 +01:00
if ( ResponseLength ( ) + 40 > ResponseSize ( ) ) { break ; } // Quit if char string becomes too long
2019-08-28 21:01:01 +01:00
}
2020-10-01 21:34:26 +01:00
uint16_t extended_length = getCorrectedRawLength ( & results ) ;
ResponseAppend_P ( PSTR ( " \" , \" " D_JSON_IR_RAWDATA " Info \" :[%d,%d,%d] " ) , extended_length , i - 1 , results . overflow ) ;
2019-08-28 21:01:01 +01:00
}
2019-09-12 13:19:44 +01:00
ResponseJsonEndEnd ( ) ;
2024-06-14 10:23:03 +01:00
if ( Settings - > flag6 . mqtt_disable_publish ) { // SetOption147 - If it is activated, Tasmota will not publish IRReceived MQTT messages, but it will proccess event trigger rules
XdrvRulesProcess ( 0 ) ;
} else {
MqttPublishPrefixTopicRulesProcess_P ( RESULT_OR_TELE , PSTR ( D_JSON_IRRECEIVED ) ) ;
}
2019-08-28 21:01:01 +01:00
}
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 ;
}
2020-09-21 20:49:32 +01:00
bool strToBool ( class JsonParserToken token , bool def ) {
if ( token . isBool ( ) | | token . isNum ( ) ) {
return token . getBool ( ) ;
} else if ( token . isStr ( ) ) {
return IRac : : strToBool ( token . getStr ( ) ) ;
} else {
return def ;
}
}
2020-10-06 16:10:07 +01:00
StateModes strToStateMode ( class JsonParserToken token , StateModes def ) {
if ( token . isStr ( ) ) {
const char * str = token . getStr ( ) ;
if ( ! strcasecmp_P ( str , PSTR ( D_JSON_IRHVAC_STATE_MODE_SEND_ONLY ) ) )
return StateModes : : SEND_ONLY ;
else if ( ! strcasecmp_P ( str , PSTR ( D_JSON_IRHVAC_STATE_MODE_STORE_ONLY ) ) )
return StateModes : : STORE_ONLY ;
else if ( ! strcasecmp_P ( str , PSTR ( D_JSON_IRHVAC_STATE_MODE_SEND_STORE ) ) )
return StateModes : : SEND_STORE ;
}
return def ;
}
2019-08-28 21:01:01 +01:00
// used to convert values 0-5 to fanspeed_t
const stdAc : : fanspeed_t IrHvacFanSpeed [ ] PROGMEM = { stdAc : : fanspeed_t : : kAuto ,
2023-05-09 20:35:56 +01:00
stdAc : : fanspeed_t : : kMin , stdAc : : fanspeed_t : : kLow , stdAc : : fanspeed_t : : kMedium , stdAc : : fanspeed_t : : kMediumHigh ,
2019-08-28 21:01:01 +01:00
stdAc : : fanspeed_t : : kHigh , stdAc : : fanspeed_t : : kMax } ;
uint32_t IrRemoteCmndIrHvacJson ( void )
{
2023-06-10 13:53:02 +01:00
// state is initialized to the IRremoteESP8266's state_t defaults anyway
// https://github.com/crankyoldgit/IRremoteESP8266/blob/v2.8.4/src/IRsend.h#L97-L116
2020-09-20 22:18:00 +01:00
stdAc : : state_t state ;
2019-08-28 21:01:01 +01:00
2021-06-05 10:47:09 +01:00
//AddLog(LOG_LEVEL_DEBUG, PSTR("IRHVAC: Received %s"), XdrvMailbox.data);
2020-09-23 18:38:24 +01:00
JsonParser parser ( XdrvMailbox . data ) ;
JsonParserObject root = parser . getRootObject ( ) ;
2020-09-21 20:49:32 +01:00
if ( ! root ) { return IE_INVALID_JSON ; }
2019-08-28 21:01:01 +01:00
2023-06-10 13:53:02 +01:00
bool incremental = strToBool ( root [ PSTR ( D_JSON_IRHVAC_INCREMENTAL ) ] , false ) ;
// TODO: add support for storing multiple states
if ( incremental )
{
state = irac_prev_state ;
}
2019-08-28 21:01:01 +01:00
2020-09-24 07:51:43 +01:00
JsonParserToken val ;
if ( val = root [ PSTR ( D_JSON_IRHVAC_VENDOR ) ] ) { state . protocol = strToDecodeType ( val . getStr ( ) ) ; }
if ( val = root [ PSTR ( D_JSON_IRHVAC_PROTOCOL ) ] ) { state . protocol = strToDecodeType ( val . getStr ( ) ) ; }
2019-08-28 21:01:01 +01:00
if ( decode_type_t : : UNKNOWN = = state . protocol ) { return IE_UNSUPPORTED_HVAC ; }
if ( ! IRac : : isProtocolSupported ( state . protocol ) ) { return IE_UNSUPPORTED_HVAC ; }
2023-05-09 20:35:56 +01:00
if ( val = root [ PSTR ( D_JSON_IRHVAC_MODEL ) ] ) { state . model = IRac : : strToModel ( val . getStr ( ) ) ; }
if ( val = root [ PSTR ( D_JSON_IRHVAC_COMMAND ) ] ) { state . command = IRac : : strToCommandType ( val . getStr ( ) ) ; }
// for fan speed, we also support 1-6 values
2020-09-21 20:49:32 +01:00
JsonParserToken tok_fan_speed = root [ PSTR ( D_JSON_IRHVAC_FANSPEED ) ] ;
if ( tok_fan_speed ) {
uint32_t fan_speed = tok_fan_speed . getUInt ( ) ;
2023-05-09 20:35:56 +01:00
if ( ( fan_speed > = 1 ) & & ( fan_speed < = 6 ) ) {
2019-08-28 21:01:01 +01:00
state . fanspeed = ( stdAc : : fanspeed_t ) pgm_read_byte ( & IrHvacFanSpeed [ fan_speed ] ) ;
} else {
2020-09-21 20:49:32 +01:00
state . fanspeed = IRac : : strToFanspeed ( tok_fan_speed . getStr ( ) ) ;
2019-08-28 21:01:01 +01:00
}
}
2020-09-24 07:51:43 +01:00
if ( val = root [ PSTR ( D_JSON_IRHVAC_MODE ) ] ) { state . mode = IRac : : strToOpmode ( val . getStr ( ) ) ; }
if ( val = root [ PSTR ( D_JSON_IRHVAC_SWINGV ) ] ) { state . swingv = IRac : : strToSwingV ( val . getStr ( ) ) ; }
if ( val = root [ PSTR ( D_JSON_IRHVAC_SWINGH ) ] ) { state . swingh = IRac : : strToSwingH ( val . getStr ( ) ) ; }
2020-09-21 20:49:32 +01:00
state . degrees = root . getFloat ( PSTR ( D_JSON_IRHVAC_TEMP ) , state . degrees ) ;
2021-01-23 16:24:54 +00:00
// AddLog(LOG_LEVEL_DEBUG, PSTR("model %d, mode %d, fanspeed %d, swingv %d, swingh %d"),
2019-08-28 21:01:01 +01:00
// state.model, state.mode, state.fanspeed, state.swingv, state.swingh);
2023-06-10 13:53:02 +01:00
StateModes stateMode = incremental ? StateModes : : SEND_STORE : StateModes : : SEND_ONLY ;
2020-10-06 16:10:07 +01:00
if ( irhvac_stateful & & ( val = root [ PSTR ( D_JSON_IRHVAC_STATE_MODE ) ] ) ) { stateMode = strToStateMode ( val , stateMode ) ; }
2019-08-28 21:01:01 +01:00
// decode booleans
2020-09-21 20:49:32 +01:00
state . power = strToBool ( root [ PSTR ( D_JSON_IRHVAC_POWER ) ] , state . power ) ;
state . celsius = strToBool ( root [ PSTR ( D_JSON_IRHVAC_CELSIUS ) ] , state . celsius ) ;
state . light = strToBool ( root [ PSTR ( D_JSON_IRHVAC_LIGHT ) ] , state . light ) ;
state . beep = strToBool ( root [ PSTR ( D_JSON_IRHVAC_BEEP ) ] , state . beep ) ;
state . econo = strToBool ( root [ PSTR ( D_JSON_IRHVAC_ECONO ) ] , state . econo ) ;
state . filter = strToBool ( root [ PSTR ( D_JSON_IRHVAC_FILTER ) ] , state . filter ) ;
state . turbo = strToBool ( root [ PSTR ( D_JSON_IRHVAC_TURBO ) ] , state . turbo ) ;
state . quiet = strToBool ( root [ PSTR ( D_JSON_IRHVAC_QUIET ) ] , state . quiet ) ;
state . clean = strToBool ( root [ PSTR ( D_JSON_IRHVAC_CLEAN ) ] , state . clean ) ;
2019-08-28 21:01:01 +01:00
2023-05-09 20:35:56 +01:00
if ( state . command = = stdAc : : ac_command_t : : kConfigCommand ) {
// Note: for `kConfigCommand` the semantics of clock/sleep is abused IRremoteESP8266 lib-side for key/value pair
state . clock = root . getInt ( PSTR ( D_JSON_IRHVAC_CONFIG_KEY ) , state . clock ) ;
state . sleep = root . getInt ( PSTR ( D_JSON_IRHVAC_CONFIG_VALUE ) , state . sleep ) ;
} else {
// optional timer and clock (Note: different json field names w/ time semantics)
state . clock = root . getInt ( PSTR ( D_JSON_IRHVAC_CLOCK ) , state . clock ) ;
state . sleep = root . getInt ( PSTR ( D_JSON_IRHVAC_SLEEP ) , state . sleep ) ;
}
state . iFeel = strToBool ( root [ PSTR ( D_JSON_IRHVAC_IFEEL ) ] , state . iFeel ) ;
state . sensorTemperature = root . getFloat ( PSTR ( D_JSON_IRHVAC_SENSOR_TEMP ) , state . sensorTemperature ) ;
2019-08-28 21:01:01 +01:00
2022-08-08 13:11:57 +01:00
if ( ! IR_RCV_WHILE_SENDING & & ( irrecv ! = nullptr ) ) { irrecv - > pause ( ) ; }
2020-10-06 16:10:07 +01:00
if ( stateMode = = StateModes : : SEND_ONLY | | stateMode = = StateModes : : SEND_STORE ) {
2022-08-02 21:52:35 +01:00
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 ) ;
2020-09-27 13:43:31 +01:00
bool success = ac . sendAc ( state , irhvac_stateful & & irac_prev_state . protocol = = state . protocol ? & irac_prev_state : nullptr ) ;
if ( ! success ) { return IE_SYNTAX_IRHVAC ; }
}
2020-10-06 16:10:07 +01:00
if ( stateMode = = StateModes : : STORE_ONLY | | stateMode = = StateModes : : SEND_STORE ) { // store state in memory
2020-09-27 13:43:31 +01:00
irac_prev_state = state ;
}
2022-08-08 13:11:57 +01:00
if ( ! IR_RCV_WHILE_SENDING & & ( irrecv ! = nullptr ) ) { irrecv - > resume ( ) ; }
2019-08-28 21:01:01 +01:00
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
}
2024-02-28 21:42:29 +00:00
// 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 ) ) ) ;
}
}
}
}
2019-08-28 21:01:01 +01:00
/*********************************************************************************************\
* Commands
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
uint32_t IrRemoteCmndIrSendJson ( void )
{
// IRsend { "protocol": "RC5", "bits": 12, "data":"0xC86" }
// IRsend { "protocol": "SAMSUNG", "bits": 32, "data": 551502015 }
2024-02-28 21:42:29 +00:00
// IRsend {"Protocol":"CARRIER_AC84","Bits":84,"Data":0x0C04233200120012001204}
2020-09-24 07:51:43 +01:00
RemoveSpace ( XdrvMailbox . data ) ; // TODO is this really needed?
JsonParser parser ( XdrvMailbox . data ) ;
JsonParserObject root = parser . getRootObject ( ) ;
if ( ! root ) { return IE_INVALID_JSON ; }
2019-08-28 21:01:01 +01:00
// IRsend { "protocol": "SAMSUNG", "bits": 32, "data": 551502015 }
// IRsend { "protocol": "NEC", "bits": 32, "data":"0x02FDFE80", "repeat": 2 }
2020-09-24 07:51:43 +01:00
JsonParserToken value ;
2019-08-28 21:01:01 +01:00
2020-09-24 07:51:43 +01:00
decode_type_t protocol = decode_type_t : : UNKNOWN ;
value = root [ PSTR ( D_JSON_IRHVAC_VENDOR ) ] ;
if ( root ) { protocol = strToDecodeType ( value . getStr ( ) ) ; }
value = root [ PSTR ( D_JSON_IRHVAC_PROTOCOL ) ] ;
if ( root ) { protocol = strToDecodeType ( value . getStr ( ) ) ; }
2019-08-28 21:01:01 +01:00
if ( decode_type_t : : UNKNOWN = = protocol ) { return IE_UNSUPPORTED_PROTOCOL ; }
2024-02-28 21:42:29 +00:00
AddLog ( LOG_LEVEL_INFO , PSTR ( " IRS: protocol %d " ) , protocol ) ;
2019-08-28 21:01:01 +01:00
2020-09-24 07:51:43 +01:00
uint16_t bits = root . getUInt ( PSTR ( D_JSON_IR_BITS ) , 0 ) ;
uint16_t repeat = root . getUInt ( PSTR ( D_JSON_IR_REPEAT ) , 0 ) ;
2022-08-02 21:52:35 +01:00
int8_t channel = root . getUInt ( PSTR ( D_JSON_IR_CHANNEL ) , 1 ) - 1 ;
2019-08-28 21:01:01 +01:00
if ( 0 = = bits ) { return IE_SYNTAX_IRSEND ; }
// check if the IRSend<x> is greater than repeat, but can be overriden with JSON
if ( XdrvMailbox . index > repeat + 1 ) { repeat = XdrvMailbox . index - 1 ; }
2024-02-28 21:42:29 +00:00
bool success = false ;
if ( bits < = 64 ) {
uint64_t data64 ; // for 64 bits and less
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)
2019-08-28 21:01:01 +01:00
2024-02-28 21:42:29 +00:00
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, repeat %d"), protocol, bits, (uint32_t) data64, repeat);
success = irsend . send ( protocol , data64 , bits , repeat ) ;
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
2024-05-04 13:42:35 +01:00
if ( HexToBytes ( data_hex , data_bytes , num_bytes ) < = 0 ) { return IE_INVALID_HEXDATA ; }
2024-02-28 21:42:29 +00:00
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 ( ) ; }
}
2019-08-28 21:01:01 +01:00
if ( ! success ) {
ResponseCmndChar ( D_JSON_PROTOCOL_NOT_SUPPORTED ) ;
}
return IE_NO_ERROR ;
}
2020-10-01 21:34:26 +01:00
//
// Send Global Cache commands
//
// Input:
// p: token for strtok_r()
// count: number of commas in parameters, i.e. it contains count+1 values
// repeat: number of repeats (0 means no repeat)
//
uint32_t IrRemoteSendGC ( char * * pp , uint32_t count , uint32_t repeat ) {
// IRsend gc,1000,2000,2000,1000
uint16_t GC [ count + 1 ] ;
for ( uint32_t i = 0 ; i < = count ; i + + ) {
GC [ i ] = strtol ( strtok_r ( nullptr , " , " , pp ) , nullptr , 0 ) ;
if ( ! GC [ i ] ) { return IE_INVALID_RAWDATA ; }
2019-08-28 21:01:01 +01:00
}
2022-08-08 13:11:57 +01:00
if ( ! IR_RCV_WHILE_SENDING & & ( irrecv ! = nullptr ) ) { irrecv - > pause ( ) ; }
2022-08-02 21:52:35 +01:00
IRsend irsend = IrSendInitGPIO ( ) ;
2020-10-01 21:34:26 +01:00
for ( uint32_t r = 0 ; r < = repeat ; r + + ) {
2022-08-02 21:52:35 +01:00
irsend . sendGC ( GC , count + 1 ) ;
2020-10-01 21:34:26 +01:00
}
2022-08-08 13:11:57 +01:00
if ( ! IR_RCV_WHILE_SENDING & & ( irrecv ! = nullptr ) ) { irrecv - > resume ( ) ; }
2020-10-01 21:34:26 +01:00
return IE_NO_ERROR ;
}
2019-08-28 21:01:01 +01:00
2020-10-01 21:34:26 +01:00
//
// Send 'raw'
//
uint32_t IrRemoteSendRawFormatted ( char * * pp , uint32_t count , uint32_t repeat ) {
if ( count < 2 ) { return IE_INVALID_RAWDATA ; }
// parse frequency
char * str = strtok_r ( nullptr , " , " , pp ) ;
uint16_t freq = parsqeFreq ( str ) ;
// parse parameters from 1 to count-1
// i.e: IRsend raw,0,889,1778,000000100110000001001 => count = 3, [889,1778]
uint16_t parm [ count - 1 ] ; // contains at least 1 value
for ( uint32_t i = 0 ; i < count - 1 ; i + + ) {
parm [ i ] = strtol ( strtok_r ( nullptr , " , " , pp ) , nullptr , 0 ) ;
if ( 0 = = parm [ i ] ) { return IE_INVALID_RAWDATA ; } // parameters may not be 0
2020-06-23 23:09:44 +01:00
}
2020-10-01 21:34:26 +01:00
uint16_t i = 0 ;
if ( count < 4 ) {
// IRsend raw,0,889,000000100110000001001
// IRsend raw,0,889,2,000000100110000001001
// IRsend raw,0,889,1778,000000100110000001001
// IRsend raw,40,900,2000,000000100110000001001
uint16_t mark , space ;
space = parm [ 0 ] ;
mark = space * 2 ; // Protocol where 0 = t, 1 = 2t (RC5)
if ( 3 = = count ) {
if ( parm [ 1 ] < = 10 ) {
// IRsend raw,0,889,2,000000100110000001001
mark = parm [ 0 ] * parm [ 1 ] ; // Protocol where 0 = t1, 1 = t1*t2 (Could be RC5)
} else {
// IRsend raw,0,889,1778,000000100110000001001
mark = parm [ 1 ] ; // Protocol where 0 = t1, 1 = t2 (Could be RC5)
2019-08-28 21:01:01 +01:00
}
}
2020-10-01 21:34:26 +01:00
// p points to the last parameter
uint16_t raw_array [ strlen ( * pp ) ] ; // Bits
for ( ; * * pp ; * ( * pp ) + + ) {
if ( * * pp = = ' 0 ' ) {
raw_array [ i + + ] = space ; // Space
2019-08-28 21:01:01 +01:00
}
2020-10-01 21:34:26 +01:00
else if ( * * pp = = ' 1 ' ) {
raw_array [ i + + ] = mark ; // Mark
2019-08-28 21:01:01 +01:00
}
2020-10-01 21:34:26 +01:00
}
2022-08-08 13:11:57 +01:00
if ( ! IR_RCV_WHILE_SENDING & & ( irrecv ! = nullptr ) ) { irrecv - > pause ( ) ; }
2022-08-02 21:52:35 +01:00
IRsend irsend = IrSendInitGPIO ( ) ;
2020-10-01 21:34:26 +01:00
for ( uint32_t r = 0 ; r < = repeat ; r + + ) {
2021-01-23 16:24:54 +00:00
// AddLog(LOG_LEVEL_DEBUG, PSTR("sendRaw count=%d, space=%d, mark=%d, freq=%d"), count, space, mark, freq);
2022-08-02 21:52:35 +01:00
irsend . sendRaw ( raw_array , i , freq ) ;
2020-10-01 21:34:26 +01:00
if ( r < repeat ) { // if it's not the last message
2022-08-02 21:52:35 +01:00
irsend . space ( 40000 ) ; // since we don't know the inter-message gap, place an arbitrary 40ms gap
2019-08-28 21:01:01 +01:00
}
}
2022-08-08 13:11:57 +01:00
if ( ! IR_RCV_WHILE_SENDING & & ( irrecv ! = nullptr ) ) { irrecv - > resume ( ) ; }
2020-10-01 21:34:26 +01:00
} else if ( 6 = = count ) { // NEC Protocol
// IRsend raw,0,8620,4260,544,411,1496,010101101000111011001110000000001100110000000001100000000000000010001100
uint16_t raw_array [ strlen ( * pp ) * 2 + 3 ] ; // Header + bits + end
raw_array [ i + + ] = parm [ 0 ] ; // Header mark
raw_array [ i + + ] = parm [ 1 ] ; // Header space
uint32_t inter_message_32 = ( parm [ 0 ] + parm [ 1 ] ) * 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 ( ; * * pp ; * ( * pp ) + + ) {
if ( * * pp = = ' 0 ' ) {
raw_array [ i + + ] = parm [ 2 ] ; // Bit mark
raw_array [ i + + ] = parm [ 3 ] ; // Zero space
2019-08-28 21:01:01 +01:00
}
2020-10-01 21:34:26 +01:00
else if ( * * pp = = ' 1 ' ) {
raw_array [ i + + ] = parm [ 2 ] ; // Bit mark
raw_array [ i + + ] = parm [ 4 ] ; // One space
2019-08-28 21:01:01 +01:00
}
}
2020-10-01 21:34:26 +01:00
raw_array [ i + + ] = parm [ 2 ] ; // Trailing mark
2022-08-08 13:11:57 +01:00
if ( ! IR_RCV_WHILE_SENDING & & ( irrecv ! = nullptr ) ) { irrecv - > pause ( ) ; }
2022-08-02 21:52:35 +01:00
IRsend irsend = IrSendInitGPIO ( ) ;
2020-10-01 21:34:26 +01:00
for ( uint32_t r = 0 ; r < = repeat ; r + + ) {
2021-01-23 16:24:54 +00:00
// 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]);
2022-08-02 21:52:35 +01:00
irsend . sendRaw ( raw_array , i , freq ) ;
2020-10-01 21:34:26 +01:00
if ( r < repeat ) { // if it's not the last message
2022-08-02 21:52:35 +01:00
irsend . space ( inter_message ) ; // since we don't know the inter-message gap, place an arbitrary 40ms gap
2020-10-01 21:34:26 +01:00
}
2019-08-28 21:01:01 +01:00
}
2022-08-08 13:11:57 +01:00
if ( ! IR_RCV_WHILE_SENDING & & ( irrecv ! = nullptr ) ) { irrecv - > resume ( ) ; }
2020-10-01 21:34:26 +01:00
}
else { return IE_INVALID_RAWDATA ; } // Invalid number of parameters
return IE_NO_ERROR ;
}
2020-10-30 11:29:48 +00:00
//
2020-10-01 21:34:26 +01:00
// Parse data as compact or standard form
//
// In:
// str: the raw format, null terminated string. Cannot be PROGMEM nor be nullptr
// arr: pointer to uint16_t array to populate, if nullptr then we just count items
// arr_len: length of destination array, to avoid corrupting data. If arr_len == 0, ignore
uint32_t IrRemoteParseRawCompact ( char * str , uint16_t * arr , size_t arr_len ) {
char * p = str ;
size_t i = 0 ;
IRRawTable raw_table ;
for ( char * p = str ; * p ; ) {
int32_t value = - 1 ;
if ( ( arr_len > 0 ) & & ( i > = arr_len ) ) { return 0 ; } // overflow
while ( ( * p = = ' , ' ) | | ( * p = = ' + ' ) | | ( * p = = ' - ' ) ) { p + + ; } // skip ',' '-' '+'
if ( ( * p > = ' 0 ' ) & & ( * p < = ' 9 ' ) ) {
// parse number
value = strtoul ( p , & p , 10 ) ;
raw_table . add ( value ) ;
} else {
value = raw_table . getTimingForLetter ( * p ) ;
p + + ;
2019-08-28 21:01:01 +01:00
}
2020-10-01 21:34:26 +01:00
if ( value < 0 ) { return 0 ; } // invalid
if ( nullptr ! = arr ) {
arr [ i ] = value ;
}
i + + ;
}
return i ;
}
2019-08-28 21:01:01 +01:00
2020-10-01 21:34:26 +01:00
//
// Send raw standard
//
// Input:
// p: token for strtok_r()
// count: number of commas in parameters, i.e. it contains count+1 values
// repeat: number of repeats (0 means no repeat)
//
2020-11-05 15:07:39 +00:00
uint32_t IrRemoteSendRawStandard ( char * * pp , uint16_t freq , uint32_t count , uint32_t repeat ) {
2020-10-01 21:34:26 +01:00
// IRsend 0,896,876,900,888,894,876,1790,874,872,1810,1736,948,872,880,872,936,872,1792,900,888,1734
// IRsend 0,+8570-4240+550-1580C-510+565-1565F-505Fh+570gFhIdChIgFeFgFgIhFgIhF-525C-1560IhIkI-520ChFhFhFgFhIkIhIgIgIkIkI-25270A-4225IkIhIgIhIhIkFhIkFjCgIhIkIkI-500IkIhIhIkFhIgIl+545hIhIoIgIhIkFhFgIkIgFgI
uint16_t * arr = nullptr ;
if ( count = = 0 ) {
// compact format, we need to parse in a first pass to know the number of frames
count = IrRemoteParseRawCompact ( * pp , nullptr , 0 ) ;
if ( 0 = = count ) { return IE_INVALID_RAWDATA ; }
} else {
2019-08-28 21:01:01 +01:00
count + + ;
2020-10-01 21:34:26 +01:00
}
2021-01-23 16:24:54 +00:00
// AddLog(LOG_LEVEL_DEBUG, PSTR("IrRemoteSendRawStandard: count_1 = %d"), count);
2020-10-01 21:34:26 +01:00
arr = ( uint16_t * ) malloc ( count * sizeof ( uint16_t ) ) ;
if ( nullptr = = arr ) { return IE_MEMORY ; }
count = IrRemoteParseRawCompact ( * pp , arr , count ) ;
2021-01-23 16:24:54 +00:00
// AddLog(LOG_LEVEL_DEBUG, PSTR("IrRemoteSendRawStandard: count_2 = %d"), count);
// AddLog(LOG_LEVEL_DEBUG, PSTR("Arr %d %d %d %d %d %d %d %d"), arr[0], arr[1], arr[2], arr[3], arr[4], arr[5], arr[6], arr[7]);
2020-10-01 21:34:26 +01:00
if ( 0 = = count ) { return IE_INVALID_RAWDATA ; }
2022-08-08 13:11:57 +01:00
if ( ! IR_RCV_WHILE_SENDING & & ( irrecv ! = nullptr ) ) { irrecv - > pause ( ) ; }
2022-08-02 21:52:35 +01:00
IRsend irsend = IrSendInitGPIO ( ) ;
2020-10-01 21:34:26 +01:00
for ( uint32_t r = 0 ; r < = repeat ; r + + ) {
2022-08-02 21:52:35 +01:00
irsend . sendRaw ( arr , count , freq ) ;
2020-10-01 21:34:26 +01:00
}
2022-08-08 13:11:57 +01:00
if ( ! IR_RCV_WHILE_SENDING & & ( irrecv ! = nullptr ) ) { irrecv - > resume ( ) ; }
2019-08-28 21:01:01 +01:00
2020-10-01 21:34:26 +01:00
if ( nullptr ! = arr ) {
free ( arr ) ;
}
return IE_NO_ERROR ;
}
2019-08-28 21:01:01 +01:00
2020-10-01 21:34:26 +01:00
// parse the frequency value
uint16_t parsqeFreq ( char * str ) {
uint16_t freq = atoi ( str ) ;
if ( 0 = = freq ) { freq = 38000 ; }
return freq ;
}
uint32_t IrRemoteCmndIrSendRaw ( void )
{
// IRsend <freq>,<rawdata>,<rawdata> ...
// IRsend <freq>,<compact_rawdata>
// or
// IRsend raw,<freq>,<zero space>,<bit stream> (one space = zero space *2)
// IRsend raw,<freq>,<zero space>,<zero space multiplier becoming one space>,<bit stream>
// IRsend raw,<freq>,<zero space>,<one space>,<bit stream>
// IRsend raw,<freq>,<header mark>,<header space>,<bit mark>,<zero space>,<one space>,<bit stream>
// check that there is at least one comma in the parameters
char * p ;
char * str = strtok_r ( XdrvMailbox . data , " , " , & p ) ;
if ( p = = nullptr ) { return IE_INVALID_RAWDATA ; }
// repeat is Index-1, so by default repeat = 0 (no repeat)
uint16_t repeat = XdrvMailbox . index > 0 ? XdrvMailbox . index - 1 : 0 ;
// count commas in parameters, after the first token skipped
uint16_t count = 0 ;
char * q = p ;
for ( ; * q ; count + = ( * q + + = = ' , ' ) ) ;
// analyze first parameter
if ( strcasecmp ( str , " gc " ) = = 0 ) {
// Global Cache protocol
// IRsend gc,xxx,xxx,...
return IrRemoteSendGC ( & p , count , repeat ) ;
} else if ( strcasecmp ( str , " raw " ) = = 0 ) {
// IRsend raw,<freq>,<zero space>,<bit stream> (one space = zero space *2)
// IRsend raw,<freq>,<zero space>,<zero space multiplier becoming one space>,<bit stream>
// IRsend raw,<freq>,<zero space>,<one space>,<bit stream>
// IRsend raw,<freq>,<header mark>,<header space>,<bit mark>,<zero space>,<one space>,<bit stream>
return IrRemoteSendRawFormatted ( & p , count , repeat ) ;
} else {
// standard raw
// IRsend <freq>,<rawdata>,<rawdata> ...
// IRsend <freq>,<compact_rawdata>
2020-11-05 15:07:39 +00:00
return IrRemoteSendRawStandard ( & p , parsqeFreq ( str ) , count , repeat ) ;
2020-10-01 21:34:26 +01:00
}
2019-08-28 21:01:01 +01:00
}
2022-08-02 21:52:35 +01:00
void CmndIrSend ( void ) {
2019-08-28 21:01:01 +01:00
uint8_t error = IE_SYNTAX_IRSEND ;
if ( XdrvMailbox . data_len ) {
2020-11-04 10:20:17 +00:00
if ( strchr ( XdrvMailbox . data , ' { ' ) = = nullptr ) {
2019-08-28 21:01:01 +01:00
error = IrRemoteCmndIrSendRaw ( ) ;
} else {
error = IrRemoteCmndIrSendJson ( ) ;
}
}
IrRemoteCmndResponse ( error ) ;
}
void IrRemoteCmndResponse ( uint32_t error )
{
switch ( error ) {
case IE_INVALID_RAWDATA :
2020-03-16 17:55:58 +00:00
ResponseCmndChar_P ( PSTR ( D_JSON_INVALID_RAWDATA ) ) ;
2019-08-28 21:01:01 +01:00
break ;
case IE_INVALID_JSON :
2020-03-16 17:55:58 +00:00
ResponseCmndChar_P ( PSTR ( D_JSON_INVALID_JSON ) ) ;
2019-08-28 21:01:01 +01:00
break ;
2024-02-28 21:42:29 +00:00
case IE_INVALID_HEXDATA :
ResponseCmndChar_P ( PSTR ( D_JSON_INVALID_HEXDATA ) ) ;
break ;
2019-08-28 21:01:01 +01:00
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 ;
2020-10-01 21:34:26 +01:00
case IE_MEMORY :
ResponseCmndChar_P ( PSTR ( D_JSON_MEMORY_ERROR ) ) ;
break ;
2019-08-28 21:01:01 +01:00
default : // IE_NO_ERROR
ResponseCmndDone ( ) ;
}
}
2022-08-02 21:52:35 +01:00
void IrInit ( void ) {
ir_send_active = PinUsed ( GPIO_IRSEND , GPIO_ANY ) ;
ir_recv_active = PinUsed ( GPIO_IRRECV , GPIO_ANY ) ;
2022-08-02 22:32:23 +01:00
if ( ir_send_active ) {
for ( uint32_t chan = 0 ; chan < MAX_IRSEND ; chan + + ) {
if ( PinUsed ( GPIO_IRSEND , chan ) ) {
IrSendInitGPIO ( chan ) ;
}
}
}
2022-08-02 21:52:35 +01:00
}
2019-08-28 21:01:01 +01:00
/*********************************************************************************************\
* Interface
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2022-11-11 09:44:56 +00:00
bool Xdrv05 ( uint32_t function )
2019-08-28 21:01:01 +01:00
{
bool result = false ;
2022-08-02 21:52:35 +01:00
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 ;
2023-12-27 21:03:56 +00:00
case FUNC_ACTIVE :
if ( ir_recv_active | | ir_recv_active ) {
result = true ;
}
break ;
2019-08-28 21:01:01 +01:00
}
return result ;
}
# endif // USE_IR_REMOTE_FULL