diff --git a/Other Examples/ProgrammableHub/DEV_Identify.h b/Other Examples/ProgrammableHub/DEV_Identify.h deleted file mode 100644 index b8d21d6..0000000 --- a/Other Examples/ProgrammableHub/DEV_Identify.h +++ /dev/null @@ -1,38 +0,0 @@ - -////////////////////////////////// -// DEVICE-SPECIFIC SERVICES // -////////////////////////////////// - -struct DEV_Identify : Service::AccessoryInformation { - - int nBlinks; // number of times to blink built-in LED in identify routine - SpanCharacteristic *identify; // reference to the Identify Characteristic - - DEV_Identify(const char *name, const char *manu, const char *sn, const char *model, const char *version, int nBlinks) : Service::AccessoryInformation(){ - - new Characteristic::Name(name); // create all the required Characteristics with values set based on above arguments - new Characteristic::Manufacturer(manu); - new Characteristic::SerialNumber(sn); - new Characteristic::Model(model); - new Characteristic::FirmwareRevision(version); - identify=new Characteristic::Identify(); // store a reference to the Identify Characteristic for use below - - this->nBlinks=nBlinks; // store the number of times to blink the LED - - pinMode(homeSpan.getStatusPin(),OUTPUT); // make sure LED is set for output - } - - boolean update(){ - - for(int i=0;isetRange(5,100,1); // sets the range of the Brightness to be from a min of 5%, to a max of 100%, in steps of 1% - } - - this->LED=new LedPin(ledPin); // configures a PWM LED for output to pin number "ledPin" - - Serial.printf("Configuring LED: Pin=%d %s\n",LED->getPin(),isDimmable?"(Dimmable)":""); // initialization message - - LED->set(power->getVal()*(isDimmable?(level->getVal()):100)); // set the LED to its initial state at startup. - - } // end constructor - - boolean update(){ // update() method - - LOG1("Updating LED on pin="); - LOG1(LED->getPin()); - LOG1(": Current Power="); - LOG1(power->getVal()?"true":"false"); - if(isDimmable){ - LOG1(" Current Brightness="); - LOG1(level->getVal()); - } - - if(power->updated()){ - LOG1(" New Power="); - LOG1(power->getNewVal()?"true":"false"); - } - - if(isDimmable && level->updated()){ - LOG1(" New Brightness="); - LOG1(level->getNewVal()); - } - - LOG1("\n"); - - LED->set(power->getNewVal()*(isDimmable?(level->getNewVal()):100)); // update the physical LED to reflect the new values - - return(true); // return true - - } // update - -}; - -////////////////////////////////// diff --git a/Other Examples/ProgrammableHub/ProgrammableHub.ino b/Other Examples/ProgrammableHub/ProgrammableHub.ino index 82e07e5..1bb4154 100644 --- a/Other Examples/ProgrammableHub/ProgrammableHub.ino +++ b/Other Examples/ProgrammableHub/ProgrammableHub.ino @@ -24,243 +24,364 @@ * SOFTWARE. * ********************************************************************************/ - -//////////////////////////////////////////////////////////// -// // -// HomeSpan: A HomeKit implementation for the ESP32 // -// ------------------------------------------------ // -// // -// Demonstrates how to implement a Web Server alongside // -// of HomeSpan to create a Programmable Hub serving up to // -// 16 Configurable Lights. // -// // -//////////////////////////////////////////////////////////// -#include "HomeSpan.h" -#include "DEV_LED.h" -#include "DEV_Identify.h" +////////////////////////////////////////////////////////////////// +// // +// HomeSpan: A HomeKit implementation for the ESP32 // +// ------------------------------------------------ // +// // +// Demonstrates how to implement a Web Server alongside // +// of HomeSpan to create a Programmable Hub serving up to // +// 24 Configurable Lights. Allows for dynamic changes // +// to Accessories without needing to reboot // +// // +////////////////////////////////////////////////////////////////// +#include "HomeSpan.h" #include // include WebServer library + WebServer webServer(80); // create WebServer on port 80 + +#define MAX_LIGHTS 24 +#define MAX_NAME_LENGTH 32 +#define HUB_NAME "lighthub" -#define NLIGHTS 16 // maximum number of Lightbulb Accessories +enum colorType_t : uint8_t { + NO_COLOR, + TEMPERATURE_ONLY, + FULL_RGB +}; -uint8_t pinList[]={0,4,5,12,14,15,16,17,18,19,22,23,25,26,27,32,33}; // list of allowed pins -char lightNames[NLIGHTS][9]; // storage for default light names +uint32_t aidStore=2; // keep track of unique AID numbers - start with AID=2 -nvs_handle lightNVS; // handle for NVS storage +struct lightData_t { + char name[MAX_NAME_LENGTH+1]=""; + uint32_t aid=0; + boolean isDimmable:1; + colorType_t colorType:2; +} lightData[MAX_LIGHTS]; -struct { // structure to store pin numbers and dimmable flag - uint8_t pin=0; - uint8_t dimmable=0; -} lightData[NLIGHTS]; +nvs_handle savedData; -//////////////////////////////////////////////////////////// +////////////////////////////////////// void setup() { - + Serial.begin(115200); + size_t len; + nvs_open("SAVED_DATA",NVS_READWRITE,&savedData); // open a new namespace called SAVED_DATA in the NVS + + if(!nvs_get_blob(savedData,"LIGHTDATA",NULL,&len)) // if LIGHTDATA data found + nvs_get_blob(savedData,"LIGHTDATA",&lightData,&len); // retrieve data + + nvs_get_u32(savedData,"AID",&aidStore); // get AID, if it exists + homeSpan.setLogLevel(1); homeSpan.setHostNameSuffix(""); // use null string for suffix (rather than the HomeSpan device ID) homeSpan.setPortNum(1201); // change port number for HomeSpan so we can use port 80 for the Web Server - homeSpan.enableOTA(); // enable OTA updates - homeSpan.setMaxConnections(5); // reduce max connection to 5 (default is 8) since WebServer and a connecting client will need 2, and OTA needs 1 - homeSpan.setWifiCallback(setupWeb); // need to start Web Server after WiFi is established - - homeSpan.enableWebLog(50,"pool.ntp.org","CST6CDT"); - - homeSpan.begin(Category::Bridges,"HomeSpan Light Hub","homespanhub"); + homeSpan.setWifiCallback(setupWeb); // need to start Web Server after WiFi is established - for(int i=0;i0){ - new SpanAccessory(i+2); - new DEV_Identify(lightNames[i],"HomeSpan",lightNames[i],lightData[i].dimmable?"Dimmable":"Not Dimmable","1.0",0); - new DEV_GenericLED(lightData[i].pin,lightData[i].dimmable); - } + for(int i=0;i - add non-dimmable light accessory using name=",[](const char *c){addLight(c+1,false,NO_COLOR);}); + new SpanUserCommand('A'," - add dimmable light accessory using name=",[](const char *c){addLight(c+1,true,NO_COLOR);}); + new SpanUserCommand('t'," - add non-dimmable light accessory with color-temperature control using name=",[](const char *c){addLight(c+1,false,TEMPERATURE_ONLY);}); + new SpanUserCommand('T'," - add dimmable light accessory with color-temperature control using name=",[](const char *c){addLight(c+1,true,TEMPERATURE_ONLY);}); + new SpanUserCommand('r'," - add non-dimmable light accessory with full RGB color control using name=",[](const char *c){addLight(c+1,false,FULL_RGB);}); + new SpanUserCommand('R'," - add dimmable light accessory with full RGB color control using name=",[](const char *c){addLight(c+1,true,FULL_RGB);}); + + new SpanUserCommand('l'," - list all light accessories",listAccessories); + new SpanUserCommand('d'," - delete a light accessory with index=",[](const char *buf){deleteAccessory(atoi(buf+1));}); + new SpanUserCommand('D'," - delete ALL light accessories",deleteAllAccessories); + new SpanUserCommand('u',"- update accessories database",updateAccessories); + } // end of setup() -////////////////////////////////////// +/////////////////////////// void loop(){ - homeSpan.poll(); - webServer.handleClient(); // need to process webServer once each loop - -} // end of loop() + webServer.handleClient(); // handle incoming web server traffic +} -////////////////////////////////////// +/////////////////////////// + +void addLight(int index){ + + Serial.printf("Adding Light Accessory: Name='%s' Dimmable=%s Color=%s\n", + lightData[index].name,lightData[index].isDimmable?"YES":"NO",lightData[index].colorType==NO_COLOR?"NONE":(lightData[index].colorType==TEMPERATURE_ONLY?"TEMPERATURE_ONLY":"FULL_RGB")); + + new SpanAccessory(lightData[index].aid); + new Service::AccessoryInformation(); + new Characteristic::Identify(); + new Characteristic::Name(lightData[index].name); + char sNum[32]; + sprintf(sNum,"Light-%02d",index); + new Characteristic::SerialNumber(sNum); + + new Service::LightBulb(); + new Characteristic::On(0,true); + if(lightData[index].isDimmable) + new Characteristic::Brightness(100,true); + if(lightData[index].colorType==TEMPERATURE_ONLY) + new Characteristic::ColorTemperature(200,true); + if(lightData[index].colorType==FULL_RGB){ + new Characteristic::Hue(0,true); + new Characteristic::Saturation(0,true); + } + +} + +/////////////////////////// + +int addLight(const char *name, boolean isDimmable, colorType_t colorType){ + + int index=0; + for(index=0;indexsizeof(lightData[index].name)) + Serial.printf("Warning - name trimmed to max length of %d characters.\n",MAX_NAME_LENGTH); + + lightData[index].isDimmable=isDimmable; + lightData[index].colorType=colorType; + lightData[index].aid=aidStore++; + + nvs_set_blob(savedData,"LIGHTDATA",&lightData,sizeof(lightData)); // update data in the NVS + nvs_set_u32(savedData,"AID",aidStore); + nvs_commit(savedData); + + addLight(index); + return(index); +} + +/////////////////////////// + +size_t strncpy_trim(char *dest, const char *src, size_t dSize){ + + while(*src==' ') // skip over any leading spaces + src++; + + size_t sLen=strlen(src); // string length of src after skipping over leading spaces + while(sLen>0 && src[sLen-1]==' ') // shorten length to remove trailing spaces + sLen--; + + size_t sSize=sLen+1; // add room for null terminator + + if(dest!=NULL) + *stpncpy(dest,src,(dSize=MAX_LIGHTS){ + Serial.printf("Invalid Light Accessory index - must be between 0 and %d.\n",MAX_LIGHTS-1); + return; + } + + if(homeSpan.deleteAccessory(lightData[index].aid)){ // if deleteAccessory() is true, a match has been found + Serial.printf("Deleting Light Accessory: Name='%s'\n",lightData[index].name); + + lightData[index].aid=0; + nvs_set_blob(savedData,"LIGHTDATA",&lightData,sizeof(lightData)); // update data in the NVS + nvs_commit(savedData); + + } else { + Serial.printf("Nothing to delete - there is no Light Accessory at index=%d.\n",index); + } +} + +/////////////////////////// + +void deleteAllAccessories(const char *buf){ + + for(int i=0;i"; + response += "":">") + ""; + response += "":">") + " NONE "; + response += "":">") + " TEMP ONLY "; + response += "":">") + " FULL COLOR "; + response += ""; + response += ""; + openSlots--; + } + } - content+="
"; - content+=""; - content+=""; - - content+="

Select Light Features:

"; - content+=""; - content+="
"; - content+=""; - content+="
"; - content+=""; - content+="

"; - - content+=""; - content+="

"; - - content+=""; - content+=""; + response += ""; + response += ""; + response += ""; + response += ""; + response += ""; + response += ""; + response += ""; + response += ""; + response += "
"; - content+=""; - content+=""; + if(!openSlots) + response += "

Can't add any more Light Accessories. Max="+ String(MAX_LIGHTS) + "

"; + + response += "

Press here to delete ALL Light Accessories:

"; + response += "

Press here to update the Home App when finished making changes:

"; - webServer.send(200, "text/html", content); + response += ""; + webServer.send(200, "text/html", response); + + }); + + webServer.on("/deleteLight", []() { + + int index=atoi(webServer.arg(0).c_str()); + + String response = "HomeSpan Programmable Light Hub"; + response += "Deleting Light Accessory '" + String(lightData[index].name) + "'..."; + deleteAccessory(index); + + webServer.send(200, "text/html", response); + + }); + + webServer.on("/deleteAll", []() { + + String response = "HomeSpan Programmable Light Hub"; + response += "Deleting All Light Accessories..."; + + webServer.send(200, "text/html", response); + deleteAllAccessories(""); + + }); + + webServer.on("/update", []() { + + String response = "HomeSpan Programmable Light Hub"; + + if(homeSpan.updateDatabase()) + response += "Accessories Database updated. New configuration number broadcasted..."; + else + response += "Nothing to update - no changes were made..."; + + response += "..."; + + webServer.send(200, "text/html", response); + }); webServer.on("/addLight", []() { - char lightName[32]; - uint8_t flags=0; - - String sColorControl("colorControl"); - String sIsDimmable("isDimmable"); - String sLightName("lightName"); - + colorType_t colorType=NO_COLOR; + boolean isDimmable=false; + int iName=-1; + for(int i=0;iAdd Another"; - content+=""; - content+=""; + if(iName!=-1){ + int index=addLight(webServer.arg(iName).c_str(),isDimmable,colorType); + response += "Adding Light Accessory '" + String(lightData[index].name) + "'"; + } else + response += "Error - bad URL request"; - webServer.send(200, "text/html", content); + response += "..."; - }); - - webServer.on("/", []() { - - String content = "
HomeSpan Light Server Hub Configuration

"; - content += "Select pins and check box if dimmable:

"; + webServer.send(200, "text/html", response); - for(int i=0;i"; - - content += String(lightNames[i]) + ": "; - - content += " "; - content += "
"; - } - - content += "

"; - - webServer.send(200, "text/html", content); - - }); - - webServer.on("/configure", []() { - - for(int i=0;i"; - else - lightData[i].dimmable=0; - - content += "
"; - content += ""; - - nvs_set_blob(lightNVS,"LIGHTDATA",&lightData,sizeof(lightData)); // update data - nvs_commit(lightNVS); // commit to NVS - - webServer.send(200, "text/html", content); - }); - webServer.on("/reboot", []() { - - String content = "Rebooting! Will return to configuration page in 10 seconds.

"; - content += ""; - webServer.send(200, "text/html", content); - for(int j=0;j