From 218e1f661158fabf3ec23a4c0a86197a77c0f7ea Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 24 Feb 2024 18:15:52 -0600 Subject: [PATCH 001/154] added Pixel::setColorMap() --- src/src/extras/Pixel.cpp | 27 +++++++++++++++------- src/src/extras/Pixel.h | 18 ++++++++++++--- src/src/extras/extras.ino | 48 +++++++++++++-------------------------- 3 files changed, 50 insertions(+), 43 deletions(-) diff --git a/src/src/extras/Pixel.cpp b/src/src/extras/Pixel.cpp index 11f837e..8c423f6 100644 --- a/src/src/extras/Pixel.cpp +++ b/src/src/extras/Pixel.cpp @@ -42,11 +42,12 @@ Pixel::Pixel(int pin, boolean isRGBW){ return; if(isRGBW) - this->lastBit=0; + this->bytesPerPixel=4; else - this->lastBit=8; + this->bytesPerPixel=3; setTiming(0.32, 0.88, 0.64, 0.56, 80.0); // set default timing parameters (suitable for most SK68 and WS28 RGB pixels) + setColorMap(ColorMap::GRB); // set default color mapping rmt_isr_register(loadData,NULL,0,NULL); // set custom interrupt handler @@ -74,6 +75,13 @@ void Pixel::setTiming(float high0, float low0, float high1, float low1, uint32_t /////////////////// +void Pixel::setColorMap(const uint8_t *map){ + + this->map=map; +} + +/////////////////// + void Pixel::set(Color *c, int nPixels, boolean multiColor){ if(!*rf || nPixels==0) @@ -82,10 +90,10 @@ void Pixel::set(Color *c, int nPixels, boolean multiColor){ status.nPixels=nPixels; status.color=c; status.iMem=0; - status.iBit=32; status.started=true; status.px=this; status.multiColor=multiColor; + status.iByte=0; loadData(this); // load first two bytes of data to get started loadData(this); @@ -112,12 +120,15 @@ void IRAM_ATTR Pixel::loadData(void *arg){ RMTMEM.chan[status.px->rf->getChannel()].data32[status.iMem].val=0; return; } + + int startBit=status.px->map[status.iByte]; + int endBit=startBit-8; - for(int i=0;i<8;i++) - RMTMEM.chan[status.px->rf->getChannel()].data32[status.iMem++].val=status.px->pattern[(status.color->val>>(--status.iBit))&1]; - - if(status.iBit==status.px->lastBit){ - status.iBit=32; + for(int iBit=startBit;iBit>endBit;iBit--) + RMTMEM.chan[status.px->rf->getChannel()].data32[status.iMem++].val=status.px->pattern[(status.color->val>>iBit)&1]; + + if(++status.iByte==status.px->bytesPerPixel){ + status.iByte=0; status.color+=status.multiColor; status.nPixels--; } diff --git a/src/src/extras/Pixel.h b/src/src/extras/Pixel.h index 0a8d37e..cd00e06 100644 --- a/src/src/extras/Pixel.h +++ b/src/src/extras/Pixel.h @@ -37,6 +37,16 @@ [[maybe_unused]] static const char* PIXEL_TAG = "Pixel"; +namespace ColorMap { + + static const uint8_t RGB[4]={32,24,16,8}; + static const uint8_t RBG[4]={32,16,24,8}; + static const uint8_t BRG[4]={24,16,32,8}; + static const uint8_t BGR[4]={16,24,32,8}; + static const uint8_t GBR[4]={16,32,24,8}; + static const uint8_t GRB[4]={24,32,16,8}; +}; + //////////////////////////////////////////// // Single-Wire RGB/RGBW NeoPixels // //////////////////////////////////////////// @@ -49,8 +59,8 @@ class Pixel : public Blinkable { struct { uint8_t white:8; uint8_t blue:8; - uint8_t red:8; uint8_t green:8; + uint8_t red:8; }; uint32_t val; }; @@ -121,11 +131,11 @@ class Pixel : public Blinkable { struct pixel_status_t { int nPixels; Color *color; - int iBit; int iMem; boolean started; Pixel *px; boolean multiColor; + int iByte; }; RFControl *rf; // Pixel utilizes RFControl @@ -133,7 +143,8 @@ class Pixel : public Blinkable { uint32_t resetTime; // minimum time (in usec) between pulse trains uint32_t txEndMask; // mask for end-of-transmission interrupt uint32_t txThrMask; // mask for threshold interrupt - uint32_t lastBit; // 0=RGBW; 8=RGB + uint8_t bytesPerPixel; // RGBW=4; RGB=3 + const uint8_t *map; // color map representing order in which color bytes are transmitted Color onColor; // color used for on() command const int memSize=sizeof(RMTMEM.chan[0].data32)/4; // determine size (in pulses) of one channel @@ -151,6 +162,7 @@ class Pixel : public Blinkable { int getPin(){return(rf->getPin());} // returns pixel pin if valid, else returns -1 void setTiming(float high0, float low0, float high1, float low1, uint32_t lowReset); // changes default timings for bit pulse - note parameters are in MICROSECONDS + void setColorMap(const uint8_t *map); // changes the default color map from GRBW to an alternative mapping - controls order in which color bytes are transmitted operator bool(){ // override boolean operator to return true/false if creation succeeded/failed return(*rf); diff --git a/src/src/extras/extras.ino b/src/src/extras/extras.ino index c86d0cc..a47cb35 100644 --- a/src/src/extras/extras.ino +++ b/src/src/extras/extras.ino @@ -25,48 +25,32 @@ * ********************************************************************************/ -// This is a placeholder .ino file that allows you to easily edit the contents of this files using the Arduino IDE, -// as well as compile and test from this point. This file is ignored when the library is included in other sketches. +#include "Pixel.h" -#include "StepperControl.h" +#define PIXEL_PIN 26 // set this to whatever pin you are using - note pin cannot be "input only" -StepperControl *bigMotor; -StepperControl *smallMotor; - -#define BIG_MOTOR_POSITION 1600 -#define SMALL_MOTOR_POSITION 2064 - -/////////////////// +Pixel testPixel(PIXEL_PIN); void setup() { - + Serial.begin(115200); delay(1000); - Serial.printf("\nHomeSpan Stepper Control\n\n"); - bigMotor=(new Stepper_TB6612(23,32,22,14,33,27))->setStepType(StepperControl::HALF_STEP)->setAccel(10,20); - smallMotor=new Stepper_A3967(18,21,5,4,19); - -// smallMotor->setStepType(StepperControl::EIGHTH_STEP); - -// bigMotor->setStepType(StepperControl::HALF_STEP); -// bigMotor->setAccel(10,20); + Serial.printf("Pixel Test on pin %d\n",PIXEL_PIN); } -/////////////////// +////////////////////////////////////// void loop(){ + Serial.print("Red\n"); + testPixel.set(Pixel::RGB(100,0,0)); + delay(2000); - if(smallMotor->position()==0) - smallMotor->moveTo(SMALL_MOTOR_POSITION,2); - else if(smallMotor->position()==SMALL_MOTOR_POSITION) - smallMotor->moveTo(0,2); - - if(bigMotor->position()==0) - bigMotor->moveTo(BIG_MOTOR_POSITION,4); - else if(bigMotor->position()==BIG_MOTOR_POSITION) - bigMotor->moveTo(0,4); - - delay(1000); - Serial.printf("Small Motor: %d Big Motor %d\n",smallMotor->stepsRemaining(),bigMotor->stepsRemaining()); + Serial.print("Green\n"); + testPixel.set(Pixel::RGB(0,100,0)); + delay(2000); + + Serial.print("Blue\n"); + testPixel.set(Pixel::RGB(0,0,100)); + delay(2000); } From 0cf12f3331cce94f2b676c45eb79b591868a4a73 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 24 Feb 2024 20:51:45 -0600 Subject: [PATCH 002/154] Completed Pixel::setColorMap() --- src/src/extras/Pixel.h | 12 ++++----- src/src/extras/extras.ino | 56 ++++++++++++++++++++++++++++++++------- 2 files changed, 53 insertions(+), 15 deletions(-) diff --git a/src/src/extras/Pixel.h b/src/src/extras/Pixel.h index cd00e06..0a8ec75 100644 --- a/src/src/extras/Pixel.h +++ b/src/src/extras/Pixel.h @@ -39,12 +39,12 @@ namespace ColorMap { - static const uint8_t RGB[4]={32,24,16,8}; - static const uint8_t RBG[4]={32,16,24,8}; - static const uint8_t BRG[4]={24,16,32,8}; - static const uint8_t BGR[4]={16,24,32,8}; - static const uint8_t GBR[4]={16,32,24,8}; - static const uint8_t GRB[4]={24,32,16,8}; + static const uint8_t RGB[4]={31,23,15,7}; + static const uint8_t RBG[4]={31,15,23,7}; + static const uint8_t BRG[4]={23,15,31,7}; + static const uint8_t BGR[4]={15,23,31,7}; + static const uint8_t GBR[4]={15,31,23,7}; + static const uint8_t GRB[4]={23,31,15,7}; }; //////////////////////////////////////////// diff --git a/src/src/extras/extras.ino b/src/src/extras/extras.ino index a47cb35..9ac0d3f 100644 --- a/src/src/extras/extras.ino +++ b/src/src/extras/extras.ino @@ -27,30 +27,68 @@ #include "Pixel.h" -#define PIXEL_PIN 26 // set this to whatever pin you are using - note pin cannot be "input only" +#define PIXEL_PIN 32 // set this to whatever pin you are using - note pin cannot be "input only" +#define NPIXELS 60 // set to number of pixels in strand +#define COLORMAP ColorMap::GRB // sets the order in which color bytes are transmitted +#define RGBW true // set to true if RGBW, else false if just RGB -Pixel testPixel(PIXEL_PIN); +Pixel testPixel(PIXEL_PIN,RGBW); void setup() { Serial.begin(115200); delay(1000); - Serial.printf("Pixel Test on pin %d\n",PIXEL_PIN); + Serial.printf("\n\nPixel Test on pin %d with %d pixels\n\n",PIXEL_PIN,NPIXELS); + testPixel.setColorMap(COLORMAP); + } ////////////////////////////////////// void loop(){ - Serial.print("Red\n"); - testPixel.set(Pixel::RGB(100,0,0)); + + Serial.printf("Red..."); + for(int i=0;i<255;i++){ + testPixel.set(Pixel::RGB(i,0,0,0),NPIXELS); + delay(10); + } + for(int i=255;i>=0;i--){ + testPixel.set(Pixel::RGB(i,0,0,0),NPIXELS); + delay(10); + } delay(2000); - Serial.print("Green\n"); - testPixel.set(Pixel::RGB(0,100,0)); + Serial.printf("Green..."); + for(int i=0;i<255;i++){ + testPixel.set(Pixel::RGB(0,i,0,0),NPIXELS); + delay(10); + } + for(int i=255;i>=0;i--){ + testPixel.set(Pixel::RGB(0,i,0,0),NPIXELS); + delay(10); + } delay(2000); - Serial.print("Blue\n"); - testPixel.set(Pixel::RGB(0,0,100)); + Serial.printf("Blue..."); + for(int i=0;i<255;i++){ + testPixel.set(Pixel::RGB(0,0,i,0),NPIXELS); + delay(10); + } + for(int i=255;i>=0;i--){ + testPixel.set(Pixel::RGB(0,0,i,0),NPIXELS); + delay(10); + } + delay(2000); + + Serial.printf("White...\n"); + for(int i=0;i<255;i++){ + testPixel.set(Pixel::RGB(0,0,0,i),NPIXELS); + delay(10); + } + for(int i=255;i>=0;i--){ + testPixel.set(Pixel::RGB(0,0,0,i),NPIXELS); + delay(10); + } delay(2000); } From a0bf2c8c8a82aa0dcdbb7c76dcd85695ee751fea Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 25 Feb 2024 17:21:52 -0600 Subject: [PATCH 003/154] Removed setColorMap and replaced as second parameter to Pixel constructor Provides for full backwards compatibility with previous constructor. --- src/HAP.cpp | 2 +- src/HomeSpan.cpp | 2 +- src/src.ino | 2 +- src/src/extras/Pixel.cpp | 19 +++++++------------ src/src/extras/Pixel.h | 30 ++++++++++++++++++++---------- src/src/extras/extras.ino | 9 +++------ 6 files changed, 33 insertions(+), 31 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index 4f08076..d6ee651 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -52,7 +52,7 @@ void HAPClient::init(){ if(nvs_get_blob(srpNVS,"VERIFYDATA",NULL,&len)) // if Pair-Setup verification code data not found in NVS homeSpan.setPairingCode(DEFAULT_SETUP_CODE); // create and save verification from using Pairing Setup Code - if(!strlen(homeSpan.qrID)){ // is Setup ID has not been specified in sketch + if(!strlen(homeSpan.qrID)){ // if Setup ID has not been specified in sketch if(!nvs_get_str(hapNVS,"SETUPID",NULL,&len)){ // check for saved value nvs_get_str(hapNVS,"SETUPID",homeSpan.qrID,&len); // retrieve data } else { diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index fd8d50a..f521c18 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -510,7 +510,7 @@ void Span::checkConnect(){ char setupHash[9]; size_t len; - memcpy(hashInput,qrID,4); // Create the Seup ID for use with optional QR Codes. This is an undocumented feature of HAP R2! + memcpy(hashInput,qrID,4); // Create the Setup ID for use with optional QR Codes. This is an undocumented feature of HAP R2! memcpy(hashInput+4,id,17); // Step 1: Concatenate 4-character Setup ID and 17-character Accessory ID into hashInput mbedtls_sha512_ret(hashInput,21,hashOutput,0); // Step 2: Perform SHA-512 hash on combined 21-byte hashInput to create 64-byte hashOutput 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 diff --git a/src/src.ino b/src/src.ino index 43de092..1db723a 100644 --- a/src/src.ino +++ b/src/src.ino @@ -33,7 +33,7 @@ void setup() { Serial.begin(115200); - homeSpan.setLogLevel(1); + homeSpan.setLogLevel(2); homeSpan.enableWebLog(50,"pool.ntp.org","UTC",NULL); // homeSpan.enableWebLog(50,"pool.ntp.org","UTC","myStatus"); // homeSpan.enableWebLog(50,NULL,NULL,NULL); diff --git a/src/src/extras/Pixel.cpp b/src/src/extras/Pixel.cpp index 8c423f6..e7aa55f 100644 --- a/src/src/extras/Pixel.cpp +++ b/src/src/extras/Pixel.cpp @@ -35,19 +35,21 @@ // Single-Wire RGB/RGBW NeoPixels // //////////////////////////////////////////// -Pixel::Pixel(int pin, boolean isRGBW){ +//Pixel::Pixel(int pin, boolean isRGBW){ +Pixel::Pixel(int pin, pixelType_t pixelType){ rf=new RFControl(pin,false,false); // set clock to 1/80 usec, no default driver if(!*rf) return; - if(isRGBW) - this->bytesPerPixel=4; + map=pixelType; + + if(map[3]) + bytesPerPixel=4; else - this->bytesPerPixel=3; + bytesPerPixel=3; setTiming(0.32, 0.88, 0.64, 0.56, 80.0); // set default timing parameters (suitable for most SK68 and WS28 RGB pixels) - setColorMap(ColorMap::GRB); // set default color mapping rmt_isr_register(loadData,NULL,0,NULL); // set custom interrupt handler @@ -75,13 +77,6 @@ void Pixel::setTiming(float high0, float low0, float high1, float low1, uint32_t /////////////////// -void Pixel::setColorMap(const uint8_t *map){ - - this->map=map; -} - -/////////////////// - void Pixel::set(Color *c, int nPixels, boolean multiColor){ if(!*rf || nPixels==0) diff --git a/src/src/extras/Pixel.h b/src/src/extras/Pixel.h index 0a8ec75..921c928 100644 --- a/src/src/extras/Pixel.h +++ b/src/src/extras/Pixel.h @@ -37,14 +37,23 @@ [[maybe_unused]] static const char* PIXEL_TAG = "Pixel"; -namespace ColorMap { +typedef const uint8_t pixelType_t[]; - static const uint8_t RGB[4]={31,23,15,7}; - static const uint8_t RBG[4]={31,15,23,7}; - static const uint8_t BRG[4]={23,15,31,7}; - static const uint8_t BGR[4]={15,23,31,7}; - static const uint8_t GBR[4]={15,31,23,7}; - static const uint8_t GRB[4]={23,31,15,7}; +namespace PixelType { + +// const uint8_t RGB[4]={31,23,15,0}; + pixelType_t RGB={31,23,15,0}; + pixelType_t RBG={31,15,23,0}; + pixelType_t BRG={23,15,31,0}; + pixelType_t BGR={15,23,31,0}; + pixelType_t GBR={15,31,23,0}; + pixelType_t GRB={23,31,15,0}; + pixelType_t RGBW={31,23,15,7}; + pixelType_t RBGW={31,15,23,7}; + pixelType_t BRGW={23,15,31,7}; + pixelType_t BGRW={15,23,31,7}; + pixelType_t GBRW={15,31,23,7}; + pixelType_t GRBW={23,31,15,7}; }; //////////////////////////////////////////// @@ -53,7 +62,7 @@ namespace ColorMap { class Pixel : public Blinkable { - public: + public: struct Color { union{ struct { @@ -153,7 +162,9 @@ class Pixel : public Blinkable { volatile static pixel_status_t status; // storage for volatile information modified in interupt handler public: - Pixel(int pin, boolean isRGBW=false); // creates addressable single-wire RGB (false) or RGBW (true) LED connected to pin (such as the SK68 or WS28) + + Pixel(int pin, pixelType_t pixelType=PixelType::GRB); // creates addressable single-wire LED of pixelType connected to pin (such as the SK68 or WS28) + Pixel(int pin, boolean isRGBW):Pixel(pin,isRGBW?PixelType::GRBW:PixelType::GRB){}; // old-style constructor included for backwards compatibility void set(Color *c, int nPixels, boolean multiColor=true); // sets colors of nPixels based on array of Colors c; setting multiColor to false repeats Color in c[0] for all nPixels void set(Color c, int nPixels=1){set(&c,nPixels,false);} // sets color of nPixels to be equal to specific Color c @@ -162,7 +173,6 @@ class Pixel : public Blinkable { int getPin(){return(rf->getPin());} // returns pixel pin if valid, else returns -1 void setTiming(float high0, float low0, float high1, float low1, uint32_t lowReset); // changes default timings for bit pulse - note parameters are in MICROSECONDS - void setColorMap(const uint8_t *map); // changes the default color map from GRBW to an alternative mapping - controls order in which color bytes are transmitted operator bool(){ // override boolean operator to return true/false if creation succeeded/failed return(*rf); diff --git a/src/src/extras/extras.ino b/src/src/extras/extras.ino index 9ac0d3f..14d5006 100644 --- a/src/src/extras/extras.ino +++ b/src/src/extras/extras.ino @@ -27,12 +27,10 @@ #include "Pixel.h" -#define PIXEL_PIN 32 // set this to whatever pin you are using - note pin cannot be "input only" -#define NPIXELS 60 // set to number of pixels in strand -#define COLORMAP ColorMap::GRB // sets the order in which color bytes are transmitted -#define RGBW true // set to true if RGBW, else false if just RGB +#define PIXEL_PIN 26 // set this to whatever pin you are using - note pin cannot be "input only" +#define NPIXELS 8 // set to number of pixels in strand -Pixel testPixel(PIXEL_PIN,RGBW); +Pixel testPixel(PIXEL_PIN); void setup() { @@ -40,7 +38,6 @@ void setup() { delay(1000); Serial.printf("\n\nPixel Test on pin %d with %d pixels\n\n",PIXEL_PIN,NPIXELS); - testPixel.setColorMap(COLORMAP); } From c16e0612be74e0198d0f79a8815cb6d6406a69c4 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 25 Feb 2024 21:53:33 -0600 Subject: [PATCH 004/154] Added Pixel::isRGBW(), and added Other Example -> PixelTester sketch Also flagged the original constructor, Pixel(int pin, boolean isRGBW), as being deprecated, though it will still work as expected. --- .../PixelTester/PixelTester.ino | 131 ++++++++++++++++++ src/src/extras/Pixel.h | 13 +- src/src/extras/extras.ino | 73 ++++------ 3 files changed, 169 insertions(+), 48 deletions(-) create mode 100644 examples/Other Examples/PixelTester/PixelTester.ino diff --git a/examples/Other Examples/PixelTester/PixelTester.ino b/examples/Other Examples/PixelTester/PixelTester.ino new file mode 100644 index 0000000..98a3278 --- /dev/null +++ b/examples/Other Examples/PixelTester/PixelTester.ino @@ -0,0 +1,131 @@ +/********************************************************************************* + * MIT License + * + * Copyright (c) 2024 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. + * + ********************************************************************************/ + +/////////////////////// PIXEL TESTER ////////////////////////// + +// This sketch is designed to help identify the proper settings to use for a NeoPixel, NeoPixel Strip, +// or any device containing one or more single-wire addressable RGB or RGBW LEDs (the "Pixel Device"). + +// Before compiling, set PIXEL_PIN to the ESP32 pin that is connected to your Pixel Device, and set NPIXELS to +// the numnber of Pixels in the Pixel Device. Note that for some strips a single chip controls more than one LED, +// in which case NPIXELS should be set to the number of controlling chips, NOT the number of LEDs. + +// To start, the second argument of the Pixel constructor for the testPixel object below should remain +// set to PixelType::RGBW + +// When run, the sketch will repeatedly cycle colors by setting ALL pixels in the device first to RED, then GREEN, +// followed by BLUE, and then finally WHITE. After a short pause, the cycle repeats. + +// For each color the brightness will increase from 0 through MAX_BRIGHTNESS, and then back to 0. You can change +// MAX_BRIGHTNESS to something lower than 255 if you want to limit how bright the pixels get. + +// For Pixel Devices with more than one pixel, diagnostics are as follows: +// +// * If all 4 colors repeatedly flash in the order expected, this means the base setting of PixelType::RGBW is correct! +// +// * If instead of each pixel being set to the same color, the pixels in the strip each light up with a different color +// (or no color at all), this means you have an RGB LED, not an RGBW LED. Change the second parameter of the constructor +// to PixelType::RGB and re-run the sketch. +// +// * If all of the pixels are being set to the same color, but the sequence is NOT in the order RED, GREEN, BLUE, change +// the second parameter of the constructor so that the order of the PixelType colors match the sequence of the colors +// that appear on the Pixel Device. For example, if your RGBW Pixel Device flashes GREEN, RED, BLUE, and than WHITE, use +// PixelType::GRBW. + +// For Pixel Devices with only a single pixel, diagnostics are as follows: + +// * If all 4 colors repeatedly flash in the order expected, this means the base setting of PixelType::RGBW is correct! +// +// * If the pixel does not light at all when set to WHITE this means you have an RGB LED, not an RGBW LED. Change the +// second parameter of the constructor to PixelType::RGB and re-run the sketch. +// +// * If all of the pixels are being set to the same color, but the sequence is NOT in the order RED, GREEN, BLUE, change +// the second parameter of the constructor so that the order of the PixelType colors match the sequence of the colors +// that appear on the Pixel Device. For example, if your RGB Pixel Device flashes GREEN, RED, and then BLUE, use +// PixelType::GRB. + +////////////////////////////////////// + +#include "HomeSpan.h" + +////////////////////////////////////// + +#define MAX_BRIGHTNESS 255 // maximum brightness when flashing RGBW [0-255] + +#define PIXEL_PIN 26 // set this to whatever pin you are using - note pin cannot be "input only" +#define NPIXELS 8 // set to number of pixels in strip + +Pixel testPixel(PIXEL_PIN, PixelType::RGBW); // change the second argument until device operates with correct colors + +////////////////////////////////////// + +void setup() { + + Serial.begin(115200); + delay(1000); + + Serial.printf("\n\nPixel Test on pin %d with %d pixels\n\n",PIXEL_PIN,NPIXELS); +} + +////////////////////////////////////// + +void flashColor(boolean r, boolean g, boolean b, boolean w){ + + for(int i=0;i=0;i--){ + testPixel.set(Pixel::RGB(i*r,i*g,i*b,i*w),NPIXELS); + delay(4); + } +} + +////////////////////////////////////// + +void loop(){ + + Serial.printf("Red..."); + flashColor(1,0,0,0); + + Serial.printf("Green..."); + flashColor(0,1,0,0); + + Serial.printf("Blue..."); + flashColor(0,0,1,0); + + if(testPixel.isRGBW()){ + Serial.printf("White..."); + flashColor(0,0,0,1); + } + + Serial.printf("Pausing.\n"); + delay(1000); +} + +////////////////////////////////////// diff --git a/src/src/extras/Pixel.h b/src/src/extras/Pixel.h index 921c928..1cd37a1 100644 --- a/src/src/extras/Pixel.h +++ b/src/src/extras/Pixel.h @@ -41,7 +41,6 @@ typedef const uint8_t pixelType_t[]; namespace PixelType { -// const uint8_t RGB[4]={31,23,15,0}; pixelType_t RGB={31,23,15,0}; pixelType_t RBG={31,15,23,0}; pixelType_t BRG={23,15,31,0}; @@ -163,15 +162,15 @@ class Pixel : public Blinkable { public: - Pixel(int pin, pixelType_t pixelType=PixelType::GRB); // creates addressable single-wire LED of pixelType connected to pin (such as the SK68 or WS28) - Pixel(int pin, boolean isRGBW):Pixel(pin,isRGBW?PixelType::GRBW:PixelType::GRB){}; // old-style constructor included for backwards compatibility - void set(Color *c, int nPixels, boolean multiColor=true); // sets colors of nPixels based on array of Colors c; setting multiColor to false repeats Color in c[0] for all nPixels - void set(Color c, int nPixels=1){set(&c,nPixels,false);} // sets color of nPixels to be equal to specific Color c + Pixel(int pin, pixelType_t pixelType=PixelType::GRB); // creates addressable single-wire LED of pixelType connected to pin (such as the SK68 or WS28) + void set(Color *c, int nPixels, boolean multiColor=true); // sets colors of nPixels based on array of Colors c; setting multiColor to false repeats Color in c[0] for all nPixels + void set(Color c, int nPixels=1){set(&c,nPixels,false);} // sets color of nPixels to be equal to specific Color c static Color RGB(uint8_t r, uint8_t g, uint8_t b, uint8_t w=0){return(Color().RGB(r,g,b,w));} // an alternative method for returning an RGB Color static Color HSV(float h, float s, float v, double w=0){return(Color().HSV(h,s,v,w));} // an alternative method for returning an HSV Color int getPin(){return(rf->getPin());} // returns pixel pin if valid, else returns -1 + boolean isRGBW(){return(bytesPerPixel==4);} // returns true if RGBW LED, else false if RGB LED void setTiming(float high0, float low0, float high1, float low1, uint32_t lowReset); // changes default timings for bit pulse - note parameters are in MICROSECONDS operator bool(){ // override boolean operator to return true/false if creation succeeded/failed @@ -181,6 +180,10 @@ class Pixel : public Blinkable { void on() {set(onColor);} void off() {set(RGB(0,0,0,0));} Pixel *setOnColor(Color c){onColor=c;return(this);} + + [[deprecated("Please use Pixel(int pin, pixelType_t pixelType) constructor instead.")]] + Pixel(int pin, boolean isRGBW):Pixel(pin,isRGBW?PixelType::GRBW:PixelType::GRB){}; + }; //////////////////////////////////////////// diff --git a/src/src/extras/extras.ino b/src/src/extras/extras.ino index 14d5006..637bf1e 100644 --- a/src/src/extras/extras.ino +++ b/src/src/extras/extras.ino @@ -27,18 +27,34 @@ #include "Pixel.h" -#define PIXEL_PIN 26 // set this to whatever pin you are using - note pin cannot be "input only" -#define NPIXELS 8 // set to number of pixels in strand +#define MAX_BRIGHTNESS 255 // maximum brightness when flashing RGB [0-255] -Pixel testPixel(PIXEL_PIN); +#define PIXEL_PIN 26 // set this to whatever pin you are using - note pin cannot be "input only" +#define NPIXELS 8 // set to number of pixels in strand + +Pixel testPixel(PIXEL_PIN, PixelType::RGBW); void setup() { Serial.begin(115200); delay(1000); - Serial.printf("\n\nPixel Test on pin %d with %d pixels\n\n",PIXEL_PIN,NPIXELS); + Serial.printf("\n\nPixel Test on pin %d with %d pixels\n\n",PIXEL_PIN,NPIXELS); +} + +////////////////////////////////////// + +void flashColor(boolean r, boolean g, boolean b, boolean w){ + for(int i=0;i=0;i--){ + testPixel.set(Pixel::RGB(i*r,i*g,i*b,i*w),NPIXELS); + delay(4); + } } ////////////////////////////////////// @@ -46,46 +62,17 @@ void setup() { void loop(){ Serial.printf("Red..."); - for(int i=0;i<255;i++){ - testPixel.set(Pixel::RGB(i,0,0,0),NPIXELS); - delay(10); - } - for(int i=255;i>=0;i--){ - testPixel.set(Pixel::RGB(i,0,0,0),NPIXELS); - delay(10); - } - delay(2000); + flashColor(1,0,0,0); Serial.printf("Green..."); - for(int i=0;i<255;i++){ - testPixel.set(Pixel::RGB(0,i,0,0),NPIXELS); - delay(10); - } - for(int i=255;i>=0;i--){ - testPixel.set(Pixel::RGB(0,i,0,0),NPIXELS); - delay(10); - } - delay(2000); - - Serial.printf("Blue..."); - for(int i=0;i<255;i++){ - testPixel.set(Pixel::RGB(0,0,i,0),NPIXELS); - delay(10); - } - for(int i=255;i>=0;i--){ - testPixel.set(Pixel::RGB(0,0,i,0),NPIXELS); - delay(10); - } - delay(2000); + flashColor(0,1,0,0); - Serial.printf("White...\n"); - for(int i=0;i<255;i++){ - testPixel.set(Pixel::RGB(0,0,0,i),NPIXELS); - delay(10); - } - for(int i=255;i>=0;i--){ - testPixel.set(Pixel::RGB(0,0,0,i),NPIXELS); - delay(10); - } - delay(2000); + Serial.printf("Blue..."); + flashColor(0,0,1,0); + + Serial.printf("White..."); + flashColor(0,0,0,1); + + Serial.printf("Pausing.\n"); + delay(1000); } From e708d1d14834ad13572d73abf5c24b75d5f45b77 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 25 Feb 2024 22:19:35 -0600 Subject: [PATCH 005/154] Update Pixel.ino --- examples/Other Examples/Pixel/Pixel.ino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/Other Examples/Pixel/Pixel.ino b/examples/Other Examples/Pixel/Pixel.ino index 30df9c7..21dc284 100644 --- a/examples/Other Examples/Pixel/Pixel.ino +++ b/examples/Other Examples/Pixel/Pixel.ino @@ -76,7 +76,7 @@ struct NeoPixel_RGB : Service::LightBulb { // Addressable single-wire RGB L NeoPixel_RGB(uint8_t pin, int nPixels) : Service::LightBulb(){ V.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% - pixel=new Pixel(pin); // creates Pixel LED on specified pin + pixel=new Pixel(pin); // creates Pixel RGB LED on specified pin this->nPixels=nPixels; // save number of Pixels in this LED Strand update(); // manually call update() to set pixel with restored initial values } @@ -110,7 +110,7 @@ struct NeoPixel_RGBW : Service::LightBulb { // Addressable single-wire RGBW NeoPixel_RGBW(uint8_t pin, int nPixels) : Service::LightBulb(){ V.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% - pixel=new Pixel(pin,true); // creates Pixel RGBW LED (second parameter set to true for RGBW) on specified pin + pixel=new Pixel(pin,PixelType::GRBW); // creates Pixel RGBW LED on specified pin (with order of colors chnanged to reflect this specific NeoPixel device) this->nPixels=nPixels; // save number of Pixels in this LED Strand update(); // manually call update() to set pixel with restored initial values } From 8aec89c0fd0a2cd79c6b8d80ba5248309f43eff7 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Mon, 26 Feb 2024 05:59:38 -0600 Subject: [PATCH 006/154] update dev (#787) * Update README.md * Update Solutions.md * Update FAQ.md * Update FAQ.md * Update Solutions.md * Update Solutions.md * Update Solutions.md --- README.md | 2 +- docs/FAQ.md | 25 ++++++++++++++++++++----- docs/Solutions.md | 28 +++++++++++++++++++++++++++- 3 files changed, 48 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 33d0b42..89f05c5 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ HomeSpan provides a microcontroller-focused implementation of Apple's HomeKit Ac HomeSpan requires version 2.0.0 or later of the [Arduino-ESP32 Board Manager](https://github.com/espressif/arduino-esp32), and has been tested up through version 2.0.14 (recommended). HomeSpan can be run on the original ESP32 as well as Espressif's ESP32-S2, ESP32-C3, and ESP32-S3 chips. > [!NOTE] -> Apple's new HomeKit architecture [requires the use of a Home Hub](https://support.apple.com/en-us/HT207057) (either a HomePod or Apple TV) for full and proper operation of any HomeKit device, include those based on HomeSpan. Without a Home Hub, HomeSpan cannot send notifications to the Home App - things like pushbuttons and temperature sensors will not be able to transmit updates to the Home App. +> Apple's new HomeKit architecture [requires the use of a Home Hub](https://support.apple.com/en-us/HT207057) (either a HomePod or Apple TV) for full and proper operation of any HomeKit device, including those based on HomeSpan. Without a Home Hub, HomeSpan cannot send notifications to the Home App - things like pushbuttons and temperature sensors will not be able to transmit updates to the Home App. ### HomeSpan Highlights diff --git a/docs/FAQ.md b/docs/FAQ.md index bedebf6..9c042fe 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -20,7 +20,7 @@ #### Can you use more than one HomeSpan device on the same HomeKit network? -* Yes, multiple ESP32's, each running a separate copy of HomeSpan, can be used on the same HomeKit network, provided that each device has a unique *Device ID*, a unique *Host Name*, and a unique *Display Name*. Normally, the *Device ID* is randomly-generated by HomeSpan at start-up and will therefore automatically be unique across multiple devices. Also, unless you override the suffix of the default *Host Name*, it will also be unique across multiple devices since HomeSpan uses the *Device ID* as the suffix of the *Host Name*. The only thing you need to ensure is that you assign a different *Display Name* to each of your devices. See the [HomeSpan API Reference](https://github.com/HomeSpan/HomeSpan/blob/master/docs/Reference.md) for details on how to do this. +* Yes, multiple ESP32's, each running a separate copy of HomeSpan, can be used on the same HomeKit network, provided that each device has a unique *Device ID*, a unique *Host Name*, and a unique *Display Name*. Normally, the *Device ID* is randomly-generated by HomeSpan at start-up and will therefore automatically be unique across multiple devices. Also, unless you override the suffix of the default *Host Name*, it will also be unique across multiple devices since HomeSpan uses the *Device ID* as the suffix of the *Host Name*. The only thing you need to ensure is that you assign a different *Display Name* to each of your devices. See the [HomeSpan API Reference](Reference.md) for details on how to do this. #### Does HomeSpan require the use of a HomeKit Hub, such as a HomePod or Apple TV? @@ -36,7 +36,7 @@ #### Will HomeSpan work on an ESP8266 device? -* No, HomeSpan is coded specifically for the ESP32 and will not operate on an ESP8266 device, though an ESP8266 can be used as a remote [SpanPoint ESP-NOW](../docs/NOW.md) Device. +* No, HomeSpan is coded specifically for the ESP32 and will not operate on an ESP8266 device, though an ESP8266 can be used as a remote [SpanPoint ESP-NOW](NOW.md) Device. #### How can I read HomeSpan's MDNS broadcast mentioned in the [OTA](OTA.md) documentation? @@ -48,7 +48,7 @@ #### Does HomeSpan support Television Services? -* Yes. Though undocumented by Apple and not officially part of HAP-R2, HomeSpan supports HomeKit Television controls. See [Television Services](../docs/TVServices.md) for details. +* Yes. Though undocumented by Apple and not officially part of HAP-R2, HomeSpan supports HomeKit Television controls. See [Television Services](TVServices.md) for details. #### Can you use HomeSpan via Bluetooth? @@ -68,7 +68,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. 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. +* 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](Reference.md#custom-characteristics-and-custom-services-macros) 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. #### Can HomeSpan be used for commercial devices? @@ -76,7 +76,22 @@ #### Why does the Home App indicate the Doorbell Service is unsupported? -* Though not documented in HAP-R2, it appears that the Doorbell Service is designed to be used in conjunction with another service, such as the Lock Mechanism. If you add in a second service, the Home App will show the appropriate Tile (such as a Lock) with the Doorbell being the second service. However, you can still use the Doorbell Service on a standalone basis --- even though the Home App says it is unsupported, a button press on the device will properly trigger a chime on your Home Pods as expected. +* Though not documented in HAP-R2, it appears that the Doorbell Service is designed to be used in conjunction with another service, such as the Lock Mechanism. If you add in a second service, the Home App will show the appropriate Tile (such as a Lock) with the Doorbell being the second service. However, you can still use the Doorbell Service on a standalone basis --- even though the Home App says it is unsupported, a button press on the device will properly trigger a chime on your Home Pods as expected. + +#### How can I read the current date and time from within my sketch? + +* HomeSpan can automatically acquire the current date and time for your local timezone when you enable the HomeSpan Web Log using `homeSpan.enableWebLog()`. Note this command can be used to set the date and time even without enabling the Web Log itself (see the [HomeSpan API Reference](Reference.md) for details on how to do this). Once the date and time has been acquired, you can use the built-in Arduino-ESP32 `getLocalTime(struct tm *info)` command whenever needed to populate *info* with the current date and time. Note *info* is in [standard Unix](https://man7.org/linux/man-pages/man0/time.h.0p.html) `struct tm` format which is easily parsed as follows: + +```C++ +struct tm myTime; // create a tm structure +getLocalTime(&myTime); // populate the tm structure with current date and time + +// print the individual elements of the tm structure (see standard Unix tm structure for details) + +Serial.printf("Current Date = %02d/%02d/%04d\n", myTime.tm_mon+1, myTime.tm_mday, myTime.tm_year+1900); +Serial.printf("Current Time = %02d:%02d:%02d\n", myTime.tm_hour, myTime.tm_min, myTime.tm_sec); +``` + --- [↩️](../README.md) Back to the Welcome page diff --git a/docs/Solutions.md b/docs/Solutions.md index 7f8fd0a..074e491 100644 --- a/docs/Solutions.md +++ b/docs/Solutions.md @@ -1,6 +1,6 @@ # Common Problems and Potential Solutions -#### *HomeSpan works correctly when my ESP32 is plugged into a computer or separately powered through the USB port, but it fails to work when powered directly through the ESP32 5V pin without any USB connection* +### *HomeSpan works correctly when my ESP32 is plugged into a computer or separately powered through the USB port, but it fails to work when powered directly through the ESP32 5V pin without any USB connection* * On some ESP32 boards, the USB-UART chip only receives power if power is applied through the USB port, and thus remains unpowered if the ESP32 is powered solely through the ESP32 5V pin. As a result, the Serial RX pin associated with UART0 on the ESP32, which is normally driven by the USB-UART chip, is free to float at any voltage. If this pin floats low, the ESP32 thinks there is data to be read from the Serial Monitor, and HomeSpan falls into an infinite loop trying to read this non-existent data. @@ -13,7 +13,33 @@ * Note that adding this pull-up resistor should **not** interefere with normal serial operation of the board, such as using the Serial Monitor or uploading sketches. * *As an alternative*, instead of adding a pull-up resistor, you can simply exclude (or comment out) the `Serial.begin()` line in your sketch. This prevents the problem of HomeSpan hanging when you power it through the 5V pin, but it unfortunately means the Serial Monitor will not function when you connect the board to your computer, and you will need to add back `Serial.begin()` whenever you want to use the Serial Monitor. + +### *HomeSpan crashes when I enable PSRAM, but works fine if I disable PSRAM* + +* If your ESP32 comes with Quad or Octal PSRAM, the chip will likely need to use one or more additional I/O pins so that it can access the PSRAM using an extended SPI bus, as required for these types of PSRAM. If you happen to use one of those pins for something else (e.g an input button, an LED, etc.) the ESP32 will likely crash whenever PSRAM is enabled. +* **Resolution:** Check the documentation for your board to see what pins are reserved for use when PSRAM is enabled, and *don't use those pins for anything else.* + +### *The Serial Monitor is reporting* "gpio: gpio_set_level(226): GPIO output gpio_num error" + +* This is an ESP32-generated error message and it occurs if you try to set the output of a pin that either does not exist on your chip, or does exist but it is input-only (i.e. it cannot be used as a output). This typically occurs when you try to compile code for one chip (such as an ESP32-S2) on another chip (such as an ESP32-C3). The code will compile fine, but may produce the above error during run-time. + +* **Resolution:** Check the documentation for your board and *use only pins that exist for your chip, and are not reserved for internal functions, and are not input-only.* + +### *My sketch is too large to be uploaded (error when trying to upload)* + +* Though all ESP32 chips have a minimum of 4MB of flash memory, the amount that can be used to store a program depends on the *partition scheme* selected. By default, the ESP32 uses a parition scheme that reserves 1408 KB for a SPIFFS partition (SPI Flash File Storage) and splits the majority of the remaining flash into two equal OTA partitions of 1280 KB each for program storage. Most HomeSpan sketches come close to filling up an entire OTA partition, and if you add a lot of other libraries you will likely exceed 1280 KB. + +* **Resolution:** Select a different partition table that does not reserve so much flash memory for a SPIFFS partition since SPIFFS is not used at all by HomeSpan and *this partition is just wasting space.* + +* From within the Arduino IDE, the easiest way to reduce the SPIFFS partition is to select the *Minimal SPIFFS* partition scheme from under the Tools menu, and then simply recompile and upload you sketch. This scheme reserves only 128 KB for the SPIFFS partition, which leaves a full 1920 KB of program storage for each OTA partition. This represents a 50% increase in program size, which should suffice for most applications. + +* If for some reason you still need more space, and you only have 4MB of flash, you can try selecting the *Huge App* partition scheme, which reserves 896 KB for SPIFFS and leaves a *single* partition of 3072 KB for program storage. OTA will unfortunately *not* be available in this scheme. + +* Note: if you are not using the Arduino IDE to compile and upload HomeSpan sketches, please consult the documentation for your IDE to learn how to change the partition scheme. + +* Note: if none of the built-in partition schemes contain the right balance of partition sizes for your sketch, you can always create a custom partition scheme as demonstrated in HomeSpan's [CustomNVSParititon Example](../examples/Other%20Examples/CustomNVSPartition). This technique should work even if not using the Arduino IDE. + --- [↩️](../README.md) Back to the Welcome page From b6c479eafccd13ca9c99d0102da86b52c975d125 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Mon, 26 Feb 2024 06:50:09 -0600 Subject: [PATCH 007/154] Update Pixels.md --- docs/Pixels.md | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/docs/Pixels.md b/docs/Pixels.md index e50a3b9..4a4d283 100644 --- a/docs/Pixels.md +++ b/docs/Pixels.md @@ -6,12 +6,21 @@ Both classes allow you to individually set each of the "pixels" in a multi-pixel The methods for both classes are nearly identical, which allows you to readily interchange code written for single-wire devices to use with two-wire devices (and vice-versa) with only minor modifications. -## *Pixel(uint8_t pin, [boolean isRGBW])* +## *Pixel(uint8_t pin, [pixelType_t pixelType])* Creating an instance of this **class** configures the specified *pin* to output a waveform signal suitable for controlling a single-wire, addressable RGB or RGBW LED device with an arbitrary number of pixels. Such devices typically contain SK6812 or WS2812 LEDs. Arguments, along with their defaults if left unspecified, are as follows: * *pin* - the pin on which the RGB control signal will be output; normally connected to the "data" input of the addressable LED device - * *isRGBW* - set to *true* for RGBW devices that contain 4-color (red/green/blue/white) LEDs; set to *false* for the more typical 3-color RGB devices. Defaults to *false* if unspecified. Note you must set the *isRGBW* flag to *true* if you are using an RGBW device, even if you do not intend on utilizing the white LED + * *pixelType* - controls the order in which color data is transmitted, as well as whether the device contains 3-color (red/green/blue) or 4‑color (red/green/blue/white) LEDs. Pre-defined values for *pixelType* are provided in the **PixelType** namespace. Choose from one of the following twelve formats: + + * *PixelType::RGB, PixelType::RBG, PixelType::BRG, PixelType::BGR, PixelType::GBR, PixelType::GRB* + * *PixelType::RGBW, PixelType::RBGW, PixelType::BRGW, PixelType::BGRW, PixelType::GBRW, PixelType::GRBW* + +* Example: `Pixel myDevice(26, PixelType::BRGW);` creates a 4-color RGBW device attached to pin 26 where the colors are transmitted in the order blue, red, green, and then white + +Note that *pixelType* is optional. If left unspecified, the default value is *PixelType::GRB*. + +Since it is often not obvious which type of LED your specific device may have, HomeSpan includes a sketch designed to help you determine the correct value of *pixelType*. See [*File → Examples → HomeSpan → Other Examples → PixelTester*](../examples/Other%20Examples/PixelTester) under the Arduino IDE for detailed instructions. The two main methods to set pixel colors are: @@ -47,12 +56,16 @@ The **Pixel** class also supports the following class-level methods as a conveni * equivalent to `return(Color().HSV(h,s,v,w));` * example: `Pixel::Color c[]={Pixel::HSV(120,100,100),Pixel::HSV(60,100,100),Pixel::HSV(0,100,100)};` to create a red-yellow-green traffic light pattern -Finally, the **Pixel** class supports these two additional, but rarely-needed, methods: +Finally, the **Pixel** class supports these additional, but less-used, methods: * `int getPin()` * returns the pin number, or -1 if the instantiation failed due to lack of resources +* `boolean isRGBW()` + + * returns *true* if *pixelType* specified an RGBW LED, else *false* if just an RGB LED + * `void setTiming(float high0, float low0, float high1, float low1, uint32_t lowReset)` * the default timing parameters used by the **Pixel** class to generate the "data" signal needed to set the colors of an RGB LED device should work with most commercial products based on SK6812 or WS2812 driver chips. Use this method **ONLY** if you need to override the class defaults and replace them with your own timing parameters, where From 959e3cbcdec17d4976db3ddd1f2679c3dd457e88 Mon Sep 17 00:00:00 2001 From: Gregg Date: Tue, 27 Feb 2024 06:17:46 -0600 Subject: [PATCH 008/154] Update Pixel.cpp --- src/src/extras/Pixel.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/src/extras/Pixel.cpp b/src/src/extras/Pixel.cpp index e7aa55f..24be2ce 100644 --- a/src/src/extras/Pixel.cpp +++ b/src/src/extras/Pixel.cpp @@ -35,7 +35,6 @@ // Single-Wire RGB/RGBW NeoPixels // //////////////////////////////////////////// -//Pixel::Pixel(int pin, boolean isRGBW){ Pixel::Pixel(int pin, pixelType_t pixelType){ rf=new RFControl(pin,false,false); // set clock to 1/80 usec, no default driver From c17fabe3268db5924c8697db3efa603fbff82e32 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Thu, 29 Feb 2024 18:36:58 -0600 Subject: [PATCH 009/154] Update Pixels.md --- docs/Pixels.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/Pixels.md b/docs/Pixels.md index 4a4d283..8d3de06 100644 --- a/docs/Pixels.md +++ b/docs/Pixels.md @@ -20,7 +20,8 @@ Creating an instance of this **class** configures the specified *pin* to output Note that *pixelType* is optional. If left unspecified, the default value is *PixelType::GRB*. -Since it is often not obvious which type of LED your specific device may have, HomeSpan includes a sketch designed to help you determine the correct value of *pixelType*. See [*File → Examples → HomeSpan → Other Examples → PixelTester*](../examples/Other%20Examples/PixelTester) under the Arduino IDE for detailed instructions. +> [!TIP] +> Since it is often not obvious which type of LED your specific device may have, HomeSpan includes a sketch designed to help you determine the correct value of *pixelType*. See [*File → Examples → HomeSpan → Other Examples → PixelTester*](../examples/Other%20Examples/PixelTester) under the Arduino IDE for detailed instructions. Please use this tester sketch if you find the colors of your Pixel device are not matching what appears in the Home App. The two main methods to set pixel colors are: @@ -56,7 +57,7 @@ The **Pixel** class also supports the following class-level methods as a conveni * equivalent to `return(Color().HSV(h,s,v,w));` * example: `Pixel::Color c[]={Pixel::HSV(120,100,100),Pixel::HSV(60,100,100),Pixel::HSV(0,100,100)};` to create a red-yellow-green traffic light pattern -Finally, the **Pixel** class supports these additional, but less-used, methods: +Finally, the **Pixel** class supports these additional methods: * `int getPin()` @@ -72,7 +73,10 @@ Finally, the **Pixel** class supports these additional, but less-used, methods: * *high0* and *low0* specify the duration (in microseconds) of the high phase and low phase for a pulse encoding a zero-bit; * *high1* and *low1* specify the duration (in microseconds) of the high phase and low phase for a pulse encoding a one-bit; and * *lowReset* specifies the delay (in microseconds) representing the end of a pulse stream - * for reference, the **Pixel** class uses the following defaults: *high0=0.32𝛍s, low0=0.88𝛍s, high1=0.64𝛍s, low1=0.56𝛍s, lowReset=80.0𝛍s* + * for reference, the **Pixel** class uses the following defaults: *high0=0.32𝛍s, low0=0.88𝛍s, high1=0.64𝛍s, low1=0.56𝛍s, lowReset=80.0𝛍s* + +> [!TIP] +> If your LED colors are flickering, this is likely due to a mismatch in timing parameters. To solve, please search the web for a specifications document for the exact **chip** used by your specific device. These documents generally provide a table listing the durations of the HIGH and LOW periods required to transmit a binary 1 or binary 0. You can then use the `setTiming` method above to set the timing parameters accordingly. ### Resource Usage and Resource Conflicts From 0d404ccf1b4ef307103fe0076150583b033cd38e Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 2 Mar 2024 11:57:40 -0600 Subject: [PATCH 010/154] Fixed bug in setPairingCode() and added second "internal" argument homeSpan.setPairingCode() operates silently if called from within a sketch, but will report error to Serial Monitor and HALT sketch if code is invalid. Otherwise, if called from 'S', or from AP, or as default during start-up, diagnostics are printed as usual and sketch will NOT halt if invalid code is provided --- src/HAP.cpp | 17 +++++----- src/HAP.h | 3 +- src/HomeSpan.cpp | 82 +++++++++++++++++++++++++++++------------------- src/HomeSpan.h | 12 ++++--- src/src.ino | 3 +- 5 files changed, 68 insertions(+), 49 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index d6ee651..b87b7b7 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -38,9 +38,6 @@ void HAPClient::init(){ size_t len; // not used but required to read blobs from NVS - nvs_open("SRP",NVS_READWRITE,&srpNVS); // open SRP data namespace in NVS - nvs_open("HAP",NVS_READWRITE,&hapNVS); // open HAP data namespace in NVS - if(strlen(homeSpan.spanOTA.otaPwd)==0){ // OTA password has not been specified in sketch 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. @@ -49,9 +46,6 @@ void HAPClient::init(){ } } - if(nvs_get_blob(srpNVS,"VERIFYDATA",NULL,&len)) // if Pair-Setup verification code data not found in NVS - homeSpan.setPairingCode(DEFAULT_SETUP_CODE); // create and save verification from using Pairing Setup Code - if(!strlen(homeSpan.qrID)){ // if Setup ID has not been specified in sketch if(!nvs_get_str(hapNVS,"SETUPID",NULL,&len)){ // check for saved value nvs_get_str(hapNVS,"SETUPID",homeSpan.qrID,&len); // retrieve data @@ -59,6 +53,9 @@ void HAPClient::init(){ sprintf(homeSpan.qrID,"%s",DEFAULT_QR_ID); // use default } } + + if(nvs_get_blob(homeSpan.srpNVS,"VERIFYDATA",NULL,&len)) // if Pair-Setup verification code data not found in NVS + homeSpan.setPairingCode(DEFAULT_SETUP_CODE,false); // create and save verification from default Pairing Setup Code if(!nvs_get_blob(hapNVS,"ACCESSORY",NULL,&len)){ // if found long-term Accessory data in NVS nvs_get_blob(hapNVS,"ACCESSORY",&accessory,&len); // retrieve data @@ -316,6 +313,8 @@ int HAPClient::unauthorizedError(){ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ + static SRP6A *srp=NULL; // must persist across multiple calls to postPairSetupURL + HAPTLV iosTLV; HAPTLV responseTLV; HAPTLV subTLV; @@ -378,7 +377,7 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ TempBuffer verifyData; // retrieve verification data (should already be stored in NVS) size_t len=verifyData.len(); - nvs_get_blob(srpNVS,"VERIFYDATA",verifyData,&len); + nvs_get_blob(homeSpan.srpNVS,"VERIFYDATA",verifyData,&len); responseTLV.add(kTLVType_Salt,16,verifyData.get()->salt); // write Salt from verification data into TLV @@ -542,6 +541,7 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ tlvRespond(responseTLV); // send response to client delete srp; // delete SRP - no longer needed once pairing is completed + srp=NULL; // reset to NULL mdns_service_txt_item_set("_hap","_tcp","sf","0"); // broadcast new status @@ -1691,11 +1691,10 @@ void HapOut::HapStreamBuffer::printFormatted(char *buf, size_t nChars, size_t ns // instantiate all static HAP Client structures and data nvs_handle HAPClient::hapNVS; -nvs_handle HAPClient::srpNVS; HKDF HAPClient::hkdf; pairState HAPClient::pairStatus; Accessory HAPClient::accessory; list> HAPClient::controllerList; -SRP6A *HAPClient::srp=NULL; +//SRP6A *HAPClient::srp=NULL; int HAPClient::conNum; diff --git a/src/HAP.h b/src/HAP.h index bbf6feb..cdf176a 100644 --- a/src/HAP.h +++ b/src/HAP.h @@ -106,10 +106,9 @@ struct HAPClient { static const int MAX_ACCESSORIES=150; // maximum number of allowed Accessories (HAP limit=150) 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 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 - static SRP6A *srp; // stores all SRP-6A keys used for Pair-Setup (must persist through multiple calls to Pair-Setup) +// static SRP6A *srp; // stores all SRP-6A keys used for Pair-Setup (must persist through multiple calls to Pair-Setup) static Accessory accessory; // Accessory ID and Ed25519 public and secret keys- permanently stored static list> controllerList; // linked-list of Paired Controller IDs and ED25519 long-term public keys - permanently stored static int conNum; // connection number - used to keep track of per-connection EV notifications diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index f521c18..2b57ce7 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -45,15 +45,35 @@ const __attribute__((section(".rodata_custom_desc"))) SpanPartition spanPartitio using namespace Utils; -HapOut hapOut; // Specialized output stream that can both print to serial monitor and encrypt/transmit to HAP Clients with minimal memory usage (global-scope) +HapOut hapOut; // Specialized output stream that can both print to serial monitor and encrypt/transmit to HAP Clients with minimal memory usage (global-scoped variable) 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) +HapCharacteristics hapChars; // Instantiation of all HAP Characteristics used to create SpanCharacteristics (global-scoped variable) /////////////////////////////// // Span // /////////////////////////////// +Span::Span(){ + + 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 + + nvs_open("SRP",NVS_READWRITE,&srpNVS); // open SRP data namespace in NVS + nvs_open("HAP",NVS_READWRITE,&HAPClient::hapNVS); // open HAP data namespace in NVS + + nvs_get_u8(wifiNVS,"REBOOTS",&rebootCount); + rebootCount++; + nvs_set_u8(wifiNVS,"REBOOTS",rebootCount); + nvs_commit(wifiNVS); + +} + +/////////////////////////////// + void Span::begin(Category catID, const char *displayName, const char *hostNameBase, const char *modelName){ loopTaskHandle=xTaskGetCurrentTaskHandle(); // a roundabout way of getting the current task handle @@ -67,7 +87,7 @@ void Span::begin(Category catID, const char *displayName, const char *hostNameBa statusLED=new Blinker(statusDevice,autoOffLED); // create Status LED, even is statusDevice is NULL - esp_task_wdt_delete(xTaskGetIdleTaskHandleForCPU(0)); // required to avoid watchdog timeout messages from ESP32-C3 + esp_task_wdt_delete(xTaskGetIdleTaskHandleForCPU(0)); // required to avoid watchdog timeout messages from ESP32-C3 if(requestedMaxCon verifyData; // temporary storage for verification data SRP6A *srp=new SRP6A; // create temporary instance of SRP + + if(!progCall) + LOG0("\nGenerating new SRP verification data for Setup Code: %.3s-%.2s-%.3s ... ",setupCode,setupCode+3,setupCode+5); + + srp->createVerifyCode(setupCode,verifyData); // create random salt and compute verification code from specified Setup Code + nvs_set_blob(srpNVS,"VERIFYDATA",verifyData,verifyData.len()); // update data + nvs_commit(srpNVS); // commit to NVS - LOG0("\nGenerating SRP verification data for new Setup Code: %.3s-%.2s-%.3s ... ",setupCode,setupCode+3,setupCode+5); - - srp->createVerifyCode(setupCode,verifyData); // create random salt and compute verification code from specified Setup Code - nvs_set_blob(HAPClient::srpNVS,"VERIFYDATA",verifyData,verifyData.len()); // update data - nvs_commit(HAPClient::srpNVS); // commit to NVS - - LOG0("New Code Saved!\n"); - LOG0("Setup Payload for Optional QR Code: %s\n\n",qrCode.get(atoi(setupCode),qrID,atoi(category))); + if(!progCall) + LOG0("New Code Saved!\nSetup Payload for Optional QR Code: %s\n\n",qrCode.get(atoi(setupCode),qrID,atoi(category))); delete srp; return(*this); diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 201bcdd..7ab6f06 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -215,9 +215,6 @@ class Span{ boolean isBridge=true; // flag indicating whether device is configured as a bridge (i.e. first Accessory contains nothing but AccessoryInformation and HAPProtocolInformation) HapQR qrCode; // optional QR Code to use for pairing 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 storage 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) @@ -225,6 +222,11 @@ class Span{ uint8_t rebootCount=0; // counts number of times device was rebooted (used in optional Reboot callback) uint32_t rebootCallbackTime; // length of time to wait (in milliseconds) before calling optional Reboot callback + 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 storage of OTA data + nvs_handle srpNVS; // handle for non-volatile storage of SRP data + int connected=0; // WiFi connection status (increments upon each connect and disconnect) 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 @@ -291,6 +293,8 @@ class Span{ public: + Span(); // constructor + void begin(Category catID=DEFAULT_CATEGORY, const char *displayName=DEFAULT_DISPLAY_NAME, const char *hostNameBase=DEFAULT_HOST_NAME, @@ -342,7 +346,7 @@ class Span{ Span& setWifiCredentials(const char *ssid, const char *pwd); // sets WiFi Credentials Span& setStatusCallback(void (*f)(HS_STATUS status)){statusCallback=f;return(*this);} // sets an optional user-defined function to call when HomeSpan status changes const char* statusString(HS_STATUS s); // returns char string for HomeSpan status change messages - Span& setPairingCode(const char *s); // sets the Pairing Code - use is NOT recommended. Use 'S' from CLI instead + Span& setPairingCode(const char *s, boolean progCall=true); // sets the Pairing Code - use is NOT recommended. Use 'S' from CLI instead void deleteStoredValues(){processSerialCommand("V");} // deletes stored Characteristic values from NVS int enableOTA(boolean auth=true, boolean safeLoad=true){return(spanOTA.init(auth, safeLoad, NULL));} // enables Over-the-Air updates, with (auth=true) or without (auth=false) authorization password diff --git a/src/src.ino b/src/src.ino index 1db723a..2909559 100644 --- a/src/src.ino +++ b/src/src.ino @@ -27,7 +27,7 @@ #include "HomeSpan.h" -#define MAX_LIGHTS 2 +#define MAX_LIGHTS 1 void setup() { @@ -35,6 +35,7 @@ void setup() { homeSpan.setLogLevel(2); homeSpan.enableWebLog(50,"pool.ntp.org","UTC",NULL); + homeSpan.setPairingCode("12345670"); // homeSpan.enableWebLog(50,"pool.ntp.org","UTC","myStatus"); // homeSpan.enableWebLog(50,NULL,NULL,NULL); From 6011d09e8d11250833f917e64448966da040d5ad Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 2 Mar 2024 12:02:58 -0600 Subject: [PATCH 011/154] 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 36542a8..dc45cbb 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -196,7 +196,7 @@ The following **optional** `homeSpan` methods enable additional features and pro * 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 + * this method operated silently unless *s* contains an invalid code, in which case a fatal error will be reported to the Serial Monitor, the code will *not* be saved, and the sketch will be HALTED * :warning: 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 * `Span& setSketchVersion(const char *sVer)` From 3bb9df48185ab8d470957d948c4f957bc3730051 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 2 Mar 2024 12:16:31 -0600 Subject: [PATCH 012/154] Update README.md --- README.md | 85 +++++++++---------------------------------------------- 1 file changed, 14 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index 89f05c5..9aeaf50 100644 --- a/README.md +++ b/README.md @@ -52,81 +52,24 @@ HomeSpan requires version 2.0.0 or later of the [Arduino-ESP32 Board Manager](ht * Launch the WiFi Access Point * A standalone, detailed End-User Guide -## ❗Latest Update - HomeSpan 1.9.0 (2/17/2024) +## ❗Latest Update - HomeSpan 1.9.1 (MM/DD/YYY) -* **HomeSpan has been optimized to use significantly less RAM!** +* **Added support for more Pixel chips** - * supports approximately **TWICE** the number of Accessories using the same amount of memory - * minimized memory use also means much more room for users to add non-HomeSpan features to their sketch without running out of memory, especially if the non-HomeSpan code consumes a lot of stack space - * HomeSpan now automatically detects the presence of **PSRAM** (SPIRAM) and will utilize that extra memory to the largest extent possible to keep internal RAM free for certain HomeSpan functions and ESP32 functions (e.g. WiFi) that require internal RAM. Also keeps internal RAM free for use by any non-HomeSpan code that does not (or cannot) use PSRAM - * increased HomeSpan's 41-Accessory limit to a 150-Accessory Limit (as specified by HAP) since it is now possible to create a device with many more than 41 Accessories without running out of memory, especially if PSRAM is used + * new constructor `Pixel(uint8_t pin, [pixelType_t pixelType])` allows your to set the order in which colors are transmitted to the pixel chip, where *pixelType* is one of the following: + * PixelType::RGB, PixelType::RBG, PixelType::BRG, PixelType::BGR, PixelType::GBR, PixelType::GRB + * PixelType::RGBW, PixelType::RBGW, PixelType::BRGW, PixelType::BGRW, PixelType::GBRW, PixelType::GRBW* + * deprecated previous constructor `Pixel(uint8_t pin, boolean isRGBW)` + * this constructor will continue to work, but you will receive a warning during compilation that it has been deprecated + * users should switch to the new constructor to avoid potential compatibility issues with future versions of HomeSpan + * added new method `boolean isRGBW()` + * returns *true* if Pixel was constructed as RGBW, else *false* if constructed as RGB only (i.e. no white LED) + * created new PixelTester sketch (found under Other-> Examples) to aid in determining the *pixelType* for any LED Strip -* **HomeSpan has been optimized to use significantly less Non-Volatile Storage (NVS)** +* **Fixed bug introduced in 1.9.0 that prevented `homeSpan.setPairingCode()` from saving (and subsequently using) the request Setup Pairing Code** - * allows you to use NVS to save the values of a many more Characteristics - * see the newly-added [CustomNVSPartition](docs/Tutorials.md#customnvspartition) example that demonstrates how to create your own Partition Scheme to further expand the size of the NVS partition beyond the ESP32 default to support sketches with a large number of Accessories each configured to use NVS to save the values of many Characteristics - -* **New features and documentation for Services and Characteristics** - * created "enumerated constants" (e.g. *SWING_ENABLED*, *HUMIDIFYING*, etc.) for every applicable Characteristic that can be used instead of integers when reading and writing values - * very helpful since Apple is no longer publishing its non-commercial HAP document that provided a list and description of the states for each Characteristic - * example: `if(target.getNewVal()==target.ARM_STAY) {...}` - * added ability to properly name individual Services within a single Accessory using new **Characteristic::ConfiguredName()** - * see revised [Example 11 - ServiceNames](docs/Tutorials.md#example-11---servicenames) for details - * new [Services and Characteristics](docs/ServiceList.md) page now provides functional descriptions and detailed specifications for every Service and Characteristic supported by HomeSpan, including a list of the enumerated constants available for every Characteristic - -* **New ability to use *Inverted Buttons* and *Touch Sensors* as a Control Button** - - * adds *triggerType* as a second, optional argument to `Span& setControlPin(uint8_t pin, triggerType_t triggerType)` - * supports TRIGGER_ON_LOW, TRIGGER_ON_HIGH, TRIGGER_ON_TOUCH, or any user-defined function - * see [API Reference](docs/Reference.md) for details - -* **New ability to "remotely" trigger user-defined actions by repeatedly power-cycling the device** - - * adds new homeSpan method `Span& setRebootCallback(void (*func)(uint8_t count), uint32_t upTime=5000)` - * the parameter *count*, which is passed by HomeSpan to *func*, indicates the number of "short" reboots that have occurred prior to the current reboot, where a "short" reboot is any that occurs before *upTime* milliseconds have elapsed - * can be use to remotely restore a device that is not easily accessible to a pre-defined state - * see [API Reference](docs/Reference.md) for details - -* **Added two new Stepper Motor Drivers** - * **Stepper_UNIPOLAR**: a generic driver for any 4-wire center-tapped unipolar motor - * **Stepper_ULN2003A**: support for the ULN2003A driver board - * see [Stepper Motor Control Stepper](docs/Stepper.md) for details - -* **Additional Web Log functionality** - - * adds new homeSpan method `Span& setWebLogCallback(void (*func)(String &))` - * allows users to include additional data and custom HTML in the Web Log - * adds new homeSpan method `getWebLog(void (*f)(const char *, void *), void *args)` - * allows users to retrieve the underlying Web Log HTML from within sketch - * modified `enableWebLog()` so that it can be used to set the time from an NTP server without actually serving Web Log pages - * see [Message Logging](docs/Logging.md) for details - -* **Added ability to "chain" *homeSpan* methods** - - * converted various *homeSpan* methods that previously returned *void* to now return *Span &* - * example: `homeSpan.setControlPin(21).setStatusPin(13);` - * see [API Reference](docs/Reference.md) for details - -* **Added ability to disable SpanPoint encryption** - - * without encryption increases the maximum number of allowed SpanPoint devices from 7 to 20 - * see [SpanPoint](docs/NOW.md) for details - -* **Other new *homeSpan* methods included in this release:** - - * `Span& setVerboseWifiReconnect()` - optionally suppresses "Trying to connect to..." messages - * `Span& setWifiCallbackAll()` - provides an optional callback every time WiFi is connected *or re-connected* - * `TaskHandle_t getAutoPollTask()` - returns the Task Handle for the HomeSpan Auto Poll Task - -* **Removed dependencies on various "extra" `#include` files** - * the following \#include files are now embedded in *HomeSpan.h* and **should not be specified in any sketch:** - * *extras/Pixel.h* - * *extras/RFControl.h* - * *extras/PwmPin.h* - * *extras/StepperControl.h* - -> [!IMPORTANT] -> At present it is okay to include the above `#include` files in your sketch (they have no effect on the compiled code), but they will be deleted at some point in the future so please remove them from your sketches now to ensure forward compatibility with subsequent releases. + * this method now operates silently, unless an invalid pairing code is provided, in which case an error is reported to the Serial Monitor and *the sketch is halted* + * the process for setting the Pairing Code using the CLI 'S' command or via the Access Point are unchanged - confirmation messages are still output to the Serial Monitor and errors do *not* cause the sketch to halt See [Releases](https://github.com/HomeSpan/HomeSpan/releases) for details on all changes and bug fixes included in this update. From 24f36bbccb7a2f2d779f23faea31f59e5d11171e Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 2 Mar 2024 15:49:06 -0600 Subject: [PATCH 013/154] Moved all NVS Handles into Span Also added Span constructor that call nvs_open for each nvs_handle upon start-up instead of waiting for homeSpan.begin() and HAP::init() --- src/HAP.cpp | 34 ++++++++++++++++------------------ src/HAP.h | 4 +--- src/HomeSpan.cpp | 22 +++++++++++----------- src/HomeSpan.h | 1 + src/src.ino | 2 +- 5 files changed, 30 insertions(+), 33 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index b87b7b7..784e496 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -47,8 +47,8 @@ void HAPClient::init(){ } if(!strlen(homeSpan.qrID)){ // if Setup ID has not been specified in sketch - if(!nvs_get_str(hapNVS,"SETUPID",NULL,&len)){ // check for saved value - nvs_get_str(hapNVS,"SETUPID",homeSpan.qrID,&len); // retrieve data + if(!nvs_get_str(homeSpan.hapNVS,"SETUPID",NULL,&len)){ // check for saved value + nvs_get_str(homeSpan.hapNVS,"SETUPID",homeSpan.qrID,&len); // retrieve data } else { sprintf(homeSpan.qrID,"%s",DEFAULT_QR_ID); // use default } @@ -57,8 +57,8 @@ void HAPClient::init(){ if(nvs_get_blob(homeSpan.srpNVS,"VERIFYDATA",NULL,&len)) // if Pair-Setup verification code data not found in NVS homeSpan.setPairingCode(DEFAULT_SETUP_CODE,false); // create and save verification from default Pairing Setup Code - if(!nvs_get_blob(hapNVS,"ACCESSORY",NULL,&len)){ // if found long-term Accessory data in NVS - nvs_get_blob(hapNVS,"ACCESSORY",&accessory,&len); // retrieve data + if(!nvs_get_blob(homeSpan.hapNVS,"ACCESSORY",NULL,&len)){ // if found long-term Accessory data in NVS + nvs_get_blob(homeSpan.hapNVS,"ACCESSORY",&accessory,&len); // retrieve data } else { LOG0("Generating new random Accessory ID and Long-Term Ed25519 Signature Keys...\n\n"); uint8_t buf[6]; @@ -71,13 +71,13 @@ void HAPClient::init(){ memcpy(accessory.ID,cBuf,17); // copy into Accessory ID for permanent storage crypto_sign_keypair(accessory.LTPK,accessory.LTSK); // generate new random set of keys using libsodium public-key signature - nvs_set_blob(hapNVS,"ACCESSORY",&accessory,sizeof(accessory)); // update data - nvs_commit(hapNVS); // commit to NVS + nvs_set_blob(homeSpan.hapNVS,"ACCESSORY",&accessory,sizeof(accessory)); // update data + nvs_commit(homeSpan.hapNVS); // commit to NVS } - if(!nvs_get_blob(hapNVS,"CONTROLLERS",NULL,&len)){ // if found long-term Controller Pairings data from NVS + if(!nvs_get_blob(homeSpan.hapNVS,"CONTROLLERS",NULL,&len)){ // if found long-term Controller Pairings data from NVS TempBuffer tBuf(len/sizeof(Controller)); - nvs_get_blob(hapNVS,"CONTROLLERS",tBuf,&len); // retrieve data + nvs_get_blob(homeSpan.hapNVS,"CONTROLLERS",tBuf,&len); // retrieve data for(int i=0;i tBuf(controllerList.size()); // create temporary buffer to hold Controller data - std::copy(controllerList.begin(),controllerList.end(),tBuf.get()); // copy data from linked list to buffer + std::copy(controllerList.begin(),controllerList.end(),tBuf.get()); // copy data from linked list to buffer - nvs_set_blob(hapNVS,"CONTROLLERS",tBuf,tBuf.len()); // update data - nvs_commit(hapNVS); // commit to NVS + nvs_set_blob(homeSpan.hapNVS,"CONTROLLERS",tBuf,tBuf.len()); // update data + nvs_commit(homeSpan.hapNVS); // commit to NVS } @@ -1690,11 +1690,9 @@ void HapOut::HapStreamBuffer::printFormatted(char *buf, size_t nChars, size_t ns // instantiate all static HAP Client structures and data -nvs_handle HAPClient::hapNVS; HKDF HAPClient::hkdf; pairState HAPClient::pairStatus; Accessory HAPClient::accessory; list> HAPClient::controllerList; -//SRP6A *HAPClient::srp=NULL; int HAPClient::conNum; diff --git a/src/HAP.h b/src/HAP.h index cdf176a..6647597 100644 --- a/src/HAP.h +++ b/src/HAP.h @@ -105,11 +105,9 @@ struct HAPClient { static const int MAX_CONTROLLERS=16; // maximum number of paired controllers (HAP requires at least 16) static const int MAX_ACCESSORIES=150; // maximum number of allowed Accessories (HAP limit=150) - static nvs_handle hapNVS; // handle for non-volatile-storage of HAP data 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 -// static SRP6A *srp; // stores all SRP-6A keys used for Pair-Setup (must persist through multiple calls to Pair-Setup) - static Accessory accessory; // Accessory ID and Ed25519 public and secret keys- permanently stored + static Accessory accessory; // Accessory ID and Ed25519 public and secret keys - permanently stored static list> controllerList; // linked-list of Paired Controller IDs and ED25519 long-term public keys - permanently stored static int conNum; // connection number - used to keep track of per-connection EV notifications diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 2b57ce7..73c0499 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -63,7 +63,7 @@ Span::Span(){ nvs_open("OTA",NVS_READWRITE,&otaNVS); // open OTA data namespace in NVS nvs_open("SRP",NVS_READWRITE,&srpNVS); // open SRP data namespace in NVS - nvs_open("HAP",NVS_READWRITE,&HAPClient::hapNVS); // open HAP data namespace in NVS + nvs_open("HAP",NVS_READWRITE,&hapNVS); // open HAP data namespace in NVS nvs_get_u8(wifiNVS,"REBOOTS",&rebootCount); rebootCount++; @@ -268,7 +268,7 @@ void Span::pollTask() { LOG1("\n"); LOG2("\n"); - hap[freeSlot]->cPair=NULL; // reset pointer to verified ID + hap[freeSlot]->cPair=NULL; // reset pointer to verified ID homeSpan.clearNotify(freeSlot); // clear all notification requests for this connection HAPClient::pairStatus=pairState_M1; // reset starting PAIR STATE (which may be needed if Accessory failed in middle of pair-setup) } @@ -648,8 +648,8 @@ void Span::processSerialCommand(const char *c){ if(strlen(s)==4 && strlen(tBuf)==4){ sprintf(qrID,"%s",tBuf); LOG0("\nChanging default Setup ID for QR Code to: '%s'. Will take effect after next restart.\n\n",qrID); - nvs_set_str(HAPClient::hapNVS,"SETUPID",qrID); - nvs_commit(HAPClient::hapNVS); + nvs_set_str(hapNVS,"SETUPID",qrID); + nvs_commit(hapNVS); } else { LOG0("\n*** Invalid request to change Setup ID for QR Code to: '%s'. Setup ID must be exactly 4 alphanumeric characters (0-9, A-Z, and a-z).\n\n",s); } @@ -777,8 +777,8 @@ void Span::processSerialCommand(const char *c){ case 'H': { - nvs_erase_all(HAPClient::hapNVS); - nvs_commit(HAPClient::hapNVS); + nvs_erase_all(hapNVS); + nvs_commit(hapNVS); LOG0("\n*** HomeSpan Device ID and Pairing Data DELETED! Restarting...\n\n"); reboot(); } @@ -792,8 +792,8 @@ void Span::processSerialCommand(const char *c){ case 'F': { - nvs_erase_all(HAPClient::hapNVS); - nvs_commit(HAPClient::hapNVS); + nvs_erase_all(hapNVS); + nvs_commit(hapNVS); nvs_erase_all(wifiNVS); nvs_commit(wifiNVS); nvs_erase_all(charNVS); @@ -1078,7 +1078,7 @@ void Span::processSerialCommand(const char *c){ readSerial(qSave,1); if(qSave[0]=='y'){ LOG0("(yes)\nData saved! Rebooting..."); - nvs_set_blob(HAPClient::hapNVS,"ACCESSORY",&HAPClient::accessory,sizeof(HAPClient::accessory)); // update data + nvs_set_blob(hapNVS,"ACCESSORY",&HAPClient::accessory,sizeof(HAPClient::accessory)); // update data (commit is included in saveControllers below) HAPClient::saveControllers(); reboot(); } else @@ -1578,8 +1578,8 @@ boolean Span::updateDatabase(boolean updateMDNS){ if(hapConfig.configNumber==65536) // reached max value hapConfig.configNumber=1; // reset to 1 - nvs_set_blob(HAPClient::hapNVS,"HAPHASH",&hapConfig,sizeof(hapConfig)); // update data - nvs_commit(HAPClient::hapNVS); // commit to NVS + nvs_set_blob(hapNVS,"HAPHASH",&hapConfig,sizeof(hapConfig)); // update data + nvs_commit(hapNVS); // commit to NVS changed=true; if(updateMDNS){ diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 7ab6f06..ad16dd6 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -226,6 +226,7 @@ class Span{ nvs_handle wifiNVS=0; // handle for non-volatile-storage of WiFi data nvs_handle otaNVS; // handle for non-volatile storage of OTA data nvs_handle srpNVS; // handle for non-volatile storage of SRP data + nvs_handle hapNVS; // handle for non-volatile-storage of HAP data int connected=0; // WiFi connection status (increments upon each connect and disconnect) unsigned long waitTime=60000; // time to wait (in milliseconds) between WiFi connection attempts diff --git a/src/src.ino b/src/src.ino index 2909559..ea87240 100644 --- a/src/src.ino +++ b/src/src.ino @@ -35,7 +35,7 @@ void setup() { homeSpan.setLogLevel(2); homeSpan.enableWebLog(50,"pool.ntp.org","UTC",NULL); - homeSpan.setPairingCode("12345670"); +// homeSpan.setPairingCode("12345670"); // homeSpan.enableWebLog(50,"pool.ntp.org","UTC","myStatus"); // homeSpan.enableWebLog(50,NULL,NULL,NULL); From e3e0b117177bfb1ddd071f02710438f2ca028579 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 2 Mar 2024 16:02:16 -0600 Subject: [PATCH 014/154] Converted HKDF from struct to simple functions in HKDF namespace Eliminated the need to instantiate a static HKDF structure within HAPClient since there is no storage within HKDF itself, just a single function call (create). --- src/HAP.cpp | 13 ++++++------- src/HAP.h | 1 - src/HKDF.h | 2 +- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index 784e496..322219f 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -448,7 +448,7 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ // The iosDeviceX HKDF calculations are separate and will be performed further below with the SALT and INFO as specified in the HAP docs. TempBuffer sessionKey(crypto_box_PUBLICKEYBYTES); // temporary space - used only in this block - hkdf.create(sessionKey,srp->K,64,"Pair-Setup-Encrypt-Salt","Pair-Setup-Encrypt-Info"); // create SessionKey + HKDF::create(sessionKey,srp->K,64,"Pair-Setup-Encrypt-Salt","Pair-Setup-Encrypt-Info"); // create SessionKey LOG2("------- DECRYPTING SUB-TLVS -------\n"); @@ -488,7 +488,7 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ // Note that the SALT and INFO text fields now match those in HAP Section 5.6.6.1 TempBuffer iosDeviceX(32); - hkdf.create(iosDeviceX,srp->K,64,"Pair-Setup-Controller-Sign-Salt","Pair-Setup-Controller-Sign-Info"); // derive iosDeviceX (32 bytes) from SRP Shared Secret using HKDF + HKDF::create(iosDeviceX,srp->K,64,"Pair-Setup-Controller-Sign-Salt","Pair-Setup-Controller-Sign-Info"); // derive iosDeviceX (32 bytes) from SRP Shared Secret using HKDF // Concatenate iosDeviceX, IOS ID, and IOS PublicKey into iosDeviceInfo @@ -507,7 +507,7 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ // Now perform the above steps in reverse to securely transmit the AccessoryLTPK to the Controller (HAP Section 5.6.6.2) TempBuffer accessoryX(32); - hkdf.create(accessoryX,srp->K,64,"Pair-Setup-Accessory-Sign-Salt","Pair-Setup-Accessory-Sign-Info"); // derive accessoryX from SRP Shared Secret using HKDF + HKDF::create(accessoryX,srp->K,64,"Pair-Setup-Accessory-Sign-Salt","Pair-Setup-Accessory-Sign-Info"); // derive accessoryX from SRP Shared Secret using HKDF // Concatenate accessoryX, Accessory ID, and Accessory PublicKey into accessoryInfo @@ -638,7 +638,7 @@ int HAPClient::postPairVerifyURL(uint8_t *content, size_t len){ crypto_scalarmult_curve25519(sharedCurveKey,secretCurveKey,iosCurveKey); // generate Shared-Secret Curve25519 Key from Accessory's Curve25519 Secret Key and Controller's Curve25519 Public Key sessionKey=(uint8_t *)HS_MALLOC(crypto_box_PUBLICKEYBYTES); // temporary space - will be deleted at end of verification process - hkdf.create(sessionKey,sharedCurveKey,crypto_box_PUBLICKEYBYTES,"Pair-Verify-Encrypt-Salt","Pair-Verify-Encrypt-Info"); // create Session Curve25519 Key from Shared-Secret Curve25519 Key using HKDF-SHA-512 + HKDF::create(sessionKey,sharedCurveKey,crypto_box_PUBLICKEYBYTES,"Pair-Verify-Encrypt-Salt","Pair-Verify-Encrypt-Info"); // create Session Curve25519 Key from Shared-Secret Curve25519 Key using HKDF-SHA-512 auto itEncryptedData=responseTLV.add(kTLVType_EncryptedData,subPack.len()+crypto_aead_chacha20poly1305_IETF_ABYTES,NULL); // create blank EncryptedData subTLV crypto_aead_chacha20poly1305_ietf_encrypt(*itEncryptedData,NULL,subPack,subPack.len(),NULL,0,NULL,(unsigned char *)"\x00\x00\x00\x00PV-Msg02",sessionKey); // encrypt data with Session Curve25519 Key and padded nonce="PV-Msg02" @@ -728,8 +728,8 @@ int HAPClient::postPairVerifyURL(uint8_t *content, size_t len){ cPair=tPair; // save Controller for this connection slot - connection is now verified and should be encrypted going forward - hkdf.create(a2cKey,sharedCurveKey,32,"Control-Salt","Control-Read-Encryption-Key"); // create AccessoryToControllerKey from (previously-saved) Shared-Secret Curve25519 Key (HAP Section 6.5.2) - hkdf.create(c2aKey,sharedCurveKey,32,"Control-Salt","Control-Write-Encryption-Key"); // create ControllerToAccessoryKey from (previously-saved) Shared-Secret Curve25519 Key (HAP Section 6.5.2) + HKDF::create(a2cKey,sharedCurveKey,32,"Control-Salt","Control-Read-Encryption-Key"); // create AccessoryToControllerKey from (previously-saved) Shared-Secret Curve25519 Key (HAP Section 6.5.2) + HKDF::create(c2aKey,sharedCurveKey,32,"Control-Salt","Control-Write-Encryption-Key"); // create ControllerToAccessoryKey from (previously-saved) Shared-Secret Curve25519 Key (HAP Section 6.5.2) a2cNonce.zero(); // reset Nonces for this session to zero c2aNonce.zero(); @@ -1690,7 +1690,6 @@ void HapOut::HapStreamBuffer::printFormatted(char *buf, size_t nChars, size_t ns // instantiate all static HAP Client structures and data -HKDF HAPClient::hkdf; pairState HAPClient::pairStatus; Accessory HAPClient::accessory; list> HAPClient::controllerList; diff --git a/src/HAP.h b/src/HAP.h index 6647597..71779cb 100644 --- a/src/HAP.h +++ b/src/HAP.h @@ -105,7 +105,6 @@ struct HAPClient { static const int MAX_CONTROLLERS=16; // maximum number of paired controllers (HAP requires at least 16) static const int MAX_ACCESSORIES=150; // maximum number of allowed Accessories (HAP limit=150) - 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 static Accessory accessory; // Accessory ID and Ed25519 public and secret keys - permanently stored static list> controllerList; // linked-list of Paired Controller IDs and ED25519 long-term public keys - permanently stored diff --git a/src/HKDF.h b/src/HKDF.h index deb2760..2db1820 100644 --- a/src/HKDF.h +++ b/src/HKDF.h @@ -38,6 +38,6 @@ // incorporated under hkdf.cpp, with a wrapper to always // use SHA-512 with 32 bytes of output as required by HAP. -struct HKDF { +namespace HKDF{ int create(uint8_t *outputKey, uint8_t *inputKey, int inputLen, const char *salt, const char *info); // output of HKDF is always a 32-byte key derived from an input key, a salt string, and an info string }; From 38e0b1efea7cdd6cd39846ae6fde2bf9f354a0eb Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 3 Mar 2024 17:27:45 -0600 Subject: [PATCH 015/154] Bug-fix in SpanPoint that caused a crash when receiveSize=0 When printing SpanPoint configuration data to the Serial Monitor info using the 'i' CLI command, HomeSpan did NOT check to see if the receiveSize was zero, in which case there would be no queue, which caused a crash when trying to report the queue depth. Since all existing SpanPoint examples were based on receiving data from remote devices, this bug was never encountered (since the receiveSize was always greater than zero). Fix: check to see if receiveSize==0. If it is, do not retrieve queue depth, but report zero instead. --- src/HomeSpan.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 73c0499..876b329 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -998,7 +998,7 @@ void Span::processSerialCommand(const char *c){ for(auto it=SpanPoint::SpanPoints.begin();it!=SpanPoint::SpanPoints.end();it++) LOG0("%-18s %02X:%02X:%02X:%02X:%02X:%02X %7d %7d %7d %s\n",(*it)->peerInfo.ifidx==WIFI_IF_AP?WiFi.softAPmacAddress().c_str():WiFi.macAddress().c_str(), (*it)->peerInfo.peer_addr[0],(*it)->peerInfo.peer_addr[1],(*it)->peerInfo.peer_addr[2],(*it)->peerInfo.peer_addr[3],(*it)->peerInfo.peer_addr[4],(*it)->peerInfo.peer_addr[5], - (*it)->sendSize,(*it)->receiveSize,uxQueueSpacesAvailable((*it)->receiveQueue),esp_now_is_peer_exist((*it)->peerInfo.peer_addr)?"":"(max connections exceeded!)"); + (*it)->sendSize,(*it)->receiveSize,(*it)->receiveSize?uxQueueSpacesAvailable((*it)->receiveQueue):0,esp_now_is_peer_exist((*it)->peerInfo.peer_addr)?"":"(max connections exceeded!)"); LOG0("\nSpanPoint using WiFi Channel %d%s\n",channel,WiFi.status()!=WL_CONNECTED?" (subject to change once WiFi connection established)":""); } From be893b0968592fd7fafcc23f92df51626dba2664 Mon Sep 17 00:00:00 2001 From: Gregg Date: Mon, 4 Mar 2024 06:04:13 -0600 Subject: [PATCH 016/154] Update DEV_Blinker.h --- examples/14-EmulatedPushButtons/DEV_Blinker.h | 7 ------- 1 file changed, 7 deletions(-) diff --git a/examples/14-EmulatedPushButtons/DEV_Blinker.h b/examples/14-EmulatedPushButtons/DEV_Blinker.h index 29a9eb7..662304b 100644 --- a/examples/14-EmulatedPushButtons/DEV_Blinker.h +++ b/examples/14-EmulatedPushButtons/DEV_Blinker.h @@ -87,10 +87,3 @@ struct DEV_Blinker : Service::LightBulb { // LED Blinker }; ////////////////////////////////// - -// HomeKit Bug Note: There is an apparent bug in HomeKit uncovered during the development of this example. -// If you have an Accessory with three or more Services, and the Accessory receives a notification message -// from the device, AND the HomeKit interface is open to show the detailed control for this Service tile -// in the HomeKit app, then for some reason HomeKit sends an update() request back to the device asking to -// set the Characteristic to the value that it just received from an Event Notification. HomeKit is not supposed -// to send update requests in response to an Event Notification. From 1510718e65e930408ad8f03cdd4cda4fd3f3a2f7 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Tue, 5 Mar 2024 05:58:15 -0600 Subject: [PATCH 017/154] Update Solutions.md --- docs/Solutions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Solutions.md b/docs/Solutions.md index 074e491..1f53147 100644 --- a/docs/Solutions.md +++ b/docs/Solutions.md @@ -32,7 +32,7 @@ * **Resolution:** Select a different partition table that does not reserve so much flash memory for a SPIFFS partition since SPIFFS is not used at all by HomeSpan and *this partition is just wasting space.* -* From within the Arduino IDE, the easiest way to reduce the SPIFFS partition is to select the *Minimal SPIFFS* partition scheme from under the Tools menu, and then simply recompile and upload you sketch. This scheme reserves only 128 KB for the SPIFFS partition, which leaves a full 1920 KB of program storage for each OTA partition. This represents a 50% increase in program size, which should suffice for most applications. +* From within the Arduino IDE, the easiest way to reduce the SPIFFS partition is to select the *Minimal SPIFFS* partition scheme from under the Tools menu, and then simply recompile and upload your sketch. This scheme reserves only 128 KB for the SPIFFS partition, which leaves a full 1920 KB of program storage for each OTA partition. This represents a 50% increase in program size, which should suffice for most applications. * If for some reason you still need more space, and you only have 4MB of flash, you can try selecting the *Huge App* partition scheme, which reserves 896 KB for SPIFFS and leaves a *single* partition of 3072 KB for program storage. OTA will unfortunately *not* be available in this scheme. From 04a10309c2359754391109859ba9e185c96cd61f Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 9 Mar 2024 08:59:21 -0600 Subject: [PATCH 018/154] Sync on-line documentation (#802) * Update NOW.md * Update Solutions.md * Update Solutions.md --- docs/NOW.md | 3 +++ docs/Solutions.md | 14 +++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/docs/NOW.md b/docs/NOW.md index 986b7e0..af4626a 100644 --- a/docs/NOW.md +++ b/docs/NOW.md @@ -91,6 +91,9 @@ Examples showing such a configuration can be found in the Arduino IDE under [*Fi * *RemoteDevice.ino* - a lightweight sketch that simulates taking periodic temperature measurements, which are then transmitted to the Main Device via SpanPoint * *RemoteTempSensor.ino* - a lightweight sketch that is similar to *RemoteDevice.ino*, except that instead of simulating a temperature sensor, it implements an actual Adafruit ADT7410 I2C-based temperature sensor. This sketch also uses some power-management techniques to extend battery life, such as lowering the CPU frequency and entering into deep-sleep after each measurement is taken * *RemoteDevice8266.ino* - similar in function to *RemoteDevice.ino*, but implemented to run on an ESP8266 device using native ESP-NOW commands (since neither HomeSpan nor SpanPoint support the ESP8266). Note that the "complementary" SpanPoint object on the ESP32 that receives data from the ESP8266 must be configured to use the ESP32's *AP MAC Address* (instead of the *STA MAC Address*) by setting *useAPaddress* to *true* in the SpanPoint constructor + +Please also see the [SpanPointLightSwitch Repository](https://github.com/HomeSpan/SpanPointLightSwitch/tree/main) for a detailed example that shows how to use SpanPoint for *bi-directional communication* between an ESP32 "Central Hub" device implementing two HomeKit Lighbulb Accessories, an remote ESP32 device controlling an LED, and a separate ESP8266 device controlling another LED. + --- [↩️](../README.md) Back to the Welcome page diff --git a/docs/Solutions.md b/docs/Solutions.md index 1f53147..cb85745 100644 --- a/docs/Solutions.md +++ b/docs/Solutions.md @@ -1,5 +1,17 @@ # Common Problems and Potential Solutions +### *My HomeSpan device does not appear in the Home App when I try to pair it* + +* There are a few reasons this may occur: + + * **You have not yet entered your WiFi credentials**. HomeSpan can't connect to your WiFi network until you provide it with your WiFi SSID and password. If HomeSpan can't find these Credentials it outputs a warning message to the Serial Monitor during start-up. You can enter your WiFi Credentials into HomeSpan either directly from the Serial Monitor by using the 'W' CLI command, or via the HomeSpan Access Point Setup Page, or by hardcoding it into your sketch with the `homeSpan.setWifiCredentials()` function, though this last method is not recommended for security reasons. + + * **You are out of range of your WiFi network, or entered the wrong WiFi Credentials**. When HomeSpan first boots, if you've previously entered your WiFi Credentials (see above) it will use them to connect to your WiFi network. Check the Serial Monitor for status on whether a connection has been successfully made. If not, make sure your device is in the range of your WiFi network, and re-enter your WiFi Credentials in case you entered them incorrectly the first time. To double-check that your HomeSpan device is indeed connected to your network after HomeSpan reports a successful connection, open up a terminal window on your computer and use the `ping` command to confirm you can reach the device. + + * **Your iPhone and ESP32 device are not connected to the same WiFi network**. Make sure your HomeSpan device is connected to the same SSID as your iPhone and any HomeKit Hubs (e.g. HomePods or Apple TV). Some routers provide a separate SSID to use for IoT ("Internet of Things") devices. If you decide to use a separate SSID, make sure to configure your router so that message traffic flows unimpeded between your main SSID and your dedicated IoT SSID with absolutely no filtering of any messages. Also note that like most commercial HomeKit devices, ESP32 devices operate only on the 2.4 GHz WiFi band. Most iPhones can operate on either the 2.4 GHz or the 5.0 GHz WiFi bands, so if your router provides multi-band access, you need to make sure it is configured to allow unimpeded cross-traffic between the bands. + + * **Your device thinks it is already paired (this is the most common reason)**. Check the Serial Monitor when HomeSpan first boots - it will let you know if the device is currently *paired* or *unpaired*. If its already *paired* you must unpair it before it can be paired again with the Home App. Normally you would unpair the device from the Home App itself, but if for whatever reason you can't (perhaps the device is no longer showing up in the Home App) you can force HomeSpan to forget all its pairing data and reset its state to *unpaired* by typing either the 'U' or 'H' CLI command into the Serial Monitor. The 'U' command instructs HomeSpan to simply erase all its *Controller* pairing data and reset its state to *unpaired*. The 'H' command instructs HomeSpan to erase all its *Controller* pairing data **and** its *HomeKit Device ID*, after which it reboots into the *unpaired* state and generates a new *HomeKit Device ID*. Typing 'H' is recommended to get the cleanest refresh. Note that your WiFi Credentials and Pairing Setup Code are not changed by either of these commands. + ### *HomeSpan works correctly when my ESP32 is plugged into a computer or separately powered through the USB port, but it fails to work when powered directly through the ESP32 5V pin without any USB connection* * On some ESP32 boards, the USB-UART chip only receives power if power is applied through the USB port, and thus remains unpowered if the ESP32 is powered solely through the ESP32 5V pin. As a result, the Serial RX pin associated with UART0 on the ESP32, which is normally driven by the USB-UART chip, is free to float at any voltage. If this pin floats low, the ESP32 thinks there is data to be read from the Serial Monitor, and HomeSpan falls into an infinite loop trying to read this non-existent data. @@ -12,7 +24,7 @@ * Note that adding this pull-up resistor should **not** interefere with normal serial operation of the board, such as using the Serial Monitor or uploading sketches. -* *As an alternative*, instead of adding a pull-up resistor, you can simply exclude (or comment out) the `Serial.begin()` line in your sketch. This prevents the problem of HomeSpan hanging when you power it through the 5V pin, but it unfortunately means the Serial Monitor will not function when you connect the board to your computer, and you will need to add back `Serial.begin()` whenever you want to use the Serial Monitor. +* *As an alternative*, instead of adding a pull-up resistor, you can simply exclude (or comment out) the `Serial.begin()` line in your sketch, or call `homeSpan.setSerialInputDisable(false)` to disable HomeSpan reading from the Serial port. This should prevent the problem of HomeSpan hanging when you power it through the 5V pin, but it unfortunately means the Serial Monitor will not function when you connect the board to your computer, and you will need to add back `Serial.begin()`, or remove `homeSpan.setSerialInputDisable(false)` whenever you want to use the Serial Monitor. ### *HomeSpan crashes when I enable PSRAM, but works fine if I disable PSRAM* From 7a58a20de104fdfbea26ec35921121ca30bbf304 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 9 Mar 2024 15:07:31 -0600 Subject: [PATCH 019/154] Created Example 21 - AccessoryIdentifier --- examples/05-WorkingLED/05-WorkingLED.ino | 3 +- .../21-AccessoryIdentifier.ino | 165 ++++++++++++++++++ 2 files changed, 166 insertions(+), 2 deletions(-) create mode 100644 examples/21-AccessoryIdentifier/21-AccessoryIdentifier.ino diff --git a/examples/05-WorkingLED/05-WorkingLED.ino b/examples/05-WorkingLED/05-WorkingLED.ino index 71a68b1..b9d3263 100644 --- a/examples/05-WorkingLED/05-WorkingLED.ino +++ b/examples/05-WorkingLED/05-WorkingLED.ino @@ -30,12 +30,11 @@ // HomeSpan: A HomeKit implementation for the ESP32 // // ------------------------------------------------ // // // -// Example 5: Two working on/off LEDs based on the // +// 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 diff --git a/examples/21-AccessoryIdentifier/21-AccessoryIdentifier.ino b/examples/21-AccessoryIdentifier/21-AccessoryIdentifier.ino new file mode 100644 index 0000000..ad30093 --- /dev/null +++ b/examples/21-AccessoryIdentifier/21-AccessoryIdentifier.ino @@ -0,0 +1,165 @@ +/********************************************************************************* + * MIT License + * + * Copyright (c) 2020-2024 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 21: Using the Identify Characteristic // +// // +// // +//////////////////////////////////////////////////////////// + +// This sketch is similar to Example 5, in which we implemented two simple Lightbulb Accessories, +// except now we will also add functionality for the Identify Characteristic (we will also configure +// the device as a Bridge Accessory, instead of two standalone Accessories). + +// Recall that the Identify Characteristic has been instantiated in every example sketch since it +// is a required Characteristic of the AccessoryInformation Service, and that Service is itself +// required to be present for every Accessory. Thus, every Accessory (including the Bridge +// Accessory if used), has its own instant of the Identify Characteristic. + +// Though not typically used during normal operation of an Accessory, the Identify Characteristic +// can be useful when first pairing your device to HomeKit. You may have noticed when pairing your +// device using the Home App that there is the word "Identify" at the bottom of each of the screens +// that ask you what you want to name each Accessory, what room the Accessory should be assigned to, etc. + +// Clicking "Identify" on any of those screens causes HomeKit to send an update request to the +// Identify Characteristic associated with the corresponding Accessory. As with any Characteristic that +// is updated via the Home App, this will trigger a call to the update() method for the enclosing Service. + +// The purpose of this is so that your device can run some sort of "identification routine" when requested, +// allowing you to visually confirm that you are indeed pairing the correct device. For example, if you +// have three separate devices wired to three different lights or appliances, you want to make sure that when +// you start pairing each of them to the Home App you are connected to the device you intend. + +// The identification routine can be anything you choose. The only HAP requirement is that it should not take +// longer than 5 seconds to run. In the sketch below we have created an identification routine that logs a +// message to the Serial Monitor and blinks the LED associated with the Accessory 3 times whenever its +// Identify Characteristic is updated. + +#include "HomeSpan.h" + +////////////////////////////////////// + +// Below is the same DEV_LED Lightbulb Service we've used in many of the previous examples + +struct DEV_LED : Service::LightBulb { + + int ledPin; + SpanCharacteristic *power; + + DEV_LED(int ledPin) : Service::LightBulb(){ + + power=new Characteristic::On(); + this->ledPin=ledPin; + pinMode(ledPin,OUTPUT); + } + + boolean update(){ + digitalWrite(ledPin,power->getNewVal()); + LOG0("LED %d: Power %s\n",ledPin,power->getNewVal()?"ON":"OFF"); + return(true); + } +}; + +////////////////////////////////////// + +// NEW: Here we derive a new class, DEV_INFO, from the Accessory Information Service + +// This structure takes a single argument (ledPin), creates a name from it, and assigns +// it to the Name Characteristic. + +// It also instantiates the required Identify Characteristic, and implements an update() method +// that logs a message to the Serial Monitor and blinks the associated LED three times. + +// Note that in the update() method we do not bother to check which Characteristic has been updated. +// This is because the only possibility is the Identify Characteristic. + +// Also, we do not need to use getNewVal() to check the value. The Home App always sends a value of 1, +// since it is just trying to trigger the identification routine (the value itself is meaningless). + +struct DEV_INFO : Service::AccessoryInformation { + + int ledPin; + + DEV_INFO(int ledPin) : Service::AccessoryInformation(){ + + new Characteristic::Identify(); + char c[64]; + sprintf(c,"LED-%d",ledPin); + new Characteristic::Name(c); + this->ledPin=ledPin; + pinMode(ledPin,OUTPUT); + } + + boolean update(){ + LOG0("Running Identification for LED %d\n",ledPin); + for(int i=0;i<3;i++){ + digitalWrite(ledPin,HIGH); + delay(500); + digitalWrite(ledPin,LOW); + delay(500); + } + return(true); + } +}; + +////////////////////////////////////// + +void setup() { + + Serial.begin(115200); + + homeSpan.setLogLevel(1); + homeSpan.begin(Category::Lighting,"HomeSpan LEDS"); + + new SpanAccessory(); + new DEV_INFO(13); // Here we instantiate the new DEV_INFO structure, instead of simply instantiating Characteristic::Name() and Characteristic::Identity() as in all previous examples + + new SpanAccessory(); + new DEV_INFO(16); // Note we instantiate a new DEV_INFO structure for each Accessory in this device + new DEV_LED(16); // Here we instantiate the usual DEV_LED structure that controls the LED during normal operation + + new SpanAccessory(); // Here we add a second LED + new DEV_INFO(17); + new DEV_LED(17); +} + +////////////////////////////////////// + +void loop(){ + homeSpan.poll(); +} + +////////////////////////////////////// + +// NOTE: Once a device has been paired, it is no longer possible to trigger the Identify Characteristic from the Home App. +// Apple assumes that the identification routine is no longer needed since you can always identify the device by simply operating it. +// However, the Eve for HomeKit app DOES provide an "ID" button in the interface for each Accessory that can be used to trigger +// the identification routine for that Accessory at any time after the device has been paired. From 2d1b5673883d194902f0fa53875bbaa5ba501015 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 9 Mar 2024 17:26:05 -0600 Subject: [PATCH 020/154] Update Tutorials.md --- docs/Tutorials.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/Tutorials.md b/docs/Tutorials.md index d92e8ce..297098d 100644 --- a/docs/Tutorials.md +++ b/docs/Tutorials.md @@ -2,7 +2,8 @@ The HomeSpan library includes many tutorial sketches of increasing complexity that take you through all the functions and features of HomeSpan. The sketches are extensively annotated, and you'll even learn a lot about HomeKit itself by working through all the examples. If you've already loaded HomeSpan into your Arduino IDE, the tutorials will be found under *File → Examples → HomeSpan*. Each sketch is ready to be compiled and uploaded to your ESP32 device so you can see them in action. Alternatively, you can explore just the code within GitHub by clicking on any of titles below. Note: you may want to first read through the [HomeSpan API Overview](Overview.md) before exploring the tutorials. They will probably make a lot more sense if you do! -> :heavy_check_mark: Each example is designed to be operated after pairing your ESP32 to HomeKit so you can control HomeSpan from the Home App on your iPhone, iPad, or Mac. In principle, once you configure and pair your device to HomeKit, your Home App should automatically reflect all changes in your configuration whenever you upload a different tutorial. However, in practice this is not always the case as it seems HomeKit sometimes caches information about devices, which means what you see in your Home App may not be fully in sync with your sketch. If this occurs, unpairing and then re-pairing the ESP32 device usually fixes the issue. If not, you may have to reset the ID on the ESP32 device so that HomeKit thinks it is a new device and will not use any cached data. This is very easy to do - see the [HomeSpan Command-Line Interface (CLI)](CLI.md) page for details. +>[!TIP] +>Each example is designed to be operated after pairing your ESP32 to HomeKit so you can control HomeSpan from the Home App on your iPhone, iPad, or Mac. In principle, once you configure and pair your device to HomeKit, your Home App should automatically reflect all changes in your configuration whenever you upload a different tutorial. However, in practice this is not always the case as it seems HomeKit sometimes caches information about devices, which means what you see in your Home App may not be fully in sync with your sketch. If this occurs, unpairing and then re-pairing the ESP32 device usually fixes the issue. If not, you may have to reset the HomeKit Device ID on the ESP32 device so that HomeKit thinks it is a new device and will not use any cached data. This is very easy to do - see the [HomeSpan Command-Line Interface (CLI)](CLI.md) page for details. ### [Example 1 - SimpleLightBulb](../examples/01-SimpleLightBulb) This first example introduces the HomeSpan library and demonstrates how to implement a simple on/off light control using a combination of HomeSpan Accessory, Service, and Characteristic objects. Once this sketch has been uploaded to your HomeSpan device and the device is paired to your home, a new "lightbulb" tile will appear in the Home App of your iPhone, iPad, or Mac. Though the tile will be fully operational (i.e. you can change the status of the lightbulb from "on" or "off"), we won't yet connect an actual light or LED to the HomeSpan device, so nothing real will light up. Instead, in this and the next few examples, we'll focus on learning about the different ways HomeKit controls can be configured. Starting in Example 5, we'll connect an LED to the device and introduce the methods that actually turn the LED on and off from your Home App. HomeSpan API topics covered in this example include: @@ -106,6 +107,9 @@ Example 20 illustrates a number of advanced techniques through the implementatio * dynamically deleting Accessories with `homeSpan.deleteAccessory()` * refreshing the Accessory database (which automatically updates the Home App) using `homeSpan.updateDatabase()` * using `homeSpan.autoPoll()` to implement HomeSpan Polling in the background (and on the second core, if available) + +### [Example 21 - AccessoryIdentifier](../examples/21-AccessoryIdentifier) +Example 21 shows how the Identifier Characteristic that is always present in each Accessory's required AccessoryInformation Service can be used to create a custom "identification routine" that can be triggered from within the Home App when pairing a device. This example does not use any new HomeSpan methods. ## Other Examples From cf2a98d1c2898f2778f3654782974817ca8794eb Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 9 Mar 2024 19:00:05 -0600 Subject: [PATCH 021/154] Review and refresh comment in Tutorial Examples as needed. Also introduced the use of Enumerated Constants in Example 13 - Target States --- .../01-SimpleLightBulb/01-SimpleLightBulb.ino | 5 +- .../02-TwoSimpleLightBulbs.ino | 2 +- .../03-CeilingFanWithLight.ino | 2 +- .../04-AdvancedCeilingFan.ino | 2 +- examples/05-WorkingLED/05-WorkingLED.ino | 2 +- examples/06-DimmableLED/06-DimmableLED.ino | 2 +- .../07-AccessoryNames/07-AccessoryNames.ino | 2 +- examples/08-Bridges/08-Bridges.ino | 2 +- .../09-MessageLogging/09-MessageLogging.ino | 2 +- examples/10-RGB_LED/10-RGB_LED.ino | 2 +- examples/11-ServiceNames/11-ServiceNames.ino | 2 +- examples/12-ServiceLoops/12-ServiceLoops.ino | 2 +- examples/13-TargetStates/13-TargetStates.ino | 15 ++--- examples/13-TargetStates/DEV_DoorsWindows.h | 62 ++++++++++--------- .../14-EmulatedPushButtons.ino | 4 +- .../15-RealPushButtons/15-RealPushButtons.ino | 2 +- .../16-ProgrammableSwitches.ino | 2 +- .../17-LinkedServices/17-LinkedServices.ino | 4 +- examples/18-SavingStatus/18-SavingStatus.ino | 2 +- examples/19-WebLog/19-WebLog.ino | 2 +- .../20-AdvancedTechniques.ino | 4 +- .../21-AccessoryIdentifier.ino | 14 ++++- 22 files changed, 74 insertions(+), 64 deletions(-) diff --git a/examples/01-SimpleLightBulb/01-SimpleLightBulb.ino b/examples/01-SimpleLightBulb/01-SimpleLightBulb.ino index 6487c48..1932f43 100644 --- a/examples/01-SimpleLightBulb/01-SimpleLightBulb.ino +++ b/examples/01-SimpleLightBulb/01-SimpleLightBulb.ino @@ -1,7 +1,7 @@ /********************************************************************************* * MIT License * - * Copyright (c) 2020-2022 Gregg E. Berman + * Copyright (c) 2020-2024 Gregg E. Berman * * https://github.com/HomeSpan/HomeSpan * @@ -51,9 +51,6 @@ // NOTE: All HomeSpan examples are best understood when reviewed in conjunction with the documentation provided on the HomeSpan GitHub page. // See https://github.com/HomeSpan/HomeSpan for details and references. In particular, you may want to review the HomeSpan API Overview // page before proceeding. - - // These examples also make frequent reference to Apple's HomeKit Accessory Protocol Specification, known as HAP. You can download this - // directly from Apple at https://developer.apple.com/support/homekit-accessory-protocol. // LET'S GET STARTED... diff --git a/examples/02-TwoSimpleLightBulbs/02-TwoSimpleLightBulbs.ino b/examples/02-TwoSimpleLightBulbs/02-TwoSimpleLightBulbs.ino index a5367eb..775d7bc 100644 --- a/examples/02-TwoSimpleLightBulbs/02-TwoSimpleLightBulbs.ino +++ b/examples/02-TwoSimpleLightBulbs/02-TwoSimpleLightBulbs.ino @@ -1,7 +1,7 @@ /********************************************************************************* * MIT License * - * Copyright (c) 2020-2022 Gregg E. Berman + * Copyright (c) 2020-2024 Gregg E. Berman * * https://github.com/HomeSpan/HomeSpan * diff --git a/examples/03-CeilingFanWithLight/03-CeilingFanWithLight.ino b/examples/03-CeilingFanWithLight/03-CeilingFanWithLight.ino index 2bd976f..ceb6c3f 100644 --- a/examples/03-CeilingFanWithLight/03-CeilingFanWithLight.ino +++ b/examples/03-CeilingFanWithLight/03-CeilingFanWithLight.ino @@ -1,7 +1,7 @@ /********************************************************************************* * MIT License * - * Copyright (c) 2020-2022 Gregg E. Berman + * Copyright (c) 2020-2024 Gregg E. Berman * * https://github.com/HomeSpan/HomeSpan * diff --git a/examples/04-AdvancedCeilingFan/04-AdvancedCeilingFan.ino b/examples/04-AdvancedCeilingFan/04-AdvancedCeilingFan.ino index 574fcc4..e265d42 100644 --- a/examples/04-AdvancedCeilingFan/04-AdvancedCeilingFan.ino +++ b/examples/04-AdvancedCeilingFan/04-AdvancedCeilingFan.ino @@ -1,7 +1,7 @@ /********************************************************************************* * MIT License * - * Copyright (c) 2020-2022 Gregg E. Berman + * Copyright (c) 2020-2024 Gregg E. Berman * * https://github.com/HomeSpan/HomeSpan * diff --git a/examples/05-WorkingLED/05-WorkingLED.ino b/examples/05-WorkingLED/05-WorkingLED.ino index b9d3263..76388f5 100644 --- a/examples/05-WorkingLED/05-WorkingLED.ino +++ b/examples/05-WorkingLED/05-WorkingLED.ino @@ -1,7 +1,7 @@ /********************************************************************************* * MIT License * - * Copyright (c) 2020-2022 Gregg E. Berman + * Copyright (c) 2020-2024 Gregg E. Berman * * https://github.com/HomeSpan/HomeSpan * diff --git a/examples/06-DimmableLED/06-DimmableLED.ino b/examples/06-DimmableLED/06-DimmableLED.ino index edeba4b..0b17b5e 100644 --- a/examples/06-DimmableLED/06-DimmableLED.ino +++ b/examples/06-DimmableLED/06-DimmableLED.ino @@ -1,7 +1,7 @@ /********************************************************************************* * MIT License * - * Copyright (c) 2020-2022 Gregg E. Berman + * Copyright (c) 2020-2024 Gregg E. Berman * * https://github.com/HomeSpan/HomeSpan * diff --git a/examples/07-AccessoryNames/07-AccessoryNames.ino b/examples/07-AccessoryNames/07-AccessoryNames.ino index 3830d46..4a9c2db 100644 --- a/examples/07-AccessoryNames/07-AccessoryNames.ino +++ b/examples/07-AccessoryNames/07-AccessoryNames.ino @@ -1,7 +1,7 @@ /********************************************************************************* * MIT License * - * Copyright (c) 2020-2022 Gregg E. Berman + * Copyright (c) 2020-2024 Gregg E. Berman * * https://github.com/HomeSpan/HomeSpan * diff --git a/examples/08-Bridges/08-Bridges.ino b/examples/08-Bridges/08-Bridges.ino index 0331f3c..80b0daf 100644 --- a/examples/08-Bridges/08-Bridges.ino +++ b/examples/08-Bridges/08-Bridges.ino @@ -1,7 +1,7 @@ /********************************************************************************* * MIT License * - * Copyright (c) 2020-2022 Gregg E. Berman + * Copyright (c) 2020-2024 Gregg E. Berman * * https://github.com/HomeSpan/HomeSpan * diff --git a/examples/09-MessageLogging/09-MessageLogging.ino b/examples/09-MessageLogging/09-MessageLogging.ino index f973669..c8c1146 100644 --- a/examples/09-MessageLogging/09-MessageLogging.ino +++ b/examples/09-MessageLogging/09-MessageLogging.ino @@ -1,7 +1,7 @@ /********************************************************************************* * MIT License * - * Copyright (c) 2020-2022 Gregg E. Berman + * Copyright (c) 2020-2024 Gregg E. Berman * * https://github.com/HomeSpan/HomeSpan * diff --git a/examples/10-RGB_LED/10-RGB_LED.ino b/examples/10-RGB_LED/10-RGB_LED.ino index 0d07250..c3b9d29 100644 --- a/examples/10-RGB_LED/10-RGB_LED.ino +++ b/examples/10-RGB_LED/10-RGB_LED.ino @@ -1,7 +1,7 @@ /********************************************************************************* * MIT License * - * Copyright (c) 2020-2022 Gregg E. Berman + * Copyright (c) 2020-2024 Gregg E. Berman * * https://github.com/HomeSpan/HomeSpan * diff --git a/examples/11-ServiceNames/11-ServiceNames.ino b/examples/11-ServiceNames/11-ServiceNames.ino index c529843..60c6485 100644 --- a/examples/11-ServiceNames/11-ServiceNames.ino +++ b/examples/11-ServiceNames/11-ServiceNames.ino @@ -1,7 +1,7 @@ /********************************************************************************* * MIT License * - * Copyright (c) 2020-2022 Gregg E. Berman + * Copyright (c) 2020-2024 Gregg E. Berman * * https://github.com/HomeSpan/HomeSpan * diff --git a/examples/12-ServiceLoops/12-ServiceLoops.ino b/examples/12-ServiceLoops/12-ServiceLoops.ino index c53878c..430b5ea 100644 --- a/examples/12-ServiceLoops/12-ServiceLoops.ino +++ b/examples/12-ServiceLoops/12-ServiceLoops.ino @@ -1,7 +1,7 @@ /********************************************************************************* * MIT License * - * Copyright (c) 2020-2022 Gregg E. Berman + * Copyright (c) 2020-2024 Gregg E. Berman * * https://github.com/HomeSpan/HomeSpan * diff --git a/examples/13-TargetStates/13-TargetStates.ino b/examples/13-TargetStates/13-TargetStates.ino index 4fa0871..aa20200 100644 --- a/examples/13-TargetStates/13-TargetStates.ino +++ b/examples/13-TargetStates/13-TargetStates.ino @@ -1,7 +1,7 @@ /********************************************************************************* * MIT License * - * Copyright (c) 2020-2022 Gregg E. Berman + * Copyright (c) 2020-2024 Gregg E. Berman * * https://github.com/HomeSpan/HomeSpan * @@ -60,18 +60,19 @@ void setup() { // guesses the actions a device is taking, and updates it tile's icon accordingly, by comparing the value of the target state // Characteristic it sets, and the current state Characteristic it receives in the form of Event Notifications. When they are the same, // HomeKit assumes the physical device has reached the required position. When they differ, HomeKit assumes something will be opening, - // closing, raising, lowering, etc. The details of this process for each Service is outlined in the HAP documentation, but beware - // the document is not always up to date with the lastest version of the HomeKit application. Sometimes a little experimenting and a lot - // of trial and error is required to fully understand how each Service responds to different combinations of Characteristic values. + // closing, raising, lowering, etc. Sometimes a little experimenting and a lot of trial and error is required to fully understand how + // each Service responds to different combinations of Characteristic values. // As always, we won't be connecting our ESP32 to an actual garage door or window shade but will instead simulate their responses and // actions for illustrative purposes. In some ways the code is more complicated because of the need to simulate values - it might be // easier if we actually were connecting to a garage door or window shade! // Fully commented code for both of our derived Services can be found in DEV_DoorsWindows.h. These examples do not introduce any new - // HomeSpan functions or features. Rather we are combining everything learned so far into two reasonably complex Services. You may - // want to reference the HAP documentation for these two parent Services to fully understand the meaning of the different value settings - // for each of the Services' Characteristics. + // HomeSpan functions, but you will see how to use HomeSpan's ENUMERATED CONSTANTS, instead of just plain integers, to set the values + // of Characteristics where the values represent discrete states (e.g. "lowering", "opening"). + + // Please see HomeSpan's Services and Characteristics page for a complete list of the enumerated constants available for Characteristics + // where they are applicable. Serial.begin(115200); diff --git a/examples/13-TargetStates/DEV_DoorsWindows.h b/examples/13-TargetStates/DEV_DoorsWindows.h index e3aa128..e5467c4 100644 --- a/examples/13-TargetStates/DEV_DoorsWindows.h +++ b/examples/13-TargetStates/DEV_DoorsWindows.h @@ -3,17 +3,33 @@ // DEVICE-SPECIFIC LED SERVICES // //////////////////////////////////// -struct DEV_GarageDoor : Service::GarageDoorOpener { // A Garage Door Opener +struct DEV_GarageDoor : Service::GarageDoorOpener { // A Garage Door Opener - Characteristic::CurrentDoorState *current; // reference to the Current Door State Characteristic (specific to Garage Door Openers) + Characteristic::CurrentDoorState *current; // reference to the Current Door State Characteristic (specific to Garage Door Openers) Characteristic::TargetDoorState *target; // reference to the Target Door State Characteristic (specific to Garage Door Openers) - SpanCharacteristic *obstruction; // reference to the Obstruction Detected Characteristic (specific to Garage Door Openers) + SpanCharacteristic *obstruction; // reference to the Obstruction Detected Characteristic (specific to Garage Door Openers) - DEV_GarageDoor() : Service::GarageDoorOpener(){ // constructor() method + DEV_GarageDoor() : Service::GarageDoorOpener(){ // constructor() method + + // Below we use enumerated constants rather than integers to set the values of the Characteristics. + // Using enumerated constants means not having to remember the integer code for each state. You'll find + // a complete list of all available enumerated constants on HomeSpan's Services and Characteristics page. + // Note the use of enumerated constants is optional - you can always use the integer code representing + // each state instead. - current=new Characteristic::CurrentDoorState(1); // initial value of 1 means closed - target=new Characteristic::TargetDoorState(1); // initial value of 1 means closed - obstruction=new Characteristic::ObstructionDetected(false); // initial value of false means NO obstruction is detected + current=new Characteristic::CurrentDoorState(Characteristic::CurrentDoorState::CLOSED); // here we use the fully-qualified name of the constant "CLOSED" + target=new Characteristic::TargetDoorState(target->CLOSED); // here we use the name of the object instead of the fully-qualified name (much less typing) + + // Below we must use the fully-qualified name of the enumerated constant and cannot use "obstruction->NOT_DETECTED". + // Why? Because above we declared "obstruction" to be a pointer to a generic SpanCharacteristic instead of a pointer to + // the more specific Characteristic::ObstructionDetected. Either is fine, and it's just a matter of programming preference + // (as you can see we use both conventions in this sketch). But the downside of using SpanCharacteristic to declare a + // Characteristic that contains enumerated constants is that the object itself does not know about these constants. This is + // because all enumerated constants are uniquely defined within their respective specific Characteristic classes, and not in the + // generic SpanCharacteristic class from which all specific Characterstics are derived. + + obstruction=new Characteristic::ObstructionDetected(Characteristic::ObstructionDetected::NOT_DETECTED); // this works +// obstruction=new Characteristic::ObstructionDetected(obstruction->NOT_DETECTED); // this would produce a compiler error (try it and see) Serial.print("Configuring Garage Door Opener"); // initialization message Serial.print("\n"); @@ -24,13 +40,13 @@ struct DEV_GarageDoor : Service::GarageDoorOpener { // A Garage Door Opener // see HAP Documentation for details on what each value represents - if(target->getNewVal()==0){ // if the target-state value is set to 0, HomeKit is requesting the door to be in open position + if(target->getNewVal()==target->OPEN){ // HomeKit is requesting the door to be in OPEN position LOG1("Opening Garage Door\n"); - current->setVal(2); // set the current-state value to 2, which means "opening" - obstruction->setVal(false); // clear any prior obstruction detection + current->setVal(current->OPENING); // set the current-state value to OPENING + obstruction->setVal(false); // clear any prior obstruction detection - note we do not bother using an enumerated constant here } else { - LOG1("Closing Garage Door\n"); // else the target-state value is set to 1, and HomeKit is requesting the door to be in the closed position - current->setVal(3); // set the current-state value to 3, which means "closing" + LOG1("Closing Garage Door\n"); // else HomeKit must be requesting the door to be in the CLOSED position + current->setVal(current->CLOSING); // set the current-state value to CLOSING obstruction->setVal(false); // clear any prior obstruction detection } @@ -43,13 +59,13 @@ struct DEV_GarageDoor : Service::GarageDoorOpener { // A Garage Door Opener if(current->getVal()==target->getVal()) // if current-state matches target-state there is nothing do -- exit loop() return; - if(current->getVal()==3 && random(100000)==0){ // here we simulate a random obstruction, but only if the door is closing (not opening) - current->setVal(4); // if our simulated obstruction is triggered, set the curent-state to 4, which means "stopped" - obstruction->setVal(true); // and set obstruction-detected to true + if(current->getVal()==current->CLOSING && random(100000)==0){ // here we simulate a random obstruction, but only if the door is closing (not opening) + current->setVal(current->STOPPED); // if our simulated obstruction is triggered, set the curent-state to STOPPED + obstruction->setVal(true); // and set obstruction-detected to true LOG1("Garage Door Obstruction Detected!\n"); } - if(current->getVal()==4) // if the current-state is stopped, there is nothing more to do - exit loop() + if(current->getVal()==current->STOPPED) // if the current-state is stopped, there is nothing more to do - exit loop() return; // This last bit of code only gets called if the door is in a state that represents actively opening or actively closing. @@ -83,15 +99,6 @@ struct DEV_WindowShade : Service::WindowCovering { // A motorized Window Sha boolean update(){ // update() method - // The logic below is based on how HomeKit appears to operate in practice, which is NOT consistent with - // HAP documentation. In that document HomeKit seems to support fully opening or fully closing a window shade, with - // an optional control to HOLD the window shade at a given in-between position while it is moving. - - // In practice, HomeKit does not appear to implement any form of a HOLD control button, even if you instantiate that - // Characteristic. Instead, HomeKit provides a full slider control, similar to the brightness control for a lightbulb, - // that allows you to set the exact position of the window shade from 0-100%. This obviates the need to any sort of HOLD button. - // The resulting logic is also very simple: - if(target->getNewVal()>current->getVal()){ // if the target-position requested is greater than the current-position, simply log a "raise" message LOG1("Raising Shade\n"); // ** there is nothing more to do - HomeKit keeps track of the current-position so knows raising is required } else @@ -116,10 +123,7 @@ struct DEV_WindowShade : Service::WindowCovering { // A motorized Window Sha // the user in the Home App. If it finds current and target positions are the same, it knows the shade is stopped. Otherwise // it will report the shade is raising or lowering depending on whether the specified target state is greater or less than // the current state. - - // According to HAP, the Characteristic Position State is also required. However, this seems duplicative and is NOT needed - // at all given the way HomeKit uses current position. - + } // loop }; diff --git a/examples/14-EmulatedPushButtons/14-EmulatedPushButtons.ino b/examples/14-EmulatedPushButtons/14-EmulatedPushButtons.ino index 7469386..f0ce6e8 100644 --- a/examples/14-EmulatedPushButtons/14-EmulatedPushButtons.ino +++ b/examples/14-EmulatedPushButtons/14-EmulatedPushButtons.ino @@ -1,7 +1,7 @@ /********************************************************************************* * MIT License * - * Copyright (c) 2020-2022 Gregg E. Berman + * Copyright (c) 2020-2024 Gregg E. Berman * * https://github.com/HomeSpan/HomeSpan * @@ -40,7 +40,7 @@ void setup() { // Though HomeKit and the HomeKit Accessory Protocol (HAP) Specification provide a very flexible framework - // for creating iOS- and MacOS-controlled devices, they does not contain every possible desired feature. + // for creating iOS- and MacOS-controlled devices, they do not contain every possible desired feature. // // One very common Characteristic HomeKit does not seem to contain is a simple pushbutton, like the type you // would find on a remote control. Unlike switches that can be "on" or "off", a pushbutton has no state. diff --git a/examples/15-RealPushButtons/15-RealPushButtons.ino b/examples/15-RealPushButtons/15-RealPushButtons.ino index 25f3fd1..56fb7c2 100644 --- a/examples/15-RealPushButtons/15-RealPushButtons.ino +++ b/examples/15-RealPushButtons/15-RealPushButtons.ino @@ -1,7 +1,7 @@ /********************************************************************************* * MIT License * - * Copyright (c) 2020-2022 Gregg E. Berman + * Copyright (c) 2020-2024 Gregg E. Berman * * https://github.com/HomeSpan/HomeSpan * diff --git a/examples/16-ProgrammableSwitches/16-ProgrammableSwitches.ino b/examples/16-ProgrammableSwitches/16-ProgrammableSwitches.ino index 47383ec..e39409b 100644 --- a/examples/16-ProgrammableSwitches/16-ProgrammableSwitches.ino +++ b/examples/16-ProgrammableSwitches/16-ProgrammableSwitches.ino @@ -1,7 +1,7 @@ /********************************************************************************* * MIT License * - * Copyright (c) 2020-2022 Gregg E. Berman + * Copyright (c) 2020-2024 Gregg E. Berman * * https://github.com/HomeSpan/HomeSpan * diff --git a/examples/17-LinkedServices/17-LinkedServices.ino b/examples/17-LinkedServices/17-LinkedServices.ino index c9bc469..bf40635 100644 --- a/examples/17-LinkedServices/17-LinkedServices.ino +++ b/examples/17-LinkedServices/17-LinkedServices.ino @@ -1,7 +1,7 @@ /********************************************************************************* * MIT License * - * Copyright (c) 2020-2022 Gregg E. Berman + * Copyright (c) 2020-2024 Gregg E. Berman * * https://github.com/HomeSpan/HomeSpan * @@ -113,7 +113,7 @@ struct Shower : Service::Faucet { // this is our Shower structur struct WaterValve : Service::Valve { // here we define our WaterValve structure as a child class of the HomeSpan Valve Service SpanCharacteristic *active=new Characteristic::Active(1);; // the Active Characteristic is used to specify whether the Valve is Active (open) or Inactive (closed) - SpanCharacteristic *inUse=new Characteristic::InUse(); // the InUser Characteristic is used to specify whether water is actually flowing through value + SpanCharacteristic *inUse=new Characteristic::InUse(); // the InUse Characteristic is used to specify whether water is actually flowing through value Shower *shower; // storage for the pointer to the "controlling" Shower Service WaterValve(Shower *s){ // this is constructor for WaterValve. It takes a single argument that points to the "controlling" Shower Service diff --git a/examples/18-SavingStatus/18-SavingStatus.ino b/examples/18-SavingStatus/18-SavingStatus.ino index 821913e..e5eaabd 100644 --- a/examples/18-SavingStatus/18-SavingStatus.ino +++ b/examples/18-SavingStatus/18-SavingStatus.ino @@ -1,7 +1,7 @@ /********************************************************************************* * MIT License * - * Copyright (c) 2021-2022 Gregg E. Berman + * Copyright (c) 2021-2024 Gregg E. Berman * * https://github.com/HomeSpan/HomeSpan * diff --git a/examples/19-WebLog/19-WebLog.ino b/examples/19-WebLog/19-WebLog.ino index ce1e080..9ccd0ac 100644 --- a/examples/19-WebLog/19-WebLog.ino +++ b/examples/19-WebLog/19-WebLog.ino @@ -1,7 +1,7 @@ /********************************************************************************* * MIT License * - * Copyright (c) 2022 Gregg E. Berman + * Copyright (c) 2020-2024 Gregg E. Berman * * https://github.com/HomeSpan/HomeSpan * diff --git a/examples/20-AdvancedTechniques/20-AdvancedTechniques.ino b/examples/20-AdvancedTechniques/20-AdvancedTechniques.ino index 90c0c88..00244b5 100644 --- a/examples/20-AdvancedTechniques/20-AdvancedTechniques.ino +++ b/examples/20-AdvancedTechniques/20-AdvancedTechniques.ino @@ -1,7 +1,7 @@ /********************************************************************************* * MIT License * - * Copyright (c) 2022 Gregg E. Berman + * Copyright (c) 2022-2024 Gregg E. Berman * * https://github.com/HomeSpan/HomeSpan * @@ -123,7 +123,7 @@ void addLight(int n){ char name[32]; sprintf(name,"Light-%d",n); // create the name of the device using the specified "ID" char sNum[32]; - sprintf(sNum,"%0.10d",n); // create serial number from the ID - this is helpful in case we rename the Light to something else using the Home App + sprintf(sNum,"%010d",n); // create serial number from the ID - this is helpful in case we rename the Light to something else using the Home App Serial.printf("Adding Accessory: %s\n",name); diff --git a/examples/21-AccessoryIdentifier/21-AccessoryIdentifier.ino b/examples/21-AccessoryIdentifier/21-AccessoryIdentifier.ino index ad30093..7603f57 100644 --- a/examples/21-AccessoryIdentifier/21-AccessoryIdentifier.ino +++ b/examples/21-AccessoryIdentifier/21-AccessoryIdentifier.ino @@ -138,15 +138,23 @@ void setup() { homeSpan.setLogLevel(1); homeSpan.begin(Category::Lighting,"HomeSpan LEDS"); - + +// Here we replace the usual construct: + +// new SpanAccessory(); +// new Service::AccessoryInformation(); +// new Characteristic::Identify(); + +// with this: + new SpanAccessory(); - new DEV_INFO(13); // Here we instantiate the new DEV_INFO structure, instead of simply instantiating Characteristic::Name() and Characteristic::Identity() as in all previous examples + new DEV_INFO(13); // instantiate a new DEV_INFO structure that will run our custom identification routine to blink an LED on pin 13 three times new SpanAccessory(); new DEV_INFO(16); // Note we instantiate a new DEV_INFO structure for each Accessory in this device new DEV_LED(16); // Here we instantiate the usual DEV_LED structure that controls the LED during normal operation - new SpanAccessory(); // Here we add a second LED + new SpanAccessory(); // Here we add a second LED Accessory new DEV_INFO(17); new DEV_LED(17); } From 181c5e99b2616d4b8392623ee5621728c27b8175 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 9 Mar 2024 19:04:59 -0600 Subject: [PATCH 022/154] Update Tutorials.md --- docs/Tutorials.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/Tutorials.md b/docs/Tutorials.md index 297098d..5bb1888 100644 --- a/docs/Tutorials.md +++ b/docs/Tutorials.md @@ -69,7 +69,10 @@ Example 12 introduces HomeKit *Event Notifications* to implement two new accesso * setting the value of a Characteristic and triggering an Event Notification with the `setVal()` method ### [Example 13 - TargetStates](../examples/13-TargetStates) -Example 13 we demonstrate the simultaneous use of both the `update()` and `loop()` methods by implementing two new Services: a Garage Door Opener and a motorized Window Shade. Both examples showcase HomeKit's Target-State/Current-State framework. +Example 13 demonstrates the simultaneous use of both the `update()` and `loop()` methods by implementing two new Services: a Garage Door Opener and a motorized Window Shade. Both examples showcase HomeKit's Target-State/Current-State framework. New HomeSpan API topics covered in this example include: + +* using Enumerated Constants to set the values of Characteristics that represent discrete states (e.g. "raising", "closing") + ### [Example 14 - EmulatedPushButtons](../examples/14-EmulatedPushButtons) Example 14 demonstrates how you can use the `setVal()` and `timeVal()` methods inside a Service's `loop()` method to create a tile in the Home App that emulates a pushbutton switch. In this example pressing the tile in the Home App will cause it to turn on, blink an LED 3 times, and then turn off (just like a real pushbutton might do). From 8ce80157b335d36e358a55a218fce2f07f519d53 Mon Sep 17 00:00:00 2001 From: Gregg Date: Fri, 15 Mar 2024 17:35:04 -0500 Subject: [PATCH 023/154] Added native support for WR (write-response) HAPClient::putCharacteristics() now parses for "r":true in JSON, which is the HAP request for a write-response. When found, HomeSpan will return with a full write-response for all Characteristics updated using "207 Multi-Status", where "value" will be included only if "r":true was specified for a Characteristic. HomeSpan processes "r":true for any Characteristic regardless if permissions includes WR. To change the value of the Characteristic from within an update() method, use setVal() WITH OPTIONAL SECOND ARGUMENT AS FALSE to supress an EV broadcast (in the unlikely event that there was a notification subscriber). To do: Consider generally disallowing the changing of any Characteristic value from within update() using setVal() unless the Characteristic has WR permission. And if so, automatically surpress EV notification. --- src/HAP.cpp | 8 ++++---- src/HomeSpan.cpp | 12 ++++++++++-- src/HomeSpan.h | 1 + 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index 322219f..a036ba8 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -998,10 +998,10 @@ int HAPClient::putCharacteristicsURL(char *json){ if(!homeSpan.updateCharacteristics(json, pObj)) // perform update return(0); // return if failed to update (error message will have been printed in update) - int multiCast=0; // check if all status is OK, or if multicast response is request - for(int i=0;i>>>>>>>>> %s >>>>>>>>>>\n",client.remoteIP().toString().c_str()); diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 876b329..1dde9af 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -1378,6 +1378,9 @@ int Span::updateCharacteristics(char *buf, SpanBuf *pObj){ pObj[nObj].ev=t3; okay|=8; } else + if(!strcmp(t2,"r") && (t3=strtok_r(t1,"}[]:, \"\t\n\r",&p2))){ + pObj[nObj].wr=(t3 && (!strcmp(t3,"1") || !strcmp(t3,"true"))); + } else if(!strcmp(t2,"pid") && (t3=strtok_r(t1,"}[]:, \"\t\n\r",&p2))){ uint64_t pid=strtoull(t3,NULL,0); if(!TimedWrites.count(pid)){ @@ -1395,7 +1398,9 @@ int Span::updateCharacteristics(char *buf, SpanBuf *pObj){ } // parse property tokens if(!t1){ // at least one token was found that was not initial "characteristics" - if(okay==7 || okay==11 || okay==15){ // all required properties found + if(okay==7 || okay==11 || okay==15){ // all required properties found + if(!pObj[nObj].val) // if value is NOT being updated + pObj[nObj].wr=false; // ignore any request for write-response nObj++; // increment number of characteristic objects found } else { LOG0("\n*** ERROR: Problems parsing JSON characteristics object - missing required properties\n\n"); @@ -1507,7 +1512,10 @@ void Span::printfAttributes(SpanBuf *pObj, int nObj){ hapOut << "{\"characteristics\":["; for(int i=0;iuvPrint(pObj[i].characteristic->value).c_str(); + hapOut << "}"; if(i+1 Date: Sat, 16 Mar 2024 13:56:04 -0500 Subject: [PATCH 024/154] Added checks for use of setVal() Throws warning, but still performs change, if setVal() is used within update() to change the value of a Characteristic that is being updated by the Home App, UNLESS the Characteristics has requested a write-response (in which case setVal() is expected). Throw warning, and DOES NOT perform change, if setVal() is used on a Characteristic without EV permission, UNLESS it has been used in update() in response to a write-response request. Also added diagnostic to 'i' CLI command that indicates which connections have currently asked to EV Notifications for each Characteristic. Only applies if EV permission is set for a Characteristic. --- src/HomeSpan.cpp | 25 ++++++++++---- src/HomeSpan.h | 84 +++++++++++++++++++++++++++--------------------- 2 files changed, 67 insertions(+), 42 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 1dde9af..5a9f1a9 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -904,9 +904,22 @@ void Span::processSerialCommand(const char *c){ else LOG0(", %sRange=[%s,%s]",(*chr)->customRange?"Custom-":"",(*chr)->uvPrint((*chr)->minValue).c_str(),(*chr)->uvPrint((*chr)->maxValue).c_str()); } + + if(((*chr)->perms)&EV){ + LOG0(", EV=("); + boolean addComma=false; + for(int i=0;iev[i] && hap[i]->client){ + LOG0("%s%d",addComma?",":"",i); + addComma=true; + } + } + LOG0(")"); + } if((*chr)->nvsKey) LOG0(" (nvs)"); + LOG0("\n"); if(!(*chr)->isCustom && !(*svc)->isCustom && std::find((*svc)->req.begin(),(*svc)->req.end(),(*chr)->hapChar)==(*svc)->req.end() && std::find((*svc)->opt.begin(),(*svc)->opt.end(),(*chr)->hapChar)==(*svc)->opt.end()) @@ -1420,10 +1433,10 @@ int Span::updateCharacteristics(char *buf, SpanBuf *pObj){ } else { pObj[i].characteristic = find(pObj[i].aid,pObj[i].iid); // find characteristic with matching aid/iid and store pointer - if(pObj[i].characteristic) // if found, initialize characterstic update with new val/ev - pObj[i].status=pObj[i].characteristic->loadUpdate(pObj[i].val,pObj[i].ev); // save status code, which is either an error, or TBD (in which case isUpdated for the characteristic has been set to true) + if(pObj[i].characteristic) // if found, initialize characterstic update with new val/ev + pObj[i].status=pObj[i].characteristic->loadUpdate(pObj[i].val,pObj[i].ev,pObj[i].wr); // save status code, which is either an error, or TBD (in which case updateFlag for the characteristic has been set to either 1 or 2) else - pObj[i].status=StatusCode::UnknownResource; // if not found, set HAP error + pObj[i].status=StatusCode::UnknownResource; // if not found, set HAP error } } // first pass @@ -1455,7 +1468,7 @@ int Span::updateCharacteristics(char *buf, SpanBuf *pObj){ pObj[j].characteristic->uvSet(pObj[j].characteristic->newValue,pObj[j].characteristic->value); // replace characteristic new value with original value LOG1(" (failed)\n"); } - pObj[j].characteristic->isUpdated=false; // reset isUpdated flag for characteristic + pObj[j].characteristic->updateFlag=0; // reset updateFlag for characteristic } } @@ -1899,7 +1912,7 @@ void SpanCharacteristic::printfAttributes(int flags){ /////////////////////////////// -StatusCode SpanCharacteristic::loadUpdate(char *val, char *ev){ +StatusCode SpanCharacteristic::loadUpdate(char *val, char *ev, boolean wr){ if(ev){ // request for notification boolean evFlag; @@ -2000,7 +2013,7 @@ StatusCode SpanCharacteristic::loadUpdate(char *val, char *ev){ } // switch - isUpdated=true; + updateFlag=1+wr; // set flag to 1 if successful update or 2 if successful AND write-response flag is set updateTime=homeSpan.snapTime; return(StatusCode::TBD); } diff --git a/src/HomeSpan.h b/src/HomeSpan.h index deeb737..f3ce254 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -500,13 +500,13 @@ class SpanCharacteristic{ boolean setValidValuesError=false; // flag to indicate attempt to set Valid Values on Characteristic that does not support changes to Valid Values 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 + uint8_t updateFlag=0; // set to either 1 (for normal write) or 2 (for write-response) inside update() when Characteristic is successfully updated via Home App unsigned long updateTime=0; // last time value was updated (in millis) either by PUT /characteristic OR by setVal() UVal newValue; // the updated value requested by PUT /characteristic SpanService *service=NULL; // pointer to Service containing this Characteristic - void printfAttributes(int flags); // writes Characteristic JSON to hapOut stream - StatusCode loadUpdate(char *val, char *ev); // load updated val/ev from PUT /characteristic JSON request. Return intitial HAP status code (checks to see if characteristic is found, is writable, etc.) + void printfAttributes(int flags); // writes Characteristic JSON to hapOut stream + StatusCode loadUpdate(char *val, char *ev, boolean wr); // load updated val/ev from PUT /characteristic JSON request. Return intitial HAP status code (checks to see if characteristic is found, is writable, etc.) String uvPrint(UVal &u){ char c[67]; // space for 64 characters + surrounding quotes + terminating null @@ -669,28 +669,35 @@ class SpanCharacteristic{ return NULL; } - void setString(const char *val){ + void setString(const char *val, boolean notify=true){ - if((perms & EV) == 0){ - LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setString() ignored. No NOTIFICATION permission on this characteristic\n\n",hapName); + if(!((perms&EV) || (updateFlag==2))){ + LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setData() ignored. No EVENT NOTIFICATION (EV) permission on this characteristic\n\n",hapName); return; - } + } + + if(updateFlag==1) + LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setVal() within update() while it is being updated by Home App. This may cause device to become non-responsive!\n\n",hapName); uvSet(value,val); 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 printfNotify knows to consider this "update" - homeSpan.Notifications.push_back(sb); // store SpanBuf in Notifications vector - if(nvsKey){ - nvs_set_str(homeSpan.charNVS,nvsKey,value.STRING); // store data - nvs_commit(homeSpan.charNVS); + if(notify){ + if(updateFlag!=2){ // do not broadcast EV if update is being done in context of write-response + 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 printfNotify knows to consider this "update" + homeSpan.Notifications.push_back(sb); // store SpanBuf in Notifications vector + } + + if(nvsKey){ + nvs_set_str(homeSpan.charNVS,nvsKey,value.STRING); // store data + nvs_commit(homeSpan.charNVS); + } } } // setString() @@ -731,12 +738,12 @@ class SpanCharacteristic{ return(olen); } - void setData(uint8_t *data, size_t len){ + void setData(uint8_t *data, size_t len, boolean notify=true){ - if((perms & EV) == 0){ - LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setData() ignored. No NOTIFICATION permission on this characteristic\n\n",hapName); + if(!((perms&EV) || (updateFlag==2))){ + LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setData() ignored. No EVENT NOTIFICATION (EV) permission on this characteristic\n\n",hapName); return; - } + } if(len<1){ LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setData() ignored. Size of data buffer must be greater than zero\n\n",hapName); @@ -744,18 +751,21 @@ class SpanCharacteristic{ } size_t olen; - mbedtls_base64_encode(NULL,0,&olen,data,len); // get length of string buffer needed (mbedtls includes the trailing null in this size) - TempBuffer tBuf(olen); // create temporary string buffer, with room for trailing null + mbedtls_base64_encode(NULL,0,&olen,data,len); // get length of string buffer needed (mbedtls includes the trailing null in this size) + TempBuffer tBuf(olen); // create temporary string buffer, with room for trailing null mbedtls_base64_encode((uint8_t*)tBuf.get(),olen,&olen,data,len ); // encode data into string buf - setString(tBuf); // call setString to continue processing as if characteristic was a string + setString(tBuf,notify); // call setString to continue processing as if characteristic was a string } template void setVal(T val, boolean notify=true){ - if((perms & EV) == 0){ - LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setVal() ignored. No NOTIFICATION permission on this characteristic\n\n",hapName); + if(!((perms&EV) || (updateFlag==2))){ + LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setData() ignored. No EVENT NOTIFICATION (EV) permission on this characteristic\n\n",hapName); return; - } + } + + if(updateFlag==1) + LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setVal() within update() while it is being updated by Home App. This may cause device to become non-responsive!\n\n",hapName); if(!((val >= uvGet(minValue)) && (val <= uvGet(maxValue)))){ LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setVal(%g) is out of range [%g,%g]. This may cause device to become non-responsive!\n\n", @@ -768,13 +778,15 @@ class SpanCharacteristic{ updateTime=homeSpan.snapTime; 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 printfNotify knows to consider this "update" - homeSpan.Notifications.push_back(sb); // store SpanBuf in Notifications vector - + if(updateFlag!=2){ // do not broadcast EV if update is being done in context of write-response + 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 printfNotify knows to consider this "update" + homeSpan.Notifications.push_back(sb); // store SpanBuf in Notifications vector + } + if(nvsKey){ nvs_set_u64(homeSpan.charNVS,nvsKey,value.UINT64); // store data as uint64_t regardless of actual type (it will be read correctly when access through uvGet()) nvs_commit(homeSpan.charNVS); @@ -783,8 +795,8 @@ class SpanCharacteristic{ } // setVal() - boolean updated(){return(isUpdated);} // returns isUpdated - unsigned long timeVal(); // returns time elapsed (in millis) since value was last updated + boolean updated(){return(updateFlag>0);} // returns true within update() if Characteristic was updated by Home App + unsigned long timeVal(); // returns time elapsed (in millis) since value was last updated, either by Home App or by using setVal() SpanCharacteristic *setValidValues(int n, ...); // sets a list of 'n' valid values allowed for a Characteristic and returns pointer to self. Only applicable if format=INT, UINT8, UINT16, or UINT32 From 41f755039f5a3992b09820f9ebbc2c6a4bd31640 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 16 Mar 2024 14:29:36 -0500 Subject: [PATCH 025/154] Update Reference.md --- docs/Reference.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/Reference.md b/docs/Reference.md index dc45cbb..56109b9 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -399,7 +399,11 @@ This is a **base class** from which all HomeSpan Characteristics are derived, an * `void setVal(value [,boolean notify])` * 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()` + * throws a runtime warning if any of the conditions hold: + * the Characteristic is not configured with Event Notification (EV) permission enabled; or + * this method is being called from within the `update()` routine of a **SpanService** and `isUpdated()` is *true* for the Characteristic (i.e. it is being updated at the same time via the Home App); or + * *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()` + * the first two restrictions above do not apply to the use of `setVal()` from within the `update()` method of a **SpanService** if you are changing the value of a Characteristic in response to a *write-response* request from HomeKit * *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) * `SpanCharacteristic *setRange(min, max, step)` From 6bc45cf8842adae594a152393fd16565f609f3e3 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 17 Mar 2024 19:54:48 -0500 Subject: [PATCH 026/154] Added enum and logic for TLV Characteristics --- src/Characteristics.h | 3 ++- src/HomeSpan.h | 9 ++++++--- src/Settings.h | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Characteristics.h b/src/Characteristics.h index c5703de..679ea9b 100644 --- a/src/Characteristics.h +++ b/src/Characteristics.h @@ -49,7 +49,8 @@ enum FORMAT { // HAP Table 6-5 INT=5, FLOAT=6, STRING=7, - DATA=8 + DATA=8, + TLV_ENC=9 }; /////////////////////////////// diff --git a/src/HomeSpan.h b/src/HomeSpan.h index f3ce254..9eaf7e4 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -529,6 +529,7 @@ class SpanCharacteristic{ return(String(c)); case FORMAT::STRING: case FORMAT::DATA: + case FORMAT::TLV_ENC: sprintf(c,"\"%.64s\"",u.STRING); // Truncating string to 64 chars return(String(c)); } // switch @@ -536,7 +537,7 @@ class SpanCharacteristic{ } void uvSet(UVal &dest, UVal &src){ - if(format==FORMAT::STRING || format==FORMAT::DATA) + if(format==FORMAT::STRING || format==FORMAT::DATA || format==FORMAT::TLV_ENC) uvSet(dest,(const char *)src.STRING); else dest=src; @@ -572,6 +573,7 @@ class SpanCharacteristic{ break; case FORMAT::STRING: case FORMAT::DATA: + case FORMAT::TLV_ENC: break; } // switch } @@ -595,6 +597,7 @@ class SpanCharacteristic{ return((T) u.FLOAT); case FORMAT::STRING: case FORMAT::DATA: + case FORMAT::TLV_ENC: break; } return((T)0); // included to prevent compiler warnings @@ -615,7 +618,7 @@ class SpanCharacteristic{ sprintf(nvsKey,"%04X%08X%03X",t,aid,iid&0xFFF); size_t len; - if(format!=FORMAT::STRING && format!=FORMAT::DATA){ + if(format!=FORMAT::STRING && format!=FORMAT::DATA && format!=FORMAT::TLV_ENC){ if(nvs_get_u64(homeSpan.charNVS,nvsKey,&(value.UINT64))!=ESP_OK) { nvs_set_u64(homeSpan.charNVS,nvsKey,value.UINT64); // store data as uint64_t regardless of actual type (it will be read correctly when access through uvGet()) nvs_commit(homeSpan.charNVS); // commit to NVS @@ -634,7 +637,7 @@ class SpanCharacteristic{ uvSet(newValue,value); - if(format!=FORMAT::STRING && format!=FORMAT::DATA) { + if(format!=FORMAT::STRING && format!=FORMAT::DATA && format!=FORMAT::TLV_ENC) { uvSet(minValue,min); uvSet(maxValue,max); uvSet(stepValue,0); diff --git a/src/Settings.h b/src/Settings.h index 9a9c3c5..a94c3c1 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -36,7 +36,7 @@ #define HS_MAJOR 1 #define HS_MINOR 9 -#define HS_PATCH 0 +#define HS_PATCH 1 #define STRINGIFY(x) _STR(x) #define _STR(x) #x From 7c5b01e96798973565bf902d34783848e745880f Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 24 Mar 2024 09:37:27 -0500 Subject: [PATCH 027/154] Additional logic for TLV Characteristics --- src/HomeSpan.cpp | 2 +- src/HomeSpan.h | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 5a9f1a9..1364b59 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -1846,7 +1846,7 @@ SpanCharacteristic::~SpanCharacteristic(){ void SpanCharacteristic::printfAttributes(int flags){ const char permCodes[][7]={"pr","pw","ev","aa","tw","hd","wr"}; - const char formatCodes[][9]={"bool","uint8","uint16","uint32","uint64","int","float","string","data"}; + const char formatCodes[][9]={"bool","uint8","uint16","uint32","uint64","int","float","string","data","tlv8"}; hapOut << "{\"iid\":" << iid; diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 9eaf7e4..983289c 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -618,7 +618,7 @@ class SpanCharacteristic{ sprintf(nvsKey,"%04X%08X%03X",t,aid,iid&0xFFF); size_t len; - if(format!=FORMAT::STRING && format!=FORMAT::DATA && format!=FORMAT::TLV_ENC){ + if(format=FORMAT::STRING) return value.STRING; return NULL; } char *getNewString(){ - if(format == FORMAT::STRING) + if(format>=FORMAT::STRING) return newValue.STRING; return NULL; @@ -706,7 +706,7 @@ class SpanCharacteristic{ } // setString() size_t getData(uint8_t *data, size_t len){ - if(format!=FORMAT::DATA) + if(format Date: Sun, 24 Mar 2024 22:03:23 -0500 Subject: [PATCH 028/154] changed TLV8 from std::forward_list to std::list Though this increases space requirement for TLV8 (just a little), using std::list allows adding elements directly to end, which ensure the order is maintained when packing and unpacking. Will avoid potential confusion when TLV8 is used for Characteristics. --- src/HomeSpan.h | 2 +- src/TLV8.cpp | 6 +++--- src/TLV8.h | 8 ++++---- src/src.ino | 50 +++++++++++++++++++++++++++++--------------------- 4 files changed, 37 insertions(+), 29 deletions(-) diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 983289c..99865a7 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -537,7 +537,7 @@ class SpanCharacteristic{ } void uvSet(UVal &dest, UVal &src){ - if(format==FORMAT::STRING || format==FORMAT::DATA || format==FORMAT::TLV_ENC) + if(format>=FORMAT::STRING) uvSet(dest,(const char *)src.STRING); else dest=src; diff --git a/src/TLV8.cpp b/src/TLV8.cpp index 9fd6e16..2f19730 100644 --- a/src/TLV8.cpp +++ b/src/TLV8.cpp @@ -70,10 +70,10 @@ void tlv8_t::osprint(std::ostream& os){ TLV8_it TLV8::add(uint8_t tag, size_t len, const uint8_t* val){ - if(!empty() && front().tag==tag) - front().update(len,val); + if(!empty() && back().tag==tag) + back().update(len,val); else - emplace_front(tag,len,val); + emplace_back(tag,len,val); return(begin()); } diff --git a/src/TLV8.h b/src/TLV8.h index 4a83cc2..af752e8 100644 --- a/src/TLV8.h +++ b/src/TLV8.h @@ -29,7 +29,7 @@ #include #include -#include +#include #include #include "PSRAM.h" @@ -51,12 +51,12 @@ struct tlv8_t { ///////////////////////////////////// -typedef std::forward_list>::iterator TLV8_it; +typedef std::list>::iterator TLV8_it; typedef struct { const uint8_t tag; const char *name; } TLV8_names; ///////////////////////////////////// -class TLV8 : public std::forward_list> { +class TLV8 : public std::list> { TLV8_it currentPackIt; TLV8_it endPackIt; @@ -109,6 +109,6 @@ class TLV8 : public std::forward_list> { void unpack(uint8_t *buf, size_t bufSize); - void wipe(){std::forward_list>().swap(*this);} + void wipe(){std::list>().swap(*this);} }; diff --git a/src/src.ino b/src/src.ino index ea87240..7fda69a 100644 --- a/src/src.ino +++ b/src/src.ino @@ -26,18 +26,43 @@ ********************************************************************************/ #include "HomeSpan.h" +#include "TLV8.h" #define MAX_LIGHTS 1 void setup() { Serial.begin(115200); + delay(1000); + Serial.printf("\n\nREADY\n\n"); + + TLV8 tlv, tlv2; + + const size_t nMax=257; + uint8_t c[nMax]; + for(int i=0;i Date: Wed, 27 Mar 2024 20:41:29 -0500 Subject: [PATCH 029/154] Fixed return value in TLV8::add() --- src/TLV8.cpp | 2 +- src/src.ino | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/TLV8.cpp b/src/TLV8.cpp index 2f19730..3a52bd9 100644 --- a/src/TLV8.cpp +++ b/src/TLV8.cpp @@ -75,7 +75,7 @@ TLV8_it TLV8::add(uint8_t tag, size_t len, const uint8_t* val){ else emplace_back(tag,len,val); - return(begin()); + return(--end()); } ///////////////////////////////////// diff --git a/src/src.ino b/src/src.ino index 7fda69a..e92ce80 100644 --- a/src/src.ino +++ b/src/src.ino @@ -50,10 +50,14 @@ void setup() { tlv.add(7,33); tlv.add(7,34); tlv.add(15,nMax,c); - tlv.print(); +// tlv.print(); + + tlv.print(--tlv.end(),tlv.end()); Serial.printf("\nSize=%d\n\n",tlv.pack_size()); + + uint8_t bOut[tlv.pack_size()]; tlv.pack(bOut); From 1b74564baf9c9da504c7862a44ec735fb178c672 Mon Sep 17 00:00:00 2001 From: Gregg Date: Fri, 29 Mar 2024 09:19:06 -0500 Subject: [PATCH 030/154] More TLV updates --- src/HomeSpan.cpp | 10 ++++++---- src/HomeSpan.h | 6 +++--- src/src.ino | 37 +++++-------------------------------- 3 files changed, 14 insertions(+), 39 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 1364b59..73973fb 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -896,7 +896,7 @@ void Span::processSerialCommand(const char *c){ LOG0("%s%s",(foundPerms++)?"+":"",pNames[i]); } - if((*chr)->format!=FORMAT::STRING && (*chr)->format!=FORMAT::BOOL && (*chr)->format!=FORMAT::DATA){ + if((*chr)->formatformat!=FORMAT::BOOL){ if((*chr)->validValues) LOG0(", Valid Values=%s",(*chr)->validValues); else if((*chr)->uvGet((*chr)->stepValue)>0) @@ -937,7 +937,7 @@ void Span::processSerialCommand(const char *c){ if((*chr)->setValidValuesError) LOG0(" *** WARNING #%d! Attempt to set Custom Valid Values for this Characteristic ignored ***\n",++nWarnings); - if((*chr)->format!=STRING && (!(((*chr)->uvGet((*chr)->value) >= (*chr)->uvGet((*chr)->minValue)) && ((*chr)->uvGet((*chr)->value) <= (*chr)->uvGet((*chr)->maxValue))))) + if((*chr)->formatuvGet((*chr)->value) >= (*chr)->uvGet((*chr)->minValue)) && ((*chr)->uvGet((*chr)->value) <= (*chr)->uvGet((*chr)->maxValue))))) LOG0(" *** WARNING #%d! Value of %g is out of range [%g,%g] ***\n",++nWarnings,(*chr)->uvGet((*chr)->value),(*chr)->uvGet((*chr)->minValue),(*chr)->uvGet((*chr)->maxValue)); } // Characteristics @@ -1457,7 +1457,7 @@ int Span::updateCharacteristics(char *buf, SpanBuf *pObj){ if(status==StatusCode::OK){ // if status is okay pObj[j].characteristic->uvSet(pObj[j].characteristic->value,pObj[j].characteristic->newValue); // update characteristic value with new value if(pObj[j].characteristic->nvsKey){ // if storage key found - if(pObj[j].characteristic->format!=FORMAT::STRING && pObj[j].characteristic->format!=FORMAT::DATA) + if(pObj[j].characteristic->formatnvsKey,pObj[j].characteristic->value.UINT64); // store data as uint64_t regardless of actual type (it will be read correctly when access through uvGet()) else nvs_set_str(charNVS,pObj[j].characteristic->nvsKey,pObj[j].characteristic->value.STRING); // store data @@ -1833,7 +1833,7 @@ SpanCharacteristic::~SpanCharacteristic(){ free(validValues); free(nvsKey); - if(format==FORMAT::STRING || format==FORMAT::DATA){ + if(format>=FORMAT::STRING){ free(value.STRING); free(newValue.STRING); } @@ -2005,6 +2005,8 @@ StatusCode SpanCharacteristic::loadUpdate(char *val, char *ev, boolean wr){ break; case STRING: + case DATA: + case TLV_ENC: uvSet(newValue,(const char *)val); break; diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 99865a7..e10c32a 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -675,12 +675,12 @@ class SpanCharacteristic{ void setString(const char *val, boolean notify=true){ if(!((perms&EV) || (updateFlag==2))){ - LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setData() ignored. No EVENT NOTIFICATION (EV) permission on this characteristic\n\n",hapName); + LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setString() ignored. No EVENT NOTIFICATION (EV) permission on this characteristic\n\n",hapName); return; } if(updateFlag==1) - LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setVal() within update() while it is being updated by Home App. This may cause device to become non-responsive!\n\n",hapName); + LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setString() within update() while it is being updated by Home App. This may cause device to become non-responsive!\n\n",hapName); uvSet(value,val); uvSet(newValue,value); @@ -763,7 +763,7 @@ class SpanCharacteristic{ template void setVal(T val, boolean notify=true){ if(!((perms&EV) || (updateFlag==2))){ - LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setData() ignored. No EVENT NOTIFICATION (EV) permission on this characteristic\n\n",hapName); + LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setVal() ignored. No EVENT NOTIFICATION (EV) permission on this characteristic\n\n",hapName); return; } diff --git a/src/src.ino b/src/src.ino index e92ce80..19677e5 100644 --- a/src/src.ino +++ b/src/src.ino @@ -30,41 +30,11 @@ #define MAX_LIGHTS 1 +CUSTOM_CHAR_DATA(UserData, AAAAAAAA-BBBB-AAAA-AAAA-AAAAAAAAAAAA, PR+PW+EV); + void setup() { Serial.begin(115200); - delay(1000); - Serial.printf("\n\nREADY\n\n"); - - TLV8 tlv, tlv2; - - const size_t nMax=257; - uint8_t c[nMax]; - for(int i=0;isetDescription("Custom Data")->setData(x,5); char c[30]; sprintf(c,"Light-%d",i); new Characteristic::Name(c); @@ -93,6 +65,7 @@ void setup() { void loop(){ homeSpan.poll(); + delay(100000); } From 69fd86f2efec612750a685d98aaf4e7d5930cf73 Mon Sep 17 00:00:00 2001 From: Gregg Date: Fri, 29 Mar 2024 22:18:04 -0500 Subject: [PATCH 031/154] Testing TLV Characteristic using DisplayOrder (custom Characteristic) --- src/HomeSpan.h | 5 +- src/Span.h | 4 + src/src.ino | 219 +++++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 202 insertions(+), 26 deletions(-) diff --git a/src/HomeSpan.h b/src/HomeSpan.h index e10c32a..2938f4a 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -509,7 +509,7 @@ class SpanCharacteristic{ StatusCode loadUpdate(char *val, char *ev, boolean wr); // load updated val/ev from PUT /characteristic JSON request. Return intitial HAP status code (checks to see if characteristic is found, is writable, etc.) String uvPrint(UVal &u){ - char c[67]; // space for 64 characters + surrounding quotes + terminating null + char c[64]; switch(format){ case FORMAT::BOOL: return(String(u.BOOL)); @@ -530,8 +530,7 @@ class SpanCharacteristic{ case FORMAT::STRING: case FORMAT::DATA: case FORMAT::TLV_ENC: - sprintf(c,"\"%.64s\"",u.STRING); // Truncating string to 64 chars - return(String(c)); + return(String("\"") + String(u.STRING) + String("\"")); } // switch return(String()); // included to prevent compiler warnings } diff --git a/src/Span.h b/src/Span.h index 1dc1395..23bba59 100644 --- a/src/Span.h +++ b/src/Span.h @@ -616,6 +616,10 @@ namespace Characteristic { HapChar _CUSTOM_##NAME {#UUID,#NAME,(PERMS)(PERMISISONS),DATA,true}; \ namespace Characteristic { struct NAME : SpanCharacteristic { NAME(const char * val="AA==", boolean nvsStore=false) : SpanCharacteristic {&_CUSTOM_##NAME,true} { init(val,nvsStore); } }; } +#define CUSTOM_CHAR_TLV(NAME,UUID,PERMISISONS) \ + HapChar _CUSTOM_##NAME {#UUID,#NAME,(PERMS)(PERMISISONS),TLV_ENC,true}; \ + namespace Characteristic { struct NAME : SpanCharacteristic { NAME(const char * val="", boolean nvsStore=false) : SpanCharacteristic {&_CUSTOM_##NAME,true} { init(val,nvsStore); } }; } + #else #define CUSTOM_CHAR(NAME,UUID,PERMISISONS,FORMAT,DEFVAL,MINVAL,MAXVAL,STATIC_RANGE) \ diff --git a/src/src.ino b/src/src.ino index 19677e5..01f20f2 100644 --- a/src/src.ino +++ b/src/src.ino @@ -28,45 +28,218 @@ #include "HomeSpan.h" #include "TLV8.h" -#define MAX_LIGHTS 1 +CUSTOM_CHAR_TLV(DisplayOrder,136,PR+EV); -CUSTOM_CHAR_DATA(UserData, AAAAAAAA-BBBB-AAAA-AAAA-AAAAAAAAAAAA, PR+PW+EV); +struct HomeSpanTV : Service::Television { + + SpanCharacteristic *active = new Characteristic::Active(0); // TV On/Off (set to Off at start-up) + SpanCharacteristic *activeID = new Characteristic::ActiveIdentifier(3); // Sets HDMI 3 on start-up + SpanCharacteristic *remoteKey = new Characteristic::RemoteKey(); // Used to receive button presses from the Remote Control widget + SpanCharacteristic *settingsKey = new Characteristic::PowerModeSelection(); // Adds "View TV Setting" option to Selection Screen + SpanCharacteristic *displayOrder = new Characteristic::DisplayOrder(); + + HomeSpanTV(const char *name) : Service::Television() { + new Characteristic::ConfiguredName(name); // Name of TV + Serial.printf("Configured TV: %s\n",name); + + TLV8 orderTLV; + uint32_t order[]={5,10,6,2,1,9,11,3,18,12}; + + for(int i=0;i0) + orderTLV.add(0); + orderTLV.add(1,sizeof(uint32_t),(uint8_t*)(order+i)); + } + + orderTLV.print(); + size_t n=orderTLV.pack_size(); + Serial.printf("Size=%d\n",n); + uint8_t c[n]; + orderTLV.pack(c); + displayOrder->setData(c,n); + + new SpanUserCommand('P', "- change order of inputs", changeOrder, this); + } + + boolean update() override { + + if(active->updated()){ + Serial.printf("Set TV Power to: %s\n",active->getNewVal()?"ON":"OFF"); + } + + if(activeID->updated()){ + Serial.printf("Set Input Source to HDMI-%d\n",activeID->getNewVal()); + } + + if(settingsKey->updated()){ + Serial.printf("Received request to \"View TV Settings\"\n"); + } + + if(remoteKey->updated()){ + Serial.printf("Remote Control key pressed: "); + switch(remoteKey->getNewVal()){ + case 4: + Serial.printf("UP ARROW\n"); + break; + case 5: + Serial.printf("DOWN ARROW\n"); + break; + case 6: + Serial.printf("LEFT ARROW\n"); + break; + case 7: + Serial.printf("RIGHT ARROW\n"); + break; + case 8: + Serial.printf("SELECT\n"); + break; + case 9: + Serial.printf("BACK\n"); + break; + case 11: + Serial.printf("PLAY/PAUSE\n"); + break; + case 15: + Serial.printf("INFO\n"); + break; + default: + Serial.print("UNKNOWN KEY\n"); + } + } + + return(true); + } + + static void changeOrder(const char *buf, void *arg){ + HomeSpanTV *hsTV=(HomeSpanTV *)arg; + + TLV8 orderTLV; + uint32_t order[]={12,10,6,2,1,9,11,3,18,5}; + + for(int i=0;i0) + orderTLV.add(0); + orderTLV.add(1,sizeof(uint32_t),(uint8_t*)(order+i)); + } + + orderTLV.print(); + size_t n=orderTLV.pack_size(); + Serial.printf("Size=%d\n",n); + uint8_t c[n]; + orderTLV.pack(c); + hsTV->displayOrder->setData(c,n); + } + +}; + +/////////////////////////////// void setup() { - + Serial.begin(115200); homeSpan.setLogLevel(2); + + homeSpan.begin(Category::Television,"HomeSpan Television"); - homeSpan.begin(Category::Lighting,"HomeSpan Max"); + SPAN_ACCESSORY(); + + SpanService *hdmi1 = new Service::InputSource(); // Source included in Selection List, but excluded from Settings Screen + new Characteristic::ConfiguredName("Alpha"); + new Characteristic::Identifier(5); + new Characteristic::IsConfigured(1); + new Characteristic::CurrentVisibilityState(0); + new Characteristic::TargetVisibilityState(0); - new SpanAccessory(); - new Service::AccessoryInformation(); - new Characteristic::Identify(); + SpanService *hdmi2 = new Service::InputSource(); + new Characteristic::ConfiguredName("Gamma"); + new Characteristic::Identifier(10); + new Characteristic::IsConfigured(1); + new Characteristic::CurrentVisibilityState(0); + new Characteristic::TargetVisibilityState(0); - for(int i=0;isetDescription("Custom Data")->setData(x,5); - char c[30]; - sprintf(c,"Light-%d",i); - new Characteristic::Name(c); - new Service::LightBulb(); - new Characteristic::On(0,false); - WEBLOG("Configuring %s",c); - } + SpanService *hdmi3 = new Service::InputSource(); + new Characteristic::ConfiguredName("Beta"); + new Characteristic::Identifier(6); + new Characteristic::IsConfigured(1); + new Characteristic::CurrentVisibilityState(0); + new Characteristic::TargetVisibilityState(0); + SpanService *hdmi4 = new Service::InputSource(); + new Characteristic::ConfiguredName("Zebra"); + new Characteristic::Identifier(2); + new Characteristic::IsConfigured(1); + new Characteristic::CurrentVisibilityState(0); + new Characteristic::TargetVisibilityState(0); + + SpanService *hdmi5 = new Service::InputSource(); + new Characteristic::ConfiguredName("Delta"); + new Characteristic::Identifier(1); + new Characteristic::IsConfigured(1); + new Characteristic::CurrentVisibilityState(0); + new Characteristic::TargetVisibilityState(0); + + SpanService *hdmi6 = new Service::InputSource(); + new Characteristic::ConfiguredName("Trident"); + new Characteristic::Identifier(9); + new Characteristic::IsConfigured(1); + new Characteristic::CurrentVisibilityState(0); + new Characteristic::TargetVisibilityState(0); + + SpanService *hdmi7 = new Service::InputSource(); + new Characteristic::ConfiguredName("Netflix"); + new Characteristic::Identifier(11); + new Characteristic::IsConfigured(1); + new Characteristic::CurrentVisibilityState(0); + new Characteristic::TargetVisibilityState(0); + + SpanService *hdmi8 = new Service::InputSource(); + new Characteristic::ConfiguredName("Alpha2"); + new Characteristic::Identifier(3); + new Characteristic::IsConfigured(1); + new Characteristic::CurrentVisibilityState(0); + new Characteristic::TargetVisibilityState(0); + + SpanService *hdmi9 = new Service::InputSource(); + new Characteristic::ConfiguredName("Moon"); + new Characteristic::Identifier(18); + new Characteristic::IsConfigured(1); + new Characteristic::CurrentVisibilityState(0); + new Characteristic::TargetVisibilityState(0); + + SpanService *hdmi10 = new Service::InputSource(); + new Characteristic::ConfiguredName("Gamba"); + new Characteristic::Identifier(12); + new Characteristic::IsConfigured(1); + new Characteristic::CurrentVisibilityState(0); + new Characteristic::TargetVisibilityState(0); + + SpanService *speaker = new Service::TelevisionSpeaker(); + new Characteristic::VolumeSelector(); + new Characteristic::VolumeControlType(3); + + (new HomeSpanTV("Test TV")) // Define a Television Service. Must link in InputSources! + ->addLink(hdmi1) + ->addLink(hdmi2) + ->addLink(hdmi3) + ->addLink(hdmi4) + ->addLink(hdmi5) + ->addLink(hdmi6) + ->addLink(hdmi7) + ->addLink(hdmi8) + ->addLink(hdmi9) + ->addLink(hdmi10) + ->addLink(speaker) + ; + } + ////////////////////////////////////// void loop(){ - homeSpan.poll(); - delay(100000); - + homeSpan.poll(); } ////////////////////////////////////// From e8f9ca8ac1ad214bd15d91268056b2b06f8b2261 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 30 Mar 2024 06:03:31 -0500 Subject: [PATCH 032/154] Modified UUID check to allow for Custom short-form UUID Also truncated string Characteristics printed via 'i' CLI Command to show only first 32 characters followed by "..." if there are more than 32. --- src/HomeSpan.cpp | 6 +++--- src/HomeSpan.h | 7 +++++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 73973fb..6b0891d 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -887,8 +887,8 @@ void Span::processSerialCommand(const char *c){ isBridge=false; // ...this is not a bridge device for(auto chr=(*svc)->Characteristics.begin(); chr!=(*svc)->Characteristics.end(); chr++){ - LOG0(" \u21e8 Characteristic %s(%s): IID=%d, %sUUID=\"%s\", %sPerms=", - (*chr)->hapName,(*chr)->uvPrint((*chr)->value).c_str(),(*chr)->iid,(*chr)->isCustom?"Custom-":"",(*chr)->type,(*chr)->perms!=(*chr)->hapChar->perms?"Custom-":""); + LOG0(" \u21e8 Characteristic %s(%.32s%s): IID=%d, %sUUID=\"%s\", %sPerms=", + (*chr)->hapName,(*chr)->uvPrint((*chr)->value).c_str(),strlen((*chr)->uvPrint((*chr)->value).c_str())>32?"...":"",(*chr)->iid,(*chr)->isCustom?"Custom-":"",(*chr)->type,(*chr)->perms!=(*chr)->hapChar->perms?"Custom-":""); int foundPerms=0; for(uint8_t i=0;i<7;i++){ @@ -925,7 +925,7 @@ void Span::processSerialCommand(const char *c){ if(!(*chr)->isCustom && !(*svc)->isCustom && std::find((*svc)->req.begin(),(*svc)->req.end(),(*chr)->hapChar)==(*svc)->req.end() && std::find((*svc)->opt.begin(),(*svc)->opt.end(),(*chr)->hapChar)==(*svc)->opt.end()) LOG0(" *** WARNING #%d! Service does not support this Characteristic ***\n",++nWarnings); else - if(invalidUUID((*chr)->type,(*chr)->isCustom)) + if(invalidUUID((*chr)->type)) LOG0(" *** ERROR #%d! Format of UUID is invalid ***\n",++nErrors); else if(std::find_if((*svc)->Characteristics.begin(),chr,[chr](SpanCharacteristic *c)->boolean{return(c->hapChar==(*chr)->hapChar);})!=chr) diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 2938f4a..2d0fa22 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -287,10 +287,13 @@ class Span{ void clearNotify(int slotNum); // set ev notification flags for connection 'slotNum' to false across all characteristics void printfNotify(SpanBuf *pObj, int nObj, int conNum); // writes notification JSON to hapOut stream based on SpanBuf objects and specified connection number - static boolean invalidUUID(const char *uuid, boolean isCustom){ + static boolean invalidUUID(const char *uuid){ int x=0; + sscanf(uuid,"%*8[0-9a-fA-F]%n",&x); // check for short-form of UUID + if(strlen(uuid)==x && uuid[0]!='0') + return(false); 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)); + return(strlen(uuid)!=36 || x!=36); } public: From f6d4d37ff7a1059ea4065b5eb6d1a1b7508f8cae Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 30 Mar 2024 17:51:37 -0500 Subject: [PATCH 033/154] Initial creation of setTLV() --- src/HAP.h | 1 - src/HomeSpan.h | 25 ++++++++++++++++++++++++- src/src.ino | 7 +------ 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/src/HAP.h b/src/HAP.h index 71779cb..c263277 100644 --- a/src/HAP.h +++ b/src/HAP.h @@ -34,7 +34,6 @@ #include "HAPConstants.h" #include "HKDF.h" #include "SRP.h" -#include "TLV8.h" const TLV8_names HAP_Names[] = { {kTLVType_Separator,"SEPARATOR"}, diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 2d0fa22..7298a7d 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -55,6 +55,7 @@ #include "HAPConstants.h" #include "HapQR.h" #include "Characteristics.h" +#include "TLV8.h" using std::vector; using std::unordered_map; @@ -756,12 +757,34 @@ class SpanCharacteristic{ } size_t olen; - mbedtls_base64_encode(NULL,0,&olen,data,len); // get length of string buffer needed (mbedtls includes the trailing null in this size) + mbedtls_base64_encode(NULL,0,&olen,NULL,len); // get length of string buffer needed (mbedtls includes the trailing null in this size) TempBuffer tBuf(olen); // create temporary string buffer, with room for trailing null mbedtls_base64_encode((uint8_t*)tBuf.get(),olen,&olen,data,len ); // encode data into string buf setString(tBuf,notify); // call setString to continue processing as if characteristic was a string } + + void setTLV(TLV8 &tlv, boolean notify=true){ + + if(updateFlag==1) + LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setVal() within update() while it is being updated by Home App. This may cause device to become non-responsive!\n\n",hapName); + + const size_t bufSize=36; // maximum size of buffer to store packed TLV bytes before encoding directly into value; must be multiple of 3 + size_t nBytes=tlv.pack_size(); // total size of packed TLV in bytes + size_t nChars; + mbedtls_base64_encode(NULL,0,&nChars,NULL,nBytes); // get length of string buffer needed (mbedtls includes the trailing null in this size) + value.STRING = (char *)HS_REALLOC(value.STRING,nChars); // allocate sufficient size for storing value + TempBuffer tBuf(bufSize); // create fixed-size buffer to store packed TLV bytes + tlv.pack_init(); // initialize TLV packing + uint8_t *p=(uint8_t *)value.STRING; // set pointer to beginning of value + while((nBytes=tlv.pack(tBuf,bufSize))>0){ // pack the next set of TLV bytes, up to a maximum of bufSize, into tBuf + size_t olen; // number of characters written (excludes null character) + mbedtls_base64_encode(p,nChars,&olen,tBuf,nBytes); // encode data directly into value + p+=olen; // advance pointer to null character + nChars-=olen; // subtract number of characters remaining + } + } + template void setVal(T val, boolean notify=true){ if(!((perms&EV) || (updateFlag==2))){ diff --git a/src/src.ino b/src/src.ino index 01f20f2..611143a 100644 --- a/src/src.ino +++ b/src/src.ino @@ -26,7 +26,6 @@ ********************************************************************************/ #include "HomeSpan.h" -#include "TLV8.h" CUSTOM_CHAR_TLV(DisplayOrder,136,PR+EV); @@ -52,11 +51,7 @@ struct HomeSpanTV : Service::Television { } orderTLV.print(); - size_t n=orderTLV.pack_size(); - Serial.printf("Size=%d\n",n); - uint8_t c[n]; - orderTLV.pack(c); - displayOrder->setData(c,n); + displayOrder->setTLV(orderTLV); new SpanUserCommand('P', "- change order of inputs", changeOrder, this); } From 11bd605a032bb15184b26757df72a1335f00c574 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 30 Mar 2024 22:13:52 -0500 Subject: [PATCH 034/154] updated setData and setTLV to use new common setValCheck() and setValFinish() --- src/HomeSpan.cpp | 4 +- src/HomeSpan.h | 100 +++++++++++++++++++++++++++++------------------ src/src.ino | 6 +++ 3 files changed, 69 insertions(+), 41 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 6b0891d..4afb96f 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -887,8 +887,8 @@ void Span::processSerialCommand(const char *c){ isBridge=false; // ...this is not a bridge device for(auto chr=(*svc)->Characteristics.begin(); chr!=(*svc)->Characteristics.end(); chr++){ - LOG0(" \u21e8 Characteristic %s(%.32s%s): IID=%d, %sUUID=\"%s\", %sPerms=", - (*chr)->hapName,(*chr)->uvPrint((*chr)->value).c_str(),strlen((*chr)->uvPrint((*chr)->value).c_str())>32?"...":"",(*chr)->iid,(*chr)->isCustom?"Custom-":"",(*chr)->type,(*chr)->perms!=(*chr)->hapChar->perms?"Custom-":""); + LOG0(" \u21e8 Characteristic %s(%.33s%s): IID=%d, %sUUID=\"%s\", %sPerms=", + (*chr)->hapName,(*chr)->uvPrint((*chr)->value).c_str(),strlen((*chr)->uvPrint((*chr)->value).c_str())>33?"...\"":"",(*chr)->iid,(*chr)->isCustom?"Custom-":"",(*chr)->type,(*chr)->perms!=(*chr)->hapChar->perms?"Custom-":""); int foundPerms=0; for(uint8_t i=0;i<7;i++){ diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 7298a7d..fb1ad7f 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -675,12 +675,7 @@ class SpanCharacteristic{ return NULL; } - void setString(const char *val, boolean notify=true){ - - if(!((perms&EV) || (updateFlag==2))){ - LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setString() ignored. No EVENT NOTIFICATION (EV) permission on this characteristic\n\n",hapName); - return; - } + void setString(const char *val, boolean notify=true){ if(updateFlag==1) LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setString() within update() while it is being updated by Home App. This may cause device to become non-responsive!\n\n",hapName); @@ -746,51 +741,78 @@ class SpanCharacteristic{ void setData(uint8_t *data, size_t len, boolean notify=true){ - if(!((perms&EV) || (updateFlag==2))){ - LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setData() ignored. No EVENT NOTIFICATION (EV) permission on this characteristic\n\n",hapName); - return; - } + setValCheck(); - if(len<1){ - LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setData() ignored. Size of data buffer must be greater than zero\n\n",hapName); - return; + if(len>0){ + size_t olen; + mbedtls_base64_encode(NULL,0,&olen,NULL,len); // get length of string buffer needed (mbedtls includes the trailing null in this size) + value.STRING = (char *)HS_REALLOC(value.STRING,olen); // allocate sufficient size for storing value + mbedtls_base64_encode((uint8_t*)value.STRING,olen,&olen,data,len ); // encode data into string buf + } else { + value.STRING = (char *)HS_REALLOC(value.STRING,1); // allocate sufficient size for just trailing null character + *value.STRING ='\0'; } - size_t olen; - mbedtls_base64_encode(NULL,0,&olen,NULL,len); // get length of string buffer needed (mbedtls includes the trailing null in this size) - TempBuffer tBuf(olen); // create temporary string buffer, with room for trailing null - mbedtls_base64_encode((uint8_t*)tBuf.get(),olen,&olen,data,len ); // encode data into string buf - setString(tBuf,notify); // call setString to continue processing as if characteristic was a string + setValFinish(notify); } void setTLV(TLV8 &tlv, boolean notify=true){ - if(updateFlag==1) - LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setVal() within update() while it is being updated by Home App. This may cause device to become non-responsive!\n\n",hapName); - + setValCheck(); + const size_t bufSize=36; // maximum size of buffer to store packed TLV bytes before encoding directly into value; must be multiple of 3 size_t nBytes=tlv.pack_size(); // total size of packed TLV in bytes - size_t nChars; - mbedtls_base64_encode(NULL,0,&nChars,NULL,nBytes); // get length of string buffer needed (mbedtls includes the trailing null in this size) - value.STRING = (char *)HS_REALLOC(value.STRING,nChars); // allocate sufficient size for storing value - TempBuffer tBuf(bufSize); // create fixed-size buffer to store packed TLV bytes - tlv.pack_init(); // initialize TLV packing - uint8_t *p=(uint8_t *)value.STRING; // set pointer to beginning of value - while((nBytes=tlv.pack(tBuf,bufSize))>0){ // pack the next set of TLV bytes, up to a maximum of bufSize, into tBuf - size_t olen; // number of characters written (excludes null character) - mbedtls_base64_encode(p,nChars,&olen,tBuf,nBytes); // encode data directly into value - p+=olen; // advance pointer to null character - nChars-=olen; // subtract number of characters remaining - } - } - template void setVal(T val, boolean notify=true){ + if(nBytes>0){ + size_t nChars; + mbedtls_base64_encode(NULL,0,&nChars,NULL,nBytes); // get length of string buffer needed (mbedtls includes the trailing null in this size) + value.STRING = (char *)HS_REALLOC(value.STRING,nChars); // allocate sufficient size for storing value + TempBuffer tBuf(bufSize); // create fixed-size buffer to store packed TLV bytes + tlv.pack_init(); // initialize TLV packing + uint8_t *p=(uint8_t *)value.STRING; // set pointer to beginning of value + while((nBytes=tlv.pack(tBuf,bufSize))>0){ // pack the next set of TLV bytes, up to a maximum of bufSize, into tBuf + size_t olen; // number of characters written (excludes null character) + mbedtls_base64_encode(p,nChars,&olen,tBuf,nBytes); // encode data directly into value + p+=olen; // advance pointer to null character + nChars-=olen; // subtract number of characters remaining + } + } else { + value.STRING = (char *)HS_REALLOC(value.STRING,1); // allocate sufficient size for just trailing null character + *value.STRING ='\0'; + } - if(!((perms&EV) || (updateFlag==2))){ - LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setVal() ignored. No EVENT NOTIFICATION (EV) permission on this characteristic\n\n",hapName); - return; - } + setValFinish(notify); + } + + void setValCheck(){ + if(updateFlag==1) + LOG0("\n*** WARNING: Attempt to set value of Characteristic::%s within update() while it is being simultaneously updated by Home App. This may cause device to become non-responsive!\n\n",hapName); + } + + void setValFinish(boolean notify){ + + uvSet(newValue,value); + updateTime=homeSpan.snapTime; + + if(notify){ + if((perms&EV) && (updateFlag!=2)){ // only broadcast notification if EV permission is set AND update is NOT being done in context of write-response + 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 printfNotify knows to consider this "update" + homeSpan.Notifications.push_back(sb); // store SpanBuf in Notifications vector + } + + if(nvsKey){ + nvs_set_str(homeSpan.charNVS,nvsKey,value.STRING); // store data + nvs_commit(homeSpan.charNVS); + } + } + } + + template void setVal(T val, boolean notify=true){ if(updateFlag==1) LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setVal() within update() while it is being updated by Home App. This may cause device to become non-responsive!\n\n",hapName); diff --git a/src/src.ino b/src/src.ino index 611143a..375d88b 100644 --- a/src/src.ino +++ b/src/src.ino @@ -28,6 +28,7 @@ #include "HomeSpan.h" CUSTOM_CHAR_TLV(DisplayOrder,136,PR+EV); +CUSTOM_CHAR_DATA(TestData,333,PR+EV); struct HomeSpanTV : Service::Television { @@ -36,6 +37,7 @@ struct HomeSpanTV : Service::Television { SpanCharacteristic *remoteKey = new Characteristic::RemoteKey(); // Used to receive button presses from the Remote Control widget SpanCharacteristic *settingsKey = new Characteristic::PowerModeSelection(); // Adds "View TV Setting" option to Selection Screen SpanCharacteristic *displayOrder = new Characteristic::DisplayOrder(); + SpanCharacteristic *testData = new Characteristic::TestData(); HomeSpanTV(const char *name) : Service::Television() { new Characteristic::ConfiguredName(name); // Name of TV @@ -53,6 +55,10 @@ struct HomeSpanTV : Service::Television { orderTLV.print(); displayOrder->setTLV(orderTLV); + uint8_t blob[]={1,2,3,4,5,6,7,8,9,10,11,12}; +// testData->setData(blob,sizeof(blob)); + testData->setData(blob,1); + new SpanUserCommand('P', "- change order of inputs", changeOrder, this); } From cbe26c7c4142f6159c8ea33b56090835994755f5 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 31 Mar 2024 08:51:06 -0500 Subject: [PATCH 035/154] update setString() to use setValCheck() and setValFinish() --- src/HomeSpan.h | 27 +++------------------------ src/src.ino | 9 ++++++++- 2 files changed, 11 insertions(+), 25 deletions(-) diff --git a/src/HomeSpan.h b/src/HomeSpan.h index fb1ad7f..919a213 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -677,31 +677,10 @@ class SpanCharacteristic{ void setString(const char *val, boolean notify=true){ - if(updateFlag==1) - LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setString() within update() while it is being updated by Home App. This may cause device to become non-responsive!\n\n",hapName); - + setValCheck(); uvSet(value,val); - uvSet(newValue,value); - - updateTime=homeSpan.snapTime; - - if(notify){ - if(updateFlag!=2){ // do not broadcast EV if update is being done in context of write-response - 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 printfNotify knows to consider this "update" - homeSpan.Notifications.push_back(sb); // store SpanBuf in Notifications vector - } - - if(nvsKey){ - nvs_set_str(homeSpan.charNVS,nvsKey,value.STRING); // store data - nvs_commit(homeSpan.charNVS); - } - } - - } // setString() + setValFinish(notify); + } size_t getData(uint8_t *data, size_t len){ if(formatsetData(blob,1); new SpanUserCommand('P', "- change order of inputs", changeOrder, this); + new SpanUserCommand('C', "- change name of TV", setTVName, this); } boolean update() override { @@ -111,6 +113,11 @@ struct HomeSpanTV : Service::Television { return(true); } + static void setTVName(const char *buf, void *arg){ + HomeSpanTV *hsTV=(HomeSpanTV *)arg; + hsTV->tvname->setString("New Name"); + } + static void changeOrder(const char *buf, void *arg){ HomeSpanTV *hsTV=(HomeSpanTV *)arg; From eb821f002f1fad8b1a78cf991d7f99271a4a0f86 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 31 Mar 2024 09:23:23 -0500 Subject: [PATCH 036/154] created getTLV(TLV8 &tlv) Works, but is memory-inefficient since it decodes entire string before unpacking. Need to add new functionality to TLV8 so that unpacking can be done in chunks similar to how pack() works. Also need to create getNewTLV() OR make all get/getNew generic to reduce code size. --- src/HomeSpan.h | 24 +++++++++++++++++++++--- src/src.ino | 7 +++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 919a213..97a102e 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -736,6 +736,25 @@ class SpanCharacteristic{ } + size_t getTLV(TLV8 &tlv){ + if(format tBuf(olen); // create temporary buffer + + int ret=mbedtls_base64_decode(tBuf,olen,&olen,(uint8_t *)value.STRING,strlen(value.STRING)); + + if(ret==MBEDTLS_ERR_BASE64_INVALID_CHARACTER){ + LOG0("\n*** WARNING: Can't decode Characteristic::%s with getTLV(). Data is not in base-64 format\n\n",hapName); + return(0); + } + + tlv.unpack(tBuf,olen); + return(tlv.pack_size()); + } + void setTLV(TLV8 &tlv, boolean notify=true){ setValCheck(); @@ -793,9 +812,8 @@ class SpanCharacteristic{ template void setVal(T val, boolean notify=true){ - if(updateFlag==1) - LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setVal() within update() while it is being updated by Home App. This may cause device to become non-responsive!\n\n",hapName); - + setValCheck(); + if(!((val >= uvGet(minValue)) && (val <= uvGet(maxValue)))){ LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setVal(%g) is out of range [%g,%g]. This may cause device to become non-responsive!\n\n", hapName,(double)val,uvGet(minValue),uvGet(maxValue)); diff --git a/src/src.ino b/src/src.ino index ef3f4b1..d6ce93f 100644 --- a/src/src.ino +++ b/src/src.ino @@ -122,6 +122,12 @@ struct HomeSpanTV : Service::Television { HomeSpanTV *hsTV=(HomeSpanTV *)arg; TLV8 orderTLV; + + Serial.printf("BEFORE:\n"); + hsTV->displayOrder->getTLV(orderTLV); + orderTLV.print(); + orderTLV.wipe(); + uint32_t order[]={12,10,6,2,1,9,11,3,18,5}; for(int i=0;i Date: Sun, 31 Mar 2024 21:13:17 -0500 Subject: [PATCH 037/154] Updated getTLV() so it uses a fixed buffer as intermediate step This is much more memory efficient. Instead of decoding entire STRING from base64 to a temporary buffer or potentially very large size and then unpacking into TLV object, we decode a maximum of 48 characters at a time and unpack the resulting 36 (max) bytes until entire STRING is consumed. getTLV() returns pack_size of final TLV, unless there is a problem with conversion, in which cae TLV is wiped and return value=0 --- src/HomeSpan.h | 40 ++++++++++++++++++++++++++++------------ src/TLV8.cpp | 13 ++++++++----- src/TLV8.h | 2 +- src/src.ino | 3 +-- 4 files changed, 38 insertions(+), 20 deletions(-) diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 97a102e..0e90699 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -735,24 +735,40 @@ class SpanCharacteristic{ setValFinish(notify); } - - size_t getTLV(TLV8 &tlv){ + size_t getTLV(TLV8 &tlv){ + if(format tBuf(olen); // create temporary buffer + const size_t bufSize=36; // maximum size of buffer to store decoded bytes before unpacking into TLV; must be multiple of 3 + TempBuffer tBuf(bufSize); // create fixed-size buffer to store decoded bytes + tlv.wipe(); // clear TLV completely - int ret=mbedtls_base64_decode(tBuf,olen,&olen,(uint8_t *)value.STRING,strlen(value.STRING)); + size_t nChars=strlen(value.STRING); // total characters to decode + uint8_t *p=(uint8_t *)value.STRING; // set pointer to beginning of value + const size_t decodeSize=bufSize/3*4; // number of characters to decode in each pass + int status=0; - if(ret==MBEDTLS_ERR_BASE64_INVALID_CHARACTER){ - LOG0("\n*** WARNING: Can't decode Characteristic::%s with getTLV(). Data is not in base-64 format\n\n",hapName); - return(0); + while(nChars>0){ + size_t olen; + size_t n=nChars0){ + LOG0("\n*** WARNING: Can't unpack Characteristic::%s with getTLV(). TLV record is incomplete or corrupted\n\n",hapName); + tlv.wipe(); + return(0); + } + return(tlv.pack_size()); } void setTLV(TLV8 &tlv, boolean notify=true){ diff --git a/src/TLV8.cpp b/src/TLV8.cpp index 3a52bd9..ff766f7 100644 --- a/src/TLV8.cpp +++ b/src/TLV8.cpp @@ -160,7 +160,10 @@ size_t TLV8::pack(uint8_t *buf, size_t bufSize){ ///////////////////////////////////// -void TLV8::unpack(uint8_t *buf, size_t bufSize){ +int TLV8::unpack(uint8_t *buf, size_t bufSize){ + + if(bufSize==0) + return(-1); if(empty()) unpackPhase=0; @@ -171,18 +174,17 @@ void TLV8::unpack(uint8_t *buf, size_t bufSize){ case 0: unpackTag=*buf++; bufSize--; + add(unpackTag); unpackPhase=1; break; case 1: unpackBytes=*buf++; bufSize--; - if(unpackBytes==0){ - add(unpackTag); + if(unpackBytes==0) unpackPhase=0; - } else { + else unpackPhase=2; - } break; case 2: @@ -196,6 +198,7 @@ void TLV8::unpack(uint8_t *buf, size_t bufSize){ break; } } + return(unpackPhase); } diff --git a/src/TLV8.h b/src/TLV8.h index af752e8..1ca1ea3 100644 --- a/src/TLV8.h +++ b/src/TLV8.h @@ -107,7 +107,7 @@ class TLV8 : public std::list> { void osprint(std::ostream& os, TLV8_it it1){osprint(os, it1, it1++);} void osprint(std::ostream& os){osprint(os, begin(), end());} - void unpack(uint8_t *buf, size_t bufSize); + int unpack(uint8_t *buf, size_t bufSize); void wipe(){std::list>().swap(*this);} diff --git a/src/src.ino b/src/src.ino index d6ce93f..aa236a0 100644 --- a/src/src.ino +++ b/src/src.ino @@ -122,8 +122,7 @@ struct HomeSpanTV : Service::Television { HomeSpanTV *hsTV=(HomeSpanTV *)arg; TLV8 orderTLV; - - Serial.printf("BEFORE:\n"); + hsTV->displayOrder->getTLV(orderTLV); orderTLV.print(); orderTLV.wipe(); From 1892a0a5a2d69b056fe0ad2d29cc42defdea9f13 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 31 Mar 2024 21:25:22 -0500 Subject: [PATCH 038/154] created getTLVGeneric() and used as base for getTLV() and getNewTLV() avoids unnecessary duplication of code where the only difference is whether you need the value or the newValue of a Characteristic. --- src/HomeSpan.h | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 0e90699..777b675 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -693,9 +693,9 @@ class SpanCharacteristic{ return(olen); if(ret==MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL) - LOG0("\n*** WARNING: Can't decode Characteristic::%s with getData(). Destination buffer is too small (%d out of %d bytes needed)\n\n",hapName,len,olen); + LOG0("\n*** WARNING: Can't decode Characteristic::%s with getData(). Destination buffer is too small (%d out of %d bytes needed)!\n\n",hapName,len,olen); else if(ret==MBEDTLS_ERR_BASE64_INVALID_CHARACTER) - LOG0("\n*** WARNING: Can't decode Characteristic::%s with getData(). Data is not in base-64 format\n\n",hapName); + LOG0("\n*** WARNING: Can't decode Characteristic::%s with getData(). Data is not in base-64 format!\n\n",hapName); return(olen); } @@ -711,9 +711,9 @@ class SpanCharacteristic{ return(olen); if(ret==MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL) - LOG0("\n*** WARNING: Can't decode Characteristic::%s with getData(). Destination buffer is too small (%d out of %d bytes needed)\n\n",hapName,len,olen); + LOG0("\n*** WARNING: Can't decode Characteristic::%s with getData(). Destination buffer is too small (%d out of %d bytes needed)!\n\n",hapName,len,olen); else if(ret==MBEDTLS_ERR_BASE64_INVALID_CHARACTER) - LOG0("\n*** WARNING: Can't decode Characteristic::%s with getData(). Data is not in base-64 format\n\n",hapName); + LOG0("\n*** WARNING: Can't decode Characteristic::%s with getData(). Data is not in base-64 format!\n\n",hapName); return(olen); } @@ -735,7 +735,7 @@ class SpanCharacteristic{ setValFinish(notify); } - size_t getTLV(TLV8 &tlv){ + size_t getTLVGeneric(TLV8 &tlv, UVal &val){ if(format tBuf(bufSize); // create fixed-size buffer to store decoded bytes tlv.wipe(); // clear TLV completely - size_t nChars=strlen(value.STRING); // total characters to decode - uint8_t *p=(uint8_t *)value.STRING; // set pointer to beginning of value - const size_t decodeSize=bufSize/3*4; // number of characters to decode in each pass + size_t nChars=strlen(val.STRING); // total characters to decode + uint8_t *p=(uint8_t *)val.STRING; // set pointer to beginning of value + const size_t decodeSize=bufSize/3*4; // number of characters to decode in each pass int status=0; while(nChars>0){ @@ -755,7 +755,7 @@ class SpanCharacteristic{ int ret=mbedtls_base64_decode(tBuf,tBuf.len(),&olen,p,n); if(ret==MBEDTLS_ERR_BASE64_INVALID_CHARACTER){ - LOG0("\n*** WARNING: Can't decode Characteristic::%s with getTLV(). Data is not in base-64 format\n\n",hapName); + LOG0("\n*** WARNING: Can't decode Characteristic::%s with getTLV(). Data is not in base-64 format!\n\n",hapName); tlv.wipe(); return(0); } @@ -764,13 +764,16 @@ class SpanCharacteristic{ nChars-=n; } if(status>0){ - LOG0("\n*** WARNING: Can't unpack Characteristic::%s with getTLV(). TLV record is incomplete or corrupted\n\n",hapName); + LOG0("\n*** WARNING: Can't unpack Characteristic::%s with getTLV(). TLV record is incomplete or corrupted!\n\n",hapName); tlv.wipe(); return(0); } return(tlv.pack_size()); } + size_t getTLV(TLV8 &tlv){return(getTLVGeneric(tlv,value));} + size_t getNewTLV(TLV8 &tlv){return(getTLVGeneric(tlv,newValue));} + void setTLV(TLV8 &tlv, boolean notify=true){ setValCheck(); From 3d4b02e492406dc237bfe99a326b8d0415a53249 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 31 Mar 2024 21:40:00 -0500 Subject: [PATCH 039/154] created getDataGeneric() and getStringGeneric() Though this simplifies the code, the code size is still the same - compiler must have already optimized these functions. --- src/HomeSpan.h | 35 ++++++++--------------------------- src/src.ino | 1 + 2 files changed, 9 insertions(+), 27 deletions(-) diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 777b675..e0563a8 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -661,19 +661,15 @@ class SpanCharacteristic{ return(uvGet(newValue)); } - char *getString(){ + char *getStringGeneric(UVal &val){ if(format>=FORMAT::STRING) - return value.STRING; + return val.STRING; return NULL; } - char *getNewString(){ - if(format>=FORMAT::STRING) - return newValue.STRING; - - return NULL; - } + char *getString(){return(getStringGeneric(value));} + char *getNewString(){return(getStringGeneric(newValue));} void setString(const char *val, boolean notify=true){ @@ -682,12 +678,12 @@ class SpanCharacteristic{ setValFinish(notify); } - size_t getData(uint8_t *data, size_t len){ + size_t getDataGeneric(uint8_t *data, size_t len, UVal &val){ if(formattvname->setString("New Name"); + Serial.printf("Reset TV Name to '%s'\n",hsTV->tvname->getString()); } static void changeOrder(const char *buf, void *arg){ From 75cbf9715f8445b33dd7fb6f582bff2c754e30aa Mon Sep 17 00:00:00 2001 From: Gregg Date: Thu, 4 Apr 2024 21:12:13 -0500 Subject: [PATCH 040/154] added mechanism to strip unnecessary backslashes from Home App JSON Apple "escapes" forward slashes in JSON output, replacing '/' with '\/', which destroys base64 strings. --- src/HomeSpan.cpp | 2 +- src/Utils.cpp | 14 ++++++++++++++ src/Utils.h | 2 +- src/src.ino | 9 +++++---- 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 4afb96f..e8153bc 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -2007,7 +2007,7 @@ StatusCode SpanCharacteristic::loadUpdate(char *val, char *ev, boolean wr){ case STRING: case DATA: case TLV_ENC: - uvSet(newValue,(const char *)val); + uvSet(newValue,(const char *)stripBackslash(val)); break; default: diff --git a/src/Utils.cpp b/src/Utils.cpp index 0b2e46c..d3711f0 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -73,6 +73,20 @@ char *Utils::readSerial(char *c, int max){ ////////////////////////////////////// +char *Utils::stripBackslash(char *c){ + + size_t n=strlen(c); + char *p=c; + for(int i=0;i<=n;i++){ + *p=c[i]; + if(*p!='\\') + p++; + } + return(c); +} + +////////////////////////////////////// + String Utils::mask(char *c, int n){ String s=""; int len=strlen(c); diff --git a/src/Utils.h b/src/Utils.h index 255d602..26e3db9 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -35,7 +35,7 @@ namespace Utils { char *readSerial(char *c, int max); // read serial port into 'c' until , but storing only first 'max' characters (the rest are discarded) String mask(char *c, int n); // simply utility that creates a String from 'c' with all except the first and last 'n' characters replaced by '*' - +char *stripBackslash(char *c); // strips backslashes out of c (Apple unecessesarily "escapes" forward slashes in JSON) } ///////////////////////////////////////////////// diff --git a/src/src.ino b/src/src.ino index a2a4477..4f57226 100644 --- a/src/src.ino +++ b/src/src.ino @@ -49,7 +49,7 @@ struct HomeSpanTV : Service::Television { for(int i=0;i0) - orderTLV.add(0); + orderTLV.add(6); orderTLV.add(1,sizeof(uint32_t),(uint8_t*)(order+i)); } @@ -117,6 +117,7 @@ struct HomeSpanTV : Service::Television { HomeSpanTV *hsTV=(HomeSpanTV *)arg; hsTV->tvname->setString("New Name"); Serial.printf("Reset TV Name to '%s'\n",hsTV->tvname->getString()); + Serial.printf("Showing displayOrder '%s'\n",hsTV->displayOrder->getString()); } static void changeOrder(const char *buf, void *arg){ @@ -128,12 +129,12 @@ struct HomeSpanTV : Service::Television { orderTLV.print(); orderTLV.wipe(); - uint32_t order[]={12,10,6,2,1,9,11,3,18,5}; + uint8_t order[]={12,10,6,2,1,9,11,3,18,5}; - for(int i=0;i0) orderTLV.add(0); - orderTLV.add(1,sizeof(uint32_t),(uint8_t*)(order+i)); + orderTLV.add(1,sizeof(uint8_t),(uint8_t*)(order+i)); } Serial.printf("AFTER:\n"); From 7e2625034cb289d4263737ee4509340e2019f90e Mon Sep 17 00:00:00 2001 From: Gregg Date: Thu, 4 Apr 2024 21:31:28 -0500 Subject: [PATCH 041/154] Added TLV8_it add(uint8_t tag, TLV8 &subTLV) Allows easy-add of a sub TLV to an existing TLV8 --- src/TLV8.cpp | 8 ++++++++ src/TLV8.h | 1 + 2 files changed, 9 insertions(+) diff --git a/src/TLV8.cpp b/src/TLV8.cpp index ff766f7..a486136 100644 --- a/src/TLV8.cpp +++ b/src/TLV8.cpp @@ -80,6 +80,14 @@ TLV8_it TLV8::add(uint8_t tag, size_t len, const uint8_t* val){ ///////////////////////////////////// +TLV8_it TLV8::add(uint8_t tag, TLV8 &subTLV){ + auto it=add(tag,subTLV.pack_size(),NULL); // create space for inserting sub TLV and store iterator to new element + subTLV.pack(*it); // pack subTLV into new element + return(--end()); +} + +///////////////////////////////////// + TLV8_it TLV8::find(uint8_t tag, TLV8_it it1, TLV8_it it2){ auto it=it1; diff --git a/src/TLV8.h b/src/TLV8.h index 1ca1ea3..5985e4d 100644 --- a/src/TLV8.h +++ b/src/TLV8.h @@ -80,6 +80,7 @@ class TLV8 : public std::list> { TLV8_it add(uint8_t tag, size_t len, const uint8_t *val); TLV8_it add(uint8_t tag, uint8_t val){return(add(tag, 1, &val));} TLV8_it add(uint8_t tag){return(add(tag, 0, NULL));} + TLV8_it add(uint8_t tag, TLV8 &subTLV); TLV8_it find(uint8_t tag, TLV8_it it1, TLV8_it it2); TLV8_it find(uint8_t tag, TLV8_it it1){return(find(tag, it1, end()));} From acebaf6caab6f88a6fc03638a99043ca1cb1d5e5 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 6 Apr 2024 21:45:49 -0500 Subject: [PATCH 042/154] Add ability to unpack TLV8 by iterator Also fixed bug using increment operator (++) by replacing with std::next() --- src/TLV8.cpp | 9 +++++++++ src/TLV8.h | 9 +++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/TLV8.cpp b/src/TLV8.cpp index a486136..2766490 100644 --- a/src/TLV8.cpp +++ b/src/TLV8.cpp @@ -209,6 +209,15 @@ int TLV8::unpack(uint8_t *buf, size_t bufSize){ return(unpackPhase); } +///////////////////////////////////// + +int TLV8::unpack(TLV8_it it){ + + if(it==end()) + return(0); + + return(unpack(*it,(*it).len)); +} ///////////////////////////////////// diff --git a/src/TLV8.h b/src/TLV8.h index 5985e4d..5240ee6 100644 --- a/src/TLV8.h +++ b/src/TLV8.h @@ -92,7 +92,7 @@ class TLV8 : public std::list> { size_t pack_size(){return(pack_size(begin(), end()));} void pack_init(TLV8_it it1, TLV8_it it2){currentPackIt=it1; endPackIt=it2; currentPackPhase=0;} - void pack_init(TLV8_it it1){pack_init(it1, it1++);} + void pack_init(TLV8_it it1){pack_init(it1, std::next(it1));} void pack_init(){pack_init(begin(),end());} size_t pack(uint8_t *buf, size_t bufSize); @@ -101,15 +101,16 @@ class TLV8 : public std::list> { const char *getName(uint8_t tag); void print(TLV8_it it1, TLV8_it it2); - void print(TLV8_it it1){print(it1, it1++);} + void print(TLV8_it it1){print(it1, std::next(it1));} void print(){print(begin(), end());} void osprint(std::ostream& os, TLV8_it it1, TLV8_it it2); - void osprint(std::ostream& os, TLV8_it it1){osprint(os, it1, it1++);} + void osprint(std::ostream& os, TLV8_it it1){osprint(os, it1, std::next(it1));} void osprint(std::ostream& os){osprint(os, begin(), end());} int unpack(uint8_t *buf, size_t bufSize); - + int unpack(TLV8_it it); + void wipe(){std::list>().swap(*this);} }; From d40d70964319a4a58cd3323efbdcf0b145054cc4 Mon Sep 17 00:00:00 2001 From: Gregg Date: Mon, 8 Apr 2024 21:33:27 -0500 Subject: [PATCH 043/154] Add TLV8 add() methods for uint64_t and char*, as well as new getVal() template for returning an integer --- src/TLV8.cpp | 18 ++++++++++++++++++ src/TLV8.h | 14 +++++++++++--- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/TLV8.cpp b/src/TLV8.cpp index 2766490..ffbe10b 100644 --- a/src/TLV8.cpp +++ b/src/TLV8.cpp @@ -81,6 +81,7 @@ TLV8_it TLV8::add(uint8_t tag, size_t len, const uint8_t* val){ ///////////////////////////////////// TLV8_it TLV8::add(uint8_t tag, TLV8 &subTLV){ + auto it=add(tag,subTLV.pack_size(),NULL); // create space for inserting sub TLV and store iterator to new element subTLV.pack(*it); // pack subTLV into new element return(--end()); @@ -88,6 +89,17 @@ TLV8_it TLV8::add(uint8_t tag, TLV8 &subTLV){ ///////////////////////////////////// +TLV8_it TLV8::add(uint8_t tag, uint64_t val){ + + uint8_t *p=reinterpret_cast(&val); + size_t nBytes=sizeof(uint64_t); + while(nBytes>1 && p[nBytes-1]==0) // TLV requires little endian without any trailing zero bytes (i.e. only use what is needed to fully represent the value) + nBytes--; + return(add(tag, nBytes, p)); +} + +///////////////////////////////////// + TLV8_it TLV8::find(uint8_t tag, TLV8_it it1, TLV8_it it2){ auto it=it1; @@ -247,6 +259,12 @@ void TLV8::print(TLV8_it it1, TLV8_it it2){ Serial.printf("(%d) ",(*it1).len); for(int i=0;i<(*it1).len;i++) Serial.printf("%02X",(*it1).val.get()[i]); + if((*it1).len==0) + Serial.printf(" (null)"); + else if((*it1).len<=4) + Serial.printf(" (%u)",(*it1).getVal()); + else if((*it1).len<=8) + Serial.printf(" (%llu)",(*it1).getVal()); Serial.printf("\n"); it1++; } diff --git a/src/TLV8.h b/src/TLV8.h index 5240ee6..aca36c4 100644 --- a/src/TLV8.h +++ b/src/TLV8.h @@ -42,10 +42,17 @@ struct tlv8_t { tlv8_t(uint8_t tag, size_t len, const uint8_t* val); void update(size_t addLen, const uint8_t *addVal); void osprint(std::ostream& os); - + operator uint8_t*() const{ return(val.get()); } + + template T getVal(){ + T iVal=0; + for(int i=0;i(val.get()[i])<<(i*8); + return(iVal); + } }; @@ -78,9 +85,10 @@ class TLV8 : public std::list> { TLV8(const TLV8_names *names, int nNames) : names{names}, nNames{nNames} {}; TLV8_it add(uint8_t tag, size_t len, const uint8_t *val); - TLV8_it add(uint8_t tag, uint8_t val){return(add(tag, 1, &val));} - TLV8_it add(uint8_t tag){return(add(tag, 0, NULL));} + TLV8_it add(uint8_t tag, uint64_t val); TLV8_it add(uint8_t tag, TLV8 &subTLV); + TLV8_it add(uint8_t tag){return(add(tag, 0, NULL));} + TLV8_it add(uint8_t tag, const char *val){return(add(tag, strlen(val), reinterpret_cast(val)));} TLV8_it find(uint8_t tag, TLV8_it it1, TLV8_it it2); TLV8_it find(uint8_t tag, TLV8_it it1){return(find(tag, it1, end()));} From 5ded77d6d98a2c6fef5f60072ae4bbd4018e3103 Mon Sep 17 00:00:00 2001 From: Gregg Date: Mon, 8 Apr 2024 21:52:51 -0500 Subject: [PATCH 044/154] Replace TLV8 (*it)[0] with new TLV8 getVal() method in HAP.cpp --- src/HAP.cpp | 12 ++++++------ src/TLV8.cpp | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index a036ba8..69720de 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -334,7 +334,7 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ return(0); } - int tlvState=(*itState)[0]; + int tlvState=(*itState).getVal(); if(nAdminControllers()){ // error: Device already paired (i.e. there is at least one admin Controller). We should not be receiving any requests for Pair-Setup! LOG0("\n*** ERROR: Device already paired!\n\n"); @@ -363,7 +363,7 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ auto itMethod=iosTLV.find(kTLVType_Method); - if(iosTLV.len(itMethod)!=1 || (*itMethod)[0]!=0){ // error: "Pair Setup" method must always be 0 to indicate setup without MiFi Authentification (HAP Table 5-3) + if(iosTLV.len(itMethod)!=1 || (*itMethod).getVal()!=0){ // error: "Pair Setup" method must always be 0 to indicate setup without MiFi Authentification (HAP Table 5-3) LOG0("\n*** ERROR: Pair 'Method' missing or not set to 0\n\n"); responseTLV.add(kTLVType_Error,tagError_Unavailable); // set Error=Unavailable tlvRespond(responseTLV); // send response to client @@ -585,7 +585,7 @@ int HAPClient::postPairVerifyURL(uint8_t *content, size_t len){ return(0); } - int tlvState=(*itState)[0]; + int tlvState=(*itState).getVal(); if(!nAdminControllers()){ // error: Device not yet paired - we should not be receiving any requests for Pair-Verify! LOG0("\n*** ERROR: Device not yet paired!\n\n"); @@ -771,7 +771,7 @@ int HAPClient::postPairingsURL(uint8_t *content, size_t len){ auto itState=iosTLV.find(kTLVType_State); auto itMethod=iosTLV.find(kTLVType_Method); - if(iosTLV.len(itState)!=1 || (*itState)[0]!=1){ // missing STATE TLV + if(iosTLV.len(itState)!=1 || (*itState).getVal()!=1){ // missing STATE TLV LOG0("\n*** ERROR: Parirings 'State' is either missing or not set to \n\n"); badRequestError(); // return with 400 error, which closes connection return(0); @@ -783,7 +783,7 @@ int HAPClient::postPairingsURL(uint8_t *content, size_t len){ return(0); } - int tlvMethod=(*itMethod)[0]; + int tlvMethod=(*itMethod).getVal(); responseTLV.add(kTLVType_State,pairState_M2); // all responses include State=M2 @@ -810,7 +810,7 @@ int HAPClient::postPairingsURL(uint8_t *content, size_t len){ return(0); } - tagError err=addController(*itIdentifier,*itPublicKey,(*itPermissions)[0]); + tagError err=addController(*itIdentifier,*itPublicKey,(*itPermissions).getVal()); if(err!=tagError_None) responseTLV.add(kTLVType_Error,err); diff --git a/src/TLV8.cpp b/src/TLV8.cpp index ffbe10b..51689f8 100644 --- a/src/TLV8.cpp +++ b/src/TLV8.cpp @@ -260,11 +260,11 @@ void TLV8::print(TLV8_it it1, TLV8_it it2){ for(int i=0;i<(*it1).len;i++) Serial.printf("%02X",(*it1).val.get()[i]); if((*it1).len==0) - Serial.printf(" (null)"); + Serial.printf(" [null]"); else if((*it1).len<=4) - Serial.printf(" (%u)",(*it1).getVal()); + Serial.printf(" [%u]",(*it1).getVal()); else if((*it1).len<=8) - Serial.printf(" (%llu)",(*it1).getVal()); + Serial.printf(" [%llu]",(*it1).getVal()); Serial.printf("\n"); it1++; } From 7a50479bacd97483f22799b06c6206fcd0bba05b Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 13 Apr 2024 19:12:26 -0500 Subject: [PATCH 045/154] Changed TLV:add() so it returns TLV8 instead of iterator. Makes it easier to chain .add() functions as well as dynamically create sub TLVs. Changing TLV8::add() in this fashion also required updating various use cases of TLV::add() in HAP.cpp where add() was used to reserve space for a blank TLV element. All changes have been tested. --- src/HAP.cpp | 32 +++++++++++++++----------------- src/TLV8.cpp | 15 +++++++-------- src/TLV8.h | 10 +++++----- 3 files changed, 27 insertions(+), 30 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index 69720de..a4aa4c0 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -370,7 +370,6 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ return(0); }; - auto itPublicKey=responseTLV.add(kTLVType_PublicKey,384,NULL); // create blank PublicKey TLV with space for 384 bytes if(srp==NULL) // create instance of SRP (if not already created) to persist until Pairing-Setup M5 completes srp=new SRP6A; @@ -381,7 +380,8 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ responseTLV.add(kTLVType_Salt,16,verifyData.get()->salt); // write Salt from verification data into TLV - srp->createPublicKey(verifyData,*itPublicKey); // create accessory Public Key from stored verification data and write result into PublicKey TLV + responseTLV.add(kTLVType_PublicKey,384,NULL); // create blank PublicKey TLV with space for 384 bytes + srp->createPublicKey(verifyData,responseTLV.back()); // create accessory Public Key from stored verification data and write result into PublicKey TLV tlvRespond(responseTLV); // send response to client pairStatus=pairState_M3; // set next expected pair-state request from client @@ -414,9 +414,8 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ return(0); }; - auto itAccProof=responseTLV.add(kTLVType_Proof,64,NULL); // create blank accessory Proof TLV with space for 64 bytes - - srp->createAccProof(*itAccProof); // M1 has been successully verified; now create accessory Proof M2 + responseTLV.add(kTLVType_Proof,64,NULL); // create blank accessory Proof TLV with space for 64 bytes + srp->createAccProof(responseTLV.back()); // M1 has been successully verified; now create accessory Proof M2 tlvRespond(responseTLV); // send response to client pairStatus=pairState_M5; // set next expected pair-state request from client @@ -513,14 +512,13 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ TempBuffer accessoryInfo(accessoryX,accessoryX.len(),accessory.ID,hap_accessory_IDBYTES,accessory.LTPK,crypto_sign_PUBLICKEYBYTES,NULL); - subTLV.clear(); // clear existing SUBTLV records + subTLV.clear(); // clear existing SUBTLV records - itSignature=subTLV.add(kTLVType_Signature,64,NULL); // create blank Signature TLV with space for 64 bytes + subTLV.add(kTLVType_Signature,64,NULL); // create blank Signature TLV with space for 64 bytes + crypto_sign_detached(subTLV.back(),NULL,accessoryInfo,accessoryInfo.len(),accessory.LTSK); // produce signature of accessoryInfo using AccessoryLTSK (Ed25519 long-term secret key) - crypto_sign_detached(*itSignature,NULL,accessoryInfo,accessoryInfo.len(),accessory.LTSK); // produce signature of accessoryInfo using AccessoryLTSK (Ed25519 long-term secret key) - - subTLV.add(kTLVType_Identifier,hap_accessory_IDBYTES,accessory.ID); // set Identifier TLV record as accessoryPairingID - subTLV.add(kTLVType_PublicKey,crypto_sign_PUBLICKEYBYTES,accessory.LTPK); // set PublicKey TLV record as accessoryLTPK + subTLV.add(kTLVType_Identifier,hap_accessory_IDBYTES,accessory.ID); // set Identifier TLV record as accessoryPairingID + subTLV.add(kTLVType_PublicKey,crypto_sign_PUBLICKEYBYTES,accessory.LTPK); // set PublicKey TLV record as accessoryLTPK LOG2("------- ENCRYPTING SUB-TLVS -------\n"); @@ -532,9 +530,9 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ // Encrypt the subTLV data using the same SRP Session Key as above with ChaCha20-Poly1305 - itEncryptedData=responseTLV.add(kTLVType_EncryptedData,subPack.len()+crypto_aead_chacha20poly1305_IETF_ABYTES,NULL); //create blank EncryptedData TLV with space for subTLV + Authentication Tag + responseTLV.add(kTLVType_EncryptedData,subPack.len()+crypto_aead_chacha20poly1305_IETF_ABYTES,NULL); //create blank EncryptedData TLV with space for subTLV + Authentication Tag - crypto_aead_chacha20poly1305_ietf_encrypt(*itEncryptedData,NULL,subPack,subPack.len(),NULL,0,NULL,(unsigned char *)"\x00\x00\x00\x00PS-Msg06",sessionKey); + crypto_aead_chacha20poly1305_ietf_encrypt(responseTLV.back(),NULL,subPack,subPack.len(),NULL,0,NULL,(unsigned char *)"\x00\x00\x00\x00PS-Msg06",sessionKey); LOG2("---------- END SUB-TLVS! ----------\n"); @@ -623,8 +621,8 @@ int HAPClient::postPairVerifyURL(uint8_t *content, size_t len){ TempBuffer accessoryInfo(publicCurveKey,crypto_box_PUBLICKEYBYTES,accessory.ID,hap_accessory_IDBYTES,iosCurveKey,crypto_box_PUBLICKEYBYTES,NULL); subTLV.add(kTLVType_Identifier,hap_accessory_IDBYTES,accessory.ID); // set Identifier subTLV record as Accessory's Pairing ID - auto itSignature=subTLV.add(kTLVType_Signature,crypto_sign_BYTES,NULL); // create blank Signature subTLV - crypto_sign_detached(*itSignature,NULL,accessoryInfo,accessoryInfo.len(),accessory.LTSK); // produce Signature of accessoryInfo using Accessory's LTSK + subTLV.add(kTLVType_Signature,crypto_sign_BYTES,NULL); // create blank Signature subTLV + crypto_sign_detached(subTLV.back(),NULL,accessoryInfo,accessoryInfo.len(),accessory.LTSK); // produce Signature of accessoryInfo using Accessory's LTSK LOG2("------- ENCRYPTING SUB-TLVS -------\n"); @@ -640,8 +638,8 @@ int HAPClient::postPairVerifyURL(uint8_t *content, size_t len){ sessionKey=(uint8_t *)HS_MALLOC(crypto_box_PUBLICKEYBYTES); // temporary space - will be deleted at end of verification process HKDF::create(sessionKey,sharedCurveKey,crypto_box_PUBLICKEYBYTES,"Pair-Verify-Encrypt-Salt","Pair-Verify-Encrypt-Info"); // create Session Curve25519 Key from Shared-Secret Curve25519 Key using HKDF-SHA-512 - auto itEncryptedData=responseTLV.add(kTLVType_EncryptedData,subPack.len()+crypto_aead_chacha20poly1305_IETF_ABYTES,NULL); // create blank EncryptedData subTLV - crypto_aead_chacha20poly1305_ietf_encrypt(*itEncryptedData,NULL,subPack,subPack.len(),NULL,0,NULL,(unsigned char *)"\x00\x00\x00\x00PV-Msg02",sessionKey); // encrypt data with Session Curve25519 Key and padded nonce="PV-Msg02" + responseTLV.add(kTLVType_EncryptedData,subPack.len()+crypto_aead_chacha20poly1305_IETF_ABYTES,NULL); // create blank EncryptedData subTLV + crypto_aead_chacha20poly1305_ietf_encrypt(responseTLV.back(),NULL,subPack,subPack.len(),NULL,0,NULL,(unsigned char *)"\x00\x00\x00\x00PV-Msg02",sessionKey); // encrypt data with Session Curve25519 Key and padded nonce="PV-Msg02" LOG2("---------- END SUB-TLVS! ----------\n"); diff --git a/src/TLV8.cpp b/src/TLV8.cpp index 51689f8..7020af0 100644 --- a/src/TLV8.cpp +++ b/src/TLV8.cpp @@ -68,28 +68,27 @@ void tlv8_t::osprint(std::ostream& os){ ///////////////////////////////////// -TLV8_it TLV8::add(uint8_t tag, size_t len, const uint8_t* val){ +TLV8 &TLV8::add(uint8_t tag, size_t len, const uint8_t* val){ if(!empty() && back().tag==tag) back().update(len,val); else emplace_back(tag,len,val); - return(--end()); + return(*this); } ///////////////////////////////////// -TLV8_it TLV8::add(uint8_t tag, TLV8 &subTLV){ +TLV8 &TLV8::add(uint8_t tag, TLV8 &subTLV){ - auto it=add(tag,subTLV.pack_size(),NULL); // create space for inserting sub TLV and store iterator to new element - subTLV.pack(*it); // pack subTLV into new element - return(--end()); + subTLV.pack(add(tag,subTLV.pack_size(),NULL).back()); // add new blank element of sufficient size and pack subTLV into this new element + return(*this); } ///////////////////////////////////// -TLV8_it TLV8::add(uint8_t tag, uint64_t val){ +TLV8 &TLV8::add(uint8_t tag, uint64_t val){ uint8_t *p=reinterpret_cast(&val); size_t nBytes=sizeof(uint64_t); @@ -105,7 +104,7 @@ TLV8_it TLV8::find(uint8_t tag, TLV8_it it1, TLV8_it it2){ auto it=it1; while(it!=it2 && (*it).tag!=tag) it++; - return(it==it2?end():it); + return(it); } ///////////////////////////////////// diff --git a/src/TLV8.h b/src/TLV8.h index aca36c4..93b3e34 100644 --- a/src/TLV8.h +++ b/src/TLV8.h @@ -84,11 +84,11 @@ class TLV8 : public std::list> { TLV8(){}; TLV8(const TLV8_names *names, int nNames) : names{names}, nNames{nNames} {}; - TLV8_it add(uint8_t tag, size_t len, const uint8_t *val); - TLV8_it add(uint8_t tag, uint64_t val); - TLV8_it add(uint8_t tag, TLV8 &subTLV); - TLV8_it add(uint8_t tag){return(add(tag, 0, NULL));} - TLV8_it add(uint8_t tag, const char *val){return(add(tag, strlen(val), reinterpret_cast(val)));} + TLV8 & add(uint8_t tag, size_t len, const uint8_t *val); + TLV8 & add(uint8_t tag, uint64_t val); + TLV8 & add(uint8_t tag, TLV8 &subTLV); + TLV8 & add(uint8_t tag){return(add(tag, 0, NULL));} + TLV8 & add(uint8_t tag, const char *val){return(add(tag, strlen(val), reinterpret_cast(val)));} TLV8_it find(uint8_t tag, TLV8_it it1, TLV8_it it2); TLV8_it find(uint8_t tag, TLV8_it it1){return(find(tag, it1, end()));} From 28990d6ed674c32f5bf2dc878eb64056912105b7 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 13 Apr 2024 22:00:37 -0500 Subject: [PATCH 046/154] Revert "Changed TLV:add() so it returns TLV8 instead of iterator." This reverts commit 7a50479bacd97483f22799b06c6206fcd0bba05b. --- src/HAP.cpp | 32 +++++++++++++++++--------------- src/TLV8.cpp | 15 ++++++++------- src/TLV8.h | 10 +++++----- 3 files changed, 30 insertions(+), 27 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index a4aa4c0..69720de 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -370,6 +370,7 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ return(0); }; + auto itPublicKey=responseTLV.add(kTLVType_PublicKey,384,NULL); // create blank PublicKey TLV with space for 384 bytes if(srp==NULL) // create instance of SRP (if not already created) to persist until Pairing-Setup M5 completes srp=new SRP6A; @@ -380,8 +381,7 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ responseTLV.add(kTLVType_Salt,16,verifyData.get()->salt); // write Salt from verification data into TLV - responseTLV.add(kTLVType_PublicKey,384,NULL); // create blank PublicKey TLV with space for 384 bytes - srp->createPublicKey(verifyData,responseTLV.back()); // create accessory Public Key from stored verification data and write result into PublicKey TLV + srp->createPublicKey(verifyData,*itPublicKey); // create accessory Public Key from stored verification data and write result into PublicKey TLV tlvRespond(responseTLV); // send response to client pairStatus=pairState_M3; // set next expected pair-state request from client @@ -414,8 +414,9 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ return(0); }; - responseTLV.add(kTLVType_Proof,64,NULL); // create blank accessory Proof TLV with space for 64 bytes - srp->createAccProof(responseTLV.back()); // M1 has been successully verified; now create accessory Proof M2 + auto itAccProof=responseTLV.add(kTLVType_Proof,64,NULL); // create blank accessory Proof TLV with space for 64 bytes + + srp->createAccProof(*itAccProof); // M1 has been successully verified; now create accessory Proof M2 tlvRespond(responseTLV); // send response to client pairStatus=pairState_M5; // set next expected pair-state request from client @@ -512,13 +513,14 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ TempBuffer accessoryInfo(accessoryX,accessoryX.len(),accessory.ID,hap_accessory_IDBYTES,accessory.LTPK,crypto_sign_PUBLICKEYBYTES,NULL); - subTLV.clear(); // clear existing SUBTLV records + subTLV.clear(); // clear existing SUBTLV records - subTLV.add(kTLVType_Signature,64,NULL); // create blank Signature TLV with space for 64 bytes - crypto_sign_detached(subTLV.back(),NULL,accessoryInfo,accessoryInfo.len(),accessory.LTSK); // produce signature of accessoryInfo using AccessoryLTSK (Ed25519 long-term secret key) + itSignature=subTLV.add(kTLVType_Signature,64,NULL); // create blank Signature TLV with space for 64 bytes - subTLV.add(kTLVType_Identifier,hap_accessory_IDBYTES,accessory.ID); // set Identifier TLV record as accessoryPairingID - subTLV.add(kTLVType_PublicKey,crypto_sign_PUBLICKEYBYTES,accessory.LTPK); // set PublicKey TLV record as accessoryLTPK + crypto_sign_detached(*itSignature,NULL,accessoryInfo,accessoryInfo.len(),accessory.LTSK); // produce signature of accessoryInfo using AccessoryLTSK (Ed25519 long-term secret key) + + subTLV.add(kTLVType_Identifier,hap_accessory_IDBYTES,accessory.ID); // set Identifier TLV record as accessoryPairingID + subTLV.add(kTLVType_PublicKey,crypto_sign_PUBLICKEYBYTES,accessory.LTPK); // set PublicKey TLV record as accessoryLTPK LOG2("------- ENCRYPTING SUB-TLVS -------\n"); @@ -530,9 +532,9 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ // Encrypt the subTLV data using the same SRP Session Key as above with ChaCha20-Poly1305 - responseTLV.add(kTLVType_EncryptedData,subPack.len()+crypto_aead_chacha20poly1305_IETF_ABYTES,NULL); //create blank EncryptedData TLV with space for subTLV + Authentication Tag + itEncryptedData=responseTLV.add(kTLVType_EncryptedData,subPack.len()+crypto_aead_chacha20poly1305_IETF_ABYTES,NULL); //create blank EncryptedData TLV with space for subTLV + Authentication Tag - crypto_aead_chacha20poly1305_ietf_encrypt(responseTLV.back(),NULL,subPack,subPack.len(),NULL,0,NULL,(unsigned char *)"\x00\x00\x00\x00PS-Msg06",sessionKey); + crypto_aead_chacha20poly1305_ietf_encrypt(*itEncryptedData,NULL,subPack,subPack.len(),NULL,0,NULL,(unsigned char *)"\x00\x00\x00\x00PS-Msg06",sessionKey); LOG2("---------- END SUB-TLVS! ----------\n"); @@ -621,8 +623,8 @@ int HAPClient::postPairVerifyURL(uint8_t *content, size_t len){ TempBuffer accessoryInfo(publicCurveKey,crypto_box_PUBLICKEYBYTES,accessory.ID,hap_accessory_IDBYTES,iosCurveKey,crypto_box_PUBLICKEYBYTES,NULL); subTLV.add(kTLVType_Identifier,hap_accessory_IDBYTES,accessory.ID); // set Identifier subTLV record as Accessory's Pairing ID - subTLV.add(kTLVType_Signature,crypto_sign_BYTES,NULL); // create blank Signature subTLV - crypto_sign_detached(subTLV.back(),NULL,accessoryInfo,accessoryInfo.len(),accessory.LTSK); // produce Signature of accessoryInfo using Accessory's LTSK + auto itSignature=subTLV.add(kTLVType_Signature,crypto_sign_BYTES,NULL); // create blank Signature subTLV + crypto_sign_detached(*itSignature,NULL,accessoryInfo,accessoryInfo.len(),accessory.LTSK); // produce Signature of accessoryInfo using Accessory's LTSK LOG2("------- ENCRYPTING SUB-TLVS -------\n"); @@ -638,8 +640,8 @@ int HAPClient::postPairVerifyURL(uint8_t *content, size_t len){ sessionKey=(uint8_t *)HS_MALLOC(crypto_box_PUBLICKEYBYTES); // temporary space - will be deleted at end of verification process HKDF::create(sessionKey,sharedCurveKey,crypto_box_PUBLICKEYBYTES,"Pair-Verify-Encrypt-Salt","Pair-Verify-Encrypt-Info"); // create Session Curve25519 Key from Shared-Secret Curve25519 Key using HKDF-SHA-512 - responseTLV.add(kTLVType_EncryptedData,subPack.len()+crypto_aead_chacha20poly1305_IETF_ABYTES,NULL); // create blank EncryptedData subTLV - crypto_aead_chacha20poly1305_ietf_encrypt(responseTLV.back(),NULL,subPack,subPack.len(),NULL,0,NULL,(unsigned char *)"\x00\x00\x00\x00PV-Msg02",sessionKey); // encrypt data with Session Curve25519 Key and padded nonce="PV-Msg02" + auto itEncryptedData=responseTLV.add(kTLVType_EncryptedData,subPack.len()+crypto_aead_chacha20poly1305_IETF_ABYTES,NULL); // create blank EncryptedData subTLV + crypto_aead_chacha20poly1305_ietf_encrypt(*itEncryptedData,NULL,subPack,subPack.len(),NULL,0,NULL,(unsigned char *)"\x00\x00\x00\x00PV-Msg02",sessionKey); // encrypt data with Session Curve25519 Key and padded nonce="PV-Msg02" LOG2("---------- END SUB-TLVS! ----------\n"); diff --git a/src/TLV8.cpp b/src/TLV8.cpp index 7020af0..51689f8 100644 --- a/src/TLV8.cpp +++ b/src/TLV8.cpp @@ -68,27 +68,28 @@ void tlv8_t::osprint(std::ostream& os){ ///////////////////////////////////// -TLV8 &TLV8::add(uint8_t tag, size_t len, const uint8_t* val){ +TLV8_it TLV8::add(uint8_t tag, size_t len, const uint8_t* val){ if(!empty() && back().tag==tag) back().update(len,val); else emplace_back(tag,len,val); - return(*this); + return(--end()); } ///////////////////////////////////// -TLV8 &TLV8::add(uint8_t tag, TLV8 &subTLV){ +TLV8_it TLV8::add(uint8_t tag, TLV8 &subTLV){ - subTLV.pack(add(tag,subTLV.pack_size(),NULL).back()); // add new blank element of sufficient size and pack subTLV into this new element - return(*this); + auto it=add(tag,subTLV.pack_size(),NULL); // create space for inserting sub TLV and store iterator to new element + subTLV.pack(*it); // pack subTLV into new element + return(--end()); } ///////////////////////////////////// -TLV8 &TLV8::add(uint8_t tag, uint64_t val){ +TLV8_it TLV8::add(uint8_t tag, uint64_t val){ uint8_t *p=reinterpret_cast(&val); size_t nBytes=sizeof(uint64_t); @@ -104,7 +105,7 @@ TLV8_it TLV8::find(uint8_t tag, TLV8_it it1, TLV8_it it2){ auto it=it1; while(it!=it2 && (*it).tag!=tag) it++; - return(it); + return(it==it2?end():it); } ///////////////////////////////////// diff --git a/src/TLV8.h b/src/TLV8.h index 93b3e34..aca36c4 100644 --- a/src/TLV8.h +++ b/src/TLV8.h @@ -84,11 +84,11 @@ class TLV8 : public std::list> { TLV8(){}; TLV8(const TLV8_names *names, int nNames) : names{names}, nNames{nNames} {}; - TLV8 & add(uint8_t tag, size_t len, const uint8_t *val); - TLV8 & add(uint8_t tag, uint64_t val); - TLV8 & add(uint8_t tag, TLV8 &subTLV); - TLV8 & add(uint8_t tag){return(add(tag, 0, NULL));} - TLV8 & add(uint8_t tag, const char *val){return(add(tag, strlen(val), reinterpret_cast(val)));} + TLV8_it add(uint8_t tag, size_t len, const uint8_t *val); + TLV8_it add(uint8_t tag, uint64_t val); + TLV8_it add(uint8_t tag, TLV8 &subTLV); + TLV8_it add(uint8_t tag){return(add(tag, 0, NULL));} + TLV8_it add(uint8_t tag, const char *val){return(add(tag, strlen(val), reinterpret_cast(val)));} TLV8_it find(uint8_t tag, TLV8_it it1, TLV8_it it2); TLV8_it find(uint8_t tag, TLV8_it it1){return(find(tag, it1, end()));} From 48cab1f82bec6b01c8a171593da06399af1c48ed Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 13 Apr 2024 22:59:37 -0500 Subject: [PATCH 047/154] Changed tlv8_t from struct to class and made variables private Add getLen() and getTag() method to get length and tag of tlv8_t. Also overrode subscript operator [] so you can access any element of internal uint8_t array. Given previous additions (such as getVal()) there is now no reason to need to access the underlying std::unique_ptr directly. --- src/HAP.cpp | 12 ++++++------ src/TLV8.cpp | 36 ++++++++++++++++++------------------ src/TLV8.h | 21 +++++++++++++++++++-- 3 files changed, 43 insertions(+), 26 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index 69720de..e90d188 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -404,7 +404,7 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ return(0); }; - srp->createSessionKey(*itPublicKey,(*itPublicKey).len); // create session key, K, from client Public Key, A + srp->createSessionKey(*itPublicKey,(*itPublicKey).getLen()); // create session key, K, from client Public Key, A if(!srp->verifyClientProof(*itClientProof)){ // verify client Proof, M1 LOG0("\n*** ERROR: SRP Proof Verification Failed\n\n"); @@ -454,9 +454,9 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ // use SessionKey to decrypt encryptedData TLV with padded nonce="PS-Msg05" - TempBuffer decrypted((*itEncryptedData).len-crypto_aead_chacha20poly1305_IETF_ABYTES); // temporary storage for decrypted data + TempBuffer decrypted((*itEncryptedData).getLen()-crypto_aead_chacha20poly1305_IETF_ABYTES); // temporary storage for decrypted data - if(crypto_aead_chacha20poly1305_ietf_decrypt(decrypted, NULL, NULL, *itEncryptedData, (*itEncryptedData).len, NULL, 0, (unsigned char *)"\x00\x00\x00\x00PS-Msg05", sessionKey)==-1){ + if(crypto_aead_chacha20poly1305_ietf_decrypt(decrypted, NULL, NULL, *itEncryptedData, (*itEncryptedData).getLen(), NULL, 0, (unsigned char *)"\x00\x00\x00\x00PS-Msg05", sessionKey)==-1){ LOG0("\n*** ERROR: Exchange-Request Authentication Failed\n\n"); responseTLV.add(kTLVType_Error,tagError_Authentication); // set Error=Authentication tlvRespond(responseTLV); // send response to client @@ -492,7 +492,7 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ // Concatenate iosDeviceX, IOS ID, and IOS PublicKey into iosDeviceInfo - TempBuffer iosDeviceInfo(iosDeviceX,iosDeviceX.len(),(*itIdentifier).val.get(),(*itIdentifier).len,(*itPublicKey).val.get(),(*itPublicKey).len,NULL); + TempBuffer iosDeviceInfo(iosDeviceX,iosDeviceX.len(),(uint8_t *)(*itIdentifier),(*itIdentifier).getLen(),(uint8_t *)(*itPublicKey),(*itPublicKey).getLen(),NULL); if(crypto_sign_verify_detached(*itSignature, iosDeviceInfo, iosDeviceInfo.len(), *itPublicKey) != 0){ // verify signature of iosDeviceInfo using iosDeviceLTPK LOG0("\n*** ERROR: LPTK Signature Verification Failed\n\n"); @@ -668,9 +668,9 @@ int HAPClient::postPairVerifyURL(uint8_t *content, size_t len){ // use Session Curve25519 Key (from previous step) to decrypt encrypytedData TLV with padded nonce="PV-Msg03" - TempBuffer decrypted((*itEncryptedData).len-crypto_aead_chacha20poly1305_IETF_ABYTES); // temporary storage for decrypted data + TempBuffer decrypted((*itEncryptedData).getLen()-crypto_aead_chacha20poly1305_IETF_ABYTES); // temporary storage for decrypted data - if(crypto_aead_chacha20poly1305_ietf_decrypt(decrypted, NULL, NULL, *itEncryptedData, (*itEncryptedData).len, NULL, 0, (unsigned char *)"\x00\x00\x00\x00PV-Msg03", sessionKey)==-1){ + if(crypto_aead_chacha20poly1305_ietf_decrypt(decrypted, NULL, NULL, *itEncryptedData, (*itEncryptedData).getLen(), NULL, 0, (unsigned char *)"\x00\x00\x00\x00PV-Msg03", sessionKey)==-1){ LOG0("\n*** ERROR: Verify Authentication Failed\n\n"); responseTLV.add(kTLVType_State,pairState_M4); // set State= responseTLV.add(kTLVType_Error,tagError_Authentication); // set Error=Authentication diff --git a/src/TLV8.cpp b/src/TLV8.cpp index 51689f8..3156005 100644 --- a/src/TLV8.cpp +++ b/src/TLV8.cpp @@ -70,7 +70,7 @@ void tlv8_t::osprint(std::ostream& os){ TLV8_it TLV8::add(uint8_t tag, size_t len, const uint8_t* val){ - if(!empty() && back().tag==tag) + if(!empty() && back().getTag()==tag) back().update(len,val); else emplace_back(tag,len,val); @@ -103,9 +103,9 @@ TLV8_it TLV8::add(uint8_t tag, uint64_t val){ TLV8_it TLV8::find(uint8_t tag, TLV8_it it1, TLV8_it it2){ auto it=it1; - while(it!=it2 && (*it).tag!=tag) + while(it!=it2 && (*it).getTag()!=tag) it++; - return(it==it2?end():it); + return(it); } ///////////////////////////////////// @@ -115,9 +115,9 @@ size_t TLV8::pack_size(TLV8_it it1, TLV8_it it2){ size_t nBytes=0; while(it1!=it2){ - nBytes+=2+(*it1).len; - if((*it1).len>255) - nBytes+=2*(((*it1).len-1)/255); + nBytes+=2+(*it1).getLen(); + if((*it1).getLen()>255) + nBytes+=2*(((*it1).getLen()-1)/255); it1++; } @@ -134,13 +134,13 @@ size_t TLV8::pack(uint8_t *buf, size_t bufSize){ switch(currentPackPhase){ case 0: - currentPackBuf=(*currentPackIt).val.get(); - endPackBuf=(*currentPackIt).val.get()+(*currentPackIt).len; + currentPackBuf=*currentPackIt; + endPackBuf=(*currentPackIt)+(*currentPackIt).getLen(); currentPackPhase=1; break; case 1: - *buf++=(*currentPackIt).tag; + *buf++=(*currentPackIt).getTag(); nBytes++; currentPackPhase=2; break; @@ -228,7 +228,7 @@ int TLV8::unpack(TLV8_it it){ if(it==end()) return(0); - return(unpack(*it,(*it).len)); + return(unpack(*it,(*it).getLen())); } ///////////////////////////////////// @@ -251,19 +251,19 @@ const char *TLV8::getName(uint8_t tag){ void TLV8::print(TLV8_it it1, TLV8_it it2){ while(it1!=it2){ - const char *name=getName((*it1).tag); + const char *name=getName((*it1).getTag()); if(name) Serial.printf("%s",name); else - Serial.printf("%d",(*it1).tag); - Serial.printf("(%d) ",(*it1).len); - for(int i=0;i<(*it1).len;i++) - Serial.printf("%02X",(*it1).val.get()[i]); - if((*it1).len==0) + Serial.printf("%d",(*it1).getTag()); + Serial.printf("(%d) ",(*it1).getLen()); + for(int i=0;i<(*it1).getLen();i++) + Serial.printf("%02X",(*it1)[i]); + if((*it1).getLen()==0) Serial.printf(" [null]"); - else if((*it1).len<=4) + else if((*it1).getLen()<=4) Serial.printf(" [%u]",(*it1).getVal()); - else if((*it1).len<=8) + else if((*it1).getLen()<=8) Serial.printf(" [%llu]",(*it1).getVal()); Serial.printf("\n"); it1++; diff --git a/src/TLV8.h b/src/TLV8.h index aca36c4..f071b8a 100644 --- a/src/TLV8.h +++ b/src/TLV8.h @@ -34,11 +34,16 @@ #include "PSRAM.h" -struct tlv8_t { +class tlv8_t { + + private: + uint8_t tag; size_t len; std::unique_ptr val; + public: + tlv8_t(uint8_t tag, size_t len, const uint8_t* val); void update(size_t addLen, const uint8_t *addVal); void osprint(std::ostream& os); @@ -47,6 +52,18 @@ struct tlv8_t { return(val.get()); } + uint8_t & operator[](int index){ + return(val.get()[index]); + } + + size_t getLen(){ + return(len); + } + + uint8_t getTag(){ + return(tag); + } + template T getVal(){ T iVal=0; for(int i=0;i> { TLV8_it find(uint8_t tag, TLV8_it it1){return(find(tag, it1, end()));} TLV8_it find(uint8_t tag){return(find(tag, begin(), end()));} - int len(TLV8_it it){return(it==end()?-1:(*it).len);} + int len(TLV8_it it){return(it==end()?-1:(*it).getLen());} size_t pack_size(TLV8_it it1, TLV8_it it2); size_t pack_size(){return(pack_size(begin(), end()));} From e4df56293a85e2694f6bef38030a4e3d85c6d62c Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 14 Apr 2024 13:37:04 -0500 Subject: [PATCH 048/154] Added TLV8:printAll() Recursively prints an entire TLV and all embedded sub-TLVs based on a best-guess if the value of any TLV is a sub-TLV. --- src/TLV8.cpp | 14 ++++++++++++++ src/TLV8.h | 5 ++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/TLV8.cpp b/src/TLV8.cpp index 3156005..aae7686 100644 --- a/src/TLV8.cpp +++ b/src/TLV8.cpp @@ -272,6 +272,20 @@ void TLV8::print(TLV8_it it1, TLV8_it it2){ ////////////////////////////////////// +void TLV8::printAll_r(String label){ + + for(auto it=begin();it!=end();it++){ + Serial.printf("%s",label.c_str()); + print(it); + TLV8 tlv; + if(tlv.unpack(*it,(*it).getLen())==0) + tlv.printAll_r(label+String((*it).getTag())+"-"); + } + Serial.printf("%sDONE\n",label.c_str()); +} + +////////////////////////////////////// + void TLV8::osprint(std::ostream& os, TLV8_it it1, TLV8_it it2){ for(auto it=it1;it!=it2;it++) diff --git a/src/TLV8.h b/src/TLV8.h index f071b8a..2c08784 100644 --- a/src/TLV8.h +++ b/src/TLV8.h @@ -95,7 +95,9 @@ class TLV8 : public std::list> { const TLV8_names *names=NULL; int nNames=0; - + + void printAll_r(String label); + public: TLV8(){}; @@ -128,6 +130,7 @@ class TLV8 : public std::list> { void print(TLV8_it it1, TLV8_it it2); void print(TLV8_it it1){print(it1, std::next(it1));} void print(){print(begin(), end());} + void printAll(){printAll_r("");} void osprint(std::ostream& os, TLV8_it it1, TLV8_it it2); void osprint(std::ostream& os, TLV8_it it1){osprint(os, it1, std::next(it1));} From a7d57699a05cc0495e61fc86eb5d45f227e9ccec Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 14 Apr 2024 21:05:09 -0500 Subject: [PATCH 049/154] Added homeSpan.resetIID()... ...as well as SpanService.getIID() and SpanCharacteristic.getIID(). This provides control of IIDs used for each Service and Characteristic. By using this with adaptive lighting test, was able to verify that HomeKit can interpret TLV values of 1 byte, 2 bytes, and 4 bytes, but not 3 bytes. Suggests that TLV values need to be multiples of 2 (i.e. uint8, uint16, uint32). Will change TLV value-writing methodology so that padding zeros are used to round to 1, 2, 4, or 8 bytes. --- src/HomeSpan.cpp | 20 ++++++++++++++++++++ src/HomeSpan.h | 19 ++++++++++++------- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index e8153bc..b0bcbe7 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -1586,6 +1586,26 @@ boolean Span::printfAttributes(char **ids, int numIDs, int flags){ /////////////////////////////// +Span& Span::resetIID(int newIID){ + + if(Accessories.empty()){ + LOG0("\nFATAL ERROR! Can't reset the Accessory IID count without a defined Accessory ***\n"); + LOG0("\n=== PROGRAM HALTED ==="); + while(1); + } + + if(newIID<1){ + LOG0("\nFATAL ERROR! Can't reset the Accessory IID count to a value less than 1 ***\n"); + LOG0("\n=== PROGRAM HALTED ==="); + while(1); + } + + Accessories.back()->iidCount=newIID-1; + return(*this); +} + +/////////////////////////////// + boolean Span::updateDatabase(boolean updateMDNS){ printfAttributes(GET_META|GET_PERMS|GET_TYPE|GET_DESC); // stream attributes database, which automtically produces a SHA-384 hash diff --git a/src/HomeSpan.h b/src/HomeSpan.h index e0563a8..f22c55d 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -353,7 +353,8 @@ class Span{ Span& setStatusCallback(void (*f)(HS_STATUS status)){statusCallback=f;return(*this);} // sets an optional user-defined function to call when HomeSpan status changes const char* statusString(HS_STATUS s); // returns char string for HomeSpan status change messages Span& setPairingCode(const char *s, boolean progCall=true); // sets the Pairing Code - use is NOT recommended. Use 'S' from CLI instead - void deleteStoredValues(){processSerialCommand("V");} // deletes stored Characteristic values from NVS + void deleteStoredValues(){processSerialCommand("V");} // deletes stored Characteristic values from NVS + Span& resetIID(int newIID); // resets the IID count for the current Accessory to start at newIID int enableOTA(boolean auth=true, boolean safeLoad=true){return(spanOTA.init(auth, safeLoad, NULL));} // enables Over-the-Air updates, with (auth=true) or without (auth=false) authorization password int enableOTA(const char *pwd, boolean safeLoad=true){return(spanOTA.init(true, safeLoad, pwd));} // enables Over-the-Air updates, with custom authorization password (overrides any password stored with the 'O' command) @@ -452,12 +453,14 @@ class SpanService{ public: - void *operator new(size_t size){return(HS_MALLOC(size));} // override new operator to use PSRAM when available - 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 - 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 + void *operator new(size_t size){return(HS_MALLOC(size));} // override new operator to use PSRAM when available + 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 + 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 getIID(){return(iid);} virtual boolean update() {return(true);} // placeholder for code that is called when a Service is updated via a Controller. Must return true/false depending on success of update virtual void loop(){} // loops for each Service - called every cycle if over-ridden with user-defined code @@ -653,6 +656,8 @@ class SpanCharacteristic{ void *operator new(size_t size){return(HS_MALLOC(size));} // override new operator to use PSRAM when available SpanCharacteristic(HapChar *hapChar, boolean isCustom=false); // constructor + int getIID(){return(iid);} + template T getVal(){ return(uvGet(value)); } From 73561328e620ac9be814a59e5aac04192d4bfa6b Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 14 Apr 2024 21:29:02 -0500 Subject: [PATCH 050/154] Changed TLV8:add() for *values* to ensure resulting size is always multiple of 2 Checked that this is correctly interpreted by HomeKit using adaptive light TLV. TLV code is now ready for documentation and then transfer to dev branch. --- src/TLV8.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/TLV8.cpp b/src/TLV8.cpp index aae7686..6c629ad 100644 --- a/src/TLV8.cpp +++ b/src/TLV8.cpp @@ -93,8 +93,12 @@ TLV8_it TLV8::add(uint8_t tag, uint64_t val){ uint8_t *p=reinterpret_cast(&val); size_t nBytes=sizeof(uint64_t); - while(nBytes>1 && p[nBytes-1]==0) // TLV requires little endian without any trailing zero bytes (i.e. only use what is needed to fully represent the value) + while(nBytes>1 && p[nBytes-1]==0) // TLV requires little endian of size 1, 2, 4, or 8 bytes (include trailing zeros as needed) nBytes--; + if(nBytes==3) // need to include a trailing zero so that total bytes=4 + nBytes=4; + else if(nBytes>4) // need to include multiple trailing zeros so that total bytes=8 + nBytes=8; return(add(tag, nBytes, p)); } From fe3269e9ef59b03e7b13f5eefc996180dbedc60a Mon Sep 17 00:00:00 2001 From: Gregg Date: Mon, 15 Apr 2024 22:14:05 -0500 Subject: [PATCH 051/154] Added new validation check for homeSpan.resetIID() New value must be equal to, or greater than, the last IID value used. This prevents possible re-use. --- src/HomeSpan.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index b0bcbe7..9380bd7 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -1594,13 +1594,15 @@ Span& Span::resetIID(int newIID){ while(1); } - if(newIID<1){ - LOG0("\nFATAL ERROR! Can't reset the Accessory IID count to a value less than 1 ***\n"); + newIID--; + + if(newIIDiidCount){ + LOG0("\nFATAL ERROR! Can't reset the Accessory IID count to a value less than already used ***\n"); LOG0("\n=== PROGRAM HALTED ==="); while(1); } - Accessories.back()->iidCount=newIID-1; + Accessories.back()->iidCount=newIID; return(*this); } From b9efa873dc76a5547293fcab4c3852301e754f88 Mon Sep 17 00:00:00 2001 From: Gregg Date: Tue, 16 Apr 2024 21:56:07 -0500 Subject: [PATCH 052/154] Added error-checking for homeSpan.resetIID() HomeSpan will throw a Warning when 'i' CLI command finds a duplicate IID within the same Accessory. --- src/HomeSpan.cpp | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 9380bd7..7cba72b 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -874,6 +874,7 @@ void Span::processSerialCommand(const char *c){ LOG0(" *** ERROR #%d! AID already in use for another Accessory ***\n",++nErrors); aidValues.push_back((*acc)->aid); + vector> iidValues; for(auto svc=(*acc)->Services.begin(); svc!=(*acc)->Services.end(); svc++){ LOG0(" \u279f Service %s: IID=%d, %sUUID=\"%s\"\n",(*svc)->hapName,(*svc)->iid,(*svc)->isCustom?"Custom-":"",(*svc)->type); @@ -881,11 +882,16 @@ void Span::processSerialCommand(const char *c){ if(!strcmp((*svc)->type,"3E")){ foundInfo=true; if((*svc)->iid!=1) - LOG0(" *** ERROR #%d! The Accessory Information Service must be defined before any other Services in an Accessory ***\n",++nErrors); + LOG0(" *** ERROR #%d! The Accessory Information Service must be defined with IID=1 (i.e. before any other Services in an Accessory) ***\n",++nErrors); } else if((*acc)->aid==1) // this is an Accessory with aid=1, but it has more than just AccessoryInfo. So... - isBridge=false; // ...this is not a bridge device - + isBridge=false; // ...this is not a bridge device + + if(std::find(iidValues.begin(),iidValues.end(),(*svc)->iid)!=iidValues.end()) + LOG0(" *** ERROR #%d! IID already in use for another Service or Characteristic within this Accessory ***\n",++nErrors); + + iidValues.push_back((*svc)->iid); + for(auto chr=(*svc)->Characteristics.begin(); chr!=(*svc)->Characteristics.end(); chr++){ LOG0(" \u21e8 Characteristic %s(%.33s%s): IID=%d, %sUUID=\"%s\", %sPerms=", (*chr)->hapName,(*chr)->uvPrint((*chr)->value).c_str(),strlen((*chr)->uvPrint((*chr)->value).c_str())>33?"...\"":"",(*chr)->iid,(*chr)->isCustom?"Custom-":"",(*chr)->type,(*chr)->perms!=(*chr)->hapChar->perms?"Custom-":""); @@ -939,6 +945,11 @@ void Span::processSerialCommand(const char *c){ if((*chr)->formatuvGet((*chr)->value) >= (*chr)->uvGet((*chr)->minValue)) && ((*chr)->uvGet((*chr)->value) <= (*chr)->uvGet((*chr)->maxValue))))) LOG0(" *** WARNING #%d! Value of %g is out of range [%g,%g] ***\n",++nWarnings,(*chr)->uvGet((*chr)->value),(*chr)->uvGet((*chr)->minValue),(*chr)->uvGet((*chr)->maxValue)); + + if(std::find(iidValues.begin(),iidValues.end(),(*chr)->iid)!=iidValues.end()) + LOG0(" *** ERROR #%d! IID already in use for another Service or Characteristic within this Accessory ***\n",++nErrors); + + iidValues.push_back((*chr)->iid); } // Characteristics @@ -1593,16 +1604,14 @@ Span& Span::resetIID(int newIID){ LOG0("\n=== PROGRAM HALTED ==="); while(1); } - - newIID--; - if(newIIDiidCount){ - LOG0("\nFATAL ERROR! Can't reset the Accessory IID count to a value less than already used ***\n"); + if(newIID<1){ + LOG0("\nFATAL ERROR! Request to reset the Accessory IID count to 0 not allowed (IID must be 1 or greater) ***\n"); LOG0("\n=== PROGRAM HALTED ==="); while(1); } - Accessories.back()->iidCount=newIID; + Accessories.back()->iidCount=newIID-1; return(*this); } From 723c343277a2bd23ff2a64a69ef34937eaba5e0a Mon Sep 17 00:00:00 2001 From: Gregg Date: Tue, 16 Apr 2024 22:17:00 -0500 Subject: [PATCH 053/154] Added error checking to warn if non-bridge device defines more than 3 Accessories It appears that HomeKit requires devices with more than 3 Accessories to be configured as a bridge. If not, the Home App will ignore any functional Services in the first Accessory and treat it like a bridge regardless. Device with 3 or less Accessories do not require a bridge configuration. --- src/HomeSpan.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 7cba72b..38667fa 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -1027,6 +1027,9 @@ void Span::processSerialCommand(const char *c){ } LOG0("\nConfigured as Bridge: %s\n",isBridge?"YES":"NO"); + if(!isBridge && Accessories.size()>3) + LOG0("*** WARNING #%d! HomeKit requires the device be configured as a Bridge when more than 3 Accessories are defined ***\n",++nWarnings); + if(hapConfig.configNumber>0) LOG0("Configuration Number: %d\n",hapConfig.configNumber); LOG0("\nDatabase Validation: Warnings=%d, Errors=%d\n",nWarnings,nErrors); From d1dd56547810edd1ef2d549f9b919cec898683b3 Mon Sep 17 00:00:00 2001 From: Gregg Date: Wed, 17 Apr 2024 07:24:08 -0500 Subject: [PATCH 054/154] Converted IID from int to uint32_t everywhere --- src/HomeSpan.cpp | 63 +++++++++++++++++++++--------------------------- src/HomeSpan.h | 48 ++++++++++++++++++------------------ 2 files changed, 51 insertions(+), 60 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 38667fa..bd8c65a 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -864,7 +864,7 @@ void Span::processSerialCommand(const char *c){ char pNames[][7]={"PR","PW","EV","AA","TW","HD","WR"}; for(auto acc=Accessories.begin(); acc!=Accessories.end(); acc++){ - LOG0("\u27a4 Accessory: AID=%d\n",(*acc)->aid); + LOG0("\u27a4 Accessory: AID=%u\n",(*acc)->aid); boolean foundInfo=false; if(acc==Accessories.begin() && (*acc)->aid!=1) @@ -877,7 +877,7 @@ void Span::processSerialCommand(const char *c){ vector> iidValues; for(auto svc=(*acc)->Services.begin(); svc!=(*acc)->Services.end(); svc++){ - LOG0(" \u279f Service %s: IID=%d, %sUUID=\"%s\"\n",(*svc)->hapName,(*svc)->iid,(*svc)->isCustom?"Custom-":"",(*svc)->type); + LOG0(" \u279f Service %s: IID=%u, %sUUID=\"%s\"\n",(*svc)->hapName,(*svc)->iid,(*svc)->isCustom?"Custom-":"",(*svc)->type); if(!strcmp((*svc)->type,"3E")){ foundInfo=true; @@ -893,7 +893,7 @@ void Span::processSerialCommand(const char *c){ iidValues.push_back((*svc)->iid); for(auto chr=(*svc)->Characteristics.begin(); chr!=(*svc)->Characteristics.end(); chr++){ - LOG0(" \u21e8 Characteristic %s(%.33s%s): IID=%d, %sUUID=\"%s\", %sPerms=", + LOG0(" \u21e8 Characteristic %s(%.33s%s): IID=%u, %sUUID=\"%s\", %sPerms=", (*chr)->hapName,(*chr)->uvPrint((*chr)->value).c_str(),strlen((*chr)->uvPrint((*chr)->value).c_str())>33?"...\"":"",(*chr)->iid,(*chr)->isCustom?"Custom-":"",(*chr)->type,(*chr)->perms!=(*chr)->hapChar->perms?"Custom-":""); int foundPerms=0; @@ -996,7 +996,7 @@ void Span::processSerialCommand(const char *c){ for(int i=0;iServices.size();j++){ SpanService *s=Accessories[i]->Services[j]; - LOG0("%-30s %8.8s %10u %3d %6s %4s %6s ",s->hapName,s->type,Accessories[i]->aid,s->iid, + LOG0("%-30s %8.8s %10u %3u %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" @@ -1004,7 +1004,7 @@ void Span::processSerialCommand(const char *c){ if(s->linkedServices.empty()) LOG0("-"); for(int k=0;klinkedServices.size();k++){ - LOG0("%d",s->linkedServices[k]->iid); + LOG0("%u",s->linkedServices[k]->iid); if(klinkedServices.size()-1) LOG0(","); } @@ -1323,7 +1323,7 @@ boolean Span::deleteAccessory(uint32_t n){ /////////////////////////////// -SpanCharacteristic *Span::find(uint32_t aid, int iid){ +SpanCharacteristic *Span::find(uint32_t aid, uint32_t iid){ int index=-1; for(int i=0;iservice->update()?StatusCode::OK:StatusCode::Unable; // update service and save statusCode as OK or Unable depending on whether return is true or false + StatusCode status=pObj[i].characteristic->service->update()?StatusCode::OK:StatusCode::Unable; // update service and save statusCode as OK or Unable depending on whether return is true or false - for(int j=i;jservice==pObj[i].characteristic->service){ // if service of this characteristic matches service that was updated - pObj[j].status=status; // save statusCode for this object - LOG1("Updating aid="); - LOG1(pObj[j].characteristic->aid); - LOG1(" iid="); - LOG1(pObj[j].characteristic->iid); - if(status==StatusCode::OK){ // if status is okay - pObj[j].characteristic->uvSet(pObj[j].characteristic->value,pObj[j].characteristic->newValue); // update characteristic value with new value - if(pObj[j].characteristic->nvsKey){ // if storage key found + if(pObj[j].characteristic->service==pObj[i].characteristic->service){ // if service of this characteristic matches service that was updated + pObj[j].status=status; // save statusCode for this object + LOG1("Updating aid=%u iid=%u",pObj[j].characteristic->aid,pObj[j].characteristic->iid); + if(status==StatusCode::OK){ // if status is okay + pObj[j].characteristic->uvSet(pObj[j].characteristic->value,pObj[j].characteristic->newValue); // update characteristic value with new value + if(pObj[j].characteristic->nvsKey){ // if storage key found if(pObj[j].characteristic->formatnvsKey,pObj[j].characteristic->value.UINT64); // store data as uint64_t regardless of actual type (it will be read correctly when access through uvGet()) + nvs_set_u64(charNVS,pObj[j].characteristic->nvsKey,pObj[j].characteristic->value.UINT64); // store data as uint64_t regardless of actual type (it will be read correctly when access through uvGet()) else - nvs_set_str(charNVS,pObj[j].characteristic->nvsKey,pObj[j].characteristic->value.STRING); // store data + nvs_set_str(charNVS,pObj[j].characteristic->nvsKey,pObj[j].characteristic->value.STRING); // store data nvs_commit(charNVS); } LOG1(" (okay)\n"); - } else { // if status not okay - pObj[j].characteristic->uvSet(pObj[j].characteristic->newValue,pObj[j].characteristic->value); // replace characteristic new value with original value + } else { // if status not okay + pObj[j].characteristic->uvSet(pObj[j].characteristic->newValue,pObj[j].characteristic->value); // replace characteristic new value with original value LOG1(" (failed)\n"); } - pObj[j].characteristic->updateFlag=0; // reset updateFlag for characteristic + pObj[j].characteristic->updateFlag=0; // reset updateFlag for characteristic } } @@ -1555,13 +1552,13 @@ void Span::printfAttributes(SpanBuf *pObj, int nObj){ boolean Span::printfAttributes(char **ids, int numIDs, int flags){ uint32_t aid; - int iid; + uint32_t iid; SpanCharacteristic *Characteristics[numIDs]; StatusCode status[numIDs]; for(int i=0;iprintfAttributes(flags); // get JSON attributes for characteristic (may or may not include status=0 attribute) else{ // else create JSON status attribute based on requested aid/iid - sscanf(ids[i],"%u.%d",&aid,&iid); + sscanf(ids[i],"%u.%u",&aid,&iid); hapOut << "{\"iid\":" << iid << ",\"aid\":" << aid << ",\"status\":" << (int)status[i] << "}"; } @@ -1600,7 +1597,7 @@ boolean Span::printfAttributes(char **ids, int numIDs, int flags){ /////////////////////////////// -Span& Span::resetIID(int newIID){ +Span& Span::resetIID(uint32_t newIID){ if(Accessories.empty()){ LOG0("\nFATAL ERROR! Can't reset the Accessory IID count without a defined Accessory ***\n"); @@ -1767,7 +1764,7 @@ SpanService::~SpanService(){ } } - LOG1("Deleted Service AID=%d IID=%d\n",accessory->aid,iid); + LOG1("Deleted Service AID=%u IID=%u\n",accessory->aid,iid); } /////////////////////////////// @@ -1872,7 +1869,7 @@ SpanCharacteristic::~SpanCharacteristic(){ free(newValue.STRING); } - LOG1("Deleted Characteristic AID=%d IID=%d\n",aid,iid); + LOG1("Deleted Characteristic AID=%u IID=%u\n",aid,iid); } /////////////////////////////// @@ -1961,13 +1958,7 @@ StatusCode SpanCharacteristic::loadUpdate(char *val, char *ev, boolean wr){ if(evFlag && !(perms&EV)) // notification is not supported for characteristic return(StatusCode::NotifyNotAllowed); - LOG1("Notification Request for aid="); - LOG1(aid); - LOG1(" iid="); - LOG1(iid); - LOG1(": "); - LOG1(evFlag?"true":"false"); - LOG1("\n"); + LOG1("Notification Request for aid=%u iid=%u: %s\n",aid,iid,evFlag?"true":"false"); this->ev[HAPClient::conNum]=evFlag; } diff --git a/src/HomeSpan.h b/src/HomeSpan.h index f22c55d..be024b8 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -136,7 +136,7 @@ struct SpanConfig{ struct SpanBuf{ // temporary storage buffer for use with putCharacteristicsURL() and checkTimedResets() uint32_t aid=0; // updated aid - int iid=0; // updated iid + uint32_t iid=0; // updated iid boolean wr=false; // flag to indicate write-response has been requested char *val=NULL; // updated value (optional, though either at least 'val' or 'ev' must be specified) char *ev=NULL; // updated event notification flag (optional, though either at least 'val' or 'ev' must be specified) @@ -280,7 +280,7 @@ class Span{ void printfAttributes(int flags=GET_VALUE|GET_META|GET_PERMS|GET_TYPE|GET_DESC); // writes Attributes JSON database to hapOut stream - SpanCharacteristic *find(uint32_t aid, int iid); // return Characteristic with matching aid and iid (else NULL if not found) + SpanCharacteristic *find(uint32_t aid, uint32_t iid); // return Characteristic with matching aid and iid (else NULL if not found) int countCharacteristics(char *buf); // return number of characteristic objects referenced in PUT /characteristics JSON request int updateCharacteristics(char *buf, SpanBuf *pObj); // parses PUT /characteristics JSON request 'buf into 'pObj' and updates referenced characteristics; returns 1 on success, 0 on fail void printfAttributes(SpanBuf *pObj, int nObj); // writes SpanBuf objects to hapOut stream @@ -354,7 +354,7 @@ class Span{ const char* statusString(HS_STATUS s); // returns char string for HomeSpan status change messages Span& setPairingCode(const char *s, boolean progCall=true); // sets the Pairing Code - use is NOT recommended. Use 'S' from CLI instead void deleteStoredValues(){processSerialCommand("V");} // deletes stored Characteristic values from NVS - Span& resetIID(int newIID); // resets the IID count for the current Accessory to start at newIID + Span& resetIID(uint32_t newIID); // resets the IID count for the current Accessory to start at newIID int enableOTA(boolean auth=true, boolean safeLoad=true){return(spanOTA.init(auth, safeLoad, NULL));} // enables Over-the-Air updates, with (auth=true) or without (auth=false) authorization password int enableOTA(const char *pwd, boolean safeLoad=true){return(spanOTA.init(true, safeLoad, pwd));} // enables Over-the-Air updates, with custom authorization password (overrides any password stored with the 'O' command) @@ -408,15 +408,15 @@ class SpanAccessory{ friend class SpanButton; friend class SpanRange; - uint32_t aid=0; // Accessory Instance ID (HAP Table 6-1) - int iidCount=0; // running count of iid to use for Services and Characteristics associated with this Accessory - vector> Services; // vector of pointers to all Services in this Accessory + uint32_t aid=0; // Accessory Instance ID (HAP Table 6-1) + uint32_t iidCount=0; // running count of iid to use for Services and Characteristics associated with this Accessory + vector> Services; // vector of pointers to all Services in this Accessory - void printfAttributes(int flags); // writes Accessory JSON to hapOut stream + void printfAttributes(int flags); // writes Accessory JSON to hapOut stream protected: - ~SpanAccessory(); // destructor + ~SpanAccessory(); // destructor public: @@ -433,23 +433,23 @@ class SpanService{ friend class SpanCharacteristic; friend class SpanRange; - int iid=0; // Instance ID (HAP Table 6-2) - const char *type; // Service Type - const char *hapName; // HAP Name - boolean hidden=false; // optional property indicating service is hidden - boolean primary=false; // optional property indicating service is primary - vector> Characteristics; // vector of pointers to all Characteristics in this Service - vector> linkedServices; // vector of pointers to any optional linked Services - boolean isCustom; // flag to indicate this is a Custom Service - SpanAccessory *accessory=NULL; // pointer to Accessory containing this Service + uint32_t iid=0; // Instance ID (HAP Table 6-2) + const char *type; // Service Type + const char *hapName; // HAP Name + boolean hidden=false; // optional property indicating service is hidden + boolean primary=false; // optional property indicating service is primary + vector> Characteristics; // vector of pointers to all Characteristics in this Service + vector> linkedServices; // vector of pointers to any optional linked Services + boolean isCustom; // flag to indicate this is a Custom Service + SpanAccessory *accessory=NULL; // pointer to Accessory containing this Service - void printfAttributes(int flags); // writes Service JSON to hapOut stream + void printfAttributes(int flags); // writes Service JSON to hapOut stream protected: - virtual ~SpanService(); // destructor - 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 + virtual ~SpanService(); // destructor + 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 public: @@ -460,7 +460,7 @@ class SpanService{ 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 getIID(){return(iid);} + uint32_t getIID(){return(iid);} // returns IID of Service virtual boolean update() {return(true);} // placeholder for code that is called when a Service is updated via a Controller. Must return true/false depending on success of update virtual void loop(){} // loops for each Service - called every cycle if over-ridden with user-defined code @@ -485,7 +485,7 @@ class SpanCharacteristic{ STRING_t STRING = NULL; }; - int iid=0; // Instance ID (HAP Table 6-3) + uint32_t iid=0; // Instance ID (HAP Table 6-3) HapChar *hapChar; // pointer to HAP Characteristic structure const char *type; // Characteristic Type const char *hapName; // HAP Name @@ -656,7 +656,7 @@ class SpanCharacteristic{ void *operator new(size_t size){return(HS_MALLOC(size));} // override new operator to use PSRAM when available SpanCharacteristic(HapChar *hapChar, boolean isCustom=false); // constructor - int getIID(){return(iid);} + uint32_t getIID(){return(iid);} // returns IID of Characteristic template T getVal(){ return(uvGet(value)); From bb531184d7e80ea1201a0658b11196aefb3ad906 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Thu, 18 Apr 2024 06:43:58 -0500 Subject: [PATCH 055/154] Documents resetIID(), Service::getIID() and Characteristic::getIID() --- docs/Reference.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/Reference.md b/docs/Reference.md index 56109b9..5222833 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -281,7 +281,7 @@ The following **optional** `homeSpan` methods provide additional run-time functi * allows for dynamically changing the Accessory database during run-time (i.e. changing the configuration *after* the Arduino `setup()` has finished) * deleting an Accessory automatically deletes all Services, Characteristics, and any other resources it contains * outputs Level-1 Log Messages listing all deleted components - * note: though deletions take effect immediately, HomeKit Controllers, such as the Home App, will not be aware of these changes until the database configuration number is updated and rebroadcast - see updateDatabase() below + * note: though deletions take effect immediately, HomeKit Controllers, such as the Home App, will not be aware of these changes until the database configuration number is updated and rebroadcast - see `updateDatabase()` below * `boolean updateDatabase()` * recomputes the database configuration number and, if changed, rebroadcasts the new number via MDNS so all connected HomeKit Controllers, such as the Home App, can request a full refresh to accurately reflect the new configuration @@ -290,6 +290,12 @@ The following **optional** `homeSpan` methods provide additional run-time functi * use anytime after dynamically adding one or more Accessories (with `new SpanAccessory(aid)`) or deleting one or more Accessories (with `homeSpan.deleteAccessory(aid)`) * **important**: once you delete an Accessory, you cannot re-use the same *aid* when adding a new Accessory (on the same device) unless the new Accessory is configured with the exact same Services and Characteristics as the deleted Accessory * note: this method is **not** needed if you have a static Accessory database that is fully defined in the Arduino `setup()` function of a sketch + +* `Span& resetIID(uint32_t newIID)` + * resets the IID count for the current Accessory to *newIID*, which must be greater than 0 + * throws an error and halts program if called before at least one Accessory is created + * example: `homeSpan.resetIID(100)` causes HomeSpan to set the IID to 100 for the very next Service or Characteristic defined within the current Accessory, and then increment the IID count going forward so that any Services or Characteristics subsequently defined (within the same Accessory) have IID=101, 102, etc. + * note: calling this function only affects the IID generation for the current Accessory (the count will be reset to IID=1 upon instantiation of a new Accessory) --- @@ -371,6 +377,9 @@ The following methods are supported: * 0=single press (SpanButton::SINGLE) * 1=double press (SpanButton::DOUBLE) * 2=long press (SpanButton::LONG) + +* `uint32_t getIID()` + * returns the IID of the Service ## *SpanCharacteristic(value [,boolean nvsStore])* @@ -485,6 +494,9 @@ This is a **base class** from which all HomeSpan Characteristics are derived, an * returns a pointer to the Characteristic itself so that the method can be chained during instantiation * example: `(new Characteristic::RotationSpeed())->setUnit("percentage");` +* `uint32_t getIID()` + * returns the IID of the Characteristic + ### *SpanButton(int pin, uint16_t longTime, uint16_t singleTime, uint16_t doubleTime, boolean (\*triggerType)(int))* Creating an instance of this **class** attaches a pushbutton handler to the ESP32 *pin* specified. From 64f67fbb8a32621aa11e7ca651d52b92abb0b94d Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Thu, 18 Apr 2024 07:04:13 -0500 Subject: [PATCH 056/154] Update Reference.md --- docs/Reference.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/Reference.md b/docs/Reference.md index 5222833..3ae3a75 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -404,16 +404,16 @@ This is a **base class** from which all HomeSpan Characteristics are derived, an * `type T 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 when called from within the `update()` loop of a **SpanService** (if called outside of the `update()` loop, the return value is that same as calling `getVal<>()`) * `void setVal(value [,boolean notify])` * 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 any of the conditions hold: - * the Characteristic is not configured with Event Notification (EV) permission enabled; or - * this method is being called from within the `update()` routine of a **SpanService** and `isUpdated()` is *true* for the Characteristic (i.e. it is being updated at the same time via the Home App); or - * *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()` - * the first two restrictions above do not apply to the use of `setVal()` from within the `update()` method of a **SpanService** if you are changing the value of a Characteristic in response to a *write-response* request from HomeKit - * *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) + * 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()` + * note that *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) + * throws a runtime warning if called from within the `update()` routine of a **SpanService** *and* `isUpdated()` is *true* for the Characteristic (i.e. it is being updated at the same time via the Home App), *unless* you are changing the value of a Characteristic in response to a *write-response* request from HomeKit (typically used only for certain TLV-based Characteristics) + * note this method can be used to update the value of a Characteristic even if the Characteristic is not permissioned for event notifications (EV), in which case the value stored by HomeSpan will be updated but the Home App will *not* be notified of the change + * `SpanCharacteristic *setRange(min, max, step)` * overrides the default HAP range for a Characteristic with the *min*, *max*, and *step* parameters specified From 6794a804f9f7aeef7c7955954916ce4c0b45f503 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Thu, 18 Apr 2024 17:59:31 -0500 Subject: [PATCH 057/154] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9aeaf50..bb379ad 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ HomeSpan requires version 2.0.0 or later of the [Arduino-ESP32 Board Manager](ht * Utilizes a unique *Service-Centric* approach to creating HomeKit devices * Takes full advantage of the widely-popular Arduino IDE * 100% HAP-R2 compliance -* 38 integrated HomeKit Services +* Dozens of integrated HomeKit Services * Operates in either Accessory or Bridge mode * Supports pairing with Setup Codes or QR Codes From 2ecbed9f26e47f15f253a90e006e0d27645db4ee Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Thu, 18 Apr 2024 18:21:02 -0500 Subject: [PATCH 058/154] Update Reference.md --- docs/Reference.md | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/docs/Reference.md b/docs/Reference.md index 3ae3a75..fe658a9 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -440,10 +440,10 @@ This is a **base class** from which all HomeSpan Characteristics are derived, an * `char *getNewString()` * equivalent to `getNewVal()`, but used exclusively for string-characteristics (i.e. a null-terminated array of characters) -* `void setString(const char *value)` +* `void setString(const char *value [,boolean notify])` * equivalent to `setVal(value)`, but used exclusively for string-characteristics (i.e. a null-terminated array of characters) - #### The following methods are supported for DATA (i.e. byte-array) Characteristics: +#### The following methods are supported for DATA (i.e. byte-array) Characteristics: * `size_t getData(uint8_t *data, size_t len)` * similar to `getVal()`, but exclusively used for byte-array Characteristics @@ -451,14 +451,34 @@ This is a **base class** from which all HomeSpan Characteristics are derived, an * returns the total number of bytes encoded in the Characteristic * if *len* is less than the total number of bytes encoded, no data is extracted (i.e. *data* is unmodified) and a warning message is thrown indicating that the size of the *data* array is insufficient to extract all the bytes encoded in the Characteristic * setting *data* to NULL returns the total number of bytes encoded without extracting any data. This can be used to help create a *data* array of sufficient size in advance of extracting the data + * note: byte-array Characteristics are encoded and transmitted as base-64 strings. HomeSpan automatically peforms all encoding and decoding between this format and the specified byte arrays. But when output to the Serial Monitor, the value of byte-array Characteristics are displayed in their base-64 format (as opposed to being shown as a byte array), since base-64 is the representation that is actually transmitted to and from HomeKit + * a warning message is thrown if the value stored in the Characteristic is not in base-64 format * `size_t getNewData(uint8_t *data, size_t len)` * similar to `getData()`, but fills byte array *data*, of specified size *len*, with bytes based on the desired **new** value to which a HomeKit Controller has requested the Characteristic be updated -* `void setData(uint8_t *data, size_t len)` +* `void setData(uint8_t *data, size_t len [,boolean notify])` * similar to `setVal()`, but exclusively used for byte-array Characteristics * updates the Characteristic by "filling" it with *len* bytes from bytes array *data* - * note: byte-array Characteristics are encoded and transmitted as base-64 strings. HomeSpan automatically peforms all encoding and decoding between this format and the specified byte arrays. But when output to the Serial Monitor, the value of byte-array Characteristics are displayed in their base-64 format (as opposed to being shown as a byte array), since base-64 is the representation that is actually transmitted to and from HomeKit + +#### The following methods are supported for TLV8 (structured-data) Characteristics: + +* `size_t getTLV(TLV8 &tlv)` + * similar to `getVal()`, but exclusively used for TLV8 Characteristics + * fills TLV8 structure *tlv* with TLV8 records from the current value of the Characteristic + * returns the total number of bytes encoded in the Characteristic + * if *tlv8* is not empty, TLV8 records from the Characteristic will be appended to any existing records + * similar DATA Characteristics, TLV8 Characteristics are encoded and transmittred as base-64 strings + * a warning message is thrown if the value stored in the Characteristic is not in base-64 format, or does not appear to contain TLV8 records + +* `size_t getNewTLV(TLV8 &tlv)` + * similar to `getTLV()`, but fills TLV8 structure *tlv* with TLV8 records based on the desired **new** value to which a HomeKit Controller has requested the Characteristic be updated + +* `void setTLV(TLV8 &tlv [,boolean notify])` + * similar to `setVal()`, but exclusively used for TLV8 Characteristics + * updates the Characteristic by packing the TLV8 structure *tlv* into a byte array and then encoding it in base-64 for storage as a string + +* see the [TLV8 Characteristics](TLV8.md) page for complete details on how to read/process/create TLV8 Characteristics #### The following methods are supported for all Characteristics: From 04443d70dd1b177ed4539514fd70eaff3ecbcdd5 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Thu, 18 Apr 2024 20:59:55 -0500 Subject: [PATCH 059/154] 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 fe658a9..93ea7b7 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -451,7 +451,7 @@ This is a **base class** from which all HomeSpan Characteristics are derived, an * returns the total number of bytes encoded in the Characteristic * if *len* is less than the total number of bytes encoded, no data is extracted (i.e. *data* is unmodified) and a warning message is thrown indicating that the size of the *data* array is insufficient to extract all the bytes encoded in the Characteristic * setting *data* to NULL returns the total number of bytes encoded without extracting any data. This can be used to help create a *data* array of sufficient size in advance of extracting the data - * note: byte-array Characteristics are encoded and transmitted as base-64 strings. HomeSpan automatically peforms all encoding and decoding between this format and the specified byte arrays. But when output to the Serial Monitor, the value of byte-array Characteristics are displayed in their base-64 format (as opposed to being shown as a byte array), since base-64 is the representation that is actually transmitted to and from HomeKit + * note: byte-array Characteristics are encoded and transmitted as base-64 strings. HomeSpan automatically peforms all encoding and decoding between this format and the specified byte arrays. But when output to the Serial Monitor using the 'i' CLI command, the value of byte-array Characteristics are displayed in their base-64 string format (only the first 32 characters are shown), since base-64 is the representation that is actually transmitted to and from HomeKit * a warning message is thrown if the value stored in the Characteristic is not in base-64 format * `size_t getNewData(uint8_t *data, size_t len)` @@ -468,7 +468,7 @@ This is a **base class** from which all HomeSpan Characteristics are derived, an * fills TLV8 structure *tlv* with TLV8 records from the current value of the Characteristic * returns the total number of bytes encoded in the Characteristic * if *tlv8* is not empty, TLV8 records from the Characteristic will be appended to any existing records - * similar DATA Characteristics, TLV8 Characteristics are encoded and transmittred as base-64 strings + * similar to DATA Characteristics, TLV8 Characteristics are stored and transmitted as base-64 strings * a warning message is thrown if the value stored in the Characteristic is not in base-64 format, or does not appear to contain TLV8 records * `size_t getNewTLV(TLV8 &tlv)` From dfec5332122e884e9b601a4f27ffc0ca805e8ff2 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Thu, 18 Apr 2024 21:06:47 -0500 Subject: [PATCH 060/154] Create TLV8.md --- docs/TLV8.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/TLV8.md diff --git a/docs/TLV8.md b/docs/TLV8.md new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/docs/TLV8.md @@ -0,0 +1 @@ + From b05be87a294f85a3e2c97d9c8e206fe34f6ec6a3 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Thu, 18 Apr 2024 22:20:58 -0500 Subject: [PATCH 061/154] Update TLV8.md --- docs/TLV8.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/TLV8.md b/docs/TLV8.md index 8b13789..6926978 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -1 +1,23 @@ +# TLV8 Characteristics +Most HomeKit Characteristics store a single numerical value or simple string. However, HomeKit supports two additional storage formats - a simple list of bytes (the **DATA** format) and a structured list of tags and values (the **TLV8** format). The DATA format is not used by any Apple-defined Characterstics but it is included in HomeSpan for use when creating Custom Characteristics for non-Apple applications. + +In contrast, The TLV8 format is used extensively by HomeKit during the initial pairing process as well as whenever a new secure (verified) connection is established between HomeKit and a HomeSpan device. There are also a variety of Apple-defined Characteristics that use the TLV8 format to store and transmit multiple sets of values, each represented as byte-arrays of arbitrary length. + +The TLV8 format itself is quite simple. A TLV8 Characteristic comprises one or more byte-arrays (sometimes called TLV8 records), where the first byte in a record represents an identifying TAG (from 0-255), the second byte represents the LENGTH of the value, and the remaining LENGTH-bytes represent the VALUE itself (hence the acronym TLV). Some notable points include: + +* since the LENGTH is stored only as a single byte, VALUES requiring more than 255 bytes must be represented as sequential TLV8 records *with the same TAG* +* it is fine (and in fact common) for a Characteristic to include multiple records with the same TAG, except that they must be *separated by a record with a different TAG*, otherwise the parser reading the data will concatenate the VALUES from sequential records having the same TAG into a single record (as per above) +* records representing zero-LENGTH values are allowed, and consist of only two bytes: a TAG and a zero (indicating a zero-length VALUE). TAGS with zero-LENGTH values are often used to separate multiple records having the same TAG +* if the VALUE stored in TLV8 format represents an integer, it should be presented in little endian format (least-significant byte first) and padded with trailing zeros as needed to bring the total LENGTH of the value to 1, 2, 4, or 8 bytes (representing uint8_t, uint16_t, uint32_t, and uint64_t values) +* if the value stored in TLV8 format represents a string, it should not include the terminating null since the LENGTH tells you have many characters are in the string +* a parser reading TLV8 records should silently ignore any TAG it is not expecting. It may be an error to omit a TAG that the parser requires, but it is not an error to include a TAG it does not recognize +* since HomeKit data transmissions are often represented in JSON, and JSON is a text-only format, HomeKit requires that TLV8 records are first encoded in base-64 when transmitting JSON to and from Controllers to Accessories. + +Fortunately, HomeSpan includes a dedicated TLV8 library (see below) that takes care of everything above automatically, which enables you to read, create, and process TLV8 data without worrying about TLV8 records with more than 255 bytes, converting numerical values to little-endian, or encoding/decoding records into base-64. + + + + + +🚧 From 9b0555d2561e41c818ea8d490f2f8aefc9bf7aaa Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Fri, 19 Apr 2024 12:58:43 -0500 Subject: [PATCH 062/154] Update TLV8.md --- docs/TLV8.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/TLV8.md b/docs/TLV8.md index 6926978..5bb0b20 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -2,19 +2,21 @@ Most HomeKit Characteristics store a single numerical value or simple string. However, HomeKit supports two additional storage formats - a simple list of bytes (the **DATA** format) and a structured list of tags and values (the **TLV8** format). The DATA format is not used by any Apple-defined Characterstics but it is included in HomeSpan for use when creating Custom Characteristics for non-Apple applications. -In contrast, The TLV8 format is used extensively by HomeKit during the initial pairing process as well as whenever a new secure (verified) connection is established between HomeKit and a HomeSpan device. There are also a variety of Apple-defined Characteristics that use the TLV8 format to store and transmit multiple sets of values, each represented as byte-arrays of arbitrary length. +In contrast, the TLV8 format is used extensively by HomeKit during the initial pairing process as well as whenever a new secure (verified) connection is established between HomeKit and a HomeSpan device. There are also a variety of Apple-defined Characteristics that use the TLV8 format to store and transmit multiple sets of values, each represented as byte-arrays of arbitrary length. -The TLV8 format itself is quite simple. A TLV8 Characteristic comprises one or more byte-arrays (sometimes called TLV8 records), where the first byte in a record represents an identifying TAG (from 0-255), the second byte represents the LENGTH of the value, and the remaining LENGTH-bytes represent the VALUE itself (hence the acronym TLV). Some notable points include: +The TLV8 format itself is quite simple. A TLV8 Characteristic comprises one or more byte-arrays (sometimes called TLV8 records), where the first byte in a record represents an identifying TAG (from 0-255), the second byte represents the LENGTH of the value, and the remaining LENGTH-bytes represent the VALUE itself (hence the acronym TLV). Notable points about the TLV8 format are as follows: * since the LENGTH is stored only as a single byte, VALUES requiring more than 255 bytes must be represented as sequential TLV8 records *with the same TAG* * it is fine (and in fact common) for a Characteristic to include multiple records with the same TAG, except that they must be *separated by a record with a different TAG*, otherwise the parser reading the data will concatenate the VALUES from sequential records having the same TAG into a single record (as per above) -* records representing zero-LENGTH values are allowed, and consist of only two bytes: a TAG and a zero (indicating a zero-length VALUE). TAGS with zero-LENGTH values are often used to separate multiple records having the same TAG -* if the VALUE stored in TLV8 format represents an integer, it should be presented in little endian format (least-significant byte first) and padded with trailing zeros as needed to bring the total LENGTH of the value to 1, 2, 4, or 8 bytes (representing uint8_t, uint16_t, uint32_t, and uint64_t values) -* if the value stored in TLV8 format represents a string, it should not include the terminating null since the LENGTH tells you have many characters are in the string +* records representing a zero-LENGTH VALUE are allowed, and consist of only two bytes: a TAG and a zero (indicating a zero-length VALUE). TAGS with a zero-LENGTH VALUE are often used to separate multiple records having the same TAG +* if the VALUE stored in TLV8 format represents an integer, it should be presented in little endian format (least-significant byte first) and padded with trailing zeros as needed to bring the total LENGTH of the VALUE to either 1, 2, 4, or 8 bytes (representing uint8_t, uint16_t, uint32_t, and uint64_t values) +* if the VALUE stored in TLV8 format represents a string, it should not include the terminating null since the LENGTH tells you have many characters are in the string +* the bytes that make up a VALUE can themselves represent a separate, complete TLV8 structure. There is no limit on the number of "nested" TLV8 records that may be embedded in TLV8 Characteristic * a parser reading TLV8 records should silently ignore any TAG it is not expecting. It may be an error to omit a TAG that the parser requires, but it is not an error to include a TAG it does not recognize +* it is not possible to determine whether any given VALUE in a TLV8 record represents an unsigned integer, a string, an arbitrary series of bytes, a separate TLV8 structure, or something else entirely. The only identifying information for any given TLV8 record is the TAG number, which ranges from 0-255. There is no general schema or TLV8 protocol that maps TAG types to VALUE types. Rather, the TAG numbers are arbitrary and users must consult the documentation for each Characteristic to learn what TAG numbers are expected, and what their VALUEs are supposed to represent for that specific Characteristic * since HomeKit data transmissions are often represented in JSON, and JSON is a text-only format, HomeKit requires that TLV8 records are first encoded in base-64 when transmitting JSON to and from Controllers to Accessories. -Fortunately, HomeSpan includes a dedicated TLV8 library (see below) that takes care of everything above automatically, which enables you to read, create, and process TLV8 data without worrying about TLV8 records with more than 255 bytes, converting numerical values to little-endian, or encoding/decoding records into base-64. +Fortunately, HomeSpan includes a dedicated TLV8 library (see below) that automatically takes care of many of the above points, which enables you to read, create, and process TLV8 data without worrying about parsing TLV8 records with more than 255 bytes, converting numerical values to little-endian, or encoding/decoding records into base-64. From 520a4f3df0c4749c6fa97d2bbf4801cc0fe5c9d7 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Fri, 19 Apr 2024 22:05:02 -0500 Subject: [PATCH 063/154] Update TLV8.md --- docs/TLV8.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/docs/TLV8.md b/docs/TLV8.md index 5bb0b20..37d79d6 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -4,6 +4,8 @@ Most HomeKit Characteristics store a single numerical value or simple string. Ho In contrast, the TLV8 format is used extensively by HomeKit during the initial pairing process as well as whenever a new secure (verified) connection is established between HomeKit and a HomeSpan device. There are also a variety of Apple-defined Characteristics that use the TLV8 format to store and transmit multiple sets of values, each represented as byte-arrays of arbitrary length. +## Overview of TLV8 Format + The TLV8 format itself is quite simple. A TLV8 Characteristic comprises one or more byte-arrays (sometimes called TLV8 records), where the first byte in a record represents an identifying TAG (from 0-255), the second byte represents the LENGTH of the value, and the remaining LENGTH-bytes represent the VALUE itself (hence the acronym TLV). Notable points about the TLV8 format are as follows: * since the LENGTH is stored only as a single byte, VALUES requiring more than 255 bytes must be represented as sequential TLV8 records *with the same TAG* @@ -18,6 +20,39 @@ The TLV8 format itself is quite simple. A TLV8 Characteristic comprises one or Fortunately, HomeSpan includes a dedicated TLV8 library (see below) that automatically takes care of many of the above points, which enables you to read, create, and process TLV8 data without worrying about parsing TLV8 records with more than 255 bytes, converting numerical values to little-endian, or encoding/decoding records into base-64. +## *TLV8()* + +Creating an instance of HomeSpan's TLV8 **class** using the above constructor builds an empty TLV8 object into which you can add and process TLV8 records. TLV8 objects are instantiated as standard C++ linked-list containers derived from `std::list`, where *tlv8_t* is an opaque structure used to store individual TLV8 records[^1]. Note that many of the TLV8 methods below rely heavily on linked-list *iterators*.[^2] + +[^1]:The *tlv8_t* structure is opaque because in general you will not have to create or interact directly with the structure or its data. Note that in addition to the above TLV8-specific methods, you can use any `std::list` method with a TLV8 object if desired. + +[^2]:You do not need expert knowledge of C++ containers and iterators in order to use the TLV8 library, but a basic understanding of containers and iterators will make the library much easier to learn and enjoyable to use. + +The method for adding a generic TLV8 record to a TLV8 object is as follows: + +* `TLV8_it add(uint8_t tag, size_t len, const uint8_t *val)` + + * where *tag* is the TAG identifier for the record to add and *val* is a pointer to a byte-array containing *len* elements + * example: `TLV8 myTLV; uint8_t v[]={0x01, 0x05, 0xE3, 0x4C}; tlv.add(1, sizeof(v), v); + * setting *val* to NULL reserves *len* bytes of space for the TLV8 record within the TLV8 object, but does not copy any data + * this method returns an *iterator* to the resulting TLV8 record so you can reference the record at a later time if needed + +In addition to the above generic method suitable for any type of data, the following methods make it easier to add TLV8 records with specific, frequently-used types of data: + +* `TLV8_it add(uint8_t tag, uintXX_t val)` + * adds a TLV8 record containing a single, unsigned numeric value, *val* (i.e. uint8_t, uint16_t, uint32_t, or uint64_t) + +* `TLV8_it add(uint8_t tag, const char *val)` + * adds a TLV8 record containing all the non-null bytes of a null-terminated character string, *val* + +* `TLV8_it add(uint8_t tag, TLV8 &subTLV)` + * adds a TLV8 record containing all the bytes of an entire TLV8 object, *subTLV* + +* `TLV8_it add(uint8_t tag)` + * adds a zero-length TLV8 record containing nothing but a TAG identifer + + + From f96721b9486a828e21cc69804a2bcef0f3c0e981 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 20 Apr 2024 07:23:18 -0500 Subject: [PATCH 064/154] Update TLV8.md --- docs/TLV8.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/TLV8.md b/docs/TLV8.md index 37d79d6..2e973b7 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -51,6 +51,14 @@ In addition to the above generic method suitable for any type of data, the follo * `TLV8_it add(uint8_t tag)` * adds a zero-length TLV8 record containing nothing but a TAG identifer +Note that if you *add* consecutive records with the same TAG identifier, the TLV8 library will concatenate their data and combine into a single record. For example, `myTLV.add(1,13); myTLV.add(1,300)` will be combined to produce a single 3-byte recording containing the data 0x0D2C01, where the first byte represents from the number 13 and the second two bytes represent the number 300. This may have been your desired outcome, but likely not what you wanted to happen. + +Instead, to create two distinct records with the same tag value, simply interpose a zero-length record with a different TAG identifier between the two as a "separator" liek this: `myTLV.add(1,13); myTLV.add(255); myTLV.add(1,300);` Here we used a TAG identifer of 255 to represent the separator, but that choice is arbitrary, unless that TAG happens to be used by the Characteristic for something else (TAG identifiers of 0 or 255 are commonly used as separators). + + + + + From cd3b525dbb4aef5081bbc8e5982f6e52d595d61c Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 20 Apr 2024 07:54:41 -0500 Subject: [PATCH 065/154] Update TLV8.md --- docs/TLV8.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/TLV8.md b/docs/TLV8.md index 2e973b7..2c413fd 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -22,7 +22,9 @@ Fortunately, HomeSpan includes a dedicated TLV8 library (see below) that automat ## *TLV8()* -Creating an instance of HomeSpan's TLV8 **class** using the above constructor builds an empty TLV8 object into which you can add and process TLV8 records. TLV8 objects are instantiated as standard C++ linked-list containers derived from `std::list`, where *tlv8_t* is an opaque structure used to store individual TLV8 records[^1]. Note that many of the TLV8 methods below rely heavily on linked-list *iterators*.[^2] +Creating an instance of HomeSpan's TLV8 **class** using the above constructor builds an empty TLV8 object into which you can add and process TLV8 records. TLV8 objects are instantiated as standard C++ linked-list containers derived from `std::list`, where *tlv8_t* is an opaque structure used to store individual TLV8 records.[^1] + +Also, as shown below, many of the TLV8 methods utilize linked-list iterators. These are represented by the typedef *TLV8_it*.[^2] [^1]:The *tlv8_t* structure is opaque because in general you will not have to create or interact directly with the structure or its data. Note that in addition to the above TLV8-specific methods, you can use any `std::list` method with a TLV8 object if desired. From 15a0396897a208dcb4ccf6d92473d74d793cf238 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 20 Apr 2024 08:00:19 -0500 Subject: [PATCH 066/154] Update TLV8.md --- docs/TLV8.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/TLV8.md b/docs/TLV8.md index 2c413fd..431da26 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -55,7 +55,14 @@ In addition to the above generic method suitable for any type of data, the follo Note that if you *add* consecutive records with the same TAG identifier, the TLV8 library will concatenate their data and combine into a single record. For example, `myTLV.add(1,13); myTLV.add(1,300)` will be combined to produce a single 3-byte recording containing the data 0x0D2C01, where the first byte represents from the number 13 and the second two bytes represent the number 300. This may have been your desired outcome, but likely not what you wanted to happen. -Instead, to create two distinct records with the same tag value, simply interpose a zero-length record with a different TAG identifier between the two as a "separator" liek this: `myTLV.add(1,13); myTLV.add(255); myTLV.add(1,300);` Here we used a TAG identifer of 255 to represent the separator, but that choice is arbitrary, unless that TAG happens to be used by the Characteristic for something else (TAG identifiers of 0 or 255 are commonly used as separators). +Instead, to create two distinct records with the same tag value, simply interpose a zero-length record with a different TAG identifier between the two as a "separator" like this: `myTLV.add(1,13); myTLV.add(255); myTLV.add(1,300);` Here we used a TAG identifer of 255 to represent the separator, but that choice is arbitrary, unless that TAG happens to be used by the Characteristic for something else (TAG identifiers of 0 or 255 are commonly used as separators). + +The method for finding a TLV8 record within a TLV8 object that contains a specific TAG identifer is as follows: + +* `TLV8_it find(uint8_t tag)` + + * where *tag* is the TAG identifier you are seeking + * From 173ba036f06700a429d24f043b1535581ff8c09c Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 20 Apr 2024 09:05:43 -0500 Subject: [PATCH 067/154] Update TLV8.md --- docs/TLV8.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/docs/TLV8.md b/docs/TLV8.md index 431da26..8061965 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -37,7 +37,7 @@ The method for adding a generic TLV8 record to a TLV8 object is as follows: * where *tag* is the TAG identifier for the record to add and *val* is a pointer to a byte-array containing *len* elements * example: `TLV8 myTLV; uint8_t v[]={0x01, 0x05, 0xE3, 0x4C}; tlv.add(1, sizeof(v), v); * setting *val* to NULL reserves *len* bytes of space for the TLV8 record within the TLV8 object, but does not copy any data - * this method returns an *iterator* to the resulting TLV8 record so you can reference the record at a later time if needed + * this method returns a TLV8 *iterator* to the resulting TLV8 record so you can reference the record at a later time if needed In addition to the above generic method suitable for any type of data, the following methods make it easier to add TLV8 records with specific, frequently-used types of data: @@ -62,7 +62,17 @@ The method for finding a TLV8 record within a TLV8 object that contains a specif * `TLV8_it find(uint8_t tag)` * where *tag* is the TAG identifier you are seeking - * + * returns a TLV8 iterator to *first* record that matches; returns *end()* if no records match + +To restrict the search range to a limited set of records, add optional starting and ending iterators *it1* and *it2*: + +* `TLV8_it find(uint8_t tag [, TLV8_it it1 [, TLV8_it it2]])` + + * returns a TLV8 iterator to the *first* record within the range of iterators from *it1* to *it2* that matches the specified *tag* + * search range is inclusive of *it1* but exclusive of *it2* + * returns *it2* if no records match + * if *it2* is unspecified, default is *end()*; if *it1* is unspecified, default is *begin()* + * note `myTLV.find(tag)` is equivalent to `myTLV.find(tag, myTLV.begin(), myTLV.end())` From b4bfac54e7f135a1bb043a81502aba966303b278 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 20 Apr 2024 10:14:31 -0500 Subject: [PATCH 068/154] small tweaks to the TLV8 library for ease of use --- src/HAP.cpp | 22 +++++++++++----------- src/TLV8.cpp | 26 +++++++++++++------------- src/TLV8.h | 4 ++++ 3 files changed, 28 insertions(+), 24 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index e90d188..417fbcd 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -334,7 +334,7 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ return(0); } - int tlvState=(*itState).getVal(); + int tlvState=itState->getVal(); if(nAdminControllers()){ // error: Device already paired (i.e. there is at least one admin Controller). We should not be receiving any requests for Pair-Setup! LOG0("\n*** ERROR: Device already paired!\n\n"); @@ -363,7 +363,7 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ auto itMethod=iosTLV.find(kTLVType_Method); - if(iosTLV.len(itMethod)!=1 || (*itMethod).getVal()!=0){ // error: "Pair Setup" method must always be 0 to indicate setup without MiFi Authentification (HAP Table 5-3) + if(iosTLV.len(itMethod)!=1 || itMethod->getVal()!=0){ // error: "Pair Setup" method must always be 0 to indicate setup without MiFi Authentification (HAP Table 5-3) LOG0("\n*** ERROR: Pair 'Method' missing or not set to 0\n\n"); responseTLV.add(kTLVType_Error,tagError_Unavailable); // set Error=Unavailable tlvRespond(responseTLV); // send response to client @@ -404,7 +404,7 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ return(0); }; - srp->createSessionKey(*itPublicKey,(*itPublicKey).getLen()); // create session key, K, from client Public Key, A + srp->createSessionKey(*itPublicKey,itPublicKey->getLen()); // create session key, K, from client Public Key, A if(!srp->verifyClientProof(*itClientProof)){ // verify client Proof, M1 LOG0("\n*** ERROR: SRP Proof Verification Failed\n\n"); @@ -454,9 +454,9 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ // use SessionKey to decrypt encryptedData TLV with padded nonce="PS-Msg05" - TempBuffer decrypted((*itEncryptedData).getLen()-crypto_aead_chacha20poly1305_IETF_ABYTES); // temporary storage for decrypted data + TempBuffer decrypted(itEncryptedData->getLen()-crypto_aead_chacha20poly1305_IETF_ABYTES); // temporary storage for decrypted data - if(crypto_aead_chacha20poly1305_ietf_decrypt(decrypted, NULL, NULL, *itEncryptedData, (*itEncryptedData).getLen(), NULL, 0, (unsigned char *)"\x00\x00\x00\x00PS-Msg05", sessionKey)==-1){ + if(crypto_aead_chacha20poly1305_ietf_decrypt(decrypted, NULL, NULL, *itEncryptedData, itEncryptedData->getLen(), NULL, 0, (unsigned char *)"\x00\x00\x00\x00PS-Msg05", sessionKey)==-1){ LOG0("\n*** ERROR: Exchange-Request Authentication Failed\n\n"); responseTLV.add(kTLVType_Error,tagError_Authentication); // set Error=Authentication tlvRespond(responseTLV); // send response to client @@ -492,7 +492,7 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ // Concatenate iosDeviceX, IOS ID, and IOS PublicKey into iosDeviceInfo - TempBuffer iosDeviceInfo(iosDeviceX,iosDeviceX.len(),(uint8_t *)(*itIdentifier),(*itIdentifier).getLen(),(uint8_t *)(*itPublicKey),(*itPublicKey).getLen(),NULL); + TempBuffer iosDeviceInfo(iosDeviceX,iosDeviceX.len(),(uint8_t *)(*itIdentifier),itIdentifier->getLen(),(uint8_t *)(*itPublicKey),itPublicKey->getLen(),NULL); if(crypto_sign_verify_detached(*itSignature, iosDeviceInfo, iosDeviceInfo.len(), *itPublicKey) != 0){ // verify signature of iosDeviceInfo using iosDeviceLTPK LOG0("\n*** ERROR: LPTK Signature Verification Failed\n\n"); @@ -585,7 +585,7 @@ int HAPClient::postPairVerifyURL(uint8_t *content, size_t len){ return(0); } - int tlvState=(*itState).getVal(); + int tlvState=itState->getVal(); if(!nAdminControllers()){ // error: Device not yet paired - we should not be receiving any requests for Pair-Verify! LOG0("\n*** ERROR: Device not yet paired!\n\n"); @@ -670,7 +670,7 @@ int HAPClient::postPairVerifyURL(uint8_t *content, size_t len){ TempBuffer decrypted((*itEncryptedData).getLen()-crypto_aead_chacha20poly1305_IETF_ABYTES); // temporary storage for decrypted data - if(crypto_aead_chacha20poly1305_ietf_decrypt(decrypted, NULL, NULL, *itEncryptedData, (*itEncryptedData).getLen(), NULL, 0, (unsigned char *)"\x00\x00\x00\x00PV-Msg03", sessionKey)==-1){ + if(crypto_aead_chacha20poly1305_ietf_decrypt(decrypted, NULL, NULL, *itEncryptedData, itEncryptedData->getLen(), NULL, 0, (unsigned char *)"\x00\x00\x00\x00PV-Msg03", sessionKey)==-1){ LOG0("\n*** ERROR: Verify Authentication Failed\n\n"); responseTLV.add(kTLVType_State,pairState_M4); // set State= responseTLV.add(kTLVType_Error,tagError_Authentication); // set Error=Authentication @@ -771,7 +771,7 @@ int HAPClient::postPairingsURL(uint8_t *content, size_t len){ auto itState=iosTLV.find(kTLVType_State); auto itMethod=iosTLV.find(kTLVType_Method); - if(iosTLV.len(itState)!=1 || (*itState).getVal()!=1){ // missing STATE TLV + if(iosTLV.len(itState)!=1 || itState->getVal()!=1){ // missing STATE TLV LOG0("\n*** ERROR: Parirings 'State' is either missing or not set to \n\n"); badRequestError(); // return with 400 error, which closes connection return(0); @@ -783,7 +783,7 @@ int HAPClient::postPairingsURL(uint8_t *content, size_t len){ return(0); } - int tlvMethod=(*itMethod).getVal(); + int tlvMethod=itMethod->getVal(); responseTLV.add(kTLVType_State,pairState_M2); // all responses include State=M2 @@ -810,7 +810,7 @@ int HAPClient::postPairingsURL(uint8_t *content, size_t len){ return(0); } - tagError err=addController(*itIdentifier,*itPublicKey,(*itPermissions).getVal()); + tagError err=addController(*itIdentifier,*itPublicKey,itPermissions->getVal()); if(err!=tagError_None) responseTLV.add(kTLVType_Error,err); diff --git a/src/TLV8.cpp b/src/TLV8.cpp index 6c629ad..d3b6cff 100644 --- a/src/TLV8.cpp +++ b/src/TLV8.cpp @@ -107,7 +107,7 @@ TLV8_it TLV8::add(uint8_t tag, uint64_t val){ TLV8_it TLV8::find(uint8_t tag, TLV8_it it1, TLV8_it it2){ auto it=it1; - while(it!=it2 && (*it).getTag()!=tag) + while(it!=it2 && it->getTag()!=tag) it++; return(it); } @@ -139,12 +139,12 @@ size_t TLV8::pack(uint8_t *buf, size_t bufSize){ case 0: currentPackBuf=*currentPackIt; - endPackBuf=(*currentPackIt)+(*currentPackIt).getLen(); + endPackBuf=(*currentPackIt)+currentPackIt->getLen(); currentPackPhase=1; break; case 1: - *buf++=(*currentPackIt).getTag(); + *buf++=currentPackIt->getTag(); nBytes++; currentPackPhase=2; break; @@ -232,7 +232,7 @@ int TLV8::unpack(TLV8_it it){ if(it==end()) return(0); - return(unpack(*it,(*it).getLen())); + return(unpack(*it,it->getLen())); } ///////////////////////////////////// @@ -255,20 +255,20 @@ const char *TLV8::getName(uint8_t tag){ void TLV8::print(TLV8_it it1, TLV8_it it2){ while(it1!=it2){ - const char *name=getName((*it1).getTag()); + const char *name=getName(it1->getTag()); if(name) Serial.printf("%s",name); else - Serial.printf("%d",(*it1).getTag()); - Serial.printf("(%d) ",(*it1).getLen()); - for(int i=0;i<(*it1).getLen();i++) + Serial.printf("%d",it1->getTag()); + Serial.printf("(%d) ",it1->getLen()); + for(int i=0;igetLen();i++) Serial.printf("%02X",(*it1)[i]); - if((*it1).getLen()==0) + if(it1->getLen()==0) Serial.printf(" [null]"); - else if((*it1).getLen()<=4) - Serial.printf(" [%u]",(*it1).getVal()); - else if((*it1).getLen()<=8) - Serial.printf(" [%llu]",(*it1).getVal()); + else if(it1->getLen()<=4) + Serial.printf(" [%u]",it1->getVal()); + else if(it1->getLen()<=8) + Serial.printf(" [%llu]",it1->getVal()); Serial.printf("\n"); it1++; } diff --git a/src/TLV8.h b/src/TLV8.h index 2c08784..282ce97 100644 --- a/src/TLV8.h +++ b/src/TLV8.h @@ -56,6 +56,10 @@ class tlv8_t { return(val.get()[index]); } + uint8_t *get(){ + return(val.get()); + } + size_t getLen(){ return(len); } From e6c7637825e90579d7e4c9b95b87e414ea6f0f7c Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 20 Apr 2024 18:35:29 -0500 Subject: [PATCH 069/154] Update TLV8.md --- docs/TLV8.md | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/docs/TLV8.md b/docs/TLV8.md index 8061965..27af9c3 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -72,7 +72,28 @@ To restrict the search range to a limited set of records, add optional starting * search range is inclusive of *it1* but exclusive of *it2* * returns *it2* if no records match * if *it2* is unspecified, default is *end()*; if *it1* is unspecified, default is *begin()* - * note `myTLV.find(tag)` is equivalent to `myTLV.find(tag, myTLV.begin(), myTLV.end())` + * note `myTLV.find(tag)` is equivalent to `myTLV.find(tag, myTLV.begin(), myTLV.end())` + +Use of the C++ `auto` keyword is generally the best way to save the TVL8_it iterator that is returned from the `find()` and `add()` methods. For example, `auto myIT = myTLV.find(6)` sets *myIT* to an iterator pointing to the first TLV8 record in *myTLV* that has a TAG identifer of 6. + +The method for finding the LENGTH of the data VALUE stored in a particular TLV8 record is as follows: + +* `int len(TLV8_it it)` + * where *it* is an iterator pointing to a specific TLV8 record + * returns the length of the data VALUE stored in the associated record, which may be zero for a zero-LENGTH record + * returns -1 if *it* points to the *end()* of the TLV8 object + +A typical use of the `len()` method is to simultaneously check whether a TLV8 object contains a particular TAG identifier, and that the LENGTH of the TAG matches an expected value. For example, if a certain Characteristic requires a TLV8 record with a TAG identifer of 6 to contain a 32-byte registration number, you can perform the following check: + +```C++ +auto myIT = myTLV.find(6); +if(myTLV.len(myIT)!=32) + Serial.printf("Error: TAG 6 is either missing or of improper length\n"); +else + Serial.printf("TAG 6 containing 32 bytes of data has been found\n"); +``` + +Once you have the iterator *myIT* pointing to the desired TLV8 record, you can then use any of the methods below to access the VALUE stored in the TLV8 record: @@ -82,6 +103,3 @@ To restrict the search range to a limited set of records, add optional starting - - -🚧 From d9e9783dc1431f19e40cd700b312c67f2d7b3b0c Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 20 Apr 2024 19:01:50 -0500 Subject: [PATCH 070/154] Update TLV8.md --- docs/TLV8.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/TLV8.md b/docs/TLV8.md index 27af9c3..436348b 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -93,6 +93,29 @@ else Serial.printf("TAG 6 containing 32 bytes of data has been found\n"); ``` +The method for printing all of the records in a TLV8 object to the Serial Monitor is as follows: + +* `void print()` + + * prints all TLV8 records, one per line, to the Serial Monitor + * format of the output is: TAG(LENGTH) VALUE [NUMERIC], where + * TAG = the TAG identifer (0-255) + * LENGTH = length of the VALUE byte-array (may be zero) + * VALUE = a sequential list, in hexadecimal, of all the bytes in the VALUE byte-array (only displayed if LENGTH>0) + * NUMERIC = an unsigned-integer interpretation of the bytes in VALUE, assuming little-endian ordering + * this decimal value is only displayed if LENGTH<=8 + * if LENGTH=0, the word "null" is displayed instead + +To restrict the the printing range to a limited set of records, add optional starting and ending iterators *it1* and *it2*: + +* `void print(TLV8_it it1 [, TLV8_it it2])` + + * prints all TLV8 records between iterators *it1* and *it2* + * print range is inclusive of *it1* but exclusive of *it2* + * if *it2* is unspecified, prints only the record pointed to by *it1* + * note `myTLV.print()` is equivalent to `myTLV.print(myTLV.begin(), myTLV.end())` + + Once you have the iterator *myIT* pointing to the desired TLV8 record, you can then use any of the methods below to access the VALUE stored in the TLV8 record: From e0f593bc2e5aeacabcaf508dcb472d5931a66d84 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 20 Apr 2024 19:50:46 -0500 Subject: [PATCH 071/154] Update TLV8.md --- docs/TLV8.md | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/docs/TLV8.md b/docs/TLV8.md index 436348b..a225a13 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -22,13 +22,13 @@ Fortunately, HomeSpan includes a dedicated TLV8 library (see below) that automat ## *TLV8()* -Creating an instance of HomeSpan's TLV8 **class** using the above constructor builds an empty TLV8 object into which you can add and process TLV8 records. TLV8 objects are instantiated as standard C++ linked-list containers derived from `std::list`, where *tlv8_t* is an opaque structure used to store individual TLV8 records.[^1] +Creating an instance of HomeSpan's TLV8 **class** using the above constructor builds an empty TLV8 object into which you can add and process TLV8 records. TLV8 objects are instantiated as standard C++ linked-list containers derived from `std::list`, where *tlv8_t* is an opaque structure used to store individual TLV8 records.[^opaque] -Also, as shown below, many of the TLV8 methods utilize linked-list iterators. These are represented by the typedef *TLV8_it*.[^2] +Also, as shown below, many of the TLV8 methods utilize linked-list iterators. These are represented by the typedef *TLV8_it*.[^iterators] -[^1]:The *tlv8_t* structure is opaque because in general you will not have to create or interact directly with the structure or its data. Note that in addition to the above TLV8-specific methods, you can use any `std::list` method with a TLV8 object if desired. +[^opaque]:The *tlv8_t* structure is opaque because in general you will not have to create or interact directly with the structure or its data. Note that in addition to the above TLV8-specific methods, you can use any `std::list` method with a TLV8 object if desired. -[^2]:You do not need expert knowledge of C++ containers and iterators in order to use the TLV8 library, but a basic understanding of containers and iterators will make the library much easier to learn and enjoyable to use. +[^iterators]:You do not need expert knowledge of C++ containers and iterators in order to use the TLV8 library, but a basic understanding of containers and iterators will make the library much easier to learn and enjoyable to use. The method for adding a generic TLV8 record to a TLV8 object is as follows: @@ -115,6 +115,18 @@ To restrict the the printing range to a limited set of records, add optional sta * if *it2* is unspecified, prints only the record pointed to by *it1* * note `myTLV.print()` is equivalent to `myTLV.print(myTLV.begin(), myTLV.end())` +The output generated by `print()` can contain some very long lines, especially if the VALUE of some of the TLV8 records represents other complete TLV8 objects (known as sub-TLVs or "nested" TLVs). To recursively print all sub-TLV objects, use the following method: + +* `void printAll()` + * recursively prints all TLV8 records, one per line, to the Serial Monitor + * inspects each TLV8 record and tries to parse as if the record represented a sub-TLV object + * if parsing is successful, prints the record and then calls `printAll()` on the sub-TLV + * if not, prints the record and ends this branch of the recursion + * the format of each line is the same as that of `print()` except that TAG displays the full path of all TAGs through the branch + * note that the output can be very voluminous if your TLV8 object contains many levels of nested sub-TLVs + * warning: some care is required when interpretating the output[^subTLVs] + +[^subTLVs]:The `printAll()` method assumes that any VALUE that is consistent with the format of a sub-TLV must be a sub-TLV, even if its just a simple numeric value. For example, `add(10,65536)` yields a record with a TAG identifer of 10 and a 4-byte VALUE of 0x00000100. The `printAll()` method will display this record along with NUMERIC=65536, but it will also then interpret (and thus display) this VALUE as a sub-TLV containing one zero-length record with TAG identifier=0 and another zero-length record with TAG identifer=1, since the VALUE can be successfully parsed as such. Once you have the iterator *myIT* pointing to the desired TLV8 record, you can then use any of the methods below to access the VALUE stored in the TLV8 record: From e83df601b2a8b548a3d3d43062d049a6c1aa57fe Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 20 Apr 2024 20:08:53 -0500 Subject: [PATCH 072/154] Update TLV8.md --- docs/TLV8.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/docs/TLV8.md b/docs/TLV8.md index a225a13..4219c4f 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -118,6 +118,7 @@ To restrict the the printing range to a limited set of records, add optional sta The output generated by `print()` can contain some very long lines, especially if the VALUE of some of the TLV8 records represents other complete TLV8 objects (known as sub-TLVs or "nested" TLVs). To recursively print all sub-TLV objects, use the following method: * `void printAll()` + * recursively prints all TLV8 records, one per line, to the Serial Monitor * inspects each TLV8 record and tries to parse as if the record represented a sub-TLV object * if parsing is successful, prints the record and then calls `printAll()` on the sub-TLV @@ -128,7 +129,20 @@ The output generated by `print()` can contain some very long lines, especially i [^subTLVs]:The `printAll()` method assumes that any VALUE that is consistent with the format of a sub-TLV must be a sub-TLV, even if its just a simple numeric value. For example, `add(10,65536)` yields a record with a TAG identifer of 10 and a 4-byte VALUE of 0x00000100. The `printAll()` method will display this record along with NUMERIC=65536, but it will also then interpret (and thus display) this VALUE as a sub-TLV containing one zero-length record with TAG identifier=0 and another zero-length record with TAG identifer=1, since the VALUE can be successfully parsed as such. -Once you have the iterator *myIT* pointing to the desired TLV8 record, you can then use any of the methods below to access the VALUE stored in the TLV8 record: +TLV8 objects manage all of their internal memory requirements, and free up all resources and memory when they go out of scope or are otherwise deleted. However, if you need to "erase" all the contents of a TLV8 object but stil retain the object so you can re-fill with new data, use the following method: + +* `void wipe()` + * erases all TLV8 records and frees all associated memory + * leaves an empty TLV8 object ready for re-use + +## *TLV8_it()* + +Objects of type *TLV8_it* are iterators that point to a specific record in an TLV8 objects (or to *end()*). If you are using the TLV8 library correctly you should rarely, if ever, need to directly instatiate a TLV8_it using its constructor. Instead, simply use `auto` as described above. + +TLV8 iterators are the **most imporant** part of the TLV8 library, since a TLV8 iterator is used to directly access the data contained in the TLV8 record to which it points. The methods to access this data are as follows: + + + From aa9d64cb5839b6c1263ca309dfec7eb47d7656da Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 20 Apr 2024 20:17:02 -0500 Subject: [PATCH 073/154] Update TLV8.md --- docs/TLV8.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/TLV8.md b/docs/TLV8.md index 4219c4f..9f259cb 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -137,9 +137,11 @@ TLV8 objects manage all of their internal memory requirements, and free up all r ## *TLV8_it()* -Objects of type *TLV8_it* are iterators that point to a specific record in an TLV8 objects (or to *end()*). If you are using the TLV8 library correctly you should rarely, if ever, need to directly instatiate a TLV8_it using its constructor. Instead, simply use `auto` as described above. +Objects of type *TLV8_it* are iterators that point to a specific record in an TLV8 objects (or to *end()*). If you are using the TLV8 library correctly you should rarely, if ever, need to directly instantiate a TLV8_it using its constructor. Instead, simply use `auto` as described above. -TLV8 iterators are the **most imporant** part of the TLV8 library, since a TLV8 iterator is used to directly access the data contained in the TLV8 record to which it points. The methods to access this data are as follows: +**TLV8 iterators are the most imporant part of the TLV8 library. A TLV8 iterator provides the only means to directly access, read from, and write to, the VALUE element in the TLV8 record to which it points.** + +TLV8_it supports the following methods: From d51b74e1c1e9da19b15d5f0264d797e663aff3f8 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 21 Apr 2024 07:06:36 -0500 Subject: [PATCH 074/154] Update TLV8.md --- docs/TLV8.md | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/docs/TLV8.md b/docs/TLV8.md index 9f259cb..5dcb3c6 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -137,11 +137,31 @@ TLV8 objects manage all of their internal memory requirements, and free up all r ## *TLV8_it()* -Objects of type *TLV8_it* are iterators that point to a specific record in an TLV8 objects (or to *end()*). If you are using the TLV8 library correctly you should rarely, if ever, need to directly instantiate a TLV8_it using its constructor. Instead, simply use `auto` as described above. +Objects of type *TLV8_it* are iterators that point to specific records in a TLV8 object (or to *end()*). TLV8 iterators are how you access, read from, and write to, the VALUE element in any given TLV8 record, and are thus a critical part of the TLV8 library. However, if you are using the TLV8 library correctly you should rarely, if ever, need to directly instantiate a *TLV8_it* using its constructor. Instead, simply use the C++ `auto` keyword as noted above. -**TLV8 iterators are the most imporant part of the TLV8 library. A TLV8 iterator provides the only means to directly access, read from, and write to, the VALUE element in the TLV8 record to which it points.** +TLV8_it supports the following methods (a detailed example is provided below that illustrates the use of each method): -TLV8_it supports the following methods: +```C++ +TLV8 myTLV; // instantiates an empty TLV8 object + +myTLV.add(1,32000); // add a TLV8 record with TAG=1 and VALUE=32000 +auto it_A = myTLV.add(2,180); // add a TLV8 record with TAG=2 and VALUE=18, and save the iterator that is returned + +uint8_t v[]={0x01, 0x05, 0xE3, 0x4C}; // create a fixed array, v, of 4 bytes +myTLV.add(200,4,v); // add a TLV8 record with TAG=200 and copy all 4 bytes of array v into its VALUE + +myTLV.add(50,60000); // add a TLV8 record with TAG=50 and VALUE=60000 +myTLV.add(255); // add a zero-length TLV8 record with TAG=255 to act as separator +myTLV.add(50,120000); // add a TLV8 record with TAG=50 and VALUE=120000 +myTLV.add(255); // add a zero-length TLV8 record with TAG=255 to act as separator +auto it_B = myTLV.add(50,30000); // add a TLV8 record with TAG=50 and VALUE=30000, and save the iterator that is returned + +auto it_C = myTLV.find(200); // find an iterator to first TLV8 record with TAG=200; +auto it_D = myTLV.find(50); // find an iterator to first TLV8 record with TAG=50; +auto it_E = myTLV.find(200); // find an iterator to first TLV8 record with TAG=200; + +myTLV.print(); // prints the contents of myTLV to the Serial Monitor +``` From bfdf114ed35ebf77d936c508e4cd1597c6a5e559 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 21 Apr 2024 07:46:01 -0500 Subject: [PATCH 075/154] Update TLV8.md --- docs/TLV8.md | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/docs/TLV8.md b/docs/TLV8.md index 5dcb3c6..1a90601 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -6,16 +6,16 @@ In contrast, the TLV8 format is used extensively by HomeKit during the initial p ## Overview of TLV8 Format -The TLV8 format itself is quite simple. A TLV8 Characteristic comprises one or more byte-arrays (sometimes called TLV8 records), where the first byte in a record represents an identifying TAG (from 0-255), the second byte represents the LENGTH of the value, and the remaining LENGTH-bytes represent the VALUE itself (hence the acronym TLV). Notable points about the TLV8 format are as follows: +The TLV8 format itself is quite simple. A TLV8 object comprises one or more TLV8 *records*, where the first byte in a record represents an identifying TAG (from 0-255), the second byte represents the LENGTH of the value, and the remaining LENGTH-bytes represent the VALUE itself, which is always in the form of a *byte-array* (i.e. an array of 0 or more *uint8_t* elements). Notable points about the TLV8 format are as follows: * since the LENGTH is stored only as a single byte, VALUES requiring more than 255 bytes must be represented as sequential TLV8 records *with the same TAG* -* it is fine (and in fact common) for a Characteristic to include multiple records with the same TAG, except that they must be *separated by a record with a different TAG*, otherwise the parser reading the data will concatenate the VALUES from sequential records having the same TAG into a single record (as per above) +* it is fine (and in fact common) for a TLV8 object to include multiple records with the same TAG, except that they must be *separated by a record with a different TAG*, otherwise the parser reading the data will concatenate the VALUES from sequential records having the same TAG into a single record (as per above) * records representing a zero-LENGTH VALUE are allowed, and consist of only two bytes: a TAG and a zero (indicating a zero-length VALUE). TAGS with a zero-LENGTH VALUE are often used to separate multiple records having the same TAG -* if the VALUE stored in TLV8 format represents an integer, it should be presented in little endian format (least-significant byte first) and padded with trailing zeros as needed to bring the total LENGTH of the VALUE to either 1, 2, 4, or 8 bytes (representing uint8_t, uint16_t, uint32_t, and uint64_t values) -* if the VALUE stored in TLV8 format represents a string, it should not include the terminating null since the LENGTH tells you have many characters are in the string -* the bytes that make up a VALUE can themselves represent a separate, complete TLV8 structure. There is no limit on the number of "nested" TLV8 records that may be embedded in TLV8 Characteristic -* a parser reading TLV8 records should silently ignore any TAG it is not expecting. It may be an error to omit a TAG that the parser requires, but it is not an error to include a TAG it does not recognize -* it is not possible to determine whether any given VALUE in a TLV8 record represents an unsigned integer, a string, an arbitrary series of bytes, a separate TLV8 structure, or something else entirely. The only identifying information for any given TLV8 record is the TAG number, which ranges from 0-255. There is no general schema or TLV8 protocol that maps TAG types to VALUE types. Rather, the TAG numbers are arbitrary and users must consult the documentation for each Characteristic to learn what TAG numbers are expected, and what their VALUEs are supposed to represent for that specific Characteristic +* if the VALUE's byte-array is supposed to represent an single, unsigned integer, it should be arranged in little endian format (i.e. least-significant byte first) and padded with trailing zeros as needed to bring the total LENGTH of the VALUE to either 1, 2, 4, or 8 bytes (representing uint8_t, uint16_t, uint32_t, and uint64_t values) +* if the VALUE's byte-array is supposed to represent a string, it should not include the terminating null since the LENGTH tells you have many characters are in the string +* the bytes that make up a VALUE can themselves represent a separate, complete TLV8 object. There is no limit on the number of "sub-TLVs" that can be recursively nested in a "parent" TLV8 object +* a parser reading TLV8 records should silently ignore any TAG it is not expecting. It may be an error to omit a TAG that the parser requires, but it will be not an error to include a TAG it does not recognize +* it is **not** possible to unambigously determine whether the VALUE byte-array in a TLV8 record is supposed to represent an unsigned integer, a string, an arbitrary series of bytes, a sub-TLV object, or something else entirely. The only identifying information for any given TLV8 record is its TAG number, which ranges from 0-255. There is no general schema or TLV8 protocol that maps TAG types to VALUE types. Rather, the TAG numbers are arbitrary and users must consult the documentation for each Characteristic to learn what TAG numbers are expected, and what their VALUEs are supposed to represent for that specific Characteristic * since HomeKit data transmissions are often represented in JSON, and JSON is a text-only format, HomeKit requires that TLV8 records are first encoded in base-64 when transmitting JSON to and from Controllers to Accessories. Fortunately, HomeSpan includes a dedicated TLV8 library (see below) that automatically takes care of many of the above points, which enables you to read, create, and process TLV8 data without worrying about parsing TLV8 records with more than 255 bytes, converting numerical values to little-endian, or encoding/decoding records into base-64. @@ -137,10 +137,17 @@ TLV8 objects manage all of their internal memory requirements, and free up all r ## *TLV8_it()* -Objects of type *TLV8_it* are iterators that point to specific records in a TLV8 object (or to *end()*). TLV8 iterators are how you access, read from, and write to, the VALUE element in any given TLV8 record, and are thus a critical part of the TLV8 library. However, if you are using the TLV8 library correctly you should rarely, if ever, need to directly instantiate a *TLV8_it* using its constructor. Instead, simply use the C++ `auto` keyword as noted above. +Objects of type *TLV8_it* are iterators that point to specific records in a TLV8 object (or to *end()*). TLV8 iterators are used to access, read from, and write to, the data elements in any given TLV8 record, and are thus a critical part of the TLV8 library. However, if you are using the TLV8 library correctly you should rarely, if ever, need to directly instantiate a *TLV8_it* using its constructor. Instead, simply use the C++ `auto` keyword as noted above. TLV8_it supports the following methods (a detailed example is provided below that illustrates the use of each method): +* `uint8_t getTag()` + * returns the TAG identifier (0-255) of the TLV8 record + +* `size_t getLen()` + * returns the LENGTH of the VALUE byte-array of the TLV8 record + + ```C++ TLV8 myTLV; // instantiates an empty TLV8 object From 998bd873bcbae8a06e7ea79604f07f5d3ceeeaaa Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 21 Apr 2024 08:27:00 -0500 Subject: [PATCH 076/154] Update TLV8.md --- docs/TLV8.md | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/docs/TLV8.md b/docs/TLV8.md index 1a90601..c822864 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -139,13 +139,33 @@ TLV8 objects manage all of their internal memory requirements, and free up all r Objects of type *TLV8_it* are iterators that point to specific records in a TLV8 object (or to *end()*). TLV8 iterators are used to access, read from, and write to, the data elements in any given TLV8 record, and are thus a critical part of the TLV8 library. However, if you are using the TLV8 library correctly you should rarely, if ever, need to directly instantiate a *TLV8_it* using its constructor. Instead, simply use the C++ `auto` keyword as noted above. -TLV8_it supports the following methods (a detailed example is provided below that illustrates the use of each method): +TLV8_it supports the following methods: * `uint8_t getTag()` + * returns the TAG identifier (0-255) of the TLV8 record + * example: `uint8_t tag = myIT->getTag()` or, equivalently, `uint8_t tag = (*myIT).getTag()` * `size_t getLen()` + * returns the LENGTH of the VALUE byte-array of the TLV8 record + * example: `size_t len = myIT->getLen()` or, equivalently, `size_t len = (*myIT).getLen()` + +* `uint8_t *get()` + + * returns `uint8_t *` pointing to the first element of the VALUE byte-array of the TLV8 record + * for zero-LENGTH TLV8 records, the return value is NULL + * example: `uint8_t *v = myIT->get();` or, equivalently, `uint8_t *v = (*myIT).get();` + * the `(uint8_t *)` casting operator has been overloaded so you can also obtain this same `uint8_t *` pointer by simply dereferencing the iterator + * example: `auto myIT = myTLV.find(6); uint8_t *v = *myIT;` + * note this only works if the compiler can determine the need to auto-cast into a `uint8_t *` pointer based on the context of the code + +* `uint8_t get()[i]` + * returns the *ith* element of the VALUE byte-array + * example: `uint8_t n = myIT->get()[i]` or, equivalently, `uint8_t n = (*myIT).get()[i]` + * the subscript operator has also been overloaded so you can obtain the *ith* element by simply dereferencing the iterator + * example: `uint8_t n = (*myIT)[i]` + * note there is no range-checking so make sure *i* does not try to reference an element beyond the end of the VALUE byte-array ```C++ From b995a759079c82a1a0d867e1843f7ed12318479b Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 21 Apr 2024 08:30:07 -0500 Subject: [PATCH 077/154] Update TLV8.md --- docs/TLV8.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/TLV8.md b/docs/TLV8.md index c822864..b7b63ff 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -167,6 +167,9 @@ TLV8_it supports the following methods: * example: `uint8_t n = (*myIT)[i]` * note there is no range-checking so make sure *i* does not try to reference an element beyond the end of the VALUE byte-array +* `T getVal()` + * this template function interprets the VALUE byte-array as a single unsigned integer of type T + * example: ```C++ TLV8 myTLV; // instantiates an empty TLV8 object From ccda8f04d9ceec94eaf3fb4ea736014dd149ec37 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 21 Apr 2024 10:31:10 -0500 Subject: [PATCH 078/154] Update TLV8.md --- docs/TLV8.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/TLV8.md b/docs/TLV8.md index b7b63ff..a3b26c0 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -168,8 +168,13 @@ TLV8_it supports the following methods: * note there is no range-checking so make sure *i* does not try to reference an element beyond the end of the VALUE byte-array * `T getVal()` - * this template function interprets the VALUE byte-array as a single unsigned integer of type T - * example: + * this template function returns a single numeric value of type *T* on the assumption that the VALUE byte-array is storing an unsigned integer in little endian format + * *T* can be *uint8_t*, *uint16_t*, *uint32_t*, or *uint64_t* (if unspecified *T* defaults to *uint32_t*) + * example: `auto myIT = myTLV.add(50,60000); uint16_t n = myIT->getVal();` + * this method returns the correct numeric value as long as sizeof(*T*) >= LENGTH of the byte-array. For example: + * setting *T=uint64_t* with a VALUE byte-array containing 2 bytes returns the *correct* numeric value + * setting *T=uint16_t* with a VALUE byte-array containing 4 bytes return an *incorrect* numeric value + * this function returns zero for all zero-LENGTH TLV8 records ```C++ TLV8 myTLV; // instantiates an empty TLV8 object From fa25369ebbaf9972442621d5648f4e6056aa94aa Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 21 Apr 2024 12:37:51 -0500 Subject: [PATCH 079/154] Update TLV8.md --- docs/TLV8.md | 112 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 99 insertions(+), 13 deletions(-) diff --git a/docs/TLV8.md b/docs/TLV8.md index a3b26c0..04f1862 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -176,29 +176,115 @@ TLV8_it supports the following methods: * setting *T=uint16_t* with a VALUE byte-array containing 4 bytes return an *incorrect* numeric value * this function returns zero for all zero-LENGTH TLV8 records +### A detailed example using the above methods + +The following code: + ```C++ TLV8 myTLV; // instantiates an empty TLV8 object -myTLV.add(1,32000); // add a TLV8 record with TAG=1 and VALUE=32000 -auto it_A = myTLV.add(2,180); // add a TLV8 record with TAG=2 and VALUE=18, and save the iterator that is returned +myTLV.add(1,8700); // add a TLV8 record with TAG=1 and VALUE=8700 +auto it_A = myTLV.add(2,180); // add a TLV8 record with TAG=2 and VALUE=180, and save the iterator that is returned -uint8_t v[]={0x01, 0x05, 0xE3, 0x4C}; // create a fixed array, v, of 4 bytes -myTLV.add(200,4,v); // add a TLV8 record with TAG=200 and copy all 4 bytes of array v into its VALUE +uint8_t v[32]; // create a 32-byte array, v, and fill it with some data +for(int i=0;i<32;i++) + v[i]=i; + +myTLV.add(200,32,v); // add a TLV8 record with TAG=200 and copy all 32 bytes of array v into its VALUE -myTLV.add(50,60000); // add a TLV8 record with TAG=50 and VALUE=60000 -myTLV.add(255); // add a zero-length TLV8 record with TAG=255 to act as separator -myTLV.add(50,120000); // add a TLV8 record with TAG=50 and VALUE=120000 -myTLV.add(255); // add a zero-length TLV8 record with TAG=255 to act as separator -auto it_B = myTLV.add(50,30000); // add a TLV8 record with TAG=50 and VALUE=30000, and save the iterator that is returned +myTLV.add(50,60000); // add a TLV8 record with TAG=50 and VALUE=60000 +myTLV.add(255); // add a zero-length TLV8 record with TAG=255 to act as separator +myTLV.add(50,120000); // add a TLV8 record with TAG=50 and VALUE=120000 +myTLV.add(255); // add a zero-length TLV8 record with TAG=255 to act as separator +myTLV.add(50,180000); // add a TLV8 record with TAG=50 and VALUE=180000 +myTLV.add(255); // add a zero-length TLV8 record with TAG=255 to act as separator +auto it_B = myTLV.add(50,240000); // add a TLV8 record with TAG=50 and VALUE=240000, and save the iterator that is returned -auto it_C = myTLV.find(200); // find an iterator to first TLV8 record with TAG=200; -auto it_D = myTLV.find(50); // find an iterator to first TLV8 record with TAG=50; -auto it_E = myTLV.find(200); // find an iterator to first TLV8 record with TAG=200; +auto it_C = myTLV.find(50); // find an iterator to the first TLV8 record with TAG=50; +auto it_D = myTLV.find(50,std::next(it_C)); // find an iterator to the first TLV8 record with TAG=50 that occurs AFTER it_C; -myTLV.print(); // prints the contents of myTLV to the Serial Monitor +auto it_E = myTLV.find(200); // find an iterator to first TLV8 record with TAG=200; + +Serial.printf("results of myTLV.print():\n\n"); + +myTLV.print(); // print the contents of myTLV to the Serial Monitor + +Serial.printf("\n"); + +// print content of it_A: + +Serial.printf("it_A: TAG=%d, LENGTH=%d, Value=%d\n", it_A->getTag(), it_A->getLen(), it_A->getVal()); + +// print content of it_B using alternative syntax: + +Serial.printf("it_B: TAG=%d, LENGTH=%d, Value=%d\n", (*it_B).getTag(), (*it_B).getLen(), (*it_B).getVal()); + +// print contents of it_C and it_D, based on previous find() above: + +Serial.printf("it_C TAG=%d, LENGTH=%d, Value=%d\n", (*it_C).getTag(), (*it_C).getLen(), (*it_C).getVal()); +Serial.printf("it_D TAG=%d, LENGTH=%d, Value=%d\n", (*it_D).getTag(), (*it_D).getLen(), (*it_D).getVal()); + +// you can also use the results of find() directly without saving as a separate iterator, though this is computationally inefficient: + +if(myTLV.find(1)!=myTLV.end()) // check for match + Serial.printf("Found: TAG=%d, LENGTH=%d, Value=%d\n", myTLV.find(1)->getTag(), myTLV.find(1)->getLen(), myTLV.find(1)->getVal()); + +// sum up all the bytes in it_E: + +int sum=0; +for(int i=0; i < it_E->getLen(); i++) + sum+= (*it_E)[i]; + +Serial.printf("it_E TAG=%d, LENGTH=%d, Sum of all bytes = %d\n", (*it_E).getTag(), (*it_E).getLen(), sum); + +// create a "blank" TLV8 record with TAG=90 and space for 16 bytes: + +auto it_F = myTLV.add(90,16,NULL); + +// copy the first 16 bytes of it_E into it_F and print the record: + +memcpy(*it_F,*it_E,16); +myTLV.print(it_F); ``` +produces the following output: +```C++ +results of myTLV.print(): + +1(2) FC21 [8700] +2(1) B4 [180] +200(32) 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F +50(2) 60EA [60000] +255(0) [null] +50(4) C0D40100 [120000] +255(0) [null] +50(4) 20BF0200 [180000] +255(0) [null] +50(4) 80A90300 [240000] + +it_A: TAG=2, LENGTH=1, Value=180 +it_B: TAG=50, LENGTH=4, Value=240000 +it_C TAG=50, LENGTH=2, Value=60000 +it_D TAG=50, LENGTH=4, Value=120000 +Found: TAG=1, LENGTH=2, Value=8700 +it_E TAG=200, LENGTH=32, Sum of all bytes = 496 +90(16) 000102030405060708090A0B0C0D0E0F +``` + +## Reading and Writing TLV8 Characteristics + +As documented in the [API Reference](Reference.md#spancharacteristicvalue-boolean-nvsstore), the following *SpanCharacteristic* methods are used to read and write TLV8 objects to TLV8 Characteristics: + +* `getVal(TLV8 &tlv)` +* `getNewVal(TLV8 &tlv)` +* `setVal(TLV8 &tlv)` + +These are analagous to the `getVal()` and `setVal()` methods used for numerical-based Characteristics. + +Note that since TLV8 Characteristics are stored as base-64 encoded strings, you can use `getString()` to read the base-64 text, or `getData()` to decode the string into the full byte-array that represents the entire TLV8 object. + +Also, if you really don't want to use HomeSpan's TLV8 library to produce TLV8 objects, but instead prefer to use your own methods to create a TLV8-compliant byte-array, you can do so and then use `setData()` to save the byte-array you produced to the TLV8 Characteristic, which will perform the base-64 encoding. Or if you want to additionally perform your own base-64 encoding (why?) you can do so and then simply use `setString()` to save the resulting encoded string to the TLV8 Characteristic. From 4334789d504615f604b047380cac78fe17437fec Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 21 Apr 2024 12:39:39 -0500 Subject: [PATCH 080/154] Update TLV8.md --- docs/TLV8.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/TLV8.md b/docs/TLV8.md index 04f1862..2f03af8 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -288,7 +288,7 @@ Also, if you really don't want to use HomeSpan's TLV8 library to produce TLV8 ob - +[API Reference](https://github.com/HomeSpan/HomeSpan/blob/tlvwork/docs/Reference.md#spancharacteristicvalue-boolean-nvsstore) From 059c58ca463bb4808811d80aa27072da63c55c2a Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 21 Apr 2024 12:41:14 -0500 Subject: [PATCH 081/154] Update TLV8.md --- docs/TLV8.md | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/docs/TLV8.md b/docs/TLV8.md index 2f03af8..3a9508a 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -274,7 +274,7 @@ it_E TAG=200, LENGTH=32, Sum of all bytes = 496 ## Reading and Writing TLV8 Characteristics -As documented in the [API Reference](Reference.md#spancharacteristicvalue-boolean-nvsstore), the following *SpanCharacteristic* methods are used to read and write TLV8 objects to TLV8 Characteristics: +As documented in the [API Reference](Reference.md), the following *SpanCharacteristic* methods are used to read and write TLV8 objects to TLV8 Characteristics: * `getVal(TLV8 &tlv)` * `getNewVal(TLV8 &tlv)` @@ -286,12 +286,8 @@ Note that since TLV8 Characteristics are stored as base-64 encoded strings, you Also, if you really don't want to use HomeSpan's TLV8 library to produce TLV8 objects, but instead prefer to use your own methods to create a TLV8-compliant byte-array, you can do so and then use `setData()` to save the byte-array you produced to the TLV8 Characteristic, which will perform the base-64 encoding. Or if you want to additionally perform your own base-64 encoding (why?) you can do so and then simply use `setString()` to save the resulting encoded string to the TLV8 Characteristic. - - -[API Reference](https://github.com/HomeSpan/HomeSpan/blob/tlvwork/docs/Reference.md#spancharacteristicvalue-boolean-nvsstore) - - - - +--- + +[↩️](../README.md) Back to the Welcome page From 04cb07a0fc999c2066abfd7dab68fd9f2e5929af Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 21 Apr 2024 12:45:27 -0500 Subject: [PATCH 082/154] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index bb379ad..34e97ce 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,7 @@ HomeSpan includes the following documentation: * [HomeSpan SpanPoint](docs/NOW.md) - facilitates point-to-point, bi-directional communication between ESP32 Devices using ESP-NOW * [HomeSpan Television Services](docs/TVServices.md) - how to use HomeKit's undocumented Television Services and Characteristics * [HomeSpan Message Logging](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 TLV8 Characteristics](docs/TLV8.md) - classes and methods for creating TLV8 objects to use with TLV8-based Characteristics * [HomeSpan Device Cloning](docs/Cloning.md) - seamlessly swap a broken device for a new one without needing to re-pair and lose HomeKit automations * [HomeSpan Projects](https://github.com/topics/homespan) - real-world applications of the HomeSpan Library * [HomeSpan FAQ](docs/FAQ.md) - answers to frequently-asked questions From 4e5d03f63f41ac1d96225d2ceea0a3ddde9823ef Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 21 Apr 2024 15:18:22 -0500 Subject: [PATCH 083/154] renamed CUSTOM_CHAR_TLV() macro to CUSTOM_CHAR_TLV8() macro --- src/Span.h | 10 +++++++--- src/src.ino | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Span.h b/src/Span.h index 23bba59..e3893a7 100644 --- a/src/Span.h +++ b/src/Span.h @@ -614,9 +614,9 @@ namespace Characteristic { #define CUSTOM_CHAR_DATA(NAME,UUID,PERMISISONS) \ HapChar _CUSTOM_##NAME {#UUID,#NAME,(PERMS)(PERMISISONS),DATA,true}; \ - namespace Characteristic { struct NAME : SpanCharacteristic { NAME(const char * val="AA==", boolean nvsStore=false) : SpanCharacteristic {&_CUSTOM_##NAME,true} { init(val,nvsStore); } }; } + namespace Characteristic { struct NAME : SpanCharacteristic { NAME(const char * val="", boolean nvsStore=false) : SpanCharacteristic {&_CUSTOM_##NAME,true} { init(val,nvsStore); } }; } -#define CUSTOM_CHAR_TLV(NAME,UUID,PERMISISONS) \ +#define CUSTOM_CHAR_TLV8(NAME,UUID,PERMISISONS) \ HapChar _CUSTOM_##NAME {#UUID,#NAME,(PERMS)(PERMISISONS),TLV_ENC,true}; \ namespace Characteristic { struct NAME : SpanCharacteristic { NAME(const char * val="", boolean nvsStore=false) : SpanCharacteristic {&_CUSTOM_##NAME,true} { init(val,nvsStore); } }; } @@ -632,7 +632,11 @@ namespace Characteristic { #define CUSTOM_CHAR_DATA(NAME,UUID,PERMISISONS) \ extern HapChar _CUSTOM_##NAME; \ - namespace Characteristic { struct NAME : SpanCharacteristic { NAME(const char * val="AA==", boolean nvsStore=false) : SpanCharacteristic {&_CUSTOM_##NAME,true} { init(val,nvsStore); } }; } + namespace Characteristic { struct NAME : SpanCharacteristic { NAME(const char * val="", boolean nvsStore=false) : SpanCharacteristic {&_CUSTOM_##NAME,true} { init(val,nvsStore); } }; } + +#define CUSTOM_CHAR_TLV8(NAME,UUID,PERMISISONS) \ + extern HapChar _CUSTOM_##NAME; \ + namespace Characteristic { struct NAME : SpanCharacteristic { NAME(const char * val="", boolean nvsStore=false) : SpanCharacteristic {&_CUSTOM_##NAME,true} { init(val,nvsStore); } }; } #endif diff --git a/src/src.ino b/src/src.ino index 4f57226..bfc4942 100644 --- a/src/src.ino +++ b/src/src.ino @@ -27,7 +27,7 @@ #include "HomeSpan.h" -CUSTOM_CHAR_TLV(DisplayOrder,136,PR+EV); +CUSTOM_CHAR_TLV8(DisplayOrder,136,PR+EV); CUSTOM_CHAR_DATA(TestData,333,PR+EV); struct HomeSpanTV : Service::Television { From 050a30f72ccaad2d0468ab693f89729ca9f562fe Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 21 Apr 2024 21:16:08 -0500 Subject: [PATCH 084/154] 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 93ea7b7..0ec35cf 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -627,8 +627,9 @@ 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)* ### *CUSTOM_CHAR_DATA(name,uuid,perms)* +### *CUSTOM_CHAR_TLV8(name,uuid,perms)* -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. The third form is used for DATA-based (i.e. byte-array) 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): +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; the third form is used for DATA-based (i.e. byte-array) Characteristics; and the fourth form is used for TLV8-based (i.e. *structured* byte-array) 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. Use UTF-8 coded string for non-ASCII characters. * *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 0be3b0cd1b4a768e9a110084b98c14b3769161e4 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 21 Apr 2024 21:23:44 -0500 Subject: [PATCH 085/154] 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 0ec35cf..1bf1bb2 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -478,7 +478,7 @@ This is a **base class** from which all HomeSpan Characteristics are derived, an * similar to `setVal()`, but exclusively used for TLV8 Characteristics * updates the Characteristic by packing the TLV8 structure *tlv* into a byte array and then encoding it in base-64 for storage as a string -* see the [TLV8 Characteristics](TLV8.md) page for complete details on how to read/process/create TLV8 Characteristics +* see the [TLV8 Characteristics](TLV8.md) page for complete details on how to read/process/create TLV8 Objects using HomeSpan's TLV8 Library. #### The following methods are supported for all Characteristics: From c7f67225d6276c96dcf03b04c6f46ef476dd9f7f Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 21 Apr 2024 22:08:04 -0500 Subject: [PATCH 086/154] Update TLV8.md --- docs/TLV8.md | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/docs/TLV8.md b/docs/TLV8.md index 3a9508a..de79755 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -274,17 +274,35 @@ it_E TAG=200, LENGTH=32, Sum of all bytes = 496 ## Reading and Writing TLV8 Characteristics -As documented in the [API Reference](Reference.md), the following *SpanCharacteristic* methods are used to read and write TLV8 objects to TLV8 Characteristics: +As fully documented in the [API Reference](Reference.md), the following *SpanCharacteristic* methods are used to read and write TLV8 objects to TLV8 Characteristics: -* `getVal(TLV8 &tlv)` -* `getNewVal(TLV8 &tlv)` -* `setVal(TLV8 &tlv)` +* `getTLV(TLV8 &tlv)` +* `getNewTLV(TLV8 &tlv)` +* `setTLV(TLV8 &tlv)` -These are analagous to the `getVal()` and `setVal()` methods used for numerical-based Characteristics. +These are analagous to the `getVal()`, `getNewVal()` and `setVal()` methods used for numerical-based Characteristics. -Note that since TLV8 Characteristics are stored as base-64 encoded strings, you can use `getString()` to read the base-64 text, or `getData()` to decode the string into the full byte-array that represents the entire TLV8 object. +Note that using the above methods *do not* require you to create a separate byte-array that splits records into chunks of 255 bytes, nor does it require you to encode or decode anything into base-64. Rather, you directly read and write to and from the Characteristic into a TLV8 object. -Also, if you really don't want to use HomeSpan's TLV8 library to produce TLV8 objects, but instead prefer to use your own methods to create a TLV8-compliant byte-array, you can do so and then use `setData()` to save the byte-array you produced to the TLV8 Characteristic, which will perform the base-64 encoding. Or if you want to additionally perform your own base-64 encoding (why?) you can do so and then simply use `setString()` to save the resulting encoded string to the TLV8 Characteristic. +However, since TLV8 Characteristics are stored as base-64 encoded strings, you can nevertheless use `getString()` to read the base-64 text, or `getData()` to decode the string into the full byte-array that represents the entire TLV8 object, if you desire. + +Also, if you really don't want to use HomeSpan's TLV8 library to produce TLV8 objects, but instead prefer to use your own methods to create a TLV8-compliant byte-array, you can do so and then use `setData()` to save the byte-array you produced to the TLV8 Characteristic, which will perform the base-64 encoding for you. Or, if you want to additionally perform your own base-64 encoding (why?), you can do so and then simply use `setString()` to save the resulting encoded text to the TLV8 Characteristic. + +### Write-Response Requests + +For most Characteristics, when the Home App sends HomeSpan a request to update a value, it is instructing HomeSpan to perform some sort of action, such as "change the brightness of a lightbulb to 30%" or "change the target state of the door to open." The only feedback the Home App expects to receive in response to such requests is basically an "OK" or "NOT OKAY" message, which is the purpose of the boolean return value in the `update()` method for every Service. + +However, sometimes the Home App sends HomeSpan a request for information, rather than a direct instruction to perform a task. In such instances, rather than sending back just an OK/NOT-OKAY message, the Home App expects the Accessory device to update the value of the Characteristic *not* with the new value that the Home App sent, but rather with the information it requested. It then expects this information to be transmitted back to the Home App at the conclusion of the update. + +This procedure is known as a "Write-Response Request", and it is the primary purpose for having TLV8 Characteristics, since TLV8 objects are ideal for storing structured information. + +Though the procedure is complex, HomeSpan fortunately handles all of the protocol details. The only thing you need to do when working with a TLV8 Characteristic that implements Write-Response Requests is: + +* check to see if the Characteristic is updated when inside the `update()` loop of a Service; +* if so, create a new TLV8 object and use `getNewTLV()` to load the contents of the updated Characterstic into that TLV8 object; +* use the TLV8 Library to read through the TAGS and VALUES in the TLV8 to determine (based on the specs for the Characteristic) what data the Home App is conveying and what information it wants returned; +* create a second TLV8 object and add the appropriate TAG and VALUES needed to respond to the information request (again, based on the on the specs for the Characteristic); +* use `setVal()` to update the Characteristic with the second TLV8 object --- From 20f74aa968f837d6f17a104a69c2796b8f1d013a Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Mon, 22 Apr 2024 20:43:11 -0500 Subject: [PATCH 087/154] Update TLV8.md --- docs/TLV8.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/TLV8.md b/docs/TLV8.md index de79755..85e853c 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -294,15 +294,17 @@ For most Characteristics, when the Home App sends HomeSpan a request to update a However, sometimes the Home App sends HomeSpan a request for information, rather than a direct instruction to perform a task. In such instances, rather than sending back just an OK/NOT-OKAY message, the Home App expects the Accessory device to update the value of the Characteristic *not* with the new value that the Home App sent, but rather with the information it requested. It then expects this information to be transmitted back to the Home App at the conclusion of the update. -This procedure is known as a "Write-Response Request", and it is the primary purpose for having TLV8 Characteristics, since TLV8 objects are ideal for storing structured information. +This procedure is known as a "Write-Response Request", and it is the primary purpose for having TLV8 Characteristics, since TLV8 objects are ideal for storing structured information. -Though the procedure is complex, HomeSpan fortunately handles all of the protocol details. The only thing you need to do when working with a TLV8 Characteristic that implements Write-Response Requests is: +Though the procedure is complex, HomeSpan handles all of the protocol details. You only need to focus on reading the TLV8 Characteristic and updating it with the required TLV8 response as follows: -* check to see if the Characteristic is updated when inside the `update()` loop of a Service; -* if so, create a new TLV8 object and use `getNewTLV()` to load the contents of the updated Characterstic into that TLV8 object; -* use the TLV8 Library to read through the TAGS and VALUES in the TLV8 to determine (based on the specs for the Characteristic) what data the Home App is conveying and what information it wants returned; -* create a second TLV8 object and add the appropriate TAG and VALUES needed to respond to the information request (again, based on the on the specs for the Characteristic); -* use `setVal()` to update the Characteristic with the second TLV8 object +* first, from within the `update()` loop of the applicable Service, check to see if the Home App has requested an update to the TLV8 Characteristic; +* if so, create a new TLV8 object and use `getNewTLV()` to load the contents of the updated Characteristic into that TLV8 object; +* then, use the TLV8 library methods described above to read through the TAGS and VALUES in the TLV8 object to determine what data the Home App is conveying and what information it wants returned (based on the specs for the Characteristic); +* next, create a *second* TLV8 object and use the TLV8 library methods above to create the appropriate TAGS and VALUES needed to respond to the information request (again, based on the on the specs for the Characteristic); +* finally, use `setVal()` to update the TLV8 Characteristic with the second TLV8 object + +HomeSpan will automatically send the new TLV8 data you placed in the TLV8 Characterstic back to the Home App in its response at the conclusion of the `update()` loop. --- From 3db4676b7d697fed1519c5616630bc4eac9eadd1 Mon Sep 17 00:00:00 2001 From: Gregg Date: Mon, 22 Apr 2024 22:49:55 -0500 Subject: [PATCH 088/154] Created initial Example 22 - TLV8_Characteristics Requires some debugging... --- .../22-TLV8_Characteristics.ino | 161 ++++++++++++++++++ src/Characteristics.h | 1 + src/Span.h | 2 + src/src.ino | 1 - 4 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 examples/22-TLV8_Characteristics/22-TLV8_Characteristics.ino diff --git a/examples/22-TLV8_Characteristics/22-TLV8_Characteristics.ino b/examples/22-TLV8_Characteristics/22-TLV8_Characteristics.ino new file mode 100644 index 0000000..5f81568 --- /dev/null +++ b/examples/22-TLV8_Characteristics/22-TLV8_Characteristics.ino @@ -0,0 +1,161 @@ +/********************************************************************************* + * MIT License + * + * Copyright (c) 2020-2024 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 24: Demonstrates the use of the TLV8 Library // +// by implementing DisplayOrder, an optional // +// TLV8 Characteristic used with the TV Service // +// to sets the order in which TV Inputs are // +// displayed for selection in the Home App // +// // +//////////////////////////////////////////////////////////////// + +#include "HomeSpan.h" + +// NOTE: Please see the "Other Examples -> Television" sketch for complete details on how to implement a Television Service. The focus +// of this sketch is solely to demonstrate how to use the TLV8 Library to create TLV8 data for use with the DisplayOrder Characteristic. + +// First we define a simple Television Input Source Service with only the Identifer and Name Characteristics + +struct TVInput : Service::InputSource { + + SpanCharacteristic *inputID; + SpanCharacteristic *inputName; + + TVInput(uint32_t id, const char *name) : Service::InputSource() { + + inputID = new Characteristic::Identifier(id); + inputName = new Characteristic::ConfiguredName(name); + new Characteristic::IsConfigured(1); + } +}; + +// Next we define a very simple Television Service + +struct HomeSpanTV : Service::Television { + + SpanCharacteristic *active = new Characteristic::Active(0,true); // TV ON/OFF (set to OFF at start-up) + SpanCharacteristic *activeID = new Characteristic::ActiveIdentifier(30,true); // Set TV to input source with ID=30 + + // SpanCharacteristic *displayOrder = new Characteristic::DisplayOrder(); // <-- This is the new TLV8 Characteristic. Note the constructor has no argument + + HomeSpanTV() : Service::Television() { + + // Unlike the constructors for numerical and string-based Characteristics (such as ActiveIdentifier and ConfiguredName), + // we cannot set the initial value of TLV8 Characteristics during construction, but must instead first instantiate the + // Characteristic (as we did above), then build a TLV8 object with the information required by the TLV8 Characteristic, and + // then use setTLV() to load the completed TLV8 object into the Characteristic's value. + + // The (undocumented by Apple!) TLV8 specifications for the DisplayOrder Characteristic are as follows: + + // TAG NAME FORMAT DESCRIPTION + // ---- ------------- ------ -------------------------------------------- + // 0x01 inputSourceID uint32 ID of the Input Source to be displayed first + // 0x00 separator none Empty element to separate the inputSourceIDs + // 0x01 inputSourceID uint32 ID of the Input Source to be displayed second + // 0x00 separator none Empty element to separate the inputSourceIDs + // 0x01 inputSourceID uint32 ID of the Input Source to be displayed third + // 0x00 separator none Empty element to separate the inputSourceIDs + // etc... + + // To start, instantiate a new TLV8 object + + TLV8 orderTLV; // creates an empty TLV8 object + + // Next, fill it with TAGS and VALUES based on the above specification. The easiest, + // though not necessarily most elegant, way to do this is as follows: + + orderTLV.add(1,10); // TAG=1, VALUE=ID of first Input Source to be displayed + orderTLV.add(0); // TAG=0 (no value) + orderTLV.add(1,20); // TAG=1, VALUE=ID of the second Input Source to be displayed + orderTLV.add(0); // TAG=0 (no value) + orderTLV.add(1,50); // TAG=1, VALUE=ID of the third Input Source to be displayed + orderTLV.add(0); // TAG=0 (no value) + orderTLV.add(1,30); // TAG=1, VALUE=ID of the fourth Input Source to be displayed + orderTLV.add(0); // TAG=0 (no value) + orderTLV.add(1,40); // TAG=1, VALUE=ID of the fifth Input Source to be displayed + + // Based on the above structure, we expect the Home App to display our input sources based on their IDs + // in the following order: 100, 200, 500, 300, 400. These IDs must of course match the IDs you choose + // for your input sources when you create them at the end of this sketch in setup() + + // The final step is to load this TLV8 object into the DisplayOrder Characteristic + + // displayOrder->setTLV(orderTLV); // set the "value" of DisplayOrder to be the orderTLV object we just created + + // That's it - you've created your first TLV8 Characteristic! + } + + // Below we define the usual update() loop. There is nothing "TLV-specific" about this part of the code + + boolean update() override { + + if(active->updated()){ + Serial.printf("Set TV Power to: %s\n",active->getNewVal()?"ON":"OFF"); + } + + if(activeID->updated()){ + Serial.printf("Set Input Source to ID=%d\n",activeID->getNewVal()); + } + + return(true); + } +}; + +/////////////////////////////// + +void setup() { + + Serial.begin(115200); + + homeSpan.setLogLevel(2); + + homeSpan.begin(Category::Television,"HomeSpan Television"); + + SPAN_ACCESSORY(); + + (new HomeSpanTV()) // Define a Television Service and link in the InputSources! + ->addLink(new TVInput(10,"Xfinity")) + ->addLink(new TVInput(20,"BlueRay Disc")) + ->addLink(new TVInput(30,"Amazon Prime")) + ->addLink(new TVInput(40,"Netflix")) + ->addLink(new TVInput(50,"Hulu")) + ; + +} + +////////////////////////////////////// + +void loop(){ + homeSpan.poll(); +} + +////////////////////////////////////// diff --git a/src/Characteristics.h b/src/Characteristics.h index 679ea9b..52d43aa 100644 --- a/src/Characteristics.h +++ b/src/Characteristics.h @@ -114,6 +114,7 @@ struct HapCharacteristics { HAPCHAR( CurrentTemperature, 11, PR+EV, FLOAT, false ); HAPCHAR( CurrentTiltAngle, C1, PR+EV, INT, false ); HAPCHAR( CurrentVisibilityState, 135, PR+EV, UINT8, true ); + HAPCHAR( DisplayOrder, 136, PR+EV, TLV_ENC, true ); HAPCHAR( FilterLifeLevel, AB, PR+EV, FLOAT, false ); HAPCHAR( FilterChangeIndication, AC, PR+EV, UINT8, true ); HAPCHAR( FirmwareRevision, 52, PR+EV, STRING, true ); diff --git a/src/Span.h b/src/Span.h index e3893a7..624f0f7 100644 --- a/src/Span.h +++ b/src/Span.h @@ -429,6 +429,7 @@ namespace Service { CREATE_SERV(Television,D8) // Defines a TV. Optional Linked Services: InputSource and TelevisionSpeaker. REQ(Active); OPT(ActiveIdentifier); + OPT(DisplayOrder); OPT(RemoteKey); OPT(PowerModeSelection); OPT(ConfiguredName); @@ -513,6 +514,7 @@ namespace Characteristic { CREATE_CHAR(double,CurrentRelativeHumidity,0,0,100); // current humidity measured as a percentage CREATE_CHAR(double,CurrentTemperature,0,0,100); // current temperature measured in Celsius CREATE_CHAR(int,CurrentTiltAngle,0,-90,90); // current angle (in degrees) of slats from fully up or left (-90) to fully open (0) to fully down or right (90) + CREATE_CHAR(const char *,DisplayOrder,"",0,1); // specifies the order in which the TV inputs are displayed for selection in the Home App CREATE_CHAR(double,FilterLifeLevel,100,0,100); // measured as a percentage of remaining life CREATE_CHAR(uint8_t,FilterChangeIndication,0,0,1,NO_CHANGE_NEEDED,CHANGE_NEEDED); // indicates state of filter CREATE_CHAR(const char *,FirmwareRevision,"1.0.0",0,1); // must be in form x[.y[.z]] - informational only diff --git a/src/src.ino b/src/src.ino index bfc4942..44be4ec 100644 --- a/src/src.ino +++ b/src/src.ino @@ -27,7 +27,6 @@ #include "HomeSpan.h" -CUSTOM_CHAR_TLV8(DisplayOrder,136,PR+EV); CUSTOM_CHAR_DATA(TestData,333,PR+EV); struct HomeSpanTV : Service::Television { From 3273f7f24d43824a5aae3ac66a4873d33c830f63 Mon Sep 17 00:00:00 2001 From: Gregg Date: Tue, 23 Apr 2024 06:41:44 -0500 Subject: [PATCH 089/154] Completed Example 22-TLV8_Characteristics Must update Television.md documentation as well as Television Example to reflect recent changes to the Characteristics Apple made - there seems to be less flexibility in what needs to be defined to use the input sources. Also need to add DisplayOrder Characteristic to TV documentation. --- .../22-TLV8_Characteristics.ino | 113 +++++++++--------- 1 file changed, 57 insertions(+), 56 deletions(-) diff --git a/examples/22-TLV8_Characteristics/22-TLV8_Characteristics.ino b/examples/22-TLV8_Characteristics/22-TLV8_Characteristics.ino index 5f81568..43b1073 100644 --- a/examples/22-TLV8_Characteristics/22-TLV8_Characteristics.ino +++ b/examples/22-TLV8_Characteristics/22-TLV8_Characteristics.ino @@ -33,7 +33,7 @@ // Example 24: Demonstrates the use of the TLV8 Library // // by implementing DisplayOrder, an optional // // TLV8 Characteristic used with the TV Service // -// to sets the order in which TV Inputs are // +// to set the order in which TV Inputs are // // displayed for selection in the Home App // // // //////////////////////////////////////////////////////////////// @@ -43,7 +43,7 @@ // NOTE: Please see the "Other Examples -> Television" sketch for complete details on how to implement a Television Service. The focus // of this sketch is solely to demonstrate how to use the TLV8 Library to create TLV8 data for use with the DisplayOrder Characteristic. -// First we define a simple Television Input Source Service with only the Identifer and Name Characteristics +// First we define a simple Television Input Source Service struct TVInput : Service::InputSource { @@ -54,80 +54,81 @@ struct TVInput : Service::InputSource { inputID = new Characteristic::Identifier(id); inputName = new Characteristic::ConfiguredName(name); - new Characteristic::IsConfigured(1); + new Characteristic::IsConfigured(Characteristic::IsConfigured::CONFIGURED); + new Characteristic::CurrentVisibilityState(Characteristic::CurrentVisibilityState::VISIBLE); } }; -// Next we define a very simple Television Service +// Next we define a simple Television Service struct HomeSpanTV : Service::Television { - SpanCharacteristic *active = new Characteristic::Active(0,true); // TV ON/OFF (set to OFF at start-up) - SpanCharacteristic *activeID = new Characteristic::ActiveIdentifier(30,true); // Set TV to input source with ID=30 - - // SpanCharacteristic *displayOrder = new Characteristic::DisplayOrder(); // <-- This is the new TLV8 Characteristic. Note the constructor has no argument + SpanCharacteristic *active = new Characteristic::Active(0); + SpanCharacteristic *activeID = new Characteristic::ActiveIdentifier(10); + + SpanCharacteristic *displayOrder = new Characteristic::DisplayOrder(); // <-- This is the new TLV8 Characteristic. Note the constructor has no argument - HomeSpanTV() : Service::Television() { + HomeSpanTV() : Service::Television() { - // Unlike the constructors for numerical and string-based Characteristics (such as ActiveIdentifier and ConfiguredName), - // we cannot set the initial value of TLV8 Characteristics during construction, but must instead first instantiate the - // Characteristic (as we did above), then build a TLV8 object with the information required by the TLV8 Characteristic, and - // then use setTLV() to load the completed TLV8 object into the Characteristic's value. + // Unlike the constructors for numerical and string-based Characteristics (such as ActiveIdentifier and ConfiguredName), + // we cannot set the initial value of TLV8 Characteristics during construction, but must instead first instantiate the + // Characteristic (as we did above), then build a TLV8 object with the information required by the TLV8 Characteristic, and + // then use setTLV() to load the completed TLV8 object into the Characteristic's value. - // The (undocumented by Apple!) TLV8 specifications for the DisplayOrder Characteristic are as follows: + // The (undocumented by Apple!) TLV8 specifications for the DisplayOrder Characteristic are as follows: - // TAG NAME FORMAT DESCRIPTION - // ---- ------------- ------ -------------------------------------------- - // 0x01 inputSourceID uint32 ID of the Input Source to be displayed first - // 0x00 separator none Empty element to separate the inputSourceIDs - // 0x01 inputSourceID uint32 ID of the Input Source to be displayed second - // 0x00 separator none Empty element to separate the inputSourceIDs - // 0x01 inputSourceID uint32 ID of the Input Source to be displayed third - // 0x00 separator none Empty element to separate the inputSourceIDs - // etc... + // TAG NAME FORMAT DESCRIPTION + // ---- ------------- ------ -------------------------------------------- + // 0x01 inputSourceID uint32 ID of the Input Source to be displayed first + // 0x00 separator none Empty element to separate the inputSourceIDs + // 0x01 inputSourceID uint32 ID of the Input Source to be displayed second + // 0x00 separator none Empty element to separate the inputSourceIDs + // 0x01 inputSourceID uint32 ID of the Input Source to be displayed third + // 0x00 separator none Empty element to separate the inputSourceIDs + // etc... - // To start, instantiate a new TLV8 object - - TLV8 orderTLV; // creates an empty TLV8 object + // To start, instantiate a new TLV8 object + + TLV8 orderTLV; // creates an empty TLV8 object - // Next, fill it with TAGS and VALUES based on the above specification. The easiest, - // though not necessarily most elegant, way to do this is as follows: + // Next, fill it with TAGS and VALUES based on the above specification. The easiest, though + // not necessarily most elegant, way to do this is by simply adding each TAG/VALUE as follows: - orderTLV.add(1,10); // TAG=1, VALUE=ID of first Input Source to be displayed - orderTLV.add(0); // TAG=0 (no value) - orderTLV.add(1,20); // TAG=1, VALUE=ID of the second Input Source to be displayed - orderTLV.add(0); // TAG=0 (no value) - orderTLV.add(1,50); // TAG=1, VALUE=ID of the third Input Source to be displayed - orderTLV.add(0); // TAG=0 (no value) - orderTLV.add(1,30); // TAG=1, VALUE=ID of the fourth Input Source to be displayed - orderTLV.add(0); // TAG=0 (no value) - orderTLV.add(1,40); // TAG=1, VALUE=ID of the fifth Input Source to be displayed + orderTLV.add(1,10); // TAG=1, VALUE=ID of first Input Source to be displayed + orderTLV.add(0); // TAG=0 (no value) + orderTLV.add(1,20); // TAG=1, VALUE=ID of the second Input Source to be displayed + orderTLV.add(0); // TAG=0 (no value) + orderTLV.add(1,50); // TAG=1, VALUE=ID of the third Input Source to be displayed + orderTLV.add(0); // TAG=0 (no value) + orderTLV.add(1,30); // TAG=1, VALUE=ID of the fourth Input Source to be displayed + orderTLV.add(0); // TAG=0 (no value) + orderTLV.add(1,40); // TAG=1, VALUE=ID of the fifth Input Source to be displayed - // Based on the above structure, we expect the Home App to display our input sources based on their IDs - // in the following order: 100, 200, 500, 300, 400. These IDs must of course match the IDs you choose - // for your input sources when you create them at the end of this sketch in setup() + // Based on the above structure, we expect the Home App to display our input sources based on their IDs + // in the following order: 10, 20, 50, 30, 40. These IDs must of course match the IDs you choose + // for your input sources when you create them at the end of this sketch in setup() - // The final step is to load this TLV8 object into the DisplayOrder Characteristic + // The final step is to load this TLV8 object into the DisplayOrder Characteristic - // displayOrder->setTLV(orderTLV); // set the "value" of DisplayOrder to be the orderTLV object we just created + displayOrder->setTLV(orderTLV); // set the "value" of DisplayOrder to be the orderTLV object we just created - // That's it - you've created your first TLV8 Characteristic! + // That's it - you've created your first TLV8 Characteristic! + } + + // Below we define the usual update() loop. There is nothing "TLV-specific" about this part of the code + + boolean update() override { + + if(active->updated()){ + LOG0("Set TV Power to: %s\n",active->getNewVal()?"ON":"OFF"); } - // Below we define the usual update() loop. There is nothing "TLV-specific" about this part of the code - - boolean update() override { + if(activeID->updated()){ + LOG0("Set Input Source to ID=%d\n",activeID->getNewVal()); + } - if(active->updated()){ - Serial.printf("Set TV Power to: %s\n",active->getNewVal()?"ON":"OFF"); - } - - if(activeID->updated()){ - Serial.printf("Set Input Source to ID=%d\n",activeID->getNewVal()); - } - - return(true); - } + return(true); + } }; /////////////////////////////// From a92f73666a8c8884f2a400494d69d5f07b48fd3b Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 27 Apr 2024 10:40:34 -0500 Subject: [PATCH 090/154] Update TLV8.md --- docs/TLV8.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/TLV8.md b/docs/TLV8.md index 85e853c..d02a1d2 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -282,11 +282,11 @@ As fully documented in the [API Reference](Reference.md), the following *SpanCha These are analagous to the `getVal()`, `getNewVal()` and `setVal()` methods used for numerical-based Characteristics. -Note that using the above methods *do not* require you to create a separate byte-array that splits records into chunks of 255 bytes, nor does it require you to encode or decode anything into base-64. Rather, you directly read and write to and from the Characteristic into a TLV8 object. +Note that using the above methods *do not* require you to create a separate byte-array that splits records into chunks of 255 bytes, nor does it require you to encode or decode anything into base-64. Rather, you directly read and write to and from the Characteristic into a TLV8 object.[^getString] -However, since TLV8 Characteristics are stored as base-64 encoded strings, you can nevertheless use `getString()` to read the base-64 text, or `getData()` to decode the string into the full byte-array that represents the entire TLV8 object, if you desire. +For a detailed example of how TLV8 Characteristics are used in practice, see [Tutorial Example 22-TL8_Characteristics](../examples/22-TL8_Characteristics) demonstrating how the **DisplayOrder** TLV8 Charactersitic can be used to set the order in which Input Sources for a TV Service are displayed in the Home App. -Also, if you really don't want to use HomeSpan's TLV8 library to produce TLV8 objects, but instead prefer to use your own methods to create a TLV8-compliant byte-array, you can do so and then use `setData()` to save the byte-array you produced to the TLV8 Characteristic, which will perform the base-64 encoding for you. Or, if you want to additionally perform your own base-64 encoding (why?), you can do so and then simply use `setString()` to save the resulting encoded text to the TLV8 Characteristic. +[^getString]:Since TLV8 Characteristics are stored as base-64 encoded strings, you can always use `getString()` to read the base-64 text, or `getData()` to decode the string into the full byte-array that represents the entire TLV8 object, if you desire. Also, if you really don't want to use HomeSpan's TLV8 library to produce TLV8 objects, but instead prefer to use your own methods to create a TLV8-compliant byte-array, you can do so and then use `setData()` to save the byte-array you produced to the TLV8 Characteristic, which will perform the base-64 encoding for you. Or, if you want to additionally perform your own base-64 encoding (why?), you can do so and then simply use `setString()` to save the resulting encoded text to the TLV8 Characteristic. ### Write-Response Requests From d2d1b79c4d192ee94e191b37fdcd0ac9f47e92d7 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 27 Apr 2024 10:44:30 -0500 Subject: [PATCH 091/154] Update TLV8.md --- docs/TLV8.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/TLV8.md b/docs/TLV8.md index d02a1d2..b085178 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -284,7 +284,7 @@ These are analagous to the `getVal()`, `getNewVal()` and `setVal()` methods used Note that using the above methods *do not* require you to create a separate byte-array that splits records into chunks of 255 bytes, nor does it require you to encode or decode anything into base-64. Rather, you directly read and write to and from the Characteristic into a TLV8 object.[^getString] -For a detailed example of how TLV8 Characteristics are used in practice, see [Tutorial Example 22-TL8_Characteristics](../examples/22-TL8_Characteristics) demonstrating how the **DisplayOrder** TLV8 Charactersitic can be used to set the order in which Input Sources for a TV Service are displayed in the Home App. +For a detailed example of how TLV8 Characteristics are used in practice, see [Tutorial Example 22-TL8_Characteristics](../examples/22-TLV8_Characteristics) demonstrating how the **DisplayOrder** TLV8 Charactersitic can be used to set the order in which Input Sources for a TV Service are displayed in the Home App. [^getString]:Since TLV8 Characteristics are stored as base-64 encoded strings, you can always use `getString()` to read the base-64 text, or `getData()` to decode the string into the full byte-array that represents the entire TLV8 object, if you desire. Also, if you really don't want to use HomeSpan's TLV8 library to produce TLV8 objects, but instead prefer to use your own methods to create a TLV8-compliant byte-array, you can do so and then use `setData()` to save the byte-array you produced to the TLV8 Characteristic, which will perform the base-64 encoding for you. Or, if you want to additionally perform your own base-64 encoding (why?), you can do so and then simply use `setString()` to save the resulting encoded text to the TLV8 Characteristic. From 0eec3920f41de28ded6472f5200b1d2c75ff99b1 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 27 Apr 2024 10:45:18 -0500 Subject: [PATCH 092/154] Update TLV8.md --- docs/TLV8.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/TLV8.md b/docs/TLV8.md index b085178..f87ae52 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -284,7 +284,7 @@ These are analagous to the `getVal()`, `getNewVal()` and `setVal()` methods used Note that using the above methods *do not* require you to create a separate byte-array that splits records into chunks of 255 bytes, nor does it require you to encode or decode anything into base-64. Rather, you directly read and write to and from the Characteristic into a TLV8 object.[^getString] -For a detailed example of how TLV8 Characteristics are used in practice, see [Tutorial Example 22-TL8_Characteristics](../examples/22-TLV8_Characteristics) demonstrating how the **DisplayOrder** TLV8 Charactersitic can be used to set the order in which Input Sources for a TV Service are displayed in the Home App. +For a detailed example of how TLV8 Characteristics are used in practice, see [Tutorial Example 22-TLV8_Characteristics](../examples/22-TLV8_Characteristics) demonstrating how the **DisplayOrder** TLV8 Charactersitic can be used to set the order in which Input Sources for a TV Service are displayed in the Home App. [^getString]:Since TLV8 Characteristics are stored as base-64 encoded strings, you can always use `getString()` to read the base-64 text, or `getData()` to decode the string into the full byte-array that represents the entire TLV8 object, if you desire. Also, if you really don't want to use HomeSpan's TLV8 library to produce TLV8 objects, but instead prefer to use your own methods to create a TLV8-compliant byte-array, you can do so and then use `setData()` to save the byte-array you produced to the TLV8 Characteristic, which will perform the base-64 encoding for you. Or, if you want to additionally perform your own base-64 encoding (why?), you can do so and then simply use `setString()` to save the resulting encoded text to the TLV8 Characteristic. From 012c778f150e32abdb0c5057b7658c4de74ece2a Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 27 Apr 2024 10:47:52 -0500 Subject: [PATCH 093/154] Update Tutorials.md --- docs/Tutorials.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/Tutorials.md b/docs/Tutorials.md index 5bb1888..28c3b2d 100644 --- a/docs/Tutorials.md +++ b/docs/Tutorials.md @@ -113,6 +113,10 @@ Example 20 illustrates a number of advanced techniques through the implementatio ### [Example 21 - AccessoryIdentifier](../examples/21-AccessoryIdentifier) Example 21 shows how the Identifier Characteristic that is always present in each Accessory's required AccessoryInformation Service can be used to create a custom "identification routine" that can be triggered from within the Home App when pairing a device. This example does not use any new HomeSpan methods. + +### [Example 22-TLV8_Characteristics](../examples/22-TLV8_Characteristics) +Example 22 demonstrates, through the implementation of the DisplayOrder Characteristic used in conjunction with the InputSource and Television Services... + ## Other Examples From 7a796bac2fc45bb0402d657e3fd112a315e5a8f5 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 27 Apr 2024 16:13:22 -0500 Subject: [PATCH 094/154] Update Tutorials.md --- docs/Tutorials.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/Tutorials.md b/docs/Tutorials.md index 28c3b2d..732c9ae 100644 --- a/docs/Tutorials.md +++ b/docs/Tutorials.md @@ -73,7 +73,6 @@ Example 13 demonstrates the simultaneous use of both the `update()` and `loop()` * using Enumerated Constants to set the values of Characteristics that represent discrete states (e.g. "raising", "closing") - ### [Example 14 - EmulatedPushButtons](../examples/14-EmulatedPushButtons) Example 14 demonstrates how you can use the `setVal()` and `timeVal()` methods inside a Service's `loop()` method to create a tile in the Home App that emulates a pushbutton switch. In this example pressing the tile in the Home App will cause it to turn on, blink an LED 3 times, and then turn off (just like a real pushbutton might do). @@ -114,8 +113,11 @@ Example 20 illustrates a number of advanced techniques through the implementatio ### [Example 21 - AccessoryIdentifier](../examples/21-AccessoryIdentifier) Example 21 shows how the Identifier Characteristic that is always present in each Accessory's required AccessoryInformation Service can be used to create a custom "identification routine" that can be triggered from within the Home App when pairing a device. This example does not use any new HomeSpan methods. -### [Example 22-TLV8_Characteristics](../examples/22-TLV8_Characteristics) -Example 22 demonstrates, through the implementation of the DisplayOrder Characteristic used in conjunction with the InputSource and Television Services... +### [Example 22 - TLV8 Characteristics](../examples/22-TLV8_Characteristics) +Example 22 demonstrates how to create and utilize TLV8-based Characteristics through the implementation of the DisplayOrder Characteristic used to set the order in which input sources for a Television Service are presented in the Home App. New HomeSpan API topics covered in this example include: + +* creating TLV8 objects using HomeSpan's TLV8 class +* updating TLV8 Characteristics using `setTLV()` ## Other Examples From ee2e850505c73c37382ea17406ce46e57349bdfe Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 27 Apr 2024 16:14:24 -0500 Subject: [PATCH 095/154] Update TLV8.md --- docs/TLV8.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/TLV8.md b/docs/TLV8.md index f87ae52..f750233 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -284,7 +284,7 @@ These are analagous to the `getVal()`, `getNewVal()` and `setVal()` methods used Note that using the above methods *do not* require you to create a separate byte-array that splits records into chunks of 255 bytes, nor does it require you to encode or decode anything into base-64. Rather, you directly read and write to and from the Characteristic into a TLV8 object.[^getString] -For a detailed example of how TLV8 Characteristics are used in practice, see [Tutorial Example 22-TLV8_Characteristics](../examples/22-TLV8_Characteristics) demonstrating how the **DisplayOrder** TLV8 Charactersitic can be used to set the order in which Input Sources for a TV Service are displayed in the Home App. +For a detailed example of how TLV8 Characteristics are used in practice, see [Tutorial Example 22 - TLV8_Characteristics](../examples/22-TLV8_Characteristics) demonstrating how the **DisplayOrder** TLV8 Charactersitic can be used to set the order in which Input Sources for a TV Service are displayed in the Home App. [^getString]:Since TLV8 Characteristics are stored as base-64 encoded strings, you can always use `getString()` to read the base-64 text, or `getData()` to decode the string into the full byte-array that represents the entire TLV8 object, if you desire. Also, if you really don't want to use HomeSpan's TLV8 library to produce TLV8 objects, but instead prefer to use your own methods to create a TLV8-compliant byte-array, you can do so and then use `setData()` to save the byte-array you produced to the TLV8 Characteristic, which will perform the base-64 encoding for you. Or, if you want to additionally perform your own base-64 encoding (why?), you can do so and then simply use `setString()` to save the resulting encoded text to the TLV8 Characteristic. From 74d27485d53e5cd76213adaad11c956f080ac4c4 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 28 Apr 2024 08:15:04 -0500 Subject: [PATCH 096/154] Added homeSpan.setControllerCallback() Also adds: homeSpan.controllerListBegin(), homeSpan.controllerListEnd() Also adds: Controller::isAdmin(), Controller::getID(), Controller::getLTPK() To accomplish this, needed to move Controller definition from HAP.h to HomeSpan.h Also, converted Controller from struct to class to ensure Controller data is protected from being modified now that it is exposed through condstant iterators. --- src/HAP.cpp | 9 +- src/HAP.h | 26 +---- src/HomeSpan.cpp | 18 +++- src/HomeSpan.h | 35 +++++++ src/src.ino | 242 +++++------------------------------------------ 5 files changed, 84 insertions(+), 246 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index 417fbcd..e5d6d07 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -1345,7 +1345,7 @@ int HAPClient::receiveEncrypted(uint8_t *httpBuf, int messageSize){ ///////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////// -void HAPClient::hexPrintColumn(uint8_t *buf, int n, int minLogLevel){ +void HAPClient::hexPrintColumn(const uint8_t *buf, int n, int minLogLevel){ if(homeSpan.logLevelcPair){ LOG0(" ID="); - HAPClient::charPrintRow(hap[i]->cPair->ID,36); - LOG0(hap[i]->cPair->admin?" (admin)":" (regular)"); + HAPClient::charPrintRow(hap[i]->cPair->getID(),36); + LOG0(hap[i]->cPair->isAdmin()?" (admin)":" (regular)"); } else { LOG0(" (unverified)"); } @@ -1092,7 +1092,7 @@ void Span::processSerialCommand(const char *c){ reboot(); } else { HAPClient::controllerList.push_back(tCont); - HAPClient::charPrintRow(tCont.ID,36); + HAPClient::charPrintRow(tCont.getID(),36); LOG0("\n"); } } @@ -1658,6 +1658,18 @@ boolean Span::updateDatabase(boolean updateMDNS){ return(changed); } +/////////////////////////////// + +list>::const_iterator Span::controllerListBegin(){ + return(HAPClient::controllerList.cbegin()); +} + +/////////////////////////////// + +list>::const_iterator Span::controllerListEnd(){ + return(HAPClient::controllerList.cend()); +} + /////////////////////////////// // SpanAccessory // /////////////////////////////// diff --git a/src/HomeSpan.h b/src/HomeSpan.h index be024b8..d5b656b 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -113,6 +113,7 @@ struct SpanRange; struct SpanBuf; struct SpanButton; struct SpanUserCommand; +class Controller; extern Span homeSpan; @@ -189,6 +190,35 @@ struct SpanOTA{ // manages OTA process static void error(ota_error_t err); }; + +////////////////////////////////////////////////////////// +// Paired Controller Structure for Permanently-Stored Data + +class Controller { + friend class HAPClient; + + boolean allocated=false; // DEPRECATED (but needed for backwards compatability with original NVS storage of Controller info) + boolean admin; // Controller has admin privileges + uint8_t ID[36]; // Pairing ID + uint8_t LTPK[32]; // Long Term Ed2519 Public Key + + public: + + Controller(uint8_t *id, uint8_t *ltpk, boolean ad){ + allocated=true; + admin=ad; + memcpy(ID,id,36); + memcpy(LTPK,ltpk,32); + } + + Controller(){} + + const uint8_t *getID() const {return(ID);} + const uint8_t *getLTPK() const {return(LTPK);} + boolean isAdmin() const {return(admin);} + +}; + ////////////////////////////////////// // USER API CLASSES BEGINS HERE // ////////////////////////////////////// @@ -250,6 +280,7 @@ class Span{ void (*apFunction)()=NULL; // optional function to invoke when starting Access Point void (*statusCallback)(HS_STATUS status)=NULL; // optional callback when HomeSpan status changes void (*rebootCallback)(uint8_t)=NULL; // optional callback when device reboots + void (*controllerCallback)()=NULL; // optional callback when Controller is added/removed/changed WiFiServer *hapServer; // pointer to the HAP Server connection Blinker *statusLED; // indicates HomeSpan status @@ -355,6 +386,7 @@ class Span{ Span& setPairingCode(const char *s, boolean progCall=true); // sets the Pairing Code - use is NOT recommended. Use 'S' from CLI instead void deleteStoredValues(){processSerialCommand("V");} // deletes stored Characteristic values from NVS Span& resetIID(uint32_t newIID); // resets the IID count for the current Accessory to start at newIID + Span& setControllerCallback(void (*f)()){controllerCallback=f;return(*this);} // sets an optional user-defined function to call whenever a Controller is added/removed/changed int enableOTA(boolean auth=true, boolean safeLoad=true){return(spanOTA.init(auth, safeLoad, NULL));} // enables Over-the-Air updates, with (auth=true) or without (auth=false) authorization password int enableOTA(const char *pwd, boolean safeLoad=true){return(spanOTA.init(true, safeLoad, pwd));} // enables Over-the-Air updates, with custom authorization password (overrides any password stored with the 'O' command) @@ -393,6 +425,9 @@ class Span{ TaskHandle_t getAutoPollTask(){return(pollTaskHandle);} Span& setTimeServerTimeout(uint32_t tSec){webLog.waitTime=tSec*1000;return(*this);} // sets wait time (in seconds) for optional web log time server to connect + + list>::const_iterator controllerListBegin(); + list>::const_iterator controllerListEnd(); [[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/src.ino b/src/src.ino index 44be4ec..13e2f91 100644 --- a/src/src.ino +++ b/src/src.ino @@ -27,227 +27,21 @@ #include "HomeSpan.h" -CUSTOM_CHAR_DATA(TestData,333,PR+EV); - -struct HomeSpanTV : Service::Television { - - SpanCharacteristic *active = new Characteristic::Active(0); // TV On/Off (set to Off at start-up) - SpanCharacteristic *activeID = new Characteristic::ActiveIdentifier(3); // Sets HDMI 3 on start-up - SpanCharacteristic *remoteKey = new Characteristic::RemoteKey(); // Used to receive button presses from the Remote Control widget - SpanCharacteristic *settingsKey = new Characteristic::PowerModeSelection(); // Adds "View TV Setting" option to Selection Screen - SpanCharacteristic *displayOrder = new Characteristic::DisplayOrder(); - SpanCharacteristic *testData = new Characteristic::TestData(); - SpanCharacteristic *tvname; - - HomeSpanTV(const char *name) : Service::Television() { - tvname = new Characteristic::ConfiguredName(name); // Name of TV - Serial.printf("Configured TV: %s\n",name); - - TLV8 orderTLV; - uint32_t order[]={5,10,6,2,1,9,11,3,18,12}; - - for(int i=0;i0) - orderTLV.add(6); - orderTLV.add(1,sizeof(uint32_t),(uint8_t*)(order+i)); - } - - orderTLV.print(); - displayOrder->setTLV(orderTLV); - - uint8_t blob[]={1,2,3,4,5,6,7,8,9,10,11,12}; -// testData->setData(blob,sizeof(blob)); - testData->setData(blob,1); - - new SpanUserCommand('P', "- change order of inputs", changeOrder, this); - new SpanUserCommand('C', "- change name of TV", setTVName, this); - } - - boolean update() override { - - if(active->updated()){ - Serial.printf("Set TV Power to: %s\n",active->getNewVal()?"ON":"OFF"); - } - - if(activeID->updated()){ - Serial.printf("Set Input Source to HDMI-%d\n",activeID->getNewVal()); - } - - if(settingsKey->updated()){ - Serial.printf("Received request to \"View TV Settings\"\n"); - } - - if(remoteKey->updated()){ - Serial.printf("Remote Control key pressed: "); - switch(remoteKey->getNewVal()){ - case 4: - Serial.printf("UP ARROW\n"); - break; - case 5: - Serial.printf("DOWN ARROW\n"); - break; - case 6: - Serial.printf("LEFT ARROW\n"); - break; - case 7: - Serial.printf("RIGHT ARROW\n"); - break; - case 8: - Serial.printf("SELECT\n"); - break; - case 9: - Serial.printf("BACK\n"); - break; - case 11: - Serial.printf("PLAY/PAUSE\n"); - break; - case 15: - Serial.printf("INFO\n"); - break; - default: - Serial.print("UNKNOWN KEY\n"); - } - } - - return(true); - } - - static void setTVName(const char *buf, void *arg){ - HomeSpanTV *hsTV=(HomeSpanTV *)arg; - hsTV->tvname->setString("New Name"); - Serial.printf("Reset TV Name to '%s'\n",hsTV->tvname->getString()); - Serial.printf("Showing displayOrder '%s'\n",hsTV->displayOrder->getString()); - } - - static void changeOrder(const char *buf, void *arg){ - HomeSpanTV *hsTV=(HomeSpanTV *)arg; - - TLV8 orderTLV; - - hsTV->displayOrder->getTLV(orderTLV); - orderTLV.print(); - orderTLV.wipe(); - - uint8_t order[]={12,10,6,2,1,9,11,3,18,5}; - - for(int i=0;i0) - orderTLV.add(0); - orderTLV.add(1,sizeof(uint8_t),(uint8_t*)(order+i)); - } - - Serial.printf("AFTER:\n"); - orderTLV.print(); - size_t n=orderTLV.pack_size(); - Serial.printf("Size=%d\n",n); - uint8_t c[n]; - orderTLV.pack(c); - hsTV->displayOrder->setData(c,n); - } - -}; - -/////////////////////////////// - void setup() { - + Serial.begin(115200); - homeSpan.setLogLevel(2); - - homeSpan.begin(Category::Television,"HomeSpan Television"); + homeSpan.begin(Category::Lighting,"HomeSpan Light"); + + new SpanAccessory(); + new Service::AccessoryInformation(); + new Characteristic::Identify(); + new Service::LightBulb(); + new Characteristic::On(); - SPAN_ACCESSORY(); - - SpanService *hdmi1 = new Service::InputSource(); // Source included in Selection List, but excluded from Settings Screen - new Characteristic::ConfiguredName("Alpha"); - new Characteristic::Identifier(5); - new Characteristic::IsConfigured(1); - new Characteristic::CurrentVisibilityState(0); - new Characteristic::TargetVisibilityState(0); - - SpanService *hdmi2 = new Service::InputSource(); - new Characteristic::ConfiguredName("Gamma"); - new Characteristic::Identifier(10); - new Characteristic::IsConfigured(1); - new Characteristic::CurrentVisibilityState(0); - new Characteristic::TargetVisibilityState(0); - - SpanService *hdmi3 = new Service::InputSource(); - new Characteristic::ConfiguredName("Beta"); - new Characteristic::Identifier(6); - new Characteristic::IsConfigured(1); - new Characteristic::CurrentVisibilityState(0); - new Characteristic::TargetVisibilityState(0); - - SpanService *hdmi4 = new Service::InputSource(); - new Characteristic::ConfiguredName("Zebra"); - new Characteristic::Identifier(2); - new Characteristic::IsConfigured(1); - new Characteristic::CurrentVisibilityState(0); - new Characteristic::TargetVisibilityState(0); - - SpanService *hdmi5 = new Service::InputSource(); - new Characteristic::ConfiguredName("Delta"); - new Characteristic::Identifier(1); - new Characteristic::IsConfigured(1); - new Characteristic::CurrentVisibilityState(0); - new Characteristic::TargetVisibilityState(0); - - SpanService *hdmi6 = new Service::InputSource(); - new Characteristic::ConfiguredName("Trident"); - new Characteristic::Identifier(9); - new Characteristic::IsConfigured(1); - new Characteristic::CurrentVisibilityState(0); - new Characteristic::TargetVisibilityState(0); - - SpanService *hdmi7 = new Service::InputSource(); - new Characteristic::ConfiguredName("Netflix"); - new Characteristic::Identifier(11); - new Characteristic::IsConfigured(1); - new Characteristic::CurrentVisibilityState(0); - new Characteristic::TargetVisibilityState(0); - - SpanService *hdmi8 = new Service::InputSource(); - new Characteristic::ConfiguredName("Alpha2"); - new Characteristic::Identifier(3); - new Characteristic::IsConfigured(1); - new Characteristic::CurrentVisibilityState(0); - new Characteristic::TargetVisibilityState(0); - - SpanService *hdmi9 = new Service::InputSource(); - new Characteristic::ConfiguredName("Moon"); - new Characteristic::Identifier(18); - new Characteristic::IsConfigured(1); - new Characteristic::CurrentVisibilityState(0); - new Characteristic::TargetVisibilityState(0); - - SpanService *hdmi10 = new Service::InputSource(); - new Characteristic::ConfiguredName("Gamba"); - new Characteristic::Identifier(12); - new Characteristic::IsConfigured(1); - new Characteristic::CurrentVisibilityState(0); - new Characteristic::TargetVisibilityState(0); - - SpanService *speaker = new Service::TelevisionSpeaker(); - new Characteristic::VolumeSelector(); - new Characteristic::VolumeControlType(3); - - (new HomeSpanTV("Test TV")) // Define a Television Service. Must link in InputSources! - ->addLink(hdmi1) - ->addLink(hdmi2) - ->addLink(hdmi3) - ->addLink(hdmi4) - ->addLink(hdmi5) - ->addLink(hdmi6) - ->addLink(hdmi7) - ->addLink(hdmi8) - ->addLink(hdmi9) - ->addLink(hdmi10) - ->addLink(speaker) - ; - -} +// new SpanUserCommand('k',"- list controllers",list_controllers); + homeSpan.setControllerCallback(list_controllers); +} ////////////////////////////////////// @@ -258,3 +52,17 @@ void loop(){ } ////////////////////////////////////// + + +void list_controllers(){ + Serial.printf("\nControllers\n"); + for(auto it=homeSpan.controllerListBegin(); it!=homeSpan.controllerListEnd(); ++it){ + Serial.printf("Admin=%d ID=",it->isAdmin()); + for(int i=0;i<36;i++) + Serial.printf("%02X",it->getID()[i]); + Serial.printf(" LTPK="); + for(int i=0;i<32;i++) + Serial.printf("%02X",it->getLTPK()[i]); + Serial.printf("\n"); + } +} From db80dab963e73f99457d77bd87145bfaae230ea5 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 28 Apr 2024 11:03:57 -0500 Subject: [PATCH 097/154] Update Reference.md --- docs/Reference.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/Reference.md b/docs/Reference.md index 1bf1bb2..e1d17cd 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -181,7 +181,13 @@ The following **optional** `homeSpan` methods enable additional features and pro * this one-time call to *func* is provided for users that would like to trigger additional actions when the device is first paired, or the device is later unpaired * note this *func* is **not** called upon start-up and should not be used to simply check whether a device is paired or unpaired. It is only called when pairing status changes * the function *func* must be of type *void* and accept one *boolean* argument - + +* `Span& setControllerCallback(void (*func)())` + * sets an optional user-defined callback function, *func*, to be called by HomeSpan every time a new controller is added, removed, or updated, even if the pairing status does not change + * note this method differs from `setPairCallback()`, which is only called if the device's pairing status changes, such as when the first controller is added during initial pairing, or the last controller is removed when unpairing + * the function *func* must be of type *void* and have no arguments + * see the `controllerListBegin()` and `controllerListEnd()` methods for details on how to read the pairing data for each paired controller (*only needed to support certain advanced use cases*) + * `Span& setStatusCallback(void (*func)(HS_STATUS status))` * sets an optional user-defined callback function, *func*, to be called by HomeSpan whenever its running state (e.g. WiFi Connecting, Pairing Needed...) changes in way that would alter the blinking pattern of the (optional) Status LED * if *func* is set, it will be called regardless of whether or not a Status LED has actually been defined From a8883c911d03d029d9fbb007d8ade2e86a12c5dd Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 28 Apr 2024 11:13:53 -0500 Subject: [PATCH 098/154] Update Reference.md --- docs/Reference.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/Reference.md b/docs/Reference.md index e1d17cd..b96b20c 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -302,6 +302,11 @@ The following **optional** `homeSpan` methods provide additional run-time functi * throws an error and halts program if called before at least one Accessory is created * example: `homeSpan.resetIID(100)` causes HomeSpan to set the IID to 100 for the very next Service or Characteristic defined within the current Accessory, and then increment the IID count going forward so that any Services or Characteristics subsequently defined (within the same Accessory) have IID=101, 102, etc. * note: calling this function only affects the IID generation for the current Accessory (the count will be reset to IID=1 upon instantiation of a new Accessory) + +* `const_iterator controllerListBegin()` and `const_iterator controllerListEnd()` + * respectively returns constant iterators pointing to the beginning (*cbegin()*) or end (*cend()*) of an opaque linked list that stores all controller data + * primarily used to loop through all controller data + * use `auto` keyword to define and save an iterator as such: `for(auto it=homeSpan.controllerListBegin(); it!=homeSpan.controllerListEnd(); ++it) {}` --- From 1267603e714d654c2f03aa252ed54eb3627a94f9 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 28 Apr 2024 12:59:42 -0500 Subject: [PATCH 099/154] Update Reference.md --- docs/Reference.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/Reference.md b/docs/Reference.md index b96b20c..d1b40ff 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -304,9 +304,13 @@ The following **optional** `homeSpan` methods provide additional run-time functi * note: calling this function only affects the IID generation for the current Accessory (the count will be reset to IID=1 upon instantiation of a new Accessory) * `const_iterator controllerListBegin()` and `const_iterator controllerListEnd()` - * respectively returns constant iterators pointing to the beginning (*cbegin()*) or end (*cend()*) of an opaque linked list that stores all controller data - * primarily used to loop through all controller data - * use `auto` keyword to define and save an iterator as such: `for(auto it=homeSpan.controllerListBegin(); it!=homeSpan.controllerListEnd(); ++it) {}` + * returns a *constant iterator* pointing to either the beginning, or the end, of an opaque linked list that stores all controller data + * iterators should be defined using the `auto` keyword as follows: `auto myIt=homeSpan.controllerListBegin();` + * controller data can be read from a de-referenced iterator using the following methods: + * `const uint8_t *getID()` returns pointer to the 36-byte ID of the controller + * `const uint8_t *getLTPK()` returns pointer to the 32-byte Long Term Public Key of the controller + * `boolean isAdmin()` returns true if controller has admin permissions, else returns false + * see this gist for an example of how to use these methods to extract the same data about each controller that HomeSpan prints to the Serial Monitor when using the 's' CLI command --- From 96a08ea84b9a3d9babb5a7dae393b078dbaf0eec Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 28 Apr 2024 13:23:54 -0500 Subject: [PATCH 100/154] 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 d1b40ff..1da6237 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -310,7 +310,7 @@ The following **optional** `homeSpan` methods provide additional run-time functi * `const uint8_t *getID()` returns pointer to the 36-byte ID of the controller * `const uint8_t *getLTPK()` returns pointer to the 32-byte Long Term Public Key of the controller * `boolean isAdmin()` returns true if controller has admin permissions, else returns false - * see this gist for an example of how to use these methods to extract the same data about each controller that HomeSpan prints to the Serial Monitor when using the 's' CLI command + * see this [gist](https://gist.github.com/HomeSpan/5486704b42027e31ab38a9f193451308) for an example of how to use these methods to extract the same data about each controller that HomeSpan prints to the Serial Monitor when using the 's' CLI command --- From bdf25cbaf96a54bf43a5c712dcf1a80141f7ad7a Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 28 Apr 2024 20:41:16 -0500 Subject: [PATCH 101/154] Update Reference.md --- docs/Reference.md | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/docs/Reference.md b/docs/Reference.md index 1da6237..3ce781d 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -304,13 +304,35 @@ The following **optional** `homeSpan` methods provide additional run-time functi * note: calling this function only affects the IID generation for the current Accessory (the count will be reset to IID=1 upon instantiation of a new Accessory) * `const_iterator controllerListBegin()` and `const_iterator controllerListEnd()` - * returns a *constant iterator* pointing to either the beginning, or the end, of an opaque linked list that stores all controller data + * returns a *constant iterator* pointing to either the *beginning*, or the *end*, of an opaque linked list that stores all controller data * iterators should be defined using the `auto` keyword as follows: `auto myIt=homeSpan.controllerListBegin();` * controller data can be read from a de-referenced iterator using the following methods: * `const uint8_t *getID()` returns pointer to the 36-byte ID of the controller * `const uint8_t *getLTPK()` returns pointer to the 32-byte Long Term Public Key of the controller * `boolean isAdmin()` returns true if controller has admin permissions, else returns false - * see this [gist](https://gist.github.com/HomeSpan/5486704b42027e31ab38a9f193451308) for an example of how to use these methods to extract the same data about each controller that HomeSpan prints to the Serial Monitor when using the 's' CLI command + *
click here for example code
+ + ```C++ + // Extract and print the same data about each controller that HomeSpan prints to the Serial Monitor when using the 's' CLI command + + Serial.printf("\nController Data\n"); + + for(auto it=homeSpan.controllerListBegin(); it!=homeSpan.controllerListEnd(); ++it){ // loop over each controller + + Serial.printf("Admin=%d",it->isAdmin()); // indicate if controller has admin permissions + + Serial.printf(" ID="); // print the 36-byte Device ID of the controller + for(int i=0;i<36;i++) + Serial.printf("%02X",it->getID()[i]); + + Serial.printf(" LTPK="); // print the 32-byte Long-Term Public Key of the controller) + for(int i=0;i<32;i++) + Serial.printf("%02X",it->getLTPK()[i]); + + Serial.printf("\n"); + } + ``` +
--- From 2bdafb534ebd3d01b51ee9fa468c40cd261c853b Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Mon, 29 Apr 2024 07:57:46 -0500 Subject: [PATCH 102/154] Update README.md --- README.md | 50 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 34e97ce..5427231 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,26 @@ HomeSpan requires version 2.0.0 or later of the [Arduino-ESP32 Board Manager](ht ## ❗Latest Update - HomeSpan 1.9.1 (MM/DD/YYY) +* **HomeSpan now supports TLV8 "TAG-LENGTH-VALUE" Characteristics!** + + * adds new, fully-integrated `TLV8()` class library for the creation and management of TLV8 objects + * includes methods to handle standard byte-stream VALUES as well as strings, numerical values, zero-length tags, and sub-TLVs + * utilizes standard C++ iterators for easy access to reading and writing TLV8 records + * adds new `Characteristic` methods `getTLV()`, `getNewTLV()`, and `setTLV()` + * adds new `CUSTOM_CHAR_TLV8()` that allows for easy creation of custom TLV8 Characteristics + * includes new [Tutorial Example 22 - TLV8 Characteristics](examples/22-TLV8_Characteristics) demonstrating use of the `TLV8()` class and TLV8 Characteristics + * see the new [TLV8 Characteristics](docs/TLV8.md) page for complete details and documentation + +* **New TLV8 Characteristic - *DisplayOrder*** + + * utlizes HomeSpan's new `TLV8()` library + * sets the order in which the Input Sources for a Television Service are displayed for selection in the Home App + * see [Tutorial Example 22 - TLV8 Characteristics](examples/22-TLV8_Characteristics) for details + +* **New [Tutorial Example 21 - AccessoryIdentifier](examples/21-AccessoryIdentifier)** + + * demonstrates how to trigger an Accessory's Identifier Characteristic, optionally used to help identify a device during initial pairing to the Home App + * **Added support for more Pixel chips** * new constructor `Pixel(uint8_t pin, [pixelType_t pixelType])` allows your to set the order in which colors are transmitted to the pixel chip, where *pixelType* is one of the following: @@ -66,10 +86,38 @@ HomeSpan requires version 2.0.0 or later of the [Arduino-ESP32 Board Manager](ht * returns *true* if Pixel was constructed as RGBW, else *false* if constructed as RGB only (i.e. no white LED) * created new PixelTester sketch (found under Other-> Examples) to aid in determining the *pixelType* for any LED Strip -* **Fixed bug introduced in 1.9.0 that prevented `homeSpan.setPairingCode()` from saving (and subsequently using) the request Setup Pairing Code** +* **New ability to read and set the IIDs of Services and Characteristics** + + * adds new `SpanService` method `getIID()` that returns the IID of a Service + * adds new `SpanCharacteristic` method `getIID()` that returns the IID of a Characteristic + * adds new `homeSpan` method `resetIID(int newIID)` that resets the IID count for the current Accessory + * see the [API Reference](docs/Reference.md) for details + +* **New ability to read Controller pairing data (for advanced use-cases only)** + + * adds new `homeSpan` methods `controllerListBegin()` and `controllerListEnd()` that returns iterators to HomeSpan's internal linked-list of Controller data records + * adds new methods to read each Controller's pairing data: + * `getID()` - returns a pointer to the 36-byte Device ID of the controller + * `getLTPK()` - a pointer to the 32-byte Long-Term Public Key of the controller + * `isAdmin()` - returns true if the controller has admin permission, else returns false + * adds new `homeSpan` method `setControllerCallback()` to set optional callback function that HomeSpan calls whenever a controller is added, removed, or updated + * see the [API Reference](docs/Reference.md) for details + +* **HomeSpan now supports the *write-response ("WR")* protocol** + * added automated handling of the HomeKits's *write-response ("WR")* protocol* + * not needed for any Characteristics that are currently supported by HomeSpan, but useful for experimentation and work with Custom Characteristics + * added extra checks when using `setVal()` + * a warning message is output on the Serial Monitor if `setVal()` is called to change the value of a Characteristic from within the `update()` method at the same time the Home App is sending an update request for that value + * does not apply if `setVal()` is called from within `update()` to change the value of a Characteristic in response to a *write-response* request from the Home App + +* **Fixed bug introduced in 1.9.0 that prevented `homeSpan.setPairingCode()` from saving (and subsequently using) the request Setup Pairing Code** (#786) * this method now operates silently, unless an invalid pairing code is provided, in which case an error is reported to the Serial Monitor and *the sketch is halted* * the process for setting the Pairing Code using the CLI 'S' command or via the Access Point are unchanged - confirmation messages are still output to the Serial Monitor and errors do *not* cause the sketch to halt + +* **Fixed latent bug in SpanPoint** + * HomeSpan would crash when printing **SpanPoint** configuration information to the Serial Monitor (the 'i' CLI command) if any of the instances of SpanPoint had *receiveSize=0* + * this bug never surfaced before since all the **SpanPoint examples** were based on receiving data and therefore had a non-zero *receiveSize* See [Releases](https://github.com/HomeSpan/HomeSpan/releases) for details on all changes and bug fixes included in this update. From 0c22bfb75210a50af76bd0bfb7b5414841f32a99 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Mon, 29 Apr 2024 15:45:34 -0500 Subject: [PATCH 103/154] Update README.md --- README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 5427231..6134b44 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ HomeSpan requires version 2.0.0 or later of the [Arduino-ESP32 Board Manager](ht ## ❗Latest Update - HomeSpan 1.9.1 (MM/DD/YYY) -* **HomeSpan now supports TLV8 "TAG-LENGTH-VALUE" Characteristics!** +* **HomeSpan now supports *Tag-Length-Value ("TLV8")* Characteristics!** * adds new, fully-integrated `TLV8()` class library for the creation and management of TLV8 objects * includes methods to handle standard byte-stream VALUES as well as strings, numerical values, zero-length tags, and sub-TLVs @@ -64,17 +64,18 @@ HomeSpan requires version 2.0.0 or later of the [Arduino-ESP32 Board Manager](ht * includes new [Tutorial Example 22 - TLV8 Characteristics](examples/22-TLV8_Characteristics) demonstrating use of the `TLV8()` class and TLV8 Characteristics * see the new [TLV8 Characteristics](docs/TLV8.md) page for complete details and documentation -* **New TLV8 Characteristic - *DisplayOrder*** +* **New *DisplayOrder* TLV8 Characteristic** * utlizes HomeSpan's new `TLV8()` library - * sets the order in which the Input Sources for a Television Service are displayed for selection in the Home App + * allows you to specify the exact order in which the Input Sources for a Television Service are displayed in the Home App * see [Tutorial Example 22 - TLV8 Characteristics](examples/22-TLV8_Characteristics) for details -* **New [Tutorial Example 21 - AccessoryIdentifier](examples/21-AccessoryIdentifier)** +* **New *AccessoryIdentifier* Tutorial** - * demonstrates how to trigger an Accessory's Identifier Characteristic, optionally used to help identify a device during initial pairing to the Home App + * demonstrates how to trigger an Accessory's Identifier Characteristic, optionally used to help identify a device during initial pairing + * see [Tutorial Example 21 - AccessoryIdentifier](examples/21-AccessoryIdentifier) -* **Added support for more Pixel chips** +* **Added support for customizing Pixel chips** * new constructor `Pixel(uint8_t pin, [pixelType_t pixelType])` allows your to set the order in which colors are transmitted to the pixel chip, where *pixelType* is one of the following: * PixelType::RGB, PixelType::RBG, PixelType::BRG, PixelType::BGR, PixelType::GBR, PixelType::GRB From 4af2bdbbe3198a24fc4ccd780767ed53eb8157a4 Mon Sep 17 00:00:00 2001 From: Gregg Date: Mon, 29 Apr 2024 21:22:37 -0500 Subject: [PATCH 104/154] Add displayOrder to ServiceList.md Run shell-script 'tools/makeServiceList' --- docs/ServiceList.md | 1 + tools/makeServices | 2 ++ 2 files changed, 3 insertions(+) diff --git a/docs/ServiceList.md b/docs/ServiceList.md index abff6e9..fb3b681 100644 --- a/docs/ServiceList.md +++ b/docs/ServiceList.md @@ -428,6 +428,7 @@ The pre-defined constant expressions for enumerated Characteristics are in names CharacteristicFormatPermsMinMaxConstants/Defaults Active (B0) :small_blue_diamond:
  • indicates if the Service is active/on
uint8PW+PR+EV01
  • INACTIVE (0) :heavy_check_mark:
  • ACTIVE (1) 
ActiveIdentifier (E7)
  • numerical Identifier of the InputSource selected in the Home App.
uint32PW+PR+EV02550 +DisplayOrder (136)
  • specifies the order in which the TV inputs are displayed for selection in the Home App
tlv8PR+EV01"" RemoteKey (E1)
  • triggers an update when the corresponding key is pressed in the Remote Control widget on an iPhone
uint8PW415
  • UP (4) 
  • DOWN (5) 
  • LEFT (6) 
  • RIGHT (7) 
  • CENTER (8) 
  • BACK (9) 
  • PLAY_PAUSE (11) 
  • INFO (15) 
PowerModeSelection (DF)
  • when defined, creates a "View TV Settings" button in the Home App that triggers an update to this Characteristic when pressed
uint8PW00
  • VIEW_SETTINGS (0) 
ConfiguredName (E3)
  • default display name of this Service
stringPW+PR+EV--"unnamed" diff --git a/tools/makeServices b/tools/makeServices index 279262b..8bc6c5d 100755 --- a/tools/makeServices +++ b/tools/makeServices @@ -54,6 +54,8 @@ BEGIN { uuid[char]=x[3] perms[char]=x[4] format[char]=tolower(x[5]) + if(format[char]=="tlv_enc") + format[char]="tlv8" static[char]=x[6] } From a52a2ad432452ed79047bf58a6d40d3d80eff3eb Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Mon, 29 Apr 2024 21:24:17 -0500 Subject: [PATCH 105/154] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6134b44..09d1c6a 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,7 @@ HomeSpan requires version 2.0.0 or later of the [Arduino-ESP32 Board Manager](ht * a warning message is output on the Serial Monitor if `setVal()` is called to change the value of a Characteristic from within the `update()` method at the same time the Home App is sending an update request for that value * does not apply if `setVal()` is called from within `update()` to change the value of a Characteristic in response to a *write-response* request from the Home App -* **Fixed bug introduced in 1.9.0 that prevented `homeSpan.setPairingCode()` from saving (and subsequently using) the request Setup Pairing Code** (#786) +* **Fixed bug introduced in 1.9.0 that prevented `homeSpan.setPairingCode()` from saving (and subsequently using) the request Setup Pairing Code** * this method now operates silently, unless an invalid pairing code is provided, in which case an error is reported to the Serial Monitor and *the sketch is halted* * the process for setting the Pairing Code using the CLI 'S' command or via the Access Point are unchanged - confirmation messages are still output to the Serial Monitor and errors do *not* cause the sketch to halt From 638285f48f55d6bd340fa757f1afde4f72b058d0 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Mon, 29 Apr 2024 21:31:56 -0500 Subject: [PATCH 106/154] Update README.md --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 09d1c6a..a3be5cd 100644 --- a/README.md +++ b/README.md @@ -85,8 +85,9 @@ HomeSpan requires version 2.0.0 or later of the [Arduino-ESP32 Board Manager](ht * users should switch to the new constructor to avoid potential compatibility issues with future versions of HomeSpan * added new method `boolean isRGBW()` * returns *true* if Pixel was constructed as RGBW, else *false* if constructed as RGB only (i.e. no white LED) - * created new PixelTester sketch (found under Other-> Examples) to aid in determining the *pixelType* for any LED Strip - + * added new [PixelTester](examples/Other%20Examples/PixelTester) sketch (found under *Other Examples*) to aid in determining the *pixelType* for any LED Strip + * see the [Pixels](docs/Pixels.md) page for details + * **New ability to read and set the IIDs of Services and Characteristics** * adds new `SpanService` method `getIID()` that returns the IID of a Service From d523a27124665d70eb00822fb7ac0bee1362f75c Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Mon, 29 Apr 2024 21:36:06 -0500 Subject: [PATCH 107/154] Update Tutorials.md --- docs/Tutorials.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/Tutorials.md b/docs/Tutorials.md index 732c9ae..256cbd5 100644 --- a/docs/Tutorials.md +++ b/docs/Tutorials.md @@ -139,6 +139,9 @@ An example of HomeKit's *undocumented* Television Service showing how different ### [Pixel](../examples/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 +### [PixelTester](../examples/Other%20Examples/PixelTester) +A sketch to aid in determining the *pixelType* for any RGB(W) LED Strip. See the [Addressable RGB LEDs](Pixels.md) page for full details + ### [CustomService](../examples/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 [Custom Characteristics and Custom Services Macros](Reference.md#custom-characteristics-and-custom-services-macros) for full details From 1863cc4309ab8201bb49efd259e23af4f2372d08 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Mon, 29 Apr 2024 21:37:54 -0500 Subject: [PATCH 108/154] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a3be5cd..db52f3d 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ HomeSpan requires version 2.0.0 or later of the [Arduino-ESP32 Board Manager](ht * added new method `boolean isRGBW()` * returns *true* if Pixel was constructed as RGBW, else *false* if constructed as RGB only (i.e. no white LED) * added new [PixelTester](examples/Other%20Examples/PixelTester) sketch (found under *Other Examples*) to aid in determining the *pixelType* for any LED Strip - * see the [Pixels](docs/Pixels.md) page for details + * see the [Adressable RGB LEDs](docs/Pixels.md) page for details * **New ability to read and set the IIDs of Services and Characteristics** From 8775a2df85d92ad8f105846322f75892a93077cb Mon Sep 17 00:00:00 2001 From: Gregg Date: Thu, 2 May 2024 20:45:30 -0500 Subject: [PATCH 109/154] Eliminated use of exceptions in PSRAM.h and added -fno-exceptions Added build_opt.h which include -fno-exceptions compiler instructions. Seems to save about 29K of flash in compiled code (compiling for ESP32) --- src/PSRAM.h | 11 +++++++---- src/build_opt.h | 1 + 2 files changed, 8 insertions(+), 4 deletions(-) create mode 100644 src/build_opt.h diff --git a/src/PSRAM.h b/src/PSRAM.h index dd9e03a..d5b0cb6 100644 --- a/src/PSRAM.h +++ b/src/PSRAM.h @@ -45,11 +45,14 @@ template struct Mallocator { typedef T value_type; Mallocator() = default; - template constexpr Mallocator(const Mallocator&) noexcept {} + template constexpr Mallocator(const Mallocator&) {} [[nodiscard]] T* allocate(std::size_t n) { - if(n > std::size_t(-1) / sizeof(T)) throw std::bad_alloc(); - if(auto p = static_cast(HS_MALLOC(n*sizeof(T)))) return p; - throw std::bad_alloc(); + auto p = static_cast(HS_MALLOC(n*sizeof(T))); + if(p==NULL){ + Serial.printf("\n\n*** FATAL ERROR: Requested allocation of %d bytes failed. Program Halting.\n\n",n*sizeof(T)); + while(1); + } + return p; } void deallocate(T* p, std::size_t) noexcept { std::free(p); } }; diff --git a/src/build_opt.h b/src/build_opt.h new file mode 100644 index 0000000..f7f5b39 --- /dev/null +++ b/src/build_opt.h @@ -0,0 +1 @@ +-fno-exceptions From 80a8935828f5d3a9b673d24b4e6d4b7ca3058f14 Mon Sep 17 00:00:00 2001 From: Gregg Date: Fri, 3 May 2024 06:36:25 -0500 Subject: [PATCH 110/154] Moved definitions of non-template SpanCharacteristic methods from HomeSpan.h to HomeSpan.cpp --- src/HomeSpan.cpp | 252 ++++++++++++++++++++++++++++++++++++++++++++++ src/HomeSpan.h | 257 +++++++---------------------------------------- 2 files changed, 286 insertions(+), 223 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index e815af0..871ef55 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -1886,6 +1886,207 @@ SpanCharacteristic::~SpanCharacteristic(){ /////////////////////////////// +String SpanCharacteristic::uvPrint(UVal &u){ + char c[64]; + switch(format){ + case FORMAT::BOOL: + return(String(u.BOOL)); + case FORMAT::INT: + return(String(u.INT)); + case FORMAT::UINT8: + return(String(u.UINT8)); + case FORMAT::UINT16: + return(String(u.UINT16)); + case FORMAT::UINT32: + return(String(u.UINT32)); + case FORMAT::UINT64: + sprintf(c,"%llu",u.UINT64); + return(String(c)); + case FORMAT::FLOAT: + sprintf(c,"%g",u.FLOAT); + return(String(c)); + case FORMAT::STRING: + case FORMAT::DATA: + case FORMAT::TLV_ENC: + return(String("\"") + String(u.STRING) + String("\"")); + } // switch + return(String()); // included to prevent compiler warnings +} + +/////////////////////////////// + +void SpanCharacteristic::uvSet(UVal &dest, UVal &src){ + if(format>=FORMAT::STRING) + uvSet(dest,(const char *)src.STRING); + else + dest=src; +} + +/////////////////////////////// + +void SpanCharacteristic::uvSet(UVal &u, const char *val){ + u.STRING = (char *)HS_REALLOC(u.STRING, strlen(val) + 1); + strcpy(u.STRING, val); +} + +/////////////////////////////// + +char *SpanCharacteristic::getStringGeneric(UVal &val){ + if(format>=FORMAT::STRING) + return val.STRING; + + return NULL; +} + +/////////////////////////////// + +void SpanCharacteristic::setString(const char *val, boolean notify){ + + setValCheck(); + uvSet(value,val); + setValFinish(notify); +} + +/////////////////////////////// + +size_t SpanCharacteristic::getDataGeneric(uint8_t *data, size_t len, UVal &val){ + if(format0){ + size_t olen; + mbedtls_base64_encode(NULL,0,&olen,NULL,len); // get length of string buffer needed (mbedtls includes the trailing null in this size) + value.STRING = (char *)HS_REALLOC(value.STRING,olen); // allocate sufficient size for storing value + mbedtls_base64_encode((uint8_t*)value.STRING,olen,&olen,data,len ); // encode data into string buf + } else { + value.STRING = (char *)HS_REALLOC(value.STRING,1); // allocate sufficient size for just trailing null character + *value.STRING ='\0'; + } + + setValFinish(notify); +} + +/////////////////////////////// + +size_t SpanCharacteristic::getTLVGeneric(TLV8 &tlv, UVal &val){ + + if(format tBuf(bufSize); // create fixed-size buffer to store decoded bytes + tlv.wipe(); // clear TLV completely + + size_t nChars=strlen(val.STRING); // total characters to decode + uint8_t *p=(uint8_t *)val.STRING; // set pointer to beginning of value + const size_t decodeSize=bufSize/3*4; // number of characters to decode in each pass + int status=0; + + while(nChars>0){ + size_t olen; + size_t n=nChars0){ + LOG0("\n*** WARNING: Can't unpack Characteristic::%s with getTLV(). TLV record is incomplete or corrupted!\n\n",hapName); + tlv.wipe(); + return(0); + } +return(tlv.pack_size()); +} + +/////////////////////////////// + +void SpanCharacteristic::setTLV(TLV8 &tlv, boolean notify){ + + setValCheck(); + + const size_t bufSize=36; // maximum size of buffer to store packed TLV bytes before encoding directly into value; must be multiple of 3 + size_t nBytes=tlv.pack_size(); // total size of packed TLV in bytes + + if(nBytes>0){ + size_t nChars; + mbedtls_base64_encode(NULL,0,&nChars,NULL,nBytes); // get length of string buffer needed (mbedtls includes the trailing null in this size) + value.STRING = (char *)HS_REALLOC(value.STRING,nChars); // allocate sufficient size for storing value + TempBuffer tBuf(bufSize); // create fixed-size buffer to store packed TLV bytes + tlv.pack_init(); // initialize TLV packing + uint8_t *p=(uint8_t *)value.STRING; // set pointer to beginning of value + while((nBytes=tlv.pack(tBuf,bufSize))>0){ // pack the next set of TLV bytes, up to a maximum of bufSize, into tBuf + size_t olen; // number of characters written (excludes null character) + mbedtls_base64_encode(p,nChars,&olen,tBuf,nBytes); // encode data directly into value + p+=olen; // advance pointer to null character + nChars-=olen; // subtract number of characters remaining + } + } else { + value.STRING = (char *)HS_REALLOC(value.STRING,1); // allocate sufficient size for just trailing null character + *value.STRING ='\0'; + } + + setValFinish(notify); +} + +/////////////////////////////// + +void SpanCharacteristic::setValCheck(){ + if(updateFlag==1) + LOG0("\n*** WARNING: Attempt to set value of Characteristic::%s within update() while it is being simultaneously updated by Home App. This may cause device to become non-responsive!\n\n",hapName); +} + +/////////////////////////////// + +void SpanCharacteristic::setValFinish(boolean notify){ + + uvSet(newValue,value); + updateTime=homeSpan.snapTime; + + if(notify){ + if((perms&EV) && (updateFlag!=2)){ // only broadcast notification if EV permission is set AND update is NOT being done in context of write-response + 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 printfNotify knows to consider this "update" + homeSpan.Notifications.push_back(sb); // store SpanBuf in Notifications vector + } + + if(nvsKey){ + nvs_set_str(homeSpan.charNVS,nvsKey,value.STRING); // store data + nvs_commit(homeSpan.charNVS); + } + } +} + +/////////////////////////////// + void SpanCharacteristic::printfAttributes(int flags){ const char permCodes[][7]={"pr","pw","ev","aa","tw","hd","wr"}; @@ -2066,6 +2267,57 @@ unsigned long SpanCharacteristic::timeVal(){ /////////////////////////////// +boolean SpanCharacteristic::updated(){ + + return(updateFlag>0); +} + +/////////////////////////////// + +uint32_t SpanCharacteristic::getIID(){ + + return(iid); +} + +/////////////////////////////// + +SpanCharacteristic *SpanCharacteristic::setPerms(uint8_t perms){ + perms&=0x7F; + if(perms>0) + this->perms=perms; + return(this); +} + +/////////////////////////////// + +SpanCharacteristic *SpanCharacteristic::addPerms(uint8_t dPerms){ + return(setPerms(perms|dPerms)); +} + +/////////////////////////////// + +SpanCharacteristic *SpanCharacteristic::removePerms(uint8_t dPerms){ + return(setPerms(perms&(~dPerms))); +} + +/////////////////////////////// + +SpanCharacteristic *SpanCharacteristic::setDescription(const char *c){ + desc = (char *)HS_REALLOC(desc, strlen(c) + 1); + strcpy(desc, c); + return(this); +} + +/////////////////////////////// + +SpanCharacteristic *SpanCharacteristic::setUnit(const char *c){ + unit = (char *)HS_REALLOC(unit, strlen(c) + 1); + strcpy(unit, c); + return(this); +} + +/////////////////////////////// + SpanCharacteristic *SpanCharacteristic::setValidValues(int n, ...){ String s="["; diff --git a/src/HomeSpan.h b/src/HomeSpan.h index d5b656b..0ce7fdb 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -549,47 +549,12 @@ class SpanCharacteristic{ void printfAttributes(int flags); // writes Characteristic JSON to hapOut stream StatusCode loadUpdate(char *val, char *ev, boolean wr); // load updated val/ev from PUT /characteristic JSON request. Return intitial HAP status code (checks to see if characteristic is found, is writable, etc.) - - String uvPrint(UVal &u){ - char c[64]; - switch(format){ - case FORMAT::BOOL: - return(String(u.BOOL)); - case FORMAT::INT: - return(String(u.INT)); - case FORMAT::UINT8: - return(String(u.UINT8)); - case FORMAT::UINT16: - return(String(u.UINT16)); - case FORMAT::UINT32: - return(String(u.UINT32)); - case FORMAT::UINT64: - sprintf(c,"%llu",u.UINT64); - return(String(c)); - case FORMAT::FLOAT: - sprintf(c,"%g",u.FLOAT); - return(String(c)); - case FORMAT::STRING: - case FORMAT::DATA: - case FORMAT::TLV_ENC: - return(String("\"") + String(u.STRING) + String("\"")); - } // switch - return(String()); // included to prevent compiler warnings - } + String uvPrint(UVal &u); // returns "printable" String for any type of Characteristic + + void uvSet(UVal &dest, UVal &src); // copies UVal src into UVal dest + void uvSet(UVal &u, const char *val); // copies string val into UVal u - void uvSet(UVal &dest, UVal &src){ - if(format>=FORMAT::STRING) - uvSet(dest,(const char *)src.STRING); - else - dest=src; - } - - void uvSet(UVal &u, const char *val){ - u.STRING = (char *)HS_REALLOC(u.STRING, strlen(val) + 1); - strcpy(u.STRING, val); - } - - template void uvSet(UVal &u, T val){ + template void uvSet(UVal &u, T val){ // copies any other type of val into UVal u switch(format){ case FORMAT::BOOL: u.BOOL=(boolean)val; @@ -619,7 +584,7 @@ class SpanCharacteristic{ } // switch } - template T uvGet(UVal &u){ + template T uvGet(UVal &u){ // returns UVal u, cast into T switch(format){ case FORMAT::BOOL: @@ -646,7 +611,7 @@ class SpanCharacteristic{ protected: - ~SpanCharacteristic(); // destructor + ~SpanCharacteristic(); // destructor template void init(T val, boolean nvsStore, A min=0, B max=1){ @@ -688,10 +653,8 @@ class SpanCharacteristic{ public: - void *operator new(size_t size){return(HS_MALLOC(size));} // override new operator to use PSRAM when available SpanCharacteristic(HapChar *hapChar, boolean isCustom=false); // constructor - - uint32_t getIID(){return(iid);} // returns IID of Characteristic + void *operator new(size_t size){return(HS_MALLOC(size));} // override new operator to use PSRAM when available template T getVal(){ return(uvGet(value)); @@ -700,155 +663,6 @@ class SpanCharacteristic{ template T getNewVal(){ return(uvGet(newValue)); } - - char *getStringGeneric(UVal &val){ - if(format>=FORMAT::STRING) - return val.STRING; - - return NULL; - } - - char *getString(){return(getStringGeneric(value));} - char *getNewString(){return(getStringGeneric(newValue));} - - void setString(const char *val, boolean notify=true){ - - setValCheck(); - uvSet(value,val); - setValFinish(notify); - } - - size_t getDataGeneric(uint8_t *data, size_t len, UVal &val){ - if(format0){ - size_t olen; - mbedtls_base64_encode(NULL,0,&olen,NULL,len); // get length of string buffer needed (mbedtls includes the trailing null in this size) - value.STRING = (char *)HS_REALLOC(value.STRING,olen); // allocate sufficient size for storing value - mbedtls_base64_encode((uint8_t*)value.STRING,olen,&olen,data,len ); // encode data into string buf - } else { - value.STRING = (char *)HS_REALLOC(value.STRING,1); // allocate sufficient size for just trailing null character - *value.STRING ='\0'; - } - - setValFinish(notify); - } - - size_t getTLVGeneric(TLV8 &tlv, UVal &val){ - - if(format tBuf(bufSize); // create fixed-size buffer to store decoded bytes - tlv.wipe(); // clear TLV completely - - size_t nChars=strlen(val.STRING); // total characters to decode - uint8_t *p=(uint8_t *)val.STRING; // set pointer to beginning of value - const size_t decodeSize=bufSize/3*4; // number of characters to decode in each pass - int status=0; - - while(nChars>0){ - size_t olen; - size_t n=nChars0){ - LOG0("\n*** WARNING: Can't unpack Characteristic::%s with getTLV(). TLV record is incomplete or corrupted!\n\n",hapName); - tlv.wipe(); - return(0); - } - return(tlv.pack_size()); - } - - size_t getTLV(TLV8 &tlv){return(getTLVGeneric(tlv,value));} - size_t getNewTLV(TLV8 &tlv){return(getTLVGeneric(tlv,newValue));} - - void setTLV(TLV8 &tlv, boolean notify=true){ - - setValCheck(); - - const size_t bufSize=36; // maximum size of buffer to store packed TLV bytes before encoding directly into value; must be multiple of 3 - size_t nBytes=tlv.pack_size(); // total size of packed TLV in bytes - - if(nBytes>0){ - size_t nChars; - mbedtls_base64_encode(NULL,0,&nChars,NULL,nBytes); // get length of string buffer needed (mbedtls includes the trailing null in this size) - value.STRING = (char *)HS_REALLOC(value.STRING,nChars); // allocate sufficient size for storing value - TempBuffer tBuf(bufSize); // create fixed-size buffer to store packed TLV bytes - tlv.pack_init(); // initialize TLV packing - uint8_t *p=(uint8_t *)value.STRING; // set pointer to beginning of value - while((nBytes=tlv.pack(tBuf,bufSize))>0){ // pack the next set of TLV bytes, up to a maximum of bufSize, into tBuf - size_t olen; // number of characters written (excludes null character) - mbedtls_base64_encode(p,nChars,&olen,tBuf,nBytes); // encode data directly into value - p+=olen; // advance pointer to null character - nChars-=olen; // subtract number of characters remaining - } - } else { - value.STRING = (char *)HS_REALLOC(value.STRING,1); // allocate sufficient size for just trailing null character - *value.STRING ='\0'; - } - - setValFinish(notify); - } - - void setValCheck(){ - if(updateFlag==1) - LOG0("\n*** WARNING: Attempt to set value of Characteristic::%s within update() while it is being simultaneously updated by Home App. This may cause device to become non-responsive!\n\n",hapName); - } - - void setValFinish(boolean notify){ - - uvSet(newValue,value); - updateTime=homeSpan.snapTime; - - if(notify){ - if((perms&EV) && (updateFlag!=2)){ // only broadcast notification if EV permission is set AND update is NOT being done in context of write-response - 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 printfNotify knows to consider this "update" - homeSpan.Notifications.push_back(sb); // store SpanBuf in Notifications vector - } - - if(nvsKey){ - nvs_set_str(homeSpan.charNVS,nvsKey,value.STRING); // store data - nvs_commit(homeSpan.charNVS); - } - } - } template void setVal(T val, boolean notify=true){ @@ -880,10 +694,29 @@ class SpanCharacteristic{ } } - } // setVal() + } // setVal() + + char *getStringGeneric(UVal &val); // return the specified UVal for string-based Characteristics + char *getString(){return(getStringGeneric(value));} // return the value for string-based Characteristics + char *getNewString(){return(getStringGeneric(newValue));} // return the newValue for string-based Characteristics + void setString(const char *val, boolean notify=true); // set the value and newValue for string-based Characteristic - boolean updated(){return(updateFlag>0);} // returns true within update() if Characteristic was updated by Home App + size_t getDataGeneric(uint8_t *data, size_t len, UVal &val); // return the specified UVal for data-based Characteristics + size_t getData(uint8_t *data, size_t len){return(getDataGeneric(data,len,value));} // return the value for data-based Characteristics + size_t getNewData(uint8_t *data, size_t len){return(getDataGeneric(data,len,newValue));} // return the newValue for data-based Characteristics + void setData(uint8_t *data, size_t len, boolean notify=true); // set the value and newValue for data-based Characteristic + + size_t getTLVGeneric(TLV8 &tlv, UVal &val); // return the specified UVal for tlv8-based Characteristics + size_t getTLV(TLV8 &tlv){return(getTLVGeneric(tlv,value));} // return the value for tlv8-based Characteristics + size_t getNewTLV(TLV8 &tlv){return(getTLVGeneric(tlv,newValue));} // return the newValue for tlv8-based Characteristics + void setTLV(TLV8 &tlv, boolean notify=true); // set the value and newValue for tlv8-based Characteristic + + void setValCheck(); // initial check before setting value of any Characteristic + void setValFinish(boolean notify); // final processing after setting value of any Characteristic + + boolean updated(); // returns true within update() if Characteristic was updated by Home App unsigned long timeVal(); // returns time elapsed (in millis) since value was last updated, either by Home App or by using setVal() + uint32_t getIID(); // returns IID of Characteristic SpanCharacteristic *setValidValues(int n, ...); // sets a list of 'n' valid values allowed for a Characteristic and returns pointer to self. Only applicable if format=INT, UINT8, UINT16, or UINT32 @@ -901,33 +734,11 @@ class SpanCharacteristic{ } // setRange() - SpanCharacteristic *setPerms(uint8_t perms){ - perms&=0x7F; - if(perms>0) - this->perms=perms; - return(this); - } - - SpanCharacteristic *addPerms(uint8_t dPerms){ - return(setPerms(perms|dPerms)); - } - - SpanCharacteristic *removePerms(uint8_t dPerms){ - return(setPerms(perms&(~dPerms))); - } - - SpanCharacteristic *setDescription(const char *c){ - desc = (char *)HS_REALLOC(desc, strlen(c) + 1); - strcpy(desc, c); - return(this); - } - - SpanCharacteristic *setUnit(const char *c){ - unit = (char *)HS_REALLOC(unit, strlen(c) + 1); - strcpy(unit, c); - return(this); - } - + SpanCharacteristic *setPerms(uint8_t perms); // sets permissions of a Characteristic + SpanCharacteristic *addPerms(uint8_t dPerms); // add permissions of a Characteristic + SpanCharacteristic *removePerms(uint8_t dPerms); // removes permissions of a Characteristic + SpanCharacteristic *setDescription(const char *c); // sets description of a Characteristic + SpanCharacteristic *setUnit(const char *c); // set unit of a Characteristic }; /////////////////////////////// From 214286f4de2ed9a3de199370d2533282d2a64a50 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 4 May 2024 08:07:03 -0500 Subject: [PATCH 111/154] remove deprecated SpanRange structure Has been deprecated many versions ago. --- src/HomeSpan.cpp | 15 --------------- src/HomeSpan.h | 10 ---------- 2 files changed, 25 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 871ef55..67093a6 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -2353,21 +2353,6 @@ SpanCharacteristic *SpanCharacteristic::setValidValues(int n, ...){ return(this); } -/////////////////////////////// -// SpanRange // -/////////////////////////////// - -SpanRange::SpanRange(int min, int max, int step){ - - if(homeSpan.Accessories.empty() || homeSpan.Accessories.back()->Services.empty() || homeSpan.Accessories.back()->Services.back()->Characteristics.empty() ){ - LOG0("\nFATAL ERROR! Can't create new SpanRange(%d,%d,%d) without a defined Characteristic ***\n",min,max,step); - LOG0("\n=== PROGRAM HALTED ==="); - while(1); - } else { - homeSpan.Accessories.back()->Services.back()->Characteristics.back()->setRange(min,max,step); - } -} - /////////////////////////////// // SpanButton // /////////////////////////////// diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 0ce7fdb..374bfdd 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -109,7 +109,6 @@ struct Span; struct SpanAccessory; struct SpanService; struct SpanCharacteristic; -struct SpanRange; struct SpanBuf; struct SpanButton; struct SpanUserCommand; @@ -230,7 +229,6 @@ class Span{ friend class SpanCharacteristic; friend class SpanUserCommand; friend class SpanButton; - friend class SpanRange; friend class SpanWebLog; friend class SpanOTA; friend class Network; @@ -441,7 +439,6 @@ class SpanAccessory{ friend class SpanService; friend class SpanCharacteristic; friend class SpanButton; - friend class SpanRange; uint32_t aid=0; // Accessory Instance ID (HAP Table 6-1) uint32_t iidCount=0; // running count of iid to use for Services and Characteristics associated with this Accessory @@ -466,7 +463,6 @@ class SpanService{ friend class Span; friend class SpanAccessory; friend class SpanCharacteristic; - friend class SpanRange; uint32_t iid=0; // Instance ID (HAP Table 6-2) const char *type; // Service Type @@ -743,12 +739,6 @@ class SpanCharacteristic{ /////////////////////////////// -struct [[deprecated("Please use Characteristic::setRange() method instead.")]] SpanRange{ - SpanRange(int min, int max, int step); -}; - -/////////////////////////////// - class SpanButton : public PushButton { friend class Span; From be4825dacb6b7318b16f975f05dd6c4997fa3658 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 4 May 2024 08:31:34 -0500 Subject: [PATCH 112/154] Move "internal" SpanCharacteristic methods from public to private As well as additional clean-up of organization and format of SpanCharacteristic prototype declarations. --- src/HomeSpan.h | 76 +++++++++++++++++++++++--------------------------- 1 file changed, 35 insertions(+), 41 deletions(-) diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 374bfdd..e14f4d4 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -550,7 +550,7 @@ class SpanCharacteristic{ void uvSet(UVal &dest, UVal &src); // copies UVal src into UVal dest void uvSet(UVal &u, const char *val); // copies string val into UVal u - template void uvSet(UVal &u, T val){ // copies any other type of val into UVal u + template void uvSet(UVal &u, T val){ // copies numeric val into UVal u switch(format){ case FORMAT::BOOL: u.BOOL=(boolean)val; @@ -580,7 +580,11 @@ class SpanCharacteristic{ } // switch } - template T uvGet(UVal &u){ // returns UVal u, cast into T + char *getStringGeneric(UVal &val); // gets the specified UVal for string-based Characteristics + size_t getDataGeneric(uint8_t *data, size_t len, UVal &val); // gets the specified UVal for data-based Characteristics + size_t getTLVGeneric(TLV8 &tlv, UVal &val); // gets the specified UVal for tlv8-based Characteristics + + template T uvGet(UVal &u){ // gets the specified UVal for numeric-based Characteristics switch(format){ case FORMAT::BOOL: @@ -604,7 +608,10 @@ class SpanCharacteristic{ } return((T)0); // included to prevent compiler warnings } - + + void setValCheck(); // initial check before setting value of any Characteristic + void setValFinish(boolean notify); // final processing after setting value of any Characteristic + protected: ~SpanCharacteristic(); // destructor @@ -649,18 +656,24 @@ class SpanCharacteristic{ public: - SpanCharacteristic(HapChar *hapChar, boolean isCustom=false); // constructor - void *operator new(size_t size){return(HS_MALLOC(size));} // override new operator to use PSRAM when available + SpanCharacteristic(HapChar *hapChar, boolean isCustom=false); // SpanCharacteristic constructor + void *operator new(size_t size){return(HS_MALLOC(size));} // override new operator to use PSRAM when available - template T getVal(){ - return(uvGet(value)); - } + template T getVal(){return(uvGet(value));} // gets the value for numeric-based Characteristics + char *getString(){return(getStringGeneric(value));} // gets the value for string-based Characteristics + size_t getData(uint8_t *data, size_t len){return(getDataGeneric(data,len,value));} // gets the value for data-based Characteristics + size_t getTLV(TLV8 &tlv){return(getTLVGeneric(tlv,value));} // gets the value for tlv8-based Characteristics - template T getNewVal(){ - return(uvGet(newValue)); - } + template T getNewVal(){return(uvGet(newValue));} // gets the newValue for numeric-based Characteristics + char *getNewString(){return(getStringGeneric(newValue));} // gets the newValue for string-based Characteristics + size_t getNewData(uint8_t *data, size_t len){return(getDataGeneric(data,len,newValue));} // gets the newValue for data-based Characteristics + size_t getNewTLV(TLV8 &tlv){return(getTLVGeneric(tlv,newValue));} // gets the newValue for tlv8-based Characteristics - template void setVal(T val, boolean notify=true){ + void setString(const char *val, boolean notify=true); // sets the value and newValue for string-based Characteristic + void setData(uint8_t *data, size_t len, boolean notify=true); // sets the value and newValue for data-based Characteristic + void setTLV(TLV8 &tlv, boolean notify=true); // sets the value and newValue for tlv8-based Characteristic + + template void setVal(T val, boolean notify=true){ // sets the value and newValue for numeric-based Characteristics setValCheck(); @@ -692,31 +705,18 @@ class SpanCharacteristic{ } // setVal() - char *getStringGeneric(UVal &val); // return the specified UVal for string-based Characteristics - char *getString(){return(getStringGeneric(value));} // return the value for string-based Characteristics - char *getNewString(){return(getStringGeneric(newValue));} // return the newValue for string-based Characteristics - void setString(const char *val, boolean notify=true); // set the value and newValue for string-based Characteristic + boolean updated(); // returns true within update() if Characteristic was updated by Home App + unsigned long timeVal(); // returns time elapsed (in millis) since value was last updated, either by Home App or by using setVal() + uint32_t getIID(); // returns IID of Characteristic - size_t getDataGeneric(uint8_t *data, size_t len, UVal &val); // return the specified UVal for data-based Characteristics - size_t getData(uint8_t *data, size_t len){return(getDataGeneric(data,len,value));} // return the value for data-based Characteristics - size_t getNewData(uint8_t *data, size_t len){return(getDataGeneric(data,len,newValue));} // return the newValue for data-based Characteristics - void setData(uint8_t *data, size_t len, boolean notify=true); // set the value and newValue for data-based Characteristic + SpanCharacteristic *setPerms(uint8_t perms); // sets permissions of a Characteristic + SpanCharacteristic *addPerms(uint8_t dPerms); // add permissions of a Characteristic + SpanCharacteristic *removePerms(uint8_t dPerms); // removes permissions of a Characteristic + SpanCharacteristic *setDescription(const char *c); // sets description of a Characteristic + SpanCharacteristic *setUnit(const char *c); // set unit of a Characteristic + SpanCharacteristic *setValidValues(int n, ...); // sets a list of 'n' valid values allowed for a Characteristic - only applicable if format=INT, UINT8, UINT16, or UINT32 - size_t getTLVGeneric(TLV8 &tlv, UVal &val); // return the specified UVal for tlv8-based Characteristics - size_t getTLV(TLV8 &tlv){return(getTLVGeneric(tlv,value));} // return the value for tlv8-based Characteristics - size_t getNewTLV(TLV8 &tlv){return(getTLVGeneric(tlv,newValue));} // return the newValue for tlv8-based Characteristics - void setTLV(TLV8 &tlv, boolean notify=true); // set the value and newValue for tlv8-based Characteristic - - void setValCheck(); // initial check before setting value of any Characteristic - void setValFinish(boolean notify); // final processing after setting value of any Characteristic - - boolean updated(); // returns true within update() if Characteristic was updated by Home App - unsigned long timeVal(); // returns time elapsed (in millis) since value was last updated, either by Home App or by using setVal() - uint32_t getIID(); // returns IID of Characteristic - - SpanCharacteristic *setValidValues(int n, ...); // sets a list of 'n' valid values allowed for a Characteristic and returns pointer to self. Only applicable if format=INT, UINT8, UINT16, or UINT32 - - template SpanCharacteristic *setRange(A min, B max, S step=0){ + template SpanCharacteristic *setRange(A min, B max, S step=0){ // sets the allowed range of a Characteristic if(!staticRange){ uvSet(minValue,min); @@ -729,12 +729,6 @@ class SpanCharacteristic{ return(this); } // setRange() - - SpanCharacteristic *setPerms(uint8_t perms); // sets permissions of a Characteristic - SpanCharacteristic *addPerms(uint8_t dPerms); // add permissions of a Characteristic - SpanCharacteristic *removePerms(uint8_t dPerms); // removes permissions of a Characteristic - SpanCharacteristic *setDescription(const char *c); // sets description of a Characteristic - SpanCharacteristic *setUnit(const char *c); // set unit of a Characteristic }; /////////////////////////////// From 747b8c32441d3b50d00e68609b9334563ae3a5c9 Mon Sep 17 00:00:00 2001 From: Gregg Date: Wed, 8 May 2024 06:45:57 -0500 Subject: [PATCH 113/154] initial change in hap[i] array to reserve space according to socket number --- src/HAP.cpp | 8 +-- src/HAP.h | 1 + src/HomeSpan.cpp | 134 ++++++++++++++++++++--------------------------- src/build_opt.h | 1 - src/src.ino | 21 ++------ 5 files changed, 65 insertions(+), 100 deletions(-) delete mode 100644 src/build_opt.h diff --git a/src/HAP.cpp b/src/HAP.cpp index e5d6d07..fb92a05 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -1253,8 +1253,8 @@ void HAPClient::checkTimedWrites(){ void HAPClient::eventNotify(SpanBuf *pObj, int nObj, int ignoreClient){ - for(int cNum=0;cNumclient && cNum!=ignoreClient){ // if there is a client connected to this slot and it is NOT flagged to be ignored (in cases where it is the client making a PUT request) + for(int cNum=0;cNumclient && cNum!=ignoreClient){ // if there is a client connected to this slot and it is NOT flagged to be ignored (in cases where it is the client making a PUT request) homeSpan.printfNotify(pObj,nObj,cNum); // create JSON (which may be of zero length if there are no applicable notifications for this cNum) size_t nBytes=hapOut.getSize(); @@ -1470,8 +1470,8 @@ void HAPClient::removeController(uint8_t *id){ void HAPClient::tearDown(uint8_t *id){ - for(int i=0;iclient && (id==NULL || (hap[i]->cPair && !memcmp(id,hap[i]->cPair->ID,hap_controller_IDBYTES)))){ + for(int i=0;iclient && (id==NULL || (hap[i]->cPair && !memcmp(id,hap[i]->cPair->ID,hap_controller_IDBYTES)))){ LOG1("*** Terminating Client #%d\n",i); hap[i]->client.stop(); } diff --git a/src/HAP.h b/src/HAP.h index 8fd6f15..99f7551 100644 --- a/src/HAP.h +++ b/src/HAP.h @@ -93,6 +93,7 @@ struct HAPClient { WiFiClient client; // handle to client Controller *cPair=NULL; // pointer to info on current, session-verified Paired Controller (NULL=un-verified, and therefore un-encrypted, connection) + boolean isConnected=false; // flag to indicate client is connect // These temporary Curve25519 keys are generated in the first call to pair-verify and used in the second call to pair-verify so must persist for a short period diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 67093a6..0683012 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -89,14 +89,9 @@ void Span::begin(Category catID, const char *displayName, const char *hostNameBa esp_task_wdt_delete(xTaskGetIdleTaskHandleForCPU(0)); // required to avoid watchdog timeout messages from ESP32-C3 - if(requestedMaxConhasClient()){ // found new client + + WiFiClient newClient=hapServer->available(); // get new client - if(newClient=hapServer->available()){ // found a new HTTP client - int freeSlot=getFreeSlot(); // get next free slot + int socket=newClient.fd()-LWIP_SOCKET_OFFSET; // get socket number (starting at zero) + + if(hap[socket]==NULL) // create HAPClient at that socket if it does not alreay exist + hap[socket]=new HAPClient; - if(freeSlot==-1){ // no available free slots - freeSlot=randombytes_uniform(maxConnections); - LOG2("=======================================\n"); - LOG1("** Freeing Client #"); - LOG1(freeSlot); - LOG1(" ("); - LOG1(millis()/1000); - LOG1(" sec) "); - LOG1(hap[freeSlot]->client.remoteIP()); - LOG1("\n"); - hap[freeSlot]->client.stop(); // disconnect client from first slot and re-use - } - - hap[freeSlot]->client=newClient; // copy new client handle into free slot + hap[socket]->client=newClient; // copy new client handle + hap[socket]->isConnected=true; // set isConnected flag + hap[socket]->cPair=NULL; // reset pointer to verified ID + homeSpan.clearNotify(socket); // clear all notification requests for this connection + HAPClient::pairStatus=pairState_M1; // reset starting PAIR STATE (which may be needed if Accessory failed in middle of pair-setup) LOG2("=======================================\n"); - LOG1("** Client #"); - LOG1(freeSlot); - LOG1(" Connected: ("); - LOG1(millis()/1000); - LOG1(" sec) "); - LOG1(hap[freeSlot]->client.remoteIP()); - LOG1(" on Socket "); - LOG1(hap[freeSlot]->client.fd()-LWIP_SOCKET_OFFSET+1); - LOG1("/"); - LOG1(CONFIG_LWIP_MAX_SOCKETS); - LOG1("\n"); + LOG1("** Client #%d Connected (%lu sec): %s\n",socket,millis()/1000,newClient.remoteIP().toString().c_str()); LOG2("\n"); - - hap[freeSlot]->cPair=NULL; // reset pointer to verified ID - homeSpan.clearNotify(freeSlot); // clear all notification requests for this connection - HAPClient::pairStatus=pairState_M1; // reset starting PAIR STATE (which may be needed if Accessory failed in middle of pair-setup) } - - for(int i=0;iclient && hap[i]->client.available()){ // if connection exists and data is available + for(int i=0;iclient.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("** Disconnected Client #"); - LOG1(i); - LOG1(" ("); - LOG1(millis()/1000); - LOG1(" sec)\n"); + if(hap[i]){ // if this socket has a configured HAPClient + if(hap[i]->client){ // if the client is connected + if(hap[i]->client.available()){ // if client has data available + 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 + } + } + else if(hap[i]->isConnected){ // if client is not connected, but HAPClient thinks it is + LOG1("** Client #%d DISCONNECTED (%lu sec)\n",i,millis()/1000); + hap[i]->isConnected=false; } - - LOG2("\n"); - - } // process HAP Client - } // for-loop over connection slots - + } + } + snapTime=millis(); // snap the current time for use in ALL loop routines for(auto it=Loops.begin();it!=Loops.end();it++) // call loop() for all Services with over-ridden loop() methods @@ -587,6 +559,13 @@ void Span::processSerialCommand(const char *c){ switch(c[0]){ + case 'Z': { + for(int i=0;iclient) + hap[i]->client.stop(); + } + break; + case 's': { LOG0("\n*** HomeSpan Status ***\n\n"); @@ -603,27 +582,26 @@ void Span::processSerialCommand(const char *c){ HAPClient::printControllers(); LOG0("\n"); - for(int i=0;iclient){ - - LOG0("%s on Socket %d/%d",hap[i]->client.remoteIP().toString().c_str(),hap[i]->client.fd()-LWIP_SOCKET_OFFSET+1,CONFIG_LWIP_MAX_SOCKETS); + for(int i=0;icPair){ - LOG0(" ID="); - HAPClient::charPrintRow(hap[i]->cPair->getID(),36); - LOG0(hap[i]->cPair->isAdmin()?" (admin)":" (regular)"); + if(hap[i]->client){ + LOG0(" %s",hap[i]->client.remoteIP().toString().c_str()); + + if(hap[i]->cPair){ + LOG0(" ID="); + HAPClient::charPrintRow(hap[i]->cPair->getID(),36); + LOG0(hap[i]->cPair->isAdmin()?" (admin)":" (regular)"); + } else { + LOG0(" (unverified)"); + } } else { - LOG0(" (unverified)"); + LOG0(" unconnected"); } - - } else { - LOG0("(unconnected)"); + LOG0("\n"); } - - LOG0("\n"); - } - + } LOG0("\n*** End Status ***\n\n"); } break; @@ -914,8 +892,8 @@ void Span::processSerialCommand(const char *c){ if(((*chr)->perms)&EV){ LOG0(", EV=("); boolean addComma=false; - for(int i=0;iev[i] && hap[i]->client){ + for(int i=0;iev[i] && hap[i] && hap[i]->client){ LOG0("%s%d",addComma?",":"",i); addComma=true; } diff --git a/src/build_opt.h b/src/build_opt.h deleted file mode 100644 index f7f5b39..0000000 --- a/src/build_opt.h +++ /dev/null @@ -1 +0,0 @@ --fno-exceptions diff --git a/src/src.ino b/src/src.ino index 13e2f91..c518e7d 100644 --- a/src/src.ino +++ b/src/src.ino @@ -31,7 +31,10 @@ void setup() { Serial.begin(115200); - homeSpan.begin(Category::Lighting,"HomeSpan Light"); + homeSpan.setLogLevel(2); + homeSpan.enableWebLog(); + + homeSpan.begin(Category::Lighting,"HomeSpan LightBulb"); new SpanAccessory(); new Service::AccessoryInformation(); @@ -39,8 +42,6 @@ void setup() { new Service::LightBulb(); new Characteristic::On(); -// new SpanUserCommand('k',"- list controllers",list_controllers); - homeSpan.setControllerCallback(list_controllers); } @@ -52,17 +53,3 @@ void loop(){ } ////////////////////////////////////// - - -void list_controllers(){ - Serial.printf("\nControllers\n"); - for(auto it=homeSpan.controllerListBegin(); it!=homeSpan.controllerListEnd(); ++it){ - Serial.printf("Admin=%d ID=",it->isAdmin()); - for(int i=0;i<36;i++) - Serial.printf("%02X",it->getID()[i]); - Serial.printf(" LTPK="); - for(int i=0;i<32;i++) - Serial.printf("%02X",it->getLTPK()[i]); - Serial.printf("\n"); - } -} From e0ec162938678c0ad66101bf25d24e4511df6bb7 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 11 May 2024 07:43:11 -0500 Subject: [PATCH 114/154] Added logic to check for hap[i] disconnect and issue stop() when found This cures memory leakage when using HomeSpan without a Home Hub. Sockets are now properly closed when the Home App abruptly disconnects. --- src/HAP.cpp | 1 + src/HomeSpan.cpp | 10 ++++++---- src/src.ino | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index fb92a05..e02ca74 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -1217,6 +1217,7 @@ void HAPClient::getStatusURL(HAPClient *hapClient, void (*callBack)(const char * if(hapClient){ hapClient->client.stop(); + delay(1); LOG2("------------ SENT! --------------\n"); } } diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 0683012..780c430 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -229,10 +229,10 @@ void Span::pollTask() { processSerialCommand(cBuf); } - if(hapServer->hasClient()){ // found new client + WiFiClient newClient; + + if(newClient=hapServer->available()){ // found new client - WiFiClient newClient=hapServer->available(); // get new client - int socket=newClient.fd()-LWIP_SOCKET_OFFSET; // get socket number (starting at zero) if(hap[socket]==NULL) // create HAPClient at that socket if it does not alreay exist @@ -262,7 +262,9 @@ void Span::pollTask() { } else if(hap[i]->isConnected){ // if client is not connected, but HAPClient thinks it is LOG1("** Client #%d DISCONNECTED (%lu sec)\n",i,millis()/1000); - hap[i]->isConnected=false; + hap[i]->isConnected=false; + hap[i]->client.stop(); + delay(1); } } } diff --git a/src/src.ino b/src/src.ino index c518e7d..e371724 100644 --- a/src/src.ino +++ b/src/src.ino @@ -30,7 +30,7 @@ void setup() { Serial.begin(115200); - + homeSpan.setLogLevel(2); homeSpan.enableWebLog(); From 3643506d89d06316f6dcf0f072387a3660728e11 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 11 May 2024 13:29:07 -0500 Subject: [PATCH 115/154] Move temporary keys for pairing/verification into separate structure Confirmed pairing and verification continue to works as expected. --- src/HAP.cpp | 40 +++++++++++++++------------------------- src/HAP.h | 12 +++++++----- 2 files changed, 22 insertions(+), 30 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index e02ca74..c63d95f 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -447,8 +447,7 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ // Note the SALT and INFO text fields used by HKDF to create this Session Key are NOT the same as those for creating iosDeviceX. // The iosDeviceX HKDF calculations are separate and will be performed further below with the SALT and INFO as specified in the HAP docs. - TempBuffer sessionKey(crypto_box_PUBLICKEYBYTES); // temporary space - used only in this block - HKDF::create(sessionKey,srp->K,64,"Pair-Setup-Encrypt-Salt","Pair-Setup-Encrypt-Info"); // create SessionKey + HKDF::create(temp.sessionKey,srp->K,64,"Pair-Setup-Encrypt-Salt","Pair-Setup-Encrypt-Info"); // create SessionKey LOG2("------- DECRYPTING SUB-TLVS -------\n"); @@ -456,7 +455,7 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ TempBuffer decrypted(itEncryptedData->getLen()-crypto_aead_chacha20poly1305_IETF_ABYTES); // temporary storage for decrypted data - if(crypto_aead_chacha20poly1305_ietf_decrypt(decrypted, NULL, NULL, *itEncryptedData, itEncryptedData->getLen(), NULL, 0, (unsigned char *)"\x00\x00\x00\x00PS-Msg05", sessionKey)==-1){ + if(crypto_aead_chacha20poly1305_ietf_decrypt(decrypted, NULL, NULL, *itEncryptedData, itEncryptedData->getLen(), NULL, 0, (unsigned char *)"\x00\x00\x00\x00PS-Msg05", temp.sessionKey)==-1){ LOG0("\n*** ERROR: Exchange-Request Authentication Failed\n\n"); responseTLV.add(kTLVType_Error,tagError_Authentication); // set Error=Authentication tlvRespond(responseTLV); // send response to client @@ -534,7 +533,7 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ itEncryptedData=responseTLV.add(kTLVType_EncryptedData,subPack.len()+crypto_aead_chacha20poly1305_IETF_ABYTES,NULL); //create blank EncryptedData TLV with space for subTLV + Authentication Tag - crypto_aead_chacha20poly1305_ietf_encrypt(*itEncryptedData,NULL,subPack,subPack.len(),NULL,0,NULL,(unsigned char *)"\x00\x00\x00\x00PS-Msg06",sessionKey); + crypto_aead_chacha20poly1305_ietf_encrypt(*itEncryptedData,NULL,subPack,subPack.len(),NULL,0,NULL,(unsigned char *)"\x00\x00\x00\x00PS-Msg06",temp.sessionKey); LOG2("---------- END SUB-TLVS! ----------\n"); @@ -611,16 +610,14 @@ int HAPClient::postPairVerifyURL(uint8_t *content, size_t len){ return(0); } - publicCurveKey=(uint8_t *)HS_MALLOC(crypto_box_PUBLICKEYBYTES); // temporary space - will be deleted at end of verification process TempBuffer secretCurveKey(crypto_box_SECRETKEYBYTES); // temporary space - used only in this block - crypto_box_keypair(publicCurveKey,secretCurveKey); // generate Accessory's random Curve25519 Public/Secret Key Pair + crypto_box_keypair(temp.publicCurveKey,secretCurveKey); // generate Accessory's random Curve25519 Public/Secret Key Pair - iosCurveKey=(uint8_t *)HS_MALLOC(crypto_box_PUBLICKEYBYTES); // temporary space - will be deleted at end of verification process - memcpy(iosCurveKey,*itPublicKey,crypto_box_PUBLICKEYBYTES); // save Controller's Curve25519 Public Key + memcpy(temp.iosCurveKey,*itPublicKey,crypto_box_PUBLICKEYBYTES); // save Controller's Curve25519 Public Key // concatenate Accessory's Curve25519 Public Key, Accessory's Pairing ID, and Controller's Curve25519 Public Key into accessoryInfo - TempBuffer accessoryInfo(publicCurveKey,crypto_box_PUBLICKEYBYTES,accessory.ID,hap_accessory_IDBYTES,iosCurveKey,crypto_box_PUBLICKEYBYTES,NULL); + TempBuffer accessoryInfo(temp.publicCurveKey,crypto_box_PUBLICKEYBYTES,accessory.ID,hap_accessory_IDBYTES,temp.iosCurveKey,crypto_box_PUBLICKEYBYTES,NULL); subTLV.add(kTLVType_Identifier,hap_accessory_IDBYTES,accessory.ID); // set Identifier subTLV record as Accessory's Pairing ID auto itSignature=subTLV.add(kTLVType_Signature,crypto_sign_BYTES,NULL); // create blank Signature subTLV @@ -634,19 +631,17 @@ int HAPClient::postPairVerifyURL(uint8_t *content, size_t len){ TempBuffer subPack(subTLV.pack_size()); // create sub-TLV by packing Identifier and Signature TLV records together subTLV.pack(subPack); - sharedCurveKey=(uint8_t *)HS_MALLOC(crypto_box_PUBLICKEYBYTES); // temporary space - will be deleted at end of verification process - crypto_scalarmult_curve25519(sharedCurveKey,secretCurveKey,iosCurveKey); // generate Shared-Secret Curve25519 Key from Accessory's Curve25519 Secret Key and Controller's Curve25519 Public Key + crypto_scalarmult_curve25519(temp.sharedCurveKey,secretCurveKey,temp.iosCurveKey); // generate Shared-Secret Curve25519 Key from Accessory's Curve25519 Secret Key and Controller's Curve25519 Public Key - sessionKey=(uint8_t *)HS_MALLOC(crypto_box_PUBLICKEYBYTES); // temporary space - will be deleted at end of verification process - HKDF::create(sessionKey,sharedCurveKey,crypto_box_PUBLICKEYBYTES,"Pair-Verify-Encrypt-Salt","Pair-Verify-Encrypt-Info"); // create Session Curve25519 Key from Shared-Secret Curve25519 Key using HKDF-SHA-512 + HKDF::create(temp.sessionKey,temp.sharedCurveKey,crypto_box_PUBLICKEYBYTES,"Pair-Verify-Encrypt-Salt","Pair-Verify-Encrypt-Info"); // create Session Curve25519 Key from Shared-Secret Curve25519 Key using HKDF-SHA-512 - auto itEncryptedData=responseTLV.add(kTLVType_EncryptedData,subPack.len()+crypto_aead_chacha20poly1305_IETF_ABYTES,NULL); // create blank EncryptedData subTLV - crypto_aead_chacha20poly1305_ietf_encrypt(*itEncryptedData,NULL,subPack,subPack.len(),NULL,0,NULL,(unsigned char *)"\x00\x00\x00\x00PV-Msg02",sessionKey); // encrypt data with Session Curve25519 Key and padded nonce="PV-Msg02" + auto itEncryptedData=responseTLV.add(kTLVType_EncryptedData,subPack.len()+crypto_aead_chacha20poly1305_IETF_ABYTES,NULL); // create blank EncryptedData subTLV + crypto_aead_chacha20poly1305_ietf_encrypt(*itEncryptedData,NULL,subPack,subPack.len(),NULL,0,NULL,(unsigned char *)"\x00\x00\x00\x00PV-Msg02",temp.sessionKey); // encrypt data with Session Curve25519 Key and padded nonce="PV-Msg02" LOG2("---------- END SUB-TLVS! ----------\n"); responseTLV.add(kTLVType_State,pairState_M2); // set State= - responseTLV.add(kTLVType_PublicKey,crypto_box_PUBLICKEYBYTES,publicCurveKey); // set PublicKey to Accessory's Curve25519 Public Key + responseTLV.add(kTLVType_PublicKey,crypto_box_PUBLICKEYBYTES,temp.publicCurveKey); // set PublicKey to Accessory's Curve25519 Public Key tlvRespond(responseTLV); // send response to client } @@ -670,7 +665,7 @@ int HAPClient::postPairVerifyURL(uint8_t *content, size_t len){ TempBuffer decrypted((*itEncryptedData).getLen()-crypto_aead_chacha20poly1305_IETF_ABYTES); // temporary storage for decrypted data - if(crypto_aead_chacha20poly1305_ietf_decrypt(decrypted, NULL, NULL, *itEncryptedData, itEncryptedData->getLen(), NULL, 0, (unsigned char *)"\x00\x00\x00\x00PV-Msg03", sessionKey)==-1){ + if(crypto_aead_chacha20poly1305_ietf_decrypt(decrypted, NULL, NULL, *itEncryptedData, itEncryptedData->getLen(), NULL, 0, (unsigned char *)"\x00\x00\x00\x00PV-Msg03", temp.sessionKey)==-1){ LOG0("\n*** ERROR: Verify Authentication Failed\n\n"); responseTLV.add(kTLVType_State,pairState_M4); // set State= responseTLV.add(kTLVType_Error,tagError_Authentication); // set Error=Authentication @@ -713,7 +708,7 @@ int HAPClient::postPairVerifyURL(uint8_t *content, size_t len){ // concatenate Controller's Curve25519 Public Key (from previous step), Controller's Pairing ID, and Accessory's Curve25519 Public Key (from previous step) into iosDeviceInfo - TempBuffer iosDeviceInfo(iosCurveKey,crypto_box_PUBLICKEYBYTES,tPair->ID,hap_controller_IDBYTES,publicCurveKey,crypto_box_PUBLICKEYBYTES,NULL); + TempBuffer iosDeviceInfo(temp.iosCurveKey,crypto_box_PUBLICKEYBYTES,tPair->ID,hap_controller_IDBYTES,temp.publicCurveKey,crypto_box_PUBLICKEYBYTES,NULL); if(crypto_sign_verify_detached(*itSignature, iosDeviceInfo, iosDeviceInfo.len(), tPair->LTPK) != 0){ // verify signature of iosDeviceInfo using Controller's LTPK LOG0("\n*** ERROR: LPTK Signature Verification Failed\n\n"); @@ -728,17 +723,12 @@ int HAPClient::postPairVerifyURL(uint8_t *content, size_t len){ cPair=tPair; // save Controller for this connection slot - connection is now verified and should be encrypted going forward - HKDF::create(a2cKey,sharedCurveKey,32,"Control-Salt","Control-Read-Encryption-Key"); // create AccessoryToControllerKey from (previously-saved) Shared-Secret Curve25519 Key (HAP Section 6.5.2) - HKDF::create(c2aKey,sharedCurveKey,32,"Control-Salt","Control-Write-Encryption-Key"); // create ControllerToAccessoryKey from (previously-saved) Shared-Secret Curve25519 Key (HAP Section 6.5.2) + HKDF::create(a2cKey,temp.sharedCurveKey,32,"Control-Salt","Control-Read-Encryption-Key"); // create AccessoryToControllerKey from (previously-saved) Shared-Secret Curve25519 Key (HAP Section 6.5.2) + HKDF::create(c2aKey,temp.sharedCurveKey,32,"Control-Salt","Control-Write-Encryption-Key"); // create ControllerToAccessoryKey from (previously-saved) Shared-Secret Curve25519 Key (HAP Section 6.5.2) a2cNonce.zero(); // reset Nonces for this session to zero c2aNonce.zero(); - free(publicCurveKey); // free storage of these temporary variables created in previous step - free(sharedCurveKey); - free(sessionKey); - free(iosCurveKey); - LOG2("\n*** SESSION VERIFICATION COMPLETE *** \n"); } break; diff --git a/src/HAP.h b/src/HAP.h index 99f7551..0a9e6fe 100644 --- a/src/HAP.h +++ b/src/HAP.h @@ -96,12 +96,14 @@ struct HAPClient { boolean isConnected=false; // flag to indicate client is connect // These temporary Curve25519 keys are generated in the first call to pair-verify and used in the second call to pair-verify so must persist for a short period - - uint8_t *publicCurveKey; // Accessory's Curve25519 Public Key - uint8_t *sharedCurveKey; // Shared-Secret Curve25519 Key derived from Accessory's Secret Key and Controller's Public Key - uint8_t *sessionKey; // Session Key Curve25519 (derived with various HKDF calls) - uint8_t *iosCurveKey; // Controller's Curve25519 Public Key + struct tempKeys_t { + uint8_t publicCurveKey[crypto_box_PUBLICKEYBYTES]; // Accessory's Curve25519 Public Key + uint8_t sharedCurveKey[crypto_box_PUBLICKEYBYTES]; // Shared-Secret Curve25519 Key derived from Accessory's Secret Key and Controller's Public Key + uint8_t sessionKey[crypto_box_PUBLICKEYBYTES]; // Session Key Curve25519 (derived with various HKDF calls) + uint8_t iosCurveKey[crypto_box_PUBLICKEYBYTES]; // Controller's Curve25519 Public Key + } temp; + // CurveKey and CurveKey Nonces are created once each new session is verified in /pair-verify. Keys persist for as long as connection is open uint8_t a2cKey[32]; // AccessoryToControllerKey derived from HKDF-SHA-512 of sharedCurveKey (HAP Section 6.5.2) From dbfad7d2225d3264b5ea5ea54d846f0640593f37 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 12 May 2024 07:17:21 -0500 Subject: [PATCH 116/154] initial change of hap[i] to linked-list from fixed array --- src/HAP.cpp | 19 +++++------ src/HAP.h | 1 - src/HomeSpan.cpp | 85 +++++++++++++++++++++--------------------------- 3 files changed, 46 insertions(+), 59 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index c63d95f..cf10f51 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -324,7 +324,7 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ iosTLV.print(); LOG2("------------ END TLVS! ------------\n"); - LOG1("In Pair Setup #%d (%s)...",conNum,client.remoteIP().toString().c_str()); + LOG1("In Pair Setup #%d (%s)...",client.fd()-LWIP_SOCKET_OFFSET,client.remoteIP().toString().c_str()); auto itState=iosTLV.find(kTLVType_State); @@ -344,7 +344,7 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ return(0); }; - LOG2("Found . Expected .\n",tlvState,pairStatus); + LOG1("Found . Expected .\n",tlvState,pairStatus); if(tlvState!=pairStatus){ // error: Device is not yet paired, but out-of-sequence pair-setup STATE was received LOG0("\n*** ERROR: Out-of-Sequence Pair-Setup request!\n\n"); @@ -574,7 +574,7 @@ int HAPClient::postPairVerifyURL(uint8_t *content, size_t len){ iosTLV.print(); LOG2("------------ END TLVS! ------------\n"); - LOG1("In Pair Verify #%d (%s)...",conNum,client.remoteIP().toString().c_str()); + LOG1("In Pair Verify #%d (%s)...",client.fd()-LWIP_SOCKET_OFFSET,client.remoteIP().toString().c_str()); auto itState=iosTLV.find(kTLVType_State); @@ -594,7 +594,7 @@ int HAPClient::postPairVerifyURL(uint8_t *content, size_t len){ return(0); }; - LOG2("Found \n",tlvState); // unlike pair-setup, out-of-sequencing can be handled gracefully for pair-verify (HAP requirement). No need to keep track of pairStatus + LOG1("Found \n",tlvState); // unlike pair-setup, out-of-sequencing can be handled gracefully for pair-verify (HAP requirement). No need to keep track of pairStatus switch(tlvState){ // Pair-Verify STATE received -- process request! (HAP Section 5.7) @@ -756,7 +756,7 @@ int HAPClient::postPairingsURL(uint8_t *content, size_t len){ iosTLV.print(); LOG2("------------ END TLVS! ------------\n"); - LOG1("In Post Pairings #%d (%s)...",conNum,client.remoteIP().toString().c_str()); + LOG1("In Post Pairings #%d (%s)...",client.fd()-LWIP_SOCKET_OFFSET,client.remoteIP().toString().c_str()); auto itState=iosTLV.find(kTLVType_State); auto itMethod=iosTLV.find(kTLVType_Method); @@ -882,7 +882,7 @@ int HAPClient::getAccessoriesURL(){ return(0); } - LOG1("In Get Accessories #%d (%s)...\n",conNum,client.remoteIP().toString().c_str()); + LOG1("In Get Accessories #%d (%s)...\n",client.fd()-LWIP_SOCKET_OFFSET,client.remoteIP().toString().c_str()); homeSpan.printfAttributes(); size_t nBytes=hapOut.getSize(); @@ -910,7 +910,7 @@ int HAPClient::getCharacteristicsURL(char *urlBuf){ return(0); } - LOG1("In Get Characteristics #%d (%s)...\n",conNum,client.remoteIP().toString().c_str()); + LOG1("In Get Characteristics #%d (%s)...\n",client.fd()-LWIP_SOCKET_OFFSET,client.remoteIP().toString().c_str()); int len=strlen(urlBuf); // determine number of IDs specified by counting commas in URL int numIDs=1; @@ -978,7 +978,7 @@ int HAPClient::putCharacteristicsURL(char *json){ return(0); } - LOG1("In Put Characteristics #%d (%s)...\n",conNum,client.remoteIP().toString().c_str()); + LOG1("In Put Characteristics #%d (%s)...\n",client.fd()-LWIP_SOCKET_OFFSET,client.remoteIP().toString().c_str()); int n=homeSpan.countCharacteristics(json); // count number of objects in JSON request if(n==0) // if no objects found, return @@ -1031,7 +1031,7 @@ int HAPClient::putPrepareURL(char *json){ return(0); } - LOG1("In Put Prepare #%d (%s)...\n",conNum,client.remoteIP().toString().c_str()); + LOG1("In Put Prepare #%d (%s)...\n",client.fd()-LWIP_SOCKET_OFFSET,client.remoteIP().toString().c_str()); char ttlToken[]="\"ttl\":"; char pidToken[]="\"pid\":"; @@ -1237,7 +1237,6 @@ void HAPClient::checkTimedWrites(){ else tw++; } - } ////////////////////////////////////// diff --git a/src/HAP.h b/src/HAP.h index 0a9e6fe..0aa42b8 100644 --- a/src/HAP.h +++ b/src/HAP.h @@ -93,7 +93,6 @@ struct HAPClient { WiFiClient client; // handle to client Controller *cPair=NULL; // pointer to info on current, session-verified Paired Controller (NULL=un-verified, and therefore un-encrypted, connection) - boolean isConnected=false; // flag to indicate client is connect // These temporary Curve25519 keys are generated in the first call to pair-verify and used in the second call to pair-verify so must persist for a short period diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 780c430..fd3a155 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -47,6 +47,8 @@ using namespace Utils; HapOut hapOut; // Specialized output stream that can both print to serial monitor and encrypt/transmit to HAP Clients with minimal memory usage (global-scoped variable) HAPClient **hap; // HAP Client structure containing HTTP client connections, parsing routines, and state variables (global-scoped variable) +list> hapList; // linked-list of HAP Client structures 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 (global-scoped variable) @@ -229,43 +231,36 @@ void Span::pollTask() { processSerialCommand(cBuf); } - WiFiClient newClient; - - if(newClient=hapServer->available()){ // found new client + if(hapServer->hasClient()){ + + auto it=hapList.emplace(hapList.begin()); + (*it).client=hapServer->available(); + +// homeSpan.clearNotify(socket); // clear all notification requests for this connection - int socket=newClient.fd()-LWIP_SOCKET_OFFSET; // get socket number (starting at zero) - - if(hap[socket]==NULL) // create HAPClient at that socket if it does not alreay exist - hap[socket]=new HAPClient; - - hap[socket]->client=newClient; // copy new client handle - hap[socket]->isConnected=true; // set isConnected flag - hap[socket]->cPair=NULL; // reset pointer to verified ID - homeSpan.clearNotify(socket); // clear all notification requests for this connection HAPClient::pairStatus=pairState_M1; // reset starting PAIR STATE (which may be needed if Accessory failed in middle of pair-setup) LOG2("=======================================\n"); - LOG1("** Client #%d Connected (%lu sec): %s\n",socket,millis()/1000,newClient.remoteIP().toString().c_str()); + LOG1("** Client #%d Connected (%lu sec): %s\n",(*it).client.fd()-LWIP_SOCKET_OFFSET,millis()/1000,(*it).client.remoteIP().toString().c_str()); LOG2("\n"); } - - for(int i=0;iclient){ // if the client is connected - if(hap[i]->client.available()){ // if client has data available - 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 - } - } - else if(hap[i]->isConnected){ // if client is not connected, but HAPClient thinks it is - LOG1("** Client #%d DISCONNECTED (%lu sec)\n",i,millis()/1000); - hap[i]->isConnected=false; - hap[i]->client.stop(); - delay(1); + auto it=hapList.begin(); + while(it!=hapList.end()){ + + if((*it).client.connected()){ // if the client is connected + if((*it).client.available()){ // if client has data available +// HAPClient::conNum=i; // set connection number + homeSpan.lastClientIP=(*it).client.remoteIP().toString(); // store IP Address for web logging + (*it).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 } + it++; + } else { + LOG1("** Client #%d DISCONNECTED (%lu sec)\n",(*it).client.fd()-LWIP_SOCKET_OFFSET,millis()/1000); + (*it).client.stop(); + delay(5); + it=hapList.erase(it); } } @@ -277,7 +272,7 @@ void Span::pollTask() { for(auto it=PushButtons.begin();it!=PushButtons.end();it++) // check for SpanButton presses (*it)->check(); - HAPClient::checkNotifications(); +////// HAPClient::checkNotifications(); HAPClient::checkTimedWrites(); if(spanOTA.enabled) @@ -584,26 +579,20 @@ void Span::processSerialCommand(const char *c){ HAPClient::printControllers(); LOG0("\n"); - for(int i=0;iclient){ - LOG0(" %s",hap[i]->client.remoteIP().toString().c_str()); - - if(hap[i]->cPair){ - LOG0(" ID="); - HAPClient::charPrintRow(hap[i]->cPair->getID(),36); - LOG0(hap[i]->cPair->isAdmin()?" (admin)":" (regular)"); - } else { - LOG0(" (unverified)"); - } - } else { - LOG0(" unconnected"); - } - LOG0("\n"); + for(auto it=hapList.begin(); it!=hapList.end(); ++it){ + LOG0("Client #%d: %s",(*it).client.fd()-LWIP_SOCKET_OFFSET,(*it).client.remoteIP().toString().c_str()); + if((*it).cPair){ + LOG0(" ID="); + HAPClient::charPrintRow((*it).cPair->getID(),36); + LOG0((*it).cPair->isAdmin()?" (admin)":" (regular)\n"); + } else { + LOG0(" (unverified)\n"); } } + + if(hapList.empty()) + LOG0("No Client Connections!\n"); + LOG0("\n*** End Status ***\n\n"); } break; From 9d29b73dac0bfbce4e17229982068970bf1b45f8 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 2 Jun 2024 20:57:41 -0500 Subject: [PATCH 117/154] Fixed memory leak [needed to free `body` in tlvRespond()] This hopefully addresses memory leak that occurs when not using a Home Hub and connections constantly drop and re-establish. To do: must add back logic for notifications using new hap linked-list structure, and then delete static hap array code. --- src/HAP.cpp | 21 +++++++++++++-------- src/HAP.h | 1 + src/HomeSpan.cpp | 17 ++++++++++------- src/src.ino | 2 ++ 4 files changed, 26 insertions(+), 15 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index cf10f51..b7027d6 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -324,7 +324,7 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ iosTLV.print(); LOG2("------------ END TLVS! ------------\n"); - LOG1("In Pair Setup #%d (%s)...",client.fd()-LWIP_SOCKET_OFFSET,client.remoteIP().toString().c_str()); + LOG1("In Pair Setup #%d (%s)...",clientNumber,client.remoteIP().toString().c_str()); auto itState=iosTLV.find(kTLVType_State); @@ -574,7 +574,7 @@ int HAPClient::postPairVerifyURL(uint8_t *content, size_t len){ iosTLV.print(); LOG2("------------ END TLVS! ------------\n"); - LOG1("In Pair Verify #%d (%s)...",client.fd()-LWIP_SOCKET_OFFSET,client.remoteIP().toString().c_str()); + LOG1("In Pair Verify #%d (%s)...",clientNumber,client.remoteIP().toString().c_str()); auto itState=iosTLV.find(kTLVType_State); @@ -756,7 +756,7 @@ int HAPClient::postPairingsURL(uint8_t *content, size_t len){ iosTLV.print(); LOG2("------------ END TLVS! ------------\n"); - LOG1("In Post Pairings #%d (%s)...",client.fd()-LWIP_SOCKET_OFFSET,client.remoteIP().toString().c_str()); + LOG1("In Post Pairings #%d (%s)...",clientNumber,client.remoteIP().toString().c_str()); auto itState=iosTLV.find(kTLVType_State); auto itMethod=iosTLV.find(kTLVType_Method); @@ -882,7 +882,7 @@ int HAPClient::getAccessoriesURL(){ return(0); } - LOG1("In Get Accessories #%d (%s)...\n",client.fd()-LWIP_SOCKET_OFFSET,client.remoteIP().toString().c_str()); + LOG1("In Get Accessories #%d (%s)...\n",clientNumber,client.remoteIP().toString().c_str()); homeSpan.printfAttributes(); size_t nBytes=hapOut.getSize(); @@ -910,7 +910,7 @@ int HAPClient::getCharacteristicsURL(char *urlBuf){ return(0); } - LOG1("In Get Characteristics #%d (%s)...\n",client.fd()-LWIP_SOCKET_OFFSET,client.remoteIP().toString().c_str()); + LOG1("In Get Characteristics #%d (%s)...\n",clientNumber,client.remoteIP().toString().c_str()); int len=strlen(urlBuf); // determine number of IDs specified by counting commas in URL int numIDs=1; @@ -978,7 +978,7 @@ int HAPClient::putCharacteristicsURL(char *json){ return(0); } - LOG1("In Put Characteristics #%d (%s)...\n",client.fd()-LWIP_SOCKET_OFFSET,client.remoteIP().toString().c_str()); + LOG1("In Put Characteristics #%d (%s)...\n",clientNumber,client.remoteIP().toString().c_str()); int n=homeSpan.countCharacteristics(json); // count number of objects in JSON request if(n==0) // if no objects found, return @@ -1031,7 +1031,7 @@ int HAPClient::putPrepareURL(char *json){ return(0); } - LOG1("In Put Prepare #%d (%s)...\n",client.fd()-LWIP_SOCKET_OFFSET,client.remoteIP().toString().c_str()); + LOG1("In Put Prepare #%d (%s)...\n",clientNumber,client.remoteIP().toString().c_str()); char ttlToken[]="\"ttl\":"; char pidToken[]="\"pid\":"; @@ -1291,6 +1291,8 @@ void HAPClient::tlvRespond(TLV8 &tlv8){ LOG2("------------ SENT! --------------\n"); else LOG2("-------- SENT ENCRYPTED! --------\n"); + + free(body); } // tlvRespond @@ -1559,9 +1561,12 @@ HapOut::HapStreamBuffer::HapStreamBuffer(){ ////////////////////////////////////// HapOut::HapStreamBuffer::~HapStreamBuffer(){ - + sync(); free(buffer); + free(encBuf); + free(hash); + free(ctx); } ////////////////////////////////////// diff --git a/src/HAP.h b/src/HAP.h index 0aa42b8..c3c9d4a 100644 --- a/src/HAP.h +++ b/src/HAP.h @@ -92,6 +92,7 @@ struct HAPClient { // individual structures and data defined for each Hap Client connection WiFiClient client; // handle to client + int clientNumber; // client number Controller *cPair=NULL; // pointer to info on current, session-verified Paired Controller (NULL=un-verified, and therefore un-encrypted, connection) // These temporary Curve25519 keys are generated in the first call to pair-verify and used in the second call to pair-verify so must persist for a short period diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index fd3a155..7bc9e0a 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -235,13 +235,14 @@ void Span::pollTask() { auto it=hapList.emplace(hapList.begin()); (*it).client=hapServer->available(); + (*it).clientNumber=(*it).client.fd()-LWIP_SOCKET_OFFSET; // homeSpan.clearNotify(socket); // clear all notification requests for this connection HAPClient::pairStatus=pairState_M1; // reset starting PAIR STATE (which may be needed if Accessory failed in middle of pair-setup) LOG2("=======================================\n"); - LOG1("** Client #%d Connected (%lu sec): %s\n",(*it).client.fd()-LWIP_SOCKET_OFFSET,millis()/1000,(*it).client.remoteIP().toString().c_str()); + LOG1("** Client #%d Connected (%lu sec): %s\n",(*it).clientNumber,millis()/1000,(*it).client.remoteIP().toString().c_str()); LOG2("\n"); } @@ -257,7 +258,7 @@ void Span::pollTask() { } it++; } else { - LOG1("** Client #%d DISCONNECTED (%lu sec)\n",(*it).client.fd()-LWIP_SOCKET_OFFSET,millis()/1000); + LOG1("** Client #%d DISCONNECTED (%lu sec)\n",(*it).clientNumber,millis()/1000); (*it).client.stop(); delay(5); it=hapList.erase(it); @@ -557,9 +558,11 @@ void Span::processSerialCommand(const char *c){ switch(c[0]){ case 'Z': { - for(int i=0;iclient) - hap[i]->client.stop(); + for(auto it=hapList.begin(); it!=hapList.end(); ++it){ + (*it).client.stop(); + delay(5); + + } } break; @@ -580,11 +583,11 @@ void Span::processSerialCommand(const char *c){ LOG0("\n"); for(auto it=hapList.begin(); it!=hapList.end(); ++it){ - LOG0("Client #%d: %s",(*it).client.fd()-LWIP_SOCKET_OFFSET,(*it).client.remoteIP().toString().c_str()); + LOG0("Client #%d: %s",(*it).clientNumber,(*it).client.remoteIP().toString().c_str()); if((*it).cPair){ LOG0(" ID="); HAPClient::charPrintRow((*it).cPair->getID(),36); - LOG0((*it).cPair->isAdmin()?" (admin)":" (regular)\n"); + LOG0((*it).cPair->isAdmin()?" (admin)\n":" (regular)\n"); } else { LOG0(" (unverified)\n"); } diff --git a/src/src.ino b/src/src.ino index e371724..98d9733 100644 --- a/src/src.ino +++ b/src/src.ino @@ -35,6 +35,8 @@ void setup() { homeSpan.enableWebLog(); homeSpan.begin(Category::Lighting,"HomeSpan LightBulb"); + + new SpanUserCommand('D', " - disconnect WiFi", [](const char *buf){WiFi.disconnect();}); new SpanAccessory(); new Service::AccessoryInformation(); From 153ab451fd74d661aa0370c21e3d1a70192563b9 Mon Sep 17 00:00:00 2001 From: Gregg Date: Wed, 5 Jun 2024 13:32:15 -0500 Subject: [PATCH 118/154] Moved Controller Class definition back to HAP.h instead of HomeSpan.h As long as `class Controller` is forward-declared in HomeSpan.h, the definition can live in HAP.h --- src/HAP.h | 28 ++++++++++++++++++++++++++++ src/HomeSpan.h | 29 ----------------------------- 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/src/HAP.h b/src/HAP.h index c3c9d4a..ad19bb2 100644 --- a/src/HAP.h +++ b/src/HAP.h @@ -72,6 +72,34 @@ struct Accessory { uint8_t LTPK[crypto_sign_PUBLICKEYBYTES]; // Long Term Ed2519 Public Key }; +////////////////////////////////////////////////////////// +// Paired Controller Structure for Permanently-Stored Data + +class Controller { + friend class HAPClient; + + boolean allocated=false; // DEPRECATED (but needed for backwards compatability with original NVS storage of Controller info) + boolean admin; // Controller has admin privileges + uint8_t ID[36]; // Pairing ID + uint8_t LTPK[32]; // Long Term Ed2519 Public Key + + public: + + Controller(uint8_t *id, uint8_t *ltpk, boolean ad){ + allocated=true; + admin=ad; + memcpy(ID,id,36); + memcpy(LTPK,ltpk,32); + } + + Controller(){} + + const uint8_t *getID() const {return(ID);} + const uint8_t *getLTPK() const {return(LTPK);} + boolean isAdmin() const {return(admin);} + +}; + ///////////////////////////////////////////////// // HAPClient Structure // Reads and Writes from each HAP Client connection diff --git a/src/HomeSpan.h b/src/HomeSpan.h index e14f4d4..6798303 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -189,35 +189,6 @@ struct SpanOTA{ // manages OTA process static void error(ota_error_t err); }; - -////////////////////////////////////////////////////////// -// Paired Controller Structure for Permanently-Stored Data - -class Controller { - friend class HAPClient; - - boolean allocated=false; // DEPRECATED (but needed for backwards compatability with original NVS storage of Controller info) - boolean admin; // Controller has admin privileges - uint8_t ID[36]; // Pairing ID - uint8_t LTPK[32]; // Long Term Ed2519 Public Key - - public: - - Controller(uint8_t *id, uint8_t *ltpk, boolean ad){ - allocated=true; - admin=ad; - memcpy(ID,id,36); - memcpy(LTPK,ltpk,32); - } - - Controller(){} - - const uint8_t *getID() const {return(ID);} - const uint8_t *getLTPK() const {return(LTPK);} - boolean isAdmin() const {return(admin);} - -}; - ////////////////////////////////////// // USER API CLASSES BEGINS HERE // ////////////////////////////////////// From 983e159adf7f2eba8e03a54bbfd55e832740cd2d Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 8 Jun 2024 16:45:46 -0500 Subject: [PATCH 119/154] Created evList to store notifications Also updated all code to use hapList and deleted all references to **hap --- src/HAP.cpp | 29 ++++++------ src/HAP.h | 4 +- src/HomeSpan.cpp | 121 +++++++++++++++++++++++------------------------ src/HomeSpan.h | 32 ++++++++----- 4 files changed, 95 insertions(+), 91 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index b7027d6..ea031a3 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -1017,7 +1017,7 @@ int HAPClient::putCharacteristicsURL(char *json){ // Create and send Event Notifications if needed - eventNotify(pObj,n,HAPClient::conNum); // transmit EVENT Notification for "n" pObj objects, except DO NOT notify client making request + eventNotify(pObj,n,this); // transmit EVENT Notification for "n" pObj objects, except DO NOT notify client making request return(1); } @@ -1241,22 +1241,22 @@ void HAPClient::checkTimedWrites(){ ////////////////////////////////////// -void HAPClient::eventNotify(SpanBuf *pObj, int nObj, int ignoreClient){ - - for(int cNum=0;cNumclient && cNum!=ignoreClient){ // if there is a client connected to this slot and it is NOT flagged to be ignored (in cases where it is the client making a PUT request) +void HAPClient::eventNotify(SpanBuf *pObj, int nObj, HAPClient *ignore){ - homeSpan.printfNotify(pObj,nObj,cNum); // create JSON (which may be of zero length if there are no applicable notifications for this cNum) + for(auto it=homeSpan.hapList.begin(); it!=homeSpan.hapList.end(); ++it){ // loop over all connection slots + if(&(*it)!=ignore){ // if NOT flagged to be ignored (in cases where it is the client making a PUT request) + + homeSpan.printfNotify(pObj,nObj,&(*it)); // create JSON (which may be of zero length if there are no applicable notifications for this cNum) size_t nBytes=hapOut.getSize(); hapOut.flush(); if(nBytes>0){ // if there ARE notifications to send to client cNum - LOG2("\n>>>>>>>>>> %s >>>>>>>>>>\n",hap[cNum]->client.remoteIP().toString().c_str()); + LOG2("\n>>>>>>>>>> %s >>>>>>>>>>\n",it->client.remoteIP().toString().c_str()); - hapOut.setLogLevel(2).setHapClient(hap[cNum]); + hapOut.setLogLevel(2).setHapClient(&(*it)); hapOut << "EVENT/1.0 200 OK\r\nContent-Type: application/hap+json\r\nContent-Length: " << nBytes << "\r\n\r\n"; - homeSpan.printfNotify(pObj,nObj,cNum); + homeSpan.printfNotify(pObj,nObj,&(*it)); hapOut.flush(); LOG2("\n-------- SENT ENCRYPTED! --------\n"); @@ -1461,11 +1461,11 @@ void HAPClient::removeController(uint8_t *id){ ////////////////////////////////////// void HAPClient::tearDown(uint8_t *id){ - - for(int i=0;iclient && (id==NULL || (hap[i]->cPair && !memcmp(id,hap[i]->cPair->ID,hap_controller_IDBYTES)))){ - LOG1("*** Terminating Client #%d\n",i); - hap[i]->client.stop(); + + for(HAPClient &hc : homeSpan.hapList){ + if(id==NULL || (hc.cPair && !memcmp(id,hc.cPair->ID,hap_controller_IDBYTES))){ + LOG1("*** Terminating Client #%d\n",hc.clientNumber); + hc.client.stop(); } } } @@ -1691,5 +1691,4 @@ void HapOut::HapStreamBuffer::printFormatted(char *buf, size_t nChars, size_t ns pairState HAPClient::pairStatus; Accessory HAPClient::accessory; list> HAPClient::controllerList; -int HAPClient::conNum; diff --git a/src/HAP.h b/src/HAP.h index ad19bb2..9532741 100644 --- a/src/HAP.h +++ b/src/HAP.h @@ -115,7 +115,6 @@ struct HAPClient { static pairState pairStatus; // tracks pair-setup status static Accessory accessory; // Accessory ID and Ed25519 public and secret keys - permanently stored static list> controllerList; // linked-list of Paired Controller IDs and ED25519 long-term public keys - permanently stored - static int conNum; // connection number - used to keep track of per-connection EV notifications // individual structures and data defined for each Hap Client connection @@ -174,7 +173,7 @@ struct HAPClient { static void tearDown(uint8_t *id); // tears down connections using Controller with ID=id; tears down all connections if id=NULL static void checkNotifications(); // checks for Event Notifications and reports to controllers as needed (HAP Section 6.8) static void checkTimedWrites(); // checks for expired Timed Write PIDs, and clears any found (HAP Section 6.7.2.4) - static void eventNotify(SpanBuf *pObj, int nObj, int ignoreClient=-1); // transmits EVENT Notifications for nObj SpanBuf objects, pObj, with optional flag to ignore a specific client + static void eventNotify(SpanBuf *pObj, int nObj, HAPClient *ignore=NULL); // transmits EVENT Notifications for nObj SpanBuf objects, pObj, with optional flag to ignore a specific client static void getStatusURL(HAPClient *, void (*)(const char *, void *), void *); // GET / status (an optional, non-HAP feature) @@ -237,5 +236,4 @@ class HapOut : public std::ostream { ///////////////////////////////////////////////// // Extern Variables -extern HAPClient **hap; extern HapOut hapOut; diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 7bc9e0a..116ba7b 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -46,9 +46,6 @@ const __attribute__((section(".rodata_custom_desc"))) SpanPartition spanPartitio using namespace Utils; HapOut hapOut; // Specialized output stream that can both print to serial monitor and encrypt/transmit to HAP Clients with minimal memory usage (global-scoped variable) -HAPClient **hap; // HAP Client structure containing HTTP client connections, parsing routines, and state variables (global-scoped variable) -list> hapList; // linked-list of HAP Client structures 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 (global-scoped variable) @@ -71,7 +68,6 @@ Span::Span(){ rebootCount++; nvs_set_u8(wifiNVS,"REBOOTS",rebootCount); nvs_commit(wifiNVS); - } /////////////////////////////// @@ -91,8 +87,6 @@ void Span::begin(Category catID, const char *displayName, const char *hostNameBa esp_task_wdt_delete(xTaskGetIdleTaskHandleForCPU(0)); // required to avoid watchdog timeout messages from ESP32-C3 - hap=(HAPClient **)HS_CALLOC(CONFIG_LWIP_MAX_SOCKETS,sizeof(HAPClient *)); // create fixed array of pointers to HAPClient objects (initially set to NULL) - hapServer=new WiFiServer(tcpPortNum); // create HAP WIFI SERVER size_t len; @@ -233,35 +227,33 @@ void Span::pollTask() { if(hapServer->hasClient()){ - auto it=hapList.emplace(hapList.begin()); - (*it).client=hapServer->available(); - (*it).clientNumber=(*it).client.fd()-LWIP_SOCKET_OFFSET; - -// homeSpan.clearNotify(socket); // clear all notification requests for this connection - - HAPClient::pairStatus=pairState_M1; // reset starting PAIR STATE (which may be needed if Accessory failed in middle of pair-setup) + auto it=hapList.emplace(hapList.begin()); // create new HAPClient connection + it->client=hapServer->available(); + it->clientNumber=it->client.fd()-LWIP_SOCKET_OFFSET; + + HAPClient::pairStatus=pairState_M1; // reset starting PAIR STATE (which may be needed if Accessory failed in middle of pair-setup) LOG2("=======================================\n"); - LOG1("** Client #%d Connected (%lu sec): %s\n",(*it).clientNumber,millis()/1000,(*it).client.remoteIP().toString().c_str()); + LOG1("** Client #%d Connected (%lu sec): %s\n",it->clientNumber,millis()/1000,it->client.remoteIP().toString().c_str()); LOG2("\n"); } - auto it=hapList.begin(); - while(it!=hapList.end()){ + currentClient=hapList.begin(); + while(currentClient!=hapList.end()){ - if((*it).client.connected()){ // if the client is connected - if((*it).client.available()){ // if client has data available -// HAPClient::conNum=i; // set connection number - homeSpan.lastClientIP=(*it).client.remoteIP().toString(); // store IP Address for web logging - (*it).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(currentClient->client.connected()){ // if the client is connected + if(currentClient->client.available()){ // if client has data available + homeSpan.lastClientIP=currentClient->client.remoteIP().toString(); // store IP Address for web logging + currentClient->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 } - it++; + currentClient++; } else { - LOG1("** Client #%d DISCONNECTED (%lu sec)\n",(*it).clientNumber,millis()/1000); - (*it).client.stop(); + LOG1("** Client #%d DISCONNECTED (%lu sec)\n",currentClient->clientNumber,millis()/1000); + currentClient->client.stop(); delay(5); - it=hapList.erase(it); + clearNotify(&*currentClient); // clear all notification requests for this connection + currentClient=hapList.erase(currentClient); // remove HAPClient connection } } @@ -273,7 +265,7 @@ void Span::pollTask() { for(auto it=PushButtons.begin();it!=PushButtons.end();it++) // check for SpanButton presses (*it)->check(); -////// HAPClient::checkNotifications(); + HAPClient::checkNotifications(); HAPClient::checkTimedWrites(); if(spanOTA.enabled) @@ -304,18 +296,6 @@ void Span::pollTask() { } // poll -/////////////////////////////// - -int Span::getFreeSlot(){ - - for(int i=0;iclient) - return(i); - } - - return(-1); -} - ////////////////////////////////////// void Span::commandMode(){ @@ -886,11 +866,9 @@ void Span::processSerialCommand(const char *c){ if(((*chr)->perms)&EV){ LOG0(", EV=("); boolean addComma=false; - for(int i=0;iev[i] && hap[i] && hap[i]->client){ - LOG0("%s%d",addComma?",":"",i); - addComma=true; - } + for(HAPClient *hc : (*chr)->evList){ + LOG0("%s%d",addComma?",":"",hc->clientNumber); + addComma=true; } LOG0(")"); } @@ -1463,29 +1441,25 @@ int Span::updateCharacteristics(char *buf, SpanBuf *pObj){ /////////////////////////////// -void Span::clearNotify(int slotNum){ - - for(int i=0;iServices.size();j++){ - for(int k=0;kServices[j]->Characteristics.size();k++){ - Accessories[i]->Services[j]->Characteristics[k]->ev[slotNum]=false; - } - } - } -} +void Span::clearNotify(HAPClient *hc){ + + for(auto const &acc : Accessories) + for(auto const &svc : acc->Services) + for(auto const &chr : svc->Characteristics) + chr->evList.remove(hc); +} /////////////////////////////// -void Span::printfNotify(SpanBuf *pObj, int nObj, int conNum){ +void Span::printfNotify(SpanBuf *pObj, int nObj, HAPClient *hc){ boolean notifyFlag=false; for(int i=0;ievList.has(hc)){ // if connection hc is subscribed to EV notifications for this characteristic - if(pObj[i].characteristic->ev[conNum]){ // if notifications requested for this characteristic by specified connection number - if(!notifyFlag) // this is first notification for any characteristic hapOut << "{\"characteristics\":["; // print start of JSON array else // else already printed at least one other characteristic @@ -1829,8 +1803,6 @@ SpanCharacteristic::SpanCharacteristic(HapChar *hapChar, boolean isCustom){ iid=++(homeSpan.Accessories.back()->iidCount); service=homeSpan.Accessories.back()->Services.back(); aid=homeSpan.Accessories.back()->aid; - - ev=(boolean *)HS_CALLOC(homeSpan.maxConnections,sizeof(boolean)); } /////////////////////////////// @@ -1842,7 +1814,6 @@ SpanCharacteristic::~SpanCharacteristic(){ chr++; service->Characteristics.erase(chr); - free(ev); free(desc); free(unit); free(validValues); @@ -2116,9 +2087,11 @@ void SpanCharacteristic::printfAttributes(int flags){ if(flags&GET_AID) hapOut << ",\"aid\":" << aid; + + HAPClient *hc=&(*(homeSpan.currentClient)); if(flags&GET_EV) - hapOut << ",\"ev\":" << (ev[HAPClient::conNum]?"true":"false"); + hapOut << ",\"ev\":" << (evList.has(hc)?"true":"false"); if(flags&GET_STATUS) hapOut << ",\"status\":0"; @@ -2144,7 +2117,12 @@ StatusCode SpanCharacteristic::loadUpdate(char *val, char *ev, boolean wr){ return(StatusCode::NotifyNotAllowed); LOG1("Notification Request for aid=%u iid=%u: %s\n",aid,iid,evFlag?"true":"false"); - this->ev[HAPClient::conNum]=evFlag; + HAPClient *hc=&(*(homeSpan.currentClient)); + + if(evFlag) + evList.add(hc); + else + evList.remove(hc); } if(!val) // no request to update value @@ -2325,6 +2303,25 @@ SpanCharacteristic *SpanCharacteristic::setValidValues(int n, ...){ return(this); } +/////////////////////////////// + +boolean SpanCharacteristic::EVLIST::has(HAPClient *hc){ + return(find_if(begin(), end(), [hc](const HAPClient *hcTemp){return(hc==hcTemp);}) != end()); +} + +/////////////////////////////// + +void SpanCharacteristic::EVLIST::add(HAPClient *hc){ + if(!has(hc)) + push_back(hc); +} + +/////////////////////////////// + +void SpanCharacteristic::EVLIST::remove(HAPClient *hc){ + remove_if(begin(), end(), [hc](const HAPClient *hcTemp){return(hc==hcTemp);}); +} + /////////////////////////////// // SpanButton // /////////////////////////////// diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 6798303..5cbb83e 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -112,6 +112,8 @@ struct SpanCharacteristic; struct SpanBuf; struct SpanButton; struct SpanUserCommand; + +struct HAPClient; class Controller; extern Span homeSpan; @@ -263,16 +265,17 @@ class Span{ 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 + + list> hapList; // linked-list of HAPClient structures containing HTTP client connections, parsing routines, and state variables + list>::iterator currentClient; // iterator to current client + vector> Accessories; // vector of pointers to all Accessories + vector> Loops; // vector of pointer to all Services that have over-ridden loop() methods vector> Notifications; // vector of SpanBuf objects that store info for Characteristics that are updated with setVal() and require a Notification Event - vector> PushButtons; // vector of pointer to all PushButtons - unordered_map TimedWrites; // map of timed-write PIDs and Alarm Times (based on TTLs) - - unordered_map UserCommands; // map of pointers to all UserCommands + vector> PushButtons; // vector of pointer to all PushButtons + unordered_map TimedWrites; // map of timed-write PIDs and Alarm Times (based on TTLs) + unordered_map UserCommands; // map of pointers to all UserCommands void pollTask(); // poll HAP Clients and process any new HAP requests - int getFreeSlot(); // returns free HAPClient slot number. HAPClients slot keep track of each active HAPClient connection void checkConnect(); // check WiFi connection; connect if needed void commandMode(); // allows user to control and reset HomeSpan settings with the control button void resetStatus(); // resets statusLED and calls statusCallback based on current HomeSpan status @@ -285,8 +288,8 @@ class Span{ int updateCharacteristics(char *buf, SpanBuf *pObj); // parses PUT /characteristics JSON request 'buf into 'pObj' and updates referenced characteristics; returns 1 on success, 0 on fail void printfAttributes(SpanBuf *pObj, int nObj); // writes SpanBuf objects to hapOut stream boolean printfAttributes(char **ids, int numIDs, int flags); // writes accessory requested characteristic ids to hapOut stream - returns true if all characteristics are found and readable, else returns false - void clearNotify(int slotNum); // set ev notification flags for connection 'slotNum' to false across all characteristics - void printfNotify(SpanBuf *pObj, int nObj, int conNum); // writes notification JSON to hapOut stream based on SpanBuf objects and specified connection number + void clearNotify(HAPClient *hc); // clear all notifications related to specific client connection + void printfNotify(SpanBuf *pObj, int nObj, HAPClient *hc); // writes notification JSON to hapOut stream based on SpanBuf objects and specified connection static boolean invalidUUID(const char *uuid){ int x=0; @@ -487,6 +490,13 @@ class SpanCharacteristic{ STRING_t STRING = NULL; }; + class EVLIST : public vector>{ // vector of current connections that have subscribed to EV notifications for this Characteristic + public: + boolean has(HAPClient *hc); // returns true if pointer to connection hc is subscribed, else returns false + void add(HAPClient *hc); // adds connection hc as new subscriber, IF not already a subscriber + void remove(HAPClient *hc); // removes connection hc as a subscriber; okay to remove even if hc was not already a subscriber + }; + uint32_t iid=0; // Instance ID (HAP Table 6-3) HapChar *hapChar; // pointer to HAP Characteristic structure const char *type; // Characteristic Type @@ -502,7 +512,6 @@ class SpanCharacteristic{ boolean staticRange; // Flag that indicates whether Range is static and cannot be changed with setRange() boolean customRange=false; // Flag for custom ranges 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 boolean setRangeError=false; // flag to indicate attempt to set Range on Characteristic that does not support changes to Range @@ -513,7 +522,8 @@ class SpanCharacteristic{ unsigned long updateTime=0; // last time value was updated (in millis) either by PUT /characteristic OR by setVal() UVal newValue; // the updated value requested by PUT /characteristic SpanService *service=NULL; // pointer to Service containing this Characteristic - + EVLIST evList; // vector of current connections that have subscribed to EV notifications for this Characteristic + void printfAttributes(int flags); // writes Characteristic JSON to hapOut stream StatusCode loadUpdate(char *val, char *ev, boolean wr); // load updated val/ev from PUT /characteristic JSON request. Return intitial HAP status code (checks to see if characteristic is found, is writable, etc.) String uvPrint(UVal &u); // returns "printable" String for any type of Characteristic From 892c2247a3189d439ef0cb939d0e732e70b2eb97 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 8 Jun 2024 17:46:49 -0500 Subject: [PATCH 120/154] Finished testing new evList functionality --- src/HomeSpan.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 116ba7b..eef3bc3 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -866,7 +866,7 @@ void Span::processSerialCommand(const char *c){ if(((*chr)->perms)&EV){ LOG0(", EV=("); boolean addComma=false; - for(HAPClient *hc : (*chr)->evList){ + for(auto const &hc : (*chr)->evList){ LOG0("%s%d",addComma?",":"",hc->clientNumber); addComma=true; } @@ -2319,7 +2319,8 @@ void SpanCharacteristic::EVLIST::add(HAPClient *hc){ /////////////////////////////// void SpanCharacteristic::EVLIST::remove(HAPClient *hc){ - remove_if(begin(), end(), [hc](const HAPClient *hcTemp){return(hc==hcTemp);}); + auto it=remove_if(begin(), end(), [hc](const HAPClient *hcTemp){return(hc==hcTemp);}); + erase(it,end()); } /////////////////////////////// From 7ab8354ed7633f750287b99ce50a857e090385e0 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 8 Jun 2024 18:03:37 -0500 Subject: [PATCH 121/154] Deleted setMaxConnections() and DEPRECATED reserveSocketConnections setMaxConnections() was deprecated many version ago and is now deleted reserveSocketConnections() is no longer needed since new HomeKit architecture does not require more than a few connections - this function has been deprecated and if used will not do anything --- src/HomeSpan.cpp | 9 +++------ src/HomeSpan.h | 12 +++++------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index eef3bc3..25ee973 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -250,8 +250,8 @@ void Span::pollTask() { currentClient++; } else { LOG1("** Client #%d DISCONNECTED (%lu sec)\n",currentClient->clientNumber,millis()/1000); - currentClient->client.stop(); - delay(5); +// currentClient->client.stop(); +// delay(5); clearNotify(&*currentClient); // clear all notification requests for this connection currentClient=hapList.erase(currentClient); // remove HAPClient connection } @@ -500,7 +500,7 @@ void Span::checkConnect(){ if(webLog.timeServer) xTaskCreateUniversal(webLog.initTime, "timeSeverTaskHandle", 8096, &webLog, 1, NULL, 0); - LOG0("Starting HAP Server on port %d supporting %d simultaneous HomeKit Controller Connections...\n\n",tcpPortNum,maxConnections); + LOG0("Starting HAP Server on port %d...\n\n",tcpPortNum); hapServer->begin(); @@ -2390,8 +2390,6 @@ void SpanWebLog::init(uint16_t maxEntries, const char *serv, const char *tz, con isEnabled=true; } log = (log_t *)HS_CALLOC(maxEntries,sizeof(log_t)); - if(timeServer) - homeSpan.reserveSocketConnections(1); } /////////////////////////////// @@ -2458,7 +2456,6 @@ int SpanOTA::init(boolean _auth, boolean _safeLoad, const char *pwd){ enabled=true; safeLoad=_safeLoad; auth=_auth; - homeSpan.reserveSocketConnections(1); if(pwd==NULL) return(0); return(setPassword(pwd)); diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 5cbb83e..94ad261 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -238,8 +238,6 @@ class Span{ const char *defaultSetupCode=DEFAULT_SETUP_CODE; // Setup Code used for pairing uint16_t autoOffLED=0; // automatic turn-off duration (in seconds) for Status LED int logLevel=DEFAULT_LOG_LEVEL; // level for writing out log messages to serial monitor - uint8_t maxConnections=CONFIG_LWIP_MAX_SOCKETS-2; // maximum number of allowed simultaneous HAP connections - uint8_t requestedMaxCon=CONFIG_LWIP_MAX_SOCKETS-2; // requested maximum number of simultaneous HAP connections 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 @@ -341,7 +339,6 @@ class Span{ int getLogLevel(){return(logLevel);} // get Log Level Span& setSerialInputDisable(boolean val){serialInputDisabled=val;return(*this);} // sets whether serial input is disabled (true) or enabled (false) boolean getSerialInputDisable(){return(serialInputDisabled);} // returns true if serial input is disabled, or false if serial input in enabled - Span& reserveSocketConnections(uint8_t n){maxConnections-=n;return(*this);} // reserves n socket connections *not* to be used for HAP Span& setHostNameSuffix(const char *suffix){hostNameSuffix=suffix;return(*this);} // sets the hostName suffix to be used instead of the 6-byte AccessoryID Span& setPortNum(uint16_t port){tcpPortNum=port;return(*this);} // sets the TCP port number to use for communications between HomeKit and HomeSpan Span& setQRID(const char *id); // sets the Setup ID for optional pairing with a QR Code @@ -399,10 +396,11 @@ class Span{ Span& setTimeServerTimeout(uint32_t tSec){webLog.waitTime=tSec*1000;return(*this);} // sets wait time (in seconds) for optional web log time server to connect list>::const_iterator controllerListBegin(); - list>::const_iterator controllerListEnd(); - - [[deprecated("Please use reserveSocketConnections(n) method instead.")]] - void setMaxConnections(uint8_t n){requestedMaxCon=n;} // sets maximum number of simultaneous HAP connections + list>::const_iterator controllerListEnd(); + + [[deprecated("This function has been deprecated (it is not needed) and no longer does anything. Please remove from sketch to ensure backwards compatilibilty with future versions.")]] + Span& reserveSocketConnections(uint8_t n){return(*this);} + }; /////////////////////////////// From e10025bd5428ea9ad0e6852fe1b93ce297debd89 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 8 Jun 2024 20:32:39 -0500 Subject: [PATCH 122/154] Update HomeSpan.cpp --- src/HomeSpan.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 25ee973..d5d7398 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -250,8 +250,6 @@ void Span::pollTask() { currentClient++; } else { LOG1("** Client #%d DISCONNECTED (%lu sec)\n",currentClient->clientNumber,millis()/1000); -// currentClient->client.stop(); -// delay(5); clearNotify(&*currentClient); // clear all notification requests for this connection currentClient=hapList.erase(currentClient); // remove HAPClient connection } From c0d1a93c5203a6405894ffc8f2e61978f03f47ee Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 9 Jun 2024 07:21:17 -0500 Subject: [PATCH 123/154] Update README.md --- README.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 093e34e..6d4d667 100644 --- a/README.md +++ b/README.md @@ -114,14 +114,27 @@ HomeSpan is currently NOT compatible with version 3.X of the Arduino-ESP32 Board * a warning message is output on the Serial Monitor if `setVal()` is called to change the value of a Characteristic from within the `update()` method at the same time the Home App is sending an update request for that value * does not apply if `setVal()` is called from within `update()` to change the value of a Characteristic in response to a *write-response* request from the Home App +* **Refactored client/slot management to save memory and prepare for future integration of Ethernet support** + * fixed-array of Client/Socket connections replaced by dynamic linked-list + * serial interface now only shows active client connections (rather than a fixed list of client slots) + * **deprecated** `homeSpan.reserveSocketConnections()` since it is no longer needed + * **Fixed bug introduced in 1.9.0 that prevented `homeSpan.setPairingCode()` from saving (and subsequently using) the request Setup Pairing Code** - * this method now operates silently, unless an invalid pairing code is provided, in which case an error is reported to the Serial Monitor and *the sketch is halted* * the process for setting the Pairing Code using the CLI 'S' command or via the Access Point are unchanged - confirmation messages are still output to the Serial Monitor and errors do *not* cause the sketch to halt +* **Fixed memory leak introduced in 1.9.0 that would fail to free a small temporary memory block created when verifying a new connection** + * had no practical impact when using a Home Hub since Home Kit only creates a few permanent connections + * had significant impact when not using a Home Hub in cases where the Home App repeatedly drops and re-establishes connections, resulting in slow erosion of heap memory and then out-of-memory failure of the device after a few days (note use of HomeSpan without a Home Hub is not formally supported) + * **Fixed latent bug in SpanPoint** * HomeSpan would crash when printing **SpanPoint** configuration information to the Serial Monitor (the 'i' CLI command) if any of the instances of SpanPoint had *receiveSize=0* - * this bug never surfaced before since all the **SpanPoint examples** were based on receiving data and therefore had a non-zero *receiveSize* + * this bug never surfaced before since all the **SpanPoint examples** were based on receiving data and therefore had a non-zero *receiveSize* + +* **Deleted `homeSpan.setMaxConnections()`, which had been *deprecated* many versions ago** + +* **Deleted stand-alone `SpanRange` structure, which had been *deprecated* many versions ago** + * this has no impact on standard use of the Characteristic method `setRange()` See [Releases](https://github.com/HomeSpan/HomeSpan/releases) for details on all changes and bug fixes included in this update. From b4a32e82bbe23b76384458c3bed355f099960090 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 9 Jun 2024 07:31:35 -0500 Subject: [PATCH 124/154] Update Reference.md --- docs/Reference.md | 9 --------- 1 file changed, 9 deletions(-) diff --git a/docs/Reference.md b/docs/Reference.md index 3ce781d..4673c7c 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -103,15 +103,6 @@ Methods with a return type of `Span&` return a reference to `homeSpan` itself an * `int getLogLevel()` * returns the current Log Level as set by `setLogLevel(level)` -* `Span& reserveSocketConnections(uint8_t nSockets)` - * reserves *nSockets* network sockets for uses **other than** by the HomeSpan HAP Server for HomeKit Controller Connections - * for sketches compiled under Arduino-ESP32 v2.0.1 or later, HomeSpan reserves 14 sockets for HAP Controller Connections - * each call to `reserveSocketConnections(nSockets)` reduces this number by *nSockets* - * use this method if you add code to a sketch that requires its own socket connections (e.g. a separate web service, an MQTT server, etc.) - * multiple calls to this method are allowed - the number of sockets reserved will be the sum of *nSockets* across all calls - * note you do not need to separately reserve sockets for built-in HomeSpan functionality - * for example, `enableOTA()` already contains an embedded call to `reserveSocketConnections(1)` since HomeSpan knows one socket must be reserved to support OTA - * `Span& setPortNum(uint16_t port)` * sets the TCP port number used for communication between HomeKit and HomeSpan (default=80) From c1b2d49da158ef474c4f0cbffc314b9025156d9b Mon Sep 17 00:00:00 2001 From: Gregg Date: Mon, 10 Jun 2024 21:11:50 -0500 Subject: [PATCH 125/154] Added version.h containing check for Arduino-ESP32 Version 2 Also includes HomeSpan version number --- src/HAP.cpp | 2 ++ src/HomeSpan.cpp | 2 ++ src/HomeSpan.h | 4 +-- src/Network.cpp | 2 ++ src/Settings.h | 32 ----------------------- src/version.h | 67 ++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 74 insertions(+), 35 deletions(-) create mode 100644 src/version.h diff --git a/src/HAP.cpp b/src/HAP.cpp index ea031a3..fe6c82e 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -24,6 +24,8 @@ * SOFTWARE. * ********************************************************************************/ + +#include "version.h" #include #include diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index d5d7398..413e5ec 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -24,6 +24,8 @@ * SOFTWARE. * ********************************************************************************/ + +#include "version.h" #include #include diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 94ad261..333380c 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -27,9 +27,7 @@ #pragma once -#ifndef ARDUINO_ARCH_ESP32 -#error ERROR: HOMESPAN IS ONLY AVAILABLE FOR ESP32 MICROCONTROLLERS! -#endif +#include "version.h" #pragma GCC diagnostic ignored "-Wpmf-conversions" // eliminates warning messages from use of pointers to member functions to detect whether update() and loop() are overridden by user #pragma GCC diagnostic ignored "-Wunused-result" // eliminates warning message regarded unused result from call to crypto_scalarmult_curve25519() diff --git a/src/Network.cpp b/src/Network.cpp index ea024bb..bf9ee39 100644 --- a/src/Network.cpp +++ b/src/Network.cpp @@ -24,6 +24,8 @@ * SOFTWARE. * ********************************************************************************/ + +#include "version.h" #include diff --git a/src/Settings.h b/src/Settings.h index a94c3c1..56e96a6 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -24,41 +24,9 @@ * SOFTWARE. * ********************************************************************************/ - -// USER-DEFINED SETTINGS AND REFERENCE ENUMERATION CLASSES - -#include #pragma once -////////////////////////////////////////////////////// -// HomeSpan Version // - -#define HS_MAJOR 1 -#define HS_MINOR 9 -#define HS_PATCH 1 - -#define STRINGIFY(x) _STR(x) -#define _STR(x) #x - -#define HOMESPAN_VERSION STRINGIFY(HS_MAJOR) "." STRINGIFY(HS_MINOR) "." STRINGIFY(HS_PATCH) - -#define VERSION(major,minor,patch) major*10000+minor*100+patch - -#ifndef REQUIRED - #define REQUIRED 0 -#endif - -#if (REQUIRED>VERSION(HS_MAJOR,HS_MINOR,HS_PATCH)) - #error THIS SKETCH REQUIRES A LATER VERSION OF THE HOMESPAN LIBRARY -#endif - -#define ARDUINO_ESP_VERSION STRINGIFY(ESP_ARDUINO_VERSION_MAJOR) "." STRINGIFY(ESP_ARDUINO_VERSION_MINOR) "." STRINGIFY(ESP_ARDUINO_VERSION_PATCH) - -#if ESP_ARDUINO_VERSION_MAJOR<2 - #error HOMESPAN REQUIRES VERSION 2 OF THE ARDUINO ESP32 LIBRARY -#endif - ////////////////////////////////////////////////////// // DEFAULT SETTINGS // diff --git a/src/version.h b/src/version.h new file mode 100644 index 0000000..953e596 --- /dev/null +++ b/src/version.h @@ -0,0 +1,67 @@ +/********************************************************************************* + * MIT License + * + * Copyright (c) 2020-2024 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. + * + ********************************************************************************/ + +#pragma once + +////////////////////////////////////////////////////// +// HomeSpan Version // + +#define HS_MAJOR 1 +#define HS_MINOR 9 +#define HS_PATCH 1 + +////////////////////////////////////////////////////// + +#ifndef ARDUINO_ARCH_ESP32 + #error ERROR: HOMESPAN IS ONLY AVAILABLE FOR ESP32 MICROCONTROLLERS! + #include +#endif + +#include + +#if ESP_ARDUINO_VERSION_MAJOR!=2 + #error ERROR: HOMESPAN REQUIRES VERSION 2 OF THE ARDUINO ESP32 LIBRARY. HOMESPAN IS NOT COMPATIBLE WITH VERSION 1 OR VERSION 3 + #include +#endif + +#define STRINGIFY(x) _STR(x) +#define _STR(x) #x + +#define HOMESPAN_VERSION STRINGIFY(HS_MAJOR) "." STRINGIFY(HS_MINOR) "." STRINGIFY(HS_PATCH) + +#define VERSION(major,minor,patch) major*10000+minor*100+patch + +#ifndef REQUIRED + #define REQUIRED 0 +#endif + +#if (REQUIRED>VERSION(HS_MAJOR,HS_MINOR,HS_PATCH)) + #error ERROR: THIS SKETCH REQUIRES A LATER VERSION OF THE HOMESPAN LIBRARY + #include +#endif + +#define ARDUINO_ESP_VERSION STRINGIFY(ESP_ARDUINO_VERSION_MAJOR) "." STRINGIFY(ESP_ARDUINO_VERSION_MINOR) "." STRINGIFY(ESP_ARDUINO_VERSION_PATCH) From 9653224acaf7bcf00f8d592e6864f9a2128bfaa0 Mon Sep 17 00:00:00 2001 From: Gregg Date: Wed, 12 Jun 2024 21:18:44 -0500 Subject: [PATCH 126/154] Allow homeSpan.begin() to take dynamic char * instead of just constant string --- src/HomeSpan.cpp | 8 ++++---- src/HomeSpan.h | 11 ++++++----- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 413e5ec..37806b7 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -74,13 +74,13 @@ Span::Span(){ /////////////////////////////// -void Span::begin(Category catID, const char *displayName, const char *hostNameBase, const char *modelName){ +void Span::begin(Category catID, const char *_displayName, const char *_hostNameBase, const char *_modelName){ loopTaskHandle=xTaskGetCurrentTaskHandle(); // a roundabout way of getting the current task handle - this->displayName=displayName; - this->hostNameBase=hostNameBase; - this->modelName=modelName; + asprintf(&displayName,"%s",_displayName); + asprintf(&hostNameBase,"%s",_hostNameBase); + asprintf(&modelName,"%s",_modelName); sprintf(this->category,"%d",(int)catID); SpanPoint::setAsHub(); diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 333380c..a75423d 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -205,11 +205,11 @@ class Span{ friend class Network; friend class HAPClient; - const char *displayName; // display name for this device - broadcast as part of Bonjour MDNS - const char *hostNameBase; // base of hostName of this device - full host name broadcast by Bonjour MDNS will have 6-byte accessoryID as well as '.local' automatically appended - const char *hostNameSuffix=NULL; // optional "suffix" of hostName of this device. If specified, will be used as the hostName suffix instead of the 6-byte accessoryID + char *displayName; // display name for this device - broadcast as part of Bonjour MDNS + char *hostNameBase; // base of hostName of this device - full host name broadcast by Bonjour MDNS will have 6-byte accessoryID as well as '.local' automatically appended + char *hostNameSuffix=NULL; // optional "suffix" of hostName of this device. If specified, will be used as the hostName suffix instead of the 6-byte accessoryID char *hostName=NULL; // derived full hostname - const char *modelName; // model name of this device - broadcast as Bonjour field "md" + char *modelName; // model name of this device - broadcast as Bonjour field "md" char category[3]=""; // category ID of primary accessory - broadcast as Bonjour field "ci" (HAP Section 13) unsigned long snapTime; // current time (in millis) snapped before entering Service loops() or updates() boolean isInitialized=false; // flag indicating HomeSpan has been initialized @@ -337,7 +337,6 @@ class Span{ int getLogLevel(){return(logLevel);} // get Log Level Span& setSerialInputDisable(boolean val){serialInputDisabled=val;return(*this);} // sets whether serial input is disabled (true) or enabled (false) boolean getSerialInputDisable(){return(serialInputDisabled);} // returns true if serial input is disabled, or false if serial input in enabled - Span& setHostNameSuffix(const char *suffix){hostNameSuffix=suffix;return(*this);} // sets the hostName suffix to be used instead of the 6-byte AccessoryID Span& setPortNum(uint16_t port){tcpPortNum=port;return(*this);} // sets the TCP port number to use for communications between HomeKit and HomeSpan Span& setQRID(const char *id); // sets the Setup ID for optional pairing with a QR Code Span& setSketchVersion(const char *sVer){sketchVersion=sVer;return(*this);} // set optional sketch version number @@ -355,6 +354,8 @@ class Span{ Span& resetIID(uint32_t newIID); // resets the IID count for the current Accessory to start at newIID Span& setControllerCallback(void (*f)()){controllerCallback=f;return(*this);} // sets an optional user-defined function to call whenever a Controller is added/removed/changed + Span& setHostNameSuffix(const char *suffix){asprintf(&hostNameSuffix,"%s",suffix);return(*this);} // sets the hostName suffix to be used instead of the 6-byte AccessoryID + int enableOTA(boolean auth=true, boolean safeLoad=true){return(spanOTA.init(auth, safeLoad, NULL));} // enables Over-the-Air updates, with (auth=true) or without (auth=false) authorization password int enableOTA(const char *pwd, boolean safeLoad=true){return(spanOTA.init(true, safeLoad, pwd));} // enables Over-the-Air updates, with custom authorization password (overrides any password stored with the 'O' command) From 8e2304bacb11254483150604323271c35bb7e814 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 15 Jun 2024 22:07:27 -0500 Subject: [PATCH 127/154] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6d4d667..6853dd5 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,12 @@ Welcome to HomeSpan - a robust and extremely easy-to-use Arduino library for cre HomeSpan provides a microcontroller-focused implementation of Apple's HomeKit Accessory Protocol Specification Release R2 (HAP-R2) designed specifically for the Espressif ESP32 microcontroller running within the Arduino IDE. HomeSpan pairs directly to HomeKit via your home WiFi network without the need for any external bridges or components. With HomeSpan you can use the full power of the ESP32's I/O functionality to create custom control software and/or hardware to automatically operate external devices from the Home App on your iPhone, iPad, or Mac, or with Siri. -HomeSpan requires version 2.0.0 or later of the [Arduino-ESP32 Board Manager](https://github.com/espressif/arduino-esp32), and has been tested up through version 2.0.14 (recommended). HomeSpan can be run on the original ESP32 as well as Espressif's ESP32-S2, ESP32-C3, and ESP32-S3 chips. +HomeSpan requires version 2 of the [Arduino-ESP32 Board Manager](https://github.com/espressif/arduino-esp32). HomeSpan can be run on the original ESP32 as well as Espressif's ESP32-S2, ESP32-C3, and ESP32-S3 chips. -HomeSpan is currently NOT compatible with version 3.X of the Arduino-ESP32 Board Manager, since version 3 contains many breaking changes and is not backwards-compatible with verison 2.X of the Arduino-ESP32 Board Manager. At present, HomeSpan can only be compiled under version 2.X of the Board Manager. +HomeSpan is currently NOT compatible with version 3.X of the Arduino-ESP32 Board Manager, since version 3 contains many breaking changes and is not backwards-compatible with version 2.X of the Arduino-ESP32 Board Manager. At present, HomeSpan can only be compiled under version 2.X of the Board Manager. > [!NOTE] -> Apple's new HomeKit architecture [requires the use of a Home Hub](https://support.apple.com/en-us/HT207057) (either a HomePod or Apple TV) for full and proper operation of any HomeKit device, including those based on HomeSpan. Without a Home Hub, HomeSpan cannot send notifications to the Home App - things like pushbuttons and temperature sensors will not be able to transmit updates to the Home App. +> Apple's new HomeKit architecture [requires the use of a Home Hub](https://support.apple.com/en-us/HT207057) (either a HomePod or Apple TV) for full and proper operation of any HomeKit device, including those based on HomeSpan. Without a Home Hub, HomeSpan cannot send notifications to the Home App - things like pushbuttons and temperature sensors will not be able to transmit updates to the Home App. Use of HomeSpan without a Home Hub is NOT recommended. ### HomeSpan Highlights From 7de8f1abb4bca73795f6fbab8a138f9a21ceaf38 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 16 Jun 2024 09:10:52 -0500 Subject: [PATCH 128/154] Update TVServices.md --- docs/TVServices.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/TVServices.md b/docs/TVServices.md index 4af359e..34710a9 100644 --- a/docs/TVServices.md +++ b/docs/TVServices.md @@ -66,7 +66,7 @@ This Service allows you to change the volume of a television using the iPhone's ### Examples -Please see [*File → Examples → HomeSpan → Other Examples → Television*](../Other%20Examples/Television) for a complete worked example demonstrating the effects of using different combinations of the above Characteristics. Also, don't forget to check out the [HomeSpan Projects](https://github.com/topics/homespan) page for some real-world examples of TV sketches and controllers. +Please see [*File → Examples → HomeSpan → Other Examples → Television*](../examples/Other%20Examples/Television) for a complete worked example demonstrating the effects of using different combinations of the above Characteristics. Also, don't forget to check out the [HomeSpan Projects](https://github.com/topics/homespan) page for some real-world examples of TV sketches and controllers. ### Credits From 2279c305108a2cc3e6a119eba4d420e62cd6c047 Mon Sep 17 00:00:00 2001 From: Gregg Date: Wed, 19 Jun 2024 13:02:11 -0500 Subject: [PATCH 129/154] Update HAP.cpp --- src/HAP.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index fe6c82e..102e382 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -695,9 +695,9 @@ int HAPClient::postPairVerifyURL(uint8_t *content, size_t len){ Controller *tPair; // temporary pointer to Controller if(!(tPair=findController(*itIdentifier))){ - LOG0("\n*** ERROR: Unrecognized Controller ID: "); - charPrintRow(*itIdentifier,hap_controller_IDBYTES,2); - LOG0("\n\n"); + LOG1("\n*** WARNING: Unrecognized Controller ID: "); + charPrintRow(*itIdentifier,hap_controller_IDBYTES,1); + LOG1("\n\n"); responseTLV.add(kTLVType_State,pairState_M4); // set State= responseTLV.add(kTLVType_Error,tagError_Authentication); // set Error=Authentication tlvRespond(responseTLV); // send response to client From c3c32dbc07f6a8d806caab854cedd33ea80205d1 Mon Sep 17 00:00:00 2001 From: Gregg Date: Fri, 21 Jun 2024 21:36:55 -0500 Subject: [PATCH 130/154] Changed SpanServices::getLinks() to template function Allows return of Linked Services to be cast into a pointer to any Service type. Default template if not specified is --- src/HomeSpan.h | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/HomeSpan.h b/src/HomeSpan.h index a75423d..3ba8555 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -460,7 +460,13 @@ class 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 + + template vector> getLinks(){ // returns linkedServices vector, mapped to , for use as range in "for-each" loops + vector> v; + for(auto svc : linkedServices) + v.push_back(static_cast(svc)); + return(v); + } uint32_t getIID(){return(iid);} // returns IID of Service From 7a13b999429e55a8382fc1bb4b6b1623f0aebdc9 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 23 Jun 2024 10:19:32 -0500 Subject: [PATCH 131/154] Added initial method to contract TLV8 Characteristic; Added UUID static char to Services; Added hapName filter to getLinks() --- src/HomeSpan.cpp | 26 ++++++++++++++++++++++++++ src/HomeSpan.h | 9 ++++++--- src/Span.h | 20 ++++++++++---------- 3 files changed, 42 insertions(+), 13 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 37806b7..f02e895 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -1874,6 +1874,32 @@ void SpanCharacteristic::uvSet(UVal &u, const char *val){ /////////////////////////////// +void SpanCharacteristic::uvSet(UVal &u, TLV8 *tlv){ + + const size_t bufSize=36; // maximum size of buffer to store packed TLV bytes before encoding directly into value; must be multiple of 3 + size_t nBytes=tlv->pack_size(); // total size of packed TLV in bytes + + if(nBytes>0){ + size_t nChars; + mbedtls_base64_encode(NULL,0,&nChars,NULL,nBytes); // get length of string buffer needed (mbedtls includes the trailing null in this size) + u.STRING = (char *)HS_REALLOC(u.STRING,nChars); // allocate sufficient size for storing value + TempBuffer tBuf(bufSize); // create fixed-size buffer to store packed TLV bytes + tlv->pack_init(); // initialize TLV packing + uint8_t *p=(uint8_t *)u.STRING; // set pointer to beginning of value + while((nBytes=tlv->pack(tBuf,bufSize))>0){ // pack the next set of TLV bytes, up to a maximum of bufSize, into tBuf + size_t olen; // number of characters written (excludes null character) + mbedtls_base64_encode(p,nChars,&olen,tBuf,nBytes); // encode data directly into value + p+=olen; // advance pointer to null character + nChars-=olen; // subtract number of characters remaining + } + } else { + u.STRING = (char *)HS_REALLOC(u.STRING,1); // allocate sufficient size for just trailing null character + *u.STRING ='\0'; + } +} + +/////////////////////////////// + char *SpanCharacteristic::getStringGeneric(UVal &val){ if(format>=FORMAT::STRING) return val.STRING; diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 3ba8555..67e06e5 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -461,10 +461,12 @@ class SpanService{ 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 - template vector> getLinks(){ // returns linkedServices vector, mapped to , for use as range in "for-each" loops + template vector> getLinks(const char *hapName=NULL){ // returns linkedServices vector, mapped to , for use as range in "for-each" loops vector> v; - for(auto svc : linkedServices) - v.push_back(static_cast(svc)); + for(auto svc : linkedServices){ + if(hapName==NULL || !strcmp(hapName,svc->hapName)) + v.push_back(static_cast(svc)); + } return(v); } @@ -533,6 +535,7 @@ class SpanCharacteristic{ void uvSet(UVal &dest, UVal &src); // copies UVal src into UVal dest void uvSet(UVal &u, const char *val); // copies string val into UVal u + void uvSet(UVal &u, TLV8 *tlv); // copies TLV8 val into UVal u (after transforming to a char *) template void uvSet(UVal &u, T val){ // copies numeric val into UVal u switch(format){ diff --git a/src/Span.h b/src/Span.h index 624f0f7..f4d6b7f 100644 --- a/src/Span.h +++ b/src/Span.h @@ -29,17 +29,16 @@ // SPAN SERVICES (HAP Chapter 8) // /////////////////////////////////// -// Macros to define services, along with vectors of required and optional characteristics for each Span Service structure -// The names of the macros are picked up by external scripts to help generate documentation - -// Note: These macros below are also parsed by an external awk script to auto-generate Services and Characteristics documentation. +// Macros to define Services, along with vectors of required and optional Characteristics for each Span Service structure. +// +// NOTE: these macros are parsed by an external awk script to auto-generate Services and Characteristics documentation. // // The CREATE_SERV_DEP() macro is the same as the CREATE_SERV() macro, except that it is used for deprecated Services that will not -// be included in documentation. The OPT_DEP() macro is that same as the OPT() macro, except that it is used for deprecated Characteristics -// that will not be included in documentation. +// be included in documentation. The REQ_DEP and OPT_DEP() macros are the same as the REQ() and OPT() macros, except that they are used +// for deprecated Characteristics that will not be included in documentation. -#define CREATE_SERV(NAME,UUID) struct NAME : SpanService { NAME() : SpanService{#UUID,#NAME}{ -#define CREATE_SERV_DEP(NAME,UUID) struct NAME : SpanService { NAME() : SpanService{#UUID,#NAME}{ +#define CREATE_SERV(NAME,_UUID) struct NAME : SpanService { static constexpr const char *UUID=#_UUID; NAME() : SpanService{#_UUID,#NAME}{ +#define CREATE_SERV_DEP(NAME,_UUID) struct NAME : SpanService { static constexpr const char *UUID=#_UUID; NAME() : SpanService{#_UUID,#NAME}{ #define END_SERV }}; #define REQ(HAPCHAR) req.push_back(&hapChars.HAPCHAR) @@ -479,7 +478,7 @@ namespace Service { struct HAPCHAR : SpanCharacteristic { __VA_OPT__(enum{) __VA_ARGS__ __VA_OPT__(};) HAPCHAR(TYPE val=DEFVAL, boolean nvsStore=false) : SpanCharacteristic {&hapChars.HAPCHAR} { init(val,nvsStore,(TYPE)MINVAL,(TYPE)MAXVAL); } }; namespace Characteristic { - + CREATE_CHAR(uint32_t,AccessoryFlags,1,1,1); // not applicable for HomeSpan CREATE_CHAR(uint8_t,Active,0,0,1,INACTIVE,ACTIVE); // indicates if the Service is active/on CREATE_CHAR(uint32_t,ActiveIdentifier,0,0,255); // numerical Identifier of the InputSource selected in the Home App. @@ -514,7 +513,8 @@ namespace Characteristic { CREATE_CHAR(double,CurrentRelativeHumidity,0,0,100); // current humidity measured as a percentage CREATE_CHAR(double,CurrentTemperature,0,0,100); // current temperature measured in Celsius CREATE_CHAR(int,CurrentTiltAngle,0,-90,90); // current angle (in degrees) of slats from fully up or left (-90) to fully open (0) to fully down or right (90) - CREATE_CHAR(const char *,DisplayOrder,"",0,1); // specifies the order in which the TV inputs are displayed for selection in the Home App +// CREATE_CHAR(const char *,DisplayOrder,"",0,1); // specifies the order in which the TV inputs are displayed for selection in the Home App + CREATE_CHAR(TLV8 *,DisplayOrder,NULL,NULL,NULL); // specifies the order in which the TV inputs are displayed for selection in the Home App CREATE_CHAR(double,FilterLifeLevel,100,0,100); // measured as a percentage of remaining life CREATE_CHAR(uint8_t,FilterChangeIndication,0,0,1,NO_CHANGE_NEEDED,CHANGE_NEEDED); // indicates state of filter CREATE_CHAR(const char *,FirmwareRevision,"1.0.0",0,1); // must be in form x[.y[.z]] - informational only From 2d25c044aea3baa165ccbe385c04b4e4a742ff96 Mon Sep 17 00:00:00 2001 From: Gregg Date: Tue, 25 Jun 2024 20:50:40 -0500 Subject: [PATCH 132/154] Allow TLV8 Characteristics to accept an initial value when instantiated Also simplified up form of SpanCharacteristic init() template --- src/HomeSpan.cpp | 49 ++++++++++++++---------------------------------- src/HomeSpan.h | 6 +++--- src/Span.h | 21 ++++++++++----------- src/TLV8.cpp | 2 ++ src/TLV8.h | 2 ++ 5 files changed, 31 insertions(+), 49 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index f02e895..1884cf1 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -1874,26 +1874,26 @@ void SpanCharacteristic::uvSet(UVal &u, const char *val){ /////////////////////////////// -void SpanCharacteristic::uvSet(UVal &u, TLV8 *tlv){ +void SpanCharacteristic::uvSet(UVal &u, TLV8 &tlv){ const size_t bufSize=36; // maximum size of buffer to store packed TLV bytes before encoding directly into value; must be multiple of 3 - size_t nBytes=tlv->pack_size(); // total size of packed TLV in bytes + size_t nBytes=tlv.pack_size(); // total size of packed TLV in bytes if(nBytes>0){ size_t nChars; - mbedtls_base64_encode(NULL,0,&nChars,NULL,nBytes); // get length of string buffer needed (mbedtls includes the trailing null in this size) - u.STRING = (char *)HS_REALLOC(u.STRING,nChars); // allocate sufficient size for storing value - TempBuffer tBuf(bufSize); // create fixed-size buffer to store packed TLV bytes - tlv->pack_init(); // initialize TLV packing - uint8_t *p=(uint8_t *)u.STRING; // set pointer to beginning of value - while((nBytes=tlv->pack(tBuf,bufSize))>0){ // pack the next set of TLV bytes, up to a maximum of bufSize, into tBuf - size_t olen; // number of characters written (excludes null character) - mbedtls_base64_encode(p,nChars,&olen,tBuf,nBytes); // encode data directly into value - p+=olen; // advance pointer to null character - nChars-=olen; // subtract number of characters remaining + mbedtls_base64_encode(NULL,0,&nChars,NULL,nBytes); // get length of string buffer needed (mbedtls includes the trailing null in this size) + u.STRING = (char *)HS_REALLOC(u.STRING,nChars); // allocate sufficient size for storing value + TempBuffer tBuf(bufSize); // create fixed-size buffer to store packed TLV bytes + tlv.pack_init(); // initialize TLV packing + uint8_t *p=(uint8_t *)u.STRING; // set pointer to beginning of value + while((nBytes=tlv.pack(tBuf,bufSize))>0){ // pack the next set of TLV bytes, up to a maximum of bufSize, into tBuf + size_t olen; // number of characters written (excludes null character) + mbedtls_base64_encode(p,nChars,&olen,tBuf,nBytes); // encode data directly into value + p+=olen; // advance pointer to null character + nChars-=olen; // subtract number of characters remaining } } else { - u.STRING = (char *)HS_REALLOC(u.STRING,1); // allocate sufficient size for just trailing null character + u.STRING = (char *)HS_REALLOC(u.STRING,1); // allocate sufficient size for just trailing null character *u.STRING ='\0'; } } @@ -1998,28 +1998,7 @@ return(tlv.pack_size()); void SpanCharacteristic::setTLV(TLV8 &tlv, boolean notify){ setValCheck(); - - const size_t bufSize=36; // maximum size of buffer to store packed TLV bytes before encoding directly into value; must be multiple of 3 - size_t nBytes=tlv.pack_size(); // total size of packed TLV in bytes - - if(nBytes>0){ - size_t nChars; - mbedtls_base64_encode(NULL,0,&nChars,NULL,nBytes); // get length of string buffer needed (mbedtls includes the trailing null in this size) - value.STRING = (char *)HS_REALLOC(value.STRING,nChars); // allocate sufficient size for storing value - TempBuffer tBuf(bufSize); // create fixed-size buffer to store packed TLV bytes - tlv.pack_init(); // initialize TLV packing - uint8_t *p=(uint8_t *)value.STRING; // set pointer to beginning of value - while((nBytes=tlv.pack(tBuf,bufSize))>0){ // pack the next set of TLV bytes, up to a maximum of bufSize, into tBuf - size_t olen; // number of characters written (excludes null character) - mbedtls_base64_encode(p,nChars,&olen,tBuf,nBytes); // encode data directly into value - p+=olen; // advance pointer to null character - nChars-=olen; // subtract number of characters remaining - } - } else { - value.STRING = (char *)HS_REALLOC(value.STRING,1); // allocate sufficient size for just trailing null character - *value.STRING ='\0'; - } - + uvSet(value,tlv); setValFinish(notify); } diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 67e06e5..9a2ccf1 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -535,7 +535,7 @@ class SpanCharacteristic{ void uvSet(UVal &dest, UVal &src); // copies UVal src into UVal dest void uvSet(UVal &u, const char *val); // copies string val into UVal u - void uvSet(UVal &u, TLV8 *tlv); // copies TLV8 val into UVal u (after transforming to a char *) + void uvSet(UVal &u, TLV8 &tlv); // copies TLV8 val into UVal u (after transforming to a char *) template void uvSet(UVal &u, T val){ // copies numeric val into UVal u switch(format){ @@ -602,8 +602,8 @@ class SpanCharacteristic{ protected: ~SpanCharacteristic(); // destructor - - template void init(T val, boolean nvsStore, A min=0, B max=1){ + + template void init(T val, boolean nvsStore, T min, T max){ uvSet(value,val); diff --git a/src/Span.h b/src/Span.h index f4d6b7f..3f36809 100644 --- a/src/Span.h +++ b/src/Span.h @@ -475,7 +475,7 @@ namespace Service { // Macro to define Span Characteristic structures based on name of HAP Characteristic, default value, and min/max value (not applicable for STRING or BOOL which default to min=0, max=1) #define CREATE_CHAR(TYPE,HAPCHAR,DEFVAL,MINVAL,MAXVAL,...) \ - struct HAPCHAR : SpanCharacteristic { __VA_OPT__(enum{) __VA_ARGS__ __VA_OPT__(};) HAPCHAR(TYPE val=DEFVAL, boolean nvsStore=false) : SpanCharacteristic {&hapChars.HAPCHAR} { init(val,nvsStore,(TYPE)MINVAL,(TYPE)MAXVAL); } }; + struct HAPCHAR : SpanCharacteristic { __VA_OPT__(enum{) __VA_ARGS__ __VA_OPT__(};) HAPCHAR(TYPE val=DEFVAL, boolean nvsStore=false) : SpanCharacteristic {&hapChars.HAPCHAR} { init(val,nvsStore,MINVAL,MAXVAL); } }; namespace Characteristic { @@ -496,7 +496,7 @@ namespace Characteristic { CREATE_CHAR(double,CoolingThresholdTemperature,10,10,35); // cooling turns on when temperature (in Celsius) rises above this threshold CREATE_CHAR(uint32_t,ColorTemperature,200,140,500); // measured in inverse megaKelvin (= 1,000,000 / Kelvin) CREATE_CHAR(uint8_t,ContactSensorState,1,0,1,DETECTED,NOT_DETECTED); // indictates if contact is detected (i.e. closed) - CREATE_CHAR(const char *,ConfiguredName,"unnamed",0,1); // default display name of this Service + CREATE_CHAR(const char *,ConfiguredName,"unnamed",NULL,NULL); // default display name of this Service CREATE_CHAR(double,CurrentAmbientLightLevel,1,0.0001,100000); // measured in Lux (lumens/m2 CREATE_CHAR(int,CurrentHorizontalTiltAngle,0,-90,90); // current angle (in degrees) of slats from fully up (-90) to fully open (0) to fully down (90) CREATE_CHAR(uint8_t,CurrentAirPurifierState,0,0,2,INACTIVE,IDLE,PURIFYING); // indicates current state of air purification @@ -513,12 +513,11 @@ namespace Characteristic { CREATE_CHAR(double,CurrentRelativeHumidity,0,0,100); // current humidity measured as a percentage CREATE_CHAR(double,CurrentTemperature,0,0,100); // current temperature measured in Celsius CREATE_CHAR(int,CurrentTiltAngle,0,-90,90); // current angle (in degrees) of slats from fully up or left (-90) to fully open (0) to fully down or right (90) -// CREATE_CHAR(const char *,DisplayOrder,"",0,1); // specifies the order in which the TV inputs are displayed for selection in the Home App - CREATE_CHAR(TLV8 *,DisplayOrder,NULL,NULL,NULL); // specifies the order in which the TV inputs are displayed for selection in the Home App + CREATE_CHAR(TLV8 &,DisplayOrder,TLV8::NULL_TLV,TLV8::NULL_TLV,TLV8::NULL_TLV); // specifies the order in which the TV inputs are displayed for selection in the Home App CREATE_CHAR(double,FilterLifeLevel,100,0,100); // measured as a percentage of remaining life CREATE_CHAR(uint8_t,FilterChangeIndication,0,0,1,NO_CHANGE_NEEDED,CHANGE_NEEDED); // indicates state of filter - CREATE_CHAR(const char *,FirmwareRevision,"1.0.0",0,1); // must be in form x[.y[.z]] - informational only - CREATE_CHAR(const char *,HardwareRevision,"1.0.0",0,1); // must be in form x[.y[.z]] - informational only + CREATE_CHAR(const char *,FirmwareRevision,"1.0.0",NULL,NULL); // must be in form x[.y[.z]] - informational only + CREATE_CHAR(const char *,HardwareRevision,"1.0.0",NULL,NULL); // must be in form x[.y[.z]] - informational only CREATE_CHAR(double,HeatingThresholdTemperature,16,0,25); // heating turns on when temperature (in Celsius) falls below this threshold CREATE_CHAR(boolean,HoldPosition,false,0,1); // deprecated CREATE_CHAR(double,Hue,0,0,360); // color (in degrees) from red (0) to green (120) to blue (240) and back to red (360) @@ -532,11 +531,11 @@ namespace Characteristic { CREATE_CHAR(uint8_t,LockCurrentState,0,0,3,UNLOCKED,LOCKED,JAMMED,UNKNOWN); // indicates state of a lock CREATE_CHAR(uint8_t,LockPhysicalControls,0,0,1,CONTROL_LOCK_DISABLED,CONTROL_LOCK_ENABLED); // indicates if local control lock is enabled CREATE_CHAR(uint8_t,LockTargetState,0,0,1,UNLOCK,LOCK); // indicates desired state of lock - CREATE_CHAR(const char *,Manufacturer,"HomeSpan",0,1); // any string - informational only - CREATE_CHAR(const char *,Model,"HomeSpan-ESP32",0,1); // any string - informational only + CREATE_CHAR(const char *,Manufacturer,"HomeSpan",NULL,NULL); // any string - informational only + CREATE_CHAR(const char *,Model,"HomeSpan-ESP32",NULL,NULL); // any string - informational only CREATE_CHAR(boolean,MotionDetected,0,0,1,NOT_DETECTED,DETECTED); // indicates if motion is detected CREATE_CHAR(boolean,Mute,0,0,1,OFF,ON); // not used - CREATE_CHAR(const char *,Name,"unnamed",0,1); // default display name of the Accessory + CREATE_CHAR(const char *,Name,"unnamed",NULL,NULL); // default display name of the Accessory CREATE_CHAR(double,NitrogenDioxideDensity,0,0,1000); // measured in µg/m3 CREATE_CHAR(boolean,ObstructionDetected,0,0,1,NOT_DETECTED,DETECTED); // indicates if obstruction is detected CREATE_CHAR(double,PM25Density,0,0,1000); // 2.5-micron particulate density, measured in µg/m3 @@ -561,7 +560,7 @@ namespace Characteristic { CREATE_CHAR(uint8_t,SecuritySystemAlarmType,0,0,1,KNOWN,UNKNOWN); // indicates whether alarm was triggered for known reason CREATE_CHAR(uint8_t,SecuritySystemCurrentState,3,0,4,ARMED_STAY,ARMED_AWAY,ARMED_NIGHT,DISARMED,ALARM_TRIGGERED); // indicates current state of the security system CREATE_CHAR(uint8_t,SecuritySystemTargetState,3,0,3,ARM_STAY,ARM_AWAY,ARM_NIGHT,DISARM); // indicates desired state of the security system - CREATE_CHAR(const char *,SerialNumber,"HS-12345",0,1); // any string - informational only + CREATE_CHAR(const char *,SerialNumber,"HS-12345",NULL,NULL); // any string - informational only CREATE_CHAR(uint8_t,ServiceLabelIndex,1,1,255); // numerical index used to distinguish multiple copies of the same Service within an Accessory CREATE_CHAR(uint8_t,ServiceLabelNamespace,1,0,1,DOTS,NUMERALS); // indicates how un-named Services linked together with a ServiceLabel Service should be displayed in the Home App CREATE_CHAR(uint8_t,SlatType,0,0,1,HORIZONTAL,VERTICAL); // indicates the direction of a slat or group of slats @@ -591,7 +590,7 @@ namespace Characteristic { CREATE_CHAR(uint8_t,TemperatureDisplayUnits,0,0,1,CELSIUS,FAHRENHEIT); // indicates the desired units to display the temperature on the device itself (has no effect on Home App) CREATE_CHAR(int,TargetVerticalTiltAngle,0,-90,90); // indicates desired angle (in degrees) of slats from fully left (-90) to fully open (0) to fully right (90) CREATE_CHAR(uint8_t,ValveType,0,0,3,GENERIC,IRRIGATION,SHOWER_HEAD,FAUCET); // indicates the type of valve - CREATE_CHAR(const char *,Version,"1.0.0",0,1); // unused + CREATE_CHAR(const char *,Version,"1.0.0",NULL,NULL); // unused CREATE_CHAR(double,VOCDensity,0,0,1000); // measured in µg/m3 CREATE_CHAR(uint8_t,Volume,0,0,100); // unused CREATE_CHAR(uint8_t,VolumeControlType,3,0,3,NONE,RELATIVE,RELATIVE_CURRENT,ABSOLUTE); // indicates the type of volume control diff --git a/src/TLV8.cpp b/src/TLV8.cpp index d3b6cff..8bb4267 100644 --- a/src/TLV8.cpp +++ b/src/TLV8.cpp @@ -297,3 +297,5 @@ void TLV8::osprint(std::ostream& os, TLV8_it it1, TLV8_it it2){ } ////////////////////////////////////// + +TLV8 TLV8::NULL_TLV; diff --git a/src/TLV8.h b/src/TLV8.h index 282ce97..50f73e1 100644 --- a/src/TLV8.h +++ b/src/TLV8.h @@ -104,6 +104,8 @@ class TLV8 : public std::list> { public: + static TLV8 NULL_TLV; + TLV8(){}; TLV8(const TLV8_names *names, int nNames) : names{names}, nNames{nNames} {}; From f73c7799137cc7fa0725248510aef364ee4a397f Mon Sep 17 00:00:00 2001 From: Gregg Date: Tue, 25 Jun 2024 21:59:23 -0500 Subject: [PATCH 133/154] Added "constantness" to all aspects of TLV8 code, including Characteristic Constructors --- src/HomeSpan.cpp | 4 +-- src/HomeSpan.h | 4 +-- src/Span.h | 2 +- src/TLV8.cpp | 16 +++++------ src/TLV8.h | 72 ++++++++++++++++++++++++------------------------ 5 files changed, 49 insertions(+), 49 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 1884cf1..e7a2475 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -1874,7 +1874,7 @@ void SpanCharacteristic::uvSet(UVal &u, const char *val){ /////////////////////////////// -void SpanCharacteristic::uvSet(UVal &u, TLV8 &tlv){ +void SpanCharacteristic::uvSet(UVal &u, const TLV8 &tlv){ const size_t bufSize=36; // maximum size of buffer to store packed TLV bytes before encoding directly into value; must be multiple of 3 size_t nBytes=tlv.pack_size(); // total size of packed TLV in bytes @@ -1995,7 +1995,7 @@ return(tlv.pack_size()); /////////////////////////////// -void SpanCharacteristic::setTLV(TLV8 &tlv, boolean notify){ +void SpanCharacteristic::setTLV(const TLV8 &tlv, boolean notify){ setValCheck(); uvSet(value,tlv); diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 9a2ccf1..b363d42 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -535,7 +535,7 @@ class SpanCharacteristic{ void uvSet(UVal &dest, UVal &src); // copies UVal src into UVal dest void uvSet(UVal &u, const char *val); // copies string val into UVal u - void uvSet(UVal &u, TLV8 &tlv); // copies TLV8 val into UVal u (after transforming to a char *) + void uvSet(UVal &u, const TLV8 &tlv); // copies TLV8 val into UVal u (after transforming to a char *) template void uvSet(UVal &u, T val){ // copies numeric val into UVal u switch(format){ @@ -658,7 +658,7 @@ class SpanCharacteristic{ void setString(const char *val, boolean notify=true); // sets the value and newValue for string-based Characteristic void setData(uint8_t *data, size_t len, boolean notify=true); // sets the value and newValue for data-based Characteristic - void setTLV(TLV8 &tlv, boolean notify=true); // sets the value and newValue for tlv8-based Characteristic + void setTLV(const TLV8 &tlv, boolean notify=true); // sets the value and newValue for tlv8-based Characteristic template void setVal(T val, boolean notify=true){ // sets the value and newValue for numeric-based Characteristics diff --git a/src/Span.h b/src/Span.h index 3f36809..e43a88a 100644 --- a/src/Span.h +++ b/src/Span.h @@ -513,7 +513,7 @@ namespace Characteristic { CREATE_CHAR(double,CurrentRelativeHumidity,0,0,100); // current humidity measured as a percentage CREATE_CHAR(double,CurrentTemperature,0,0,100); // current temperature measured in Celsius CREATE_CHAR(int,CurrentTiltAngle,0,-90,90); // current angle (in degrees) of slats from fully up or left (-90) to fully open (0) to fully down or right (90) - CREATE_CHAR(TLV8 &,DisplayOrder,TLV8::NULL_TLV,TLV8::NULL_TLV,TLV8::NULL_TLV); // specifies the order in which the TV inputs are displayed for selection in the Home App + CREATE_CHAR(const TLV8 &,DisplayOrder,TLV8::NULL_TLV,TLV8::NULL_TLV,TLV8::NULL_TLV); // specifies the order in which the TV inputs are displayed for selection in the Home App CREATE_CHAR(double,FilterLifeLevel,100,0,100); // measured as a percentage of remaining life CREATE_CHAR(uint8_t,FilterChangeIndication,0,0,1,NO_CHANGE_NEEDED,CHANGE_NEEDED); // indicates state of filter CREATE_CHAR(const char *,FirmwareRevision,"1.0.0",NULL,NULL); // must be in form x[.y[.z]] - informational only diff --git a/src/TLV8.cpp b/src/TLV8.cpp index 8bb4267..4b161da 100644 --- a/src/TLV8.cpp +++ b/src/TLV8.cpp @@ -52,7 +52,7 @@ void tlv8_t::update(size_t addLen, const uint8_t *addVal){ ///////////////////////////////////// -void tlv8_t::osprint(std::ostream& os){ +void tlv8_t::osprint(std::ostream& os) const { uint8_t *p=val.get(); // starting pointer uint8_t *pend=p+len; // ending pointer (may equal starting if len=0) @@ -104,7 +104,7 @@ TLV8_it TLV8::add(uint8_t tag, uint64_t val){ ///////////////////////////////////// -TLV8_it TLV8::find(uint8_t tag, TLV8_it it1, TLV8_it it2){ +TLV8_itc TLV8::find(uint8_t tag, TLV8_itc it1, TLV8_itc it2) const { auto it=it1; while(it!=it2 && it->getTag()!=tag) @@ -114,7 +114,7 @@ TLV8_it TLV8::find(uint8_t tag, TLV8_it it1, TLV8_it it2){ ///////////////////////////////////// -size_t TLV8::pack_size(TLV8_it it1, TLV8_it it2){ +size_t TLV8::pack_size(TLV8_itc it1, TLV8_itc it2) const { size_t nBytes=0; @@ -130,7 +130,7 @@ size_t TLV8::pack_size(TLV8_it it1, TLV8_it it2){ ///////////////////////////////////// -size_t TLV8::pack(uint8_t *buf, size_t bufSize){ +size_t TLV8::pack(uint8_t *buf, size_t bufSize) const { size_t nBytes=0; @@ -237,7 +237,7 @@ int TLV8::unpack(TLV8_it it){ ///////////////////////////////////// -const char *TLV8::getName(uint8_t tag){ +const char *TLV8::getName(uint8_t tag) const { if(names==NULL) return(NULL); @@ -252,7 +252,7 @@ const char *TLV8::getName(uint8_t tag){ ///////////////////////////////////// -void TLV8::print(TLV8_it it1, TLV8_it it2){ +void TLV8::print(TLV8_itc it1, TLV8_itc it2) const { while(it1!=it2){ const char *name=getName(it1->getTag()); @@ -276,7 +276,7 @@ void TLV8::print(TLV8_it it1, TLV8_it it2){ ////////////////////////////////////// -void TLV8::printAll_r(String label){ +void TLV8::printAll_r(String label) const{ for(auto it=begin();it!=end();it++){ Serial.printf("%s",label.c_str()); @@ -290,7 +290,7 @@ void TLV8::printAll_r(String label){ ////////////////////////////////////// -void TLV8::osprint(std::ostream& os, TLV8_it it1, TLV8_it it2){ +void TLV8::osprint(std::ostream& os, TLV8_itc it1, TLV8_itc it2) const { for(auto it=it1;it!=it2;it++) (*it).osprint(os); diff --git a/src/TLV8.h b/src/TLV8.h index 50f73e1..a31f5ad 100644 --- a/src/TLV8.h +++ b/src/TLV8.h @@ -46,29 +46,29 @@ class tlv8_t { tlv8_t(uint8_t tag, size_t len, const uint8_t* val); void update(size_t addLen, const uint8_t *addVal); - void osprint(std::ostream& os); + void osprint(std::ostream& os) const; - operator uint8_t*() const{ + operator uint8_t*() const { return(val.get()); } - uint8_t & operator[](int index){ + uint8_t & operator[](int index) const { return(val.get()[index]); } - uint8_t *get(){ + uint8_t *get() const { return(val.get()); } - size_t getLen(){ + size_t getLen() const { return(len); } - uint8_t getTag(){ + uint8_t getTag() const { return(tag); } - template T getVal(){ + template T getVal() const { T iVal=0; for(int i=0;i(val.get()[i])<<(i*8); @@ -80,18 +80,19 @@ class tlv8_t { ///////////////////////////////////// typedef std::list>::iterator TLV8_it; +typedef std::list>::const_iterator TLV8_itc; typedef struct { const uint8_t tag; const char *name; } TLV8_names; ///////////////////////////////////// class TLV8 : public std::list> { - TLV8_it currentPackIt; - TLV8_it endPackIt; - uint8_t *currentPackBuf; - uint8_t *endPackBuf; - int currentPackPhase; - size_t currentPackLen; + TLV8_itc mutable currentPackIt; + TLV8_itc mutable endPackIt; + uint8_t mutable *currentPackBuf; + uint8_t mutable *endPackBuf; + int mutable currentPackPhase; + size_t mutable currentPackLen; uint8_t unpackTag; size_t unpackBytes; @@ -100,12 +101,10 @@ class TLV8 : public std::list> { const TLV8_names *names=NULL; int nNames=0; - void printAll_r(String label); + void printAll_r(String label) const; public: - static TLV8 NULL_TLV; - TLV8(){}; TLV8(const TLV8_names *names, int nNames) : names{names}, nNames{nNames} {}; @@ -115,36 +114,37 @@ class TLV8 : public std::list> { TLV8_it add(uint8_t tag){return(add(tag, 0, NULL));} TLV8_it add(uint8_t tag, const char *val){return(add(tag, strlen(val), reinterpret_cast(val)));} - TLV8_it find(uint8_t tag, TLV8_it it1, TLV8_it it2); - TLV8_it find(uint8_t tag, TLV8_it it1){return(find(tag, it1, end()));} - TLV8_it find(uint8_t tag){return(find(tag, begin(), end()));} + TLV8_itc find(uint8_t tag, TLV8_itc it1, TLV8_itc it2) const; + TLV8_itc find(uint8_t tag, TLV8_itc it1) const {return(find(tag, it1, end()));} + TLV8_itc find(uint8_t tag) const {return(find(tag, begin(), end()));} - int len(TLV8_it it){return(it==end()?-1:(*it).getLen());} + int len(TLV8_itc it) const {return(it==end()?-1:(*it).getLen());} - size_t pack_size(TLV8_it it1, TLV8_it it2); - size_t pack_size(){return(pack_size(begin(), end()));} + size_t pack_size(TLV8_itc it1, TLV8_itc it2) const; + size_t pack_size() const {return(pack_size(begin(), end()));} - void pack_init(TLV8_it it1, TLV8_it it2){currentPackIt=it1; endPackIt=it2; currentPackPhase=0;} - void pack_init(TLV8_it it1){pack_init(it1, std::next(it1));} - void pack_init(){pack_init(begin(),end());} + void pack_init(TLV8_itc it1, TLV8_itc it2) const {currentPackIt=it1; endPackIt=it2; currentPackPhase=0;} + void pack_init(TLV8_itc it1) const {pack_init(it1, std::next(it1));} + void pack_init() const {pack_init(begin(),end());} - size_t pack(uint8_t *buf, size_t bufSize); - size_t pack(uint8_t *buf){pack_init(); return(pack(buf,pack_size()));} + size_t pack(uint8_t *buf, size_t bufSize) const; + size_t pack(uint8_t *buf) const {pack_init(); return(pack(buf,pack_size()));} - const char *getName(uint8_t tag); + const char *getName(uint8_t tag) const; - void print(TLV8_it it1, TLV8_it it2); - void print(TLV8_it it1){print(it1, std::next(it1));} - void print(){print(begin(), end());} - void printAll(){printAll_r("");} + void print(TLV8_itc it1, TLV8_itc it2) const; + void print(TLV8_itc it1) const {print(it1, std::next(it1));} + void print() const {print(begin(), end());} + void printAll() const {printAll_r("");} - void osprint(std::ostream& os, TLV8_it it1, TLV8_it it2); - void osprint(std::ostream& os, TLV8_it it1){osprint(os, it1, std::next(it1));} - void osprint(std::ostream& os){osprint(os, begin(), end());} + void osprint(std::ostream& os, TLV8_itc it1, TLV8_itc it2) const; + void osprint(std::ostream& os, TLV8_itc it1) const {osprint(os, it1, std::next(it1));} + void osprint(std::ostream& os) const {osprint(os, begin(), end());} int unpack(uint8_t *buf, size_t bufSize); int unpack(TLV8_it it); void wipe(){std::list>().swap(*this);} - + + static TLV8 NULL_TLV; }; From 9b791e2ac349edeea02535ee5a022024524a734a Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Wed, 26 Jun 2024 21:00:52 -0500 Subject: [PATCH 134/154] Update Reference.md --- docs/Reference.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/docs/Reference.md b/docs/Reference.md index 4673c7c..94076b3 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -386,11 +386,15 @@ The following methods are supported: * note that Linked Services are only applicable for select HAP Services. See Apple's HAP-R2 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 a Faucet +* `vector getLinks(const char *serviceName=NULL)` + * template function that returns a vector of pointers to Services that were added using `addLink()` + * if template parameter, *T*, is left blank, the elements of the returned vector will be of type *SpanService \** + * if template parameter, *T*, is specified, the elements of the returned vector will be cast into type *T* + * if *serviceName* is specified, only those services matching *serviceName* will be included in the return vector + * *serviceName* must be one of HomeSpan's built-in Services (e.g. "Valve") + * if *serviceName* is left blank or set to NULL, all services will be included in the return vector + * this function is useful for creating loops that iterate over all linked Services + * example: from within a Faucet Service containing linked Valves defined in *MyValveService*, use `for(auto valve : getLinks()) { if(valve->active->getVal()) ... }` to check which Valves are active * `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. From 970f4d7ce8bda866cfd940cf9df81b7677dca336 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Wed, 26 Jun 2024 21:14:23 -0500 Subject: [PATCH 135/154] Update README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 6853dd5..eee7595 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,11 @@ HomeSpan is currently NOT compatible with version 3.X of the Arduino-ESP32 Board * a warning message is output on the Serial Monitor if `setVal()` is called to change the value of a Characteristic from within the `update()` method at the same time the Home App is sending an update request for that value * does not apply if `setVal()` is called from within `update()` to change the value of a Characteristic in response to a *write-response* request from the Home App +* **Converted the `getLinks()` SpanService method to a template function** + * allows user to automatically cast the elements of the returned vector into any specific Service type + * also adds an optional parameter to restrict the elements of the returned vector to match a specified HomeSpan Service + * see the [API Reference](docs/Reference.md) for details + * **Refactored client/slot management to save memory and prepare for future integration of Ethernet support** * fixed-array of Client/Socket connections replaced by dynamic linked-list * serial interface now only shows active client connections (rather than a fixed list of client slots) From d7dbb5fee5d9768fd75c3baf63bc6a6d0475494e Mon Sep 17 00:00:00 2001 From: Gregg Date: Wed, 26 Jun 2024 22:08:36 -0500 Subject: [PATCH 136/154] Update HomeSpan.h --- src/HomeSpan.h | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/HomeSpan.h b/src/HomeSpan.h index b363d42..9297f50 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -560,9 +560,7 @@ class SpanCharacteristic{ case FORMAT::FLOAT: u.FLOAT=(double)val; break; - case FORMAT::STRING: - case FORMAT::DATA: - case FORMAT::TLV_ENC: + default: break; } // switch } @@ -588,9 +586,7 @@ class SpanCharacteristic{ return((T) u.UINT64); case FORMAT::FLOAT: return((T) u.FLOAT); - case FORMAT::STRING: - case FORMAT::DATA: - case FORMAT::TLV_ENC: + default: break; } return((T)0); // included to prevent compiler warnings From b8338cad71acf119f9ca1e18c73d1f1c1cbf5d73 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Thu, 27 Jun 2024 06:59:32 -0500 Subject: [PATCH 137/154] Update TLV8.md --- docs/TLV8.md | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/docs/TLV8.md b/docs/TLV8.md index f750233..29f90d2 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -24,7 +24,7 @@ Fortunately, HomeSpan includes a dedicated TLV8 library (see below) that automat Creating an instance of HomeSpan's TLV8 **class** using the above constructor builds an empty TLV8 object into which you can add and process TLV8 records. TLV8 objects are instantiated as standard C++ linked-list containers derived from `std::list`, where *tlv8_t* is an opaque structure used to store individual TLV8 records.[^opaque] -Also, as shown below, many of the TLV8 methods utilize linked-list iterators. These are represented by the typedef *TLV8_it*.[^iterators] +Also, as shown below, many of the TLV8 methods utilize linked-list *constant* iterators. These are represented by the typedef *TLV8_itc*.[^iterators] [^opaque]:The *tlv8_t* structure is opaque because in general you will not have to create or interact directly with the structure or its data. Note that in addition to the above TLV8-specific methods, you can use any `std::list` method with a TLV8 object if desired. @@ -32,25 +32,25 @@ Also, as shown below, many of the TLV8 methods utilize linked-list iterators. T The method for adding a generic TLV8 record to a TLV8 object is as follows: -* `TLV8_it add(uint8_t tag, size_t len, const uint8_t *val)` +* `TLV8_itc add(uint8_t tag, size_t len, const uint8_t *val)` * where *tag* is the TAG identifier for the record to add and *val* is a pointer to a byte-array containing *len* elements * example: `TLV8 myTLV; uint8_t v[]={0x01, 0x05, 0xE3, 0x4C}; tlv.add(1, sizeof(v), v); * setting *val* to NULL reserves *len* bytes of space for the TLV8 record within the TLV8 object, but does not copy any data - * this method returns a TLV8 *iterator* to the resulting TLV8 record so you can reference the record at a later time if needed + * this method returns a TLV8 constant iterator to the resulting TLV8 record so you can reference the record at a later time if needed In addition to the above generic method suitable for any type of data, the following methods make it easier to add TLV8 records with specific, frequently-used types of data: -* `TLV8_it add(uint8_t tag, uintXX_t val)` +* `TLV8_itc add(uint8_t tag, uintXX_t val)` * adds a TLV8 record containing a single, unsigned numeric value, *val* (i.e. uint8_t, uint16_t, uint32_t, or uint64_t) -* `TLV8_it add(uint8_t tag, const char *val)` +* `TLV8_itc add(uint8_t tag, const char *val)` * adds a TLV8 record containing all the non-null bytes of a null-terminated character string, *val* -* `TLV8_it add(uint8_t tag, TLV8 &subTLV)` +* `TLV8_itc add(uint8_t tag, TLV8 &subTLV)` * adds a TLV8 record containing all the bytes of an entire TLV8 object, *subTLV* -* `TLV8_it add(uint8_t tag)` +* `TLV8_itc add(uint8_t tag)` * adds a zero-length TLV8 record containing nothing but a TAG identifer Note that if you *add* consecutive records with the same TAG identifier, the TLV8 library will concatenate their data and combine into a single record. For example, `myTLV.add(1,13); myTLV.add(1,300)` will be combined to produce a single 3-byte recording containing the data 0x0D2C01, where the first byte represents from the number 13 and the second two bytes represent the number 300. This may have been your desired outcome, but likely not what you wanted to happen. @@ -59,27 +59,27 @@ Instead, to create two distinct records with the same tag value, simply interpos The method for finding a TLV8 record within a TLV8 object that contains a specific TAG identifer is as follows: -* `TLV8_it find(uint8_t tag)` +* `TLV8_itc find(uint8_t tag)` * where *tag* is the TAG identifier you are seeking - * returns a TLV8 iterator to *first* record that matches; returns *end()* if no records match + * returns a TLV8 constant iterator to *first* record that matches; returns *end()* if no records match To restrict the search range to a limited set of records, add optional starting and ending iterators *it1* and *it2*: -* `TLV8_it find(uint8_t tag [, TLV8_it it1 [, TLV8_it it2]])` +* `TLV8_itc find(uint8_t tag [, TLV8_itc it1 [, TLV8_itc it2]])` - * returns a TLV8 iterator to the *first* record within the range of iterators from *it1* to *it2* that matches the specified *tag* + * returns a TLV8 constant iterator to the *first* record within the range of constant iterators from *it1* to *it2* that matches the specified *tag* * search range is inclusive of *it1* but exclusive of *it2* * returns *it2* if no records match * if *it2* is unspecified, default is *end()*; if *it1* is unspecified, default is *begin()* * note `myTLV.find(tag)` is equivalent to `myTLV.find(tag, myTLV.begin(), myTLV.end())` -Use of the C++ `auto` keyword is generally the best way to save the TVL8_it iterator that is returned from the `find()` and `add()` methods. For example, `auto myIT = myTLV.find(6)` sets *myIT* to an iterator pointing to the first TLV8 record in *myTLV* that has a TAG identifer of 6. +Use of the C++ `auto` keyword is generally the best way to save the TVL8_itc iterator that is returned from the `find()` and `add()` methods. For example, `auto myIT = myTLV.find(6)` sets *myIT* to a constant iterator pointing to the first TLV8 record in *myTLV* that has a TAG identifer of 6. The method for finding the LENGTH of the data VALUE stored in a particular TLV8 record is as follows: -* `int len(TLV8_it it)` - * where *it* is an iterator pointing to a specific TLV8 record +* `int len(TLV8_itc it)` + * where *it* is an constant iterator pointing to a specific TLV8 record * returns the length of the data VALUE stored in the associated record, which may be zero for a zero-LENGTH record * returns -1 if *it* points to the *end()* of the TLV8 object @@ -106,11 +106,11 @@ The method for printing all of the records in a TLV8 object to the Serial Monito * this decimal value is only displayed if LENGTH<=8 * if LENGTH=0, the word "null" is displayed instead -To restrict the the printing range to a limited set of records, add optional starting and ending iterators *it1* and *it2*: +To restrict the the printing range to a limited set of records, add optional starting and ending constant iterators *it1* and *it2*: -* `void print(TLV8_it it1 [, TLV8_it it2])` +* `void print(TLV8_itc it1 [, TLV8_itc it2])` - * prints all TLV8 records between iterators *it1* and *it2* + * prints all TLV8 records between constant iterators *it1* and *it2* * print range is inclusive of *it1* but exclusive of *it2* * if *it2* is unspecified, prints only the record pointed to by *it1* * note `myTLV.print()` is equivalent to `myTLV.print(myTLV.begin(), myTLV.end())` @@ -135,9 +135,9 @@ TLV8 objects manage all of their internal memory requirements, and free up all r * erases all TLV8 records and frees all associated memory * leaves an empty TLV8 object ready for re-use -## *TLV8_it()* +## *TLV8_itc()* -Objects of type *TLV8_it* are iterators that point to specific records in a TLV8 object (or to *end()*). TLV8 iterators are used to access, read from, and write to, the data elements in any given TLV8 record, and are thus a critical part of the TLV8 library. However, if you are using the TLV8 library correctly you should rarely, if ever, need to directly instantiate a *TLV8_it* using its constructor. Instead, simply use the C++ `auto` keyword as noted above. +Objects of type *TLV8_it* are constant iterators that point to specific records in a TLV8 object (or to *end()*). TLV8 iterators are used to access, read from, and write to, the data elements in any given TLV8 record, and are thus a critical part of the TLV8 library. However, if you are using the TLV8 library correctly you should rarely, if ever, need to directly instantiate a *TLV8_itc* using its constructor. Instead, simply use the C++ `auto` keyword as noted above. TLV8_it supports the following methods: From a223d3046ad525a5b86768fc793b060e9127cacf Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Thu, 27 Jun 2024 07:10:19 -0500 Subject: [PATCH 138/154] Update TLV8.md --- docs/TLV8.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/TLV8.md b/docs/TLV8.md index 29f90d2..925754c 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -137,9 +137,9 @@ TLV8 objects manage all of their internal memory requirements, and free up all r ## *TLV8_itc()* -Objects of type *TLV8_it* are constant iterators that point to specific records in a TLV8 object (or to *end()*). TLV8 iterators are used to access, read from, and write to, the data elements in any given TLV8 record, and are thus a critical part of the TLV8 library. However, if you are using the TLV8 library correctly you should rarely, if ever, need to directly instantiate a *TLV8_itc* using its constructor. Instead, simply use the C++ `auto` keyword as noted above. +Objects of type *TLV8_itc* are constant iterators that point to specific *tlv8_t* records in a TLV8 object (or to *end()*). TLV8 iterators are used to access, read from, and write to, the data elements in any given TLV8 record, and are thus a critical part of the TLV8 library. However, if you are using the TLV8 library correctly you should rarely, if ever, need to directly instantiate a *TLV8_itc* using its constructor. Instead, simply use the C++ `auto` keyword as noted above. -TLV8_it supports the following methods: +TLV8_itc iterators can be dereferenced to work with data in an individual TLV8 record using the follow methods: * `uint8_t getTag()` From 3ed18b3f797ba972027ad828aed753203f28e743 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 29 Jun 2024 10:03:07 -0500 Subject: [PATCH 139/154] Added constructors for TLV8 and DATA Characteristics Conformed all CUSTOM_CHAR macros to allow for these constructors --- src/Characteristics.h | 11 ----------- src/HomeSpan.cpp | 31 ++++++++++++++++++------------- src/HomeSpan.h | 35 +++++++++++++++++++++++++---------- src/Span.h | 37 ++++++++----------------------------- src/TLV8.cpp | 10 ++++------ src/TLV8.h | 17 +++++++---------- src/src.ino | 30 ++++++++++++++++++++++++++++++ 7 files changed, 92 insertions(+), 79 deletions(-) diff --git a/src/Characteristics.h b/src/Characteristics.h index 52d43aa..8500728 100644 --- a/src/Characteristics.h +++ b/src/Characteristics.h @@ -55,17 +55,6 @@ enum FORMAT { // HAP Table 6-5 /////////////////////////////// -typedef boolean BOOL_t; -typedef uint8_t UINT8_t; -typedef uint16_t UINT16_t; -typedef uint32_t UINT32_t; -typedef uint64_t UINT64_t; -typedef int32_t INT_t; -typedef double FLOAT_t; -typedef char * STRING_t; - -/////////////////////////////// - struct HapChar { const char *type; const char *hapName; diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index e7a2475..a0241be 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -1867,14 +1867,29 @@ void SpanCharacteristic::uvSet(UVal &dest, UVal &src){ /////////////////////////////// -void SpanCharacteristic::uvSet(UVal &u, const char *val){ +void SpanCharacteristic::uvSet(UVal &u, STRING_t val){ u.STRING = (char *)HS_REALLOC(u.STRING, strlen(val) + 1); strcpy(u.STRING, val); } /////////////////////////////// -void SpanCharacteristic::uvSet(UVal &u, const TLV8 &tlv){ +void SpanCharacteristic::uvSet(UVal &u, DATA_t data){ + + if(data.second>0){ + size_t olen; + mbedtls_base64_encode(NULL,0,&olen,NULL,data.second); // get length of string buffer needed (mbedtls includes the trailing null in this size) + value.STRING = (char *)HS_REALLOC(value.STRING,olen); // allocate sufficient size for storing value + mbedtls_base64_encode((uint8_t*)value.STRING,olen,&olen,data.first,data.second ); // encode data into string buf + } else { + value.STRING = (char *)HS_REALLOC(value.STRING,1); // allocate sufficient size for just trailing null character + *value.STRING ='\0'; + } +} + +/////////////////////////////// + +void SpanCharacteristic::uvSet(UVal &u, TLV_ENC_t tlv){ const size_t bufSize=36; // maximum size of buffer to store packed TLV bytes before encoding directly into value; must be multiple of 3 size_t nBytes=tlv.pack_size(); // total size of packed TLV in bytes @@ -1941,17 +1956,7 @@ size_t SpanCharacteristic::getDataGeneric(uint8_t *data, size_t len, UVal &val){ void SpanCharacteristic::setData(uint8_t *data, size_t len, boolean notify){ setValCheck(); - - if(len>0){ - size_t olen; - mbedtls_base64_encode(NULL,0,&olen,NULL,len); // get length of string buffer needed (mbedtls includes the trailing null in this size) - value.STRING = (char *)HS_REALLOC(value.STRING,olen); // allocate sufficient size for storing value - mbedtls_base64_encode((uint8_t*)value.STRING,olen,&olen,data,len ); // encode data into string buf - } else { - value.STRING = (char *)HS_REALLOC(value.STRING,1); // allocate sufficient size for just trailing null character - *value.STRING ='\0'; - } - + uvSet(value,{data,len}); setValFinish(notify); } diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 9297f50..d173ea7 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -71,6 +71,20 @@ enum { GET_STATUS=256 }; +typedef boolean BOOL_t; +typedef uint8_t UINT8_t; +typedef uint16_t UINT16_t; +typedef uint32_t UINT32_t; +typedef uint64_t UINT64_t; +typedef int32_t INT_t; +typedef double FLOAT_t; +typedef const char * STRING_t; +typedef const TLV8 & TLV_ENC_t; +typedef std::pair DATA_t; + +static DATA_t NULL_DATA={NULL,0}; +static TLV8 NULL_TLV{}; + /////////////////////////////// #define STATUS_UPDATE(LED_UPDATE,MESSAGE_UPDATE) {homeSpan.statusLED->LED_UPDATE;if(homeSpan.statusCallback)homeSpan.statusCallback(MESSAGE_UPDATE);} @@ -485,14 +499,14 @@ class SpanCharacteristic{ friend class SpanService; union UVal { - BOOL_t BOOL; - UINT8_t UINT8; - UINT16_t UINT16; - UINT32_t UINT32; - UINT64_t UINT64; - INT_t INT; - FLOAT_t FLOAT; - STRING_t STRING = NULL; + boolean BOOL; + uint8_t UINT8; + uint16_t UINT16; + uint32_t UINT32; + uint64_t UINT64; + int32_t INT; + double FLOAT; + char * STRING = NULL; }; class EVLIST : public vector>{ // vector of current connections that have subscribed to EV notifications for this Characteristic @@ -534,8 +548,9 @@ class SpanCharacteristic{ String uvPrint(UVal &u); // returns "printable" String for any type of Characteristic void uvSet(UVal &dest, UVal &src); // copies UVal src into UVal dest - void uvSet(UVal &u, const char *val); // copies string val into UVal u - void uvSet(UVal &u, const TLV8 &tlv); // copies TLV8 val into UVal u (after transforming to a char *) + void uvSet(UVal &u, STRING_t val); // copies string val into UVal u + void uvSet(UVal &u, DATA_t data); // copies DATA data into UVal u (after transforming to a char *) + void uvSet(UVal &u, TLV_ENC_t tlv); // copies TLV8 tlv into UVal u (after transforming to a char *) template void uvSet(UVal &u, T val){ // copies numeric val into UVal u switch(format){ diff --git a/src/Span.h b/src/Span.h index e43a88a..d255d48 100644 --- a/src/Span.h +++ b/src/Span.h @@ -513,7 +513,7 @@ namespace Characteristic { CREATE_CHAR(double,CurrentRelativeHumidity,0,0,100); // current humidity measured as a percentage CREATE_CHAR(double,CurrentTemperature,0,0,100); // current temperature measured in Celsius CREATE_CHAR(int,CurrentTiltAngle,0,-90,90); // current angle (in degrees) of slats from fully up or left (-90) to fully open (0) to fully down or right (90) - CREATE_CHAR(const TLV8 &,DisplayOrder,TLV8::NULL_TLV,TLV8::NULL_TLV,TLV8::NULL_TLV); // specifies the order in which the TV inputs are displayed for selection in the Home App + CREATE_CHAR(const TLV8 &,DisplayOrder,NULL_TLV,NULL_TLV,NULL_TLV); // specifies the order in which the TV inputs are displayed for selection in the Home App CREATE_CHAR(double,FilterLifeLevel,100,0,100); // measured as a percentage of remaining life CREATE_CHAR(uint8_t,FilterChangeIndication,0,0,1,NO_CHANGE_NEEDED,CHANGE_NEEDED); // indicates state of filter CREATE_CHAR(const char *,FirmwareRevision,"1.0.0",NULL,NULL); // must be in form x[.y[.z]] - informational only @@ -607,46 +607,25 @@ namespace Characteristic { #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,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,true} { init(val,nvsStore); } }; } - -#define CUSTOM_CHAR_DATA(NAME,UUID,PERMISISONS) \ - HapChar _CUSTOM_##NAME {#UUID,#NAME,(PERMS)(PERMISISONS),DATA,true}; \ - namespace Characteristic { struct NAME : SpanCharacteristic { NAME(const char * val="", boolean nvsStore=false) : SpanCharacteristic {&_CUSTOM_##NAME,true} { init(val,nvsStore); } }; } - -#define CUSTOM_CHAR_TLV8(NAME,UUID,PERMISISONS) \ - HapChar _CUSTOM_##NAME {#UUID,#NAME,(PERMS)(PERMISISONS),TLV_ENC,true}; \ - namespace Characteristic { struct NAME : SpanCharacteristic { NAME(const char * val="", boolean nvsStore=false) : SpanCharacteristic {&_CUSTOM_##NAME,true} { init(val,nvsStore); } }; } + namespace Characteristic { struct NAME : SpanCharacteristic { NAME(FORMAT##_t val=DEFVAL, boolean nvsStore=false) : SpanCharacteristic {&_CUSTOM_##NAME,true} { init(val,nvsStore,MINVAL,MAXVAL); } }; } #else #define CUSTOM_CHAR(NAME,UUID,PERMISISONS,FORMAT,DEFVAL,MINVAL,MAXVAL,STATIC_RANGE) \ extern HapChar _CUSTOM_##NAME; \ - 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); } }; } + namespace Characteristic { struct NAME : SpanCharacteristic { NAME(FORMAT##_t val=DEFVAL, boolean nvsStore=false) : SpanCharacteristic {&_CUSTOM_##NAME,true} { init(val,nvsStore,MINVAL,MAXVAL); } }; } -#define CUSTOM_CHAR_STRING(NAME,UUID,PERMISISONS,DEFVAL) \ - extern HapChar _CUSTOM_##NAME; \ - namespace Characteristic { struct NAME : SpanCharacteristic { NAME(const char * val=DEFVAL, boolean nvsStore=false) : SpanCharacteristic {&_CUSTOM_##NAME,true} { init(val,nvsStore); } }; } +#endif -#define CUSTOM_CHAR_DATA(NAME,UUID,PERMISISONS) \ - extern HapChar _CUSTOM_##NAME; \ - namespace Characteristic { struct NAME : SpanCharacteristic { NAME(const char * val="", boolean nvsStore=false) : SpanCharacteristic {&_CUSTOM_##NAME,true} { init(val,nvsStore); } }; } - -#define CUSTOM_CHAR_TLV8(NAME,UUID,PERMISISONS) \ - extern HapChar _CUSTOM_##NAME; \ - namespace Characteristic { struct NAME : SpanCharacteristic { NAME(const char * val="", boolean nvsStore=false) : SpanCharacteristic {&_CUSTOM_##NAME,true} { init(val,nvsStore); } }; } - -#endif +#define CUSTOM_CHAR_STRING(NAME,UUID,PERMISISONS,DEFVAL) CUSTOM_CHAR(NAME,UUID,PERMISISONS,STRING,DEFVAL,NULL,NULL,true); +#define CUSTOM_CHAR_TLV8(NAME,UUID,PERMISISONS) CUSTOM_CHAR(NAME,UUID,PERMISISONS,TLV_ENC,NULL_TLV,NULL_TLV,NULL_TLV,true); +#define CUSTOM_CHAR_DATA(NAME,UUID,PERMISISONS) CUSTOM_CHAR(NAME,UUID,PERMISISONS,DATA,NULL_DATA,NULL_DATA,NULL_DATA,true); #define CUSTOM_SERV(NAME,UUID) \ namespace Service { struct NAME : SpanService { NAME() : SpanService{#UUID,#NAME,true}{} }; } - //////////////////////////////////////////////////////// -// MACROS TO ADD A NEW ACCESSORT WITH OPTIONAL NAME // +// MACROS TO ADD A NEW ACCESSORY WITH OPTIONAL NAME // //////////////////////////////////////////////////////// #define SPAN_ACCESSORY(...) new SpanAccessory(); new Service::AccessoryInformation(); new Characteristic::Identify(); __VA_OPT__(new Characteristic::Name(__VA_ARGS__)); diff --git a/src/TLV8.cpp b/src/TLV8.cpp index 4b161da..e700946 100644 --- a/src/TLV8.cpp +++ b/src/TLV8.cpp @@ -68,7 +68,7 @@ void tlv8_t::osprint(std::ostream& os) const { ///////////////////////////////////// -TLV8_it TLV8::add(uint8_t tag, size_t len, const uint8_t* val){ +TLV8_itc TLV8::add(uint8_t tag, size_t len, const uint8_t* val) { if(!empty() && back().getTag()==tag) back().update(len,val); @@ -80,7 +80,7 @@ TLV8_it TLV8::add(uint8_t tag, size_t len, const uint8_t* val){ ///////////////////////////////////// -TLV8_it TLV8::add(uint8_t tag, TLV8 &subTLV){ +TLV8_itc TLV8::add(uint8_t tag, TLV8 &subTLV){ auto it=add(tag,subTLV.pack_size(),NULL); // create space for inserting sub TLV and store iterator to new element subTLV.pack(*it); // pack subTLV into new element @@ -89,7 +89,7 @@ TLV8_it TLV8::add(uint8_t tag, TLV8 &subTLV){ ///////////////////////////////////// -TLV8_it TLV8::add(uint8_t tag, uint64_t val){ +TLV8_itc TLV8::add(uint8_t tag, uint64_t val){ uint8_t *p=reinterpret_cast(&val); size_t nBytes=sizeof(uint64_t); @@ -227,7 +227,7 @@ int TLV8::unpack(uint8_t *buf, size_t bufSize){ ///////////////////////////////////// -int TLV8::unpack(TLV8_it it){ +int TLV8::unpack(TLV8_itc it){ if(it==end()) return(0); @@ -297,5 +297,3 @@ void TLV8::osprint(std::ostream& os, TLV8_itc it1, TLV8_itc it2) const { } ////////////////////////////////////// - -TLV8 TLV8::NULL_TLV; diff --git a/src/TLV8.h b/src/TLV8.h index a31f5ad..e51dc62 100644 --- a/src/TLV8.h +++ b/src/TLV8.h @@ -79,7 +79,6 @@ class tlv8_t { ///////////////////////////////////// -typedef std::list>::iterator TLV8_it; typedef std::list>::const_iterator TLV8_itc; typedef struct { const uint8_t tag; const char *name; } TLV8_names; @@ -108,11 +107,11 @@ class TLV8 : public std::list> { TLV8(){}; TLV8(const TLV8_names *names, int nNames) : names{names}, nNames{nNames} {}; - TLV8_it add(uint8_t tag, size_t len, const uint8_t *val); - TLV8_it add(uint8_t tag, uint64_t val); - TLV8_it add(uint8_t tag, TLV8 &subTLV); - TLV8_it add(uint8_t tag){return(add(tag, 0, NULL));} - TLV8_it add(uint8_t tag, const char *val){return(add(tag, strlen(val), reinterpret_cast(val)));} + TLV8_itc add(uint8_t tag, size_t len, const uint8_t *val); + TLV8_itc add(uint8_t tag, uint64_t val); + TLV8_itc add(uint8_t tag, TLV8 &subTLV); + TLV8_itc add(uint8_t tag){return(add(tag, 0, NULL));} + TLV8_itc add(uint8_t tag, const char *val){return(add(tag, strlen(val), reinterpret_cast(val)));} TLV8_itc find(uint8_t tag, TLV8_itc it1, TLV8_itc it2) const; TLV8_itc find(uint8_t tag, TLV8_itc it1) const {return(find(tag, it1, end()));} @@ -142,9 +141,7 @@ class TLV8 : public std::list> { void osprint(std::ostream& os) const {osprint(os, begin(), end());} int unpack(uint8_t *buf, size_t bufSize); - int unpack(TLV8_it it); + int unpack(TLV8_itc it); - void wipe(){std::list>().swap(*this);} - - static TLV8 NULL_TLV; + void wipe() {std::list>().swap(*this);} }; diff --git a/src/src.ino b/src/src.ino index 98d9733..16da73d 100644 --- a/src/src.ino +++ b/src/src.ino @@ -27,6 +27,12 @@ #include "HomeSpan.h" + +CUSTOM_CHAR(TestChar,3F4F,PR+PW,UINT8,20,0,100,false) +CUSTOM_CHAR_STRING(TestString,3F45,PR+EV,"Hello"); +CUSTOM_CHAR_TLV8(TestTLV,4F45,PW+PR); +CUSTOM_CHAR_DATA(TestData,303,PW+PW); + void setup() { Serial.begin(115200); @@ -43,7 +49,31 @@ void setup() { new Characteristic::Identify(); new Service::LightBulb(); new Characteristic::On(); + new Characteristic::TestChar(30); + new Characteristic::TestString(); + new Characteristic::TestString("MyName"); + new Characteristic::TestTLV(); + Characteristic::TestData *testData = new Characteristic::TestData(); + TLV8 myTLV; + + myTLV.add(5,0x20); + myTLV.add(5,0x30); + myTLV.add(1); + myTLV.add(5,255); + + Characteristic::TestTLV *testTLV = new Characteristic::TestTLV(myTLV); + + size_t n=testTLV->getData(NULL,0); + uint8_t buf[n]; + testTLV->getData(buf,n); + + Serial.printf("\n"); + for(int i=0;isetData(buf,0); } From d9af8032a466b9ec4bb539cb8ca3e6b973ab6666 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 29 Jun 2024 15:14:52 -0500 Subject: [PATCH 140/154] Update Reference.md --- docs/Reference.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/Reference.md b/docs/Reference.md index 94076b3..0fefc05 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -664,13 +664,15 @@ To create more than one user-defined command, simply create multiple instances o 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; the third form is used for DATA-based (i.e. byte-array) Characteristics; and the fourth form is used for TLV8-based (i.e. *structured* byte-array) 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. Use UTF-8 coded string for non-ASCII characters. -* *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 formats). Not applicable for the STRING or DATA Characteristic macros -* *defaultValue* - specifies the default value of the Characteristic if not defined during instantiation. Not applicable for the DATA Characteristic macro. -* *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 the STRING or DATA Characteristic macros -* *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 the STRING or DATA Characteristic macros -* *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 the STRING or DATA Characteristic macros +* *uuid* - the UUID of the Characteristic as defined by the manufacturer. Must be either: + * *exactly* 36 characters of the form XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX, where *X* represent a valid hexidecimal digit, or + * a single hexidecimal number of the form XXXXXXXX with *8 digits or less*, and no leading zeros +* *perms* - additive list of permissions. Valid values are PR, PW, EV, AA, TW, HD, and WR (e.g. PR+PW+EV) +* *format* - for numerical Characteristics, specifies the number format. Valid value are BOOL, UINT8, UINT16, UNIT32, UINT64, INT, and FLOAT. Not applicable for the STRING, DATA, or TLV8 Characteristic macros +* *defaultValue* - specifies the default value of the Characteristic when not defined during instantiation. Not applicable for the DATA or TLV7 Characteristic macros. +* *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 the STRING, DATA or TLV8 Characteristic macros +* *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 the STRING, DATA or TLV8 Characteristic macros +* *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 the STRING, DATA or TLV8 Characteristic macros 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: From a3891cd983074ef1444c7ea58354c0ccd7137dc7 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 29 Jun 2024 15:36:04 -0500 Subject: [PATCH 141/154] 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 0fefc05..e46f355 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -419,13 +419,18 @@ This is a **base class** from which all HomeSpan Characteristics are derived, an * instantiated Characteristics are added to the HomeSpan HAP Database and associated with the last Service instantiated * instantiating a Characteristic without first instantiating a Service throws an error during initialization -* the first argument optionally allows you to set the initial *value* of the Characteristic at startup. If *value* is not specified, HomeSpan will supply a reasonable default for the Characteristic -* 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 values set via a call to `setRange()` +* the first argument optionally allows you to set the initial *value* of the Characteristic at startup using the following formats: + * for NUMERIC Characteristics, *value* can be any integer or decimal numeric type, such as `boolean`, `int`, `uint64_t`, `double`, etc. HomeSpan will automatically cast *value* into a variable with the correct numerical precision for the Characteristic specified + * for STRING Characteristics, *value* must be either of the type `char *`, or a literal quote-enclosed UTF-8 string + * for TLV8 Characteristics, *value* must be of the type `TLV8` + * for DATA Characteristics, *value* must be a brace-enclosed *pair* of the form `{uint8_t *data, size_t len}`, where *len* specifies the length of the size of the byte-array *data* +* if *value* is not specified, HomeSpan will supply a reasonable default for the Characteristic +* for numerical Characteristics, 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 values set via a call to `setRange()` * the second optional argument, if set to `true`, instructs HomeSpan to save updates to this Characteristic's value in the device's non-volative storage (NVS) for restoration at startup if the device should lose power. If not specified, *nvsStore* will default to `false` (no storage) * examples: * `new Characteristic::Brightness();` Brightness initialized to default value * `new Characteristic::Brightness(50);` Brightness initialized to 50 - * `new Characteristic::Brightness(50,true);` Brightness initialized to 50; updates saved in NVS + * `new Characteristic::Brightness(50,true);` Brightness initialized to 50; updates to the value are saved to, and restored from, NVS #### The following methods are supported for numerical-based Characteristics (e.g. *int*, *float*...): From 2a91ed645a7e523af644b598f8a19aff0d440d7b Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 29 Jun 2024 15:47:03 -0500 Subject: [PATCH 142/154] updated ServiceList.md --- docs/ServiceList.md | 2 +- src/src.ino | 2 +- tools/makeServices | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/ServiceList.md b/docs/ServiceList.md index fb3b681..a1e2a3d 100644 --- a/docs/ServiceList.md +++ b/docs/ServiceList.md @@ -428,7 +428,7 @@ The pre-defined constant expressions for enumerated Characteristics are in names CharacteristicFormatPermsMinMaxConstants/Defaults Active (B0) :small_blue_diamond:
  • indicates if the Service is active/on
uint8PW+PR+EV01
  • INACTIVE (0) :heavy_check_mark:
  • ACTIVE (1) 
ActiveIdentifier (E7)
  • numerical Identifier of the InputSource selected in the Home App.
uint32PW+PR+EV02550 -DisplayOrder (136)
  • specifies the order in which the TV inputs are displayed for selection in the Home App
tlv8PR+EV01"" +DisplayOrder (136)
  • specifies the order in which the TV inputs are displayed for selection in the Home App
tlv8PR+EV--NULL_TLV RemoteKey (E1)
  • triggers an update when the corresponding key is pressed in the Remote Control widget on an iPhone
uint8PW415
  • UP (4) 
  • DOWN (5) 
  • LEFT (6) 
  • RIGHT (7) 
  • CENTER (8) 
  • BACK (9) 
  • PLAY_PAUSE (11) 
  • INFO (15) 
PowerModeSelection (DF)
  • when defined, creates a "View TV Settings" button in the Home App that triggers an update to this Characteristic when pressed
uint8PW00
  • VIEW_SETTINGS (0) 
ConfiguredName (E3)
  • default display name of this Service
stringPW+PR+EV--"unnamed" diff --git a/src/src.ino b/src/src.ino index 16da73d..ca921a9 100644 --- a/src/src.ino +++ b/src/src.ino @@ -30,7 +30,7 @@ CUSTOM_CHAR(TestChar,3F4F,PR+PW,UINT8,20,0,100,false) CUSTOM_CHAR_STRING(TestString,3F45,PR+EV,"Hello"); -CUSTOM_CHAR_TLV8(TestTLV,4F45,PW+PR); +CUSTOM_CHAR_TLV8(TestTLV,45674F457,PW+PR); CUSTOM_CHAR_DATA(TestData,303,PW+PW); void setup() { diff --git a/tools/makeServices b/tools/makeServices index 8bc6c5d..a69be28 100755 --- a/tools/makeServices +++ b/tools/makeServices @@ -84,7 +84,7 @@ END { printf("%s",format[char]) printf("%s",perms[char]) - if(format[char]!="string") + if(format[char]!="string" && format[char]!="tlv8" && format[char]!="data") printf("%s%s",min[char],max[char]) else printf("--") From 8f133585f96649303e6d12919fc1303357c7207c Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 30 Jun 2024 12:19:58 -0500 Subject: [PATCH 143/154] Update 22-TLV8_Characteristics.ino --- .../22-TLV8_Characteristics.ino | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/examples/22-TLV8_Characteristics/22-TLV8_Characteristics.ino b/examples/22-TLV8_Characteristics/22-TLV8_Characteristics.ino index 43b1073..af58ab2 100644 --- a/examples/22-TLV8_Characteristics/22-TLV8_Characteristics.ino +++ b/examples/22-TLV8_Characteristics/22-TLV8_Characteristics.ino @@ -66,16 +66,13 @@ struct HomeSpanTV : Service::Television { SpanCharacteristic *active = new Characteristic::Active(0); SpanCharacteristic *activeID = new Characteristic::ActiveIdentifier(10); - SpanCharacteristic *displayOrder = new Characteristic::DisplayOrder(); // <-- This is the new TLV8 Characteristic. Note the constructor has no argument + SpanCharacteristic *displayOrder; // Create a pointer to use for the new TLV8 DisplayOrder Characteristic, which will be instantiated below once we build the TLV8 record HomeSpanTV() : Service::Television() { - // Unlike the constructors for numerical and string-based Characteristics (such as ActiveIdentifier and ConfiguredName), - // we cannot set the initial value of TLV8 Characteristics during construction, but must instead first instantiate the - // Characteristic (as we did above), then build a TLV8 object with the information required by the TLV8 Characteristic, and - // then use setTLV() to load the completed TLV8 object into the Characteristic's value. - - // The (undocumented by Apple!) TLV8 specifications for the DisplayOrder Characteristic are as follows: + // Before we instantiate displayOrder, we need to build a TLV8 object with the information required + // by the DisplayOrder Characteristic. The (undocumented by Apple!) TLV8 specifications for the + // DisplayOrder Characteristic are as follows: // TAG NAME FORMAT DESCRIPTION // ---- ------------- ------ -------------------------------------------- @@ -108,9 +105,9 @@ struct HomeSpanTV : Service::Television { // in the following order: 10, 20, 50, 30, 40. These IDs must of course match the IDs you choose // for your input sources when you create them at the end of this sketch in setup() - // The final step is to load this TLV8 object into the DisplayOrder Characteristic + // Now we can instantiate displayOrder using the TLV8 object created above as its initial value - displayOrder->setTLV(orderTLV); // set the "value" of DisplayOrder to be the orderTLV object we just created + displayOrder = new Characteristic::DisplayOrder(orderTLV); // set the "value" of DisplayOrder to be the orderTLV object we just created // That's it - you've created your first TLV8 Characteristic! } From b6ed73aed1857c06201b1f80d1e236bae8781c57 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 30 Jun 2024 12:35:37 -0500 Subject: [PATCH 144/154] Use typedefs for CREATE_CHAR() to ensure consistency --- src/HomeSpan.cpp | 2 +- src/HomeSpan.h | 2 +- src/Span.h | 234 +++++++++++++++++++++++------------------------ src/src.ino | 2 +- 4 files changed, 120 insertions(+), 120 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index a0241be..3ed0421 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -1953,7 +1953,7 @@ size_t SpanCharacteristic::getDataGeneric(uint8_t *data, size_t len, UVal &val){ /////////////////////////////// -void SpanCharacteristic::setData(uint8_t *data, size_t len, boolean notify){ +void SpanCharacteristic::setData(const uint8_t *data, size_t len, boolean notify){ setValCheck(); uvSet(value,{data,len}); diff --git a/src/HomeSpan.h b/src/HomeSpan.h index d173ea7..f0376f1 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -668,7 +668,7 @@ class SpanCharacteristic{ size_t getNewTLV(TLV8 &tlv){return(getTLVGeneric(tlv,newValue));} // gets the newValue for tlv8-based Characteristics void setString(const char *val, boolean notify=true); // sets the value and newValue for string-based Characteristic - void setData(uint8_t *data, size_t len, boolean notify=true); // sets the value and newValue for data-based Characteristic + void setData(const uint8_t *data, size_t len, boolean notify=true); // sets the value and newValue for data-based Characteristic void setTLV(const TLV8 &tlv, boolean notify=true); // sets the value and newValue for tlv8-based Characteristic template void setVal(T val, boolean notify=true){ // sets the value and newValue for numeric-based Characteristics diff --git a/src/Span.h b/src/Span.h index d255d48..bf2892a 100644 --- a/src/Span.h +++ b/src/Span.h @@ -479,123 +479,123 @@ namespace Service { namespace Characteristic { - CREATE_CHAR(uint32_t,AccessoryFlags,1,1,1); // not applicable for HomeSpan - CREATE_CHAR(uint8_t,Active,0,0,1,INACTIVE,ACTIVE); // indicates if the Service is active/on - CREATE_CHAR(uint32_t,ActiveIdentifier,0,0,255); // numerical Identifier of the InputSource selected in the Home App. - CREATE_CHAR(uint8_t,AirQuality,0,0,5,UNKNOWN,EXCELLENT,GOOD,FAIR,INFERIOR,POOR); // a subjective description - CREATE_CHAR(uint8_t,BatteryLevel,100,0,100); // measured as a percentage - CREATE_CHAR(int,Brightness,0,0,100); // measured as a percentage - CREATE_CHAR(double,CarbonMonoxideLevel,0,0,100); // measured in parts per million (ppm) - CREATE_CHAR(double,CarbonMonoxidePeakLevel,0,0,100); // measured in parts per million (ppm) - CREATE_CHAR(uint8_t,CarbonMonoxideDetected,0,0,1,NORMAL,ABNORMAL); // indicates if abnormal level is detected - CREATE_CHAR(double,CarbonDioxideLevel,0,0,100000); // measured on parts per million (ppm) - CREATE_CHAR(double,CarbonDioxidePeakLevel,0,0,100000); // measured in parts per million (ppm) - CREATE_CHAR(uint8_t,CarbonDioxideDetected,0,0,1,NORMAL,ABNORMAL); // indicates if abnormal level is detected - CREATE_CHAR(uint8_t,ChargingState,0,0,2,NOT_CHARGING,CHARGING,NOT_CHARGEABLE); // indicates state of battery charging - CREATE_CHAR(uint8_t,ClosedCaptions,0,0,1); // unused by any Service - CREATE_CHAR(double,CoolingThresholdTemperature,10,10,35); // cooling turns on when temperature (in Celsius) rises above this threshold - CREATE_CHAR(uint32_t,ColorTemperature,200,140,500); // measured in inverse megaKelvin (= 1,000,000 / Kelvin) - CREATE_CHAR(uint8_t,ContactSensorState,1,0,1,DETECTED,NOT_DETECTED); // indictates if contact is detected (i.e. closed) - CREATE_CHAR(const char *,ConfiguredName,"unnamed",NULL,NULL); // default display name of this Service - CREATE_CHAR(double,CurrentAmbientLightLevel,1,0.0001,100000); // measured in Lux (lumens/m2 - CREATE_CHAR(int,CurrentHorizontalTiltAngle,0,-90,90); // current angle (in degrees) of slats from fully up (-90) to fully open (0) to fully down (90) - CREATE_CHAR(uint8_t,CurrentAirPurifierState,0,0,2,INACTIVE,IDLE,PURIFYING); // indicates current state of air purification - CREATE_CHAR(uint8_t,CurrentSlatState,0,0,2,FIXED,JAMMED,SWINGING); // indicates current state of slats - CREATE_CHAR(uint8_t,CurrentPosition,0,0,100); // current position (as a percentage) from fully closed (0) to full open (100) - CREATE_CHAR(int,CurrentVerticalTiltAngle,0,-90,90); // current angle (in degrees) of slats from fully left (-90) to fully open (0) to fully right (90) - CREATE_CHAR(uint8_t,CurrentVisibilityState,0,0,1,VISIBLE,NOT_VISIBLE); // current visibility of the Service, as selectable on the Settings Page of the Home App - CREATE_CHAR(uint8_t,CurrentHumidifierDehumidifierState,1,0,3,INACTIVE,IDLE,HUMIDIFYING,DEHUMIDIFYING); // indicates current state of humidifier/dehumidifer - CREATE_CHAR(uint8_t,CurrentDoorState,1,0,4,OPEN,CLOSED,OPENING,CLOSING,STOPPED); // indicates current state of a door - CREATE_CHAR(uint8_t,CurrentFanState,1,0,2,INACTIVE,IDLE,BLOWING); // indicates current state of a fan - CREATE_CHAR(uint8_t,CurrentHeatingCoolingState,0,0,2,IDLE,HEATING,COOLING); // indicates whether appliance is currently heating, cooling, or just idle - CREATE_CHAR(uint8_t,CurrentHeaterCoolerState,1,0,3,INACTIVE,IDLE,HEATING,COOLING); // indicates whether appliance is currently heating, cooling, idle, or off - CREATE_CHAR(uint8_t,CurrentMediaState,0,0,5); // not used - CREATE_CHAR(double,CurrentRelativeHumidity,0,0,100); // current humidity measured as a percentage - CREATE_CHAR(double,CurrentTemperature,0,0,100); // current temperature measured in Celsius - CREATE_CHAR(int,CurrentTiltAngle,0,-90,90); // current angle (in degrees) of slats from fully up or left (-90) to fully open (0) to fully down or right (90) - CREATE_CHAR(const TLV8 &,DisplayOrder,NULL_TLV,NULL_TLV,NULL_TLV); // specifies the order in which the TV inputs are displayed for selection in the Home App - CREATE_CHAR(double,FilterLifeLevel,100,0,100); // measured as a percentage of remaining life - CREATE_CHAR(uint8_t,FilterChangeIndication,0,0,1,NO_CHANGE_NEEDED,CHANGE_NEEDED); // indicates state of filter - CREATE_CHAR(const char *,FirmwareRevision,"1.0.0",NULL,NULL); // must be in form x[.y[.z]] - informational only - CREATE_CHAR(const char *,HardwareRevision,"1.0.0",NULL,NULL); // must be in form x[.y[.z]] - informational only - CREATE_CHAR(double,HeatingThresholdTemperature,16,0,25); // heating turns on when temperature (in Celsius) falls below this threshold - CREATE_CHAR(boolean,HoldPosition,false,0,1); // deprecated - CREATE_CHAR(double,Hue,0,0,360); // color (in degrees) from red (0) to green (120) to blue (240) and back to red (360) - CREATE_CHAR(boolean,Identify,1,1,1,RUN_ID=1); // triggers an update when HomeKit wants HomeSpan to run its identification routine for an Accessory - CREATE_CHAR(uint32_t,Identifier,0,0,255); // numerical Identifer of the InputSource. - CREATE_CHAR(uint8_t,InputDeviceType,0,0,6); // not used - CREATE_CHAR(uint8_t,InputSourceType,0,0,10); // not used - CREATE_CHAR(uint8_t,InUse,0,0,1,NOT_IN_USE,IN_USE); // if Service is set to active, this indictes whether it is currently in use - CREATE_CHAR(uint8_t,IsConfigured,0,0,1,NOT_CONFIGURED,CONFIGURED); // indicates if a predefined Service has been configured - CREATE_CHAR(uint8_t,LeakDetected,0,0,1,NOT_DETECTED,DETECTED); // indictates if a leak is detected - CREATE_CHAR(uint8_t,LockCurrentState,0,0,3,UNLOCKED,LOCKED,JAMMED,UNKNOWN); // indicates state of a lock - CREATE_CHAR(uint8_t,LockPhysicalControls,0,0,1,CONTROL_LOCK_DISABLED,CONTROL_LOCK_ENABLED); // indicates if local control lock is enabled - CREATE_CHAR(uint8_t,LockTargetState,0,0,1,UNLOCK,LOCK); // indicates desired state of lock - CREATE_CHAR(const char *,Manufacturer,"HomeSpan",NULL,NULL); // any string - informational only - CREATE_CHAR(const char *,Model,"HomeSpan-ESP32",NULL,NULL); // any string - informational only - CREATE_CHAR(boolean,MotionDetected,0,0,1,NOT_DETECTED,DETECTED); // indicates if motion is detected - CREATE_CHAR(boolean,Mute,0,0,1,OFF,ON); // not used - CREATE_CHAR(const char *,Name,"unnamed",NULL,NULL); // default display name of the Accessory - CREATE_CHAR(double,NitrogenDioxideDensity,0,0,1000); // measured in µg/m3 - CREATE_CHAR(boolean,ObstructionDetected,0,0,1,NOT_DETECTED,DETECTED); // indicates if obstruction is detected - CREATE_CHAR(double,PM25Density,0,0,1000); // 2.5-micron particulate density, measured in µg/m3 - CREATE_CHAR(uint8_t,OccupancyDetected,0,0,1,NOT_DETECTED,DETECTED); // indicates if occupanccy is detected - CREATE_CHAR(boolean,OutletInUse,0,0,1,NOT_IN_USE,IN_USE); // indicates if an appliance or light is plugged into the outlet, regardless of whether on or off - CREATE_CHAR(boolean,On,0,0,1,OFF,ON); // indicates if the Service is active/on - CREATE_CHAR(double,OzoneDensity,0,0,1000); // measured in µg/m3 - CREATE_CHAR(uint8_t,PictureMode,0,0,13); // not used - CREATE_CHAR(double,PM10Density,0,0,1000); // 10-micron particulate density, measured in µg/m3 - CREATE_CHAR(uint8_t,PositionState,2,0,2,GOING_TO_MINIMUM,GOING_TO_MAXIMUM,STOPPED); // deprecated - CREATE_CHAR(uint8_t,PowerModeSelection,0,0,0,VIEW_SETTINGS); // when defined, creates a "View TV Settings" button in the Home App that triggers an update to this Characteristic when pressed - CREATE_CHAR(uint8_t,ProgramMode,0,0,2,NONE,SCHEDULED,SCHEDULE_OVERRIDEN); // indicates if pre-scheduled program is running - CREATE_CHAR(uint8_t,ProgrammableSwitchEvent,0,0,2,SINGLE_PRESS,DOUBLE_PRESS,LONG_PRESS); // specifies type of button press - CREATE_CHAR(double,RelativeHumidityDehumidifierThreshold,50,0,100); // dehumidfier turns on when humidity rises above this threshold - CREATE_CHAR(double,RelativeHumidityHumidifierThreshold,50,0,100); // humidfier turns on when humidity falls below this threshold - CREATE_CHAR(uint32_t,RemainingDuration,60,0,3600); // duration (in seconds) remaining for Service to be active/on - CREATE_CHAR(uint8_t,RemoteKey,4,4,15,UP=4,DOWN,LEFT,RIGHT,CENTER,BACK,PLAY_PAUSE=11,INFO=15); // triggers an update when the corresponding key is pressed in the Remote Control widget on an iPhone - CREATE_CHAR(uint8_t,ResetFilterIndication,1,1,1,RESET_FILTER=1); // triggers an update when the user chooses to reset the FilterChangeIndication (only appears in Eve App, not Home App) - CREATE_CHAR(int,RotationDirection,0,0,1,CLOCKWISE,COUNTERCLOCKWISE); // indicates the rotation direction of a fan - CREATE_CHAR(double,RotationSpeed,0,0,100); // measured as a percentage - CREATE_CHAR(double,Saturation,0,0,100); // color saturation, measured as a percentage - CREATE_CHAR(uint8_t,SecuritySystemAlarmType,0,0,1,KNOWN,UNKNOWN); // indicates whether alarm was triggered for known reason - CREATE_CHAR(uint8_t,SecuritySystemCurrentState,3,0,4,ARMED_STAY,ARMED_AWAY,ARMED_NIGHT,DISARMED,ALARM_TRIGGERED); // indicates current state of the security system - CREATE_CHAR(uint8_t,SecuritySystemTargetState,3,0,3,ARM_STAY,ARM_AWAY,ARM_NIGHT,DISARM); // indicates desired state of the security system - CREATE_CHAR(const char *,SerialNumber,"HS-12345",NULL,NULL); // any string - informational only - CREATE_CHAR(uint8_t,ServiceLabelIndex,1,1,255); // numerical index used to distinguish multiple copies of the same Service within an Accessory - CREATE_CHAR(uint8_t,ServiceLabelNamespace,1,0,1,DOTS,NUMERALS); // indicates how un-named Services linked together with a ServiceLabel Service should be displayed in the Home App - CREATE_CHAR(uint8_t,SlatType,0,0,1,HORIZONTAL,VERTICAL); // indicates the direction of a slat or group of slats - CREATE_CHAR(uint8_t,SleepDiscoveryMode,0,0,1); // not used - CREATE_CHAR(uint8_t,SmokeDetected,0,0,1,NOT_DETECTED,DETECTED); // indicates if smoke is detected - CREATE_CHAR(boolean,StatusActive,1,0,1,NOT_FUNCTIONING,FUNCTIONING); // indicates whether the Service is properly functioning - CREATE_CHAR(uint8_t,StatusFault,0,0,1,NO_FAULT,FAULT); // indicates whether the Service has a fault (only appears in Eve App, not Home App) - CREATE_CHAR(uint8_t,StatusJammed,0,0,1,NOT_JAMMED,JAMMED); // indicates whether the Service has been "jammed" - CREATE_CHAR(uint8_t,StatusLowBattery,0,0,1,NOT_LOW_BATTERY,LOW_BATTERY); // indicates state of battery - CREATE_CHAR(uint8_t,StatusTampered,0,0,1,NOT_TAMPERED,TAMPERED); // indicates whether the Service has been tampered with - CREATE_CHAR(double,SulphurDioxideDensity,0,0,1000); // measured in µg/m3 - CREATE_CHAR(uint8_t,SwingMode,0,0,1,SWING_DISABLED,SWING_ENABLED); // indicates whether swing-mode is enabled - CREATE_CHAR(uint8_t,TargetAirPurifierState,1,0,1,MANUAL,AUTO); // indicates desired state of air purifier - CREATE_CHAR(uint8_t,TargetFanState,1,0,1,MANUAL,AUTO); // indicates desired state of fan - CREATE_CHAR(int,TargetTiltAngle,0,-90,90); // indicated desired angle (in degrees) of slats from fully up or left (-90) to fully open (0) to fully down or right (90) - CREATE_CHAR(uint8_t,TargetHeaterCoolerState,0,0,2,AUTO,HEAT,COOL); // indicates desired state of heater/cooler - CREATE_CHAR(uint32_t,SetDuration,60,0,3600); // specifies the duration (in seconds) for a Service to remain on once activated - CREATE_CHAR(int,TargetHorizontalTiltAngle,0,-90,90); // indicates desired angle (in degrees) of slats from fully up (-90) to fully open (0) to fully down (90) - CREATE_CHAR(uint8_t,TargetHumidifierDehumidifierState,0,0,2,AUTO,HUMIDIFY,DEHUMIDIFY); // indicates desired state of humidifier/dehumidifier - CREATE_CHAR(uint8_t,TargetPosition,0,0,100); // indicates target position (as a percentage) from fully closed (0) to full open (100) - CREATE_CHAR(uint8_t,TargetDoorState,1,0,1,OPEN,CLOSED); // indicates desired state of door - CREATE_CHAR(uint8_t,TargetHeatingCoolingState,0,0,3,OFF,HEAT,COOL,AUTO); // indicates desired state of appliance - CREATE_CHAR(uint8_t,TargetMediaState,0,0,2); // unused - CREATE_CHAR(double,TargetRelativeHumidity,0,0,100); // indicates desired humidity measured as a percentage - CREATE_CHAR(double,TargetTemperature,16,10,38); // indicates desired temperature measures in Celsius - CREATE_CHAR(uint8_t,TargetVisibilityState,0,0,1,VISIBLE,NOT_VISIBLE); // indicates desired visibility of the Service, as selectable on the Settings Page of the Home App - CREATE_CHAR(uint8_t,TemperatureDisplayUnits,0,0,1,CELSIUS,FAHRENHEIT); // indicates the desired units to display the temperature on the device itself (has no effect on Home App) - CREATE_CHAR(int,TargetVerticalTiltAngle,0,-90,90); // indicates desired angle (in degrees) of slats from fully left (-90) to fully open (0) to fully right (90) - CREATE_CHAR(uint8_t,ValveType,0,0,3,GENERIC,IRRIGATION,SHOWER_HEAD,FAUCET); // indicates the type of valve - CREATE_CHAR(const char *,Version,"1.0.0",NULL,NULL); // unused - CREATE_CHAR(double,VOCDensity,0,0,1000); // measured in µg/m3 - CREATE_CHAR(uint8_t,Volume,0,0,100); // unused - CREATE_CHAR(uint8_t,VolumeControlType,3,0,3,NONE,RELATIVE,RELATIVE_CURRENT,ABSOLUTE); // indicates the type of volume control - CREATE_CHAR(uint8_t,VolumeSelector,0,0,1,VOLUME_UP,VOLUME_DOWN); // triggered by presses to the iPhone's volume up/down buttons when TV is selected in the Remote Control widget - CREATE_CHAR(double,WaterLevel,0,0,100); // measured as a percentage + CREATE_CHAR(UINT32_t,AccessoryFlags,1,1,1); // not applicable for HomeSpan + CREATE_CHAR(UINT8_t,Active,0,0,1,INACTIVE,ACTIVE); // indicates if the Service is active/on + CREATE_CHAR(UINT32_t,ActiveIdentifier,0,0,255); // numerical Identifier of the InputSource selected in the Home App. + CREATE_CHAR(UINT8_t,AirQuality,0,0,5,UNKNOWN,EXCELLENT,GOOD,FAIR,INFERIOR,POOR); // a subjective description + CREATE_CHAR(UINT8_t,BatteryLevel,100,0,100); // measured as a percentage + CREATE_CHAR(INT_t,Brightness,0,0,100); // measured as a percentage + CREATE_CHAR(FLOAT_t,CarbonMonoxideLevel,0,0,100); // measured in parts per million (ppm) + CREATE_CHAR(FLOAT_t,CarbonMonoxidePeakLevel,0,0,100); // measured in parts per million (ppm) + CREATE_CHAR(UINT8_t,CarbonMonoxideDetected,0,0,1,NORMAL,ABNORMAL); // indicates if abnormal level is detected + CREATE_CHAR(FLOAT_t,CarbonDioxideLevel,0,0,100000); // measured on parts per million (ppm) + CREATE_CHAR(FLOAT_t,CarbonDioxidePeakLevel,0,0,100000); // measured in parts per million (ppm) + CREATE_CHAR(UINT8_t,CarbonDioxideDetected,0,0,1,NORMAL,ABNORMAL); // indicates if abnormal level is detected + CREATE_CHAR(UINT8_t,ChargingState,0,0,2,NOT_CHARGING,CHARGING,NOT_CHARGEABLE); // indicates state of battery charging + CREATE_CHAR(UINT8_t,ClosedCaptions,0,0,1); // unused by any Service + CREATE_CHAR(FLOAT_t,CoolingThresholdTemperature,10,10,35); // cooling turns on when temperature (in Celsius) rises above this threshold + CREATE_CHAR(UINT32_t,ColorTemperature,200,140,500); // measured in inverse megaKelvin (= 1,000,000 / Kelvin) + CREATE_CHAR(UINT8_t,ContactSensorState,1,0,1,DETECTED,NOT_DETECTED); // indictates if contact is detected (i.e. closed) + CREATE_CHAR(STRING_t,ConfiguredName,"unnamed",NULL,NULL); // default display name of this Service + CREATE_CHAR(FLOAT_t,CurrentAmbientLightLevel,1,0.0001,100000); // measured in Lux (lumens/m2 + CREATE_CHAR(INT_t,CurrentHorizontalTiltAngle,0,-90,90); // current angle (in degrees) of slats from fully up (-90) to fully open (0) to fully down (90) + CREATE_CHAR(UINT8_t,CurrentAirPurifierState,0,0,2,INACTIVE,IDLE,PURIFYING); // indicates current state of air purification + CREATE_CHAR(UINT8_t,CurrentSlatState,0,0,2,FIXED,JAMMED,SWINGING); // indicates current state of slats + CREATE_CHAR(UINT8_t,CurrentPosition,0,0,100); // current position (as a percentage) from fully closed (0) to full open (100) + CREATE_CHAR(INT_t,CurrentVerticalTiltAngle,0,-90,90); // current angle (in degrees) of slats from fully left (-90) to fully open (0) to fully right (90) + CREATE_CHAR(UINT8_t,CurrentVisibilityState,0,0,1,VISIBLE,NOT_VISIBLE); // current visibility of the Service, as selectable on the Settings Page of the Home App + CREATE_CHAR(UINT8_t,CurrentHumidifierDehumidifierState,1,0,3,INACTIVE,IDLE,HUMIDIFYING,DEHUMIDIFYING); // indicates current state of humidifier/dehumidifer + CREATE_CHAR(UINT8_t,CurrentDoorState,1,0,4,OPEN,CLOSED,OPENING,CLOSING,STOPPED); // indicates current state of a door + CREATE_CHAR(UINT8_t,CurrentFanState,1,0,2,INACTIVE,IDLE,BLOWING); // indicates current state of a fan + CREATE_CHAR(UINT8_t,CurrentHeatingCoolingState,0,0,2,IDLE,HEATING,COOLING); // indicates whether appliance is currently heating, cooling, or just idle + CREATE_CHAR(UINT8_t,CurrentHeaterCoolerState,1,0,3,INACTIVE,IDLE,HEATING,COOLING); // indicates whether appliance is currently heating, cooling, idle, or off + CREATE_CHAR(UINT8_t,CurrentMediaState,0,0,5); // not used + CREATE_CHAR(FLOAT_t,CurrentRelativeHumidity,0,0,100); // current humidity measured as a percentage + CREATE_CHAR(FLOAT_t,CurrentTemperature,0,0,100); // current temperature measured in Celsius + CREATE_CHAR(INT_t,CurrentTiltAngle,0,-90,90); // current angle (in degrees) of slats from fully up or left (-90) to fully open (0) to fully down or right (90) + CREATE_CHAR(TLV_ENC_t,DisplayOrder,NULL_TLV,NULL_TLV,NULL_TLV); // specifies the order in which the TV inputs are displayed for selection in the Home App + CREATE_CHAR(FLOAT_t,FilterLifeLevel,100,0,100); // measured as a percentage of remaining life + CREATE_CHAR(UINT8_t,FilterChangeIndication,0,0,1,NO_CHANGE_NEEDED,CHANGE_NEEDED); // indicates state of filter + CREATE_CHAR(STRING_t,FirmwareRevision,"1.0.0",NULL,NULL); // must be in form x[.y[.z]] - informational only + CREATE_CHAR(STRING_t,HardwareRevision,"1.0.0",NULL,NULL); // must be in form x[.y[.z]] - informational only + CREATE_CHAR(FLOAT_t,HeatingThresholdTemperature,16,0,25); // heating turns on when temperature (in Celsius) falls below this threshold + CREATE_CHAR(BOOL_t,HoldPosition,false,0,1); // deprecated + CREATE_CHAR(FLOAT_t,Hue,0,0,360); // color (in degrees) from red (0) to green (120) to blue (240) and back to red (360) + CREATE_CHAR(BOOL_t,Identify,1,1,1,RUN_ID=1); // triggers an update when HomeKit wants HomeSpan to run its identification routine for an Accessory + CREATE_CHAR(UINT32_t,Identifier,0,0,255); // numerical Identifer of the InputSource. + CREATE_CHAR(UINT8_t,InputDeviceType,0,0,6); // not used + CREATE_CHAR(UINT8_t,InputSourceType,0,0,10); // not used + CREATE_CHAR(UINT8_t,InUse,0,0,1,NOT_IN_USE,IN_USE); // if Service is set to active, this indictes whether it is currently in use + CREATE_CHAR(UINT8_t,IsConfigured,0,0,1,NOT_CONFIGURED,CONFIGURED); // indicates if a predefined Service has been configured + CREATE_CHAR(UINT8_t,LeakDetected,0,0,1,NOT_DETECTED,DETECTED); // indictates if a leak is detected + CREATE_CHAR(UINT8_t,LockCurrentState,0,0,3,UNLOCKED,LOCKED,JAMMED,UNKNOWN); // indicates state of a lock + CREATE_CHAR(UINT8_t,LockPhysicalControls,0,0,1,CONTROL_LOCK_DISABLED,CONTROL_LOCK_ENABLED); // indicates if local control lock is enabled + CREATE_CHAR(UINT8_t,LockTargetState,0,0,1,UNLOCK,LOCK); // indicates desired state of lock + CREATE_CHAR(STRING_t,Manufacturer,"HomeSpan",NULL,NULL); // any string - informational only + CREATE_CHAR(STRING_t,Model,"HomeSpan-ESP32",NULL,NULL); // any string - informational only + CREATE_CHAR(BOOL_t,MotionDetected,0,0,1,NOT_DETECTED,DETECTED); // indicates if motion is detected + CREATE_CHAR(BOOL_t,Mute,0,0,1,OFF,ON); // not used + CREATE_CHAR(STRING_t,Name,"unnamed",NULL,NULL); // default display name of the Accessory + CREATE_CHAR(FLOAT_t,NitrogenDioxideDensity,0,0,1000); // measured in µg/m3 + CREATE_CHAR(BOOL_t,ObstructionDetected,0,0,1,NOT_DETECTED,DETECTED); // indicates if obstruction is detected + CREATE_CHAR(FLOAT_t,PM25Density,0,0,1000); // 2.5-micron particulate density, measured in µg/m3 + CREATE_CHAR(UINT8_t,OccupancyDetected,0,0,1,NOT_DETECTED,DETECTED); // indicates if occupanccy is detected + CREATE_CHAR(BOOL_t,OutletInUse,0,0,1,NOT_IN_USE,IN_USE); // indicates if an appliance or light is plugged into the outlet, regardless of whether on or off + CREATE_CHAR(BOOL_t,On,0,0,1,OFF,ON); // indicates if the Service is active/on + CREATE_CHAR(FLOAT_t,OzoneDensity,0,0,1000); // measured in µg/m3 + CREATE_CHAR(UINT8_t,PictureMode,0,0,13); // not used + CREATE_CHAR(FLOAT_t,PM10Density,0,0,1000); // 10-micron particulate density, measured in µg/m3 + CREATE_CHAR(UINT8_t,PositionState,2,0,2,GOING_TO_MINIMUM,GOING_TO_MAXIMUM,STOPPED); // deprecated + CREATE_CHAR(UINT8_t,PowerModeSelection,0,0,0,VIEW_SETTINGS); // when defined, creates a "View TV Settings" button in the Home App that triggers an update to this Characteristic when pressed + CREATE_CHAR(UINT8_t,ProgramMode,0,0,2,NONE,SCHEDULED,SCHEDULE_OVERRIDEN); // indicates if pre-scheduled program is running + CREATE_CHAR(UINT8_t,ProgrammableSwitchEvent,0,0,2,SINGLE_PRESS,DOUBLE_PRESS,LONG_PRESS); // specifies type of button press + CREATE_CHAR(FLOAT_t,RelativeHumidityDehumidifierThreshold,50,0,100); // dehumidfier turns on when humidity rises above this threshold + CREATE_CHAR(FLOAT_t,RelativeHumidityHumidifierThreshold,50,0,100); // humidfier turns on when humidity falls below this threshold + CREATE_CHAR(UINT32_t,RemainingDuration,60,0,3600); // duration (in seconds) remaining for Service to be active/on + CREATE_CHAR(UINT8_t,RemoteKey,4,4,15,UP=4,DOWN,LEFT,RIGHT,CENTER,BACK,PLAY_PAUSE=11,INFO=15); // triggers an update when the corresponding key is pressed in the Remote Control widget on an iPhone + CREATE_CHAR(UINT8_t,ResetFilterIndication,1,1,1,RESET_FILTER=1); // triggers an update when the user chooses to reset the FilterChangeIndication (only appears in Eve App, not Home App) + CREATE_CHAR(INT_t,RotationDirection,0,0,1,CLOCKWISE,COUNTERCLOCKWISE); // indicates the rotation direction of a fan + CREATE_CHAR(FLOAT_t,RotationSpeed,0,0,100); // measured as a percentage + CREATE_CHAR(FLOAT_t,Saturation,0,0,100); // color saturation, measured as a percentage + CREATE_CHAR(UINT8_t,SecuritySystemAlarmType,0,0,1,KNOWN,UNKNOWN); // indicates whether alarm was triggered for known reason + CREATE_CHAR(UINT8_t,SecuritySystemCurrentState,3,0,4,ARMED_STAY,ARMED_AWAY,ARMED_NIGHT,DISARMED,ALARM_TRIGGERED); // indicates current state of the security system + CREATE_CHAR(UINT8_t,SecuritySystemTargetState,3,0,3,ARM_STAY,ARM_AWAY,ARM_NIGHT,DISARM); // indicates desired state of the security system + CREATE_CHAR(STRING_t,SerialNumber,"HS-12345",NULL,NULL); // any string - informational only + CREATE_CHAR(UINT8_t,ServiceLabelIndex,1,1,255); // numerical index used to distinguish multiple copies of the same Service within an Accessory + CREATE_CHAR(UINT8_t,ServiceLabelNamespace,1,0,1,DOTS,NUMERALS); // indicates how un-named Services linked together with a ServiceLabel Service should be displayed in the Home App + CREATE_CHAR(UINT8_t,SlatType,0,0,1,HORIZONTAL,VERTICAL); // indicates the direction of a slat or group of slats + CREATE_CHAR(UINT8_t,SleepDiscoveryMode,0,0,1); // not used + CREATE_CHAR(UINT8_t,SmokeDetected,0,0,1,NOT_DETECTED,DETECTED); // indicates if smoke is detected + CREATE_CHAR(BOOL_t,StatusActive,1,0,1,NOT_FUNCTIONING,FUNCTIONING); // indicates whether the Service is properly functioning + CREATE_CHAR(UINT8_t,StatusFault,0,0,1,NO_FAULT,FAULT); // indicates whether the Service has a fault (only appears in Eve App, not Home App) + CREATE_CHAR(UINT8_t,StatusJammed,0,0,1,NOT_JAMMED,JAMMED); // indicates whether the Service has been "jammed" + CREATE_CHAR(UINT8_t,StatusLowBattery,0,0,1,NOT_LOW_BATTERY,LOW_BATTERY); // indicates state of battery + CREATE_CHAR(UINT8_t,StatusTampered,0,0,1,NOT_TAMPERED,TAMPERED); // indicates whether the Service has been tampered with + CREATE_CHAR(FLOAT_t,SulphurDioxideDensity,0,0,1000); // measured in µg/m3 + CREATE_CHAR(UINT8_t,SwingMode,0,0,1,SWING_DISABLED,SWING_ENABLED); // indicates whether swing-mode is enabled + CREATE_CHAR(UINT8_t,TargetAirPurifierState,1,0,1,MANUAL,AUTO); // indicates desired state of air purifier + CREATE_CHAR(UINT8_t,TargetFanState,1,0,1,MANUAL,AUTO); // indicates desired state of fan + CREATE_CHAR(INT_t,TargetTiltAngle,0,-90,90); // indicated desired angle (in degrees) of slats from fully up or left (-90) to fully open (0) to fully down or right (90) + CREATE_CHAR(UINT8_t,TargetHeaterCoolerState,0,0,2,AUTO,HEAT,COOL); // indicates desired state of heater/cooler + CREATE_CHAR(UINT32_t,SetDuration,60,0,3600); // specifies the duration (in seconds) for a Service to remain on once activated + CREATE_CHAR(INT_t,TargetHorizontalTiltAngle,0,-90,90); // indicates desired angle (in degrees) of slats from fully up (-90) to fully open (0) to fully down (90) + CREATE_CHAR(UINT8_t,TargetHumidifierDehumidifierState,0,0,2,AUTO,HUMIDIFY,DEHUMIDIFY); // indicates desired state of humidifier/dehumidifier + CREATE_CHAR(UINT8_t,TargetPosition,0,0,100); // indicates target position (as a percentage) from fully closed (0) to full open (100) + CREATE_CHAR(UINT8_t,TargetDoorState,1,0,1,OPEN,CLOSED); // indicates desired state of door + CREATE_CHAR(UINT8_t,TargetHeatingCoolingState,0,0,3,OFF,HEAT,COOL,AUTO); // indicates desired state of appliance + CREATE_CHAR(UINT8_t,TargetMediaState,0,0,2); // unused + CREATE_CHAR(FLOAT_t,TargetRelativeHumidity,0,0,100); // indicates desired humidity measured as a percentage + CREATE_CHAR(FLOAT_t,TargetTemperature,16,10,38); // indicates desired temperature measures in Celsius + CREATE_CHAR(UINT8_t,TargetVisibilityState,0,0,1,VISIBLE,NOT_VISIBLE); // indicates desired visibility of the Service, as selectable on the Settings Page of the Home App + CREATE_CHAR(UINT8_t,TemperatureDisplayUnits,0,0,1,CELSIUS,FAHRENHEIT); // indicates the desired units to display the temperature on the device itself (has no effect on Home App) + CREATE_CHAR(INT_t,TargetVerticalTiltAngle,0,-90,90); // indicates desired angle (in degrees) of slats from fully left (-90) to fully open (0) to fully right (90) + CREATE_CHAR(UINT8_t,ValveType,0,0,3,GENERIC,IRRIGATION,SHOWER_HEAD,FAUCET); // indicates the type of valve + CREATE_CHAR(STRING_t,Version,"1.0.0",NULL,NULL); // unused + CREATE_CHAR(FLOAT_t,VOCDensity,0,0,1000); // measured in µg/m3 + CREATE_CHAR(UINT8_t,Volume,0,0,100); // unused + CREATE_CHAR(UINT8_t,VolumeControlType,3,0,3,NONE,RELATIVE,RELATIVE_CURRENT,ABSOLUTE); // indicates the type of volume control + CREATE_CHAR(UINT8_t,VolumeSelector,0,0,1,VOLUME_UP,VOLUME_DOWN); // triggered by presses to the iPhone's volume up/down buttons when TV is selected in the Remote Control widget + CREATE_CHAR(FLOAT_t,WaterLevel,0,0,100); // measured as a percentage } diff --git a/src/src.ino b/src/src.ino index ca921a9..83c11e3 100644 --- a/src/src.ino +++ b/src/src.ino @@ -73,7 +73,7 @@ void setup() { Serial.printf("%d %0X\n",i,buf[i]); Serial.printf("\n"); - testData->setData(buf,0); + testData->setData(buf,8); } From e522e709c95c985aef9349593de7608427a3bc96 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 30 Jun 2024 15:31:15 -0500 Subject: [PATCH 145/154] Update Reference.md --- docs/Reference.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/Reference.md b/docs/Reference.md index e46f355..d5ec8e0 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -514,6 +514,10 @@ This is a **base class** from which all HomeSpan Characteristics are derived, an * `void setTLV(TLV8 &tlv [,boolean notify])` * similar to `setVal()`, but exclusively used for TLV8 Characteristics * updates the Characteristic by packing the TLV8 structure *tlv* into a byte array and then encoding it in base-64 for storage as a string + +* `NULL_TLV` + * this is not a method, but rather a static HomeSpan constant defined as an empty TLV8 object. It may used a placeholder wherever an empty TLV8 object is needed, such as when you want to instantiate a TLV8 Characteristic with *nvsStore=true*, but you don't yet want set the TLV8 value + * example: `new Characteristic::DisplayOrder(NULL_TLV,true);` * see the [TLV8 Characteristics](TLV8.md) page for complete details on how to read/process/create TLV8 Objects using HomeSpan's TLV8 Library. From aad5d26c5b8588f2715531810c21aaccf7501b56 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 30 Jun 2024 16:00:03 -0500 Subject: [PATCH 146/154] Update TVServices.md --- docs/TVServices.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/docs/TVServices.md b/docs/TVServices.md index 34710a9..289910c 100644 --- a/docs/TVServices.md +++ b/docs/TVServices.md @@ -21,7 +21,7 @@ new Service::Television(); new Characteristic::Active(0); // set power to OFF at start-up new Characteristic::ConfiguredName("Sony TV"); // optional Characteristic to set name of TV ``` -More advanced control of a TV can enabled with two other optional Characteristics: +More advanced control of a TV can enabled with these *optional* Characteristics: * `Characteristic::RemoteKey()` - this write-only numerical Characteristic enables HomeSpan to read button presses from the Remote Control widget on an iPhone that can be found under the Control Center. This widget is normally used to control Apple TVs, but it seems any Television Accessory created per above can also be operated from the Remote Control widget. The layout of the widget (which cannot be modified) includes 4 arrows, a central select button, a play/pause button, a large "back" button, and an "info" button. When a "key" is pressed, the Home App sends an update to `Characteristic::RemoteKey()` that can be read by HomeSpan using the usual `update()` method. Values are as follows: @@ -38,6 +38,23 @@ More advanced control of a TV can enabled with two other optional Characteristic * `Characteristic::ActiveIdentifier()` - this numerical Characteristic is used to control the input source for the TV (e.g. HDMI-1, HDMI-2, Netflix, etc.). It is only used when input sources are defined and linked using `Service::InputSource()` (see below), in which case it is a *required* Characteristic +* `Characteristic::DisplayOrder()` - this TLV8 Characteristic is used to control the order in which linked Input Sources are displayed in the Home App + * absent specifying the order with this Characteristic, the Home App will display the Input Sources in a random order within the selection section (under the power button), and in numerical order on the settings page of the Accessory based on the numeric Identifier for each Input Source + * the format of the TLV8 object use by this Characteristic is a series of TLV records of TAG=1 and VALUE set to the Identifer of an Input Source, interleaved with an empty TLV record of TAG=0 used as a separator + * example, the following code snippet sets the display order for three input sources with Identifiers 10, 20, and 30 to be 20, 30, and then 10: + +```C++ +TLV8 orderTLV; // create an empty TLV8 object named "orderTLV" + +orderTLV.add(1,20); // TAG=1, VALUE=20 (the Identifier of the first Input Source to be displayed) +orderTLV.add(0); // TAG=0 (empty record used as a separator) +orderTLV.add(1,30); // TAG=1, VALUE=30 (the Identifier of the second Input Source to be displayed) +orderTLV.add(0); // TAG=0 (empty record used as a separator) +orderTLV.add(1,10); // TAG=1, VALUE=10 (the Identifier of the third Input Source to be displayed) + +new Characteristic::DisplayOrder(orderTLV); // instantiate the DisplayOrder Characteristic and set its value to the orderTLV object +``` + ### `Service::InputSource()` Use `Service::InputSource()` to create a new input source selection for the TV, such as HDMI-1, HDMI-2, Netflix, etc. The use of `Service::InputSource()` is optional - it is perfectly okay to create a Television Service without the ability to select different Input Sources. However, if used, each Input Source Service added should be defined in the *same* Accessory as the Television Service to which it applies, and ***must*** be linked to that Television Service using `addLink()`. The Home App behaves unexpectedly if it finds any Input Source Services that are not linked to a Television Service. From beee92f6eee3e9ce9e0100d8b26306a3a7bd7bde Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 30 Jun 2024 16:10:49 -0500 Subject: [PATCH 147/154] Update TVServices.md --- docs/TVServices.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/TVServices.md b/docs/TVServices.md index 289910c..f07365e 100644 --- a/docs/TVServices.md +++ b/docs/TVServices.md @@ -40,7 +40,7 @@ More advanced control of a TV can enabled with these *optional* Characteristics: * `Characteristic::DisplayOrder()` - this TLV8 Characteristic is used to control the order in which linked Input Sources are displayed in the Home App * absent specifying the order with this Characteristic, the Home App will display the Input Sources in a random order within the selection section (under the power button), and in numerical order on the settings page of the Accessory based on the numeric Identifier for each Input Source - * the format of the TLV8 object use by this Characteristic is a series of TLV records of TAG=1 and VALUE set to the Identifer of an Input Source, interleaved with an empty TLV record of TAG=0 used as a separator + * the format of the TLV8 object used by this Characteristic is a series of TLV8 "Identifier" records with TAG=1 and a VALUE set to the Identifer of a particular Input Source; the "Identifier" records should each be separated by an empty TLV8 record with TAG=0 * example, the following code snippet sets the display order for three input sources with Identifiers 10, 20, and 30 to be 20, 30, and then 10: ```C++ @@ -83,7 +83,7 @@ This Service allows you to change the volume of a television using the iPhone's ### Examples -Please see [*File → Examples → HomeSpan → Other Examples → Television*](../examples/Other%20Examples/Television) for a complete worked example demonstrating the effects of using different combinations of the above Characteristics. Also, don't forget to check out the [HomeSpan Projects](https://github.com/topics/homespan) page for some real-world examples of TV sketches and controllers. +Please see [*File → Examples → HomeSpan → Other Examples → Television*](../examples/Other%20Examples/Television) for a complete worked example demonstrating the effects of using different combinations of the above Characteristics. For details on how to use TLV8 records with the DisplayOrder Characteristic, see [Tutorial Example 22 - TLV8 Characteristics](../examples/22-TLV8_Characteristics). Also, don't forget to check out the [HomeSpan Projects](https://github.com/topics/homespan) page for some real-world examples of TV sketches and controllers. ### Credits From 40e8dcbec2207e023673c8a44346672f02676f8e Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 30 Jun 2024 16:16:45 -0500 Subject: [PATCH 148/154] Update TVServices.md --- docs/TVServices.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/TVServices.md b/docs/TVServices.md index f07365e..951b559 100644 --- a/docs/TVServices.md +++ b/docs/TVServices.md @@ -83,7 +83,10 @@ This Service allows you to change the volume of a television using the iPhone's ### Examples -Please see [*File → Examples → HomeSpan → Other Examples → Television*](../examples/Other%20Examples/Television) for a complete worked example demonstrating the effects of using different combinations of the above Characteristics. For details on how to use TLV8 records with the DisplayOrder Characteristic, see [Tutorial Example 22 - TLV8 Characteristics](../examples/22-TLV8_Characteristics). Also, don't forget to check out the [HomeSpan Projects](https://github.com/topics/homespan) page for some real-world examples of TV sketches and controllers. +* Please see [*File → Examples → HomeSpan → Other Examples → Television*](../examples/Other%20Examples/Television) for a complete worked example demonstrating the effects of using different combinations of the above Characteristics +* For details on how to use TLV8 records with the DisplayOrder Characteristic, see [Tutorial Example 22 - TLV8 Characteristics](../examples/22-TLV8_Characteristics) +* For more advanced use case, see the Television Example on the [HomeSpan Reference Sketches](https://github.com/HomeSpan/HomeSpanReferenceSketches) page +* Also, don't forget to check out the [HomeSpan Projects](https://github.com/topics/homespan) page for some real-world examples of TV sketches and controllers. ### Credits From 767a125a704af864068003bc6e53ded8c264cb13 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 30 Jun 2024 16:17:30 -0500 Subject: [PATCH 149/154] Update 22-TLV8_Characteristics.ino --- examples/22-TLV8_Characteristics/22-TLV8_Characteristics.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/22-TLV8_Characteristics/22-TLV8_Characteristics.ino b/examples/22-TLV8_Characteristics/22-TLV8_Characteristics.ino index af58ab2..62e8d1b 100644 --- a/examples/22-TLV8_Characteristics/22-TLV8_Characteristics.ino +++ b/examples/22-TLV8_Characteristics/22-TLV8_Characteristics.ino @@ -30,7 +30,7 @@ // HomeSpan: A HomeKit implementation for the ESP32 // // ------------------------------------------------ // // // -// Example 24: Demonstrates the use of the TLV8 Library // +// Example 22: Demonstrates the use of the TLV8 Library // // by implementing DisplayOrder, an optional // // TLV8 Characteristic used with the TV Service // // to set the order in which TV Inputs are // From e23247c39f20e05997e2fb73b25ad29aefe2c2dc Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 30 Jun 2024 17:15:00 -0500 Subject: [PATCH 150/154] Added NAN as allowed value for ServoPin::set() Sets duty-cycle to 0, which allows an analog servo to freely rotate. Use Servo::set() with an actual number in degrees to restore position. --- src/src/extras/PwmPin.cpp | 23 +++++++++------- src/src/extras/extras.ino | 56 +++++++++++++++------------------------ 2 files changed, 35 insertions(+), 44 deletions(-) diff --git a/src/src/extras/PwmPin.cpp b/src/src/extras/PwmPin.cpp index 2a286e6..703e142 100644 --- a/src/src/extras/PwmPin.cpp +++ b/src/src/extras/PwmPin.cpp @@ -251,16 +251,21 @@ void ServoPin::set(double degrees){ if(!channel) return; - double usec=(degrees-minDegrees)*microsPerDegree+minMicros; + if(!isnan(degrees)){ + double usec=(degrees-minDegrees)*microsPerDegree+minMicros; + + if(usecmaxMicros) + usec=maxMicros; + + usec*=timer->freq_hz/1e6*(pow(2,(int)timer->duty_resolution)-1); + + channel->duty=usec; + } else { + channel->duty=0; + } - if(usecmaxMicros) - usec=maxMicros; - - usec*=timer->freq_hz/1e6*(pow(2,(int)timer->duty_resolution)-1); - - channel->duty=usec; ledc_channel_config(channel); } diff --git a/src/src/extras/extras.ino b/src/src/extras/extras.ino index 637bf1e..0e6d435 100644 --- a/src/src/extras/extras.ino +++ b/src/src/extras/extras.ino @@ -25,54 +25,40 @@ * ********************************************************************************/ -#include "Pixel.h" +#include "PwmPin.h" -#define MAX_BRIGHTNESS 255 // maximum brightness when flashing RGB [0-255] - -#define PIXEL_PIN 26 // set this to whatever pin you are using - note pin cannot be "input only" -#define NPIXELS 8 // set to number of pixels in strand - -Pixel testPixel(PIXEL_PIN, PixelType::RGBW); +ServoPin servo(21,0,500,2200,-60,60); void setup() { Serial.begin(115200); delay(1000); - Serial.printf("\n\nPixel Test on pin %d with %d pixels\n\n",PIXEL_PIN,NPIXELS); -} + Serial.print("\n\nReady\n\n"); -////////////////////////////////////// + for(int count=0;count<3;count++){ + for(int i=-60;i<61;i++){ + servo.set(i); + delay(10); + } -void flashColor(boolean r, boolean g, boolean b, boolean w){ - - for(int i=0;i=0;i--){ - testPixel.set(Pixel::RGB(i*r,i*g,i*b,i*w),NPIXELS); - delay(4); + for(int i=60;i>-61;i--){ + servo.set(i); + delay(10); + } } + + delay(5000); + + servo.set(NAN); + + delay(10000); + + servo.set(0); + } ////////////////////////////////////// void loop(){ - - Serial.printf("Red..."); - flashColor(1,0,0,0); - - Serial.printf("Green..."); - flashColor(0,1,0,0); - - Serial.printf("Blue..."); - flashColor(0,0,1,0); - - Serial.printf("White..."); - flashColor(0,0,0,1); - - Serial.printf("Pausing.\n"); - delay(1000); } From 7494e0bb2251b32bd52af7199efe554c06267929 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 30 Jun 2024 20:48:30 -0500 Subject: [PATCH 151/154] Update PWM.md --- docs/PWM.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/PWM.md b/docs/PWM.md index ea6c7c2..27e21c7 100644 --- a/docs/PWM.md +++ b/docs/PWM.md @@ -71,7 +71,8 @@ The *minMicros* parameter must be less than the *maxMicros* parameter, but setti * `void set(double position)` - * sets the position of the Servo Motor to *position* (in degrees). In order to protect the Servo Motor, values of *position* less than *minDegrees* are automatically reset to *minDegrees*, and values greater than *maxDegrees* are automatically reset to *maxDegrees*. + * sets the position of the Servo Motor to *position* (in degrees). In order to protect the Servo Motor, values of *position* less than *minDegrees* are automatically reset to *minDegrees*, and values greater than *maxDegrees* are automatically reset to *maxDegrees* + * if *position* is specified as *NAN* (i.e. the C++ "not-a-number" constant), the duty-cycle is set to zero, which effectively stops the pulse generation --- for most analog servos this means the motor can be freely rotated. Calling `set()` once again with *position* equal to an actual number of degrees restarts the pulse train and sets the servo position accordingly * `int getPin()` From 875c53f8fd02a68c7b109769fa09a36f3bbf79d3 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 30 Jun 2024 20:54:29 -0500 Subject: [PATCH 152/154] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index eee7595..c4ecb5c 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,10 @@ HomeSpan is currently NOT compatible with version 3.X of the Arduino-ESP32 Board * allows user to automatically cast the elements of the returned vector into any specific Service type * also adds an optional parameter to restrict the elements of the returned vector to match a specified HomeSpan Service * see the [API Reference](docs/Reference.md) for details + +* **New ability to halt the pulse generation for a ServoPin** + * calling `set(NAN)` for a ServoPin halts the pulse generation, which (for most analog servos) allows the motor to be freely rotated + * calling `set(position)`, where *position* equal the desired number of degrees, restarts the pulse generation and sets the servo position accordingly * **Refactored client/slot management to save memory and prepare for future integration of Ethernet support** * fixed-array of Client/Socket connections replaced by dynamic linked-list From f3066ea9c6072b79146095e74c9c2dae821e7a6f Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Wed, 3 Jul 2024 16:52:19 -0500 Subject: [PATCH 153/154] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c4ecb5c..ef3fefd 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ HomeSpan is currently NOT compatible with version 3.X of the Arduino-ESP32 Board * Launch the WiFi Access Point * A standalone, detailed End-User Guide -## ❗Latest Update - HomeSpan 1.9.1 (MM/DD/YYY) +## ❗Latest Update - HomeSpan 1.9.1 (07/03/2024) * **HomeSpan now supports *Tag-Length-Value ("TLV8")* Characteristics!** From 144559f6d60331405fec8790b673db0f15bb00ce Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Wed, 3 Jul 2024 16:55:36 -0500 Subject: [PATCH 154/154] Update library.properties --- library.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library.properties b/library.properties index 31f678e..4316e91 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=HomeSpan -version=1.9.0 +version=1.9.1 author=Gregg maintainer=Gregg sentence=A robust and extremely easy-to-use HomeKit implementation for the Espressif ESP32 running on the Arduino IDE.