From 689ea86991ca74966ad02d0de0cd64471d5b2b52 Mon Sep 17 00:00:00 2001 From: Gregg Date: Thu, 28 Jul 2022 17:13:04 -0500 Subject: [PATCH] Updating ProgrammableHub2 Example --- .../ProgrammableHub2/ProgrammableHub2.ino | 267 +++++++----------- src/HomeSpan.cpp | 4 +- 2 files changed, 102 insertions(+), 169 deletions(-) diff --git a/Other Examples/ProgrammableHub2/ProgrammableHub2.ino b/Other Examples/ProgrammableHub2/ProgrammableHub2.ino index 65436dd..3489364 100644 --- a/Other Examples/ProgrammableHub2/ProgrammableHub2.ino +++ b/Other Examples/ProgrammableHub2/ProgrammableHub2.ino @@ -30,27 +30,13 @@ // HomeSpan: A HomeKit implementation for the ESP32 // // ------------------------------------------------ // // // -// Example 20: Demonstrates various advance HomeSpan functions // -// by implementing a Bridge in which one or more // -// Lightbulb Accessories can be added and deleted // -// *dynamically* without needing to restart the // -// device // // // ////////////////////////////////////////////////////////////////// #include "HomeSpan.h" - - // In Example 20 we will implement a bridge device supporting up to 10 Lightbulb Accessories. However, rather than pre-specifying the number of Lights, we - // will allow Light Accessories to be added and deleted dynamically by the user via the CLI. Changes are reflected in the Home App without the need to restart - // the device! Note this example uses a variety of advanced HomeSpan functions, as well as some detailed features of both the ESP32-IDF and C++ that have not been used - // in any of the previous examples. - - // We will use a C++ array with 10 elements containing integers representing the Light "ID" of each Lightbulb Accessory implemented. An ID of zero means there is no - // Light defined in that element. - -#include // include the C++ standard library array container - -#define MAX_LIGHTS 10 + +#define MAX_LIGHTS 10 +#define MAX_NAME_LENGTH 10 enum colorType_t : uint8_t{ NO_COLOR, @@ -59,27 +45,18 @@ enum colorType_t : uint8_t{ }; struct lightData_t { - char name[33]=""; + char name[MAX_NAME_LENGTH+1]=""; union { struct { boolean isConfigured:1; - boolean isdimmable:1; + boolean isDimmable:1; colorType_t colorType:2; }; uint8_t val=0; }; -}; +} lightData[MAX_LIGHTS]; -std::array lightData; - -std::array lights; // declare "lights" to be an array of 10 integers - -using std::fill; // place the std library function fill, remove, and find, into the global namespace so we can use them below without prefacing with "std::" -using std::remove; -using std::find; - - // We will use non-volatile storage (NVS) to store the lights array so that the device can restore the current configuration upon rebooting - + nvs_handle savedData; // declare savdData as a handle to be used with the NVS (see the ESP32-IDF for details on how to use NVS storage) ////////////////////////////////////// @@ -88,170 +65,130 @@ void setup() { Serial.begin(115200); - fill(lights.begin(),lights.end(),0); // initialize lights array with zeros in each of the 10 elements (no Light Accessories defined) - 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,"LIGHTS",NULL,&len)) // if LIGHTS data found - nvs_get_blob(savedData,"LIGHTS",&lights,&len); // retrieve data + + if(!nvs_get_blob(savedData,"LIGHTDATA",NULL,&len)) // if LIGHTDATA data found + nvs_get_blob(savedData,"LIGHTDATA",&lightData,&len); // retrieve data homeSpan.setLogLevel(1); - homeSpan.begin(Category::Lighting,"HomeSpan Lights"); - - // We begin by creating the Bridge Accessory + homeSpan.begin(Category::Lighting,"HomeSpan Light Hub"); 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 Dynamic Bridge"); // defining the Model is optional + new Characteristic::Model("HomeSpan Programmable Hub"); - // Now we create Light Accessories based on what is recorded in the lights array - // We'll use C++ iterators to loop over all elements until we reach the end of the array, or find an element with a value of zero - - for(auto it=lights.begin(); it!=lights.end() && *it!=0; it++) // loop over all elements (stopping when we get to the end, or hit an element with a value of zero) - addLight(*it); // call addLight (defined further below) with an argument equal to the integer stored in that element - - for(auto it=lightData.begin(); it!=lightData.end(); it++){ // loop over all elements in lightData array - if((*it).isConfigured); + for(int i=0;i - add non-dimmable light accessory using name=",[](const char *c){addLight(-1,c+1,false,NO_COLOR);}); + new SpanUserCommand('A'," - add dimmable light accessory using name=",[](const char *c){addLight(-1,c+1,true,NO_COLOR);}); + new SpanUserCommand('t'," - add non-dimmable light accessory with color-temperature control using name=",[](const char *c){addLight(-1,c+1,false,TEMPERATURE_ONLY);}); + new SpanUserCommand('T'," - add dimmable light accessory with color-temperature control using name=",[](const char *c){addLight(-1,c+1,true,TEMPERATURE_ONLY);}); + new SpanUserCommand('r'," - add non-dimmable light accessory with full RGB color control using name=",[](const char *c){addLight(-1,c+1,false,FULL_RGB);}); + new SpanUserCommand('R'," - add dimmable light accessory with full RGB color control using name=",[](const char *c){addLight(-1,c+1,true,FULL_RGB);}); - // Next we create user-defined CLI commands so we can add Light Accessories of different types from the CLI. - // We'll use inline lambda functions each calling addLight() with different parameters. - - new SpanUserCommand('a'," - 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('d'," - delete a light accessory with id=",deleteAccessory); + new SpanUserCommand('l'," - list all light accessories",listAccessories); + new SpanUserCommand('d'," - delete a light accessory with index=",deleteAccessory); new SpanUserCommand('D'," - delete ALL light accessories",deleteAllAccessories); new SpanUserCommand('u',"- update accessories database",updateAccessories); - // Finally we call autoPoll to start polling the background. Note this is purely optional and only used here to illustrate how to - // use autoPoll - you could instead have called the usual homeSpan.poll() function by including it inside the Arduino loop() function - homeSpan.autoPoll(); } // end of setup() -// Usually the Arduino loop() function would be defined somewhere here. But since we used autoPoll in the setup() function, -// we don't have to define the loop() function at all in this sketch! Why don't we get an error? Because HomeSpan includes -// a default loop() function, which prevents the compiler from complaining about loop() being undefined. - /////////////////////////// -// This function creates a new Light Accessory. It is called initially in setup() above to create Light Accessories based -// on what was stored in the lights array. It is also called in response to adding lights via the CLI, which dynamically -// adds a new Light Accessory while the device is running. +void addLight(int index, const char *name, boolean isDimmable, colorType_t colorType){ -void addLight(const char *name, boolean isDimmable, colorType_t colorType){ + if(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].isConfigured=true; + name=lightData[index].name; -/////////////////////////// + nvs_set_blob(savedData,"LIGHTDATA",&lightData,sizeof(lightData)); // update data in the NVS + nvs_commit(savedData); + } -// This function creates a new Light Accessory with n as the "ID". -// It is called initially in setup() above to create Light Accessories based -// on what was stored in the lights array. It is also called in response to -// typing 'a' into the CLI (see below), which dynamically adds a new Light Accessory -// while the device is running. + Serial.printf("Adding Light Accessory: Name='%s' Dimmable=%s Color=%s\n",name,isDimmable?"YES":"NO",colorType==NO_COLOR?"NONE":(colorType==TEMPERATURE_ONLY?"TEMPERATURE_ONLY":"FULL_RGB")); -void addLight(int n){ - - char name[32]; - sprintf(name,"Light-%d",n); // create the name of the device using the specified "ID" - char sNum[32]; - sprintf(sNum,"%0.10d",n); // create serial number from the ID - this is helpful in case we rename the Light to something else using the Home App - - Serial.printf("Adding Accessory: %s\n",name); - - new SpanAccessory(n+1); // IMPORTANT: add 1, since first Accessory with AID=1 is already used by the Bridge Accessory + new SpanAccessory(index+2); // IMPORTANT: add 2, since first Accessory with AID=1 is already used by the Bridge Accessory new Service::AccessoryInformation(); new Characteristic::Identify(); new Characteristic::Name(name); - new Characteristic::SerialNumber(sNum); new Service::LightBulb(); - new Characteristic::On(0,true); + new Characteristic::On(0,true); + if(isDimmable) + new Characteristic::Brightness(100,true); + if(colorType==TEMPERATURE_ONLY) + new Characteristic::ColorTemperature(200,true); + if(colorType==FULL_RGB){ + new Characteristic::Hue(0,true); + new Characteristic::Saturation(0,true); + } + } /////////////////////////// -// This function is called in response to typing '@a ' into the CLI. -// It adds a new Light Accessory with ID=num, by calling addLight(num) above. +size_t strncpy_trim(char *dest, const char *src, size_t dSize){ -void addAccessory(const char *buf){ - - int n=atoi(buf+1); // read the value of specified + while(*src==' ') // skip over any leading spaces + src++; - if(n<1){ // ensure is greater than 0 - Serial.printf("Invalid Accessory number!\n"); - return; - } + 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--; - if(find(lights.begin(),lights.end(),n)!=lights.end()){ // search for this ID in the existing lights array - if found, report an error and return - Serial.printf("Accessory Light-%d already implemented!\n",n); - return; - } - - auto it=find(lights.begin(),lights.end(),0); // find the next "free" element in the light array (the first element with a value of zero) - - if(it==lights.end()){ // if there were no elements with a zero, the array is full and no new Lights can be added - Serial.printf("Can't add any more lights - max is %d!\n",lights.size()); - return; - } - - *it=n; // save light number - nvs_set_blob(savedData,"LIGHTS",&lights,sizeof(lights)); // update data in the NVS - nvs_commit(savedData); - addLight(n); // add light accessory by calling the function above! + size_t sSize=sLen+1; // add room for null terminator + + if(dest!=NULL) + *stpncpy(dest,src,(dSize' into the CLI. - void deleteAccessory(const char *buf){ - int n=atoi(buf+1); // same as above, we read the specified and check that it is valid (i.e. greater than 0) + int n=atoi(buf+1); - if(n<1){ - Serial.printf("Invalid Accessory number!\n"); + if(n<1 || n>MAX_LIGHTS){ + Serial.printf("Invalid Light Accessory index - must be between 1 and %d.\n",MAX_LIGHTS); return; } - // Below we use the homeSpan method deleteAccessory(aid) to completely delete the Accessory with AID=n+1. - // We add 1 because the AID of the first Light Accessory is 2, since the Bridge Accessory has an AID of 1. - // The deleteAccessory() method returns true if an Accessory with matching AID is found, otherwise it returns false. - // When deleting an Accessory, HomeSpan will print a delete message for every Service, Characteristic, loop() method, - // button() method, and SpanButton, associated with that Accessory. These are Level-1 Log messages, so you'll need - // to have the Log Level in the sketch set to 1 or 2 to receive the output. - if(homeSpan.deleteAccessory(n+1)){ // if deleteAccessory() is true, a match has been found Serial.printf("Deleting Accessory: Light-%d\n",n); - - fill(remove(lights.begin(),lights.end(),n),lights.end(),0); // remove entry from lights array and fill any undefined elements with zero - nvs_set_blob(savedData,"LIGHTS",&lights,sizeof(lights)); // update data in the NVS + + lightData[n].isConfigured=false; + nvs_set_blob(savedData,"LIGHTDATA",&lightData,sizeof(lightData)); // update data in the NVS nvs_commit(savedData); } else { - Serial.printf("No such Accessory: Light-%d\n",n); + Serial.printf("Nothing to delete - there is no Light Accessory at index=%d.\n",n); } } @@ -259,19 +196,13 @@ void deleteAccessory(const char *buf){ void deleteAllAccessories(const char *buf){ -// This function is called in response to typing '@D' into the CLI. -// It deletes all Light Accessories - - if(lights[0]==0){ // first check that there is at least one Light Accessory by checking for a non-zero ID in lights[0] - Serial.printf("There are no Light Accessories to delete!\n"); - return; + for(int i=0;i