1010 */
1111/*
1212 * @title Espalexa library
13- * @version 2.5 .0
13+ * @version 2.7 .0
1414 * @author Christian Schwinne
1515 * @license MIT
1616 * @contributors d-999
2525// #define ESPALEXA_NO_SUBPAGE
2626
2727#ifndef ESPALEXA_MAXDEVICES
28- #define ESPALEXA_MAXDEVICES 10 // this limit only has memory reasons, set it higher should you need to
28+ #define ESPALEXA_MAXDEVICES 10 // this limit only has memory reasons, set it higher should you need to, max 128
2929#endif
3030
3131// #define ESPALEXA_DEBUG
5050#include " ../network/Network.h"
5151
5252#ifdef ESPALEXA_DEBUG
53- #pragma message "Espalexa 2.5 .0 debug mode"
53+ #pragma message "Espalexa 2.7 .0 debug mode"
5454 #define EA_DEBUG (x ) Serial.print (x)
5555 #define EA_DEBUGLN (x ) Serial.println (x)
5656#else
@@ -76,13 +76,14 @@ class Espalexa {
7676 #endif
7777 uint8_t currentDeviceCount = 0 ;
7878 bool discoverable = true ;
79+ bool udpConnected = false ;
7980
8081 EspalexaDevice* devices[ESPALEXA_MAXDEVICES] = {};
8182 // Keep in mind that Device IDs go from 1 to DEVICES, cpp arrays from 0 to DEVICES-1!!
8283
8384 WiFiUDP espalexaUdp;
8485 IPAddress ipMulti;
85- bool udpConnected = false ;
86+ uint32_t mac24; // bottom 24 bits of mac
8687 String escapedMac=" " ; // lowercase mac address
8788
8889 // private member functions
@@ -119,33 +120,32 @@ class Espalexa {
119120
120121 void encodeLightId (uint8_t idx, char * out)
121122 {
122- // Unique id must be 12 character len
123- // use the last 10 characters of the MAC followed by the device id in hex value
124- // uniqueId: aabbccddeeii
125-
126123 uint8_t mac[6 ];
127124 WiFi.macAddress (mac);
128125
129- // shift the mac address to the left (discard first byte)
130- for (uint8_t i = 0 ; i < 5 ; i++) {
131- mac[i] = mac[i+1 ];
132- }
133- mac[5 ] = idx;
126+ sprintf_P (out, PSTR (" %02X:%02X:%02X:%02X:%02X:%02X:00:11-%02X" ), mac[0 ],mac[1 ],mac[2 ],mac[3 ],mac[4 ],mac[5 ], idx);
127+ }
134128
135- for (uint8_t i = 0 ; i < 6 ; i++) {
136- sprintf (out + i*2 , " %.2x" , mac[i]);
137- }
129+ // construct 'globally unique' Json dict key fitting into signed int
130+ inline int encodeLightKey (uint8_t idx)
131+ {
132+ // return idx +1;
133+ static_assert (ESPALEXA_MAXDEVICES <= 128 , " " );
134+ return (mac24<<7 ) | idx;
138135 }
139-
136+
137+ // get device index from Json key
138+ uint8_t decodeLightKey (int key)
139+ {
140+ // return key -1;
141+ return (((uint32_t )key>>7 ) == mac24) ? (key & 127U ) : 255U ;
142+ }
143+
140144 // device JSON string: color+temperature device emulates LCT015, dimmable device LWB010, (TODO: on/off Plug 01, color temperature device LWT010, color device LST001)
141- void deviceJsonString (uint8_t deviceId , char * buf)
145+ void deviceJsonString (EspalexaDevice* dev , char * buf)
142146 {
143- deviceId--;
144- if (deviceId >= currentDeviceCount) {strcpy (buf," {}" ); return ;} // error
145- EspalexaDevice* dev = devices[deviceId];
146-
147- char buf_lightid[13 ];
148- encodeLightId (deviceId + 1 , buf_lightid);
147+ char buf_lightid[27 ];
148+ encodeLightId (dev->getId () + 1 , buf_lightid);
149149
150150 char buf_col[80 ] = " " ;
151151 // color support
@@ -167,7 +167,7 @@ class Espalexa {
167167
168168 sprintf_P (buf, PSTR (" {\" state\" :{\" on\" :%s,\" bri\" :%u%s%s,\" alert\" :\" none%s\" ,\" mode\" :\" homeautomation\" ,\" reachable\" :true},"
169169 " \" type\" :\" %s\" ,\" name\" :\" %s\" ,\" modelid\" :\" %s\" ,\" manufacturername\" :\" Philips\" ,\" productname\" :\" E%u"
170- " \" ,\" uniqueid\" :\" %s\" ,\" swversion\" :\" espalexa-2.5 .0\" }" )
170+ " \" ,\" uniqueid\" :\" %s\" ,\" swversion\" :\" espalexa-2.7 .0\" }" )
171171
172172 , (dev->getValue ())?" true" :" false" , dev->getLastValue ()-1 , buf_col, buf_ct, buf_cm, typeString (dev->getType ()),
173173 dev->getName ().c_str (), modelidString (dev->getType ()), static_cast <uint8_t >(dev->getType ()), buf_lightid);
@@ -192,7 +192,7 @@ class Espalexa {
192192 }
193193 res += " \r\n Free Heap: " + (String)ESP.getFreeHeap ();
194194 res += " \r\n Uptime: " + (String)millis ();
195- res += " \r\n\r\n Espalexa library v2.5 .0 by Christian Schwinne 2020 " ;
195+ res += " \r\n\r\n Espalexa library v2.7 .0 by Christian Schwinne 2021 " ;
196196 server->send (200 , " text/plain" , res);
197197 }
198198 #endif
@@ -335,6 +335,9 @@ class Espalexa {
335335 escapedMac.replace (" :" , " " );
336336 escapedMac.toLowerCase ();
337337
338+ String macSubStr = escapedMac.substring (6 , 12 );
339+ mac24 = strtol (macSubStr.c_str (), 0 , 16 );
340+
338341 #ifdef ESPALEXA_ASYNC
339342 serverAsync = externalServer;
340343 #else
@@ -390,48 +393,55 @@ class Espalexa {
390393 }
391394 }
392395
393- bool addDevice (EspalexaDevice* d)
396+ // returns device index or 0 on failure
397+ uint8_t addDevice (EspalexaDevice* d)
394398 {
395399 EA_DEBUG (" Adding device " );
396400 EA_DEBUGLN ((currentDeviceCount+1 ));
397- if (currentDeviceCount >= ESPALEXA_MAXDEVICES) return false ;
398- if (d == nullptr ) return false ;
401+ if (currentDeviceCount >= ESPALEXA_MAXDEVICES) return 0 ;
402+ if (d == nullptr ) return 0 ;
399403 d->setId (currentDeviceCount);
400404 devices[currentDeviceCount] = d;
401- currentDeviceCount++;
402- return true ;
405+ return ++currentDeviceCount;
403406 }
404407
405408 // brightness-only callback
406- bool addDevice (String deviceName, BrightnessCallbackFunction callback, uint8_t initialValue = 0 )
409+ uint8_t addDevice (String deviceName, BrightnessCallbackFunction callback, uint8_t initialValue = 0 )
407410 {
408411 EA_DEBUG (" Constructing device " );
409412 EA_DEBUGLN ((currentDeviceCount+1 ));
410- if (currentDeviceCount >= ESPALEXA_MAXDEVICES) return false ;
413+ if (currentDeviceCount >= ESPALEXA_MAXDEVICES) return 0 ;
411414 EspalexaDevice* d = new EspalexaDevice (deviceName, callback, initialValue);
412415 return addDevice (d);
413416 }
414417
415418 // brightness-only callback
416- bool addDevice (String deviceName, ColorCallbackFunction callback, uint8_t initialValue = 0 )
419+ uint8_t addDevice (String deviceName, ColorCallbackFunction callback, uint8_t initialValue = 0 )
417420 {
418421 EA_DEBUG (" Constructing device " );
419422 EA_DEBUGLN ((currentDeviceCount+1 ));
420- if (currentDeviceCount >= ESPALEXA_MAXDEVICES) return false ;
423+ if (currentDeviceCount >= ESPALEXA_MAXDEVICES) return 0 ;
421424 EspalexaDevice* d = new EspalexaDevice (deviceName, callback, initialValue);
422425 return addDevice (d);
423426 }
424427
425428
426- bool addDevice (String deviceName, DeviceCallbackFunction callback, EspalexaDeviceType t = EspalexaDeviceType::dimmable, uint8_t initialValue = 0 )
429+ uint8_t addDevice (String deviceName, DeviceCallbackFunction callback, EspalexaDeviceType t = EspalexaDeviceType::dimmable, uint8_t initialValue = 0 )
427430 {
428431 EA_DEBUG (" Constructing device " );
429432 EA_DEBUGLN ((currentDeviceCount+1 ));
430- if (currentDeviceCount >= ESPALEXA_MAXDEVICES) return false ;
433+ if (currentDeviceCount >= ESPALEXA_MAXDEVICES) return 0 ;
431434 EspalexaDevice* d = new EspalexaDevice (deviceName, callback, t, initialValue);
432435 return addDevice (d);
433436 }
434-
437+
438+ void renameDevice (uint8_t id, const String& deviceName)
439+ {
440+ unsigned int index = id - 1 ;
441+ if (index < currentDeviceCount)
442+ devices[index]->setName (deviceName);
443+ }
444+
435445 // basic implementation of Philips hue api functions needed for basic Alexa control
436446 #ifdef ESPALEXA_ASYNC
437447 bool handleAlexaApiCall (AsyncWebServerRequest* request)
@@ -450,6 +460,8 @@ class Espalexa {
450460 bool handleAlexaApiCall (String req, String body)
451461 {
452462 #endif
463+ EA_DEBUG (" URL: " );
464+ EA_DEBUGLN (req);
453465 EA_DEBUGLN (" AlexaApiCall" );
454466 if (req.indexOf (" api" ) <0 ) return false ; // return if not an API call
455467 EA_DEBUGLN (" ok" );
@@ -458,70 +470,72 @@ class Espalexa {
458470 {
459471 EA_DEBUGLN (" devType" );
460472 body = " " ;
461- server->send (200 , " application/json" , F (" [{\" success\" :{\" username\" :\" 2WLEDHardQrI3WHYTHoMcXHgEspsM8ZZRpSKtBQr \" }}]" ));
473+ server->send (200 , " application/json" , F (" [{\" success\" :{\" username\" :\" 2BLEDHardQrI3WHYTHoMcXHgEspsM8ZZRpSKtBGr \" }}]" ));
462474 return true ;
463475 }
464476
465477 if ((req.indexOf (" state" ) > 0 ) && (body.length () > 0 )) // client wants to control light
466478 {
467- server->send (200 , " application/json" , F (" [{\" success\" :{\" /lights/1/state/\" : true}}]" ));
468-
469479 uint32_t devId = req.substring (req.indexOf (" lights" )+7 ).toInt ();
470480 EA_DEBUG (" ls" ); EA_DEBUGLN (devId);
471- EA_DEBUGLN (devId);
472- devId--; // zero-based for devices array
473- if (devId >= currentDeviceCount) return true ; // return if invalid ID
481+ unsigned idx = decodeLightKey (devId);
482+ EA_DEBUGLN (idx);
483+ char buf[50 ];
484+ sprintf_P (buf,PSTR (" [{\" success\" :{\" /lights/%u/state/\" : true}}]" ),devId);
485+ server->send (200 , " application/json" , buf);
486+ if (idx >= currentDeviceCount) return true ; // return if invalid ID
487+ EspalexaDevice* dev = devices[idx];
474488
475- devices[devId] ->setPropertyChanged (EspalexaDeviceProperty::none);
489+ dev ->setPropertyChanged (EspalexaDeviceProperty::none);
476490
477491 if (body.indexOf (" false" )>0 ) // OFF command
478492 {
479- devices[devId] ->setValue (0 );
480- devices[devId] ->setPropertyChanged (EspalexaDeviceProperty::off);
481- devices[devId] ->doCallback ();
493+ dev ->setValue (0 );
494+ dev ->setPropertyChanged (EspalexaDeviceProperty::off);
495+ dev ->doCallback ();
482496 return true ;
483497 }
484498
485499 if (body.indexOf (" true" ) >0 ) // ON command
486500 {
487- devices[devId] ->setValue (devices[devId] ->getLastValue ());
488- devices[devId] ->setPropertyChanged (EspalexaDeviceProperty::on);
501+ dev ->setValue (dev ->getLastValue ());
502+ dev ->setPropertyChanged (EspalexaDeviceProperty::on);
489503 }
490504
491505 if (body.indexOf (" bri" ) >0 ) // BRIGHTNESS command
492506 {
493507 uint8_t briL = body.substring (body.indexOf (" bri" ) +5 ).toInt ();
494508 if (briL == 255 )
495509 {
496- devices[devId] ->setValue (255 );
510+ dev ->setValue (255 );
497511 } else {
498- devices[devId] ->setValue (briL+1 );
512+ dev ->setValue (briL+1 );
499513 }
500- devices[devId] ->setPropertyChanged (EspalexaDeviceProperty::bri);
514+ dev ->setPropertyChanged (EspalexaDeviceProperty::bri);
501515 }
502516
503517 if (body.indexOf (" xy" ) >0 ) // COLOR command (XY mode)
504518 {
505- devices[devId] ->setColorXY (body.substring (body.indexOf (" [" ) +1 ).toFloat (), body.substring (body.indexOf (" ,0" ) +1 ).toFloat ());
506- devices[devId] ->setPropertyChanged (EspalexaDeviceProperty::xy);
519+ dev ->setColorXY (body.substring (body.indexOf (" [" ) +1 ).toFloat (), body.substring (body.indexOf (" ,0" ) +1 ).toFloat ());
520+ dev ->setPropertyChanged (EspalexaDeviceProperty::xy);
507521 }
508522
509523 if (body.indexOf (" hue" ) >0 ) // COLOR command (HS mode)
510524 {
511- devices[devId] ->setColor (body.substring (body.indexOf (" hue" ) +5 ).toInt (), body.substring (body.indexOf (" sat" ) +5 ).toInt ());
512- devices[devId] ->setPropertyChanged (EspalexaDeviceProperty::hs);
525+ dev ->setColor (body.substring (body.indexOf (" hue" ) +5 ).toInt (), body.substring (body.indexOf (" sat" ) +5 ).toInt ());
526+ dev ->setPropertyChanged (EspalexaDeviceProperty::hs);
513527 }
514528
515529 if (body.indexOf (" ct" ) >0 ) // COLOR TEMP command (white spectrum)
516530 {
517- devices[devId] ->setColor (body.substring (body.indexOf (" ct" ) +4 ).toInt ());
518- devices[devId] ->setPropertyChanged (EspalexaDeviceProperty::ct);
531+ dev ->setColor (body.substring (body.indexOf (" ct" ) +4 ).toInt ());
532+ dev ->setPropertyChanged (EspalexaDeviceProperty::ct);
519533 }
520534
521- devices[devId] ->doCallback ();
535+ dev ->doCallback ();
522536
523537 #ifdef ESPALEXA_DEBUG
524- if (devices[devId] ->getLastChangedProperty () == EspalexaDeviceProperty::none)
538+ if (dev ->getLastChangedProperty () == EspalexaDeviceProperty::none)
525539 EA_DEBUGLN (" STATE REQ WITHOUT BODY (likely Content-Type issue #6)" );
526540 #endif
527541 return true ;
@@ -539,25 +553,31 @@ class Espalexa {
539553 String jsonTemp = " {" ;
540554 for (int i = 0 ; i<currentDeviceCount; i++)
541555 {
542- jsonTemp += " \" " + String (i+1 ) + " \" :" ;
556+ jsonTemp += ' "' ;
557+ jsonTemp += encodeLightKey (i);
558+ jsonTemp += ' "' ;
559+ jsonTemp += ' :' ;
560+
543561 char buf[512 ];
544- deviceJsonString (i+ 1 , buf);
562+ deviceJsonString (devices[i] , buf);
545563 jsonTemp += buf;
546- if (i < currentDeviceCount-1 ) jsonTemp += " , " ;
564+ if (i < currentDeviceCount-1 ) jsonTemp += ' , ' ;
547565 }
548- jsonTemp += " } " ;
566+ jsonTemp += ' } ' ;
549567 server->send (200 , " application/json" , jsonTemp);
550568 } else // client wants one light (devId)
551569 {
552570 EA_DEBUGLN (devId);
553- if (devId > currentDeviceCount)
554- {
571+ unsigned int idx = decodeLightKey (devId);
572+
573+ if (idx >= currentDeviceCount) idx = 0 ; // send first device if invalid
574+ if (currentDeviceCount == 0 ) {
555575 server->send (200 , " application/json" , " {}" );
556- } else {
557- char buf[512 ];
558- deviceJsonString (devId, buf);
559- server->send (200 , " application/json" , buf);
576+ return true ;
560577 }
578+ char buf[512 ];
579+ deviceJsonString (devices[idx], buf);
580+ server->send (200 , " application/json" , buf);
561581 }
562582
563583 return true ;
0 commit comments