/********************************************************************************* * MIT License * * Copyright (c) 2022 Gregg E. Berman * * https://github.com/HomeSpan/HomeSpan * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * 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 // // 12 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 12 #define MAX_NAME_LENGTH 32 #define HUB_NAME "lighthub" enum colorType_t : uint8_t { NO_COLOR, TEMPERATURE_ONLY, FULL_RGB }; uint32_t aidStore=2; // keep track of unique AID numbers - start with AID=2 struct lightData_t { char name[MAX_NAME_LENGTH+1]=""; uint32_t aid=0; boolean isDimmable:1; colorType_t colorType:2; } lightData[MAX_LIGHTS]; 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.setWifiCallback(setupWeb); // need to start Web Server after WiFi is established homeSpan.begin(Category::Lighting,"HomeSpan Light Hub",HUB_NAME); new SpanAccessory(1); // here we specified the AID=1 for clarity (it would default to 1 anyway if left blank) new Service::AccessoryInformation(); new Characteristic::Identify(); new Characteristic::Model("HomeSpan Programmable Hub"); new Characteristic::AccessoryFlags(); 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(); // 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--; } } response += ""; response += ""; response += ""; response += ""; response += ""; response += ""; response += ""; response += ""; response += ""; 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:

"; 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", []() { colorType_t colorType=NO_COLOR; boolean isDimmable=false; int iName=-1; for(int i=0;i