diff --git a/Other Examples/Television/Television.ino b/Other Examples/Television/Television.ino index e745dbc..1d3fe58 100644 --- a/Other Examples/Television/Television.ino +++ b/Other Examples/Television/Television.ino @@ -193,7 +193,7 @@ void setup() { new Characteristic::TargetVisibilityState(0); SpanService *hdmi10 = new Service::InputSource(); - new Characteristic::ConfiguredNameStatic("HDMI 10"); // Source Name is static and cannot be edited in Settings Screen + (new Characteristic::ConfiguredName("HDMI 10"))->removePerms(PW); // Source Name permissions changed and now cannot be edited in Settings Screen new Characteristic::Identifier(10); new Characteristic::IsConfigured(1); // Source included in the Settings Screen... new Characteristic::CurrentVisibilityState(0); // ...and included in the Selection List... diff --git a/docs/README.md b/docs/README.md index ab8e871..7452336 100644 --- a/docs/README.md +++ b/docs/README.md @@ -41,14 +41,21 @@ 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.4.1 (10/31/2021) - -* **Television Services and Characteristics have been added to HomeSpan!** See [HomeSpan Television Services](https://github.com/HomeSpan/HomeSpan/blob/master/docs/TVServices.md) for complete details +## ❗Latest Update - HomeSpan 1.4.2 (11/27/2021) -* **The RFControl library has been updated to allow for the generation of a modulating carrier wave suitable for controlling an infrared LED.** This allows you to create HomeKit-enabled TV remote controls with HomeSpan. See [HomeSpan Projects](https://github.com/topics/homespan) for some real-world examples! +* **Updated for compatability with Arduino-ESP32 Board Manager 2.0.1** + * Maintains backward compatability with all previous versions + +* **Some new methods and options for advance-use circumstances:** + + * Added *optional* second argument to the `setVal()` method that allows the value of a Characteristic to be updated *without* sending notification messages to HomeKit. Useful for keeping track of duration time when implementing a Sprinkler System - see [HomeSpan Reference Sprinkler](https://github.com/HomeSpan/HomeSpanReferenceSketches/tree/main/ReferenceSprinklers) for an example + + * Added `getLinks()` as a new method to SpanService. Returns a vector of pointers to SpanServices that have been linked to another Service with the addLink() method. Useful for looping over all linked services, such as checking all valves in a Shower System - see [HomeSpan Reference Shower](https://github.com/HomeSpan/HomeSpanReferenceSketches/tree/main/ReferenceShower) or an example + + * Added `setPerms()`, `addPerms()`, and `removePerms()` as new methods to SpanCharacteristic. Allows the user to modify (set/add/remove) the default permissions for any Characteristic. Useful for adding/deleting write-permissions for certain Characteristics + + * Added `setPairingCode()` method to the global homeSpan object that allows for programmatically configuring the Pairing Setup Code inside your sketch. See the HomeSpan API for important security considerations when using this function! -* **User-defined Custom Characteristics can be added to HomeSpan with a new macro.** See the [HomeSpan API](https://github.com/HomeSpan/HomeSpan/blob/master/docs/Reference.md#define-custom_charnameuuidpermsformatdefaultvalueminvaluemaxvaluestaticrange) for details (for *advanced* users only) - See [Releases](https://github.com/HomeSpan/HomeSpan/releases) for details on all changes included in this update. # HomeSpan Resources diff --git a/docs/Reference.md b/docs/Reference.md index 025c11d..cb7e88d 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -106,11 +106,18 @@ The following **optional** `homeSpan` methods enable additional features and pro * *ssid* and *pwd* are automatically saved in HomeSpan's non-volatile storage (NVS) for retrieval when the device restarts * note that the saved values are truncated if they exceed the maximum allowable characters (ssid=32; pwd=64) -> :warning: SECURITY WARNING: The purpose of this function is to allow advanced users to *dynamically* set the device's WiFi Credentials using a customized Access Point function specified by `setApFunction(func)`. It it NOT recommended to use this function to hardcode your WiFi SSID and password directly into your sketch. Instead, use one of the more secure methods provided by HomeSpan, such as typing 'W' from the CLI, or launching HomeSpan's Access Point by typing 'A' from the CLI, to set your WiFi credentials without hardcoding them into your sketch. +> :warning: SECURITY WARNING: The purpose of this function is to allow advanced users to *dynamically* set the device's WiFi Credentials using a customized Access Point function specified by `setApFunction(func)`. It it NOT recommended to use this function to hardcode your WiFi SSID and password directly into your sketch. Instead, use one of the more secure methods provided by HomeSpan, such as typing 'W' from the CLI, or launching HomeSpan's Access Point, to set your WiFi credentials without hardcoding them into your sketch * `void setWifiCallback(void (*func)())` * Sets an optional user-defined callback function, *func*, to be called by HomeSpan upon start-up just after WiFi connectivity has been established. This one-time call to *func* is provided for users that are implementing other network-related services as part of their sketch, but that cannot be started until WiFi connectivity is established. The function *func* must be of type *void* and have no arguments +* `void setPairingCode(const char *s)` + * sets the Setup Pairing Code to *s*, which **must** be exactly eight numerical digits (no dashes) + * example: `homeSpan.setPairingCode("46637726");` + * a hashed version of the Pairing Code will be saved to the device's non-volatile storage, overwriting any currently-stored Pairing Code + * if *s* contains an invalid code, an error will be reported and the code will *not* be saved. Instead, the currently-stored Pairing Code (or the HomeSpan default Pairing Code if no code has been stored) will be used + * :exclamation: SECURTY WARNING: Hardcoding a device's Pairing Code into your sketch is considered a security risk and is **not** recommended. Instead, use one of the more secure methods provided by HomeSpan, such as typing 'S \' from the CLI, or launching HomeSpan's Access Point, to set your Pairing Code without hardcoding it into your sketch + * `void setSketchVersion(const char *sVer)` * sets the version of a HomeSpan sketch to *sVer*, which can be any arbitrary character string * if unspecified, HomeSpan uses "n/a" as the default version text @@ -159,6 +166,12 @@ The following methods are supported: * adds *svc* as a Linked Service. Returns a pointer to the calling Service itself so that the method can be chained during instantiation. * note that Linked Services are only applicable for select HAP Services. See Apple's [HAP-R2](https://developer.apple.com/support/homekit-accessory-protocol/) documentation for full details. * example: `(new Service::Faucet)->addLink(new Service::Valve)->addLink(new Service::Valve);` (links two Valves to a Faucet) + +* `vector getLinks()` + * returns a vector of pointers to Services that were added using `addLink()` + * useful for creating loops that iterate over all linked Services + * note that the returned vector points to generic SpanServices, which should be re-cast as needed + * example: `for(auto myValve : faucet::getLinks()) { if((MyValve *)myValve)->active->getVal()) ... }` checks all Valves linked to to a Faucet * `virtual boolean update()` * HomeSpan calls this method upon receiving a request from a HomeKit Controller to update one or more Characteristics associated with the Service. Users should override this method with code that implements that requested updates using one or more of the SpanCharacteristic methods below. Method **must** return *true* if update succeeds, or *false* if not. @@ -201,8 +214,8 @@ The following methods are supported: * `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<>()` -* `void setVal(value)` - * sets the value of the Characteristic to *value*, and notifies all HomeKit Controllers of the change +* `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 * 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) @@ -228,6 +241,22 @@ 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 +* `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 + * example: `(new Characteristic::IsConfigured(1))->setPerms(PW+PR+EV);` + +* `SpanCharacteristic *addPerms(uint8_t perms)` + * adds new permissions, *perms*, to the default permissions for a Characteristic, 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 + * example: `(new Characteristic::IsConfigured(1))->addPerms(PW);` + +* `SpanCharacteristic *removePerms(uint8_t perms)` + * removes permissions, *perms*, from the default permissions of a Characteristic, 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 + * example: `(new Characteristic::ConfiguredName("HDMI 1"))->removePerms(PW);` + + ## *SpanButton(int pin, uint16_t longTime, uint16_t singleTime, uint16_t doubleTime)* Creating an instance of this **class** attaches a pushbutton handler to the ESP32 *pin* specified. diff --git a/library.properties b/library.properties index 274ff4b..42657a9 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=HomeSpan -version=1.4.1 +version=1.4.2 author=Gregg maintainer=Gregg sentence=A robust and extremely easy-to-use HomeKit implementation for the Espressif ESP32 running on the Arduino IDE. diff --git a/src/Characteristics.h b/src/Characteristics.h index 8b85fa2..dbb3424 100644 --- a/src/Characteristics.h +++ b/src/Characteristics.h @@ -94,7 +94,6 @@ struct HapCharacteristics { HAPCHAR( CoolingThresholdTemperature, D, PR+PW+EV, FLOAT, false ); HAPCHAR( ColorTemperature, CE, PR+PW+EV, UINT32, false ); HAPCHAR( ConfiguredName, E3, PW+PR+EV, STRING, false ); - HAPCHAR( ConfiguredNameStatic, E3, PR+EV, STRING, false ); HAPCHAR( ContactSensorState, 6A, PR+EV, UINT8, true ); HAPCHAR( CurrentAmbientLightLevel, 6B, PR+EV, FLOAT, false ); HAPCHAR( CurrentHorizontalTiltAngle, 6C, PR+EV, INT, false ); diff --git a/src/HAP.cpp b/src/HAP.cpp index 822f84e..2120511 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -52,11 +52,15 @@ void HAPClient::init(){ otaPwdHash.getChars(homeSpan.otaPwd); } + if(strlen(homeSpan.pairingCodeCommand)){ // load verification setup code if provided + homeSpan.processSerialCommand(homeSpan.pairingCodeCommand); // if load failed due to invalid code, the logic below still runs and will pick up previous code or use the default one + } + struct { // temporary structure to hold SRP verification code and salt stored in NVS uint8_t salt[16]; uint8_t verifyCode[384]; } verifyData; - + if(!nvs_get_blob(srpNVS,"VERIFYDATA",NULL,&len)){ // if found verification code data in NVS nvs_get_blob(srpNVS,"VERIFYDATA",&verifyData,&len); // retrieve data srp.loadVerifyCode(verifyData.verifyCode,verifyData.salt); // load verification code and salt into SRP structure @@ -1281,14 +1285,18 @@ void HAPClient::checkTimedWrites(){ unsigned long cTime=millis(); // get current time char c[64]; - - for(auto tw=homeSpan.TimedWrites.begin(); tw!=homeSpan.TimedWrites.end(); tw++){ // loop over all Timed Writes using an iterator + + auto tw=homeSpan.TimedWrites.begin(); + while(tw!=homeSpan.TimedWrites.end()){ if(cTime>tw->second){ // timer has expired sprintf(c,"Removing PID=%llu ALARM=%u\n",tw->first,tw->second); LOG2(c); - homeSpan.TimedWrites.erase(tw); + tw=homeSpan.TimedWrites.erase(tw); } + else + tw++; } + } ////////////////////////////////////// diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 3c2928e..f3af167 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -721,11 +721,11 @@ void Span::processSerialCommand(const char *c){ sscanf(c+1," %9[0-9]",setupCode); if(strlen(setupCode)!=8){ - Serial.print("\n*** Invalid request to change Setup Code. Code must be exactly 8 digits.\n"); + Serial.print("\n*** Invalid request to change Setup Code. Code must be exactly 8 digits.\n\n"); } else if(!network.allowedCode(setupCode)){ - Serial.print("\n*** Invalid request to change Setup Code. Code too simple.\n"); + Serial.print("\n*** Invalid request to change Setup Code. Code too simple.\n\n"); } else { sprintf(buf,"\n\nGenerating SRP verification data for new Setup Code: %.3s-%.2s-%.3s ... ",setupCode,setupCode+3,setupCode+5); diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 299c0b8..664d4b9 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -112,6 +112,7 @@ 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 + char pairingCodeCommand[12]=""; // user-specified Pairing Code - only needed if Pairing Setup Code is specified in sketch using setPairingCode() boolean connected=false; // WiFi connection status unsigned long waitTime=60000; // time to wait (in milliseconds) between WiFi connection attempts @@ -188,6 +189,8 @@ struct Span{ void setWifiCallback(void (*f)()){wifiCallback=f;} // sets an optional user-defined function to call once WiFi connectivity is established void setApFunction(void (*f)()){apFunction=f;} // sets an optional user-defined function to call when activating the WiFi Access Point + void setPairingCode(const char *s){sprintf(pairingCodeCommand,"S %9s",s);} // sets the Pairing Code - use is NOT recommended. Use 'S' from CLI instead + 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 }; @@ -225,6 +228,7 @@ struct SpanService{ 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 SpanService *addLink(SpanService *svc); // adds svc as a Linked Service and returns pointer to self + vector getLinks(){return(linkedServices);} // returns linkedServices vector for use as range in "for-each" loops int sprintfAttributes(char *cBuf); // prints Service JSON records into buf; return number of characters printed, excluding null terminator void validate(); // error-checks Service @@ -238,7 +242,6 @@ struct SpanService{ struct SpanCharacteristic{ - union UVal { BOOL_t BOOL; UINT8_t UINT8; @@ -534,7 +537,7 @@ struct SpanCharacteristic{ } // setString() - template void setVal(T val){ + template void setVal(T val, boolean notify=true){ if((perms & EV) == 0){ Serial.printf("\n*** WARNING: Attempt to update Characteristic::%s with setVal() ignored. No NOTIFICATION permission on this characteristic\n\n",hapName); @@ -550,21 +553,54 @@ struct SpanCharacteristic{ uvSet(newValue,value); updateTime=homeSpan.snapTime; - - SpanBuf sb; // create SpanBuf object - sb.characteristic=this; // set characteristic - sb.status=StatusCode::OK; // set status - char dummy[]=""; - sb.val=dummy; // set dummy "val" so that sprintfNotify knows to consider this "update" - homeSpan.Notifications.push_back(sb); // store SpanBuf in Notifications vector - if(nvsKey){ - nvs_set_blob(homeSpan.charNVS,nvsKey,&value,sizeof(UVal)); // store data - nvs_commit(homeSpan.charNVS); + if(notify){ + SpanBuf sb; // create SpanBuf object + sb.characteristic=this; // set characteristic + sb.status=StatusCode::OK; // set status + char dummy[]=""; + sb.val=dummy; // set dummy "val" so that sprintfNotify knows to consider this "update" + homeSpan.Notifications.push_back(sb); // store SpanBuf in Notifications vector + + if(nvsKey){ + nvs_set_blob(homeSpan.charNVS,nvsKey,&value,sizeof(UVal)); // store data + nvs_commit(homeSpan.charNVS); + } } } // setVal() + + SpanCharacteristic *setPerms(uint8_t perms){ + this->perms=perms; + homeSpan.configLog+=String(" \u2b0c Change Permissions for ") + String(hapName) + " with AID=" + String(aid) + ", IID=" + String(iid) + ":"; + + char pNames[][7]={"PR","PW","EV","AA","TW","HD","WR"}; + char sep=' '; + + for(uint8_t i=0;i<7;i++){ + if(perms & (1<= ESP_IDF_VERSION_VAL(4, 0, 0) // use new method that is generic to ESP32, S2, and C3 + timer_group_clr_intr_status_in_isr(b->group,b->idx); +#else // use older method that is only for ESP32 if(b->group){ if(b->idx) TIMERG1.int_clr_timers.t1=1; @@ -291,26 +293,8 @@ void Blinker::isrTimer(void *arg){ else TIMERG0.int_clr_timers.t0=1; } -#elif CONFIG_IDF_TARGET_ESP32S2 // for some reason, the ESP32-S2 and ESP32-C3 use "int_clr" instead of "int_clr_timers" in their timer structure - if(b->group){ - if(b->idx) - TIMERG1.int_clr.t1=1; - else - TIMERG1.int_clr.t0=1; - } else { - if(b->idx) - TIMERG0.int_clr.t1=1; - else - TIMERG0.int_clr.t0=1; - } -#elif CONFIG_IDF_TARGET_ESP32C3 // ESP32-C3 only has one timer per timer group - if(b->group){ - TIMERG1.int_clr.t0=1; - } else { - TIMERG0.int_clr.t0=1; - } #endif - + if(!digitalRead(b->pin)){ digitalWrite(b->pin,1); timer_set_alarm_value(b->group,b->idx,b->onTime); diff --git a/src/src.ino b/src/src.ino index cb21b6b..671ca89 100644 --- a/src/src.ino +++ b/src/src.ino @@ -11,8 +11,8 @@ void setup() { Serial.begin(115200); homeSpan.setLogLevel(2); - homeSpan.setStatusPin(5); - homeSpan.setControlPin(33); +// homeSpan.setStatusPin(13); +// homeSpan.setControlPin(33); homeSpan.setHostNameSuffix("-lamp1"); homeSpan.setPortNum(1201); @@ -74,6 +74,7 @@ void setup() { new Characteristic::Name("Light 3"); new Characteristic::TargetPosition(); new Characteristic::OzoneDensity(); + (new Characteristic::OzoneDensity())->addPerms(PW|AA)->removePerms(EV|PR); } // end of setup()