2018-01-09 14:21:37 +01:00
/*
* Alexa Voice On / Off / Brightness Control . Emulates a Philips Hue bridge to Alexa .
*
* This was put together from these two excellent projects :
* https : //github.com/kakopappa/arduino-esp8266-alexa-wemo-switch
* https : //github.com/probonopd/ESP8266HueEmulator
*/
2018-01-09 23:13:29 +01:00
# ifndef TEST //ARDUINO_ARCH_ESP32
2017-02-21 23:59:47 +01:00
void alexaInit ( )
{
if ( alexaEnabled & & WiFi . status ( ) = = WL_CONNECTED )
{
2018-01-07 22:52:48 +01:00
prepareIds ( ) ;
udpConnected = connectUDP ( ) ;
if ( udpConnected ) alexaInitPages ( ) ;
2017-02-21 23:59:47 +01:00
}
}
void handleAlexa ( )
{
if ( alexaEnabled & & WiFi . status ( ) = = WL_CONNECTED )
{
2018-01-07 22:52:48 +01:00
if ( udpConnected ) {
// if there’ s data available, read a packet
int packetSize = UDP . parsePacket ( ) ;
2018-01-09 23:13:29 +01:00
if ( packetSize > 0 ) {
2018-01-07 22:52:48 +01:00
IPAddress remote = UDP . remoteIP ( ) ;
int len = UDP . read ( packetBuffer , 255 ) ;
if ( len > 0 ) {
packetBuffer [ len ] = 0 ;
}
String request = packetBuffer ;
if ( request . indexOf ( " M-SEARCH " ) > = 0 ) {
2018-01-09 11:55:07 +01:00
if ( request . indexOf ( " upnp:rootdevice " ) > 0 ) {
2018-01-09 14:21:37 +01:00
DEBUG_PRINTLN ( " Responding search req... " ) ;
2018-01-07 22:52:48 +01:00
respondToSearch ( ) ;
}
}
}
}
2017-02-21 23:59:47 +01:00
}
}
void alexaOn ( )
{
2017-12-28 00:37:13 +01:00
if ( alexaOnMacro = = 255 )
2017-02-21 23:59:47 +01:00
{
2017-12-28 00:37:13 +01:00
handleSet ( ( alexaNotify ) ? " win&T=1&IN " : " win&T=1&NN&IN " ) ;
2017-02-21 23:59:47 +01:00
} else
{
2017-12-28 00:37:13 +01:00
applyMacro ( alexaOnMacro ) ;
2017-02-21 23:59:47 +01:00
}
2018-01-07 22:52:48 +01:00
2018-01-09 11:55:07 +01:00
String body = " [{ \" success \" :{ \" /lights/1/state/on \" :true}}] " ;
2018-01-07 22:52:48 +01:00
server . send ( 200 , " text/xml " , body . c_str ( ) ) ;
2018-01-09 14:21:37 +01:00
DEBUG_PRINT ( " Sending : " ) ;
DEBUG_PRINTLN ( body ) ;
2017-02-21 23:59:47 +01:00
}
void alexaOff ( )
{
2017-12-28 00:37:13 +01:00
if ( alexaOffMacro = = 255 )
2017-02-21 23:59:47 +01:00
{
2017-12-28 00:37:13 +01:00
handleSet ( ( alexaNotify ) ? " win&T=0&IN " : " win&T=0&NN&IN " ) ;
2017-02-21 23:59:47 +01:00
} else
{
2017-12-28 00:37:13 +01:00
applyMacro ( alexaOffMacro ) ;
2017-02-21 23:59:47 +01:00
}
2018-01-07 22:52:48 +01:00
2018-01-09 11:55:07 +01:00
String body = " [{ \" success \" :{ \" /lights/1/state/on \" :false}}] " ;
2018-01-07 22:52:48 +01:00
2018-01-09 11:55:07 +01:00
server . send ( 200 , " application/json " , body . c_str ( ) ) ;
2018-01-07 22:52:48 +01:00
2018-01-09 14:21:37 +01:00
DEBUG_PRINT ( " Sending: " ) ;
DEBUG_PRINTLN ( body ) ;
2017-02-21 23:59:47 +01:00
}
2018-01-09 11:55:07 +01:00
void alexaDim ( uint8_t briL )
2017-12-28 00:37:13 +01:00
{
2018-01-09 11:55:07 +01:00
String body = " [{ \" success \" :{ \" /lights/1/state/bri \" : " + String ( briL ) + " }}] " ;
server . send ( 200 , " application/json " , body . c_str ( ) ) ;
2017-12-28 00:37:13 +01:00
String ct = ( alexaNotify ) ? " win&IN&A= " : " win&NN&IN&A= " ;
2018-01-09 11:55:07 +01:00
ct = ct + ( briL + 1 ) ;
2017-12-28 00:37:13 +01:00
handleSet ( ct ) ;
}
2018-01-07 22:52:48 +01:00
void prepareIds ( ) {
escapedMac = WiFi . macAddress ( ) ;
escapedMac . replace ( " : " , " " ) ;
escapedMac . toLowerCase ( ) ;
}
void respondToSearch ( ) {
2018-01-09 14:21:37 +01:00
DEBUG_PRINTLN ( " " ) ;
DEBUG_PRINT ( " Send resp to " ) ;
DEBUG_PRINTLN ( UDP . remoteIP ( ) ) ;
DEBUG_PRINT ( " Port : " ) ;
DEBUG_PRINTLN ( UDP . remotePort ( ) ) ;
2018-01-07 22:52:48 +01:00
IPAddress localIP = WiFi . localIP ( ) ;
char s [ 16 ] ;
sprintf ( s , " %d.%d.%d.%d " , localIP [ 0 ] , localIP [ 1 ] , localIP [ 2 ] , localIP [ 3 ] ) ;
String response =
" HTTP/1.1 200 OK \r \n "
" EXT: \r \n "
2018-01-09 11:55:07 +01:00
" CACHE-CONTROL: max-age=100 \r \n " // SSDP_INTERVAL
2018-01-07 22:52:48 +01:00
" LOCATION: http:// " + String ( s ) + " :80/description.xml \r \n "
" SERVER: FreeRTOS/6.0.5, UPnP/1.0, IpBridge/1.17.0 \r \n " // _modelName, _modelNumber
" hue-bridgeid: " + escapedMac + " \r \n "
" ST: urn:schemas-upnp-org:device:basic:1 \r \n " // _deviceType
" USN: uuid:2f402f80-da50-11e1-9b23- " + escapedMac + " ::upnp:rootdevice \r \n " // _uuid::_deviceType
" \r \n " ;
UDP . beginPacket ( UDP . remoteIP ( ) , UDP . remotePort ( ) ) ;
2018-01-09 23:13:29 +01:00
# ifdef ARDUINO_ARCH_ESP32
UDP . write ( ( uint8_t * ) response . c_str ( ) , response . length ( ) ) ;
# else
2018-01-07 22:52:48 +01:00
UDP . write ( response . c_str ( ) ) ;
2018-01-09 23:13:29 +01:00
# endif
2018-01-07 22:52:48 +01:00
UDP . endPacket ( ) ;
2018-01-09 14:21:37 +01:00
DEBUG_PRINTLN ( " Response sent! " ) ;
2018-01-07 22:52:48 +01:00
}
void alexaInitPages ( ) {
server . on ( " /description.xml " , HTTP_GET , [ ] ( ) {
2018-01-09 14:21:37 +01:00
DEBUG_PRINTLN ( " # Responding to description.xml ... # \n " ) ;
2018-01-07 22:52:48 +01:00
IPAddress localIP = WiFi . localIP ( ) ;
char s [ 16 ] ;
sprintf ( s , " %d.%d.%d.%d " , localIP [ 0 ] , localIP [ 1 ] , localIP [ 2 ] , localIP [ 3 ] ) ;
String setup_xml = " <?xml version= \" 1.0 \" ?> "
" <root xmlns= \" urn:schemas-upnp-org:device-1-0 \" > "
" <specVersion><major>1</major><minor>0</minor></specVersion> "
" <URLBase>http:// " + String ( s ) + " :80/</URLBase> "
" <device> "
" <deviceType>urn:schemas-upnp-org:device:Basic:1</deviceType> "
" <friendlyName>Philips hue ( " + String ( s ) + " )</friendlyName> "
" <manufacturer>Royal Philips Electronics</manufacturer> "
" <manufacturerURL>http://www.philips.com</manufacturerURL> "
" <modelDescription>Philips hue Personal Wireless Lighting</modelDescription> "
" <modelName>Philips hue bridge 2012</modelName> "
" <modelNumber>929000226503</modelNumber> "
" <modelURL>http://www.meethue.com</modelURL> "
" <serialNumber> " + escapedMac + " </serialNumber> "
" <UDN>uuid:2f402f80-da50-11e1-9b23- " + escapedMac + " </UDN> "
" <presentationURL>index.html</presentationURL> "
" <iconList> "
" <icon> "
" <mimetype>image/png</mimetype> "
" <height>48</height> "
" <width>48</width> "
" <depth>24</depth> "
" <url>hue_logo_0.png</url> "
" </icon> "
" <icon> "
" <mimetype>image/png</mimetype> "
" <height>120</height> "
" <width>120</width> "
" <depth>24</depth> "
" <url>hue_logo_3.png</url> "
" </icon> "
" </iconList> "
" </device> "
" </root> " ;
server . send ( 200 , " text/xml " , setup_xml . c_str ( ) ) ;
2018-01-09 14:21:37 +01:00
DEBUG_PRINT ( " Sending : " ) ;
DEBUG_PRINTLN ( setup_xml ) ;
2018-01-07 22:52:48 +01:00
} ) ;
// openHAB support
server . on ( " /on.html " , HTTP_GET , [ ] ( ) {
2018-01-09 14:21:37 +01:00
DEBUG_PRINTLN ( " on req " ) ;
2018-01-07 22:52:48 +01:00
server . send ( 200 , " text/plain " , " turned on " ) ;
alexaOn ( ) ;
} ) ;
server . on ( " /off.html " , HTTP_GET , [ ] ( ) {
2018-01-09 14:21:37 +01:00
DEBUG_PRINTLN ( " off req " ) ;
2018-01-07 22:52:48 +01:00
server . send ( 200 , " text/plain " , " turned off " ) ;
alexaOff ( ) ;
} ) ;
server . on ( " /status.html " , HTTP_GET , [ ] ( ) {
2018-01-09 14:21:37 +01:00
DEBUG_PRINTLN ( " Got status request " ) ;
2018-01-07 22:52:48 +01:00
String statrespone = " 0 " ;
if ( bri > 0 ) {
statrespone = " 1 " ;
}
server . send ( 200 , " text/plain " , statrespone ) ;
} ) ;
}
2018-01-09 11:55:07 +01:00
String boolString ( bool st )
{
return ( st ) ? " true " : " false " ;
}
String briForHue ( int realBri )
{
realBri - - ;
if ( realBri < 0 ) realBri = 0 ;
return String ( realBri ) ;
}
boolean handleAlexaApiCall ( String req , String body ) //basic implementation of Philips hue api functions needed for basic Alexa control
{
DEBUG_PRINTLN ( " AlexaApiCall " ) ;
if ( req . indexOf ( " api " ) < 0 ) return false ;
DEBUG_PRINTLN ( " ok " ) ;
if ( body . indexOf ( " devicetype " ) > 0 ) //client wants a hue api username, we dont care and give static
{
DEBUG_PRINTLN ( " devType " ) ;
server . send ( 200 , " application/json " , " [{ \" success \" :{ \" username \" : \" 2WLEDHardQrI3WHYTHoMcXHgEspsM8ZZRpSKtBQr \" }}] " ) ;
return true ;
}
if ( req . indexOf ( " state " ) > 0 ) //client wants to control light
{
DEBUG_PRINTLN ( " ls " ) ;
if ( body . indexOf ( " bri " ) > 0 ) { alexaDim ( body . substring ( body . indexOf ( " bri " ) + 5 ) . toInt ( ) ) ; return true ; }
if ( body . indexOf ( " false " ) > 0 ) { alexaOff ( ) ; return true ; }
alexaOn ( ) ;
return true ;
}
if ( req . indexOf ( " lights/1 " ) > 0 ) //client wants light info
{
DEBUG_PRINTLN ( " l1 " ) ;
server . send ( 200 , " application/json " , " { \" manufacturername \" : \" OpenSource \" , \" modelid \" : \" LST001 \" , \" name \" : \" " + alexaInvocationName + " \" , \" state \" :{ \" on \" : " + boolString ( bri ) + " , \" hue \" :0, \" bri \" : " + briForHue ( bri ) + " , \" sat \" :0, \" xy \" :[0.00000,0.00000], \" ct \" :500, \" alert \" : \" none \" , \" effect \" : \" none \" , \" colormode \" : \" hs \" , \" reachable \" :true}, \" swversion \" : \" 0.1 \" , \" type \" : \" Extended color light \" , \" uniqueid \" : \" 2 \" } " ) ;
return true ;
}
if ( req . indexOf ( " lights " ) > 0 ) //client wants all lights
{
DEBUG_PRINTLN ( " lAll " ) ;
2018-01-09 23:13:29 +01:00
server . send ( 200 , " application/json " , " { \" 1 \" :{ \" type \" : \" Extended color light \" , \" manufacturername \" : \" OpenSource \" , \" swversion \" : \" 0.1 \" , \" name \" : \" " + alexaInvocationName + " \" , \" uniqueid \" : \" " + WiFi . macAddress ( ) + " -2 \" , \" modelid \" : \" LST001 \" , \" state \" :{ \" on \" : " + boolString ( bri ) + " , \" bri \" : " + briForHue ( bri ) + " , \" xy \" :[0.00000,0.00000], \" colormode \" : \" hs \" , \" effect \" : \" none \" , \" ct \" :500, \" hue \" :0, \" sat \" :0, \" alert \" : \" none \" , \" reachable \" :true}}} " ) ;
2018-01-09 11:55:07 +01:00
return true ;
}
//we dont care about other api commands at this time and send empty JSON
server . send ( 200 , " application/json " , " {} " ) ;
return true ;
}
2018-01-07 22:52:48 +01:00
boolean connectUDP ( ) {
boolean state = false ;
2018-01-09 14:21:37 +01:00
DEBUG_PRINTLN ( " " ) ;
DEBUG_PRINTLN ( " Con UDP " ) ;
2018-01-09 23:13:29 +01:00
# ifdef ARDUINO_ARCH_ESP32
if ( UDP . beginMulticast ( ipMulti , portMulti ) )
# else
if ( UDP . beginMulticast ( WiFi . localIP ( ) , ipMulti , portMulti ) )
# endif
{
2018-01-09 14:21:37 +01:00
DEBUG_PRINTLN ( " Con success " ) ;
2018-01-07 22:52:48 +01:00
state = true ;
}
else {
2018-01-09 14:21:37 +01:00
DEBUG_PRINTLN ( " Con failed " ) ;
2018-01-07 22:52:48 +01:00
}
return state ;
}
2018-01-09 23:13:29 +01:00
# else
void handleAlexa ( ) { } ;
bool handleAlexaApiCall ( String u , String b ) { return false ; } ;
void alexaInit ( ) { } ;
# endif