#include #include #include #include #include "Utils.h" #include "HAP.h" using namespace Utils; WiFiServer hapServer(80); // HTTP Server (i.e. this acccesory) running on usual port 80 (local-scoped variable to this file only) HAPClient hap[MAX_CONNECTIONS]; // 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) /////////////////////////////// // Span // /////////////////////////////// void Span::begin(Category catID, char *displayName, char *hostNameBase, char *modelName){ this->displayName=displayName; this->hostNameBase=hostNameBase; this->modelName=modelName; sprintf(this->category,"%d",catID); pinMode(resetPin,INPUT_PULLUP); delay(2000); Serial.print("\n************************************************************\n" "Welcome to HomeSpan!\n" "Apple HomeKit for the Espressif ESP-32 WROOM and Arduino IDE\n" "************************************************************\n\n" "** Please ensure serial monitor is set to transmit \n"); Serial.print("** Ground pin "); Serial.print(resetPin); Serial.print(" to delete all stored WiFi Network and HomeKit Pairing data (factory reset)\n\n"); Serial.print("HomeSpan Version: "); Serial.print(HOMESPAN_VERSION); Serial.print("\n"); Serial.print("ESP-IDF Version: "); Serial.print(esp_get_idf_version()); Serial.print("\n"); Serial.print("Sketch Compiled: "); Serial.print(__DATE__); Serial.print(" "); Serial.print(__TIME__); Serial.print("\n\n"); if(!digitalRead(resetPin)){ // factory reset pin is low nvs_flash_erase(); // erase NVS storage Serial.print("** FACTORY RESET PIN LOW! ALL STORED DATA ERASED **\n** PROGRAM HALTED **\n"); while(1){ digitalWrite(LED_BUILTIN,HIGH); delay(100); digitalWrite(LED_BUILTIN,LOW); delay(500); } } } // begin /////////////////////////////// void Span::poll() { if(!strlen(category)){ Serial.print("\n** FATAL ERROR: Cannot run homeSpan.poll() without an initial call to homeSpan.begin()!\n** PROGRAM HALTED **\n\n"); while(1); } else if(WiFi.status()!=WL_CONNECTED){ nvs_flash_init(); // initialize non-volatile-storage partition in flash HAPClient::init(); // read NVS and load HAP settings initWifi(); // initialize WiFi if(!HAPClient::nAdminControllers()){ Serial.print("DEVICE NOT YET PAIRED -- PLEASE PAIR WITH HOMEKIT APP\n\n"); statusLED.start(500,0.5,2,1000); } else { statusLED.on(); } Serial.print(displayName); Serial.print(" is READY!\n\n"); } char cBuf[8]="?"; if(Serial.available()){ readSerial(cBuf,1); processSerialCommand(cBuf); } WiFiClient newClient; if(newClient=hapServer.available()){ // found a new HTTP client int freeSlot=getFreeSlot(); // get next free slot if(freeSlot==-1){ // no available free slots freeSlot=randombytes_uniform(MAX_CONNECTIONS); 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 LOG2("=======================================\n"); LOG1("** Client #"); LOG1(freeSlot); LOG1(" Connected: ("); LOG1(millis()/1000); LOG1(" sec) "); LOG1(hap[freeSlot].client.remoteIP()); LOG1("\n"); 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;iresetTime){ resetPressed=2; statusLED.start(200,0.5,4,800); resetTime=millis()+6000; } break; case 2: if(digitalRead(resetPin)){ statusLED.off(); processSerialCommand("H"); } else if(millis()>resetTime){ statusLED.off(); processSerialCommand("F"); } break; } // switch } // poll /////////////////////////////// int Span::getFreeSlot(){ for(int i=0;i>> WiFi SSID ("); Serial.print(wifiData.ssid); Serial.print("): "); readSerial(wifiData.ssid,MAX_SSID); Serial.print(wifiData.ssid); Serial.print("\n>>> WiFi Password ("); Serial.print(wifiData.pwd); Serial.print("): "); readSerial(wifiData.pwd,MAX_PWD); Serial.print(mask(wifiData.pwd,2)); Serial.print("\n\n"); nvs_set_blob(wifiHandle,"WIFIDATA",&wifiData,sizeof(wifiData)); // update data nvs_commit(wifiHandle); // commit to NVS } int nTries=0; statusLED.start(1000); while(WiFi.status()!=WL_CONNECTED){ Serial.print("Connecting to: "); Serial.print(wifiData.ssid); Serial.print("... "); nTries++; if(WiFi.begin(wifiData.ssid,wifiData.pwd)!=WL_CONNECTED){ int delayTime=nTries%6?5000:60000; char buf[8]=""; Serial.print("Can't connect. Re-trying in "); Serial.print(delayTime/1000); Serial.print(" seconds (or type 'W ' to reset WiFi data)...\n"); long sTime=millis(); while(millis()-sTimeHomeSpan Configuration"); apClient.print("

HomeSpan_12_54_DD_E4_23_F5

"); apClient.print("

Welcome to HomeSpan! This page allows you to configure the above HomeSpan device to connect to your WiFi network, and to create a Setup Code for pairing this device to HomeKit.

"); apClient.print("

WiFi Network is a required field. If your network name is broadcast you can select it from the dropdown list. If you don't see your network name in the dropdown list you may type it into the text box.

"); apClient.print("

WiFi Password is required only if your network is password-protected.

"); apClient.print("

Every HomeKit device requires an 8-digit Setup Code. If this device is being configured for the first time, or if the device has been factory reset, you must create a new Setup Code. "); apClient.print("Otherwise, either leave this field blank to retain the current Setup Code, or type a new 8-digit Setup Code to replace the current one.

"); apClient.print("

Note that HomeSpan cannot display a previously saved Setup Code, so if you've forgotten what it is, this is the place to create a new one.

"); apClient.print("

The LED on this device should be double-blinking during this configuration.

"); apClient.print("

"); apClient.print(""); apClient.print(""); apClient.print(""); apClient.print(""); apClient.print(""); apClient.print(""); apClient.print(""); apClient.print("

"); apClient.print(""); apClient.print(""); apClient.print("

"); apClient.print(""); apClient.print(""); apClient.print("

"); apClient.print("This device already has a Setup Code. You may leave Setup Code blank to retain the current one, or type a new Setup Code to change."); apClient.print("This device does not yet have a Setup Code. You must create one above."); apClient.print("

"); apClient.print("
"); apClient.print(""); } // GET if(!strncmp(p,"POST /",6)){ apClient.println("HTTP/1.1 200 OK"); apClient.println("Content-type:text/html"); apClient.println(); apClient.println("SUCCESS"); } } // data available apClient.stop(); } } } /////////////////////////////// void Span::processSerialCommand(char *c){ switch(c[0]){ case 's': { Serial.print("\n*** HomeSpan Status ***\n\n"); Serial.print("IP Address: "); Serial.print(WiFi.localIP()); Serial.print("\n\n"); Serial.print("Accessory ID: "); HAPClient::charPrintRow(HAPClient::accessory.ID,17); Serial.print(" LTPK: "); HAPClient::hexPrintRow(HAPClient::accessory.LTPK,32); Serial.print("\n"); HAPClient::printControllers(); Serial.print("\n"); for(int i=0;i qBuf(sprintfAttributes(NULL)+1); sprintfAttributes(qBuf.buf); Serial.print("\n*** Attributes Database: size="); Serial.print(qBuf.len()-1); Serial.print(" configuration="); Serial.print(hapConfig.configNumber); Serial.print(" ***\n\n"); prettyPrint(qBuf.buf); Serial.print("\n*** End Database ***\n\n"); } break; case 'W': { nvs_handle wifiHandle; nvs_open("WIFI",NVS_READWRITE,&wifiHandle); // open WIFI data namespace in NVS nvs_erase_all(wifiHandle); nvs_commit(wifiHandle); Serial.print("\n** WIFI Network Data DELETED **\n** Restarting...\n\n"); delay(2000); ESP.restart(); } break; case 'H': { nvs_erase_all(HAPClient::nvsHandle); nvs_commit(HAPClient::nvsHandle); Serial.print("\n** HomeKit Pairing Data DELETED **\n** Restarting...\n\n"); delay(1000); ESP.restart(); } break; case 'F': { nvs_flash_erase(); Serial.print("\n** FACTORY RESET **\n** Restarting...\n\n"); delay(1000); ESP.restart(); } break; case 'i':{ Serial.print("\n*** HomeSpan Info ***\n\n"); char cBuf[128]; for(int i=0;iServices.size();j++){ SpanService *s=Accessories[i]->Services[j]; sprintf(cBuf,"Service aid=%2d iid=%2d Update: %3s Loop: %3s Button: %3s\n",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" ); Serial.print(cBuf); } } Serial.print("\n*** End Status ***\n"); } break; case '?': { Serial.print("\n*** HomeSpan Commands ***\n\n"); Serial.print(" s - print connection status\n"); Serial.print(" d - print attributes database\n"); Serial.print(" i - print detailed info about configuration\n"); Serial.print(" W - delete stored WiFi data and restart\n"); Serial.print(" H - delete stored HomeKit Pairing data and restart\n"); Serial.print(" F - delete all stored WiFi Network and HomeKit Pairing data and restart\n"); Serial.print(" ? - print this list of commands\n"); Serial.print("\n*** End Commands ***\n\n"); } break; default: Serial.print("** Unknown command: '"); Serial.print(c); Serial.print("' - type '?' for list of commands.\n"); break; } // switch } /////////////////////////////// int Span::sprintfAttributes(char *cBuf){ int nBytes=0; nBytes+=snprintf(cBuf,cBuf?64:0,"{\"accessories\":["); for(int i=0;isprintfAttributes(cBuf?(cBuf+nBytes):NULL); if(i+1Accessories.size()) // aid out of range return(NULL); aid--; // convert from aid to array index number for(int i=0;iServices.size();i++){ // loop over all Services in this Accessory for(int j=0;jServices[i]->Characteristics.size();j++){ // loop over all Characteristics in this Service if(iid == Accessories[aid]->Services[i]->Characteristics[j]->iid) // if matching iid return(Accessories[aid]->Services[i]->Characteristics[j]); // return pointer to Characteristic } } return(NULL); } /////////////////////////////// int Span::countCharacteristics(char *buf){ int nObj=0; const char tag[]="\"aid\""; while(buf=strstr(buf,tag)){ // count number of characteristic objects in PUT JSON request nObj++; buf+=strlen(tag); } return(nObj); } /////////////////////////////// int Span::updateCharacteristics(char *buf, SpanBuf *pObj){ int nObj=0; char *p1; int cFound=0; boolean twFail=false; while(char *t1=strtok_r(buf,"{",&p1)){ // parse 'buf' and extract objects into 'pObj' unless NULL buf=NULL; char *p2; int okay=0; while(char *t2=strtok_r(t1,"}[]:, \"\t\n\r",&p2)){ if(!cFound){ // first token found if(strcmp(t2,"characteristics")){ Serial.print("\n*** ERROR: Problems parsing JSON - initial \"characteristics\" tag not found\n\n"); return(0); } cFound=1; break; } t1=NULL; char *t3; if(!strcmp(t2,"aid") && (t3=strtok_r(t1,"}[]:, \"\t\n\r",&p2))){ pObj[nObj].aid=atoi(t3); okay|=1; } else if(!strcmp(t2,"iid") && (t3=strtok_r(t1,"}[]:, \"\t\n\r",&p2))){ pObj[nObj].iid=atoi(t3); okay|=2; } else if(!strcmp(t2,"value") && (t3=strtok_r(t1,"}[]:, \"\t\n\r",&p2))){ pObj[nObj].val=t3; okay|=4; } else if(!strcmp(t2,"ev") && (t3=strtok_r(t1,"}[]:, \"\t\n\r",&p2))){ pObj[nObj].ev=t3; okay|=8; } else if(!strcmp(t2,"pid") && (t3=strtok_r(t1,"}[]:, \"\t\n\r",&p2))){ uint64_t pid=strtoull(t3,NULL,0); if(!TimedWrites.count(pid)){ Serial.print("\n*** ERROR: Timed Write PID not found\n\n"); twFail=true; } else if(millis()>TimedWrites[pid]){ Serial.print("\n*** ERROR: Timed Write Expired\n\n"); twFail=true; } } else { Serial.print("\n*** ERROR: Problems parsing JSON characteristics object - unexpected property \""); Serial.print(t2); Serial.print("\"\n\n"); return(0); } } // 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 nObj++; // increment number of characteristic objects found } else { Serial.print("\n*** ERROR: Problems parsing JSON characteristics object - missing required properties\n\n"); return(0); } } } // parse objects snapTime=millis(); // timestamp for this series of updates, assigned to each characteristic in loadUpdate() for(int i=0;iloadUpdate(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) else pObj[i].status=StatusCode::UnknownResource; // if not found, set HAP error } } // first pass for(int i=0;iservice->update(); // update service and save returned statusCode 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->value =pObj[j].characteristic->newValue; // update characteristic value with new value LOG1(" (okay)\n"); } else { // if status not okay 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 } } } // object had TBD status } // loop over all objects return(1); } /////////////////////////////// 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; } } } } /////////////////////////////// int Span::sprintfNotify(SpanBuf *pObj, int nObj, char *cBuf, int conNum){ int nChars=0; boolean notifyFlag=false; nChars+=snprintf(cBuf,cBuf?64:0,"{\"characteristics\":["); for(int i=0;iev[conNum]){ // if notifications requested for this characteristic by specified connection number if(notifyFlag) // already printed at least one other characteristic nChars+=snprintf(cBuf?(cBuf+nChars):NULL,cBuf?64:0,","); // add preceeding comma before printing next characteristic nChars+=pObj[i].characteristic->sprintfAttributes(cBuf?(cBuf+nChars):NULL,GET_AID); // get JSON attributes for characteristic notifyFlag=true; } // notification requested } // characteristic updated } // loop over all objects nChars+=snprintf(cBuf?(cBuf+nChars):NULL,cBuf?64:0,"]}"); return(notifyFlag?nChars:0); // if notifyFlag is not set, return 0, else return number of characters printed to cBuf } /////////////////////////////// int Span::sprintfAttributes(SpanBuf *pObj, int nObj, char *cBuf){ int nChars=0; nChars+=snprintf(cBuf,cBuf?64:0,"{\"characteristics\":["); for(int i=0;iperms&SpanCharacteristic::PR){ // if permissions allow reading status[i]=StatusCode::OK; // always set status to OK (since no actual reading of device is needed) } else { Characteristics[i]=NULL; status[i]=StatusCode::WriteOnly; sFlag=true; // set flag indicating there was an error } } else { status[i]=StatusCode::UnknownResource; sFlag=true; // set flag indicating there was an error } } nChars+=snprintf(cBuf,cBuf?64:0,"{\"characteristics\":["); for(int i=0;isprintfAttributes(cBuf?(cBuf+nChars):NULL,flags); // get JSON attributes for characteristic else{ sscanf(ids[i],"%d.%d",&aid,&iid); // parse aid and iid nChars+=snprintf(cBuf?(cBuf+nChars):NULL,cBuf?64:0,"{\"iid\":%d,\"aid\":%d}",iid,aid); // else create JSON attributes based on requested aid/iid } if(sFlag){ // status flag is needed - overlay at end nChars--; nChars+=snprintf(cBuf?(cBuf+nChars):NULL,cBuf?64:0,",\"status\":%d}",status[i]); } if(i+1sprintfAttributes(cBuf?(cBuf+nBytes):NULL); if(i+1type=type; hidden=(mod==ServiceType::Hidden); primary=(mod==ServiceType::Primary); if(homeSpan.Accessories.empty()){ Serial.print("*** FATAL ERROR: Can't create new Service without a defined Accessory. Program halted!\n\n"); while(1); } homeSpan.Accessories.back()->Services.push_back(this); iid=++(homeSpan.Accessories.back()->iidCount); } /////////////////////////////// int SpanService::sprintfAttributes(char *cBuf){ int nBytes=0; nBytes+=snprintf(cBuf,cBuf?64:0,"{\"iid\":%d,\"type\":\"%s\",",iid,type); if(hidden) nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,"\"hidden\":true,"); if(primary) nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,"\"primary\":true,"); nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,"\"characteristics\":["); for(int i=0;isprintfAttributes(cBuf?(cBuf+nBytes):NULL,GET_META|GET_PERMS|GET_TYPE|GET_DESC); if(i+1type=type; this->perms=perms; if(homeSpan.Accessories.empty() || homeSpan.Accessories.back()->Services.empty()){ Serial.print("*** FATAL ERROR: Can't create new Characteristic without a defined Service. Program halted!\n\n"); while(1); } homeSpan.Accessories.back()->Services.back()->Characteristics.push_back(this); iid=++(homeSpan.Accessories.back()->iidCount); service=homeSpan.Accessories.back()->Services.back(); aid=homeSpan.Accessories.back()->aid; } /////////////////////////////// SpanCharacteristic::SpanCharacteristic(char *type, uint8_t perms, boolean value) : SpanCharacteristic(type, perms) { this->format=BOOL; this->value.BOOL=value; } /////////////////////////////// SpanCharacteristic::SpanCharacteristic(char *type, uint8_t perms, int32_t value) : SpanCharacteristic(type, perms) { this->format=INT; this->value.INT=value; } /////////////////////////////// SpanCharacteristic::SpanCharacteristic(char *type, uint8_t perms, uint8_t value) : SpanCharacteristic(type, perms) { this->format=UINT8; this->value.UINT8=value; } /////////////////////////////// SpanCharacteristic::SpanCharacteristic(char *type, uint8_t perms, uint16_t value) : SpanCharacteristic(type, perms) { this->format=UINT16; this->value.UINT16=value; } /////////////////////////////// SpanCharacteristic::SpanCharacteristic(char *type, uint8_t perms, uint32_t value) : SpanCharacteristic(type, perms) { this->format=UINT32; this->value.UINT32=value; } /////////////////////////////// SpanCharacteristic::SpanCharacteristic(char *type, uint8_t perms, uint64_t value) : SpanCharacteristic(type, perms) { this->format=UINT64; this->value.UINT64=value; } /////////////////////////////// SpanCharacteristic::SpanCharacteristic(char *type, uint8_t perms, double value) : SpanCharacteristic(type, perms) { this->format=FLOAT; this->value.FLOAT=value; } /////////////////////////////// SpanCharacteristic::SpanCharacteristic(char *type, uint8_t perms, const char* value) : SpanCharacteristic(type, perms) { this->format=STRING; this->value.STRING=value; } /////////////////////////////// int SpanCharacteristic::sprintfAttributes(char *cBuf, int flags){ int nBytes=0; const char permCodes[][7]={"pr","pw","ev","aa","tw","hd","wr"}; nBytes+=snprintf(cBuf,cBuf?64:0,"{\"iid\":%d",iid); if(flags&GET_TYPE) nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"type\":\"%s\"",type); switch(format){ case BOOL: if(perms&PR) nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"value\":%s",value.BOOL?"true":"false"); if(flags&GET_META) nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"format\":\"bool\""); break; case INT: if(perms&PR) nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"value\":%d",value.INT); if(flags&GET_META) nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"format\":\"int\""); break; case UINT8: if(perms&PR) nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"value\":%u",value.UINT8); if(flags&GET_META) nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"format\":\"uint8\""); break; case UINT16: if(perms&PR) nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"value\":%u",value.UINT16); if(flags&GET_META) nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"format\":\"uint16\""); break; case UINT32: if(perms&PR) nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"value\":%lu",value.UINT32); if(flags&GET_META) nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"format\":\"uint32\""); break; case UINT64: if(perms&PR) nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"value\":%llu",value.UINT64); if(flags&GET_META) nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"format\":\"uint64\""); break; case FLOAT: if(perms&PR) nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"value\":%lg",value.FLOAT); if(flags&GET_META) nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"format\":\"float\""); break; case STRING: if(perms&PR) nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"value\":\"%s\"",value.STRING); if(flags&GET_META) nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"format\":\"string\""); break; } // switch if(range && (flags&GET_META)){ nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?128:0,",\"minValue\":%d,\"maxValue\":%d,\"minStep\":%d",range->min,range->max,range->step); } if(desc && (flags&GET_DESC)){ nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?128:0,",\"description\":\"%s\"",desc); } if(flags&GET_PERMS){ nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"perms\":["); for(int i=0;i<7;i++){ if(perms&(1<=(1<<(i+1))) nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,","); } } nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,"]"); } if(flags&GET_AID) nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"aid\":%d",aid); if(flags&GET_EV) nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"ev\":%s",ev[HAPClient::conNum]?"true":"false"); nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,"}"); return(nBytes); } /////////////////////////////// StatusCode SpanCharacteristic::loadUpdate(char *val, char *ev){ if(ev){ // request for notification boolean evFlag; if(!strcmp(ev,"0") || !strcmp(ev,"false")) evFlag=false; else if(!strcmp(ev,"1") || !strcmp(ev,"true")) evFlag=true; else return(StatusCode::InvalidValue); 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"); this->ev[HAPClient::conNum]=evFlag; } if(!val) // no request to update value return(StatusCode::OK); if(!(perms&PW)) // cannot write to read only characteristic return(StatusCode::ReadOnly); switch(format){ case BOOL: if(!strcmp(val,"0") || !strcmp(val,"false")) newValue.BOOL=false; else if(!strcmp(val,"1") || !strcmp(val,"true")) newValue.BOOL=true; else return(StatusCode::InvalidValue); break; case INT: if(!sscanf(val,"%d",&newValue.INT)) return(StatusCode::InvalidValue); break; case UINT8: if(!sscanf(val,"%u",&newValue.UINT8)) return(StatusCode::InvalidValue); break; case UINT16: if(!sscanf(val,"%u",&newValue.UINT16)) return(StatusCode::InvalidValue); break; case UINT32: if(!sscanf(val,"%llu",&newValue.UINT32)) return(StatusCode::InvalidValue); break; case UINT64: if(!sscanf(val,"%llu",&newValue.UINT64)) return(StatusCode::InvalidValue); break; case FLOAT: if(!sscanf(val,"%lg",&newValue.FLOAT)) return(StatusCode::InvalidValue); break; } // switch isUpdated=true; updateTime=homeSpan.snapTime; return(StatusCode::TBD); } /////////////////////////////// void SpanCharacteristic::setVal(int val){ switch(format){ case BOOL: value.BOOL=(boolean)val; break; case INT: value.INT=(int)val; break; case UINT8: value.UINT8=(uint8_t)val; break; case UINT16: value.UINT16=(uint16_t)val; break; case UINT32: value.UINT32=(uint32_t)val; break; case UINT64: value.UINT64=(uint64_t)val; break; } updateTime=homeSpan.snapTime; SpanBuf sb; // create SpanBuf object sb.characteristic=this; // set characteristic sb.status=StatusCode::OK; // set status sb.val=""; // set dummy "val" so that sprintfNotify knows to consider this "update" homeSpan.Notifications.push_back(sb); // store SpanBuf in Notifications vector } /////////////////////////////// void SpanCharacteristic::setVal(double val){ value.FLOAT=(double)val; updateTime=homeSpan.snapTime; SpanBuf sb; // create SpanBuf object sb.characteristic=this; // set characteristic sb.status=StatusCode::OK; // set status sb.val=""; // set dummy "val" so that sprintfNotify knows to consider this "update" homeSpan.Notifications.push_back(sb); // store SpanBuf in Notifications vector } /////////////////////////////// int SpanCharacteristic::timeVal(){ return(homeSpan.snapTime-updateTime); } /////////////////////////////// // SpanRange // /////////////////////////////// SpanRange::SpanRange(int min, int max, int step){ this->min=min; this->max=max; this->step=step; if(homeSpan.Accessories.empty() || homeSpan.Accessories.back()->Services.empty() || homeSpan.Accessories.back()->Services.back()->Characteristics.empty() ){ Serial.print("*** FATAL ERROR: Can't create new Range without a defined Characteristic. Program halted!\n\n"); while(1); } homeSpan.Accessories.back()->Services.back()->Characteristics.back()->range=this; } /////////////////////////////// // SpanButton // /////////////////////////////// SpanButton::SpanButton(int pin, unsigned long longTime, unsigned long shortTime){ if(homeSpan.Accessories.empty() || homeSpan.Accessories.back()->Services.empty()){ Serial.print("*** FATAL ERROR: Can't create new PushButton without a defined Service. Program halted!\n\n"); while(1); } Serial.print("Configuring PushButton: Pin="); // initialization message Serial.print(pin); Serial.print("\n"); this->pin=pin; this->shortTime=shortTime; this->longTime=longTime; service=homeSpan.Accessories.back()->Services.back(); if((void(*)(int,boolean))(service->*(&SpanService::button))==(void(*)(int,boolean))(&SpanService::button)) Serial.print("*** WARNING: No button() method defined for this PushButton!\n\n"); homeSpan.PushButtons.push_back(this); pinMode(pin,INPUT_PULLUP); } /////////////////////////////// void SpanButton::check(){ if(!isTriggered && !digitalRead(pin)){ isTriggered=true; unsigned long cTime=millis(); shortAlarm=cTime+shortTime; longAlarm=cTime+longTime; } else if(isTriggered && digitalRead(pin)){ unsigned long cTime=millis(); if(cTime>longAlarm) service->button(pin, true); else if(cTime>shortAlarm) service->button(pin, false); isTriggered=false; } } ///////////////////////////////