Added Other Examples -> ProgrammableHub

This commit is contained in:
Gregg 2022-06-25 18:44:31 -05:00
parent 758b8880b1
commit 6c7bf03c3b
4 changed files with 332 additions and 1 deletions

View File

@ -0,0 +1,38 @@
//////////////////////////////////
// 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;i<nBlinks;i++){
digitalWrite(homeSpan.getStatusPin(),LOW);
delay(250);
digitalWrite(homeSpan.getStatusPin(),HIGH);
delay(250);
}
return(true); // return true
} // update
};

View File

@ -0,0 +1,69 @@
////////////////////////////////////
// DEVICE-SPECIFIC LED SERVICES //
////////////////////////////////////
#include "extras/PwmPin.h" // library of various PWM functions
////////////////////////////////////
struct DEV_GenericLED : Service::LightBulb { // Generic LED
// This version of the LED Service allows for dimmable and non-dimmable devices. Status of both the
// power state and the brightness of the LED are stored in NVS for restoration if the device reboots.
LedPin *LED; // reference to an LedPin
SpanCharacteristic *power; // reference to the On Characteristic
SpanCharacteristic *level; // reference to the Brightness Characteristic
boolean isDimmable; // flag to indicate whether light is dimmable
DEV_GenericLED(int ledPin, uint8_t dimmable=0) : Service::LightBulb(){
power=new Characteristic::On(0,true); // second argument is true, so the value of the On Characteristic (initially set to 0) will be saved in NVS
isDimmable=dimmable;
if(isDimmable){
level=new Characteristic::Brightness(50,true); // second argument is true, so the value of the Brightness Characteristic (initially set to 50) will be saved in NVS
level->setRange(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
};
//////////////////////////////////

View File

@ -0,0 +1,224 @@
/*********************************************************************************
* 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.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><br><br>";
content+="<input type='radio' id='0' name='isDimmable' value='Not Dimmable'>";
content+="<br><input type='submit' value='Add'>";
content+="<button type='button' onclick='window.history.back();'>Cancel</button>";
content+="</form>";
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

View File

@ -1250,7 +1250,7 @@ int HAPClient::getStatusURL(){
String response="HTTP/1.1 200 OK\r\nContent-type: text/html\r\n\r\n"; String response="HTTP/1.1 200 OK\r\nContent-type: text/html\r\n\r\n";
response+="<html><head><title>HomeSpan Status</title>\n"; response+="<html><head><title>" + String(homeSpan.displayName) + "</title>\n";
response+="<style>th, td {padding-right: 10px; padding-left: 10px; border:1px solid black;}"; response+="<style>th, td {padding-right: 10px; padding-left: 10px; border:1px solid black;}";
response+="</style></head>\n"; response+="</style></head>\n";
response+="<body style=\"background-color:lightblue;\">\n"; response+="<body style=\"background-color:lightblue;\">\n";