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