commit
f09499359b
|
|
@ -85,7 +85,8 @@ 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
|
* users should switch to the new constructor to avoid potential compatibility issues with future versions of HomeSpan
|
||||||
* added new method `boolean isRGBW()`
|
* 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)
|
* 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 [Adressable RGB LEDs](docs/Pixels.md) page for details
|
||||||
|
|
||||||
* **New ability to read and set the IIDs of Services and Characteristics**
|
* **New ability to read and set the IIDs of Services and Characteristics**
|
||||||
|
|
||||||
|
|
@ -111,7 +112,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
|
* 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
|
* 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*
|
* 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
|
* 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
|
||||||
|
|
|
||||||
|
|
@ -428,6 +428,7 @@ The pre-defined constant expressions for enumerated Characteristics are in names
|
||||||
<tr><th>Characteristic</th><th>Format</th><th>Perms</th><th>Min</th><th>Max</th><th>Constants/Defaults</th></tr>
|
<tr><th>Characteristic</th><th>Format</th><th>Perms</th><th>Min</th><th>Max</th><th>Constants/Defaults</th></tr>
|
||||||
<tr><td><b>Active (B0) :small_blue_diamond:</b><ul><li> indicates if the Service is active/on</li></ul></td><td align="center">uint8</td><td align="center">PW+PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>INACTIVE (0) </span>:heavy_check_mark:</li><li><span>ACTIVE (1) </span></li></ul></td></tr>
|
<tr><td><b>Active (B0) :small_blue_diamond:</b><ul><li> indicates if the Service is active/on</li></ul></td><td align="center">uint8</td><td align="center">PW+PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>INACTIVE (0) </span>:heavy_check_mark:</li><li><span>ACTIVE (1) </span></li></ul></td></tr>
|
||||||
<tr><td><b>ActiveIdentifier (E7) </b><ul><li> numerical Identifier of the <b>InputSource</b> selected in the Home App.</li></ul></td><td align="center">uint32</td><td align="center">PW+PR+EV</td><td align="center">0</td><td align="center">255</td><td align="center">0</td></tr>
|
<tr><td><b>ActiveIdentifier (E7) </b><ul><li> numerical Identifier of the <b>InputSource</b> selected in the Home App.</li></ul></td><td align="center">uint32</td><td align="center">PW+PR+EV</td><td align="center">0</td><td align="center">255</td><td align="center">0</td></tr>
|
||||||
|
<tr><td><b>DisplayOrder (136) </b><ul><li> specifies the order in which the TV inputs are displayed for selection in the Home App</li></ul></td><td align="center">tlv8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td align="center">""</td></tr>
|
||||||
<tr><td><b>RemoteKey (E1) </b><ul><li> triggers an update when the corresponding key is pressed in the Remote Control widget on an iPhone </li></ul></td><td align="center">uint8</td><td align="center">PW</td><td align="center">4</td><td align="center">15</td><td><ul><li><span>UP (4) </span></li><li><span>DOWN (5) </span></li><li><span>LEFT (6) </span></li><li><span>RIGHT (7) </span></li><li><span>CENTER (8) </span></li><li><span>BACK (9) </span></li><li><span>PLAY_PAUSE (11) </span></li><li><span>INFO (15) </span></li></ul></td></tr>
|
<tr><td><b>RemoteKey (E1) </b><ul><li> triggers an update when the corresponding key is pressed in the Remote Control widget on an iPhone </li></ul></td><td align="center">uint8</td><td align="center">PW</td><td align="center">4</td><td align="center">15</td><td><ul><li><span>UP (4) </span></li><li><span>DOWN (5) </span></li><li><span>LEFT (6) </span></li><li><span>RIGHT (7) </span></li><li><span>CENTER (8) </span></li><li><span>BACK (9) </span></li><li><span>PLAY_PAUSE (11) </span></li><li><span>INFO (15) </span></li></ul></td></tr>
|
||||||
<tr><td><b>PowerModeSelection (DF) </b><ul><li> when defined, creates a "View TV Settings" button in the Home App that triggers an update to this Characteristic when pressed </li></ul></td><td align="center">uint8</td><td align="center">PW</td><td align="center">0</td><td align="center">0</td><td><ul><li><span>VIEW_SETTINGS (0) </span></li></ul></td></tr>
|
<tr><td><b>PowerModeSelection (DF) </b><ul><li> when defined, creates a "View TV Settings" button in the Home App that triggers an update to this Characteristic when pressed </li></ul></td><td align="center">uint8</td><td align="center">PW</td><td align="center">0</td><td align="center">0</td><td><ul><li><span>VIEW_SETTINGS (0) </span></li></ul></td></tr>
|
||||||
<tr><td><b>ConfiguredName (E3) </b><ul><li> default display name of this Service</li></ul></td><td align="center">string</td><td align="center">PW+PR+EV</td><td align="center">-</td><td align="center">-</td><td align="center">"unnamed"</td></tr>
|
<tr><td><b>ConfiguredName (E3) </b><ul><li> default display name of this Service</li></ul></td><td align="center">string</td><td align="center">PW+PR+EV</td><td align="center">-</td><td align="center">-</td><td align="center">"unnamed"</td></tr>
|
||||||
|
|
|
||||||
|
|
@ -139,6 +139,9 @@ An example of HomeKit's *undocumented* Television Service showing how different
|
||||||
### [Pixel](../examples/Other%20Examples/Pixel)
|
### [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
|
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)
|
### [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
|
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
|
||||||
|
|
||||||
|
|
|
||||||
90
src/HAP.cpp
90
src/HAP.cpp
|
|
@ -324,7 +324,7 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){
|
||||||
iosTLV.print();
|
iosTLV.print();
|
||||||
LOG2("------------ END TLVS! ------------\n");
|
LOG2("------------ END TLVS! ------------\n");
|
||||||
|
|
||||||
LOG1("In Pair Setup #%d (%s)...",conNum,client.remoteIP().toString().c_str());
|
LOG1("In Pair Setup #%d (%s)...",clientNumber,client.remoteIP().toString().c_str());
|
||||||
|
|
||||||
auto itState=iosTLV.find(kTLVType_State);
|
auto itState=iosTLV.find(kTLVType_State);
|
||||||
|
|
||||||
|
|
@ -344,7 +344,7 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){
|
||||||
return(0);
|
return(0);
|
||||||
};
|
};
|
||||||
|
|
||||||
LOG2("Found <M%d>. Expected <M%d>.\n",tlvState,pairStatus);
|
LOG1("Found <M%d>. Expected <M%d>.\n",tlvState,pairStatus);
|
||||||
|
|
||||||
if(tlvState!=pairStatus){ // error: Device is not yet paired, but out-of-sequence pair-setup STATE was received
|
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");
|
LOG0("\n*** ERROR: Out-of-Sequence Pair-Setup request!\n\n");
|
||||||
|
|
@ -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.
|
// 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.
|
// The iosDeviceX HKDF calculations are separate and will be performed further below with the SALT and INFO as specified in the HAP docs.
|
||||||
|
|
||||||
TempBuffer<uint8_t> sessionKey(crypto_box_PUBLICKEYBYTES); // temporary space - used only in this block
|
HKDF::create(temp.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");
|
LOG2("------- DECRYPTING SUB-TLVS -------\n");
|
||||||
|
|
||||||
|
|
@ -456,7 +455,7 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){
|
||||||
|
|
||||||
TempBuffer<uint8_t> decrypted(itEncryptedData->getLen()-crypto_aead_chacha20poly1305_IETF_ABYTES); // temporary storage for decrypted data
|
TempBuffer<uint8_t> 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");
|
LOG0("\n*** ERROR: Exchange-Request Authentication Failed\n\n");
|
||||||
responseTLV.add(kTLVType_Error,tagError_Authentication); // set Error=Authentication
|
responseTLV.add(kTLVType_Error,tagError_Authentication); // set Error=Authentication
|
||||||
tlvRespond(responseTLV); // send response to client
|
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
|
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");
|
LOG2("---------- END SUB-TLVS! ----------\n");
|
||||||
|
|
||||||
|
|
@ -575,7 +574,7 @@ int HAPClient::postPairVerifyURL(uint8_t *content, size_t len){
|
||||||
iosTLV.print();
|
iosTLV.print();
|
||||||
LOG2("------------ END TLVS! ------------\n");
|
LOG2("------------ END TLVS! ------------\n");
|
||||||
|
|
||||||
LOG1("In Pair Verify #%d (%s)...",conNum,client.remoteIP().toString().c_str());
|
LOG1("In Pair Verify #%d (%s)...",clientNumber,client.remoteIP().toString().c_str());
|
||||||
|
|
||||||
auto itState=iosTLV.find(kTLVType_State);
|
auto itState=iosTLV.find(kTLVType_State);
|
||||||
|
|
||||||
|
|
@ -595,7 +594,7 @@ int HAPClient::postPairVerifyURL(uint8_t *content, size_t len){
|
||||||
return(0);
|
return(0);
|
||||||
};
|
};
|
||||||
|
|
||||||
LOG2("Found <M%d>\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 <M%d>\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)
|
switch(tlvState){ // Pair-Verify STATE received -- process request! (HAP Section 5.7)
|
||||||
|
|
||||||
|
|
@ -611,16 +610,14 @@ int HAPClient::postPairVerifyURL(uint8_t *content, size_t len){
|
||||||
return(0);
|
return(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
publicCurveKey=(uint8_t *)HS_MALLOC(crypto_box_PUBLICKEYBYTES); // temporary space - will be deleted at end of verification process
|
|
||||||
TempBuffer<uint8_t> secretCurveKey(crypto_box_SECRETKEYBYTES); // temporary space - used only in this block
|
TempBuffer<uint8_t> 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(temp.iosCurveKey,*itPublicKey,crypto_box_PUBLICKEYBYTES); // save Controller's Curve25519 Public Key
|
||||||
memcpy(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
|
// concatenate Accessory's Curve25519 Public Key, Accessory's Pairing ID, and Controller's Curve25519 Public Key into accessoryInfo
|
||||||
|
|
||||||
TempBuffer<uint8_t> accessoryInfo(publicCurveKey,crypto_box_PUBLICKEYBYTES,accessory.ID,hap_accessory_IDBYTES,iosCurveKey,crypto_box_PUBLICKEYBYTES,NULL);
|
TempBuffer<uint8_t> 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
|
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
|
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<uint8_t> subPack(subTLV.pack_size()); // create sub-TLV by packing Identifier and Signature TLV records together
|
TempBuffer<uint8_t> subPack(subTLV.pack_size()); // create sub-TLV by packing Identifier and Signature TLV records together
|
||||||
subTLV.pack(subPack);
|
subTLV.pack(subPack);
|
||||||
|
|
||||||
sharedCurveKey=(uint8_t *)HS_MALLOC(crypto_box_PUBLICKEYBYTES); // temporary space - will be deleted at end of verification process
|
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
|
||||||
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(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
|
||||||
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
|
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"
|
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");
|
LOG2("---------- END SUB-TLVS! ----------\n");
|
||||||
|
|
||||||
responseTLV.add(kTLVType_State,pairState_M2); // set State=<M2>
|
responseTLV.add(kTLVType_State,pairState_M2); // set State=<M2>
|
||||||
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
|
tlvRespond(responseTLV); // send response to client
|
||||||
}
|
}
|
||||||
|
|
@ -670,7 +665,7 @@ int HAPClient::postPairVerifyURL(uint8_t *content, size_t len){
|
||||||
|
|
||||||
TempBuffer<uint8_t> decrypted((*itEncryptedData).getLen()-crypto_aead_chacha20poly1305_IETF_ABYTES); // temporary storage for decrypted data
|
TempBuffer<uint8_t> 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");
|
LOG0("\n*** ERROR: Verify Authentication Failed\n\n");
|
||||||
responseTLV.add(kTLVType_State,pairState_M4); // set State=<M4>
|
responseTLV.add(kTLVType_State,pairState_M4); // set State=<M4>
|
||||||
responseTLV.add(kTLVType_Error,tagError_Authentication); // set Error=Authentication
|
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
|
// 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<uint8_t> iosDeviceInfo(iosCurveKey,crypto_box_PUBLICKEYBYTES,tPair->ID,hap_controller_IDBYTES,publicCurveKey,crypto_box_PUBLICKEYBYTES,NULL);
|
TempBuffer<uint8_t> 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
|
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");
|
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
|
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(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,sharedCurveKey,32,"Control-Salt","Control-Write-Encryption-Key"); // create ControllerToAccessoryKey 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
|
a2cNonce.zero(); // reset Nonces for this session to zero
|
||||||
c2aNonce.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");
|
LOG2("\n*** SESSION VERIFICATION COMPLETE *** \n");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -766,7 +756,7 @@ int HAPClient::postPairingsURL(uint8_t *content, size_t len){
|
||||||
iosTLV.print();
|
iosTLV.print();
|
||||||
LOG2("------------ END TLVS! ------------\n");
|
LOG2("------------ END TLVS! ------------\n");
|
||||||
|
|
||||||
LOG1("In Post Pairings #%d (%s)...",conNum,client.remoteIP().toString().c_str());
|
LOG1("In Post Pairings #%d (%s)...",clientNumber,client.remoteIP().toString().c_str());
|
||||||
|
|
||||||
auto itState=iosTLV.find(kTLVType_State);
|
auto itState=iosTLV.find(kTLVType_State);
|
||||||
auto itMethod=iosTLV.find(kTLVType_Method);
|
auto itMethod=iosTLV.find(kTLVType_Method);
|
||||||
|
|
@ -892,7 +882,7 @@ int HAPClient::getAccessoriesURL(){
|
||||||
return(0);
|
return(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG1("In Get Accessories #%d (%s)...\n",conNum,client.remoteIP().toString().c_str());
|
LOG1("In Get Accessories #%d (%s)...\n",clientNumber,client.remoteIP().toString().c_str());
|
||||||
|
|
||||||
homeSpan.printfAttributes();
|
homeSpan.printfAttributes();
|
||||||
size_t nBytes=hapOut.getSize();
|
size_t nBytes=hapOut.getSize();
|
||||||
|
|
@ -920,7 +910,7 @@ int HAPClient::getCharacteristicsURL(char *urlBuf){
|
||||||
return(0);
|
return(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG1("In Get Characteristics #%d (%s)...\n",conNum,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 len=strlen(urlBuf); // determine number of IDs specified by counting commas in URL
|
||||||
int numIDs=1;
|
int numIDs=1;
|
||||||
|
|
@ -988,7 +978,7 @@ int HAPClient::putCharacteristicsURL(char *json){
|
||||||
return(0);
|
return(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG1("In Put Characteristics #%d (%s)...\n",conNum,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
|
int n=homeSpan.countCharacteristics(json); // count number of objects in JSON request
|
||||||
if(n==0) // if no objects found, return
|
if(n==0) // if no objects found, return
|
||||||
|
|
@ -1027,7 +1017,7 @@ int HAPClient::putCharacteristicsURL(char *json){
|
||||||
|
|
||||||
// Create and send Event Notifications if needed
|
// 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);
|
return(1);
|
||||||
}
|
}
|
||||||
|
|
@ -1041,7 +1031,7 @@ int HAPClient::putPrepareURL(char *json){
|
||||||
return(0);
|
return(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG1("In Put Prepare #%d (%s)...\n",conNum,client.remoteIP().toString().c_str());
|
LOG1("In Put Prepare #%d (%s)...\n",clientNumber,client.remoteIP().toString().c_str());
|
||||||
|
|
||||||
char ttlToken[]="\"ttl\":";
|
char ttlToken[]="\"ttl\":";
|
||||||
char pidToken[]="\"pid\":";
|
char pidToken[]="\"pid\":";
|
||||||
|
|
@ -1217,6 +1207,7 @@ void HAPClient::getStatusURL(HAPClient *hapClient, void (*callBack)(const char *
|
||||||
|
|
||||||
if(hapClient){
|
if(hapClient){
|
||||||
hapClient->client.stop();
|
hapClient->client.stop();
|
||||||
|
delay(1);
|
||||||
LOG2("------------ SENT! --------------\n");
|
LOG2("------------ SENT! --------------\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1246,27 +1237,26 @@ void HAPClient::checkTimedWrites(){
|
||||||
else
|
else
|
||||||
tw++;
|
tw++;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//////////////////////////////////////
|
//////////////////////////////////////
|
||||||
|
|
||||||
void HAPClient::eventNotify(SpanBuf *pObj, int nObj, int ignoreClient){
|
void HAPClient::eventNotify(SpanBuf *pObj, int nObj, HAPClient *ignore){
|
||||||
|
|
||||||
for(int cNum=0;cNum<homeSpan.maxConnections;cNum++){ // loop over all connection slots
|
for(auto it=homeSpan.hapList.begin(); it!=homeSpan.hapList.end(); ++it){ // loop over all connection slots
|
||||||
if(hap[cNum]->client && 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)
|
if(&(*it)!=ignore){ // if 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)
|
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();
|
size_t nBytes=hapOut.getSize();
|
||||||
hapOut.flush();
|
hapOut.flush();
|
||||||
|
|
||||||
if(nBytes>0){ // if there ARE notifications to send to client cNum
|
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";
|
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();
|
hapOut.flush();
|
||||||
|
|
||||||
LOG2("\n-------- SENT ENCRYPTED! --------\n");
|
LOG2("\n-------- SENT ENCRYPTED! --------\n");
|
||||||
|
|
@ -1302,6 +1292,8 @@ void HAPClient::tlvRespond(TLV8 &tlv8){
|
||||||
else
|
else
|
||||||
LOG2("-------- SENT ENCRYPTED! --------\n");
|
LOG2("-------- SENT ENCRYPTED! --------\n");
|
||||||
|
|
||||||
|
free(body);
|
||||||
|
|
||||||
} // tlvRespond
|
} // tlvRespond
|
||||||
|
|
||||||
//////////////////////////////////////
|
//////////////////////////////////////
|
||||||
|
|
@ -1470,10 +1462,10 @@ void HAPClient::removeController(uint8_t *id){
|
||||||
|
|
||||||
void HAPClient::tearDown(uint8_t *id){
|
void HAPClient::tearDown(uint8_t *id){
|
||||||
|
|
||||||
for(int i=0;i<homeSpan.maxConnections;i++){ // loop over all connection slots
|
for(HAPClient &hc : homeSpan.hapList){
|
||||||
if(hap[i]->client && (id==NULL || (hap[i]->cPair && !memcmp(id,hap[i]->cPair->ID,hap_controller_IDBYTES)))){
|
if(id==NULL || (hc.cPair && !memcmp(id,hc.cPair->ID,hap_controller_IDBYTES))){
|
||||||
LOG1("*** Terminating Client #%d\n",i);
|
LOG1("*** Terminating Client #%d\n",hc.clientNumber);
|
||||||
hap[i]->client.stop();
|
hc.client.stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1572,6 +1564,9 @@ HapOut::HapStreamBuffer::~HapStreamBuffer(){
|
||||||
|
|
||||||
sync();
|
sync();
|
||||||
free(buffer);
|
free(buffer);
|
||||||
|
free(encBuf);
|
||||||
|
free(hash);
|
||||||
|
free(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
//////////////////////////////////////
|
//////////////////////////////////////
|
||||||
|
|
@ -1696,5 +1691,4 @@ void HapOut::HapStreamBuffer::printFormatted(char *buf, size_t nChars, size_t ns
|
||||||
pairState HAPClient::pairStatus;
|
pairState HAPClient::pairStatus;
|
||||||
Accessory HAPClient::accessory;
|
Accessory HAPClient::accessory;
|
||||||
list<Controller, Mallocator<Controller>> HAPClient::controllerList;
|
list<Controller, Mallocator<Controller>> HAPClient::controllerList;
|
||||||
int HAPClient::conNum;
|
|
||||||
|
|
||||||
|
|
|
||||||
43
src/HAP.h
43
src/HAP.h
|
|
@ -72,6 +72,34 @@ struct Accessory {
|
||||||
uint8_t LTPK[crypto_sign_PUBLICKEYBYTES]; // Long Term Ed2519 Public Key
|
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
|
// HAPClient Structure
|
||||||
// Reads and Writes from each HAP Client connection
|
// Reads and Writes from each HAP Client connection
|
||||||
|
|
@ -87,19 +115,21 @@ struct HAPClient {
|
||||||
static pairState pairStatus; // tracks pair-setup status
|
static pairState pairStatus; // tracks pair-setup status
|
||||||
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<Controller, Mallocator<Controller>> controllerList; // linked-list of Paired Controller IDs and ED25519 long-term public keys - permanently stored
|
static list<Controller, Mallocator<Controller>> 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
|
// individual structures and data defined for each Hap Client connection
|
||||||
|
|
||||||
WiFiClient client; // handle to client
|
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)
|
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
|
// 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
|
struct tempKeys_t {
|
||||||
uint8_t *sharedCurveKey; // Shared-Secret Curve25519 Key derived from Accessory's Secret Key and Controller's Public Key
|
uint8_t publicCurveKey[crypto_box_PUBLICKEYBYTES]; // Accessory's Curve25519 Public Key
|
||||||
uint8_t *sessionKey; // Session Key Curve25519 (derived with various HKDF calls)
|
uint8_t sharedCurveKey[crypto_box_PUBLICKEYBYTES]; // Shared-Secret Curve25519 Key derived from Accessory's Secret Key and Controller's Public Key
|
||||||
uint8_t *iosCurveKey; // Controller's Curve25519 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
|
// CurveKey and CurveKey Nonces are created once each new session is verified in /pair-verify. Keys persist for as long as connection is open
|
||||||
|
|
||||||
|
|
@ -143,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 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 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 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)
|
static void getStatusURL(HAPClient *, void (*)(const char *, void *), void *); // GET / status (an optional, non-HAP feature)
|
||||||
|
|
||||||
|
|
@ -206,5 +236,4 @@ class HapOut : public std::ostream {
|
||||||
/////////////////////////////////////////////////
|
/////////////////////////////////////////////////
|
||||||
// Extern Variables
|
// Extern Variables
|
||||||
|
|
||||||
extern HAPClient **hap;
|
|
||||||
extern HapOut hapOut;
|
extern HapOut hapOut;
|
||||||
|
|
|
||||||
450
src/HomeSpan.cpp
450
src/HomeSpan.cpp
|
|
@ -46,7 +46,6 @@ const __attribute__((section(".rodata_custom_desc"))) SpanPartition spanPartitio
|
||||||
using namespace Utils;
|
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)
|
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)
|
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)
|
HapCharacteristics hapChars; // Instantiation of all HAP Characteristics used to create SpanCharacteristics (global-scoped variable)
|
||||||
|
|
||||||
|
|
@ -69,7 +68,6 @@ Span::Span(){
|
||||||
rebootCount++;
|
rebootCount++;
|
||||||
nvs_set_u8(wifiNVS,"REBOOTS",rebootCount);
|
nvs_set_u8(wifiNVS,"REBOOTS",rebootCount);
|
||||||
nvs_commit(wifiNVS);
|
nvs_commit(wifiNVS);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////
|
///////////////////////////////
|
||||||
|
|
@ -89,14 +87,7 @@ 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
|
esp_task_wdt_delete(xTaskGetIdleTaskHandleForCPU(0)); // required to avoid watchdog timeout messages from ESP32-C3
|
||||||
|
|
||||||
if(requestedMaxCon<maxConnections) // if specific request for max connections is less than computed max connections
|
hapServer=new WiFiServer(tcpPortNum); // create HAP WIFI SERVER
|
||||||
maxConnections=requestedMaxCon; // over-ride max connections with requested value
|
|
||||||
|
|
||||||
hap=(HAPClient **)HS_CALLOC(maxConnections,sizeof(HAPClient *));
|
|
||||||
for(int i=0;i<maxConnections;i++)
|
|
||||||
hap[i]=new HAPClient;
|
|
||||||
|
|
||||||
hapServer=new WiFiServer(tcpPortNum);
|
|
||||||
|
|
||||||
size_t len;
|
size_t len;
|
||||||
|
|
||||||
|
|
@ -234,66 +225,35 @@ void Span::pollTask() {
|
||||||
processSerialCommand(cBuf);
|
processSerialCommand(cBuf);
|
||||||
}
|
}
|
||||||
|
|
||||||
WiFiClient newClient;
|
if(hapServer->hasClient()){
|
||||||
|
|
||||||
if(newClient=hapServer->available()){ // found a new HTTP client
|
auto it=hapList.emplace(hapList.begin()); // create new HAPClient connection
|
||||||
int freeSlot=getFreeSlot(); // get next free slot
|
it->client=hapServer->available();
|
||||||
|
it->clientNumber=it->client.fd()-LWIP_SOCKET_OFFSET;
|
||||||
|
|
||||||
if(freeSlot==-1){ // no available free slots
|
HAPClient::pairStatus=pairState_M1; // reset starting PAIR STATE (which may be needed if Accessory failed in middle of pair-setup)
|
||||||
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
|
|
||||||
|
|
||||||
LOG2("=======================================\n");
|
LOG2("=======================================\n");
|
||||||
LOG1("** Client #");
|
LOG1("** Client #%d Connected (%lu sec): %s\n",it->clientNumber,millis()/1000,it->client.remoteIP().toString().c_str());
|
||||||
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");
|
|
||||||
LOG2("\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;i<maxConnections;i++){ // loop over all HAP Connection slots
|
currentClient=hapList.begin();
|
||||||
|
while(currentClient!=hapList.end()){
|
||||||
|
|
||||||
if(hap[i]->client && hap[i]->client.available()){ // if connection exists and data is available
|
if(currentClient->client.connected()){ // if the client is connected
|
||||||
|
if(currentClient->client.available()){ // if client has data available
|
||||||
HAPClient::conNum=i; // set connection number
|
homeSpan.lastClientIP=currentClient->client.remoteIP().toString(); // store IP Address for web logging
|
||||||
homeSpan.lastClientIP=hap[i]->client.remoteIP().toString(); // store IP Address for web logging
|
currentClient->processRequest(); // PROCESS HAP REQUEST
|
||||||
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
|
||||||
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");
|
|
||||||
}
|
}
|
||||||
|
currentClient++;
|
||||||
LOG2("\n");
|
} else {
|
||||||
|
LOG1("** Client #%d DISCONNECTED (%lu sec)\n",currentClient->clientNumber,millis()/1000);
|
||||||
} // process HAP Client
|
clearNotify(&*currentClient); // clear all notification requests for this connection
|
||||||
} // for-loop over connection slots
|
currentClient=hapList.erase(currentClient); // remove HAPClient connection
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
snapTime=millis(); // snap the current time for use in ALL loop routines
|
snapTime=millis(); // snap the current time for use in ALL loop routines
|
||||||
|
|
||||||
|
|
@ -334,18 +294,6 @@ void Span::pollTask() {
|
||||||
|
|
||||||
} // poll
|
} // poll
|
||||||
|
|
||||||
///////////////////////////////
|
|
||||||
|
|
||||||
int Span::getFreeSlot(){
|
|
||||||
|
|
||||||
for(int i=0;i<maxConnections;i++){
|
|
||||||
if(!hap[i]->client)
|
|
||||||
return(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
return(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
//////////////////////////////////////
|
//////////////////////////////////////
|
||||||
|
|
||||||
void Span::commandMode(){
|
void Span::commandMode(){
|
||||||
|
|
@ -550,7 +498,7 @@ void Span::checkConnect(){
|
||||||
if(webLog.timeServer)
|
if(webLog.timeServer)
|
||||||
xTaskCreateUniversal(webLog.initTime, "timeSeverTaskHandle", 8096, &webLog, 1, NULL, 0);
|
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();
|
hapServer->begin();
|
||||||
|
|
||||||
|
|
@ -587,6 +535,15 @@ void Span::processSerialCommand(const char *c){
|
||||||
|
|
||||||
switch(c[0]){
|
switch(c[0]){
|
||||||
|
|
||||||
|
case 'Z': {
|
||||||
|
for(auto it=hapList.begin(); it!=hapList.end(); ++it){
|
||||||
|
(*it).client.stop();
|
||||||
|
delay(5);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case 's': {
|
case 's': {
|
||||||
|
|
||||||
LOG0("\n*** HomeSpan Status ***\n\n");
|
LOG0("\n*** HomeSpan Status ***\n\n");
|
||||||
|
|
@ -603,27 +560,20 @@ void Span::processSerialCommand(const char *c){
|
||||||
HAPClient::printControllers();
|
HAPClient::printControllers();
|
||||||
LOG0("\n");
|
LOG0("\n");
|
||||||
|
|
||||||
for(int i=0;i<maxConnections;i++){
|
for(auto it=hapList.begin(); it!=hapList.end(); ++it){
|
||||||
LOG0("Connection #%d ",i);
|
LOG0("Client #%d: %s",(*it).clientNumber,(*it).client.remoteIP().toString().c_str());
|
||||||
if(hap[i]->client){
|
if((*it).cPair){
|
||||||
|
LOG0(" ID=");
|
||||||
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);
|
HAPClient::charPrintRow((*it).cPair->getID(),36);
|
||||||
|
LOG0((*it).cPair->isAdmin()?" (admin)\n":" (regular)\n");
|
||||||
if(hap[i]->cPair){
|
|
||||||
LOG0(" ID=");
|
|
||||||
HAPClient::charPrintRow(hap[i]->cPair->getID(),36);
|
|
||||||
LOG0(hap[i]->cPair->isAdmin()?" (admin)":" (regular)");
|
|
||||||
} else {
|
|
||||||
LOG0(" (unverified)");
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
LOG0("(unconnected)");
|
LOG0(" (unverified)\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG0("\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(hapList.empty())
|
||||||
|
LOG0("No Client Connections!\n");
|
||||||
|
|
||||||
LOG0("\n*** End Status ***\n\n");
|
LOG0("\n*** End Status ***\n\n");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -914,11 +864,9 @@ void Span::processSerialCommand(const char *c){
|
||||||
if(((*chr)->perms)&EV){
|
if(((*chr)->perms)&EV){
|
||||||
LOG0(", EV=(");
|
LOG0(", EV=(");
|
||||||
boolean addComma=false;
|
boolean addComma=false;
|
||||||
for(int i=0;i<homeSpan.maxConnections;i++){
|
for(auto const &hc : (*chr)->evList){
|
||||||
if((*chr)->ev[i] && hap[i]->client){
|
LOG0("%s%d",addComma?",":"",hc->clientNumber);
|
||||||
LOG0("%s%d",addComma?",":"",i);
|
addComma=true;
|
||||||
addComma=true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
LOG0(")");
|
LOG0(")");
|
||||||
}
|
}
|
||||||
|
|
@ -1491,28 +1439,24 @@ int Span::updateCharacteristics(char *buf, SpanBuf *pObj){
|
||||||
|
|
||||||
///////////////////////////////
|
///////////////////////////////
|
||||||
|
|
||||||
void Span::clearNotify(int slotNum){
|
void Span::clearNotify(HAPClient *hc){
|
||||||
|
|
||||||
for(int i=0;i<Accessories.size();i++){
|
for(auto const &acc : Accessories)
|
||||||
for(int j=0;j<Accessories[i]->Services.size();j++){
|
for(auto const &svc : acc->Services)
|
||||||
for(int k=0;k<Accessories[i]->Services[j]->Characteristics.size();k++){
|
for(auto const &chr : svc->Characteristics)
|
||||||
Accessories[i]->Services[j]->Characteristics[k]->ev[slotNum]=false;
|
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;
|
boolean notifyFlag=false;
|
||||||
|
|
||||||
for(int i=0;i<nObj;i++){ // loop over all objects
|
for(int i=0;i<nObj;i++){ // loop over all objects
|
||||||
|
|
||||||
if(pObj[i].status==StatusCode::OK && pObj[i].val){ // characteristic was successfully updated with a new value (i.e. not just an EV request)
|
if(pObj[i].status==StatusCode::OK && pObj[i].val){ // characteristic was successfully updated with a new value (i.e. not just an EV request)
|
||||||
|
if(pObj[i].characteristic->evList.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
|
if(!notifyFlag) // this is first notification for any characteristic
|
||||||
hapOut << "{\"characteristics\":["; // print start of JSON array
|
hapOut << "{\"characteristics\":["; // print start of JSON array
|
||||||
|
|
@ -1857,8 +1801,6 @@ SpanCharacteristic::SpanCharacteristic(HapChar *hapChar, boolean isCustom){
|
||||||
iid=++(homeSpan.Accessories.back()->iidCount);
|
iid=++(homeSpan.Accessories.back()->iidCount);
|
||||||
service=homeSpan.Accessories.back()->Services.back();
|
service=homeSpan.Accessories.back()->Services.back();
|
||||||
aid=homeSpan.Accessories.back()->aid;
|
aid=homeSpan.Accessories.back()->aid;
|
||||||
|
|
||||||
ev=(boolean *)HS_CALLOC(homeSpan.maxConnections,sizeof(boolean));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////
|
///////////////////////////////
|
||||||
|
|
@ -1870,7 +1812,6 @@ SpanCharacteristic::~SpanCharacteristic(){
|
||||||
chr++;
|
chr++;
|
||||||
service->Characteristics.erase(chr);
|
service->Characteristics.erase(chr);
|
||||||
|
|
||||||
free(ev);
|
|
||||||
free(desc);
|
free(desc);
|
||||||
free(unit);
|
free(unit);
|
||||||
free(validValues);
|
free(validValues);
|
||||||
|
|
@ -1886,6 +1827,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(format<FORMAT::DATA)
|
||||||
|
return(0);
|
||||||
|
|
||||||
|
size_t olen;
|
||||||
|
int ret=mbedtls_base64_decode(data,len,&olen,(uint8_t *)val.STRING,strlen(val.STRING));
|
||||||
|
|
||||||
|
if(data==NULL)
|
||||||
|
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);
|
||||||
|
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);
|
||||||
|
|
||||||
|
return(olen);
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////
|
||||||
|
|
||||||
|
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';
|
||||||
|
}
|
||||||
|
|
||||||
|
setValFinish(notify);
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////
|
||||||
|
|
||||||
|
size_t SpanCharacteristic::getTLVGeneric(TLV8 &tlv, UVal &val){
|
||||||
|
|
||||||
|
if(format<FORMAT::TLV_ENC)
|
||||||
|
return(0);
|
||||||
|
|
||||||
|
const size_t bufSize=36; // maximum size of buffer to store decoded bytes before unpacking into TLV; must be multiple of 3
|
||||||
|
TempBuffer<uint8_t> 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=nChars<decodeSize?nChars:decodeSize;
|
||||||
|
|
||||||
|
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);
|
||||||
|
tlv.wipe();
|
||||||
|
return(0);
|
||||||
|
}
|
||||||
|
status=tlv.unpack(tBuf,olen);
|
||||||
|
p+=n;
|
||||||
|
nChars-=n;
|
||||||
|
}
|
||||||
|
if(status>0){
|
||||||
|
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<uint8_t> 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){
|
void SpanCharacteristic::printfAttributes(int flags){
|
||||||
|
|
||||||
const char permCodes[][7]={"pr","pw","ev","aa","tw","hd","wr"};
|
const char permCodes[][7]={"pr","pw","ev","aa","tw","hd","wr"};
|
||||||
|
|
@ -1944,8 +2086,10 @@ void SpanCharacteristic::printfAttributes(int flags){
|
||||||
if(flags&GET_AID)
|
if(flags&GET_AID)
|
||||||
hapOut << ",\"aid\":" << aid;
|
hapOut << ",\"aid\":" << aid;
|
||||||
|
|
||||||
|
HAPClient *hc=&(*(homeSpan.currentClient));
|
||||||
|
|
||||||
if(flags&GET_EV)
|
if(flags&GET_EV)
|
||||||
hapOut << ",\"ev\":" << (ev[HAPClient::conNum]?"true":"false");
|
hapOut << ",\"ev\":" << (evList.has(hc)?"true":"false");
|
||||||
|
|
||||||
if(flags&GET_STATUS)
|
if(flags&GET_STATUS)
|
||||||
hapOut << ",\"status\":0";
|
hapOut << ",\"status\":0";
|
||||||
|
|
@ -1971,7 +2115,12 @@ StatusCode SpanCharacteristic::loadUpdate(char *val, char *ev, boolean wr){
|
||||||
return(StatusCode::NotifyNotAllowed);
|
return(StatusCode::NotifyNotAllowed);
|
||||||
|
|
||||||
LOG1("Notification Request for aid=%u iid=%u: %s\n",aid,iid,evFlag?"true":"false");
|
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
|
if(!val) // no request to update value
|
||||||
|
|
@ -2066,6 +2215,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, ...){
|
SpanCharacteristic *SpanCharacteristic::setValidValues(int n, ...){
|
||||||
|
|
||||||
String s="[";
|
String s="[";
|
||||||
|
|
@ -2102,18 +2302,23 @@ SpanCharacteristic *SpanCharacteristic::setValidValues(int n, ...){
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////
|
///////////////////////////////
|
||||||
// SpanRange //
|
|
||||||
|
boolean SpanCharacteristic::EVLIST::has(HAPClient *hc){
|
||||||
|
return(find_if(begin(), end(), [hc](const HAPClient *hcTemp){return(hc==hcTemp);}) != end());
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////
|
///////////////////////////////
|
||||||
|
|
||||||
SpanRange::SpanRange(int min, int max, int step){
|
void SpanCharacteristic::EVLIST::add(HAPClient *hc){
|
||||||
|
if(!has(hc))
|
||||||
|
push_back(hc);
|
||||||
|
}
|
||||||
|
|
||||||
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 ===");
|
void SpanCharacteristic::EVLIST::remove(HAPClient *hc){
|
||||||
while(1);
|
auto it=remove_if(begin(), end(), [hc](const HAPClient *hcTemp){return(hc==hcTemp);});
|
||||||
} else {
|
erase(it,end());
|
||||||
homeSpan.Accessories.back()->Services.back()->Characteristics.back()->setRange(min,max,step);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////
|
///////////////////////////////
|
||||||
|
|
@ -2183,8 +2388,6 @@ void SpanWebLog::init(uint16_t maxEntries, const char *serv, const char *tz, con
|
||||||
isEnabled=true;
|
isEnabled=true;
|
||||||
}
|
}
|
||||||
log = (log_t *)HS_CALLOC(maxEntries,sizeof(log_t));
|
log = (log_t *)HS_CALLOC(maxEntries,sizeof(log_t));
|
||||||
if(timeServer)
|
|
||||||
homeSpan.reserveSocketConnections(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////
|
///////////////////////////////
|
||||||
|
|
@ -2251,7 +2454,6 @@ int SpanOTA::init(boolean _auth, boolean _safeLoad, const char *pwd){
|
||||||
enabled=true;
|
enabled=true;
|
||||||
safeLoad=_safeLoad;
|
safeLoad=_safeLoad;
|
||||||
auth=_auth;
|
auth=_auth;
|
||||||
homeSpan.reserveSocketConnections(1);
|
|
||||||
if(pwd==NULL)
|
if(pwd==NULL)
|
||||||
return(0);
|
return(0);
|
||||||
return(setPassword(pwd));
|
return(setPassword(pwd));
|
||||||
|
|
|
||||||
346
src/HomeSpan.h
346
src/HomeSpan.h
|
|
@ -109,10 +109,11 @@ struct Span;
|
||||||
struct SpanAccessory;
|
struct SpanAccessory;
|
||||||
struct SpanService;
|
struct SpanService;
|
||||||
struct SpanCharacteristic;
|
struct SpanCharacteristic;
|
||||||
struct SpanRange;
|
|
||||||
struct SpanBuf;
|
struct SpanBuf;
|
||||||
struct SpanButton;
|
struct SpanButton;
|
||||||
struct SpanUserCommand;
|
struct SpanUserCommand;
|
||||||
|
|
||||||
|
struct HAPClient;
|
||||||
class Controller;
|
class Controller;
|
||||||
|
|
||||||
extern Span homeSpan;
|
extern Span homeSpan;
|
||||||
|
|
@ -190,35 +191,6 @@ struct SpanOTA{ // manages OTA process
|
||||||
static void error(ota_error_t err);
|
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 //
|
// USER API CLASSES BEGINS HERE //
|
||||||
//////////////////////////////////////
|
//////////////////////////////////////
|
||||||
|
|
@ -230,7 +202,6 @@ class Span{
|
||||||
friend class SpanCharacteristic;
|
friend class SpanCharacteristic;
|
||||||
friend class SpanUserCommand;
|
friend class SpanUserCommand;
|
||||||
friend class SpanButton;
|
friend class SpanButton;
|
||||||
friend class SpanRange;
|
|
||||||
friend class SpanWebLog;
|
friend class SpanWebLog;
|
||||||
friend class SpanOTA;
|
friend class SpanOTA;
|
||||||
friend class Network;
|
friend class Network;
|
||||||
|
|
@ -267,8 +238,6 @@ class Span{
|
||||||
const char *defaultSetupCode=DEFAULT_SETUP_CODE; // Setup Code used for pairing
|
const char *defaultSetupCode=DEFAULT_SETUP_CODE; // Setup Code used for pairing
|
||||||
uint16_t autoOffLED=0; // automatic turn-off duration (in seconds) for Status LED
|
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
|
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
|
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
|
uint16_t tcpPortNum=DEFAULT_TCP_PORT; // port for TCP communications between HomeKit and HomeSpan
|
||||||
char qrID[5]=""; // Setup ID used for pairing with QR Code
|
char qrID[5]=""; // Setup ID used for pairing with QR Code
|
||||||
|
|
@ -294,16 +263,17 @@ class Span{
|
||||||
|
|
||||||
SpanOTA spanOTA; // manages OTA process
|
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
|
SpanConfig hapConfig; // track configuration changes to the HAP Accessory database; used to increment the configuration number (c#) when changes found
|
||||||
vector<SpanAccessory *, Mallocator<SpanAccessory *>> Accessories; // vector of pointers to all Accessories
|
|
||||||
vector<SpanService *, Mallocator<SpanService *>> Loops; // vector of pointer to all Services that have over-ridden loop() methods
|
|
||||||
vector<SpanBuf, Mallocator<SpanBuf>> Notifications; // vector of SpanBuf objects that store info for Characteristics that are updated with setVal() and require a Notification Event
|
|
||||||
vector<SpanButton *, Mallocator<SpanButton *>> PushButtons; // vector of pointer to all PushButtons
|
|
||||||
unordered_map<uint64_t, uint32_t> TimedWrites; // map of timed-write PIDs and Alarm Times (based on TTLs)
|
|
||||||
|
|
||||||
unordered_map<char, SpanUserCommand *> UserCommands; // map of pointers to all UserCommands
|
list<HAPClient, Mallocator<HAPClient>> hapList; // linked-list of HAPClient structures containing HTTP client connections, parsing routines, and state variables
|
||||||
|
list<HAPClient, Mallocator<HAPClient>>::iterator currentClient; // iterator to current client
|
||||||
|
vector<SpanAccessory *, Mallocator<SpanAccessory *>> Accessories; // vector of pointers to all Accessories
|
||||||
|
vector<SpanService *, Mallocator<SpanService *>> Loops; // vector of pointer to all Services that have over-ridden loop() methods
|
||||||
|
vector<SpanBuf, Mallocator<SpanBuf>> Notifications; // vector of SpanBuf objects that store info for Characteristics that are updated with setVal() and require a Notification Event
|
||||||
|
vector<SpanButton *, Mallocator<SpanButton *>> PushButtons; // vector of pointer to all PushButtons
|
||||||
|
unordered_map<uint64_t, uint32_t> TimedWrites; // map of timed-write PIDs and Alarm Times (based on TTLs)
|
||||||
|
unordered_map<char, SpanUserCommand *> UserCommands; // map of pointers to all UserCommands
|
||||||
|
|
||||||
void pollTask(); // poll HAP Clients and process any new HAP requests
|
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 checkConnect(); // check WiFi connection; connect if needed
|
||||||
void commandMode(); // allows user to control and reset HomeSpan settings with the control button
|
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
|
void resetStatus(); // resets statusLED and calls statusCallback based on current HomeSpan status
|
||||||
|
|
@ -316,8 +286,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
|
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
|
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
|
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 clearNotify(HAPClient *hc); // clear all notifications related to specific client connection
|
||||||
void printfNotify(SpanBuf *pObj, int nObj, int conNum); // writes notification JSON to hapOut stream based on SpanBuf objects and specified connection number
|
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){
|
static boolean invalidUUID(const char *uuid){
|
||||||
int x=0;
|
int x=0;
|
||||||
|
|
@ -369,7 +339,6 @@ class Span{
|
||||||
int getLogLevel(){return(logLevel);} // get Log Level
|
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)
|
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
|
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& 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& 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& setQRID(const char *id); // sets the Setup ID for optional pairing with a QR Code
|
||||||
|
|
@ -429,8 +398,9 @@ class Span{
|
||||||
list<Controller, Mallocator<Controller>>::const_iterator controllerListBegin();
|
list<Controller, Mallocator<Controller>>::const_iterator controllerListBegin();
|
||||||
list<Controller, Mallocator<Controller>>::const_iterator controllerListEnd();
|
list<Controller, Mallocator<Controller>>::const_iterator controllerListEnd();
|
||||||
|
|
||||||
[[deprecated("Please use reserveSocketConnections(n) method instead.")]]
|
[[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.")]]
|
||||||
void setMaxConnections(uint8_t n){requestedMaxCon=n;} // sets maximum number of simultaneous HAP connections
|
Span& reserveSocketConnections(uint8_t n){return(*this);}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
///////////////////////////////
|
///////////////////////////////
|
||||||
|
|
@ -441,7 +411,6 @@ class SpanAccessory{
|
||||||
friend class SpanService;
|
friend class SpanService;
|
||||||
friend class SpanCharacteristic;
|
friend class SpanCharacteristic;
|
||||||
friend class SpanButton;
|
friend class SpanButton;
|
||||||
friend class SpanRange;
|
|
||||||
|
|
||||||
uint32_t aid=0; // Accessory Instance ID (HAP Table 6-1)
|
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
|
uint32_t iidCount=0; // running count of iid to use for Services and Characteristics associated with this Accessory
|
||||||
|
|
@ -466,7 +435,6 @@ class SpanService{
|
||||||
friend class Span;
|
friend class Span;
|
||||||
friend class SpanAccessory;
|
friend class SpanAccessory;
|
||||||
friend class SpanCharacteristic;
|
friend class SpanCharacteristic;
|
||||||
friend class SpanRange;
|
|
||||||
|
|
||||||
uint32_t iid=0; // Instance ID (HAP Table 6-2)
|
uint32_t iid=0; // Instance ID (HAP Table 6-2)
|
||||||
const char *type; // Service Type
|
const char *type; // Service Type
|
||||||
|
|
@ -520,6 +488,13 @@ class SpanCharacteristic{
|
||||||
STRING_t STRING = NULL;
|
STRING_t STRING = NULL;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class EVLIST : public vector<HAPClient *, Mallocator<HAPClient *>>{ // 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)
|
uint32_t iid=0; // Instance ID (HAP Table 6-3)
|
||||||
HapChar *hapChar; // pointer to HAP Characteristic structure
|
HapChar *hapChar; // pointer to HAP Characteristic structure
|
||||||
const char *type; // Characteristic Type
|
const char *type; // Characteristic Type
|
||||||
|
|
@ -535,7 +510,6 @@ class SpanCharacteristic{
|
||||||
boolean staticRange; // Flag that indicates whether Range is static and cannot be changed with setRange()
|
boolean staticRange; // Flag that indicates whether Range is static and cannot be changed with setRange()
|
||||||
boolean customRange=false; // Flag for custom ranges
|
boolean customRange=false; // Flag for custom ranges
|
||||||
char *validValues=NULL; // Optional JSON array of valid values. Applicable only to uint8 Characteristics
|
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
|
char *nvsKey=NULL; // key for NVS storage of Characteristic value
|
||||||
boolean isCustom; // flag to indicate this is a Custom Characteristic
|
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
|
boolean setRangeError=false; // flag to indicate attempt to set Range on Characteristic that does not support changes to Range
|
||||||
|
|
@ -546,50 +520,16 @@ class SpanCharacteristic{
|
||||||
unsigned long updateTime=0; // last time value was updated (in millis) either by PUT /characteristic OR by setVal()
|
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
|
UVal newValue; // the updated value requested by PUT /characteristic
|
||||||
SpanService *service=NULL; // pointer to Service containing this 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
|
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.)
|
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
|
||||||
|
|
||||||
String uvPrint(UVal &u){
|
void uvSet(UVal &dest, UVal &src); // copies UVal src into UVal dest
|
||||||
char c[64];
|
void uvSet(UVal &u, const char *val); // copies string val into UVal u
|
||||||
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 uvSet(UVal &dest, UVal &src){
|
template <typename T> void uvSet(UVal &u, T val){ // copies numeric val into UVal u
|
||||||
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 <typename T> void uvSet(UVal &u, T val){
|
|
||||||
switch(format){
|
switch(format){
|
||||||
case FORMAT::BOOL:
|
case FORMAT::BOOL:
|
||||||
u.BOOL=(boolean)val;
|
u.BOOL=(boolean)val;
|
||||||
|
|
@ -619,7 +559,11 @@ class SpanCharacteristic{
|
||||||
} // switch
|
} // switch
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class T> T uvGet(UVal &u){
|
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 <class T> T uvGet(UVal &u){ // gets the specified UVal for numeric-based Characteristics
|
||||||
|
|
||||||
switch(format){
|
switch(format){
|
||||||
case FORMAT::BOOL:
|
case FORMAT::BOOL:
|
||||||
|
|
@ -644,9 +588,12 @@ class SpanCharacteristic{
|
||||||
return((T)0); // included to prevent compiler warnings
|
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:
|
protected:
|
||||||
|
|
||||||
~SpanCharacteristic(); // destructor
|
~SpanCharacteristic(); // destructor
|
||||||
|
|
||||||
template <typename T, typename A=boolean, typename B=boolean> void init(T val, boolean nvsStore, A min=0, B max=1){
|
template <typename T, typename A=boolean, typename B=boolean> void init(T val, boolean nvsStore, A min=0, B max=1){
|
||||||
|
|
||||||
|
|
@ -688,169 +635,24 @@ class SpanCharacteristic{
|
||||||
|
|
||||||
public:
|
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); // SpanCharacteristic constructor
|
||||||
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
|
||||||
|
|
||||||
uint32_t getIID(){return(iid);} // returns IID of Characteristic
|
template <class T=int> T getVal(){return(uvGet<T>(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 <class T=int> T getVal(){
|
template <class T=int> T getNewVal(){return(uvGet<T>(newValue));} // gets the newValue for numeric-based Characteristics
|
||||||
return(uvGet<T>(value));
|
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 <class T=int> T getNewVal(){
|
void setString(const char *val, boolean notify=true); // sets the value and newValue for string-based Characteristic
|
||||||
return(uvGet<T>(newValue));
|
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
|
||||||
|
|
||||||
char *getStringGeneric(UVal &val){
|
template <typename T> void setVal(T val, boolean notify=true){ // sets the value and newValue for numeric-based Characteristics
|
||||||
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(format<FORMAT::DATA)
|
|
||||||
return(0);
|
|
||||||
|
|
||||||
size_t olen;
|
|
||||||
int ret=mbedtls_base64_decode(data,len,&olen,(uint8_t *)val.STRING,strlen(val.STRING));
|
|
||||||
|
|
||||||
if(data==NULL)
|
|
||||||
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);
|
|
||||||
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);
|
|
||||||
|
|
||||||
return(olen);
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t getData(uint8_t *data, size_t len){return(getDataGeneric(data,len,value));}
|
|
||||||
size_t getNewData(uint8_t *data, size_t len){return(getDataGeneric(data,len,newValue));}
|
|
||||||
|
|
||||||
void setData(uint8_t *data, size_t len, boolean notify=true){
|
|
||||||
|
|
||||||
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';
|
|
||||||
}
|
|
||||||
|
|
||||||
setValFinish(notify);
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t getTLVGeneric(TLV8 &tlv, UVal &val){
|
|
||||||
|
|
||||||
if(format<FORMAT::TLV_ENC)
|
|
||||||
return(0);
|
|
||||||
|
|
||||||
const size_t bufSize=36; // maximum size of buffer to store decoded bytes before unpacking into TLV; must be multiple of 3
|
|
||||||
TempBuffer<uint8_t> 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=nChars<decodeSize?nChars:decodeSize;
|
|
||||||
|
|
||||||
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);
|
|
||||||
tlv.wipe();
|
|
||||||
return(0);
|
|
||||||
}
|
|
||||||
status=tlv.unpack(tBuf,olen);
|
|
||||||
p+=n;
|
|
||||||
nChars-=n;
|
|
||||||
}
|
|
||||||
if(status>0){
|
|
||||||
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<uint8_t> 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 <typename T> void setVal(T val, boolean notify=true){
|
|
||||||
|
|
||||||
setValCheck();
|
setValCheck();
|
||||||
|
|
||||||
|
|
@ -882,12 +684,18 @@ class SpanCharacteristic{
|
||||||
|
|
||||||
} // setVal()
|
} // setVal()
|
||||||
|
|
||||||
boolean updated(){return(updateFlag>0);} // returns true within update() if Characteristic was updated by Home App
|
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()
|
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
|
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
|
||||||
|
|
||||||
template <typename A, typename B, typename S=int> SpanCharacteristic *setRange(A min, B max, S step=0){
|
template <typename A, typename B, typename S=int> SpanCharacteristic *setRange(A min, B max, S step=0){ // sets the allowed range of a Characteristic
|
||||||
|
|
||||||
if(!staticRange){
|
if(!staticRange){
|
||||||
uvSet(minValue,min);
|
uvSet(minValue,min);
|
||||||
|
|
@ -900,40 +708,6 @@ class SpanCharacteristic{
|
||||||
return(this);
|
return(this);
|
||||||
|
|
||||||
} // setRange()
|
} // 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
///////////////////////////////
|
|
||||||
|
|
||||||
struct [[deprecated("Please use Characteristic::setRange() method instead.")]] SpanRange{
|
|
||||||
SpanRange(int min, int max, int step);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
///////////////////////////////
|
///////////////////////////////
|
||||||
|
|
|
||||||
11
src/PSRAM.h
11
src/PSRAM.h
|
|
@ -45,11 +45,14 @@ template <class T>
|
||||||
struct Mallocator {
|
struct Mallocator {
|
||||||
typedef T value_type;
|
typedef T value_type;
|
||||||
Mallocator() = default;
|
Mallocator() = default;
|
||||||
template <class U> constexpr Mallocator(const Mallocator<U>&) noexcept {}
|
template <class U> constexpr Mallocator(const Mallocator<U>&) {}
|
||||||
[[nodiscard]] T* allocate(std::size_t n) {
|
[[nodiscard]] T* allocate(std::size_t n) {
|
||||||
if(n > std::size_t(-1) / sizeof(T)) throw std::bad_alloc();
|
auto p = static_cast<T*>(HS_MALLOC(n*sizeof(T)));
|
||||||
if(auto p = static_cast<T*>(HS_MALLOC(n*sizeof(T)))) return p;
|
if(p==NULL){
|
||||||
throw std::bad_alloc();
|
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); }
|
void deallocate(T* p, std::size_t) noexcept { std::free(p); }
|
||||||
};
|
};
|
||||||
|
|
|
||||||
23
src/src.ino
23
src/src.ino
|
|
@ -31,7 +31,12 @@ void setup() {
|
||||||
|
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
|
|
||||||
homeSpan.begin(Category::Lighting,"HomeSpan Light");
|
homeSpan.setLogLevel(2);
|
||||||
|
homeSpan.enableWebLog();
|
||||||
|
|
||||||
|
homeSpan.begin(Category::Lighting,"HomeSpan LightBulb");
|
||||||
|
|
||||||
|
new SpanUserCommand('D', " - disconnect WiFi", [](const char *buf){WiFi.disconnect();});
|
||||||
|
|
||||||
new SpanAccessory();
|
new SpanAccessory();
|
||||||
new Service::AccessoryInformation();
|
new Service::AccessoryInformation();
|
||||||
|
|
@ -39,8 +44,6 @@ void setup() {
|
||||||
new Service::LightBulb();
|
new Service::LightBulb();
|
||||||
new Characteristic::On();
|
new Characteristic::On();
|
||||||
|
|
||||||
// new SpanUserCommand('k',"- list controllers",list_controllers);
|
|
||||||
homeSpan.setControllerCallback(list_controllers);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -52,17 +55,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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,8 @@ BEGIN {
|
||||||
uuid[char]=x[3]
|
uuid[char]=x[3]
|
||||||
perms[char]=x[4]
|
perms[char]=x[4]
|
||||||
format[char]=tolower(x[5])
|
format[char]=tolower(x[5])
|
||||||
|
if(format[char]=="tlv_enc")
|
||||||
|
format[char]="tlv8"
|
||||||
static[char]=x[6]
|
static[char]=x[6]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue