2019-08-28 21:01:01 +01:00
/*
xdrv_05_irremote_full . ino - complete intefration of IRremoteESP8266
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 < http : //www.gnu.org/licenses/>.
*/
# 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>
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 ;
}
char * IrUint64toHex ( uint64_t value , char * str , uint16_t bits )
{
ulltoa ( value , str , 16 ) ; // Get 64bit value
int fill = 8 ;
if ( ( bits > 3 ) & & ( bits < 65 ) ) {
fill = bits / 4 ; // Max 16
if ( bits % 4 ) { fill + + ; }
}
int len = strlen ( str ) ;
fill - = len ;
if ( fill > 0 ) {
memmove ( str + fill , str , len + 1 ) ;
memset ( str , ' 0 ' , fill ) ;
}
memmove ( str + 2 , str , strlen ( str ) + 1 ) ;
str [ 0 ] = ' 0 ' ;
str [ 1 ] = ' x ' ;
return str ;
}
/*********************************************************************************************\
* 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 ) ) {
2019-08-28 21:24:40 +01:00
json + = " , \" " D_JSON_IR_DATA " \" : \" 0x " ;
2019-08-28 21:01:01 +01:00
json + = resultToHexidecimal ( & results ) ;
json + = " \" " ;
} else {
json + = " , \" " D_JSON_IR_DATA " \" : " ;
if ( Settings . flag . ir_receive_decimal ) {
char svalue [ 32 ] ;
ulltoa ( results . value , svalue , 10 ) ;
json + = svalue ;
} else {
char hvalue [ 64 ] ;
IrUint64toHex ( results . value , hvalue , results . bits ) ; // Get 64bit value as hex 0x00123456
json + = " \" " ;
json + = hvalue ;
json + = " \" , \" " D_JSON_IR_DATALSB " \" : \" " ;
IrUint64toHex ( reverseBitsInBytes64 ( results . value ) , hvalue , results . bits ) ; // Get 64bit value as hex 0x00123456, LSB
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 )
{
char sirtype [ 14 ] ; // Max is AIWA_RC_T501
int8_t iridx = 0 ;
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 ;
2019-09-04 17:06:34 +01:00
ResponseTime_P ( PSTR ( " , \" " D_JSON_IRRECEIVED " \" :%s " ) , sendIRJsonState ( results ) . c_str ( ) ) ;
2019-08-28 21:01:01 +01:00
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 ) ;
}
ResponseAppend_P ( PSTR ( " }} " ) ) ;
MqttPublishPrefixTopic_P ( RESULT_OR_TELE , PSTR ( D_JSON_IRRECEIVED ) ) ;
if ( iridx ) {
XdrvRulesProcess ( ) ;
# ifdef USE_DOMOTICZ
unsigned long value = results . value | ( iridx < < 28 ) ; // [Protocol:4, Data:28]
DomoticzSensor ( DZ_COUNT , value ) ; // Send data as Domoticz Counter value
# endif // USE_DOMOTICZ
}
}
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<x> 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 %s (%s), repeat %d " ) ,
protocol , bits , ulltoa ( data , dvalue , 10 ) , IrUint64toHex ( 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 <freq>,<rawdata>,<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>
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 < uint16_t * > ( 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