From fbbfd3f4a6ea507bb81dcd69aaa0f1427c6600f4 Mon Sep 17 00:00:00 2001 From: Gregg Date: Mon, 21 Feb 2022 10:57:35 -0600 Subject: [PATCH 001/100] Update Pixel tutorial to refresh update() twice on initialization for DotStar Seems to be required to "sync" LEDs --- Other Examples/Pixel/Pixel.ino | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Other Examples/Pixel/Pixel.ino b/Other Examples/Pixel/Pixel.ino index f32643a..8f121d7 100644 --- a/Other Examples/Pixel/Pixel.ino +++ b/Other Examples/Pixel/Pixel.ino @@ -51,10 +51,11 @@ #elif defined(CONFIG_IDF_TARGET_ESP32C3) - #define NEOPIXEL_RGB_PIN 8 - #define NEOPIXEL_RGBW_PIN 2 - #define DOTSTAR_DATA_PIN 0 - #define DOTSTAR_CLOCK_PIN 1 + #define NEOPIXEL_RGB_PIN 0 + #define NEOPIXEL_RGBW_PIN 3 + #define DOTSTAR_DATA_PIN 7 + #define DOTSTAR_CLOCK_PIN 2 + #define DEVICE_SUFFIX "-C3" #endif @@ -149,6 +150,7 @@ struct DotStar_RGB : Service::LightBulb { // Addressable two-wire RGB LED S pixel=new Dot(dataPin,clockPin); // creates Dot LED on specified pins this->nPixels=nPixels; // save number of Pixels in this LED Strand update(); // manually call update() to set pixel with restored initial values + update(); // call second update() a second time - DotStar seems to need to be "refreshed" upon start-up } boolean update() override { @@ -164,8 +166,8 @@ struct DotStar_RGB : Service::LightBulb { // Addressable two-wire RGB LED S float hueStep=360.0/nPixels; // step size for change in hue from one pixel to the next for(int i=0;iset(color,nPixels); // set the colors according to the array return(true); From ea09e5c8f3eeb0624552989c1914ffdcc1dc8822 Mon Sep 17 00:00:00 2001 From: Gregg Date: Mon, 21 Feb 2022 15:08:19 -0600 Subject: [PATCH 002/100] Create FeatherPins.h For developer use only - facilitates testing the same code across an ESP32, ESP32-S2, and ESP32-C3 without re-wiring by using an Adafruit FeatherBoard as a common jig. --- src/FeatherPins.h | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 src/FeatherPins.h diff --git a/src/FeatherPins.h b/src/FeatherPins.h new file mode 100644 index 0000000..b4a35d6 --- /dev/null +++ b/src/FeatherPins.h @@ -0,0 +1,45 @@ +/********************************************************************************* + * MIT License + * + * Copyright (c) 2020-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. + * + ********************************************************************************/ + +// For developer use and testing only - provides a common set of pin numbers mapped to the Adafruit Feather Board +// Facilitates the testing of identical code on an ESP32, ESP32-S2, and ESP32-C3 using a common jig without rewiring + +#pragma once + +#if defined(CONFIG_IDF_TARGET_ESP32) + enum {F13=13,F12=12,F27=27,F33=33,F15=15,F32=32,F14=14,F22=22,F23=23,F26=26,F25=25,F34=34,F39=39,F36=36,F4=4,F5=5,F18=18,F19=19,F16=16,F17=17,F21=21}; + #define DEVICE_SUFFIX "" + +#elif defined(CONFIG_IDF_TARGET_ESP32S2) + enum {F13=11,F12=10,F27=7,F33=3,F15=1,F32=38,F14=33,F22=9,F23=8,F26=17,F25=18,F34=14,F39=12,F36=6,F4=5,F5=36,F18=35,F19=37,F16=44,F17=43}; + #define DEVICE_SUFFIX "-S2" + +#elif defined(CONFIG_IDF_TARGET_ESP32C3) + enum {F27=2,F33=7,F32=3,F14=10,F22=9,F23=8,F26=0,F25=1,F4=18,F5=4,F18=6,F19=5,F16=20,F17=21,F21=19}; + #define DEVICE_SUFFIX "-C3" + +#endif From bf2e1354b1720cf3c887220be037f48a52035e39 Mon Sep 17 00:00:00 2001 From: Gregg Date: Tue, 22 Feb 2022 21:53:17 -0600 Subject: [PATCH 003/100] Added second version of SpanUserCommand() that takes a void *arg Useful for passing a pointer to a Service that can be used in the CLI command. --- src/HomeSpan.cpp | 19 ++++++++++++++++--- src/HomeSpan.h | 9 ++++++--- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index c4d2596..b945744 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -982,7 +982,10 @@ void Span::processSerialCommand(const char *c){ auto uCom=UserCommands.find(c[1]); if(uCom!=UserCommands.end()){ - uCom->second->userFunction(c+1); + if(uCom->second->userFunction1) + uCom->second->userFunction1(c+1); + else + uCom->second->userFunction2(c+1,uCom->second->userArg); break; } } @@ -1893,9 +1896,19 @@ SpanButton::SpanButton(int pin, uint16_t longTime, uint16_t singleTime, uint16_t // SpanUserCommand // /////////////////////////////// -SpanUserCommand::SpanUserCommand(char c, const char *s, void (*f)(const char *v)){ +SpanUserCommand::SpanUserCommand(char c, const char *s, void (*f)(const char *)){ this->s=s; - userFunction=f; + userFunction1=f; + + homeSpan.UserCommands[c]=this; +} + +/////////////////////////////// + +SpanUserCommand::SpanUserCommand(char c, const char *s, void (*f)(const char *, void *), void *arg){ + this->s=s; + userFunction2=f; + userArg=arg; homeSpan.UserCommands[c]=this; } diff --git a/src/HomeSpan.h b/src/HomeSpan.h index f8df8cf..d692064 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -657,10 +657,13 @@ struct SpanButton{ /////////////////////////////// struct SpanUserCommand { - const char *s; // description of command - void (*userFunction)(const char *v); // user-defined function to call + const char *s; // description of command + void (*userFunction1)(const char *v)=NULL; // user-defined function to call + void (*userFunction2)(const char *v, void *arg)=NULL; // user-defined function to call with user-defined arg + void *userArg; - SpanUserCommand(char c, const char *s, void (*f)(const char *v)); + SpanUserCommand(char c, const char *s, void (*f)(const char *)); + SpanUserCommand(char c, const char *s, void (*f)(const char *, void *), void *arg); }; ///////////////////////////////////////////////// From 170e0b61b168197a51dd65116465c32f906b0a98 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 27 Feb 2022 09:51:27 -0600 Subject: [PATCH 004/100] Add new macro CUSTOM_SERV() to created custom services Also, updated error checking so that the UUID for both custom Services and custom Characteristics are checked for syntax. A fatal error is thrown if an ill-formatted UUID is found, since this will definitely prevent pairing with the HomeApp. The UUID for HAP Services and Characteristics are NOT error checked, since these are fixed in HomeSpan. Also, the custom Characteristics are not validated against the optional list for a service. If the user adds a custom Characteristic to a HAP Service, it is assumed to be valid. Similarly, none of the Characteristics (HAP of Custom) in a Custom Service are validated at all. --- src/HomeSpan.cpp | 19 +++++++++++++------ src/HomeSpan.h | 26 +++++++++++++++++--------- src/Span.h | 21 +++++++++++++++------ 3 files changed, 45 insertions(+), 21 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index b945744..6f523cf 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -917,12 +917,12 @@ void Span::processSerialCommand(const char *c){ Serial.print("\n\n"); char d[]="------------------------------"; - Serial.printf("%-30s %s %10s %s %s %s %s %s\n","Service","UUID","AID","IID","Update","Loop","Button","Linked Services"); - Serial.printf("%.30s %.4s %.10s %.3s %.6s %.4s %.6s %.15s\n",d,d,d,d,d,d,d,d); + Serial.printf("%-30s %8s %10s %s %s %s %s %s\n","Service","UUID","AID","IID","Update","Loop","Button","Linked Services"); + Serial.printf("%.30s %.8s %.10s %.3s %.6s %.4s %.6s %.15s\n",d,d,d,d,d,d,d,d); for(int i=0;iServices.size();j++){ SpanService *s=Accessories[i]->Services[j]; - Serial.printf("%-30s %4s %10u %3d %6s %4s %6s ",s->hapName,s->type,Accessories[i]->aid,s->iid, + Serial.printf("%-30s %8.8s %10u %3d %6s %4s %6s ",s->hapName,s->type,Accessories[i]->aid,s->iid, (void(*)())(s->*(&SpanService::update))!=(void(*)())(&SpanService::update)?"YES":"NO", (void(*)())(s->*(&SpanService::loop))!=(void(*)())(&SpanService::loop)?"YES":"NO", (void(*)(int,boolean))(s->*(&SpanService::button))!=(void(*)(int,boolean))(&SpanService::button)?"YES":"NO" @@ -1489,13 +1489,14 @@ int SpanAccessory::sprintfAttributes(char *cBuf){ // SpanService // /////////////////////////////// -SpanService::SpanService(const char *type, const char *hapName){ +SpanService::SpanService(const char *type, const char *hapName, boolean isCustom){ if(!homeSpan.Accessories.empty() && !homeSpan.Accessories.back()->Services.empty()) // this is not the first Service to be defined for this Accessory homeSpan.Accessories.back()->Services.back()->validate(); this->type=type; this->hapName=hapName; + this->isCustom=isCustom; homeSpan.configLog+=" \u279f Service " + String(hapName); @@ -1508,7 +1509,12 @@ SpanService::SpanService(const char *type, const char *hapName){ homeSpan.Accessories.back()->Services.push_back(this); iid=++(homeSpan.Accessories.back()->iidCount); - homeSpan.configLog+=": IID=" + String(iid) + ", UUID=\"" + String(type) + "\""; + homeSpan.configLog+=": IID=" + String(iid) + ", " + (isCustom?"Custom-":"") + "UUID=\"" + String(type) + "\""; + + if(Span::invalidUUID(type,isCustom)){ + homeSpan.configLog+=" *** ERROR! Format of UUID is invalid. ***"; + homeSpan.nFatalErrors++; + } if(!strcmp(this->type,"3E") && iid!=1){ homeSpan.configLog+=" *** ERROR! The AccessoryInformation Service must be defined before any other Services in an Accessory. ***"; @@ -1600,12 +1606,13 @@ void SpanService::validate(){ // SpanCharacteristic // /////////////////////////////// -SpanCharacteristic::SpanCharacteristic(HapChar *hapChar){ +SpanCharacteristic::SpanCharacteristic(HapChar *hapChar, boolean isCustom){ type=hapChar->type; perms=hapChar->perms; hapName=hapChar->hapName; format=hapChar->format; staticRange=hapChar->staticRange; + this->isCustom=isCustom; homeSpan.configLog+=" \u21e8 Characteristic " + String(hapName); diff --git a/src/HomeSpan.h b/src/HomeSpan.h index d692064..79ca2d2 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -202,7 +202,13 @@ struct Span{ void enableOTA(boolean auth=true){otaEnabled=true;otaAuth=auth;reserveSocketConnections(1);} // enables Over-the-Air updates, with (auth=true) or without (auth=false) authorization password [[deprecated("Please use reserveSocketConnections(n) method instead.")]] - void setMaxConnections(uint8_t n){requestedMaxCon=n;} // sets maximum number of simultaneous HAP connections + void setMaxConnections(uint8_t n){requestedMaxCon=n;} // sets maximum number of simultaneous HAP connections + + static boolean invalidUUID(const char *uuid, boolean isCustom){ + int x=0; + sscanf(uuid,"%*8[0-9a-fA-F]-%*4[0-9a-fA-F]-%*4[0-9a-fA-F]-%*4[0-9a-fA-F]-%*12[0-9a-fA-F]%n",&x); + return(isCustom && (strlen(uuid)!=36 || x!=36)); + } }; @@ -233,8 +239,9 @@ struct SpanService{ vector req; // vector of pointers to all required HAP Characteristic Types for this Service vector opt; // vector of pointers to all optional HAP Characteristic Types for this Service vector linkedServices; // vector of pointers to any optional linked Services + boolean isCustom; // flag to indicate this is a Custom Service - SpanService(const char *type, const char *hapName); + SpanService(const char *type, const char *hapName, boolean isCustom=false); // constructor SpanService *setPrimary(); // sets the Service Type to be primary and returns pointer to self SpanService *setHidden(); // sets the Service Type to be hidden and returns pointer to self @@ -280,6 +287,7 @@ struct SpanCharacteristic{ const char *validValues=NULL; // Optional JSON array of valid values. Applicable only to uint8 Characteristics boolean *ev; // Characteristic Event Notify Enable (per-connection) char *nvsKey=NULL; // key for NVS storage of Characteristic value + boolean isCustom; // flag to indicate this is a Custom Characteristic uint32_t aid=0; // Accessory ID - passed through from Service containing this Characteristic boolean isUpdated=false; // set to true when new value has been requested by PUT /characteristic @@ -287,7 +295,7 @@ struct SpanCharacteristic{ UVal newValue; // the updated value requested by PUT /characteristic SpanService *service=NULL; // pointer to Service containing this Characteristic - SpanCharacteristic(HapChar *hapChar); // contructor + SpanCharacteristic(HapChar *hapChar, boolean isCustom=false); // contructor int sprintfAttributes(char *cBuf, int flags); // prints Characteristic JSON records into buf, according to flags mask; return number of characters printed, excluding null terminator StatusCode loadUpdate(char *val, char *ev); // load updated val/ev from PUT /characteristic JSON request. Return intiial HAP status code (checks to see if characteristic is found, is writable, etc.) @@ -457,11 +465,6 @@ struct SpanCharacteristic{ uvSet(stepValue,0); } - int x=0; - sscanf(type,"%*8[0-9a-fA-F]-%*4[0-9a-fA-F]-%*4[0-9a-fA-F]-%*4[0-9a-fA-F]-%*12[0-9a-fA-F]%n",&x); - - boolean isCustom=(strlen(type)==36 && x==36); - homeSpan.configLog+="(" + uvPrint(value) + ")" + ": IID=" + String(iid) + ", " + (isCustom?"Custom-":"") + "UUID=\"" + String(type) + "\""; if(format!=FORMAT::STRING && format!=FORMAT::BOOL) homeSpan.configLog+= ", Range=[" + String(uvPrint(minValue)) + "," + String(uvPrint(maxValue)) + "]"; @@ -471,7 +474,12 @@ struct SpanCharacteristic{ else if(nvsFlag==1) homeSpan.configLog+=" (storing)"; - boolean valid=isCustom; + if(Span::invalidUUID(type,isCustom)){ + homeSpan.configLog+=" *** ERROR! Format of UUID is invalid. ***"; + homeSpan.nFatalErrors++; + } + + boolean valid=isCustom|service->isCustom; // automatically set valid if either Characteristic or containing Service is Custom for(int i=0; !valid && iServices.back()->req.size(); i++) valid=!strcmp(type,homeSpan.Accessories.back()->Services.back()->req[i]->type); diff --git a/src/Span.h b/src/Span.h index 03fd31a..c0155f7 100644 --- a/src/Span.h +++ b/src/Span.h @@ -43,7 +43,7 @@ namespace Service { REQ(Model); REQ(Name); REQ(SerialNumber); - OPT(HardwareRevision); + OPT(HardwareRevision); }}; struct AirPurifier : SpanService { AirPurifier() : SpanService{"BB","AirPurifier"}{ @@ -521,14 +521,23 @@ namespace Characteristic { } -////////////////////////////////////////// -// MACRO TO ADD CUSTOM CHARACTERISTICS // -////////////////////////////////////////// +//////////////////////////////////////////////////////// +// MACROS TO ADD CUSTOM SERVICES AND CHARACTERISTICS // +//////////////////////////////////////////////////////// #define CUSTOM_CHAR(NAME,UUID,PERMISISONS,FORMAT,DEFVAL,MINVAL,MAXVAL,STATIC_RANGE) \ HapChar _CUSTOM_##NAME {#UUID,#NAME,(PERMS)(PERMISISONS),FORMAT,STATIC_RANGE}; \ - namespace Characteristic { struct NAME : SpanCharacteristic { NAME(FORMAT##_t val=DEFVAL, boolean nvsStore=false) : SpanCharacteristic {&_CUSTOM_##NAME} { init(val,nvsStore,(FORMAT##_t)MINVAL,(FORMAT##_t)MAXVAL); } }; } + namespace Characteristic { struct NAME : SpanCharacteristic { NAME(FORMAT##_t val=DEFVAL, boolean nvsStore=false) : SpanCharacteristic {&_CUSTOM_##NAME,true} { init(val,nvsStore,(FORMAT##_t)MINVAL,(FORMAT##_t)MAXVAL); } }; } #define CUSTOM_CHAR_STRING(NAME,UUID,PERMISISONS,DEFVAL) \ HapChar _CUSTOM_##NAME {#UUID,#NAME,(PERMS)(PERMISISONS),STRING,true}; \ - namespace Characteristic { struct NAME : SpanCharacteristic { NAME(const char * val=DEFVAL, boolean nvsStore=false) : SpanCharacteristic {&_CUSTOM_##NAME} { init(val,nvsStore); } }; } + namespace Characteristic { struct NAME : SpanCharacteristic { NAME(const char * val=DEFVAL, boolean nvsStore=false) : SpanCharacteristic {&_CUSTOM_##NAME,true} { init(val,nvsStore); } }; } + +#define CUSTOM_SERV(NAME,UUID) \ + namespace Service { struct NAME : SpanService { NAME() : SpanService{#UUID,#NAME,true}{} }; } + + + + + + From e51320cced402ff44d7350353dd57843be2ebc48 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 27 Feb 2022 09:53:17 -0600 Subject: [PATCH 005/100] Create CustomService.ino Added to Other Examples --- .../CustomService/CustomService.ino | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 Other Examples/CustomService/CustomService.ino diff --git a/Other Examples/CustomService/CustomService.ino b/Other Examples/CustomService/CustomService.ino new file mode 100644 index 0000000..2551e9b --- /dev/null +++ b/Other Examples/CustomService/CustomService.ino @@ -0,0 +1,122 @@ +/********************************************************************************* + * 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. + * + ********************************************************************************/ + +#include "HomeSpan.h" + +// Apple's HomeKit does not provide any native services or characteristics for measuring atmospheric pressure. +// However, Eve for HomeKit does support pressure measurements. + +// This brief sketch demonstrates how you can use HomeSpan's Custom Service and Custom Characteristic features +// to create a Pressure Sensor Accessory that will be recognized by the Eve for HomeKit App. Note that the +// Apple Home App will show this as a "Not Supported" Accessory Tile indicating it cannot be used in the Home App. +// However, this does not create any problems or errors in the Home App. + +// Step 1: + +// Use the CUSTOM_SERV macro to create a new service named AtmosphericPressureSensor with +// a UUID=E863F00A-079E-48FF-8F27-9C2605A29F52. This new service will be added to HomeSpan's Service namespace +// and can be accessed using the fully-qualified name Service::AtmosphericPressureSensor. The UUID specified +// will not be recognized by Apple's Home App, but will be recognized by the Eve for HomeKit App. Note you +// do NOT enclose either of the parameters in quotes! + + CUSTOM_SERV(AtmosphericPressureSensor, E863F00A-079E-48FF-8F27-9C2605A29F52); + +// Step 2: + +// Use the CUSTOM_CHAR macro to create a new characteristic named AtmosphericPressure with +// a UUID=E863F10F-079E-48FF-8F27-9C2605A29F52. This new characteristic will be added to HomeSpan's Characteristic namespace +// and can be accessed using the fully-qualified name Characteristic::AtmosphericPressure. The UUID specified will not be +// recognized by Apple's Home App, but will be recognized by the Eve for HomeKit App. Note you do NOT enclose any of the +// parameters in quotes! +// +// The meaning of the parmameters are as follows: +// +// PR+EV: sets permission for "read" and "notify" +// FLOAT: sets the format to floating-point decimal number +// 1013: sets the default starting value to 1013, which is 1 atm in millibars +// 700: sets the default lower range of allowed values to 700 millibars +// 1200: sets the default upper range of allowed values to 1200 millibars +// false: sets the "static range" flag to false, indicating that users CAN override the default range setRange() if desired + + CUSTOM_CHAR(AtmosphericPressure, E863F10F-079E-48FF-8F27-9C2605A29F52, PR+EV, FLOAT, 1013, 700, 1200, false); + +// Now that AtmosphericPressureSensor and AtmosphericPressure have been created, they can be used just as any other native HomeSpan +// Service and Characteristic. + +////////////////////////////////////// + +struct PressureSensor : Service::AtmosphericPressureSensor { // A standalone Air Pressure Sensor + + Characteristic::AtmosphericPressure pressure; // Eve Air Pressure with range 700-1200 hPa (millibars), where 1 atm=1013 hPa + + PressureSensor() : Service::AtmosphericPressureSensor{} { + + Serial.print("Configuring Air Pressure Sensor"); // initialization message + Serial.print("\n"); + + } // end constructor + + void loop(){ + + if(pressure.timeVal()>5000) // here we simulate an actual sensor by generating a random pressure reading every 5 seconds + pressure.setVal((double)random(900,1100)); + + } // end loop + +}; // end PressureSensor + +////////////////////////////////////// + +void setup() { + + Serial.begin(115200); + + homeSpan.begin(Category::Sensors,"Eve Air Pressure"); + + new SpanAccessory(); + + new Service::AccessoryInformation(); + new Characteristic::Name("Air Pressure"); + new Characteristic::Manufacturer("HomeSpan"); + new Characteristic::SerialNumber("123-ABC"); + new Characteristic::Model("Simulated Sensor"); + new Characteristic::FirmwareRevision("1.0"); + new Characteristic::Identify(); + + new Service::HAPProtocolInformation(); + new Characteristic::Version("1.1.0"); + + new PressureSensor(); + +} + +////////////////////////////////////// + +void loop(){ + + homeSpan.poll(); +} From ead3ba674708db4dd0c0a14a4bcd487faf3e727a Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 27 Feb 2022 10:29:37 -0600 Subject: [PATCH 006/100] Update Reference.md --- docs/Reference.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/docs/Reference.md b/docs/Reference.md index 7762734..0d8746d 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -338,7 +338,7 @@ If REQUIRED is defined in the main sketch *prior* to including the HomeSpan libr ### *#define CUSTOM_CHAR(name,uuid,perms,format,defaultValue,minValue,maxValue,staticRange)* ### *#define CUSTOM_CHAR_STRING(name,uuid,perms,defaultValue)* -Creates a custom Characteristic that can be added to any Service. Custom Characteristics are generally ignored by the Home App but may be used by other third-party applications (such as Eve for HomeKit). The first form should be used create numerical Characterstics (e.g., UINT8, BOOL...). The second form is used to String-based Characteristics. Parameters are as follows (note that quotes should NOT be used in any of the macro parameters, except for defaultValue): +Creates a custom Characteristic that can be added to any Service. Custom Characteristics are generally ignored by the Home App but may be used by other third-party applications (such as *Eve for HomeKit*). The first form should be used create numerical Characterstics (e.g., UINT8, BOOL...). The second form is used to String-based Characteristics. Parameters are as follows (note that quotes should NOT be used in any of the macro parameters, except for defaultValue): * *name* - the name of the custom Characteristic. This will be added to the Characteristic namespace so that it is accessed the same as any HomeSpan Characteristic * *uuid* - the UUID of the Characteristic as defined by the manufacturer. Must be *exactly* 36 characters in the form XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX, where *X* represent a valid hexidecimal digit. Leading zeros are required if needed as described more fully in HAP-R2 Section 6.6.1 @@ -349,7 +349,7 @@ Creates a custom Characteristic that can be added to any Service. Custom Charac * *minValue* - specifies the default minimum range for a valid value, which may be able to be overriden by a call to `setRange()`. Not applicable for Strings-based Characteristics * *staticRange* - set to *true* if *minValue* and *maxValue* are static and cannot be overridden with a call to `setRange()`. Set to *false* if calls to `setRange()` are allowed. Not applicable for Strings-based Characteristics -As an example, the first line below creates a custom Characteristic named "Voltage" with a UUID code that is recognized by Eve for HomeKit. The parameters show that the Characteristic is read-only (PR) and notifications are enabled (EV). The default range of allowed values is 0-240, with a default of 120. The range *can* be overridden by subsequent calls to `setRange()`. The second line below creates a custom read-only String-based Characteristic: +As an example, the first line below creates a custom Characteristic named "Voltage" with a UUID code that is recognized by the *Eve for HomeKit* app. The parameters show that the Characteristic is read-only (PR) and notifications are enabled (EV). The default range of allowed values is 0-240, with a default of 120. The range *can* be overridden by subsequent calls to `setRange()`. The second line below creates a custom read-only String-based Characteristic: ```C++ CUSTOM_CHAR(Voltage, E863F10A-079E-48FF-8F27-9C2605A29F52, PR+EV, UINT16, 120, 0, 240, false); @@ -365,7 +365,18 @@ new Service::LightBulb(); Note that Custom Characteristics must be created prior to calling `homeSpan.begin()` -> Advanced Tip: When presented with an unrecognized Custom Characteristic, Eve for HomeKit helpfully displays a *generic control* allowing you to interact with any Custom Characteristic you create in HomeSpan. However, since Eve does not recognize the Characteristic, it will only render the generic control if the Characteristic includes a **description** field, which you can add to any Characteristic using the `setDescription()` method described above. You may also want to use `setUnit()` and `setRange()` so that the Eve App displays a control with appropriate ranges for your Custom Characteristic. +> Advanced Tip: When presented with an unrecognized Custom Characteristic, *Eve for HomeKit* helpfully displays a *generic control* allowing you to interact with any Custom Characteristic you create in HomeSpan. However, since Eve does not recognize the Characteristic, it will only render the generic control if the Characteristic includes a **description** field, which you can add to any Characteristic using the `setDescription()` method described above. You may also want to use `setUnit()` and `setRange()` so that the Eve App displays a control with appropriate ranges for your Custom Characteristic. + +### *#define CUSTOM_SERV(name,uuid)* + +Creates a custom Service for use with third-party applications (such as *Eve for HomeKit*). Custom Services will be displayed in the native Apple Home App with a Tile labeled "Not Supported", but otherwise the Service will be safely ignored by the Home App. Parameters are as follows (note that quotes should NOT be used in either of the macro parameters): + +* *name* - the name of the custom Service. This will be added to the Service namespace so that it is accessed the same as any HomeSpan Service +* *uuid* - the UUID of the Service as defined by the manufacturer. Must be *exactly* 36 characters in the form XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX, where *X* represent a valid hexidecimal digit. Leading zeros are required if needed as described more fully in HAP-R2 Section 6.6.1 + +Custom Services may contain a mix of both Custom Characteristics and standard HAP Characteristics, though since the Service itself is custom, the Home App will ignore the entire Service even if it contains some standard HAP Characterstics. Note that Custom Services must be created prior to calling `homeSpan.begin()` + +A fully worked example showing how to use both the ***CUSTOM_SERV()*** and ***CUSTOM_CHAR()*** macros to create a Pressure Sensor Accessory that is recognized by *Eve for HomeKit* can be found in the Arduino IDE under [*File → Examples → HomeSpan → Other Examples → CustomService*](../Other%20Examples/CustomService). --- From eb49f606ceb0ac4ad4acc5e64f3a898a9ae389e3 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 27 Feb 2022 16:18:55 -0600 Subject: [PATCH 007/100] Update FAQ.md --- docs/FAQ.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/FAQ.md b/docs/FAQ.md index fd53287..564bd87 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -55,7 +55,11 @@ #### Can you add a Web Server to HomeSpan? * Yes, provided you implement your Web Server using standard ESP32-Arduino libraries, such as `WebServer.h`. See [ProgrammableHub](https://github.com/HomeSpan/ProgrammableHub) for an illustrative example of how to easily integrate a Web Server into HomeSpan. This project also covers various other advanced topics, including TCP slot management, dynamic creation of Accessories, and saving arbitrary data in the ESP32's NVS. -* Note *ESP32AsyncWebServer* cannot be used since it requires a TCP stack that is unfortunately incompatible with HomeSpan. +* Note *ESP32AsyncWebServer* cannot be used since it requires a TCP stack that is unfortunately incompatible with HomeSpan. + +#### Can you add *custom* Services and Characteristics to HomeSpan? + +* Yes, HomeSpan includes two easy-to-use macros to define your own custom Services and custom Characteristics beyond those specified in HAP-R2. See the [HomeSpan API Reference](https://github.com/HomeSpan/HomeSpan/blob/master/docs/Reference.md) for details and examples demonstrating how to do this. Note that any new Characteristics you create will be *completely ignored* by the Home App; and any new Services you create will be shown in the Home App on a tile labeled "Not Supported". Apple ***does not*** provide any mechanism to extend the functionality of the Home App itself. However, where custom Services and Characteristics are used is in 3rd party applications designed for these extra features. For example, the *Eve for HomeKit* App properly handles all the Services and Characteristics defined in HAP-R2, *plus* a variety of additional Services and Characteristics designed explictly for use with Eve products. If you know the UUID codes for these extra Services and Characteristics you can add them to HomeKit and use them within the Eve App just as if they were HAP-R2 Services and Characteristics. --- From 71071198c3ce81f4984376f4c3dde27fa097d9fe Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 27 Feb 2022 16:23:02 -0600 Subject: [PATCH 008/100] Update FAQ.md --- docs/FAQ.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/FAQ.md b/docs/FAQ.md index 564bd87..9a79c5d 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -59,7 +59,7 @@ #### Can you add *custom* Services and Characteristics to HomeSpan? -* Yes, HomeSpan includes two easy-to-use macros to define your own custom Services and custom Characteristics beyond those specified in HAP-R2. See the [HomeSpan API Reference](https://github.com/HomeSpan/HomeSpan/blob/master/docs/Reference.md) for details and examples demonstrating how to do this. Note that any new Characteristics you create will be *completely ignored* by the Home App; and any new Services you create will be shown in the Home App on a tile labeled "Not Supported". Apple ***does not*** provide any mechanism to extend the functionality of the Home App itself. However, where custom Services and Characteristics are used is in 3rd party applications designed for these extra features. For example, the *Eve for HomeKit* App properly handles all the Services and Characteristics defined in HAP-R2, *plus* a variety of additional Services and Characteristics designed explictly for use with Eve products. If you know the UUID codes for these extra Services and Characteristics you can add them to HomeKit and use them within the Eve App just as if they were HAP-R2 Services and Characteristics. +* Yes, HomeSpan includes two easy-to-use macros to define your own custom Services and custom Characteristics beyond those specified in HAP-R2. See the [HomeSpan API Reference](https://github.com/HomeSpan/HomeSpan/blob/master/docs/Reference.md) for details and examples demonstrating how to do this. Note that any new Characteristics you create will be *completely ignored* by the Home App. Similarly, any new Services you create will be shown in the Home App on a tile labeled "Not Supported". Apple ***does not*** provide any mechanism to extend the functionality of the Home App itself. However, the place where custom Services and Characteristics can be used is in third-party applications designed for these extra features. For example, the *Eve for HomeKit* App properly handles all the Services and Characteristics defined in HAP-R2, *plus* a variety of additional Services and Characteristics designed explictly for use with Eve products. If you know the UUID codes for these extra Services and Characteristics you can add them to HomeKit and use them within the Eve App just as if they were HAP-R2 Services and Characteristics. --- From ae4b6e8df1f6f82b0e7a3f26ebfc309f1d0a2510 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 27 Feb 2022 18:04:12 -0600 Subject: [PATCH 009/100] Initial creation of getStatusURL() web log Built basic framework to response to "GET /status". Next up: Add timeclock. --- src/HAP.cpp | 52 +++++++++++++++++++++++++++++++++++++++++++++++++++- src/HAP.h | 1 + 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index 89bdc9d..5b15003 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -342,6 +342,11 @@ void HAPClient::processRequest(){ return; } + if(!strncmp(body,"GET /status ",12)){ // GET STATUS - AN OPTIONAL, NON-HAP-R2 FEATURE + getStatusURL(); + return; + } + notFoundError(); Serial.print("\n*** ERROR: Bad GET request - URL not found\n\n"); return; @@ -1249,6 +1254,52 @@ int HAPClient::putPrepareURL(char *json){ ////////////////////////////////////// +int HAPClient::getStatusURL(){ + + char uptime[16]; + int seconds=esp_timer_get_time()/1e6; + int secs=seconds%60; + int mins=(seconds/=60)%60; + int hours=(seconds/=60)%24; + int days=(seconds/=24); + + sprintf(uptime,"%d:%02d:%02d:%02d",days,hours,mins,secs); + + String response="HTTP/1.1 200 OK\r\nContent-type: text/html\r\n\r\n"; + + response+="HomeSpan Status\n"; + response+="\n"; + response+="\n"; + response+="

" + String(homeSpan.displayName) + "

\n"; + + response+="\n"; + response+="\n"; + response+="\n"; + response+="\n"; + response+="\n"; + response+="\n"; + response+="\n"; + response+="
Uptime:" + String(uptime) + "
ESP32 Board:" + String(ARDUINO_BOARD) + "
Arduino-ESP Version:" + String(ARDUINO_ESP_VERSION) + "
ESP-IDF Version:" + String(ESP_IDF_VERSION_MAJOR) + "." + String(ESP_IDF_VERSION_MINOR) + "." + String(ESP_IDF_VERSION_PATCH) + "
HomeSpan Version:" + String(HOMESPAN_VERSION) + "
Sketch Version:" + String(homeSpan.getSketchVersion()) + "
\n"; + + response+=""; + + LOG2("\n>>>>>>>>>> "); + LOG2(client.remoteIP()); + LOG2(" >>>>>>>>>>\n"); + LOG2(response); + LOG2("\n"); + client.print(response); + LOG2("------------ SENT! --------------\n"); + + delay(1); + client.stop(); + + return(1); +} + +////////////////////////////////////// + void HAPClient::callServiceLoops(){ homeSpan.snapTime=millis(); // snap the current time for use in ALL loop routines @@ -1304,7 +1355,6 @@ void HAPClient::checkTimedWrites(){ ////////////////////////////////////// - void HAPClient::eventNotify(SpanBuf *pObj, int nObj, int ignoreClient){ for(int cNum=0;cNum0) void sendEncrypted(char *body, uint8_t *dataBuf, int dataLen); // send client complete ChaCha20-Poly1305 encrypted HTTP mesage comprising a null-terminated 'body' and 'dataBuf' with 'dataLen' bytes From 571bc55852951aea2d7616642bb59fef7fe9880c Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 27 Feb 2022 18:29:58 -0600 Subject: [PATCH 010/100] Added optional homeSpan.setTimeServer() method If specified, HomeSpan will try to get internet time after establishing WiFi connection. Consumes one socket connection. --- src/HomeSpan.cpp | 11 ++++++++++- src/HomeSpan.h | 13 ++++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 6f523cf..9c2b6d4 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -559,6 +559,15 @@ void Span::checkConnect(){ } } + if(timeServer){ + Serial.printf("Acquiring Time from %s... ",timeServer,timeZone); + configTzTime(timeZone,timeServer); + getLocalTime(&timeinfo); + char c[65]; + strftime(c,64,"%a %b %e %Y %I:%M:%S %p",&timeinfo); + Serial.printf("%s (%s)\n\n",c,timeZone); + } + Serial.printf("Starting HAP Server on port %d supporting %d simultaneous HomeKit Controller Connections...\n",tcpPortNum,maxConnections); hapServer->begin(); @@ -571,7 +580,7 @@ void Span::checkConnect(){ } else { statusLED.on(); } - + if(wifiCallback) wifiCallback(); diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 79ca2d2..6312da7 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -113,7 +113,10 @@ struct Span{ nvs_handle charNVS; // handle for non-volatile-storage of Characteristics data nvs_handle wifiNVS=0; // handle for non-volatile-storage of WiFi data char pairingCodeCommand[12]=""; // user-specified Pairing Code - only needed if Pairing Setup Code is specified in sketch using setPairingCode() - + const char *timeZone=NULL; // optional time-zone specification + const char *timeServer=NULL; // optional time server to use for acquiring clock time + struct tm timeinfo; // optional time info structure + boolean connected=false; // WiFi connection status unsigned long waitTime=60000; // time to wait (in milliseconds) between WiFi connection attempts unsigned long alarmConnect=0; // time after which WiFi connection attempt should be tried again @@ -191,16 +194,16 @@ struct Span{ const char *getSketchVersion(){return sketchVersion;} // get sketch version number void setWifiCallback(void (*f)()){wifiCallback=f;} // sets an optional user-defined function to call once WiFi connectivity is established void setPairCallback(void (*f)(boolean isPaired)){pairCallback=f;} // sets an optional user-defined function to call when Pairing is established (true) or lost (false) - void setApFunction(void (*f)()){apFunction=f;} // sets an optional user-defined function to call when activating the WiFi Access Point - + void setApFunction(void (*f)()){apFunction=f;} // sets an optional user-defined function to call when activating the WiFi Access Point void enableAutoStartAP(){autoStartAPEnabled=true;} // enables auto start-up of Access Point when WiFi Credentials not found void setWifiCredentials(const char *ssid, const char *pwd); // sets WiFi Credentials void setPairingCode(const char *s){sprintf(pairingCodeCommand,"S %9s",s);} // sets the Pairing Code - use is NOT recommended. Use 'S' from CLI instead void deleteStoredValues(){processSerialCommand("V");} // deletes stored Characteristic values from NVS - void enableOTA(boolean auth=true){otaEnabled=true;otaAuth=auth;reserveSocketConnections(1);} // enables Over-the-Air updates, with (auth=true) or without (auth=false) authorization password - + void enableOTA(boolean auth=true){otaEnabled=true;otaAuth=auth;reserveSocketConnections(1);} // enables Over-the-Air updates, with (auth=true) or without (auth=false) authorization password + void setTimeServer(const char *serv, const char *tz){timeServer=serv;timeZone=tz;reserveSocketConnections(1);} // sets optional time server and time zone + [[deprecated("Please use reserveSocketConnections(n) method instead.")]] void setMaxConnections(uint8_t n){requestedMaxCon=n;} // sets maximum number of simultaneous HAP connections From 20ce79ef34bcf7672d16d49dc9f9697c4b70e865 Mon Sep 17 00:00:00 2001 From: Gregg Date: Wed, 2 Mar 2022 21:41:06 -0600 Subject: [PATCH 011/100] Added check to ensure time server is available If not, homeSpan.timeServer is reset to NULL --- src/HomeSpan.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 9c2b6d4..98bc563 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include "HomeSpan.h" #include "HAP.h" @@ -562,10 +563,14 @@ void Span::checkConnect(){ if(timeServer){ Serial.printf("Acquiring Time from %s... ",timeServer,timeZone); configTzTime(timeZone,timeServer); - getLocalTime(&timeinfo); - char c[65]; - strftime(c,64,"%a %b %e %Y %I:%M:%S %p",&timeinfo); - Serial.printf("%s (%s)\n\n",c,timeZone); + if(getLocalTime(&timeinfo)){ + char c[65]; + strftime(c,64,"%a %b %e %Y %I:%M:%S %p",&timeinfo); + Serial.printf("%s (%s)\n\n",c,timeZone); + } else { + Serial.printf("Can't access Time Server - time-keeping disabled!\n\n"); + timeServer=NULL; + } } Serial.printf("Starting HAP Server on port %d supporting %d simultaneous HomeKit Controller Connections...\n",tcpPortNum,maxConnections); From b6c019d1a8744bd66686e78ac27205d5686d561b Mon Sep 17 00:00:00 2001 From: Gregg Date: Thu, 3 Mar 2022 21:37:20 -0600 Subject: [PATCH 012/100] Finished Time Stamping - Started Web Log Structure --- src/HAP.cpp | 3 ++- src/HomeSpan.cpp | 6 +++--- src/HomeSpan.h | 11 ++++++++++- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index 5b15003..98383e2 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -1274,7 +1274,8 @@ int HAPClient::getStatusURL(){ response+="

" + String(homeSpan.displayName) + "

\n"; response+="\n"; - response+="\n"; + response+="\n"; + response+="\n"; response+="\n"; response+="\n"; response+="\n"; diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 98bc563..2942197 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -563,10 +563,10 @@ void Span::checkConnect(){ if(timeServer){ Serial.printf("Acquiring Time from %s... ",timeServer,timeZone); configTzTime(timeZone,timeServer); + struct tm timeinfo; if(getLocalTime(&timeinfo)){ - char c[65]; - strftime(c,64,"%a %b %e %Y %I:%M:%S %p",&timeinfo); - Serial.printf("%s (%s)\n\n",c,timeZone); + strftime(bootTime,sizeof(bootTime),"%c",&timeinfo); + Serial.printf("%s\n\n",bootTime); } else { Serial.printf("Can't access Time Server - time-keeping disabled!\n\n"); timeServer=NULL; diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 6312da7..bddc282 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -94,6 +94,14 @@ struct SpanBuf{ // temporary storage buffer for us /////////////////////////////// +struct SpanWebLog{ // optional web status/log data + int maxEntries=-1; // max number of log entries; -1 = do not create log or status; 0 = create status but no log; 1..N = create status and log with N entries + int nEntries=0; // current number of log entries + char **entry; // pointers to log entries of arbitrary size +}; + +/////////////////////////////// + struct Span{ const char *displayName; // display name for this device - broadcast as part of Bonjour MDNS @@ -115,7 +123,8 @@ struct Span{ char pairingCodeCommand[12]=""; // user-specified Pairing Code - only needed if Pairing Setup Code is specified in sketch using setPairingCode() const char *timeZone=NULL; // optional time-zone specification const char *timeServer=NULL; // optional time server to use for acquiring clock time - struct tm timeinfo; // optional time info structure + char bootTime[33]="Unknown"; // boot time + SpanWebLog webLog; // optional web status/log boolean connected=false; // WiFi connection status unsigned long waitTime=60000; // time to wait (in milliseconds) between WiFi connection attempts From 91ab626d6c6885d8caa249b0a0d24b60749aee09 Mon Sep 17 00:00:00 2001 From: Gregg Date: Fri, 4 Mar 2022 06:15:48 -0600 Subject: [PATCH 013/100] Update HomeSpan.h --- src/HomeSpan.h | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/HomeSpan.h b/src/HomeSpan.h index bddc282..c8cf736 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -71,6 +71,7 @@ struct SpanRange; struct SpanBuf; struct SpanButton; struct SpanUserCommand; +struct SpanWebLog; extern Span homeSpan; @@ -94,14 +95,6 @@ struct SpanBuf{ // temporary storage buffer for us /////////////////////////////// -struct SpanWebLog{ // optional web status/log data - int maxEntries=-1; // max number of log entries; -1 = do not create log or status; 0 = create status but no log; 1..N = create status and log with N entries - int nEntries=0; // current number of log entries - char **entry; // pointers to log entries of arbitrary size -}; - -/////////////////////////////// - struct Span{ const char *displayName; // display name for this device - broadcast as part of Bonjour MDNS @@ -124,7 +117,7 @@ struct Span{ const char *timeZone=NULL; // optional time-zone specification const char *timeServer=NULL; // optional time server to use for acquiring clock time char bootTime[33]="Unknown"; // boot time - SpanWebLog webLog; // optional web status/log + SpanWebLog *webLog=NULL; // optional web status/log boolean connected=false; // WiFi connection status unsigned long waitTime=60000; // time to wait (in milliseconds) between WiFi connection attempts @@ -686,6 +679,20 @@ struct SpanUserCommand { SpanUserCommand(char c, const char *s, void (*f)(const char *, void *), void *arg); }; +/////////////////////////////// + +struct SpanWebLog{ // optional web status/log data + int maxEntries; // max number of log entries; -1 = do not create log or status; 0 = create status but no log; 1..N = create status and log with N entries + int nEntries=0; // current number of log entries + + struct log_t { // log entry type + uint32_t upTime; // number of seconds since booting + struct tm clockTime; // clock time + char *message; // pointers to log entries of arbitrary size + } *log=NULL; // array of log entries + +}; + ///////////////////////////////////////////////// #include "Span.h" From 168be0558683e864b5ea88d66b9ea486a1d67831 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 5 Mar 2022 10:03:13 -0600 Subject: [PATCH 014/100] Progress on SpanWebLog() --- src/HAP.cpp | 21 +++++++++++++--- src/HomeSpan.cpp | 63 +++++++++++++++++++++++++++++++++++++++--------- src/HomeSpan.h | 46 ++++++++++++++++++++--------------- src/Settings.h | 2 ++ 4 files changed, 99 insertions(+), 33 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index 98383e2..c343d4b 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -342,7 +342,7 @@ void HAPClient::processRequest(){ return; } - if(!strncmp(body,"GET /status ",12)){ // GET STATUS - AN OPTIONAL, NON-HAP-R2 FEATURE + if(homeSpan.webLog && !strncmp(body,homeSpan.webLog->statusURL.c_str(),homeSpan.webLog->statusURL.length())){ // GET STATUS - AN OPTIONAL, NON-HAP-R2 FEATURE getStatusURL(); return; } @@ -1268,21 +1268,36 @@ int HAPClient::getStatusURL(){ String response="HTTP/1.1 200 OK\r\nContent-type: text/html\r\n\r\n"; response+="HomeSpan Status\n"; - response+="\n"; response+="\n"; response+="

" + String(homeSpan.displayName) + "

\n"; response+="
Uptime:" + String(uptime) + "
Up Time:" + String(uptime) + "
Boot Time:" + String(homeSpan.bootTime) + "
ESP32 Board:" + String(ARDUINO_BOARD) + "
Arduino-ESP Version:" + String(ARDUINO_ESP_VERSION) + "
ESP-IDF Version:" + String(ESP_IDF_VERSION_MAJOR) + "." + String(ESP_IDF_VERSION_MINOR) + "." + String(ESP_IDF_VERSION_PATCH) + "
\n"; response+="\n"; - response+="\n"; + response+="\n"; response+="\n"; response+="\n"; response+="\n"; response+="\n"; response+="\n"; + response+="\n"; response+="
Up Time:" + String(uptime) + "
Boot Time:" + String(homeSpan.bootTime) + "
Boot Time:" + String(homeSpan.webLog->bootTime) + "
ESP32 Board:" + String(ARDUINO_BOARD) + "
Arduino-ESP Version:" + String(ARDUINO_ESP_VERSION) + "
ESP-IDF Version:" + String(ESP_IDF_VERSION_MAJOR) + "." + String(ESP_IDF_VERSION_MINOR) + "." + String(ESP_IDF_VERSION_PATCH) + "
HomeSpan Version:" + String(HOMESPAN_VERSION) + "
Sketch Version:" + String(homeSpan.getSketchVersion()) + "
Max Log Entries:" + String(homeSpan.webLog->maxEntries) + "
\n"; + response+="

"; + + if(homeSpan.webLog->maxEntries>0){ + response+="\n"; + int lastIndex=homeSpan.webLog->nEntries-homeSpan.webLog->maxEntries; + if(lastIndex<0) + lastIndex=0; + for(int i=homeSpan.webLog->nEntries-1;i>=lastIndex;i--){ + response+=""; + } + + response+="
EntryUp TimeLog TimeMessage
" + String(i+1) + "" + "TBD1" "" + "TBD2" + "" + String(homeSpan.webLog->log[i%homeSpan.webLog->maxEntries].message) + "
\n"; + } + response+=""; LOG2("\n>>>>>>>>>> "); diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 2942197..69a6948 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -560,17 +560,9 @@ void Span::checkConnect(){ } } - if(timeServer){ - Serial.printf("Acquiring Time from %s... ",timeServer,timeZone); - configTzTime(timeZone,timeServer); - struct tm timeinfo; - if(getLocalTime(&timeinfo)){ - strftime(bootTime,sizeof(bootTime),"%c",&timeinfo); - Serial.printf("%s\n\n",bootTime); - } else { - Serial.printf("Can't access Time Server - time-keeping disabled!\n\n"); - timeServer=NULL; - } + if(webLog){ + Serial.printf("Web Logging enabled at http://%s.local:%d%swith max number of entries=%d\n\n",hostName,tcpPortNum,webLog->statusURL.c_str()+4,webLog->maxEntries); + webLog->initTime(); } Serial.printf("Starting HAP Server on port %d supporting %d simultaneous HomeKit Controller Connections...\n",tcpPortNum,maxConnections); @@ -1933,3 +1925,52 @@ SpanUserCommand::SpanUserCommand(char c, const char *s, void (*f)(const char *, homeSpan.UserCommands[c]=this; } + +/////////////////////////////// +// SpanWebLog // +/////////////////////////////// + +SpanWebLog::SpanWebLog(uint16_t maxEntries, const char *serv, const char *tz, const char *url){ + this->maxEntries=maxEntries; + timeServer=serv; + timeZone=tz; + statusURL="GET /" + String(url) + " "; + log = (log_t *)calloc(maxEntries,sizeof(log_t)); +} + +/////////////////////////////// + +void SpanWebLog::initTime(){ + if(!timeServer) + return; + + Serial.printf("Acquiring Time from %s (%s)... ",timeServer,timeZone); + configTzTime(timeZone,timeServer); + struct tm timeinfo; + if(getLocalTime(&timeinfo)){ + strftime(bootTime,sizeof(bootTime),"%c",&timeinfo); + Serial.printf("%s\n\n",bootTime); + homeSpan.reserveSocketConnections(1); + timeInit=true; + } else { + Serial.printf("Can't access Time Server (or Time Zone improperly specified) - time-keeping not initialized!\n\n"); + } +} + +/////////////////////////////// + +void SpanWebLog::addLog(const char *m){ + if(maxEntries==0) + return; + + int index=nEntries%maxEntries; + + log[index].upTime=esp_timer_get_time()/1e6; + if(timeInit) + getLocalTime(&log[index].clockTime); + else + log[index].clockTime.tm_year=0; + log[index].message=m; + + nEntries++; +} diff --git a/src/HomeSpan.h b/src/HomeSpan.h index c8cf736..362fa7e 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -95,6 +95,28 @@ struct SpanBuf{ // temporary storage buffer for us /////////////////////////////// +struct SpanWebLog{ // optional web status/log data + uint16_t maxEntries; // max number of log entries; + int nEntries=0; // total cumulative number of log entries + const char *timeServer; // optional time server to use for acquiring clock time + const char *timeZone; // optional time-zone specification + boolean timeInit=false; // flag to indicate time has been initialized + char bootTime[33]="Unknown"; // boot time + String statusURL; // URL of status log + + struct log_t { // log entry type + uint32_t upTime; // number of seconds since booting + struct tm clockTime; // clock time + const char *message; // pointers to log entries of arbitrary size + } *log=NULL; // array of log entries + + SpanWebLog(uint16_t maxEntries, const char *serv, const char *tz, const char *url); + void initTime(); + void addLog(const char *m); +}; + +/////////////////////////////// + struct Span{ const char *displayName; // display name for this device - broadcast as part of Bonjour MDNS @@ -114,10 +136,6 @@ struct Span{ nvs_handle charNVS; // handle for non-volatile-storage of Characteristics data nvs_handle wifiNVS=0; // handle for non-volatile-storage of WiFi data char pairingCodeCommand[12]=""; // user-specified Pairing Code - only needed if Pairing Setup Code is specified in sketch using setPairingCode() - const char *timeZone=NULL; // optional time-zone specification - const char *timeServer=NULL; // optional time server to use for acquiring clock time - char bootTime[33]="Unknown"; // boot time - SpanWebLog *webLog=NULL; // optional web status/log boolean connected=false; // WiFi connection status unsigned long waitTime=60000; // time to wait (in milliseconds) between WiFi connection attempts @@ -145,6 +163,7 @@ struct Span{ Blinker statusLED; // indicates HomeSpan status PushButton controlButton; // controls HomeSpan configuration and resets Network network; // configures WiFi and Setup Code via either serial monitor or temporary Access Point + SpanWebLog *webLog=NULL; // optional web status/log SpanConfig hapConfig; // track configuration changes to the HAP Accessory database; used to increment the configuration number (c#) when changes found vector Accessories; // vector of pointers to all Accessories @@ -204,7 +223,10 @@ struct Span{ void deleteStoredValues(){processSerialCommand("V");} // deletes stored Characteristic values from NVS void enableOTA(boolean auth=true){otaEnabled=true;otaAuth=auth;reserveSocketConnections(1);} // enables Over-the-Air updates, with (auth=true) or without (auth=false) authorization password - void setTimeServer(const char *serv, const char *tz){timeServer=serv;timeZone=tz;reserveSocketConnections(1);} // sets optional time server and time zone + + void enableWebLog(uint16_t maxEntries=0, const char *serv=NULL, const char *tz="UTC", const char *url=DEFAULT_WEBLOG_URL){ // enable Web Logging + webLog=new SpanWebLog(maxEntries, serv, tz, url); + } [[deprecated("Please use reserveSocketConnections(n) method instead.")]] void setMaxConnections(uint8_t n){requestedMaxCon=n;} // sets maximum number of simultaneous HAP connections @@ -679,20 +701,6 @@ struct SpanUserCommand { SpanUserCommand(char c, const char *s, void (*f)(const char *, void *), void *arg); }; -/////////////////////////////// - -struct SpanWebLog{ // optional web status/log data - int maxEntries; // max number of log entries; -1 = do not create log or status; 0 = create status but no log; 1..N = create status and log with N entries - int nEntries=0; // current number of log entries - - struct log_t { // log entry type - uint32_t upTime; // number of seconds since booting - struct tm clockTime; // clock time - char *message; // pointers to log entries of arbitrary size - } *log=NULL; // array of log entries - -}; - ///////////////////////////////////////////////// #include "Span.h" diff --git a/src/Settings.h b/src/Settings.h index 1c73c56..6c975cc 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -81,6 +81,8 @@ #define DEFAULT_TCP_PORT 80 // change with homeSpan.setPort(port); +#define DEFAULT_WEBLOG_URL "status" // change with optional fourth argument in homeSpan.enableWebLog() + ///////////////////////////////////////////////////// // STATUS LED SETTINGS // From acc64f863a2086c2594721e273e209d987f20422 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 5 Mar 2022 10:40:01 -0600 Subject: [PATCH 015/100] Added addLog(const char *) to SpanWebLog() Also included logic so that clockTime is set to "Unknown" is addLog() is called prior to WiFi being established. Next up: replace addLog(const char *) with a variadic set of parameters with dynamic storage allocation. --- src/HAP.cpp | 29 ++++++++++++++++++++++++++--- src/HomeSpan.cpp | 4 ++-- src/HomeSpan.h | 2 +- 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index c343d4b..a72df6a 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -1256,6 +1256,16 @@ int HAPClient::putPrepareURL(char *json){ int HAPClient::getStatusURL(){ + char clocktime[33]; + + if(homeSpan.webLog->timeInit){ + struct tm timeinfo; + getLocalTime(&timeinfo,10); + strftime(clocktime,sizeof(clocktime),"%c",&timeinfo); + } else { + sprintf(clocktime,"Unknown"); + } + char uptime[16]; int seconds=esp_timer_get_time()/1e6; int secs=seconds%60; @@ -1268,13 +1278,14 @@ int HAPClient::getStatusURL(){ String response="HTTP/1.1 200 OK\r\nContent-type: text/html\r\n\r\n"; response+="HomeSpan Status\n"; - response+="\n"; response+="\n"; response+="

" + String(homeSpan.displayName) + "

\n"; response+="\n"; response+="\n"; + response+="\n"; response+="\n"; response+="\n"; response+="\n"; @@ -1292,9 +1303,21 @@ int HAPClient::getStatusURL(){ lastIndex=0; for(int i=homeSpan.webLog->nEntries-1;i>=lastIndex;i--){ - response+=""; + int index=i%homeSpan.webLog->maxEntries; + seconds=homeSpan.webLog->log[index].upTime/1e6; + secs=seconds%60; + mins=(seconds/=60)%60; + hours=(seconds/=60)%24; + days=(seconds/=24); + sprintf(uptime,"%d:%02d:%02d:%02d",days,hours,mins,secs); + + if(homeSpan.webLog->log[index].clockTime.tm_year>0) + strftime(clocktime,sizeof(clocktime),"%c",&homeSpan.webLog->log[index].clockTime); + else + sprintf(clocktime,"Unknown"); + + response+=""; } - response+="
Up Time:" + String(uptime) + "
Current Time:" + String(clocktime) + "
Boot Time:" + String(homeSpan.webLog->bootTime) + "
ESP32 Board:" + String(ARDUINO_BOARD) + "
Arduino-ESP Version:" + String(ARDUINO_ESP_VERSION) + "
" + String(i+1) + "" + "TBD1" "" + "TBD2" + "" + String(homeSpan.webLog->log[i%homeSpan.webLog->maxEntries].message) + "
" + String(i+1) + "" + String(uptime) + "" + String(clocktime) + "" + String(homeSpan.webLog->log[index].message) + "
\n"; } diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 69a6948..0d7a4cb 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -1965,9 +1965,9 @@ void SpanWebLog::addLog(const char *m){ int index=nEntries%maxEntries; - log[index].upTime=esp_timer_get_time()/1e6; + log[index].upTime=esp_timer_get_time(); if(timeInit) - getLocalTime(&log[index].clockTime); + getLocalTime(&log[index].clockTime,10); else log[index].clockTime.tm_year=0; log[index].message=m; diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 362fa7e..748fd72 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -105,7 +105,7 @@ struct SpanWebLog{ // optional web status/log data String statusURL; // URL of status log struct log_t { // log entry type - uint32_t upTime; // number of seconds since booting + uint64_t upTime; // number of seconds since booting struct tm clockTime; // clock time const char *message; // pointers to log entries of arbitrary size } *log=NULL; // array of log entries From d9a9e6f31cbc649edbf3861fe789b981a47cb29e Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 5 Mar 2022 12:50:20 -0600 Subject: [PATCH 016/100] Changed webLog in HomeSpan from pointer to instantiated variable Added isEnabled to SpanWebLog to indicate whether Web Log has been enabled. This allows the use of homeSpan.webLog.addLog() without ever enabling (in which case the log entries are ignored). --- src/HAP.cpp | 24 ++++++++++++------------ src/HomeSpan.cpp | 9 +++++---- src/HomeSpan.h | 7 ++++--- 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index a72df6a..b18eecc 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -342,7 +342,7 @@ void HAPClient::processRequest(){ return; } - if(homeSpan.webLog && !strncmp(body,homeSpan.webLog->statusURL.c_str(),homeSpan.webLog->statusURL.length())){ // GET STATUS - AN OPTIONAL, NON-HAP-R2 FEATURE + if(homeSpan.webLog.isEnabled && !strncmp(body,homeSpan.webLog.statusURL.c_str(),homeSpan.webLog.statusURL.length())){ // GET STATUS - AN OPTIONAL, NON-HAP-R2 FEATURE getStatusURL(); return; } @@ -1258,7 +1258,7 @@ int HAPClient::getStatusURL(){ char clocktime[33]; - if(homeSpan.webLog->timeInit){ + if(homeSpan.webLog.timeInit){ struct tm timeinfo; getLocalTime(&timeinfo,10); strftime(clocktime,sizeof(clocktime),"%c",&timeinfo); @@ -1286,37 +1286,37 @@ int HAPClient::getStatusURL(){ response+="\n"; response+="\n"; response+="\n"; - response+="\n"; + response+="\n"; response+="\n"; response+="\n"; response+="\n"; response+="\n"; response+="\n"; - response+="\n"; + response+="\n"; response+="
Up Time:" + String(uptime) + "
Current Time:" + String(clocktime) + "
Boot Time:" + String(homeSpan.webLog->bootTime) + "
Boot Time:" + String(homeSpan.webLog.bootTime) + "
ESP32 Board:" + String(ARDUINO_BOARD) + "
Arduino-ESP Version:" + String(ARDUINO_ESP_VERSION) + "
ESP-IDF Version:" + String(ESP_IDF_VERSION_MAJOR) + "." + String(ESP_IDF_VERSION_MINOR) + "." + String(ESP_IDF_VERSION_PATCH) + "
HomeSpan Version:" + String(HOMESPAN_VERSION) + "
Sketch Version:" + String(homeSpan.getSketchVersion()) + "
Max Log Entries:" + String(homeSpan.webLog->maxEntries) + "
Max Log Entries:" + String(homeSpan.webLog.maxEntries) + "
\n"; response+="

"; - if(homeSpan.webLog->maxEntries>0){ + if(homeSpan.webLog.maxEntries>0){ response+="\n"; - int lastIndex=homeSpan.webLog->nEntries-homeSpan.webLog->maxEntries; + int lastIndex=homeSpan.webLog.nEntries-homeSpan.webLog.maxEntries; if(lastIndex<0) lastIndex=0; - for(int i=homeSpan.webLog->nEntries-1;i>=lastIndex;i--){ - int index=i%homeSpan.webLog->maxEntries; - seconds=homeSpan.webLog->log[index].upTime/1e6; + for(int i=homeSpan.webLog.nEntries-1;i>=lastIndex;i--){ + int index=i%homeSpan.webLog.maxEntries; + seconds=homeSpan.webLog.log[index].upTime/1e6; secs=seconds%60; mins=(seconds/=60)%60; hours=(seconds/=60)%24; days=(seconds/=24); sprintf(uptime,"%d:%02d:%02d:%02d",days,hours,mins,secs); - if(homeSpan.webLog->log[index].clockTime.tm_year>0) - strftime(clocktime,sizeof(clocktime),"%c",&homeSpan.webLog->log[index].clockTime); + if(homeSpan.webLog.log[index].clockTime.tm_year>0) + strftime(clocktime,sizeof(clocktime),"%c",&homeSpan.webLog.log[index].clockTime); else sprintf(clocktime,"Unknown"); - response+=""; + response+=""; } response+="
EntryUp TimeLog TimeMessage
" + String(i+1) + "" + String(uptime) + "" + String(clocktime) + "" + String(homeSpan.webLog->log[index].message) + "
" + String(i+1) + "" + String(uptime) + "" + String(clocktime) + "" + String(homeSpan.webLog.log[index].message) + "
\n"; } diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 0d7a4cb..0acb692 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -560,9 +560,9 @@ void Span::checkConnect(){ } } - if(webLog){ - Serial.printf("Web Logging enabled at http://%s.local:%d%swith max number of entries=%d\n\n",hostName,tcpPortNum,webLog->statusURL.c_str()+4,webLog->maxEntries); - webLog->initTime(); + if(webLog.isEnabled){ + Serial.printf("Web Logging enabled at http://%s.local:%d%swith max number of entries=%d\n\n",hostName,tcpPortNum,webLog.statusURL.c_str()+4,webLog.maxEntries); + webLog.initTime(); } Serial.printf("Starting HAP Server on port %d supporting %d simultaneous HomeKit Controller Connections...\n",tcpPortNum,maxConnections); @@ -1930,7 +1930,8 @@ SpanUserCommand::SpanUserCommand(char c, const char *s, void (*f)(const char *, // SpanWebLog // /////////////////////////////// -SpanWebLog::SpanWebLog(uint16_t maxEntries, const char *serv, const char *tz, const char *url){ +void SpanWebLog::init(uint16_t maxEntries, const char *serv, const char *tz, const char *url){ + isEnabled=true; this->maxEntries=maxEntries; timeServer=serv; timeZone=tz; diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 748fd72..4f61d86 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -96,6 +96,7 @@ struct SpanBuf{ // temporary storage buffer for us /////////////////////////////// struct SpanWebLog{ // optional web status/log data + boolean isEnabled=false; // flag to inidicate WebLog has been enabled uint16_t maxEntries; // max number of log entries; int nEntries=0; // total cumulative number of log entries const char *timeServer; // optional time server to use for acquiring clock time @@ -110,7 +111,7 @@ struct SpanWebLog{ // optional web status/log data const char *message; // pointers to log entries of arbitrary size } *log=NULL; // array of log entries - SpanWebLog(uint16_t maxEntries, const char *serv, const char *tz, const char *url); + void init(uint16_t maxEntries, const char *serv, const char *tz, const char *url); void initTime(); void addLog(const char *m); }; @@ -163,7 +164,7 @@ struct Span{ Blinker statusLED; // indicates HomeSpan status PushButton controlButton; // controls HomeSpan configuration and resets Network network; // configures WiFi and Setup Code via either serial monitor or temporary Access Point - SpanWebLog *webLog=NULL; // optional web status/log + SpanWebLog webLog; // optional web status/log SpanConfig hapConfig; // track configuration changes to the HAP Accessory database; used to increment the configuration number (c#) when changes found vector Accessories; // vector of pointers to all Accessories @@ -225,7 +226,7 @@ struct Span{ void enableOTA(boolean auth=true){otaEnabled=true;otaAuth=auth;reserveSocketConnections(1);} // enables Over-the-Air updates, with (auth=true) or without (auth=false) authorization password void enableWebLog(uint16_t maxEntries=0, const char *serv=NULL, const char *tz="UTC", const char *url=DEFAULT_WEBLOG_URL){ // enable Web Logging - webLog=new SpanWebLog(maxEntries, serv, tz, url); + webLog.init(maxEntries, serv, tz, url); } [[deprecated("Please use reserveSocketConnections(n) method instead.")]] From 2787966c48b20fb7adf7633c8b6d1f2b5ef4c49b Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 5 Mar 2022 18:13:27 -0600 Subject: [PATCH 017/100] Converted webLog.addLog() to variadic form Also created WEBLOG() variadic macro --- src/HAP.cpp | 2 +- src/HomeSpan.cpp | 11 ++++++++--- src/HomeSpan.h | 4 ++-- src/Settings.h | 2 +- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index b18eecc..fe5482f 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -1316,7 +1316,7 @@ int HAPClient::getStatusURL(){ else sprintf(clocktime,"Unknown"); - response+="" + String(i+1) + "" + String(uptime) + "" + String(clocktime) + "" + String(homeSpan.webLog.log[index].message) + ""; + response+="" + String(i+1) + "" + String(uptime) + "" + String(clocktime) + "" + String(homeSpan.webLog.log[index].message) + "\n"; } response+="\n"; } diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 0acb692..3cd8658 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -1948,7 +1948,7 @@ void SpanWebLog::initTime(){ Serial.printf("Acquiring Time from %s (%s)... ",timeServer,timeZone); configTzTime(timeZone,timeServer); struct tm timeinfo; - if(getLocalTime(&timeinfo)){ + if(getLocalTime(&timeinfo,10000)){ strftime(bootTime,sizeof(bootTime),"%c",&timeinfo); Serial.printf("%s\n\n",bootTime); homeSpan.reserveSocketConnections(1); @@ -1960,7 +1960,7 @@ void SpanWebLog::initTime(){ /////////////////////////////// -void SpanWebLog::addLog(const char *m){ +void SpanWebLog::addLog(const char *fmt, ...){ if(maxEntries==0) return; @@ -1971,7 +1971,12 @@ void SpanWebLog::addLog(const char *m){ getLocalTime(&log[index].clockTime,10); else log[index].clockTime.tm_year=0; - log[index].message=m; + free(log[index].message); + va_list ap; + va_start(ap,fmt); + vasprintf(&log[index].message,fmt,ap); + va_end(ap); + nEntries++; } diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 4f61d86..610b125 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -108,12 +108,12 @@ struct SpanWebLog{ // optional web status/log data struct log_t { // log entry type uint64_t upTime; // number of seconds since booting struct tm clockTime; // clock time - const char *message; // pointers to log entries of arbitrary size + char *message; // pointers to log entries of arbitrary size } *log=NULL; // array of log entries void init(uint16_t maxEntries, const char *serv, const char *tz, const char *url); void initTime(); - void addLog(const char *m); + void addLog(const char *fmr, ...); }; /////////////////////////////// diff --git a/src/Settings.h b/src/Settings.h index 6c975cc..0057897 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -100,8 +100,8 @@ #define LOG1(x) if(homeSpan.logLevel>0)Serial.print(x) #define LOG2(x) if(homeSpan.logLevel>1)Serial.print(x) +#define WEBLOG(format,...) homeSpan.webLog.addLog(format __VA_OPT__(,) __VA_ARGS__) - ////////////////////////////////////////////////////// // Types of Accessory Categories // // Reference: HAP Section 13 // From e3b9ed99cbb6c27f3c68bb17ae5c4065b025b7ab Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 5 Mar 2022 20:35:01 -0600 Subject: [PATCH 018/100] Added homeSpan.setTimeServerTimeout(uint32_t tSec); Sets timeout when connecting to Time Server (default is 10 seconds if not specified) --- src/HomeSpan.cpp | 6 +++--- src/HomeSpan.h | 3 +++ src/Settings.h | 1 + 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 3cd8658..cab148b 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -1945,16 +1945,16 @@ void SpanWebLog::initTime(){ if(!timeServer) return; - Serial.printf("Acquiring Time from %s (%s)... ",timeServer,timeZone); + Serial.printf("Acquiring Time from %s (%s). Waiting %d second(s) for response... ",timeServer,timeZone,waitTime/1000); configTzTime(timeZone,timeServer); struct tm timeinfo; - if(getLocalTime(&timeinfo,10000)){ + if(getLocalTime(&timeinfo,waitTime)){ strftime(bootTime,sizeof(bootTime),"%c",&timeinfo); Serial.printf("%s\n\n",bootTime); homeSpan.reserveSocketConnections(1); timeInit=true; } else { - Serial.printf("Can't access Time Server (or Time Zone improperly specified) - time-keeping not initialized!\n\n"); + Serial.printf("Can't access Time Server - time-keeping not initialized!\n\n"); } } diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 610b125..7916fa2 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -104,6 +104,7 @@ struct SpanWebLog{ // optional web status/log data boolean timeInit=false; // flag to indicate time has been initialized char bootTime[33]="Unknown"; // boot time String statusURL; // URL of status log + uint32_t waitTime=10000; // number of milliseconds to wait for initial connection to time server struct log_t { // log entry type uint64_t upTime; // number of seconds since booting @@ -228,6 +229,8 @@ struct Span{ void enableWebLog(uint16_t maxEntries=0, const char *serv=NULL, const char *tz="UTC", const char *url=DEFAULT_WEBLOG_URL){ // enable Web Logging webLog.init(maxEntries, serv, tz, url); } + + void setTimeServerTimeout(uint32_t tSec){webLog.waitTime=tSec*1000;} // sets wait time (in seconds) for optional web log time server to connect [[deprecated("Please use reserveSocketConnections(n) method instead.")]] void setMaxConnections(uint8_t n){requestedMaxCon=n;} // sets maximum number of simultaneous HAP connections diff --git a/src/Settings.h b/src/Settings.h index 0057897..9c1f345 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -100,6 +100,7 @@ #define LOG1(x) if(homeSpan.logLevel>0)Serial.print(x) #define LOG2(x) if(homeSpan.logLevel>1)Serial.print(x) + #define WEBLOG(format,...) homeSpan.webLog.addLog(format __VA_OPT__(,) __VA_ARGS__) ////////////////////////////////////////////////////// From cf22ff1a92e19758d25e053113ff0b90a7e880a1 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 5 Mar 2022 22:06:34 -0600 Subject: [PATCH 019/100] Added homeSpan.getClientIP() Gets IP address (as char *) of last client to send a request. Useful as part of web log messages. Will return 0.0.0.0 if used outside of any code that is responding to a client request. --- examples/19-WebLog/19-WebLog.ino | 129 +++++++++++++++++++++++++++++++ examples/19-WebLog/DEV_LED.h | 101 ++++++++++++++++++++++++ src/HomeSpan.cpp | 6 +- src/HomeSpan.h | 2 + 4 files changed, 236 insertions(+), 2 deletions(-) create mode 100644 examples/19-WebLog/19-WebLog.ino create mode 100644 examples/19-WebLog/DEV_LED.h diff --git a/examples/19-WebLog/19-WebLog.ino b/examples/19-WebLog/19-WebLog.ino new file mode 100644 index 0000000..8706e94 --- /dev/null +++ b/examples/19-WebLog/19-WebLog.ino @@ -0,0 +1,129 @@ +/********************************************************************************* + * MIT License + * + * Copyright (c) 2020 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 // +// ------------------------------------------------ // +// // +// Example 5: Two working on/off LEDs based on the // +// LightBulb Service // +// // +//////////////////////////////////////////////////////////// + + +#include "HomeSpan.h" +#include "DEV_LED.h" // NEW! Include this new file, DEV_LED.h, which will be fully explained below + +void setup() { + + // First light! Control an LED from HomeKit! + + // Example 5 expands on Example 2 by adding in the code needed to actually control LEDs connected to the ESP32 from HomeKit. + // In Example 2 we built out all the functionality to create a "Tile" Acessories inside HomeKit that displayed an on/off light, but + // these control did not actually operate anything on the ESP32. To operate actual devices HomeSpan needs to be programmed to + // respond to "update" requests from HomeKit by performing some form of operation. + + // Though HomeKit itself sends "update" requests to individual Characteristics, this is not intuitive and leads to complex coding requirements + // when a Service has more than one Characteristic, such as both "On" and "Brightness." To make this MUCH easier for the user, HomeSpan + // uses a framework in which Services are updated instead of individual Characteristics. It does so by calling the update() method of + // each Service with flags indicating all the Characteristics in that Service that HomeKit requested to update. The user simply + // implements code to perform the actual operation, and returns either true or false if the update was successful. HomeSpan takes care of all + // the underlying nuts and bolts. + + // Every Service defined in HomeKit, such as Service:LightBulb and Service:Fan (and even Service::AccessoryInformation) implements an update() + // method that, as a default, does nothing but returns a value of true. To actually operate real devices you need to over-ride this default update() + // method with your own code. The easiest way to do this is by creating a DERIVED class based on one of the built-in HomeSpan Services. + // Within this derived class you can perform initial set-up routines (if needed), over-ride the update() method with your own code, and even create + // any other methods or class-specific variables you need to fully operate complex devices. Most importantly, the derived class can take arguments + // so that you can make them more generic, re-use them multiple times (as will be seen below), and convert them to standalone modules (also shown below). + + // All of the HomeKit Services implemented by HomeSpan can be found in the Services.h file. Any can be used as the parent for a derived Service. + + // We begin by repeating nearly the same code from Example 2, but with a few key changes. For ease of reading, all prior comments have been removed + // from lines that simply repeat Example 2, and new comments have been added to explictly show the new code. + + Serial.begin(115200); + + homeSpan.enableWebLog(10,"pool.ntp.org","CST6CDT"); + homeSpan.begin(Category::Lighting,"HomeSpan LEDs"); + + new SpanAccessory(); + + new Service::AccessoryInformation(); + new Characteristic::Name("LED #1"); + new Characteristic::Manufacturer("HomeSpan"); + new Characteristic::SerialNumber("123-ABC"); + new Characteristic::Model("20mA LED"); + new Characteristic::FirmwareRevision("0.9"); + new Characteristic::Identify(); + + new Service::HAPProtocolInformation(); + new Characteristic::Version("1.1.0"); + + // In Example 2 we instantiated a LightBulb Service and its "On" Characteristic here. We are now going to replace these two lines (by commenting them out)... + + // new Service::LightBulb(); + // new Characteristic::On(); + + // ...with a single new line instantiating a new class we will call DEV_LED(): + + new DEV_LED(16); // this instantiates a new LED Service. Where is this defined? What happpened to Characteristic::On? Keep reading... + + // The full definition and code for DEV_LED is implemented in a separate file called "DEV_LED.h" that is specified using the #include at the top of this program. + // The prefix DEV_ is not required but it's a helpful convention when naming all your device-specific Services. Note that DEV_LED will include all the required + // Characterictics of the Service, so you DO NOT have to separately instantiate Characteristic::On --- everything HomeSpan needs for DEV_LED should be implemented + // in DEV_LED itself (though it's not all that much). Finally, note that we created DEV_LED to take a single integer argument. If you guessed this is + // the number of the Pin to which you have attached an LED, you'd be right. See DEV_LED.h for a complete explanation of how it works. + + new SpanAccessory(); + + new Service::AccessoryInformation(); + new Characteristic::Name("LED #2"); + new Characteristic::Manufacturer("HomeSpan"); + new Characteristic::SerialNumber("123-ABC"); + new Characteristic::Model("20mA LED"); + new Characteristic::FirmwareRevision("0.9"); + new Characteristic::Identify(); + + new Service::HAPProtocolInformation(); + new Characteristic::Version("1.1.0"); + + // new Service::LightBulb(); // Same as above, this line is deleted... + // new Characteristic::On(); // This line is also deleted... + + new DEV_LED(17); // ...and replaced with a single line that instantiates a second DEV_LED Service on Pin 17 + +} // end of setup() + +////////////////////////////////////// + +void loop(){ + + homeSpan.poll(); + +} // end of loop() diff --git a/examples/19-WebLog/DEV_LED.h b/examples/19-WebLog/DEV_LED.h new file mode 100644 index 0000000..0dbe012 --- /dev/null +++ b/examples/19-WebLog/DEV_LED.h @@ -0,0 +1,101 @@ + +//////////////////////////////////// +// DEVICE-SPECIFIC LED SERVICES // +//////////////////////////////////// + +// HERE'S WHERE WE DEFINE OUR NEW LED SERVICE! + +struct DEV_LED : Service::LightBulb { // First we create a derived class from the HomeSpan LightBulb Service + + int ledPin; // this variable stores the pin number defined for this LED + SpanCharacteristic *power; // here we create a generic pointer to a SpanCharacteristic named "power" that we will use below + + // Next we define the constructor for DEV_LED. Note that it takes one argument, ledPin, + // which specifies the pin to which the LED is attached. + + DEV_LED(int ledPin) : Service::LightBulb(){ + + power=new Characteristic::On(); // this is where we create the On Characterstic we had previously defined in setup(). Save this in the pointer created above, for use below + this->ledPin=ledPin; // don't forget to store ledPin... + pinMode(ledPin,OUTPUT); // ...and set the mode for ledPin to be an OUTPUT (standard Arduino function) + + } // end constructor + + // Finally, we over-ride the default update() method with instructions that actually turn on/off the LED. Note update() returns type boolean + + boolean update(){ + + digitalWrite(ledPin,power->getNewVal()); // use a standard Arduino function to turn on/off ledPin based on the return of a call to power->getNewVal() (see below for more info) + WEBLOG("LED on Pin %d: %s (%s)",ledPin,power->getNewVal()?"ON":"OFF",homeSpan.getClientIP()); + + return(true); // return true to indicate the update was successful (otherwise create code to return false if some reason you could not turn on the LED) + + } // update +}; + +////////////////////////////////// + +// HOW update() WORKS: +// ------------------ +// +// Whenever a HomeKit controller requests HomeSpan to update a Characteristic, HomeSpan calls the update() method for the SERVICE that contains the +// Characteristic. It calls this only one time, even if multiple Characteristics updates are requested for that Service. For example, if you +// direct HomeKit to turn on a light and set it to 50% brightness, it will send HomeSpan two requests: one to update the "On" Characteristic of the +// LightBulb Service from "false" to "true" and another to update the "Brightness" Characteristic of that same Service to 50. This is VERY inefficient +// and would require the user to process multiple updates to the same Service. +// +// Instead, HomeSpan combines both requests into a single call to update() for the Service itself, where you can process all of the Characteristics +// that change at the same time. In the example above, we only have a single Characteristic to deal with, so this does not mean much. But in later +// examples we'll see how this works with multiple Characteristics. + +// HOW TO ACCESS A CHARACTERISTIC'S NEW AND CURRENT VALUES +// ------------------------------------------------------- +// +// HomeSpan stores the values for its Characteristics in a union structure that allows for different types, such as floats, booleans, etc. The specific +// types are defined by HAP for each Characteristic. Looking up whether a Characteristic is a uint8 or uint16 can be tiresome, so HomeSpan abstracts +// all these details. Since C++ adheres to strict variable typing, this is done through the use of template methods. Every Characteristic supports +// the following two methods: +// +// getVal() - returns the CURRENT value of the Characterisic, after casting into "type" +// getNewVal() - returns the NEW value (i.e. to be updated) of the Characteritic, after casting into "type" +// +// For example, MyChar->getVal() returns the current value of SpanCharacterstic MyChar as an int, REGARDLESS of how the value is stored by HomeSpan. +// Similarly, MyChar->getVal() returns a value as a double, even it is stored as as a boolean (in which case you'll either get 0.00 or 1.00). +// Of course you need to make sure you understand the range of expected values so that you don't try to access a value stored as 2-byte int using getVal(). +// But it's perfectly okay to use getVal() to access the value of a Characteristic that HAP insists on storing as a float, even though its range is +// strictly between 0 and 100 in steps of 1. Knowing the range and step size is all you need to know in determining you can access this as an or even a . +// +// Because most Characteristic values can properly be cast into int, getVal and getNewVal both default to if the template parameter is not specified. +// As you can see above, we retrieved the new value HomeKit requested for the On Characteristic that we named "power" by simply calling power->getNewVal(). +// Since no template parameter is specified, getNewVal() will return an int. And since the On Characteristic is natively stored as a boolean, getNewVal() +// will either return a 0 or a 1, depending on whether HomeKit is requesting the Characteristic to be turned off or on. +// +// You may also note that in the above example we needed to use getNewVal(), but did not use getVal() anywhere. This is because we know exactly what +// to do if HomeKit requests an LED to be turned on or off. The current status of the LED (on or off) does not matter. In latter examples we will see +// instances where the current state of the device DOES matter, and we will need to access both current and new values. +// +// Finally, there is one additional method for Characteristics that is not used above but will be in later examples: updated(). This method returns a +// boolean indicating whether HomeKit has requested a Characteristic to be updated, which means that getNewVal() will contain the new value it wants to set +// for that Characteristic. For a Service with only one Characteristic, as above, we don't need to ask if "power" was updated using power->updated() because +// the fact the the update() method for the Service is being called means that HomeKit is requesting an update, and the only thing to update is "power". +// But for Services with two or more Characteristics, update() can be called with a request to update only a subset of the Characteristics. We will +// find good use for the updated() method in later, multi-Characteristic examples. + +// UNDER THE HOOD: WHAT THE RETURN CODE FOR UPDATE() DOES +// ------------------------------------------------------ +// +// HomeKit requires each Characteristic to return a special HAP status code when an attempt to update its value is made. HomeSpan automatically takes care of +// most of the errors, such as a Characteristic not being found, or a request to update a Characteristic that is read only. In these cases update() is never +// even called. But if it is, HomeSpan needs to return a HAP status code for each of the Characteristics that were to be updated in that Service. +// By returning "true" you tell HomeSpan that the newValues requested are okay and you've made the required updates to the physical device. Upon +// receiving a true return value, HomeSpan updates the Characteristics themselves by copying the "newValue" data elements into the current "value" data elements. +// HomeSpan then sends a message back to HomeKit with a HAP code representing "OK," which lets the Controller know that the new values it requested have been +// sucessfully processed. At no point does HomeKit ask for, or allow, a data value to be sent back from HomeSpan indicating the data in a Characteristic. +// When requesting an update, HomeKit simply expects a HAP status code of OK, or some other status code representing an error. To tell HomeSpan to send the Controller +// an error code, indicating that you were not able to successfully process the update, simply have update() return a value of "false." HomeSpan converts a +// return of "false" to the HAP status code representing "UNABLE," which will cause the Controller to show that the device is not responding. + +// There are very few reasons you should need to return "false" since so much checking is done in advance by either HomeSpan or HomeKit +// itself. For instance, HomeKit does not allow you to use the Controller, or even Siri, to change the brightness of LightBulb to a value outside the +// range of allowable values you specified. This means that any update() requests you receive should only contain newValue data elements that are in-range. +// diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index cab148b..1e768fe 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -257,8 +257,10 @@ void Span::poll() { if(hap[i]->client && hap[i]->client.available()){ // if connection exists and data is available - HAPClient::conNum=i; // set connection number - hap[i]->processRequest(); // process HAP request + HAPClient::conNum=i; // set connection number + homeSpan.lastClientIP=hap[i]->client.remoteIP().toString(); // store IP Address for web logging + hap[i]->processRequest(); // process HAP request + homeSpan.lastClientIP="0.0.0.0"; // reset stored IP address to show "0.0.0.0" if homeSpan.getClientIP() is used in any other context if(!hap[i]->client){ // client disconnected by server LOG1("** Disconnecting Client #"); diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 7916fa2..d115498 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -138,6 +138,7 @@ struct Span{ nvs_handle charNVS; // handle for non-volatile-storage of Characteristics data nvs_handle wifiNVS=0; // handle for non-volatile-storage of WiFi data char pairingCodeCommand[12]=""; // user-specified Pairing Code - only needed if Pairing Setup Code is specified in sketch using setPairingCode() + String lastClientIP="0.0.0.0"; // IP address of last client accessing device through encrypted channel boolean connected=false; // WiFi connection status unsigned long waitTime=60000; // time to wait (in milliseconds) between WiFi connection attempts @@ -220,6 +221,7 @@ struct Span{ void setApFunction(void (*f)()){apFunction=f;} // sets an optional user-defined function to call when activating the WiFi Access Point void enableAutoStartAP(){autoStartAPEnabled=true;} // enables auto start-up of Access Point when WiFi Credentials not found void setWifiCredentials(const char *ssid, const char *pwd); // sets WiFi Credentials + const char *getClientIP(){return(lastClientIP.c_str());} // get IP address of last client to send an encrypted request - to be used only in update() commands void setPairingCode(const char *s){sprintf(pairingCodeCommand,"S %9s",s);} // sets the Pairing Code - use is NOT recommended. Use 'S' from CLI instead void deleteStoredValues(){processSerialCommand("V");} // deletes stored Characteristic values from NVS From f37889f8da21f7f80c7226093dcb021b26e20b85 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 5 Mar 2022 22:24:23 -0600 Subject: [PATCH 020/100] Added ClientIP as permanent part of Web Log ; DELETED homeSpan.getClientIP() ClientIP will show as "0.0.0.0" if log message is not related to client request --- examples/19-WebLog/DEV_LED.h | 3 ++- src/HAP.cpp | 4 ++-- src/HomeSpan.cpp | 3 ++- src/HomeSpan.h | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/examples/19-WebLog/DEV_LED.h b/examples/19-WebLog/DEV_LED.h index 0dbe012..07c7552 100644 --- a/examples/19-WebLog/DEV_LED.h +++ b/examples/19-WebLog/DEV_LED.h @@ -18,6 +18,7 @@ struct DEV_LED : Service::LightBulb { // First we create a derived power=new Characteristic::On(); // this is where we create the On Characterstic we had previously defined in setup(). Save this in the pointer created above, for use below this->ledPin=ledPin; // don't forget to store ledPin... pinMode(ledPin,OUTPUT); // ...and set the mode for ledPin to be an OUTPUT (standard Arduino function) + WEBLOG("Configuring LED on Pin %d",ledPin); } // end constructor @@ -26,7 +27,7 @@ struct DEV_LED : Service::LightBulb { // First we create a derived boolean update(){ digitalWrite(ledPin,power->getNewVal()); // use a standard Arduino function to turn on/off ledPin based on the return of a call to power->getNewVal() (see below for more info) - WEBLOG("LED on Pin %d: %s (%s)",ledPin,power->getNewVal()?"ON":"OFF",homeSpan.getClientIP()); + WEBLOG("LED on Pin %d: %s",ledPin,power->getNewVal()?"ON":"OFF"); return(true); // return true to indicate the update was successful (otherwise create code to return false if some reason you could not turn on the LED) diff --git a/src/HAP.cpp b/src/HAP.cpp index fe5482f..dd8eb19 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -1297,7 +1297,7 @@ int HAPClient::getStatusURL(){ response+="

"; if(homeSpan.webLog.maxEntries>0){ - response+="\n"; + response+="
EntryUp TimeLog TimeMessage
\n"; int lastIndex=homeSpan.webLog.nEntries-homeSpan.webLog.maxEntries; if(lastIndex<0) lastIndex=0; @@ -1316,7 +1316,7 @@ int HAPClient::getStatusURL(){ else sprintf(clocktime,"Unknown"); - response+="\n"; + response+="\n"; } response+="
EntryUp TimeLog TimeClientMessage
" + String(i+1) + "" + String(uptime) + "" + String(clocktime) + "" + String(homeSpan.webLog.log[index].message) + "
" + String(i+1) + "" + String(uptime) + "" + String(clocktime) + "" + homeSpan.webLog.log[index].clientIP + "" + String(homeSpan.webLog.log[index].message) + "
\n"; } diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 1e768fe..6ce1b1c 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -1979,6 +1979,7 @@ void SpanWebLog::addLog(const char *fmt, ...){ va_start(ap,fmt); vasprintf(&log[index].message,fmt,ap); va_end(ap); - + + log[index].clientIP=homeSpan.lastClientIP; nEntries++; } diff --git a/src/HomeSpan.h b/src/HomeSpan.h index d115498..e7d9469 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -110,6 +110,7 @@ struct SpanWebLog{ // optional web status/log data uint64_t upTime; // number of seconds since booting struct tm clockTime; // clock time char *message; // pointers to log entries of arbitrary size + String clientIP; // IP address of client making request (or "0.0.0.0" if not applicable) } *log=NULL; // array of log entries void init(uint16_t maxEntries, const char *serv, const char *tz, const char *url); @@ -221,7 +222,6 @@ struct Span{ void setApFunction(void (*f)()){apFunction=f;} // sets an optional user-defined function to call when activating the WiFi Access Point void enableAutoStartAP(){autoStartAPEnabled=true;} // enables auto start-up of Access Point when WiFi Credentials not found void setWifiCredentials(const char *ssid, const char *pwd); // sets WiFi Credentials - const char *getClientIP(){return(lastClientIP.c_str());} // get IP address of last client to send an encrypted request - to be used only in update() commands void setPairingCode(const char *s){sprintf(pairingCodeCommand,"S %9s",s);} // sets the Pairing Code - use is NOT recommended. Use 'S' from CLI instead void deleteStoredValues(){processSerialCommand("V");} // deletes stored Characteristic values from NVS From 33042f191e7890fe54967088d231c83a44041882 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 5 Mar 2022 22:34:30 -0600 Subject: [PATCH 021/100] Added logURL to MDNS broadcast Broadcast as "logURL" only if Web Logging is enabled. --- src/HomeSpan.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 6ce1b1c..501b1af 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -563,6 +563,8 @@ void Span::checkConnect(){ } if(webLog.isEnabled){ + mdns_service_txt_item_set("_hap","_tcp","logURL",webLog.statusURL.c_str()+4); // Web Log Enabled (info only - NOT used by HAP) + Serial.printf("Web Logging enabled at http://%s.local:%d%swith max number of entries=%d\n\n",hostName,tcpPortNum,webLog.statusURL.c_str()+4,webLog.maxEntries); webLog.initTime(); } From 682e65129d7fefe89aad20401181eef0573ba3e0 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 5 Mar 2022 23:08:14 -0600 Subject: [PATCH 022/100] Added new Example #19 showing Web Logging Based on Example 5 (Two Working LEDs) --- examples/19-WebLog/19-WebLog.ino | 43 +++++++++++--------------------- 1 file changed, 14 insertions(+), 29 deletions(-) diff --git a/examples/19-WebLog/19-WebLog.ino b/examples/19-WebLog/19-WebLog.ino index 8706e94..32a4905 100644 --- a/examples/19-WebLog/19-WebLog.ino +++ b/examples/19-WebLog/19-WebLog.ino @@ -30,46 +30,31 @@ // HomeSpan: A HomeKit implementation for the ESP32 // // ------------------------------------------------ // // // -// Example 5: Two working on/off LEDs based on the // -// LightBulb Service // +// Example 19: Web Logging // // // //////////////////////////////////////////////////////////// #include "HomeSpan.h" -#include "DEV_LED.h" // NEW! Include this new file, DEV_LED.h, which will be fully explained below +#include "DEV_LED.h" void setup() { - // First light! Control an LED from HomeKit! - - // Example 5 expands on Example 2 by adding in the code needed to actually control LEDs connected to the ESP32 from HomeKit. - // In Example 2 we built out all the functionality to create a "Tile" Acessories inside HomeKit that displayed an on/off light, but - // these control did not actually operate anything on the ESP32. To operate actual devices HomeSpan needs to be programmed to - // respond to "update" requests from HomeKit by performing some form of operation. - - // Though HomeKit itself sends "update" requests to individual Characteristics, this is not intuitive and leads to complex coding requirements - // when a Service has more than one Characteristic, such as both "On" and "Brightness." To make this MUCH easier for the user, HomeSpan - // uses a framework in which Services are updated instead of individual Characteristics. It does so by calling the update() method of - // each Service with flags indicating all the Characteristics in that Service that HomeKit requested to update. The user simply - // implements code to perform the actual operation, and returns either true or false if the update was successful. HomeSpan takes care of all - // the underlying nuts and bolts. - - // Every Service defined in HomeKit, such as Service:LightBulb and Service:Fan (and even Service::AccessoryInformation) implements an update() - // method that, as a default, does nothing but returns a value of true. To actually operate real devices you need to over-ride this default update() - // method with your own code. The easiest way to do this is by creating a DERIVED class based on one of the built-in HomeSpan Services. - // Within this derived class you can perform initial set-up routines (if needed), over-ride the update() method with your own code, and even create - // any other methods or class-specific variables you need to fully operate complex devices. Most importantly, the derived class can take arguments - // so that you can make them more generic, re-use them multiple times (as will be seen below), and convert them to standalone modules (also shown below). - - // All of the HomeKit Services implemented by HomeSpan can be found in the Services.h file. Any can be used as the parent for a derived Service. - - // We begin by repeating nearly the same code from Example 2, but with a few key changes. For ease of reading, all prior comments have been removed - // from lines that simply repeat Example 2, and new comments have been added to explictly show the new code. +// This is a duplicate of Example 5 (Two Working LEDs) with the addition of HomesSpan Web Logging Serial.begin(115200); - homeSpan.enableWebLog(10,"pool.ntp.org","CST6CDT"); +// Below we enable Web Logging. The first parameter sets the maximum number of log messages to save (as the +// log fills with messages, older ones are replaced by newer ones). The second parameter specifies a Timer Server +// that HomeSpan calls to set its clock. Setting the clock in this fashion is optional, and you can leave this +// argument blank (or set to NULL) if you don't care about setting the absolute time of the device. The third +// argument defines the Time Zone used for setting the device clock. See the HomeSpan API Reference for complete details +// and more options related to this function call. + + homeSpan.enableWebLog(10,"pool.ntp.org","UTC"); + +// The rest of the sketch below is identical to Example 5. All of the Web Logging occurs in DEV_LED.h + homeSpan.begin(Category::Lighting,"HomeSpan LEDs"); new SpanAccessory(); From 1be40ad6fc7b8a3f1ead9bc67f557d29c44e3217 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 6 Mar 2022 07:48:12 -0600 Subject: [PATCH 023/100] Converted LOG1() and LOG2() to variadic macros! If LOG1() or LOG2() is only provided with a SINGLE argument, then Serial.print() is called. This allows you to continue using LOG1() and LOG2() to directly print any variable or object that is handled by Serial.print(), such as an int, double, or even an IPAddress. If LOG1() or LOG2() is provided with multiple arguments, the first is considered the format and Serial.printf(format...) is called. This allows you to use printf-like functionality within LOG1() and LOG2(). --- examples/19-WebLog/19-WebLog.ino | 2 +- examples/19-WebLog/DEV_LED.h | 2 ++ src/Settings.h | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/19-WebLog/19-WebLog.ino b/examples/19-WebLog/19-WebLog.ino index 32a4905..6278ff8 100644 --- a/examples/19-WebLog/19-WebLog.ino +++ b/examples/19-WebLog/19-WebLog.ino @@ -1,7 +1,7 @@ /********************************************************************************* * MIT License * - * Copyright (c) 2020 Gregg E. Berman + * Copyright (c) 2022 Gregg E. Berman * * https://github.com/HomeSpan/HomeSpan * diff --git a/examples/19-WebLog/DEV_LED.h b/examples/19-WebLog/DEV_LED.h index 07c7552..23f555c 100644 --- a/examples/19-WebLog/DEV_LED.h +++ b/examples/19-WebLog/DEV_LED.h @@ -28,6 +28,8 @@ struct DEV_LED : Service::LightBulb { // First we create a derived digitalWrite(ledPin,power->getNewVal()); // use a standard Arduino function to turn on/off ledPin based on the return of a call to power->getNewVal() (see below for more info) WEBLOG("LED on Pin %d: %s",ledPin,power->getNewVal()?"ON":"OFF"); + LOG1("LOG1: LED on Pin %d: %s",ledPin,power->getNewVal()?"ON":"OFF"); + LOG2("LOG2: LED on Pin %d: %s",ledPin,power->getNewVal()?"ON":"OFF"); return(true); // return true to indicate the update was successful (otherwise create code to return false if some reason you could not turn on the LED) diff --git a/src/Settings.h b/src/Settings.h index 9c1f345..ec43965 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -98,8 +98,8 @@ // Message Log Level Control Macros // // 0=Minimal, 1=Informative, 2=All // -#define LOG1(x) if(homeSpan.logLevel>0)Serial.print(x) -#define LOG2(x) if(homeSpan.logLevel>1)Serial.print(x) +#define LOG1(format,...) if(homeSpan.logLevel>0)Serial.print ##__VA_OPT__(f)(format __VA_OPT__(,) __VA_ARGS__) +#define LOG2(format,...) if(homeSpan.logLevel>1)Serial.print ##__VA_OPT__(f)(format __VA_OPT__(,) __VA_ARGS__) #define WEBLOG(format,...) homeSpan.webLog.addLog(format __VA_OPT__(,) __VA_ARGS__) From db3bea3b5c5fca25944a1c4103737b39ddd685d9 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 6 Mar 2022 09:25:17 -0600 Subject: [PATCH 024/100] Completed Example 19 - Web Logs Also updated SpanWebLog::addLog() so that the log message is also output to the Serial Monitor if the HomeSpan Log Level is set to 1 or greater. To do: DOCUMENT ALL THIS! --- examples/19-WebLog/19-WebLog.ino | 45 +++++-------- examples/19-WebLog/DEV_LED.h | 104 ++++++++----------------------- src/HomeSpan.cpp | 3 + 3 files changed, 45 insertions(+), 107 deletions(-) diff --git a/examples/19-WebLog/19-WebLog.ino b/examples/19-WebLog/19-WebLog.ino index 6278ff8..9eca1da 100644 --- a/examples/19-WebLog/19-WebLog.ino +++ b/examples/19-WebLog/19-WebLog.ino @@ -30,7 +30,7 @@ // HomeSpan: A HomeKit implementation for the ESP32 // // ------------------------------------------------ // // // -// Example 19: Web Logging // +// Example 19: Web Logging with time-keeping // // // //////////////////////////////////////////////////////////// @@ -40,20 +40,25 @@ void setup() { -// This is a duplicate of Example 5 (Two Working LEDs) with the addition of HomesSpan Web Logging +// This is a duplicate of Example 5 (Two Working LEDs) with the addition of HomeSpan Web Logging Serial.begin(115200); -// Below we enable Web Logging. The first parameter sets the maximum number of log messages to save (as the -// log fills with messages, older ones are replaced by newer ones). The second parameter specifies a Timer Server -// that HomeSpan calls to set its clock. Setting the clock in this fashion is optional, and you can leave this +// Below we enable Web Logging. The first parameter sets the maximum number of log messages to save. As the +// log fills with messages, older ones are replaced by newer ones. The second parameter specifies a Timer Server +// that HomeSpan calls to set the device clock. Setting the clock is optional, and you can leave this // argument blank (or set to NULL) if you don't care about setting the absolute time of the device. The third -// argument defines the Time Zone used for setting the device clock. See the HomeSpan API Reference for complete details -// and more options related to this function call. +// argument defines the Time Zone used for setting the device clock. The fourth argument specifies the URL page +// of the Web Log. See the HomeSpan API Reference for complete details, as well as additional options, related +// to this function call. - homeSpan.enableWebLog(10,"pool.ntp.org","UTC"); + homeSpan.enableWebLog(10,"pool.ntp.org","UTC","myLog"); // creates a web log on the URL /HomeSpan-[DEVICE-ID].local:[TCP-PORT]/myLog -// The rest of the sketch below is identical to Example 5. All of the Web Logging occurs in DEV_LED.h +// The full URL of the Web Log will be shown in the Serial Monitor at boot time for reference. +// The Web Log output displays a variety of device parameters, plus any log messages you choose +// to provide with the WEBLOG() macro (see DEV_LED.h) + +// Note the rest of the sketch below is identical to Example 5. All of the Web Logging occurs in DEV_LED.h homeSpan.begin(Category::Lighting,"HomeSpan LEDs"); @@ -68,22 +73,9 @@ void setup() { new Characteristic::Identify(); new Service::HAPProtocolInformation(); - new Characteristic::Version("1.1.0"); + new Characteristic::Version("1.1.0"); - // In Example 2 we instantiated a LightBulb Service and its "On" Characteristic here. We are now going to replace these two lines (by commenting them out)... - - // new Service::LightBulb(); - // new Characteristic::On(); - - // ...with a single new line instantiating a new class we will call DEV_LED(): - - new DEV_LED(16); // this instantiates a new LED Service. Where is this defined? What happpened to Characteristic::On? Keep reading... - - // The full definition and code for DEV_LED is implemented in a separate file called "DEV_LED.h" that is specified using the #include at the top of this program. - // The prefix DEV_ is not required but it's a helpful convention when naming all your device-specific Services. Note that DEV_LED will include all the required - // Characterictics of the Service, so you DO NOT have to separately instantiate Characteristic::On --- everything HomeSpan needs for DEV_LED should be implemented - // in DEV_LED itself (though it's not all that much). Finally, note that we created DEV_LED to take a single integer argument. If you guessed this is - // the number of the Pin to which you have attached an LED, you'd be right. See DEV_LED.h for a complete explanation of how it works. + new DEV_LED(16); new SpanAccessory(); @@ -97,11 +89,8 @@ void setup() { new Service::HAPProtocolInformation(); new Characteristic::Version("1.1.0"); - - // new Service::LightBulb(); // Same as above, this line is deleted... - // new Characteristic::On(); // This line is also deleted... - new DEV_LED(17); // ...and replaced with a single line that instantiates a second DEV_LED Service on Pin 17 + new DEV_LED(17); } // end of setup() diff --git a/examples/19-WebLog/DEV_LED.h b/examples/19-WebLog/DEV_LED.h index 23f555c..993d73e 100644 --- a/examples/19-WebLog/DEV_LED.h +++ b/examples/19-WebLog/DEV_LED.h @@ -3,102 +3,48 @@ // DEVICE-SPECIFIC LED SERVICES // //////////////////////////////////// -// HERE'S WHERE WE DEFINE OUR NEW LED SERVICE! +struct DEV_LED : Service::LightBulb { -struct DEV_LED : Service::LightBulb { // First we create a derived class from the HomeSpan LightBulb Service - - int ledPin; // this variable stores the pin number defined for this LED - SpanCharacteristic *power; // here we create a generic pointer to a SpanCharacteristic named "power" that we will use below - - // Next we define the constructor for DEV_LED. Note that it takes one argument, ledPin, - // which specifies the pin to which the LED is attached. + int ledPin; + SpanCharacteristic *power; DEV_LED(int ledPin) : Service::LightBulb(){ - power=new Characteristic::On(); // this is where we create the On Characterstic we had previously defined in setup(). Save this in the pointer created above, for use below - this->ledPin=ledPin; // don't forget to store ledPin... - pinMode(ledPin,OUTPUT); // ...and set the mode for ledPin to be an OUTPUT (standard Arduino function) - WEBLOG("Configuring LED on Pin %d",ledPin); + power=new Characteristic::On(); + this->ledPin=ledPin; + pinMode(ledPin,OUTPUT); + WEBLOG("Configuring LED on Pin %d",ledPin); // NEW! This creates a Web Log message announcing the configuration of the device } // end constructor - // Finally, we over-ride the default update() method with instructions that actually turn on/off the LED. Note update() returns type boolean - boolean update(){ - digitalWrite(ledPin,power->getNewVal()); // use a standard Arduino function to turn on/off ledPin based on the return of a call to power->getNewVal() (see below for more info) - WEBLOG("LED on Pin %d: %s",ledPin,power->getNewVal()?"ON":"OFF"); - LOG1("LOG1: LED on Pin %d: %s",ledPin,power->getNewVal()?"ON":"OFF"); - LOG2("LOG2: LED on Pin %d: %s",ledPin,power->getNewVal()?"ON":"OFF"); - - return(true); // return true to indicate the update was successful (otherwise create code to return false if some reason you could not turn on the LED) + digitalWrite(ledPin,power->getNewVal()); + WEBLOG("LED on Pin %d: %s",ledPin,power->getNewVal()?"ON":"OFF"); // NEW! This creates a Web Log message whenever an LED is turned ON or OFF + return(true); } // update }; ////////////////////////////////// -// HOW update() WORKS: -// ------------------ +// SOME MORE ABOUT WEB LOGGING +// --------------------------- // -// Whenever a HomeKit controller requests HomeSpan to update a Characteristic, HomeSpan calls the update() method for the SERVICE that contains the -// Characteristic. It calls this only one time, even if multiple Characteristics updates are requested for that Service. For example, if you -// direct HomeKit to turn on a light and set it to 50% brightness, it will send HomeSpan two requests: one to update the "On" Characteristic of the -// LightBulb Service from "false" to "true" and another to update the "Brightness" Characteristic of that same Service to 50. This is VERY inefficient -// and would require the user to process multiple updates to the same Service. +// * The WEBLOG() macro operates by calling Serial.printf(), so the first argument always needs to be a text string containing printf-like format instructions. +// The rest of the arguments, if any, are the variables to print. For example, you cannot simply write WEBLOG(ledPin). This will cause errors at compile time, +// though you can write LOG1(ledPin) or LOG2(ledPin) to output log messages just to the Serial Monitor. // -// Instead, HomeSpan combines both requests into a single call to update() for the Service itself, where you can process all of the Characteristics -// that change at the same time. In the example above, we only have a single Characteristic to deal with, so this does not mean much. But in later -// examples we'll see how this works with multiple Characteristics. - -// HOW TO ACCESS A CHARACTERISTIC'S NEW AND CURRENT VALUES -// ------------------------------------------------------- +// * You do NOT need to include a "\n" at the end of your format string since all Web Log messages are formatted into an HTML table when presented, and HTML ignores "\n". // -// HomeSpan stores the values for its Characteristics in a union structure that allows for different types, such as floats, booleans, etc. The specific -// types are defined by HAP for each Characteristic. Looking up whether a Characteristic is a uint8 or uint16 can be tiresome, so HomeSpan abstracts -// all these details. Since C++ adheres to strict variable typing, this is done through the use of template methods. Every Characteristic supports -// the following two methods: +// * Every Web Log message is recorded with TWO timestamps. The first timestamp is relative to when the device first booted, and is presented as DAYS:HH:MM:SS. This timestamp +// is always present. The second timestamp is an absolute clock time, in standard Unix form, such as "Mon Aug 10 13:52:48 2020". This timestamp will only be present +// if the clock time of the device was set, else it will be shown as "Unknown". Note that in the example above, the first Web Log message ("Configuring...") will +// have a clock timestamp of "Unknown" even though we enabled Web Logging with a Time Server. This is because the Time Server cannot be configured until WiFi has +// been established, and the first Web Log message above is created during initial configuratin of the device, BEFORE a WiFi connection is made. This is perfectly fine to do. // -// getVal() - returns the CURRENT value of the Characterisic, after casting into "type" -// getNewVal() - returns the NEW value (i.e. to be updated) of the Characteritic, after casting into "type" -// -// For example, MyChar->getVal() returns the current value of SpanCharacterstic MyChar as an int, REGARDLESS of how the value is stored by HomeSpan. -// Similarly, MyChar->getVal() returns a value as a double, even it is stored as as a boolean (in which case you'll either get 0.00 or 1.00). -// Of course you need to make sure you understand the range of expected values so that you don't try to access a value stored as 2-byte int using getVal(). -// But it's perfectly okay to use getVal() to access the value of a Characteristic that HAP insists on storing as a float, even though its range is -// strictly between 0 and 100 in steps of 1. Knowing the range and step size is all you need to know in determining you can access this as an or even a . -// -// Because most Characteristic values can properly be cast into int, getVal and getNewVal both default to if the template parameter is not specified. -// As you can see above, we retrieved the new value HomeKit requested for the On Characteristic that we named "power" by simply calling power->getNewVal(). -// Since no template parameter is specified, getNewVal() will return an int. And since the On Characteristic is natively stored as a boolean, getNewVal() -// will either return a 0 or a 1, depending on whether HomeKit is requesting the Characteristic to be turned off or on. -// -// You may also note that in the above example we needed to use getNewVal(), but did not use getVal() anywhere. This is because we know exactly what -// to do if HomeKit requests an LED to be turned on or off. The current status of the LED (on or off) does not matter. In latter examples we will see -// instances where the current state of the device DOES matter, and we will need to access both current and new values. -// -// Finally, there is one additional method for Characteristics that is not used above but will be in later examples: updated(). This method returns a -// boolean indicating whether HomeKit has requested a Characteristic to be updated, which means that getNewVal() will contain the new value it wants to set -// for that Characteristic. For a Service with only one Characteristic, as above, we don't need to ask if "power" was updated using power->updated() because -// the fact the the update() method for the Service is being called means that HomeKit is requesting an update, and the only thing to update is "power". -// But for Services with two or more Characteristics, update() can be called with a request to update only a subset of the Characteristics. We will -// find good use for the updated() method in later, multi-Characteristic examples. - -// UNDER THE HOOD: WHAT THE RETURN CODE FOR UPDATE() DOES -// ------------------------------------------------------ -// -// HomeKit requires each Characteristic to return a special HAP status code when an attempt to update its value is made. HomeSpan automatically takes care of -// most of the errors, such as a Characteristic not being found, or a request to update a Characteristic that is read only. In these cases update() is never -// even called. But if it is, HomeSpan needs to return a HAP status code for each of the Characteristics that were to be updated in that Service. -// By returning "true" you tell HomeSpan that the newValues requested are okay and you've made the required updates to the physical device. Upon -// receiving a true return value, HomeSpan updates the Characteristics themselves by copying the "newValue" data elements into the current "value" data elements. -// HomeSpan then sends a message back to HomeKit with a HAP code representing "OK," which lets the Controller know that the new values it requested have been -// sucessfully processed. At no point does HomeKit ask for, or allow, a data value to be sent back from HomeSpan indicating the data in a Characteristic. -// When requesting an update, HomeKit simply expects a HAP status code of OK, or some other status code representing an error. To tell HomeSpan to send the Controller -// an error code, indicating that you were not able to successfully process the update, simply have update() return a value of "false." HomeSpan converts a -// return of "false" to the HAP status code representing "UNABLE," which will cause the Controller to show that the device is not responding. - -// There are very few reasons you should need to return "false" since so much checking is done in advance by either HomeSpan or HomeKit -// itself. For instance, HomeKit does not allow you to use the Controller, or even Siri, to change the brightness of LightBulb to a value outside the -// range of allowable values you specified. This means that any update() requests you receive should only contain newValue data elements that are in-range. +// * Every Web Log message also includes the IP Address of the Client that made the request, unless the Web Log message was generated independently of any Client request, +// such as in the first message above. In these cases the IP Address will be displayed as 0.0.0.0. // +// * Web Log messages are printed to the Serial Monitor whenever the HomeSpan Log Level is set to 1 or greater. Hence there is no reason to duplicate the same message +// using WEBLOG() and LOG1() at the same time. diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 501b1af..27a4692 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -1984,4 +1984,7 @@ void SpanWebLog::addLog(const char *fmt, ...){ log[index].clientIP=homeSpan.lastClientIP; nEntries++; + + if(homeSpan.logLevel>0) + Serial.printf("WEBLOG: %s\n",log[index].message); } From 3336ac7fbe2267d468a5b847c939400173580d47 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 6 Mar 2022 18:39:05 -0600 Subject: [PATCH 025/100] Small clean-up of OTA Researching potential use of rollback of OTA. --- src/HomeSpan.cpp | 22 +++++++++++++--------- src/src.ino | 2 +- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 27a4692..eb63e71 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -44,6 +44,7 @@ using namespace Utils; HAPClient **hap; // HAP Client structure containing HTTP client connections, parsing routines, and state variables (global-scoped variable) Span homeSpan; // HAP Attributes database and all related control functions for this Accessory (global-scoped variable) HapCharacteristics hapChars; // Instantiation of all HAP Characteristics (used to create SpanCharacteristics) +int otaPercent; /////////////////////////////// // Span // @@ -134,7 +135,9 @@ void Span::begin(Category catID, const char *displayName, const char *hostNameBa Serial.print(__DATE__); Serial.print(" "); Serial.print(__TIME__); - + + Serial.printf("\nOTA Partition: %s",otaEnabled?esp_ota_get_running_partition()->label:"N/A"); + Serial.print("\n\nDevice Name: "); Serial.print(displayName); Serial.print("\n\n"); @@ -526,20 +529,21 @@ void Span::checkConnect(){ ArduinoOTA .onStart([]() { - String type; - if (ArduinoOTA.getCommand() == U_FLASH) - type = "sketch"; - else // U_SPIFFS - type = "filesystem"; - Serial.println("\n*** OTA Starting:" + type); + Serial.printf("\n*** Current Partition: %s\n*** New Partition: %s\n*** OTA Starting..", + esp_ota_get_running_partition()->label,esp_ota_get_next_update_partition(NULL)->label); + otaPercent=-10; homeSpan.statusLED.start(LED_OTA_STARTED); }) .onEnd([]() { - Serial.println("\n*** OTA Completed. Rebooting..."); + Serial.printf(" DONE! Rebooting...\n"); homeSpan.statusLED.off(); }) .onProgress([](unsigned int progress, unsigned int total) { - Serial.printf("*** Progress: %u%%\r", (progress / (total / 100))); + int percent=progress*100/total; + if(percent/10 != otaPercent/10){ + otaPercent=percent; + Serial.printf("%d%%..",progress*100/total); + } }) .onError([](ota_error_t error) { Serial.printf("*** OTA Error[%u]: ", error); diff --git a/src/src.ino b/src/src.ino index bb37e6d..2476f58 100644 --- a/src/src.ino +++ b/src/src.ino @@ -22,7 +22,7 @@ void setup() { // homeSpan.setMaxConnections(6); // homeSpan.setQRID("One1"); // homeSpan.enableOTA(); - homeSpan.setSketchVersion("Test 1.3.1"); + homeSpan.setSketchVersion("OTA Test 5"); homeSpan.setWifiCallback(wifiEstablished); new SpanUserCommand('d',"- My Description",userCom1); From 7ddbfd55ccde2bc381f585770b44c137f745b469 Mon Sep 17 00:00:00 2001 From: Gregg Date: Mon, 7 Mar 2022 22:02:23 -0600 Subject: [PATCH 026/100] Slight OTA cleanup plus confirmed that you cannot enable ROLLBACK in Arduino-ESP32 Since much of the Arduino-ESP32 library is precompiled, you cannot use -DCONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE to define this during compile time (it gets defined but is not used by the precompiled libraries). TO DO: create an NVS entry that flags whether last update was via OTA. If so, automatically enable OTA regardless of enableOTA setting. This would ensure that OTA cannot be disabled accidentally by uploading a non-enabledOTA sketch to remote device. --- src/HomeSpan.cpp | 16 ++++++++++------ src/src.ino | 2 +- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index eb63e71..aec9753 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -44,7 +44,7 @@ using namespace Utils; HAPClient **hap; // HAP Client structure containing HTTP client connections, parsing routines, and state variables (global-scoped variable) Span homeSpan; // HAP Attributes database and all related control functions for this Accessory (global-scoped variable) HapCharacteristics hapChars; // Instantiation of all HAP Characteristics (used to create SpanCharacteristics) -int otaPercent; +int otaPercent; // local variable to keep track of %progress when OTA is loading new sketch /////////////////////////////// // Span // @@ -135,13 +135,11 @@ void Span::begin(Category catID, const char *displayName, const char *hostNameBa Serial.print(__DATE__); Serial.print(" "); Serial.print(__TIME__); - - Serial.printf("\nOTA Partition: %s",otaEnabled?esp_ota_get_running_partition()->label:"N/A"); - + Serial.print("\n\nDevice Name: "); Serial.print(displayName); Serial.print("\n\n"); - + } // begin /////////////////////////////// @@ -559,15 +557,21 @@ void Span::checkConnect(){ Serial.print(displayName); Serial.print(" at "); Serial.print(WiFi.localIP()); + esp_ota_img_states_t otaState; + esp_ota_get_state_partition(esp_ota_get_running_partition(),&otaState); +// Serial.printf("\nPartition: %s State: %X",esp_ota_get_running_partition()->label,otaState); Serial.print("\nAuthorization Password: "); Serial.print(otaAuth?"Enabled\n\n":"DISABLED!\n\n"); } else { Serial.print("\n*** WARNING: Can't start OTA Server - Partition table used to compile this sketch is not configured for OTA.\n\n"); + otaEnabled=false; } } + + mdns_service_txt_item_set("_hap","_tcp","ota",otaEnabled?"yes":"no"); // OTA status (info only - NOT used by HAP) if(webLog.isEnabled){ - mdns_service_txt_item_set("_hap","_tcp","logURL",webLog.statusURL.c_str()+4); // Web Log Enabled (info only - NOT used by HAP) + mdns_service_txt_item_set("_hap","_tcp","logURL",webLog.statusURL.c_str()+4); // Web Log status (info only - NOT used by HAP) Serial.printf("Web Logging enabled at http://%s.local:%d%swith max number of entries=%d\n\n",hostName,tcpPortNum,webLog.statusURL.c_str()+4,webLog.maxEntries); webLog.initTime(); diff --git a/src/src.ino b/src/src.ino index 2476f58..8ac2f66 100644 --- a/src/src.ino +++ b/src/src.ino @@ -21,7 +21,7 @@ void setup() { homeSpan.setPortNum(1201); // homeSpan.setMaxConnections(6); // homeSpan.setQRID("One1"); -// homeSpan.enableOTA(); + homeSpan.enableOTA(); homeSpan.setSketchVersion("OTA Test 5"); homeSpan.setWifiCallback(wifiEstablished); From 2f1044b0136992af609e74aaaa7430eb185759a7 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 12 Mar 2022 08:24:01 -0600 Subject: [PATCH 027/100] Adding Custom Magic Cookie to OTA Partition Will prevent uploading a non-HomeSpan sketch via OTA to an existing HomeSpan device. --- src/HAP.cpp | 2 + src/HAP.h | 1 + src/HomeSpan.cpp | 153 ++++++++++++++++++++++++++++++++++++----------- src/HomeSpan.h | 24 +++++++- src/Settings.h | 5 ++ 5 files changed, 149 insertions(+), 36 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index dd8eb19..98c0015 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -41,6 +41,7 @@ void HAPClient::init(){ nvs_open("SRP",NVS_READWRITE,&srpNVS); // open SRP data namespace in NVS nvs_open("HAP",NVS_READWRITE,&hapNVS); // open HAP data namespace in NVS nvs_open("OTA",NVS_READWRITE,&otaNVS); // open OTA data namespace in NVS + nvs_open("STATE",NVS_READWRITE,&stateNVS); // open STATE data namespace in NVS if(!nvs_get_str(otaNVS,"OTADATA",NULL,&len)){ // if found OTA data in NVS nvs_get_str(otaNVS,"OTADATA",homeSpan.otaPwd,&len); // retrieve data @@ -1750,6 +1751,7 @@ TLV HAPClient::tlv8; nvs_handle HAPClient::hapNVS; nvs_handle HAPClient::srpNVS; nvs_handle HAPClient::otaNVS; +nvs_handle HAPClient::stateNVS; uint8_t HAPClient::httpBuf[MAX_HTTP+1]; HKDF HAPClient::hkdf; pairState HAPClient::pairStatus; diff --git a/src/HAP.h b/src/HAP.h index ebeee29..2f35b47 100644 --- a/src/HAP.h +++ b/src/HAP.h @@ -81,6 +81,7 @@ struct HAPClient { static nvs_handle hapNVS; // handle for non-volatile-storage of HAP data static nvs_handle srpNVS; // handle for non-volatile-storage of SRP data static nvs_handle otaNVS; // handle for non-volatile-storage of OTA data + static nvs_handle stateNVS; // handle for non-volatile-storage of HomeSpan STATE data static uint8_t httpBuf[MAX_HTTP+1]; // buffer to store HTTP messages (+1 to leave room for storing an extra 'overflow' character) static HKDF hkdf; // generates (and stores) HKDF-SHA-512 32-byte keys derived from an inputKey of arbitrary length, a salt string, and an info string static pairState pairStatus; // tracks pair-setup status diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index aec9753..5c8eb97 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -29,22 +29,22 @@ #include #include #include -#include -#include #include #include #include #include +#include #include "HomeSpan.h" #include "HAP.h" +const __attribute__((section(".rodata_custom_desc"))) SpanPartition spanPartition = {HOMESPAN_MAGIC_COOKIE}; + using namespace Utils; HAPClient **hap; // HAP Client structure containing HTTP client connections, parsing routines, and state variables (global-scoped variable) Span homeSpan; // HAP Attributes database and all related control functions for this Accessory (global-scoped variable) HapCharacteristics hapChars; // Instantiation of all HAP Characteristics (used to create SpanCharacteristics) -int otaPercent; // local variable to keep track of %progress when OTA is loading new sketch /////////////////////////////// // Span // @@ -135,11 +135,35 @@ void Span::begin(Category catID, const char *displayName, const char *hostNameBa Serial.print(__DATE__); Serial.print(" "); Serial.print(__TIME__); - + + esp_ota_img_states_t otaState; + esp_ota_get_state_partition(esp_ota_get_running_partition(),&otaState); + Serial.printf("\nPartition: %s (%X)",esp_ota_get_running_partition()->label,otaState); + Serial.printf("\nMagic Cookie: %s",spanPartition.magicCookie); + + esp_app_desc_t appDesc; + esp_ota_get_partition_description(esp_ota_get_running_partition(),&appDesc); + + char newDesc[256]; + esp_partition_read(esp_ota_get_running_partition(), sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t), newDesc, sizeof(newDesc)); + + Serial.println(); + Serial.println(appDesc.version); + Serial.println(appDesc.project_name); + Serial.println(appDesc.time); + Serial.println(appDesc.date); + Serial.println(appDesc.idf_ver); + if(strlen(spanPartition.magicCookie)) + {Serial.println("HI\n"); + } + Serial.println(newDesc); + + Serial.print("\n\nDevice Name: "); Serial.print(displayName); Serial.print("\n\n"); - + + } // begin /////////////////////////////// @@ -524,42 +548,48 @@ void Span::checkConnect(){ if(otaAuth) ArduinoOTA.setPasswordHash(otaPwd); + + ArduinoOTA.onStart(spanOTA.start).onEnd(spanOTA.end).onProgress(spanOTA.progress).onError(spanOTA.error); - ArduinoOTA - .onStart([]() { - Serial.printf("\n*** Current Partition: %s\n*** New Partition: %s\n*** OTA Starting..", - esp_ota_get_running_partition()->label,esp_ota_get_next_update_partition(NULL)->label); - otaPercent=-10; - homeSpan.statusLED.start(LED_OTA_STARTED); - }) - .onEnd([]() { - Serial.printf(" DONE! Rebooting...\n"); - homeSpan.statusLED.off(); - }) - .onProgress([](unsigned int progress, unsigned int total) { - int percent=progress*100/total; - if(percent/10 != otaPercent/10){ - otaPercent=percent; - Serial.printf("%d%%..",progress*100/total); - } - }) - .onError([](ota_error_t error) { - Serial.printf("*** OTA Error[%u]: ", error); - if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed\n"); - else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed\n"); - else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed\n"); - else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed\n"); - else if (error == OTA_END_ERROR) Serial.println("End Failed\n"); - }); +// ArduinoOTA +// .onStart([]() { +// Serial.printf("\n*** Current Partition: %s\n*** New Partition: %s\n*** OTA Starting..", +// esp_ota_get_running_partition()->label,esp_ota_get_next_update_partition(NULL)->label); +// otaPercent=-10; +// homeSpan.statusLED.start(LED_OTA_STARTED); +// }) +// .onEnd([]() { +// Serial.printf(" DONE! Rebooting...\n"); +// char newDesc[256]; +// esp_partition_read(esp_ota_get_next_update_partition(NULL), sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t), newDesc, sizeof(newDesc)); +// Serial.printf("Found: %s\n",newDesc); +// homeSpan.statusLED.off(); +// }) +// .onProgress([](unsigned int progress, unsigned int total) { +// int percent=progress*100/total; +// if(percent/10 != otaPercent/10){ +// otaPercent=percent; +// Serial.printf("%d%%..",progress*100/total); +// if(progress*100/total>20){ +// Serial.println("BAD!!\n"); +// Update.abort(); +// } +// } +// }) +// .onError([](ota_error_t error) { +// Serial.printf("*** OTA Error[%u]: ", error); +// if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed\n"); +// else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed\n"); +// else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed\n"); +// else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed\n"); +// else if (error == OTA_END_ERROR) Serial.println("End Failed\n"); +// }); ArduinoOTA.begin(); Serial.print("Starting OTA Server: "); Serial.print(displayName); Serial.print(" at "); Serial.print(WiFi.localIP()); - esp_ota_img_states_t otaState; - esp_ota_get_state_partition(esp_ota_get_running_partition(),&otaState); -// Serial.printf("\nPartition: %s State: %X",esp_ota_get_running_partition()->label,otaState); Serial.print("\nAuthorization Password: "); Serial.print(otaAuth?"Enabled\n\n":"DISABLED!\n\n"); } else { @@ -1996,3 +2026,58 @@ void SpanWebLog::addLog(const char *fmt, ...){ if(homeSpan.logLevel>0) Serial.printf("WEBLOG: %s\n",log[index].message); } + +/////////////////////////////// +// SpanOTA // +/////////////////////////////// + +void SpanOTA::start(){ + Serial.printf("\n*** Current Partition: %s\n*** New Partition: %s\n*** OTA Starting..", + esp_ota_get_running_partition()->label,esp_ota_get_next_update_partition(NULL)->label); + otaPercent=0; + homeSpan.statusLED.start(LED_OTA_STARTED); +} + +/////////////////////////////// + +void SpanOTA::end(){ + Serial.printf(" DONE! Rebooting...\n"); +// char newDesc[256]; + SpanPartition newSpanPartition; + esp_partition_read(esp_ota_get_next_update_partition(NULL), sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t), &newSpanPartition, sizeof(newSpanPartition)); + Serial.printf("Found: %s\n",newSpanPartition.magicCookie); + homeSpan.statusLED.off(); +} + +/////////////////////////////// + +void SpanOTA::progress(uint32_t progress, uint32_t total){ + int percent=progress*100/total; + if(percent/10 != otaPercent/10){ + otaPercent=percent; + Serial.printf("%d%%..",progress*100/total); + if(progress*100/total>20){ + Serial.println("BAD!!\n"); +// Update.abort(); + } + } +} + +/////////////////////////////// + +void SpanOTA::error(ota_error_t err){ + Serial.printf("*** OTA Error[%u]: ", err); + if (err == OTA_AUTH_ERROR) Serial.println("Auth Failed\n"); + else if (err == OTA_BEGIN_ERROR) Serial.println("Begin Failed\n"); + else if (err == OTA_CONNECT_ERROR) Serial.println("Connect Failed\n"); + else if (err == OTA_RECEIVE_ERROR) Serial.println("Receive Failed\n"); + else if (err == OTA_END_ERROR) Serial.println("End Failed\n"); +} + +/////////////////////////////// + +int SpanOTA::otaPercent; +boolean SpanOTA::verified; + + + diff --git a/src/HomeSpan.h b/src/HomeSpan.h index e7d9469..e8314b2 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -37,6 +37,7 @@ #include #include #include +#include #include "Settings.h" #include "Utils.h" @@ -71,13 +72,19 @@ struct SpanRange; struct SpanBuf; struct SpanButton; struct SpanUserCommand; -struct SpanWebLog; extern Span homeSpan; /////////////////////////////// -struct SpanConfig { +struct SpanPartition{ + char magicCookie[32]; + uint8_t reserved[224]; +}; + +/////////////////////////////// + +struct SpanConfig{ int configNumber=0; // configuration number - broadcast as Bonjour "c#" (computed automatically) uint8_t hashCode[48]={0}; // SHA-384 hash of Span Database stored as a form of unique "signature" to know when to update the config number upon changes }; @@ -120,6 +127,17 @@ struct SpanWebLog{ // optional web status/log data /////////////////////////////// +struct SpanOTA{ // manages OTA process + static int otaPercent; + static boolean verified; + static void start(); + static void end(); + static void progress(uint32_t progress, uint32_t total); + static void error(ota_error_t err); +}; + +/////////////////////////////// + struct Span{ const char *displayName; // display name for this device - broadcast as part of Bonjour MDNS @@ -158,6 +176,7 @@ struct Span{ boolean otaEnabled=false; // enables Over-the-Air ("OTA") updates char otaPwd[33]; // MD5 Hash of OTA password, represented as a string of hexidecimal characters boolean otaAuth; // OTA requires password when set to true + boolean otaDownload=false; // set to true in NVS if last download was via OTA void (*wifiCallback)()=NULL; // optional callback function to invoke once WiFi connectivity is established void (*pairCallback)(boolean isPaired)=NULL; // optional callback function to invoke when pairing is established (true) or lost (false) boolean autoStartAPEnabled=false; // enables auto start-up of Access Point when WiFi Credentials not found @@ -169,6 +188,7 @@ struct Span{ Network network; // configures WiFi and Setup Code via either serial monitor or temporary Access Point SpanWebLog webLog; // optional web status/log + SpanOTA spanOTA; // manages OTA process SpanConfig hapConfig; // track configuration changes to the HAP Accessory database; used to increment the configuration number (c#) when changes found vector Accessories; // vector of pointers to all Accessories vector Loops; // vector of pointer to all Services that have over-ridden loop() methods diff --git a/src/Settings.h b/src/Settings.h index ec43965..b424dcb 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -83,6 +83,11 @@ #define DEFAULT_WEBLOG_URL "status" // change with optional fourth argument in homeSpan.enableWebLog() +///////////////////////////////////////////////////// +// OTA PARTITION INFO // + +#define HOMESPAN_MAGIC_COOKIE "HomeSpanMagicCookie" + ///////////////////////////////////////////////////// // STATUS LED SETTINGS // From f3d5092340f3a1f0130186096a84f549642c417a Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 12 Mar 2022 14:31:15 -0600 Subject: [PATCH 028/100] SpanOTA in progress --- src/HomeSpan.cpp | 100 ++++++++++++----------------------------------- src/HomeSpan.h | 9 +++-- src/Settings.h | 2 +- 3 files changed, 32 insertions(+), 79 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 5c8eb97..9f0f519 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -139,31 +139,11 @@ void Span::begin(Category catID, const char *displayName, const char *hostNameBa esp_ota_img_states_t otaState; esp_ota_get_state_partition(esp_ota_get_running_partition(),&otaState); Serial.printf("\nPartition: %s (%X)",esp_ota_get_running_partition()->label,otaState); - Serial.printf("\nMagic Cookie: %s",spanPartition.magicCookie); - - esp_app_desc_t appDesc; - esp_ota_get_partition_description(esp_ota_get_running_partition(),&appDesc); - - char newDesc[256]; - esp_partition_read(esp_ota_get_running_partition(), sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t), newDesc, sizeof(newDesc)); - - Serial.println(); - Serial.println(appDesc.version); - Serial.println(appDesc.project_name); - Serial.println(appDesc.time); - Serial.println(appDesc.date); - Serial.println(appDesc.idf_ver); - if(strlen(spanPartition.magicCookie)) - {Serial.println("HI\n"); - } - Serial.println(newDesc); - Serial.print("\n\nDevice Name: "); Serial.print(displayName); Serial.print("\n\n"); - } // begin /////////////////////////////// @@ -305,7 +285,7 @@ void Span::poll() { HAPClient::checkNotifications(); HAPClient::checkTimedWrites(); - if(otaEnabled) + if(spanOTA.enabled) ArduinoOTA.handle(); if(controlButton.primed()){ @@ -525,11 +505,10 @@ void Span::checkConnect(){ else mdns_service_txt_item_set("_hap","_tcp","sf","0"); // set Status Flag = 0 - mdns_service_txt_item_set("_hap","_tcp","hspn",HOMESPAN_VERSION); // HomeSpan Version Number (info only - NOT used by HAP) - mdns_service_txt_item_set("_hap","_tcp","ard-esp32",ARDUINO_ESP_VERSION); // Arduino-ESP32 Version Number (info only - NOT used by HAP) - mdns_service_txt_item_set("_hap","_tcp","board",ARDUINO_VARIANT); // Board Name (info only - NOT used by HAP) - mdns_service_txt_item_set("_hap","_tcp","sketch",sketchVersion); // Sketch Version (info only - NOT used by HAP) - mdns_service_txt_item_set("_hap","_tcp","ota",otaEnabled?"yes":"no"); // OTA Enabled (info only - NOT used by HAP) + mdns_service_txt_item_set("_hap","_tcp","hspn",HOMESPAN_VERSION); // HomeSpan Version Number (info only - NOT used by HAP) + mdns_service_txt_item_set("_hap","_tcp","ard-esp32",ARDUINO_ESP_VERSION); // Arduino-ESP32 Version Number (info only - NOT used by HAP) + mdns_service_txt_item_set("_hap","_tcp","board",ARDUINO_VARIANT); // Board Name (info only - NOT used by HAP) + mdns_service_txt_item_set("_hap","_tcp","sketch",sketchVersion); // Sketch Version (info only - NOT used by HAP) uint8_t hashInput[22]; uint8_t hashOutput[64]; @@ -542,48 +521,14 @@ void Span::checkConnect(){ mbedtls_base64_encode((uint8_t *)setupHash,9,&len,hashOutput,4); // Step 3: Encode the first 4 bytes of hashOutput in base64, which results in an 8-character, null-terminated, setupHash mdns_service_txt_item_set("_hap","_tcp","sh",setupHash); // Step 4: broadcast the resulting Setup Hash - if(otaEnabled){ + if(spanOTA.enabled){ if(esp_ota_get_running_partition()!=esp_ota_get_next_update_partition(NULL)){ ArduinoOTA.setHostname(hostName); if(otaAuth) ArduinoOTA.setPasswordHash(otaPwd); - ArduinoOTA.onStart(spanOTA.start).onEnd(spanOTA.end).onProgress(spanOTA.progress).onError(spanOTA.error); - -// ArduinoOTA -// .onStart([]() { -// Serial.printf("\n*** Current Partition: %s\n*** New Partition: %s\n*** OTA Starting..", -// esp_ota_get_running_partition()->label,esp_ota_get_next_update_partition(NULL)->label); -// otaPercent=-10; -// homeSpan.statusLED.start(LED_OTA_STARTED); -// }) -// .onEnd([]() { -// Serial.printf(" DONE! Rebooting...\n"); -// char newDesc[256]; -// esp_partition_read(esp_ota_get_next_update_partition(NULL), sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t), newDesc, sizeof(newDesc)); -// Serial.printf("Found: %s\n",newDesc); -// homeSpan.statusLED.off(); -// }) -// .onProgress([](unsigned int progress, unsigned int total) { -// int percent=progress*100/total; -// if(percent/10 != otaPercent/10){ -// otaPercent=percent; -// Serial.printf("%d%%..",progress*100/total); -// if(progress*100/total>20){ -// Serial.println("BAD!!\n"); -// Update.abort(); -// } -// } -// }) -// .onError([](ota_error_t error) { -// Serial.printf("*** OTA Error[%u]: ", error); -// if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed\n"); -// else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed\n"); -// else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed\n"); -// else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed\n"); -// else if (error == OTA_END_ERROR) Serial.println("End Failed\n"); -// }); + ArduinoOTA.onStart(spanOTA.start).onEnd(spanOTA.end).onProgress(spanOTA.progress).onError(spanOTA.error); ArduinoOTA.begin(); Serial.print("Starting OTA Server: "); @@ -594,11 +539,11 @@ void Span::checkConnect(){ Serial.print(otaAuth?"Enabled\n\n":"DISABLED!\n\n"); } else { Serial.print("\n*** WARNING: Can't start OTA Server - Partition table used to compile this sketch is not configured for OTA.\n\n"); - otaEnabled=false; + spanOTA.enabled=false; } } - mdns_service_txt_item_set("_hap","_tcp","ota",otaEnabled?"yes":"no"); // OTA status (info only - NOT used by HAP) + mdns_service_txt_item_set("_hap","_tcp","ota",spanOTA.enabled?"yes":"no"); // OTA status (info only - NOT used by HAP) if(webLog.isEnabled){ mdns_service_txt_item_set("_hap","_tcp","logURL",webLog.statusURL.c_str()+4); // Web Log status (info only - NOT used by HAP) @@ -755,7 +700,7 @@ void Span::processSerialCommand(const char *c){ nvs_commit(HAPClient::otaNVS); Serial.print("... Accepted! Password change will take effect after next restart.\n"); - if(!otaEnabled) + if(!spanOTA.enabled) Serial.print("... Note: OTA has not been enabled in this sketch.\n"); Serial.print("\n"); } @@ -2031,6 +1976,13 @@ void SpanWebLog::addLog(const char *fmt, ...){ // SpanOTA // /////////////////////////////// +void SpanOTA::init(boolean auth, boolean safeLoad){ + enabled=true; + safeLoad=safeLoad; + this->auth=auth; + homeSpan.reserveSocketConnections(1); +} + void SpanOTA::start(){ Serial.printf("\n*** Current Partition: %s\n*** New Partition: %s\n*** OTA Starting..", esp_ota_get_running_partition()->label,esp_ota_get_next_update_partition(NULL)->label); @@ -2042,10 +1994,6 @@ void SpanOTA::start(){ void SpanOTA::end(){ Serial.printf(" DONE! Rebooting...\n"); -// char newDesc[256]; - SpanPartition newSpanPartition; - esp_partition_read(esp_ota_get_next_update_partition(NULL), sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t), &newSpanPartition, sizeof(newSpanPartition)); - Serial.printf("Found: %s\n",newSpanPartition.magicCookie); homeSpan.statusLED.off(); } @@ -2056,10 +2004,14 @@ void SpanOTA::progress(uint32_t progress, uint32_t total){ if(percent/10 != otaPercent/10){ otaPercent=percent; Serial.printf("%d%%..",progress*100/total); - if(progress*100/total>20){ - Serial.println("BAD!!\n"); -// Update.abort(); - } + } + + if(safeLoad && progress==total){ + SpanPartition newSpanPartition; + esp_partition_read(esp_ota_get_next_update_partition(NULL), sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t), &newSpanPartition, sizeof(newSpanPartition)); + Serial.printf("Checking for HomeSpan Magic Cookie: %s..",newSpanPartition.magicCookie); + if(strcmp(newSpanPartition.magicCookie,spanPartition.magicCookie)) + Update.abort(); } } @@ -2077,7 +2029,7 @@ void SpanOTA::error(ota_error_t err){ /////////////////////////////// int SpanOTA::otaPercent; -boolean SpanOTA::verified; +boolean SpanOTA::safeLoad; diff --git a/src/HomeSpan.h b/src/HomeSpan.h index e8314b2..dc50436 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -128,8 +128,11 @@ struct SpanWebLog{ // optional web status/log data /////////////////////////////// struct SpanOTA{ // manages OTA process + boolean enabled=false; + boolean auth; static int otaPercent; - static boolean verified; + static boolean safeLoad; + void init(boolean auth, boolean safeLoad); static void start(); static void end(); static void progress(uint32_t progress, uint32_t total); @@ -173,10 +176,8 @@ struct Span{ unsigned long comModeLife=DEFAULT_COMMAND_TIMEOUT*1000; // length of time (in milliseconds) to keep Command Mode alive before resuming normal operations uint16_t tcpPortNum=DEFAULT_TCP_PORT; // port for TCP communications between HomeKit and HomeSpan char qrID[5]=""; // Setup ID used for pairing with QR Code - boolean otaEnabled=false; // enables Over-the-Air ("OTA") updates char otaPwd[33]; // MD5 Hash of OTA password, represented as a string of hexidecimal characters boolean otaAuth; // OTA requires password when set to true - boolean otaDownload=false; // set to true in NVS if last download was via OTA void (*wifiCallback)()=NULL; // optional callback function to invoke once WiFi connectivity is established void (*pairCallback)(boolean isPaired)=NULL; // optional callback function to invoke when pairing is established (true) or lost (false) boolean autoStartAPEnabled=false; // enables auto start-up of Access Point when WiFi Credentials not found @@ -246,7 +247,7 @@ struct Span{ void setPairingCode(const char *s){sprintf(pairingCodeCommand,"S %9s",s);} // sets the Pairing Code - use is NOT recommended. Use 'S' from CLI instead void deleteStoredValues(){processSerialCommand("V");} // deletes stored Characteristic values from NVS - void enableOTA(boolean auth=true){otaEnabled=true;otaAuth=auth;reserveSocketConnections(1);} // enables Over-the-Air updates, with (auth=true) or without (auth=false) authorization password + void enableOTA(boolean auth=true, boolean safeLoad=true){spanOTA.init(auth, safeLoad);} // enables Over-the-Air updates, with (auth=true) or without (auth=false) authorization password void enableWebLog(uint16_t maxEntries=0, const char *serv=NULL, const char *tz="UTC", const char *url=DEFAULT_WEBLOG_URL){ // enable Web Logging webLog.init(maxEntries, serv, tz, url); diff --git a/src/Settings.h b/src/Settings.h index b424dcb..406f991 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -86,7 +86,7 @@ ///////////////////////////////////////////////////// // OTA PARTITION INFO // -#define HOMESPAN_MAGIC_COOKIE "HomeSpanMagicCookie" +#define HOMESPAN_MAGIC_COOKIE "HomeSpanMagicCookie##2022" ///////////////////////////////////////////////////// // STATUS LED SETTINGS // From eac06129fc40a3db332a3b4fe574fdf5a490e82f Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 12 Mar 2022 16:31:50 -0600 Subject: [PATCH 029/100] Moved all OTA logic into SpanOTA and completed "safeLoad" protocol --- src/HAP.cpp | 4 ++-- src/HomeSpan.cpp | 13 +++++++------ src/HomeSpan.h | 9 ++++----- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index 98c0015..86d50ec 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -44,13 +44,13 @@ void HAPClient::init(){ nvs_open("STATE",NVS_READWRITE,&stateNVS); // open STATE data namespace in NVS if(!nvs_get_str(otaNVS,"OTADATA",NULL,&len)){ // if found OTA data in NVS - nvs_get_str(otaNVS,"OTADATA",homeSpan.otaPwd,&len); // retrieve data + nvs_get_str(otaNVS,"OTADATA",homeSpan.spanOTA.otaPwd,&len); // retrieve data } else { MD5Builder otaPwdHash; otaPwdHash.begin(); otaPwdHash.add(DEFAULT_OTA_PASSWORD); otaPwdHash.calculate(); - otaPwdHash.getChars(homeSpan.otaPwd); + otaPwdHash.getChars(homeSpan.spanOTA.otaPwd); } if(strlen(homeSpan.pairingCodeCommand)){ // load verification setup code if provided diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 9f0f519..cff9a12 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -525,8 +525,8 @@ void Span::checkConnect(){ if(esp_ota_get_running_partition()!=esp_ota_get_next_update_partition(NULL)){ ArduinoOTA.setHostname(hostName); - if(otaAuth) - ArduinoOTA.setPasswordHash(otaPwd); + if(spanOTA.auth) + ArduinoOTA.setPasswordHash(spanOTA.otaPwd); ArduinoOTA.onStart(spanOTA.start).onEnd(spanOTA.end).onProgress(spanOTA.progress).onError(spanOTA.error); @@ -536,7 +536,7 @@ void Span::checkConnect(){ Serial.print(" at "); Serial.print(WiFi.localIP()); Serial.print("\nAuthorization Password: "); - Serial.print(otaAuth?"Enabled\n\n":"DISABLED!\n\n"); + Serial.print(spanOTA.auth?"Enabled\n\n":"DISABLED!\n\n"); } else { Serial.print("\n*** WARNING: Can't start OTA Server - Partition table used to compile this sketch is not configured for OTA.\n\n"); spanOTA.enabled=false; @@ -695,8 +695,8 @@ void Span::processSerialCommand(const char *c){ otaPwdHash.begin(); otaPwdHash.add(textPwd); otaPwdHash.calculate(); - otaPwdHash.getChars(otaPwd); - nvs_set_str(HAPClient::otaNVS,"OTADATA",otaPwd); // update data + otaPwdHash.getChars(spanOTA.otaPwd); + nvs_set_str(HAPClient::otaNVS,"OTADATA",spanOTA.otaPwd); // update data nvs_commit(HAPClient::otaNVS); Serial.print("... Accepted! Password change will take effect after next restart.\n"); @@ -1983,6 +1983,8 @@ void SpanOTA::init(boolean auth, boolean safeLoad){ homeSpan.reserveSocketConnections(1); } +/////////////////////////////// + void SpanOTA::start(){ Serial.printf("\n*** Current Partition: %s\n*** New Partition: %s\n*** OTA Starting..", esp_ota_get_running_partition()->label,esp_ota_get_next_update_partition(NULL)->label); @@ -2031,5 +2033,4 @@ void SpanOTA::error(ota_error_t err){ int SpanOTA::otaPercent; boolean SpanOTA::safeLoad; - diff --git a/src/HomeSpan.h b/src/HomeSpan.h index dc50436..8cc3179 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -128,10 +128,11 @@ struct SpanWebLog{ // optional web status/log data /////////////////////////////// struct SpanOTA{ // manages OTA process - boolean enabled=false; - boolean auth; + boolean enabled=false; // enables OTA - default if not enabled + boolean auth; // indicates whether OTA password is required + char otaPwd[33]; // MD5 Hash of OTA password, represented as a string of hexidecimal characters static int otaPercent; - static boolean safeLoad; + static boolean safeLoad; // indicates whether OTA update should reject any application update that is not another HomeSpan sketch void init(boolean auth, boolean safeLoad); static void start(); static void end(); @@ -176,8 +177,6 @@ struct Span{ unsigned long comModeLife=DEFAULT_COMMAND_TIMEOUT*1000; // length of time (in milliseconds) to keep Command Mode alive before resuming normal operations uint16_t tcpPortNum=DEFAULT_TCP_PORT; // port for TCP communications between HomeKit and HomeSpan char qrID[5]=""; // Setup ID used for pairing with QR Code - char otaPwd[33]; // MD5 Hash of OTA password, represented as a string of hexidecimal characters - boolean otaAuth; // OTA requires password when set to true void (*wifiCallback)()=NULL; // optional callback function to invoke once WiFi connectivity is established void (*pairCallback)(boolean isPaired)=NULL; // optional callback function to invoke when pairing is established (true) or lost (false) boolean autoStartAPEnabled=false; // enables auto start-up of Access Point when WiFi Credentials not found From b6eb5afcbf83fd9de41a37e719a734ccf8090b6a Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 13 Mar 2022 10:19:11 -0500 Subject: [PATCH 030/100] Started work on auto-enabling OTA to start even if not enabled. --- src/HAP.cpp | 8 ++----- src/HAP.h | 2 -- src/HomeSpan.cpp | 62 ++++++++++++++++++++++++++++++++++++++++++++---- src/HomeSpan.h | 8 +++++++ src/src.ino | 2 +- 5 files changed, 69 insertions(+), 13 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index 86d50ec..bad8448 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -40,11 +40,9 @@ void HAPClient::init(){ nvs_open("SRP",NVS_READWRITE,&srpNVS); // open SRP data namespace in NVS nvs_open("HAP",NVS_READWRITE,&hapNVS); // open HAP data namespace in NVS - nvs_open("OTA",NVS_READWRITE,&otaNVS); // open OTA data namespace in NVS - nvs_open("STATE",NVS_READWRITE,&stateNVS); // open STATE data namespace in NVS - if(!nvs_get_str(otaNVS,"OTADATA",NULL,&len)){ // if found OTA data in NVS - nvs_get_str(otaNVS,"OTADATA",homeSpan.spanOTA.otaPwd,&len); // retrieve data + if(!nvs_get_str(homeSpan.otaNVS,"OTADATA",NULL,&len)){ // if found OTA data in NVS + nvs_get_str(homeSpan.otaNVS,"OTADATA",homeSpan.spanOTA.otaPwd,&len); // retrieve data } else { MD5Builder otaPwdHash; otaPwdHash.begin(); @@ -1750,8 +1748,6 @@ void Nonce::inc(){ TLV HAPClient::tlv8; nvs_handle HAPClient::hapNVS; nvs_handle HAPClient::srpNVS; -nvs_handle HAPClient::otaNVS; -nvs_handle HAPClient::stateNVS; uint8_t HAPClient::httpBuf[MAX_HTTP+1]; HKDF HAPClient::hkdf; pairState HAPClient::pairStatus; diff --git a/src/HAP.h b/src/HAP.h index 2f35b47..707f438 100644 --- a/src/HAP.h +++ b/src/HAP.h @@ -80,8 +80,6 @@ struct HAPClient { static TLV tlv8; // TLV8 structure (HAP Section 14.1) with space for 10 TLV records of type kTLVType (HAP Table 5-6) static nvs_handle hapNVS; // handle for non-volatile-storage of HAP data static nvs_handle srpNVS; // handle for non-volatile-storage of SRP data - static nvs_handle otaNVS; // handle for non-volatile-storage of OTA data - static nvs_handle stateNVS; // handle for non-volatile-storage of HomeSpan STATE data static uint8_t httpBuf[MAX_HTTP+1]; // buffer to store HTTP messages (+1 to leave room for storing an extra 'overflow' character) static HKDF hkdf; // generates (and stores) HKDF-SHA-512 32-byte keys derived from an inputKey of arbitrary length, a salt string, and an info string static pairState pairStatus; // tracks pair-setup status diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index cff9a12..7c4340f 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -74,6 +74,7 @@ void Span::begin(Category catID, const char *displayName, const char *hostNameBa nvs_flash_init(); // initialize non-volatile-storage partition in flash nvs_open("CHAR",NVS_READWRITE,&charNVS); // open Characteristic data namespace in NVS nvs_open("WIFI",NVS_READWRITE,&wifiNVS); // open WIFI data namespace in NVS + nvs_open("OTA",NVS_READWRITE,&otaNVS); // open OTA data namespace in NVS size_t len; @@ -136,9 +137,18 @@ void Span::begin(Category catID, const char *displayName, const char *hostNameBa Serial.print(" "); Serial.print(__TIME__); + uint8_t prevSHA[32]={0}; + uint8_t sha256[32]; + if(!nvs_get_blob(otaNVS,"SHA256",NULL,&len)) // get previous app SHA256 (if it exists) + nvs_get_blob(otaNVS,"SHA256",prevSHA,&len); + esp_partition_get_sha256(esp_ota_get_running_partition(),sha256); // get current app SHA256 + newCode=(memcmp(prevSHA,sha256,32)!=0); // set newCode flag based on comparison of previous and current SHA256 values + nvs_set_blob(otaNVS,"SHA256",sha256,sizeof(sha256)); // save current SHA256 + nvs_commit(otaNVS); + esp_ota_img_states_t otaState; esp_ota_get_state_partition(esp_ota_get_running_partition(),&otaState); - Serial.printf("\nPartition: %s (%X)",esp_ota_get_running_partition()->label,otaState); + Serial.printf("\nPartition: %s (%s-0x%0X)",esp_ota_get_running_partition()->label,newCode?"NEW":"REBOOTED",otaState); Serial.print("\n\nDevice Name: "); Serial.print(displayName); @@ -521,6 +531,45 @@ void Span::checkConnect(){ mbedtls_base64_encode((uint8_t *)setupHash,9,&len,hashOutput,4); // Step 3: Encode the first 4 bytes of hashOutput in base64, which results in an 8-character, null-terminated, setupHash mdns_service_txt_item_set("_hap","_tcp","sh",setupHash); // Step 4: broadcast the resulting Setup Hash + int otaStatus=SpanOTA::OTA_OPTIONAL; + nvs_get_i32(otaNVS,"OTASTATUS",&otaStatus); + + Serial.printf("*** OTA STATUS: %d ***\n\r",otaStatus); + + if(otaStatus==SpanOTA::OTA_REQUIRED){ // most recent reboot was a result of new code being downloaded via OTA + spanOTA.enabled=true; // must enable OTA even if it is not set + Serial.printf("AUTO-ENABLING OTA-1\n\r"); + nvs_set_i32(otaNVS,"OTASTATUS",SpanOTA::OTA_MAINTAIN); // reset flag to OTA_MAINTAIN + + } // OTA_REQUIRED + + else if(otaStatus==SpanOTA::OTA_MAINTAIN){ // most recent reboot was NOT a direct result of new code being downloaded via OTA + if(!newCode){ // codebase has not changed - this is just a reboot of code previously downloaded via OTA + spanOTA.enabled=true; // must enable OTA even if it is not set + Serial.printf("AUTO-ENABLING OTA-2\n\r"); + } else { // codebase has changed, but was NOT a result of an OTA update (must be serial download) + Serial.printf("SKIPPING OTA\n\r"); + nvs_set_i32(otaNVS,"OTASTATUS",SpanOTA::OTA_OPTIONAL); // reset flag to OTA_OPTIONAL + } + } // OTA_MAINTAIN + + nvs_commit(otaNVS); + + Serial.printf("\n\rRESET REASON=%d\n\r",esp_reset_reason()); + +// for(int i=0;i<32;i++) +// Serial.printf("%02X",prevSHA[i]); +// Serial.printf("\n"); +// +// for(int i=0;i<32;i++) +// Serial.printf("%02X",sha256[i]); +// Serial.printf("\n"); +// +// if(memcmp(prevSHA,sha256,32)) +// Serial.printf("SHAs are DIFFERENT\n"); +// else +// Serial.printf("SHAs do MATCH\n"); + if(spanOTA.enabled){ if(esp_ota_get_running_partition()!=esp_ota_get_next_update_partition(NULL)){ ArduinoOTA.setHostname(hostName); @@ -696,8 +745,8 @@ void Span::processSerialCommand(const char *c){ otaPwdHash.add(textPwd); otaPwdHash.calculate(); otaPwdHash.getChars(spanOTA.otaPwd); - nvs_set_str(HAPClient::otaNVS,"OTADATA",spanOTA.otaPwd); // update data - nvs_commit(HAPClient::otaNVS); + nvs_set_str(otaNVS,"OTADATA",spanOTA.otaPwd); // update data + nvs_commit(otaNVS); Serial.print("... Accepted! Password change will take effect after next restart.\n"); if(!spanOTA.enabled) @@ -865,7 +914,9 @@ void Span::processSerialCommand(const char *c){ nvs_erase_all(wifiNVS); nvs_commit(wifiNVS); nvs_erase_all(charNVS); - nvs_commit(charNVS); + nvs_commit(charNVS); + nvs_erase_all(otaNVS); + nvs_commit(otaNVS); Serial.print("\n*** FACTORY RESET! Restarting...\n\n"); delay(1000); ESP.restart(); @@ -1995,8 +2046,11 @@ void SpanOTA::start(){ /////////////////////////////// void SpanOTA::end(){ + nvs_set_i32(homeSpan.otaNVS,"OTASTATUS",OTA_REQUIRED); + nvs_commit(homeSpan.otaNVS); Serial.printf(" DONE! Rebooting...\n"); homeSpan.statusLED.off(); + delay(100); // make sure commit it finished before reboot } /////////////////////////////// diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 8cc3179..7046bdb 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -128,6 +128,12 @@ struct SpanWebLog{ // optional web status/log data /////////////////////////////// struct SpanOTA{ // manages OTA process + enum { // keep track of whether OTA need to be required based on prior download + OTA_OPTIONAL, + OTA_MAINTAIN, + OTA_REQUIRED + }; + boolean enabled=false; // enables OTA - default if not enabled boolean auth; // indicates whether OTA password is required char otaPwd[33]; // MD5 Hash of OTA password, represented as a string of hexidecimal characters @@ -160,8 +166,10 @@ struct Span{ const char *sketchVersion="n/a"; // version of the sketch nvs_handle charNVS; // handle for non-volatile-storage of Characteristics data nvs_handle wifiNVS=0; // handle for non-volatile-storage of WiFi data + nvs_handle otaNVS; // handle for non-volatile storaget of OTA data char pairingCodeCommand[12]=""; // user-specified Pairing Code - only needed if Pairing Setup Code is specified in sketch using setPairingCode() String lastClientIP="0.0.0.0"; // IP address of last client accessing device through encrypted channel + boolean newCode; // flag indicating new application code has been loaded (based on keeping track of app SHA256) boolean connected=false; // WiFi connection status unsigned long waitTime=60000; // time to wait (in milliseconds) between WiFi connection attempts diff --git a/src/src.ino b/src/src.ino index 8ac2f66..58c5dae 100644 --- a/src/src.ino +++ b/src/src.ino @@ -21,7 +21,7 @@ void setup() { homeSpan.setPortNum(1201); // homeSpan.setMaxConnections(6); // homeSpan.setQRID("One1"); - homeSpan.enableOTA(); +// homeSpan.enableOTA(false); homeSpan.setSketchVersion("OTA Test 5"); homeSpan.setWifiCallback(wifiEstablished); From 1b0c4835cbc02a358ac7e9341e748e45d4e0c60a Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 13 Mar 2022 14:42:19 -0500 Subject: [PATCH 031/100] Progress on additional SafeMode logic to Auto-enable OTA --- src/HomeSpan.cpp | 69 ++++++++++++++++++++++-------------------------- src/HomeSpan.h | 15 ++++++----- src/src.ino | 2 +- 3 files changed, 41 insertions(+), 45 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 7c4340f..bdf6d8c 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -531,44 +531,35 @@ void Span::checkConnect(){ mbedtls_base64_encode((uint8_t *)setupHash,9,&len,hashOutput,4); // Step 3: Encode the first 4 bytes of hashOutput in base64, which results in an 8-character, null-terminated, setupHash mdns_service_txt_item_set("_hap","_tcp","sh",setupHash); // Step 4: broadcast the resulting Setup Hash - int otaStatus=SpanOTA::OTA_OPTIONAL; - nvs_get_i32(otaNVS,"OTASTATUS",&otaStatus); + boolean autoEnable=false; + uint32_t otaStatus=0; + nvs_get_u32(otaNVS,"OTASTATUS",&otaStatus); Serial.printf("*** OTA STATUS: %d ***\n\r",otaStatus); - if(otaStatus==SpanOTA::OTA_REQUIRED){ // most recent reboot was a result of new code being downloaded via OTA - spanOTA.enabled=true; // must enable OTA even if it is not set - Serial.printf("AUTO-ENABLING OTA-1\n\r"); - nvs_set_i32(otaNVS,"OTASTATUS",SpanOTA::OTA_MAINTAIN); // reset flag to OTA_MAINTAIN - - } // OTA_REQUIRED - - else if(otaStatus==SpanOTA::OTA_MAINTAIN){ // most recent reboot was NOT a direct result of new code being downloaded via OTA - if(!newCode){ // codebase has not changed - this is just a reboot of code previously downloaded via OTA - spanOTA.enabled=true; // must enable OTA even if it is not set - Serial.printf("AUTO-ENABLING OTA-2\n\r"); - } else { // codebase has changed, but was NOT a result of an OTA update (must be serial download) - Serial.printf("SKIPPING OTA\n\r"); - nvs_set_i32(otaNVS,"OTASTATUS",SpanOTA::OTA_OPTIONAL); // reset flag to OTA_OPTIONAL - } - } // OTA_MAINTAIN - + if(otaStatus&SpanOTA::OTA_DOWNLOADED ){ // if OTA was used for last download + otaStatus^=SpanOTA::OTA_DOWNLOADED; // turn off OTA_DOWNLOADED flag + if(!spanOTA.enabled && (otaStatus&SpanOTA::OTA_SAFEMODE)) // if OTA is not enabled, but it was last enabled in safe mode + autoEnable=true; // activate auto-enable + + } else if(otaStatus&SpanOTA::OTA_BOOTED ){ // if OTA was present in last boot, but not used for download + if(!newCode){ // if code has NOT changed + if(!spanOTA.enabled && (otaStatus&SpanOTA::OTA_SAFEMODE)) // if OTA is not enabled, but it was last enabled in safe mode + autoEnable=true; // activate auto-enable + } else { // code has changed - do not activate auto-enable + otaStatus^=SpanOTA::OTA_BOOTED; // turn off OTA_DOWNLOADED flag + } + } + + nvs_set_u32(otaNVS,"OTASTATUS",otaStatus); nvs_commit(otaNVS); - Serial.printf("\n\rRESET REASON=%d\n\r",esp_reset_reason()); - -// for(int i=0;i<32;i++) -// Serial.printf("%02X",prevSHA[i]); -// Serial.printf("\n"); -// -// for(int i=0;i<32;i++) -// Serial.printf("%02X",sha256[i]); -// Serial.printf("\n"); -// -// if(memcmp(prevSHA,sha256,32)) -// Serial.printf("SHAs are DIFFERENT\n"); -// else -// Serial.printf("SHAs do MATCH\n"); + if(autoEnable){ + spanOTA.enabled=true; + spanOTA.auth=otaStatus&SpanOTA::OTA_AUTHORIZED; + spanOTA.safeLoad=true; + Serial.printf("OTA Safe Mode: OTA Auto-Enabled\n"); + } if(spanOTA.enabled){ if(esp_ota_get_running_partition()!=esp_ota_get_next_update_partition(NULL)){ @@ -580,6 +571,7 @@ void Span::checkConnect(){ ArduinoOTA.onStart(spanOTA.start).onEnd(spanOTA.end).onProgress(spanOTA.progress).onError(spanOTA.error); ArduinoOTA.begin(); + reserveSocketConnections(1); Serial.print("Starting OTA Server: "); Serial.print(displayName); Serial.print(" at "); @@ -2027,11 +2019,10 @@ void SpanWebLog::addLog(const char *fmt, ...){ // SpanOTA // /////////////////////////////// -void SpanOTA::init(boolean auth, boolean safeLoad){ +void SpanOTA::init(boolean _auth, boolean _safeLoad){ enabled=true; - safeLoad=safeLoad; - this->auth=auth; - homeSpan.reserveSocketConnections(1); + safeLoad=_safeLoad; + auth=_auth; } /////////////////////////////// @@ -2046,7 +2037,7 @@ void SpanOTA::start(){ /////////////////////////////// void SpanOTA::end(){ - nvs_set_i32(homeSpan.otaNVS,"OTASTATUS",OTA_REQUIRED); + nvs_set_u32(homeSpan.otaNVS, "OTASTATUS", OTA_DOWNLOADED | OTA_BOOTED | (auth?OTA_AUTHORIZED:0) | (safeLoad?OTA_SAFEMODE:0)); nvs_commit(homeSpan.otaNVS); Serial.printf(" DONE! Rebooting...\n"); homeSpan.statusLED.off(); @@ -2086,5 +2077,7 @@ void SpanOTA::error(ota_error_t err){ int SpanOTA::otaPercent; boolean SpanOTA::safeLoad; +boolean SpanOTA::enabled=false; +boolean SpanOTA::auth; diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 7046bdb..e61bdf8 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -128,17 +128,20 @@ struct SpanWebLog{ // optional web status/log data /////////////////////////////// struct SpanOTA{ // manages OTA process - enum { // keep track of whether OTA need to be required based on prior download - OTA_OPTIONAL, - OTA_MAINTAIN, - OTA_REQUIRED + enum { // flag to keep track of OTA status between reboots + OTA_BOOTED=1, + OTA_DOWNLOADED=2, + OTA_AUTHORIZED=4, + OTA_SAFEMODE=8 }; - boolean enabled=false; // enables OTA - default if not enabled - boolean auth; // indicates whether OTA password is required char otaPwd[33]; // MD5 Hash of OTA password, represented as a string of hexidecimal characters + + static boolean enabled; // enables OTA - default if not enabled + static boolean auth; // indicates whether OTA password is required static int otaPercent; static boolean safeLoad; // indicates whether OTA update should reject any application update that is not another HomeSpan sketch + void init(boolean auth, boolean safeLoad); static void start(); static void end(); diff --git a/src/src.ino b/src/src.ino index 58c5dae..8ac2f66 100644 --- a/src/src.ino +++ b/src/src.ino @@ -21,7 +21,7 @@ void setup() { homeSpan.setPortNum(1201); // homeSpan.setMaxConnections(6); // homeSpan.setQRID("One1"); -// homeSpan.enableOTA(false); + homeSpan.enableOTA(); homeSpan.setSketchVersion("OTA Test 5"); homeSpan.setWifiCallback(wifiEstablished); From ffdf0296c6f84cd9ec0c09171759e81fd7b90ad4 Mon Sep 17 00:00:00 2001 From: Gregg Date: Mon, 14 Mar 2022 21:46:37 -0500 Subject: [PATCH 032/100] Dramatically simplified OTA enable check in safemode Rather than auto-enable OTA if not already enabled in safemode, changed the logic to simply rollback to previous app if OTA was used to download a sketch that does not itself have OTA enabled, unless OTA was previously enabled without safemode. To do: Delete all complicated SpanOTA logic that (unsuccessfully) tried to track OTA status and check SHA246 partition codes to determine if reboot was OTA or Serial. None of this is need, but some of the code may be useful for other things in the future. --- src/HomeSpan.cpp | 71 ++++++++++++++++++++++++++++-------------------- src/src.ino | 4 +-- 2 files changed, 43 insertions(+), 32 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index bdf6d8c..8e9a3e8 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -154,6 +154,16 @@ void Span::begin(Category catID, const char *displayName, const char *hostNameBa Serial.print(displayName); Serial.print("\n\n"); + uint8_t otaRequired=0; + nvs_get_u8(otaNVS,"OTA_REQUIRED",&otaRequired); + nvs_set_u8(otaNVS,"OTA_REQUIRED",0); + nvs_commit(otaNVS); + if(otaRequired && !spanOTA.enabled){ + Serial.printf("\n\n*** OTA SAFE MODE ALERT: OTA REQUIRED BUT NOT ENABLED. ROLLING BACK TO PREVIOUS APPLICATION ***\n\n"); + delay(100); + esp_ota_mark_app_invalid_rollback_and_reboot(); + } + } // begin /////////////////////////////// @@ -531,35 +541,35 @@ void Span::checkConnect(){ mbedtls_base64_encode((uint8_t *)setupHash,9,&len,hashOutput,4); // Step 3: Encode the first 4 bytes of hashOutput in base64, which results in an 8-character, null-terminated, setupHash mdns_service_txt_item_set("_hap","_tcp","sh",setupHash); // Step 4: broadcast the resulting Setup Hash - boolean autoEnable=false; - uint32_t otaStatus=0; - nvs_get_u32(otaNVS,"OTASTATUS",&otaStatus); - - Serial.printf("*** OTA STATUS: %d ***\n\r",otaStatus); - - if(otaStatus&SpanOTA::OTA_DOWNLOADED ){ // if OTA was used for last download - otaStatus^=SpanOTA::OTA_DOWNLOADED; // turn off OTA_DOWNLOADED flag - if(!spanOTA.enabled && (otaStatus&SpanOTA::OTA_SAFEMODE)) // if OTA is not enabled, but it was last enabled in safe mode - autoEnable=true; // activate auto-enable - - } else if(otaStatus&SpanOTA::OTA_BOOTED ){ // if OTA was present in last boot, but not used for download - if(!newCode){ // if code has NOT changed - if(!spanOTA.enabled && (otaStatus&SpanOTA::OTA_SAFEMODE)) // if OTA is not enabled, but it was last enabled in safe mode - autoEnable=true; // activate auto-enable - } else { // code has changed - do not activate auto-enable - otaStatus^=SpanOTA::OTA_BOOTED; // turn off OTA_DOWNLOADED flag - } - } - - nvs_set_u32(otaNVS,"OTASTATUS",otaStatus); - nvs_commit(otaNVS); - - if(autoEnable){ - spanOTA.enabled=true; - spanOTA.auth=otaStatus&SpanOTA::OTA_AUTHORIZED; - spanOTA.safeLoad=true; - Serial.printf("OTA Safe Mode: OTA Auto-Enabled\n"); - } +// boolean autoEnable=false; +// uint32_t otaStatus=0; +// nvs_get_u32(otaNVS,"OTASTATUS",&otaStatus); +// +// Serial.printf("*** OTA STATUS: %d ***\n\r",otaStatus); +// +// if(otaStatus&SpanOTA::OTA_DOWNLOADED ){ // if OTA was used for last download +// otaStatus^=SpanOTA::OTA_DOWNLOADED; // turn off OTA_DOWNLOADED flag +// if(!spanOTA.enabled && (otaStatus&SpanOTA::OTA_SAFEMODE)) // if OTA is not enabled, but it was last enabled in safe mode +// autoEnable=true; // activate auto-enable +// +// } else if(otaStatus&SpanOTA::OTA_BOOTED ){ // if OTA was present in last boot, but not used for download +// if(!newCode){ // if code has NOT changed +// if(!spanOTA.enabled && (otaStatus&SpanOTA::OTA_SAFEMODE)) // if OTA is not enabled, but it was last enabled in safe mode +// autoEnable=true; // activate auto-enable +// } else { // code has changed - do not activate auto-enable +// otaStatus^=SpanOTA::OTA_BOOTED; // turn off OTA_DOWNLOADED flag +// } +// } +// +// nvs_set_u32(otaNVS,"OTASTATUS",otaStatus); +// nvs_commit(otaNVS); +// +// if(autoEnable){ +// spanOTA.enabled=true; +// spanOTA.auth=otaStatus&SpanOTA::OTA_AUTHORIZED; +// spanOTA.safeLoad=true; +// Serial.printf("OTA Safe Mode: OTA Auto-Enabled\n"); +// } if(spanOTA.enabled){ if(esp_ota_get_running_partition()!=esp_ota_get_next_update_partition(NULL)){ @@ -2037,7 +2047,8 @@ void SpanOTA::start(){ /////////////////////////////// void SpanOTA::end(){ - nvs_set_u32(homeSpan.otaNVS, "OTASTATUS", OTA_DOWNLOADED | OTA_BOOTED | (auth?OTA_AUTHORIZED:0) | (safeLoad?OTA_SAFEMODE:0)); +// nvs_set_u32(homeSpan.otaNVS, "OTASTATUS", OTA_DOWNLOADED | OTA_BOOTED | (auth?OTA_AUTHORIZED:0) | (safeLoad?OTA_SAFEMODE:0)); + nvs_set_u8(homeSpan.otaNVS,"OTA_REQUIRED",safeLoad); nvs_commit(homeSpan.otaNVS); Serial.printf(" DONE! Rebooting...\n"); homeSpan.statusLED.off(); diff --git a/src/src.ino b/src/src.ino index 8ac2f66..b345897 100644 --- a/src/src.ino +++ b/src/src.ino @@ -21,8 +21,8 @@ void setup() { homeSpan.setPortNum(1201); // homeSpan.setMaxConnections(6); // homeSpan.setQRID("One1"); - homeSpan.enableOTA(); - homeSpan.setSketchVersion("OTA Test 5"); +// homeSpan.enableOTA(false,false); + homeSpan.setSketchVersion("OTA Test 8"); homeSpan.setWifiCallback(wifiEstablished); new SpanUserCommand('d',"- My Description",userCom1); From 64091571801ec9f1349e8d4ce6ff25d04c13e609 Mon Sep 17 00:00:00 2001 From: Gregg Date: Mon, 14 Mar 2022 22:01:28 -0500 Subject: [PATCH 033/100] Completed all SpanOTA safemode logic and cleaned up previous iterations of the logic Must document in safemode in API as well as discuss in OTA documentation. --- src/HomeSpan.cpp | 44 +------------------------------------------- src/HomeSpan.h | 6 ------ 2 files changed, 1 insertion(+), 49 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 8e9a3e8..d6e5e38 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -137,18 +137,7 @@ void Span::begin(Category catID, const char *displayName, const char *hostNameBa Serial.print(" "); Serial.print(__TIME__); - uint8_t prevSHA[32]={0}; - uint8_t sha256[32]; - if(!nvs_get_blob(otaNVS,"SHA256",NULL,&len)) // get previous app SHA256 (if it exists) - nvs_get_blob(otaNVS,"SHA256",prevSHA,&len); - esp_partition_get_sha256(esp_ota_get_running_partition(),sha256); // get current app SHA256 - newCode=(memcmp(prevSHA,sha256,32)!=0); // set newCode flag based on comparison of previous and current SHA256 values - nvs_set_blob(otaNVS,"SHA256",sha256,sizeof(sha256)); // save current SHA256 - nvs_commit(otaNVS); - - esp_ota_img_states_t otaState; - esp_ota_get_state_partition(esp_ota_get_running_partition(),&otaState); - Serial.printf("\nPartition: %s (%s-0x%0X)",esp_ota_get_running_partition()->label,newCode?"NEW":"REBOOTED",otaState); + Serial.printf("\nPartition: %s",esp_ota_get_running_partition()->label); Serial.print("\n\nDevice Name: "); Serial.print(displayName); @@ -541,36 +530,6 @@ void Span::checkConnect(){ mbedtls_base64_encode((uint8_t *)setupHash,9,&len,hashOutput,4); // Step 3: Encode the first 4 bytes of hashOutput in base64, which results in an 8-character, null-terminated, setupHash mdns_service_txt_item_set("_hap","_tcp","sh",setupHash); // Step 4: broadcast the resulting Setup Hash -// boolean autoEnable=false; -// uint32_t otaStatus=0; -// nvs_get_u32(otaNVS,"OTASTATUS",&otaStatus); -// -// Serial.printf("*** OTA STATUS: %d ***\n\r",otaStatus); -// -// if(otaStatus&SpanOTA::OTA_DOWNLOADED ){ // if OTA was used for last download -// otaStatus^=SpanOTA::OTA_DOWNLOADED; // turn off OTA_DOWNLOADED flag -// if(!spanOTA.enabled && (otaStatus&SpanOTA::OTA_SAFEMODE)) // if OTA is not enabled, but it was last enabled in safe mode -// autoEnable=true; // activate auto-enable -// -// } else if(otaStatus&SpanOTA::OTA_BOOTED ){ // if OTA was present in last boot, but not used for download -// if(!newCode){ // if code has NOT changed -// if(!spanOTA.enabled && (otaStatus&SpanOTA::OTA_SAFEMODE)) // if OTA is not enabled, but it was last enabled in safe mode -// autoEnable=true; // activate auto-enable -// } else { // code has changed - do not activate auto-enable -// otaStatus^=SpanOTA::OTA_BOOTED; // turn off OTA_DOWNLOADED flag -// } -// } -// -// nvs_set_u32(otaNVS,"OTASTATUS",otaStatus); -// nvs_commit(otaNVS); -// -// if(autoEnable){ -// spanOTA.enabled=true; -// spanOTA.auth=otaStatus&SpanOTA::OTA_AUTHORIZED; -// spanOTA.safeLoad=true; -// Serial.printf("OTA Safe Mode: OTA Auto-Enabled\n"); -// } - if(spanOTA.enabled){ if(esp_ota_get_running_partition()!=esp_ota_get_next_update_partition(NULL)){ ArduinoOTA.setHostname(hostName); @@ -2047,7 +2006,6 @@ void SpanOTA::start(){ /////////////////////////////// void SpanOTA::end(){ -// nvs_set_u32(homeSpan.otaNVS, "OTASTATUS", OTA_DOWNLOADED | OTA_BOOTED | (auth?OTA_AUTHORIZED:0) | (safeLoad?OTA_SAFEMODE:0)); nvs_set_u8(homeSpan.otaNVS,"OTA_REQUIRED",safeLoad); nvs_commit(homeSpan.otaNVS); Serial.printf(" DONE! Rebooting...\n"); diff --git a/src/HomeSpan.h b/src/HomeSpan.h index e61bdf8..c970652 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -128,12 +128,6 @@ struct SpanWebLog{ // optional web status/log data /////////////////////////////// struct SpanOTA{ // manages OTA process - enum { // flag to keep track of OTA status between reboots - OTA_BOOTED=1, - OTA_DOWNLOADED=2, - OTA_AUTHORIZED=4, - OTA_SAFEMODE=8 - }; char otaPwd[33]; // MD5 Hash of OTA password, represented as a string of hexidecimal characters From 057901b5bb272e1581a8c28ebf59c8318d31ddff Mon Sep 17 00:00:00 2001 From: Gregg Date: Mon, 14 Mar 2022 22:14:59 -0500 Subject: [PATCH 034/100] Update src.ino --- src/src.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/src.ino b/src/src.ino index b345897..aea281e 100644 --- a/src/src.ino +++ b/src/src.ino @@ -21,7 +21,7 @@ void setup() { homeSpan.setPortNum(1201); // homeSpan.setMaxConnections(6); // homeSpan.setQRID("One1"); -// homeSpan.enableOTA(false,false); + homeSpan.enableOTA(); homeSpan.setSketchVersion("OTA Test 8"); homeSpan.setWifiCallback(wifiEstablished); From f813894d0076867ae54a1b5548acb4a7a4b7b0be Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Tue, 15 Mar 2022 21:02:03 -0500 Subject: [PATCH 035/100] Update Reference.md --- docs/Reference.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Reference.md b/docs/Reference.md index 0d8746d..47dca27 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -371,7 +371,7 @@ Note that Custom Characteristics must be created prior to calling `homeSpan.begi Creates a custom Service for use with third-party applications (such as *Eve for HomeKit*). Custom Services will be displayed in the native Apple Home App with a Tile labeled "Not Supported", but otherwise the Service will be safely ignored by the Home App. Parameters are as follows (note that quotes should NOT be used in either of the macro parameters): -* *name* - the name of the custom Service. This will be added to the Service namespace so that it is accessed the same as any HomeSpan Service +* *name* - the name of the custom Service. This will be added to the Service namespace so that it is accessed the same as any HomeSpan Service. For example, if *name*="Vent", HomeSpan would recognize `Service::Vent` as a new service class * *uuid* - the UUID of the Service as defined by the manufacturer. Must be *exactly* 36 characters in the form XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX, where *X* represent a valid hexidecimal digit. Leading zeros are required if needed as described more fully in HAP-R2 Section 6.6.1 Custom Services may contain a mix of both Custom Characteristics and standard HAP Characteristics, though since the Service itself is custom, the Home App will ignore the entire Service even if it contains some standard HAP Characterstics. Note that Custom Services must be created prior to calling `homeSpan.begin()` From 29b02c4f38448caf19eeb6a33f7e6246d3bffb1f Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Tue, 15 Mar 2022 22:47:07 -0500 Subject: [PATCH 036/100] Update Reference.md --- docs/Reference.md | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/docs/Reference.md b/docs/Reference.md index 47dca27..6daf7b4 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -313,15 +313,32 @@ HomeSpan automatically calls the `button(int pin, int pressType)` method of a Se HomeSpan will report a warning, but not an error, during initialization if the user had not overridden the virtual button() method for a Service contaning one or more Buttons; triggers of those Buttons will simply ignored. -## *SpanUserCommand(char c, const char \*s, void (\*f)(const char \*v))* +### *SpanUserCommand(char c, const char \*desc, void (\*f)(const char \*buf [,void \*obj]) [,void \*userObject])* Creating an instance of this **class** adds a user-defined command to the HomeSpan Command-Line Interface (CLI), where: * *c* is the single-letter name of the user-defined command - * *s* is a description of the user-defined command that is displayed when the user types '?' into the CLI - * *f* is a pointer to a user-defined function that is called when the command is invoked. This function must be of the form `void f(const char *v)`, where *v* points to all characters typed into the CLI, beginning with the single-letter command name *c*. + * *desc* is a description of the user-defined command that is displayed when the user types '?' into the CLI + * *f* is a pointer to a user-defined function that is called when the command is invoked. Allowable forms for *f* are: + 1. `void f(const char *buf)`, or + 1. `void f(const char *buf, void *obj)` + * *userObject* is a pointer to an arbitrary object HomeSpan passes to the function *f* as the second argument when the second form of *f* is used. Note it is an error to include *userObject* when the first form of *f* is used, and it is similarly an error to exclude *userObject* when the second form of *f* is used -To invoke this command from the CLI, preface the single-letter name *c* with '@'. This allows HomeSpan to distinguish user-defined commands from its built-in commands. For example, `new SpanUserCommand('s', "save current configuration",saveConfig)` would add a new command '@s' to the CLI with description "save current configuration" that will call the user-defined function `void saveConfig(const char *v)` when invoked. The argument *v* points to an array of all characters typed into the CLI after the '@'. This allows the user to pass arguments from the CLI to the user-defined function. For example, typing '@s123' into the CLI sets *v* to "s123" when saveConfig is called. +To invoke your custom command from the CLI, preface the single-letter name *c* with '@'. This allows HomeSpan to distinguish user-defined commands from its built-in commands. For example, + +```C++ +new SpanUserCommand('s', "save current configuration", saveConfig) +``` + +would add a new command '@s' to the CLI with description "save current configuration" that will call the user-defined function `void saveConfig(const char *buf)` when invoked. The argument *buf* points to an array of all characters typed into the CLI after the '@'. This allows the user to pass arguments from the CLI to the user-defined function. For example, typing '@s123' into the CLI sets *buf* to "s123" when saveConfig is called. + +In the second form of the argument, HomeSpan will pass an additional object to your function *f*. For example, + +```C++ +new SpanUserCommand('s', "save current configuration", saveConfig, &myArray) +``` + +might be used to save all the elements in *myArray* when called with just the '@s' command, and perhaps save only one element based on an index added to the command, such as '@s34' to save element 34 in *myArray*. It is up to the user to create all necessary logic within the function *f* to parse and process the full command text passed in *buf*. To create more than one user-defined command, simply create multiple instances of SpanUserCommand, each with its own single-letter name. Note that re-using the same single-letter name in an instance of SpanUserCommand over-rides any previous instances using that same letter. From 147530185594a9fc63fa639ccf377ce0e5b8e154 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Wed, 16 Mar 2022 06:30:41 -0500 Subject: [PATCH 037/100] Update Reference.md --- docs/Reference.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/docs/Reference.md b/docs/Reference.md index 6daf7b4..299a37d 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -327,7 +327,9 @@ Creating an instance of this **class** adds a user-defined command to the HomeSp To invoke your custom command from the CLI, preface the single-letter name *c* with '@'. This allows HomeSpan to distinguish user-defined commands from its built-in commands. For example, ```C++ -new SpanUserCommand('s', "save current configuration", saveConfig) +new SpanUserCommand('s', "save current configuration", saveConfig); +... +void saveConfig(const char *buf){ ... }; ``` would add a new command '@s' to the CLI with description "save current configuration" that will call the user-defined function `void saveConfig(const char *buf)` when invoked. The argument *buf* points to an array of all characters typed into the CLI after the '@'. This allows the user to pass arguments from the CLI to the user-defined function. For example, typing '@s123' into the CLI sets *buf* to "s123" when saveConfig is called. @@ -335,10 +337,13 @@ would add a new command '@s' to the CLI with description "save current configura In the second form of the argument, HomeSpan will pass an additional object to your function *f*. For example, ```C++ -new SpanUserCommand('s', "save current configuration", saveConfig, &myArray) +struct myConfigurations[10]; +new SpanUserCommand('s', " save current configuration for specified index, n", saveConfig, myConfigurations); +... +void saveConfig(const char *buf, void *obj){ ... do something with myConfigurations ... }; ``` -might be used to save all the elements in *myArray* when called with just the '@s' command, and perhaps save only one element based on an index added to the command, such as '@s34' to save element 34 in *myArray*. It is up to the user to create all necessary logic within the function *f* to parse and process the full command text passed in *buf*. +might be used to save all the elements in *myArray* when called with just the '@s' command, and perhaps save only one element based on an index added to the command, such as '@s34' to save element 34 in *myArray*. It is up to the user to create all necessary logic within the function *f* to parse and process the full command text passed in *buf*, as well as act on whatever is being passed via *obj. To create more than one user-defined command, simply create multiple instances of SpanUserCommand, each with its own single-letter name. Note that re-using the same single-letter name in an instance of SpanUserCommand over-rides any previous instances using that same letter. From 4254ff457f76a5fe4d0e92f41e0265e0b0cc03d6 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Thu, 17 Mar 2022 06:31:45 -0500 Subject: [PATCH 038/100] Update OTA.md --- docs/OTA.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/OTA.md b/docs/OTA.md index a079482..9d72b13 100644 --- a/docs/OTA.md +++ b/docs/OTA.md @@ -18,11 +18,11 @@ This is because HomeSpan checks that a sketch has been compiled with OTA partiti * The name of the device HomeSpan uses for OTA is the same as the name you assigned in your call to `homeSpan.begin()`. If you have multiple devices you intend to maintain with OTA, use `homeSpan.begin()` to give them each different names so you can tell them apart when selecting which one to connect to from the Arduino IDE. -* Use the `homeSpan.setSketchVersion()` method to set a version for your sketch (see the [HomeSpan API](Reference.md) for details). If specified, HomeSpan will include the sketch version as part of its HAP MDNS broadcast. This allows you determine which version of a sketch is running on a remote HomeSpan device, even if you can't plug it into a serial port for use with the Arduino Serial Monitor. In addition to the sketch version, HomeSpan also includes two other fields in its MDNS broadcast: the version number of the HomeSpan *library* used to compile the sketch, and a field indicating whether or not OTA is enabled for the sketch. +* Use the `homeSpan.setSketchVersion()` method to set a version for your sketch (see the [HomeSpan API](Reference.md) for details). If specified, HomeSpan will include the sketch version as part of its HAP MDNS broadcast. This allows you determine which version of a sketch is running on a remote HomeSpan device, even if you can't plug it into a serial port for use with the Arduino Serial Monitor. In addition to the sketch version, HomeSpan also includes other fields in its MDNS broadcast that are useful in identifying the device: the version number of the HomeSpan *library* used to compile the sketch, a field indicating whether or not OTA is enabled for the sketch, the version number of the Arduino-ESP32 library used when compiling, and the type of board (e.g. *feather_esp32*). * If a sketch you've uploaded with OTA does not operate as expected, you can continue making modifications to the code and re-upload again. Or, you can upload a prior version that was working properly. However, this assumes that the sketch you uploaded does not have major problems, such as causing a kernel panic that leads to an endless cycle of device reboots. If this happens, HomeSpan won't be able to run the OTA Server code, and further OTA updates will *not* ne possible. Instead, you'll have to connect the device through a serial port to upload a new, working sketch. **For this reason you should always fully test out a new sketch on a local device connected to your computer *before* uploading it to a remote, hard-to-access device via OTA.** -* The ESP32 itself supports "automated" rollbacks that are designed to restore a device with a previously-working sketch if the latest sketch causes a reboot before being able to set a self-test flag verifiying the code is operating correctly. However, the version of the ESP32-IDF library (3.2.3) used by the latest version of the Arduino-ESP32 Library (1.0.4, at the time of this posting) does not support this feature. +* The ESP32 itself supports "automated" rollbacks that are designed to restore a device with a previously-working sketch if the latest sketch causes a reboot before being able to set a self-test flag verifiying the code is operating correctly. However, this feature is not enabled in the latest version of the Arudino-ESP32 library (2.0.2 at the time of this posting), and cannot be accessed without recompiling a custom version of the board manager. --- From 9b55588039a89c301c382feeff3c625f2cec2a68 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Fri, 18 Mar 2022 06:31:12 -0500 Subject: [PATCH 039/100] Update OTA.md --- docs/OTA.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/OTA.md b/docs/OTA.md index 9d72b13..058c05f 100644 --- a/docs/OTA.md +++ b/docs/OTA.md @@ -14,15 +14,17 @@ Note that in in order for OTA to properly operate, your sketch must be compiled This is because HomeSpan checks that a sketch has been compiled with OTA partitions if OTA has been enabled for that sketch. If OTA has been enabled but HomeSpan does not find any OTA partitioms, it will indicate it cannot start the OTA Server via a warning message sent to the Serial Monitor immediately after WiFi connectivity has been established. Otherwise it will output a confirmation message indicating the OTA Server has sucessfully started. +### HomeSpan Safe Load Mode + ### OTA Tips and Tricks * The name of the device HomeSpan uses for OTA is the same as the name you assigned in your call to `homeSpan.begin()`. If you have multiple devices you intend to maintain with OTA, use `homeSpan.begin()` to give them each different names so you can tell them apart when selecting which one to connect to from the Arduino IDE. * Use the `homeSpan.setSketchVersion()` method to set a version for your sketch (see the [HomeSpan API](Reference.md) for details). If specified, HomeSpan will include the sketch version as part of its HAP MDNS broadcast. This allows you determine which version of a sketch is running on a remote HomeSpan device, even if you can't plug it into a serial port for use with the Arduino Serial Monitor. In addition to the sketch version, HomeSpan also includes other fields in its MDNS broadcast that are useful in identifying the device: the version number of the HomeSpan *library* used to compile the sketch, a field indicating whether or not OTA is enabled for the sketch, the version number of the Arduino-ESP32 library used when compiling, and the type of board (e.g. *feather_esp32*). -* If a sketch you've uploaded with OTA does not operate as expected, you can continue making modifications to the code and re-upload again. Or, you can upload a prior version that was working properly. However, this assumes that the sketch you uploaded does not have major problems, such as causing a kernel panic that leads to an endless cycle of device reboots. If this happens, HomeSpan won't be able to run the OTA Server code, and further OTA updates will *not* ne possible. Instead, you'll have to connect the device through a serial port to upload a new, working sketch. **For this reason you should always fully test out a new sketch on a local device connected to your computer *before* uploading it to a remote, hard-to-access device via OTA.** +* If a sketch you've uploaded with OTA does not operate as expected, you can continue making modifications to the code and re-upload again. Or, you can upload a prior version that was working properly. However, the Safe Load features described above cannot protect against a HomeSpan sketch that has major run-time problems, such as causing a kernel panic that leads to an endless cycle of device reboots. If this happens, HomeSpan won't be able to run the OTA Server code, and further OTA updates will *not* be possible. Instead, you'll have to connect the device through a serial port to upload a new, working sketch. **For this reason you should always fully test out a new sketch on a local device connected to your computer *before* uploading it to a remote, hard-to-access device via OTA.** -* The ESP32 itself supports "automated" rollbacks that are designed to restore a device with a previously-working sketch if the latest sketch causes a reboot before being able to set a self-test flag verifiying the code is operating correctly. However, this feature is not enabled in the latest version of the Arudino-ESP32 library (2.0.2 at the time of this posting), and cannot be accessed without recompiling a custom version of the board manager. +* The ESP IDF supports "automated" rollbacks that are designed to solve the problem of endless reboots after a bad upload by automatically rolling back the device to a previously-working sketch if the latest sketch causes a reboot before being able to set a self-test flag verifiying the code is operating correctly. However, this feature is *not* enabled in the latest version of the Arudino-ESP32 library (2.0.2 at the time of this posting), and cannot be accessed without recompiling a custom version of the board manager. --- From d706776c544203ad81848730348ad81eb5f96d18 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 19 Mar 2022 22:13:52 -0500 Subject: [PATCH 040/100] Update Reference.md --- docs/Reference.md | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/docs/Reference.md b/docs/Reference.md index 299a37d..a62f590 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -221,27 +221,21 @@ This is a **base class** from which all HomeSpan Characteristics are derived, an * `new Characteristic::Brightness(50);` Brightness initialized to 50 * `new Characteristic::Brightness(50,true);` Brightness initialized to 50; updates saved in NVS -The following methods are supported: +#### The following methods are supported for numerical-based Characteristics (e.g. *int*, *float*...): * `type T getVal()` - * a template method that returns the **current** value of the Characteristic, after casting into the type *T* specified (e.g. *int*, *double*, etc.). If template parameter is excluded, value will be cast to *int*. + * a template method that returns the **current** value of a numerical-based Characteristic, after casting into the type *T* specified (e.g. *int*, *double*, etc.). If template parameter is excluded, value will be cast to *int*. * example with template specified: `double temp = Characteristic::CurrentTemperature->getVal();` * example with template excluded : `int tilt = Characteristic::CurrentTiltAngle->getVal();` * `type T getNewVal()` - * a template method that returns the desired **new** value to which a HomeKit Controller has requested to the Characteristic be updated. Same casting rules as for `getVal<>()` - -* `boolean updated()` - * returns *true* if a HomeKit Controller has requested an update to the value of the Characteristic, otherwise *false*. The requested value itself can retrieved with `getNewVal<>()` - + * a template method that returns the desired **new** value to which a HomeKit Controller has requested the Characteristic be updated. Same casting rules as for `getVal<>()`. Only applicable for numerical-based Characteristics + * `void setVal(value [,boolean notify])` - * sets the value of the Characteristic to *value*, and, if *notify* is set to true, notifies all HomeKit Controllers of the change. The *notify* flag is optional and will be set to true if not specified. Setting the *notify* flag to false allows you to update a Characateristic without notifying any HomeKit Controllers, which is useful for Characteristics that HomeKit automatically adjusts (such as a countdown timer) but will be requested from the Accessory if the Home App closes and is then re-opened + * sets the value of a numerical-based Characteristic to *value*, and, if *notify* is set to true, notifies all HomeKit Controllers of the change. The *notify* flag is optional and will be set to true if not specified. Setting the *notify* flag to false allows you to update a Characateristic without notifying any HomeKit Controllers, which is useful for Characteristics that HomeKit automatically adjusts (such as a countdown timer) but will be requested from the Accessory if the Home App closes and is then re-opened * works with any integer, boolean, or floating-based numerical *value*, though HomeSpan will convert *value* into the appropriate type for each Characteristic (e.g. calling `setValue(5.5)` on an integer-based Characteristic results in *value*=5) * throws a runtime warning if *value* is outside of the min/max range for the Characteristic, where min/max is either the HAP default, or any new min/max range set via a prior call to `setRange()` * *value* is **not** restricted to being an increment of the step size; for example it is perfectly valid to call `setVal(43.5)` after calling `setRange(0,100,5)` on a floating-based Characteristic even though 43.5 does does not align with the step size specified. The Home App will properly retain the value as 43.5, though it will round to the nearest step size increment (in this case 45) when used in a slider graphic (such as setting the temperature of a thermostat) - -* `int timeVal()` - * returns time elapsed (in millis) since value of the Characteristic was last updated (whether by `setVal()` or as the result of a successful update request from a HomeKit Controller) * `SpanCharacteristic *setRange(min, max, step)` * overrides the default HAP range for a Characteristic with the *min*, *max*, and *step* parameters specified @@ -261,6 +255,25 @@ The following methods are supported: * returns a pointer to the Characteristic itself so that the method can be chained during instantiation * example: `(new Characteristic::SecuritySystemTargetState())->setValidValues(3,0,1,3);` creates a new Valid Value list of length=3 containing the values 0, 1, and 3. This has the effect of informing HomeKit that a SecuritySystemTargetState value of 2 (Night Arm) is not valid and should not be shown as a choice in the Home App +#### The following methods are supported for string-based Characteristics (i.e. a null-terminated C-style array of characters): + +* `char *getString()` + * equivalent to `getVal()`, but used exclusively for string-characteristics (i.e. a null-terminated array of characters) + +* `char *getNewString()` + * equivalent to `getNewVal()`, but used exclusively for string-characteristics (i.e. a null-terminated array of characters) + +* `void setString(const char *value)` + * equivalent to `setVal(value)`, but used exclusively for string-characteristics (i.e. a null-terminated array of characters) + +#### The following methods are supported for all Characteristics: + +* `boolean updated()` + * returns *true* if a HomeKit Controller has requested an update to the value of the Characteristic, otherwise *false*. The requested value itself can retrieved with `getNewVal<>()` or `getNewString()` + +* `int timeVal()` + * returns time elapsed (in millis) since value of the Characteristic was last updated (whether by `setVal()`, `setString()` or as the result of a successful update request from a HomeKit Controller) + * `SpanCharacteristic *setPerms(uint8_t perms)` * changes the default permissions for a Characteristic to *perms*, where *perms* is an additive list of permissions as described in HAP-R2 Table 6-4. Valid values are PR, PW, EV, AA, TW, HD, and WR * returns a pointer to the Characteristic itself so that the method can be chained during instantiation From a2094c7fb6ac84d4eec23c1ee4458b25cefb460e Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 19 Mar 2022 22:18:18 -0500 Subject: [PATCH 041/100] Update OTA.md --- docs/OTA.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/OTA.md b/docs/OTA.md index 058c05f..908f14c 100644 --- a/docs/OTA.md +++ b/docs/OTA.md @@ -24,7 +24,7 @@ This is because HomeSpan checks that a sketch has been compiled with OTA partiti * If a sketch you've uploaded with OTA does not operate as expected, you can continue making modifications to the code and re-upload again. Or, you can upload a prior version that was working properly. However, the Safe Load features described above cannot protect against a HomeSpan sketch that has major run-time problems, such as causing a kernel panic that leads to an endless cycle of device reboots. If this happens, HomeSpan won't be able to run the OTA Server code, and further OTA updates will *not* be possible. Instead, you'll have to connect the device through a serial port to upload a new, working sketch. **For this reason you should always fully test out a new sketch on a local device connected to your computer *before* uploading it to a remote, hard-to-access device via OTA.** -* The ESP IDF supports "automated" rollbacks that are designed to solve the problem of endless reboots after a bad upload by automatically rolling back the device to a previously-working sketch if the latest sketch causes a reboot before being able to set a self-test flag verifiying the code is operating correctly. However, this feature is *not* enabled in the latest version of the Arudino-ESP32 library (2.0.2 at the time of this posting), and cannot be accessed without recompiling a custom version of the board manager. +* The ESP IDF supports "automated" rollbacks that are designed to solve the problem of endless reboots after a bad upload by automatically rolling back the device to a previously-working sketch if the latest sketch causes a reboot before being able to set a self-test flag verifying the code is operating correctly. However, this feature is *not* enabled in the latest version of the Arudino-ESP32 library (2.0.2 at the time of this posting), and cannot be accessed without recompiling a custom version of the board manager. --- From 97a2238a0919e2d9220a2732f67e9b1dc484da51 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 27 Mar 2022 09:21:48 -0500 Subject: [PATCH 042/100] Update OTA.md --- docs/OTA.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/OTA.md b/docs/OTA.md index 908f14c..413449b 100644 --- a/docs/OTA.md +++ b/docs/OTA.md @@ -12,10 +12,18 @@ You can change the password for a HomeSpan device from the [HomeSpan CLI](CLI.md Note that in in order for OTA to properly operate, your sketch must be compiled with a partition scheme that includes OTA partitions. Partition schemes are found under the *Tools → Partition Scheme* menu of the Arduino IDE. Select a scheme that indicates it supports OTA. Note that schemes labeled "default" usually include OTA partitions. If unsure, try it out. HomeSpan will let you know if it does or does not. -This is because HomeSpan checks that a sketch has been compiled with OTA partitions if OTA has been enabled for that sketch. If OTA has been enabled but HomeSpan does not find any OTA partitioms, it will indicate it cannot start the OTA Server via a warning message sent to the Serial Monitor immediately after WiFi connectivity has been established. Otherwise it will output a confirmation message indicating the OTA Server has sucessfully started. +This is because HomeSpan checks that a sketch has been compiled with OTA partitions if OTA has been enabled for that sketch. If OTA has been enabled but HomeSpan does not find any OTA partitions, it will indicate it cannot start the OTA Server via a warning message sent to the Serial Monitor immediately after WiFi connectivity has been established. Otherwise it will output a confirmation message indicating the OTA Server has sucessfully started. ### HomeSpan Safe Load Mode +HomeSpan includes two additional safety checks when using OTA to upload a sketch: + +1. HomeSpan checks to make sure the new sketch being uploaded is also another HomeSpan sketch. If not, HomeSpan will reject the new sketch and report an OTA error back to the Arduino IDE after the new sketch is uploaded, but before the device reboots. Instead, HomeSpan will close the OTA connection and resume normal operations based on the existing sketch without rebooting. The purpose of this safety check is to prevent you from accidentally uploading a non-HomeSpan sketch onto a remote device, making it impossible for you to re-upload the correct sketch without retreiving the remote device and connecting to you computer via the serial port. + +1. After a successful upload of a new HomeSpan sketch via OTA, HomeSpan will check that the new HomeSpan sketch just loaded *also* has OTA enabled. This check occurs after HomeSpan is rebooted with the new sketch. If HomeSpan does not find OTA enabled, it will mark the current partition as invalid and reboot the device, causing the device to "roll back" to the previous version of the sketch that had OTA enabled. The purpose of this safety check is to ensure you do not use OTA to upload a new HomeSpan sketch to a remote device, but failed to enable OTA in the new HomeSpan sketch. If you did this you would be locked out of making any further updated via OTA and would instead need to retreive the remote device and connect it to your computer via the serial port. + +Note that these check are *only* applicable when uploading sketches via OTA. They are ignored whenever sketches are uploaded via the serial port. Also, though these safty checks are turned on by default, they can be disabled when you first enable OTA by setting the second (optional) argument of `homeSpan.enableOTA()` to *false*. See the API for details. + ### OTA Tips and Tricks * The name of the device HomeSpan uses for OTA is the same as the name you assigned in your call to `homeSpan.begin()`. If you have multiple devices you intend to maintain with OTA, use `homeSpan.begin()` to give them each different names so you can tell them apart when selecting which one to connect to from the Arduino IDE. @@ -24,7 +32,7 @@ This is because HomeSpan checks that a sketch has been compiled with OTA partiti * If a sketch you've uploaded with OTA does not operate as expected, you can continue making modifications to the code and re-upload again. Or, you can upload a prior version that was working properly. However, the Safe Load features described above cannot protect against a HomeSpan sketch that has major run-time problems, such as causing a kernel panic that leads to an endless cycle of device reboots. If this happens, HomeSpan won't be able to run the OTA Server code, and further OTA updates will *not* be possible. Instead, you'll have to connect the device through a serial port to upload a new, working sketch. **For this reason you should always fully test out a new sketch on a local device connected to your computer *before* uploading it to a remote, hard-to-access device via OTA.** -* The ESP IDF supports "automated" rollbacks that are designed to solve the problem of endless reboots after a bad upload by automatically rolling back the device to a previously-working sketch if the latest sketch causes a reboot before being able to set a self-test flag verifying the code is operating correctly. However, this feature is *not* enabled in the latest version of the Arudino-ESP32 library (2.0.2 at the time of this posting), and cannot be accessed without recompiling a custom version of the board manager. +* Note that though the ESP IDF supports "automated" rollbacks that are designed to solve the problem of endless reboots after a bad upload, this feature is *not* enabled in the latest version of the Arudino-ESP32 library (2.0.2 at the time of this posting). --- From 8e357f8605c0be7302e788f16921b79ad0b7c452 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 27 Mar 2022 09:30:45 -0500 Subject: [PATCH 043/100] Update Reference.md --- docs/Reference.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/Reference.md b/docs/Reference.md index a62f590..2aaf3c3 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -88,12 +88,13 @@ The following **optional** `homeSpan` methods override various HomeSpan initiali The following **optional** `homeSpan` methods enable additional features and provide for further customization of the HomeSpan environment. Unless otherwise noted, calls **should** be made before `begin()` to take effect: -* `void enableOTA(boolean auth=true)` +* `void enableOTA(boolean auth=true, boolean safeMode=true)` * enables [Over-the-Air (OTA) Updating](OTA.md) of a HomeSpan device, which is otherwise disabled * HomeSpan OTA requires an authorizing password unless *auth* is specified and set to *false* * the default OTA password for new HomeSpan devices is "homespan-ota" * this can be changed via the [HomeSpan CLI](CLI.md) using the 'O' command * note enabling OTA reduces the number of HAP Controller Connections by 1 + * OTA Safe Mode will be enabled by default unless the second argument is specified and set to *false*. HomeSpan OTA Safe Mode checks to ensure that sketches uploaded to an existing HomeSpan device are themselves HomeSpan sketches, and that they also have OTA enabled. See [HomeSpan OTA](../OTA.md) for details * `void enableAutoStartAP()` * enables automatic start-up of WiFi Access Point if WiFi Credentials are **not** found at boot time From c928793dcce579aa41998a274f36615188883e66 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 27 Mar 2022 14:12:22 -0500 Subject: [PATCH 044/100] Update Reference.md --- docs/Reference.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Reference.md b/docs/Reference.md index 2aaf3c3..76bc0e1 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -94,7 +94,7 @@ The following **optional** `homeSpan` methods enable additional features and pro * the default OTA password for new HomeSpan devices is "homespan-ota" * this can be changed via the [HomeSpan CLI](CLI.md) using the 'O' command * note enabling OTA reduces the number of HAP Controller Connections by 1 - * OTA Safe Mode will be enabled by default unless the second argument is specified and set to *false*. HomeSpan OTA Safe Mode checks to ensure that sketches uploaded to an existing HomeSpan device are themselves HomeSpan sketches, and that they also have OTA enabled. See [HomeSpan OTA](../OTA.md) for details + * OTA Safe Mode will be enabled by default unless the second argument is specified and set to *false*. HomeSpan OTA Safe Mode checks to ensure that sketches uploaded to an existing HomeSpan device are themselves HomeSpan sketches, and that they also have OTA enabled. See [HomeSpan OTA](OTA.md) for details * `void enableAutoStartAP()` * enables automatic start-up of WiFi Access Point if WiFi Credentials are **not** found at boot time From c5d9e7000983e3fa2e4e5fd5b303b6844e0b13fb Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 27 Mar 2022 14:13:45 -0500 Subject: [PATCH 045/100] Update Reference.md --- docs/Reference.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Reference.md b/docs/Reference.md index 76bc0e1..db8ee20 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -94,7 +94,7 @@ The following **optional** `homeSpan` methods enable additional features and pro * the default OTA password for new HomeSpan devices is "homespan-ota" * this can be changed via the [HomeSpan CLI](CLI.md) using the 'O' command * note enabling OTA reduces the number of HAP Controller Connections by 1 - * OTA Safe Mode will be enabled by default unless the second argument is specified and set to *false*. HomeSpan OTA Safe Mode checks to ensure that sketches uploaded to an existing HomeSpan device are themselves HomeSpan sketches, and that they also have OTA enabled. See [HomeSpan OTA](OTA.md) for details + * OTA Safe Mode will be enabled by default unless the second argument is specified and set to *false*. HomeSpan OTA Safe Mode checks to ensure that sketches uploaded to an existing HomeSpan device are themselves HomeSpan sketches, and that they also have OTA enabled. See [HomeSpan OTA Safe Mode](OTA.md#homespan-safe-load-mode) for details * `void enableAutoStartAP()` * enables automatic start-up of WiFi Access Point if WiFi Credentials are **not** found at boot time From 9ab50928f3008b1df33992b127c161cf471ba112 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 27 Mar 2022 14:19:31 -0500 Subject: [PATCH 046/100] Update OTA.md --- docs/OTA.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/OTA.md b/docs/OTA.md index 413449b..a524956 100644 --- a/docs/OTA.md +++ b/docs/OTA.md @@ -14,7 +14,7 @@ Note that in in order for OTA to properly operate, your sketch must be compiled This is because HomeSpan checks that a sketch has been compiled with OTA partitions if OTA has been enabled for that sketch. If OTA has been enabled but HomeSpan does not find any OTA partitions, it will indicate it cannot start the OTA Server via a warning message sent to the Serial Monitor immediately after WiFi connectivity has been established. Otherwise it will output a confirmation message indicating the OTA Server has sucessfully started. -### HomeSpan Safe Load Mode +### OTA Safe Load HomeSpan includes two additional safety checks when using OTA to upload a sketch: @@ -22,7 +22,7 @@ HomeSpan includes two additional safety checks when using OTA to upload a sketch 1. After a successful upload of a new HomeSpan sketch via OTA, HomeSpan will check that the new HomeSpan sketch just loaded *also* has OTA enabled. This check occurs after HomeSpan is rebooted with the new sketch. If HomeSpan does not find OTA enabled, it will mark the current partition as invalid and reboot the device, causing the device to "roll back" to the previous version of the sketch that had OTA enabled. The purpose of this safety check is to ensure you do not use OTA to upload a new HomeSpan sketch to a remote device, but failed to enable OTA in the new HomeSpan sketch. If you did this you would be locked out of making any further updated via OTA and would instead need to retreive the remote device and connect it to your computer via the serial port. -Note that these check are *only* applicable when uploading sketches via OTA. They are ignored whenever sketches are uploaded via the serial port. Also, though these safty checks are turned on by default, they can be disabled when you first enable OTA by setting the second (optional) argument of `homeSpan.enableOTA()` to *false*. See the API for details. +Note that these check are *only* applicable when uploading sketches via OTA. They are ignored whenever sketches are uploaded via the serial port. Also, though these safety checks are enabled by default, they can be disabled when you first enable OTA by setting the second (optional) argument to *false* as such: `homeSpan.enableOTA(..., false)`. See the API for details. ### OTA Tips and Tricks From 43e93da24b0abc8f7fd3edb3f1a2b7c179a71245 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 27 Mar 2022 14:21:02 -0500 Subject: [PATCH 047/100] Update Reference.md --- docs/Reference.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Reference.md b/docs/Reference.md index db8ee20..c6a9470 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -88,13 +88,13 @@ The following **optional** `homeSpan` methods override various HomeSpan initiali The following **optional** `homeSpan` methods enable additional features and provide for further customization of the HomeSpan environment. Unless otherwise noted, calls **should** be made before `begin()` to take effect: -* `void enableOTA(boolean auth=true, boolean safeMode=true)` +* `void enableOTA(boolean auth=true, boolean safeLoad=true)` * enables [Over-the-Air (OTA) Updating](OTA.md) of a HomeSpan device, which is otherwise disabled * HomeSpan OTA requires an authorizing password unless *auth* is specified and set to *false* * the default OTA password for new HomeSpan devices is "homespan-ota" * this can be changed via the [HomeSpan CLI](CLI.md) using the 'O' command * note enabling OTA reduces the number of HAP Controller Connections by 1 - * OTA Safe Mode will be enabled by default unless the second argument is specified and set to *false*. HomeSpan OTA Safe Mode checks to ensure that sketches uploaded to an existing HomeSpan device are themselves HomeSpan sketches, and that they also have OTA enabled. See [HomeSpan OTA Safe Mode](OTA.md#homespan-safe-load-mode) for details + * OTA Safe Load will be enabled by default unless the second argument is specified and set to *false*. HomeSpan OTA Safe Load checks to ensure that sketches uploaded to an existing HomeSpan device are themselves HomeSpan sketches, and that they also have OTA enabled. See [HomeSpan OTA Safe Load](OTA.md#homespan-safe-load) for details * `void enableAutoStartAP()` * enables automatic start-up of WiFi Access Point if WiFi Credentials are **not** found at boot time From fdd0ae7cff9a24d5355e2905276698ed71f152b1 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 27 Mar 2022 14:21:48 -0500 Subject: [PATCH 048/100] Update Reference.md --- docs/Reference.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Reference.md b/docs/Reference.md index c6a9470..05242ac 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -94,7 +94,7 @@ The following **optional** `homeSpan` methods enable additional features and pro * the default OTA password for new HomeSpan devices is "homespan-ota" * this can be changed via the [HomeSpan CLI](CLI.md) using the 'O' command * note enabling OTA reduces the number of HAP Controller Connections by 1 - * OTA Safe Load will be enabled by default unless the second argument is specified and set to *false*. HomeSpan OTA Safe Load checks to ensure that sketches uploaded to an existing HomeSpan device are themselves HomeSpan sketches, and that they also have OTA enabled. See [HomeSpan OTA Safe Load](OTA.md#homespan-safe-load) for details + * OTA Safe Load will be enabled by default unless the second argument is specified and set to *false*. HomeSpan OTA Safe Load checks to ensure that sketches uploaded to an existing HomeSpan device are themselves HomeSpan sketches, and that they also have OTA enabled. See [HomeSpan OTA Safe Load](OTA.md#ota-safe-load) for details * `void enableAutoStartAP()` * enables automatic start-up of WiFi Access Point if WiFi Credentials are **not** found at boot time From 621f4ddd1e967257940b911314ea8c964fcfaf25 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 27 Mar 2022 17:55:04 -0500 Subject: [PATCH 049/100] Update Reference.md --- docs/Reference.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/docs/Reference.md b/docs/Reference.md index 05242ac..a204e81 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -147,7 +147,20 @@ The following **optional** `homeSpan` methods enable additional features and pro * `const char *getSketchVersion()` * returns the version of a HomeSpan sketch, as set using `void setSketchVersion(const char *sVer)`, or "n/a" if not set * can by called from anywhere in a sketch - + +* `void enableWebLog(uint16_t maxEntries, const char *timeServerURL, const char *timeZone, const char *logURL)` + * enables a rolling web log that displays the most recent *maxEntries* entries created with the [WEBLOG\(\) macro](./#weblog). Parameters, and their default values if unspecified, are as follows: + * *maxEntries* - maximum number of (most recent) entries to save. If unspecified, defaults to 0, in which case the web log will only display status without any log entries + * *timeServerURL* - the URL of a time server that HomeSpan will use to set its clock upon startup after a WiFi connection has been established. If unspecified, default to NULL, in which case HomeSpan skips setting the device clock + * *timeZone* - specifies the time zone to use for setting the clock. Uses standard Unix timezone formatting as interpreted by Espressif IDF. Note the IDF uses a somewhat non-intuitive convention such that a timezone of "UTC+5:00" *subtracts* 5 hours from UTC time, and "UTC-5:00" *adds* 5 hours to UTC time. If *serverURL=NULL* this field is ignored; if *serverURL!=NULL* this field is required + * *logURL* - the URL of the log page for this device. If unspecified, defaults to "status" + * example: `homeSpan.enableWebLog(50,"pool.ntp.org","UTC-1:00","myLog");` creates a web log at the URL *http://HomeSpan-\[DEVICE-ID\].local:\[TCP-PORT\]/myLog* that will display the 50 most-recent log messages produced with the WEBLOG() macro. Upon start-up (after a WiFi connection has been established) HomeSpan will attempt to set the device clock by calling the server "pool.ntp.org" and adjusting the time to be 1 hour ahead of UTC. + * When attemping to connect to *timeServerURL*, HomeSpan waits 10 seconds for a response. If no response is received after the 10-second timeout period, HomeSpan assumes the server is unreachable and skips the clock-setting procedure. Use `setTimeServerTimeout()` to re-configure the 10-second timeout period to another value + * See [HomeSpan Logging](Logging.md) for complete details + +* `void setTimeServerTimeout(uint32_t tSec)` + * changes the default 10-second timeout period HomeSpan uses when `enableWebLog()` tries set the device clock from an internet time server to *tSec* seconds + ## *SpanAccessory(uint32_t aid)* Creating an instance of this **class** adds a new HAP Accessory to the HomeSpan HAP Database. From 935c3f9810bb902ede8826d77419bfb0e22a6797 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 27 Mar 2022 18:01:47 -0500 Subject: [PATCH 050/100] Update Reference.md --- docs/Reference.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/Reference.md b/docs/Reference.md index a204e81..3c4fc37 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -149,7 +149,7 @@ The following **optional** `homeSpan` methods enable additional features and pro * can by called from anywhere in a sketch * `void enableWebLog(uint16_t maxEntries, const char *timeServerURL, const char *timeZone, const char *logURL)` - * enables a rolling web log that displays the most recent *maxEntries* entries created with the [WEBLOG\(\) macro](./#weblog). Parameters, and their default values if unspecified, are as follows: + * enables a rolling web log that displays the most recent *maxEntries* entries created with the [WEBLOG\(\) macro](./#user-macros). Parameters, and their default values if unspecified, are as follows: * *maxEntries* - maximum number of (most recent) entries to save. If unspecified, defaults to 0, in which case the web log will only display status without any log entries * *timeServerURL* - the URL of a time server that HomeSpan will use to set its clock upon startup after a WiFi connection has been established. If unspecified, default to NULL, in which case HomeSpan skips setting the device clock * *timeZone* - specifies the time zone to use for setting the clock. Uses standard Unix timezone formatting as interpreted by Espressif IDF. Note the IDF uses a somewhat non-intuitive convention such that a timezone of "UTC+5:00" *subtracts* 5 hours from UTC time, and "UTC-5:00" *adds* 5 hours to UTC time. If *serverURL=NULL* this field is ignored; if *serverURL!=NULL* this field is required @@ -375,6 +375,8 @@ might be used to save all the elements in *myArray* when called with just the '@ To create more than one user-defined command, simply create multiple instances of SpanUserCommand, each with its own single-letter name. Note that re-using the same single-letter name in an instance of SpanUserCommand over-rides any previous instances using that same letter. ## User Macros + +### LOG1(x), LOG1(const char *fmt, ...), LOG2(x), LOG2(const char *fmt, ...), WEBLOG(const char *fmt, ...) ### *#define REQUIRED VERSION(major,minor,patch)* From 55aa5fd6d319a187247eaa6cc7318ed5bcdaec4f Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 27 Mar 2022 18:03:01 -0500 Subject: [PATCH 051/100] Update Reference.md --- docs/Reference.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Reference.md b/docs/Reference.md index 3c4fc37..b28914b 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -149,7 +149,7 @@ The following **optional** `homeSpan` methods enable additional features and pro * can by called from anywhere in a sketch * `void enableWebLog(uint16_t maxEntries, const char *timeServerURL, const char *timeZone, const char *logURL)` - * enables a rolling web log that displays the most recent *maxEntries* entries created with the [WEBLOG\(\) macro](./#user-macros). Parameters, and their default values if unspecified, are as follows: + * enables a rolling web log that displays the most recent *maxEntries* entries created with the [WEBLOG\(\) macro](#user-macros). Parameters, and their default values if unspecified, are as follows: * *maxEntries* - maximum number of (most recent) entries to save. If unspecified, defaults to 0, in which case the web log will only display status without any log entries * *timeServerURL* - the URL of a time server that HomeSpan will use to set its clock upon startup after a WiFi connection has been established. If unspecified, default to NULL, in which case HomeSpan skips setting the device clock * *timeZone* - specifies the time zone to use for setting the clock. Uses standard Unix timezone formatting as interpreted by Espressif IDF. Note the IDF uses a somewhat non-intuitive convention such that a timezone of "UTC+5:00" *subtracts* 5 hours from UTC time, and "UTC-5:00" *adds* 5 hours to UTC time. If *serverURL=NULL* this field is ignored; if *serverURL!=NULL* this field is required From 213a29c924bf6e156c2498487287dc5e627325d1 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 27 Mar 2022 18:41:29 -0500 Subject: [PATCH 052/100] Update Reference.md --- docs/Reference.md | 52 ++++++++++++++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/docs/Reference.md b/docs/Reference.md index b28914b..da37ac7 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -56,10 +56,11 @@ The following **optional** `homeSpan` methods override various HomeSpan initiali * `void setLogLevel(uint8_t level)` * sets the logging level for diagnostic messages, where: - * 0 = top-level status messages only (default), - * 1 = all status messages, and - * 2 = all status messages plus all HAP communication packets to and from the HomeSpan device - * this parameter can also be changed at runtime via the [HomeSpan CLI](CLI.md) + * 0 = top-level HomeSpan status messages, and any messages output by the user using `Serial.print()` or `Serial.printf()` (default) + * 1 = all HomeSpan status messages, and any `LOG1()` messages specified in the sketch by the user + * 2 = all HomeSpan status messages plus all HAP communication packets to and from the HomeSpan device, as well as all `LOG1()` and `LOG2()` messages specified in the sketch by the user + * see [Logging]() below for more details + * the log level can also be changed at runtime with the 'L' command via the [HomeSpan CLI](CLI.md) * `void reserveSocketConnections(uint8_t nSockets)` * reserves *nSockets* network sockets for uses **other than** by the HomeSpan HAP Server for HomeKit Controller Connections @@ -149,14 +150,14 @@ The following **optional** `homeSpan` methods enable additional features and pro * can by called from anywhere in a sketch * `void enableWebLog(uint16_t maxEntries, const char *timeServerURL, const char *timeZone, const char *logURL)` - * enables a rolling web log that displays the most recent *maxEntries* entries created with the [WEBLOG\(\) macro](#user-macros). Parameters, and their default values if unspecified, are as follows: + * enables a rolling web log that displays the most recent *maxEntries* entries created by the user with the `WEBLOG()` macro. Parameters, and their default values if unspecified, are as follows: * *maxEntries* - maximum number of (most recent) entries to save. If unspecified, defaults to 0, in which case the web log will only display status without any log entries * *timeServerURL* - the URL of a time server that HomeSpan will use to set its clock upon startup after a WiFi connection has been established. If unspecified, default to NULL, in which case HomeSpan skips setting the device clock * *timeZone* - specifies the time zone to use for setting the clock. Uses standard Unix timezone formatting as interpreted by Espressif IDF. Note the IDF uses a somewhat non-intuitive convention such that a timezone of "UTC+5:00" *subtracts* 5 hours from UTC time, and "UTC-5:00" *adds* 5 hours to UTC time. If *serverURL=NULL* this field is ignored; if *serverURL!=NULL* this field is required * *logURL* - the URL of the log page for this device. If unspecified, defaults to "status" * example: `homeSpan.enableWebLog(50,"pool.ntp.org","UTC-1:00","myLog");` creates a web log at the URL *http://HomeSpan-\[DEVICE-ID\].local:\[TCP-PORT\]/myLog* that will display the 50 most-recent log messages produced with the WEBLOG() macro. Upon start-up (after a WiFi connection has been established) HomeSpan will attempt to set the device clock by calling the server "pool.ntp.org" and adjusting the time to be 1 hour ahead of UTC. * When attemping to connect to *timeServerURL*, HomeSpan waits 10 seconds for a response. If no response is received after the 10-second timeout period, HomeSpan assumes the server is unreachable and skips the clock-setting procedure. Use `setTimeServerTimeout()` to re-configure the 10-second timeout period to another value - * See [HomeSpan Logging](Logging.md) for complete details + * See [Logging]() below for more details * `void setTimeServerTimeout(uint32_t tSec)` * changes the default 10-second timeout period HomeSpan uses when `enableWebLog()` tries set the device clock from an internet time server to *tSec* seconds @@ -374,20 +375,22 @@ might be used to save all the elements in *myArray* when called with just the '@ To create more than one user-defined command, simply create multiple instances of SpanUserCommand, each with its own single-letter name. Note that re-using the same single-letter name in an instance of SpanUserCommand over-rides any previous instances using that same letter. -## User Macros +## Message Logging Macros + +### *LOG1(X)* and *LOG2(X)* +### *LOG1(const char \*fmt, ...) and *LOG2(const char \*fmt, ...)* + +Displays user-defined log messages on the Arduino Serial Monitor according to the log level specified with `setLogLevel()`, or as specified at runtime with the 'L' command via the [HomeSpan CLI](CLI.md). `LOG1()` messages will be output only if the log level is set to 1 or greater. `LOG2()` messages will be output only if the log level is set to 2. -### LOG1(x), LOG1(const char *fmt, ...), LOG2(x), LOG2(const char *fmt, ...), WEBLOG(const char *fmt, ...) +* In the first form (e.g. `LOG1(X)`), the macro calls `Serial.print(X)`. The argument *X* can be any variable recognized by the Arduino `Serial.print()` function. For example, `int val=255; LOG1(val);` outputs "255" to the Serial Monitor +* In the second form (e.g. `LOG1(const char *fmt, ...)`), the macro calls `Serial.printf(fmt, ...)` enabling you to create printf-style formatted output. For example, `int val=255; LOG1("The value is: %X",val);` outputs the "The value is: FF" to the Serial Monitor + +### WEBLOG(const char *fmt, ...) -### *#define REQUIRED VERSION(major,minor,patch)* +## Custom Characteristics and Custom Services Macros -If REQUIRED is defined in the main sketch *prior* to including the HomeSpan library with `#include "HomeSpan.h"`, HomeSpan will throw a compile-time error unless the version of the library included is equal to, or later than, the version specified using the VERSION macro. Example: - -```C++ -#define REQUIRED VERSION(1,3,0) // throws a compile-time error unless HomeSpan library used is version 1.3.0 or later -#include "HomeSpan.h" -``` -### *#define CUSTOM_CHAR(name,uuid,perms,format,defaultValue,minValue,maxValue,staticRange)* -### *#define CUSTOM_CHAR_STRING(name,uuid,perms,defaultValue)* +### *CUSTOM_CHAR(name,uuid,perms,format,defaultValue,minValue,maxValue,staticRange)* +### *CUSTOM_CHAR_STRING(name,uuid,perms,defaultValue)* Creates a custom Characteristic that can be added to any Service. Custom Characteristics are generally ignored by the Home App but may be used by other third-party applications (such as *Eve for HomeKit*). The first form should be used create numerical Characterstics (e.g., UINT8, BOOL...). The second form is used to String-based Characteristics. Parameters are as follows (note that quotes should NOT be used in any of the macro parameters, except for defaultValue): @@ -418,7 +421,7 @@ Note that Custom Characteristics must be created prior to calling `homeSpan.begi > Advanced Tip: When presented with an unrecognized Custom Characteristic, *Eve for HomeKit* helpfully displays a *generic control* allowing you to interact with any Custom Characteristic you create in HomeSpan. However, since Eve does not recognize the Characteristic, it will only render the generic control if the Characteristic includes a **description** field, which you can add to any Characteristic using the `setDescription()` method described above. You may also want to use `setUnit()` and `setRange()` so that the Eve App displays a control with appropriate ranges for your Custom Characteristic. -### *#define CUSTOM_SERV(name,uuid)* +### *CUSTOM_SERV(name,uuid)* Creates a custom Service for use with third-party applications (such as *Eve for HomeKit*). Custom Services will be displayed in the native Apple Home App with a Tile labeled "Not Supported", but otherwise the Service will be safely ignored by the Home App. Parameters are as follows (note that quotes should NOT be used in either of the macro parameters): @@ -429,7 +432,18 @@ Custom Services may contain a mix of both Custom Characteristics and standard HA A fully worked example showing how to use both the ***CUSTOM_SERV()*** and ***CUSTOM_CHAR()*** macros to create a Pressure Sensor Accessory that is recognized by *Eve for HomeKit* can be found in the Arduino IDE under [*File → Examples → HomeSpan → Other Examples → CustomService*](../Other%20Examples/CustomService). ---- +## User-Definable Macros + +### *#define REQUIRED VERSION(major,minor,patch)* + +If REQUIRED is defined in the main sketch *prior* to including the HomeSpan library with `#include "HomeSpan.h"`, HomeSpan will throw a compile-time error unless the version of the library included is equal to, or later than, the version specified using the VERSION macro. Example: + +```C++ +#define REQUIRED VERSION(1,3,0) // throws a compile-time error unless HomeSpan library used is version 1.3.0 or later +#include "HomeSpan.h" +``` + + --- #### Deprecated functions (available for backwards compatibility with older sketches): From 6ca74f030d64677586f0058a395a614d67e1a2f3 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 27 Mar 2022 18:49:43 -0500 Subject: [PATCH 053/100] Update Reference.md --- docs/Reference.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/Reference.md b/docs/Reference.md index da37ac7..cff55c5 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -59,7 +59,7 @@ The following **optional** `homeSpan` methods override various HomeSpan initiali * 0 = top-level HomeSpan status messages, and any messages output by the user using `Serial.print()` or `Serial.printf()` (default) * 1 = all HomeSpan status messages, and any `LOG1()` messages specified in the sketch by the user * 2 = all HomeSpan status messages plus all HAP communication packets to and from the HomeSpan device, as well as all `LOG1()` and `LOG2()` messages specified in the sketch by the user - * see [Logging]() below for more details + * see [Message Logging Macros](#message-logging-macros) below for more details * the log level can also be changed at runtime with the 'L' command via the [HomeSpan CLI](CLI.md) * `void reserveSocketConnections(uint8_t nSockets)` @@ -157,7 +157,7 @@ The following **optional** `homeSpan` methods enable additional features and pro * *logURL* - the URL of the log page for this device. If unspecified, defaults to "status" * example: `homeSpan.enableWebLog(50,"pool.ntp.org","UTC-1:00","myLog");` creates a web log at the URL *http://HomeSpan-\[DEVICE-ID\].local:\[TCP-PORT\]/myLog* that will display the 50 most-recent log messages produced with the WEBLOG() macro. Upon start-up (after a WiFi connection has been established) HomeSpan will attempt to set the device clock by calling the server "pool.ntp.org" and adjusting the time to be 1 hour ahead of UTC. * When attemping to connect to *timeServerURL*, HomeSpan waits 10 seconds for a response. If no response is received after the 10-second timeout period, HomeSpan assumes the server is unreachable and skips the clock-setting procedure. Use `setTimeServerTimeout()` to re-configure the 10-second timeout period to another value - * See [Logging]() below for more details + * see [Message Logging Macros](#message-logging-macros) below for more details * `void setTimeServerTimeout(uint32_t tSec)` * changes the default 10-second timeout period HomeSpan uses when `enableWebLog()` tries set the device clock from an internet time server to *tSec* seconds @@ -378,12 +378,13 @@ To create more than one user-defined command, simply create multiple instances o ## Message Logging Macros ### *LOG1(X)* and *LOG2(X)* -### *LOG1(const char \*fmt, ...) and *LOG2(const char \*fmt, ...)* +### *LOG1(const char \*fmt, ...)* and *LOG2(const char \*fmt, ...)* Displays user-defined log messages on the Arduino Serial Monitor according to the log level specified with `setLogLevel()`, or as specified at runtime with the 'L' command via the [HomeSpan CLI](CLI.md). `LOG1()` messages will be output only if the log level is set to 1 or greater. `LOG2()` messages will be output only if the log level is set to 2. * In the first form (e.g. `LOG1(X)`), the macro calls `Serial.print(X)`. The argument *X* can be any variable recognized by the Arduino `Serial.print()` function. For example, `int val=255; LOG1(val);` outputs "255" to the Serial Monitor * In the second form (e.g. `LOG1(const char *fmt, ...)`), the macro calls `Serial.printf(fmt, ...)` enabling you to create printf-style formatted output. For example, `int val=255; LOG1("The value is: %X",val);` outputs the "The value is: FF" to the Serial Monitor +* See [Example 9 - Message Logging](https://github.com/HomeSpan/HomeSpan/blob/master/docs/Tutorials.md#example-9---messagelogging) for a tutorial sketch demonstrating these functions ### WEBLOG(const char *fmt, ...) From 8a6a67410ad6b03bcb5edba3e20c1800866b0300 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 27 Mar 2022 18:50:48 -0500 Subject: [PATCH 054/100] Update Reference.md --- docs/Reference.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Reference.md b/docs/Reference.md index cff55c5..ddc2699 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -384,7 +384,7 @@ Displays user-defined log messages on the Arduino Serial Monitor according to th * In the first form (e.g. `LOG1(X)`), the macro calls `Serial.print(X)`. The argument *X* can be any variable recognized by the Arduino `Serial.print()` function. For example, `int val=255; LOG1(val);` outputs "255" to the Serial Monitor * In the second form (e.g. `LOG1(const char *fmt, ...)`), the macro calls `Serial.printf(fmt, ...)` enabling you to create printf-style formatted output. For example, `int val=255; LOG1("The value is: %X",val);` outputs the "The value is: FF" to the Serial Monitor -* See [Example 9 - Message Logging](https://github.com/HomeSpan/HomeSpan/blob/master/docs/Tutorials.md#example-9---messagelogging) for a tutorial sketch demonstrating these functions +* See [Example 9 - MessageLogging](https://github.com/HomeSpan/HomeSpan/blob/master/docs/Tutorials.md#example-9---messagelogging) for a tutorial sketch demonstrating these functions ### WEBLOG(const char *fmt, ...) From 0fbe0abccde09461a559493c9dd1e1d9e8af978b Mon Sep 17 00:00:00 2001 From: Gregg Date: Mon, 28 Mar 2022 18:39:46 -0500 Subject: [PATCH 055/100] Create Logging.md --- docs/Logging.md | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 docs/Logging.md diff --git a/docs/Logging.md b/docs/Logging.md new file mode 100644 index 0000000..a524956 --- /dev/null +++ b/docs/Logging.md @@ -0,0 +1,41 @@ +# Over-the-Air (OTA) Updates + +HomeSpan supports Over-the-Air (OTA) updates, which allows you to *wirelessly* upload sketches directly from the Arduino IDE - no serial connection needed. To activate this feature for your sketch, simply call the method `homeSpan.enableOTA()` prior to calling `homeSpan.begin()`. + +When a HomeSpan sketch is run with OTA enabled, the device shows up as a "network" port that can be selected under the *Tools → Port* menu in the Arduino IDE. Once selected, the IDE will direct all uploads to the device via WiFi instead of looking for it on a serial port. Note that you can upload via OTA even if your device is still connected to a serial port, but the Arduino IDE does not presently support multiple port connections at the same time. If you select a "network" port, the IDE will automatically close the Serial Monitor if it is open. To re-instate uploads via the "serial" port, simply choose that port from the *Tools → Port* menu in the Arduino IDE. Uploading via the serial port is always possible regardless of whether you have enabled OTA for a sketch. + +By default, HomeSpan requires the use of a password whenever you begin an OTA upload. The default OTA password is "homespan-ota". The Arduino will prompt you for this password upon your first attempt to upload a sketch to a newly-connected device. However, once the password for a specific device is entered, the Arduino IDE retains it in memory as long as the IDE is running, thereby saving you from having to type it again every time you re-upload a sketch via OTA. + +You can change the password for a HomeSpan device from the [HomeSpan CLI](CLI.md) with the 'O' command. Similar to a device's Setup Code, HomeSpan saves a non-recoverable hashed version of the OTA password you specify in non-volatile storage (NVS). If you forget the password you specified, you'll need to create a new one using the 'O' command, or you can restore the default OTA password by fully erasing the NVS with the 'E' command. + +> :exclamation: Though not recommended, you can override the requirement for a password when enabling OTA for your sketch by including *false* as a parameter to the enabling method as such: `homeSpan.enableOTA(false)`. Use with caution! Anyone who can access the device over your network will now be able to upload a new sketch. + +Note that in in order for OTA to properly operate, your sketch must be compiled with a partition scheme that includes OTA partitions. Partition schemes are found under the *Tools → Partition Scheme* menu of the Arduino IDE. Select a scheme that indicates it supports OTA. Note that schemes labeled "default" usually include OTA partitions. If unsure, try it out. HomeSpan will let you know if it does or does not. + +This is because HomeSpan checks that a sketch has been compiled with OTA partitions if OTA has been enabled for that sketch. If OTA has been enabled but HomeSpan does not find any OTA partitions, it will indicate it cannot start the OTA Server via a warning message sent to the Serial Monitor immediately after WiFi connectivity has been established. Otherwise it will output a confirmation message indicating the OTA Server has sucessfully started. + +### OTA Safe Load + +HomeSpan includes two additional safety checks when using OTA to upload a sketch: + +1. HomeSpan checks to make sure the new sketch being uploaded is also another HomeSpan sketch. If not, HomeSpan will reject the new sketch and report an OTA error back to the Arduino IDE after the new sketch is uploaded, but before the device reboots. Instead, HomeSpan will close the OTA connection and resume normal operations based on the existing sketch without rebooting. The purpose of this safety check is to prevent you from accidentally uploading a non-HomeSpan sketch onto a remote device, making it impossible for you to re-upload the correct sketch without retreiving the remote device and connecting to you computer via the serial port. + +1. After a successful upload of a new HomeSpan sketch via OTA, HomeSpan will check that the new HomeSpan sketch just loaded *also* has OTA enabled. This check occurs after HomeSpan is rebooted with the new sketch. If HomeSpan does not find OTA enabled, it will mark the current partition as invalid and reboot the device, causing the device to "roll back" to the previous version of the sketch that had OTA enabled. The purpose of this safety check is to ensure you do not use OTA to upload a new HomeSpan sketch to a remote device, but failed to enable OTA in the new HomeSpan sketch. If you did this you would be locked out of making any further updated via OTA and would instead need to retreive the remote device and connect it to your computer via the serial port. + +Note that these check are *only* applicable when uploading sketches via OTA. They are ignored whenever sketches are uploaded via the serial port. Also, though these safety checks are enabled by default, they can be disabled when you first enable OTA by setting the second (optional) argument to *false* as such: `homeSpan.enableOTA(..., false)`. See the API for details. + +### OTA Tips and Tricks + +* The name of the device HomeSpan uses for OTA is the same as the name you assigned in your call to `homeSpan.begin()`. If you have multiple devices you intend to maintain with OTA, use `homeSpan.begin()` to give them each different names so you can tell them apart when selecting which one to connect to from the Arduino IDE. + +* Use the `homeSpan.setSketchVersion()` method to set a version for your sketch (see the [HomeSpan API](Reference.md) for details). If specified, HomeSpan will include the sketch version as part of its HAP MDNS broadcast. This allows you determine which version of a sketch is running on a remote HomeSpan device, even if you can't plug it into a serial port for use with the Arduino Serial Monitor. In addition to the sketch version, HomeSpan also includes other fields in its MDNS broadcast that are useful in identifying the device: the version number of the HomeSpan *library* used to compile the sketch, a field indicating whether or not OTA is enabled for the sketch, the version number of the Arduino-ESP32 library used when compiling, and the type of board (e.g. *feather_esp32*). + +* If a sketch you've uploaded with OTA does not operate as expected, you can continue making modifications to the code and re-upload again. Or, you can upload a prior version that was working properly. However, the Safe Load features described above cannot protect against a HomeSpan sketch that has major run-time problems, such as causing a kernel panic that leads to an endless cycle of device reboots. If this happens, HomeSpan won't be able to run the OTA Server code, and further OTA updates will *not* be possible. Instead, you'll have to connect the device through a serial port to upload a new, working sketch. **For this reason you should always fully test out a new sketch on a local device connected to your computer *before* uploading it to a remote, hard-to-access device via OTA.** + +* Note that though the ESP IDF supports "automated" rollbacks that are designed to solve the problem of endless reboots after a bad upload, this feature is *not* enabled in the latest version of the Arudino-ESP32 library (2.0.2 at the time of this posting). + +--- + +[↩️](README.md) Back to the Welcome page + + From 8c97cb1038c609cbdb1296497b60fb9219772069 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Mon, 28 Mar 2022 18:41:49 -0500 Subject: [PATCH 056/100] Update Logging.md --- docs/Logging.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Logging.md b/docs/Logging.md index a524956..211879e 100644 --- a/docs/Logging.md +++ b/docs/Logging.md @@ -1,4 +1,4 @@ -# Over-the-Air (OTA) Updates +# Message Logging HomeSpan supports Over-the-Air (OTA) updates, which allows you to *wirelessly* upload sketches directly from the Arduino IDE - no serial connection needed. To activate this feature for your sketch, simply call the method `homeSpan.enableOTA()` prior to calling `homeSpan.begin()`. From c96bf8c590d39d8c3cc2596f961708082870aca4 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Mon, 28 Mar 2022 18:46:21 -0500 Subject: [PATCH 057/100] Update Reference.md --- docs/Reference.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/Reference.md b/docs/Reference.md index ddc2699..437648d 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -59,8 +59,8 @@ The following **optional** `homeSpan` methods override various HomeSpan initiali * 0 = top-level HomeSpan status messages, and any messages output by the user using `Serial.print()` or `Serial.printf()` (default) * 1 = all HomeSpan status messages, and any `LOG1()` messages specified in the sketch by the user * 2 = all HomeSpan status messages plus all HAP communication packets to and from the HomeSpan device, as well as all `LOG1()` and `LOG2()` messages specified in the sketch by the user - * see [Message Logging Macros](#message-logging-macros) below for more details - * the log level can also be changed at runtime with the 'L' command via the [HomeSpan CLI](CLI.md) + * note the log level can also be changed at runtime with the 'L' command via the [HomeSpan CLI](CLI.md) + * see [Message Logging](Logging.md) for complete details * `void reserveSocketConnections(uint8_t nSockets)` * reserves *nSockets* network sockets for uses **other than** by the HomeSpan HAP Server for HomeKit Controller Connections @@ -156,8 +156,8 @@ The following **optional** `homeSpan` methods enable additional features and pro * *timeZone* - specifies the time zone to use for setting the clock. Uses standard Unix timezone formatting as interpreted by Espressif IDF. Note the IDF uses a somewhat non-intuitive convention such that a timezone of "UTC+5:00" *subtracts* 5 hours from UTC time, and "UTC-5:00" *adds* 5 hours to UTC time. If *serverURL=NULL* this field is ignored; if *serverURL!=NULL* this field is required * *logURL* - the URL of the log page for this device. If unspecified, defaults to "status" * example: `homeSpan.enableWebLog(50,"pool.ntp.org","UTC-1:00","myLog");` creates a web log at the URL *http://HomeSpan-\[DEVICE-ID\].local:\[TCP-PORT\]/myLog* that will display the 50 most-recent log messages produced with the WEBLOG() macro. Upon start-up (after a WiFi connection has been established) HomeSpan will attempt to set the device clock by calling the server "pool.ntp.org" and adjusting the time to be 1 hour ahead of UTC. - * When attemping to connect to *timeServerURL*, HomeSpan waits 10 seconds for a response. If no response is received after the 10-second timeout period, HomeSpan assumes the server is unreachable and skips the clock-setting procedure. Use `setTimeServerTimeout()` to re-configure the 10-second timeout period to another value - * see [Message Logging Macros](#message-logging-macros) below for more details + * when attemping to connect to *timeServerURL*, HomeSpan waits 10 seconds for a response. If no response is received after the 10-second timeout period, HomeSpan assumes the server is unreachable and skips the clock-setting procedure. Use `setTimeServerTimeout()` to re-configure the 10-second timeout period to another value + * see [Message Logging](Logging.md) for complete details * `void setTimeServerTimeout(uint32_t tSec)` * changes the default 10-second timeout period HomeSpan uses when `enableWebLog()` tries set the device clock from an internet time server to *tSec* seconds From f2de496fa1b47bb4fcf63a9c5f5bc55d4cb07150 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Mon, 28 Mar 2022 18:48:28 -0500 Subject: [PATCH 058/100] Update Reference.md --- docs/Reference.md | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/docs/Reference.md b/docs/Reference.md index 437648d..7fe1647 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -375,19 +375,6 @@ might be used to save all the elements in *myArray* when called with just the '@ To create more than one user-defined command, simply create multiple instances of SpanUserCommand, each with its own single-letter name. Note that re-using the same single-letter name in an instance of SpanUserCommand over-rides any previous instances using that same letter. -## Message Logging Macros - -### *LOG1(X)* and *LOG2(X)* -### *LOG1(const char \*fmt, ...)* and *LOG2(const char \*fmt, ...)* - -Displays user-defined log messages on the Arduino Serial Monitor according to the log level specified with `setLogLevel()`, or as specified at runtime with the 'L' command via the [HomeSpan CLI](CLI.md). `LOG1()` messages will be output only if the log level is set to 1 or greater. `LOG2()` messages will be output only if the log level is set to 2. - -* In the first form (e.g. `LOG1(X)`), the macro calls `Serial.print(X)`. The argument *X* can be any variable recognized by the Arduino `Serial.print()` function. For example, `int val=255; LOG1(val);` outputs "255" to the Serial Monitor -* In the second form (e.g. `LOG1(const char *fmt, ...)`), the macro calls `Serial.printf(fmt, ...)` enabling you to create printf-style formatted output. For example, `int val=255; LOG1("The value is: %X",val);` outputs the "The value is: FF" to the Serial Monitor -* See [Example 9 - MessageLogging](https://github.com/HomeSpan/HomeSpan/blob/master/docs/Tutorials.md#example-9---messagelogging) for a tutorial sketch demonstrating these functions - -### WEBLOG(const char *fmt, ...) - ## Custom Characteristics and Custom Services Macros ### *CUSTOM_CHAR(name,uuid,perms,format,defaultValue,minValue,maxValue,staticRange)* From 459360c4cb3549ffb86cc096969f52853c5734c9 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Mon, 28 Mar 2022 18:51:23 -0500 Subject: [PATCH 059/100] Update Logging.md --- docs/Logging.md | 41 +++++++++-------------------------------- 1 file changed, 9 insertions(+), 32 deletions(-) diff --git a/docs/Logging.md b/docs/Logging.md index 211879e..a2a6d43 100644 --- a/docs/Logging.md +++ b/docs/Logging.md @@ -1,38 +1,15 @@ # Message Logging -HomeSpan supports Over-the-Air (OTA) updates, which allows you to *wirelessly* upload sketches directly from the Arduino IDE - no serial connection needed. To activate this feature for your sketch, simply call the method `homeSpan.enableOTA()` prior to calling `homeSpan.begin()`. +### *LOG1(X)* and *LOG2(X)* +### *LOG1(const char \*fmt, ...)* and *LOG2(const char \*fmt, ...)* -When a HomeSpan sketch is run with OTA enabled, the device shows up as a "network" port that can be selected under the *Tools → Port* menu in the Arduino IDE. Once selected, the IDE will direct all uploads to the device via WiFi instead of looking for it on a serial port. Note that you can upload via OTA even if your device is still connected to a serial port, but the Arduino IDE does not presently support multiple port connections at the same time. If you select a "network" port, the IDE will automatically close the Serial Monitor if it is open. To re-instate uploads via the "serial" port, simply choose that port from the *Tools → Port* menu in the Arduino IDE. Uploading via the serial port is always possible regardless of whether you have enabled OTA for a sketch. - -By default, HomeSpan requires the use of a password whenever you begin an OTA upload. The default OTA password is "homespan-ota". The Arduino will prompt you for this password upon your first attempt to upload a sketch to a newly-connected device. However, once the password for a specific device is entered, the Arduino IDE retains it in memory as long as the IDE is running, thereby saving you from having to type it again every time you re-upload a sketch via OTA. - -You can change the password for a HomeSpan device from the [HomeSpan CLI](CLI.md) with the 'O' command. Similar to a device's Setup Code, HomeSpan saves a non-recoverable hashed version of the OTA password you specify in non-volatile storage (NVS). If you forget the password you specified, you'll need to create a new one using the 'O' command, or you can restore the default OTA password by fully erasing the NVS with the 'E' command. - -> :exclamation: Though not recommended, you can override the requirement for a password when enabling OTA for your sketch by including *false* as a parameter to the enabling method as such: `homeSpan.enableOTA(false)`. Use with caution! Anyone who can access the device over your network will now be able to upload a new sketch. - -Note that in in order for OTA to properly operate, your sketch must be compiled with a partition scheme that includes OTA partitions. Partition schemes are found under the *Tools → Partition Scheme* menu of the Arduino IDE. Select a scheme that indicates it supports OTA. Note that schemes labeled "default" usually include OTA partitions. If unsure, try it out. HomeSpan will let you know if it does or does not. - -This is because HomeSpan checks that a sketch has been compiled with OTA partitions if OTA has been enabled for that sketch. If OTA has been enabled but HomeSpan does not find any OTA partitions, it will indicate it cannot start the OTA Server via a warning message sent to the Serial Monitor immediately after WiFi connectivity has been established. Otherwise it will output a confirmation message indicating the OTA Server has sucessfully started. - -### OTA Safe Load - -HomeSpan includes two additional safety checks when using OTA to upload a sketch: - -1. HomeSpan checks to make sure the new sketch being uploaded is also another HomeSpan sketch. If not, HomeSpan will reject the new sketch and report an OTA error back to the Arduino IDE after the new sketch is uploaded, but before the device reboots. Instead, HomeSpan will close the OTA connection and resume normal operations based on the existing sketch without rebooting. The purpose of this safety check is to prevent you from accidentally uploading a non-HomeSpan sketch onto a remote device, making it impossible for you to re-upload the correct sketch without retreiving the remote device and connecting to you computer via the serial port. - -1. After a successful upload of a new HomeSpan sketch via OTA, HomeSpan will check that the new HomeSpan sketch just loaded *also* has OTA enabled. This check occurs after HomeSpan is rebooted with the new sketch. If HomeSpan does not find OTA enabled, it will mark the current partition as invalid and reboot the device, causing the device to "roll back" to the previous version of the sketch that had OTA enabled. The purpose of this safety check is to ensure you do not use OTA to upload a new HomeSpan sketch to a remote device, but failed to enable OTA in the new HomeSpan sketch. If you did this you would be locked out of making any further updated via OTA and would instead need to retreive the remote device and connect it to your computer via the serial port. - -Note that these check are *only* applicable when uploading sketches via OTA. They are ignored whenever sketches are uploaded via the serial port. Also, though these safety checks are enabled by default, they can be disabled when you first enable OTA by setting the second (optional) argument to *false* as such: `homeSpan.enableOTA(..., false)`. See the API for details. - -### OTA Tips and Tricks - -* The name of the device HomeSpan uses for OTA is the same as the name you assigned in your call to `homeSpan.begin()`. If you have multiple devices you intend to maintain with OTA, use `homeSpan.begin()` to give them each different names so you can tell them apart when selecting which one to connect to from the Arduino IDE. - -* Use the `homeSpan.setSketchVersion()` method to set a version for your sketch (see the [HomeSpan API](Reference.md) for details). If specified, HomeSpan will include the sketch version as part of its HAP MDNS broadcast. This allows you determine which version of a sketch is running on a remote HomeSpan device, even if you can't plug it into a serial port for use with the Arduino Serial Monitor. In addition to the sketch version, HomeSpan also includes other fields in its MDNS broadcast that are useful in identifying the device: the version number of the HomeSpan *library* used to compile the sketch, a field indicating whether or not OTA is enabled for the sketch, the version number of the Arduino-ESP32 library used when compiling, and the type of board (e.g. *feather_esp32*). - -* If a sketch you've uploaded with OTA does not operate as expected, you can continue making modifications to the code and re-upload again. Or, you can upload a prior version that was working properly. However, the Safe Load features described above cannot protect against a HomeSpan sketch that has major run-time problems, such as causing a kernel panic that leads to an endless cycle of device reboots. If this happens, HomeSpan won't be able to run the OTA Server code, and further OTA updates will *not* be possible. Instead, you'll have to connect the device through a serial port to upload a new, working sketch. **For this reason you should always fully test out a new sketch on a local device connected to your computer *before* uploading it to a remote, hard-to-access device via OTA.** - -* Note that though the ESP IDF supports "automated" rollbacks that are designed to solve the problem of endless reboots after a bad upload, this feature is *not* enabled in the latest version of the Arudino-ESP32 library (2.0.2 at the time of this posting). +Displays user-defined log messages on the Arduino Serial Monitor according to the log level specified with `setLogLevel()`, or as specified at runtime with the 'L' command via the [HomeSpan CLI](CLI.md). `LOG1()` messages will be output only if the log level is set to 1 or greater. `LOG2()` messages will be output only if the log level is set to 2. + +* In the first form (e.g. `LOG1(X)`), the macro calls `Serial.print(X)`. The argument *X* can be any variable recognized by the Arduino `Serial.print()` function. For example, `int val=255; LOG1(val);` outputs "255" to the Serial Monitor +* In the second form (e.g. `LOG1(const char *fmt, ...)`), the macro calls `Serial.printf(fmt, ...)` enabling you to create printf-style formatted output. For example, `int val=255; LOG1("The value is: %X",val);` outputs the "The value is: FF" to the Serial Monitor +* See [Example 9 - MessageLogging](https://github.com/HomeSpan/HomeSpan/blob/master/docs/Tutorials.md#example-9---messagelogging) for a tutorial sketch demonstrating these functions + +### WEBLOG(const char *fmt, ...) --- From c54e355a613802eba9f2f1cfb78b1548d36746d4 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Mon, 28 Mar 2022 18:56:31 -0500 Subject: [PATCH 060/100] Update Reference.md --- docs/Reference.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Reference.md b/docs/Reference.md index 7fe1647..eccd129 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -380,7 +380,7 @@ To create more than one user-defined command, simply create multiple instances o ### *CUSTOM_CHAR(name,uuid,perms,format,defaultValue,minValue,maxValue,staticRange)* ### *CUSTOM_CHAR_STRING(name,uuid,perms,defaultValue)* -Creates a custom Characteristic that can be added to any Service. Custom Characteristics are generally ignored by the Home App but may be used by other third-party applications (such as *Eve for HomeKit*). The first form should be used create numerical Characterstics (e.g., UINT8, BOOL...). The second form is used to String-based Characteristics. Parameters are as follows (note that quotes should NOT be used in any of the macro parameters, except for defaultValue): +Creates a custom Characteristic that can be added to any Service. Custom Characteristics are generally ignored by the Home App but may be used by other third-party applications (such as *Eve for HomeKit*). The first form should be used create numerical Characterstics (e.g., UINT8, BOOL...). The second form is used to String-based Characteristics. Parameters are as follows (note that quotes should NOT be used in any of the macro parameters, except for *defaultValue* when applied to a STRING-based Characteristic): * *name* - the name of the custom Characteristic. This will be added to the Characteristic namespace so that it is accessed the same as any HomeSpan Characteristic * *uuid* - the UUID of the Characteristic as defined by the manufacturer. Must be *exactly* 36 characters in the form XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX, where *X* represent a valid hexidecimal digit. Leading zeros are required if needed as described more fully in HAP-R2 Section 6.6.1 From ce343be55d9b1d25e368e32cd83347ce4a19dea6 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Mon, 28 Mar 2022 21:42:13 -0500 Subject: [PATCH 061/100] Update ServiceList.md --- docs/ServiceList.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/ServiceList.md b/docs/ServiceList.md index 54c1b8e..52af85d 100644 --- a/docs/ServiceList.md +++ b/docs/ServiceList.md @@ -176,6 +176,22 @@ Additionally, when first starting up, HomeSpan begins by validating the device's |Volume|uint8_t|0| |WaterLevel|double|0| + +### HAP Format Codes (HAP-R2 Table 6-5) + +|HAP-R2 Format Code|HomeSpan C++ Type| +|------------------|-----------------| +|BOOL|boolean| +|UINT8|uint8_t| +|UINT16|uint16_t| +|UINT32|uint32_t| +|UINT64|uint64_t| +|INT|int| +|FLOAT|double| +|STRING|char \*| +|TLV8|(not implemented)| +|DATA|(not implemented)| + --- [↩️](README.md) Back to the Welcome page From c7a515b4be578724ff0e01212ba9938e628e0814 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Mon, 28 Mar 2022 21:48:13 -0500 Subject: [PATCH 062/100] Update Reference.md --- docs/Reference.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Reference.md b/docs/Reference.md index eccd129..fdf2b67 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -385,7 +385,7 @@ Creates a custom Characteristic that can be added to any Service. Custom Charac * *name* - the name of the custom Characteristic. This will be added to the Characteristic namespace so that it is accessed the same as any HomeSpan Characteristic * *uuid* - the UUID of the Characteristic as defined by the manufacturer. Must be *exactly* 36 characters in the form XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX, where *X* represent a valid hexidecimal digit. Leading zeros are required if needed as described more fully in HAP-R2 Section 6.6.1 * *perms* - additive list of permissions as described in HAP-R2 Table 6-4. Valid values are PR, PW, EV, AA, TW, HD, and WR -* *format* - specifies the format of the Characteristic value, as described in HAP-R2 Table 6-5. Valid value are BOOL, UINT8, UINT16, UNIT32, UINT64, INT, and FLOAT. Note that the HomeSpan does not presently support the TLV8 or DATA formats. Not applicable for Strings-based Characteristics +* *format* - specifies the format of the Characteristic value, as described in HAP-R2 Table 6-5. Valid value are BOOL, UINT8, UINT16, UNIT32, UINT64, INT, and FLOAT (note that the HomeSpan does not presently support the TLV8 or DATA formats). Not applicable for Strings-based Characteristics * *defaultValue* - specifies the default value of the Characteristic if not defined during instantiation * *minValue* - specifies the default minimum range for a valid value, which may be able to be overriden by a call to `setRange()`. Not applicable for Strings-based Characteristics * *minValue* - specifies the default minimum range for a valid value, which may be able to be overriden by a call to `setRange()`. Not applicable for Strings-based Characteristics From 7a3b07f65d76215b2a2471a0a0833978484f064c Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Wed, 30 Mar 2022 06:28:24 -0500 Subject: [PATCH 063/100] Update Logging.md --- docs/Logging.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/Logging.md b/docs/Logging.md index a2a6d43..43c348d 100644 --- a/docs/Logging.md +++ b/docs/Logging.md @@ -1,5 +1,26 @@ # Message Logging +HomeSpan includes three types of message logging: + +* **HomeSpan Log Messages** - these are messages produced by HomeSpan and output to the Arduino Serial Monitor. The messages are categorized according to 3 levels of detail + + * Level-0 messages contain all HomeSpan configuration data and some basic status information + * Level-1 adds in a more verbose set of status messages + * Level-2 further includes all HAP communication packages to and from the HomeSpan device. + * Users can programmatically control which log messages are output to the Serial Monitor by setting the log level using the homeSpan method `setLogLevel(uint8_t level)` as described in the [HomeSpan API Reference](API.md). Level-0 messages are always output. Level-1 messages are only output if the log level is set to 1 or greater. Level-2 messages are only output if the log level is set to to 2 + * The log level can also be changed dynamically via the Serial Monitor at any time via the 'L' as described in the [HomeSpan CLI](CLI.md). + +1. **User Log Messages** - these are messages produced by the user and output to the Arduino Serial. User can output messages using either the Arduino standard Serial.print(x) method, or the + + + += top-level HomeSpan status messages, and any messages output by the user using Serial.print() or Serial.printf() (default) +1 = all HomeSpan status messages, and any LOG1() messages specified in the sketch by the user +2 = all HomeSpan status messages plus all HAP communication packets to and from the HomeSpan device, as well as all LOG1() and LOG2() messages specified in the sketch by the user + + + + ### *LOG1(X)* and *LOG2(X)* ### *LOG1(const char \*fmt, ...)* and *LOG2(const char \*fmt, ...)* From 6a3e36121441fbb7a77b78a5f3e7a97f88330136 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Wed, 30 Mar 2022 22:03:42 -0500 Subject: [PATCH 064/100] Update Logging.md --- docs/Logging.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/Logging.md b/docs/Logging.md index 43c348d..fbe5df0 100644 --- a/docs/Logging.md +++ b/docs/Logging.md @@ -1,8 +1,10 @@ # Message Logging -HomeSpan includes three types of message logging: +HomeSpan includes a variety of message logs and message logging functions as described below. -* **HomeSpan Log Messages** - these are messages produced by HomeSpan and output to the Arduino Serial Monitor. The messages are categorized according to 3 levels of detail +### HomeSpan Log Messages + +HomeSpan these are messages produced by HomeSpan and output to the Arduino Serial Monitor. The messages are categorized according to 3 levels of detail * Level-0 messages contain all HomeSpan configuration data and some basic status information * Level-1 adds in a more verbose set of status messages From b564d25f61e4a763920996e49eb53eb269e79c04 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 2 Apr 2022 22:06:53 -0500 Subject: [PATCH 065/100] Update Logging.md --- docs/Logging.md | 38 +++++++++++++++----------------------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/docs/Logging.md b/docs/Logging.md index fbe5df0..36cfe84 100644 --- a/docs/Logging.md +++ b/docs/Logging.md @@ -1,38 +1,30 @@ # Message Logging -HomeSpan includes a variety of message logs and message logging functions as described below. +HomeSpan includes a variety of message logs with different levels of verbosity, as well built-in methods to create your own log messages and web logs. -### HomeSpan Log Messages +## HomeSpan Log Messages -HomeSpan these are messages produced by HomeSpan and output to the Arduino Serial Monitor. The messages are categorized according to 3 levels of detail +HomeSpan log messages are typically output directly to the Arduino Serial Monitor with three possible levels of verbosity: - * Level-0 messages contain all HomeSpan configuration data and some basic status information - * Level-1 adds in a more verbose set of status messages - * Level-2 further includes all HAP communication packages to and from the HomeSpan device. - * Users can programmatically control which log messages are output to the Serial Monitor by setting the log level using the homeSpan method `setLogLevel(uint8_t level)` as described in the [HomeSpan API Reference](API.md). Level-0 messages are always output. Level-1 messages are only output if the log level is set to 1 or greater. Level-2 messages are only output if the log level is set to to 2 - * The log level can also be changed dynamically via the Serial Monitor at any time via the 'L' as described in the [HomeSpan CLI](CLI.md). +|Log Level|Output| +|---------|------| +|Level 0|HomeSpan configuration data and some basic status information| +|Level 1|Eveything in Level 0 plus additional and more verbose status messages| +|Level 2|Everything im Level 1 plus all HAP communication packages sent to and from the HomeSpan device| -1. **User Log Messages** - these are messages produced by the user and output to the Arduino Serial. User can output messages using either the Arduino standard Serial.print(x) method, or the +You can set the *Log Level* in your sketch using the method `homeSpan.setLogLevel(uint8_t level)` as described in the [HomeSpan API Reference](API.md). Level 0 messages are always output; Level 1 messages are only output if the *Log Level* is set to 1 or greater; and Level 2 messages are only output if the *Log Level* is set to to 2. The *Log Level* can also be changed dynamically via the Serial Monitor at any time by typing either the 'L0', 'L1', or 'L2' as described in the [HomeSpan CLI](CLI.md). +## User-Defined Log Messages +You can add your own log messages to any sketch using HomeSpan's **LOG0()**, **LOG1()**, and **LOG2()** macros. Messages created with these macros will be output to the Arduino Serial Monitor according the *Log Level* setting described above. Each **LOGn()** macro (where n=\[0,2\]) is available in two flavors depending on the number of arguments specified: -= top-level HomeSpan status messages, and any messages output by the user using Serial.print() or Serial.printf() (default) -1 = all HomeSpan status messages, and any LOG1() messages specified in the sketch by the user -2 = all HomeSpan status messages plus all HAP communication packets to and from the HomeSpan device, as well as all LOG1() and LOG2() messages specified in the sketch by the user +* `LOGn(val)` - when only one argument is specified, HomeSpan outputs *val* using the standard Arduino `Serial.print(val)` method, which means *val* can be nearly any timvariable type. The downside is you have no control over the format. For example, `int n=255; LOG1(n);` outputs the number "255" to the Arduino Serial Monitor, provided that the *Log Level* is set to 1 or greater. +* `LOGn(const char *fmt, ...)` - when more than one argument is specified, HomeSpan outputs the message using the ESP32 `Serial.printf(fmt, ...)` method, which allows you to format messages with a variable number of arguments using standard C++ *printf* conventions. For example, `int n=255; LOG2("The value is 0x%X",n);` outputs the message "The value is 0xFF" to the Arduino Serial Monitor, provided that the *Log Level* is set to 2. - - -### *LOG1(X)* and *LOG2(X)* -### *LOG1(const char \*fmt, ...)* and *LOG2(const char \*fmt, ...)* - -Displays user-defined log messages on the Arduino Serial Monitor according to the log level specified with `setLogLevel()`, or as specified at runtime with the 'L' command via the [HomeSpan CLI](CLI.md). `LOG1()` messages will be output only if the log level is set to 1 or greater. `LOG2()` messages will be output only if the log level is set to 2. +See [Example 9 - MessageLogging](https://github.com/HomeSpan/HomeSpan/blob/master/docs/Tutorials.md#example-9---messagelogging) for a tutorial sketch demonstrating these macros. -* In the first form (e.g. `LOG1(X)`), the macro calls `Serial.print(X)`. The argument *X* can be any variable recognized by the Arduino `Serial.print()` function. For example, `int val=255; LOG1(val);` outputs "255" to the Serial Monitor -* In the second form (e.g. `LOG1(const char *fmt, ...)`), the macro calls `Serial.printf(fmt, ...)` enabling you to create printf-style formatted output. For example, `int val=255; LOG1("The value is: %X",val);` outputs the "The value is: FF" to the Serial Monitor -* See [Example 9 - MessageLogging](https://github.com/HomeSpan/HomeSpan/blob/master/docs/Tutorials.md#example-9---messagelogging) for a tutorial sketch demonstrating these functions - -### WEBLOG(const char *fmt, ...) +## Web Logging --- From ed306ff9ceb50ab9bb327ea321bafc0b4db027fe Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 2 Apr 2022 22:07:29 -0500 Subject: [PATCH 066/100] Update Logging.md --- docs/Logging.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Logging.md b/docs/Logging.md index 36cfe84..419411a 100644 --- a/docs/Logging.md +++ b/docs/Logging.md @@ -22,7 +22,7 @@ You can add your own log messages to any sketch using HomeSpan's **LOG0()**, **L * `LOGn(const char *fmt, ...)` - when more than one argument is specified, HomeSpan outputs the message using the ESP32 `Serial.printf(fmt, ...)` method, which allows you to format messages with a variable number of arguments using standard C++ *printf* conventions. For example, `int n=255; LOG2("The value is 0x%X",n);` outputs the message "The value is 0xFF" to the Arduino Serial Monitor, provided that the *Log Level* is set to 2. -See [Example 9 - MessageLogging](https://github.com/HomeSpan/HomeSpan/blob/master/docs/Tutorials.md#example-9---messagelogging) for a tutorial sketch demonstrating these macros. +See [Example 9 - MessageLogging](Tutorials.md#example-9---messagelogging) for a tutorial sketch demonstrating these macros. ## Web Logging From 240a995c86bbb9d9d902eb3ac3919bc974e854f0 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 2 Apr 2022 22:10:05 -0500 Subject: [PATCH 067/100] Added LOG0() macro --- src/Settings.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Settings.h b/src/Settings.h index 406f991..2c2279b 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -103,6 +103,7 @@ // Message Log Level Control Macros // // 0=Minimal, 1=Informative, 2=All // +#define LOG0(format,...) Serial.print ##__VA_OPT__(f)(format __VA_OPT__(,) __VA_ARGS__) #define LOG1(format,...) if(homeSpan.logLevel>0)Serial.print ##__VA_OPT__(f)(format __VA_OPT__(,) __VA_ARGS__) #define LOG2(format,...) if(homeSpan.logLevel>1)Serial.print ##__VA_OPT__(f)(format __VA_OPT__(,) __VA_ARGS__) From 91f6ecb95882f8413a315e3d8a317882b75f5982 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 3 Apr 2022 07:21:43 -0500 Subject: [PATCH 068/100] Updated Message Logging Example 9 Added LOG0() and variadic LOG1() messages. Also needed to rename Accessory from "On/Off" to "On-Off" since HomeKit no longer allows "/" characters! --- .../09-MessageLogging/09-MessageLogging.ino | 29 +++++--- examples/09-MessageLogging/DEV_LED.h | 68 ++++++++----------- 2 files changed, 47 insertions(+), 50 deletions(-) diff --git a/examples/09-MessageLogging/09-MessageLogging.ino b/examples/09-MessageLogging/09-MessageLogging.ino index 26f5081..eefb533 100644 --- a/examples/09-MessageLogging/09-MessageLogging.ino +++ b/examples/09-MessageLogging/09-MessageLogging.ino @@ -42,27 +42,36 @@ void setup() { // HomeSpan sends a variety of messages to the Serial Monitor of the Arduino IDE whenever the device is connected - // to a computer. Message output is performed either by the usual Serial.print() function, or by one of two macros, - // LOG1() and LOG2(). These two macros call Serial.print() depending on HomeSpan's Log Level setting. A setting - // of 0 means that LOG1() and LOG2() messages are ignored. A setting of 1 causes HomeSpan to print LOG1() messages - // to the Serial Monitor, but ignores LOG2() message. And a setting of 2 causes HomeSpan to print both LOG1() and - // LOG2() messages. + // to a computer. Message output can be performed either by the usual Serial.print() or Serial.printf() functions, + // or by one of three user macros: LOG0(), LOG1() and LOG2(). These three macros output messages to the Serial Monitor + // depending on HomeSpan's Log Level setting: + + // at a setting of 0, only LOG0() message are output; LOG1() and LOG2() messages are ignored + // at a setting of 1, both LOG0() and LOG1() messages are output; LOG2() messages are ignored + // at a setting of 2, all LOG0(), LOG1(), and LOG2() messages are output // Example 9 illustrates how to add such log messages. The code is identical to Example 8 (without comments), except - // that Serial.print() and LOG1() messages have been added to DEV_LED.h. The Serial.print() messages will always be + // that LOG0() and LOG1() messages have been added to DEV_LED.h. The LOG0() messages will always be // output to the Arduino Serial Monitor. The LOG1() messages will only be output if the Log Level is set to 1 or 2. - // The setLogLevel() method of homeSpan is used to change the log level as follows: + // The setLogLevel() method of homeSpan can used to change the log level as follows: // homeSpan.setLogLevel(0) - sets Log Level to 0 // homeSpan.setLogLevel(1) - sets Log Level to 1 // homeSpan.setLogLevel(2) - sets Log Level to 2 - // The method should be called BEFORE homeSpan.begin() - see below for proper use. + // The method should be called BEFORE homeSpan.begin() - see below for proper use. Note that the Log Level + // can also be changed dynamically during runtime via the HomeSpan CLI by typing 'L0', 'L1', or 'L2' into the Serial Monitor + + // There are two forms of the LOG0(), LOG1(), and LOG2() macros. The first form takes only a single argument and outputs + // messsges using the Serial.print(var) function. This allows you to output any single variable or text message, but does not allow you + // to control the format, or to output more than one variable at a time. The second form take multiple arguments, where the first + // is a standard C++ formatting string, and any remaining arguments are consumed according to the format string. This form + // utilizes the variadic Serial.printf(char *fmt [,var1, var2...]) function. // RECOMMENDATION: Since a HomeSpan ESP32 is meant to be physically connected to real-world devices, you may find // yourself with numerous ESP32s each configured with a different set of Accessories. To aid in identification - // you may want to add Serial.print() statements containing some sort of initialization message to the constructors for + // you may want to add LOG0() statements containing some sort of initialization message to the constructors for // each derived Service, such as DEV_LED. Doing so allows HomeSpan to "report" on its configuration upon start-up. See // DEV_LED for examples. @@ -82,7 +91,7 @@ void setup() { // Defines an ON/OFF LED Accessory attached to pin 16 new SpanAccessory(); - new DEV_Identify("On/Off LED","HomeSpan","123-ABC","20mA LED","0.9",0); + new DEV_Identify("On-Off LED","HomeSpan","123-ABC","20mA LED","0.9",0); new DEV_LED(16); // Defines a Dimmable (PWM-driven) LED Accessory attached to pin 17 diff --git a/examples/09-MessageLogging/DEV_LED.h b/examples/09-MessageLogging/DEV_LED.h index 50ad054..94726e8 100644 --- a/examples/09-MessageLogging/DEV_LED.h +++ b/examples/09-MessageLogging/DEV_LED.h @@ -17,22 +17,23 @@ struct DEV_LED : Service::LightBulb { // ON/OFF LED pinMode(ledPin,OUTPUT); // Here we output log messages when the constructor is initially called. - // We use Serial.print() since to ensure the message is always output - // regardless of the VERBOSITY setting. + // We use LOG0() to ensure the message is always output regardless of the + // LOG Level setting. Note this uses the single-argument form of LOG(), so + // multiple calls are needed to create a complete message - Serial.print("Configuring On/Off LED: Pin="); // initialization message - Serial.print(ledPin); - Serial.print("\n"); + LOG0("Configuring On/Off LED: Pin="); // initialization message + LOG0(ledPin); + LOG0("\n"); } // end constructor boolean update(){ // update() method - // Here we output log messages whenever update() is called, - // which is helpful for debugging purposes if your physical device - // is not functioning as expected. Since it's just for debugging, - // we use LOG1() instead of Serial.print(). Note we can output - // both the current as well as the new power settings. + // Here we output log messages whenever update() is called, which is helpful + // for debugging purposes if your physical device is not functioning as expected. + // Since it's just for debugging, we use LOG1() instead of LOG0(). Note we can + // output both the current as well as the new power settings. We've again + // used the single-argument form of LOG() to create this message LOG1("Updating On/Off LED on pin="); LOG1(ledPin); @@ -64,33 +65,18 @@ struct DEV_DimmableLED : Service::LightBulb { // Dimmable LED level=new Characteristic::Brightness(50); // Brightness Characteristic with an initial value of 50% 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% + // Here we once again output log messages when the constructor is initially called. + // However, this time we use the multi-argument form of LOG() that resembles a + // standard printf() function, which makes for more compact code. + + LOG0("Configuring Dimmable LED: Pin=%d\n",pin); // initialization message + this->ledPin=new LedPin(pin); // configures a PWM LED for output to the specified pin - - // Here we output log messages when the constructor is initially called. - // We use Serial.print() since to ensure the message is always output - // regardless of the VERBOSITY setting. Note that ledPin has a method getPin() - // that retrieves the pin number so you don't need to store it separately. - - Serial.print("Configuring Dimmable LED: Pin="); // initialization message - Serial.print(ledPin->getPin()); - Serial.print("\n"); } // end constructor boolean update(){ // update() method - // Here we output log messages whenever update() is called, - // which is helpful for debugging purposes if your physical device - // is not functioning as expected. Since it's just for debugging, - // we use LOG1() instead of Serial.print(). - - LOG1("Updating Dimmable LED on pin="); - LOG1(ledPin->getPin()); - LOG1(": Current Power="); - LOG1(power->getVal()?"true":"false"); - LOG1(" Current Brightness="); - LOG1(level->getVal()); - // Note that since Dimmable_LED has two updateable Characteristics, // HomeKit may be requesting either or both to be updated. We can // use the "isUpdated" flag of each Characteristic to output a message @@ -98,16 +84,18 @@ struct DEV_DimmableLED : Service::LightBulb { // Dimmable LED // Since update() is called whenever there is an update to at least // one of the Characteristics in a Service, either power, level, or both // will have its "isUpdated" flag set. - - if(power->updated()){ - LOG1(" New Power="); - LOG1(power->getNewVal()?"true":"false"); - } - if(level->updated()){ - LOG1(" New Brightness="); - LOG1(level->getNewVal()); - } + // As above, we use the multi-argument form of LOG() to create the messages + // Note that for DimmableLED, ledPin has a method getPin() that retrieves the + // pin number so you don't need to store it separately. + + LOG1("Updating Dimmable LED on pin=%dL Current Power=%s Current Brightness=%d",ledPin->getPin(),power->getVal()?"true":"false",level->getVal()); + + if(power->updated()) + LOG1(" New Power=%s",power->getNewVal()?"true":"false"); + + if(level->updated()) + LOG1(" New Brightness=%d",level->getNewVal()); LOG1("\n"); From 98f812ba0c870d1bcce11254bfe467ad9c98147e Mon Sep 17 00:00:00 2001 From: Gregg Date: Mon, 4 Apr 2022 06:13:30 -0500 Subject: [PATCH 069/100] Updating examples 1-7 to conform with latest iOS changes --- .../01-SimpleLightBulb/01-SimpleLightBulb.ino | 39 ++++---- .../02-TwoSimpleLightBulbs.ino | 32 +++---- .../03-CeilingFanWithLight.ino | 18 ++-- .../04-AdvancedCeilingFan.ino | 7 +- examples/05-WorkingLED/05-WorkingLED.ino | 12 +-- examples/06-DimmableLED/06-DimmableLED.ino | 12 +-- .../07-AccessoryNames/07-AccessoryNames.ino | 92 +++++++++++++++++++ examples/07-AccessoryNames/DEV_LED.h | 69 ++++++++++++++ src/Settings.h | 2 +- src/Span.h | 10 +- 10 files changed, 215 insertions(+), 78 deletions(-) create mode 100644 examples/07-AccessoryNames/07-AccessoryNames.ino create mode 100644 examples/07-AccessoryNames/DEV_LED.h diff --git a/examples/01-SimpleLightBulb/01-SimpleLightBulb.ino b/examples/01-SimpleLightBulb/01-SimpleLightBulb.ino index d17197d..8f1a706 100644 --- a/examples/01-SimpleLightBulb/01-SimpleLightBulb.ino +++ b/examples/01-SimpleLightBulb/01-SimpleLightBulb.ino @@ -66,30 +66,37 @@ void setup() { // Your HomeSpan code should be placed within the // The HomeSpan library creates a global object named "homeSpan" that encapsulates all HomeSpan functionality. // The begin() method is used to initialize HomeSpan and start all HomeSpan processes. - // The first two parameters are Category and Name, which are used by HomeKit to configure the icon and name of the device shown in your Home App - // when initially pairing your device. + // The first two parameters are Category and Name, which are used by HomeKit to configure the icon and name + // of the device shown in the Home App when initially pairing a HomeSpan device with your iPhone. + + // In addition, the Name you choose below will be used as the "default name" for all Accessory Tiles. When you first + // pair the device, the Home App will display this default name and allow you to change it (for each Accessory Tile) + // before pairing is complete. However, even after the device is paired you can always change the name of any + // Accessory Tile directly from the Home App via the set-up screen for any Tile. + + // IMPORTANT: The Name you choose below MUST BE UNIQUE across all your HomeSpan devices! homeSpan.begin(Category::Lighting,"HomeSpan LightBulb"); // initializes a HomeSpan device named "HomeSpan Lightbulb" with Category set to Lighting // Next, we construct a simple HAP Accessory Database with a single Accessory containing 3 Services, // each with their own required Characteristics. - new SpanAccessory(); // Begin by creating a new Accessory using SpanAccessory(), which takes no arguments - - new Service::AccessoryInformation(); // HAP requires every Accessory to implement an AccessoryInformation Service, which has 6 required Characteristics: - new Characteristic::Name("My Table Lamp"); // Name of the Accessory, which shows up on the HomeKit "tiles", and should be unique across Accessories - - // The next 4 Characteristics serve no function except for being displayed in HomeKit's setting panel for each Accessory. They are nevertheless required by HAP: - - new Characteristic::Manufacturer("HomeSpan"); // Manufacturer of the Accessory (arbitrary text string, and can be the same for every Accessory) - new Characteristic::SerialNumber("123-ABC"); // Serial Number of the Accessory (arbitrary text string, and can be the same for every Accessory) - new Characteristic::Model("120-Volt Lamp"); // Model of the Accessory (arbitrary text string, and can be the same for every Accessory) - new Characteristic::FirmwareRevision("0.9"); // Firmware of the Accessory (arbitrary text string, and can be the same for every Accessory) + new SpanAccessory(); // Begin by creating a new Accessory using SpanAccessory(), no arguments needed - // The last required Characteristic for the Accessory Information Service is the special Identify Characteristic. We'll learn more about this - // Characteristic in later examples. For now, you can just instantiate it without any arguments. + new Service::AccessoryInformation(); // HAP requires every Accessory to implement an AccessoryInformation Service + + // The only required Characteristic for the Accessory Information Service is the special Identify Characteristic. It takes no arguments: - new Characteristic::Identify(); // Create the required Identify + new Characteristic::Identify(); // Create the required Identify Characteristic + + // The Accessory Information Service also includes these four OPTIONAL Characteristics. They perform no function and are for + // informational purposes only --- their values are displayed in HomeKit's setting panel for each Accessory. Feel free + // to uncomment the lines and implement any combination of them, or none at all. + +// new Characteristic::Manufacturer("HomeSpan"); // Manufacturer of the Accessory (arbitrary text string, and can be the same for every Accessory) +// new Characteristic::SerialNumber("123-ABC"); // Serial Number of the Accessory (arbitrary text string, and can be the same for every Accessory) +// new Characteristic::Model("120-Volt Lamp"); // Model of the Accessory (arbitrary text string, and can be the same for every Accessory) +// new Characteristic::FirmwareRevision("0.9"); // Firmware of the Accessory (arbitrary text string, and can be the same for every Accessory) // *NOTE* HAP requires that the AccessoryInformation Service always be instantiated BEFORE any other Services, which is why we created it first. diff --git a/examples/02-TwoSimpleLightBulbs/02-TwoSimpleLightBulbs.ino b/examples/02-TwoSimpleLightBulbs/02-TwoSimpleLightBulbs.ino index dece10a..99588a6 100644 --- a/examples/02-TwoSimpleLightBulbs/02-TwoSimpleLightBulbs.ino +++ b/examples/02-TwoSimpleLightBulbs/02-TwoSimpleLightBulbs.ino @@ -44,18 +44,13 @@ void setup() { Serial.begin(115200); - homeSpan.begin(Category::Lighting,"HomeSpan LightBulbs"); // initialize HomeSpan - note the name is now "HomeSpan LightBulbs" + homeSpan.begin(Category::Lighting,"HomeSpan LightBulb"); // initializes a HomeSpan device named "HomeSpan Lightbulb" with Category set to Lighting // Here we create the first LightBulb Accessory just as in Example 1 - new SpanAccessory(); // Begin by creating a new Accessory using SpanAccessory(), which takes no arguments + new SpanAccessory(); // Begin by creating a new Accessory using SpanAccessory(), no arguments needed - new Service::AccessoryInformation(); // HAP requires every Accessory to implement an AccessoryInformation Service, which has 6 required Characteristics - new Characteristic::Name("My Table Lamp"); // Name of the Accessory, which shows up on the HomeKit "tiles", and should be unique across Accessories - new Characteristic::Manufacturer("HomeSpan"); // Manufacturer of the Accessory (arbitrary text string, and can be the same for every Accessory) - new Characteristic::SerialNumber("123-ABC"); // Serial Number of the Accessory (arbitrary text string, and can be the same for every Accessory) - new Characteristic::Model("120-Volt Lamp"); // Model of the Accessory (arbitrary text string, and can be the same for every Accessory) - new Characteristic::FirmwareRevision("0.9"); // Firmware of the Accessory (arbitrary text string, and can be the same for every Accessory) + new Service::AccessoryInformation(); // HAP requires every Accessory to implement an AccessoryInformation Service, with the required Identify Characteristic new Characteristic::Identify(); // Create the required Identify new Service::HAPProtocolInformation(); // Create the HAP Protcol Information Service @@ -64,16 +59,11 @@ void setup() { new Service::LightBulb(); // Create the Light Bulb Service new Characteristic::On(); // This Service requires the "On" Characterstic to turn the light on and off - // Now we create a second Accessory, which is just a duplicate of Accessory 1 with the exception of changing the Name from "My Table Lamp" to "My Floor Lamp" + // Now we create a second Accessory, which is just a duplicate of the first Accessory - new SpanAccessory(); // Begin by creating a new Accessory using SpanAccessory(), which takes no arguments + new SpanAccessory(); // Begin by creating a new Accessory using SpanAccessory(), no arguments needed - new Service::AccessoryInformation(); // HAP requires every Accessory to implement an AccessoryInformation Service, which has 6 required Characteristics - new Characteristic::Name("My Floor Lamp"); // Name of the Accessory, which shows up on the HomeKit "tiles", and should be unique across Accessories - new Characteristic::Manufacturer("HomeSpan"); // Manufacturer of the Accessory (arbitrary text string, and can be the same for every Accessory) - new Characteristic::SerialNumber("123-ABC"); // Serial Number of the Accessory (arbitrary text string, and can be the same for every Accessory) - new Characteristic::Model("120-Volt Lamp"); // Model of the Accessory (arbitrary text string, and can be the same for every Accessory) - new Characteristic::FirmwareRevision("0.9"); // Firmware of the Accessory (arbitrary text string, and can be the same for every Accessory) + new Service::AccessoryInformation(); // HAP requires every Accessory to implement an AccessoryInformation Service, with the required Identify Characteristic new Characteristic::Identify(); // Create the required Identify new Service::HAPProtocolInformation(); // Create the HAP Protcol Information Service @@ -82,7 +72,15 @@ void setup() { new Service::LightBulb(); // Create the Light Bulb Service new Characteristic::On(); // This Service requires the "On" Characterstic to turn the light on and off - // That's it - our device now has two Accessories! + // That's it - our device now has two Accessories, each displayed up as a separate Tile in the Home App! + + // Note that for a device with multiple Accessories, the Home App generates a default name for each Accessory Tile from the Name + // specified in homeSpan.begin(). In this case, the default name for the first Accessory Tile will be "HomeSpan Lightbulb", + // just as it was in Example 1, and the default name for the second Accessory Tile will be "HomeSpan Lightbulb 2". + + // You can of course change the name of each Accessory Tile from these defaults when prompted by the Home App during pairing. You + // can also change the name of any Accessory Tile, even after pairing, directly from the Home App by opening the settings page + // for any given Tile. // IMPORTANT: You should NOT have to re-pair your device with HomeKit when moving from Example 1 to Example 2. HomeSpan will note // that the Attribute Database has been updated, and will broadcast a new configuration number when the program restarts. This should diff --git a/examples/03-CeilingFanWithLight/03-CeilingFanWithLight.ino b/examples/03-CeilingFanWithLight/03-CeilingFanWithLight.ino index ec0ce8a..200da33 100644 --- a/examples/03-CeilingFanWithLight/03-CeilingFanWithLight.ino +++ b/examples/03-CeilingFanWithLight/03-CeilingFanWithLight.ino @@ -44,18 +44,11 @@ void setup() { Serial.begin(115200); // Start a serial connection - this is needed for you to type in your WiFi credentials - homeSpan.begin(Category::Fans,"HomeSpan Ceiling Fan"); // Begin a HomeSpan Session - note the Category has been set to "Fans" + homeSpan.begin(Category::Fans,"HomeSpan Ceiling Fan"); // Initialize HomeSpan - note the Category has been set to "Fans" - // We begin by creating a Light Bulb Accessory just as in Examples 1 and 2, but with Name now set to "My Ceiling Fan" - - new SpanAccessory(); // Begin by creating a new Accessory using SpanAccessory(), which takes no arguments + // We begin by creating a Light Bulb Accessory just as in Examples 1 and 2 - new Service::AccessoryInformation(); // HAP requires every Accessory to implement an AccessoryInformation Service, which has 6 required Characteristics - new Characteristic::Name("My Ceiling Fan"); // Name of the Accessory, which shows up on the HomeKit "tiles", and should be unique across Accessories - new Characteristic::Manufacturer("HomeSpan"); // Manufacturer of the Accessory (arbitrary text string, and can be the same for every Accessory) - new Characteristic::SerialNumber("123-ABC"); // Serial Number of the Accessory (arbitrary text string, and can be the same for every Accessory) - new Characteristic::Model("120-Volt Lamp"); // Model of the Accessory (arbitrary text string, and can be the same for every Accessory) - new Characteristic::FirmwareRevision("0.9"); // Firmware of the Accessory (arbitrary text string, and can be the same for every Accessory) + new Service::AccessoryInformation(); // HAP requires every Accessory to implement an AccessoryInformation Service, with the required Identify Characteristic new Characteristic::Identify(); // Create the required Identify new Service::HAPProtocolInformation(); // Create the HAP Protcol Information Service @@ -64,11 +57,14 @@ void setup() { new Service::LightBulb(); // Create the Light Bulb Service new Characteristic::On(); // This Service requires the "On" Characterstic to turn the light on and off - // Now we create the a Fan Service within this same Accessory + // Now we add a Fan Service within this same Accessory new Service::Fan(); // Create the Fan Service new Characteristic::Active(); // This Service requires the "Active" Characterstic to turn the fan on and off + // If everything worked correctly you should now see a single Tile named "HomeSpan Ceiling Fan" within the Home App. + // Clicking that Tile should open a window displaying two on/off controls - one for the Fan, and one for the Light. + // IMPORTANT: HomeKit Controllers often cache a lot of information. If your Controller does not update to match the above configuration, // simply select the Accessory in your Controller and under setting, select "Remove Accessory" and then re-pair. diff --git a/examples/04-AdvancedCeilingFan/04-AdvancedCeilingFan.ino b/examples/04-AdvancedCeilingFan/04-AdvancedCeilingFan.ino index f849b2b..87c76b7 100644 --- a/examples/04-AdvancedCeilingFan/04-AdvancedCeilingFan.ino +++ b/examples/04-AdvancedCeilingFan/04-AdvancedCeilingFan.ino @@ -49,12 +49,7 @@ void setup() { new SpanAccessory(); - new Service::AccessoryInformation(); - new Characteristic::Name("My Ceiling Fan"); - new Characteristic::Manufacturer("HomeSpan"); - new Characteristic::SerialNumber("123-ABC"); - new Characteristic::Model("120-Volt Lamp"); - new Characteristic::FirmwareRevision("0.9"); + new Service::AccessoryInformation(); new Characteristic::Identify(); new Service::HAPProtocolInformation(); diff --git a/examples/05-WorkingLED/05-WorkingLED.ino b/examples/05-WorkingLED/05-WorkingLED.ino index d1af08b..4316c6b 100644 --- a/examples/05-WorkingLED/05-WorkingLED.ino +++ b/examples/05-WorkingLED/05-WorkingLED.ino @@ -69,16 +69,11 @@ void setup() { Serial.begin(115200); - homeSpan.begin(Category::Lighting,"HomeSpan LEDs"); + homeSpan.begin(Category::Lighting,"HomeSpan LED"); new SpanAccessory(); new Service::AccessoryInformation(); - new Characteristic::Name("LED #1"); - new Characteristic::Manufacturer("HomeSpan"); - new Characteristic::SerialNumber("123-ABC"); - new Characteristic::Model("20mA LED"); - new Characteristic::FirmwareRevision("0.9"); new Characteristic::Identify(); new Service::HAPProtocolInformation(); @@ -102,11 +97,6 @@ void setup() { new SpanAccessory(); new Service::AccessoryInformation(); - new Characteristic::Name("LED #2"); - new Characteristic::Manufacturer("HomeSpan"); - new Characteristic::SerialNumber("123-ABC"); - new Characteristic::Model("20mA LED"); - new Characteristic::FirmwareRevision("0.9"); new Characteristic::Identify(); new Service::HAPProtocolInformation(); diff --git a/examples/06-DimmableLED/06-DimmableLED.ino b/examples/06-DimmableLED/06-DimmableLED.ino index b28fc82..71d255a 100644 --- a/examples/06-DimmableLED/06-DimmableLED.ino +++ b/examples/06-DimmableLED/06-DimmableLED.ino @@ -59,16 +59,11 @@ void setup() { Serial.begin(115200); - homeSpan.begin(Category::Lighting,"HomeSpan LEDs"); + homeSpan.begin(Category::Lighting,"HomeSpan LED"); new SpanAccessory(); new Service::AccessoryInformation(); - new Characteristic::Name("On/Off LED"); - new Characteristic::Manufacturer("HomeSpan"); - new Characteristic::SerialNumber("123-ABC"); - new Characteristic::Model("20mA LED"); - new Characteristic::FirmwareRevision("0.9"); new Characteristic::Identify(); new Service::HAPProtocolInformation(); @@ -79,11 +74,6 @@ void setup() { new SpanAccessory(); new Service::AccessoryInformation(); - new Characteristic::Name("Dimmable LED"); - new Characteristic::Manufacturer("HomeSpan"); - new Characteristic::SerialNumber("123-ABC"); - new Characteristic::Model("20mA LED"); - new Characteristic::FirmwareRevision("0.9"); new Characteristic::Identify(); new Service::HAPProtocolInformation(); diff --git a/examples/07-AccessoryNames/07-AccessoryNames.ino b/examples/07-AccessoryNames/07-AccessoryNames.ino new file mode 100644 index 0000000..5b2e0e5 --- /dev/null +++ b/examples/07-AccessoryNames/07-AccessoryNames.ino @@ -0,0 +1,92 @@ +/********************************************************************************* + * MIT License + * + * Copyright (c) 2020 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 // +// ------------------------------------------------ // +// // +// Example 7: Changing an Accessory's default name // +// // +//////////////////////////////////////////////////////////// + + +#include "HomeSpan.h" +#include "DEV_LED.h" + +void setup() { + + // As discusses in previous examples, the Home App automatically generate default names for each Accessory Tile + // based on the Name provided in the second argument of homeSpan.begin(). And though you can change these names + // both during, and anytime after, pairing, HAP also allows you to customize the default names themselves, so + // something more intuitive is presented to the user when the device is first paired. + + // Changing the default name for an Accessory is done by adding the optional Characteristic Name(char *tileName) to the + // Accessory Information Service. This causes the Home App to use "tileName" as the default name for an Accessory + // Tile instead of generating one from the name used in homeSpan.begin(). Howevever, there is one caveat: The Name() + // Characteristic has no affect when used in the first Accessory of a device. The default name of the first Accessory + // Tile will always be showsn by the Home App as the name specified in homeSpan.begin() regardless of whether or not + // the Name() Characteristic has been added to the Accessory Information Service. + + // Below is a replay of Example 6 showing how the Name Characteristic can be used to change the default names of the second, + // but not the first, Accessory. + + Serial.begin(115200); + + homeSpan.begin(Category::Lighting,"HomeSpan LED"); // Note this results in the default name of "HomeSpan LED", "HomeSpan LED 2", etc. for each Accessory Tile + + new SpanAccessory(); + + new Service::AccessoryInformation(); + new Characteristic::Identify(); + new Characteristic::Name("Simply LED"); // This use of Name() will be ignored by the Home App. The default name for the Accessory will continue to be shown as "HomeSpan LED" + + new Service::HAPProtocolInformation(); + new Characteristic::Version("1.1.0"); + + new DEV_LED(16); // create an on/off LED attached to pin 16 (same as in Example 5) + + new SpanAccessory(); + + new Service::AccessoryInformation(); + new Characteristic::Identify(); + new Characteristic::Name("Dimmable LED"); // This DOES change the default name for the Accessory from "HomeSpan LED 2" to "Dimmable LED" + + new Service::HAPProtocolInformation(); + new Characteristic::Version("1.1.0"); + + new DEV_DimmableLED(17); + +} // end of setup() + +////////////////////////////////////// + +void loop(){ + + homeSpan.poll(); + +} // end of loop() diff --git a/examples/07-AccessoryNames/DEV_LED.h b/examples/07-AccessoryNames/DEV_LED.h new file mode 100644 index 0000000..d9445e9 --- /dev/null +++ b/examples/07-AccessoryNames/DEV_LED.h @@ -0,0 +1,69 @@ + +//////////////////////////////////// +// DEVICE-SPECIFIC LED SERVICES // +//////////////////////////////////// + +#include "extras/PwmPin.h" // NEW! Include this HomeSpan "extra" to create LED-compatible PWM signals on one or more pins + +struct DEV_LED : Service::LightBulb { // ON/OFF LED + + int ledPin; // pin number defined for this LED + SpanCharacteristic *power; // reference to the On Characteristic + + DEV_LED(int ledPin) : Service::LightBulb(){ // constructor() method + + power=new Characteristic::On(); + this->ledPin=ledPin; + pinMode(ledPin,OUTPUT); + + } // end constructor + + boolean update(){ // update() method + + digitalWrite(ledPin,power->getNewVal()); + + return(true); // return true + + } // update +}; + +////////////////////////////////// + +// Here's the new code defining DEV_DimmableLED - changes from above are noted in the comments + +struct DEV_DimmableLED : Service::LightBulb { // Dimmable LED + + LedPin *ledPin; // NEW! Create reference to LED Pin instantiated below + SpanCharacteristic *power; // reference to the On Characteristic + SpanCharacteristic *level; // NEW! Create a reference to the Brightness Characteristic instantiated below + + DEV_DimmableLED(int pin) : Service::LightBulb(){ // constructor() method + + power=new Characteristic::On(); + + level=new Characteristic::Brightness(50); // NEW! Instantiate the Brightness Characteristic with an initial value of 50% (same as we did in Example 4) + level->setRange(5,100,1); // NEW! This sets the range of the Brightness to be from a min of 5%, to a max of 100%, in steps of 1% (different from Example 4 values) + + this->ledPin=new LedPin(pin); // NEW! Configures a PWM LED for output to the specified pin. Note pinMode() does NOT need to be called in advance + + } // end constructor + + boolean update(){ // update() method + + // Here we set the brightness of the LED by calling ledPin->set(brightness), where brightness=0-100. + // Note HomeKit sets the on/off status of a LightBulb separately from its brightness, which means HomeKit + // can request a LightBulb be turned off, but still retains the brightness level so that it does not need + // to be resent once the LightBulb is turned back on. + + // Multiplying the newValue of the On Characteristic ("power", which is a boolean) with the newValue of the + // Brightness Characteristic ("level", which is an integer) is a short-hand way of creating the logic to + // set the LED level to zero when the LightBulb is off, or to the current brightness level when it is on. + + ledPin->set(power->getNewVal()*level->getNewVal()); + + return(true); // return true + + } // update +}; + +////////////////////////////////// diff --git a/src/Settings.h b/src/Settings.h index 2c2279b..3da1fbb 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -36,7 +36,7 @@ #define HS_MAJOR 1 #define HS_MINOR 5 -#define HS_PATCH 0 +#define HS_PATCH 1 #define STRINGIFY(x) _STR(x) #define _STR(x) #x diff --git a/src/Span.h b/src/Span.h index c0155f7..c4d23a4 100644 --- a/src/Span.h +++ b/src/Span.h @@ -37,12 +37,12 @@ namespace Service { struct AccessoryInformation : SpanService { AccessoryInformation() : SpanService{"3E","AccessoryInformation"}{ - REQ(FirmwareRevision); REQ(Identify); - REQ(Manufacturer); - REQ(Model); - REQ(Name); - REQ(SerialNumber); + OPT(FirmwareRevision); + OPT(Manufacturer); + OPT(Model); + OPT(Name); + OPT(SerialNumber); OPT(HardwareRevision); }}; From 4ada3542cd20c5c0093d66530fdec9f887296b28 Mon Sep 17 00:00:00 2001 From: Gregg Date: Wed, 6 Apr 2022 06:21:42 -0500 Subject: [PATCH 070/100] Update 07-AccessoryNames.ino --- examples/07-AccessoryNames/07-AccessoryNames.ino | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/examples/07-AccessoryNames/07-AccessoryNames.ino b/examples/07-AccessoryNames/07-AccessoryNames.ino index 5b2e0e5..c99c967 100644 --- a/examples/07-AccessoryNames/07-AccessoryNames.ino +++ b/examples/07-AccessoryNames/07-AccessoryNames.ino @@ -31,6 +31,7 @@ // ------------------------------------------------ // // // // Example 7: Changing an Accessory's default name // +// to distinguish On/Off from Dimmable LEDs // // // //////////////////////////////////////////////////////////// @@ -45,15 +46,16 @@ void setup() { // both during, and anytime after, pairing, HAP also allows you to customize the default names themselves, so // something more intuitive is presented to the user when the device is first paired. - // Changing the default name for an Accessory is done by adding the optional Characteristic Name(char *tileName) to the - // Accessory Information Service. This causes the Home App to use "tileName" as the default name for an Accessory - // Tile instead of generating one from the name used in homeSpan.begin(). Howevever, there is one caveat: The Name() - // Characteristic has no affect when used in the first Accessory of a device. The default name of the first Accessory - // Tile will always be showsn by the Home App as the name specified in homeSpan.begin() regardless of whether or not - // the Name() Characteristic has been added to the Accessory Information Service. + // Changing the default name for an Accessory is done by adding an optional Name Characteristic to the + // Accessory Information Service. This causes the Home App to use the value of that Characteristic as the default name + // for an Accessory Tile, instead of generating one from the name used in homeSpan.begin(). + + // Howevever, there is one caveat: The Name Characteristic has no affect when used in the first Accessory of a device. + // Rather, the default name of the first Accessory Tile will always be shown by the Home App as the name specified in + // homeSpan.begin() regardless of whether or not the Name Characteristic has been added to the Accessory Information Service. // Below is a replay of Example 6 showing how the Name Characteristic can be used to change the default names of the second, - // but not the first, Accessory. + // but not the first, Accessory Tile. Serial.begin(115200); From 4d881c844714110716648a9211d28ed7925669cc Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Wed, 6 Apr 2022 06:28:53 -0500 Subject: [PATCH 071/100] Update Tutorials.md --- docs/Tutorials.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/Tutorials.md b/docs/Tutorials.md index 76b25d0..64e71f6 100644 --- a/docs/Tutorials.md +++ b/docs/Tutorials.md @@ -39,10 +39,8 @@ Example 6 changes Example 5 so that LED #2 is now dimmable, instead of just on/o * setting the brightness of an LED using the LedPin `set()` method * storing similar derived Service classes in the same \*.h file for ease of use -### [Example 7 - IdentifyRoutines](../examples/07-IdentifyRoutines) -Example 7 uses the encapsulation techniques illustrated in Examples 5 and 6 to derive an easier-to-use Identify Service from HomeSpan's AccessoryInformation Service. The example includes the implementation of an `update()` method that responds to HomeKit requests writing to the Identify Characteristic. New HomeSpan API topics covered in this example include: - -* storing dissimilar derived Service classes in the different \*.h files for better portability +### [Example 7 - AccessoryNames](../examples/07-AccessoryNames) +Example 7 demonstrates how the names of two LED Accessories created in Example 6 can be changed from the defaults generated by the Home App to something more useful and customized. ### [Example 8 - Bridges](../examples/08-Bridges) Example 8 is functionally identical to Example 7, except that instead of defining two Accessories (one for the on/off LED and one for the dimmable LED), we define three Accessories, where the first acts as a HomeKit Bridge. From d20df43a58ca859912f436c5d8ad300ea01445b6 Mon Sep 17 00:00:00 2001 From: Gregg Date: Fri, 8 Apr 2022 17:46:33 -0500 Subject: [PATCH 072/100] Eliminated HAP Protocol Service and updated Examples 1-8 Experimentation reveals that the HAP Protocol Information Service no longer seems to be required by HomeKit. Examples work fine without it. --- .../01-SimpleLightBulb/01-SimpleLightBulb.ino | 11 +- .../02-TwoSimpleLightBulbs.ino | 8 +- .../03-CeilingFanWithLight.ino | 5 +- .../04-AdvancedCeilingFan.ino | 7 +- examples/05-WorkingLED/05-WorkingLED.ino | 12 +- examples/06-DimmableLED/06-DimmableLED.ino | 12 +- .../07-AccessoryNames/07-AccessoryNames.ino | 14 +-- .../07-IdentifyRoutines.ino | 115 ------------------ examples/07-IdentifyRoutines/DEV_Identify.h | 65 ---------- examples/07-IdentifyRoutines/DEV_LED.h | 58 --------- examples/08-Bridges/08-Bridges.ino | 86 ++++++------- examples/08-Bridges/DEV_Identify.h | 38 ------ src/HomeSpan.cpp | 2 +- 13 files changed, 52 insertions(+), 381 deletions(-) delete mode 100644 examples/07-IdentifyRoutines/07-IdentifyRoutines.ino delete mode 100644 examples/07-IdentifyRoutines/DEV_Identify.h delete mode 100644 examples/07-IdentifyRoutines/DEV_LED.h delete mode 100644 examples/08-Bridges/DEV_Identify.h diff --git a/examples/01-SimpleLightBulb/01-SimpleLightBulb.ino b/examples/01-SimpleLightBulb/01-SimpleLightBulb.ino index 8f1a706..6487c48 100644 --- a/examples/01-SimpleLightBulb/01-SimpleLightBulb.ino +++ b/examples/01-SimpleLightBulb/01-SimpleLightBulb.ino @@ -1,7 +1,7 @@ /********************************************************************************* * MIT License * - * Copyright (c) 2020 Gregg E. Berman + * Copyright (c) 2020-2022 Gregg E. Berman * * https://github.com/HomeSpan/HomeSpan * @@ -98,14 +98,7 @@ void setup() { // Your HomeSpan code should be placed within the // new Characteristic::Model("120-Volt Lamp"); // Model of the Accessory (arbitrary text string, and can be the same for every Accessory) // new Characteristic::FirmwareRevision("0.9"); // Firmware of the Accessory (arbitrary text string, and can be the same for every Accessory) - // *NOTE* HAP requires that the AccessoryInformation Service always be instantiated BEFORE any other Services, which is why we created it first. - - // HAP also requires every Accessory (with the exception of those in Bridges, as we will see later) to implement the HAP Protocol Information Service. - // This Service supports a single required Characteristic that defines the version number of HAP used by the device. - // HAP Release R2 requires this version to be set to "1.1.0" - - new Service::HAPProtocolInformation(); // Create the HAP Protcol Information Service - new Characteristic::Version("1.1.0"); // Set the Version Characteristicto "1.1.0" as required by HAP + // *NOTE* HAP requires that the Accessory Information Service always be instantiated BEFORE any other Services, which is why we created it first. // Now that the required "informational" Services have been defined, we can finally create our Light Bulb Service diff --git a/examples/02-TwoSimpleLightBulbs/02-TwoSimpleLightBulbs.ino b/examples/02-TwoSimpleLightBulbs/02-TwoSimpleLightBulbs.ino index 99588a6..cf07904 100644 --- a/examples/02-TwoSimpleLightBulbs/02-TwoSimpleLightBulbs.ino +++ b/examples/02-TwoSimpleLightBulbs/02-TwoSimpleLightBulbs.ino @@ -1,7 +1,7 @@ /********************************************************************************* * MIT License * - * Copyright (c) 2020 Gregg E. Berman + * Copyright (c) 2020-2022 Gregg E. Berman * * https://github.com/HomeSpan/HomeSpan * @@ -52,9 +52,6 @@ void setup() { new Service::AccessoryInformation(); // HAP requires every Accessory to implement an AccessoryInformation Service, with the required Identify Characteristic new Characteristic::Identify(); // Create the required Identify - - new Service::HAPProtocolInformation(); // Create the HAP Protcol Information Service - new Characteristic::Version("1.1.0"); // Set the Version Characteristicto "1.1.0" as required by HAP new Service::LightBulb(); // Create the Light Bulb Service new Characteristic::On(); // This Service requires the "On" Characterstic to turn the light on and off @@ -65,9 +62,6 @@ void setup() { new Service::AccessoryInformation(); // HAP requires every Accessory to implement an AccessoryInformation Service, with the required Identify Characteristic new Characteristic::Identify(); // Create the required Identify - - new Service::HAPProtocolInformation(); // Create the HAP Protcol Information Service - new Characteristic::Version("1.1.0"); // Set the Version Characteristicto "1.1.0" as required by HAP new Service::LightBulb(); // Create the Light Bulb Service new Characteristic::On(); // This Service requires the "On" Characterstic to turn the light on and off diff --git a/examples/03-CeilingFanWithLight/03-CeilingFanWithLight.ino b/examples/03-CeilingFanWithLight/03-CeilingFanWithLight.ino index 200da33..dbeb060 100644 --- a/examples/03-CeilingFanWithLight/03-CeilingFanWithLight.ino +++ b/examples/03-CeilingFanWithLight/03-CeilingFanWithLight.ino @@ -1,7 +1,7 @@ /********************************************************************************* * MIT License * - * Copyright (c) 2020 Gregg E. Berman + * Copyright (c) 2020-2022 Gregg E. Berman * * https://github.com/HomeSpan/HomeSpan * @@ -50,9 +50,6 @@ void setup() { new Service::AccessoryInformation(); // HAP requires every Accessory to implement an AccessoryInformation Service, with the required Identify Characteristic new Characteristic::Identify(); // Create the required Identify - - new Service::HAPProtocolInformation(); // Create the HAP Protcol Information Service - new Characteristic::Version("1.1.0"); // Set the Version Characteristicto "1.1.0" as required by HAP new Service::LightBulb(); // Create the Light Bulb Service new Characteristic::On(); // This Service requires the "On" Characterstic to turn the light on and off diff --git a/examples/04-AdvancedCeilingFan/04-AdvancedCeilingFan.ino b/examples/04-AdvancedCeilingFan/04-AdvancedCeilingFan.ino index 87c76b7..560cbd1 100644 --- a/examples/04-AdvancedCeilingFan/04-AdvancedCeilingFan.ino +++ b/examples/04-AdvancedCeilingFan/04-AdvancedCeilingFan.ino @@ -1,7 +1,7 @@ /********************************************************************************* * MIT License * - * Copyright (c) 2020 Gregg E. Berman + * Copyright (c) 2020-2022 Gregg E. Berman * * https://github.com/HomeSpan/HomeSpan * @@ -50,10 +50,7 @@ void setup() { new SpanAccessory(); new Service::AccessoryInformation(); - new Characteristic::Identify(); - - new Service::HAPProtocolInformation(); - new Characteristic::Version("1.1.0"); + new Characteristic::Identify(); new Service::LightBulb(); new Characteristic::On(true); // NEW: Providing an argument sets its initial value. In this case it means the LightBulb will be turned on at start-up diff --git a/examples/05-WorkingLED/05-WorkingLED.ino b/examples/05-WorkingLED/05-WorkingLED.ino index 4316c6b..71a68b1 100644 --- a/examples/05-WorkingLED/05-WorkingLED.ino +++ b/examples/05-WorkingLED/05-WorkingLED.ino @@ -1,7 +1,7 @@ /********************************************************************************* * MIT License * - * Copyright (c) 2020 Gregg E. Berman + * Copyright (c) 2020-2022 Gregg E. Berman * * https://github.com/HomeSpan/HomeSpan * @@ -74,10 +74,7 @@ void setup() { new SpanAccessory(); new Service::AccessoryInformation(); - new Characteristic::Identify(); - - new Service::HAPProtocolInformation(); - new Characteristic::Version("1.1.0"); + new Characteristic::Identify(); // In Example 2 we instantiated a LightBulb Service and its "On" Characteristic here. We are now going to replace these two lines (by commenting them out)... @@ -97,10 +94,7 @@ void setup() { new SpanAccessory(); new Service::AccessoryInformation(); - new Characteristic::Identify(); - - new Service::HAPProtocolInformation(); - new Characteristic::Version("1.1.0"); + new Characteristic::Identify(); // new Service::LightBulb(); // Same as above, this line is deleted... // new Characteristic::On(); // This line is also deleted... diff --git a/examples/06-DimmableLED/06-DimmableLED.ino b/examples/06-DimmableLED/06-DimmableLED.ino index 71d255a..f81ea0f 100644 --- a/examples/06-DimmableLED/06-DimmableLED.ino +++ b/examples/06-DimmableLED/06-DimmableLED.ino @@ -1,7 +1,7 @@ /********************************************************************************* * MIT License * - * Copyright (c) 2020 Gregg E. Berman + * Copyright (c) 2020-2022 Gregg E. Berman * * https://github.com/HomeSpan/HomeSpan * @@ -64,20 +64,14 @@ void setup() { new SpanAccessory(); new Service::AccessoryInformation(); - new Characteristic::Identify(); - - new Service::HAPProtocolInformation(); - new Characteristic::Version("1.1.0"); + new Characteristic::Identify(); new DEV_LED(16); // create an on/off LED attached to pin 16 (same as in Example 5) new SpanAccessory(); new Service::AccessoryInformation(); - new Characteristic::Identify(); - - new Service::HAPProtocolInformation(); - new Characteristic::Version("1.1.0"); + new Characteristic::Identify(); new DEV_DimmableLED(17); // NEW! create a dimmable (PWM-driven) LED attached to pin 17. See new code at end of DEV_LED.h diff --git a/examples/07-AccessoryNames/07-AccessoryNames.ino b/examples/07-AccessoryNames/07-AccessoryNames.ino index c99c967..d1d7320 100644 --- a/examples/07-AccessoryNames/07-AccessoryNames.ino +++ b/examples/07-AccessoryNames/07-AccessoryNames.ino @@ -1,7 +1,7 @@ /********************************************************************************* * MIT License * - * Copyright (c) 2020 Gregg E. Berman + * Copyright (c) 2020-2022 Gregg E. Berman * * https://github.com/HomeSpan/HomeSpan * @@ -65,21 +65,15 @@ void setup() { new Service::AccessoryInformation(); new Characteristic::Identify(); - new Characteristic::Name("Simply LED"); // This use of Name() will be ignored by the Home App. The default name for the Accessory will continue to be shown as "HomeSpan LED" - - new Service::HAPProtocolInformation(); - new Characteristic::Version("1.1.0"); + new Characteristic::Name("Simple LED"); // This use of Name() will be ignored by the Home App. The default name for the Accessory will continue to be shown as "HomeSpan LED" - new DEV_LED(16); // create an on/off LED attached to pin 16 (same as in Example 5) + new DEV_LED(16); new SpanAccessory(); new Service::AccessoryInformation(); new Characteristic::Identify(); - new Characteristic::Name("Dimmable LED"); // This DOES change the default name for the Accessory from "HomeSpan LED 2" to "Dimmable LED" - - new Service::HAPProtocolInformation(); - new Characteristic::Version("1.1.0"); + new Characteristic::Name("Dimmable LED"); // This DOES change the default name for the Accessory from "HomeSpan LED 2" to "Dimmable LED" new DEV_DimmableLED(17); diff --git a/examples/07-IdentifyRoutines/07-IdentifyRoutines.ino b/examples/07-IdentifyRoutines/07-IdentifyRoutines.ino deleted file mode 100644 index 62d9f5c..0000000 --- a/examples/07-IdentifyRoutines/07-IdentifyRoutines.ino +++ /dev/null @@ -1,115 +0,0 @@ -/********************************************************************************* - * MIT License - * - * Copyright (c) 2020 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 // -// ------------------------------------------------ // -// // -// Example 7: Transforming AccessoryInformation into a // -// derived Service that implements the // -// Identify Characteristic // -// // -//////////////////////////////////////////////////////////// - -#include "HomeSpan.h" -#include "DEV_LED.h" -#include "DEV_Identify.h" // NEW! This is where we store all code for the DEV_Identify Service - -void setup() { - - // In Example 5 we saw how to create a derived Service to encapsulate all the functionality needed to implement DEV_LED - // in it's own DEV_LED.h file. Then, in Example 6 we extended that further by implementing DEV_DimmableLED. In this - // example we do the same for the AccessoryInformation Service. Note how AccessoryInformation, and all of its - // Characteristics, need to be defined for every Accessory. By deriving a new Service that implements a multi-argument - // constructor we can avoid having to separately create each required Characteristic every time. Creating a derived Service - // also allows us to implement device-specific code for the Identify Characteristic. We will call this derived Service - // DEV_Identify, and store its code in "DEV_Identify.h" which has already been included above. - - // As usual, all previous comments have been deleted and only new changes from the previous example are shown. - - // NOTE: To see how this works in practice, you'll need to unpair your device and re-pair it once the new code is loaded. - // This will allow oyu to activate the identify routines. - - Serial.begin(115200); - - homeSpan.begin(Category::Lighting,"HomeSpan LEDs"); - - new SpanAccessory(); - - // Rather than instantiate the AccessoryInformation Service and all of it's required Characteristics, - // we'll delete these line (comment them out)... - - // new Service::AccessoryInformation(); - // new Characteristic::Name("On/Off LED"); - // new Characteristic::Manufacturer("HomeSpan"); - // new Characteristic::SerialNumber("123-ABC"); - // new Characteristic::Model("20mA LED"); - // new Characteristic::FirmwareRevision("0.9"); - // new Characteristic::Identify(); - - // ...and replace them with this single line that implements everything above. See DEV_Identify.h for - // details on how this is defined. Note there is an extra argument at the end we set to 3. - // This optional argument will be used to run the identify routine (see code for details) - - new DEV_Identify("On/Off LED","HomeSpan","123-ABC","20mA LED","0.9",3); // NEW! This implements all the Characteristics above - - new Service::HAPProtocolInformation(); - new Characteristic::Version("1.1.0"); - - new DEV_LED(16); // create an on/off LED attached to pin 16 (same as in Example 5) - - new SpanAccessory(); - - // Same as above, we can replace all of this... - - // new Service::AccessoryInformation(); - // new Characteristic::Name("Dimmable LED"); - // new Characteristic::Manufacturer("HomeSpan"); - // new Characteristic::SerialNumber("123-ABC"); - // new Characteristic::Model("20mA LED"); - // new Characteristic::FirmwareRevision("0.9"); - // new Characteristic::Identify(); - - // ...with this (note we set last argument to 5 this time - see code for what this does) - - new DEV_Identify("Dimmable LED","HomeSpan","123-ABC","20mA LED","0.9",5); // NEW! This implements all the Characteristics above - - new Service::HAPProtocolInformation(); - new Characteristic::Version("1.1.0"); - - new DEV_DimmableLED(17); // create a dimmable (PWM-driven) LED attached to pin 17 - -} // end of setup() - -////////////////////////////////////// - -void loop(){ - - homeSpan.poll(); - -} // end of loop() diff --git a/examples/07-IdentifyRoutines/DEV_Identify.h b/examples/07-IdentifyRoutines/DEV_Identify.h deleted file mode 100644 index 01e591b..0000000 --- a/examples/07-IdentifyRoutines/DEV_Identify.h +++ /dev/null @@ -1,65 +0,0 @@ - -////////////////////////////////// -// DEVICE-SPECIFIC SERVICES // -////////////////////////////////// - -// Here we define the DEV_Identify Service as derived class of AccessoryInformation - -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 - - // Next we define the constructor using all the arguments needed to implement the required Characteristics - // of AccessoryInformation, plus one extra argument at the end called "nBlinks" we will use to specify how many - // times HomeSpan should blink the built-in LED when HomeKit calls this device's Identify routine during pairing. - - 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 - } - - // How HomeKit Identifies Devices: - // - // When HomeKit first pairs with a new device it "calls" that device's identify routine for every defined Accessory. - // To do so, HomeKit requests the Identify Characteristic for each defined AccessoryInformation Service to be set to "true". - // The Identify Characteristic is write-only, so no value is ever stored, even though HomeKit is requesting its value - // be updated. We can therefore use the same update() method as if the Identify Characteristic was the same as any - // other boolean Characteristic. - - // There are many ways to implement some form of identification. For an LED, you could blink it one or more times. - // For a LightBulb, you can flash it on and off. For window shade, you could raise and lower it. - // Most commerical devices don't do anything. Because HomeSpan can be used to control many different types of - // device, below we implement a very generic routine that simply blinks the Status LED the number of times specified above. - // In principle, this code could call a user-defined routine that is different for each physcially-attached device (light, shade, fan, etc), - // but in practice this is overkill. - - // Note that the blink routine below starts by turning off the Status LED and then leaves it on once it has blinked - // the specified number of times. This is because when HomeSpan starts up if confirms to user that it has connected - // to the WiFi network by turning on the Status LED. Thus we want to leave it on when blinking is completed. - - // Also note we use the homeSpan.getStatusPin() method to find the pin number associated with the Status LED - - boolean update(){ - - for(int i=0;iledPin=ledPin; - pinMode(ledPin,OUTPUT); - - } // end constructor - - boolean update(){ // update() method - - digitalWrite(ledPin,power->getNewVal()); - - return(true); // return true - - } // update -}; - -////////////////////////////////// - -struct DEV_DimmableLED : Service::LightBulb { // Dimmable LED - - LedPin *ledPin; // reference to Led Pin - SpanCharacteristic *power; // reference to the On Characteristic - SpanCharacteristic *level; // reference to the Brightness Characteristic - - DEV_DimmableLED(int pin) : Service::LightBulb(){ // constructor() method - - power=new Characteristic::On(); - - level=new Characteristic::Brightness(50); // Brightness Characteristic with an initial value of 50% - 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->ledPin=new LedPin(pin); // configures a PWM LED for output to the specified pin - - } // end constructor - - boolean update(){ // update() method - - ledPin->set(power->getNewVal()*level->getNewVal()); - - return(true); // return true - - } // update -}; - -////////////////////////////////// diff --git a/examples/08-Bridges/08-Bridges.ino b/examples/08-Bridges/08-Bridges.ino index b01a4dd..4469daa 100644 --- a/examples/08-Bridges/08-Bridges.ino +++ b/examples/08-Bridges/08-Bridges.ino @@ -1,7 +1,7 @@ /********************************************************************************* * MIT License * - * Copyright (c) 2020 Gregg E. Berman + * Copyright (c) 2020-2022 Gregg E. Berman * * https://github.com/HomeSpan/HomeSpan * @@ -31,33 +31,29 @@ // ------------------------------------------------ // // // // Example 8: HomeKit Bridges and Bridge Accessories // -// ** the preferred method for HomeSpan ** // // // //////////////////////////////////////////////////////////// #include "HomeSpan.h" #include "DEV_LED.h" -#include "DEV_Identify.h" void setup() { - // Though we've seen in prior examples that one device can support multiple Accessories, HomeKit provides a more - // general multi-Accessory framework that is somewhat more robust and easier to use: HomeKit Bridges. - // A Bridge is a device that includes multiple Accessories, except that the FIRST defined Accessory contains - // nothing but the AccessoryInformation Service and the HAPProtcolInformation Service. When such a device is paired - // to HomeKit, it is automatically recognized as a Bridge. All of the other Accessories defined in the device are - // associated with this Bridge. If you unpair the Bridge from HomeKit, all associated Accessories are automatically - // removed. - // - // Adding, editing, and deleting the other Accessories occurs in the same manner as before, but because the device - // is paired as a Bridge, changes to the other Accessories is less likely to require you to un-pair and re-pair - // the device. HomeKit seems to be able to better process changes when they are done within a Bridge framework. - // - // One added bonus is that the HAPProtcolInformation Service only needs to be defined for the Bridge Accessory, and - // does not need to be repeated for other Accessories. - // - // Example 8 is functionally identical to Example 7, except that instead of defining two Accessories (one for the on/off - // LED and one for the dimmable LED), we define three Accessories, where the first acts as the Bridge. + // If the only Service defined in the FIRST Accessory of a mult-Accessory device is the required Accessory Information Service, + // the device is said to be configured as a "Bridge". Historically there may have been a number of functional differences between bridge + // devices and non-bridge devices, but since iOS 15, it's not obvious there are any differences in functionality, with two exceptions: + + // 1. Recall from Example 7 that the use of Characteristic::Name() to change the default name of an Accessory Tile + // does not work for the first Accessory defined. The Home App always displays the default name of the first Accessory Tile + // as the name of the device specified in homeSpan.begin(). However, this is not an issue when implementing a device + // as a Bridge, since the first Accessory is nothing but the Bridge itself - having the default name match the name + // of the device in this case makes much more sense. More importantly, you can now use Characteristic::Name() to change the + // default name of BOTH the LED Accessory Tiles. + + // 2. Devices configured as a Bridge appear in the Home App under the main settings page that displays all Hubs and Bridges. + + // The sketch below is functionally identical to Example 7, except that instead of defining two Accessories (one for the Simple On/Off + // LED and one for the Dimmable LED), we define three Accessories, where the first acts as the Bridge. // As usual, all previous comments have been deleted and only new changes from the previous example are shown. @@ -65,40 +61,28 @@ void setup() { Serial.begin(115200); - homeSpan.begin(Category::Bridges,"HomeSpan Bridge"); // CHANGED! Note that we replaced Category::Lighting with Bridges (this changes the icon when pairing) + // Below we replace Category::Lighting with Category::Bridges. This changes the icon of the device shown when pairing + // with the Home App. It does NOT change any of the icons for an Accessory Tile (these are determined by the types of + // Services implemented in each Accessory - see Example 9). Note you can choose any Category you like - for instance, + // we could have continued to use Category::Lighting, even though we are configuring the device as a Bridge. - // We begin by creating a Bridge Accessory, which look just like any other Accessory, - // except that is only contains DEV_Identify (which is derived from AccessoryInformation) - // and HAPProtcolInformation (required). Note that HomeKit will still call the identify - // update() routine upon pairing, so we specify the number of blinks to be 3. + homeSpan.begin(Category::Bridges,"HomeSpan Bridge"); - new SpanAccessory(); - new DEV_Identify("Bridge #1","HomeSpan","123-ABC","HS Bridge","0.9",3); - new Service::HAPProtocolInformation(); - new Characteristic::Version("1.1.0"); + new SpanAccessory(); // This first Accessory is the new "Bridge" Accessory. It contains no functional Services, just the Accessory Information Service + new Service::AccessoryInformation(); + new Characteristic::Identify(); + + new SpanAccessory(); // This second Accessory is the same as the first Accessory in Example 7, with the exception that Characteristic::Name() now does something + new Service::AccessoryInformation(); + new Characteristic::Identify(); + new Characteristic::Name("Simple LED"); // Note that unlike in Example 7, this use of Name() is properly implented by the Home App since it is not the first Accessory (the Bridge above is the first) + new DEV_LED(16); - // Now we simply repeat the definitions of the previous LED Accessories, as per Example 7, with two exceptions: - // 1) We no longer need to include the HAPProtocolInformation Service. - // 2) We will set the number of blinks to zero, so that only the bridge accessory will cause the Built-In - // LED to blink. This becomes especially important if you had 20 Accessories defined and needed to wait a - // minute or more for all the blinking to finish while pairing. - - new SpanAccessory(); - new DEV_Identify("On/Off LED","HomeSpan","123-ABC","20mA LED","0.9",0); // CHANGED! The number of blinks is now set to zero - - // new Service::HAPProtocolInformation(); - DELETED - NO LONGER NEEDED - // new Characteristic::Version("1.1.0"); - DELETED - NO LONGER NEEDED - - new DEV_LED(16); // create an on/off LED attached to pin 16 - - new SpanAccessory(); - - new DEV_Identify("Dimmable LED","HomeSpan","123-ABC","20mA LED","0.9",0); // CHANGED! The number of blinks is now set to zero - - // new Service::HAPProtocolInformation(); - DELETED - NO LONGER NEEDED - // new Characteristic::Version("1.1.0"); - DELETED - NO LONGER NEEDED - - new DEV_DimmableLED(17); // create a dimmable (PWM-driven) LED attached to pin 17 + new SpanAccessory(); // This third Accessory is the same as the second Accessory in Example 7 + new Service::AccessoryInformation(); + new Characteristic::Identify(); + new Characteristic::Name("Dimmable LED"); + new DEV_DimmableLED(17); } // end of setup() diff --git a/examples/08-Bridges/DEV_Identify.h b/examples/08-Bridges/DEV_Identify.h deleted file mode 100644 index b8d21d6..0000000 --- a/examples/08-Bridges/DEV_Identify.h +++ /dev/null @@ -1,38 +0,0 @@ - -////////////////////////////////// -// 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 Date: Fri, 8 Apr 2022 17:54:50 -0500 Subject: [PATCH 073/100] Removed requirement to include HAP Protocol in Accessory Information Service HAP Protocol does not appear to be needed anymore (as of iOS 15?) --- src/HomeSpan.cpp | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 3343563..a7c67b2 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -1448,14 +1448,11 @@ SpanAccessory::SpanAccessory(uint32_t aid){ void SpanAccessory::validate(){ boolean foundInfo=false; - boolean foundProtocol=false; for(int i=0;itype,"3E")) foundInfo=true; - else if(!strcmp(Services[i]->type,"A2")) - foundProtocol=true; - else if(aid==1) // this is an Accessory with aid=1, but it has more than just AccessoryInfo and HAPProtocolInformation. So... + else if(aid==1) // this is an Accessory with aid=1, but it has more than just AccessoryInfo. So... homeSpan.isBridge=false; // ...this is not a bridge device } @@ -1464,12 +1461,7 @@ void SpanAccessory::validate(){ homeSpan.configLog+=" *** ERROR! Required Service for this Accessory not found. ***\n"; homeSpan.nFatalErrors++; } - - if(!foundProtocol && (aid==1 || !homeSpan.isBridge)){ // HAPProtocolInformation must always be present in Accessory if aid=1, and any other Accessory if the device is not a bridge) - homeSpan.configLog+=" \u2718 Service HAPProtocolInformation"; - homeSpan.configLog+=" *** ERROR! Required Service for this Accessory not found. ***\n"; -// homeSpan.nFatalErrors++; - } + } /////////////////////////////// From ac0344ebb29f4408287edea12c4cc71030fbc7ce Mon Sep 17 00:00:00 2001 From: Gregg Date: Fri, 8 Apr 2022 18:06:46 -0500 Subject: [PATCH 074/100] Updated Examples 9 and 10 --- .../09-MessageLogging/09-MessageLogging.ino | 32 +++++++--------- examples/09-MessageLogging/DEV_Identify.h | 38 ------------------- examples/10-RGB_LED/10-RGB_LED.ino | 23 ++++++----- examples/10-RGB_LED/DEV_Identify.h | 38 ------------------- 4 files changed, 27 insertions(+), 104 deletions(-) delete mode 100644 examples/09-MessageLogging/DEV_Identify.h delete mode 100644 examples/10-RGB_LED/DEV_Identify.h diff --git a/examples/09-MessageLogging/09-MessageLogging.ino b/examples/09-MessageLogging/09-MessageLogging.ino index eefb533..f973669 100644 --- a/examples/09-MessageLogging/09-MessageLogging.ino +++ b/examples/09-MessageLogging/09-MessageLogging.ino @@ -1,7 +1,7 @@ /********************************************************************************* * MIT License * - * Copyright (c) 2020 Gregg E. Berman + * Copyright (c) 2020-2022 Gregg E. Berman * * https://github.com/HomeSpan/HomeSpan * @@ -37,7 +37,6 @@ #include "HomeSpan.h" #include "DEV_LED.h" -#include "DEV_Identify.h" void setup() { @@ -81,24 +80,21 @@ void setup() { homeSpan.begin(Category::Bridges,"HomeSpan Bridge"); - // Defines the Bridge Accessory - - new SpanAccessory(); - new DEV_Identify("Bridge #1","HomeSpan","123-ABC","HS Bridge","0.9",3); - new Service::HAPProtocolInformation(); - new Characteristic::Version("1.1.0"); - - // Defines an ON/OFF LED Accessory attached to pin 16 - - new SpanAccessory(); - new DEV_Identify("On-Off LED","HomeSpan","123-ABC","20mA LED","0.9",0); + new SpanAccessory(); + new Service::AccessoryInformation(); + new Characteristic::Identify(); + + new SpanAccessory(); + new Service::AccessoryInformation(); + new Characteristic::Identify(); + new Characteristic::Name("Simple LED"); new DEV_LED(16); - // Defines a Dimmable (PWM-driven) LED Accessory attached to pin 17 - - new SpanAccessory(); - new DEV_Identify("Dimmable LED","HomeSpan","123-ABC","20mA LED","0.9",0); - new DEV_DimmableLED(17); + new SpanAccessory(); + new Service::AccessoryInformation(); + new Characteristic::Identify(); + new Characteristic::Name("Dimmable LED"); + new DEV_DimmableLED(17); } // end of setup() diff --git a/examples/09-MessageLogging/DEV_Identify.h b/examples/09-MessageLogging/DEV_Identify.h deleted file mode 100644 index b8d21d6..0000000 --- a/examples/09-MessageLogging/DEV_Identify.h +++ /dev/null @@ -1,38 +0,0 @@ - -////////////////////////////////// -// 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;inBlinks=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 Date: Sat, 9 Apr 2022 18:17:59 -0500 Subject: [PATCH 075/100] Updated Examples 1-11 Completely replaced Example 11, which showed how to use setPrimary() since this no longer seems to have any impact on HomeKit. Changed sketch name from ServiceOptions to ServiceNames. --- .../02-TwoSimpleLightBulbs.ino | 2 + .../03-CeilingFanWithLight.ino | 37 +++- .../04-AdvancedCeilingFan.ino | 2 +- .../07-AccessoryNames/07-AccessoryNames.ino | 2 +- examples/08-Bridges/08-Bridges.ino | 7 +- examples/11-ServiceNames/11-ServiceNames.ino | 171 +++++++++++++++ .../11-ServiceOptions/11-ServiceOptions.ino | 202 ------------------ examples/11-ServiceOptions/DEV_Identify.h | 38 ---- examples/11-ServiceOptions/DEV_LED.h | 95 -------- 9 files changed, 212 insertions(+), 344 deletions(-) create mode 100644 examples/11-ServiceNames/11-ServiceNames.ino delete mode 100644 examples/11-ServiceOptions/11-ServiceOptions.ino delete mode 100644 examples/11-ServiceOptions/DEV_Identify.h delete mode 100644 examples/11-ServiceOptions/DEV_LED.h diff --git a/examples/02-TwoSimpleLightBulbs/02-TwoSimpleLightBulbs.ino b/examples/02-TwoSimpleLightBulbs/02-TwoSimpleLightBulbs.ino index cf07904..a5367eb 100644 --- a/examples/02-TwoSimpleLightBulbs/02-TwoSimpleLightBulbs.ino +++ b/examples/02-TwoSimpleLightBulbs/02-TwoSimpleLightBulbs.ino @@ -76,6 +76,8 @@ void setup() { // can also change the name of any Accessory Tile, even after pairing, directly from the Home App by opening the settings page // for any given Tile. + // In Example 7 we will demonstrate how the default names can be changed from within a HomeSpan sketch. + // IMPORTANT: You should NOT have to re-pair your device with HomeKit when moving from Example 1 to Example 2. HomeSpan will note // that the Attribute Database has been updated, and will broadcast a new configuration number when the program restarts. This should // cause all iOS and MacOS HomeKit Controllers to automatically update and reflect the new configuration above. diff --git a/examples/03-CeilingFanWithLight/03-CeilingFanWithLight.ino b/examples/03-CeilingFanWithLight/03-CeilingFanWithLight.ino index dbeb060..2bd976f 100644 --- a/examples/03-CeilingFanWithLight/03-CeilingFanWithLight.ino +++ b/examples/03-CeilingFanWithLight/03-CeilingFanWithLight.ino @@ -47,6 +47,8 @@ void setup() { homeSpan.begin(Category::Fans,"HomeSpan Ceiling Fan"); // Initialize HomeSpan - note the Category has been set to "Fans" // We begin by creating a Light Bulb Accessory just as in Examples 1 and 2 + + new SpanAccessory(); // Begin by creating a new Accessory using SpanAccessory(), no arguments needed new Service::AccessoryInformation(); // HAP requires every Accessory to implement an AccessoryInformation Service, with the required Identify Characteristic new Characteristic::Identify(); // Create the required Identify @@ -59,11 +61,40 @@ void setup() { new Service::Fan(); // Create the Fan Service new Characteristic::Active(); // This Service requires the "Active" Characterstic to turn the fan on and off - // If everything worked correctly you should now see a single Tile named "HomeSpan Ceiling Fan" within the Home App. - // Clicking that Tile should open a window displaying two on/off controls - one for the Fan, and one for the Light. + // Similar to Example 2, we will also implement a LightBulb as a second Accessory + + new SpanAccessory(); // Begin by creating a new Accessory using SpanAccessory(), no arguments needed + + new Service::AccessoryInformation(); // HAP requires every Accessory to implement an AccessoryInformation Service, with the required Identify Characteristic + new Characteristic::Identify(); // Create the required Identify + + new Service::LightBulb(); // Create the Light Bulb Service + new Characteristic::On(); // This Service requires the "On" Characterstic to turn the light on and off + + // If everything worked correctly you should now see two Tiles in the Home App: + // + // * a Tile named "HomeSpan Ceiling Fan" with an icon of a Fan. Clicking this Tile should open the + // control page showing a Fan control on the left, and a Light control on the right + // + // * a Tile named "HomeSpan Ceiling Fan 2" with an icon of a LightBulb. Clicking this Tile should + // toggle the Light On/Off + + // The reason for including the second LightBulb Accessories in this example is to illustrate the impact of the device's Category + // on various icons. Setting Category to Fan in homeSpan.begin() serves two purposes. First, it sets the icon for the device itself, + // as shown by the Home App during initial pairing, to a Fan. Second, it helps the Home App to determine which icon to use for an + // Accessory Tile when there is ambiguity. The second Accessory contains nothing but a LightBulb Service, so the Home App sensibly + // uses a LightBulb icon for the Tile. But what icon should the Home App use for the first Accessory containing both a Fan Service + // and a LightBulb Service? Either a Fan or LightBulb icon would make sense. Setting the Category of the device to Fan causes + // the Home App to choose a Fan icon for the first Accessory. + + // As a test of this, unpair the device; change the Category to Lighting (as in Example 2); re-load the sketch; and re-pair the device. + // You should now see the icon for the "HomeSpan Ceiling Fan" Tile is a LightBulb, and the control screen for the Accessory should + // show the Light control on the left and the Fan control on the right. // IMPORTANT: HomeKit Controllers often cache a lot of information. If your Controller does not update to match the above configuration, - // simply select the Accessory in your Controller and under setting, select "Remove Accessory" and then re-pair. + // simply select the Accessory in your Controller and under settings, select "Remove Accessory", but BEFORE re-pairing the device, type + // 'H' into the HomeSpan CLI. This forces HomeSpan to reboot and generate a new device ID so that it will look "brand new" to the Home App + // when you re-pair. } // end of setup() diff --git a/examples/04-AdvancedCeilingFan/04-AdvancedCeilingFan.ino b/examples/04-AdvancedCeilingFan/04-AdvancedCeilingFan.ino index 560cbd1..574fcc4 100644 --- a/examples/04-AdvancedCeilingFan/04-AdvancedCeilingFan.ino +++ b/examples/04-AdvancedCeilingFan/04-AdvancedCeilingFan.ino @@ -40,7 +40,7 @@ void setup() { - // Example 4 expands on Example 3 by adding Characteristics to set FAN SPEED, FAN DIRECTION, and LIGHT BRIGHTNESS. + // Example 4 expands on the first Accessory in Example 3 by adding Characteristics to set FAN SPEED, FAN DIRECTION, and LIGHT BRIGHTNESS. // For ease of reading, all prior comments have been removed and new comments added to show explicit changes from the previous example. Serial.begin(115200); diff --git a/examples/07-AccessoryNames/07-AccessoryNames.ino b/examples/07-AccessoryNames/07-AccessoryNames.ino index d1d7320..dc7b82c 100644 --- a/examples/07-AccessoryNames/07-AccessoryNames.ino +++ b/examples/07-AccessoryNames/07-AccessoryNames.ino @@ -41,7 +41,7 @@ void setup() { - // As discusses in previous examples, the Home App automatically generate default names for each Accessory Tile + // As discusses in previous examples, the Home App automatically generates default names for each Accessory Tile // based on the Name provided in the second argument of homeSpan.begin(). And though you can change these names // both during, and anytime after, pairing, HAP also allows you to customize the default names themselves, so // something more intuitive is presented to the user when the device is first paired. diff --git a/examples/08-Bridges/08-Bridges.ino b/examples/08-Bridges/08-Bridges.ino index 4469daa..072daa6 100644 --- a/examples/08-Bridges/08-Bridges.ino +++ b/examples/08-Bridges/08-Bridges.ino @@ -62,9 +62,8 @@ void setup() { Serial.begin(115200); // Below we replace Category::Lighting with Category::Bridges. This changes the icon of the device shown when pairing - // with the Home App. It does NOT change any of the icons for an Accessory Tile (these are determined by the types of - // Services implemented in each Accessory - see Example 9). Note you can choose any Category you like - for instance, - // we could have continued to use Category::Lighting, even though we are configuring the device as a Bridge. + // with the Home App, but does NOT change the icons of the Accessory Tiles. You can choose any Category you like. + // For instance, we could have continued to use Category::Lighting, even though we are configuring the device as a Bridge. homeSpan.begin(Category::Bridges,"HomeSpan Bridge"); @@ -75,7 +74,7 @@ void setup() { new SpanAccessory(); // This second Accessory is the same as the first Accessory in Example 7, with the exception that Characteristic::Name() now does something new Service::AccessoryInformation(); new Characteristic::Identify(); - new Characteristic::Name("Simple LED"); // Note that unlike in Example 7, this use of Name() is properly implented by the Home App since it is not the first Accessory (the Bridge above is the first) + new Characteristic::Name("Simple LED"); // Note that unlike in Example 7, this use of Name() is now utilized by the Home App since it is not the first Accessory (the Bridge above is the first) new DEV_LED(16); new SpanAccessory(); // This third Accessory is the same as the second Accessory in Example 7 diff --git a/examples/11-ServiceNames/11-ServiceNames.ino b/examples/11-ServiceNames/11-ServiceNames.ino new file mode 100644 index 0000000..9547c02 --- /dev/null +++ b/examples/11-ServiceNames/11-ServiceNames.ino @@ -0,0 +1,171 @@ +/********************************************************************************* + * MIT License + * + * Copyright (c) 2020-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 // +// ------------------------------------------------ // +// // +// Example 11: Service Names: // +// * setting the names of individual Services // +// * changing the icons in a bridge Accessory // +// // +//////////////////////////////////////////////////////////// + +#include "HomeSpan.h" + +void setup() { + + // As described in previous examples, when pairing a device the Home App will choose default names for each + // Accessory Tile, unless you override those default names with your own names by adding a Name Characteristic + // to the Accessory Information Service for each Accessory (except the first, which is typically the Bridge Accessory). + + // The same process holds true for the names of the Services in an Accessory with multiple Services, such as a Ceiling Fan with a Light. + // When pairing, the Home App will choose default names for each Service (such as Fan, Fan 2, Light, Light 2) depending on the types + // of Services included. Similar to the names of Accessory Tiles, you can change the names of individual Services when prompted + // during the pairing process, or at any time after pairing from within the appropriate settings pages in the Home App. More importantly, + // you can override the default Service names generated by the Home App by simply adding the Name Characteristic to any Service. + + // However, note that Service names (whether or not overridden) only appear in the Home App if there is a chance of ambiguity, + // such as a Accessory with two Services of the same type. But even if a Service name does not appear in the Home App, + // it will still be used by Siri to control a specific Service within an Accessory by voice. + + // In the example below we create 5 different functional Accessories, each illustrating how names, as well as icons, are chosen by the Home App + + Serial.begin(115200); + + // This device will be configured as a Bridge, with the Category set to Bridges + + homeSpan.begin(Category::Bridges,"HomeSpan Bridge"); + + // Our first Accessory is the "Bridge" Accessory + + new SpanAccessory(); + new Service::AccessoryInformation(); + new Characteristic::Identify(); + + // Our second Accessory is a Ceiling Fan with a single Light. There are three things to note: + // + // * when pairing, the Home App will generate default names of "Light" and "Fan" for the two Services. + // However, these names are not displayed on the control screen of the Accessory since there is no + // ambiguity between the Light and Fan controls - the Home App displays them differently + // + // * the icon used by the Home App for the Accessory Tile is a Lightbulb. Why does it choose this instead of a Fan icon? + // Recall from Example 3 that for Accessories with multiple Services, if there is any ambiguity of which icon to use, + // the Home App chooses based on the Category of the device. But since this device is configured as a Bridge, the + // Category provides no helpful information to the Home App. In such cases the Home App picks an icon for the + // Accessory Tile that matches the first functional Service in the Accessory, which in this instance in a LightBulb + // + // * when opening the control screen by clicking the Accessory Tile, the LightBulb control will appear on the left, and + // the Fan control will appear on the right + + new SpanAccessory(); + new Service::AccessoryInformation(); + new Characteristic::Identify(); + new Characteristic::Name("Light with Fan"); // this sets the name of the Accessory Tile + new Service::LightBulb(); // the icon of the Accessory Tile will be a Lightbulb, since this is the first functional Service + new Characteristic::On(); + new Service::Fan(); + new Characteristic::Active(); + + // Our third Accessory is identical to the second, except we swapped the order of the Lightbulb and Fan Services. + // The result is that the Home App now displays the Accessory Tile with a Fan icon intead of a Lightbulb icon. + // Also, when opening the control screen by clicking on the Accessory Tile, the Fan control will now appear on the + // left, and the LightBulb control on the right. + + new SpanAccessory(); + new Service::AccessoryInformation(); + new Characteristic::Identify(); + new Characteristic::Name("Fan with Light"); // this sets the name of the Accessory Tile + new Service::Fan(); // the icon of the Accessory Tile will be a Fan, since this is the first functional Service + new Characteristic::Active(); + new Service::LightBulb(); + new Characteristic::On(); + + // Our fourth Accessory shows what happens if we implement two identical LightBulb Services (without any Fan Service). + // Since both Services are LightBulbs, the Home App sensibly picks a Lightbulb icon for the Accessory Tile. However, + // when you click the Accessory Tile and open the control screen, you'll note that the Home App now does display the names + // of the Service beneath each control. In this case the Home App uses the default names "Light 1" and "Light 2". The Home App + // presumably shows the names of each Service since the two controls are identical and there is otherwise no way of telling which + // control operates which light. + + new SpanAccessory(); + new Service::AccessoryInformation(); + new Characteristic::Identify(); + new Characteristic::Name("Ceiling Lights"); // this sets the name of the Accessory Tile + new Service::LightBulb(); + new Characteristic::On(); + new Service::LightBulb(); + new Characteristic::On(); + + // Our fifth Accessory combines a single Fan Service with two identical LightBulb Services. Since the first functional Service implemented + // is a Fan, the Home App will pick a Fan icon for the Accessory Tile. Also, since we added Name Characteristics to two LightBulb + // Services, their default names generated by the Home App ("Light 1" and "Light 2") will be changed to the names specified. Finally, + // note that the Home App displays a more compact form of controls on the control screen since there are three Services. The arrangement + // and style of the controls will depend on what combination of Characteristics are implemented for each Service. + + new SpanAccessory(); + new Service::AccessoryInformation(); + new Characteristic::Identify(); + new Characteristic::Name("Fan with Lights"); // this sets the name of the Accessory Tile + new Service::Fan(); + new Characteristic::Active(); + new Service::LightBulb(); + new Characteristic::Name("Main Light"); // this changes the default name of this LightBulb Service from "Light 1" to "Main Light" + new Characteristic::On(); + new Service::LightBulb(); + new Characteristic::Name("Night Light"); // this changes the default name of this LightBulb Service from "Light 2" to "Night Light" + new Characteristic::On(); + + // Our sixth Accessory is similar to the fifth, except we added some more features to some of the Services. Note how this changes + // the layout of the controls on the control screen. + + new SpanAccessory(); + new Service::AccessoryInformation(); + new Characteristic::Identify(); + new Characteristic::Name("Multi-Function Fan"); + new Service::Fan(); + new Characteristic::Active(); + new Characteristic::RotationDirection(); // add a control to change the direcion of rotation + new Characteristic::RotationSpeed(0); // add a control to set the rotation speed + new Service::LightBulb(); + new Characteristic::Name("Main Light"); + new Characteristic::On(); + new Characteristic::Brightness(100); // make this light dimmable (with intitial value set to 100%) + new Service::LightBulb(); + new Characteristic::Name("Night Light"); // don't add anything new to this light + new Characteristic::On(); + +} // end of setup() + +////////////////////////////////////// + +void loop(){ + + homeSpan.poll(); + +} // end of loop() diff --git a/examples/11-ServiceOptions/11-ServiceOptions.ino b/examples/11-ServiceOptions/11-ServiceOptions.ino deleted file mode 100644 index 29764af..0000000 --- a/examples/11-ServiceOptions/11-ServiceOptions.ino +++ /dev/null @@ -1,202 +0,0 @@ -/********************************************************************************* - * MIT License - * - * Copyright (c) 2020 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 // -// ------------------------------------------------ // -// // -// Example 11: Service Options: // -// * setting the Primary Service // -// * setting Service Names // -// // -//////////////////////////////////////////////////////////// - -#include "HomeSpan.h" -#include "DEV_LED.h" -#include "DEV_Identify.h" - -void setup() { - - // Every Accessory we define has at least two Services, one of which will always be the required Accessory Information Service (and - // which we've conveniently wrapped into a derived Service we called DEV_Identify). The second is usually a Service that performs - // some sort of actual operation, such as the LightBulb Service or the Fan Service. It's this second operative Service that creates - // the controls we see in the HomeKit iOS or MacOS application. These appear as tiles with a lightbulb control or fan control. - - // We've also created Accessories with more than two operational Service, such as our definition of a ceiling fan that includes - // BOTH a Fan Service and a LightBulb Service. The HomeKit application can display an Accessory with two or more operational Services - // in one of two ways: one way is to display each Service as a separate tile, so that our ceiling fan would show up as one standalone - // lightbulb tile for the LightBulb Service and one standalone fan tile for the Fan Service. The second way is for HomeKit to display - // our ceiling fan as a single tile that you click to open a new screen showing both the fan control and the lightbulb control side by side. - - // HomeSpan has no control over whether HomeKit displays multiple Services as separate tiles or as a single combined single tile. - // This is determined by the user from within the HomeKit iOS or MacOS application (the default is to use the combined tile mode). - // However, HomeSpan does have control over the which icon is used to display the ceiling fan in combined-tile mode. Should it be a - // lightbulb or a fan? - - // HomeKit determines which icon to show on the combined tile according to what is considered the Primary Service of the Accessory. - // HomeKit will also list the Primary Service first when you click a combined tile to open its controls. - - // A Service can be set to Primary with the setPrimary() method. The easiest way to do this is by "chaining" setPrimary() to the - // end of a new Service when first instantiated. See below for examples of how to do this. - - // To begin, we first initialize HomeSpan and define our Bridge Accessory as in the previous examples: - - Serial.begin(115200); - - homeSpan.begin(Category::Bridges,"HomeSpan Bridge"); - - new SpanAccessory(); - new DEV_Identify("Bridge #1","HomeSpan","123-ABC","HS Bridge","0.9",3); - new Service::HAPProtocolInformation(); - new Characteristic::Version("1.1.0"); - - // Next, we create two Accessories each configured to control a Ceiling Fan containing a bi-directional, multi-speed fan AND a dimmable light. - // In Ceiling Fan #1 we specify the Dimmable LED as the Primary Service. In Ceiling Fan #2 we specify the Fan as the Primary Service. - // If you set HomeKit to display each of these Accessories as combined tiles, you'll immediately see the difference. Ceiling Fan #1 shows as - // Lightbulb Tile with the dimmable LED listed first when you click open its controls. Ceiling Fan #2 shows as a Fan Tile with the fan listed first - // when you click open its controls. Nice and easy. - - new SpanAccessory(); - new DEV_Identify("Ceiling Fan #1","HomeSpan","123-ABC","20mA LED","0.9",0); - (new DEV_DimmableLED(17))->setPrimary(); // Here we specify DEV_DimmableLED as the Primary Service by "chaining" setPrimary() to the pointer return by new. Note parentheses! - new Service::Fan(); - new Characteristic::Active(); - new Characteristic::RotationDirection(); - new Characteristic::RotationSpeed(0); - - new SpanAccessory(); - new DEV_Identify("Ceiling Fan #2","HomeSpan","123-ABC","20mA LED","0.9",0); - new DEV_DimmableLED(17); - (new Service::Fan())->setPrimary(); // Here we specify the Fan as the Primary Service. Again, note how we encapsulated the "new" command in parentheses, then chained setPrimary() - new Characteristic::Active(); - new Characteristic::RotationDirection(); - new Characteristic::RotationSpeed(0); - - ////////////////////////////////// - - // In addition to being able to specify which Service in an Accessory is the Primary Service, HomeKit also allows you to give names to each of - // the individual Services. This is done by instantiating a Name Characteristic for a Service. We've already used this feature in creating - // DEV_Identify --- the first argument is used to to name the Service (see DEV_Identify.h). In fact, the Name Characteristic is required - // for the AccessoryInformation Service, so we had to instantiate as part of DEV_Identify. - - // For all other Services the name Characteristic is optional. If not instantiated, the name will be defaulted to whatever name we specified - // in DEV_Identify, which means that if we have more than one operational Service in an Accessory, they will all be named the same. - // This is not necessarily a problem since names don't always come into play in the HomeKit interface. In the examples above, the only name - // that gets displayed in combined-tile mode is "Ceiling Fan #1" or "Ceiling Fan #2", which makes sense. When you click open the controls - // for either Accessory you see a lightbulb control and a fan control. They are not individually named, but the controls look different (one - // is a light control, the other a fan control) so there is no confusion. - - // If instead you set HomeKit to display the controls for these Accessories as separate tiles, you'll see that each of the light and fan controls - // has their own name. But since in the above examples we did not provide specific names for each of these Services, they will simply inherit the - // name "Ceiling Fan #1" or "Ceiling Fan #2". Again, there is no confusion since the light and fan controls each look different. - - // The situation becomes more interesting when you have an Accessory with 3 or more operational Services. Sometimes HomeKit will display the names - // of the Services on the control panel even in combined-tile mode. Consider our ceiling fan example above, but with the added feature of a night-light, - // which we will represent as a simple On/Off LED. Let's instantiate the Name Characteristic for each Service, as shown below. - - new SpanAccessory(); - new DEV_Identify("Ceiling Fan #3","HomeSpan","123-ABC","20mA LED","0.9",0); - new DEV_DimmableLED(17); - new Characteristic::Name("Main Light"); // Here we create a name for the Dimmable LED - new DEV_LED(16); - new Characteristic::Name("Night Light"); // Here we create a name for the On/Off LED - (new Service::Fan())->setPrimary(); - new Characteristic::Active(); - new Characteristic::RotationDirection(); - new Characteristic::RotationSpeed(0); - new Characteristic::Name("Fan"); // Here we create a name for the Fan - - // If you let HomeKit display this as a single, combined tile, you'll notice two things. The first is that the name of the tile is now "Fan" instead - // of "Ceiling Fan #3". Why is that? It's because we set Fan to be the Primary Service AND gave it a name --- this is the name that shows up - // on the combined tile. If we did not give it a name, it would have inherited the name "Ceiling Fan #3", which would have been the name of the tile - // as in the prior example. - - // The second thing you'll notice is that these names now appear next to each control if you click open the combined tile. It says "Fan" next to the Fan - // control, "Main Light" next to the Dimmable LED control, and "Night Light" next to the On/Off LED control. - - // If instead you tell HomeKit to display the controls for Ceiling Fan #3 as three separate tiles, you'll see that each tile contains the name specified - // above for that Service. In some circumstances that can be helpful, in others it can be confusing. For instance, if you had two ceiling fans that - // each had a main light and a night night, how would you know which "Main Light" is which? One solution is to create names like "Main Light #3" and - // "Main Light #4". The other solution is to keep them combined in a single tile, but keep the name "Ceiling Fan #3" for the combined tile, instead of - // having it over-ridden with the word "Fan" - - // This is easily done by specifying DEV_Identify as the Primary Service, instead of Fan, as follows: - - new SpanAccessory(); - (new DEV_Identify("Ceiling Fan #4","HomeSpan","123-ABC","20mA LED","0.9",0))->setPrimary(); // specify DEV_Identify as the Primary Service - new DEV_DimmableLED(17); - new Characteristic::Name("Main Light"); - new DEV_LED(16); - new Characteristic::Name("Night Light"); - new Service::Fan(); - new Characteristic::Active(); - new Characteristic::RotationDirection(); - new Characteristic::RotationSpeed(0); - new Characteristic::Name("Fan"); - - // HomeKit now shows the name "Ceiling Fan #4" for the combined tile AND it still shows the individual names for each control when you click open the tile. - // The only downside to this configuration is that since the Fan is no longer specified as the Primary Service, the main icon on the combined tile now shows - // as a lightbulb, instead of the fan. HomeKit documentation is not clear on how the main icon is chosen under these circumstances, but I've found - // that changing the order of Services as they are instantiated can impact the icon. Here is the same example as above, but with the Fan - // instantiated as the first operational Service, ahead of the Main Light and Night Night: - - new SpanAccessory(); - (new DEV_Identify("Ceiling Fan #5","HomeSpan","123-ABC","20mA LED","0.9",0))->setPrimary(); // specify DEV_Identify as the Primary Service - new Service::Fan(); - new Characteristic::Active(); - new Characteristic::RotationDirection(); - new Characteristic::RotationSpeed(0); - new Characteristic::Name("Fan"); - new DEV_DimmableLED(17); - new Characteristic::Name("Main Light"); - new DEV_LED(16); - new Characteristic::Name("Night Light"); - - // This seems to cause HomeKit to display the Fan icon on the combined tile, as well as to list the Fan Service control first when the tile is clicked - // open, almost as if it were the Primary Service in all ways except for its name. - - // As you can see, there is no right or wrong way for how to name your Accessories and Services, including whether or not to even bother naming your - // individual Services. It's purely a matter of taste - experiment and see which combinations best serve your purposes. - - // IMPORTANT: HomeKit tries to cache as many items as possible, and although it should know when configurations change, it does not always respond - // as expected. If you are experimenting and find that name changes are NOT being reflected in the HomeKit interface, simply unpair HomeSpan and - // re-pair. This often causes a refresh. If not, after unpairing you can additionally reset the HAP data in HomeSpan so that its HAP ID changes (see - // HomeSpan documentation for how to easily do this). This way when you re-pair, HomeKit should think this is a completely new device and start with - // a clean slate. *** In some limited circumstances, HomeKit may get so confused it refuses to operate the Accessories at all. You may get a No Response - // icon even though HomeSpan is operating correctly. Though it is possible to misconfigure HomeSpan in ways that cause this, if you do get this - // error please try the re-pairing methods above as it often fixes the problem. - -} // end of setup() - -////////////////////////////////////// - -void loop(){ - - homeSpan.poll(); - -} // end of loop() diff --git a/examples/11-ServiceOptions/DEV_Identify.h b/examples/11-ServiceOptions/DEV_Identify.h deleted file mode 100644 index b8d21d6..0000000 --- a/examples/11-ServiceOptions/DEV_Identify.h +++ /dev/null @@ -1,38 +0,0 @@ - -////////////////////////////////// -// 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;iledPin=ledPin; - pinMode(ledPin,OUTPUT); - - Serial.print("Configuring On/Off LED: Pin="); // initialization message - Serial.print(ledPin); - Serial.print("\n"); - - } // end constructor - - boolean update(){ // update() method - - LOG1("Updating On/Off LED on pin="); - LOG1(ledPin); - LOG1(": Current Power="); - LOG1(power->getVal()?"true":"false"); - LOG1(" New Power="); - LOG1(power->getNewVal()?"true":"false"); - LOG1("\n"); - - digitalWrite(ledPin,power->getNewVal()); - - return(true); // return true - - } // update -}; - -////////////////////////////////// - -struct DEV_DimmableLED : Service::LightBulb { // Dimmable LED - - LedPin *ledPin; // reference to Led Pin - SpanCharacteristic *power; // reference to the On Characteristic - SpanCharacteristic *level; // reference to the Brightness Characteristic - - DEV_DimmableLED(int pin) : Service::LightBulb(){ // constructor() method - - power=new Characteristic::On(); - - level=new Characteristic::Brightness(50); // Brightness Characteristic with an initial value of 50% - 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->ledPin=new LedPin(pin); // configures a PWM LED for output to the specified pin - - Serial.print("Configuring Dimmable LED: Pin="); // initialization message - Serial.print(ledPin->getPin()); - Serial.print("\n"); - - } // end constructor - - boolean update(){ // update() method - - LOG1("Updating Dimmable LED on pin="); - LOG1(ledPin->getPin()); - LOG1(": Current Power="); - LOG1(power->getVal()?"true":"false"); - LOG1(" Current Brightness="); - LOG1(level->getVal()); - - if(power->updated()){ - LOG1(" New Power="); - LOG1(power->getNewVal()?"true":"false"); - } - - if(level->updated()){ - LOG1(" New Brightness="); - LOG1(level->getNewVal()); - } - - LOG1("\n"); - - ledPin->set(power->getNewVal()*level->getNewVal()); - - return(true); // return true - - } // update -}; - -////////////////////////////////// From e6964a7cbb9b4b9d4e0b370249dc117369dc815d Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 9 Apr 2022 18:21:30 -0500 Subject: [PATCH 076/100] Update Tutorials.md --- docs/Tutorials.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/Tutorials.md b/docs/Tutorials.md index 64e71f6..3004b01 100644 --- a/docs/Tutorials.md +++ b/docs/Tutorials.md @@ -57,10 +57,8 @@ Example 10 illustrates how to control an RGB LED to set any color and brightness * converting HomeKit Hue/Saturation/Brightness levels to Red/Green/Blue levels using `PwmPin::HSVtoRGB()` * using the optional template functionality of `getVal()`, such as `getVal()` -### [Example 11 - ServiceOptions](../examples/11-ServiceOptions) -This example explores how the Name Characteristic can be used to create different naming schemes for multi-Service Accessories, and how these appear in the Home App depending on how you display the Accessory tile. New HomeSpan API topics covered in this example include: - -* setting the primary Service for an Accessory with the `setPrimary()` method +### [Example 11 - ServiceNames](../examples/11-ServiceNames) +This example explores how the Name Characteristic can be used to create different naming schemes for multi-Service Accessories, and how icons are chosen by the Home App for a Bridge device based on the order of the Services implemented within an Accessory. ### [Example 12 - ServiceLoops](../examples/12-ServiceLoops) Example 12 introduces HomeKit *Event Notifications* to implement two new accessories - a Temperature Sensor and an Air Quality Sensor. Of course we won't actually have these physical devices attached to the ESP32 for the purpose of this example, but we will simulate "reading" their properties on a periodic basis, and notify HomeKit of any changed values. New HomeSpan API topics covered in this example include: From e100e313f2ff99e12c02a8853b9e5cd2a6ddf2d4 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 9 Apr 2022 18:25:13 -0500 Subject: [PATCH 077/100] Update Tutorials.md --- docs/Tutorials.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Tutorials.md b/docs/Tutorials.md index 3004b01..e9f5234 100644 --- a/docs/Tutorials.md +++ b/docs/Tutorials.md @@ -48,7 +48,7 @@ Example 8 is functionally identical to Example 7, except that instead of definin ### [Example 9 - MessageLogging](../examples/09-MessageLogging) Example 9 illustrates how to add log messages to your HomeSpan sketch. The code is identical to Example 8 except for the inclusion of new log messages. New HomeSpan API topics covered in this example include: -* using the `LOG1()` and `LOG2()` macros to create log messages for different log levels +* using the `LOG0()`, `LOG1()`, and `LOG2()` macros to create log messages for different log levels * setting the initial log level for a sketch with the `homeSpan.setLogLevel()` method ### [Example 10 - RGB_LED](../examples/10-RGB_LED) @@ -58,7 +58,7 @@ Example 10 illustrates how to control an RGB LED to set any color and brightness * using the optional template functionality of `getVal()`, such as `getVal()` ### [Example 11 - ServiceNames](../examples/11-ServiceNames) -This example explores how the Name Characteristic can be used to create different naming schemes for multi-Service Accessories, and how icons are chosen by the Home App for a Bridge device based on the order of the Services implemented within an Accessory. +Example 11 demonstrates how the names of the different Services in a multi-Service Accessory can be changed from the defaults generated by the Home App to something more useful and customized. The examples also explores how and when these names are displayed by the Home App, as well as how the Home App chooses an appropriate icon for an Accessory Tile when the device is configured as a Bridge. ### [Example 12 - ServiceLoops](../examples/12-ServiceLoops) Example 12 introduces HomeKit *Event Notifications* to implement two new accessories - a Temperature Sensor and an Air Quality Sensor. Of course we won't actually have these physical devices attached to the ESP32 for the purpose of this example, but we will simulate "reading" their properties on a periodic basis, and notify HomeKit of any changed values. New HomeSpan API topics covered in this example include: From c2a43eca5544fe223baa7f38aa60b79f6daf1761 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 9 Apr 2022 18:51:22 -0500 Subject: [PATCH 078/100] Updated Examples 12-14 --- examples/12-ServiceLoops/12-ServiceLoops.ino | 51 ++++--------------- examples/12-ServiceLoops/DEV_Identify.h | 38 -------------- examples/13-TargetStates/13-TargetStates.ino | 17 ++++--- examples/13-TargetStates/DEV_Identify.h | 38 -------------- .../14-EmulatedPushButtons.ino | 12 ++--- .../14-EmulatedPushButtons/DEV_Identify.h | 38 -------------- 6 files changed, 26 insertions(+), 168 deletions(-) delete mode 100644 examples/12-ServiceLoops/DEV_Identify.h delete mode 100644 examples/13-TargetStates/DEV_Identify.h delete mode 100644 examples/14-EmulatedPushButtons/DEV_Identify.h diff --git a/examples/12-ServiceLoops/12-ServiceLoops.ino b/examples/12-ServiceLoops/12-ServiceLoops.ino index 5d169cf..c53878c 100644 --- a/examples/12-ServiceLoops/12-ServiceLoops.ino +++ b/examples/12-ServiceLoops/12-ServiceLoops.ino @@ -1,7 +1,7 @@ /********************************************************************************* * MIT License * - * Copyright (c) 2020 Gregg E. Berman + * Copyright (c) 2020-2022 Gregg E. Berman * * https://github.com/HomeSpan/HomeSpan * @@ -37,7 +37,6 @@ //////////////////////////////////////////////////////////// #include "HomeSpan.h" -#include "DEV_Identify.h" #include "DEV_Sensors.h" void setup() { @@ -91,16 +90,19 @@ void setup() { homeSpan.begin(Category::Bridges,"HomeSpan Bridge"); new SpanAccessory(); - new DEV_Identify("Bridge #1","HomeSpan","123-ABC","HS Bridge","0.9",3); - new Service::HAPProtocolInformation(); - new Characteristic::Version("1.1.0"); + new Service::AccessoryInformation(); + new Characteristic::Identify(); - new SpanAccessory(); - new DEV_Identify("Temp Sensor","HomeSpan","123-ABC","Sensor","0.9",0); + new SpanAccessory(); + new Service::AccessoryInformation(); + new Characteristic::Identify(); + new Characteristic::Name("Temp Sensor"); new DEV_TempSensor(); // Create a Temperature Sensor (see DEV_Sensors.h for definition) - new SpanAccessory(); - new DEV_Identify("Air Quality","HomeSpan","123-ABC","Sensor","0.9",0); + new SpanAccessory(); + new Service::AccessoryInformation(); + new Characteristic::Identify(); + new Characteristic::Name("Air Quality"); new DEV_AirQualitySensor(); // Create an Air Quality Sensor (see DEV_Sensors.h for definition) } // end of setup() @@ -114,34 +116,3 @@ void loop(){ } // end of loop() ////////////////////////////////////// - - -// Additional Technical Notes about Event Notifications in HomeKit -// --------------------------------------------------------------- - -// HomeKit is designed for two-way communication: HomeSpan devices not only receive and act on operational instructions from HomeKit Controllers, but -// HomeSpan can also send HomeKit unsolicited messages regarding changes to the state of the device. Though it may not be apparent, this has already been -// ocurring in the background in all prior examples. This is because when a HomeKit Controller sends an operational request to any HomeKit device, it expects -// to receive a status message back indicating whether the request was successful or not. This is the purpose of returning StatusCode:OK in custom update() -// methods. With this information returned, HomeKit can update its own status and properly reflect a change in the device, such as by showing a light is now -// turned on instead of off. However, HomeKit unfortunately does NOT inform any other HomeKit Controllers of this new information. So if you have two iPhones -// and use one to turn on a light, this iPhone does not relay a message to the second iPhone that a light has been turned on. This is the case even -// if you are using an AppleTV or HomePod as a central hub for HomeKit. - -// Normally this does not matter much, since the second iPhone will automatically update itself as to the status of all HomeKit devices as soon as the HomeKit -// application is launched on that iPhone. It does this by sending every HomeKit device a message asking for a status update. In this fashion the second -// iPhone quickly synchronizes itself as soon as its HomeKit app is opened, but ONLY when it is first opened (or re-opened if you first close it). However, if you -// have two iPhones BOTH opened to the HomeKit app (or one iPhone and one Mac opened to the HomeKit app) and you use one Controller app to turn on a light, the -// resulting change in status of that light will NOT be reflected in the second Controller app, unless you close tha app and re-open (at which point it goes -// through the request procedure discussed above). This can be very annoying and counterintuitive. - -// Fortunately, HomeKit provides a solution to this in the form of an Event Notification protcol. This protcol allows a device to send unsoliciated messages -// to all Controllers that have previously registered themselves with the device indicating the Characteristics for which they would like to receive an event -// message from the device whenever there is a change in the status of one or more of those Characteristics. - -// HomeSpan takes care of this automatically, and has being doing so in the background in all prior examples. To see this for yourself, use two iPhones -// (or an iPhone and Mac) with any of the previous examples and open the HomeKit app on both. Any changes you make to the device using one of the Controllers, -// such as turning on an LED, is immediately reflected in the other Controller. Not quite magic, but close. - -// As described above, and fully explored in this example, HomeSpan automatically generates Event Notification messages and transmits them to all registered -// Controllers every time you change the value of a Characteristic using the setVal() function from within a derived Service's loop() method. diff --git a/examples/12-ServiceLoops/DEV_Identify.h b/examples/12-ServiceLoops/DEV_Identify.h deleted file mode 100644 index b8d21d6..0000000 --- a/examples/12-ServiceLoops/DEV_Identify.h +++ /dev/null @@ -1,38 +0,0 @@ - -////////////////////////////////// -// 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;inBlinks=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;inBlinks=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 Date: Sun, 10 Apr 2022 09:13:49 -0500 Subject: [PATCH 079/100] Update Reference.md --- docs/Reference.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/Reference.md b/docs/Reference.md index fdf2b67..04f3763 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -190,12 +190,14 @@ This is a **base class** from which all HomeSpan Services are derived, and shoul The following methods are supported: * `SpanService *setPrimary()` - * specifies that this is the primary Service for the Accessory. Returns a pointer to the Service itself so that the method can be chained during instantiation. + * specifies that this is the primary Service for the Accessory. Returns a pointer to the Service itself so that the method can be chained during instantiation * example: `(new Service::Fan)->setPrimary();` + * note though this functionality is defined by Apple in HAP-R2, it seems to have been deprecated and no longer serves any purpose or has any affect on the Home App * `SpanService *setHidden()` * specifies that this is hidden Service for the Accessory. Returns a pointer to the Service itself so that the method can be chained during instantiation. - * note this does not seem to have any affect on the Home App. Services marked as hidden still appear as normal + * example: `(new Service::Fan)->setHidden();` + * note though this functionality is defined by Apple in HAP-R2, it seems to have been deprecated and no longer serves any purpose or has any affect on the Home App * `SpanService *addLink(SpanService *svc)` * adds *svc* as a Linked Service. Returns a pointer to the calling Service itself so that the method can be chained during instantiation. From f322f2b0f198aee9052232379ae110a4a8804409 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 10 Apr 2022 16:04:36 -0500 Subject: [PATCH 080/100] Updated Examples 14-17 --- .../14-EmulatedPushButtons.ino | 4 +- .../15-RealPushButtons/15-RealPushButtons.ino | 12 +++--- examples/15-RealPushButtons/DEV_Identify.h | 38 ------------------- .../16-ProgrammableSwitches.ino | 12 +++--- .../16-ProgrammableSwitches/DEV_Identify.h | 38 ------------------- .../17-LinkedServices/17-LinkedServices.ino | 34 ++++++++--------- 6 files changed, 29 insertions(+), 109 deletions(-) delete mode 100644 examples/15-RealPushButtons/DEV_Identify.h delete mode 100644 examples/16-ProgrammableSwitches/DEV_Identify.h diff --git a/examples/14-EmulatedPushButtons/14-EmulatedPushButtons.ino b/examples/14-EmulatedPushButtons/14-EmulatedPushButtons.ino index f62fa59..7469386 100644 --- a/examples/14-EmulatedPushButtons/14-EmulatedPushButtons.ino +++ b/examples/14-EmulatedPushButtons/14-EmulatedPushButtons.ino @@ -1,7 +1,7 @@ /********************************************************************************* * MIT License * - * Copyright (c) 2020 Gregg E. Berman + * Copyright (c) 2020-2022 Gregg E. Berman * * https://github.com/HomeSpan/HomeSpan * @@ -90,7 +90,7 @@ void setup() { new Service::AccessoryInformation(); new Characteristic::Identify(); new Characteristic::Name("LED Blinker"); - new DEV_Blinker(13,3); // DEV_Blinker takes two arguments - pin, and number of times to blink + new DEV_Blinker(16,3); // DEV_Blinker takes two arguments - pin, and number of times to blink } // end of setup() diff --git a/examples/15-RealPushButtons/15-RealPushButtons.ino b/examples/15-RealPushButtons/15-RealPushButtons.ino index 1fd56af..c6219a8 100644 --- a/examples/15-RealPushButtons/15-RealPushButtons.ino +++ b/examples/15-RealPushButtons/15-RealPushButtons.ino @@ -1,7 +1,7 @@ /********************************************************************************* * MIT License * - * Copyright (c) 2020 Gregg E. Berman + * Copyright (c) 2020-2022 Gregg E. Berman * * https://github.com/HomeSpan/HomeSpan * @@ -38,7 +38,6 @@ #include "HomeSpan.h" #include "DEV_LED.h" -#include "DEV_Identify.h" void setup() { @@ -132,12 +131,13 @@ void setup() { homeSpan.begin(Category::Bridges,"HomeSpan Bridge"); new SpanAccessory(); - new DEV_Identify("Bridge #1","HomeSpan","123-ABC","HS Bridge","0.9",3); - new Service::HAPProtocolInformation(); - new Characteristic::Version("1.1.0"); + new Service::AccessoryInformation(); + new Characteristic::Identify(); new SpanAccessory(); - new DEV_Identify("PushButton LED","HomeSpan","123-ABC","20mA LED","0.9",0); + new Service::AccessoryInformation(); + new Characteristic::Identify(); + new Characteristic::Name("PushButton LED"); new DEV_DimmableLED(17,23,5,18); // NEW! added three extra arguments to specify the pin numbers for three SpanButtons() - see DEV_LED.h diff --git a/examples/15-RealPushButtons/DEV_Identify.h b/examples/15-RealPushButtons/DEV_Identify.h deleted file mode 100644 index b8d21d6..0000000 --- a/examples/15-RealPushButtons/DEV_Identify.h +++ /dev/null @@ -1,38 +0,0 @@ - -////////////////////////////////// -// 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;inBlinks=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 Date: Tue, 12 Apr 2022 21:36:00 -0500 Subject: [PATCH 081/100] Updated Example 17 with note to indicate it is not functioning correctly This appears to be a Home App problem. The problem cannot be fixed by adding in ServiceLabel and ServiceIndex (no impact), nor adding in isConfigured(), nor adding Name() Characteristics. The Home App refuses to render the valves regardless of what valve type. They will sometimes appear, and then disappear from the interface for no reason. Testing with the Apple's HomeKit Simulator yields the same symptoms. --- examples/17-LinkedServices/17-LinkedServices.ino | 5 ----- 1 file changed, 5 deletions(-) diff --git a/examples/17-LinkedServices/17-LinkedServices.ino b/examples/17-LinkedServices/17-LinkedServices.ino index 784208a..77708d0 100644 --- a/examples/17-LinkedServices/17-LinkedServices.ino +++ b/examples/17-LinkedServices/17-LinkedServices.ino @@ -119,8 +119,6 @@ struct Shower : Service::Faucet { // this is our Shower structur WaterValve(Shower *s, int i){ // this is constructor for WaterValve. It takes a single argument that points to the "controlling" Shower Service shower=s; // store the pointer to the Shower Service new Characteristic::ValveType(2); // specify the Value Type (2=Shower Head; see HAP R2 for other choices) - new Characteristic::ServiceLabelIndex(i); - new Characteristic::IsConfigured(1); // Source included in the Settings Screen... } boolean update() override { // HomeSpan calls this whenever the Home App requests a change in a Valve's Active Characteristic @@ -153,9 +151,6 @@ void setup() { new Shower(4); // Create a Spa Shower with 4 spray heads - new Service::ServiceLabel(); - new Characteristic::ServiceLabelNamespace(1); - } // end of setup() ////////////////////////////////////// From 78ddd8a330cb27d60ad3f900539a0c9edc3dd37d Mon Sep 17 00:00:00 2001 From: Gregg Date: Tue, 12 Apr 2022 22:14:29 -0500 Subject: [PATCH 082/100] Updated Examples 18 and 19 All tutorial examples now updated and confirmed working s expected in ios15.4.1 except for Example 17 (Shower). DEV_Identify.h removed from all examples and optional AccessoryInformation() Characteristics deleted to make examples easier to use and understand. To Do: Review and update "Other Examples" and add link to Example 19 in Tutorials.md --- .../17-LinkedServices/17-LinkedServices.ino | 6 +++ examples/18-SavingStatus/18-SavingStatus.ino | 16 ++++---- examples/18-SavingStatus/DEV_Identify.h | 38 ------------------- examples/19-WebLog/19-WebLog.ino | 32 +++++----------- 4 files changed, 24 insertions(+), 68 deletions(-) delete mode 100644 examples/18-SavingStatus/DEV_Identify.h diff --git a/examples/17-LinkedServices/17-LinkedServices.ino b/examples/17-LinkedServices/17-LinkedServices.ino index 77708d0..78555d7 100644 --- a/examples/17-LinkedServices/17-LinkedServices.ino +++ b/examples/17-LinkedServices/17-LinkedServices.ino @@ -35,6 +35,12 @@ // // //////////////////////////////////////////////////////////// +// WARNING: THIS EXAMPLE STOPPED WORKING CORRECTLY SOMEWHERE AROUND THE IOS 15.2 OR IOS 15.3 UPDATE +// AND DOES NOT WORK AS OF IOS 15.4.1 +// +// THE PROBLEM APPEARS TO BE IN THE RENDERING OF INDIVIDUAL VALVES IN THE HOME APP INTERFACE. THEY +// APPEAR IN THE EVE HOMEKIT APPLICATION, BUT NOT APPLE'S HOME APP. + #include "HomeSpan.h" // HAP normally treats multiple Services created within the same Accessory as independent of one another. However, certain HAP Services are designed to represent a central point diff --git a/examples/18-SavingStatus/18-SavingStatus.ino b/examples/18-SavingStatus/18-SavingStatus.ino index f9f8031..821913e 100644 --- a/examples/18-SavingStatus/18-SavingStatus.ino +++ b/examples/18-SavingStatus/18-SavingStatus.ino @@ -1,7 +1,7 @@ /********************************************************************************* * MIT License * - * Copyright (c) 2021 Gregg E. Berman + * Copyright (c) 2021-2022 Gregg E. Berman * * https://github.com/HomeSpan/HomeSpan * @@ -38,7 +38,6 @@ #include "HomeSpan.h" #include "DEV_LED.h" -#include "DEV_Identify.h" void setup() { @@ -76,16 +75,19 @@ void setup() { homeSpan.begin(Category::Bridges,"HomeSpan Bridge"); new SpanAccessory(); - new DEV_Identify("Bridge #1","HomeSpan","123-ABC","HS Bridge","0.9",3); - new Service::HAPProtocolInformation(); - new Characteristic::Version("1.1.0"); + new Service::AccessoryInformation(); + new Characteristic::Identify(); new SpanAccessory(); - new DEV_Identify("LED 1","HomeSpan","123-ABC","20mA LED","0.9",0); + new Service::AccessoryInformation(); + new Characteristic::Identify(); + new Characteristic::Name("LED 1"); new DEV_DimmableLED(17,19); // The first argument specifies the LED pin; the second argument specifies the PushButton pin new SpanAccessory(); - new DEV_Identify("LED 2","HomeSpan","123-ABC","20mA LED","0.9",0); + new Service::AccessoryInformation(); + new Characteristic::Identify(); + new Characteristic::Name("LED 2"); new DEV_DimmableLED(16,18); // The first argument specifies the LED pin; the second argument specifies the PushButton pin } // end of setup() diff --git a/examples/18-SavingStatus/DEV_Identify.h b/examples/18-SavingStatus/DEV_Identify.h deleted file mode 100644 index b8d21d6..0000000 --- a/examples/18-SavingStatus/DEV_Identify.h +++ /dev/null @@ -1,38 +0,0 @@ - -////////////////////////////////// -// 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 Date: Fri, 15 Apr 2022 06:16:41 -0500 Subject: [PATCH 083/100] Update Tutorials.md --- docs/Tutorials.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/Tutorials.md b/docs/Tutorials.md index e9f5234..1dbad9b 100644 --- a/docs/Tutorials.md +++ b/docs/Tutorials.md @@ -89,10 +89,16 @@ Example 17 introduces the HAP concept of Linked Services and demonstrates how th * creating Linked Services using the `addLink()` method ### [Example 18 - SavingStatus](../examples/18-SavingStatus) -Example 18 demonstrates, through the implementaton of two Dimmable LEDs, how the values of Characteristics can be automatically saved in the device's non-volatile storage (NVS) for restoration upon start-up if the device is loses power. New HomeSpan API topics covered in this example include: +Example 18 demonstrates, through the implementation of two Dimmable LEDs, how the values of Characteristics can be automatically saved in the device's non-volatile storage (NVS) for restoration upon start-up if the device is loses power. New HomeSpan API topics covered in this example include: * instructing HomeSpan to store the value of a Characteristic in NVS by setting the second parameter of the constuctor to `true` +### [Example 19 - WebLog](../examples/19-WebLog) +Example 19 illustrates, through the implementation of two On/Off LEDs, how to add a Web Log to your HomeSpan sketch, how to syncronize the internal clock of your device using an NTP time server, and how to create your own Web Log messages. New HomeSpan API topics covered in this example include: + +* enabling the HomeSpan Web Log and specifying an optional NTP time server with the `homeSpan.enableWebLog()` method +* using the `WEBLOG()` macro to create Web Log messages + --- [↩️](README.md) Back to the Welcome page From 2277b2506f85fe5da6754fe593558d531a0c5198 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Fri, 15 Apr 2022 06:28:50 -0500 Subject: [PATCH 084/100] Update Overview.md --- docs/Overview.md | 30 +++++++----------------------- 1 file changed, 7 insertions(+), 23 deletions(-) diff --git a/docs/Overview.md b/docs/Overview.md index a6438f9..763aed0 100644 --- a/docs/Overview.md +++ b/docs/Overview.md @@ -109,7 +109,7 @@ As you can see, you do not need to name any objects, or specify any HAP paramete > :heavy_check_mark: HomeSpan has extensive error checking. At start-up, HomeSpan will validate the configuration of the HAP Accessory Attribute Database you instantiated to ensure that every Accessory has all the required Services, and that each Service has all its required Characteristics. If HomeSpan finds an Accessory is missing a required Service, a Service is missing a required Characteristic, or a Characteristic that is neither required nor optional has been added to a Service that does not support that Characteristic, HomeSpan will report these errors and halt the program. -In fact, if you tried to run the above sketch you would find it failed to validate. That's because each Accessory is missing two required Services - the HAP Accessory Information Service, and the HAP Protcol Information Service. See the [Tutorials](Tutorials.md) for full completed and valid configurations that include these required HAP Services, such as this complete, working example for a simple table lamp: +In fact, if you tried to run the above sketch you would find it failed to validate. That's because each Accessory is missing a required Service and Characteristic - the HAP Accessory Information Service and the Identify Characteristic. See the [Tutorials](Tutorials.md) for a variety of complete and valid examples that include all required HAP elements, such as this sketch for a simple table lamp: ```C++ /* HomeSpan Table Lamp Example */ @@ -124,17 +124,9 @@ void setup() { new SpanAccessory(); // Table Lamp Accessory - new Service::AccessoryInformation(); // HAP requires every Accessory to implement an AccessoryInformation Service, with 6 *required* Characteristics - new Characteristic::Name("My Table Lamp"); // Name of the Accessory, which shows up on the HomeKit "tiles", and should be unique across Accessories - new Characteristic::Manufacturer("HomeSpan"); // Manufacturer of the Accessory (arbitrary text string, and can be the same for every Accessory) - new Characteristic::SerialNumber("123-ABC"); // Serial Number of the Accessory (arbitrary text string, and can be the same for every Accessory) - new Characteristic::Model("120-Volt Lamp"); // Model of the Accessory (arbitrary text string, and can be the same for every Accessory) - new Characteristic::FirmwareRevision("0.9"); // Firmware of the Accessory (arbitrary text string, and can be the same for every Accessory) - new Characteristic::Identify(); // Provides a hook that allows a HomeKit Client to identify the device - - new Service::HAPProtocolInformation(); // HAP requires every Accessory (except those in a bridge) to implement a Protcol Information Service - new Characteristic::Version("1.1.0"); // Set the Version Characteristic to "1.1.0," which is required by HAP - + new Service::AccessoryInformation(); // HAP requires every Accessory to implement an AccessoryInformation Service + new Characteristic::Identify(); // HAP requires the Accessory Information Service to include the Identify Characteristic + new Service::LightBulb(); // Create the Light Bulb Service new Characteristic::On(); // Characteristic that stores that state of the light bulb: ON or OFF @@ -263,17 +255,9 @@ void setup() { new SpanAccessory(); // Table Lamp Accessory - new Service::AccessoryInformation(); // HAP requires every Accessory to implement an AccessoryInformation Service, with 6 *required* Characteristics - new Characteristic::Name("My Table Lamp"); // Name of the Accessory, which shows up on the HomeKit "tiles", and should be unique across Accessories - new Characteristic::Manufacturer("HomeSpan"); // Manufacturer of the Accessory (arbitrary text string, and can be the same for every Accessory) - new Characteristic::SerialNumber("123-ABC"); // Serial Number of the Accessory (arbitrary text string, and can be the same for every Accessory) - new Characteristic::Model("120-Volt Lamp"); // Model of the Accessory (arbitrary text string, and can be the same for every Accessory) - new Characteristic::FirmwareRevision("0.9"); // Firmware of the Accessory (arbitrary text string, and can be the same for every Accessory) - new Characteristic::Identify(); // Provides a hook that allows a HomeKit Client to identify the device - - new Service::HAPProtocolInformation(); // HAP requires every Accessory (except those in a bridge) to implement a Protcol Information Service - new Characteristic::Version("1.1.0"); // Set the Version Characteristic to "1.1.0," which is required by HAP - + new Service::AccessoryInformation(); // HAP requires every Accessory to implement an AccessoryInformation Service + new Characteristic::Identify(); // HAP requires the Accessory Information Service to include the Identify Characteristic + new TableLamp(17); // instantiate the TableLamp Service (defined below) with lampPin set to 17 } // end of setup() From 0507f26b13fc9d4091e8c79df7c99176cb8ef21c Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 16 Apr 2022 15:21:13 -0500 Subject: [PATCH 085/100] Updated "Other Examples" and added SPAN_ACCESSORY() Macro Need to revisit Television sketch - the latest iOS seemed to have disabled the ability to dynamically set visibility of input sources. Checking/Un-Checking the visibility radio buttons either during pairing, or after pairing on the settings screen, seems to be ignored by the Home App (the same input sources are shown in the selector regardless of any changes made). However, dynamically changing the name of an input source seems to work fine. --- .../CustomService/CustomService.ino | 14 +---- Other Examples/Pixel/Pixel.ino | 54 +++---------------- .../RemoteControl/RemoteControl.ino | 27 +++++++++- Other Examples/ServoControl/DEV_Identify.h | 38 ------------- Other Examples/ServoControl/ServoControl.ino | 12 ++--- Other Examples/TableLamp/TableLamp.ino | 43 ++++++++++----- Other Examples/Television/Television.ino | 12 +---- src/Span.h | 5 ++ 8 files changed, 76 insertions(+), 129 deletions(-) delete mode 100644 Other Examples/ServoControl/DEV_Identify.h diff --git a/Other Examples/CustomService/CustomService.ino b/Other Examples/CustomService/CustomService.ino index 2551e9b..ec2f15d 100644 --- a/Other Examples/CustomService/CustomService.ino +++ b/Other Examples/CustomService/CustomService.ino @@ -97,19 +97,7 @@ void setup() { homeSpan.begin(Category::Sensors,"Eve Air Pressure"); - new SpanAccessory(); - - new Service::AccessoryInformation(); - new Characteristic::Name("Air Pressure"); - new Characteristic::Manufacturer("HomeSpan"); - new Characteristic::SerialNumber("123-ABC"); - new Characteristic::Model("Simulated Sensor"); - new Characteristic::FirmwareRevision("1.0"); - new Characteristic::Identify(); - - new Service::HAPProtocolInformation(); - new Characteristic::Version("1.1.0"); - + SPAN_ACCESSORY(); new PressureSensor(); } diff --git a/Other Examples/Pixel/Pixel.ino b/Other Examples/Pixel/Pixel.ino index 8f121d7..5723de4 100644 --- a/Other Examples/Pixel/Pixel.ino +++ b/Other Examples/Pixel/Pixel.ino @@ -59,7 +59,7 @@ #define DEVICE_SUFFIX "-C3" #endif - + #include "HomeSpan.h" #include "extras/Pixel.h" // include the HomeSpan Pixel class @@ -182,56 +182,16 @@ void setup() { homeSpan.begin(Category::Lighting,"Pixel LEDS" DEVICE_SUFFIX); - new SpanAccessory(); // create Bridge - new Service::AccessoryInformation(); - new Characteristic::Name("Pixel LEDS" DEVICE_SUFFIX); - new Characteristic::Manufacturer("HomeSpan"); - new Characteristic::SerialNumber("123-ABC"); - new Characteristic::Model("Neo/Dot Pixels"); - new Characteristic::FirmwareRevision("1.0"); - new Characteristic::Identify(); + SPAN_ACCESSORY(); // create Bridge (note this sketch uses the SPAN_ACCESSORY() macro, introduced in v1.5.1 --- see the HomeSpan API Reference for details on this convenience macro) - new Service::HAPProtocolInformation(); - new Characteristic::Version("1.1.0"); - -///////// - - new SpanAccessory(); - new Service::AccessoryInformation(); - new Characteristic::Name("Neo RGB"); - new Characteristic::Manufacturer("HomeSpan"); - new Characteristic::SerialNumber("123-ABC"); - new Characteristic::Model("8-LED Strand"); - new Characteristic::FirmwareRevision("1.0"); - new Characteristic::Identify(); - + SPAN_ACCESSORY("Neo RGB"); new NeoPixel_RGB(NEOPIXEL_RGB_PIN,8); // create 8-LED NeoPixel RGB Strand with full color control -///////// + SPAN_ACCESSORY("Neo RGBW"); + new NeoPixel_RGBW(NEOPIXEL_RGBW_PIN,60); // create 60-LED NeoPixel RGBW Strand with simulated color temperature control - new SpanAccessory(); - new Service::AccessoryInformation(); - new Characteristic::Name("Neo RGBW"); - new Characteristic::Manufacturer("HomeSpan"); - new Characteristic::SerialNumber("123-ABC"); - new Characteristic::Model("60-LED Strand"); - new Characteristic::FirmwareRevision("1.0"); - new Characteristic::Identify(); - - new NeoPixel_RGBW(NEOPIXEL_RGBW_PIN,60); // create 60-LED NeoPixel RGBW Strand with simulated color temperature control - -///////// - - new SpanAccessory(); - new Service::AccessoryInformation(); - new Characteristic::Name("Dot RGB"); - new Characteristic::Manufacturer("HomeSpan"); - new Characteristic::SerialNumber("123-ABC"); - new Characteristic::Model("30-LED Strand"); - new Characteristic::FirmwareRevision("1.0"); - new Characteristic::Identify(); - - new DotStar_RGB(DOTSTAR_DATA_PIN,DOTSTAR_CLOCK_PIN,30); // create 30-LED DotStar RGB Strand displaying a spectrum of colors and using the current-limiting feature of DotStars to create flicker-free dimming + SPAN_ACCESSORY("Dot RGB"); + new DotStar_RGB(DOTSTAR_DATA_PIN,DOTSTAR_CLOCK_PIN,30); // create 30-LED DotStar RGB Strand displaying a spectrum of colors and using the current-limiting feature of DotStars to create flicker-free dimming } diff --git a/Other Examples/RemoteControl/RemoteControl.ino b/Other Examples/RemoteControl/RemoteControl.ino index eca7bc2..bc752ef 100644 --- a/Other Examples/RemoteControl/RemoteControl.ino +++ b/Other Examples/RemoteControl/RemoteControl.ino @@ -1,4 +1,29 @@ -/* HomeSpan Remote Control Example */ +/********************************************************************************* + * MIT License + * + * Copyright (c) 2020-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. + * + ********************************************************************************/ #include "HomeSpan.h" // include the HomeSpan library #include "extras/RFControl.h" // include RF Control Library diff --git a/Other Examples/ServoControl/DEV_Identify.h b/Other Examples/ServoControl/DEV_Identify.h deleted file mode 100644 index b8d21d6..0000000 --- a/Other Examples/ServoControl/DEV_Identify.h +++ /dev/null @@ -1,38 +0,0 @@ - -////////////////////////////////// -// 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 Date: Sat, 16 Apr 2022 22:09:25 -0500 Subject: [PATCH 086/100] Update Tutorials.md --- docs/Tutorials.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/Tutorials.md b/docs/Tutorials.md index 1dbad9b..6cde7d9 100644 --- a/docs/Tutorials.md +++ b/docs/Tutorials.md @@ -98,6 +98,13 @@ Example 19 illustrates, through the implementation of two On/Off LEDs, how to ad * enabling the HomeSpan Web Log and specifying an optional NTP time server with the `homeSpan.enableWebLog()` method * using the `WEBLOG()` macro to create Web Log messages + +## Other Examples + +The following examples showcase a variety of HomeSpan and HomeKit functionality as referenced in different sections of the HomeSpan documentation. The sketches can be found in the Arduino IDE under *File → Examples → HomeSpan → Other Examples* + +### [Table Lamp](../Other%20Examples/TableLamp) +A basic implementation of a Table Lamp Accessory as fully described in used in the [HomeSpan API Overview](Overview.md) --- From aabdde2a3025cdc17489a5d57ae4021fb5453ec5 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 16 Apr 2022 22:41:50 -0500 Subject: [PATCH 087/100] Update Tutorials.md --- docs/Tutorials.md | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/docs/Tutorials.md b/docs/Tutorials.md index 6cde7d9..5cf646c 100644 --- a/docs/Tutorials.md +++ b/docs/Tutorials.md @@ -103,8 +103,23 @@ Example 19 illustrates, through the implementation of two On/Off LEDs, how to ad The following examples showcase a variety of HomeSpan and HomeKit functionality as referenced in different sections of the HomeSpan documentation. The sketches can be found in the Arduino IDE under *File → Examples → HomeSpan → Other Examples* -### [Table Lamp](../Other%20Examples/TableLamp) -A basic implementation of a Table Lamp Accessory as fully described in used in the [HomeSpan API Overview](Overview.md) +### [TableLamp](../Other%20Examples/TableLamp) +A basic implementation of a Table Lamp Accessory. Used as the tutorial in [HomeSpan API Overview](Overview.md) + +### [RemoteControl](../Other%20Examples/RemoteControl) +A standalone example that shows how to use HomeSpan's *RFControl* class to produce a custom pulse train. For illustrative purposes the pulse widths are very long and suitable for output to an LED so you can "see" the pulse train. See the [RF/IR Generation](RMT.md) page for full details + +### [ServoControl](../Other%20Examples/ServoControl) +An implementation of a Window Shade that uses HomeSpan's *ServoPin* class to control the horizontal tilt of the slats. See [ServoPin](PWM.md#servopinuint8_t-pin-double-initdegrees-uint16_t-minmicros-uint16_t-maxmicros-double-mindegrees-double-maxdegrees) for full details + +### [Television](../Other%20Examples/Television) +An example of HomeKit's *undocumented* Television Service showing how different Characteristics can be used to control a TV's power, input sources, and a few other functions. See the [Television Services and Characteristics](TVServices.md) page for full details + +### [Pixels](../Other%20Examples/Pixels) +Demonstrates how to use HomeSpan's *Pixel* and *Dot* classes to control one- and two-wire Addressable RGB and RGBW LEDs. See the [Addressable RGB LEDs](Pixels.md) page for full details + +### [CustomService](../Other%20Examples/CustomService) +Demonstrates how to create Custom Services and Custom Characteristics in HomeSpan to implement an Atmospheric Pressure Sensor recognized by the *Eve for HomeKit* app. See the [Custom Characteristics and Custom Services Macros](Reference.md#custom-characteristics-and-custom-services-macros) page for full details --- From 34e2d79c9feb61054580835f07b1ca8ea570ac1a Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 16 Apr 2022 22:44:45 -0500 Subject: [PATCH 088/100] Update Tutorials.md --- docs/Tutorials.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Tutorials.md b/docs/Tutorials.md index 5cf646c..b561b0e 100644 --- a/docs/Tutorials.md +++ b/docs/Tutorials.md @@ -115,7 +115,7 @@ An implementation of a Window Shade that uses HomeSpan's *ServoPin* class to con ### [Television](../Other%20Examples/Television) An example of HomeKit's *undocumented* Television Service showing how different Characteristics can be used to control a TV's power, input sources, and a few other functions. See the [Television Services and Characteristics](TVServices.md) page for full details -### [Pixels](../Other%20Examples/Pixels) +### [Pixel](../Other%20Examples/Pixel) Demonstrates how to use HomeSpan's *Pixel* and *Dot* classes to control one- and two-wire Addressable RGB and RGBW LEDs. See the [Addressable RGB LEDs](Pixels.md) page for full details ### [CustomService](../Other%20Examples/CustomService) From 78fc6fb8cd9f67bd7b331eb4c9d63c326eae7770 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 16 Apr 2022 22:45:37 -0500 Subject: [PATCH 089/100] Update Tutorials.md --- docs/Tutorials.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Tutorials.md b/docs/Tutorials.md index b561b0e..b04c2ff 100644 --- a/docs/Tutorials.md +++ b/docs/Tutorials.md @@ -119,7 +119,7 @@ An example of HomeKit's *undocumented* Television Service showing how different Demonstrates how to use HomeSpan's *Pixel* and *Dot* classes to control one- and two-wire Addressable RGB and RGBW LEDs. See the [Addressable RGB LEDs](Pixels.md) page for full details ### [CustomService](../Other%20Examples/CustomService) -Demonstrates how to create Custom Services and Custom Characteristics in HomeSpan to implement an Atmospheric Pressure Sensor recognized by the *Eve for HomeKit* app. See the [Custom Characteristics and Custom Services Macros](Reference.md#custom-characteristics-and-custom-services-macros) page for full details +Demonstrates how to create Custom Services and Custom Characteristics in HomeSpan to implement an Atmospheric Pressure Sensor recognized by the *Eve for HomeKit* app. See [Custom Characteristics and Custom Services Macros](Reference.md#custom-characteristics-and-custom-services-macros) for full details --- From b7e62f86db804e276882ae66cefee0649565ad90 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 16 Apr 2022 22:46:19 -0500 Subject: [PATCH 090/100] Update Television.ino --- Other Examples/Television/Television.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Other Examples/Television/Television.ino b/Other Examples/Television/Television.ino index ed32d7e..fda3581 100644 --- a/Other Examples/Television/Television.ino +++ b/Other Examples/Television/Television.ino @@ -1,7 +1,7 @@ /********************************************************************************* * MIT License * - * Copyright (c) 2021 Gregg E. Berman + * Copyright (c) 2021-2022 Gregg E. Berman * * https://github.com/HomeSpan/HomeSpan * From 1d7d4e3093ac3331a73cf61cd119be0167d49fc3 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 16 Apr 2022 22:55:13 -0500 Subject: [PATCH 091/100] Update Reference.md --- docs/Reference.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/Reference.md b/docs/Reference.md index 04f3763..0738d1f 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -422,6 +422,19 @@ Custom Services may contain a mix of both Custom Characteristics and standard HA A fully worked example showing how to use both the ***CUSTOM_SERV()*** and ***CUSTOM_CHAR()*** macros to create a Pressure Sensor Accessory that is recognized by *Eve for HomeKit* can be found in the Arduino IDE under [*File → Examples → HomeSpan → Other Examples → CustomService*](../Other%20Examples/CustomService). +## Other Macros + +### *SPAN_ACCESSORY()* and *SPAN_ACCESSORY(NAME)* + +A "convenience" macro that implements the following very common code snippet used when creating Accessories. The last line is only included if *NAME* (a c-style string) has been included as an argument to the macro: + +```C++ +new SpanAccessory(); + new Service::AccessoryInformation(); + new Characteristic::Identify(); + new Characteristic::Name(NAME); // included only in the second form of the macro +``` + ## User-Definable Macros ### *#define REQUIRED VERSION(major,minor,patch)* From 84d5348e9157406a71895589e22a77c132e04f37 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 16 Apr 2022 23:03:00 -0500 Subject: [PATCH 092/100] Update Reference.md --- docs/Reference.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Reference.md b/docs/Reference.md index 0738d1f..ea01578 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -426,7 +426,7 @@ A fully worked example showing how to use both the ***CUSTOM_SERV()*** and ***CU ### *SPAN_ACCESSORY()* and *SPAN_ACCESSORY(NAME)* -A "convenience" macro that implements the following very common code snippet used when creating Accessories. The last line is only included if *NAME* (a c-style string) has been included as an argument to the macro: +A "convenience" macro that implements the following very common code snippet used when creating Accessories. The last line is only included if *NAME* (a C-style string) has been included as an argument to the macro: ```C++ new SpanAccessory(); From a90cebcf600b8555dc5df8f32079cabb911733e3 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 17 Apr 2022 09:15:37 -0500 Subject: [PATCH 093/100] Update Logging.md --- docs/Logging.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/docs/Logging.md b/docs/Logging.md index 419411a..84a1516 100644 --- a/docs/Logging.md +++ b/docs/Logging.md @@ -26,6 +26,31 @@ See [Example 9 - MessageLogging](Tutorials.md#example-9---messagelogging) for a ## Web Logging +In addition to logging messages to the Arduino Serial Monitor, HomeSpan can optionally serve a HomeSpan Web Log at any page address you choose. Since the Web Log is hosted as part of HomeSpan's HAP Server, its base address and port will be the same as that of your device. For example, if your device name is *http://homespan-4e8eb8504e59.local* (assuming port 80) and you choose "myLog" as the Web Log page address, it will be hosted at *http://homespan-4e8eb8504e59.local/myLog*. + +Also embedded in the HomeSpan's Web Log functionality is the ability to call an NTP time server to set the device clock. This optional feature allows HomeSpan to create clock-based timestamps (e.g. *Sat Apr 16 19:48:41 2022*) as well as relative (e.g. *18:06:50:49* since start-up) timestamps. + +The HomeSpan Web Log page itself comprises two parts. The top of the page provides HomeSpan-generated status information, such as the name of the device, total uptime since last reboot, and version numbers of the various software components. + +The bottom of the page posts messages you create using the **WEBLOG()** macro. This macro comes only in the *printf*-style form `WEBLOG(const char *fmt, ...)`, similar to the second version of the LOG() macros described above. Web Log messages produced with the WEBLOG() are also echoed to the Arduino Serial Monitor with the same priority as LOG1() messages, meaning they will be displayed if the *Log Level* is set to 1 or greater. + +The Web Log page posts WEBLOG() messages in reverse-chronological order and supplements each message with the following additional items: + +* *Entry* - a cumulative message number (starting at 1 for the first message posted after boot up) +* *Up Time* - message time, relative to boot up, in the form DDD:HH:MM:SS +* *Log Time* - message timestamp, in standard UNIX format, provided that Web Logging has been enabled with an NTP Time Server (see below) +* *Client* - the IP Address of the Client connected to HomeSpan at the time the WEBLOG() message was created. Only applicable for messages produce in the `update()` method of a Service. Client is set to '0.0.0.0' in all other instances +* Message* - the message specified in the WEBLOG() macro. For example, `WEBLOG("Request to turn LED %d OFF",ledNumber);` could produce the message *Request to turn LED 5 OFF*. Note there is no need to add a trailing newline character to WEBLOG() messages. + +To enable Web Logging (it's turned off by default), call the `homeSpan.enableWebLog()` method near the top of your sketch. This method allows you to set: + +* the total number of WEBLOG() messages to be stored - older messages are discarded in favor of newer ones once the limit you set is reached +* the URL of an NTP time server - this is optional and only needed if you want to set the clock of the device at start-up +* the time zone for the device - this is only needed if an NTP time server has been specified +* the URL of the Web Log page - if unspecified, HomeSpan will serve the Web Log at a page named "status" + +Please see the [HomeSpan API Reference](Reference.md) for full details on `enableWebLog()`, and [Example 19 - WebLog](Tutorials.md#example-19---weblog) for a tutorial sketch demonstrating everything described above. + --- [↩️](README.md) Back to the Welcome page From 62081f50d191db3d284d50e3abaa0d5ac653860c Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 17 Apr 2022 09:18:45 -0500 Subject: [PATCH 094/100] Update Logging.md --- docs/Logging.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Logging.md b/docs/Logging.md index 84a1516..645cc27 100644 --- a/docs/Logging.md +++ b/docs/Logging.md @@ -26,7 +26,7 @@ See [Example 9 - MessageLogging](Tutorials.md#example-9---messagelogging) for a ## Web Logging -In addition to logging messages to the Arduino Serial Monitor, HomeSpan can optionally serve a HomeSpan Web Log at any page address you choose. Since the Web Log is hosted as part of HomeSpan's HAP Server, its base address and port will be the same as that of your device. For example, if your device name is *http://homespan-4e8eb8504e59.local* (assuming port 80) and you choose "myLog" as the Web Log page address, it will be hosted at *http://homespan-4e8eb8504e59.local/myLog*. +In addition to logging messages to the Arduino Serial Monitor, HomeSpan can optionally serve a HomeSpan Web Log at any page address you choose. Since the Web Log is hosted as part of HomeSpan's HAP Server, its base address and port will be the same as that of your device. For example, if your device name is *http://homespan-4e8eb8504e59.local* (assuming port 80) and you choose "myLog" as the Web Log page address, it will be hosted at *http://homespan-4e8eb8504e59.local/myLog*. Also embedded in the HomeSpan's Web Log functionality is the ability to call an NTP time server to set the device clock. This optional feature allows HomeSpan to create clock-based timestamps (e.g. *Sat Apr 16 19:48:41 2022*) as well as relative (e.g. *18:06:50:49* since start-up) timestamps. From 5ef1189425e5b41677f303299d45f53ecb25df04 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 17 Apr 2022 09:19:19 -0500 Subject: [PATCH 095/100] Update Logging.md --- docs/Logging.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Logging.md b/docs/Logging.md index 645cc27..d2c89c6 100644 --- a/docs/Logging.md +++ b/docs/Logging.md @@ -26,7 +26,7 @@ See [Example 9 - MessageLogging](Tutorials.md#example-9---messagelogging) for a ## Web Logging -In addition to logging messages to the Arduino Serial Monitor, HomeSpan can optionally serve a HomeSpan Web Log at any page address you choose. Since the Web Log is hosted as part of HomeSpan's HAP Server, its base address and port will be the same as that of your device. For example, if your device name is *http://homespan-4e8eb8504e59.local* (assuming port 80) and you choose "myLog" as the Web Log page address, it will be hosted at *http://homespan-4e8eb8504e59.local/myLog*. +In addition to logging messages to the Arduino Serial Monitor, HomeSpan can optionally serve a HomeSpan Web Log at any page address you choose. Since the Web Log is hosted as part of HomeSpan's HAP Server, its base address and port will be the same as that of your device. For example, if your device name is *http://homespan-4e8eb8504e59.local* (assuming port 80) and you choose "myLog" as the Web Log page address, it will be hosted at *http://homespan-4e8eb8504e59.local/myLog*. Also embedded in the HomeSpan's Web Log functionality is the ability to call an NTP time server to set the device clock. This optional feature allows HomeSpan to create clock-based timestamps (e.g. *Sat Apr 16 19:48:41 2022*) as well as relative (e.g. *18:06:50:49* since start-up) timestamps. From 5f20dd851e281d16cecd8a27756a5b4a35cb8c4a Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 17 Apr 2022 09:22:55 -0500 Subject: [PATCH 096/100] Update Logging.md --- docs/Logging.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Logging.md b/docs/Logging.md index d2c89c6..cb819d0 100644 --- a/docs/Logging.md +++ b/docs/Logging.md @@ -42,14 +42,14 @@ The Web Log page posts WEBLOG() messages in reverse-chronological order and supp * *Client* - the IP Address of the Client connected to HomeSpan at the time the WEBLOG() message was created. Only applicable for messages produce in the `update()` method of a Service. Client is set to '0.0.0.0' in all other instances * Message* - the message specified in the WEBLOG() macro. For example, `WEBLOG("Request to turn LED %d OFF",ledNumber);` could produce the message *Request to turn LED 5 OFF*. Note there is no need to add a trailing newline character to WEBLOG() messages. -To enable Web Logging (it's turned off by default), call the `homeSpan.enableWebLog()` method near the top of your sketch. This method allows you to set: +To enable Web Logging (it's turned off by default), call the `homeSpan.enableWebLog()` method (as fully described in [HomeSpan API Reference](Reference.md)) near the top of your sketch. This method allows you to set: * the total number of WEBLOG() messages to be stored - older messages are discarded in favor of newer ones once the limit you set is reached * the URL of an NTP time server - this is optional and only needed if you want to set the clock of the device at start-up * the time zone for the device - this is only needed if an NTP time server has been specified * the URL of the Web Log page - if unspecified, HomeSpan will serve the Web Log at a page named "status" -Please see the [HomeSpan API Reference](Reference.md) for full details on `enableWebLog()`, and [Example 19 - WebLog](Tutorials.md#example-19---weblog) for a tutorial sketch demonstrating everything described above. +See [Example 19 - WebLog](Tutorials.md#example-19---weblog) for a tutorial sketch demonstrating everything described above. --- From 601d456a54ae4618d21d63028f5d25ab9764b9e7 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 17 Apr 2022 13:43:59 -0500 Subject: [PATCH 097/100] Update Logging.md --- docs/Logging.md | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/docs/Logging.md b/docs/Logging.md index cb819d0..4f446f8 100644 --- a/docs/Logging.md +++ b/docs/Logging.md @@ -26,31 +26,38 @@ See [Example 9 - MessageLogging](Tutorials.md#example-9---messagelogging) for a ## Web Logging -In addition to logging messages to the Arduino Serial Monitor, HomeSpan can optionally serve a HomeSpan Web Log at any page address you choose. Since the Web Log is hosted as part of HomeSpan's HAP Server, its base address and port will be the same as that of your device. For example, if your device name is *http://homespan-4e8eb8504e59.local* (assuming port 80) and you choose "myLog" as the Web Log page address, it will be hosted at *http://homespan-4e8eb8504e59.local/myLog*. +In addition to logging messages to the Arduino Serial Monitor, HomeSpan can optionally serve a Web Log page at any page address you choose. Since the Web Log is hosted as part of HomeSpan's HAP Server, its base address and port will be the same as that of your device. For example, if your device name is *http://homespan-4e8eb8504e59.local* (assuming port 80) and you choose "myLog" as the Web Log page address, it will be hosted at *http://homespan-4e8eb8504e59.local/myLog*. -Also embedded in the HomeSpan's Web Log functionality is the ability to call an NTP time server to set the device clock. This optional feature allows HomeSpan to create clock-based timestamps (e.g. *Sat Apr 16 19:48:41 2022*) as well as relative (e.g. *18:06:50:49* since start-up) timestamps. +Also embedded in the HomeSpan's Web Log functionality is the ability to call an NTP time server to set the device clock. This optional feature allows HomeSpan to create clock-based timestamps (e.g. *Sat Apr 16 19:48:41 2022*). -The HomeSpan Web Log page itself comprises two parts. The top of the page provides HomeSpan-generated status information, such as the name of the device, total uptime since last reboot, and version numbers of the various software components. +The HomeSpan Web Log page itself comprises two parts: + + * the top of the page provides HomeSpan-generated status information, such as the name of the device, total uptime since last reboot, and version numbers of the various software components + + * the bottom of the page posts messages you create using the **WEBLOG()** macro. This macro comes only in the *printf*-style form `WEBLOG(const char *fmt, ...)`, similar to the second version of the LOG() macros described above. + +Messages produced with WEBLOG() are *also* echoed to the Arduino Serial Monitor with the same priority as LOG1() messages, meaning they will be output to the Serial Monitor if the *Log Level* is set to 1 or greater. The Web Log page displays messages in reverse-chronological order, supplemented with the following additional items: -The bottom of the page posts messages you create using the **WEBLOG()** macro. This macro comes only in the *printf*-style form `WEBLOG(const char *fmt, ...)`, similar to the second version of the LOG() macros described above. Web Log messages produced with the WEBLOG() are also echoed to the Arduino Serial Monitor with the same priority as LOG1() messages, meaning they will be displayed if the *Log Level* is set to 1 or greater. +* *Entry Number* - HomeSpan numbers each message, starting with 1 for the first message after rebooting +* *Up Time* - relative message time, in the form DDD:HH:MM:SS, starting at 000:00:00:00 after rebooting +* *Log Time* - absolute message time, in standard UNIX format, provided that Web Logging has been enabled with an NTP Time Server (see below) +* *Client* - the IP Address of the Client connected to HomeSpan at the time the WEBLOG() message was created. Only applicable for messages produced within the `update()` method of a Service. Client is otherwise set to '0.0.0.0' in all other instances +* *Message* - the text of the formatted message. For example, `int ledNumber=5; WEBLOG("Request to turn LED %d OFF\n",ledNumber);` would produce the message "Request to turn LED 5 OFF" -The Web Log page posts WEBLOG() messages in reverse-chronological order and supplements each message with the following additional items: - -* *Entry* - a cumulative message number (starting at 1 for the first message posted after boot up) -* *Up Time* - message time, relative to boot up, in the form DDD:HH:MM:SS -* *Log Time* - message timestamp, in standard UNIX format, provided that Web Logging has been enabled with an NTP Time Server (see below) -* *Client* - the IP Address of the Client connected to HomeSpan at the time the WEBLOG() message was created. Only applicable for messages produce in the `update()` method of a Service. Client is set to '0.0.0.0' in all other instances -* Message* - the message specified in the WEBLOG() macro. For example, `WEBLOG("Request to turn LED %d OFF",ledNumber);` could produce the message *Request to turn LED 5 OFF*. Note there is no need to add a trailing newline character to WEBLOG() messages. - -To enable Web Logging (it's turned off by default), call the `homeSpan.enableWebLog()` method (as fully described in [HomeSpan API Reference](Reference.md)) near the top of your sketch. This method allows you to set: +To enable Web Logging (it's turned off by default), call the method `homeSpan.enableWebLog()`, as more fully described in the [HomeSpan API Reference](Reference.md), near the top of your sketch. This method allows you to set: * the total number of WEBLOG() messages to be stored - older messages are discarded in favor of newer ones once the limit you set is reached * the URL of an NTP time server - this is optional and only needed if you want to set the clock of the device at start-up * the time zone for the device - this is only needed if an NTP time server has been specified * the URL of the Web Log page - if unspecified, HomeSpan will serve the Web Log at a page named "status" - -See [Example 19 - WebLog](Tutorials.md#example-19---weblog) for a tutorial sketch demonstrating everything described above. - + +Additional notes: + + * it is okay to include WEBLOG() messages in your sketch even if Web Logging is *not* enabled. In such cases HomeSpan will not serve a Web Log page, but WEBLOG() messages will still be output to the Arduino Serial Monitor if the *Log Level* is set to 1 or greater + * messages are **not** stored in NVS and are thus **not** saved between reboots + +See [Example 19 - WebLog](Tutorials.md#example-19---weblog) for a tutorial sketch demonstrating the use of `homeSpan.enableWebLog()` and the WEBLOG() macro. + --- [↩️](README.md) Back to the Welcome page From a8809aa7e086b3955bc371648bc7b8e2bc13d9ec Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 17 Apr 2022 13:48:54 -0500 Subject: [PATCH 098/100] Update README.md --- docs/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/README.md b/docs/README.md index f568117..8c4443a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -83,6 +83,7 @@ HomeSpan includes the following documentation: * [HomeSpan RFControl](https://github.com/HomeSpan/HomeSpan/blob/master/docs/RMT.md) - easy generation of RF and IR Remote Control signals using the ESP32's on-chip RMT peripheral * [HomeSpan Pixels](https://github.com/HomeSpan/HomeSpan/blob/master/docs/Pixels.md) - integrated control of addressable one- and two-wire RGB and RGBW LEDs and LED strips * [HomeSpan Television Services](https://github.com/HomeSpan/HomeSpan/blob/master/docs/TVServices.md) - how to use HomeKit's undocumented Television Services and Characteristics +* [HomeSpan Message Logging](https://github.com/HomeSpan/HomeSpan/blob/master/docs/Logging.md) - how to generate log messages for display on the Arduino Serial Monitor as well as optionally posted to an integrated Web Log page * [HomeSpan Projects](https://github.com/topics/homespan) - real-world applications of the HomeSpan Library * [HomeSpan FAQ](https://github.com/HomeSpan/HomeSpan/blob/master/docs/FAQ.md) - answers to frequently-asked questions From c42d118a3972755578f3f96e36445474ee1712dc Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 17 Apr 2022 16:33:27 -0500 Subject: [PATCH 099/100] Updated version number to 1.5.1 --- library.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library.properties b/library.properties index 8a9eb30..55fa778 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=HomeSpan -version=1.5.0 +version=1.5.1 author=Gregg maintainer=Gregg sentence=A robust and extremely easy-to-use HomeKit implementation for the Espressif ESP32 running on the Arduino IDE. From 2ac9cc25807dacc8f9b58127e0e00b5ef38e7d2f Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 17 Apr 2022 17:09:52 -0500 Subject: [PATCH 100/100] Update README.md --- docs/README.md | 39 +++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/docs/README.md b/docs/README.md index 8c4443a..867c898 100644 --- a/docs/README.md +++ b/docs/README.md @@ -29,6 +29,7 @@ HomeSpan is fully compatible with both Versions 1 and 2 of the [Arduino-ESP32 Bo * Integrated Push Button functionality supporting single, double, and long presses * Integrated access to the ESP32's on-chip Remote Control peripheral for easy generation of IR and RF signals * Dedicated classes to control one- and two-wire addressable RGB and RGBW LEDs and LED strips +* Integrated Web Log for user-defined log messages * Extensively-commented Tutorial Sketches taking you from the very basics of HomeSpan through advanced HomeKit topics * Additional examples and projects showcasing real-world implementations of HomeSpan * A complete set of documentation explaining every aspect of the HomeSpan API @@ -44,24 +45,34 @@ HomeSpan is fully compatible with both Versions 1 and 2 of the [Arduino-ESP32 Bo * Launch the WiFi Access Point * A standalone, detailed End-User Guide -## ❗Latest Update - HomeSpan 1.5.0 (2/20/2022) +## ❗Latest Update - HomeSpan 1.5.1 (4/17/2022) -* **New integrated library to control one- and two-wire Addressable RGB and RGBW LEDs and LED strips!** - * Adds two new class: - * **Pixel()** for control of one-wire RGB and RGBW LEDs and LED strips, such as this [NeoPixel RGBW LED](https://www.adafruit.com/product/2759) - * **Dot()** for control of two-wire RGB LEDs and LED strips, such as this [DotStar RGB Strip](https://www.adafruit.com/product/2241) - - * See [HomeSpan Pixels](https://github.com/HomeSpan/HomeSpan/blob/master/docs/Pixel.md) for full details, including a detailed tutorial sketch demonstrating different ways of using the **Pixel()** and **Dot()** classes, as well an advanced HomeSpan "HolidayLights" Project that shows how to develop custom special effects! +* **New Web Logging functionality** + * HomeSpan can now host a Web Log page for message logging + * New WEBLOG() macro makes is easy to create user-defined log messages + * Provides for the optional use of an NTP Time Server to set the device clock so all messages can be properly timestamped + * See [HomeSpan Message Logging](https://github.com/HomeSpan/HomeSpan/blob/master/docs/Logging.md) for full details -* **Increased the maximum number of simultaneous Controller connections from 8 to 14 (for Arduino-ESP32 version 2.0.1 and later)** - * Added new method `reserveSocketConnection(uint8_t nSockets)` to the global homeSpan object that allows for better management of custom connections - * Deprecated older `setMaxConnections(uint8_t nCon)` method +* **New *printf*-style formatting for LOG() macros** + * Adds variadic forms of the LOG0(), LOG1(), and LOG2() macros so they can be used in the same manner as a standard C printf function + * Greatly simplifies the creation of log messages + * See [HomeSpan Message Logging](https://github.com/HomeSpan/HomeSpan/blob/master/docs/Logging.md) for full details +* **New CUSTOM_SERV() macro** + * Allows for the creation of Custom Services + * Can be used in conjunction with the existing CUSTOM_CHAR() macro to produce Services beyond those provided in HAP-R2 + * Includes a fully worked example of a custom [Pressure Sensor Accessory](https://github.com/HomeSpan/HomeSpan/blob/master/Other%20Examples/CustomService) that is recognized by *Eve for HomeKit* + * See [HomeSpan API Reference](https://github.com/HomeSpan/HomeSpan/blob/master/docs/Reference.md) for details + +* **New "Safe-Load" mode for OTA updates** + * HomeSpan can check to make sure the new sketch being uploaded via OTA is another HomeSpan sketch. If not, the upload fails + * Upon rebooting after an OTA update, HomeSpan checks to ensure that OTA is enabled in the updated sketch. If not, HomeSpan rolls back to the previous version of the sketch + * See [HomeSpan OTA](https://github.com/HomeSpan/HomeSpan/blob/master/docs/OTA.md) for full details + * **Additional updates include:** - * Added new methods `setDescription(const char *desc)` and `setUnit(const char *unit)` to SpanCharacteristic. Useful when creating and working with Custom Characteristics - * Added new method `setStatusAutoOff(uint16_t duration)` to the global homeSpan object. Causes the Status LED (if used) to automatically turn off after *duration* seconds. Very handy for devices located in bedrooms or TV rooms! - * Added new method `setPairCallback(func)` to the global homeSpan object. Allows you to create custom actions whenever HomeSpan pairs or subsequently unpairs the device to the Home App - * Added new method `deleteStoredValues()` to the global homeSpan object. Provides a programmatic way of deleting the value settings of all Characteristics stored in the NVS + * a new (optional) argument to `SpanUserCommand()` that allows for passing a pointer to any arbitrary data structure + * a new SPAN_ACCESSORY() macro that expands to a common snippet of code often used when creating Accessories + * refreshed and streamlined example Tutorials, and fully reworked Examples 7 and 11, to best conform with Home App behavior under iOS 15.4 See [Releases](https://github.com/HomeSpan/HomeSpan/releases) for details on all changes and bug fixes included in this update.