Added Other Examples -> ProgrammableHub
This commit is contained in:
parent
758b8880b1
commit
6c7bf03c3b
|
|
@ -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
|
||||||
|
|
||||||
|
};
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
//////////////////////////////////
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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";
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue