267 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			267 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
| /*********************************************************************************
 | |
|  *  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 //
 | |
| // 16 Configurable Lights.                                //
 | |
| //                                                        //
 | |
| ////////////////////////////////////////////////////////////
 | |
| 
 | |
| #include "HomeSpan.h" 
 | |
| #include "DEV_LED.h"     
 | |
| #include "DEV_Identify.h"       
 | |
| 
 | |
| #include <WebServer.h>                    // include WebServer library
 | |
| WebServer webServer(80);                  // create WebServer on port 80
 | |
| 
 | |
| #define NLIGHTS 16                        // maximum number of Lightbulb Accessories
 | |
| 
 | |
| 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 
 | |
| 
 | |
| nvs_handle lightNVS;                                                      // handle for NVS storage
 | |
| 
 | |
| struct {                                                                  // structure to store pin numbers and dimmable flag
 | |
|   uint8_t pin=0;
 | |
|   uint8_t dimmable=0;
 | |
| } lightData[NLIGHTS];
 | |
| 
 | |
| ////////////////////////////////////////////////////////////
 | |
| 
 | |
| void setup() {
 | |
|  
 | |
|   Serial.begin(115200);
 | |
| 
 | |
|   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");
 | |
| 
 | |
|   for(int i=0;i<NLIGHTS;i++)                              // create default names for each light
 | |
|     sprintf(lightNames[i],"Light-%02d",i+1);
 | |
| 
 | |
|   size_t len;  
 | |
|   nvs_open("LIGHTS",NVS_READWRITE,&lightNVS);             // open LIGHTS NVS
 | |
|   if(!nvs_get_blob(lightNVS,"LIGHTDATA",NULL,&len))       // if data found
 | |
|     nvs_get_blob(lightNVS,"LIGHTDATA",&lightData,&len);   // retrieve data
 | |
| 
 | |
|   // Create Bridge Accessory
 | |
|   
 | |
|   new SpanAccessory(1);  
 | |
|     new DEV_Identify("HomeSpan Hub","HomeSpan","LS-123","Light Server","1.0",3);
 | |
|     new Service::HAPProtocolInformation();
 | |
|       new Characteristic::Version("1.1.0");
 | |
| 
 | |
|   // Dynamically create a new Accessory for each Light defined
 | |
| 
 | |
|   for(int i=0;i<NLIGHTS;i++){
 | |
|     if(lightData[i].pin>0){
 | |
|       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);       
 | |
|     }
 | |
|   }
 | |
|   
 | |
| } // end of setup()
 | |
| 
 | |
| //////////////////////////////////////
 | |
| 
 | |
| void loop(){
 | |
|   
 | |
|   homeSpan.poll();
 | |
|   webServer.handleClient();               // need to process webServer once each loop
 | |
|   
 | |
| } // end of loop()
 | |
| 
 | |
| //////////////////////////////////////
 | |
| 
 | |
| void setupWeb(){
 | |
|   Serial.print("Starting Light Server Hub...\n\n");
 | |
|   webServer.begin();
 | |
| 
 | |
|   // Create web routines inline
 | |
| 
 | |
|   webServer.on("/addForm", []() {
 | |
|     String content="<html><body><h3>Add New Light Accessory</h3>";
 | |
| 
 | |
|     content+="<form action='/addLight' method='get'>";
 | |
|     content+="<label for='lightName'>Light Name:</label>";
 | |
|     content+="<input type='text' id='lightName' name='lightName' maxlength='24' required>";
 | |
|     
 | |
|     content+="<p>Select Light Features:</p>";
 | |
|     content+="<input type='radio' id='noColor' name='colorControl' value='0' checked>";
 | |
|     content+="<label for='noColor'>No Color</label><br>";
 | |
|     content+="<input type='radio' id='colorTemp' name='colorControl' value='1'>";
 | |
|     content+="<label for='colorTemp'>Color Temperature Only</label><br>";
 | |
|     content+="<input type='radio' id='fullColor' name='colorControl' value='2'>";
 | |
|     content+="<label for='fullColor'>Full RGB Color</label><br><br>";
 | |
|     
 | |
|     content+="<input type='checkbox' id='isDimmable' name='isDimmable' value='4'>";
 | |
|     content+="<label for='isDimmable'>Dimmable</label><br><br>";
 | |
|     
 | |
|     content+="<input type='submit' value='Add'>";
 | |
|     content+="<button type='button' onclick=\"document.location='/'\">Cancel</button>";
 | |
| 
 | |
| 
 | |
|     content+="</form>";
 | |
|     content+="</body></html>";
 | |
|     
 | |
|     webServer.send(200, "text/html", content);
 | |
|     
 | |
|   });  
 | |
| 
 | |
|   webServer.on("/addLight", []() {
 | |
| 
 | |
|     char lightName[32];
 | |
|     uint8_t flags=0;
 | |
| 
 | |
|     String sColorControl("colorControl");
 | |
|     String sIsDimmable("isDimmable");
 | |
|     String sLightName("lightName");
 | |
|     
 | |
|     for(int i=0;i<webServer.args();i++){
 | |
|       if(!webServer.argName(i).compareTo(sColorControl) || !webServer.argName(i).compareTo(sIsDimmable))
 | |
|         flags+=webServer.arg(i).toInt();
 | |
|       else if(!webServer.argName(i).compareTo(sLightName)){
 | |
|         webServer.arg(i).toCharArray(lightName,sizeof(lightName)-1);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     Serial.printf("'%s' %d\n",lightName,flags);
 | |
| 
 | |
|     String content="<html><body><h3>Light Added!</h3>";
 | |
|     content+="</body></html>";
 | |
|     content+="<button type='button' onclick=\"document.location='/addForm'\">Add Another</button>";
 | |
|     content+="<button type='button' onclick=\"document.location='/'\">Done</button>";
 | |
|     content+="</body></html>";
 | |
| 
 | |
|     webServer.send(200, "text/html", content);
 | |
|     
 | |
|   });  
 | |
|   
 | |
|   webServer.on("/", []() {
 | |
|   
 | |
|     String content = "<html><body><form action='/configure' method='POST'><b>HomeSpan Light Server Hub Configuration</b><br><br>";
 | |
|     content += "Select pins and check box if dimmable:<br><br>";
 | |
| 
 | |
|     for(int i=0;i<NLIGHTS;i++){
 | |
|       content += "<span style=\"color:";
 | |
|       if(lightData[i].pin==0)
 | |
|         content += "grey";
 | |
|       else if(lightData[i].dimmable)
 | |
|         content += "red";
 | |
|       else
 | |
|         content += "blue";
 | |
|       content += ";\">";
 | |
|         
 | |
|       content += String(lightNames[i]) + ": ";
 | |
|       
 | |
|       content += "<select name='p" + String(i) + "'>";
 | |
|       for(int j=0;j<sizeof(pinList);j++){
 | |
|         content += "<option value='" + String(pinList[j]) + "'";
 | |
|         if(lightData[i].pin==pinList[j])
 | |
|           content += " selected";
 | |
|         content += ">";
 | |
|         if(j>0)
 | |
|           content += "Pin " + String(pinList[j]);
 | |
|         else
 | |
|           content += "None";
 | |
|         content += "</option>";
 | |
|       }
 | |
|       content += "</select> ";
 | |
|       content += "<input type='checkbox' value='1'";
 | |
|       if(lightData[i].dimmable)
 | |
|         content += " checked";
 | |
|       content += " name='t" + String(i) + "'></span><br>";      
 | |
|     }
 | |
|     
 | |
|     content += "<br><input type='reset'><input type='submit' value='Update'></form><br>";
 | |
|     
 | |
|     webServer.send(200, "text/html", content);
 | |
|     
 | |
|   });  
 | |
| 
 | |
|   webServer.on("/configure", []() {
 | |
| 
 | |
|     for(int i=0;i<NLIGHTS;i++)      // clear dimmable status since checkboxes only provide data if box is checked
 | |
|       lightData[i].dimmable=0;
 | |
|     
 | |
|     for(int i=0;i<webServer.args();i++){
 | |
|       switch(webServer.argName(i).charAt(0)){
 | |
|         case 'p':
 | |
|           lightData[webServer.argName(i).substring(1).toInt()].pin=webServer.arg(i).toInt();
 | |
|           break;
 | |
|         case 't':
 | |
|           lightData[webServer.argName(i).substring(1).toInt()].dimmable=webServer.arg(i).toInt();
 | |
|           break;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     String content = "<html><body>Settings Saved!<br><br>";
 | |
| 
 | |
|     for(int i=0;i<NLIGHTS;i++)
 | |
|       if(lightData[i].pin)
 | |
|         content += lightNames[i] + String(": Pin=") + String(lightData[i].pin) + String(lightData[i].dimmable?" Dimmable":"") + "<br>";
 | |
|       else
 | |
|         lightData[i].dimmable=0;
 | |
|     
 | |
|     content += "<br><button onclick=\"document.location='/'\">Return</button> ";
 | |
|     content += "<button onclick=\"document.location='/reboot'\">Reboot</button>";
 | |
| 
 | |
|     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 = "<html><body>Rebooting!  Will return to configuration page in 10 seconds.<br><br>";
 | |
|     content += "<meta http-equiv = \"refresh\" content = \"10; url = /\" />";
 | |
|     webServer.send(200, "text/html", content);
 | |
| 
 | |
|     for(int j=0;j<sizeof(pinList);j++)            // this seems to be needed to ensure all pins are disconnected from led PWM on reboot
 | |
|       gpio_reset_pin((gpio_num_t)pinList[j]);     // otherwise ESP32 seems to be retaining some info about pins connectivity?
 | |
|       
 | |
|     ESP.restart();
 | |
|   });
 | |
| 
 | |
| } // setupWeb
 |